feat: 支持审批步骤绑定部门与多对象

- 新增审批对象关联表和存量数据迁移

- 支持用户、角色、部门多选及办理权限匹配

- 完善部门状态与批量管理交互
This commit is contained in:
2026-07-30 15:13:11 +08:00
parent 864cea6135
commit 57bd7b5d06
29 changed files with 3694 additions and 365 deletions

View File

@@ -1,27 +1,38 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.approval.entity.ApprovalFlowScope;
import tech.easyflow.approval.entity.ApprovalFlowStepAssignee;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalScopeType;
import tech.easyflow.approval.mapper.ApprovalFlowScopeMapper;
import tech.easyflow.approval.mapper.ApprovalFlowStepAssigneeMapper;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
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 tech.easyflow.common.satoken.util.SaTokenUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 部门表 控制层。
@@ -33,18 +44,47 @@ import java.util.List;
@RequestMapping("/api/v1/sysDept")
public class SysDeptController extends BaseCurdController<SysDeptService, SysDept> {
@Resource
private SysAccountService sysAccountService;
private final SysAccountService sysAccountService;
private final ApprovalFlowStepAssigneeMapper approvalFlowStepAssigneeMapper;
private final ApprovalFlowScopeMapper approvalFlowScopeMapper;
public SysDeptController(SysDeptService service) {
/**
* 创建部门管理控制器。
*
* @param service 部门服务
* @param sysAccountService 用户服务
* @param approvalFlowStepAssigneeMapper 审批步骤对象 Mapper
* @param approvalFlowScopeMapper 审批范围 Mapper
*/
public SysDeptController(SysDeptService service,
SysAccountService sysAccountService,
ApprovalFlowStepAssigneeMapper approvalFlowStepAssigneeMapper,
ApprovalFlowScopeMapper approvalFlowScopeMapper) {
super(service);
this.sysAccountService = sysAccountService;
this.approvalFlowStepAssigneeMapper = approvalFlowStepAssigneeMapper;
this.approvalFlowScopeMapper = approvalFlowScopeMapper;
}
/**
* 获取部门列表默认排序规则。
*
* @return 默认排序表达式
*/
@Override
protected String getDefaultOrderBy() {
return "sort_no asc";
}
/**
* 查询部门列表并组装为树形结构。
*
* @param entity 查询条件
* @param asTree 是否返回树形结构
* @param sortKey 排序字段
* @param sortType 排序方向
* @return 部门树
*/
@Override
@GetMapping("list")
public Result<List<SysDept>> list(SysDept entity, Boolean asTree, String sortKey, String sortType) {
@@ -54,9 +94,64 @@ public class SysDeptController extends BaseCurdController<SysDeptService, SysDep
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
}
/**
* 批量修改部门状态。
*
* @param ids 部门主键集合
* @param status 目标状态
* @return 实际更新数量
*/
@PostMapping("changeStatusBatch")
@SaCheckPermission("/api/v1/sysDept/save")
@Transactional(rollbackFor = Exception.class)
public Result<Integer> changeStatusBatch(
@JsonBody(value = "ids", required = true) List<BigInteger> ids,
@JsonBody(value = "status", required = true) Integer status) {
Set<BigInteger> uniqueIds = normalizeIds(ids);
if (uniqueIds.isEmpty()) {
return Result.fail("请选择需要操作的部门", null);
}
if (!EnumDataStatus.AVAILABLE.getCode().equals(status)
&& !EnumDataStatus.UNAVAILABLE.getCode().equals(status)) {
return Result.fail("部门状态不合法", null);
}
List<SysDept> records = service.listByIds(uniqueIds);
if (records.size() != uniqueIds.size()) {
return Result.fail("部分部门不存在或已删除,请刷新后重试", null);
}
if (EnumDataStatus.UNAVAILABLE.getCode().equals(status) && containsRootDept(records)) {
return Result.fail("根部门不能禁用", null);
}
if (EnumDataStatus.UNAVAILABLE.getCode().equals(status)
&& isUsedByApprovalFlow(uniqueIds)) {
return Result.fail("所选部门已被审批流程使用,不能禁用", null);
}
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
SysDept update = new SysDept();
update.setStatus(status);
update.setModified(new Date());
update.setModifiedBy(loginUser.getId());
QueryWrapper updateWrapper = QueryWrapper.create();
updateWrapper.in(SysDept::getId, uniqueIds);
int updated = service.getMapper().updateByQuery(update, updateWrapper);
if (updated <= 0) {
return Result.fail("部门状态修改失败", null);
}
return Result.ok(updated);
}
/**
* {@inheritDoc}
*/
@Override
protected Result onSaveOrUpdateBefore(SysDept entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave && entity.getStatus() == null) {
entity.setStatus(EnumDataStatus.AVAILABLE.getCode());
}
BigInteger parentId = entity.getParentId();
if (parentId.equals(BigInteger.ZERO)) {
entity.setAncestors(parentId.toString());
@@ -65,7 +160,7 @@ public class SysDeptController extends BaseCurdController<SysDeptService, SysDep
entity.setAncestors(parent.getAncestors() + "," + parentId);
}
if (isSave) {
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
@@ -73,20 +168,87 @@ public class SysDeptController extends BaseCurdController<SysDeptService, SysDep
return null;
}
/**
* {@inheritDoc}
*/
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
List<SysDept> records = service.listByIds(ids);
for (SysDept dept : records) {
if (Constants.ROOT_DEPT.equals(dept.getDeptCode())) {
return Result.fail(1, "无法删除根部门");
}
if (records.size() != ids.size()) {
return Result.fail(1, "部分部门不存在或已删除,请刷新后重试");
}
QueryWrapper w = QueryWrapper.create();
w.in(SysAccount::getDeptId, ids);
long count = sysAccountService.count(w);
if (containsRootDept(records)) {
return Result.fail(1, "无法删除根部门");
}
QueryWrapper childQuery = QueryWrapper.create();
childQuery.in(SysDept::getParentId, ids);
childQuery.notIn(SysDept::getId, ids);
if (service.count(childQuery) > 0) {
return Result.fail(1, "所选部门包含未选中的下级部门,不能删除");
}
if (isUsedByApprovalFlow(ids)) {
return Result.fail(1, "所选部门已被审批流程使用,请先调整审批配置");
}
QueryWrapper accountQuery = QueryWrapper.create();
accountQuery.in(SysAccount::getDeptId, ids);
long count = sysAccountService.count(accountQuery);
if (count > 0) {
return Result.fail(1, "部门下有员工,不能删除");
return Result.fail(1, "所选部门下有员工,不能删除");
}
return super.onRemoveBefore(ids);
}
}
/**
* 去重并过滤无效部门主键。
*
* @param ids 原始部门主键集合
* @return 有效且去重后的主键集合
*/
private Set<BigInteger> normalizeIds(Collection<BigInteger> ids) {
Set<BigInteger> uniqueIds = new LinkedHashSet<>();
if (ids == null) {
return uniqueIds;
}
for (BigInteger id : ids) {
if (id != null) {
uniqueIds.add(id);
}
}
return uniqueIds;
}
/**
* 判断部门集合中是否包含根部门。
*
* @param records 部门集合
* @return 包含根部门时返回 {@code true}
*/
private boolean containsRootDept(Collection<SysDept> records) {
return records.stream()
.anyMatch(dept -> Constants.ROOT_DEPT.equals(dept.getDeptCode()));
}
/**
* 判断部门是否仍被审批步骤或审批范围引用。
*
* @param ids 部门主键集合
* @return 存在审批流程引用时返回 {@code true}
*/
private boolean isUsedByApprovalFlow(Collection<? extends Serializable> ids) {
QueryWrapper assigneeQuery = QueryWrapper.create();
assigneeQuery.eq(
ApprovalFlowStepAssignee::getAssigneeType,
ApprovalAssigneeType.DEPT.getCode());
assigneeQuery.in(ApprovalFlowStepAssignee::getTargetId, ids);
if (approvalFlowStepAssigneeMapper.selectCountByQuery(assigneeQuery) > 0) {
return true;
}
QueryWrapper scopeQuery = QueryWrapper.create();
scopeQuery.eq(ApprovalFlowScope::getScopeType, ApprovalScopeType.DEPT.getCode());
scopeQuery.in(ApprovalFlowScope::getScopeValue, ids);
return approvalFlowScopeMapper.selectCountByQuery(scopeQuery) > 0;
}
}