From e45e74e5ae207f7642be9a88ed99b593de89fb3b Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期三, 24 六月 2026 16:54:46 +0800
Subject: [PATCH] 1.0

---
 backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java |  369 ++++++++++++++++++++++-----------------------------
 1 files changed, 160 insertions(+), 209 deletions(-)

diff --git a/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java b/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java
index e2c629f..0825a05 100644
--- a/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/ApprovalEngineService.java
@@ -48,6 +48,7 @@
 
     private static final TypeReference<List<Long>> LONG_LIST_TYPE = new TypeReference<>() {};
     private static final String AUTO_APPROVE_COMMENT = "前序节点已审批,系统自动同意";
+    private static final String LOCAL_UNIT_GROUP_TOKEN = ":UNIT:";
 
     private final BizApprovalNodeConfigMapper bizApprovalNodeConfigMapper;
     private final ApprovalInstanceMapper approvalInstanceMapper;
@@ -122,7 +123,7 @@
         }
         if (ApprovalNodeCode.LEVEL4_VISIT_DEPT.equals(nodeCode)) {
             Long competentDeptId = getVisitCompetentDeptId();
-            return listEmployeeIdsByDeptAndLeaderFlag(competentDeptId, false);
+            return listEmployeeIdsByDeptWithLeaderFallback(competentDeptId);
         }
         if (ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER.equals(nodeCode)) {
             Long competentDeptId = getVisitCompetentDeptId();
@@ -174,6 +175,28 @@
                                 .eq(MpUser::getDeptId, deptId)
                                 .eq(MpUser::getDeptLeaderFlag, leaderFlag));
         return users.stream().map(MpUser::getId).filter(Objects::nonNull).toList();
+    }
+
+    private List<Long> listAllActiveEmployeeIdsByDept(Long deptId) {
+        if (deptId == null || deptId <= 0) {
+            return List.of();
+        }
+        List<MpUser> users =
+                mpUserMapper.selectList(
+                        new LambdaQueryWrapper<MpUser>()
+                                .eq(MpUser::getDelFlag, 0)
+                                .eq(MpUser::getStatus, 1)
+                                .eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
+                                .eq(MpUser::getDeptId, deptId));
+        return users.stream().map(MpUser::getId).filter(Objects::nonNull).distinct().toList();
+    }
+
+    private List<Long> listEmployeeIdsByDeptWithLeaderFallback(Long deptId) {
+        List<Long> normalEmployees = listEmployeeIdsByDeptAndLeaderFlag(deptId, false);
+        if (normalEmployees != null && !normalEmployees.isEmpty()) {
+            return normalEmployees;
+        }
+        return listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -252,7 +275,7 @@
         inst.setStatus(ApprovalInstanceStatus.REJECTED);
         inst.setFinishedAt(LocalDateTime.now());
         approvalInstanceMapper.updateById(inst);
-        List<ApprovalTask> pending =
+        List<ApprovalTask> remainingTasks =
                 approvalTaskMapper.selectList(
                         new LambdaQueryWrapper<ApprovalTask>()
                                 .eq(ApprovalTask::getInstanceId, inst.getId())
@@ -260,9 +283,9 @@
                                         ApprovalTask::getTaskStatus,
                                         ApprovalTaskStatus.PENDING,
                                         ApprovalTaskStatus.WAITING));
-        for (ApprovalTask t : pending) {
+        for (ApprovalTask t : remainingTasks) {
             t.setTaskStatus(ApprovalTaskStatus.REJECTED);
-            t.setComment(reason);
+            t.setComment(null);
             approvalTaskMapper.updateById(t);
         }
     }
