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 |  357 ++++++++++++++++++++++++++++------------------------------
 1 files changed, 172 insertions(+), 185 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 1b1bba9..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,6 +275,26 @@
         inst.setStatus(ApprovalInstanceStatus.REJECTED);
         inst.setFinishedAt(LocalDateTime.now());
         approvalInstanceMapper.updateById(inst);
+        List<ApprovalTask> remainingTasks =
+                approvalTaskMapper.selectList(
+                        new LambdaQueryWrapper<ApprovalTask>()
+                                .eq(ApprovalTask::getInstanceId, inst.getId())
+                                .in(
+                                        ApprovalTask::getTaskStatus,
+                                        ApprovalTaskStatus.PENDING,
+                                        ApprovalTaskStatus.WAITING));
+        for (ApprovalTask t : remainingTasks) {
+            t.setTaskStatus(ApprovalTaskStatus.REJECTED);
+            t.setComment(null);
+            approvalTaskMapper.updateById(t);
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelInstance(ApprovalInstance inst, String reason) {
+        inst.setStatus(ApprovalInstanceStatus.CANCELLED);
+        inst.setFinishedAt(LocalDateTime.now());
+        approvalInstanceMapper.updateById(inst);
         List<ApprovalTask> pending =
                 approvalTaskMapper.selectList(
                         new LambdaQueryWrapper<ApprovalTask>()
@@ -261,7 +304,7 @@
                                         ApprovalTaskStatus.PENDING,
                                         ApprovalTaskStatus.WAITING));
         for (ApprovalTask t : pending) {
-            t.setTaskStatus(ApprovalTaskStatus.REJECTED);
+            t.setTaskStatus(ApprovalTaskStatus.CANCELLED);
             t.setComment(reason);
             approvalTaskMapper.updateById(t);
         }
@@ -360,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;
     }
@@ -457,7 +422,9 @@
         List<Long> assignees = resolveAssignees(nodeCode, order);
         int created = createTasksWithGroupKey(inst.getId(), nodeCode, nodeCode, assignees, initialStatus);
         if (created <= 0) {
-            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "节点无审批人: " + nodeCode);
+            throw new BizException(
+                    BizErrorCode.CONFIG_NOT_FOUND,
+                    "节点无审批人: " + resolveNodeName(nodeCode));
         }
         return created;
     }
@@ -468,88 +435,100 @@
         if (!Boolean.TRUE.equals(order.getInvolveOtherLocalUnit()) || localUnitIds.isEmpty()) {
             throw new BizException(
                     BizErrorCode.CONFIG_NOT_FOUND,
-                    "节点无审批人: " + ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER);
+                    "节点无审批人: " + resolveNodeName(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER));
         }
 
+        List<LocalUnitApprovalGroup> groups = resolveLocalUnitApprovalGroups(order, true);
+        int total = 0;
+        for (LocalUnitApprovalGroup group : groups) {
+            total +=
+                    createTasksWithGroupKey(
+                            instanceId,
+                            ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
+                            buildLocalUnitGroupKey(group.unitId()),
+                            group.assigneeIds(),
+                            initialStatus);
+        }
+        if (total <= 0) {
+            throw new BizException(
+                    BizErrorCode.CONFIG_NOT_FOUND,
+                    "节点无审批人: " + 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));
-        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));
+        if (strict && unitMap.size() != localUnitIds.size()) {
+            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位配置缺失,无法生成审批人");
+        }
 
-        LinkedHashSet<Long> deptIds = new LinkedHashSet<>();
-        LinkedHashSet<Long> groupIds = new LinkedHashSet<>();
-        LinkedHashMap<String, List<Long>> tempGroups = new LinkedHashMap<>();
-
+        List<LocalUnitApprovalGroup> groups = new ArrayList<>();
         for (Long unitId : localUnitIds) {
             MpPassLocalUnit unit = unitMap.get(unitId);
-            if (unit == null || unit.getApproverType() == null) {
+            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);
             }
-            if (unit.getApproverType() == LocalUnitApproverType.DEPT) {
-                List<Long> ids = parseLongJsonArray(unit.getApproverDeptIds());
-                if (ids.isEmpty()) {
+            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);
                 }
-                deptIds.addAll(ids);
-                continue;
+                return List.of();
             }
-            if (unit.getApproverType() == LocalUnitApproverType.STAFF_GROUP) {
-                Long gid = unit.getApproverGroupId();
-                if (gid == null || gid <= 0) {
+            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);
                 }
-                groupIds.add(gid);
-                continue;
+                return List.of();
             }
-            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);
-        }
-
-        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;
-            total +=
-                    createTasksWithGroupKey(
-                            instanceId,
-                            ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
-                            key,
-                            assignees,
-                            initialStatus);
-        }
-        for (Long groupId : groupIds) {
             List<MpStaffGroupMember> members =
                     mpStaffGroupMemberMapper.selectList(
                             new LambdaQueryWrapper<MpStaffGroupMember>()
@@ -557,44 +536,58 @@
                                     .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());
+            for (MpStaffGroupMember member : members) {
+                if (member.getMpUserId() != null && member.getMpUserId() > 0) {
+                    mids.add(member.getMpUserId());
                 }
             }
             List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
-            if (assignees.isEmpty()) {
+            if (strict && 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);
+            return assignees;
         }
-        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, "临时审批组无有效成员");
+        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());
+                }
             }
-            String key = ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + ":TMP:" + e.getKey();
-            total +=
-                    createTasksWithGroupKey(
-                            instanceId,
-                            ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER,
-                            key,
-                            assignees,
-                            initialStatus);
+            List<Long> assignees = filterActiveEmployeeIds(mids.stream().toList());
+            if (strict && assignees.isEmpty()) {
+                throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批人员未配置: " + unitId);
+            }
+            return assignees;
         }
-        if (total <= 0) {
-            throw new BizException(
-                    BizErrorCode.CONFIG_NOT_FOUND,
-                    "节点无审批人: " + ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER);
+        if (strict) {
+            throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批类型无效: " + unitId);
         }
-        return total;
+        return List.of();
+    }
+
+    private String buildLocalUnitGroupKey(Long unitId) {
+        return ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER + LOCAL_UNIT_GROUP_TOKEN + unitId;
+    }
+
+    private String resolveNodeName(String nodeCode) {
+        if (!StringUtils.hasText(nodeCode)) {
+            return "未知节点";
+        }
+        return switch (nodeCode) {
+            case ApprovalNodeCode.LEVEL1_HANDLER -> "经办人(一级)";
+            case ApprovalNodeCode.LEVEL2_DEPT_LEADER -> "部门负责人(二级)";
+            case ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER -> "属地管理部门领导(三级)";
+            case ApprovalNodeCode.LEVEL4_VISIT_DEPT -> "访客主管部门(四级)";
+            case ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER -> "访客主管部门领导(五级)";
+            default -> nodeCode;
+        };
     }
 
     private int createTasksWithGroupKey(
@@ -707,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