很多实施同学第一次批量点 Register Payment 时,都会有一个很自然的预期:
- 选了 3 张发票;
- 填一个金额;
- 系统就生成 1 笔付款。
但真实结果经常不是这样。
有时 3 张单据会合并成 1 笔 payment; 有时却会拆成 2 笔、3 笔,甚至向导直接变成“不可编辑”的汇总态。
这不是 Odoo 任性,而是 addons/account/wizard/account_payment_register.py 里本来就有一套明确的 batch 分组规则。
Register Payment 不是“把当前勾选单据一起付款”。它先把待付款行按可共用付款参数分批,再决定每批怎么落成 payment。
一、向导真正处理的不是发票本身,而是 receivable / payable lines
在 default_get() 里,向导不是直接拿整张 account.move 做支付,而是先把上下文里的发票或分录展开成 account.move.line。
然后它会筛掉:
- 不是可付款账户类型的行;
- residual 已经为 0 的行;
- 不满足公司边界的行;
- 同时混入应收与应付的行。
所以从源码视角看,付款向导的输入不是“几张发票”,而是:
一组仍有未清金额、而且方向一致的应收/应付 journal items。
这一步很关键,因为后面所有分组都是围绕这些 line 来做,不是围绕 invoice 头来做。
二、真正决定“能不能放进同一批”的,是 _get_line_batch_key()
源码里最值得盯的函数是:
_get_line_batch_key(line)
它给每一条 line 生成一个 batch key,核心字段包括:
partner_idaccount_idcurrency_idpartner_bank_idpartner_type
这几个字段几乎已经把“能否共用一笔付款”说透了。
1)partner 不同,当然不能混
收 A 客户的钱、收 B 客户的钱,或者付给两个供应商,通常都不会落成同一笔 account.payment。
2)account 不同,也不会混
哪怕都是同一个 partner,只要挂的应收/应付科目不同,Odoo 也会拆批。
原因很简单:
payment 最终要去对账的是目标科目上的未清项。
如果目标科目都不一样,硬塞进同一 payment,后面的 reconcile 语义就会变脏。
3)currency 不同,不会混
不同币种对应不同的付款金额口径、汇差逻辑和 residual 计算方式。
所以币种不同,批次就不同,这个边界非常硬。
4)partner_bank_id 也会影响分批
这里最容易被忽略。
如果是发票场景,源码会优先从 move.partner_bank_id 拿对方银行账户。
这意味着:
- 同一个供应商;
- 同一币种;
- 同一应付科目;
- 但发票上指定了不同收款账户;
也可能被拆成不同 batch。
因为在 Odoo 看来,这已经不是“同一笔该往哪打”的问题,而是 付款目的账户本身不同。
三、_compute_batches() 真正做的是“先按 key 聚类,再补出 payment_type”
在 _compute_batches() 里,Odoo 会先把所有 line 按上述 key 聚类。
但这里还有一个容易误判的点:
一开始的 key 里并没有直接写死 payment_type,而是后面根据该批 lines 的 balance 合计来判断:
- 合计余额大于 0 →
inbound - 否则 →
outbound
这说明源码的思路不是“先决定收款还是付款,再找单子”,而是:
先把可共批的 line 放到一起,再根据余额方向判定这批到底是收款还是付款。
这也解释了为什么某些边界场景里,向导会先显得像一坨混合数据,最后才确定 payment type。
四、为什么有时不同 bank account 还能并回同一批
源码里还有一段特别容易错过的 merge 逻辑。
当同一 partner 的 inbound / outbound 银行账户集合都各自只有 1 个唯一值时,Odoo 会尝试把只差 partner_bank_id 的批次重新合并,再按最后得出的 payment_type 选正确的银行账户。
这背后的业务含义很朴素:
- 如果这个 partner 在当前方向上只有唯一收/付款账户;
- 那前面按 line 取 bank account 造成的拆分,其实没有业务必要;
- 那就可以重新并回去。
所以你看到“有时同一个 partner 的几张单又被合并回一笔”,并不是规则不稳定,恰恰是规则在做 去歧义后的合并。
五、为什么向导有时可以编辑,有时突然变成汇总态
_create_payments() 里有个很重要的判断:
edit_mode = can_edit_wizard and (len(first_batch_result['lines']) == 1 or group_payment)
这句话等于在说:
可编辑的典型情况
- 只有 1 条 line;或
- 用户明确选择
group_payment,允许按 batch 生成单笔 payment。
不可编辑的典型情况
- 存在多个 batch;
- 或虽然是一个 batch,但不允许 group payment;
- 或系统需要按 move 再拆更细。
一旦进入不可编辑的路径,Odoo 不再把当前向导当成“创建一笔 payment 的表单”,而是把它当成:
一个批量支付调度器。
它后面会循环每个 batch,分别调 _create_payment_vals_from_batch() 去生成多笔 payment。
六、group_payment=False 时,系统还会继续细拆
很多人以为 batch 已经算完了,后面就结束了。
并没有。
在 _create_payments() 里,如果 group_payment 为假,Odoo 还会继续把 batch 按 move_id 再拆成 sub-batches。
也就是说:
- 前一层 batch 解决的是“哪些 lines 理论上能共用付款参数”;
- 后一层按 move 拆分,解决的是“你是否允许多张单共用同一笔 payment”。
所以你会看到这种情况:
- 向导里看起来是同一 partner、同一币种、同一账户;
- 但最终还是生成多笔 payment;
- 原因不是 batch 失败,而是
group_payment没开。
七、实务上最常见的 4 个误区
误区 1:同一个客户的发票一定能合成一笔
不一定。
只要下面任一条件不同,就可能拆开:
- 应收/应付科目不同
- 币种不同
- 对方银行账户不同
- 最后方向不同
误区 2:向导不可编辑就是坏了
也不对。
很多时候不可编辑只是因为当前选中的 lines 会生成多笔 payment,Odoo 不想让你在一个表单里误改只适用于其中一笔的字段。
误区 3:勾了多张发票,系统就一定会 group payment
不会。
是否共用一笔 payment,除了批次兼容性,还取决于 group_payment。
误区 4:payment 拆单只和前台金额有关
不是。
拆单首先是数据模型兼容性问题,其次才是金额显示问题。
八、排错时最值得先看的顺序
如果用户问:
为什么这几张单没有合成一笔付款?
最有效的检查顺序通常是:
- 这些行是不是同一个
partner_id account_id是否一致currency_id是否一致- 发票上的
partner_bank_id是否一致 - 这批 line 的余额方向是否一致
group_payment有没有打开- 是否在
_create_payments()里又被按move_id二次拆开
你按这个顺序看,比在界面上盯金额来回猜快得多。
九、一句话记住这条链
Register Payment 的真实链路不是:
- 选发票 → 生成付款
而是:
- 选中可付款 lines
- 按
partner/account/currency/bank/partner_type形成 batch - 根据余额方向补出
payment_type - 决定是否允许
group_payment - 必要时再按
move二次拆分 - 最后才创建
account.payment并执行 reconciliation
所以“为什么一笔变多笔”,本质上不是前台按钮问题,而是:
Odoo 在保护付款对象的一致性,不让不该混在一起的未清项被硬合并。
这也是为什么理解 account.payment.register 时,最该看的不是页面字段,而是 _get_line_batch_key()、_compute_batches() 和 _create_payments() 这三段源码。
DISCUSSION
评论区