三面拆分⑲:审批点卸岗位 —— 负责人一律运营时分配,绑员工组,实例记 assignee

模型(用户定):审批点不绑岗位;节点负责人是运营时分配的,绑定到员工组。

- 契约:approval 节点删 approver(功能属性只剩驳回走向/催办/委托消息);
  CLOSURE_VIOLATION_KINDS 删 missing-owner-position(岗位不入编排,无岗可核);
  治理审批列表删 approverPosition 字段
- 运行时统一口径(活动点=审批点):工作流绑了员工组按组轮值(组员并集最闲者先),
  没绑组全员轮值;staffId 稳定排序保证确定性,组员全不在岗退回全员。
  ADR-011「绑定先于策略」随岗位退场 —— 工作组快照的成员集合成为轮值池
- 实例记 assignee(迁移 0014 node_instance.assignee_id):分派结果是流转事实 ——
  审批箱收件(i.assigneeId === me)、裁决权(同一判据)、员工面链条报实际承接人、
  治理台审批列表报人,全部按它认,不再有岗位比对
- closure 卸 positions 入参;desk/desk-approval/governance-ops 三路由改口;
  web 属性卡删「审批岗位」选择与警示、positions 管线拆除;种子 A5 卸 PM;
  导出依赖文件审批段只留编排语义

测试:closure 25、save-package 6、seed 8、graph-to-pkg 5、端到端八例真库全绿;
五闸退出码全过。库:迁移 + v4 就地同步(唯一生效版)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-28 21:24:41 +08:00
co-authored by Claude Fable 5
parent ecfaef1463
commit f145bde875
24 changed files with 96 additions and 273 deletions
+10 -27
View File
@@ -94,38 +94,21 @@ export function registerDeskRoutes(app: Hono<AppEnv>, container: Container): voi
return await Promise.all(
nodes.map(async (n) => {
/*
* 报谁,要和**真正派单时的口径一致**。
* 审批点:ADR-011 绑定先于策略 —— 先看工作组绑定,再退回岗位解析。
* 活动点:契约不带岗位(分派归运营)—— 起了单的报**实际承接人**(任务上的),
* 还没起的如实报「轮值待派」,不编造具体承接人。
* 报谁,要和**真正派单时的口径一致**:负责人是运营时分配的(员工组轮值 /
* 全员轮值),分派结果记在实例 assignee 上 —— 起了单的报实际承接人,
* 还没起的如实报「轮值待派」,不编造具体承接人。活动点与审批点同一口径。
*/
let who;
let fallbackName = '轮值待派';
let fallbackRole = '全员轮值';
if (n.kind === 'approval') {
const spec = n.approver;
fallbackName = spec.position;
fallbackRole = spec.position;
const bound = workflow.workGroup?.[spec.position];
const boundStaff =
bound === undefined
? undefined
: await container.org.getStaff(staffIdSchema.parse(bound)).catch(() => undefined);
// 岗位唯一解析(sole)即报人名;否则只报岗位
const holders =
boundStaff !== undefined ? [] : await container.org.staffOfPosition(spec.position).catch(() => []);
who = boundStaff ?? (holders.length === 1 ? holders[0] : undefined);
} else {
const inst = instances.find((x) => x.nodeId === n.id && x.taskId !== undefined);
const owned = inst?.taskId === undefined ? undefined : await container.task.get(taskIdSchema.parse(inst.taskId)).catch(() => undefined);
who = owned === undefined ? undefined : await container.org.getStaff(owned.ownerId).catch(() => undefined);
}
const inst = [...instances].reverse().find((x) => x.nodeId === n.id && x.assigneeId !== undefined);
const who =
inst?.assigneeId === undefined
? undefined
: await container.org.getStaff(staffIdSchema.parse(inst.assigneeId)).catch(() => undefined);
return {
nodeId: n.id,
label: n.label,
kind: n.kind === 'approval' ? ('approval' as const) : ('activity' as const),
ownerName: who?.name ?? fallbackName,
ownerRole: who?.position.name ?? fallbackRole,
ownerName: who?.name ?? '轮值待派',
ownerRole: who?.position.name ?? '运营分派',
state:
n.id === task.nodeId
? ('current' as const)