fix(mes): 修复采购入库上架明细批次丢失

This commit is contained in:
YunaiV
2026-09-26 11:56:09 +08:00
parent 8d5468d592
commit a40c4c9b97
3 changed files with 101 additions and 0 deletions
@@ -467,6 +467,7 @@ public interface ErrorCodeConstants {
ErrorCode WM_ITEM_RECEIPT_LINE_ARRIVAL_NOTICE_LINE_NOT_ALLOWED = new ErrorCode(1_040_705_102,
"入库单未关联到货通知单,不能选择到货通知单行");
ErrorCode WM_ITEM_RECEIPT_DETAIL_NOT_EXISTS = new ErrorCode(1_040_705_200, "采购入库明细不存在");
ErrorCode WM_ITEM_RECEIPT_DETAIL_LINE_MISMATCH = new ErrorCode(1_040_705_201, "入库单行与上架明细的单据或物料不一致");
ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_705_202, "上架明细总数量不能超过行入库数量");
// ========== MES 仓库管理-领料申请单(1-040-706-000) ==========
@@ -8,6 +8,8 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceip
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptDetailMapper;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -15,6 +17,7 @@ import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -26,6 +29,11 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@Validated
public class MesWmItemReceiptDetailServiceImpl implements MesWmItemReceiptDetailService {
@Resource
private MesMdItemService itemService;
@Resource
private MesWmBatchService batchService;
@Resource
private MesWmItemReceiptDetailMapper itemReceiptDetailMapper;
@@ -64,6 +72,8 @@ public class MesWmItemReceiptDetailServiceImpl implements MesWmItemReceiptDetail
MesWmItemReceiptDetailDO detail = validateItemReceiptDetailExists(updateReqVO.getId());
// 1.2 校验数据
updateReqVO.setReceiptId(detail.getReceiptId());
updateReqVO.setLineId(detail.getLineId());
updateReqVO.setItemId(detail.getItemId());
validateItemReceiptDetailSaveData(updateReqVO);
// 2. 更新
@@ -77,6 +87,23 @@ public class MesWmItemReceiptDetailServiceImpl implements MesWmItemReceiptDetail
private void validateItemReceiptDetailSaveData(MesWmItemReceiptDetailSaveReqVO reqVO) {
// 校验父单据存在且为可编辑状态
itemReceiptService.validateItemReceiptEditable(reqVO.getReceiptId());
// 上架明细沿用入库单行的物料和批次,避免客户端漏传或传错批次。
MesWmItemReceiptLineDO line = itemReceiptLineService.getItemReceiptLine(reqVO.getLineId());
if (line == null) {
throw exception(WM_ITEM_RECEIPT_LINE_NOT_EXISTS);
}
if (!Objects.equals(line.getReceiptId(), reqVO.getReceiptId())
|| !Objects.equals(line.getItemId(), reqVO.getItemId())) {
throw exception(WM_ITEM_RECEIPT_DETAIL_LINE_MISMATCH);
}
reqVO.setBatchId(line.getBatchId());
if (Boolean.TRUE.equals(itemService.validateItemExists(reqVO.getItemId()).getBatchFlag())
&& reqVO.getBatchId() == null) {
throw exception(MD_ITEM_BATCH_REQUIRED);
}
if (reqVO.getBatchId() != null) {
batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId());
}
// 校验库区关系
warehouseAreaService.validateWarehouseAreaExists(reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
// 校验库位物料/批次混放规则
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.mes.service.wm.itemreceipt;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.detail.MesWmItemReceiptDetailSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptDetailMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.math.BigDecimal;
import java.util.Objects;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class MesWmItemReceiptDetailServiceImplTest extends BaseMockitoUnitTest {
@InjectMocks private MesWmItemReceiptDetailServiceImpl service;
@Mock private MesWmItemReceiptDetailMapper itemReceiptDetailMapper;
@Mock private MesWmItemReceiptService itemReceiptService;
@Mock private MesWmItemReceiptLineService itemReceiptLineService;
@Mock private MesWmWarehouseAreaService warehouseAreaService;
@Mock private MesWmMaterialStockService materialStockService;
@Mock private MesMdItemService itemService;
@Mock private MesWmBatchService batchService;
private MesWmItemReceiptDetailSaveReqVO prepare(Long batchId, boolean batchFlag) {
when(itemReceiptLineService.getItemReceiptLine(11L)).thenReturn(
new MesWmItemReceiptLineDO().setId(11L).setReceiptId(1L).setItemId(2L).setBatchId(batchId));
when(itemService.validateItemExists(2L)).thenReturn(new MesMdItemDO().setId(2L).setBatchFlag(batchFlag));
return new MesWmItemReceiptDetailSaveReqVO().setReceiptId(1L).setLineId(11L)
.setItemId(2L).setAreaId(3L).setQuantity(BigDecimal.ONE);
}
@Test
void createInheritsLineBatchBeforeMixingValidation() {
MesWmItemReceiptDetailSaveReqVO request = prepare(22L, true).setBatchId(999L);
service.createItemReceiptDetail(request);
verify(batchService).validateBatchExists(22L, 2L);
verify(materialStockService).checkAreaMixingRule(3L, 2L, 22L);
verify(itemReceiptDetailMapper).insert(argThat((MesWmItemReceiptDetailDO row) -> Objects.equals(row.getBatchId(), 22L)));
}
@Test
void missingLineBatchIsRejectedBeforeInsert() {
MesWmItemReceiptDetailSaveReqVO request = prepare(null, true);
assertServiceException(() -> service.createItemReceiptDetail(request), MD_ITEM_BATCH_REQUIRED);
verify(itemReceiptDetailMapper, never()).insert(any(MesWmItemReceiptDetailDO.class));
}
@Test
void unrelatedReceiptLineIsRejected() {
MesWmItemReceiptDetailSaveReqVO request = prepare(22L, true).setReceiptId(99L);
reset(itemService);
assertServiceException(() -> service.createItemReceiptDetail(request), WM_ITEM_RECEIPT_DETAIL_LINE_MISMATCH);
verifyNoInteractions(materialStockService);
}
@Test
void nonBatchItemCanRemainWithoutBatch() {
service.createItemReceiptDetail(prepare(null, false));
verifyNoInteractions(batchService);
verify(itemReceiptDetailMapper).insert(argThat((MesWmItemReceiptDetailDO row) -> row.getBatchId() == null));
}
}