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);
|
return R.data(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取编排结果失败, competitionId: {}", competitionId, 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("草稿保存失败");
|
return success ? R.success("草稿保存成功") : R.fail("草稿保存失败");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("保存编排草稿失败", e);
|
log.error("保存编排草稿失败", e);
|
||||||
return R.fail("保存编排草稿失败: " + e.getMessage());
|
return R.fail("保存编排草稿失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,17 +91,11 @@ public class MartialScheduleArrangeController extends BladeController {
|
|||||||
try {
|
try {
|
||||||
BladeUser user = AuthUtil.getUser();
|
BladeUser user = AuthUtil.getUser();
|
||||||
String userId = user != null ? user.getUserName() : "system";
|
String userId = user != null ? user.getUserName() : "system";
|
||||||
|
boolean success = scheduleService.saveDraftAndLockSchedule(dto, userId);
|
||||||
boolean success = scheduleService.saveAndLockSchedule(dto.getCompetitionId());
|
return success ? R.success("编排已完成并锁定") : R.fail("编排锁定失败");
|
||||||
if (success) {
|
|
||||||
scheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
|
|
||||||
return R.success("编排已完成并锁定");
|
|
||||||
} else {
|
|
||||||
return R.fail("编排锁定失败");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("保存并锁定编排失败", e);
|
log.error("保存并锁定编排失败", e);
|
||||||
return R.fail("保存并锁定编排失败: " + e.getMessage());
|
return R.fail("保存并锁定编排失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +111,7 @@ public class MartialScheduleArrangeController extends BladeController {
|
|||||||
return R.success("自动编排完成");
|
return R.success("自动编排完成");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("自动编排失败", 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("分组移动失败");
|
return success ? R.success("分组移动成功") : R.fail("分组移动失败");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("移动分组失败", 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);
|
return R.data(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取调度数据失败", 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("顺序调整失败");
|
return success ? R.success("顺序调整成功") : R.fail("顺序调整失败");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("调整顺序失败", 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("调度保存失败");
|
return success ? R.success("调度保存成功") : R.fail("调度保存失败");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("保存调度失败", 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("状态更新失败");
|
return success ? R.success("状态更新成功") : R.fail("状态更新失败");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("更新签到状态失败", 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 id="selectScheduleGroupDetails" resultType="org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO">
|
||||||
SELECT
|
SELECT
|
||||||
g.id AS groupId,
|
g.id AS groupId,
|
||||||
|
g.project_id AS projectId,
|
||||||
g.group_name AS groupName,
|
g.group_name AS groupName,
|
||||||
g.category AS category,
|
g.category AS category,
|
||||||
g.project_type AS projectType,
|
g.project_type AS projectType,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class ScheduleGroupDetailVO implements Serializable {
|
|||||||
|
|
||||||
// === 分组信息 ===
|
// === 分组信息 ===
|
||||||
private Long groupId;
|
private Long groupId;
|
||||||
|
private Long projectId;
|
||||||
private String groupName;
|
private String groupName;
|
||||||
private String category;
|
private String category;
|
||||||
private Integer projectType;
|
private Integer projectType;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public interface IMartialScheduleService extends IService<MartialSchedule> {
|
|||||||
|
|
||||||
boolean saveAndLockSchedule(Long competitionId);
|
boolean saveAndLockSchedule(Long competitionId);
|
||||||
|
|
||||||
|
boolean saveDraftAndLockSchedule(SaveScheduleDraftDTO dto, String userId);
|
||||||
|
|
||||||
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
boolean moveScheduleGroup(MoveScheduleGroupDTO dto);
|
||||||
|
|
||||||
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
org.springblade.modules.martial.pojo.vo.DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex);
|
||||||
|
|||||||
+112
-52
@@ -2,7 +2,6 @@ package org.springblade.modules.martial.service.impl;
|
|||||||
|
|
||||||
import org.springblade.modules.martial.service.IScheduleExportService;
|
import org.springblade.modules.martial.service.IScheduleExportService;
|
||||||
import org.springblade.modules.martial.service.IScheduleQueryService;
|
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.IScheduleDispatchService;
|
||||||
import org.springblade.modules.martial.service.IScheduleStatusService;
|
import org.springblade.modules.martial.service.IScheduleStatusService;
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
private IScheduleQueryService scheduleQueryService;
|
private IScheduleQueryService scheduleQueryService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IScheduleArrangeService scheduleArrangeService;
|
private IMartialScheduleArrangeService martialScheduleArrangeService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IScheduleDispatchService scheduleDispatchService;
|
private IScheduleDispatchService scheduleDispatchService;
|
||||||
@@ -177,6 +176,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
|
|
||||||
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
|
CompetitionGroupDTO groupDTO = new CompetitionGroupDTO();
|
||||||
groupDTO.setId(firstDetail.getGroupId());
|
groupDTO.setId(firstDetail.getGroupId());
|
||||||
|
groupDTO.setProjectId(firstDetail.getProjectId());
|
||||||
groupDTO.setTitle(firstDetail.getGroupName());
|
groupDTO.setTitle(firstDetail.getGroupName());
|
||||||
groupDTO.setCode(firstDetail.getCategory());
|
groupDTO.setCode(firstDetail.getCategory());
|
||||||
|
|
||||||
@@ -468,70 +468,71 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean saveDraftSchedule(SaveScheduleDraftDTO dto) {
|
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;
|
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()) {
|
for (CompetitionGroupDTO groupDTO : dto.getCompetitionGroups()) {
|
||||||
// 1. 更新或创建编排明细
|
// 1. 更新或创建编排明细
|
||||||
Long groupId = groupDTO.getId();
|
Long groupId = groupDTO.getId();
|
||||||
Long realGroupId = groupId;
|
Long realGroupId = groupId;
|
||||||
|
|
||||||
// Handle negative ID (draft data from auto-arrange)
|
// 负数分组ID表示前端临时分组,优先使用DTO中的projectId,不再从code后缀推断
|
||||||
if (groupId != null && groupId < 0) {
|
if (groupId != null && groupId < 0) {
|
||||||
Long projectId = null;
|
realGroupId = tempGroupIdMappings.get(groupId);
|
||||||
if (groupDTO.getCode() != null && groupDTO.getCode().contains("-P")) {
|
if (realGroupId == null) {
|
||||||
try {
|
Long projectId = resolveProjectId(groupDTO);
|
||||||
String suffix = groupDTO.getCode().substring(groupDTO.getCode().lastIndexOf("-P") + 2);
|
if (projectId == null) {
|
||||||
projectId = Long.parseLong(suffix);
|
throw new IllegalArgumentException("临时分组缺少projectId,无法保存");
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to parse projectId from code: {}", groupDTO.getCode());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
MartialScheduleGroup existingGroup = null;
|
Integer projectType = resolveProjectType(groupDTO.getType());
|
||||||
if (projectId != null) {
|
int participantCount = groupDTO.getParticipants() != null ? groupDTO.getParticipants().size() : 0;
|
||||||
existingGroup = scheduleGroupMapper.selectOne(
|
MartialScheduleGroup existingGroup = findReusableGroup(existingGroups, occupiedGroupIds, projectId, groupDTO.getCode());
|
||||||
new QueryWrapper<MartialScheduleGroup>()
|
|
||||||
.eq("competition_id", dto.getCompetitionId())
|
|
||||||
.eq("project_id", projectId)
|
|
||||||
.eq("is_deleted", 0)
|
|
||||||
.last("LIMIT 1")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine project type from DTO
|
if (existingGroup != null) {
|
||||||
Integer projectType = null;
|
realGroupId = existingGroup.getId();
|
||||||
if ("集体".equals(groupDTO.getType())) {
|
existingGroup.setProjectId(projectId);
|
||||||
projectType = 2;
|
existingGroup.setGroupName(groupDTO.getTitle());
|
||||||
} else if ("单人".equals(groupDTO.getType())) {
|
existingGroup.setProjectType(projectType);
|
||||||
projectType = 1;
|
existingGroup.setTotalParticipants(participantCount);
|
||||||
}
|
existingGroup.setTotalTeams(participantCount);
|
||||||
|
existingGroup.setCategory(groupDTO.getCode());
|
||||||
// Calculate participant/team counts
|
scheduleGroupMapper.updateById(existingGroup);
|
||||||
int participantCount = groupDTO.getParticipants() != null ? groupDTO.getParticipants().size() : 0;
|
} else {
|
||||||
|
MartialScheduleGroup newGroup = new MartialScheduleGroup();
|
||||||
if (existingGroup != null) {
|
newGroup.setCompetitionId(dto.getCompetitionId());
|
||||||
realGroupId = existingGroup.getId();
|
newGroup.setProjectId(projectId);
|
||||||
existingGroup.setGroupName(groupDTO.getTitle());
|
newGroup.setGroupName(groupDTO.getTitle());
|
||||||
existingGroup.setProjectType(projectType);
|
newGroup.setProjectType(projectType);
|
||||||
existingGroup.setTotalParticipants(participantCount);
|
newGroup.setTotalParticipants(participantCount);
|
||||||
existingGroup.setTotalTeams(participantCount);
|
newGroup.setTotalTeams(participantCount);
|
||||||
existingGroup.setCategory(groupDTO.getCode());
|
newGroup.setCategory(groupDTO.getCode());
|
||||||
scheduleGroupMapper.updateById(existingGroup);
|
scheduleGroupMapper.insert(newGroup);
|
||||||
} else {
|
realGroupId = newGroup.getId();
|
||||||
MartialScheduleGroup newGroup = new MartialScheduleGroup();
|
existingGroups.add(newGroup);
|
||||||
newGroup.setCompetitionId(Long.valueOf(dto.getCompetitionId()));
|
}
|
||||||
newGroup.setProjectId(projectId);
|
|
||||||
newGroup.setGroupName(groupDTO.getTitle());
|
tempGroupIdMappings.put(groupId, realGroupId);
|
||||||
newGroup.setProjectType(projectType);
|
occupiedGroupIds.add(realGroupId);
|
||||||
newGroup.setTotalParticipants(participantCount);
|
|
||||||
newGroup.setTotalTeams(participantCount);
|
|
||||||
newGroup.setCategory(groupDTO.getCode());
|
|
||||||
scheduleGroupMapper.insert(newGroup);
|
|
||||||
realGroupId = newGroup.getId();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (realGroupId == null) {
|
||||||
|
throw new IllegalArgumentException("分组ID不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
MartialScheduleDetail detail = scheduleDetailMapper.selectOne(
|
MartialScheduleDetail detail = scheduleDetailMapper.selectOne(
|
||||||
new QueryWrapper<MartialScheduleDetail>()
|
new QueryWrapper<MartialScheduleDetail>()
|
||||||
@@ -692,6 +693,65 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
|||||||
return true;
|
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
|
* 根据时间段字符串计算 timeSlotIndex
|
||||||
* 按照时间从早到晚分配索引: 08:30=0, 13:30=1, 其他时间按字符串顺序排序
|
* 按照时间从早到晚分配索引: 08:30=0, 13:30=1, 其他时间按字符串顺序排序
|
||||||
|
|||||||
Reference in New Issue
Block a user