@@ -380,94 +403,16 @@
     }
 
     private int createTasksForLocalUnitNode(Long instanceId, AppointmentOrder order, String initialStatus) {
-        List<Long> localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds());
-        if (localUnitIds.isEmpty()) {
-            return 0;
-        }
-        List<MpPassLocalUnit> units =
-                mpPassLocalUnitMapper.selectList(
-                        new LambdaQueryWrapper<MpPassLocalUnit>()
-                                .eq(MpPassLocalUnit::getDelFlag, 0)
-                                .in(MpPassLocalUnit::getId, localUnitIds));
-        if (units.isEmpty()) {
-            return 0;
-        }
-        Map<Long, MpPassLocalUnit> unitMap =
-                units.stream().collect(java.util.stream.Collectors.toMap(MpPassLocalUnit::getId, x -> x, (a, b) -> a));
-
-        LinkedHashSet<Long> deptIds = new LinkedHashSet<>();
-        LinkedHashSet<Long> groupIds = new LinkedHashSet<>();
-        LinkedHashMap<String, List<Long>> tempGroups = new LinkedHashMap<>();
-
-        for (Long unitId : localUnitIds) {
-            MpPassLocalUnit unit = unitMap.get(unitId);
-            if (unit == null || unit.getApproverType() == null) {
-                continue;
-            }
-            if (unit.getApproverType() == LocalUnitApproverType.DEPT) {
-                for (Long deptId : parseLongJsonArray(unit.getApproverDeptIds())) {
-                    if (deptId != null && deptId > 0) {
-                        deptIds.add(deptId);
-                    }
-                }
-                continue;
-            }
-            if (unit.getApproverType() == LocalUnitApproverType.STAFF_GROUP) {
-                Long gid = unit.getApproverGroupId();
-                if (gid != null && gid > 0) {
-                    groupIds.add(gid);
-                }
-                continue;
-            }
-            if (unit.getApproverType() == LocalUnitApproverType.STAFF) {
-                List<MpPassLocalUnitMember> members =
-                        mpPassLocalUnitMemberMapper.selectList(
-                                new LambdaQueryWrapper<MpPassLocalUnitMember>()
-                                        .eq(MpPassLocalUnitMember::getDelFlag, 0)
-                                        .eq(MpPassLocalUnitMember::getUnitId, unitId)
-                                        .orderByAsc(MpPassLocalUnitMember::getId));
-                LinkedHashSet<Long> mids = new LinkedHashSet<>();
-                for (MpPassLocalUnitMember m : members) {
-                    if (m.getMpUserId() != null && m.getMpUserId() > 0) {
-                        mids.add(m.getMpUserId());
-                    }
-                }
-                if (mids.isEmpty()) {
-                    continue;
-                }
-                List<Long> ordered = mids.stream().toList();
-                String sign = buildTempGroupSign(ordered);
-                tempGroups.putIfAbsent(sign, ordered);
-            }
-        }
-
+        List<LocalUnitApprovalGroup> groups = resolveLocalUnitApprovalGroups(order, false);
         int total = 0;
-        for (Long deptId : deptIds) {
-            List<Long> assignees = listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":DEPT:" + deptId;
-            total += createTasksWithGroupKey(instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, key, assignees, initialStatus);
-        }
-        for (Long groupId : groupIds) {
-            List<MpStaffGroupMember> members =
-                    mpStaffGroupMemberMapper.selectList(
-                            new LambdaQueryWrapper<MpStaffGroupMember>()
-                                    .eq(MpStaffGroupMember::getDelFlag, 0)
-                                    .eq(MpStaffGroupMember::getGroupId, groupId)
-                                    .orderByAsc(MpStaffGroupMember::getId));
-            LinkedHashSet<Long> mids = new LinkedHashSet<>();
-            for (MpStaffGroupMember m : members) {
-                if (m.getMpUserId() != null && m.getMpUserId() > 0) {
-                    mids.add(m.getMpUserId());
-                }
-            }
-            List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":GROUP:" + groupId;
-            total += createTasksWithGroupKey(instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, key, assignees, initialStatus);
-        }
-        for (Map.Entry<String, List<Long>> e : tempGroups.entrySet()) {
-            List<Long> assignees = filterActiveEmployeeIds(e.getValue());
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":TMP:" + e.getKey();
-            total += createTasksWithGroupKey(instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, key, assignees, initialStatus);
+        for (LocalUnitApprovalGroup group : groups) {
+            total +=
+                    createTasksWithGroupKey(
+                            instanceId,
+                            ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
+                            buildLocalUnitGroupKey(group.unitId()),
+                            group.assigneeIds(),
+                            initialStatus);
         }
         return total;
     }
@@ -493,122 +438,15 @@
                     "节点无审批人: " + resolveNodeName(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER));
         }
 
