diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index ffafef6..493f685 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -420,36 +420,91 @@ public class TaskService { log.info("分组完成,共 {} 个子任务组", currentGroupNo); - // **步骤2:按分组汇总,每个分组创建一个子任务** - // 先按分组号收集任务类型 - Map> groupedItems = new java.util.LinkedHashMap<>(); - Map groupFirstTaskTypeId = new java.util.HashMap<>(); // 每组第一个任务类型ID + // **步骤2:两级分组 - 先按部门分组,再按设计师分组** + // 第一级:按部门分组(用于编号的第一段) + // 第二级:同一部门内按设计师分组(用于编号的第二段) + // 收集所有分组键和对应的任务项 + Map> groupedItems = new java.util.LinkedHashMap<>(); for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) { int itemGroupNo = itemDto.getSortOrder() / 1000; groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto); - // 记录每组第一个任务类型ID - if (!groupFirstTaskTypeId.containsKey(itemGroupNo)) { - groupFirstTaskTypeId.put(itemGroupNo, itemDto.getTaskTypeId()); + } + + // 按部门重新组织分组,计算部门序号 + // 格式:deptKey -> List + Map> deptToGroups = new java.util.LinkedHashMap<>(); + Map groupToDeptKey = new java.util.HashMap<>(); + + for (int i = 0; i < dto.getItems().size(); i++) { + com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i); + int itemGroupNo = itemDto.getSortOrder() / 1000; + + // 获取部门键 + String deptKey; + if (itemDto.getSelectedDeptId() != null) { + deptKey = String.valueOf(itemDto.getSelectedDeptId()); + } else { + var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); + deptKey = demandType.getAllowedDeptIds(); + if (deptKey == null && demandType.getParentId() != null) { + var parentType = configService.getDemandTypeById(demandType.getParentId()); + if (parentType != null) { + deptKey = parentType.getAllowedDeptIds(); + } + } + } + if (deptKey == null) deptKey = "null"; + + // 记录分组到部门的映射 + if (!groupToDeptKey.containsKey(itemGroupNo)) { + groupToDeptKey.put(itemGroupNo, deptKey); + deptToGroups.computeIfAbsent(deptKey, k -> new java.util.ArrayList<>()).add(itemGroupNo); } } + // 计算每个部门的序号 + Map deptIndexMap = new java.util.LinkedHashMap<>(); + int deptIndex = 1; + for (String deptKey : deptToGroups.keySet()) { + deptIndexMap.put(deptKey, deptIndex++); + } + + // 计算每个分组在其部门内的子序号 + Map groupSubIndexMap = new java.util.HashMap<>(); + for (Map.Entry> entry : deptToGroups.entrySet()) { + List groups = entry.getValue(); + for (int subIdx = 0; subIdx < groups.size(); subIdx++) { + groupSubIndexMap.put(groups.get(subIdx), subIdx + 1); + } + } + + log.info("部门分组统计: 共 {} 个部门, 各部门子任务数: {}", deptToGroups.size(), + deptToGroups.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue().size()) + .collect(Collectors.joining(", "))); + // 为每个分组创建一个子任务 int itemIndex = 1; for (Map.Entry> entry : groupedItems.entrySet()) { int groupNo = entry.getKey(); List groupItems = entry.getValue(); + // 获取该分组的部门序号和部门内子序号 + String deptKey = groupToDeptKey.get(groupNo); + int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1); + int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1); + // 汇总该分组的积分、工时、数量 int groupTotalScore = 0; BigDecimal groupTotalHours = BigDecimal.ZERO; int groupTotalQuantity = 0; - StringBuilder taskTypeIds = new StringBuilder(); + Long firstTaskTypeId = null; // 构建任务类型明细 JSON 数组 java.util.List> typeDetailsList = new java.util.ArrayList<>(); for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) { - var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); + var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); int itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())).intValue(); BigDecimal itemHours = demandType.getEstimatedHours() != null ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())) @@ -459,10 +514,9 @@ public class TaskService { groupTotalHours = groupTotalHours.add(itemHours); groupTotalQuantity += itemDto.getQuantity(); - if (taskTypeIds.length() > 0) { - taskTypeIds.append(","); + if (firstTaskTypeId == null) { + firstTaskTypeId = itemDto.getTaskTypeId(); } - taskTypeIds.append(itemDto.getTaskTypeId()); // 添加任务类型明细 java.util.Map detail = new java.util.LinkedHashMap<>(); @@ -477,9 +531,10 @@ public class TaskService { // 创建子任务 com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem(); item.setTaskId(task.getId()); - item.setItemNo(generateTaskItemNo(task.getTaskNo(), itemIndex)); + // 使用新的编号格式:任务编号-部门序号-部门内子任务序号 + item.setItemNo(generateTaskItemNoWithDept(task.getTaskNo(), currentDeptIndex, subIndex)); item.setGroupNo(groupNo); - item.setTaskTypeId(groupFirstTaskTypeId.get(groupNo)); // 使用第一个任务类型ID + item.setTaskTypeId(firstTaskTypeId); // 保存任务类型明细 JSON try { com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); @@ -488,7 +543,7 @@ public class TaskService { log.warn("序列化任务类型明细失败: {}", e.getMessage()); } item.setQuantity(groupTotalQuantity); - item.setUnitScore(groupTotalScore / groupTotalQuantity); // 平均单价 + item.setUnitScore(groupTotalScore / Math.max(groupTotalQuantity, 1)); // 平均单价 item.setTotalScore(groupTotalScore); item.setEstimatedHours(groupTotalHours); item.setSortOrder(itemIndex); @@ -500,40 +555,43 @@ public class TaskService { if (itemReceiverId != null) { item.setReceiverId(itemReceiverId); User itemReceiver = userService.getUserById(itemReceiverId); - if (itemReceiver != null && itemReceiver.getDeptId() != null) { - item.setReceiverDeptId(itemReceiver.getDeptId()); + if (itemReceiver != null) { + item.setReceiverName(itemReceiver.getRealName()); + if (itemReceiver.getDeptId() != null) { + item.setReceiverDeptId(itemReceiver.getDeptId()); + } } - log.info("子任务组 {} 已预先指定设计师: {}", groupNo, itemReceiverId); + log.info("子任务 {} 已预先指定设计师: {}", item.getItemNo(), itemReceiverId); } } // 关键:根据流水线模式和分组设置初始状态 if ("sequential".equals(dto.getPipelineMode())) { - if (groupNo == 1) { - // 顺序流水线:第一组待接单 + // 顺序流水线:只有第一个部门的第一个子任务可接单 + if (currentDeptIndex == 1 && subIndex == 1) { item.setStatus("pending"); item.setIsLocked(0); item.setPrevItemId(null); } else { - // 其他组的子任务都是 locked(锁定) + // 其他子任务都是 locked(锁定) item.setStatus("locked"); item.setIsLocked(1); } } else { // 并行流水线:全部待接单 - item.setStatus("pending"); + item.setStatus("pending"); item.setIsLocked(0); } taskItemMapper.insert(item); - log.info("子任务创建成功,ID: {}, 编号: {}, 组号: {}, 积分: {}, 工时: {}, 状态: {}", - item.getId(), item.getItemNo(), item.getGroupNo(), groupTotalScore, groupTotalHours, item.getStatus()); + log.info("子任务创建成功,ID: {}, 编号: {}, 部门序号: {}, 部门内序号: {}, 积分: {}, 工时: {}, 状态: {}", + item.getId(), item.getItemNo(), currentDeptIndex, subIndex, groupTotalScore, groupTotalHours, item.getStatus()); itemIndex++; } - log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 分组数: {}", - task.getId(), groupedItems.size(), currentGroupNo); + log.info("流水线任务创建完成,主任务ID: {}, 子任务数: {}, 部门数: {}", + task.getId(), groupedItems.size(), deptToGroups.size()); // 【两级审核流程】发送审核通知给部门负责人 sendApprovalNotification(task); @@ -542,11 +600,23 @@ public class TaskService { } /** - * 生成子任务编号 + * 生成子任务编号(旧格式,保留兼容) + * 格式:任务编号-子任务序号 */ private String generateTaskItemNo(String taskNo, int itemIndex) { return taskNo + "-" + String.format("%02d", itemIndex); } + + /** + * 生成子任务编号(新格式,支持部门内多子任务) + * 格式:任务编号-部门序号-部门内子任务序号 + * @param taskNo 主任务编号 + * @param deptIndex 部门序号(从1开始) + * @param subIndex 部门内子任务序号(从1开始) + */ + private String generateTaskItemNoWithDept(String taskNo, int deptIndex, int subIndex) { + return taskNo + "-" + String.format("%02d", deptIndex) + "-" + String.format("%02d", subIndex); + } /** * 生成任务编号 @@ -1375,15 +1445,55 @@ public class TaskService { lastGroupKey = currentGroupKey; } - // **步骤2:按分组汇总,每个分组创建一个子任务** + // **步骤2:两级分组 - 先按部门分组,再按设计师分组** Map> groupedItems = new java.util.LinkedHashMap<>(); - Map groupFirstTaskTypeId = new java.util.HashMap<>(); - for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : dto.getItems()) { int itemGroupNo = itemDto.getSortOrder() / 1000; groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto); - if (!groupFirstTaskTypeId.containsKey(itemGroupNo)) { - groupFirstTaskTypeId.put(itemGroupNo, itemDto.getTaskTypeId()); + } + + // 按部门重新组织分组,计算部门序号 + Map> deptToGroups = new java.util.LinkedHashMap<>(); + Map groupToDeptKey = new java.util.HashMap<>(); + + for (int i = 0; i < dto.getItems().size(); i++) { + com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = dto.getItems().get(i); + int itemGroupNo = itemDto.getSortOrder() / 1000; + + String deptKey; + if (itemDto.getSelectedDeptId() != null) { + deptKey = String.valueOf(itemDto.getSelectedDeptId()); + } else { + var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); + deptKey = demandType.getAllowedDeptIds(); + if (deptKey == null && demandType.getParentId() != null) { + var parentType = configService.getDemandTypeById(demandType.getParentId()); + if (parentType != null) { + deptKey = parentType.getAllowedDeptIds(); + } + } + } + if (deptKey == null) deptKey = "null"; + + if (!groupToDeptKey.containsKey(itemGroupNo)) { + groupToDeptKey.put(itemGroupNo, deptKey); + deptToGroups.computeIfAbsent(deptKey, k -> new java.util.ArrayList<>()).add(itemGroupNo); + } + } + + // 计算每个部门的序号 + Map deptIndexMap = new java.util.LinkedHashMap<>(); + int deptIndex = 1; + for (String deptKey : deptToGroups.keySet()) { + deptIndexMap.put(deptKey, deptIndex++); + } + + // 计算每个分组在其部门内的子序号 + Map groupSubIndexMap = new java.util.HashMap<>(); + for (Map.Entry> entry : deptToGroups.entrySet()) { + List groups = entry.getValue(); + for (int subIdx = 0; subIdx < groups.size(); subIdx++) { + groupSubIndexMap.put(groups.get(subIdx), subIdx + 1); } } @@ -1393,11 +1503,15 @@ public class TaskService { int groupNo = entry.getKey(); List groupItems = entry.getValue(); + String deptKey = groupToDeptKey.get(groupNo); + int currentDeptIndex = deptIndexMap.getOrDefault(deptKey, 1); + int subIndex = groupSubIndexMap.getOrDefault(groupNo, 1); + // 汇总该分组的积分、工时、数量 int groupTotalScore = 0; BigDecimal groupTotalHours = BigDecimal.ZERO; int groupTotalQuantity = 0; - StringBuilder taskTypeIds = new StringBuilder(); + Long firstTaskTypeId = null; java.util.List> typeDetailsList = new java.util.ArrayList<>(); for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) { @@ -1411,10 +1525,9 @@ public class TaskService { groupTotalHours = groupTotalHours.add(itemHours); groupTotalQuantity += itemDto.getQuantity(); - if (taskTypeIds.length() > 0) { - taskTypeIds.append(","); + if (firstTaskTypeId == null) { + firstTaskTypeId = itemDto.getTaskTypeId(); } - taskTypeIds.append(itemDto.getTaskTypeId()); java.util.Map detail = new java.util.LinkedHashMap<>(); detail.put("typeId", itemDto.getTaskTypeId()); @@ -1428,9 +1541,10 @@ public class TaskService { // 创建子任务 com.designhub.entity.TaskItem item = new com.designhub.entity.TaskItem(); item.setTaskId(mainTask.getId()); - item.setItemNo(generateTaskItemNo(mainTask.getTaskNo(), itemIndex)); + // 使用新的编号格式:任务编号-部门序号-部门内子任务序号 + item.setItemNo(generateTaskItemNoWithDept(mainTask.getTaskNo(), currentDeptIndex, subIndex)); item.setGroupNo(groupNo); - item.setTaskTypeId(groupFirstTaskTypeId.get(groupNo)); + item.setTaskTypeId(firstTaskTypeId); try { com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList)); @@ -1438,7 +1552,7 @@ public class TaskService { log.warn("序列化任务类型明细失败: {}", e.getMessage()); } item.setQuantity(groupTotalQuantity); - item.setUnitScore(groupTotalScore / groupTotalQuantity); + item.setUnitScore(groupTotalScore / Math.max(groupTotalQuantity, 1)); item.setTotalScore(groupTotalScore); item.setEstimatedHours(groupTotalHours); item.setSortOrder(itemIndex); @@ -1450,15 +1564,19 @@ public class TaskService { if (itemReceiverId != null) { item.setReceiverId(itemReceiverId); User itemReceiver = userService.getUserById(itemReceiverId); - if (itemReceiver != null && itemReceiver.getDeptId() != null) { - item.setReceiverDeptId(itemReceiver.getDeptId()); + if (itemReceiver != null) { + item.setReceiverName(itemReceiver.getRealName()); + if (itemReceiver.getDeptId() != null) { + item.setReceiverDeptId(itemReceiver.getDeptId()); + } } } } // 根据流水线模式和分组设置初始状态 if ("sequential".equals(dto.getPipelineMode())) { - if (groupNo == 1) { + // 顺序流水线:只有第一个部门的第一个子任务可接单 + if (currentDeptIndex == 1 && subIndex == 1) { item.setStatus("pending"); item.setIsLocked(0); item.setPrevItemId(null); @@ -1472,7 +1590,8 @@ public class TaskService { } taskItemMapper.insert(item); - log.info("子任务创建成功,ID: {}, 编号: {}, 组号: {}", item.getId(), item.getItemNo(), item.getGroupNo()); + log.info("子任务创建成功,ID: {}, 编号: {}, 部门序号: {}, 部门内序号: {}", + item.getId(), item.getItemNo(), currentDeptIndex, subIndex); itemIndex++; }