审批流转

模型起草的单据走同一条审批链,超时与缺席都不改单据状态

审批流转决定一张单从建单到批准走哪条路:沿建单时冻结的那一版链走,谁能签由签核位和签字人的资格决定。模型起草的单据走同一条审批链,没有为模型另开的通道。超时与缺席都不改单据状态,所以没人签时单会积压;一类单据如果延误的损失高于错放的损失,比如必须到点通过的单据,这套取舍不适合它。

链是一行带版本的数据

每种单据在单型注册表里占一行。单型即单据的类型,如采购申请、费用报销。

列写什么
前缀单据号前缀
可否人工建单是否允许由人发起这类单
签核位序列按签法版本分组的一串规则
金额线引用指向金额档表的一行,只引用,不抄数
时限等待签字的期限
写入步骤批准后执行业务写入的那一步

签核位序列里,每条规则有三项:何时(一个判据名)、位序列或「回落到旧版本」、是否先审。最后一条必须是无条件的默认规则,所以每张单总能找到一条规则可走。

建单时,系统把签法版本和命中的金额档行一起冻结进单据。注册表之后追加的新版本,只影响新建的单。审计时拿出一张旧单,它的链和建单那天一样;反过来,紧急收紧规则时,在途单仍按旧链走完。

「何时」一栏只写判据名,判据的含义在解释器里。用已有判据重排位序列、换金额线,改的是数据,不改代码。注册表与金额档表随版本发布追加新行,旧行由触发器拒改拒删。新增一种判据,要给解释器加代码。所以改动前先问一句:这次用到新判据了吗?没有,就只是追加一行数据。

签核位的六种

签核位:链上需要一个人签字的位置。每个位有一个种类。

种类规则
必签该位必须有人签
先签有先后:前置位签完,后面的位才轮到
加签金额达到某条金额线,该位才加入链
n 选 m一组人里任 m 人签即可,如任一、任二
先审只审核内容;该位不可代签
可代高一级的人可以签低一级的位

可代是位上的资格规则;代签是持有人缺席时的补位,见「缺席与代签」。核对一条链有没有缺位,对着这六种逐位看即可。

金额档表

单型只引用金额档表,不抄数。改一条线,所有引用它的单型一起变,不会有哪一处漏改。

列含义
加签线达到它,链上加入加签位
最高档线达到它,链上加入最高层级的位
免审线金额在这条线以内的单免审
累计线与窗口在一个时间窗口内累计比较的金额线
每日免审单数上限一天最多免审几张,超出即拒单
含 / 不含金额恰好等于线时算不算达到,单独成列

含不含单独成列,因为边界上要有明确的答案。一张恰好 50,000 元的单走不走总经理,取决于比较写成 > 还是 >=。比较符写在程序里,这个决定藏在一行代码中,改它要发版,审计也看不到。写成一列,它就和金额线一起留在表里、一起有版本。

免审单数超过当日上限,系统直接拒单,不转成待审批单,审批人的待签列表里也不会多出一批本该免审的单。

下面一段 SQL 演示:金额存成整数的最小币种单位(分);含不含与金额线并排成列,线和比较符要么都填,要么都空。示意,未执行。

SQL
-- Illustrative sketch, not executed. Amounts are integer minor units (cents).-- Rolling line, its window and the daily exempt cap follow the same pattern; omitted here.CREATE TABLE approval_amount_tier (  order_type       text        NOT NULL,  effective_from   timestamptz NOT NULL,  extra_line       bigint CHECK (extra_line >= 0),    -- NULL = no such line  extra_inclusive  boolean,  top_line         bigint CHECK (top_line >= 0),  top_inclusive    boolean,  exempt_line      bigint CHECK (exempt_line >= 0),  exempt_inclusive boolean,  PRIMARY KEY (order_type, effective_from),  CHECK ((extra_line IS NULL) = (extra_inclusive IS NULL)),  CHECK ((top_line IS NULL) = (top_inclusive IS NULL)),  CHECK ((exempt_line IS NULL) = (exempt_inclusive IS NULL)),  CHECK (extra_line IS NULL OR top_line IS NULL OR extra_line < top_line));INSERT INTO approval_amount_tier  (order_type, effective_from, extra_line, extra_inclusive, top_line, top_inclusive)VALUES ('purchase_request', '2026-01-01', 500000, false, 5000000, false);

示例的 5,000 元与 50,000 元存成 500000 与 5000000 分,两条线都不含本数,所以恰好 50,000 元的单不加总经理位。填了线却没写含不含,这一行写不进表。

通则

通则不写进链:

  • 发起人不签自己的单。
  • 一人在一张单上只占一个位。唯一例外:最高档位与加签位可以由同一人一次满足。
  • 有一份「不走审批」清单,清单上的事项不建审批单。

通则对所有单型生效,不必每条链各写一遍,也就没有哪条链会漏写。

一张示例单

示例:采购申请,金额 50,000 元,金额档取上一节的示例行。

位种类谁可签状态
部门负责人先签该职位持有人,发起人本人除外已签
财务负责人加签,超过 5,000 元加入该职位持有人,已在本单签过的人除外开放,待签
总经理加签,超过 50,000 元加入无恰好 50,000 元,不含本数,不在链上

