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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,7 +23,7 @@ public interface IScheduleArrangeService {
|
||||
* @param dto Schedule draft data
|
||||
* @return Success flag
|
||||
*/
|
||||
boolean saveAndLockSchedule(SaveScheduleDraftDTO dto);
|
||||
boolean saveAndLockSchedule(Long competitionId);
|
||||
|
||||
/**
|
||||
* Move schedule group to different venue/time slot
|
||||
|
||||
+314
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user