会计日期重算

invoice_date 改了为什么连会计日期和分录行都一起变:account.move 的日期重算链路

从 `account.move._compute_date()` 看 Odoo 如何把 invoice_date、date 和 line_ids.date 绑在同一条重算链路上。

Odoo 开发 会计
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

先看现象

很多开发者都会疑惑:

  • 我只是改了 invoice_date
  • 为什么 date 也跟着变了?
  • 为什么分录行的日期还要一起重算?

这不是“前端表单自动帮你改了一下”,而是 Odoo 在模型层专门设计的一条重算链。

入口其实很清楚:_compute_date()

addons/account/models/account_move.py 里,和这个问题直接相关的是:

@api.depends('invoice_date', 'company_id', 'move_type', 'taxable_supply_date')
def _compute_date(self):
    for move in self:
        accounting_date = move._get_accounting_date_source()
        if not accounting_date or not move.is_invoice(include_receipts=True):
            if not move.date:
                move.date = fields.Date.context_today(self)
            continue
        if not move.is_sale_document(include_receipts=True):
            accounting_date = move._get_accounting_date(accounting_date, move._affect_tax_report())
        if accounting_date and accounting_date != move.date:
            move.date = accounting_date
            self.env.add_to_compute(move.line_ids._fields['date'], move.line_ids)
            self.env.add_to_compute(self._fields['name'], move)

这段代码告诉我们三件事:

  1. invoice_date 是依赖源之一
  2. date 不是完全独立的字段
  3. line_ids.date 也在同一条重算链上

_get_accounting_date_source() 先决定“看谁做源头”

它的实现很直接:

def _get_accounting_date_source(self):
    self.ensure_one()
    return self.invoice_date or self.date

也就是说,系统会优先看 invoice_date。 如果它没有值,才回退到 date

这就是为什么很多场景里,开发者只改 date 看起来没效果: 因为模型逻辑已经把 invoice_date 当成更上游的日期来源了。

为什么销售单和采购单还不完全一样

_compute_date() 里,有一个分支很关键:

  • 如果不是销售类单据,Odoo 还会调用 _get_accounting_date() 做进一步换算
  • 同时它会考虑税务报表影响 _affect_tax_report()

这说明会计日期不只是“用户选了哪一天”,还要受税务和公司规则影响。

对开发者来说,这里最重要的理解是:

日期字段在 Odoo 里常常不是一个独立值,而是业务、税务和公司逻辑共同算出来的结果。

为什么要 add_to_compute()

这一句最容易被忽略:

self.env.add_to_compute(move.line_ids._fields['date'], move.line_ids)
self.env.add_to_compute(self._fields['name'], move)

它的意思不是“立刻把这些字段改掉”,而是把它们加入后续重算队列。

这样做的好处是:

  • 避免半成品状态被别的逻辑提前读到
  • 让依赖字段按统一节奏重算
  • 减少手工改一个字段却漏掉相关字段的风险

你可以把它理解成:

Odoo 不只是在改字段,而是在安排一整批依赖字段重新计算。

为什么新手总觉得“它偷偷改了我的值”

因为模型层有自己的优先级:

  • 用户手工输入是一个来源
  • 计算字段和依赖链也是来源
  • 业务规则可能还会再修正一次

当这些来源冲突时,Odoo 会遵循模型定义里的依赖关系,而不是纯粹听表单里的最后一次输入。

所以,定制会计单据时不能只看界面,必须看字段依赖和重算逻辑。

实战开发建议

  1. 不要把 invoice_datedate 当成完全等价字段 它们在某些单据上是相关的,但逻辑上不是一回事。

  2. 修改日期相关逻辑时,要一起检查分录行 很多会计 bug 都是主表和行表日期不同步导致的。

  3. 如果你扩展了日期计算,记得补依赖 否则 @api.depends 不会触发你期望的重算。

  4. 先看重算链,再看表单表现 页面上看到的值,往往已经经过多轮模型层处理。

一句话总结

invoice_date 之所以会带着 date 和分录行一起变,不是因为表单层做了联动,而是因为 account.move._compute_date() 把它们放进了同一条重算链。 这正是 Odoo ORM 的典型风格:一个关键字段变化,相关依赖字段就要一起重新计算。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。