浏览代码

✨ feat(mes): 增强产品入库单功能,新增数量校验和明细检查

新增上架数量的正数校验,确保入库单的数量大于0。同时,增加了收货单明细为空的错误提示,提升数据完整性和用户体验。
YunaiV 4 月之前
父节点
当前提交
deb6cac081

+ 2 - 0
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productreceipt/vo/detail/MesWmProductReceiptDetailSaveReqVO.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.detail
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -27,6 +28,7 @@ public class MesWmProductReceiptDetailSaveReqVO {
 
     @Schema(description = "上架数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
     @NotNull(message = "上架数量不能为空")
+    @Positive(message = "上架数量必须大于 0")
     private BigDecimal quantity;
 
     @Schema(description = "批次编号", example = "1")

+ 1 - 0
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java

@@ -491,6 +491,7 @@ public interface ErrorCodeConstants {
     ErrorCode WM_PRODUCT_RECPT_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_712_004, "明细上架总数与行收货数量不匹配");
     ErrorCode WM_PRODUCT_RECPT_STATUS_ERROR = new ErrorCode(1_040_712_005, "收货单状态不正确");
     ErrorCode WM_PRODUCT_RECPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_712_006, "已完成或已取消的收货单不允许取消");
+    ErrorCode WM_PRODUCT_RECPT_NO_DETAIL = new ErrorCode(1_040_712_007, "收货单没有上架明细,无法执行入库");
     ErrorCode WM_PRODUCT_RECPT_LINE_NOT_EXISTS = new ErrorCode(1_040_712_100, "产品收货单行不存在");
     ErrorCode WM_PRODUCT_RECPT_DETAIL_NOT_EXISTS = new ErrorCode(1_040_712_200, "产品收货明细不存在");
 

+ 23 - 13
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptDetailServiceImpl.java

@@ -34,12 +34,10 @@ public class MesWmProductReceiptDetailServiceImpl implements MesWmProductReceipt
 
     @Override
     public Long createProductReceiptDetail(MesWmProductReceiptDetailSaveReqVO createReqVO) {
-        // 校验父单据存在且为可编辑状态
-        productReceiptService.validateProductReceiptEditable(createReqVO.getReceiptId());
-        // 校验库区关系
-        warehouseAreaService.validateWarehouseAreaExists(
-                createReqVO.getWarehouseId(), createReqVO.getLocationId(), createReqVO.getAreaId());
+        // 1. 校验关联数据
+        validateProductReceiptDetailSaveData(createReqVO);
 
+        // 2. 新增
         MesWmProductReceiptDetailDO detail = BeanUtils.toBean(createReqVO, MesWmProductReceiptDetailDO.class);
         productReceiptDetailMapper.insert(detail);
         return detail.getId();
@@ -47,15 +45,13 @@ public class MesWmProductReceiptDetailServiceImpl implements MesWmProductReceipt
 
     @Override
     public void updateProductReceiptDetail(MesWmProductReceiptDetailSaveReqVO updateReqVO) {
-        // 校验存在
+        // 1.1 校验存在
         MesWmProductReceiptDetailDO detail = validateProductReceiptDetailExists(updateReqVO.getId());
-        // 校验父单据存在且为可编辑状态
-        productReceiptService.validateProductReceiptEditable(detail.getReceiptId());
-        // 校验库区关系
-        warehouseAreaService.validateWarehouseAreaExists(
-                updateReqVO.getWarehouseId(), updateReqVO.getLocationId(), updateReqVO.getAreaId());
+        // 1.2 校验关联数据
+        updateReqVO.setReceiptId(detail.getReceiptId());
+        validateProductReceiptDetailSaveData(updateReqVO);
 
-        // 更新
+        // 2. 更新
         MesWmProductReceiptDetailDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductReceiptDetailDO.class);
         productReceiptDetailMapper.updateById(updateObj);
     }
@@ -63,7 +59,10 @@ public class MesWmProductReceiptDetailServiceImpl implements MesWmProductReceipt
     @Override
     public void deleteProductReceiptDetail(Long id) {
         // 校验存在
-        validateProductReceiptDetailExists(id);
+        MesWmProductReceiptDetailDO detail = validateProductReceiptDetailExists(id);
+        // 校验父单据存在且为可编辑状态
+        productReceiptService.validateProductReceiptEditable(detail.getReceiptId());
+
         // 删除
         productReceiptDetailMapper.deleteById(id);
     }
@@ -101,4 +100,15 @@ public class MesWmProductReceiptDetailServiceImpl implements MesWmProductReceipt
         return detail;
     }
 
