liu
2026-10-24 15edc521582e08e0e59ee5da9eaec2f2049bb462
fix(mes): 自动生成生产入库及倒冲领料单
已修改9个文件
230 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.mes.service.pro.feedback;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -33,6 +32,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
@@ -92,6 +92,8 @@
    private MesProTaskService taskService;
    @Resource
    private MesWmItemConsumeService itemConsumeService;
    @Resource
    private MesWmProductIssueService productIssueService;
    @Resource
    private MesWmProductProduceService productProduceService;
    @Resource
@@ -274,9 +276,9 @@
        // 3. 物料消耗:倒冲工序按 BOM 自动扣料;非倒冲工序跳过(物料走手工领料/投料)
        boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag());
        if (backflushFlag) {
            // 3.1 校验倒冲工序必须配置 BOM 消耗,避免静默不扣料
            List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId(
                    feedback.getWorkOrderId());
            // 3.1 校验当前倒冲工序必须配置投料 BOM,避免静默不扣料
            List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId(
                    feedback.getWorkOrderId(), feedback.getProcessId());
            if (CollUtil.isEmpty(boms)) {
                throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED);
            }
@@ -284,6 +286,8 @@
            MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
            if (itemConsume != null) {
                itemConsumeService.finishItemConsume(itemConsume.getId());
                // 消耗单负责实际扣库;领料单仅作为倒冲业务凭证,避免重复扣减库存
                productIssueService.generateBackflushProductIssue(feedback, itemConsume);
            }
        }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomService.java
