package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sinata.appointment.constants.ApprovalInstanceStatus; import com.sinata.appointment.constants.ApprovalNodeCode; import com.sinata.appointment.constants.ApprovalTaskStatus; import com.sinata.appointment.constants.LocalUnitApproverType; import com.sinata.appointment.constants.MiniUserType; import com.sinata.appointment.constants.SystemSettingKeys; import com.sinata.appointment.entity.AppointmentOrder; import com.sinata.appointment.entity.ApprovalInstance; import com.sinata.appointment.entity.ApprovalTask; import com.sinata.appointment.entity.BizApprovalNodeConfig; import com.sinata.appointment.entity.MpPassLocalUnit; import com.sinata.appointment.entity.MpPassLocalUnitMember; import com.sinata.appointment.entity.MpStaffGroupMember; import com.sinata.appointment.entity.MpUser; import com.sinata.appointment.entity.SysDept; import com.sinata.appointment.exception.BizException; import com.sinata.appointment.mapper.ApprovalInstanceMapper; import com.sinata.appointment.mapper.ApprovalTaskMapper; import com.sinata.appointment.mapper.BizApprovalNodeConfigMapper; import com.sinata.appointment.mapper.MpPassLocalUnitMapper; import com.sinata.appointment.mapper.MpPassLocalUnitMemberMapper; import com.sinata.appointment.mapper.MpStaffGroupMemberMapper; import com.sinata.appointment.mapper.MpUserMapper; import com.sinata.appointment.mapper.SysDeptMapper; import com.sinata.appointment.constants.BizErrorCode; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; /** * 审批引擎:节点链解析、任务创建、组内任一通过、拒绝终止。 */ @Service @RequiredArgsConstructor public class ApprovalEngineService { private static final TypeReference> 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; private final ApprovalTaskMapper approvalTaskMapper; private final MpUserMapper mpUserMapper; private final SysDeptMapper sysDeptMapper; private final MpPassLocalUnitMapper mpPassLocalUnitMapper; private final MpPassLocalUnitMemberMapper mpPassLocalUnitMemberMapper; private final MpStaffGroupMemberMapper mpStaffGroupMemberMapper; private final SysBizSettingCacheService sysBizSettingCacheService; private final ObjectMapper objectMapper; public List resolveEnabledNodeChain(AppointmentOrder order) { List configs = bizApprovalNodeConfigMapper.selectList( new LambdaQueryWrapper() .eq(BizApprovalNodeConfig::getEnabled, 1) .orderByAsc(BizApprovalNodeConfig::getSortOrder)); List chain = new ArrayList<>(); for (BizApprovalNodeConfig c : configs) { String nc = c.getNodeCode(); if (ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER.equals(nc) && !Boolean.TRUE.equals(order.getHolidayFlag())) { continue; } // 预约流程已不使用“是否涉及其他属地单位(key_area_flag)”字段,三级节点在预约阶段固定跳过。 if (ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(nc)) { continue; } chain.add(nc); } return chain; } @Transactional(rollbackFor = Exception.class) public ApprovalInstance startFlow(AppointmentOrder order) { List chain = resolveEnabledNodeChain(order); if (chain.isEmpty()) { return null; } String firstNode = resolveFirstNodeWithAssignees(chain, order); if (firstNode == null) { return null; } ApprovalInstance inst = new ApprovalInstance(); inst.setOrderId(order.getId()); inst.setCurrentNodeCode(firstNode); inst.setStatus(ApprovalInstanceStatus.IN_PROGRESS); approvalInstanceMapper.insert(inst); for (String nodeCode : chain) { String status = Objects.equals(nodeCode, firstNode) ? ApprovalTaskStatus.PENDING : ApprovalTaskStatus.WAITING; createTasksForNode(inst, order, nodeCode, status); } return inst; } public int createTasksForNode( ApprovalInstance inst, AppointmentOrder order, String nodeCode, String initialStatus) { List assignees = resolveAssignees(nodeCode, order); return createTasksWithGroupKey(inst.getId(), nodeCode, nodeCode, assignees, initialStatus); } public List resolveAssignees(String nodeCode, AppointmentOrder order) { if (ApprovalNodeCode.LEVEL1_HANDLER.equals(nodeCode)) { return order.getVisitedUserId() == null ? List.of() : List.of(order.getVisitedUserId()); } if (ApprovalNodeCode.LEVEL2_DEPT_LEADER.equals(nodeCode)) { Long deptId = resolveVisitedDeptId(order); return getDeptLeaderIds(deptId); } if (ApprovalNodeCode.LEVEL4_VISIT_DEPT.equals(nodeCode)) { Long competentDeptId = getVisitCompetentDeptId(); return listEmployeeIdsByDeptWithLeaderFallback(competentDeptId); } if (ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER.equals(nodeCode)) { Long competentDeptId = getVisitCompetentDeptId(); return getDeptLeaderIds(competentDeptId); } if (ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(nodeCode)) { return List.of(); } return List.of(); } private Long resolveVisitedDeptId(AppointmentOrder order) { if (order.getVisitedUserId() != null) { MpUser visited = mpUserMapper.selectById(order.getVisitedUserId()); if (visited != null && (visited.getDelFlag() == null || visited.getDelFlag() == 0) && MiniUserType.EMPLOYEE.equals(visited.getUserType()) && visited.getDeptId() != null && visited.getDeptId() > 0) { return visited.getDeptId(); } } return order.getVisitedDeptId(); } private Long getVisitCompetentDeptId() { String raw = sysBizSettingCacheService.getValue(SystemSettingKeys.VISIT_COMPETENT_DEPT_ID); if (raw == null || raw.trim().isEmpty()) { return null; } try { long id = Long.parseLong(raw.trim()); return id > 0 ? id : null; } catch (NumberFormatException ex) { return null; } } private List getDeptLeaderIds(Long deptId) { if (deptId == null || deptId <= 0) { return List.of(); } SysDept dept = sysDeptMapper.selectById(deptId); if (dept == null || dept.getDelFlag() != null && dept.getDelFlag() == 1) { return List.of(); } List leaderIds = parseDeptLeaderIds(dept); if (leaderIds.isEmpty()) { return List.of(); } List leaders = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .eq(MpUser::getStatus, 1) .eq(MpUser::getUserType, MiniUserType.EMPLOYEE) .in(MpUser::getId, leaderIds)); Map leaderMap = new LinkedHashMap<>(); for (MpUser leader : leaders) { leaderMap.put(leader.getId(), leader); } List result = new ArrayList<>(); for (Long id : leaderIds) { if (leaderMap.containsKey(id)) { result.add(id); } } return result; } private List parseDeptLeaderIds(SysDept dept) { List ids = new ArrayList<>(); Set seen = new LinkedHashSet<>(); if (StringUtils.hasText(dept.getLeaderIds())) { for (String part : dept.getLeaderIds().split(",")) { if (!StringUtils.hasText(part)) { continue; } try { Long id = Long.parseLong(part.trim()); if (id > 0 && seen.add(id)) { ids.add(id); } } catch (NumberFormatException ignored) { } } } return ids; } private List listAllActiveEmployeeIdsByDept(Long deptId) { if (deptId == null || deptId <= 0) { return List.of(); } List users = mpUserMapper.selectList( new LambdaQueryWrapper() .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 listEmployeeIdsByDeptWithLeaderFallback(Long deptId) { return listAllActiveEmployeeIdsByDept(deptId); } @Transactional(rollbackFor = Exception.class) public void markGroupSkippedExcept(ApprovalTask approvedTask) { List siblings = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, approvedTask.getInstanceId()) .eq(ApprovalTask::getNodeCode, approvedTask.getNodeCode()) .eq(ApprovalTask::getGroupKey, approvedTask.getGroupKey()) .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)); for (ApprovalTask s : siblings) { if (!Objects.equals(s.getId(), approvedTask.getId())) { s.setTaskStatus(ApprovalTaskStatus.SKIPPED); approvalTaskMapper.updateById(s); } } } public boolean isCurrentNodeComplete(ApprovalInstance inst) { long pending = approvalTaskMapper.selectCount( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, inst.getId()) .eq(ApprovalTask::getNodeCode, inst.getCurrentNodeCode()) .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)); return pending == 0; } @Transactional(rollbackFor = Exception.class) public void advanceOrComplete(ApprovalInstance inst, AppointmentOrder order) { List chain = resolveAdvanceChain(inst, order); int idx = chain.indexOf(inst.getCurrentNodeCode()); if (idx < 0) { inst.setStatus(ApprovalInstanceStatus.APPROVED); inst.setFinishedAt(LocalDateTime.now()); inst.setCurrentNodeCode(null); approvalInstanceMapper.updateById(inst); return; } for (int i = idx + 1; i < chain.size(); i++) { String next = chain.get(i); if (activateWaitingTasks(inst.getId(), next) > 0) { inst.setCurrentNodeCode(next); approvalInstanceMapper.updateById(inst); autoApproveCurrentNodeByHistory(inst.getId(), next); if (!isCurrentNodeComplete(inst)) { return; } } } inst.setStatus(ApprovalInstanceStatus.APPROVED); inst.setFinishedAt(LocalDateTime.now()); inst.setCurrentNodeCode(null); approvalInstanceMapper.updateById(inst); } private List resolveAdvanceChain(ApprovalInstance inst, AppointmentOrder order) { List instanceTasks = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getDelFlag, 0) .eq(ApprovalTask::getInstanceId, inst.getId()) .select(ApprovalTask::getNodeCode)); boolean containsLocalUnitNode = instanceTasks.stream() .map(ApprovalTask::getNodeCode) .anyMatch(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER::equals); return containsLocalUnitNode ? resolveEnabledNodeChainForPostHandler(order) : resolveEnabledNodeChain(order); } @Transactional(rollbackFor = Exception.class) public void rejectInstance(ApprovalInstance inst, String reason) { inst.setStatus(ApprovalInstanceStatus.REJECTED); inst.setFinishedAt(LocalDateTime.now()); approvalInstanceMapper.updateById(inst); List remainingTasks = approvalTaskMapper.selectList( new LambdaQueryWrapper() .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 pending = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, inst.getId()) .in( ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING, ApprovalTaskStatus.WAITING)); for (ApprovalTask t : pending) { t.setTaskStatus(ApprovalTaskStatus.CANCELLED); t.setComment(reason); approvalTaskMapper.updateById(t); } } private String resolveFirstNodeWithAssignees(List chain, AppointmentOrder order) { for (String nodeCode : chain) { List assignees = resolveAssignees(nodeCode, order); if (assignees != null && !assignees.isEmpty()) { return nodeCode; } } return null; } private int activateWaitingTasks(Long instanceId, String nodeCode) { List waiting = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, instanceId) .eq(ApprovalTask::getNodeCode, nodeCode) .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.WAITING)); for (ApprovalTask t : waiting) { t.setTaskStatus(ApprovalTaskStatus.PENDING); approvalTaskMapper.updateById(t); } return waiting.size(); } @Transactional(rollbackFor = Exception.class) public void rebuildAfterHandlerApproved( ApprovalTask approvedTask, ApprovalInstance inst, AppointmentOrder order) { approvalTaskMapper.delete( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, inst.getId()) .ne(ApprovalTask::getId, approvedTask.getId())); List chain = resolveEnabledNodeChainForPostHandler(order); int idx = chain.indexOf(ApprovalNodeCode.LEVEL1_HANDLER); int start = idx >= 0 ? idx + 1 : 0; String firstNode = null; for (int i = start; i < chain.size(); i++) { String nodeCode = chain.get(i); String status = firstNode == null ? ApprovalTaskStatus.PENDING : ApprovalTaskStatus.WAITING; int created; if (ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(nodeCode)) { created = createTasksForLocalUnitNodeStrict(inst.getId(), order, status); } else { created = createTasksForNodeStrict(inst, order, nodeCode, status); } if (created > 0 && firstNode == null) { firstNode = nodeCode; } } if (firstNode == null) { inst.setStatus(ApprovalInstanceStatus.APPROVED); inst.setFinishedAt(LocalDateTime.now()); inst.setCurrentNodeCode(null); approvalInstanceMapper.updateById(inst); return; } inst.setCurrentNodeCode(firstNode); inst.setStatus(ApprovalInstanceStatus.IN_PROGRESS); inst.setFinishedAt(null); approvalInstanceMapper.updateById(inst); autoApproveCurrentNodeByHistory(inst.getId(), firstNode); if (isCurrentNodeComplete(inst)) { advanceOrComplete(inst, order); } } private List resolveEnabledNodeChainForPostHandler(AppointmentOrder order) { List configs = bizApprovalNodeConfigMapper.selectList( new LambdaQueryWrapper() .eq(BizApprovalNodeConfig::getEnabled, 1) .orderByAsc(BizApprovalNodeConfig::getSortOrder)); List chain = new ArrayList<>(); List localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds()); for (BizApprovalNodeConfig c : configs) { String nc = c.getNodeCode(); if (ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER.equals(nc) && !Boolean.TRUE.equals(order.getHolidayFlag())) { continue; } if (ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(nc)) { if (!Boolean.TRUE.equals(order.getInvolveOtherLocalUnit()) || localUnitIds.isEmpty()) { continue; } } chain.add(nc); } return chain; } private int createTasksForLocalUnitNode(Long instanceId, AppointmentOrder order, String initialStatus) { List groups = resolveLocalUnitApprovalGroups(order, false); int total = 0; for (LocalUnitApprovalGroup group : groups) { total += createTasksWithGroupKey( instanceId, ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER, buildLocalUnitGroupKey(group.unitId()), group.assigneeIds(), initialStatus); } return total; } private int createTasksForNodeStrict( ApprovalInstance inst, AppointmentOrder order, String nodeCode, String initialStatus) { List assignees = resolveAssignees(nodeCode, order); int created = createTasksWithGroupKey(inst.getId(), nodeCode, nodeCode, assignees, initialStatus); if (created <= 0) { throw new BizException( BizErrorCode.CONFIG_NOT_FOUND, "节点无审批人: " + resolveNodeName(nodeCode)); } return created; } private int createTasksForLocalUnitNodeStrict( Long instanceId, AppointmentOrder order, String initialStatus) { List localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds()); if (!Boolean.TRUE.equals(order.getInvolveOtherLocalUnit()) || localUnitIds.isEmpty()) { throw new BizException( BizErrorCode.CONFIG_NOT_FOUND, "节点无审批人: " + resolveNodeName(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER)); } List 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 resolveLocalUnitApprovalGroups( AppointmentOrder order, boolean strict) { List localUnitIds = parseLongJsonArray(order.getPassLocalUnitIds()); if (localUnitIds.isEmpty()) { return List.of(); } List units = mpPassLocalUnitMapper.selectList( new LambdaQueryWrapper() .eq(MpPassLocalUnit::getDelFlag, 0) .in(MpPassLocalUnit::getId, localUnitIds)); Map 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 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 assignees = resolveLocalUnitAssignees(unitId, unit, strict); if (assignees.isEmpty()) { continue; } groups.add(new LocalUnitApprovalGroup(unitId, assignees)); } return groups; } private List 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 deptIds = parseLongJsonArray(unit.getApproverDeptIds()); if (deptIds.isEmpty()) { if (strict) { throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "属地单位审批部门未配置: " + unitId); } return List.of(); } LinkedHashSet assignees = new LinkedHashSet<>(); for (Long deptId : deptIds) { List 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 members = mpStaffGroupMemberMapper.selectList( new LambdaQueryWrapper() .eq(MpStaffGroupMember::getDelFlag, 0) .eq(MpStaffGroupMember::getGroupId, groupId) .orderByAsc(MpStaffGroupMember::getId)); LinkedHashSet mids = new LinkedHashSet<>(); for (MpStaffGroupMember member : members) { if (member.getMpUserId() != null && member.getMpUserId() > 0) { mids.add(member.getMpUserId()); } } List assignees = filterActiveEmployeeIds(mids.stream().toList()); if (strict && assignees.isEmpty()) { throw new BizException(BizErrorCode.CONFIG_NOT_FOUND, "审批分组无有效成员: " + groupId); } return assignees; } if (approverType == LocalUnitApproverType.STAFF) { List members = mpPassLocalUnitMemberMapper.selectList( new LambdaQueryWrapper() .eq(MpPassLocalUnitMember::getDelFlag, 0) .eq(MpPassLocalUnitMember::getUnitId, unitId) .orderByAsc(MpPassLocalUnitMember::getId)); LinkedHashSet mids = new LinkedHashSet<>(); for (MpPassLocalUnitMember member : members) { if (member.getMpUserId() != null && member.getMpUserId() > 0) { mids.add(member.getMpUserId()); } } List 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) { 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( Long instanceId, String nodeCode, String groupKey, List assignees, String initialStatus) { Set unique = new LinkedHashSet<>(assignees == null ? List.of() : assignees); unique.removeIf(x -> x == null || x <= 0); if (unique.isEmpty()) { return 0; } for (Long uid : unique) { ApprovalTask t = new ApprovalTask(); t.setInstanceId(instanceId); t.setNodeCode(nodeCode); t.setGroupKey(groupKey); t.setAssigneeUserId(uid); t.setTaskStatus(initialStatus); approvalTaskMapper.insert(t); } return unique.size(); } private void autoApproveCurrentNodeByHistory(Long instanceId, String nodeCode) { List approvedHistory = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, instanceId) .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.APPROVED) .ne(ApprovalTask::getNodeCode, nodeCode)); if (approvedHistory.isEmpty()) { return; } Set approvedAssigneeIds = new LinkedHashSet<>(); for (ApprovalTask t : approvedHistory) { if (t.getAssigneeUserId() != null) { approvedAssigneeIds.add(t.getAssigneeUserId()); } } if (approvedAssigneeIds.isEmpty()) { return; } List pending = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getInstanceId, instanceId) .eq(ApprovalTask::getNodeCode, nodeCode) .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING) .orderByAsc(ApprovalTask::getId)); Set handledGroupKeys = new LinkedHashSet<>(); for (ApprovalTask task : pending) { if (!approvedAssigneeIds.contains(task.getAssigneeUserId())) { continue; } String groupKey = StringUtils.hasText(task.getGroupKey()) ? task.getGroupKey() : ("TASK:" + task.getId()); if (!handledGroupKeys.add(groupKey)) { continue; } ApprovalTask latest = approvalTaskMapper.selectById(task.getId()); if (latest == null || !ApprovalTaskStatus.PENDING.equals(latest.getTaskStatus())) { continue; } latest.setTaskStatus(ApprovalTaskStatus.APPROVED); if (!StringUtils.hasText(latest.getComment())) { latest.setComment(AUTO_APPROVE_COMMENT); } approvalTaskMapper.updateById(latest); markGroupSkippedExcept(latest); } } private List parseLongJsonArray(String raw) { if (!StringUtils.hasText(raw)) { return List.of(); } try { List list = objectMapper.readValue(raw, LONG_LIST_TYPE); if (list == null || list.isEmpty()) { return List.of(); } LinkedHashSet set = new LinkedHashSet<>(); for (Long id : list) { if (id != null && id > 0) { set.add(id); } } return set.stream().toList(); } catch (Exception ex) { return List.of(); } } private List filterActiveEmployeeIds(List ids) { if (ids == null || ids.isEmpty()) { return List.of(); } List users = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .eq(MpUser::getStatus, 1) .eq(MpUser::getUserType, MiniUserType.EMPLOYEE) .in(MpUser::getId, ids)); Map userMap = users.stream().collect(java.util.stream.Collectors.toMap(MpUser::getId, x -> x, (a, b) -> a)); List out = new ArrayList<>(); for (Long id : ids) { if (userMap.containsKey(id)) { out.add(id); } } return out; } private record LocalUnitApprovalGroup(Long unitId, List assigneeIds) {} }