yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -16,6 +16,7 @@
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.crm.api.contract.dto.CrmContractCreateFromSaleOrderReqDTO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractImportExcelVO;
@@ -57,6 +58,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -834,6 +836,73 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public CrmContractDO createContractDraftFromSaleOrder(CrmContractCreateFromSaleOrderReqDTO reqDTO) {
        // 1. 幂等:同一销售订单只生成一张合同
        CrmContractDO existed = contractMapper.selectByOrderId(reqDTO.getOrderId());
        if (existed != null) {
            log.info("[createContractDraftFromSaleOrder] 销售订单({}) 已生成合同({}),跳过创建",
                    reqDTO.getOrderNo(), existed.getNo());
            return existed;
        }
        // 2. 校验客户与负责人
        CrmCustomerDO customer = customerService.getCustomer(reqDTO.getCustomerId());
        if (customer == null) {
            throw new IllegalArgumentException("销售订单[" + reqDTO.getOrderNo()
                    + "]关联的客户不存在或已被删除,无法自动生成销售合同");
        }
        if (reqDTO.getOwnerUserId() == null) {
            throw new IllegalArgumentException("销售订单[" + reqDTO.getOrderNo()
                    + "]未填写销售员,无法确定自动生成销售合同的负责人");
        }
        if (CollUtil.isEmpty(reqDTO.getItems())) {
            throw new IllegalArgumentException("销售订单[" + reqDTO.getOrderNo()
                    + "]没有明细,无法自动生成销售合同");
        }
        // 3. 组装合同明细
        List<CrmContractProductDO> products = new ArrayList<>(reqDTO.getItems().size());
        for (CrmContractCreateFromSaleOrderReqDTO.Item item : reqDTO.getItems()) {
            CrmContractProductDO product = new CrmContractProductDO();
            product.setItemId(item.getItemId());
            product.setItemUnitId(item.getItemUnitId());
            product.setItemPrice(item.getPrice());
            product.setContractPrice(item.getPrice());
            product.setCount(item.getCount());
            product.setTotalPrice(MoneyUtils.priceMultiply(item.getPrice(), item.getCount()));
            products.add(product);
        }
        // 4. 组装合同(未提交草稿,不发起审批)
        CrmContractDO contract = new CrmContractDO();
        contract.setNo(noRedisDAO.generate(CrmNoRedisDAO.CONTRACT_NO_PREFIX));
        contract.setName(customer.getName() + "-销售合同-" + reqDTO.getOrderNo());
        contract.setCustomerId(reqDTO.getCustomerId());
        contract.setOwnerUserId(reqDTO.getOwnerUserId());
        contract.setOrderDate(reqDTO.getOrderTime());
        contract.setDiscountPercent(reqDTO.getDiscountPercent() != null ? reqDTO.getDiscountPercent() : BigDecimal.ZERO);
        contract.setDepositPrice(reqDTO.getDepositPrice() != null ? reqDTO.getDepositPrice() : BigDecimal.ZERO);
        contract.setOrderId(reqDTO.getOrderId());
        contract.setOrderNo(reqDTO.getOrderNo());
        contract.setRemark("由销售订单【" + reqDTO.getOrderNo() + "】审批通过后自动生成");
        contract.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
        calculateTotalPrice(contract, products);
        // 5. 落库
        contractMapper.insert(contract);
        products.forEach(product -> product.setContractId(contract.getId()));
        contractProductMapper.insertBatch(products);
        // 6. 创建数据权限,保证负责人能看到该合同
        crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(contract.getOwnerUserId())
                .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId())
                .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
        log.info("[createContractDraftFromSaleOrder] 销售订单({}) 自动生成合同({}) 成功",
                reqDTO.getOrderNo(), contract.getNo());
        return contract;
    }
    @Override
    public void updateContractStatus(Long id, Integer status) {
        contractMapper.updateById(new CrmContractDO()
                .setId(id)