feat: 支持审批步骤绑定部门与多对象
- 新增审批对象关联表和存量数据迁移 - 支持用户、角色、部门多选及办理权限匹配 - 完善部门状态与批量管理交互
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
package tech.easyflow.approval.service.impl;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import tech.easyflow.approval.entity.ApprovalTask;
|
||||
import tech.easyflow.approval.entity.ApprovalTaskAssignee;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalAssigneeTargetVo;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||
import tech.easyflow.approval.enums.ApprovalAssigneeType;
|
||||
import tech.easyflow.approval.enums.ApprovalTaskStatus;
|
||||
import tech.easyflow.approval.mapper.ApprovalFlowStepAssigneeMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalTaskAssigneeMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysDept;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
import tech.easyflow.system.service.SysDeptService;
|
||||
import tech.easyflow.system.service.SysRoleService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link ApprovalAssigneeServiceImpl} 多审批对象语义测试。
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ApprovalAssigneeServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
@Mock
|
||||
private SysAccountService sysAccountService;
|
||||
|
||||
@Mock
|
||||
private SysDeptService sysDeptService;
|
||||
|
||||
@Mock
|
||||
private ApprovalTaskMapper approvalTaskMapper;
|
||||
|
||||
@Mock
|
||||
private ApprovalFlowStepAssigneeMapper approvalFlowStepAssigneeMapper;
|
||||
|
||||
@Mock
|
||||
private ApprovalTaskAssigneeMapper approvalTaskAssigneeMapper;
|
||||
|
||||
@InjectMocks
|
||||
private ApprovalAssigneeServiceImpl service;
|
||||
|
||||
/**
|
||||
* 验证同一步骤支持多个角色,并按 ID 去重、由服务端补齐名称。
|
||||
*/
|
||||
@Test
|
||||
public void normalizeStepAssigneeShouldResolveAndDeduplicateMultipleRoles() {
|
||||
SysRole reviewer = role(11, "reviewer", "审核员");
|
||||
SysRole owner = role(12, "owner", "负责人");
|
||||
when(sysRoleService.getById(reviewer.getId())).thenReturn(reviewer);
|
||||
when(sysRoleService.getById(owner.getId())).thenReturn(owner);
|
||||
|
||||
ApprovalFlowStepVo step = new ApprovalFlowStepVo();
|
||||
step.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
|
||||
step.setAssigneeTargets(List.of(
|
||||
target(reviewer.getId(), 0),
|
||||
target(owner.getId(), 0),
|
||||
target(reviewer.getId(), 0)));
|
||||
|
||||
ApprovalFlowStepVo normalized = service.normalizeStepAssignee(step);
|
||||
|
||||
assertEquals(2, normalized.getAssigneeTargets().size());
|
||||
assertEquals(reviewer.getId(), normalized.getAssigneeTargetId());
|
||||
assertEquals("审核员", normalized.getAssigneeTargetName());
|
||||
assertEquals("负责人", normalized.getAssigneeTargets().get(1).getTargetName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证部门审批可通过“包含子部门”命中下级部门成员。
|
||||
*/
|
||||
@Test
|
||||
public void canHandleTaskShouldMatchDescendantDepartment() {
|
||||
BigInteger operatorId = BigInteger.valueOf(7);
|
||||
BigInteger parentDeptId = BigInteger.valueOf(21);
|
||||
BigInteger currentDeptId = BigInteger.valueOf(22);
|
||||
ApprovalTask task = new ApprovalTask();
|
||||
task.setId(BigInteger.valueOf(101));
|
||||
task.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
|
||||
|
||||
ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
|
||||
relation.setTaskId(task.getId());
|
||||
relation.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
|
||||
relation.setTargetId(parentDeptId);
|
||||
relation.setIncludeChildren(1);
|
||||
when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(relation));
|
||||
SysAccount operator = new SysAccount();
|
||||
operator.setId(operatorId);
|
||||
operator.setDeptId(currentDeptId);
|
||||
when(sysAccountService.getById(operatorId)).thenReturn(operator);
|
||||
when(sysDeptService.getSelfAndAncestorDeptIds(currentDeptId))
|
||||
.thenReturn(Set.of(parentDeptId, currentDeptId));
|
||||
|
||||
assertTrue(service.canHandleTask(task, operatorId, Set.of()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证未启用“包含子部门”时,下级部门成员不能处理父部门任务。
|
||||
*/
|
||||
@Test
|
||||
public void canHandleTaskShouldRejectDescendantDepartmentWhenFlagDisabled() {
|
||||
BigInteger operatorId = BigInteger.valueOf(7);
|
||||
BigInteger parentDeptId = BigInteger.valueOf(21);
|
||||
BigInteger currentDeptId = BigInteger.valueOf(22);
|
||||
ApprovalTask task = new ApprovalTask();
|
||||
task.setId(BigInteger.valueOf(101));
|
||||
task.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
|
||||
|
||||
ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
|
||||
relation.setTaskId(task.getId());
|
||||
relation.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
|
||||
relation.setTargetId(parentDeptId);
|
||||
relation.setIncludeChildren(0);
|
||||
when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(relation));
|
||||
SysAccount operator = new SysAccount();
|
||||
operator.setId(operatorId);
|
||||
operator.setDeptId(currentDeptId);
|
||||
when(sysAccountService.getById(operatorId)).thenReturn(operator);
|
||||
when(sysDeptService.getSelfAndAncestorDeptIds(currentDeptId))
|
||||
.thenReturn(Set.of(parentDeptId, currentDeptId));
|
||||
|
||||
assertFalse(service.canHandleTask(task, operatorId, Set.of()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证包含子部门的部门任务会出现在下级部门成员的待审批列表。
|
||||
*/
|
||||
@Test
|
||||
public void listPendingInstanceIdsShouldMatchDescendantDepartment() {
|
||||
BigInteger operatorId = BigInteger.valueOf(7);
|
||||
BigInteger parentDeptId = BigInteger.valueOf(21);
|
||||
BigInteger currentDeptId = BigInteger.valueOf(22);
|
||||
BigInteger taskId = BigInteger.valueOf(101);
|
||||
BigInteger instanceId = BigInteger.valueOf(201);
|
||||
|
||||
SysAccount operator = new SysAccount();
|
||||
operator.setId(operatorId);
|
||||
operator.setDeptId(currentDeptId);
|
||||
when(sysAccountService.getById(operatorId)).thenReturn(operator);
|
||||
when(sysDeptService.getSelfAndAncestorDeptIds(currentDeptId))
|
||||
.thenReturn(Set.of(parentDeptId, currentDeptId));
|
||||
|
||||
ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
|
||||
relation.setTaskId(taskId);
|
||||
relation.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
|
||||
relation.setTargetId(parentDeptId);
|
||||
relation.setIncludeChildren(1);
|
||||
when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(), List.of(relation));
|
||||
|
||||
ApprovalTask task = new ApprovalTask();
|
||||
task.setId(taskId);
|
||||
task.setInstanceId(instanceId);
|
||||
task.setStatus(ApprovalTaskStatus.PENDING.getCode());
|
||||
when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(task), List.of(), List.of());
|
||||
|
||||
assertEquals(
|
||||
Set.of(instanceId),
|
||||
service.listPendingInstanceIds(operatorId, Set.of(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证多个角色审批对象采用 OR 语义,命中任一角色即可处理。
|
||||
*/
|
||||
@Test
|
||||
public void canHandleTaskShouldMatchAnyRoleTarget() {
|
||||
BigInteger operatorId = BigInteger.valueOf(7);
|
||||
ApprovalTask task = new ApprovalTask();
|
||||
task.setId(BigInteger.valueOf(101));
|
||||
task.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
|
||||
|
||||
ApprovalTaskAssignee first = taskAssignee(task.getId(), 31);
|
||||
ApprovalTaskAssignee second = taskAssignee(task.getId(), 32);
|
||||
when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(first, second));
|
||||
|
||||
assertTrue(service.canHandleTask(task, operatorId, Set.of(BigInteger.valueOf(32))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造有效角色。
|
||||
*
|
||||
* @param id 角色 ID
|
||||
* @param code 角色编码
|
||||
* @param name 角色名称
|
||||
* @return 角色
|
||||
*/
|
||||
private SysRole role(long id, String code, String name) {
|
||||
SysRole role = new SysRole();
|
||||
role.setId(BigInteger.valueOf(id));
|
||||
role.setRoleKey(code);
|
||||
role.setRoleName(name);
|
||||
role.setStatus(EnumDataStatus.AVAILABLE.getCode());
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造审批对象。
|
||||
*
|
||||
* @param targetId 对象 ID
|
||||
* @param includeChildren 是否包含子部门
|
||||
* @return 审批对象
|
||||
*/
|
||||
private ApprovalAssigneeTargetVo target(BigInteger targetId, int includeChildren) {
|
||||
ApprovalAssigneeTargetVo target = new ApprovalAssigneeTargetVo();
|
||||
target.setTargetId(targetId);
|
||||
target.setIncludeChildren(includeChildren);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造角色任务关联。
|
||||
*
|
||||
* @param taskId 任务 ID
|
||||
* @param targetId 角色 ID
|
||||
* @return 任务关联
|
||||
*/
|
||||
private ApprovalTaskAssignee taskAssignee(BigInteger taskId, long targetId) {
|
||||
ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
|
||||
relation.setTaskId(taskId);
|
||||
relation.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
|
||||
relation.setTargetId(BigInteger.valueOf(targetId));
|
||||
relation.setIncludeChildren(0);
|
||||
return relation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package tech.easyflow.approval.service.impl;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||
import tech.easyflow.approval.entity.ApprovalLog;
|
||||
import tech.easyflow.approval.entity.ApprovalTask;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalAssigneeTargetVo;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||
import tech.easyflow.approval.enums.ApprovalAssigneeType;
|
||||
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
|
||||
import tech.easyflow.approval.enums.ApprovalTaskStatus;
|
||||
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalLogMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||
import tech.easyflow.approval.service.ApprovalMatchService;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link ApprovalInstanceServiceImpl} 多审批对象冻结测试。
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ApprovalInstanceMultiAssigneeTest {
|
||||
|
||||
@Mock
|
||||
private ApprovalMatchService approvalMatchService;
|
||||
|
||||
@Mock
|
||||
private ApprovalInstanceMapper approvalInstanceMapper;
|
||||
|
||||
@Mock
|
||||
private ApprovalTaskMapper approvalTaskMapper;
|
||||
|
||||
@Mock
|
||||
private ApprovalLogMapper approvalLogMapper;
|
||||
|
||||
@Mock
|
||||
private ApprovalFlowStepMapper approvalFlowStepMapper;
|
||||
|
||||
@Mock
|
||||
private ApprovalAssigneeService approvalAssigneeService;
|
||||
|
||||
@Mock
|
||||
private SysAccountService sysAccountService;
|
||||
|
||||
@InjectMocks
|
||||
private ApprovalInstanceServiceImpl service;
|
||||
|
||||
/**
|
||||
* 验证提审时会把全部审批对象写入实例快照和任务关联。
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void submitApprovalShouldFreezeAllAssigneeTargets() {
|
||||
BigInteger applicantId = BigInteger.valueOf(7);
|
||||
BigInteger tenantId = BigInteger.valueOf(42);
|
||||
BigInteger instanceId = BigInteger.valueOf(101);
|
||||
BigInteger taskId = BigInteger.valueOf(201);
|
||||
ApprovalSubmitRequest request = new ApprovalSubmitRequest();
|
||||
request.setApplicantId(applicantId);
|
||||
request.setResourceId(BigInteger.valueOf(501));
|
||||
request.setSummary("多用户审批");
|
||||
|
||||
ApprovalFlowStepVo step = new ApprovalFlowStepVo();
|
||||
step.setStepNo(1);
|
||||
step.setStepName("审核");
|
||||
step.setAssigneeType(ApprovalAssigneeType.USER.getCode());
|
||||
step.setAssigneeTargets(List.of(
|
||||
target(11, "u11", "用户甲"),
|
||||
target(12, "u12", "用户乙")));
|
||||
ApprovalFlowDetailVo flow = new ApprovalFlowDetailVo();
|
||||
flow.setId(BigInteger.valueOf(301));
|
||||
flow.setVersion(3);
|
||||
flow.setResourceType("WORKFLOW");
|
||||
flow.setActionType("PUBLISH");
|
||||
flow.setSteps(List.of(step));
|
||||
when(approvalMatchService.matchFlow(request)).thenReturn(flow);
|
||||
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
loginAccount.setId(applicantId);
|
||||
loginAccount.setTenantId(tenantId);
|
||||
SysAccount applicant = new SysAccount();
|
||||
applicant.setId(applicantId);
|
||||
applicant.setTenantId(tenantId);
|
||||
when(sysAccountService.getById(applicantId)).thenReturn(applicant);
|
||||
doAnswer(invocation -> {
|
||||
((ApprovalInstance) invocation.getArgument(0)).setId(instanceId);
|
||||
return 1;
|
||||
}).when(approvalInstanceMapper).insert(any(ApprovalInstance.class));
|
||||
doAnswer(invocation -> {
|
||||
((ApprovalTask) invocation.getArgument(0)).setId(taskId);
|
||||
return 1;
|
||||
}).when(approvalTaskMapper).insert(any(ApprovalTask.class));
|
||||
|
||||
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
|
||||
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount);
|
||||
assertEquals(instanceId, service.submitApproval(request));
|
||||
}
|
||||
|
||||
ArgumentCaptor<ApprovalInstance> instanceCaptor = ArgumentCaptor.forClass(ApprovalInstance.class);
|
||||
verify(approvalInstanceMapper).insert(instanceCaptor.capture());
|
||||
List<Map<String, Object>> frozenSteps =
|
||||
(List<Map<String, Object>>) instanceCaptor.getValue().getSnapshotJson().get("steps");
|
||||
List<Map<String, Object>> frozenTargets =
|
||||
(List<Map<String, Object>>) frozenSteps.get(0).get("assigneeTargets");
|
||||
assertEquals(2, frozenTargets.size());
|
||||
assertEquals(BigInteger.valueOf(12), frozenTargets.get(1).get("targetId"));
|
||||
verify(approvalAssigneeService).saveTaskAssigneeTargets(
|
||||
eq(taskId),
|
||||
eq(ApprovalAssigneeType.USER.getCode()),
|
||||
argThat(targets -> targets.size() == 2),
|
||||
eq(applicantId),
|
||||
any(Date.class));
|
||||
verify(approvalLogMapper, org.mockito.Mockito.times(2)).insert(any(ApprovalLog.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证进入下一步骤时继续使用实例快照中冻结的全部审批对象。
|
||||
*/
|
||||
@Test
|
||||
public void approveShouldCreateNextTaskFromFrozenAssigneeTargets() {
|
||||
BigInteger operatorId = BigInteger.valueOf(7);
|
||||
BigInteger tenantId = BigInteger.valueOf(42);
|
||||
BigInteger instanceId = BigInteger.valueOf(101);
|
||||
BigInteger currentTaskId = BigInteger.valueOf(201);
|
||||
BigInteger nextTaskId = BigInteger.valueOf(202);
|
||||
BigInteger firstDeptId = BigInteger.valueOf(31);
|
||||
BigInteger secondDeptId = BigInteger.valueOf(32);
|
||||
|
||||
ApprovalInstance instance = new ApprovalInstance();
|
||||
instance.setId(instanceId);
|
||||
instance.setTenantId(tenantId);
|
||||
instance.setFlowId(BigInteger.valueOf(301));
|
||||
instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
|
||||
instance.setCurrentStepNo(1);
|
||||
instance.setSnapshotJson(Map.of(
|
||||
"steps", List.of(
|
||||
frozenStep(1, ApprovalAssigneeType.USER.getCode(),
|
||||
List.of(frozenTarget(11, 0))),
|
||||
frozenStep(2, ApprovalAssigneeType.DEPT.getCode(), List.of(
|
||||
frozenTarget(firstDeptId.longValue(), 0),
|
||||
frozenTarget(secondDeptId.longValue(), 1))))));
|
||||
|
||||
ApprovalTask currentTask = new ApprovalTask();
|
||||
currentTask.setId(currentTaskId);
|
||||
currentTask.setInstanceId(instanceId);
|
||||
currentTask.setStepNo(1);
|
||||
currentTask.setStatus(ApprovalTaskStatus.PENDING.getCode());
|
||||
currentTask.setAssigneeType(ApprovalAssigneeType.USER.getCode());
|
||||
|
||||
SysAccount operator = new SysAccount();
|
||||
operator.setId(operatorId);
|
||||
operator.setTenantId(tenantId);
|
||||
when(sysAccountService.getById(operatorId)).thenReturn(operator);
|
||||
when(approvalInstanceMapper.selectOneByQuery(any())).thenReturn(instance);
|
||||
when(approvalTaskMapper.selectOneByQuery(any())).thenReturn(currentTask);
|
||||
when(approvalAssigneeService.getAvailableRoleIds(operatorId)).thenReturn(Set.of());
|
||||
when(approvalAssigneeService.canHandleTask(currentTask, operatorId, Set.of())).thenReturn(true);
|
||||
when(approvalFlowStepMapper.selectListByQuery(any())).thenReturn(List.of());
|
||||
doAnswer(invocation -> {
|
||||
((ApprovalTask) invocation.getArgument(0)).setId(nextTaskId);
|
||||
return 1;
|
||||
}).when(approvalTaskMapper).insert(any(ApprovalTask.class));
|
||||
|
||||
service.approve(instanceId, "同意", operatorId);
|
||||
|
||||
verify(approvalAssigneeService).saveTaskAssigneeTargets(
|
||||
eq(nextTaskId),
|
||||
eq(ApprovalAssigneeType.DEPT.getCode()),
|
||||
argThat(targets -> targets.size() == 2
|
||||
&& firstDeptId.equals(targets.get(0).getTargetId())
|
||||
&& secondDeptId.equals(targets.get(1).getTargetId())
|
||||
&& Integer.valueOf(1).equals(targets.get(1).getIncludeChildren())),
|
||||
eq(operatorId),
|
||||
any(Date.class));
|
||||
assertEquals(ApprovalInstanceStatus.PROCESSING.getCode(), instance.getStatus());
|
||||
assertEquals(Integer.valueOf(2), instance.getCurrentStepNo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造冻结步骤。
|
||||
*
|
||||
* @param stepNo 步骤序号
|
||||
* @param assigneeType 审批对象类型
|
||||
* @param targets 审批对象
|
||||
* @return 冻结步骤
|
||||
*/
|
||||
private Map<String, Object> frozenStep(int stepNo, String assigneeType, List<Map<String, Object>> targets) {
|
||||
Map<String, Object> primary = targets.get(0);
|
||||
return Map.of(
|
||||
"stepNo", stepNo,
|
||||
"stepName", "第" + stepNo + "步",
|
||||
"assigneeType", assigneeType,
|
||||
"assigneeTargetId", primary.get("targetId"),
|
||||
"assigneeTargetCode", "target-" + primary.get("targetId"),
|
||||
"assigneeTargetName", "对象" + primary.get("targetId"),
|
||||
"assigneeTargets", targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造冻结审批对象。
|
||||
*
|
||||
* @param id 对象 ID
|
||||
* @param includeChildren 是否包含子部门
|
||||
* @return 冻结审批对象
|
||||
*/
|
||||
private Map<String, Object> frozenTarget(long id, int includeChildren) {
|
||||
return Map.of(
|
||||
"targetId", BigInteger.valueOf(id),
|
||||
"targetCode", "target-" + id,
|
||||
"targetName", "对象" + id,
|
||||
"includeChildren", includeChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造审批对象。
|
||||
*
|
||||
* @param id 用户 ID
|
||||
* @param code 用户编码
|
||||
* @param name 用户名称
|
||||
* @return 审批对象
|
||||
*/
|
||||
private ApprovalAssigneeTargetVo target(long id, String code, String name) {
|
||||
ApprovalAssigneeTargetVo target = new ApprovalAssigneeTargetVo();
|
||||
target.setTargetId(BigInteger.valueOf(id));
|
||||
target.setTargetCode(code);
|
||||
target.setTargetName(name);
|
||||
target.setIncludeChildren(0);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package tech.easyflow.approval.service.impl;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* V37 审批多对象迁移契约测试。
|
||||
*/
|
||||
public class ApprovalMultiAssigneeMigrationContractTest {
|
||||
|
||||
/**
|
||||
* 验证迁移先阻断在途审批,再创建关联表并完整回填配置和历史任务。
|
||||
*
|
||||
* @throws Exception 迁移文件不可读时抛出
|
||||
*/
|
||||
@Test
|
||||
public void migrationShouldGuardAndBackfillBothAssigneeRelations() throws Exception {
|
||||
String sql = migrationSql();
|
||||
|
||||
assertTrue(sql.contains("WHERE `status` IN ('PENDING', 'PROCESSING')"));
|
||||
assertTrue(sql.contains("WHERE `status` = 'PENDING'"));
|
||||
assertTrue(sql.contains("SIGNAL SQLSTATE '45000'"));
|
||||
assertTrue(sql.indexOf("CALL `sp_guard_approval_multi_assignee`()")
|
||||
< sql.indexOf("CREATE TABLE IF NOT EXISTS `tb_approval_flow_step_assignee`"));
|
||||
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS `tb_approval_task_assignee`"));
|
||||
assertTrue(sql.contains("UNIQUE KEY `uk_approval_step_assignee`"));
|
||||
assertTrue(sql.contains("UNIQUE KEY `uk_approval_task_assignee`"));
|
||||
assertTrue(sql.contains("FROM `tb_approval_flow_step` step"));
|
||||
assertTrue(sql.contains("FROM `tb_approval_task` task"));
|
||||
assertTrue(sql.contains("approval multi-assignee backfill verification failed"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取工作区中的 V37 MySQL 迁移。
|
||||
*
|
||||
* @return 迁移 SQL
|
||||
* @throws Exception 迁移文件不存在或不可读时抛出
|
||||
*/
|
||||
private String migrationSql() throws Exception {
|
||||
Path root = Path.of(System.getProperty("maven.multiModuleProjectDirectory",
|
||||
Path.of(System.getProperty("user.dir")).toAbsolutePath().toString()));
|
||||
while (root != null) {
|
||||
Path migration = root.resolve("easyflow-starter/easyflow-starter-all/src/main/resources/"
|
||||
+ "db/migration/mysql/V37__mysql_approval_multi_assignee.sql");
|
||||
if (Files.isRegularFile(migration)) {
|
||||
return Files.readString(migration, StandardCharsets.UTF_8);
|
||||
}
|
||||
root = root.getParent();
|
||||
}
|
||||
throw new IllegalStateException("找不到 V37 审批多对象迁移");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user