package jnpf.dmsService.impl;
|
|
import jnpf.base.ActionResult;
|
import jnpf.base.BillRuleApi;
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.fenfa.DmsWenjianDayinJiluEntity;
|
import jnpf.dmsEntity.fenfa.DmsWenjianDayinRenwuEntity;
|
import jnpf.dmsEntity.fenfa.DmsWenjianFenfaFanweiEntity;
|
import jnpf.dmsEntity.fenfa.DmsWenjianFenfaRenwuEntity;
|
import jnpf.dmsEntity.fenfa.form.DmsWenjianQianshouFafangForm;
|
import jnpf.dmsEntity.fenfa.form.DmsWenjianHuishouForm;
|
import jnpf.dmsEntity.fenfa.form.DmsWenjianHuishouMingxiForm;
|
import jnpf.dmsEntity.fenfa.vo.DmsWenjianJiaojieXiangqingVO;
|
import jnpf.dmsEntity.fenfa.vo.DmsWenjianHuishouMingxiVO;
|
import jnpf.dmsEntity.fenfa.vo.DmsWenjianHuishouResultVO;
|
import jnpf.dmsEntity.fenfa.vo.DmsWenjianXiaohuiMingxiVO;
|
import jnpf.dmsIntegration.audit.DmsAuditAdapter;
|
import jnpf.dmsIntegration.audit.DmsWenjianHuishouAuditEvent;
|
import jnpf.dmsMapper.DmsWenjianFenfaMapper;
|
import jnpf.dmsPermission.DmsCurrentIdentity;
|
import jnpf.exception.DataException;
|
import jnpf.permission.OrganizeApi;
|
import jnpf.permission.UserApi;
|
import jnpf.permission.entity.OrganizeEntity;
|
import jnpf.permission.entity.UserEntity;
|
import jnpf.permission.model.user.UserRelationIds;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.mockito.ArgumentCaptor;
|
import org.mockito.InjectMocks;
|
import org.mockito.Mock;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
import java.util.Collections;
|
import java.util.Arrays;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
@ExtendWith(MockitoExtension.class)
|
class DmsWenjianFenfaServiceImplTest {
|
|
@Mock private DmsCurrentIdentity identity;
|
@Mock private DmsWenjianFenfaMapper mapper;
|
@Mock private BillRuleApi billRuleApi;
|
@Mock private OrganizeApi organizeApi;
|
@Mock private UserApi userApi;
|
@Mock private DmsAuditAdapter auditAdapter;
|
@Mock private jnpf.dmsService.DmsWenjianDayinHaomaService numbers;
|
@Mock private jnpf.dmsService.DmsWenjianChongdaGuard reprintGuard;
|
@InjectMocks private DmsWenjianFenfaServiceImpl service;
|
|
private UserInfo user;
|
|
@BeforeEach
|
void setUp() {
|
user = new UserInfo();
|
user.setUserId("user-1");
|
user.setTenantId("tenant-1");
|
user.setOrganizeId("org-1");
|
user.setIsAdministrator(false);
|
}
|
|
@Test
|
void approvalCreatesOnlyOneIdempotentPrintTaskForEachValidScope() throws Exception {
|
when(mapper.selectNewScopes("tenant-1", "audit-1"))
|
.thenReturn(Collections.singletonList(scope("scope-1", 2)));
|
when(mapper.selectAuditPrintSnapshot("tenant-1", "xinzeng", "audit-1"))
|
.thenReturn(snapshot());
|
when(mapper.insertPrintTaskIfAbsent(any())).thenReturn(1);
|
service.ensureNewPrintTasks("tenant-1", "audit-1", "file-1", "user-1");
|
|
ArgumentCaptor<DmsWenjianDayinRenwuEntity> printTaskCaptor =
|
ArgumentCaptor.forClass(DmsWenjianDayinRenwuEntity.class);
|
verify(mapper).insertPrintTaskIfAbsent(printTaskCaptor.capture());
|
assertEquals("wenjian_xinzeng_shenhe", printTaskCaptor.getValue().getLaiyuanLeixing());
|
assertEquals("audit-1", printTaskCaptor.getValue().getLaiyuanDanjuId());
|
assertEquals("scope-1", printTaskCaptor.getValue().getLaiyuanMingxiId());
|
assertEquals(2, printTaskCaptor.getValue().getDayinFenshu());
|
assertEquals("DOC-001", printTaskCaptor.getValue().getWenjianBianhao());
|
verify(numbers).ensureTaskNumbers("tenant-1", printTaskCaptor.getValue(), 0);
|
verify(mapper, never()).insertDistributionTaskIfAbsent(any());
|
}
|
|
@Test
|
void approvalRejectsNonPositiveDistributionCopies() {
|
when(mapper.selectNewScopes("tenant-1", "audit-1"))
|
.thenReturn(Collections.singletonList(scope("scope-1", 0)));
|
when(mapper.selectAuditPrintSnapshot("tenant-1", "xinzeng", "audit-1"))
|
.thenReturn(snapshot());
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.ensureNewPrintTasks(
|
"tenant-1", "audit-1", "file-1", "user-1"));
|
|
assertEquals("分发份数必须大于0:0", exception.getMessage());
|
verify(mapper, never()).insertPrintTaskIfAbsent(any());
|
}
|
|
@Test
|
void confirmationWritesBothFrozenSerialsWithoutAllocatingAgain() throws Exception {
|
prepareIdentity();
|
var task = printTask("task-1");
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(task);
|
when(mapper.insertPrintRecord(any())).thenReturn(1);
|
when(mapper.updateTaskStatus(any(), any(), any(), any(), any())).thenReturn(1);
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(distributionTask());
|
var number = new jnpf.dmsEntity.fenfa.DmsWenjianDayinHaoma();
|
number.setId("number-1"); number.setTaskSequence(1); number.setCounterId("counter-1");
|
number.setYear(2026); number.setSequence(1); number.setSerialNumber("20261003000001"); number.setCountSerialNumber("20260001");
|
var frozen = Collections.singletonList(number);
|
var result = service.printReserved("task-1", 1, frozen);
|
verify(numbers).requireReservedNumbers("tenant-1", task, 0, frozen);
|
var record = ArgumentCaptor.forClass(DmsWenjianDayinJiluEntity.class);
|
verify(mapper).insertPrintRecord(record.capture());
|
assertEquals("20261003000001",record.getValue().getDayinLiushuihao());
|
assertEquals("20260001",record.getValue().getDayinJishuLiushuihao());
|
assertEquals("counter-1",record.getValue().getDayinJishuId());
|
assertEquals("20260001",result.getFirst().getDayinJishuLiushuihao());
|
verify(billRuleApi, never()).getBillNumber(any());
|
}
|
|
@Test
|
void rejectsTaskThatAlreadyHasPrintRecordsBeforeTakingNumber() {
|
prepareIdentity();
|
DmsWenjianDayinRenwuEntity task = printTask("task-1");
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(task);
|
when(mapper.countConsumedPrints("tenant-1", "task-1")).thenReturn(1);
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.print("task-1", 2));
|
|
assertEquals("本次打印份数超过剩余份数或无效,剩余:1",
|
exception.getMessage());
|
verify(billRuleApi, never()).getBillNumber(any());
|
verify(auditAdapter, never()).print(any());
|
}
|
|
@Test
|
void newAuditValidatesLaterRowsBeforeInsertingAnyTask() {
|
when(mapper.selectNewScopes("tenant-1", "audit-1"))
|
.thenReturn(Arrays.asList(scope("first", 1), scope("second", 0)));
|
when(mapper.selectAuditPrintSnapshot("tenant-1", "xinzeng", "audit-1"))
|
.thenReturn(snapshot());
|
assertThrows(DataException.class,
|
() -> service.ensureNewPrintTasks("tenant-1", "audit-1", "file-1", "user-1"));
|
verify(mapper, never()).insertPrintTaskIfAbsent(any());
|
}
|
|
@Test
|
void revisionAuditValidatesLaterRowsBeforeInsertingAnyTask() {
|
when(mapper.selectRevisionScopes("tenant-1", "audit-1"))
|
.thenReturn(Arrays.asList(scope("first", 1), scope("second", -1)));
|
when(mapper.selectAuditPrintSnapshot("tenant-1", "xiuding", "audit-1"))
|
.thenReturn(snapshot());
|
assertThrows(DataException.class,
|
() -> service.ensureRevisionPrintTasks("tenant-1", "audit-1", "file-1", "user-1"));
|
verify(mapper, never()).insertPrintTaskIfAbsent(any());
|
}
|
|
@Test
|
void printsCopiesWithPlatformSerialsAndMarksTaskPrinted() throws Exception {
|
prepareIdentity();
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(printTask("task-1"));
|
when(mapper.countConsumedPrints("tenant-1", "task-1")).thenReturn(0);
|
when(mapper.maxPrintSequence("tenant-1", "task-1")).thenReturn(0);
|
when(billRuleApi.getBillNumber("wenjianDayinLiushuihao"))
|
.thenReturn(ActionResult.success("取号成功", "20260825000001"),
|
ActionResult.success("取号成功", "20260825000002"));
|
when(billRuleApi.getBillNumber("wenjianFenfaBianhao"))
|
.thenReturn(ActionResult.success("取号成功", "FF20260825000001"));
|
when(mapper.insertPrintRecord(any())).thenReturn(1);
|
when(mapper.updateTaskStatus(any(), any(), any(), any(), any())).thenReturn(1);
|
when(mapper.insertDistributionTaskIfAbsent(any())).thenReturn(1);
|
|
java.util.List<jnpf.dmsEntity.fenfa.vo.DmsWenjianDayinJiluVO> printed =
|
service.print("task-1", 2);
|
assertEquals(2, printed.size());
|
assertEquals("org-1", printed.get(0).getDayinBumenId());
|
|
ArgumentCaptor<DmsWenjianDayinJiluEntity> captor =
|
ArgumentCaptor.forClass(DmsWenjianDayinJiluEntity.class);
|
verify(mapper, org.mockito.Mockito.times(2)).insertPrintRecord(captor.capture());
|
assertEquals(1, captor.getAllValues().get(0).getDayinXuhao());
|
assertEquals(2, captor.getAllValues().get(1).getDayinXuhao());
|
assertEquals("org-1", captor.getAllValues().get(0).getDayinBumenId());
|
assertNull(captor.getAllValues().get(0).getQianshouBumenId());
|
verify(mapper).updateTaskStatus(
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq("task-1"),
|
org.mockito.ArgumentMatchers.eq("yidayin"),
|
org.mockito.ArgumentMatchers.eq("user-1"), any());
|
ArgumentCaptor<DmsWenjianFenfaRenwuEntity> taskCaptor =
|
ArgumentCaptor.forClass(DmsWenjianFenfaRenwuEntity.class);
|
verify(mapper).insertDistributionTaskIfAbsent(taskCaptor.capture());
|
assertEquals("task-1", taskCaptor.getValue().getDayinRenwuId());
|
assertEquals("FF20260825000001", taskCaptor.getValue().getFenfaBianhao());
|
assertEquals("zidongfenfa", taskCaptor.getValue().getFafangLeixing());
|
assertEquals("daifafang", taskCaptor.getValue().getBizStatus());
|
verify(auditAdapter, never()).print(any());
|
}
|
|
@Test
|
void partialPrintCreatesDistributionAndRemainingPrintReusesIt() throws Exception {
|
prepareIdentity();
|
DmsWenjianDayinRenwuEntity task = printTask("task-1");
|
task.setDayinFenshu(10);
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(task);
|
when(mapper.countConsumedPrints("tenant-1", "task-1")).thenReturn(0);
|
when(billRuleApi.getBillNumber("wenjianDayinLiushuihao"))
|
.thenReturn(ActionResult.success("取号成功", "serial"));
|
when(mapper.insertPrintRecord(any())).thenReturn(1);
|
when(mapper.updateTaskStatus(any(), any(), any(), any(), any())).thenReturn(1);
|
when(billRuleApi.getBillNumber("wenjianFenfaBianhao"))
|
.thenReturn(ActionResult.success("取号成功", "distribution"));
|
when(mapper.insertDistributionTaskIfAbsent(any())).thenReturn(1);
|
assertEquals(3, service.print("task-1", 3).size());
|
verify(mapper).updateTaskStatus(org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq("task-1"), org.mockito.ArgumentMatchers.eq("bufendayin"),
|
org.mockito.ArgumentMatchers.eq("user-1"), any());
|
verify(mapper).insertDistributionTaskIfAbsent(any());
|
task.setBizStatus("bufendayin");
|
when(mapper.countConsumedPrints("tenant-1", "task-1")).thenReturn(3);
|
when(mapper.maxPrintSequence("tenant-1", "task-1")).thenReturn(3);
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(distributionTask());
|
assertEquals(7, service.print("task-1", 7).size());
|
verify(mapper).insertDistributionTaskIfAbsent(any());
|
verify(billRuleApi).getBillNumber("wenjianFenfaBianhao");
|
verify(mapper).updateTaskStatus(eq("tenant-1"), eq("task-1"), eq("yidayin"), eq("user-1"), any());
|
verify(mapper, never()).countActivePrints(any(), any());
|
}
|
|
@Test
|
void voidedCopiesDoNotRestoreAllowance() {
|
prepareIdentity();
|
DmsWenjianDayinRenwuEntity task = printTask("task-1");
|
task.setBizStatus("bufendayin");
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(task);
|
when(mapper.countConsumedPrints("tenant-1", "task-1")).thenReturn(2);
|
assertThrows(DataException.class, () -> service.print("task-1", 1));
|
verify(mapper, never()).countActivePrints(any(), any());
|
verify(billRuleApi, never()).getBillNumber(any());
|
}
|
|
@Test
|
void batchPrintingPrintsEverySelectedTaskInFull() throws Exception {
|
prepareIdentity();
|
DmsWenjianDayinRenwuEntity firstTask = printTask("task-1");
|
DmsWenjianDayinRenwuEntity secondTask = printTask("task-2");
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(firstTask);
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-2")).thenReturn(secondTask);
|
when(mapper.countConsumedPrints("tenant-1", "task-1")).thenReturn(0);
|
when(mapper.countConsumedPrints("tenant-1", "task-2")).thenReturn(0);
|
when(mapper.maxPrintSequence("tenant-1", "task-1")).thenReturn(0);
|
when(mapper.maxPrintSequence("tenant-1", "task-2")).thenReturn(0);
|
when(billRuleApi.getBillNumber("wenjianDayinLiushuihao"))
|
.thenReturn(ActionResult.success("取号成功", "serial-2"),
|
ActionResult.success("取号成功", "serial-3"),
|
ActionResult.success("取号成功", "serial-4"),
|
ActionResult.success("取号成功", "serial-5"));
|
when(billRuleApi.getBillNumber("wenjianFenfaBianhao"))
|
.thenReturn(ActionResult.success("取号成功", "FF-1"),
|
ActionResult.success("取号成功", "FF-2"));
|
when(mapper.insertPrintRecord(any())).thenReturn(1);
|
when(mapper.updateTaskStatus(any(), any(), any(), any(), any())).thenReturn(1);
|
when(mapper.insertDistributionTaskIfAbsent(any())).thenReturn(1);
|
|
assertEquals(4, service.printRemaining(Arrays.asList("task-2", "task-1")).size());
|
verify(mapper, org.mockito.Mockito.times(4)).insertPrintRecord(any());
|
verify(mapper, org.mockito.Mockito.times(2))
|
.updateTaskStatus(any(), any(), any(), any(), any());
|
verify(mapper, org.mockito.Mockito.times(2))
|
.insertDistributionTaskIfAbsent(any());
|
verify(auditAdapter, never()).print(any());
|
}
|
|
@Test
|
void activeReprintStopsHandoverBeforeTaskUpdates() throws Exception {
|
when(identity.requireUser()).thenReturn(user);when(identity.tenantId(user)).thenReturn("tenant-1");
|
var record=new DmsWenjianDayinJiluEntity();record.setId("print-1");
|
when(mapper.selectPrintForUpdate("tenant-1","print-1",null)).thenReturn(record);
|
org.mockito.Mockito.doThrow(new DataException("locked")).when(reprintGuard).requireUnlocked("tenant-1","print-1");
|
assertThrows(DataException.class,()->service.handoverByRecordId("print-1",handoverForm()));
|
verify(mapper,never()).selectPrintTaskForUpdate(any(),any());verifyNoAuditAfterLocked();
|
}
|
private void verifyNoAuditAfterLocked() { org.mockito.Mockito.verifyNoInteractions(auditAdapter); }
|
|
@Test
|
void handoverWritesIssueAndReceiptOnlyForSelectedPrint() throws Exception {
|
user.setOrganizeId("company-org-1");
|
prepareIdentity();
|
DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity();
|
record.setId("print-1");
|
record.setDayinRenwuId("task-1");
|
record.setBizStatus("daifafang");
|
when(mapper.selectPrintForUpdate("tenant-1", "print-1", null)).thenReturn(record);
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(distributionTask());
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(printTask("task-1"));
|
when(userApi.getUserObjectIdList("user-1"))
|
.thenReturn(userRelationIds("org-1"));
|
when(userApi.getUserName(Collections.singletonList("receiver-1")))
|
.thenReturn(Collections.singletonList(user("receiver-1", "signer-org-1", 1)));
|
when(userApi.getUserObjectIdList("receiver-1"))
|
.thenReturn(userRelationIds("signer-org-1"));
|
when(mapper.handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(1);
|
when(mapper.completeDistributionTaskHandoverIfFinished(
|
any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(0);
|
DmsWenjianQianshouFafangForm form = new DmsWenjianQianshouFafangForm();
|
form.setBizUserId("user-1");
|
form.setBizReviewUser("receiver-1");
|
form.setBizSign("issuer-signature-data");
|
form.setBizReviewSign("receiver-signature-data");
|
form.setBeizhuShuoming("单份交接");
|
|
service.handoverByRecordId("print-1", form);
|
|
verify(mapper).handoverPrintRecord(
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq("print-1"),
|
org.mockito.ArgumentMatchers.eq("user-1"),
|
org.mockito.ArgumentMatchers.eq("org-1"),
|
org.mockito.ArgumentMatchers.eq("receiver-1"),
|
org.mockito.ArgumentMatchers.eq("signer-org-1"),
|
org.mockito.ArgumentMatchers.eq("issuer-signature-data"),
|
org.mockito.ArgumentMatchers.eq("receiver-signature-data"),
|
org.mockito.ArgumentMatchers.eq("单份交接"), any());
|
verify(mapper).completeDistributionTaskHandoverIfFinished(
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq("distribution-1"),
|
org.mockito.ArgumentMatchers.eq("task-1"),
|
org.mockito.ArgumentMatchers.eq("receiver-1"),
|
org.mockito.ArgumentMatchers.eq("user-1"),
|
org.mockito.ArgumentMatchers.eq("org-1"),
|
org.mockito.ArgumentMatchers.eq("单份交接"), any());
|
}
|
|
@Test
|
void handoverDetailUsesCurrentTenantAndRejectsMissingRecord() throws Exception {
|
when(identity.tenantId()).thenReturn("tenant-1");
|
DmsWenjianJiaojieXiangqingVO detail = new DmsWenjianJiaojieXiangqingVO();
|
detail.setId("print-1");
|
detail.setWenjianBianhao("DOC-001");
|
detail.setJieshouBumenId("org-2");
|
detail.setJieshouBumen("信息管理部");
|
OrganizeEntity organization = new OrganizeEntity();
|
organization.setId("org-2");
|
organization.setOrgNameTree("玻思韬/信息管理部");
|
when(mapper.selectHandoverDetail("tenant-1", "print-1")).thenReturn(detail);
|
when(organizeApi.getInfoById("org-2")).thenReturn(organization);
|
|
assertEquals(detail, service.handoverDetail("print-1"));
|
assertEquals("玻思韬/信息管理部", detail.getJieshouBumen());
|
|
when(mapper.selectHandoverDetail("tenant-1", "missing")).thenReturn(null);
|
DataException exception = assertThrows(DataException.class,
|
() -> service.handoverDetail("missing"));
|
assertEquals("未找到对应的有效打印记录", exception.getMessage());
|
}
|
|
@Test
|
void barcodeHandoverUsesTheSameAtomicOperation() throws Exception {
|
prepareIdentity();
|
DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity();
|
record.setId("print-1");
|
record.setDayinRenwuId("task-1");
|
record.setBizStatus("daifafang");
|
when(mapper.selectPrintForUpdate("tenant-1", null, "serial-1"))
|
.thenReturn(record);
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(distributionTask());
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(printTask("task-1"));
|
when(userApi.getUserObjectIdList("user-1"))
|
.thenReturn(userRelationIds("org-1"));
|
when(userApi.getUserName(Collections.singletonList("receiver-1")))
|
.thenReturn(Collections.singletonList(user("receiver-1", "signer-org-1", 1)));
|
when(userApi.getUserObjectIdList("receiver-1"))
|
.thenReturn(userRelationIds("signer-org-1"));
|
when(mapper.handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(1);
|
when(mapper.completeDistributionTaskHandoverIfFinished(
|
any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(1);
|
DmsWenjianQianshouFafangForm form = new DmsWenjianQianshouFafangForm();
|
form.setBizUserId("user-1");
|
form.setBizReviewUser("receiver-1");
|
form.setBizSign("issuer-signature-data");
|
form.setBizReviewSign("receiver-signature-data");
|
|
service.handoverBySerialNumber("serial-1", form);
|
|
verify(mapper).handoverPrintRecord(
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq("print-1"),
|
org.mockito.ArgumentMatchers.eq("user-1"),
|
org.mockito.ArgumentMatchers.eq("org-1"),
|
org.mockito.ArgumentMatchers.eq("receiver-1"),
|
org.mockito.ArgumentMatchers.eq("signer-org-1"),
|
org.mockito.ArgumentMatchers.eq("issuer-signature-data"),
|
org.mockito.ArgumentMatchers.eq("receiver-signature-data"),
|
org.mockito.ArgumentMatchers.isNull(),
|
any());
|
}
|
|
@Test
|
void handoverIsIdempotentForAlreadyReceivedPrint() throws Exception {
|
prepareIdentity();
|
DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity();
|
record.setId("print-1");
|
record.setDayinRenwuId("task-1");
|
record.setBizStatus("yiqianshou");
|
when(mapper.selectPrintForUpdate("tenant-1", "print-1", null)).thenReturn(record);
|
DmsWenjianFenfaRenwuEntity task = distributionTask();
|
task.setBizStatus("yiwancheng");
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(task);
|
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(printTask("task-1"));
|
|
service.handoverByRecordId("print-1", handoverForm());
|
|
verify(mapper, never()).handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
|
verify(mapper, never()).completeDistributionTaskHandoverIfFinished(
|
any(), any(), any(), any(), any(), any(), any(), any());
|
}
|
|
@Test
|
void handoverIsIdempotentAfterTheReceiptLifecycleHasAdvanced() throws Exception {
|
prepareIdentity();
|
DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity();
|
record.setId("print-1");
|
record.setDayinRenwuId("task-1");
|
record.setBizStatus("yixiaohui");
|
when(mapper.selectPrintForUpdate("tenant-1", "print-1", null)).thenReturn(record);
|
DmsWenjianFenfaRenwuEntity task = distributionTask();
|
task.setBizStatus("yiwancheng");
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(task);
|
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(printTask("task-1"));
|
|
service.handoverByRecordId("print-1", handoverForm());
|
|
verify(mapper, never()).handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
|
verify(mapper, never()).completeDistributionTaskHandoverIfFinished(
|
any(), any(), any(), any(), any(), any(), any(), any());
|
}
|
|
@Test
|
void handoverRejectsConfirmUserDifferentFromCurrentOperator() {
|
prepareIdentity();
|
DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity();
|
record.setId("print-1");
|
record.setDayinRenwuId("task-1");
|
record.setBizStatus("daifafang");
|
when(mapper.selectPrintForUpdate("tenant-1", "print-1", null)).thenReturn(record);
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(distributionTask());
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(printTask("task-1"));
|
DmsWenjianQianshouFafangForm form = new DmsWenjianQianshouFafangForm();
|
form.setBizUserId("another-user");
|
form.setBizReviewUser("receiver-1");
|
form.setBizSign("issuer-signature-data");
|
form.setBizReviewSign("receiver-signature-data");
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.handoverByRecordId("print-1", form));
|
|
assertEquals("发放人必须是当前操作人", exception.getMessage());
|
verify(mapper, never()).handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
|
}
|
|
@Test
|
void handoverRejectsMissingDisabledOrDepartmentlessSigner() {
|
for (java.util.List<UserEntity> resolved : Arrays.asList(
|
Collections.<UserEntity>emptyList(),
|
Collections.singletonList(user("receiver-1", "signer-org-1", 0)),
|
Collections.singletonList(user("receiver-1", null, 1)))) {
|
org.mockito.Mockito.reset(mapper, userApi);
|
prepareIdentity();
|
prepareHandoverRecord();
|
when(userApi.getUserObjectIdList("user-1"))
|
.thenReturn(userRelationIds("org-1"));
|
when(userApi.getUserName(Collections.singletonList("receiver-1")))
|
.thenReturn(resolved);
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.handoverByRecordId("print-1", handoverForm()));
|
|
if (resolved.isEmpty() || Integer.valueOf(0).equals(resolved.get(0).getEnabledMark())) {
|
assertEquals("所选签收人不存在或已停用", exception.getMessage());
|
} else {
|
assertEquals("所选签收人未设置所属部门", exception.getMessage());
|
}
|
verify(mapper, never()).handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
|
}
|
}
|
|
@Test
|
void handoverRejectsNonAdministratorWithoutRelatedDepartment() {
|
user.setOrganizeId(null);
|
prepareIdentity();
|
prepareHandoverRecord();
|
when(userApi.getUserObjectIdList("user-1"))
|
.thenReturn(UserRelationIds.builder().build());
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.handoverByRecordId("print-1", handoverForm()));
|
|
assertEquals("当前发放人未设置所属部门", exception.getMessage());
|
verify(userApi).getUserObjectIdList("user-1");
|
verify(userApi, never()).getUserName(any());
|
verify(mapper, never()).handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
|
}
|
|
@Test
|
void handoverAllowsAdministratorWithoutRelatedDepartment() throws Exception {
|
user.setOrganizeId(null);
|
user.setIsAdministrator(true);
|
prepareIdentity();
|
prepareHandoverRecord();
|
when(userApi.getUserObjectIdList("user-1"))
|
.thenReturn(UserRelationIds.builder().build());
|
when(userApi.getUserName(Collections.singletonList("receiver-1")))
|
.thenReturn(Collections.singletonList(user("receiver-1", "signer-org-1", 1)));
|
when(userApi.getUserObjectIdList("receiver-1"))
|
.thenReturn(userRelationIds("signer-org-1"));
|
when(mapper.handoverPrintRecord(
|
any(), any(), any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(1);
|
when(mapper.completeDistributionTaskHandoverIfFinished(
|
any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(0);
|
|
service.handoverByRecordId("print-1", handoverForm());
|
|
verify(mapper).handoverPrintRecord(
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq("print-1"),
|
org.mockito.ArgumentMatchers.eq("user-1"),
|
org.mockito.ArgumentMatchers.isNull(),
|
org.mockito.ArgumentMatchers.eq("receiver-1"),
|
org.mockito.ArgumentMatchers.eq("signer-org-1"),
|
org.mockito.ArgumentMatchers.eq("issuer-signature-data"),
|
org.mockito.ArgumentMatchers.eq("receiver-signature-data"),
|
org.mockito.ArgumentMatchers.isNull(), any());
|
}
|
|
@Test
|
void signerDepartmentEndpointUsesTheSameAuthoritativeResolution() throws Exception {
|
when(userApi.getUserName(Collections.singletonList("receiver-1")))
|
.thenReturn(Collections.singletonList(user("receiver-1", "company-org-1", 1)));
|
when(userApi.getUserObjectIdList("receiver-1"))
|
.thenReturn(userRelationIds("department-org-1"));
|
|
assertEquals("department-org-1", service.signerDepartmentId("receiver-1"));
|
}
|
|
@Test
|
void signerDepartmentPrefersDefaultOrganizationWhenItIsAUserRelation() throws Exception {
|
when(userApi.getUserName(Collections.singletonList("receiver-1")))
|
.thenReturn(Collections.singletonList(user("receiver-1", "department-org-2", 1)));
|
when(userApi.getUserObjectIdList("receiver-1"))
|
.thenReturn(UserRelationIds.builder()
|
.organize(Arrays.asList("department-org-1", "department-org-2"))
|
.build());
|
|
assertEquals("department-org-2", service.signerDepartmentId("receiver-1"));
|
}
|
|
@Test
|
void recycleUsesServerIdentityAndTimeForEveryDetail() throws Exception {
|
prepareIdentity();
|
when(mapper.insertRecycle(any())).thenReturn(1);
|
when(mapper.selectRecyclePrintsForUpdate("tenant-1", Arrays.asList("print-1", "print-2")))
|
.thenReturn(Arrays.asList(recycleRecord("print-1", "serial-1", "daihuishou"),
|
recycleRecord("print-2", "serial-2", "daihuishou")));
|
when(mapper.markPrintsRecycled(any(), any(), any(), any())).thenReturn(2);
|
when(mapper.insertRecycleDetail(any())).thenReturn(1);
|
|
DmsWenjianHuishouResultVO result = service.recycle(recycleForm("print-2", "print-1"));
|
|
org.junit.jupiter.api.Assertions.assertTrue(result.getId().matches("\\d+"));
|
assertEquals(2, result.getHuishouFenshu());
|
org.junit.jupiter.api.Assertions.assertFalse(result.isRepeated());
|
ArgumentCaptor<jnpf.dmsEntity.fenfa.DmsWenjianHuishouEntity> recycleCaptor =
|
ArgumentCaptor.forClass(jnpf.dmsEntity.fenfa.DmsWenjianHuishouEntity.class);
|
verify(mapper).insertRecycle(recycleCaptor.capture());
|
assertEquals(result.getId(), recycleCaptor.getValue().getId());
|
assertEquals("xiaohui", recycleCaptor.getValue().getChuliFangshi());
|
ArgumentCaptor<jnpf.dmsEntity.fenfa.DmsWenjianHuishouMingxiEntity> details =
|
ArgumentCaptor.forClass(jnpf.dmsEntity.fenfa.DmsWenjianHuishouMingxiEntity.class);
|
verify(mapper, org.mockito.Mockito.times(2)).insertRecycleDetail(details.capture());
|
org.junit.jupiter.api.Assertions.assertTrue(
|
details.getAllValues().get(0).getId().matches("\\d+"));
|
assertEquals(result.getId(), details.getAllValues().get(0).getForeignId());
|
assertEquals("user-1", details.getAllValues().get(0).getHuishouRenId());
|
assertEquals("serial-1", details.getAllValues().get(0).getDayinLiushuihao());
|
ArgumentCaptor<DmsWenjianHuishouAuditEvent> auditCaptor =
|
ArgumentCaptor.forClass(DmsWenjianHuishouAuditEvent.class);
|
verify(auditAdapter).recycle(auditCaptor.capture());
|
assertEquals(result.getId(), auditCaptor.getValue().getHuishouDanjuId());
|
assertEquals(2, auditCaptor.getValue().getDetails().size());
|
assertEquals(result.getId(), auditCaptor.getValue().getDetails().get(0).getForeignId());
|
assertEquals("recycle-signature-data",
|
auditCaptor.getValue().getRelatedSigns().get(0).getSignId());
|
verify(mapper).markPrintsRecycled(
|
org.mockito.ArgumentMatchers.eq("tenant-1"),
|
org.mockito.ArgumentMatchers.eq(Arrays.asList("print-1", "print-2")),
|
org.mockito.ArgumentMatchers.eq("user-1"), any());
|
}
|
|
@Test
|
void destructionCandidateUsesTenantAndRequiresPendingDestructionStatus()
|
throws Exception {
|
when(identity.tenantId()).thenReturn("tenant-1");
|
DmsWenjianXiaohuiMingxiVO candidate = new DmsWenjianXiaohuiMingxiVO();
|
candidate.setDayinJiluId("print-1");
|
candidate.setDayinLiushuihao("serial-1");
|
candidate.setBizStatus("daixiaohui");
|
when(mapper.selectDestructionCandidateBySerialNumber("tenant-1", "serial-1"))
|
.thenReturn(candidate);
|
|
assertEquals(candidate, service.destructionCandidate(" serial-1 "));
|
verify(mapper).selectDestructionCandidateBySerialNumber("tenant-1", "serial-1");
|
|
when(mapper.selectDestructionCandidateBySerialNumber("tenant-1", "missing"))
|
.thenReturn(null);
|
DataException exception = assertThrows(DataException.class,
|
() -> service.destructionCandidate("missing"));
|
assertEquals("未找到待销毁的文件副本", exception.getMessage());
|
}
|
|
@Test
|
void recycleCallbackBackedCopyPreservesUnsignedFieldsAndClosesDistribution() throws Exception {
|
prepareIdentity();
|
when(mapper.insertRecycle(any())).thenReturn(1);
|
DmsWenjianHuishouMingxiVO copy = recycleRecord("print-1", "serial-1", "daifafang");
|
copy.setEarlyRecycleEligible(true);
|
copy.setDayinRenwuId("task-1");
|
copy.setQianshouRen(null);
|
copy.setQianshouBumen(null);
|
copy.setQianshouShijian(null);
|
when(mapper.selectRecyclePrintsForUpdate("tenant-1", Arrays.asList("print-1")))
|
.thenReturn(Collections.singletonList(copy));
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1")).thenReturn(printTask("task-1"));
|
when(mapper.markPrintsRecycled(any(), any(), any(), any())).thenReturn(1);
|
when(mapper.insertRecycleDetail(any())).thenReturn(1);
|
service.recycle(recycleForm("print-1"));
|
verify(reprintGuard).requireUnlocked("tenant-1", "print-1");
|
verify(mapper).completeDistributionAfterRecycle(eq("tenant-1"), eq("task-1"), eq("user-1"), any());
|
ArgumentCaptor<DmsWenjianHuishouAuditEvent> audit = ArgumentCaptor.forClass(DmsWenjianHuishouAuditEvent.class);
|
verify(auditAdapter).recycle(audit.capture());
|
assertEquals("daifafang", audit.getValue().getBeforeStatuses().get("print-1"));
|
assertNull(audit.getValue().getDetails().get(0).getQianshouRen());
|
assertNull(audit.getValue().getDetails().get(0).getQianshouShijian());
|
}
|
|
@Test
|
void recycleRejectsAnActiveReprintBeforeStateChanges() throws Exception {
|
prepareIdentity();
|
when(mapper.insertRecycle(any())).thenReturn(1);
|
DmsWenjianHuishouMingxiVO copy = recycleRecord("print-1", "serial-1", "daifafang");
|
copy.setEarlyRecycleEligible(true);
|
when(mapper.selectRecyclePrintsForUpdate("tenant-1", Arrays.asList("print-1")))
|
.thenReturn(Collections.singletonList(copy));
|
org.mockito.Mockito.doThrow(new DataException("重打占用"))
|
.when(reprintGuard).requireUnlocked("tenant-1", "print-1");
|
assertThrows(DataException.class, () -> service.recycle(recycleForm("print-1")));
|
verify(mapper, never()).markPrintsRecycled(any(), any(), any(), any());
|
verify(mapper, never()).insertRecycleDetail(any());
|
verify(auditAdapter, never()).recycle(any());
|
}
|
|
@Test
|
void recycleRejectsWhenTheGeneratedMainRecordCannotBeInserted() {
|
prepareIdentity();
|
when(mapper.insertRecycle(any())).thenReturn(0);
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.recycle(recycleForm("print-1")));
|
|
assertEquals("保存回收单失败", exception.getMessage());
|
verify(mapper, never()).selectRecyclePrintsForUpdate(any(), any());
|
verify(auditAdapter, never()).recycle(any());
|
}
|
|
@Test
|
void recycleRejectsMixedStatusesBeforeUpdatingAnyRecord() {
|
prepareIdentity();
|
when(mapper.insertRecycle(any())).thenReturn(1);
|
when(mapper.selectRecyclePrintsForUpdate("tenant-1", Arrays.asList("print-1", "print-2")))
|
.thenReturn(Arrays.asList(recycleRecord("print-1", "serial-1", "daihuishou"),
|
recycleRecord("print-2", "serial-2", "daixiaohui")));
|
|
DataException exception = assertThrows(DataException.class,
|
() -> service.recycle(recycleForm("print-1", "print-2")));
|
|
assertEquals("文件副本尚未满足回收条件或已处理:serial-2", exception.getMessage());
|
verify(mapper, never()).markPrintsRecycled(any(), any(), any(), any());
|
verify(mapper, never()).insertRecycleDetail(any());
|
}
|
|
@Test
|
void recycleRejectsDuplicateDetailBeforeCreatingMainRecord() {
|
DataException exception = assertThrows(DataException.class,
|
() -> service.recycle(recycleForm("print-1", "print-1")));
|
|
assertEquals("打印记录不能重复回收:print-1", exception.getMessage());
|
verify(mapper, never()).insertRecycle(any());
|
verify(auditAdapter, never()).recycle(any());
|
}
|
|
private void prepareIdentity() {
|
when(identity.requireUser()).thenReturn(user);
|
when(identity.tenantId(user)).thenReturn("tenant-1");
|
}
|
|
private UserRelationIds userRelationIds(String organizationId) {
|
return UserRelationIds.builder()
|
.organize(Collections.singletonList(organizationId))
|
.build();
|
}
|
|
private void prepareHandoverRecord() {
|
DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity();
|
record.setId("print-1");
|
record.setDayinRenwuId("task-1");
|
record.setBizStatus("daifafang");
|
when(mapper.selectPrintForUpdate("tenant-1", "print-1", null)).thenReturn(record);
|
when(mapper.selectDistributionTaskByPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(distributionTask());
|
when(mapper.selectPrintTaskForUpdate("tenant-1", "task-1"))
|
.thenReturn(printTask("task-1"));
|
}
|
|
private DmsWenjianQianshouFafangForm handoverForm() {
|
DmsWenjianQianshouFafangForm form = new DmsWenjianQianshouFafangForm();
|
form.setBizUserId("user-1");
|
form.setBizReviewUser("receiver-1");
|
form.setBizSign("issuer-signature-data");
|
form.setBizReviewSign("receiver-signature-data");
|
return form;
|
}
|
|
private DmsWenjianHuishouForm recycleForm(String... recordIds) {
|
DmsWenjianHuishouForm form = new DmsWenjianHuishouForm();
|
form.setBizSign("recycle-signature-data");
|
java.util.List<DmsWenjianHuishouMingxiForm> details = new java.util.ArrayList<>();
|
for (String recordId : recordIds) {
|
DmsWenjianHuishouMingxiForm detail = new DmsWenjianHuishouMingxiForm();
|
detail.setDayinJiluId(recordId);
|
details.add(detail);
|
}
|
form.setHuishouMingxi(details);
|
return form;
|
}
|
|
private DmsWenjianHuishouMingxiVO recycleRecord(
|
String id, String serialNumber, String status) {
|
DmsWenjianHuishouMingxiVO result = new DmsWenjianHuishouMingxiVO();
|
result.setDayinJiluId(id);
|
result.setDayinLiushuihao(serialNumber);
|
result.setWenjianBianhao("DOC-001");
|
result.setWenjianMingcheng("文件一");
|
result.setBanbenhao("D(00)");
|
result.setBizStatus(status);
|
return result;
|
}
|
|
private UserEntity user(String id, String departmentId, Integer enabledMark) {
|
UserEntity result = new UserEntity();
|
result.setId(id);
|
result.setOrganizeId(departmentId);
|
result.setEnabledMark(enabledMark);
|
return result;
|
}
|
|
private DmsWenjianFenfaFanweiEntity scope(String id, Integer copies) {
|
DmsWenjianFenfaFanweiEntity scope = new DmsWenjianFenfaFanweiEntity();
|
scope.setId(id);
|
scope.setBumenId("target-org-1");
|
scope.setWenjianLeibie("受控副本");
|
scope.setFenshu(copies);
|
return scope;
|
}
|
|
private DmsWenjianDayinRenwuEntity printTask(String id) {
|
DmsWenjianDayinRenwuEntity task = new DmsWenjianDayinRenwuEntity();
|
task.setId(id);
|
task.setTenantId("tenant-1");
|
task.setLaiyuanLeixing("wenjian_xinzeng_shenhe");
|
task.setLaiyuanDanjuId("audit-1");
|
task.setJieshouBumenId("target-org-1");
|
task.setDayinFenshu(2);
|
task.setBizStatus("daidayin");
|
return task;
|
}
|
|
private DmsWenjianFenfaRenwuEntity distributionTask() {
|
DmsWenjianFenfaRenwuEntity task = new DmsWenjianFenfaRenwuEntity();
|
task.setId("distribution-1");
|
task.setDayinRenwuId("task-1");
|
task.setBizStatus("daifafang");
|
return task;
|
}
|
|
private DmsWenjianDayinRenwuEntity snapshot() {
|
DmsWenjianDayinRenwuEntity snapshot = new DmsWenjianDayinRenwuEntity();
|
snapshot.setWenjianBianhao("DOC-001");
|
snapshot.setWenjianMingcheng("文件一");
|
snapshot.setBanbenhao("D(00)");
|
return snapshot;
|
}
|
}
|