package jnpf.dmsService.impl; import jnpf.base.Pagination; import jnpf.base.UserInfo; import jnpf.dmsEntity.DmsWenjianBianhaoEntity; import jnpf.dmsEntity.DmsWenjianBianhaoGuizeEntity; import jnpf.dmsEntity.DmsWenjianBianhaoGuizeZixiangEntity; import jnpf.dmsEntity.DmsWenjianBianhaoShenqingEntity; import jnpf.dmsEntity.DmsWenjianMingchengBiangengShenqingEntity; import jnpf.dmsEntity.wenjianbianhao.DmsFlowEventType; import jnpf.dmsMapper.DmsWenjianBianhaoGuizeMapper; import jnpf.dmsMapper.DmsWenjianBianhaoGuizeZixiangMapper; import jnpf.dmsMapper.DmsWenjianBianhaoLiushuiJishuMapper; import jnpf.dmsMapper.DmsWenjianBianhaoMapper; import jnpf.dmsMapper.DmsWenjianBianhaoShenqingMapper; import jnpf.dmsMapper.DmsWenjianMingchengBiangengShenqingMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsService.DmsWenjianBianhaoPermissionService; import jnpf.exception.DataException; import jnpf.permission.UserApi; import jnpf.permission.entity.UserEntity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Arrays; import java.util.Collections; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class DmsWenjianBianhaoApplicationServiceImplTest { private static final String TENANT_ID = "tenant-1"; private static final String FLOW_ID = "flow-1"; private static final String TASK_ID = "task-1"; @Mock private DmsCurrentIdentity identity; @Mock private DmsWenjianBianhaoPermissionService permissionService; @Mock private DmsWenjianBianhaoGuizeMapper ruleMapper; @Mock private DmsWenjianBianhaoGuizeZixiangMapper partMapper; @Mock private DmsWenjianBianhaoShenqingMapper applicationMapper; @Mock private DmsWenjianBianhaoMapper numberMapper; @Mock private DmsWenjianBianhaoLiushuiJishuMapper counterMapper; @Mock private DmsWenjianMingchengBiangengShenqingMapper nameChangeMapper; @Mock private UserApi userApi; @InjectMocks private DmsWenjianBianhaoApplicationServiceImpl service; @BeforeEach void setUpIdentity() { UserInfo user = new UserInfo(); user.setUserId("operator-1"); user.setTenantId(TENANT_ID); when(identity.requireUser()).thenReturn(user); lenient().when(identity.tenantId(user)).thenReturn(TENANT_ID); } @Test void enabledRulesArePermissionFilteredBeforePaging() throws DataException { Pagination pagination = new Pagination(); pagination.setCurrentPage(1); pagination.setPageSize(20); DmsWenjianBianhaoGuizeEntity allowed = rule("rule-1", "QC-SOP规则"); allowed.setGuizeLeixing("SOP"); allowed.setWenjianMulu("/质量控制部QC/SOP"); DmsWenjianBianhaoGuizeEntity denied = rule("rule-2", "QA-SOP规则"); when(ruleMapper.selectEnabled(TENANT_ID)).thenReturn(Arrays.asList(allowed, denied)); when(permissionService.canApplyRule("operator-1", "rule-1")).thenReturn(true); when(permissionService.canApplyRule("operator-1", "rule-2")).thenReturn(false); when(ruleMapper.selectActiveById(TENANT_ID, "rule-1")).thenReturn(allowed); when(partMapper.selectActiveByRule(TENANT_ID, "rule-1")).thenReturn(Arrays.asList( part("1", "gudingzhi", "QC", "-"), part("2", "liushuihao", "000", ""))); jnpf.dmsEntity.wenjianbianhao.vo.DmsWenjianBianhaoRuleVO result = service.enabledRules(pagination, null, "SOP").get(0); assertEquals("rule-1", result.getId()); assertEquals("QC-待审", result.getYulanBianhao()); assertEquals("QC-SOP规则 / QC-待审", result.getGuizeXianshi()); assertEquals(1, pagination.getTotal()); verify(permissionService).canApplyRule("operator-1", "rule-1"); verify(permissionService).canApplyRule("operator-1", "rule-2"); } @Test void rejectsEventUntilFormAndWorkflowStatesMatch() { DmsWenjianBianhaoShenqingEntity application = application(1); when(applicationMapper.selectByFlowForUpdate(TENANT_ID, FLOW_ID, TASK_ID)) .thenReturn(Collections.singletonList(application)); assertThrows(DataException.class, () -> service.syncApplicationFlow( FLOW_ID, TASK_ID, 2, DmsFlowEventType.END)); verify(numberMapper, never()).selectByApplicationForUpdate(any(), any()); } @Test void startEventFreezesOrderedRuleSnapshotAndUsesApplicantPermission() { DmsWenjianBianhaoShenqingEntity application = application(1); application.setShenqingRenId("applicant-1"); DmsWenjianBianhaoEntity number = number("number-1", "rule-1", "设备清洁规程"); when(applicationMapper.selectByFlowForUpdate(TENANT_ID, FLOW_ID, TASK_ID)) .thenReturn(Collections.singletonList(application)); when(numberMapper.selectByApplicationForUpdate(TENANT_ID, application.getId())) .thenReturn(Collections.singletonList(number)); when(permissionService.canApplyRule("applicant-1", "rule-1")).thenReturn(true); when(ruleMapper.selectActiveById(TENANT_ID, "rule-1")) .thenReturn(rule("rule-1", "QC-SOP规则")); when(partMapper.selectActiveByRule(TENANT_ID, "rule-1")).thenReturn(Arrays.asList( part("1", "gudingzhi", "QC", "-"), part("2", "liushuihao", "000", ""))); when(numberMapper.updateById(number)).thenReturn(1); service.syncApplicationFlow(FLOW_ID, TASK_ID, 1, DmsFlowEventType.START); service.syncApplicationFlow(FLOW_ID, TASK_ID, 1, DmsFlowEventType.START); assertEquals("pending_review", number.getBizStatus()); assertEquals("rule-1", number.getGuizeKuaizhaoId()); assertEquals("QC-{SERIAL:3}", number.getBianhaoMuban()); assertEquals("QC-待审", number.getYulanBianhao()); assertEquals("QC-待审", number.getShenqingBianhao()); assertEquals("设备清洁规程", number.getDangqianWenjianMingcheng()); assertEquals(1, number.getMingchengBanben()); verify(permissionService).canApplyRule("applicant-1", "rule-1"); verify(ruleMapper, times(1)).selectActiveById(TENANT_ID, "rule-1"); verify(partMapper, times(1)).selectActiveByRule(TENANT_ID, "rule-1"); verify(numberMapper, times(1)).updateById(number); } @Test void repeatedPassedEventDoesNotConsumeAnotherSerial() { DmsWenjianBianhaoShenqingEntity application = application(2); DmsWenjianBianhaoEntity number = number("number-1", "rule-1", "设备清洁规程"); number.setGuizeKuaizhao("[{}]"); number.setGuizeKuaizhaoId("rule-1"); number.setBianhaoMuban("QC-{SERIAL:3}"); number.setBizStatus("pending_review"); when(applicationMapper.selectByFlowForUpdate(TENANT_ID, FLOW_ID, TASK_ID)) .thenReturn(Collections.singletonList(application)); when(numberMapper.selectByApplicationForUpdate(TENANT_ID, application.getId())) .thenReturn(Collections.singletonList(number)); when(counterMapper.next(TENANT_ID, "rule-1", "QC-{SERIAL:3}")).thenReturn(7L); when(numberMapper.updateById(number)).thenReturn(1); service.syncApplicationFlow(FLOW_ID, TASK_ID, 2, DmsFlowEventType.END); service.syncApplicationFlow(FLOW_ID, TASK_ID, 2, DmsFlowEventType.END); assertEquals("QC-007", number.getWenjianBianhao()); assertEquals("QC-007", number.getShenqingBianhao()); assertEquals("007", number.getLiushuiHao()); assertEquals("using", number.getBizStatus()); org.junit.jupiter.api.Assertions.assertNotNull(number.getBianhaoFafangShijian()); verify(counterMapper, times(1)).next(TENANT_ID, "rule-1", "QC-{SERIAL:3}"); verify(numberMapper, times(1)).updateById(number); } @Test void usingNumbersReturnsPopupFieldsFromTenantScopedQuery() throws DataException { Pagination pagination = new Pagination(); pagination.setCurrentPage(1); pagination.setPageSize(20); DmsWenjianBianhaoEntity number = number("number-1", "rule-1", "原文件名称"); number.setWenjianBianhao("QC-001"); number.setDangqianWenjianMingcheng("当前文件名称"); number.setMingchengBanben(2); number.setGuizeMingchengKuaizhao("QC-SOP规则"); number.setShiyongRenId("user-1"); number.setBianhaoFafangShijian(new Date()); when(numberMapper.selectUsing(TENANT_ID, "number-1", pagination)) .thenReturn(Collections.singletonList(number)); when(permissionService.canViewNumber("operator-1", "number-1")).thenReturn(true); UserEntity user = new UserEntity(); user.setId("user-1"); user.setRealName("张三"); when(userApi.getUserName(Collections.singletonList("user-1"))) .thenReturn(Collections.singletonList(user)); jnpf.dmsEntity.wenjianbianhao.vo.DmsWenjianBianhaoOptionVO result = service.usingNumbers(pagination, " number-1 ", "当前").get(0); assertEquals("number-1", result.getId()); assertEquals("QC-001", result.getWenjianBianhao()); assertEquals("当前文件名称", result.getWenjianMingcheng()); assertEquals(2, result.getMingchengBanben()); assertEquals("张三", result.getShiyongRen()); assertEquals("QC-001 / 当前文件名称", result.getBianhaoXianshi()); assertEquals("当前", pagination.getKeyword()); verify(permissionService).canViewNumber("operator-1", "number-1"); verify(numberMapper).selectUsing(TENANT_ID, "number-1", pagination); } @Test void usingNumbersExcludeRowsDeniedByPermissionService() throws DataException { Pagination pagination = new Pagination(); pagination.setCurrentPage(1); pagination.setPageSize(20); DmsWenjianBianhaoEntity denied = number("number-1", "rule-1", "受限文件"); denied.setWenjianBianhao("QA-001"); when(numberMapper.selectUsing(TENANT_ID, "", pagination)) .thenReturn(Collections.singletonList(denied)); when(permissionService.canViewNumber("operator-1", "number-1")).thenReturn(false); assertEquals(0, service.usingNumbers(pagination, null, null).size()); verify(permissionService).canViewNumber("operator-1", "number-1"); verify(userApi, never()).getUserName(any()); } @Test void recallOnlyRestoresUnissuedNumberToDraft() { DmsWenjianBianhaoShenqingEntity application = application(9); DmsWenjianBianhaoEntity pending = number("number-1", "rule-1", "文件一"); pending.setBizStatus("pending_review"); DmsWenjianBianhaoEntity issued = number("number-2", "rule-1", "文件二"); issued.setBizStatus("using"); issued.setWenjianBianhao("QC-001"); when(applicationMapper.selectByFlowForUpdate(TENANT_ID, FLOW_ID, TASK_ID)) .thenReturn(Collections.singletonList(application)); when(numberMapper.selectByApplicationForUpdate(TENANT_ID, application.getId())) .thenReturn(Arrays.asList(pending, issued)); when(numberMapper.updateById(pending)).thenReturn(1); service.syncApplicationFlow(FLOW_ID, TASK_ID, 9, DmsFlowEventType.RECALL); assertEquals("draft", pending.getBizStatus()); assertEquals("using", issued.getBizStatus()); verify(numberMapper).updateById(pending); verify(numberMapper, never()).updateById(issued); } @Test void nameChangePassIsVersionCheckedAndIdempotent() { DmsWenjianMingchengBiangengShenqingEntity change = new DmsWenjianMingchengBiangengShenqingEntity(); change.setId("change-1"); change.setTenantId(TENANT_ID); change.setFlowId(FLOW_ID); change.setFlowTaskId(TASK_ID); change.setFlowState(1); change.setBianhaoId("number-1"); change.setBiangengHouWenjianMingcheng("新文件名称"); DmsWenjianBianhaoEntity number = number("number-1", "rule-1", "原文件名称"); number.setBizStatus("using"); number.setWenjianBianhao("QC-001"); number.setDangqianWenjianMingcheng("原文件名称"); number.setMingchengBanben(3); when(nameChangeMapper.selectByFlowForUpdate(TENANT_ID, FLOW_ID, TASK_ID)) .thenReturn(Collections.singletonList(change)); when(numberMapper.selectActiveByIdForUpdate(TENANT_ID, "number-1")).thenReturn(number); when(nameChangeMapper.updateById(change)).thenReturn(1); when(numberMapper.updateById(number)).thenReturn(1); service.syncNameChangeFlow(FLOW_ID, TASK_ID, 1, DmsFlowEventType.START); change.setFlowState(2); service.syncNameChangeFlow(FLOW_ID, TASK_ID, 2, DmsFlowEventType.END); service.syncNameChangeFlow(FLOW_ID, TASK_ID, 2, DmsFlowEventType.END); assertEquals("原文件名称", change.getYuanWenjianMingcheng()); assertEquals(3, change.getYuanMingchengBanben()); assertEquals("新文件名称", number.getDangqianWenjianMingcheng()); assertEquals(4, number.getMingchengBanben()); verify(numberMapper, times(1)).updateById(number); } @Test void nameChangeStartRejectsAnotherActiveApplicationForTheSameNumber() { DmsWenjianMingchengBiangengShenqingEntity change = new DmsWenjianMingchengBiangengShenqingEntity(); change.setId("change-2"); change.setTenantId(TENANT_ID); change.setFlowId(FLOW_ID); change.setFlowTaskId(TASK_ID); change.setFlowState(1); change.setBianhaoId("number-1"); change.setBiangengHouWenjianMingcheng("另一个新名称"); DmsWenjianBianhaoEntity number = number("number-1", "rule-1", "原文件名称"); number.setBizStatus("using"); number.setWenjianBianhao("QC-001"); when(nameChangeMapper.selectByFlowForUpdate(TENANT_ID, FLOW_ID, TASK_ID)) .thenReturn(Collections.singletonList(change)); when(numberMapper.selectActiveByIdForUpdate(TENANT_ID, "number-1")).thenReturn(number); when(nameChangeMapper.countActiveByNumber(TENANT_ID, "number-1", "change-2")) .thenReturn(1); DataException exception = assertThrows(DataException.class, () -> service.syncNameChangeFlow(FLOW_ID, TASK_ID, 1, DmsFlowEventType.START)); assertEquals("该文件编号已有进行中的名称变更申请,请勿重复提交", exception.getMessage()); verify(nameChangeMapper, never()).updateById(change); } private DmsWenjianBianhaoShenqingEntity application(int flowState) { DmsWenjianBianhaoShenqingEntity application = new DmsWenjianBianhaoShenqingEntity(); application.setId("application-1"); application.setTenantId(TENANT_ID); application.setFlowId(FLOW_ID); application.setFlowTaskId(TASK_ID); application.setFlowState(flowState); return application; } private DmsWenjianBianhaoEntity number(String id, String ruleId, String name) { DmsWenjianBianhaoEntity number = new DmsWenjianBianhaoEntity(); number.setId(id); number.setForeignId("application-1"); number.setTenantId(TENANT_ID); number.setGuizeId(ruleId); number.setYuanWenjianMingcheng(name); return number; } private DmsWenjianBianhaoGuizeEntity rule(String id, String name) { DmsWenjianBianhaoGuizeEntity rule = new DmsWenjianBianhaoGuizeEntity(); rule.setId(id); rule.setTenantId(TENANT_ID); rule.setGuizeMingcheng(name); rule.setBizEnabled("enabled"); return rule; } private DmsWenjianBianhaoGuizeZixiangEntity part( String id, String type, String value, String separator) { DmsWenjianBianhaoGuizeZixiangEntity part = new DmsWenjianBianhaoGuizeZixiangEntity(); part.setId(id); part.setTenantId(TENANT_ID); part.setForeignId("rule-1"); part.setBianhaoZuheLeixing(type); part.setZhi(value); part.setFengefu(separator); return part; } }