-        List<MpPassLocalUnit> units =
-                mpPassLocalUnitMapper.selectList(
-                        new LambdaQueryWrapper<MpPassLocalUnit>()
-                                .eq(MpPassLocalUnit::getDelFlag, 0)
-                                .in(MpPassLocalUnit::getId, localUnitIds));
-        if (units.size() != localUnitIds.size()) {
-            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
-        }
-        Map<Long, MpPassLocalUnit> unitMap =
-                units.stream()
-                        .collect(
-                                java.util.stream.Collectors.toMap(
-                                        MpPassLocalUnit::getId, x -> x, (a, b) -> a));
-
-        LinkedHashSet<Long> deptIds = new LinkedHashSet<>();
-        LinkedHashSet<Long> groupIds = new LinkedHashSet<>();
-        LinkedHashMap<String, List<Long>> tempGroups = new LinkedHashMap<>();
-
-        for (Long unitId : localUnitIds) {
-            MpPassLocalUnit unit = unitMap.get(unitId);
-            if (unit == null || unit.getApproverType() == null) {
-                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型缺失: " + unitId);
-            }
-            if (unit.getApproverType() == LocalUnitApproverType.DEPT) {
-                List<Long> ids = parseLongJsonArray(unit.getApproverDeptIds());
-                if (ids.isEmpty()) {
-                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批部门未配置: " + unitId);
-                }
-                deptIds.addAll(ids);
-                continue;
-            }
-            if (unit.getApproverType() == LocalUnitApproverType.STAFF_GROUP) {
-                Long gid = unit.getApproverGroupId();
-                if (gid == null || gid <= 0) {
-                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批分组未配置: " + unitId);
-                }
-                groupIds.add(gid);
-                continue;
-            }
-            if (unit.getApproverType() == LocalUnitApproverType.STAFF) {
-                List<MpPassLocalUnitMember> members =
-                        mpPassLocalUnitMemberMapper.selectList(
-                                new LambdaQueryWrapper<MpPassLocalUnitMember>()
-                                        .eq(MpPassLocalUnitMember::getDelFlag, 0)
-                                        .eq(MpPassLocalUnitMember::getUnitId, unitId)
-                                        .orderByAsc(MpPassLocalUnitMember::getId));
-                LinkedHashSet<Long> mids = new LinkedHashSet<>();
-                for (MpPassLocalUnitMember m : members) {
-                    if (m.getMpUserId() != null && m.getMpUserId() > 0) {
-                        mids.add(m.getMpUserId());
-                    }
-                }
-                if (mids.isEmpty()) {
-                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批人员未配置: " + unitId);
-                }
-                List<Long> ordered = mids.stream().toList();
-                String sign = buildTempGroupSign(ordered);
-                tempGroups.putIfAbsent(sign, ordered);
-                continue;
-            }
-            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型无效: " + unitId);
-        }
-
+        List<LocalUnitApprovalGroup> groups = resolveLocalUnitApprovalGroups(order, true);
         int total = 0;
-        for (Long deptId : deptIds) {
-            List<Long> assignees = listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
-            if (assignees.isEmpty()) {
-                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批部门无负责人: " + deptId);
-            }
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":DEPT:" + deptId;
+        for (LocalUnitApprovalGroup group : groups) {
             total +=
                     createTasksWithGroupKey(
                             instanceId,
                             ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
-                            key,
-                            assignees,
-                            initialStatus);
-        }
-        for (Long groupId : groupIds) {
-            List<MpStaffGroupMember> members =
-                    mpStaffGroupMemberMapper.selectList(
-                            new LambdaQueryWrapper<MpStaffGroupMember>()
-                                    .eq(MpStaffGroupMember::getDelFlag, 0)
-                                    .eq(MpStaffGroupMember::getGroupId, groupId)
-                                    .orderByAsc(MpStaffGroupMember::getId));
-            LinkedHashSet<Long> mids = new LinkedHashSet<>();
-            for (MpStaffGroupMember m : members) {
-                if (m.getMpUserId() != null && m.getMpUserId() > 0) {
-                    mids.add(m.getMpUserId());
-                }
-            }
-            List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
-            if (assignees.isEmpty()) {
-                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批分组无有效成员: " + groupId);
-            }
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":GROUP:" + groupId;
-            total +=
-                    createTasksWithGroupKey(
-                            instanceId,
-                            ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
-                            key,
-                            assignees,
-                            initialStatus);
-        }
-        for (Map.Entry<String, List<Long>> e : tempGroups.entrySet()) {
-            List<Long> assignees = filterActiveEmployeeIds(e.getValue());
-            if (assignees.isEmpty()) {
-                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "临时审批组无有效成员");
-            }
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":TMP:" + e.getKey();
-            total +=
-                    createTasksWithGroupKey(
-                            instanceId,
-                            ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
-                            key,
-                            assignees,
+                            buildLocalUnitGroupKey(group.unitId()),
+                            group.assigneeIds(),
                             initialStatus);
         }
         if (total <= 0) {
@@ -617,6 +455,125 @@
                     "节点无审批人: " + resolveNodeName(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER));
         }
         return total;
