package cn.iocoder.yudao.module.mes.service.pro.bagcode; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeGenerateTaskMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeReplaceMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper; import cn.iocoder.yudao.module.mes.dal.redis.MesCodeRedisDAO; import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateProperties; import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder; import cn.iocoder.yudao.module.mes.framework.qrcode.QrCodeImageUtils; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.hutool.extra.qrcode.QrCodeUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.test.util.ReflectionTestUtils; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class BagBusinessCodeTest { private final MesProBagCodeServiceImpl service = new MesProBagCodeServiceImpl(); private final MesProBagCodeMapper bags = mock(MesProBagCodeMapper.class); private final MesCodeRedisDAO codes = mock(MesCodeRedisDAO.class); private final MesMdmItemService items = mock(MesMdmItemService.class); private final MesProBatchMapper batches = mock(MesProBatchMapper.class); private final MesProBagCodeReplaceMapper replacements = mock(MesProBagCodeReplaceMapper.class); private final MdmUnitMeasureApi units = mock(MdmUnitMeasureApi.class); private final MesProBagCodeChunkWriter writer = mock(MesProBagCodeChunkWriter.class); private final MesProBagCodeBatchGenerator generator = new MesProBagCodeBatchGenerator(); private final MesProBagCodeGenerateProperties properties = new MesProBagCodeGenerateProperties(); @BeforeEach void setup() { ReflectionTestUtils.setField(service, "bagCodeMapper", bags); ReflectionTestUtils.setField(service, "codeRedisDao", codes); ReflectionTestUtils.setField(service, "mdmItemService", items); ReflectionTestUtils.setField(service, "batchMapper", batches); ReflectionTestUtils.setField(service, "replaceMapper", replacements); ReflectionTestUtils.setField(service, "unitMeasureApi", units); ReflectionTestUtils.setField(service, "batchGenerator", generator); ReflectionTestUtils.setField(service, "generateProperties", properties); ReflectionTestUtils.setField(service, "generateCleaner", mock(MesProBagCodeGenerateCleaner.class)); ReflectionTestUtils.setField(service, "generateTaskService", mock(MesProBagCodeGenerateTaskService.class)); // 分段执行器使用真实实现,仅把事务写入器与 Redis 取码替换为 mock ReflectionTestUtils.setField(generator, "bagCodeMapper", bags); ReflectionTestUtils.setField(generator, "taskMapper", mock(MesProBagCodeGenerateTaskMapper.class)); ReflectionTestUtils.setField(generator, "codeRedisDao", codes); ReflectionTestUtils.setField(generator, "chunkWriter", writer); ReflectionTestUtils.setField(generator, "properties", properties); doAnswer(invocation -> { bags.insertBatch(invocation.getArgument(0)); return null; }).when(writer).insertBagCodes(anyList()); doAnswer(invocation -> { List pallets = invocation.getArgument(0); for (int i = 0; i < pallets.size(); i++) { pallets.get(i).setId((long) (i + 1)); } return null; }).when(writer).insertPallets(anyList()); when(items.validateItemExists(1L)).thenReturn(new MesMdmItemDO().setId(1L).setUnitMeasureId(2L)); AtomicLong sequence = new AtomicLong(1000000000000000L); when(codes.generate()).thenAnswer(invocation -> String.valueOf(sequence.incrementAndGet())); when(codes.generateBatch(anyInt())).thenAnswer(invocation -> { int count = invocation.getArgument(0); List codes = new ArrayList<>(count); for (int i = 0; i < count; i++) { codes.add(String.valueOf(sequence.incrementAndGet())); } return codes; }); when(codes.allocateBagNoStart(anyInt(), any())).thenReturn(1); } @Test void rawGenerationCreatesDistinctPairsInSameRecords() { MesProBagCodeRawGenerateReqVO req = new MesProBagCodeRawGenerateReqVO(); req.setItemId(1L); req.setCount(2); service.generateRawBagCodes(req); assertDistinctPairs(2); } @Test void dataPackageGenerationCreatesBusinessCodes() { when(units.getUnitMeasure(2L)).thenReturn(CommonResult.success(new MdmUnitMeasureRespDTO().setType("吨"))); MesProBagCodeGenerateReqVO req = new MesProBagCodeGenerateReqVO(); req.setItemId(1L); req.setBagCount(2); service.generateBagCodes(req); assertDistinctPairs(2); } @Test @SuppressWarnings({"unchecked", "rawtypes"}) void automaticPalletCarriesVariety() { when(units.getUnitMeasure(2L)).thenReturn(CommonResult.success(new MdmUnitMeasureRespDTO().setType("48袋"))); var req=new MesProBagCodeGenerateReqVO();req.setItemId(1L);req.setPalletCount(1); service.generateBagCodes(req); ArgumentCaptor> saved=ArgumentCaptor.forClass((Class)List.class); verify(writer).insertPallets(saved.capture());assertEquals(1L,saved.getValue().iterator().next().getItemId()); } @Test void replacementIssuesNewPairInsteadOfReusingOldBusinessCode() { when(batches.selectByIdForUpdate(2L)).thenReturn(new MesProBatchDO().setId(2L).setStatus(20)); when(bags.selectListByIdsForUpdate(List.of(1L))).thenReturn(List.of(new MesProBagCodeDO() .setId(1L).setBatchId(2L).setStatus(10).setCode("9999999999999999").setBusinessCode("8888888888888888"))); when(bags.cancelById(eq(1L), eq(10), eq(30), any(), any())).thenReturn(1); MesProBagCodeReplaceReqVO req = new MesProBagCodeReplaceReqVO(); req.setBatchId(2L); req.setOldIds(List.of(1L)); req.setReason("破损"); service.replaceBagCodes(req); assertDistinctPairs(1); } @Test void supplementationLocksAndNeverChangesExistingCode() { when(bags.selectListByIdsForUpdate(List.of(1L, 2L))).thenReturn(List.of( new MesProBagCodeDO().setId(1L).setStatus(10), new MesProBagCodeDO().setId(2L).setStatus(20).setBusinessCode("8888888888888888"))); assertEquals(1, service.generateBusinessCodes(List.of(2L, 1L, 1L))); ArgumentCaptor update = ArgumentCaptor.forClass(MesProBagCodeDO.class); verify(bags).updateById(update.capture()); assertEquals(1L, update.getValue().getId()); assertNull(update.getValue().getCode()); assertNotNull(update.getValue().getBusinessCode()); when(bags.selectListByIdsForUpdate(List.of(1L))).thenReturn(List.of(update.getValue().setStatus(10))); assertEquals(0, service.generateBusinessCodes(List.of(1L))); verify(codes, times(1)).generate(); } @Test void canceledOrMissingBagCannotBeSupplemented() { when(bags.selectListByIdsForUpdate(List.of(1L))).thenReturn(List.of(new MesProBagCodeDO().setId(1L).setStatus(30))); assertThrows(ServiceException.class, () -> service.generateBusinessCodes(List.of(1L))); assertThrows(ServiceException.class, () -> service.generateBusinessCodes(List.of(2L))); verifyNoInteractions(codes); verify(bags, never()).updateById(any(MesProBagCodeDO.class)); } @Test void publicLookupUsesTraceColumnAndInternalLookupUsesBoth() { service.getBagCodeByCode("1111111111111111"); verify(bags).selectByCode("1111111111111111"); service.getBagCodeByScanCode("2222222222222222"); verify(bags).selectByScanCode("2222222222222222"); } @Test void businessQrEncodesStoredValueAndRejectsMissingCode() { var label = MesProQrLabelBuilder.buildBagBusinessCodeLabel(new MesProBagCodeDO() .setCode("9999999999999999").setBusinessCode("8888888888888888")); assertEquals("8888888888888888", QrCodeUtil.decode(QrCodeImageUtils.createQrLabel(label.getCode(), label.getFileName()))); assertThrows(ServiceException.class, () -> MesProQrLabelBuilder.buildBagBusinessCodeLabel(new MesProBagCodeDO())); } @SuppressWarnings({"unchecked", "rawtypes"}) private void assertDistinctPairs(int count) { ArgumentCaptor> saved = ArgumentCaptor.forClass((Class) Collection.class); verify(bags).insertBatch(saved.capture()); assertEquals(count, saved.getValue().size()); var values = new HashSet(); for (MesProBagCodeDO bag : saved.getValue()) { assertTrue(bag.getBusinessCode().matches("[0-9]{16}")); assertTrue(values.add(bag.getCode())); assertTrue(values.add(bag.getBusinessCode())); } } }