先说结论
在 Odoo 里,analytic_distribution 不是“你填什么,系统就永远存什么”。
它真正处在一条会被自动补全、税行继承、分析分录反写的链路里:
业务行先决定基础分摊,税行通常继承或聚合这份分摊,分析分录又可能反向改写 journal item 上的 JSON。
所以很多人看到的“界面上是 A,过账后像 B,后来又变成 C”,并不是随机 bug,而是 Odoo 本来就允许多个层次参与这份分摊语义。
为什么这个问题在发票和供应商账单上最容易爆出来
普通总账分录里,account.move.line 往往就是最终载体。
但在客户发票、供应商账单里,同一条业务语义会扩散成多类行:
- product/base line
- tax line
- payment term line
- 某些折扣或 rounding line
这时你在“发票行”上看到的 analytic distribution,只是基础业务行的起点,并不等于整张凭证里每一条行都各自独立维护同一份分摊。
源码里这条链路到底从哪里开始
addons/account/models/account_move_line.py 里,analytic_distribution 是 fields.Json,并带有:
- inverse:
_inverse_analytic_distribution - compute 相关逻辑:
_compute_analytic_distribution - 从分析分录反写的
_update_analytic_distribution
这已经说明它不是一个“纯手填纯存储”的普通字段。
Odoo 对它的期待是:
- 业务规则能先算出默认值
- 用户仍可在行上手改
- 分析行变动后,还能把结果同步回 move line
也就是说,move line 上的 JSON 是工作副本,不是唯一真相来源。
自动补全到底什么时候发生
在 _compute_analytic_distribution 里,Odoo 会综合:
- 当前科目
- 公司
- 产品
- 分析计划
- 相关联的上游分摊
- analytic distribution model
去决定当前行应得到什么默认分摊。
因此发票行、账单行并不是只靠用户手输;只要你切换了:
- product
- account
- company
- 某些触发分摊模型的维度
系统就可能重新命中 distribution model,补上一份默认 JSON。
这也是为什么很多实施现场会觉得:
- “我明明没填,它怎么自己出来了?”
- “我改过一次,为什么改产品后又变了?”
答案不是 UI 抽风,而是默认分摊本来就在重新参与计算。
税行为什么会“跟着继承”,但又不是简单复制
addons/account/models/account_tax.py 里能看到大量 analytic_distribution 参与税基与税行分组键的逻辑。
这意味着税行并不是脱离业务行单独决定分析分摊,而往往会基于 base line 的分摊继续传播。
关键点在于:
- 如果一条税只对应一条 base line,理解上更像“继承”
- 如果税行是多条 base line 聚合出来的,分摊更接近“按金额聚合后重算占比”
所以税行上的 analytic distribution 经常看起来像复制,本质上却可能是聚合后的结果。
这也是排错时最容易误判的一点。
为什么 payment term line 通常不是你要盯的对象
应收应付的 payment term line 主要解决的是结算,而不是业务成本/收入归因。
因此当你在整张发票里检查 analytic distribution 时,真正重要的通常是:
- 产品/费用基础行有没有正确分摊
- 税行有没有沿着基础行正确继承
- 过账后 analytic item 有没有按预期生成
而不是先去纠结 receivable/payable term line 为什么没有和产品行长得一模一样。
因为它们承载的是不同会计语义。
反向回写为什么会让人觉得“越改越乱”
account_analytic_line.py 对新增、修改、删除 analytic line 后,会调用受影响 move line 的 _update_analytic_distribution()。
这件事很重要。
它说明 Odoo 不是只会“从 move line 生成 analytic line”,也支持:
分析分录的实际状态,反向折算成 move line 上看到的 analytic_distribution。
所以如果你:
- 已经过账
- 又手动调整了分析分录
- 或某个自动化脚本改了 analytic line
那 move line 上最终显示的 JSON 可能被重算。
这不是数据不一致,恰恰是 Odoo 在主动追求一致。
发票、供应商账单、普通凭证的边界差异
三者都落到 account.move.line,但调试顺序不一样。
客户发票
优先看:
- 产品收入行的默认分摊从哪里来
- 税行是否继承/聚合正确
- 折扣、现金舍入是否引入额外基础行
供应商账单
优先看:
- 费用科目或产品费用科目是否命中 analytic distribution model
- 税行是否沿账单费用行延续分摊
- 后续资产/递延逻辑是否继续带上这份分摊
普通总账凭证
优先看:
- 行上是否直接维护 analytic_distribution
- 是否存在后续 analytic line 人工调整
实战里最容易误解的 4 个点
1. 以为 analytic distribution 只在 invoice line 上生效
其实它最终属于 account.move.line,发票行只是常见入口。
2. 以为税行一定不带分析分摊
源码里税行是会参与 analytic distribution 传播与聚合的,不应一概而论。
3. 以为过账后 JSON 就冻结
如果分析分录被改,move line 上的分摊仍可能被回写更新。
4. 以为看到的 JSON 就等于分析账最终明细
真正落分析账的是 analytic line。move line 上的 JSON 更像“分析拆分摘要”。
建议的排错顺序
当你发现发票或账单上的分析分摊不对时,别直接怀疑报表。
建议按这个顺序查:
- 基础业务行的
analytic_distribution初值是谁给的 - 是否命中了 analytic distribution model
- 税行是继承单一 base line,还是聚合多条 base line
- 过账后有没有生成或修改过 analytic line
- 有没有脚本/人工操作动过分析分录,触发反向同步
这套顺序比“只盯界面字段值”可靠得多。
一句话记忆
Odoo 的 analytic distribution 不是单行字段,而是一条会在业务行、税行和分析分录之间同步的会计归因链。
DISCUSSION
评论区