假设财务负责人出差。这个位的代签向兜底职位开放,兜底人(事先按职位排好的代签人)亲自签,签核记录标「代签」,原因写「出差」。兜底职位上的人如果正是已签的部门负责人,「一人一位」会拦下此人,系统继续往下兜。兜到底无人可签,单据停在待签,并通知上层。

工作台审批页 · 演示数据。这张单 86,400 元,超过 50,000 元,链上多了总经理位;当前停在财务位
示意 · 演示数据工作台审批页 · 演示数据。这张单 86,400 元,超过 50,000 元,链上多了总经理位;当前停在财务位

单据状态

审批单的状态与转移

图里只有签字和撤销能让单据离开待签,时间不能。

单据分三个阶段:open(待签,可裁决)、decided(已批准)、closed(已驳回或已作废)。状态本身是状态注册表里的行,加一种状态是加一行;注册表里恰有一个状态处在可裁决阶段。

  • 已批准之后,状态与决审字段冻结,只许追加附签。
  • 签核记录只追加,已签的内容不能改;单据不能删。
  • 签核人单签驳回,单据即终态。驳回后重提,生成一张新单。
  • 撤销业务单时,同一事务作废它挂着的审批单,已撤销的业务不会再获批执行。

状态转移由一支纯函数判定:输入前后两版单据,输出允许或一个错误码。纯函数:同样的输入得到同样的输出,不读外部状态。这支函数写在 PostgreSQL 里,守卫触发器只读状态注册表,再调用它。所以应用之外的连接写库,同样要过这道判定;能停用触发器的角色(表属主、超级用户)除外。

超时处理

时限到了,系统做下面这些事:

  1. 到点标红,并按配置的比例提前提示。
  2. 每天定时提醒仍可签的人,每人每单有次数上限;另给管理层发汇总。
  3. 不改单据状态:不自动批准,不自动驳回。

等满时限后,该位的代签向兜底人开放(见下一节),但签字仍要人。少数申请类单据另有规定:到期即失效,须重新申请。失效同样不算通过。

超时按失败安全(fail-closed)处理:系统缺少输入时,停在不通过的一侧。审批里最坏的结果是无人签字的单据通过,钱或货出去了,留痕里那一笔找不到签字人。积压看得见,有提醒和汇总追着;错放往往事后才发现。SZV 选积压。

缺席与代签

系统里没有「转交单据」的操作。持有人缺席时,开放代签位:

  • 兜底表按职位排好:某个位的持有人不在,由哪个职位接。
  • 三种情形触发,按单、按位分别计算:持有人全部请假、出差或停用;持有人在岗,但长时间未出现;该位等满时限。
  • 兜底人仍须亲自签,仍受「一人一位」「发起人不自签」约束;先审位不可代签。
  • 代签的开启与关闭都留痕,签核记录打「代签」标记。
  • 兜到底无人可签,单据停在待签,并通知上层;单据本身不变。

每个位因此有一份开合状态:位、是否开放、可签的人、代签的人。列表页与详情页读同一份,两处显示不会不一致。

限制与不适用

  • 积压:无人签的单一直停在待签。提醒、代签、汇总能缓解,消不掉。
  • 在途单不追溯:改链只管新单,旧单按旧链走完。
  • 一票驳回:单签驳回即终态。
  • 新判据要发版:链是数据,判据的含义是代码。
  • 延误损失高于错放损失的单据,比如必须到点通过的单据,不适用。

其他做法

做法它解决什么为什么不用
超时自动批准单据不积压出现无人签字就通过的单据,事后找不到负责的人
超时自动驳回单据按期结束驳回也是裁决,应当由人做出;积压转给发起人,重提一张新单,链从头再走
转交给临时指定的人换人灵活谁能签取决于转交人当时的选择,只能事后追问理由;兜底人由职位表算出,事前可查

与常见做法对照

Temporal 是一个开源的持久化执行框架,它把等待、计时器、升级和改派当作核心能力。它的文档审批指南里,工作流等待审批信号;时限到了,升级给备用联系人,再给一个时限;两人都不回应,自动驳回,状态记为 TIMED_OUT。

问题Temporal 审批示例SZV
等待状态存在哪工作流的事件历史,重启后重放恢复单据状态与每个位的开合状态
时限到了升级给备用人;仍无回应,自动驳回提醒、汇总,并开放代签位;状态不变
换人通知备用审批人代签位,兜底人按职位表算出
事后核对事件历史只追加的签核记录与建单快照

两边在超时后都不批准,分歧在于要不要替人做出驳回。SZV 把等待状态存成单据上的数据,和规则表放在同一个 PostgreSQL 库里,由同一套触发器与纯函数守住。

验收时可以问的问题

  • 请 SZV 演示:一个位的持有人全部请假后,代签位怎样开放、怎样关闭,签核记录里的「代签」标记在哪里。
  • 兜底表由谁维护,「长时间未出现」按多久算。这两项按企业要求在合同中约定。

相关页

这页有帮助吗谢谢,我们会据此改进这一页。

需要帮助?联系我们

想先知道我们怎么想?关于 SZV

联系我们

企业人员规模(人)

试用工作台