凭证冲销

Odoo 冲销为什么不是删掉原凭证:reversal、reversed_entry_id 和 cancel/modify 到底怎么分

很多人看到 Reverse Entry,会以为它只是“做一张金额相反的凭证”。其实从 wizard 到 _reverse_moves,Odoo 还要处理原凭证关联、自动过账、是否一起核销,以及 modify 模式下的新草稿。本文把冲销链路讲透。

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

结论先行

在 Odoo 里,冲销不是把原凭证“撤回到不存在”,而是:

保留原凭证作为已发生事实,再创建一张方向相反的新凭证,通过 reversed_entry_id 把两者连起来,并在需要时进一步做核销或重建。

所以 Reverse Entry 的本质不是删除,而是用新的会计事实去抵消旧的会计事实

这听起来很保守,但对审计和追溯来说反而最稳。


第一层:为什么会计系统宁愿冲销,也不喜欢“直接删”

很多业务系统在发现录错单据时,会倾向于:

  • 打开原单
  • 改掉内容
  • 或者直接删除重来

但会计系统不太能这么干。

因为 posted 凭证一旦存在,就不仅仅是一个表单记录,它还是:

  • 报表的一部分
  • 对账链的一部分
  • 税务链的一部分
  • 审计轨迹的一部分

如果你把原凭证直接抹掉,结果虽然“看起来正确了”,但过程消失了。

而冲销的哲学是:

  1. 承认原来那笔已经发生过
  2. 再用一笔相反记录把它抵掉
  3. 必要时再生成正确的新凭证

这比“删除历史”更符合会计世界的可信度要求。


第二层:源码里的 reversal 不是简单 copy 一下负数

/home/ubuntu/odoo-temp/addons/account/wizard/account_move_reversal.pyaccount_move.py 可以看到,标准冲销链路至少包含这些元素:

  • account.move.reversal 向导
  • 目标 move_ids 必须是 posted
  • 生成默认 reversal 值
  • 调用 _reverse_moves()
  • 为新凭证写入 reversed_entry_id
  • 视情况决定是否 cancel=True

这说明官方没有把 reversal 理解成“随手复制一张反向凭证”,而是理解成一套有语义的会计动作。

尤其 reversed_entry_id 很关键。

它意味着系统不是只关心“金额反了没有”,还关心:

这张新凭证究竟是在反哪一张原凭证。

有了这层链接,后续追溯、界面提示、自动核销才有基础。


第三层:cancel 和普通 reversal 到底差在哪

_reverse_moves(self, default_values_list=None, cancel=False) 这个签名非常值得记。

因为它直接告诉你:

  • reversal 不一定等于 cancel
  • cancel 只是 reversal 的一种更强语义

源码里,如果 cancel=True

  1. 先对原 move 的相关行 remove_move_reconcile()
  2. 再生成反向 move
  3. 再把反向 move 过账
  4. 后续让原分录和冲销分录进入 _reconcile_reversed_moves()

也就是说,cancel 不是单纯“多打一张反方向凭证”,它通常还带着“把旧关系拆开,再让新旧正式闭环”的动作。

这比表面按钮文案深很多。


第四层:为什么 modify 模式更像“冲销 + 重做”

向导里除了 refund_moves(),还有 modify_moves()

这个模式特别容易被忽略。

它做的不是只生成反向单,而是:

  • 先按 reversal 逻辑把原凭证冲掉
  • 再基于原单内容复制出一张新的草稿
  • 并且通常只保留真正业务相关的行

所以 modify 的真实含义更接近:

先承认旧的错了,再留一张可继续修正的新草稿。

这比“直接回到原单继续改”更稳,因为它保留了错误发生过、后来被纠正的完整链路。


第五层:为什么 future date reversal 会出现 auto_post

向导 _prepare_default_reversal() 里还会根据 reversal date 判断:

  • 如果 reversal date 在未来
  • 那新 move 可以 auto_post = 'at_date'

这说明 Odoo 的 reversal 不是只面向“马上冲掉”。 它也承认现实世界里存在:

  • 今天决定要冲销
  • 但会计效果要在未来某个日期正式生效

比如跨期调整、预先安排的更正动作等。

所以 reversal 在 Odoo 里其实是一种可被计划的正式会计动作,而不是一个 UI 撤销键。


常见误区

误区 1:冲销就是删除原凭证

不是。 冲销的价值恰恰在于不删除原凭证。

误区 2:reversal 就是一张金额相反的 copy

不完整。 它还带着 reversed_entry_id、过账、核销与后续追溯语义。

误区 3:cancel 和 reverse 是完全同义词

不是。 cancel=True 代表更强的闭环动作。

误区 4:modify 只是普通 reversal 的别名

也不是。 modify 更像“冲销后再生成新草稿继续修正”。

误区 5:Reverse Entry 按钮就是界面便捷功能

不对。 它背后是完整的会计更正设计。


实战判断顺序

当你需要更正 posted 凭证时,可以这样判断:

1. 先问:你是要保留历史还是假装历史没发生

会计系统里通常只能选前者。

2. 再问:只是做一张反向抵消,还是冲完还要重做正确版本

这决定你更接近 reverse 还是 modify。

3. 再看:原凭证是否已经参与核销

这会影响 cancel 链路里的拆解与重连。

4. 再决定 reversal date

别忽略未来日期会触发 auto_post 语义。

5. 最后才去看按钮文案

真正决定行为的是底层链路,不是按钮上那几个字。


一句话记忆法

Odoo 的冲销不是把原凭证删掉,而是新增一张带 reversed_entry_id 的反向凭证,让“错误发生过、后来被抵消”这件事留下完整轨迹。

DISCUSSION

评论区

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