企业 签署流程

Odoo 企业版 Sign 为什么不是“群发一个签字请求”:签署顺序、Reminder Cron、SMS 二次认证与逐批投递链路讲透

基于 sign 源码,讲清 Odoo 企业版签署请求如何用 signer 的 mail_sent_order 控制分批发送,再用 _cron_reminder 续推下一批,并在 SMS 二次认证与无余额降级之间维持签署闭环,而不是把所有签署人一次性丢进同一封邮件里。

企业 协同办公
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

很多人把 Odoo 企业版 Sign 理解成“选几个人,系统发几封邮件,对方点进去签掉就好”。

但只要认真看 enterprise/sign,你会发现官方真正在意的不是“把 PDF 群发出去”,而是另外三件更难的事:

  1. 多个签署人到底是不是同时收到请求;
  2. 前一批签完以后,下一批由谁触发、何时触发;
  3. 要求额外认证时,短信验证码、信用额度不足、签署继续与否如何收口。

所以这篇文章的核心结论是:

Odoo 企业版 Sign 的重点,不是生成签署链接,而是把“投递顺序、提醒节奏、认证边界”绑成一条可持续推进的状态机。

一、签署顺序不是 UI 排序,而是 mail_sent_order

入口在 enterprise/sign/wizard/sign_send_request.py

最值得看的不是按钮,而是 _compute_signer_ids()

这个方法会根据模板里的 responsible_id 角色生成 sign.send.request.signer,并给每个 signer 写一个 mail_sent_order

  • 如果没启用 set_sign_order,所有 signer 默认都是 1
  • 如果启用了顺序签署,则按角色顺序把 signer 依次标成 1、2、3...

这里的关键在于:

  • 顺序签署不是依赖前端列表显示顺序;
  • 它是持久化到 signer 数据里的投递层级。

所以当用户在向导里调整签署顺序时,系统真正保存的是“第几批发送”,不是一个好看的排序号。

这也是为什么源码里会特别判断:如果当前 signer 还是默认顺序,就跟着新的顺序重算;如果用户已经手工改过 mail_sent_order,则尽量保留现值。

二、create_request() 创建的不是一封邮件,而是一份可分批推进的请求

同一个 wizard 里的 create_request() 会把 signer 列表转成 sign.request 和它的 request_item_ids

每个 item 都带上:

  • partner_id
  • role_id
  • mail_sent_order

这三个字段组合起来,才构成后续真正的签署投递策略。

也就是说,sign.request 创建时并没有“直接群发给所有 signer”,而是先把:

  • 谁来签;
  • 以什么角色签;
  • 在第几批收到邀请;

全部落成结构化数据。

这一步的设计非常像审批系统,而不是普通发邮件动作。

三、真正决定“现在该发给谁”的是 send_signature_accesses()

enterprise/sign/models/sign_request.py 里的 send_signature_accesses() 是整条链路的关键。

它不会无脑遍历所有 signer,而是:

  • 先只处理 state == 'sent' 的请求;
  • 再对每个 request 调用 _get_next_sign_request_items()
  • 只给当前“应该出场的那一批” item 发送签署入口;
  • 发送后更新 last_reminder

这说明顺序签署的本质不是“前一位签完以后把状态改一下”,而是:

系统每次真正对外发邮件时,都只挑当前 mail_sent_order 那一层。

于是顺序控制被稳定地落在投递动作上,而不只是落在页面规则上。

对企业协同尤其重要,因为很多合规场景在意的不是“签名顺序在数据库里写了什么”,而是“第二位到底有没有在第一位签之前收到链接”。

四、Reminder Cron 不只是提醒,它还是顺序流转的继续器

很多人以为 _cron_reminder() 只是定时补发提醒邮件。

其实它做的事情更大。

源码逻辑是:

  1. 扫描所有 state = 'sent'active = True 的请求;
  2. 如果 validity < today,直接把请求标成 expired
  3. 否则,如果开启 reminder,且 last_reminder + reminder interval <= today,把请求加入待发送集合;
  4. 最后对这批 request 调用 send_signature_accesses()

注意最后一步。

