14 小时以前 74ad846852f42b4e2258bfddd5c2a33cc1ce9847
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
@@ -8,18 +8,29 @@
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
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.controller.admin.wm.productreceipt.vo.MesWmProductReceiptStockReqVO;
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.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;
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.dal.mysql.wm.productproduce.MesWmProductProduceMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptDetailMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productreceipt.MesWmProductReceiptLineMapper;
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.md.autocode.MesMdAutoCodeRuleCodeEnum;
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.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
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;
@@ -31,7 +42,9 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.*;
@@ -45,6 +58,10 @@
    @Resource
    private MesWmProductReceiptMapper productReceiptMapper;
    @Resource
    private MesWmProductReceiptLineMapper productReceiptLineMapper;
    @Resource
    private MesWmProductReceiptDetailMapper productReceiptDetailMapper;
    @Resource
    private MesWmProductReceiptLineService productReceiptLineService;
@@ -60,6 +77,14 @@
    private MesWmWarehouseLocationService locationService;
    @Resource
    private MesWmWarehouseAreaService areaService;
    @Resource
    private MesWmProductProduceMapper productProduceMapper;
    @Resource
    private MesWmProductProduceLineService productProduceLineService;
    @Resource
    private MesProFeedbackService feedbackService;
    @Resource
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Override
    public Long createProductReceipt(MesWmProductReceiptSaveReqVO createReqVO) {
@@ -166,6 +191,8 @@
        if (ObjUtil.notEqual(MesWmProductReceiptStatusEnum.APPROVED.getStatus(), receipt.getStatus())) {
            throw exception(WM_PRODUCT_RECPT_STATUS_ERROR);
        }
        // 未勾选入库或未分配完的数量默认补到不合格仓库
        supplementDefectDetails(receipt);
        // 校验明细非空
        List<MesWmProductReceiptDetailDO> details = productReceiptDetailService.getProductReceiptDetailListByRecptId(id);
        if (CollUtil.isEmpty(details)) {
@@ -180,6 +207,136 @@
                .setId(id).setStatus(MesWmProductReceiptStatusEnum.FINISHED.getStatus()));
    }
    /**
     * 为未勾选入库(is_stock=false/空)或分配数量不足的行补一条不合格仓库明细
     *
     * <p>每行剩余数量 = 行收货数量 - 已分配明细数量之和,剩余 &gt; 0 时自动插入不合格仓库三级明细,
     * 保证执行入库时一个数量对应一条流水。
     */
    private void supplementDefectDetails(MesWmProductReceiptDO receipt) {
        List<MesWmProductReceiptLineDO> lines = productReceiptLineService.getProductReceiptLineListByRecptId(receipt.getId());
        for (MesWmProductReceiptLineDO line : lines) {
            List<MesWmProductReceiptDetailDO> details = productReceiptDetailService.getProductReceiptDetailListByLineId(line.getId());
            BigDecimal sumDetailQty = CollectionUtils.getSumValue(details,
                    MesWmProductReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
            BigDecimal remaining = line.getQuantity().subtract(sumDetailQty);
            if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            // 查询不合格仓库三级(按编码约定识别)
            MesWmWarehouseDO defectWarehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.DEFECT_WAREHOUSE);
            MesWmWarehouseLocationDO defectLocation = locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.DEFECT_LOCATION);
            MesWmWarehouseAreaDO defectArea = areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.DEFECT_AREA);
            if (defectWarehouse == null || defectLocation == null || defectArea == null) {
                throw exception(WM_PRODUCT_RECPT_NO_DEFECT_WAREHOUSE);
            }
            productReceiptDetailMapper.insert(new MesWmProductReceiptDetailDO()
                    .setLineId(line.getId())
                    .setReceiptId(receipt.getId())
                    .setItemId(line.getItemId())
                    .setQuantity(remaining)
                    .setBatchId(line.getBatchId())
                    .setWarehouseId(defectWarehouse.getId())
                    .setLocationId(defectLocation.getId())
                    .setAreaId(defectArea.getId()));
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createProductReceiptByFeedback(Long feedbackId) {
        // 幂等:一次报工只生成一条产品入库单
        MesWmProductReceiptDO existing = productReceiptMapper.selectByFeedbackId(feedbackId);
        if (existing != null) {
            return existing.getId();
        }
        // 校验报工存在
        MesProFeedbackDO feedback = feedbackService.validateFeedbackExists(feedbackId);
        // 生成编码并插入入库单
        MesWmProductReceiptDO receipt = new MesWmProductReceiptDO()
                .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRODUCTRECPT_CODE.getCode()))
                .setName("产品入库-" + feedback.getCode())
                .setWorkOrderId(feedback.getWorkOrderId())
                .setItemId(feedback.getItemId())
                .setFeedbackId(feedbackId)
                .setReceiptDate(LocalDateTime.now())
                .setStatus(MesWmProductReceiptStatusEnum.PREPARE.getStatus());
        productReceiptMapper.insert(receipt);
        // 从产出单产出行生成入库单行(合格/不合格分行,质量状态原样带入)
        MesWmProductProduceDO produce = productProduceMapper.selectByFeedbackId(feedbackId);
        if (produce != null) {
            List<MesWmProductProduceLineDO> produceLines = productProduceLineService.getProductProduceLineListByProduceId(produce.getId());
            for (MesWmProductProduceLineDO produceLine : produceLines) {
                if (produceLine.getQuantity() == null || produceLine.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                    continue;
                }
                productReceiptLineMapper.insert(new MesWmProductReceiptLineDO()
                        .setReceiptId(receipt.getId())
                        .setItemId(produceLine.getItemId())
                        .setQuantity(produceLine.getQuantity())
                        .setBatchId(produceLine.getBatchId())
                        .setBatchCode(produceLine.getBatchCode())
                        .setQualityStatus(produceLine.getQualityStatus())
                        .setIsStock(Boolean.FALSE));
            }
        }
        return receipt.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitStock(MesWmProductReceiptStockReqVO reqVO) {
        // 校验入库单存在且为待上架(APPROVING)
        MesWmProductReceiptDO receipt = validateProductReceiptExists(reqVO.getReceiptId());
        if (ObjUtil.notEqual(MesWmProductReceiptStatusEnum.APPROVING.getStatus(), receipt.getStatus())) {
            throw exception(WM_PRODUCT_RECPT_STATUS_ERROR);
        }
        List<MesWmProductReceiptStockReqVO.Row> rows = reqVO.getRows();
        if (CollUtil.isEmpty(rows)) {
            throw exception(WM_PRODUCT_RECPT_NO_LINE);
        }
        // 逐行处理:更新是否入库 + 重建上架明细
        for (MesWmProductReceiptStockReqVO.Row row : rows) {
            MesWmProductReceiptLineDO line = productReceiptLineMapper.selectById(row.getLineId());
            if (line == null || !ObjUtil.equal(line.getReceiptId(), receipt.getId())) {
                throw exception(WM_PRODUCT_RECPT_LINE_NOT_EXISTS);
            }
            Boolean isStock = Boolean.TRUE.equals(row.getIsStock());
            productReceiptLineMapper.updateById(new MesWmProductReceiptLineDO()
                    .setId(line.getId()).setIsStock(isStock));
            // 删除该行旧明细,重建分配
            productReceiptDetailService.deleteProductReceiptDetailByLineId(line.getId());
            if (!isStock) {
                continue; // 未勾选:不分配,执行入库时自动进不合格仓库
            }
            // 勾选入库:分配数量之和必须等于行收货数量
            List<MesWmProductReceiptStockReqVO.Detail> details = row.getDetails();
            if (CollUtil.isEmpty(details)) {
                throw exception(WM_PRODUCT_RECPT_STOCK_QUANTITY_MISMATCH);
            }
            BigDecimal sumDetailQty = details.stream()
                    .map(MesWmProductReceiptStockReqVO.Detail::getQuantity)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            if (line.getQuantity() == null || sumDetailQty.compareTo(line.getQuantity()) != 0) {
                throw exception(WM_PRODUCT_RECPT_STOCK_QUANTITY_MISMATCH);
            }
            // 校验仓库层级关系并插入明细
            for (MesWmProductReceiptStockReqVO.Detail detail : details) {
                areaService.validateWarehouseAreaExists(detail.getWarehouseId(), detail.getLocationId(), detail.getAreaId());
                productReceiptDetailMapper.insert(new MesWmProductReceiptDetailDO()
                        .setLineId(line.getId())
                        .setReceiptId(receipt.getId())
                        .setItemId(line.getItemId())
                        .setQuantity(detail.getQuantity())
                        .setBatchId(line.getBatchId())
                        .setWarehouseId(detail.getWarehouseId())
                        .setLocationId(detail.getLocationId())
                        .setAreaId(detail.getAreaId()));
            }
        }
    }
    private void createTransactionList(MesWmProductReceiptDO receipt, List<MesWmProductReceiptDetailDO> details) {
        // 1. 查询虚拟线边库
        MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);