yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -327,46 +327,58 @@
        CrmContractImportExcelVO head = rows.get(0);
        // 1. 主表必填
        if (StrUtil.isBlank(head.getName())) {
            throw new IllegalArgumentException("合同名称不能为空");
            throw new IllegalArgumentException("合同名称不能为空:请填写模板中该行的「合同名称」列;"
                    + "若同一张合同的多行使用了合并单元格,需填写在合并区域的首行");
        }
        if (StrUtil.isBlank(head.getCustomerName())) {
            throw new IllegalArgumentException("客户名称不能为空");
            throw new IllegalArgumentException("客户名称不能为空:请填写模板中该行的「客户名称」列;"
                    + "若同一张合同的多行使用了合并单元格,需填写在合并区域的首行");
        }
        Long customerId = customerIdMap.get(head.getCustomerName().trim());
        if (customerId == null) {
            throw new IllegalArgumentException("客户不存在:" + head.getCustomerName());
            throw new IllegalArgumentException("客户不存在:" + head.getCustomerName()
                    + "。请先在「CRM 客户管理」中创建该客户(名称需与模板完全一致),或修改模板中的客户名称后重新导入");
        }
        customerService.validateCustomer(customerId);
        // 2. 明细
        List<CrmContractSaveReqVO.Product> products = new ArrayList<>();
        List<CrmContractProductDO> productDOs = new ArrayList<>();
        for (CrmContractImportExcelVO row : rows) {
            if (StrUtil.isBlank(row.getProductCode())) {
                throw new IllegalArgumentException("产品编码不能为空");
                throw new IllegalArgumentException("产品编码不能为空:请填写模板中该行的「产品编码」列");
            }
            CommonResult<MdmItemRespDTO> itemResult = mdmItemApi.getItemByCode(row.getProductCode().trim());
            MdmItemRespDTO item = itemResult == null ? null : itemResult.getData();
            if (item == null) {
                throw new IllegalArgumentException("产品不存在:" + row.getProductCode());
                throw new IllegalArgumentException("产品不存在:" + row.getProductCode()
                        + "。请先在「物料管理」中维护该物料(编码需与模板完全一致),或修改模板中的产品编码后重新导入");
            }
            if (row.getCount() == null || row.getCount() <= 0) {
                throw new IllegalArgumentException("产品[" + row.getProductCode() + "]的数量必须为正整数");
            if (row.getCount() == null) {
                throw new IllegalArgumentException("产品[" + row.getProductCode()
                        + "]的数量不能为空:请填写模板中该行的「数量」列");
            }
            if (row.getCount().compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("产品[" + row.getProductCode() + "]的数量必须大于 0,当前为 "
                        + row.getCount().stripTrailingZeros().toPlainString() + "。请修改模板后重新导入");
            }
            BigDecimal contractPrice = row.getContractPrice() != null ? row.getContractPrice()
                    : (item.getSalesPrice() != null ? item.getSalesPrice() : BigDecimal.ZERO);
            CrmContractSaveReqVO.Product product = new CrmContractSaveReqVO.Product();
            CrmContractProductDO product = new CrmContractProductDO();
            product.setItemId(item.getId());
            product.setItemUnitId(item.getUnitMeasureId());
            product.setItemPrice(item.getSalesPrice() != null ? item.getSalesPrice() : contractPrice);
            product.setContractPrice(contractPrice);
            product.setCount(row.getCount());
            products.add(product);
            product.setTotalPrice(MoneyUtils.priceMultiply(contractPrice, row.getCount()));
            productDOs.add(product);
        }
        // 3. 合同编号
        String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
                : noRedisDAO.generate(CrmNoRedisDAO.CONTRACT_NO_PREFIX);
        if (contractMapper.selectByNo(no) != null) {
            throw new IllegalArgumentException("合同编号已存在:" + no);
            throw new IllegalArgumentException("合同编号已存在:" + no
                    + "。请修改模板中的合同编号,或先在系统中删除该编号的合同后重新导入");
        }
        // 4. 组装主表(负责人=登录用户;状态=未提交)
@@ -384,7 +396,6 @@
        contract.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
        // 5. 明细落库 + 自动算价
        List<CrmContractProductDO> productDOs = validateContractProducts(products);
        calculateTotalPrice(contract, productDOs);
        contractMapper.insert(contract);
        productDOs.forEach(product -> product.setContractId(contract.getId()));
@@ -472,7 +483,9 @@
        if (quotation != null) {
            crmSaleQuotationService.clearContractReference(quotation.getId());
        }
        // 2.2 删除数据权限
        // 2.3 删除合同产品明细
        contractProductMapper.delete(CrmContractProductDO::getContractId, id);
        // 2.4 删除数据权限
        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id);
        // 3. 记录操作日志上下文
@@ -644,6 +657,11 @@
    }
    @Override
    public CrmContractDO getContractByNo(String no) {
        return contractMapper.selectByNo(no);
    }
    @Override
    public CrmContractDO validateContract(Long id) {
        return validateContractExists(id);
    }