gaoluyang
4 天以前 2eeeb70e4733e7fc41635c3aa2143f13cbc43d58
feat(erp): 采购来票分摊合计改为不超过来票金额即可

- 分摊合计允许小于来票金额(未分摊部分可后续编辑来票再补充采购订单),只拦截超出
- 错误提示列出来票金额、分摊合计与超出金额及处理动作
- 订单未开票金额按实际分摊累计,未分摊部分不占用订单额度

Co-Authored-By: Claude Code <noreply@anthropic.com>
已修改3个文件
22 ■■■■■ 文件已修改
docs/erp_purchase_invoice_multi_order_integration.md 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/erp_purchase_invoice_multi_order_integration.md
@@ -37,7 +37,7 @@
| orderAllocations[].price | BigDecimal | 是 | 本张来票分摊到该订单的金额,不能小于 0 |
| purchaseOrderId | Long | 否 | **不再由前端传**,后端取分摊明细第一行回填 |
| supplierId | Long | 否 | **不再由前端传**,后端取第一张订单的供应商回填 |
| price | BigDecimal | 是 | 来票金额,必须等于各行分摊金额之和 |
| price | BigDecimal | 是 | 来票金额,必须大于等于各行分摊金额之和(未分摊部分可后续编辑补充订单) |
**page 请求参数新增:**
@@ -80,7 +80,7 @@
| 字段 | 展示位置 | 说明 |
|------|----------|------|
| 采购订单 | 来票新增/编辑弹窗 | 多选弹窗(已审核 + 未开票金额大于 0),表格列:订单号、供应商、订单金额、未开票金额、本次分摊金额(可编辑)、移除 |
| 分摊合计 / 待分摊差额 | 分摊表格下方 | 实时显示,差额不为 0 时保存被拦截 |
| 分摊合计 / 待分摊差额 | 分摊表格下方 | 实时显示;差额为正(未分摊)允许保存,为负(超出)时保存被拦截 |
| 供应商 | 来票表单 | 由所选订单供应商带出(同一供应商),只读 |
| 采购订单编号 | 来票列表 | 展示 orderNos;历史数据回退到主订单号 |
| 未开票金额 | 采购订单列表 / 选择弹窗 | 按分摊明细实时计算 |
@@ -89,7 +89,7 @@
| 场景 | 规则 |
|------|------|
| 分摊合计 | 必须等于来票金额,否则报错并列出来票金额、分摊合计和差额 |
| 分摊合计 | 不能大于来票金额(超出时报错并列出来票金额、分摊合计和超出金额);允许小于来票金额,未分摊部分可保存后编辑来票再补充采购订单 |
| 单订单上限 | 每行分摊金额不得超过该订单「未开票金额」(订单金额 − 其他来票已占用) |
| 订单重复 | 同一张来票内同一订单只能出现一次 |
| 订单状态 | 只有已审核通过(status=20)的采购订单才能登记来票 |
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -65,7 +65,7 @@
    ErrorCode PURCHASE_INVOICE_ALLOCATION_EMPTY = new ErrorCode(1_030_105_009, "请至少选择一张采购订单,并把来票金额分摊到订单上");
    ErrorCode PURCHASE_INVOICE_ALLOCATION_ORDER_DUPLICATE = new ErrorCode(1_030_105_010, "同一张来票内的采购订单不能重复选择");
    ErrorCode PURCHASE_INVOICE_ALLOCATION_PRICE_INVALID = new ErrorCode(1_030_105_011, "分摊金额必须填写且不能小于 0");
    ErrorCode PURCHASE_INVOICE_ALLOCATION_SUM_MISMATCH = new ErrorCode(1_030_105_012, "各采购订单的分摊金额合计必须等于来票金额");
    ErrorCode PURCHASE_INVOICE_ALLOCATION_SUM_MISMATCH = new ErrorCode(1_030_105_012, "各采购订单的分摊金额合计不能大于来票金额");
    ErrorCode PURCHASE_INVOICE_ALLOCATION_SUPPLIER_MISMATCH = new ErrorCode(1_030_105_013, "同一张来票只能关联同一供应商的采购订单");
    // ========== ERP 采购退货(1-030-103-000) ==========
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
@@ -132,18 +132,16 @@
        }
        // 1.2 校验所有采购订单必须是同一供应商(来票对应一个供应商)
        validateSameSupplier(orders);
        // 2. 校验分摊合计 = 来票金额
        // 2. 校验分摊合计不能大于来票金额(允许小于:未分摊部分可后续编辑来票时再补充采购订单)
        BigDecimal allocationTotal = allocations.stream().map(ErpPurchaseInvoiceOrderReqVO::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (allocationTotal.compareTo(reqVO.getPrice()) != 0) {
            BigDecimal diff = reqVO.getPrice().subtract(allocationTotal);
            String detail = diff.compareTo(BigDecimal.ZERO) > 0
                    ? StrUtil.format("还有 {} 元未分摊到订单,请为订单填写分摊金额或改选订单", diff.stripTrailingZeros().toPlainString())
                    : StrUtil.format("分摊合计比来票金额多 {} 元,请减少分摊金额", diff.negate().stripTrailingZeros().toPlainString());
        if (allocationTotal.compareTo(reqVO.getPrice()) > 0) {
            throw new ServiceException(PURCHASE_INVOICE_ALLOCATION_SUM_MISMATCH.getCode(),
                    StrUtil.format("来票金额 {} 元,各订单分摊合计 {} 元,两者必须相等(差额:{})。",
                    StrUtil.format("来票金额 {} 元,各订单分摊合计 {} 元,分摊合计不能大于来票金额(超出 {} 元)。"
                                    + "请减少订单的分摊金额,或提高来票金额后重试;未分摊的部分可以留待以后再编辑来票补充采购订单。",
                            reqVO.getPrice().stripTrailingZeros().toPlainString(),
                            allocationTotal.stripTrailingZeros().toPlainString(), detail));
                            allocationTotal.stripTrailingZeros().toPlainString(),
                            allocationTotal.subtract(reqVO.getPrice()).stripTrailingZeros().toPlainString()));
        }
        // 3. 主订单、供应商取分摊明细第一张(一张来票涉及多个供应商时,列表/付款沿用第一家)
        reqVO.setPurchaseOrderId(allocations.get(0).getPurchaseOrderId());