fix(schedule): correct project binding and atomic lock flow
This commit is contained in:
+11
-17
@@ -63,7 +63,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return R.data(result);
|
||||
} catch (Exception e) {
|
||||
log.error("获取编排结果失败, competitionId: {}", competitionId, e);
|
||||
return R.fail("获取编排结果失败: " + e.getMessage());
|
||||
return R.fail("获取编排结果失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return success ? R.success("草稿保存成功") : R.fail("草稿保存失败");
|
||||
} catch (Exception e) {
|
||||
log.error("保存编排草稿失败", e);
|
||||
return R.fail("保存编排草稿失败: " + e.getMessage());
|
||||
return R.fail("保存编排草稿失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,17 +91,11 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
try {
|
||||
BladeUser user = AuthUtil.getUser();
|
||||
String userId = user != null ? user.getUserName() : "system";
|
||||
|
||||
boolean success = scheduleService.saveAndLockSchedule(dto.getCompetitionId());
|
||||
if (success) {
|
||||
scheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
|
||||
return R.success("编排已完成并锁定");
|
||||
} else {
|
||||
return R.fail("编排锁定失败");
|
||||
}
|
||||
boolean success = scheduleService.saveDraftAndLockSchedule(dto, userId);
|
||||
return success ? R.success("编排已完成并锁定") : R.fail("编排锁定失败");
|
||||
} catch (Exception e) {
|
||||
log.error("保存并锁定编排失败", e);
|
||||
return R.fail("保存并锁定编排失败: " + e.getMessage());
|
||||
return R.fail("保存并锁定编排失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +111,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return R.success("自动编排完成");
|
||||
} catch (Exception e) {
|
||||
log.error("自动编排失败", e);
|
||||
return R.fail("自动编排失败: " + e.getMessage());
|
||||
return R.fail("自动编排失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +126,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return success ? R.success("分组移动成功") : R.fail("分组移动失败");
|
||||
} catch (Exception e) {
|
||||
log.error("移动分组失败", e);
|
||||
return R.fail("移动分组失败: " + e.getMessage());
|
||||
return R.fail("移动分组失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +145,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return R.data(data);
|
||||
} catch (Exception e) {
|
||||
log.error("获取调度数据失败", e);
|
||||
return R.fail("获取调度数据失败: " + e.getMessage());
|
||||
return R.fail("获取调度数据失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +160,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return success ? R.success("顺序调整成功") : R.fail("顺序调整失败");
|
||||
} catch (Exception e) {
|
||||
log.error("调整顺序失败", e);
|
||||
return R.fail("调整顺序失败: " + e.getMessage());
|
||||
return R.fail("调整顺序失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +175,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return success ? R.success("调度保存成功") : R.fail("调度保存失败");
|
||||
} catch (Exception e) {
|
||||
log.error("保存调度失败", e);
|
||||
return R.fail("保存调度失败: " + e.getMessage());
|
||||
return R.fail("保存调度失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +192,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
return success ? R.success("状态更新成功") : R.fail("状态更新失败");
|
||||
} catch (Exception e) {
|
||||
log.error("更新签到状态失败", e);
|
||||
return R.fail("更新签到状态失败: " + e.getMessage());
|
||||
return R.fail("更新签到状态失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<select id="selectScheduleGroupDetails" resultType="org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO">
|
||||
SELECT
|
||||
g.id AS groupId,
|
||||
g.project_id AS projectId,
|
||||
g.group_name AS groupName,
|
||||
g.category AS category,
|
||||
g.project_type AS projectType,
|
||||
|
||||
@@ -15,6 +15,7 @@ public class ScheduleGroupDetailVO implements Serializable {
|
||||
|
||||
// === 分组信息 ===
|
||||
private Long groupId;
|
||||
private Long projectId;
|
||||
private String groupName;
|
||||
private String category;
|
||||
private Integer projectType;
|
||||
|
||||
@@ -25,6 +25,8 @@ public interface IMartialScheduleService extends IService<MartialSchedule> {
|
||||
|
||||
boolean saveAndLockSchedule(Long competitionId);
|
||||
|
||||
boolean saveDraftAndLockSchedule(SaveScheduleDraftDTO dto, String userId);
|
||||
|
||||
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
||||
|
||||
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
||||
|
||||
+93
-33
@@ -2,7 +2,6 @@ package org.springblade.modules.martial.service.impl;
|
||||
|
||||
import org.springblade.modules.martial.service.IScheduleExportService;
|
||||
import org.springblade.modules.martial.service.IScheduleQueryService;
|
||||
import org.springblade.modules.martial.service.IScheduleArrangeService;
|
||||
import org.springblade.modules.martial.service.IScheduleDispatchService;
|
||||
import org.springblade.modules.martial.service.IScheduleStatusService;
|
||||
|
||||
@@ -62,7 +61,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
private IScheduleQueryService scheduleQueryService;
|
||||
|
||||
@Autowired
|
||||
private IScheduleArrangeService scheduleArrangeService;
|
||||
private IMartialScheduleArrangeService martialScheduleArrangeService;
|
||||
|
||||
@Autowired
|
||||
private IScheduleDispatchService scheduleDispatchService;
|
||||
@@ -177,6 +176,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
|
||||
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
|
||||
groupDTO.setId(firstDetail.getGroupId());
|
||||
groupDTO.setProjectId(firstDetail.getProjectId());
|
||||
groupDTO.setTitle(firstDetail.getGroupName());
|
||||
groupDTO.setCode(firstDetail.getCategory());
|
||||
|
||||
@@ -468,51 +468,44 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveDraftSchedule(SaveScheduleDraftDTO dto) {
|
||||
if (dto.getCompetitionGroups() == null || dto.getCompetitionGroups().isEmpty()) {
|
||||
if (dto == null || dto.getCompetitionId() == null || dto.getCompetitionGroups() == null || dto.getCompetitionGroups().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<MartialScheduleGroup> existingGroups = scheduleGroupMapper.selectList(
|
||||
new QueryWrapper<MartialScheduleGroup>()
|
||||
.eq("competition_id", dto.getCompetitionId())
|
||||
.eq("is_deleted", 0)
|
||||
.orderByAsc("id")
|
||||
);
|
||||
Set<Long> occupiedGroupIds = dto.getCompetitionGroups().stream()
|
||||
.map(CompetitionGroupDTO::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(id -> id > 0)
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
Map<Long, Long> tempGroupIdMappings = new HashMap<>();
|
||||
|
||||
for (CompetitionGroupDTO groupDTO : dto.getCompetitionGroups()) {
|
||||
// 1. 更新或创建编排明细
|
||||
Long groupId = groupDTO.getId();
|
||||
Long realGroupId = groupId;
|
||||
|
||||
// Handle negative ID (draft data from auto-arrange)
|
||||
// 负数分组ID表示前端临时分组,优先使用DTO中的projectId,不再从code后缀推断
|
||||
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());
|
||||
}
|
||||
realGroupId = tempGroupIdMappings.get(groupId);
|
||||
if (realGroupId == null) {
|
||||
Long projectId = resolveProjectId(groupDTO);
|
||||
if (projectId == null) {
|
||||
throw new IllegalArgumentException("临时分组缺少projectId,无法保存");
|
||||
}
|
||||
|
||||
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
|
||||
Integer projectType = resolveProjectType(groupDTO.getType());
|
||||
int participantCount = groupDTO.getParticipants() != null ? groupDTO.getParticipants().size() : 0;
|
||||
MartialScheduleGroup existingGroup = findReusableGroup(existingGroups, occupiedGroupIds, projectId, groupDTO.getCode());
|
||||
|
||||
if (existingGroup != null) {
|
||||
realGroupId = existingGroup.getId();
|
||||
existingGroup.setProjectId(projectId);
|
||||
existingGroup.setGroupName(groupDTO.getTitle());
|
||||
existingGroup.setProjectType(projectType);
|
||||
existingGroup.setTotalParticipants(participantCount);
|
||||
@@ -521,7 +514,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
scheduleGroupMapper.updateById(existingGroup);
|
||||
} else {
|
||||
MartialScheduleGroup newGroup = new MartialScheduleGroup();
|
||||
newGroup.setCompetitionId(Long.valueOf(dto.getCompetitionId()));
|
||||
newGroup.setCompetitionId(dto.getCompetitionId());
|
||||
newGroup.setProjectId(projectId);
|
||||
newGroup.setGroupName(groupDTO.getTitle());
|
||||
newGroup.setProjectType(projectType);
|
||||
@@ -530,7 +523,15 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
newGroup.setCategory(groupDTO.getCode());
|
||||
scheduleGroupMapper.insert(newGroup);
|
||||
realGroupId = newGroup.getId();
|
||||
existingGroups.add(newGroup);
|
||||
}
|
||||
|
||||
tempGroupIdMappings.put(groupId, realGroupId);
|
||||
occupiedGroupIds.add(realGroupId);
|
||||
}
|
||||
}
|
||||
if (realGroupId == null) {
|
||||
throw new IllegalArgumentException("分组ID不能为空");
|
||||
}
|
||||
|
||||
MartialScheduleDetail detail = scheduleDetailMapper.selectOne(
|
||||
@@ -692,6 +693,65 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveDraftAndLockSchedule(SaveScheduleDraftDTO dto, String userId) {
|
||||
if (dto == null || dto.getCompetitionId() == null) {
|
||||
throw new IllegalArgumentException("competitionId不能为空");
|
||||
}
|
||||
boolean hasDraftGroups = dto.getCompetitionGroups() != null && !dto.getCompetitionGroups().isEmpty();
|
||||
if (hasDraftGroups && !saveDraftSchedule(dto)) {
|
||||
throw new IllegalStateException("保存编排草稿失败");
|
||||
}
|
||||
if (!saveAndLockSchedule(dto.getCompetitionId())) {
|
||||
throw new IllegalStateException("保存并锁定编排失败");
|
||||
}
|
||||
martialScheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Long resolveProjectId(CompetitionGroupDTO groupDTO) {
|
||||
if (groupDTO.getProjectId() != null) {
|
||||
return groupDTO.getProjectId();
|
||||
}
|
||||
Long groupId = groupDTO.getId();
|
||||
if (groupId != null && groupId < 0) {
|
||||
Long fallbackProjectId = -groupId;
|
||||
log.warn("临时分组缺少projectId,使用groupId反推projectId。groupId: {}, fallbackProjectId: {}", groupId, fallbackProjectId);
|
||||
return fallbackProjectId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer resolveProjectType(String type) {
|
||||
if ("集体".equals(type)) {
|
||||
return 2;
|
||||
}
|
||||
if ("单人".equals(type)) {
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MartialScheduleGroup findReusableGroup(List<MartialScheduleGroup> existingGroups, Set<Long> occupiedGroupIds,
|
||||
Long projectId, String category) {
|
||||
MartialScheduleGroup categoryMatchedGroup = existingGroups.stream()
|
||||
.filter(group -> !occupiedGroupIds.contains(group.getId()))
|
||||
.filter(group -> Objects.equals(group.getProjectId(), projectId))
|
||||
.filter(group -> Objects.equals(group.getCategory(), category))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (categoryMatchedGroup != null) {
|
||||
return categoryMatchedGroup;
|
||||
}
|
||||
|
||||
return existingGroups.stream()
|
||||
.filter(group -> !occupiedGroupIds.contains(group.getId()))
|
||||
.filter(group -> Objects.equals(group.getProjectId(), projectId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据时间段字符串计算 timeSlotIndex
|
||||
* 按照时间从早到晚分配索引: 08:30=0, 13:30=1, 其他时间按字符串顺序排序
|
||||
|
||||
Reference in New Issue
Block a user