+    }
+
+    private List<LocalUnitApprovalGroup> resolveLocalUnitApprovalGroups(
+            AppointmentOrder order, boolean strict) {
+        List<Long> localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds());
+        if (localUnitIds.isEmpty()) {
+            return List.of();
+        }
+        List<MpPassLocalUnit> units =
+                mpPassLocalUnitMapper.selectList(
+                        new LambdaQueryWrapper<MpPassLocalUnit>()
+                                .eq(MpPassLocalUnit::getDelFlag, 0)
+                                .in(MpPassLocalUnit::getId, localUnitIds));
+        Map<Long, MpPassLocalUnit> unitMap =
+                units.stream()
+                        .collect(
+                                java.util.stream.Collectors.toMap(
+                                        MpPassLocalUnit::getId, x -> x, (a, b) -> a));
+        if (strict && unitMap.size() != localUnitIds.size()) {
+            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
+        }
+
+        List<LocalUnitApprovalGroup> groups = new ArrayList<>();
+        for (Long unitId : localUnitIds) {
+            MpPassLocalUnit unit = unitMap.get(unitId);
+            if (unit == null) {
+                if (strict) {
+                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
+                }
+                continue;
+            }
+            List<Long> assignees = resolveLocalUnitAssignees(unitId, unit, strict);
+            if (assignees.isEmpty()) {
+                continue;
+            }
+            groups.add(new LocalUnitApprovalGroup(unitId, assignees));
+        }
+        return groups;
+    }
+
+    private List<Long> resolveLocalUnitAssignees(Long unitId, MpPassLocalUnit unit, boolean strict) {
+        Integer approverType = unit.getApproverType();
+        if (approverType == null) {
+            if (strict) {
+                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型缺失: " + unitId);
+            }
+            return List.of();
+        }
+        if (approverType == LocalUnitApproverType.DEPT) {
+            List<Long> deptIds = parseLongJsonArray(unit.getApproverDeptIds());
+            if (deptIds.isEmpty()) {
+                if (strict) {
+                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批部门未配置: " + unitId);
+                }
+                return List.of();
+            }
+            LinkedHashSet<Long> assignees = new LinkedHashSet<>();
+            for (Long deptId : deptIds) {
+                List<Long> deptApprovers = listAllActiveEmployeeIdsByDept(deptId);
+                if (strict && deptApprovers.isEmpty()) {
+                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批部门无可用审批人: " + deptId);
+                }
+                assignees.addAll(deptApprovers);
+            }
+            return assignees.stream().toList();
+        }
+        if (approverType == LocalUnitApproverType.STAFF_GROUP) {
+            Long groupId = unit.getApproverGroupId();
+            if (groupId == null || groupId <= 0) {
+                if (strict) {
+                    throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批分组未配置: " + unitId);
+                }
+                return List.of();
+            }
+            List<MpStaffGroupMember> members =
+                    mpStaffGroupMemberMapper.selectList(
+                            new LambdaQueryWrapper<MpStaffGroupMember>()
+                                    .eq(MpStaffGroupMember::getDelFlag, 0)
+                                    .eq(MpStaffGroupMember::getGroupId, groupId)
+                                    .orderByAsc(MpStaffGroupMember::getId));
+            LinkedHashSet<Long> mids = new LinkedHashSet<>();
+            for (MpStaffGroupMember member : members) {
+                if (member.getMpUserId() != null && member.getMpUserId() > 0) {
+                    mids.add(member.getMpUserId());
+                }
+            }
+            List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
+            if (strict && assignees.isEmpty()) {
+                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批分组无有效成员: " + groupId);
+            }
+            return assignees;
+        }
+        if (approverType == LocalUnitApproverType.STAFF) {
+            List<MpPassLocalUnitMember> members =
+                    mpPassLocalUnitMemberMapper.selectList(
+                            new LambdaQueryWrapper<MpPassLocalUnitMember>()
+                                    .eq(MpPassLocalUnitMember::getDelFlag, 0)
+                                    .eq(MpPassLocalUnitMember::getUnitId, unitId)
+                                    .orderByAsc(MpPassLocalUnitMember::getId));
+            LinkedHashSet<Long> mids = new LinkedHashSet<>();
+            for (MpPassLocalUnitMember member : members) {
+                if (member.getMpUserId() != null && member.getMpUserId() > 0) {
+                    mids.add(member.getMpUserId());
+                }
+            }
+            List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
+            if (strict && assignees.isEmpty()) {
+                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批人员未配置: " + unitId);
+            }
+            return assignees;
+        }
+        if (strict) {
+            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型无效: " + unitId);
+        }
+        return List.of();
+    }
+
+    private String buildLocalUnitGroupKey(Long unitId) {
+        return ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + LOCAL_UNIT_GROUP_TOKEN + unitId;
     }
 
     private String resolveNodeName(String nodeCode) {
@@ -743,11 +700,5 @@
         return out;
     }
 
-    private String buildTempGroupSign(List<Long> memberIds) {
-        String joined = memberIds.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining("-"));
-        if (joined.length() <= 40) {
-            return joined;
-        }
-        return Integer.toHexString(joined.hashCode());
-    }
+    private record LocalUnitApprovalGroup(Long unitId, List<Long> assigneeIds) {}
 }

--
Gitblit v1.7.1