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:
@@ -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