refactor: implement ScheduleArrangeServiceImpl

Phase 2, Step 2.7: Extract arrangement logic from MartialScheduleServiceImpl
- Implement saveDraftSchedule method (160 lines) with complete business logic
- Implement saveAndLockSchedule method (53 lines) with locking logic
- Implement moveScheduleGroup method (58 lines) with group movement logic
- Total: 271 lines of arrangement logic extracted
- All 443 tests passing

Related to Phase 2 refactoring plan
This commit is contained in:
2026-01-18 00:49:41 +08:00
parent 73826cd8f3
commit 147d48c8a6
10 changed files with 315 additions and 1 deletions
Binary file not shown.
@@ -23,7 +23,7 @@ public interface IScheduleArrangeService {
* @param dto Schedule draft data * @param dto Schedule draft data
* @return Success flag * @return Success flag
*/ */
boolean saveAndLockSchedule(SaveScheduleDraftDTO dto); boolean saveAndLockSchedule(Long competitionId);
/** /**
* Move schedule group to different venue/time slot * Move schedule group to different venue/time slot
@@ -0,0 +1,314 @@
package org.springblade.modules.martial.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.modules.martial.mapper.*;
import org.springblade.modules.martial.pojo.dto.*;
import org.springblade.modules.martial.pojo.entity.*;
import org.springblade.modules.martial.service.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class ScheduleArrangeServiceImpl implements IScheduleArrangeService {
private final MartialScheduleGroupMapper scheduleGroupMapper;
private final MartialScheduleMapper scheduleMapper;
private final MartialScheduleAthleteMapper scheduleAthleteMapper;
private final IMartialProjectService projectService;
private final IMartialVenueService venueService;
private final MartialScheduleDetailMapper scheduleDetailMapper;
private final MartialScheduleParticipantMapper scheduleParticipantMapper;
private final MartialAthleteMapper athleteMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveDraftSchedule(SaveScheduleDraftDTO dto) {
if (dto.getCompetitionGroups() == null || dto.getCompetitionGroups().isEmpty()) {
return false;
}
for (CompetitionGroupDTO groupDTO : dto.getCompetitionGroups()) {
// 1. 更新或创建编排明细
Long groupId = groupDTO.getId();
Long realGroupId = groupId;
// Handle negative ID (draft data from auto-arrange)
if (groupId != null && groupId < 0) {
Long projectId = null;
if (groupDTO.getCode() != null && groupDTO.getCode().contains("-P")) {
try {
String suffix = groupDTO.getCode().substring(groupDTO.getCode().lastIndexOf("-P") + 2);
projectId = Long.parseLong(suffix);
} catch (Exception e) {
log.warn("Failed to parse projectId from code: {}", groupDTO.getCode());
}
}
MartialScheduleGroup existingGroup = null;
if (projectId != null) {
existingGroup = scheduleGroupMapper.selectOne(
new QueryWrapper<MartialScheduleGroup>()
.eq("competition_id", dto.getCompetitionId())
.eq("project_id", projectId)
.eq("is_deleted", 0)
.last("LIMIT 1")
);
}
// Determine project type from DTO
Integer projectType = null;
if ("集体".equals(groupDTO.getType())) {
projectType = 2;
} else if ("单人".equals(groupDTO.getType())) {
projectType = 1;
}
// Calculate participant/team counts
int participantCount = groupDTO.getParticipants() != null ? groupDTO.getParticipants().size() : 0;
if (existingGroup != null) {
realGroupId = existingGroup.getId();
existingGroup.setGroupName(groupDTO.getTitle());
existingGroup.setProjectType(projectType);
existingGroup.setTotalParticipants(participantCount);
existingGroup.setTotalTeams(participantCount);
existingGroup.setCategory(groupDTO.getCode());
scheduleGroupMapper.updateById(existingGroup);
} else {
MartialScheduleGroup newGroup = new MartialScheduleGroup();
newGroup.setCompetitionId(Long.valueOf(dto.getCompetitionId()));
newGroup.setProjectId(projectId);
newGroup.setGroupName(groupDTO.getTitle());
newGroup.setProjectType(projectType);
newGroup.setTotalParticipants(participantCount);
newGroup.setTotalTeams(participantCount);
newGroup.setCategory(groupDTO.getCode());
scheduleGroupMapper.insert(newGroup);
realGroupId = newGroup.getId();
}
}
MartialScheduleDetail detail = scheduleDetailMapper.selectOne(
new QueryWrapper<MartialScheduleDetail>()
.eq("schedule_group_id", realGroupId)
.eq("is_deleted", 0)
.last("LIMIT 1")
);
if (detail == null) {
detail = new MartialScheduleDetail();
detail.setScheduleGroupId(realGroupId);
detail.setCompetitionId(dto.getCompetitionId());
}
detail.setVenueId(groupDTO.getVenueId());
detail.setVenueName(groupDTO.getVenueName());
detail.setTimeSlot(groupDTO.getTimeSlot());
// 设置时间段(上午/下午)
if (groupDTO.getTimeSlot() != null && groupDTO.getTimeSlot().contains("上午")) {
detail.setTimePeriod("morning");
} else if (groupDTO.getTimeSlot() != null && groupDTO.getTimeSlot().contains("下午")) {
detail.setTimePeriod("afternoon");
} else {
detail.setTimePeriod("morning"); // 默认上午
}
// 解析日期
if (groupDTO.getTimeSlot() != null && groupDTO.getTimeSlot().contains("")) {
try {
String dateStr = groupDTO.getTimeSlot().split(" ")[0];
dateStr = dateStr.replace("", "-").replace("", "-").replace("", "");
detail.setScheduleDate(LocalDate.parse(dateStr));
} catch (Exception e) {
// 日期解析失败,使用当天日期
detail.setScheduleDate(LocalDate.now());
}
}
// 如果日期仍为空,设置默认日期
if (detail.getScheduleDate() == null) {
detail.setScheduleDate(LocalDate.now());
}
if (detail.getId() == null) {
scheduleDetailMapper.insert(detail);
} else {
scheduleDetailMapper.updateById(detail);
}
// 1.5 同步更新项目的venue_id,保持数据一致性
if (groupDTO.getVenueId() != null) {
MartialScheduleGroup group = scheduleGroupMapper.selectById(realGroupId);
if (group != null && group.getProjectId() != null) {
MartialProject project = projectService.getById(group.getProjectId());
if (project != null) {
project.setVenueId(groupDTO.getVenueId());
projectService.updateById(project);
}
}
}
// 2. 更新参赛者信息
if (groupDTO.getParticipants() != null) {
for (ParticipantDTO participantDTO : groupDTO.getParticipants()) {
// Query by participant_id (registration ID) and schedule_group_id, not by primary key
MartialScheduleParticipant participant = scheduleParticipantMapper.selectOne(
new QueryWrapper<MartialScheduleParticipant>()
.eq("participant_id", participantDTO.getId())
.eq("schedule_group_id", realGroupId)
.eq("is_deleted", 0)
.last("LIMIT 1")
);
if (participant != null) {
participant.setCheckInStatus(participantDTO.getStatus());
participant.setPerformanceOrder(participantDTO.getSortOrder());
participant.setScheduleStatus("draft");
scheduleParticipantMapper.updateById(participant);
} else {
// Create new participant record if not exists
MartialScheduleParticipant newParticipant = new MartialScheduleParticipant();
newParticipant.setScheduleGroupId(realGroupId);
newParticipant.setScheduleDetailId(detail.getId()); // Set schedule_detail_id
newParticipant.setParticipantId(Long.valueOf(participantDTO.getId()));
newParticipant.setOrganization(participantDTO.getSchoolUnit());
newParticipant.setCheckInStatus(participantDTO.getStatus());
newParticipant.setPerformanceOrder(participantDTO.getSortOrder());
newParticipant.setScheduleStatus("draft");
scheduleParticipantMapper.insert(newParticipant);
}
}
}
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveAndLockSchedule(Long competitionId) {
log.info("=== saveAndLockSchedule 开始 === competitionId: {}", competitionId);
// 1. 查询所有分组
List<MartialScheduleGroup> groups = scheduleGroupMapper.selectList(
new QueryWrapper<MartialScheduleGroup>()
.eq("competition_id", competitionId)
.eq("is_deleted", 0)
);
if (groups.isEmpty()) {
// 没有分组数据,视为成功(可能是初始状态,尚未进行自动编排)
return true;
}
// 2. 获取所有分组ID
List<Long> groupIds = groups.stream().map(MartialScheduleGroup::getId).collect(Collectors.toList());
// 3. 更新所有参赛者的编排状态为completed
List<MartialScheduleParticipant> participants = scheduleParticipantMapper.selectList(
new QueryWrapper<MartialScheduleParticipant>()
.in("schedule_group_id", groupIds)
.eq("is_deleted", 0)
);
// 按分组和出场顺序分配选手编号
Map<Long, Integer> groupCounters = new HashMap<>();
participants.sort((a, b) -> {
int groupCompare = a.getScheduleGroupId().compareTo(b.getScheduleGroupId());
if (groupCompare != 0) return groupCompare;
return Integer.compare(a.getPerformanceOrder() != null ? a.getPerformanceOrder() : 0,
b.getPerformanceOrder() != null ? b.getPerformanceOrder() : 0);
});
for (MartialScheduleParticipant participant : participants) {
participant.setScheduleStatus("completed");
scheduleParticipantMapper.updateById(participant);
// 分配选手编号
if (participant.getParticipantId() != null) {
Long groupId = participant.getScheduleGroupId();
int counter = groupCounters.getOrDefault(groupId, 0) + 1;
groupCounters.put(groupId, counter);
// 更新选手编号 (格式: 分组序号-出场序号)
MartialAthlete athlete = athleteMapper.selectById(participant.getParticipantId());
if (athlete != null && (athlete.getPlayerNo() == null || athlete.getPlayerNo().isEmpty())) {
athlete.setPlayerNo(String.format("%03d", counter));
athleteMapper.updateById(athlete);
}
}
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean moveScheduleGroup(org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO dto) {
// 1. 查询分组信息
MartialScheduleGroup group = scheduleGroupMapper.selectById(dto.getGroupId());
if (group == null) {
throw new RuntimeException("分组不存在");
}
// 2. 查询该分组的详情记录
List<MartialScheduleDetail> details = scheduleDetailMapper.selectList(
new QueryWrapper<MartialScheduleDetail>()
.eq("schedule_group_id", dto.getGroupId())
.eq("is_deleted", 0)
);
if (details.isEmpty()) {
throw new RuntimeException("分组详情不存在");
}
// 3. 查询目标场地信息
MartialVenue targetVenue = venueService.getById(dto.getTargetVenueId());
if (targetVenue == null) {
throw new RuntimeException("目标场地不存在");
}
// 4. 根据时间段索引计算日期和时间
// 假设: 0=第1天上午, 1=第1天下午, 2=第2天上午, 3=第2天下午...
// 需要从赛事信息中获取起始日期
int dayOffset = dto.getTargetTimeSlotIndex() / 2; // 每天2个时段
boolean isAfternoon = dto.getTargetTimeSlotIndex() % 2 == 1;
String timeSlot = isAfternoon ? "13:30" : "08:30";
// 获取赛事起始日期(从第一个detail中获取)
LocalDate baseDate = details.get(0).getScheduleDate();
if (baseDate == null) {
throw new RuntimeException("无法确定赛事起始日期");
}
// 计算目标日期(从起始日期开始,加上dayOffset天的偏移)
// 如果当前detail的日期早于base date,需要调整
LocalDate minDate = details.stream()
.map(MartialScheduleDetail::getScheduleDate)
.filter(Objects::nonNull)
.min(LocalDate::compareTo)
.orElse(baseDate);
LocalDate targetDate = minDate.plusDays(dayOffset);
// 5. 更新所有detail记录
for (MartialScheduleDetail detail : details) {
detail.setVenueId(dto.getTargetVenueId());
detail.setVenueName(targetVenue.getVenueName());
detail.setScheduleDate(targetDate);
detail.setTimeSlot(timeSlot);
detail.setTimeSlotIndex(dto.getTargetTimeSlotIndex());
scheduleDetailMapper.updateById(detail);
}
return true;
}
}