+    /**
+     * 校验明细保存时的关联数据
+     */
+    private void validateProductReceiptDetailSaveData(MesWmProductReceiptDetailSaveReqVO reqVO) {
+        // 校验父单据存在且为可编辑状态
+        productReceiptService.validateProductReceiptEditable(reqVO.getReceiptId());
+        // 校验仓库层级关系
+        warehouseAreaService.validateWarehouseAreaExists(
+                reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
+    }
+
 }

+ 25 - 8
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptLineServiceImpl.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.line.Me
 import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.line.MesWmProductReceiptLineSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptLineDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptLineMapper;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -34,12 +35,15 @@ public class MesWmProductReceiptLineServiceImpl implements MesWmProductReceiptLi
     @Resource
     private MesWmProductReceiptDetailService productReceiptDetailService;
 
+    @Resource
+    private MesMdItemService itemService;
+
     @Override
     public Long createProductReceiptLine(MesWmProductReceiptLineSaveReqVO createReqVO) {
-        // 校验父单据存在且为可编辑状态
-        productReceiptService.validateProductReceiptEditable(createReqVO.getReceiptId());
+        // 1. 校验关联数据
+        validateProductReceiptLineSaveData(createReqVO);
 
-        // 新增
+        // 2. 新增
         MesWmProductReceiptLineDO line = BeanUtils.toBean(createReqVO, MesWmProductReceiptLineDO.class);
         productReceiptLineMapper.insert(line);
         return line.getId();
@@ -47,12 +51,13 @@ public class MesWmProductReceiptLineServiceImpl implements MesWmProductReceiptLi
 
     @Override
     public void updateProductReceiptLine(MesWmProductReceiptLineSaveReqVO updateReqVO) {
-        // 校验存在
+        // 1.1 校验存在
         MesWmProductReceiptLineDO line = validateProductReceiptLineExists(updateReqVO.getId());
-        // 校验父单据存在且为可编辑状态
-        productReceiptService.validateProductReceiptEditable(line.getReceiptId());
+        // 1.2 校验关联数据
+        updateReqVO.setReceiptId(line.getReceiptId());
+        validateProductReceiptLineSaveData(updateReqVO);
 
-        // 更新
+        // 2. 更新
         MesWmProductReceiptLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductReceiptLineDO.class);
         productReceiptLineMapper.updateById(updateObj);
     }
@@ -61,7 +66,9 @@ public class MesWmProductReceiptLineServiceImpl implements MesWmProductReceiptLi
     @Transactional(rollbackFor = Exception.class)
     public void deleteProductReceiptLine(Long id) {
         // 校验存在
-        validateProductReceiptLineExists(id);
+        MesWmProductReceiptLineDO line = validateProductReceiptLineExists(id);
+        // 校验父单据存在且为可编辑状态
+        productReceiptService.validateProductReceiptEditable(line.getReceiptId());
 
         // 级联删除明细
         productReceiptDetailService.deleteProductReceiptDetailByLineId(id);
@@ -97,4 +104,14 @@ public class MesWmProductReceiptLineServiceImpl implements MesWmProductReceiptLi
         return line;
     }
 
+    /**
+     * 校验行保存时的关联数据
+     */
+    private void validateProductReceiptLineSaveData(MesWmProductReceiptLineSaveReqVO reqVO) {
+        // 校验父单据存在且为可编辑状态
+        productReceiptService.validateProductReceiptEditable(reqVO.getReceiptId());
+        // 校验物料存在
+        itemService.validateItemExists(reqVO.getItemId());
+    }
+
 }

+ 28 - 22
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java

@@ -12,15 +12,14 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderD
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDetailDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptLineDO;
-import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptMapper;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
-import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductReceiptStatusEnum;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptMapper;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductReceiptStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
-import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
@@ -53,9 +52,6 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
     @Resource
     private MesWmProductReceiptDetailService productReceiptDetailService;
 