由于 send_signature_accesses() 本身只会发送“下一批应收到链接的人”,所以 cron 并不只是提醒老 signer,它也可能承担顺序链继续推进的职责。

换句话说:

  • 当前批次还没签完时,它是 reminder;
  • 当前批次已完成、下一批已具备发送条件时,它也是顺序投递的再触发器。

这就是为什么 Odoo 把 reminder 和顺序发送放在同一条业务链里,而不是拆成两套完全独立的 job。

五、SMS 二次认证不是附属插件,而是签署闭环的一道门

额外认证主要落在两个位置:

  • sign.request.item._send_sms()
  • sign.controllers.main._validate_auth_method()

1. _send_sms() 先重置验证码,再发送

每次发短信时,_send_sms() 都会先调用 _reset_sms_token(),重新生成六位验证码,再通过 sms.sms 发送。

这意味着短信验证码不是创建请求时一次生成、长期有效,而是每次触发短信发送时刷新。

2. _validate_auth_method() 真正决定“能不能签”

当 signer 进入 /sign/sign/... 路由时,如果当前角色配置了 auth_method,controller 会先走 _validate_auth_method()

对于 sms 场景,逻辑并不只是“验证码对不对”,而是更细:

  • 有短信认证要求;
  • 如果系统已经没有 SMS credits,且用户没有填验证码,Odoo 仍允许签署继续;
  • 同时把 signed_without_extra_auth = True 标出来;
  • 如果有 credits 却没填或填错验证码,就返回 {'success': False, 'sms': True},阻止签署;
  • 验证成功后,还会往 sign request 写一条日志,记录签署人和校验手机号。

这套逻辑特别值得注意,因为它体现了一个很现实的企业取舍:

额外认证很重要,但业务闭环也重要。

如果因为 IAP 短信余额耗尽,所有签署全部卡死,很多企业流程会直接停摆。所以官方做了“可追踪的降级”:允许继续签,但明确标记这次签署没有完成原定的额外认证。

六、有效期、提醒周期、签署顺序三者不是各管各的

在 wizard 里,_check_validity() 限制了 validity 不能早于今天;_onchange_reminder() 又把 reminder 最大值限制在 365。

看起来这些只是表单校验,但它们其实是给整个状态机画边界:

  • validity 决定这份请求还能不能继续推进;
  • reminder 决定下一次系统尝试推动流程是什么时候;
  • mail_sent_order 决定下一次尝试时应该发给哪一批人。

三者合在一起,才是 Odoo Sign 的“节奏控制层”。

七、最容易误解的地方

1. “启用签署顺序,就是前端上排个 1、2、3”

不对。真正起作用的是 request item 上持久化的 mail_sent_order,以及发送时只挑当前批次的逻辑。

2. “Reminder 只是催当前 signer”

不完全对。它也可能成为顺序流转继续前进的触发器。

3. “开了 SMS 认证,就一定必须输验证码才能签”

不绝对。源码明确允许在短信 credits 不足时做可审计的降级签署。

八、实战建议

如果你要在企业里排查“为什么第二位签署人还没收到邮件”这类问题,建议按下面顺序看:

  1. wizard 里生成的 signer 是否写对了 mail_sent_order
  2. sign.request 当前 state 是否仍为 sent
  3. 前一批 signer 是否已满足 _get_next_sign_request_items() 的推进条件;
  4. last_reminderremindervalidity 是否让 cron 有机会继续触发;
  5. 如果是 SMS 认证场景,再看短信 credits 与 _validate_auth_method() 的返回分支。

这样比单看“邮件有没有发出”更接近 Odoo 官方实现。

九、结论

Odoo 企业版 Sign 做得最像企业系统的地方,不在 PDF 签名本身,而在流程推进:

  • mail_sent_order 把签署人拆成一批批;
  • send_signature_accesses() 保证每次只放行该出场的人;
  • _cron_reminder() 在过期与继续推进之间做调度;
  • 用 SMS 认证和可审计降级,把安全要求接进真实业务场景。

所以它不是“群发签字请求”,而是一套围绕顺序、节奏和认证组织起来的签署协作机制。

DISCUSSION

评论区

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