云
2026-10-25 d54fbc9d4f8981fa0f7f613b33a28a8cd4f764ea
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java
@@ -4,9 +4,12 @@
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLineSaveReqVO;
import cn.iocoder.yudao.module.mes.api.event.MesReturnSalesProgressEvent;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper;
@@ -16,6 +19,7 @@
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -25,7 +29,9 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MD_ITEM_BATCH_REQUIRED;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_BATCH_CLIENT_MISMATCH;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_SALES_LINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_SALES_RQC_SOURCE_INVALID;
/**
 * MES 销售退货单行 Service 实现类
@@ -49,6 +55,8 @@
    @Resource
    @Lazy
    private MesWmReturnSalesDetailService returnSalesDetailService;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Override
    public Long createReturnSalesLine(MesWmReturnSalesLineSaveReqVO createReqVO) {
@@ -82,9 +90,9 @@
     */
    private void validateLineSaveData(MesWmReturnSalesLineSaveReqVO reqVO) {
        // 校验退货单存在 + 草稿状态
        returnSalesService.validateReturnSalesExistsAndPrepare(reqVO.getReturnId());
        // 校验物料存在 + 批次管理
        validateItemBatchManagement(reqVO.getReturnId(), reqVO.getItemId(), reqVO.getBatchId());
        MesWmReturnSalesDO returnSales = returnSalesService.validateReturnSalesExistsAndPrepare(reqVO.getReturnId());
        // 校验物料存在 + 解析批次
        resolveAndValidateBatch(returnSales, reqVO);
    }
    @Override
@@ -148,30 +156,47 @@
    }
    /**
     * 校验物料批次管理
     * 解析并校验物料批次
     *
     * @param returnId 退货单ID
     * @param itemId   物料ID
     * @param batchId  批次ID
     * <p>退货商品在销售出库时可能未记录批次,此时按物料 + 客户 + 销售订单自动生成退货批次,
     * 保证退货单仍可追溯到具体批次;已有批次则校验物料归属,批次未指定客户时视为通用批次。
     */
    private void validateItemBatchManagement(Long returnId, Long itemId, Long batchId) {
        MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(itemId);
        // 如果物料启用了批次管理,则必须选择批次
        if (Boolean.TRUE.equals(item.getIsBatchManaged()) && batchId == null) {
    private void resolveAndValidateBatch(MesWmReturnSalesDO returnSales, MesWmReturnSalesLineSaveReqVO reqVO) {
        MesMdmItemDO item = mdmItemService.validateItemExistsAndEnable(reqVO.getItemId());
        if (reqVO.getBatchId() != null) {
            MesWmBatchDO batch = batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId());
            if (batch.getClientId() != null && ObjUtil.notEqual(batch.getClientId(), returnSales.getClientId())) {
                throw exception(WM_BATCH_CLIENT_MISMATCH);
            }
            reqVO.setBatchCode(batch.getCode());
            return;
        }
        if (!Boolean.TRUE.equals(item.getIsBatchManaged())) {
            return;
        }
        MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO()
                .setItemId(reqVO.getItemId())
                .setClientId(returnSales.getClientId())
                .setSalesOrderCode(returnSales.getSalesOrderCode());
        MesWmBatchDO batch = batchService.getOrGenerateBatchCode(batchReqVO);
        if (batch == null) {
            throw exception(MD_ITEM_BATCH_REQUIRED);
        }
        // 校验批次存在且属于当前物料和客户
        if (batchId != null) {
            // 从父单获取客户ID,校验批次归属
            MesWmReturnSalesDO returnSales = returnSalesService.validateReturnSalesExistsAndPrepare(returnId);
            batchService.validateBatchExists(batchId, itemId, returnSales.getClientId(), null);
        }
        reqVO.setBatchId(batch.getId()).setBatchCode(batch.getCode());
    }
    @Override
    public void updateReturnSalesLineWhenRqcFinish(Long sourceLineId, Long sourceDocId, Integer checkResult,
                                                    BigDecimal qualifiedQuantity, BigDecimal unqualifiedQuantity) {
        MesWmReturnSalesLineDO sourceLine = validateReturnSalesLineExists(sourceLineId);
        if (sourceDocId != null) {
            MesWmReturnSalesDO sourceDoc = returnSalesService.validateReturnSalesExists(sourceDocId);
            if (!sourceDocId.equals(sourceLine.getReturnId())
                    || !MesWmReturnSalesStatusEnum.CONFIRMED.getStatus().equals(sourceDoc.getStatus())
                    || !MesWmQualityStatusEnum.PENDING.getStatus().equals(sourceLine.getQualityStatus())) {
                throw exception(WM_RETURN_SALES_RQC_SOURCE_INVALID);
            }
        }
        boolean hasUnqualified = unqualifiedQuantity != null && unqualifiedQuantity.compareTo(BigDecimal.ZERO) > 0;
        boolean hasQualified = qualifiedQuantity != null && qualifiedQuantity.compareTo(BigDecimal.ZERO) > 0;
@@ -210,7 +235,16 @@
            boolean allInspected = !CollUtil.contains(allLines,
                    line -> Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus()));
            if (allInspected) {
                returnSalesService.updateReturnSalesStatus(sourceDocId, MesWmReturnSalesStatusEnum.APPROVING.getStatus());
                MesWmReturnSalesDO returnSales = returnSalesService.validateReturnSalesExists(sourceDocId);
                if (MesWmReturnSalesStatusEnum.CONFIRMED.getStatus().equals(returnSales.getStatus())) {
                    returnSalesService.updateReturnSalesStatus(sourceDocId, MesWmReturnSalesStatusEnum.APPROVING.getStatus());
                    if (returnSales.getAfterSaleReturnId() != null) {
                        boolean failed = CollUtil.contains(allLines,
                                line -> MesWmQualityStatusEnum.FAIL.getStatus().equals(line.getQualityStatus()));
                        eventPublisher.publishEvent(new MesReturnSalesProgressEvent(sourceDocId,
                                MesReturnSalesProgressEvent.Stage.QUALITY_FINISHED, failed ? "含不合格品" : "合格"));
                    }
                }
            }
        }
    }