From 8d65610643356706e0124da0e479a2458fd887ac Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 22 Jan 2026 18:10:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=80=E7=BA=A7?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E9=83=A8=E9=97=A8=E4=BB=BB=E5=8A=A1=E5=9C=A8?= =?UTF-8?q?=E6=8E=A5=E5=8D=95=E9=83=A8=E9=97=A8=E5=AE=A1=E6=A0=B8=E6=97=B6?= =?UTF-8?q?=E6=8C=89=E6=8E=A5=E5=8D=95=E9=83=A8=E9=97=A8=E6=8B=86=E5=88=86?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=AD=90=E4=BB=BB=E5=8A=A1=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/designhub/service/TaskService.java | 280 +++++++++++++++--- 1 file changed, 231 insertions(+), 49 deletions(-) diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index b145e57..dfbf387 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -944,6 +944,17 @@ public class TaskService { // 查询处于审批流程中的任务(包括一级需求审核和明细完善状态) wrapper.in(Task::getApprovalStatus, "pending_primary_approval", "pending_detail_completion", "pending_demand_approval", "pending_receiving_approval"); + // 🔥 过滤掉已经拆分成子任务组的一级需求部门任务 + // 一级需求部门任务在接单部门审核时,应该显示子任务组,而不是主任务 + // 条件:isPrimaryTask=1 AND isPipeline=1 AND approvalStatus='pending_receiving_approval' 的任务应该被过滤 + wrapper.and(w -> w + .ne(Task::getIsPrimaryTask, 1) // 不是一级需求部门任务 + .or() + .eq(Task::getIsPipeline, 0) // 或者不是流水线任务 + .or() + .ne(Task::getApprovalStatus, "pending_receiving_approval") // 或者不是待接单部门审核状态 + ); + // 检查用户是否是管理员 User user = userService.getUserById(userId); boolean isAdmin = user != null && Integer.valueOf(1).equals(user.getIsAdmin()); @@ -4079,6 +4090,7 @@ public class TaskService { /** * 从DTO创建任务子项(用于一级需求部门任务明细完善) + * 按接单部门分组,与普通流水线任务保持一致 */ @Transactional(rollbackFor = Exception.class) public void createTaskItemsFromDTO(Long taskId, List taskItems) { @@ -4087,57 +4099,240 @@ public class TaskService { throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在"); } - log.info("为任务 {} 创建子任务,共 {} 个", taskId, taskItems.size()); + log.info("为任务 {} 创建子任务(按接单部门分组),共 {} 个任务类型", taskId, taskItems.size()); - int itemIndex = 1; - BigDecimal totalScore = BigDecimal.ZERO; - BigDecimal totalHours = BigDecimal.ZERO; - int totalQuantity = 0; + // **步骤1:按接单部门和设计师自动分组** + int currentGroupNo = 1; + String lastGroupKey = null; // 格式:deptKey_receiverId - for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : taskItems) { - // 获取任务类型信息 + for (int i = 0; i < taskItems.size(); i++) { + com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = taskItems.get(i); var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId()); if (demandType == null) { throw new BusinessException(ResultCode.PARAM_ERROR, "任务类型不存在: " + itemDto.getTaskTypeId()); } - // 计算积分和工时 - BigDecimal itemScore = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())); - BigDecimal itemHours = demandType.getEstimatedHours() != null - ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())) - : BigDecimal.ZERO; + // 获取当前任务类型的接单部门配置 + String currentDeptKey; - totalScore = totalScore.add(itemScore); - totalHours = totalHours.add(itemHours); - totalQuantity += itemDto.getQuantity(); + // 🔥 优先使用用户选择的接单部门 + if (itemDto.getSelectedDeptId() != null) { + currentDeptKey = String.valueOf(itemDto.getSelectedDeptId()); + log.info("任务类型 {} 使用用户选择的接单部门: {}", demandType.getName(), currentDeptKey); + } else { + // 否则使用任务类型配置的 allowedDeptIds(支持三级分类继承二级分类的配置) + currentDeptKey = demandType.getAllowedDeptIds(); + if (currentDeptKey == null && demandType.getParentId() != null) { + // 如果当前类型没有配置,尝试获取父级的配置 + var parentType = configService.getDemandTypeById(demandType.getParentId()); + if (parentType != null) { + currentDeptKey = parentType.getAllowedDeptIds(); + log.info("三级分类 {} 继承父级 {} 的接单部门配置: {}", + demandType.getName(), parentType.getName(), currentDeptKey); + } + } + } + + // 🔥 构建分组键:部门 + 设计师(如果指定了设计师) + String currentGroupKey; + if (itemDto.getReceiverId() != null) { + // 如果指定了设计师,分组键 = 部门_设计师ID + currentGroupKey = (currentDeptKey != null ? currentDeptKey : "null") + "_" + itemDto.getReceiverId(); + } else { + // 没有指定设计师,分组键 = 部门_0 + currentGroupKey = (currentDeptKey != null ? currentDeptKey : "null") + "_0"; + } + + // 判断是否需要创建新分组 + boolean needNewGroup = false; + if (i > 0) { + // 如果分组键不同,则需要创建新分组 + if (lastGroupKey == null || !lastGroupKey.equals(currentGroupKey)) { + needNewGroup = true; + } + } + + if (needNewGroup) { + currentGroupNo++; + log.info("检测到分组变化,创建新分组: groupNo={}, 前一分组键={}, 当前分组键={}", + currentGroupNo, lastGroupKey, currentGroupKey); + } + + // 记录当前分组号(临时存储在sortOrder字段,稍后会恢复) + itemDto.setSortOrder(currentGroupNo * 1000 + i); // 高位存group_no,低位存原始顺序 + lastGroupKey = currentGroupKey; + } + + log.info("分组完成,共 {} 个子任务组", currentGroupNo); + + // **步骤2:两级分组 - 先按部门分组,再按设计师分组** + // 收集所有分组键和对应的任务项 + Map> groupedItems = new java.util.LinkedHashMap<>(); + for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : taskItems) { + int itemGroupNo = itemDto.getSortOrder() / 1000; + groupedItems.computeIfAbsent(itemGroupNo, k -> new java.util.ArrayList<>()).add(itemDto); + } + + // 按部门重新组织分组,计算部门序号 + Map> deptToGroups = new java.util.LinkedHashMap<>(); + Map groupToDeptKey = new java.util.HashMap<>(); + + for (int i = 0; i < taskItems.size(); i++) { + com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto = taskItems.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(", "))); + + // **步骤3:为每个分组创建一个子任务** + int itemIndex = 1; + BigDecimal totalScore = BigDecimal.ZERO; + BigDecimal totalHours = BigDecimal.ZERO; + int totalQuantity = 0; + + 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); + + // 汇总该分组的积分、工时、数量 + BigDecimal groupTotalScoreDecimal = BigDecimal.ZERO; + BigDecimal groupTotalHours = BigDecimal.ZERO; + int groupTotalQuantity = 0; + 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()); + BigDecimal itemScoreDecimal = demandType.getUnitScore().multiply(BigDecimal.valueOf(itemDto.getQuantity())); + BigDecimal itemHours = demandType.getEstimatedHours() != null + ? demandType.getEstimatedHours().multiply(BigDecimal.valueOf(itemDto.getQuantity())) + : BigDecimal.ZERO; + + groupTotalScoreDecimal = groupTotalScoreDecimal.add(itemScoreDecimal); + groupTotalHours = groupTotalHours.add(itemHours); + groupTotalQuantity += itemDto.getQuantity(); + + if (firstTaskTypeId == null) { + firstTaskTypeId = itemDto.getTaskTypeId(); + } + + // 添加任务类型明细 + java.util.Map detail = new java.util.LinkedHashMap<>(); + detail.put("typeId", itemDto.getTaskTypeId()); + detail.put("typeName", demandType.getName()); + detail.put("quantity", itemDto.getQuantity()); + detail.put("unitScore", demandType.getUnitScore()); + detail.put("totalScore", itemScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue()); + // 如果指定了设计师,也保存到明细中 + if (itemDto.getReceiverId() != null) { + detail.put("receiverId", itemDto.getReceiverId()); + User receiver = userService.getUserById(itemDto.getReceiverId()); + if (receiver != null) { + detail.put("receiverName", receiver.getRealName()); + } + } + typeDetailsList.add(detail); + } + + BigDecimal groupTotalScore = groupTotalScoreDecimal.setScale(2, java.math.RoundingMode.HALF_UP); // 创建子任务 TaskItem item = new TaskItem(); item.setTaskId(taskId); - item.setItemNo(task.getTaskNo() + "-" + String.format("%02d", itemIndex)); - item.setTaskTypeId(itemDto.getTaskTypeId()); - item.setTaskTypeName(demandType.getName()); - item.setQuantity(itemDto.getQuantity()); - item.setUnitScore(demandType.getUnitScore()); - item.setTotalScore(itemScore.setScale(2, java.math.RoundingMode.HALF_UP)); - item.setEstimatedHours(itemHours); + // 使用新的编号格式:任务编号-部门序号-部门内子任务序号 + item.setItemNo(generateTaskItemNoWithDept(task.getTaskNo(), currentDeptIndex, subIndex)); + item.setGroupNo(groupNo); + item.setTaskTypeId(firstTaskTypeId); + // 保存任务类型明细 JSON + try { + com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); + item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList)); + } catch (Exception e) { + log.warn("序列化任务类型明细失败: {}", e.getMessage()); + } + item.setQuantity(groupTotalQuantity); + // 计算平均单价(保留2位小数) + item.setUnitScore(groupTotalQuantity > 0 + ? groupTotalScore.divide(BigDecimal.valueOf(groupTotalQuantity), 2, java.math.RoundingMode.HALF_UP) + : BigDecimal.ZERO); + item.setTotalScore(groupTotalScore); + item.setEstimatedHours(groupTotalHours); item.setSortOrder(itemIndex); item.setProgress(0); item.setStatus("pending"); item.setIsLocked(itemIndex == 1 ? 0 : 1); // 第一个子任务解锁,其他锁定 - // 设置接单部门(如果任务类型有配置) - if (demandType.getAllowedDeptIds() != null && !demandType.getAllowedDeptIds().isEmpty()) { - String[] deptIds = demandType.getAllowedDeptIds().split(","); - if (deptIds.length == 1) { - item.setReceiverDeptId(Long.parseLong(deptIds[0].trim())); + // 🔥 设置子任务的接单部门 + // 优先使用用户选择的部门,否则尝试从任务类型配置获取 + if (!groupItems.isEmpty()) { + Long selectedDeptId = groupItems.get(0).getSelectedDeptId(); + if (selectedDeptId != null) { + item.setReceiverDeptId(selectedDeptId); + log.info("子任务 {} 设置用户选择的接单部门: {}", item.getItemNo(), selectedDeptId); + } else if (deptKey != null && !deptKey.equals("null") && !deptKey.contains(",")) { + // 任务类型只配置了一个部门,自动设置 + try { + Long singleDeptId = Long.parseLong(deptKey.trim()); + item.setReceiverDeptId(singleDeptId); + log.info("子任务 {} 自动设置唯一的接单部门: {}", item.getItemNo(), singleDeptId); + } catch (NumberFormatException e) { + log.warn("无法解析部门ID: {}", deptKey); + } } } // 如果指定了设计师 - if (itemDto.getReceiverId() != null) { - item.setReceiverId(itemDto.getReceiverId()); - User receiver = userService.getUserById(itemDto.getReceiverId()); + if (!groupItems.isEmpty() && groupItems.get(0).getReceiverId() != null) { + Long receiverId = groupItems.get(0).getReceiverId(); + item.setReceiverId(receiverId); + User receiver = userService.getUserById(receiverId); if (receiver != null) { item.setReceiverName(receiver.getRealName()); if (item.getReceiverDeptId() == null && receiver.getDeptId() != null) { @@ -4146,26 +4341,13 @@ public class TaskService { } } - // 构建任务类型明细 JSON - java.util.List> typeDetailsList = new java.util.ArrayList<>(); - java.util.Map detail = new java.util.LinkedHashMap<>(); - detail.put("typeId", itemDto.getTaskTypeId()); - detail.put("typeName", demandType.getName()); - detail.put("quantity", itemDto.getQuantity()); - detail.put("unitScore", demandType.getUnitScore()); - detail.put("totalScore", itemScore.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue()); - typeDetailsList.add(detail); - - try { - com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); - item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList)); - } catch (Exception e) { - log.warn("序列化任务类型明细失败: {}", e.getMessage()); - } - taskItemMapper.insert(item); - log.info("创建子任务: itemNo={}, typeId={}, score={}", item.getItemNo(), itemDto.getTaskTypeId(), itemScore); + log.info("创建子任务组: itemNo={}, groupNo={}, deptIndex={}, subIndex={}, types={}, score={}", + item.getItemNo(), groupNo, currentDeptIndex, subIndex, typeDetailsList.size(), groupTotalScore); + totalScore = totalScore.add(groupTotalScore); + totalHours = totalHours.add(groupTotalHours); + totalQuantity += groupTotalQuantity; itemIndex++; } @@ -4179,7 +4361,7 @@ public class TaskService { } taskMapper.updateById(task); - log.info("任务 {} 子任务创建完成,共 {} 个子任务,总积分 {}", taskId, taskItems.size(), totalScore); + log.info("任务 {} 子任务创建完成,共 {} 个子任务组,总积分 {}", taskId, groupedItems.size(), totalScore); } }