Commit 053a100f5facee21ea30cb57763fe29f14e2f083
1 parent
7cca10fc
fix(mes): 修复采购入库上架明细批次丢失
Showing
3 changed files
with
101 additions
and
0 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
| ... | ... | @@ -467,6 +467,7 @@ public interface ErrorCodeConstants { |
| 467 | 467 | ErrorCode WM_ITEM_RECEIPT_LINE_ARRIVAL_NOTICE_LINE_NOT_ALLOWED = new ErrorCode(1_040_705_102, |
| 468 | 468 | "入库单未关联到货通知单,不能选择到货通知单行"); |
| 469 | 469 | ErrorCode WM_ITEM_RECEIPT_DETAIL_NOT_EXISTS = new ErrorCode(1_040_705_200, "采购入库明细不存在"); |
| 470 | + ErrorCode WM_ITEM_RECEIPT_DETAIL_LINE_MISMATCH = new ErrorCode(1_040_705_201, "入库单行与上架明细的单据或物料不一致"); | |
| 470 | 471 | ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_705_202, "上架明细总数量不能超过行入库数量"); |
| 471 | 472 | |
| 472 | 473 | // ========== MES 仓库管理-领料申请单(1-040-706-000) ========== | ... | ... |
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java
| ... | ... | @@ -8,6 +8,8 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceip |
| 8 | 8 | import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptDetailMapper; |
| 9 | 9 | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| 10 | 10 | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| 11 | +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | |
| 12 | +import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; | |
| 11 | 13 | import jakarta.annotation.Resource; |
| 12 | 14 | import org.springframework.context.annotation.Lazy; |
| 13 | 15 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -15,6 +17,7 @@ import org.springframework.validation.annotation.Validated; |
| 15 | 17 | |
| 16 | 18 | import java.math.BigDecimal; |
| 17 | 19 | import java.util.List; |
| 20 | +import java.util.Objects; | |
| 18 | 21 | |
| 19 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 20 | 23 | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -27,6 +30,11 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| 27 | 30 | public class MesWmItemReceiptDetailServiceImpl implements MesWmItemReceiptDetailService { |
| 28 | 31 | |
| 29 | 32 | @Resource |
| 33 | + private MesMdItemService itemService; | |
| 34 | + @Resource | |
| 35 | + private MesWmBatchService batchService; | |
| 36 | + | |
| 37 | + @Resource | |
| 30 | 38 | private MesWmItemReceiptDetailMapper itemReceiptDetailMapper; |
| 31 | 39 | |
| 32 | 40 | @Resource |
| ... | ... | @@ -62,6 +70,8 @@ public class MesWmItemReceiptDetailServiceImpl implements MesWmItemReceiptDetail |
| 62 | 70 | MesWmItemReceiptDetailDO detail = validateItemReceiptDetailExists(updateReqVO.getId()); |
| 63 | 71 | // 1.2 校验数据 |
| 64 | 72 | updateReqVO.setReceiptId(detail.getReceiptId()); |
| 73 | + updateReqVO.setLineId(detail.getLineId()); | |
| 74 | + updateReqVO.setItemId(detail.getItemId()); | |
| 65 | 75 | validateItemReceiptDetailSaveData(updateReqVO); |
| 66 | 76 | |
| 67 | 77 | // 2. 更新 |
| ... | ... | @@ -75,6 +85,23 @@ public class MesWmItemReceiptDetailServiceImpl implements MesWmItemReceiptDetail |
| 75 | 85 | private void validateItemReceiptDetailSaveData(MesWmItemReceiptDetailSaveReqVO reqVO) { |
| 76 | 86 | // 校验父单据存在且为可编辑状态 |
| 77 | 87 | itemReceiptService.validateItemReceiptEditable(reqVO.getReceiptId()); |
| 88 | + // 上架明细沿用入库单行的物料和批次,避免客户端漏传或传错批次。 | |
| 89 | + MesWmItemReceiptLineDO line = itemReceiptLineService.getItemReceiptLine(reqVO.getLineId()); | |
| 90 | + if (line == null) { | |
| 91 | + throw exception(WM_ITEM_RECEIPT_LINE_NOT_EXISTS); | |
| 92 | + } | |
| 93 | + if (!Objects.equals(line.getReceiptId(), reqVO.getReceiptId()) | |
| 94 | + || !Objects.equals(line.getItemId(), reqVO.getItemId())) { | |
| 95 | + throw exception(WM_ITEM_RECEIPT_DETAIL_LINE_MISMATCH); | |
| 96 | + } | |
| 97 | + reqVO.setBatchId(line.getBatchId()); | |
| 98 | + if (Boolean.TRUE.equals(itemService.validateItemExists(reqVO.getItemId()).getBatchFlag()) | |
| 99 | + && reqVO.getBatchId() == null) { | |
| 100 | + throw exception(MD_ITEM_BATCH_REQUIRED); | |
| 101 | + } | |
| 102 | + if (reqVO.getBatchId() != null) { | |
| 103 | + batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId()); | |
| 104 | + } | |
| 78 | 105 | // 校验库区关系 |
| 79 | 106 | warehouseAreaService.validateWarehouseAreaExists(reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId()); |
| 80 | 107 | // 校验库位物料/批次混放规则 | ... | ... |
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mes.service.wm.itemreceipt; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.detail.MesWmItemReceiptDetailSaveReqVO; | |
| 5 | +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; | |
| 6 | +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptLineDO; | |
| 7 | +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO; | |
| 8 | +import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptDetailMapper; | |
| 9 | +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; | |
| 10 | +import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; | |
| 11 | +import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; | |
| 12 | +import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; | |
| 13 | +import org.junit.jupiter.api.Test; | |
| 14 | +import org.mockito.InjectMocks; | |
| 15 | +import org.mockito.Mock; | |
| 16 | + | |
| 17 | +import java.math.BigDecimal; | |
| 18 | +import java.util.Objects; | |
| 19 | + | |
| 20 | +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | |
| 21 | +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; | |
| 22 | +import static org.mockito.ArgumentMatchers.*; | |
| 23 | +import static org.mockito.Mockito.*; | |
| 24 | + | |
| 25 | +class MesWmItemReceiptDetailServiceImplTest extends BaseMockitoUnitTest { | |
| 26 | + @InjectMocks private MesWmItemReceiptDetailServiceImpl service; | |
| 27 | + @Mock private MesWmItemReceiptDetailMapper itemReceiptDetailMapper; | |
| 28 | + @Mock private MesWmItemReceiptService itemReceiptService; | |
| 29 | + @Mock private MesWmItemReceiptLineService itemReceiptLineService; | |
| 30 | + @Mock private MesWmWarehouseAreaService warehouseAreaService; | |
| 31 | + @Mock private MesWmMaterialStockService materialStockService; | |
| 32 | + @Mock private MesMdItemService itemService; | |
| 33 | + @Mock private MesWmBatchService batchService; | |
| 34 | + | |
| 35 | + private MesWmItemReceiptDetailSaveReqVO prepare(Long batchId, boolean batchFlag) { | |
| 36 | + when(itemReceiptLineService.getItemReceiptLine(11L)).thenReturn( | |
| 37 | + new MesWmItemReceiptLineDO().setId(11L).setReceiptId(1L).setItemId(2L).setBatchId(batchId)); | |
| 38 | + when(itemService.validateItemExists(2L)).thenReturn(new MesMdItemDO().setId(2L).setBatchFlag(batchFlag)); | |
| 39 | + return new MesWmItemReceiptDetailSaveReqVO().setReceiptId(1L).setLineId(11L) | |
| 40 | + .setItemId(2L).setAreaId(3L).setQuantity(BigDecimal.ONE); | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Test | |
| 44 | + void createInheritsLineBatchBeforeMixingValidation() { | |
| 45 | + MesWmItemReceiptDetailSaveReqVO request = prepare(22L, true).setBatchId(999L); | |
| 46 | + service.createItemReceiptDetail(request); | |
| 47 | + verify(batchService).validateBatchExists(22L, 2L); | |
| 48 | + verify(materialStockService).checkAreaMixingRule(3L, 2L, 22L); | |
| 49 | + verify(itemReceiptDetailMapper).insert(argThat((MesWmItemReceiptDetailDO row) -> Objects.equals(row.getBatchId(), 22L))); | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Test | |
| 53 | + void missingLineBatchIsRejectedBeforeInsert() { | |
| 54 | + MesWmItemReceiptDetailSaveReqVO request = prepare(null, true); | |
| 55 | + assertServiceException(() -> service.createItemReceiptDetail(request), MD_ITEM_BATCH_REQUIRED); | |
| 56 | + verify(itemReceiptDetailMapper, never()).insert(any(MesWmItemReceiptDetailDO.class)); | |
| 57 | + } | |
| 58 | + | |
| 59 | + @Test | |
| 60 | + void unrelatedReceiptLineIsRejected() { | |
| 61 | + MesWmItemReceiptDetailSaveReqVO request = prepare(22L, true).setReceiptId(99L); | |
| 62 | + reset(itemService); | |
| 63 | + assertServiceException(() -> service.createItemReceiptDetail(request), WM_ITEM_RECEIPT_DETAIL_LINE_MISMATCH); | |
| 64 | + verifyNoInteractions(materialStockService); | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Test | |
| 68 | + void nonBatchItemCanRemainWithoutBatch() { | |
| 69 | + service.createItemReceiptDetail(prepare(null, false)); | |
| 70 | + verifyNoInteractions(batchService); | |
| 71 | + verify(itemReceiptDetailMapper).insert(argThat((MesWmItemReceiptDetailDO row) -> row.getBatchId() == null)); | |
| 72 | + } | |
| 73 | +} | ... | ... |