yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java
@@ -20,6 +20,9 @@ @Schema(description = "åè´§éç¥åè¡ID", example = "1") private Long noticeLineId; @Schema(description = "éå®è®¢åæç»ID") private Long saleOrderItemId; @Schema(description = "ç©æID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "ç©æä¸è½ä¸ºç©º") private Long itemId; yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
@@ -188,11 +188,18 @@ // 2. éåæç»ï¼æ¯æ¡æç»äº§ç OUTï¼èæçº¿è¾¹åºæ£åï¼+ INï¼å®é ä»åºå¢å ï¼ for (MesWmProductReceiptDetailDO detail : details) { MesWmProductReceiptLineDO line = productReceiptLineService.getProductReceiptLine(detail.getLineId()); if (line == null || !receipt.getId().equals(line.getReceiptId()) || !detail.getItemId().equals(line.getItemId())) { throw exception(WM_PRODUCT_RECPT_NO_LINE); } Long batchId = line.getBatchId(); String batchCode = line.getBatchCode(); // 2.1 å ä»èæçº¿è¾¹åºåºåºï¼åºååå°ï¼ Long outTransactionId = wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO() .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId()) .setQuantity(detail.getQuantity().negate()) // åºååå° .setBatchId(detail.getBatchId()) .setBatchId(batchId).setBatchCode(batchCode) .setWarehouseId(virtualWarehouse.getId()).setLocationId(virtualLocation.getId()).setAreaId(virtualArea.getId()) .setCheckFlag(false) // 线边åºå 许è´åºå .setBizType(MesBizTypeConstants.WM_PRODUCT_RECPT).setBizId(receipt.getId()) @@ -201,7 +208,7 @@ wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO() .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId()) .setQuantity(detail.getQuantity()) // åºåå¢å .setBatchId(detail.getBatchId()) .setBatchId(batchId).setBatchCode(batchCode) .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId()) .setBizType(MesBizTypeConstants.WM_PRODUCT_RECPT).setBizId(receipt.getId()) .setBizCode(receipt.getCode()).setBizLineId(detail.getLineId()) yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
@@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.mes.service.wm.productsales; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.ObjUtil; @@ -60,6 +61,8 @@ @Resource @Lazy private MesQcOqcService oqcService; @Resource private ErpSaleOrderApi saleOrderApi; @Override public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { @@ -196,6 +199,16 @@ mdmItemService.validateItemExistsAndEnable(reqVO.getItemId()); // æ ¡éªå ³èåè´§éç¥åè¡ï¼å卿§ + å½å± + åæ®µä¸è´æ§ï¼ MesWmSalesNoticeLineDO noticeLine = validateSalesNoticeLine(sales, reqVO); if (noticeLine == null && sales.getSaleOrderId() != null) { if (reqVO.getSaleOrderItemId() == null) { throw exception(WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY); } var item = saleOrderApi.getSaleOrderItem(reqVO.getSaleOrderItemId()).getCheckedData(); if (item == null || !Objects.equals(item.getOrderId(), sales.getSaleOrderId()) || !Objects.equals(item.getProductId(), reqVO.getItemId())) { throw exception(WM_SALES_NOTICE_LINE_SALE_ITEM_MISMATCH); } } // æ ¹æ® batchCode è§£æ batchId fillBatchId(reqVO); return noticeLine; yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
@@ -169,7 +169,7 @@ BigDecimal totalDetailQuantity = CollectionUtils.getSumValue(details, MesWmReturnSalesDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO); // 对æ¯è¡æ°éä¸æç»æ»æ°éï¼ä¸æ»¡è¶³ç´æ¥æåº if (line.getQuantity().compareTo(totalDetailQuantity) > 0) { if (line.getQuantity() == null || line.getQuantity().compareTo(totalDetailQuantity) != 0) { MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(line.getItemId()); throw exception(WM_RETURN_SALES_DETAIL_QUANTITY_MISMATCH, item.getCode() + " " + item.getName() + " æªå®æä¸æ¶"); yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineReelTest.java
@@ -7,6 +7,9 @@ import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService; import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeLineService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,6 +21,7 @@ import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -29,6 +33,44 @@ @Mock private MesWmSalesNoticeLineService noticeService; @Mock private MesMdmItemService itemService; @Mock private MesQcOqcService oqcService; @Mock private ErpSaleOrderApi saleOrderApi; @Test void directSaleRetainsSelectedOrderItem() { var req = new MesWmProductSalesLineSaveReqVO(); req.setSalesId(1L); req.setSaleOrderItemId(5L); req.setItemId(3L); req.setQuantity(BigDecimal.ONE); when(salesService.validateProductSalesExistsAndDraft(1L)) .thenReturn(MesWmProductSalesDO.builder().id(1L).saleOrderId(4L).build()); var item = new ErpSaleOrderItemRespDTO(); item.setId(5L); item.setOrderId(4L); item.setProductId(3L); when(saleOrderApi.getSaleOrderItem(5L)).thenReturn(CommonResult.success(item)); service.createProductSalesLine(req); var line = ArgumentCaptor.forClass(MesWmProductSalesLineDO.class); verify(mapper).insert(line.capture()); assertEquals(5L, line.getValue().getSaleOrderItemId()); } @Test void directSaleRejectsItemFromAnotherOrder() { var req = new MesWmProductSalesLineSaveReqVO(); req.setSalesId(1L); req.setSaleOrderItemId(5L); req.setItemId(3L); req.setQuantity(BigDecimal.ONE); when(salesService.validateProductSalesExistsAndDraft(1L)) .thenReturn(MesWmProductSalesDO.builder().id(1L).saleOrderId(4L).build()); var item = new ErpSaleOrderItemRespDTO(); item.setOrderId(99L); item.setProductId(3L); when(saleOrderApi.getSaleOrderItem(5L)).thenReturn(CommonResult.success(item)); assertThrows(RuntimeException.class, () -> service.createProductSalesLine(req)); verify(mapper, never()).insert(any(MesWmProductSalesLineDO.class)); } private MesWmProductSalesLineSaveReqVO request() { var req = new MesWmProductSalesLineSaveReqVO(); yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesStockTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,62 @@ package cn.iocoder.yudao.module.mes.service.wm.returnsales; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesMapper; import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; 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.math.BigDecimal; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class MesWmReturnSalesStockTest { @InjectMocks private MesWmReturnSalesServiceImpl service; @Mock private MesWmReturnSalesMapper mapper; @Mock private MesWmReturnSalesLineService lineService; @Mock private MesWmReturnSalesDetailService detailService; @Mock private MesMdmItemService itemService; @Mock private MesWmTransactionService transactionService; private void setup(BigDecimal quantity) { when(mapper.selectById(1L)).thenReturn(MesWmReturnSalesDO.builder().id(1L).code("TEST") .status(MesWmReturnSalesStatusEnum.APPROVED.getStatus()).build()); when(lineService.getReturnSalesLineListByReturnId(1L)).thenReturn(List.of( MesWmReturnSalesLineDO.builder().id(2L).itemId(3L).quantity(BigDecimal.ONE).build())); when(detailService.getReturnSalesDetailListByReturnId(1L)).thenReturn(List.of( MesWmReturnSalesDetailDO.builder().lineId(2L).itemId(3L).quantity(quantity) .batchId(4L).batchCode("TEST-BATCH").warehouseId(5L).build())); } @Test void rejectsExcessStocking() { setup(BigDecimal.TWO); when(itemService.validateItemExistsAndEnable(3L)).thenReturn(MesMdmItemDO.builder().code("TEST").name("test").build()); assertThrows(ServiceException.class, () -> service.stockReturnSales(1L)); verifyNoInteractions(transactionService); } @Test void preservesBatchOnStockTransaction() { setup(BigDecimal.ONE); service.stockReturnSales(1L); ArgumentCaptor<List<MesWmTransactionSaveReqDTO>> captor = ArgumentCaptor.forClass(List.class); verify(transactionService).createTransactionList(captor.capture()); assertEquals(4L, captor.getValue().get(0).getBatchId()); assertEquals("TEST-BATCH", captor.getValue().get(0).getBatchCode()); assertEquals(BigDecimal.ONE, captor.getValue().get(0).getQuantity()); } }