-    @Resource
-    private MesWmMaterialStockService materialStockService;
-
     @Resource
     private MesWmTransactionService wmTransactionService;
 
@@ -70,13 +66,10 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
 
     @Override
     public Long createProductReceipt(MesWmProductReceiptSaveReqVO createReqVO) {
-        // 校验编码唯一
-        validateCodeUnique(null, createReqVO.getCode());
-        // 校验工单存在并设置 itemId
-        MesProWorkOrderDO workOrder = createReqVO.getWorkOrderId() != null ?
-                workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()) : null;
+        // 1. 校验关联数据
+        MesProWorkOrderDO workOrder = validateProductReceiptSaveData(createReqVO);
 
-        // 插入
+        // 2. 插入
         MesWmProductReceiptDO receipt = BeanUtils.toBean(createReqVO, MesWmProductReceiptDO.class);
         if (workOrder != null) {
             receipt.setItemId(workOrder.getProductId());
@@ -88,15 +81,12 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
 
     @Override
     public void updateProductReceipt(MesWmProductReceiptSaveReqVO updateReqVO) {
-        // 校验存在 + 草稿状态
+        // 1.1 校验存在 + 草稿状态
         validateProductReceiptExistsAndDraft(updateReqVO.getId());
-        // 校验编码唯一
-        validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
-        // 校验工单存在
-        MesProWorkOrderDO workOrder = updateReqVO.getWorkOrderId() != null ?
-                workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId()) : null;
+        // 1.2 校验关联数据
+        MesProWorkOrderDO workOrder = validateProductReceiptSaveData(updateReqVO);
 
-        // 更新
+        // 2. 更新
         MesWmProductReceiptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductReceiptDO.class);
         if (workOrder != null) {
             updateObj.setItemId(workOrder.getProductId());
@@ -179,23 +169,27 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
         if (ObjUtil.notEqual(MesWmProductReceiptStatusEnum.APPROVED.getStatus(), receipt.getStatus())) {
             throw exception(WM_PRODUCT_RECPT_STATUS_ERROR);
         }
+        // 校验明细非空
+        List<MesWmProductReceiptDetailDO> details = productReceiptDetailService.getProductReceiptDetailListByRecptId(id);
+        if (CollUtil.isEmpty(details)) {
+            throw exception(WM_PRODUCT_RECPT_NO_DETAIL);
+        }
 
         // 创建库存事务
-        createTransactionList(receipt);
+        createTransactionList(receipt, details);
 
         // 更新收货单状态
         productReceiptMapper.updateById(new MesWmProductReceiptDO()
                 .setId(id).setStatus(MesWmProductReceiptStatusEnum.FINISHED.getStatus()));
     }
 
-    private void createTransactionList(MesWmProductReceiptDO receipt) {
+    private void createTransactionList(MesWmProductReceiptDO receipt, List<MesWmProductReceiptDetailDO> details) {
         // 1. 查询虚拟线边库
         MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
         MesWmWarehouseLocationDO virtualLocation = locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION);
         MesWmWarehouseAreaDO virtualArea = areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA);
 
         // 2. 遍历明细,每条明细产生 OUT(虚拟线边库扣减)+ IN(实际仓库增加)
-        List<MesWmProductReceiptDetailDO> details = productReceiptDetailService.getProductReceiptDetailListByRecptId(receipt.getId());
         for (MesWmProductReceiptDetailDO detail : details) {
             // 2.1 先从虚拟线边库出库(库存减少)
             Long outTransactionId = wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO()
@@ -263,6 +257,18 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
         }
         return receipt;
     }
+    /**
+     * 校验保存时的关联数据
+     *
+     * @return 工单对象(可能为 null)
+     */
+    private MesProWorkOrderDO validateProductReceiptSaveData(MesWmProductReceiptSaveReqVO reqVO) {
+        // 校验编码唯一
+        validateCodeUnique(reqVO.getId(), reqVO.getCode());
+        // 校验工单存在
+        return reqVO.getWorkOrderId() != null ?
+                workOrderService.validateWorkOrderExists(reqVO.getWorkOrderId()) : null;
+    }
 
     private void validateCodeUnique(Long id, String code) {
         MesWmProductReceiptDO receipt = productReceiptMapper.selectByCode(code);