云
10 天以前 616a6b2e9a7824da8d11a4e11730fd2f9b615299
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -14,20 +14,25 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService;
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;
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -42,21 +47,24 @@
/**
 * MES 销售出库单 Service 实现类
 *
 * @author 芋道源码
 * @author 超级管理员
 */
@Service
@Validated
@Slf4j
public class MesWmProductSalesServiceImpl implements MesWmProductSalesService {
    @Resource
    private MesWmProductSalesMapper productSalesMapper;
    @Resource
    private MesWmProductSalesLineMapper productSalesLineMapper;
    @Resource
    private MesWmProductSalesLineService productSalesLineService;
    @Resource
    private MesWmProductSalesDetailService productSalesDetailService;
    @Resource
    private MesMdClientService clientService;
    private CrmCustomerApi customerApi;
    @Resource
    private MesWmSalesNoticeService salesNoticeService;
    @Resource
@@ -65,9 +73,14 @@
    private MesWmMaterialStockService materialStockService;
    @Resource
    private MesWmStockReserveService stockReserveService;
    @Resource
    @Lazy
    private MesQcOqcService oqcService;
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    @Resource
    private MesWmSalesReelGuard salesReelGuard;
    @Override
    public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -113,7 +126,7 @@
    @Override
    public PageResult<MesWmProductSalesDO> getProductSalesPage(MesWmProductSalesPageReqVO pageReqVO) {
        return productSalesMapper.selectPage(pageReqVO);
        return productSalesMapper.selectPage(pageReqVO, (List<Long>) null);
    }
    @Override
@@ -133,10 +146,14 @@
            }
        }
        // 获取出库单信息
        MesWmProductSalesDO sales = productSalesMapper.selectById(id);
        // 检查所有行的 oqcCheckFlag:如果有需要 OQC 检验的行,进入待检测状态
        boolean needOqc = CollectionUtils.anyMatch(lines,
                line -> Boolean.TRUE.equals(line.getOqcCheckFlag()));
        if (needOqc) {
            // 检验单由质检人员先行录入,待检明细通过盘号关联已完成的检验单。
            // 批量初始化 OQC 行的质量状态为"待检验"
            List<Long> oqcLineIds = convertList(
                    CollectionUtils.filterList(lines, line -> Boolean.TRUE.equals(line.getOqcCheckFlag())),
@@ -187,6 +204,7 @@
            }
            // 校验库存充足并创建占用
            for (MesWmProductSalesDetailDO detail : details) {
                salesReelGuard.validate(line, detail.getBatchId());
                materialStockService.validateSelectedStock(
                        detail.getMaterialStockId(),
                        detail.getItemId(),
@@ -213,6 +231,12 @@
        // 执行拣货(待拣货 → 待填写运单)
        productSalesMapper.updateById(new MesWmProductSalesDO()
                .setId(id).setStatus(MesWmProductSalesStatusEnum.SHIPPING.getStatus()));
        // 回写 ERP 销售订单出库状态为"部分出库"(表示已拣货/占用)
        if (sales.getSaleOrderId() != null) {
            saleOrderApi.updateOrderOutStatusTo(sales.getSaleOrderId(),
                    ErpSaleOrderOutStatusEnum.PART_OUT.getStatus());
        }
    }
    @Override
@@ -251,13 +275,30 @@
        productSalesMapper.updateById(new MesWmProductSalesDO()
                .setId(id).setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus()));
        // 5. 更新关联的发货通知单状态
        // 5. 更新关联的发货通知单出库数量和状态
        if (sales.getNoticeId() != null) {
            salesNoticeService.finishSalesNotice(sales.getNoticeId());
            updateSalesNoticeOutCount(sales);
        }
        // 6. 回写 ERP 销售订单的出库数量和状态
        updateErpSaleOrderOutCount(sales);
    }
    /**
     * 更新发货通知单的出库数量和状态
     */
    private void updateSalesNoticeOutCount(MesWmProductSalesDO sales) {
        // 获取出库单行
        List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(sales.getId());
        if (CollUtil.isEmpty(lines)) {
            return;
        }
        // 遍历行,按 noticeLineId 更新出库数量
        for (MesWmProductSalesLineDO line : lines) {
            if (line.getNoticeLineId() != null && line.getQuantity() != null) {
                salesNoticeService.updateSalesNoticeLineOutCount(line.getNoticeLineId(), line.getQuantity());
            }
        }
    }
    /**
@@ -293,13 +334,21 @@
    private void createTransactionList(MesWmProductSalesDO sales) {
        List<MesWmProductSalesDetailDO> details = productSalesDetailService.getProductSalesDetailListBySalesId(sales.getId());
        for (MesWmProductSalesDetailDO detail : details) {
            MesWmProductSalesLineDO line = productSalesLineService.getProductSalesLine(detail.getLineId());
            if (line == null || !ObjUtil.equal(line.getSalesId(), sales.getId())) {
                throw exception(WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH);
            }
            salesReelGuard.validate(line, detail.getBatchId());
        }
        wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
                .setQuantity(detail.getQuantity().negate()) // 出库数量为负数
                .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
                .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
                .setBizType(MesBizTypeConstants.WM_PRODUCT_SALES).setBizId(sales.getId())
                .setBizCode(sales.getCode()).setBizLineId(detail.getLineId())));
                .setBizCode(sales.getCode()).setBizLineId(detail.getLineId())
                .setMaterialStockId(detail.getMaterialStockId()))); // 指定库存记录ID,避免重新匹配
    }
    @Override
@@ -341,7 +390,7 @@
        // 校验编码唯一
        validateCodeUnique(id, reqVO.getCode());
        // 校验客户存在
        clientService.validateClientExistsAndEnable(reqVO.getClientId());
        customerApi.validateCustomer(reqVO.getClientId());
        // 校验发货通知单
        if (reqVO.getNoticeId() != null) {
            MesWmSalesNoticeDO notice = salesNoticeService.validateSalesNoticeExists(reqVO.getNoticeId());