| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | @Validated |
| | | @Slf4j |
| | | public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { |
| | | |
| | | private static final Integer MPS_STATUS_CANCELED = 3; |
| | | |
| | | @Resource |
| | | private ErpSaleOrderMapper saleOrderMapper; |
| | |
| | | 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; |
| | |
| | | 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); |
| | | } |
| | | |
| | | /** |