feat: 增加审批步骤发起人部门限制

- 增加配置期与提审期合法审批账号校验

- 冻结发起人部门并统一待办与办理权限判断

- 增加兼容迁移、前端联动和自动化测试
This commit is contained in:
2026-07-31 09:38:53 +08:00
parent 0b764b79de
commit 048aa9bc1e
19 changed files with 1089 additions and 18 deletions

View File

@@ -39,6 +39,12 @@ public class ApprovalFlowStepBase implements Serializable {
@Column(comment = "审批对象名称")
private String assigneeTargetName;
/**
* 是否限定发起人直属部门。
*/
@Column(comment = "是否限定发起人直属部门")
private Integer restrictToApplicantDept;
@Column(comment = "创建时间")
private Date created;
@@ -115,6 +121,14 @@ public class ApprovalFlowStepBase implements Serializable {
this.assigneeTargetName = assigneeTargetName;
}
public Integer getRestrictToApplicantDept() {
return restrictToApplicantDept;
}
public void setRestrictToApplicantDept(Integer restrictToApplicantDept) {
this.restrictToApplicantDept = restrictToApplicantDept;
}
public Date getCreated() {
return created;
}

View File

@@ -59,6 +59,18 @@ public class ApprovalInstanceBase implements Serializable {
@Column(comment = "申请人ID")
private BigInteger applicantId;
/**
* 提交时冻结的申请人直属部门 ID。
*/
@Column(comment = "申请人直属部门ID")
private BigInteger applicantDeptId;
/**
* 提交时冻结的申请人直属部门名称。
*/
@Column(comment = "申请人直属部门名称")
private String applicantDeptName;
@Column(comment = "提交时间")
private Date submittedAt;
@@ -181,6 +193,22 @@ public class ApprovalInstanceBase implements Serializable {
this.applicantId = applicantId;
}
public BigInteger getApplicantDeptId() {
return applicantDeptId;
}
public void setApplicantDeptId(BigInteger applicantDeptId) {
this.applicantDeptId = applicantDeptId;
}
public String getApplicantDeptName() {
return applicantDeptName;
}
public void setApplicantDeptName(String applicantDeptName) {
this.applicantDeptName = applicantDeptName;
}
public Date getSubmittedAt() {
return submittedAt;
}

View File

@@ -42,6 +42,12 @@ public class ApprovalTaskBase implements Serializable {
@Column(comment = "审批对象名称")
private String assigneeTargetName;
/**
* 任务要求的直属部门 ID为空表示不限制部门。
*/
@Column(comment = "限定直属部门ID")
private BigInteger requiredDeptId;
@Column(comment = "处理人ID")
private BigInteger actedBy;
@@ -135,6 +141,14 @@ public class ApprovalTaskBase implements Serializable {
this.assigneeTargetName = assigneeTargetName;
}
public BigInteger getRequiredDeptId() {
return requiredDeptId;
}
public void setRequiredDeptId(BigInteger requiredDeptId) {
this.requiredDeptId = requiredDeptId;
}
public BigInteger getActedBy() {
return actedBy;
}

View File

@@ -24,6 +24,11 @@ public class ApprovalFlowStepVo {
private List<ApprovalAssigneeTargetVo> assigneeTargets;
/**
* 是否限定发起人直属部门。
*/
private Integer restrictToApplicantDept;
public BigInteger getId() {
return id;
}
@@ -97,4 +102,22 @@ public class ApprovalFlowStepVo {
public void setAssigneeTargets(List<ApprovalAssigneeTargetVo> assigneeTargets) {
this.assigneeTargets = assigneeTargets;
}
/**
* 获取发起人部门限制标记。
*
* @return 1 表示限制0 表示不限制
*/
public Integer getRestrictToApplicantDept() {
return restrictToApplicantDept;
}
/**
* 设置发起人部门限制标记。
*
* @param restrictToApplicantDept 限制标记
*/
public void setRestrictToApplicantDept(Integer restrictToApplicantDept) {
this.restrictToApplicantDept = restrictToApplicantDept;
}
}

View File

@@ -26,6 +26,14 @@ public interface ApprovalAssigneeService {
*/
ApprovalFlowStepVo normalizeStepAssignee(ApprovalFlowStepVo step);
/**
* 校验受发起人部门限制的步骤至少存在一个合法审批账号。
*
* @param steps 步骤配置
* @param requiredDeptId 指定直属部门 ID为空时校验任一有效部门
*/
void validateRestrictedStepCandidates(List<ApprovalFlowStepVo> steps, BigInteger requiredDeptId);
/**
* 批量加载流程步骤的审批对象,关联记录缺失时兼容旧单值字段。
*

View File

@@ -21,8 +21,10 @@ import tech.easyflow.approval.service.ApprovalAssigneeService;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysAccountRole;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.service.SysAccountRoleService;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysDeptService;
import tech.easyflow.system.service.SysRoleService;
@@ -52,6 +54,9 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
@Resource
private SysAccountService sysAccountService;
@Resource
private SysAccountRoleService sysAccountRoleService;
@Resource
private SysDeptService sysDeptService;
@@ -72,7 +77,12 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
if (step == null) {
throw new BusinessException("审批步骤不能为空");
}
boolean restricted = Integer.valueOf(1).equals(step.getRestrictToApplicantDept());
step.setRestrictToApplicantDept(restricted ? 1 : 0);
ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(step.getAssigneeType());
if (restricted && ApprovalAssigneeType.DEPT == assigneeType) {
throw new BusinessException("限定发起人部门后,审批对象类型不能选择部门");
}
List<ApprovalAssigneeTargetVo> requestedTargets = step.getAssigneeTargets();
if (CollectionUtil.isEmpty(requestedTargets) && step.getAssigneeTargetId() != null) {
requestedTargets = List.of(legacyTarget(
@@ -105,6 +115,102 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
return step;
}
/**
* {@inheritDoc}
*/
@Override
public void validateRestrictedStepCandidates(List<ApprovalFlowStepVo> steps, BigInteger requiredDeptId) {
List<ApprovalFlowStepVo> restrictedSteps = safeList(steps).stream()
.filter(Objects::nonNull)
.filter(step -> Integer.valueOf(1).equals(step.getRestrictToApplicantDept()))
.collect(Collectors.toList());
if (restrictedSteps.isEmpty()) {
return;
}
if (requiredDeptId != null) {
requireAvailableDepartment(requiredDeptId, "发起人部门不存在或未启用");
}
Set<BigInteger> directAccountIds = new LinkedHashSet<>();
Set<BigInteger> roleIds = new LinkedHashSet<>();
for (ApprovalFlowStepVo step : restrictedSteps) {
ApprovalAssigneeType type = ApprovalAssigneeType.from(step.getAssigneeType());
if (ApprovalAssigneeType.DEPT == type) {
throw new BusinessException("限定发起人部门后,审批对象类型不能选择部门");
}
Set<BigInteger> targetIds = stepTargetIds(step);
if (ApprovalAssigneeType.USER == type) {
directAccountIds.addAll(targetIds);
} else {
roleIds.addAll(targetIds);
}
}
Set<BigInteger> availableRoleIds = roleIds.isEmpty() ? Set.of() : safeList(
sysRoleService.list(QueryWrapper.create()
.in(SysRole::getId, roleIds)
.eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())))
.stream()
.map(SysRole::getId)
.collect(Collectors.toSet());
List<SysAccountRole> roleRelations = availableRoleIds.isEmpty() ? List.of() : safeList(
sysAccountRoleService.list(QueryWrapper.create()
.in(SysAccountRole::getRoleId, availableRoleIds)));
Set<BigInteger> candidateAccountIds = new LinkedHashSet<>(directAccountIds);
roleRelations.stream()
.map(SysAccountRole::getAccountId)
.filter(Objects::nonNull)
.forEach(candidateAccountIds::add);
QueryWrapper accountQuery = QueryWrapper.create()
.in(SysAccount::getId, candidateAccountIds)
.eq(SysAccount::getStatus, EnumDataStatus.AVAILABLE.getCode());
if (requiredDeptId != null) {
accountQuery.eq(SysAccount::getDeptId, requiredDeptId);
}
List<SysAccount> accounts = candidateAccountIds.isEmpty()
? List.of()
: safeList(sysAccountService.list(accountQuery));
Set<BigInteger> accountDeptIds = accounts.stream()
.map(SysAccount::getDeptId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
Set<BigInteger> availableDeptIds;
if (requiredDeptId != null) {
availableDeptIds = Set.of(requiredDeptId);
} else if (accountDeptIds.isEmpty()) {
availableDeptIds = Set.of();
} else {
availableDeptIds = safeList(sysDeptService.list(QueryWrapper.create()
.in(SysDept::getId, accountDeptIds)
.eq(SysDept::getStatus, EnumDataStatus.AVAILABLE.getCode())))
.stream()
.map(SysDept::getId)
.collect(Collectors.toSet());
}
Set<BigInteger> eligibleAccountIds = accounts.stream()
.filter(account -> account.getDeptId() != null
&& availableDeptIds.contains(account.getDeptId()))
.map(SysAccount::getId)
.collect(Collectors.toSet());
Map<BigInteger, Set<BigInteger>> roleAccountMap = roleRelations.stream()
.filter(relation -> eligibleAccountIds.contains(relation.getAccountId()))
.collect(Collectors.groupingBy(
SysAccountRole::getRoleId,
Collectors.mapping(SysAccountRole::getAccountId, Collectors.toSet())));
for (ApprovalFlowStepVo step : restrictedSteps) {
ApprovalAssigneeType type = ApprovalAssigneeType.from(step.getAssigneeType());
boolean hasCandidate = stepTargetIds(step).stream().anyMatch(targetId ->
ApprovalAssigneeType.USER == type
? eligibleAccountIds.contains(targetId)
: CollectionUtil.isNotEmpty(roleAccountMap.get(targetId)));
if (!hasCandidate) {
throw new BusinessException(stepCandidateError(step, requiredDeptId));
}
}
}
/**
* {@inheritDoc}
*/
@@ -291,6 +397,14 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
return false;
}
ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(task.getAssigneeType());
SysAccount operator = null;
if (task.getRequiredDeptId() != null) {
operator = sysAccountService.getById(operatorId);
if (ApprovalAssigneeType.DEPT == assigneeType
|| !Objects.equals(task.getRequiredDeptId(), resolveAvailableDirectDeptId(operator))) {
return false;
}
}
List<ApprovalAssigneeTargetVo> targets = loadTaskAssigneeTargets(List.of(task))
.getOrDefault(task.getId(), List.of());
if (ApprovalAssigneeType.USER == assigneeType) {
@@ -299,7 +413,9 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
if (ApprovalAssigneeType.ROLE == assigneeType) {
return roleIds != null && targets.stream().anyMatch(target -> roleIds.contains(target.getTargetId()));
}
SysAccount operator = sysAccountService.getById(operatorId);
if (operator == null) {
operator = sysAccountService.getById(operatorId);
}
return operator != null && matchesDepartment(targets, operator.getDeptId());
}
@@ -321,6 +437,7 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
}
SysAccount operator = sysAccountService.getById(operatorId);
BigInteger availableDirectDeptId = resolveAvailableDirectDeptId(operator);
Set<BigInteger> departmentIds = resolveDepartmentIds(operator == null ? null : operator.getDeptId());
if (CollectionUtil.isNotEmpty(departmentIds)) {
matchedTaskIds.addAll(loadMatchingRelationTaskIds(
@@ -328,6 +445,7 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
}
Set<BigInteger> result = loadPendingTasks(matchedTaskIds, instanceIds).stream()
.filter(task -> matchesRequiredDepartment(task, availableDirectDeptId))
.map(ApprovalTask::getInstanceId)
.collect(Collectors.toCollection(LinkedHashSet::new));
@@ -348,11 +466,42 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
.collect(Collectors.toSet());
legacyCandidates.stream()
.filter(task -> !relatedTaskIds.contains(task.getId()))
.filter(task -> matchesRequiredDepartment(task, availableDirectDeptId))
.map(ApprovalTask::getInstanceId)
.forEach(result::add);
return result;
}
/**
* 判断任务的冻结部门限制是否命中当前账号直属部门。
*
* @param task 审批任务
* @param availableDirectDeptId 当前账号的有效直属部门 ID
* @return 是否命中
*/
private boolean matchesRequiredDepartment(ApprovalTask task, BigInteger availableDirectDeptId) {
return task.getRequiredDeptId() == null
|| Objects.equals(task.getRequiredDeptId(), availableDirectDeptId);
}
/**
* 获取账号当前有效的直属部门 ID。
*
* @param account 账号
* @return 有效直属部门 ID无有效部门时返回空
*/
private BigInteger resolveAvailableDirectDeptId(SysAccount account) {
if (account == null
|| !EnumDataStatus.AVAILABLE.getCode().equals(account.getStatus())
|| account.getDeptId() == null) {
return null;
}
SysDept dept = sysDeptService.getById(account.getDeptId());
return dept != null && EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())
? dept.getId()
: null;
}
/**
* 查询能命中当前主体的任务关联 ID。
*
@@ -488,6 +637,59 @@ public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
return currentDeptId == null ? Set.of() : sysDeptService.getSelfAndAncestorDeptIds(currentDeptId);
}
/**
* 校验部门存在且处于启用状态。
*
* @param deptId 部门 ID
* @param message 校验失败消息
* @return 有效部门
*/
private SysDept requireAvailableDepartment(BigInteger deptId, String message) {
SysDept dept = sysDeptService.getById(deptId);
if (dept == null || !EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())) {
throw new BusinessException(message);
}
return dept;
}
/**
* 提取步骤审批对象 ID。
*
* @param step 步骤
* @return 审批对象 ID 集合
*/
private Set<BigInteger> stepTargetIds(ApprovalFlowStepVo step) {
List<ApprovalAssigneeTargetVo> targets = step.getAssigneeTargets();
if (CollectionUtil.isEmpty(targets) && step.getAssigneeTargetId() != null) {
targets = List.of(legacyTarget(
step.getAssigneeTargetId(),
step.getAssigneeTargetCode(),
step.getAssigneeTargetName()));
}
return safeList(targets).stream()
.filter(Objects::nonNull)
.map(ApprovalAssigneeTargetVo::getTargetId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* 组装受限步骤无候选人的业务提示。
*
* @param step 步骤
* @param requiredDeptId 指定部门 ID
* @return 业务提示
*/
private String stepCandidateError(ApprovalFlowStepVo step, BigInteger requiredDeptId) {
String stepLabel = step.getStepNo() == null ? "审批步骤" : "" + step.getStepNo() + "";
if (StringUtils.hasText(step.getStepName())) {
stepLabel += "" + step.getStepName().trim() + "";
}
return requiredDeptId == null
? stepLabel + "没有可用审批账号,请检查角色成员及部门归属"
: stepLabel + "在发起人部门内没有可用审批账号";
}
/**
* 解析并校验单个审批对象。
*

View File

@@ -295,12 +295,15 @@ public class ApprovalFlowServiceImpl extends ServiceImpl<ApprovalFlowMapper, App
normalized.setAssigneeType(item.getAssigneeType());
normalized.setAssigneeTargetId(item.getAssigneeTargetId());
normalized.setAssigneeTargets(item.getAssigneeTargets());
normalized.setRestrictToApplicantDept(
Integer.valueOf(1).equals(item.getRestrictToApplicantDept()) ? 1 : 0);
approvalAssigneeService.normalizeStepAssignee(normalized);
result.add(normalized);
}
if (result.isEmpty()) {
throw new BusinessException("审批步骤不能为空");
}
approvalAssigneeService.validateRestrictedStepCandidates(result, null);
return result;
}
@@ -337,6 +340,7 @@ public class ApprovalFlowServiceImpl extends ServiceImpl<ApprovalFlowMapper, App
step.setAssigneeTargetId(stepVo.getAssigneeTargetId());
step.setAssigneeTargetCode(stepVo.getAssigneeTargetCode());
step.setAssigneeTargetName(stepVo.getAssigneeTargetName());
step.setRestrictToApplicantDept(stepVo.getRestrictToApplicantDept());
step.setCreated(now);
step.setCreatedBy(operatorId);
step.setModified(now);
@@ -394,6 +398,8 @@ public class ApprovalFlowServiceImpl extends ServiceImpl<ApprovalFlowMapper, App
stepVo.setAssigneeTargetCode(item.getAssigneeTargetCode());
stepVo.setAssigneeTargetName(item.getAssigneeTargetName());
stepVo.setAssigneeTargets(targetMap.getOrDefault(item.getId(), List.of()));
stepVo.setRestrictToApplicantDept(
Integer.valueOf(1).equals(item.getRestrictToApplicantDept()) ? 1 : 0);
return stepVo;
})
.collect(Collectors.toList());
@@ -412,6 +418,7 @@ public class ApprovalFlowServiceImpl extends ServiceImpl<ApprovalFlowMapper, App
for (ApprovalFlowStepVo step : steps) {
approvalAssigneeService.normalizeStepAssignee(step);
}
approvalAssigneeService.validateRestrictedStepCandidates(steps, null);
}
private long countPendingInstances(BigInteger flowId) {

View File

@@ -28,8 +28,11 @@ import tech.easyflow.approval.mapper.ApprovalLogMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalInstanceService;
import tech.easyflow.approval.service.ApprovalMatchService;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysDeptService;
import javax.annotation.Resource;
import java.math.BigInteger;
@@ -69,6 +72,9 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
@Resource
private SysAccountService sysAccountService;
@Resource
private SysDeptService sysDeptService;
@Lazy
@Resource
private ApprovalActionFacade approvalActionFacade;
@@ -100,6 +106,11 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
List<ApprovalFlowStepVo> steps = new ArrayList<>(flow.getSteps());
steps.sort(Comparator.comparing(ApprovalFlowStepVo::getStepNo));
SysDept applicantDept = resolveApplicantDepartment(steps, applicant);
if (applicantDept != null) {
// 在创建任何实例或任务前确认每个受限步骤在发起人直属部门内都有候选账号。
approvalAssigneeService.validateRestrictedStepCandidates(steps, applicantDept.getId());
}
ApprovalFlowStepVo firstStep = steps.get(0);
Date now = new Date();
@@ -112,10 +123,14 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
instance.setActionType(flow.getActionType());
instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
instance.setCurrentStepNo(firstStep.getStepNo());
instance.setSnapshotJson(buildInstanceSnapshot(request, flow, steps));
instance.setSnapshotJson(buildInstanceSnapshot(request, flow, steps, applicantDept));
instance.setSummary(request.getSummary());
instance.setApplicationReason(request.getApplicationReason());
instance.setApplicantId(request.getApplicantId());
if (applicantDept != null) {
instance.setApplicantDeptId(applicantDept.getId());
instance.setApplicantDeptName(applicantDept.getDeptName());
}
instance.setSubmittedAt(now);
instance.setCreated(now);
instance.setCreatedBy(request.getApplicantId());
@@ -123,7 +138,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
instance.setModifiedBy(request.getApplicantId());
approvalInstanceMapper.insert(instance);
createTask(instance.getId(), firstStep, request.getApplicantId(), now);
createTask(instance.getId(), firstStep, instance.getApplicantDeptId(), request.getApplicantId(), now);
Map<String, Object> submittedPayload = new LinkedHashMap<>();
submittedPayload.put("flowId", flow.getId());
submittedPayload.put("flowVersion", flow.getVersion());
@@ -150,16 +165,24 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
List<ApprovalFlowStepVo> steps = resolveFrozenSteps(instance);
Date now = new Date();
finishTask(currentTask, ApprovalTaskStatus.APPROVED.getCode(), comment, operatorId, now);
int currentIndex = findCurrentStepIndex(steps, instance.getCurrentStepNo());
ApprovalFlowStepVo nextStep = currentIndex == steps.size() - 1 ? null : steps.get(currentIndex + 1);
if (nextStep != null && Integer.valueOf(1).equals(nextStep.getRestrictToApplicantDept())) {
if (instance.getApplicantDeptId() == null) {
throw new BusinessException("审批实例缺少冻结的发起人部门");
}
// 先校验下一步候选人,校验失败时当前待办保持未处理。
approvalAssigneeService.validateRestrictedStepCandidates(
List.of(nextStep), instance.getApplicantDeptId());
}
finishTask(currentTask, ApprovalTaskStatus.APPROVED.getCode(), comment, operatorId, now);
if (currentIndex == steps.size() - 1) {
instance.setStatus(ApprovalInstanceStatus.APPROVED.getCode());
instance.setFinishedAt(now);
} else {
ApprovalFlowStepVo nextStep = steps.get(currentIndex + 1);
instance.setStatus(ApprovalInstanceStatus.PROCESSING.getCode());
instance.setCurrentStepNo(nextStep.getStepNo());
createTask(instance.getId(), nextStep, operatorId, now);
createTask(instance.getId(), nextStep, instance.getApplicantDeptId(), operatorId, now);
appendLog(instance.getId(), ApprovalEventType.STEP_CREATED.getCode(), operatorId,
buildStepCreatedPayload(nextStep), now);
}
@@ -252,7 +275,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
}
private Map<String, Object> buildInstanceSnapshot(ApprovalSubmitRequest request, ApprovalFlowDetailVo flow,
List<ApprovalFlowStepVo> steps) {
List<ApprovalFlowStepVo> steps, SysDept applicantDept) {
Map<String, Object> snapshot = new LinkedHashMap<>();
if (request.getSnapshotJson() != null) {
snapshot.putAll(request.getSnapshotJson());
@@ -261,6 +284,10 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
snapshot.put("deptId", request.getDeptId());
snapshot.put("flowId", flow.getId());
snapshot.put("flowVersion", flow.getVersion());
if (applicantDept != null) {
snapshot.put("applicantDeptId", applicantDept.getId());
snapshot.put("applicantDeptName", applicantDept.getDeptName());
}
if (request.getApplicationReason() != null) {
snapshot.put("applicationReason", request.getApplicationReason());
}
@@ -272,6 +299,8 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
map.put("assigneeTargetId", item.getAssigneeTargetId());
map.put("assigneeTargetCode", item.getAssigneeTargetCode());
map.put("assigneeTargetName", item.getAssigneeTargetName());
map.put("restrictToApplicantDept",
Integer.valueOf(1).equals(item.getRestrictToApplicantDept()) ? 1 : 0);
map.put("assigneeTargets", resolveStepTargets(item).stream().map(target -> {
Map<String, Object> targetMap = new LinkedHashMap<>();
targetMap.put("targetId", target.getTargetId());
@@ -324,6 +353,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
if (assigneeTargetName != null) {
stepVo.setAssigneeTargetName(String.valueOf(assigneeTargetName));
}
stepVo.setRestrictToApplicantDept(parseEnabledFlag(stepMap.get("restrictToApplicantDept")));
stepVo.setAssigneeTargets(parseAssigneeTargets(stepMap.get("assigneeTargets")));
if (stepVo.getAssigneeTargets().isEmpty() && stepVo.getAssigneeTargetId() != null) {
stepVo.setAssigneeTargets(List.of(legacyTarget(stepVo)));
@@ -338,7 +368,8 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
return result;
}
private void createTask(BigInteger instanceId, ApprovalFlowStepVo step, BigInteger operatorId, Date now) {
private void createTask(BigInteger instanceId, ApprovalFlowStepVo step, BigInteger applicantDeptId,
BigInteger operatorId, Date now) {
List<ApprovalAssigneeTargetVo> targets = resolveStepTargets(step);
if (CollectionUtil.isEmpty(targets)) {
throw new BusinessException("审批步骤缺少审批对象");
@@ -356,6 +387,12 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
task.setAssigneeTargetId(primaryTarget.getTargetId());
task.setAssigneeTargetCode(primaryTarget.getTargetCode());
task.setAssigneeTargetName(primaryTarget.getTargetName());
if (Integer.valueOf(1).equals(step.getRestrictToApplicantDept())) {
if (applicantDeptId == null) {
throw new BusinessException("审批任务缺少冻结的发起人部门");
}
task.setRequiredDeptId(applicantDeptId);
}
task.setCreated(now);
task.setCreatedBy(operatorId);
task.setModified(now);
@@ -536,9 +573,34 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
payload.put("assigneeTargetCode", step.getAssigneeTargetCode());
payload.put("assigneeTargetName", step.getAssigneeTargetName());
payload.put("assigneeTargets", resolveStepTargets(step));
payload.put("restrictToApplicantDept",
Integer.valueOf(1).equals(step.getRestrictToApplicantDept()) ? 1 : 0);
return payload;
}
/**
* 解析受限步骤提交时需要冻结的发起人直属部门。
*
* @param steps 流程步骤
* @param applicant 发起人账号
* @return 有受限步骤时返回有效部门,否则返回空
*/
private SysDept resolveApplicantDepartment(List<ApprovalFlowStepVo> steps, SysAccount applicant) {
boolean restricted = steps.stream()
.anyMatch(step -> Integer.valueOf(1).equals(step.getRestrictToApplicantDept()));
if (!restricted) {
return null;
}
if (applicant.getDeptId() == null) {
throw new BusinessException("发起人未归属部门,无法提交当前审批");
}
SysDept dept = sysDeptService.getById(applicant.getDeptId());
if (dept == null || !EnumDataStatus.AVAILABLE.getCode().equals(dept.getStatus())) {
throw new BusinessException("发起人部门不存在或未启用");
}
return dept;
}
/**
* 从冻结快照中解析审批对象列表。
*
@@ -589,6 +651,21 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
return null;
}
/**
* 解析快照中的开关标记,旧快照缺失时默认关闭。
*
* @param value 快照值
* @return 1 表示开启0 表示关闭
*/
private int parseEnabledFlag(Object value) {
return value instanceof Number number && number.intValue() == 1
|| Boolean.TRUE.equals(value)
|| "1".equals(value)
|| "true".equalsIgnoreCase(String.valueOf(value))
? 1
: 0;
}
/**
* 将旧单值步骤转换为单元素审批对象。
*