yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
@@ -17,6 +18,8 @@
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.crm.api.contract.CrmContractApi;
import cn.iocoder.yudao.module.crm.api.contract.dto.CrmContractCreateFromSaleOrderReqDTO;
import cn.iocoder.yudao.module.crm.api.contract.dto.CrmContractRespDTO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportExcelVO;
@@ -36,6 +39,7 @@
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 com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.flowable.engine.repository.ProcessDefinition;
import lombok.extern.slf4j.Slf4j;
@@ -59,6 +63,7 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.erp.enums.LogRecordConstants.*;
/**
 * ERP 销售订单 Service 实现类
@@ -190,6 +195,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = ERP_SALE_ORDER_TYPE, subType = ERP_SALE_ORDER_IMPORT_SUB_TYPE, bizNo = "0",
            success = ERP_SALE_ORDER_IMPORT_SUCCESS)
    public ErpSaleOrderImportRespVO importSaleOrderList(List<ErpSaleOrderImportExcelVO> importList) {
        ErpSaleOrderImportRespVO respVO = ErpSaleOrderImportRespVO.builder()
                .createList(new ArrayList<>())
@@ -197,6 +204,8 @@
                .failureList(new LinkedHashMap<>())
                .build();
        if (CollUtil.isEmpty(importList)) {
            // 记录导入结果汇总,供 @LogRecord 的 success 文案引用
            OperateLogUtils.putImportSummary(respVO.getCreateList(), respVO.getUpdateList(), respVO.getFailureList());
            return respVO;
        }
@@ -235,6 +244,8 @@
                respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
            }
        }
        // 记录导入结果汇总,供 @LogRecord 的 success 文案引用
        OperateLogUtils.putImportSummary(respVO.getCreateList(), respVO.getUpdateList(), respVO.getFailureList());
        return respVO;
    }
@@ -392,6 +403,16 @@
        }
        // 4. 创建 BPM 流程实例(审批在协同办公完成)
        // 全局免审批:业务校验通过后直接执行审批通过逻辑,无需流程模型。
        if (processInstanceApi.isGlobalAutoApprove()) {
            saleOrderMapper.updateById(new ErpSaleOrderDO()
                    .setId(id)
                    .setStatus(ErpAuditStatus.APPROVING.getStatus())
                    .setProcessInstanceId(""));
            updateSaleOrderAuditStatus(id, cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.APPROVE.getStatus());
            return;
        }
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
@@ -421,9 +442,10 @@
                    .setId(id).setStatus(newStatus));
        }
        // 3. 审核通过时,为需要生产的明细创建 MPS
        // 3. 审核通过时,为需要生产的明细创建 MPS,并自动生成销售合同草稿
        if (ErpAuditStatus.APPROVE.getStatus().equals(newStatus)) {
            createMpsForSaleOrder(id, saleOrder);
            createContractForSaleOrder(saleOrder);
        }
    }
@@ -528,6 +550,58 @@
            }
        }
        log.info("[createMpsForSaleOrder] 销售订单({}) 审核完成,创建 MPS {} 条", orderId, mpsCount);
    }
    /**
     * 销售订单审批通过后,自动生成销售合同草稿(不提交审批)
     */
    private void createContractForSaleOrder(ErpSaleOrderDO saleOrder) {
        // 订单本身由合同生成,避免反向生成
        if (saleOrder.getContractId() != null) {
            log.info("[createContractForSaleOrder] 销售订单({}) 已关联合同({}),跳过自动生成",
                    saleOrder.getNo(), saleOrder.getContractNo());
            return;
        }
        try {
            List<ErpSaleOrderItemDO> items = saleOrderItemMapper.selectListByOrderId(saleOrder.getId());
            if (CollUtil.isEmpty(items)) {
                log.warn("[createContractForSaleOrder] 销售订单({}) 没有明细,跳过自动生成合同", saleOrder.getNo());
                return;
            }
            // 负责人优先取销售员,未填写时回退到订单创建人
            Long ownerUserId = saleOrder.getSaleUserId();
            if (ownerUserId == null && StrUtil.isNumeric(saleOrder.getCreator())) {
                ownerUserId = Long.valueOf(saleOrder.getCreator());
            }
            CrmContractCreateFromSaleOrderReqDTO createReqDTO = new CrmContractCreateFromSaleOrderReqDTO();
            createReqDTO.setOrderId(saleOrder.getId());
            createReqDTO.setOrderNo(saleOrder.getNo());
            createReqDTO.setCustomerId(saleOrder.getCustomerId());
            createReqDTO.setOwnerUserId(ownerUserId);
            createReqDTO.setOrderTime(saleOrder.getOrderTime());
            createReqDTO.setDiscountPercent(saleOrder.getDiscountPercent());
            createReqDTO.setDepositPrice(saleOrder.getDepositPrice());
            createReqDTO.setItems(items.stream().map(item -> {
                CrmContractCreateFromSaleOrderReqDTO.Item contractItem = new CrmContractCreateFromSaleOrderReqDTO.Item();
                contractItem.setItemId(item.getProductId());
                contractItem.setItemUnitId(item.getProductUnitId());
                contractItem.setPrice(item.getProductPrice());
                contractItem.setCount(item.getCount());
                return contractItem;
            }).toList());
            CrmContractRespDTO contract = contractApi.createContractDraftFromSaleOrder(createReqDTO).getCheckedData();
            if (contract != null) {
                saleOrderMapper.updateById(new ErpSaleOrderDO().setId(saleOrder.getId())
                        .setContractId(contract.getId()).setContractNo(contract.getNo()));
                log.info("[createContractForSaleOrder] 销售订单({}) 自动生成合同({}) 成功",
                        saleOrder.getNo(), contract.getNo());
            }
        } catch (Exception e) {
            // 合同生成失败不阻塞订单审批,记录日志便于排查
            log.error("[createContractForSaleOrder] 销售订单({}) 自动生成销售合同失败", saleOrder.getNo(), e);
        }
    }
    /**
@@ -724,8 +798,7 @@
    @Override
    public PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) {
        List<Long> permittedCustomerIds = customerApi.getPermittedCustomerIds();
        return saleOrderMapper.selectPage(pageReqVO, permittedCustomerIds);
        return saleOrderMapper.selectPage(pageReqVO);
    }
    // ==================== 订单项 ====================