diff --git a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta index 7f3a6e0..fc25b5b 100644 Binary files a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta and b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta index 738f8cd..082e263 100644 Binary files a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta index e63556d..b221ca0 100644 Binary files a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/.usage.json/xl.meta b/minio_data/.minio.sys/buckets/.usage.json/xl.meta index a3ba632..bcced4a 100644 Binary files a/minio_data/.minio.sys/buckets/.usage.json/xl.meta and b/minio_data/.minio.sys/buckets/.usage.json/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta index eccb323..7fb32a7 100644 Binary files a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta index 3e10c41..ae4bb2e 100644 Binary files a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta index 76edf14..acf1dbd 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta and b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin.bkp/xl.meta differ diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta index 565c1c0..68dfe39 100644 Binary files a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta and b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta differ diff --git a/src/main/java/org/springblade/modules/martial/service/IScheduleArrangeService.java b/src/main/java/org/springblade/modules/martial/service/IScheduleArrangeService.java index 26be18e..adf293d 100644 --- a/src/main/java/org/springblade/modules/martial/service/IScheduleArrangeService.java +++ b/src/main/java/org/springblade/modules/martial/service/IScheduleArrangeService.java @@ -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 diff --git a/src/main/java/org/springblade/modules/martial/service/impl/ScheduleArrangeServiceImpl.java b/src/main/java/org/springblade/modules/martial/service/impl/ScheduleArrangeServiceImpl.java new file mode 100644 index 0000000..56d1e3d --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/service/impl/ScheduleArrangeServiceImpl.java @@ -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() + .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() + .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() + .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 groups = scheduleGroupMapper.selectList( + new QueryWrapper() + .eq("competition_id", competitionId) + .eq("is_deleted", 0) + ); + + if (groups.isEmpty()) { + // 没有分组数据,视为成功(可能是初始状态,尚未进行自动编排) + return true; + } + + // 2. 获取所有分组ID + List groupIds = groups.stream().map(MartialScheduleGroup::getId).collect(Collectors.toList()); + + // 3. 更新所有参赛者的编排状态为completed + List participants = scheduleParticipantMapper.selectList( + new QueryWrapper() + .in("schedule_group_id", groupIds) + .eq("is_deleted", 0) + ); + + // 按分组和出场顺序分配选手编号 + Map 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 details = scheduleDetailMapper.selectList( + new QueryWrapper() + .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; + } +}