云
2026-10-26 0857cf1830039d60ce73005f386f284cf575e511
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -23,6 +23,7 @@
import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.ErpSaleOrderProductionStatusEnum;
import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
@@ -31,17 +32,15 @@
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO;
import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.PostConstruct;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
@@ -67,6 +66,8 @@
@Validated
@Slf4j
public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
    private static final Integer MPS_STATUS_CANCELED = 3;
    @Resource
    private ErpSaleOrderMapper saleOrderMapper;
@@ -94,22 +95,6 @@
    private ErpAccountMapper accountMapper;
    @Resource
    private MesProMpsApi mpsApi;
    @Resource
    private PlatformTransactionManager transactionManager;
    /**
     * MPS 创建使用独立事务(REQUIRES_NEW):
     * 审核时自动创建 MPS 属于尽力而为的联动,失败仅记录日志并回滚自身,
     * 不能污染销售订单审核事务(否则内层异常会把外层事务标记为 rollback-only,导致整个审核被回滚)
     */
    private TransactionTemplate mpsTransactionTemplate;
    @PostConstruct
    public void initMpsTransactionTemplate() {
        mpsTransactionTemplate = new TransactionTemplate(transactionManager);
        mpsTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    }
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
@@ -237,31 +222,43 @@
            return;
        }
        int mpsCount = 0;
        int createdCount = 0;
        int reusedCount = 0;
        for (ErpSaleOrderItemDO item : items) {
            if (item.getNeedProduction() == null || item.getNeedProduction() != 1) {
                continue;
            }
            try {
                // 独立事务创建 MPS:失败只回滚 MPS 自身,不影响审核事务
                Long mpsId = mpsTransactionTemplate.execute(txStatus -> {
                    Long newMpsId = createMpsForSaleOrderItem(saleOrder, item);
                    if (newMpsId != null) {
                        // 更新销售订单明细的 MPS 关联
                        saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
                                .setId(item.getId())
                                .setMpsId(newMpsId));
                    }
                    return newMpsId;
                });
                if (mpsId != null) {
                    mpsCount++;
            // 同一销售明细只保留一条有效 MPS,避免反审核后再次审核产生重复计划
            MesProMpsRespDTO existingMps = mpsApi.getMpsBySaleOrderItemId(item.getId());
            if (existingMps != null && ObjectUtil.notEqual(existingMps.getStatus(), MPS_STATUS_CANCELED)) {
                if (ObjectUtil.notEqual(item.getMpsId(), existingMps.getId())) {
                    saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
                            .setId(item.getId())
                            .setMpsId(existingMps.getId()));
                }
            } catch (Exception e) {
                log.error("[createMpsForSaleOrder] 创建 MPS 失败,saleOrderItemId={}", item.getId(), e);
                reusedCount++;
                log.info("[createMpsForSaleOrder] 销售订单明细({}) 已存在有效 MPS({}),跳过重复创建",
                        item.getId(), existingMps.getId());
                continue;
            }
            // 与销售订单审核共用事务;任一明细创建失败都会回滚审核状态和本次已创建的 MPS
            Long mpsId = createMpsForSaleOrderItem(saleOrder, item);
            saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
                    .setId(item.getId())
                    .setMpsId(mpsId));
            createdCount++;
        }
        log.info("[createMpsForSaleOrder] 销售订单({}) 审核完成,创建 MPS {} 条", orderId, mpsCount);
        // 重新生成生产计划后,订单生产状态回到未完成(覆盖此前的“已取消”)
        if (createdCount > 0) {
            saleOrderMapper.update(null, new LambdaUpdateWrapper<ErpSaleOrderDO>()
                    .eq(ErpSaleOrderDO::getId, orderId)
                    .set(ErpSaleOrderDO::getProductionStatus, ErpSaleOrderProductionStatusEnum.NOT_FINISHED.getStatus())
                    .set(ErpSaleOrderDO::getProductionFinishTime, null));
        }
        log.info("[createMpsForSaleOrder] 销售订单({}) 审核完成,创建 MPS {} 条,复用 MPS {} 条",
                orderId, createdCount, reusedCount);
    }
    /**