云
5 天以前 f02d0df2f1dfc7a8b775cf50d45375e57628a5ef
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -2,6 +2,8 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -9,11 +11,13 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
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.framework.mybatis.core.query.LambdaQueryWrapperX;
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;
@@ -22,6 +26,7 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
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;
@@ -39,6 +44,7 @@
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -64,6 +70,8 @@
    @Resource
    private MesWmProductSalesDetailService productSalesDetailService;
    @Resource
    private MesWmOqcConcessionGuard oqcConcessionGuard;
    @Resource
    private CrmCustomerApi customerApi;
    @Resource
    private MesWmSalesNoticeService salesNoticeService;
@@ -81,6 +89,8 @@
    private ErpSaleOrderApi saleOrderApi;
    @Resource
    private MesWmSalesReelGuard salesReelGuard;
    @Resource
    private MesMdItemService mdItemService;
    @Override
    public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -153,19 +163,7 @@
        boolean needOqc = CollectionUtils.anyMatch(lines,
                line -> Boolean.TRUE.equals(line.getOqcCheckFlag()));
        if (needOqc) {
            // 为需要 OQC 检验且尚未创建检验单的明细创建 OQC 检验单
            List<MesWmProductSalesLineDO> oqcLines = CollectionUtils.filterList(lines,
                    line -> Boolean.TRUE.equals(line.getOqcCheckFlag()) && line.getOqcId() == null);
            for (MesWmProductSalesLineDO line : oqcLines) {
                // 直接调用,让异常抛出,保证数据一致性
                Long oqcId = oqcService.createOqcFromProductSales(
                        sales.getId(), line, sales.getCode(), sales.getClientId());
                // 更新出库明细的 oqcId
                productSalesLineMapper.updateById(new MesWmProductSalesLineDO()
                        .setId(line.getId())
                        .setOqcId(oqcId));
            }
            // 检验单由质检人员先行录入,待检明细通过盘号关联已完成的检验单。
            // 批量初始化 OQC 行的质量状态为"待检验"
            List<Long> oqcLineIds = convertList(
                    CollectionUtils.filterList(lines, line -> Boolean.TRUE.equals(line.getOqcCheckFlag())),
@@ -203,7 +201,10 @@
    @Transactional(rollbackFor = Exception.class)
    public void stockProductSales(Long id) {
        // 校验存在
        MesWmProductSalesDO sales = validateProductSalesExists(id);
        validateProductSalesExists(id);
        MesWmProductSalesDO sales = productSalesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>()
                .eq(MesWmProductSalesDO::getId, id).last("FOR UPDATE"));
        if (sales == null) throw exception(WM_PRODUCT_SALES_NOT_EXISTS);
        if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.APPROVING.getStatus(), sales.getStatus())) {
            throw exception(WM_PRODUCT_SALES_CANNOT_PICK);
        }
@@ -214,9 +215,12 @@
            if (CollUtil.isEmpty(details)) {
                throw exception(WM_PRODUCT_SALES_DETAILS_EMPTY);
            }
            oqcConcessionGuard.validateQuantity(line, CollectionUtils.getSumValue(details,
                    MesWmProductSalesDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO));
            // 校验库存充足并创建占用
            for (MesWmProductSalesDetailDO detail : details) {
                salesReelGuard.validate(line, detail.getBatchId());
                oqcConcessionGuard.validate(line, detail.getMaterialStockId(), detail.getBatchCode(), detail.getQuantity());
                materialStockService.validateSelectedStock(
                        detail.getMaterialStockId(),
                        detail.getItemId(),
@@ -346,12 +350,19 @@
    private void createTransactionList(MesWmProductSalesDO sales) {
        List<MesWmProductSalesDetailDO> details = productSalesDetailService.getProductSalesDetailListBySalesId(sales.getId());
        for (MesWmProductSalesLineDO line : productSalesLineService.getProductSalesLineListBySalesId(sales.getId())) {
            BigDecimal quantity = details.stream().filter(detail -> ObjUtil.equal(detail.getLineId(), line.getId()))
                    .map(MesWmProductSalesDetailDO::getQuantity).filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            oqcConcessionGuard.validateQuantity(line, quantity);
        }
        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());
            oqcConcessionGuard.validate(line, detail.getMaterialStockId(), detail.getBatchCode(), detail.getQuantity());
        }
        wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
@@ -388,11 +399,36 @@
        if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.CONFIRMED.getStatus(), sales.getStatus())) {
            throw exception(WM_PRODUCT_SALES_CANNOT_CONFIRM);
        }
        // 出货检验不合格的出库行必须先完成不合格品处置,禁止手工确认绕过返工/让步闭环
        List<MesWmProductSalesLineDO> lines = productSalesLineMapper.selectListBySalesId(id);
        if (CollUtil.isNotEmpty(lines)) {
            int rowNo = 0;
            for (MesWmProductSalesLineDO line : lines) {
                rowNo++;
                if (Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())
                        || line.getNcrDispositionId() != null) {
                    throw new ServiceException(400, String.format(
                            "销售出库单[%s]第[%d]行物料[%s]出货检验不合格,尚未完成不合格品处置;"
                                    + "请先在不合格品处理单中完成返工/返修/重新生产或让步放行后再确认出库",
                            sales.getCode(), rowNo, itemLabel(line.getItemId())));
                }
            }
        }
        // OQC 检验完成,流转到待拣货
        productSalesMapper.updateById(new MesWmProductSalesDO()
                .setId(id).setStatus(MesWmProductSalesStatusEnum.APPROVING.getStatus()));
    }
    private String itemLabel(Long itemId) {
        MesMdItemDO item = itemId == null ? null : mdItemService.getItem(itemId);
        if (item == null) {
            return "ID=" + itemId;
        }
        String label = (StrUtil.blankToDefault(item.getCode(), "") + " "
                + StrUtil.blankToDefault(item.getName(), "")).trim();
        return label.isEmpty() ? "ID=" + itemId : label;
    }
    @Override
    public List<MesWmProductSalesDO> getProductSalesListByClientId(Long clientId) {
        return productSalesMapper.selectListByClientId(clientId);