去年做一套采购审批,单笔八十万的设备采购要走三方会签:技术确认参数、财务确认预算、法务确认合同条款。上线两周,业务部门提了一个需求:主管在审批时想临时加一个人看,加的人看完,流程还要回到主管这里。
这个需求听着小,改起来牵动整个模型。原来一个节点只有一个人,"通过"就是把节点标成通过。现在一个节点可能挂五个人,中途还会加人、减人,节点什么时候算过,得重新定义。这篇文章把加签与会签的模型、收敛规则和踩过的坑写清楚。
一、加签与会签不是一回事
1.1 会签:一个节点挂多个人
会签指的是同一个审批节点上有多个审批人,全部处理完这个节点才有结论。三人会签,两人通过一人驳回,节点结论是驳回还是通过,取决于预先定的规则。
会签的关键是"节点的结论"和"某个人的动作"要分开存。人的动作只有通过与驳回两种,节点的结论要等所有人处理完再算。混在一起存,中途加人就会算错。
1.2 加签:临时插一个人进来
加签是在已有节点上增加审批人,或者在节点之间插入一个新节点。前者叫节点内加签,后者叫节点间加签,两者的模型完全不同。
节点内加签只改任务表,节点不动;节点间加签要新建节点,还要处理序号。我们后来只开放节点内加签,节点间加签走"退回发起人重提",因为插入节点会让已通过的节点失效,业务上很难解释。
1.3 三条收敛规则
节点的收敛规则我们只留三条。全票通过:所有人都通过才算过,有一个人驳回就是驳回。一票通过:任意一人通过即通过,其余人的任务自动关闭。否决制:所有人通过才算过,任意一人驳回立即终止。
这三条覆盖了实际遇到的场景。想加"两人以上通过即可"这种比例规则,先问清楚为什么,多数时候是审批人太多导致等待,解决办法是减少人,不是改规则。
规则定得越少,越容易解释。业务方问"为什么这单被驳回",答得出来是因为规则只有三条。规则一多,答复就变成查日志。
二、流程模型怎么落表
2.1 实例、节点、任务三层
表分成三层。实例表记录一张单据的整体状态,节点表记录流程走到哪一步,任务表记录每个人要做什么。三层之间用外键关联,不冗余存状态。
这样分的好处是加签只影响任务表,节点表的行数不变。查询"这张单现在等谁"就是从任务表里捞状态为待处理的行,加不加签都一样查。
2.2 收敛判断放在一条 SQL 里
下面的语句是节点收敛的核心。审批人提交动作后先更新任务,再用一条带聚合的更新重算节点状态。判断全部落在 case 里,规则改了只改这一段。
sql
create table ap_node (
id bigserial primary key, instance_id bigint not null, seq int not null,
mode text not null check (mode in ('ALL', 'ANY', 'VETO')),
required int not null, approved int not null default 0, rejected int not null default 0,
state text not null default 'PENDING'
);
create table ap_task (
id bigserial primary key, node_id bigint not null references ap_node (id),
approver text not null, kind text not null default 'NORMAL',
state text not null default 'PENDING', opinion text, acted_at timestamptz
);
with act as (
update ap_task t set state = , opinion = , acted_at = now()
where t.id = and t.state = 'PENDING'
returning t.node_id
), agg as (
select n.id, n.mode, n.required,
count(*) filter (where k.state = 'APPROVED') as ok,
count(*) filter (where k.state = 'REJECTED') as no
from ap_node n join ap_task k on k.node_id = n.id
where n.id in (select node_id from act)
group by n.id, n.mode, n.required
)
update ap_node n
set approved = agg.ok, rejected = agg.no,
state = case
when agg.mode in ('ALL', 'VETO') and agg.no > 0 then 'REJECTED'
when agg.mode = 'ANY' and agg.ok > 0 then 'APPROVED'
when agg.mode <> 'ANY' and agg.ok >= agg.required then 'APPROVED'
else 'PENDING'
end
from agg
where n.id = agg.id and n.state = 'PENDING';
required 是节点的应到人数,加签时要跟着加一。这一点容易漏:任务表加了人,节点表的应到人数没加,节点会提前通过。我们的做法是把加签和改 required 放在同一个事务里,用行锁锁住节点。
2.3 为什么不用状态字符串串起来
早期想过把整个流程塞进一个字段,比如"技术通过-财务待办-法务待办"。写起来快,查起来难受。想知道"谁还没处理",得解析字符串;加签要在中间插一段,字符串就乱了。
拆表之后,加签是插入一行,撤回是更新一行,统计是聚合查询。代价是关联查询多,收益是任何变更都不需要改写已有数据。
2.4 节点序号怎么维护
节点序号用整数留空位,起步按十递增,十、二十、三十。中间要插节点,取前后两个序号的中间值,不用整表重排。这个做法撑得住十来次插入,超过就在低峰期重排一次。
序号重复的检查放在写入时做,用实例加序号建唯一索引。没有这个索引,并发插入会撞号,撞了之后流程走向取决于查询的排序,这种问题查起来极难复现。
三、加签插在哪里
3.1 前加签与后加签
前加签是"在我处理之前,先让某人看一眼",后加签是"我处理完了,再让某人确认"。业务上两种都需要,实现上区别在于任务的处理顺序。
我们的做法是给任务加一个 sort_no,前加签插入比当前任务小的序号,后加签插入比当前任务大的序号。加签产生的新任务带 kind = 'ADDED',在界面上和普通任务区分显示。
3.2 加签不改变已生效的结论
这条是硬约束。节点已经通过,再加签一个人,不能把节点拉回待处理。否则会出现"流程走完了又被加签卡住"的情况,业务上没人能接受。
如果确实需要补一道审批,正确做法是发起一个新的节点,序号排在当前节点之后,节点状态从头算。这样责任清楚:前面那次通过是有效的,补的这一道是新增的。
3.3 意见怎么合并展示
会签的意见要合并成一段给后续审批人看,不能只显示"三人通过"。我们的展示顺序是驳回意见优先,其次是有内容的通过意见,空意见折叠。
驳回意见优先是因为它决定后续动作。有人只看汇总不看明细,漏掉一条驳回意见,等于白走一遍流程。折叠空意见是为了让有内容的意见显眼,多数人点通过时不写理由,全展开反而淹没有效信息。
四、落地时的几个决定
4.1 会签要不要设主办人
设主办人的好处是有人负责推进,坏处是其他人容易变成"挂名"。我们试过设主办人,三个月后发现七成单据的主办人只是点了通过,没有实际推动。
后来取消主办人,改成节点超时提醒全员,谁先处理谁先写意见。等待时间反而短了,因为没人觉得"有人会管"。
4.2 一票否决怎么表达
否决不是驳回。驳回是"这份材料不行,改完再来",否决是"这件事本身不能做"。两者在流程上的区别是驳回可以回到发起人重提,否决直接终止实例。
界面上要给这两个按钮不同的措辞,还要在点击时二次确认。混用会导致数据失真:明明是材料问题,被记成否决,季度统计里就会出现一堆莫名其妙的终止单。
4.3 超时、代签与撤回
超时策略按节点配,不按人配。节点超过约定时长,先提醒本人,再提醒其上级,再超时就转给代理人。三级递进,每级都要留记录。
代签只允许在代理人关系里做,不允许管理员直接改。撤回只允许发起人在首个节点未完成前操作,已经有人处理过的节点不能撤回,否则处理记录没有意义。
4.4 审批人怎么选
按角色选,不按人配。角色从组织数据里取,人员变动时改组织关系,不改流程定义。这条原则省了大量维护工作,也避免出现"人调岗了流程还发给他"的尴尬。
角色之下可以再加一层范围,比如按金额区间或者按所属部门筛。范围写在节点的配置里,不写在代码里。业务上调整区间时,运营自己在后台改,不需要研发介入。
常见问题
问:会签节点的人可以中途减吗?
答:可以减,但要区分两种情形。还没处理的人可以移除,required 同步减一;已经处理的人不能移除,只能新增一条"意见保留、任务关闭"的记录。前者不影响结论,后者要保留痕迹,否则审计时说不清这个人的意见为什么消失了。
问:加签的人驳回,流程回到哪里?
答:按节点的收敛规则走。节点内加签的人属于同一节点,他驳回就是节点驳回,按配置回到发起人或者上一个节点。如果业务上希望加签只是"提意见不影响结论",那就不该用加签,应该用抄送或者评论。
问:多人会签的意见冲突怎么处理?
答:系统不做判断,把冲突原样呈现给下一个节点。技术上能做的是给意见加标签,比如"预算"、"条款"、"参数",让后续审批人按维度看。真正的裁决靠人,流程只保证每个意见都被看到、都有落款。
问:流程改版之后,在途单据怎么办?
答:在途单据沿用发起时的版本,不跟着改。做法是实例表存一个版本号,节点和规则按版本取。这样改版不会影响已经在走的单,新单按新规则走。代价是要同时维护多个版本的规则,所以改版频率要控制。