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<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;
|
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<String> resolveEnabledNodeChain(AppointmentOrder order) {
|
List<BizApprovalNodeConfig> configs =
|
bizApprovalNodeConfigMapper.selectList(
|
new LambdaQueryWrapper<BizApprovalNodeConfig>()
|
.eq(BizApprovalNodeConfig::getEnabled, 1)
|
.orderByAsc(BizApprovalNodeConfig::getSortOrder));
|
List<String> 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<String> 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<Long> assignees = resolveAssignees(nodeCode, order);
|
return createTasksWithGroupKey(inst.getId(), nodeCode, nodeCode, assignees, initialStatus);
|
}
|
|
public List<Long> 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<Long> 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<Long> leaderIds = parseDeptLeaderIds(dept);
|
if (leaderIds.isEmpty()) {
|
return List.of();
|
}
|
List<MpUser> leaders =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.eq(MpUser::getStatus, 1)
|
.eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
|
.in(MpUser::getId, leaderIds));
|
Map<Long, MpUser> leaderMap = new LinkedHashMap<>();
|
for (MpUser leader : leaders) {
|
leaderMap.put(leader.getId(), leader);
|
}
|
List<Long> result = new ArrayList<>();
|
for (Long id : leaderIds) {
|
if (leaderMap.containsKey(id)) {
|
result.add(id);
|
}
|
}
|
return result;
|
}
|
|
private List<Long> parseDeptLeaderIds(SysDept dept) {
|
List<Long> ids = new ArrayList<>();
|
Set<Long> 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<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) {
|
return listAllActiveEmployeeIdsByDept(deptId);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void markGroupSkippedExcept(ApprovalTask approvedTask) {
|
List<ApprovalTask> siblings =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.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<ApprovalTask>()
|
.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<String> 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<String> resolveAdvanceChain(ApprovalInstance inst, AppointmentOrder order) {
|
List<ApprovalTask> instanceTasks =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.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<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>()
|
.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<String> chain, AppointmentOrder order) {
|
for (String nodeCode : chain) {
|
List<Long> assignees = resolveAssignees(nodeCode, order);
|
if (assignees != null && !assignees.isEmpty()) {
|
return nodeCode;
|
}
|
}
|
return null;
|
}
|
|
private int activateWaitingTasks(Long instanceId, String nodeCode) {
|
List<ApprovalTask> waiting =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.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<ApprovalTask>()
|
.eq(ApprovalTask::getInstanceId, inst.getId())
|
.ne(ApprovalTask::getId, approvedTask.getId()));
|
|
List<String> 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<String> resolveEnabledNodeChainForPostHandler(AppointmentOrder order) {
|
List<BizApprovalNodeConfig> configs =
|
bizApprovalNodeConfigMapper.selectList(
|
new LambdaQueryWrapper<BizApprovalNodeConfig>()
|
.eq(BizApprovalNodeConfig::getEnabled, 1)
|
.orderByAsc(BizApprovalNodeConfig::getSortOrder));
|
List<String> chain = new ArrayList<>();
|
List<Long> 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<LocalUnitApprovalGroup> 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<Long> 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<Long> 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<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));
|
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) {
|
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<Long> assignees, String initialStatus) {
|
Set<Long> 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<ApprovalTask> approvedHistory =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.eq(ApprovalTask::getInstanceId, instanceId)
|
.eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.APPROVED)
|
.ne(ApprovalTask::getNodeCode, nodeCode));
|
if (approvedHistory.isEmpty()) {
|
return;
|
}
|
Set<Long> approvedAssigneeIds = new LinkedHashSet<>();
|
for (ApprovalTask t : approvedHistory) {
|
if (t.getAssigneeUserId() != null) {
|
approvedAssigneeIds.add(t.getAssigneeUserId());
|
}
|
}
|
if (approvedAssigneeIds.isEmpty()) {
|
return;
|
}
|
List<ApprovalTask> pending =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.eq(ApprovalTask::getInstanceId, instanceId)
|
.eq(ApprovalTask::getNodeCode, nodeCode)
|
.eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)
|
.orderByAsc(ApprovalTask::getId));
|
Set<String> 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<Long> parseLongJsonArray(String raw) {
|
if (!StringUtils.hasText(raw)) {
|
return List.of();
|
}
|
try {
|
List<Long> list = objectMapper.readValue(raw, LONG_LIST_TYPE);
|
if (list == null || list.isEmpty()) {
|
return List.of();
|
}
|
LinkedHashSet<Long> 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<Long> filterActiveEmployeeIds(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return List.of();
|
}
|
List<MpUser> users =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.eq(MpUser::getStatus, 1)
|
.eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
|
.in(MpUser::getId, ids));
|
Map<Long, MpUser> userMap =
|
users.stream().collect(java.util.stream.Collectors.toMap(MpUser::getId, x -> x, (a, b) -> a));
|
List<Long> out = new ArrayList<>();
|
for (Long id : ids) {
|
if (userMap.containsKey(id)) {
|
out.add(id);
|
}
|
}
|
return out;
|
}
|
|
private record LocalUnitApprovalGroup(Long unitId, List<Long> assigneeIds) {}
|
}
|