@@ -63,6 +63,15 @@
    List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderId(Long workOrderId);
    /**
     * 根据工单编号与工序编号获得 BOM 列表(倒冲仅处理当前报工工序的投料)
     *
     * @param workOrderId 工单编号
     * @param processId 工序编号
     * @return BOM 列表
     */
    List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderIdAndProcessId(Long workOrderId, Long processId);
    /**
     * 根据工单编号删除 BOM
     *
     * @param workOrderId 工单编号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderBomServiceImpl.java
@@ -116,6 +116,11 @@
    }
    @Override
    public List<MesProWorkOrderBomDO> getWorkOrderBomListByWorkOrderIdAndProcessId(Long workOrderId, Long processId) {
        return workOrderBomMapper.selectListByWorkOrderIdAndProcessId(workOrderId, processId);
    }
    @Override
    public void deleteWorkOrderBomByWorkOrderId(Long workOrderId) {
        workOrderBomMapper.deleteByWorkOrderId(workOrderId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
@@ -78,9 +78,9 @@
        if (feedback.getWorkOrderId() == null) {
            throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID);
        }
        // 1.2 查询工单 BOM 物料列表(倒冲按工单投料,非工艺路线)
        List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId(
                feedback.getWorkOrderId());
        // 1.2 只查询当前倒冲工序的投料 BOM,避免重复扣减其他工序的物料
        List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId(
                feedback.getWorkOrderId(), feedback.getProcessId());
        if (CollUtil.isEmpty(boms)) {
            return null;
        }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueService.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssueImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssuePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssueSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import jakarta.validation.Valid;
@@ -24,6 +26,17 @@
    Long createProductIssue(@Valid MesWmProductIssueSaveReqVO createReqVO);
    /**
     * 根据倒冲消耗记录生成已完成的生产领料单。
     *
     * <p>该方法只生成领料业务凭证,不再创建库存事务;实际扣库由物料消耗单完成。</p>
     *
     * @param feedback 报工记录
     * @param consume 物料消耗记录
     * @return 领料单编号
     */
    Long generateBackflushProductIssue(MesProFeedbackDO feedback, MesWmItemConsumeDO consume);
    /**
     * 修改领料出库单
     *
     * @param updateReqVO 修改信息
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
@@ -12,12 +12,18 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssuePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.MesWmProductIssueSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskIssueDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskIssueMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueDetailMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueLineMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueStatusEnum;
@@ -29,6 +35,8 @@
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskIssueService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeDetailService;
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeLineService;
import cn.iocoder.yudao.module.mes.service.wm.stockreserve.MesWmStockReserveService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -63,11 +71,19 @@
    @Resource
    private MesWmProductIssueMapper issueMapper;
    @Resource
    private MesWmProductIssueLineMapper issueLineMapper;
    @Resource
    private MesWmProductIssueDetailMapper issueDetailMapper;
    @Resource
    private MesWmProductIssueLineService issueLineService;
    @Resource
    private MesWmProductIssueDetailService issueDetailService;
    @Resource
    private MesWmItemConsumeLineService itemConsumeLineService;
    @Resource
    private MesWmItemConsumeDetailService itemConsumeDetailService;
    @Resource
    private MesMdWorkstationService workstationService;
    @Resource
@@ -118,6 +134,70 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generateBackflushProductIssue(MesProFeedbackDO feedback, MesWmItemConsumeDO consume) {
        String code = "AUTO-BC-" + consume.getId();
        MesWmProductIssueDO existing = issueMapper.selectByCode(code);
        if (existing != null) {
            return existing.getId();
        }
        LocalDateTime now = LocalDateTime.now();
        MesWmProductIssueDO issue = MesWmProductIssueDO.builder()
                .code(code)
                .name("倒冲自动领料-" + feedback.getCode())
                .taskId(feedback.getTaskId())
                .workOrderId(feedback.getWorkOrderId())
                .workstationId(feedback.getWorkstationId())
                .issueType(MesWmProductIssueTypeEnum.NORMAL.getType())
                .issueDate(now)
                .requiredTime(now)
                .status(MesWmProductIssueStatusEnum.FINISHED.getStatus())
                .remark("工序倒冲自动生成,物料消耗单ID:" + consume.getId())
                .build();
        issueMapper.insert(issue);
        List<MesWmItemConsumeLineDO> consumeLines = itemConsumeLineService
                .getItemConsumeLineListByConsumeId(consume.getId());
        List<MesWmItemConsumeDetailDO> consumeDetails = itemConsumeDetailService
                .getItemConsumeDetailListByConsumeId(consume.getId());
        Map<Long, List<MesWmItemConsumeDetailDO>> detailsByLineId = CollectionUtils.convertMultiMap(
                consumeDetails, MesWmItemConsumeDetailDO::getLineId);
        for (MesWmItemConsumeLineDO consumeLine : consumeLines) {
            MesWmProductIssueLineDO issueLine = MesWmProductIssueLineDO.builder()
                    .issueId(issue.getId())
                    .taskId(feedback.getTaskId())
                    .itemId(consumeLine.getItemId())
                    .quantity(consumeLine.getQuantity())
                    .remark("工序倒冲自动生成")
                    .build();
            issueLineMapper.insert(issueLine);
            for (MesWmItemConsumeDetailDO consumeDetail : detailsByLineId
                    .getOrDefault(consumeLine.getId(), List.of())) {
                MesMdItemDO item = mdmItemService.getItem(consumeDetail.getItemId());
                Long mdmItemId = item != null && item.getMdmItemId() != null
                        ? item.getMdmItemId() : consumeDetail.getItemId();
                issueDetailMapper.insert(MesWmProductIssueDetailDO.builder()
                        .issueId(issue.getId())
                        .lineId(issueLine.getId())
                        .materialStockId(consumeDetail.getMaterialStockId())
                        .itemId(mdmItemId)
                        .quantity(consumeDetail.getQuantity())
                        .batchId(consumeDetail.getBatchId())
                        .batchCode(consumeDetail.getBatchCode())
                        .warehouseId(consumeDetail.getWarehouseId())
                        .locationId(consumeDetail.getLocationId())
                        .areaId(consumeDetail.getAreaId())
                        .remark("工序倒冲自动生成")
                        .build());
            }
        }
        return issue.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProductIssue(MesWmProductIssueSaveReqVO updateReqVO) {
        // 1.1 校验存在 + 准备中状态
        validateProductIssueExistsAndPrepare(updateReqVO.getId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
@@ -8,6 +8,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
@@ -21,7 +22,9 @@
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.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.productreceipt.MesWmProductReceiptService;
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;
@@ -57,6 +60,10 @@
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    private MesProWorkOrderProcessService workOrderProcessService;
    @Resource
    private MesWmProductReceiptService productReceiptService;
    @Resource
    private MesWmBatchService batchService;
    @Resource
    private MesWmTransactionService wmTransactionService;
@@ -71,7 +78,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void finishProductProduce(Long id) {
        // 1.1 校验存在 + 草稿状态
        validateProductProduceExistsAndPrepare(id);
        MesWmProductProduceDO produce = validateProductProduceExistsAndPrepare(id);
        // 1.2 校验至少有一条行
        List<MesWmProductProduceLineDO> lines = productProduceLineService.getProductProduceLineListByProduceId(id);
        if (CollUtil.isEmpty(lines)) {
@@ -84,6 +91,13 @@
        // 3. 更新入库单状态
        productProduceMapper.updateById(new MesWmProductProduceDO()
                .setId(id).setStatus(MesWmProductProduceStatusEnum.FINISHED.getStatus()));
        // 4. 只有末道工序的合格产出才生成产品入库单,避免中间工序重复生成成品入库
        MesProWorkOrderProcessDO lastProcess = workOrderProcessService
                .getLastWorkOrderProcessByWorkOrderId(produce.getWorkOrderId());
        if (lastProcess != null && ObjUtil.equal(lastProcess.getProcessId(), produce.getProcessId())) {
            productReceiptService.generateProductReceipt(produce, lines);
        }
    }
    private void createTransactionList(Long produceId, List<MesWmProductProduceLineDO> lines) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptService.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productreceipt.MesWmProductReceiptDO;
import jakarta.validation.Valid;
@@ -24,6 +26,16 @@
    Long createProductReceipt(@Valid MesWmProductReceiptSaveReqVO createReqVO);
    /**
     * 根据末道工序的产品产出生成产品入库单草稿及合格品行。
     *
     * @param produce 产品产出单
     * @param produceLines 产品产出行
     * @return 产品入库单编号;没有合格品时返回 {@code null}
     */
    Long generateProductReceipt(MesWmProductProduceDO produce,
                                List<MesWmProductProduceLineDO> produceLines);
    /**
     * 修改产品收货单
     *
     * @param updateReqVO 修改信息
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
@@ -11,7 +11,11 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productreceipt.vo.MesWmProductReceiptSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
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;
@@ -19,10 +23,14 @@
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.dal.mysql.wm.productreceipt.MesWmProductReceiptMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptLineMapper;
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.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
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;
@@ -34,6 +42,7 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -51,6 +60,8 @@
    @Resource
    private MesWmProductReceiptMapper productReceiptMapper;
    @Resource
    private MesWmProductReceiptLineMapper productReceiptLineMapper;
    @Resource
    private MesWmProductReceiptLineService productReceiptLineService;
@@ -60,6 +71,10 @@
    private MesWmTransactionService wmTransactionService;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    private MesMdItemService mdItemService;
    @Resource
    private MesWmMaterialStockService materialStockService;
    @Resource
    private MesWmWarehouseService warehouseService;
    @Resource
@@ -83,6 +98,68 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generateProductReceipt(MesWmProductProduceDO produce,
                                       List<MesWmProductProduceLineDO> produceLines) {
        List<MesWmProductProduceLineDO> qualifiedLines = produceLines.stream()
                .filter(line -> ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus()))
                .filter(line -> line.getQuantity() != null && line.getQuantity().compareTo(BigDecimal.ZERO) > 0)
                .toList();
        if (CollUtil.isEmpty(qualifiedLines)) {
            return null;
        }
        String code = "AUTO-PR-" + produce.getId();
        MesWmProductReceiptDO existing = productReceiptMapper.selectByCode(code);
        if (existing != null) {
            return existing.getId();
        }
        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(produce.getWorkOrderId());
        MesMdItemDO firstItem = mdItemService.getItem(qualifiedLines.get(0).getItemId());
        Long firstMdmItemId = firstItem != null && firstItem.getMdmItemId() != null
                ? firstItem.getMdmItemId() : qualifiedLines.get(0).getItemId();
        String workOrderCode = workOrder != null ? workOrder.getCode() : String.valueOf(produce.getWorkOrderId());
        MesWmProductReceiptDO receipt = MesWmProductReceiptDO.builder()
                .code(code)
                .name("生产完成自动入库-" + workOrderCode)
                .workOrderId(produce.getWorkOrderId())
                .itemId(firstMdmItemId)
                .receiptDate(LocalDateTime.now())
                .status(MesWmProductReceiptStatusEnum.PREPARE.getStatus())
                .remark("末道工序产出自动生成,产出单ID:" + produce.getId())
                .build();
        productReceiptMapper.insert(receipt);
        MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode(
                MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
        MesWmWarehouseLocationDO virtualLocation = locationService.getWarehouseLocationByCode(
                MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION);
        MesWmWarehouseAreaDO virtualArea = areaService.getWarehouseAreaByCode(
                MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA);
        for (MesWmProductProduceLineDO produceLine : qualifiedLines) {
            MesMdItemDO item = mdItemService.getItem(produceLine.getItemId());
            Long mdmItemId = item != null && item.getMdmItemId() != null
                    ? item.getMdmItemId() : produceLine.getItemId();
            MesWmMaterialStockDO stock = materialStockService.getMaterialStockByCompositeKey(
                    mdmItemId, virtualWarehouse.getId(), virtualLocation.getId(), virtualArea.getId(),
                    produceLine.getBatchId(), null);
            productReceiptLineMapper.insert(MesWmProductReceiptLineDO.builder()
                    .receiptId(receipt.getId())
                    .itemId(mdmItemId)
                    .materialStockId(stock != null ? stock.getId() : null)
                    .quantity(produceLine.getQuantity())
                    .batchId(produceLine.getBatchId())
                    .batchCode(produceLine.getBatchCode())
                    .remark("末道工序合格品自动生成")
                    .build());
        }
        return receipt.getId();
    }
    @Override
    public void updateProductReceipt(MesWmProductReceiptSaveReqVO updateReqVO) {
        // 1.1 校验存在 + 草稿状态
        validateProductReceiptExistsAndDraft(updateReqVO.getId());