修复顺序流水线同部门内子任务并行逻辑

1. 修改TaskService创建子任务时的状态设置:
   - 第一个部门的所有子任务都设为pending(可接单)
   - 其他部门的子任务设为locked(锁定)

2. 修改TaskItemService子任务完成后的解锁逻辑:
   - 基于部门序号(从itemNo解析)而不是groupNo
   - 当前部门的所有子任务完成后,才解锁下一个部门的所有子任务
   - 同一部门内的子任务并行执行

3. 新增parseDeptIndexFromItemNo辅助方法:
   - 从子任务编号解析部门序号
   - 支持新格式(TASK...-01-02)和旧格式兼容
This commit is contained in:
Your Name 2026-01-14 18:18:52 +08:00
parent 777658b72c
commit 9599cf74a8
2 changed files with 84 additions and 48 deletions

View File

@ -475,77 +475,82 @@ public class TaskItemService {
Map<String, Object> result = new HashMap<>();
result.put("completedItem", item);
// 6. 🔥 关键步骤:检查是否是顺序流水线,基于group_no解锁下一组
// 6. 🔥 关键步骤:检查是否是顺序流水线,基于部门解锁下一个部门
// 新逻辑:同一部门内的所有子任务并行,部门间串行
Task mainTask = taskMapper.selectById(item.getTaskId());
if (mainTask != null && "sequential".equals(mainTask.getPipelineMode())) {
// 获取当前子任务的分组号
Integer currentGroupNo = item.getGroupNo();
if (currentGroupNo == null) {
currentGroupNo = 1; // 兼容旧数据
}
// 从子任务编号解析部门序号(格式:TASK...-部门序号-部门内序号)
int currentDeptIndex = parseDeptIndexFromItemNo(item.getItemNo());
log.info("当前子任务 {} 属于第 {} 个部门", item.getItemNo(), currentDeptIndex);
// 检查当前组的所有子任务是否都已完成
List<TaskItem> currentGroupItems = taskItemMapper.selectList(
// 获取当前部门的所有子任务(通过编号匹配)
List<TaskItem> allItems = taskItemMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, item.getTaskId())
.eq(TaskItem::getGroupNo, currentGroupNo)
);
boolean allGroupCompleted = currentGroupItems.stream()
// 筛选出同一部门的子任务
List<TaskItem> currentDeptItems = allItems.stream()
.filter(i -> parseDeptIndexFromItemNo(i.getItemNo()) == currentDeptIndex)
.collect(java.util.stream.Collectors.toList());
// 检查当前部门的所有子任务是否都已完成
boolean allDeptCompleted = currentDeptItems.stream()
.allMatch(i -> "completed".equals(i.getStatus()));
if (allGroupCompleted) {
log.info("✅ 第 {} 组所有子任务已完成,准备解锁下一组", currentGroupNo);
log.info("当前部门共 {} 个子任务,已完成: {}", currentDeptItems.size(),
currentDeptItems.stream().filter(i -> "completed".equals(i.getStatus())).count());
if (allDeptCompleted) {
log.info("✅ 第 {} 个部门的所有子任务已完成,准备解锁下一个部门", currentDeptIndex);
// 🔥 收集当前组所有子任务的下载链接,传递给下一组
// 🔥 收集当前部门所有子任务的下载链接,传递给下一个部门
StringBuilder prevDownloadUrls = new StringBuilder();
for (TaskItem groupItem : currentGroupItems) {
if (groupItem.getDownloadUrl() != null && !groupItem.getDownloadUrl().isEmpty()) {
for (TaskItem deptItem : currentDeptItems) {
if (deptItem.getDownloadUrl() != null && !deptItem.getDownloadUrl().isEmpty()) {
if (prevDownloadUrls.length() > 0) {
prevDownloadUrls.append(",");
}
prevDownloadUrls.append(groupItem.getDownloadUrl());
prevDownloadUrls.append(deptItem.getDownloadUrl());
}
// 同时继承前面环节的下载链接
if (groupItem.getPrevDownloadUrl() != null && !groupItem.getPrevDownloadUrl().isEmpty()) {
if (deptItem.getPrevDownloadUrl() != null && !deptItem.getPrevDownloadUrl().isEmpty()) {
if (prevDownloadUrls.length() > 0) {
prevDownloadUrls.append(",");
}
prevDownloadUrls.append(groupItem.getPrevDownloadUrl());
prevDownloadUrls.append(deptItem.getPrevDownloadUrl());
}
}
String prevDownloadUrlStr = prevDownloadUrls.toString();
log.info("前置环节素材下载链接: {}", prevDownloadUrlStr);
log.info("前置部门素材下载链接: {}", prevDownloadUrlStr);
// 解锁下一组的所有子任务
Integer nextGroupNo = currentGroupNo + 1;
List<TaskItem> nextGroupItems = taskItemMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
.eq(TaskItem::getTaskId, item.getTaskId())
.eq(TaskItem::getGroupNo, nextGroupNo)
.eq(TaskItem::getStatus, "locked")
);
// 找出下一个部门的所有子任务(状态为locked)
int nextDeptIndex = currentDeptIndex + 1;
List<TaskItem> nextDeptItems = allItems.stream()
.filter(i -> parseDeptIndexFromItemNo(i.getItemNo()) == nextDeptIndex)
.filter(i -> "locked".equals(i.getStatus()))
.collect(java.util.stream.Collectors.toList());
if (!nextGroupItems.isEmpty()) {
// 批量解锁下一组的所有子任务,状态设为待审核,并传递前置环节的下载链接
for (TaskItem nextItem : nextGroupItems) {
if (!nextDeptItems.isEmpty()) {
// 批量解锁下一个部门的所有子任务,状态设为待审核,并传递前置环节的下载链接
for (TaskItem nextItem : nextDeptItems) {
nextItem.setStatus("pending_approval"); // 待接单部门负责人审核
nextItem.setIsLocked(0);
nextItem.setPrevDownloadUrl(prevDownloadUrlStr); // 🔥 传递前置环节的下载链接
nextItem.setUpdateTime(LocalDateTime.now());
taskItemMapper.updateById(nextItem);
log.info("✅ 解锁子任务: {} (组号: {}, ID: {}),待接单部门负责人审核,已传递前置素材链接",
nextItem.getItemNo(), nextGroupNo, nextItem.getId());
log.info("✅ 解锁子任务: {} (部门序号: {}, ID: {}),待接单部门负责人审核,已传递前置素材链接",
nextItem.getItemNo(), nextDeptIndex, nextItem.getId());
}
result.put("unlockedGroupNo", nextGroupNo);
result.put("unlockedCount", nextGroupItems.size());
result.put("unlockedDeptIndex", nextDeptIndex);
result.put("unlockedCount", nextDeptItems.size());
// 发送通知给接单部门负责人
// 从子任务的任务类型配置中获取allowed_dept_ids
Set<Long> notifiedLeaderIds = new HashSet<>();
for (TaskItem nextItem : nextGroupItems) {
for (TaskItem nextItem : nextDeptItems) {
Long targetDeptId = getTargetDeptIdFromTaskType(nextItem.getTaskTypeId());
if (targetDeptId != null) {
com.designhub.entity.Department receiverDept = departmentMapper.selectById(targetDeptId);
@ -557,29 +562,29 @@ public class TaskItemService {
notification.setReceiverId(receiverDept.getLeaderId());
notification.setNotificationType("task_approval");
notification.setTitle("新子任务组待审核");
notification.setContent("任务《" + mainTask.getTitle() + "》第 " + nextGroupNo + " 组共 " +
nextGroupItems.size() + " 个子任务已解锁,请审核并分配!");
notification.setContent("任务《" + mainTask.getTitle() + "》第 " + nextDeptIndex + " 个部门共 " +
nextDeptItems.size() + " 个子任务已解锁,请审核并分配!");
notification.setRelatedId(item.getTaskId());
notification.setRelatedType("task");
notification.setPriority(2); // 重要
notification.setPushDingtalk(1); // 推送到钉钉
notificationService.sendNotification(notification);
log.info("已通知接单部门负责人(部门: {}, 负责人ID: {})审核第 {} 组子任务",
receiverDept.getDeptName(), receiverDept.getLeaderId(), nextGroupNo);
log.info("已通知接单部门负责人(部门: {}, 负责人ID: {})审核第 {} 个部门子任务",
receiverDept.getDeptName(), receiverDept.getLeaderId(), nextDeptIndex);
}
} else {
log.warn("子任务 {} 的任务类型未配置接单部门", nextItem.getItemNo());
}
}
log.info("第 {} 组 {} 个子任务已解锁,等待接单部门负责人审核,已通知 {} 个部门负责人",
nextGroupNo, nextGroupItems.size(), notifiedLeaderIds.size());
log.info("第 {} 个部门 {} 个子任务已解锁,等待接单部门负责人审核,已通知 {} 个部门负责人",
nextDeptIndex, nextDeptItems.size(), notifiedLeaderIds.size());
} else {
log.info("没有更多子任务组需要解锁");
log.info("没有更多部门的子任务需要解锁");
}
} else {
log.info("当前组还有子任务未完成,暂不解锁下一组");
log.info("当前部门还有子任务未完成,暂不解锁下一个部门");
}
}
@ -1310,6 +1315,33 @@ public class TaskItemService {
return new java.util.ArrayList<>(groupMap.values());
}
/**
* 从子任务编号解析部门序号
* 编号格式:任务编号-部门序号-部门内序号,例如 TASK20260114-01-02
* @param itemNo 子任务编号
* @return 部门序号,解析失败返回1
*/
private int parseDeptIndexFromItemNo(String itemNo) {
if (itemNo == null || itemNo.isEmpty()) {
return 1;
}
String[] parts = itemNo.split("-");
if (parts.length >= 3) {
try {
// 新格式:TASK...-部门序号-部门内序号
// 倒数第二段是部门序号
return Integer.parseInt(parts[parts.length - 2]);
} catch (NumberFormatException e) {
log.warn("解析子任务编号部门序号失败: {}", itemNo);
}
} else if (parts.length == 2) {
// 旧格式:TASK...-序号,认为是第一个部门
return 1;
}
return 1;
}
/**
* 从任务类型配置中获取目标接单部门ID
* 如果当前类型未配置,则递归向上查找父级类型的配置

View File

@ -567,15 +567,18 @@ public class TaskService {
// 关键:根据流水线模式和分组设置初始状态
if ("sequential".equals(dto.getPipelineMode())) {
// 顺序流水线:只有第一个部门的第一个子任务可接单
if (currentDeptIndex == 1 && subIndex == 1) {
// 顺序流水线:第一个部门的所有子任务都可接单(部门内并行)
// 只有当第一个部门的所有子任务完成后,才解锁下一个部门的子任务
if (currentDeptIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
item.setPrevItemId(null);
log.info("子任务 {} 属于第一个部门,设置为待接单状态", item.getItemNo());
} else {
// 其他子任务都是 locked(锁定)
// 其他部门的子任务都是 locked(锁定),等待前一个部门完成后解锁
item.setStatus("locked");
item.setIsLocked(1);
log.info("子任务 {} 属于第{}个部门,设置为锁定状态", item.getItemNo(), currentDeptIndex);
}
} else {
// 并行流水线:全部待接单
@ -1575,12 +1578,13 @@ public class TaskService {
// 根据流水线模式和分组设置初始状态
if ("sequential".equals(dto.getPipelineMode())) {
// 顺序流水线:只有第一个部门的第一个子任务可接单
if (currentDeptIndex == 1 && subIndex == 1) {
// 顺序流水线:第一个部门的所有子任务都可接单(部门内并行)
if (currentDeptIndex == 1) {
item.setStatus("pending");
item.setIsLocked(0);
item.setPrevItemId(null);
} else {
// 其他部门的子任务锁定,等待前一个部门完成
item.setStatus("locked");
item.setIsLocked(1);
}