可编辑单号

Odoo 单号为什么能手改却还要尽量不重号:sequence.mixin、日期约束和并发取号到底怎么配合

Odoo 里不少业务单号不是纯黑盒序列,而是建立在 sequence.mixin 上的“可编辑编号”机制。本文从官方源码讲清它为什么既允许改号,又要拼命守住唯一性和日期一致性。

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

先说结论

很多人一提到 Odoo 单号,就只想到 ir.sequence

但在实际业务里,像会计分录这类对象,编号往往不是“点一下拿个纯递增值”这么简单。

官方源码里的 sequence.mixin 解决的是一个更难的问题:

单号既要允许业务上可编辑,又要尽量保持日期一致、格式连续,并在并发下避免重号。

这也是为什么你会觉得 Odoo 的某些编号机制“既灵活又严格”。


为什么 sequence.mixin 比普通 sequence 更难

因为它处理的不是简单“发号器”,而是可编辑编号体系

这意味着系统要同时面对几种张力:

  • 用户可能会手工改前缀
  • 不同月份 / 年度可能要重置编号
  • 旧号已经存在,下一号要沿着现有格式继续
  • 并发过账时又不能撞号

你会发现,这不是单一技术问题,而是:

  • 格式识别
  • 日期约束
  • 历史续号
  • 并发锁定

四件事一起存在。


官方源码在做什么

addons/account/models/sequence_mixin.py,这套机制大致分成四层。

1. 先把现有编号拆成“前缀 + 数字 + 日期片段”

源码里准备了多组 regex:

  • 月度重置
  • 年度重置
  • 年区间
  • 固定序列

它不是假设所有编号都长一样,而是先尝试从已有编号里反推出格式

这点很关键。

因为 Odoo 在很多场景不是“完全自己定义未来的号”,而是:

根据你现在已经用起来的格式,继续往后编号。

2. 再检查“编号和日期是不是匹配”

源码里 _constrains_date_sequence() 会校验:

  • 单号里带的年份 / 月份
  • 是否和记录日期一致

所以如果你把日期改到另一月、另一年,但编号前缀还是旧时期,系统就可能要求你清空编号重新取。

这就是很多人感受到的“为什么我明明只是改个日期,编号却被卡住”。

因为在这套设计里,日期不是装饰,而是编号语义的一部分。

3. _get_last_sequence() 不是随便找最大数字

源码注释专门提醒了一件容易忽略的事:

  • 它是在指定 domain 内
  • 找到字母序最大的前缀链
  • 再取其中 sequence_number 最大的那条

所以如果你中途手工把前缀改成另一个字母序更小的前缀,下一号不一定会沿着你刚改的那条继续。

这也是官方文档式注释反复提醒“改前缀要小心”的原因。

4. _locked_increment() 处理并发取号

这是最值得开发者认真看的部分。

源码里没有天真地假设“查到最后一个号,再 +1”就安全。

它明确用了:

  • 唯一约束保护
  • savepoint
  • 事务内 cache
  • 重试机制

核心思路是:

  • 先尝试更新受唯一约束保护的那一行
  • 如果并发下撞到唯一冲突,就 rollback 到 savepoint 再试
  • 一旦当前事务拿住这条序列链,后续同事务递增可借助 cache,减少 savepoint 开销

这说明 sequence.mixin 的重点根本不只是“格式化字符串”,而是在业务可编辑前提下尽量守住并发唯一性


为什么它允许编辑,又不鼓励乱编辑

因为这套机制的目标,从来不是“让用户随便改号”。

它真正支持的是:

  • 某些业务场景下需要修正编号
  • 系统仍尽量根据历史编号继续工作
  • 但会对日期一致性和唯一性加护栏

所以如果你把它理解成“可编辑版 ir.sequence”,只对了一半。

更准确地说,它是:

带历史续号能力和并发保护的可编辑编号框架。


新手最容易误解的点

1. 以为下一号只看最大数字

不对。

它还会受到前缀链和 domain 的影响。

2. 以为手工改前缀不会影响后续编号

实际上很可能影响,尤其当字母序链发生变化时。

3. 以为日期只是显示字段

在这套机制里,日期经常是编号合法性的一部分。

4. 以为并发安全是数据库“自然保证”的

不是自动白送的。

源码里为了处理这件事,专门用了 savepoint、唯一索引和 retry 逻辑。


实战开发该怎么用这层理解

如果你在自定义一个也需要“业务可读、可编辑、按周期续号”的对象,我会先问:

  • 这个编号是否真的允许用户改?
  • 日期是否要嵌入编号语义?
  • 前缀变化会不会让历史续号变得混乱?
  • 并发下谁来守住唯一性?

如果这些问题没想清楚,只是“做个看起来像单号的字符串”,后面迟早出事。


一句话记忆法

sequence.mixin 不是普通发号器,而是 Odoo 用来平衡“可编辑单号、日期一致性、历史续号和并发唯一性”的一整套机制。

理解这一点,很多“为什么单号能改、又为什么改了会报错”的现象就都顺了。

DISCUSSION

评论区

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