yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -81,6 +81,8 @@
    private ErpPurchaseOrderMapper purchaseOrderMapper;
    @Resource
    private ErpPurchaseOrderItemMapper purchaseOrderItemMapper;
    @Resource
    private ErpSupplierContractService supplierContractService;
    @Resource
    private ErpNoRedisDAO noRedisDAO;
@@ -125,6 +127,8 @@
        if (createReqVO.getAccountId() != null) {
            accountService.validateAccount(createReqVO.getAccountId());
        }
        // 1.3.1 校验并解析关联合同(订单必须与合同属于同一供应商)
        String contractNo = resolveContractNo(createReqVO.getContractId(), createReqVO.getSupplierId());
        // 1.4 生成订单号,并校验唯一性
        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
        if (purchaseOrderMapper.selectByNo(no) != null) {
@@ -134,7 +138,8 @@
        // 2.1 插入订单(采购订单创建后为未审核状态,需要提交审批)
        ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
                .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()));
                .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus())
                .setContractNo(contractNo));
        calculateTotalPrice(purchaseOrder, purchaseOrderItems);
        purchaseOrderMapper.insert(purchaseOrder);
        // 2.2 插入订单项
@@ -167,9 +172,12 @@
        }
        // 1.4 校验订单项的有效性
        List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems());
        // 1.5 校验并解析关联合同
        String contractNo = resolveContractNo(updateReqVO.getContractId(), updateReqVO.getSupplierId());
        // 2.1 更新订单
        ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class);
        updateObj.setContractNo(contractNo);
        calculateTotalPrice(updateObj, purchaseOrderItems);
        purchaseOrderMapper.updateById(updateObj);
        // 2.2 更新订单项
@@ -349,9 +357,12 @@
    private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List<ErpPurchaseOrderItemDO> purchaseOrderItems) {
        purchaseOrder.setTotalCount(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getCount, BigDecimal::add));
        purchaseOrder.setTotalProductPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseOrder.setTotalTaxPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice().add(purchaseOrder.getTotalTaxPrice()));
        // 明细的 totalPrice 是「数量 × 含税单价」,即含税总价;不含税金额 = 含税总价 − 税额
        BigDecimal totalPriceWithTax = getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO);
        BigDecimal totalTaxPrice = getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO);
        purchaseOrder.setTotalTaxPrice(totalTaxPrice);
        purchaseOrder.setTotalPrice(totalPriceWithTax);
        purchaseOrder.setTotalProductPrice(totalPriceWithTax.subtract(totalTaxPrice));
        // 计算优惠价格
        if (purchaseOrder.getDiscountPercent() == null) {
            purchaseOrder.setDiscountPercent(BigDecimal.ZERO);
@@ -399,6 +410,16 @@
        }
        // 2. 创建 BPM 流程实例
        // 全局免审批:业务校验通过后直接执行审批通过逻辑,无需流程模型。
        if (processInstanceApi.isGlobalAutoApprove()) {
            purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                    .setId(id)
                    .setStatus(ErpAuditStatus.APPROVING.getStatus())
                    .setProcessInstanceId(""));
            updatePurchaseOrderAuditStatus(id, cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.APPROVE.getStatus());
            return;
        }
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
@@ -495,13 +516,12 @@
    private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) {
        // 转化为 ErpPurchaseOrderItemDO 列表(产品验证已移除)
        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
            // 产品单价为含税单价:行总价 = 数量 × 含税单价,税额再从含税总价里倒算
            item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
            if (item.getTotalPrice() == null) {
                return;
            }
            if (item.getTaxPercent() != null) {
                item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
            }
            item.setTaxPrice(MoneyUtils.priceCalculateTaxIncluded(item.getTotalPrice(), item.getTaxPercent()));
        }));
    }
@@ -589,6 +609,21 @@
        });
    }
    /**
     * 校验关联合同,返回冗余的合同编码;未关联合同时返回 null
     */
    private String resolveContractNo(Long contractId, Long supplierId) {
        if (contractId == null) {
            return null;
        }
        cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierContractDO contract =
                supplierContractService.validateContractExists(contractId);
        if (!Objects.equals(contract.getSupplierId(), supplierId)) {
            throw exception(SUPPLIER_CONTRACT_SUPPLIER_MISMATCH);
        }
        return contract.getCode();
    }
    private ErpPurchaseOrderDO validatePurchaseOrderExists(Long id) {
        ErpPurchaseOrderDO purchaseOrder = purchaseOrderMapper.selectById(id);
        if (purchaseOrder == null) {