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.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.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 final BizApprovalNodeConfigMapper bizApprovalNodeConfigMapper;
|
private final ApprovalInstanceMapper approvalInstanceMapper;
|
private final ApprovalTaskMapper approvalTaskMapper;
|
private final MpUserMapper mpUserMapper;
|
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 listEmployeeIdsByDeptAndLeaderFlag(deptId, true);
|
}
|
if (ApprovalNodeCode.LEVEL4_VISIT_DEPT.equals(nodeCode)) {
|
Long competentDeptId = getVisitCompetentDeptId();
|
return listEmployeeIdsByDeptAndLeaderFlag(competentDeptId, false);
|
}
|
if (ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER.equals(nodeCode)) {
|
Long competentDeptId = getVisitCompetentDeptId();
|
return listEmployeeIdsByDeptAndLeaderFlag(competentDeptId, true);
|
}
|
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> listEmployeeIdsByDeptAndLeaderFlag(Long deptId, boolean leaderFlag) {
|
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)
|
.eq(MpUser::getDeptLeaderFlag, leaderFlag));
|
return users.stream().map(MpUser::getId).filter(Objects::nonNull).toList();
|
}
|
|
@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> 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.REJECTED);
|
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<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);
|
}
|
}
|
|
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);
|
}
|
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, "节点无审批人: " + 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,
|
"节点无审批人: " + 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);
|
}
|
|
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>()
|
.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,
|
initialStatus);
|
}
|
if (total <= 0) {
|
throw new BizException(
|
BizErrorCode.CONFIG_NOT_FOUND,
|
"节点无审批人: " + ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER);
|
}
|
return total;
|
}
|
|
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 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());
|
}
|
}
|