From 462e1a4e880bf00afaf134d19943c2a553fbb36b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 28 Mar 2026 15:05:45 +0800 Subject: [PATCH] fix: expand receiving department children for task type routing --- .../designhub/service/DepartmentService.java | 58 ++++++++++++- .../service/TaskApprovalService.java | 6 +- .../designhub/service/TaskItemService.java | 87 +++++++++---------- .../com/designhub/service/TaskService.java | 46 +++++----- .../designhub/service/TaskUnlockService.java | 25 +----- frontend/src/views/Settings.vue | 22 ++++- 6 files changed, 149 insertions(+), 95 deletions(-) diff --git a/backend/src/main/java/com/designhub/service/DepartmentService.java b/backend/src/main/java/com/designhub/service/DepartmentService.java index 4540965..7b34959 100644 --- a/backend/src/main/java/com/designhub/service/DepartmentService.java +++ b/backend/src/main/java/com/designhub/service/DepartmentService.java @@ -646,6 +646,63 @@ public class DepartmentService { log.info("获取部门{}的所有子部门ID: {}", deptId, result); return result; } + + /** + * 获取部门自身及所有子部门ID + */ + public List getDeptAndChildIds(Long deptId) { + if (deptId == null) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + result.add(deptId); + result.addAll(getAllChildDeptIds(deptId)); + return result.stream().distinct().collect(Collectors.toList()); + } + + /** + * 展开逗号分隔的部门ID,自动包含所有子部门 + */ + public List expandDeptIdsWithChildren(String deptIdsText) { + if (!StringUtils.hasText(deptIdsText)) { + return Collections.emptyList(); + } + + List deptIds = Arrays.stream(deptIdsText.split(",")) + .map(String::trim) + .filter(StringUtils::hasText) + .map(idText -> { + try { + return Long.parseLong(idText); + } catch (NumberFormatException e) { + log.warn("解析部门ID失败: {}", idText); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return expandDeptIdsWithChildren(deptIds); + } + + /** + * 展开部门ID列表,自动包含所有子部门 + */ + public List expandDeptIdsWithChildren(List deptIds) { + if (CollectionUtils.isEmpty(deptIds)) { + return Collections.emptyList(); + } + + java.util.LinkedHashSet result = new java.util.LinkedHashSet<>(); + for (Long deptId : deptIds) { + if (deptId == null) { + continue; + } + result.addAll(getDeptAndChildIds(deptId)); + } + return new ArrayList<>(result); + } /** * 检查用户是否为任何部门的负责人 @@ -665,4 +722,3 @@ public class DepartmentService { return count != null && count > 0; } } - diff --git a/backend/src/main/java/com/designhub/service/TaskApprovalService.java b/backend/src/main/java/com/designhub/service/TaskApprovalService.java index 96b08cf..e56fc3f 100644 --- a/backend/src/main/java/com/designhub/service/TaskApprovalService.java +++ b/backend/src/main/java/com/designhub/service/TaskApprovalService.java @@ -39,6 +39,7 @@ public class TaskApprovalService { private final CategoryMapper categoryMapper; private final PlatformMapper platformMapper; private final NotificationService notificationService; + private final DepartmentService departmentService; private final TeamScorePoolService teamScorePoolService; @org.springframework.beans.factory.annotation.Autowired @@ -1013,9 +1014,8 @@ public class TaskApprovalService { } if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) { - String[] deptIds = allowedDeptIds.split(","); - if (deptIds.length > 0) { - Long deptId = Long.parseLong(deptIds[0].trim()); + List expandedDeptIds = departmentService.expandDeptIdsWithChildren(allowedDeptIds); + for (Long deptId : expandedDeptIds) { Department dept = departmentMapper.selectById(deptId); if (dept != null && dept.getLeaderId() != null) { log.info("从任务类型配置获取接单部门负责人(降级方案): 部门={}, 负责人ID={}", diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java index 72c5c06..9d5db30 100644 --- a/backend/src/main/java/com/designhub/service/TaskItemService.java +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -132,19 +132,7 @@ public class TaskItemService { if (allowedDepts == null || allowedDepts.isEmpty()) { return false; } - String[] deptIds = allowedDepts.split(","); - for (String deptIdStr : deptIds) { - try { - Long deptId = Long.parseLong(deptIdStr.trim()); - // 🔥 检查是否匹配筛选部门或其子部门 - if (filterDeptAndChildIds.contains(deptId)) { - return true; - } - } catch (NumberFormatException e) { - // 忽略无效的部门ID - } - } - return false; + return expandAllowedDeptIds(allowedDepts).stream().anyMatch(filterDeptAndChildIds::contains); }) .map(com.designhub.entity.DemandType::getId) .collect(java.util.stream.Collectors.toList()); @@ -369,23 +357,7 @@ public class TaskItemService { if (allowedDeptIds == null || allowedDeptIds.isBlank()) { return java.util.Collections.emptyList(); } - - List result = new java.util.ArrayList<>(); - Set seen = new java.util.HashSet<>(); - for (String deptIdText : allowedDeptIds.split(",")) { - if (deptIdText == null || deptIdText.isBlank()) { - continue; - } - try { - Long deptId = Long.parseLong(deptIdText.trim()); - if (seen.add(deptId)) { - result.add(deptId); - } - } catch (NumberFormatException e) { - log.warn("解析排队部门ID失败: taskItemId={}, deptIdText={}", item.getId(), deptIdText); - } - } - return result; + return new java.util.ArrayList<>(expandAllowedDeptIds(allowedDeptIds)); } private int getDeptDistance(Long userDeptId, Long targetDeptId) { @@ -1883,11 +1855,11 @@ public class TaskItemService { log.info("管理员模式,有权限"); } else { // 部门负责人:检查子任务的任务类型配置的 allowed_dept_ids 是否包含该部门 - Long targetDeptId = getTargetDeptIdFromTaskType(item.getTaskTypeId()); - log.info("子任务 {} 的目标部门ID: {}, 查询的部门ID: {}", - item.getId(), targetDeptId, receiverDeptId); + List targetDeptIds = getTargetDeptIdsFromTaskType(item.getTaskTypeId()); + log.info("子任务 {} 的目标部门ID列表: {}, 查询的部门ID: {}", + item.getId(), targetDeptIds, receiverDeptId); - if (targetDeptId != null && targetDeptId.equals(receiverDeptId)) { + if (targetDeptIds.contains(receiverDeptId)) { hasPermission = true; log.info("匹配成功(通过任务类型配置)"); } @@ -2108,17 +2080,22 @@ public class TaskItemService { * 如果当前类型未配置,则递归向上查找父级类型的配置 */ private Long getTargetDeptIdFromTaskType(Long taskTypeId) { + List targetDeptIds = getTargetDeptIdsFromTaskType(taskTypeId); + return targetDeptIds.isEmpty() ? null : targetDeptIds.get(0); + } + + private List getTargetDeptIdsFromTaskType(Long taskTypeId) { log.debug("getTargetDeptIdFromTaskType 调用, taskTypeId: {}", taskTypeId); if (taskTypeId == null) { log.debug("taskTypeId 为空,返回 null"); - return null; + return java.util.Collections.emptyList(); } com.designhub.entity.DemandType taskType = demandTypeMapper.selectById(taskTypeId); if (taskType == null) { log.warn("任务类型 {} 不存在", taskTypeId); - return null; + return java.util.Collections.emptyList(); } log.debug("任务类型: id={}, name={}, parentId={}, allowedDeptIds={}", @@ -2127,26 +2104,42 @@ public class TaskItemService { // 如果当前类型配置了allowed_dept_ids,返回第一个部门ID String allowedDeptIds = taskType.getAllowedDeptIds(); if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) { - try { - String[] deptIds = allowedDeptIds.split(","); - if (deptIds.length > 0) { - Long result = Long.parseLong(deptIds[0].trim()); - log.debug("找到 allowed_dept_ids: {}, 返回第一个: {}", allowedDeptIds, result); - return result; - } - } catch (NumberFormatException e) { - log.warn("解析allowed_dept_ids失败: {}", allowedDeptIds, e); + List expandedDeptIds = new java.util.ArrayList<>(expandAllowedDeptIds(allowedDeptIds)); + if (!expandedDeptIds.isEmpty()) { + log.debug("找到 allowed_dept_ids: {}, 展开后返回: {}", allowedDeptIds, expandedDeptIds); + return expandedDeptIds; } } // 如果当前类型未配置,递归查找父级 if (taskType.getParentId() != null) { log.debug("当前类型未配置 allowed_dept_ids, 递归查找父级: {}", taskType.getParentId()); - return getTargetDeptIdFromTaskType(taskType.getParentId()); + return getTargetDeptIdsFromTaskType(taskType.getParentId()); } log.debug("未找到配置的部门,返回 null"); - return null; + return java.util.Collections.emptyList(); + } + + private Set expandAllowedDeptIds(String allowedDeptIds) { + Set result = new java.util.LinkedHashSet<>(); + if (allowedDeptIds == null || allowedDeptIds.trim().isEmpty()) { + return result; + } + + for (String deptIdText : allowedDeptIds.split(",")) { + if (deptIdText == null || deptIdText.isBlank()) { + continue; + } + try { + Long deptId = Long.parseLong(deptIdText.trim()); + result.addAll(getDeptAndChildIds(deptId)); + } catch (NumberFormatException e) { + log.warn("解析接单部门ID失败: {}", deptIdText); + } + } + + return result; } private void markPipelineItemUnlocked(TaskItem item) { diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index 5276f11..49c227e 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -1260,14 +1260,8 @@ public class TaskService { if (allowedDepts == null || allowedDepts.isEmpty()) { return true; } - // 检查用户部门是否在允许列表中 - String[] deptIds = allowedDepts.split(","); - for (String deptId : deptIds) { - if (deptId.trim().equals(String.valueOf(userDeptId))) { - return true; - } - } - return false; + // 选择父级接单部门时,运行时需要自动覆盖所有子部门。 + return expandAllowedDeptIds(allowedDepts).contains(userDeptId); }) .map(DemandType::getId) .collect(Collectors.toList()); @@ -1294,19 +1288,7 @@ public class TaskService { if (allowedDepts == null || allowedDepts.isEmpty()) { return false; // 所有部门都可接单的不纳入筛选结果 } - String[] deptIds = allowedDepts.split(","); - for (String deptIdStr : deptIds) { - try { - Long deptId = Long.parseLong(deptIdStr.trim()); - // 🔥 检查是否匹配筛选部门或其子部门 - if (filterDeptAndChildSet.contains(deptId)) { - return true; - } - } catch (NumberFormatException e) { - // 忽略无效的部门ID - } - } - return false; + return expandAllowedDeptIds(allowedDepts).stream().anyMatch(filterDeptAndChildSet::contains); }) .map(DemandType::getId) .collect(Collectors.toList()); @@ -4263,6 +4245,28 @@ public class TaskService { collectChildDeptIds(deptId, result); return result; } + + private Set expandAllowedDeptIds(String allowedDeptIds) { + Set result = new HashSet<>(); + if (!StringUtils.hasText(allowedDeptIds)) { + return result; + } + + for (String deptIdText : allowedDeptIds.split(",")) { + String trimmed = deptIdText == null ? "" : deptIdText.trim(); + if (trimmed.isEmpty()) { + continue; + } + try { + Long deptId = Long.parseLong(trimmed); + result.addAll(getDeptAndChildIds(deptId)); + } catch (NumberFormatException e) { + log.warn("解析接单部门ID失败: {}", deptIdText); + } + } + + return result; + } /** * 递归收集所有子部门ID diff --git a/backend/src/main/java/com/designhub/service/TaskUnlockService.java b/backend/src/main/java/com/designhub/service/TaskUnlockService.java index 3ff736c..ee6d70f 100644 --- a/backend/src/main/java/com/designhub/service/TaskUnlockService.java +++ b/backend/src/main/java/com/designhub/service/TaskUnlockService.java @@ -40,6 +40,9 @@ public class TaskUnlockService { @Autowired private DemandTypeMapper demandTypeMapper; + + @Autowired + private DepartmentService departmentService; @Autowired private TaskStatusNotificationService taskStatusNotificationService; @@ -744,27 +747,7 @@ public class TaskUnlockService { if (allowedDeptIds == null || allowedDeptIds.trim().isEmpty()) { return Collections.emptyList(); } - String[] parts = allowedDeptIds.split(","); - List result = new ArrayList<>(); - Set seen = new HashSet<>(); - for (String part : parts) { - if (part == null) { - continue; - } - String s = part.trim(); - if (s.isEmpty()) { - continue; - } - try { - Long id = Long.parseLong(s); - if (seen.add(id)) { - result.add(id); - } - } catch (NumberFormatException e) { - // 忽略 - } - } - return result; + return departmentService.expandDeptIdsWithChildren(allowedDeptIds); } /** diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 756fa1b..ef69d45 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -4351,6 +4351,24 @@ const flatDepartmentOptions = computed(() => { return groups }) +const expandReceivingDepartmentIds = (selectedDeptIds: number[] = []) => { + const expanded = new Set() + + const appendDeptAndChildren = (deptId?: number) => { + if (!deptId || expanded.has(deptId)) { + return + } + + expanded.add(deptId) + departments.value + .filter(d => d.parentId === deptId && d.deptType === 'receiving' && d.status === 1) + .forEach(child => appendDeptAndChildren(child.id)) + } + + selectedDeptIds.forEach(id => appendDeptAndChildren(id)) + return Array.from(expanded) +} + // 根据部门ID字符串获取部门名称 const getDeptNamesFromIds = (idsStr: string) => { if (!idsStr) return '' @@ -5175,9 +5193,9 @@ const saveDemandType = async () => { isEnabled: formData.demandType.isEnabled ?? 1, // 父级ID(二级、三级分类需要设置) parentId: (addLevel === 'level2' || addLevel === 'level3') ? formData.demandType.parentId : null, - // 将部门ID数组转换为逗号分隔的字符串(仅二级分类需要设置) + // 选择父级接单部门时,同时持久化所有子级接单部门,保证运行时精确匹配也能命中。 allowedDeptIds: addLevel === 'level2' && formData.demandType.departmentIds?.length > 0 - ? formData.demandType.departmentIds.join(',') + ? expandReceivingDepartmentIds(formData.demandType.departmentIds).join(',') : null }