fix: 代码审查修复
- 会签:一人多组时取消其所在**每个组**的其余待办(原先只取消点击任务所在组,另一组永久 PENDING 卡单) - 跨库:提交首节点审批人解析移出事务;协调单受理部门改为 prepare 事务外解析 - 回填:CONFIRM 节点支持 writableFields(ADR 0006 三类节点均可声明) - 收据单:受理部门(财务部)快照到实例,列表/导出可见 - 明细导出:ENUM 列输出中文文案而非存储编码 - 价格表:历史版本包含未来生效的已归档版本 - 导出会签进度一次装配,去掉按节点重复查询 - 菜单种子:价格菜单在 superAdmin 授权之前,授权条件覆盖 approval:% - doc_type 与 flow_code 常量分开;删除未使用的 findByDocType / ApprovalDetail.fieldChanges - 补附件三档白名单用例(VOUCHER 通过、EXTERNAL 被拒)
This commit is contained in:
@@ -93,6 +93,42 @@ class ApprovalAttachmentFileRoundTripIT {
|
||||
assertThat(download.getResponse().getContentAsByteArray()).isEqualTo(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件分类三档(#50):VOUCHER 上传成功、详情按三档显示名称、白名单外的值被拒。
|
||||
*/
|
||||
@Test
|
||||
void voucherCategoryIsAcceptedAndUnknownCategoryRejected() throws Exception {
|
||||
long instanceId = createCompletionDraft("附件分类三档");
|
||||
|
||||
MockMultipartFile file = new MockMultipartFile("file", "voucher.pdf", "application/pdf",
|
||||
new byte[]{'%', 'P', 'D', 'F', '-', '1', '.', '4'});
|
||||
String response = mockMvc.perform(multipart("/api/approval/attachment/upload")
|
||||
.file(file)
|
||||
.param("instanceId", String.valueOf(instanceId))
|
||||
.param("category", "VOUCHER")
|
||||
.header("tenantId", TENANT)
|
||||
.with(authentication(TestAuthentication.user(reporter.userId, reporter.username,
|
||||
organizationId, TENANT, SUBMIT))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.category").value("VOUCHER"))
|
||||
.andExpect(jsonPath("$.data.categoryName").value("凭证"))
|
||||
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
assertThat(objectMapper.readTree(response).path("data").path("id").asLong()).isPositive();
|
||||
|
||||
MockMultipartFile again = new MockMultipartFile("file", "sign.png", "image/png", pngBytes());
|
||||
mockMvc.perform(multipart("/api/approval/attachment/upload")
|
||||
.file(again)
|
||||
.param("instanceId", String.valueOf(instanceId))
|
||||
.param("category", "EXTERNAL")
|
||||
.header("tenantId", TENANT)
|
||||
.with(authentication(TestAuthentication.user(reporter.userId, reporter.username,
|
||||
organizationId, TENANT, SUBMIT))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(1))
|
||||
.andExpect(jsonPath("$.message").value("附件分类不正确"));
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private long createCompletionDraft(String name) throws Exception {
|
||||
|
||||
@@ -135,24 +135,31 @@ class ApprovalCountersignEngineIT {
|
||||
|
||||
@Test
|
||||
void onePersonInTwoGroupsCompletesBothWithSingleClick() throws Exception {
|
||||
// 让第一组负责人同时挂上第二组的部门负责人角色:他会拿到两条会签任务
|
||||
// 让第一组负责人同时挂上第二组负责人角色:他会拿到两条会签任务,
|
||||
// 而第二组另有一名成员(groupLeaders.get(1))——该成员的待办必须随这次点击一并取消,
|
||||
// 否则第二组永远有 PENDING,节点无法推进。
|
||||
Integer secondRoleId = data.server().queryForObject(
|
||||
"SELECT role_id FROM sys_role WHERE tenant_id = ? AND deleted = 0 AND role_code = ?",
|
||||
Integer.class, TENANT, data.leaderRoleCode(groupOrgs.get(1)));
|
||||
data.bind(groupLeaders.get(0).userId, secondRoleId);
|
||||
// 第二组只留这一个成员,使总任务数仍是 4,其中该用户占 2 条
|
||||
data.server().update("DELETE FROM sys_user_role WHERE user_id = ? AND role_id = ?",
|
||||
groupLeaders.get(1).userId, secondRoleId);
|
||||
|
||||
long instanceId = createAndSubmit();
|
||||
assertThat(countersignTaskCount(instanceId)).isEqualTo(4);
|
||||
assertThat(countersignTaskCount(instanceId)).isEqualTo(5);
|
||||
|
||||
approve(taskFor(instanceId, groupLeaders.get(0))).andExpect(jsonPath("$.code").value(0));
|
||||
|
||||
// 本人两条任务都完成
|
||||
assertThat(data.countMaster("SELECT COUNT(*) FROM approval_task WHERE instance_id = ? "
|
||||
+ "AND assignee_id = ? AND status = 'DONE'", instanceId, groupLeaders.get(0).userId)).isEqualTo(2);
|
||||
assertThat(currentNodeKey(instanceId)).isEqualTo("countersign");
|
||||
// 他所在两个组的其余成员待办被取消,只剩另外两组共 2 条待办
|
||||
assertThat(countersignPendingCount(instanceId)).isEqualTo(2);
|
||||
assertThat(currentNodeKey(instanceId)).isEqualTo("countersign");
|
||||
|
||||
// 另外两组签完后节点正常推进:证明一人双组没有把节点卡死
|
||||
approve(taskFor(instanceId, groupLeaders.get(2))).andExpect(jsonPath("$.code").value(0));
|
||||
approve(taskFor(instanceId, groupLeaders.get(3)))
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.currentNodeName").value("存档"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -114,7 +114,8 @@ public class CountersignProbeDocumentType extends AbstractApprovalDocumentType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long persist(Long instanceId, Long bizId, ApprovalDocumentParam param, User user) {
|
||||
public Long persist(Long instanceId, Long bizId, ApprovalDocumentParam param, User user,
|
||||
Object prepared) {
|
||||
return bizId == null ? instanceId : bizId;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user