@yss123/cage-approval 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/bridge.js +8 -4
- package/dist/bridge.js.map +1 -1
- package/dist/deadlines.d.ts +29 -0
- package/dist/deadlines.js +72 -0
- package/dist/deadlines.js.map +1 -0
- package/dist/engine.d.ts +86 -10
- package/dist/engine.js +205 -116
- package/dist/engine.js.map +1 -1
- package/dist/execution.d.ts +41 -0
- package/dist/execution.js +85 -0
- package/dist/execution.js.map +1 -0
- package/dist/http.d.ts +2 -1
- package/dist/http.js +73 -12
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +17 -0
- package/dist/openapi.js +71 -0
- package/dist/openapi.js.map +1 -0
- package/dist/operations-schema.d.ts +20 -0
- package/dist/operations-schema.js +20 -0
- package/dist/operations-schema.js.map +1 -0
- package/dist/operations.d.ts +118 -0
- package/dist/operations.js +349 -0
- package/dist/operations.js.map +1 -0
- package/dist/route.d.ts +59 -7
- package/dist/route.js +65 -27
- package/dist/route.js.map +1 -1
- package/dist/selectors.d.ts +8 -0
- package/dist/selectors.js +53 -0
- package/dist/selectors.js.map +1 -0
- package/dist/task-actions.d.ts +94 -0
- package/dist/task-actions.js +198 -0
- package/dist/task-actions.js.map +1 -0
- package/dist/task-stages.d.ts +17 -0
- package/dist/task-stages.js +47 -0
- package/dist/task-stages.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,6 +60,9 @@ export const poRoute = route('po-approval', [
|
|
|
60
60
|
| `ApprovalAdapter` 等适配类型 | 对接外部审批实现时遵守的生命周期协议 |
|
|
61
61
|
| `approvalRoutes` | 受认证保护的 HTTP 审批接口装配 |
|
|
62
62
|
| `AiStepOptions`、`approvalRoleContext` | AI 节点配置与自动执行角色上下文 |
|
|
63
|
+
| `TaskActionPolicy`、`TaskActionInput`、`InCodeEngine.act` | 人工节点显式开放转办、委派归还、当前节点并行必签;版本与操作记录共同提交 |
|
|
64
|
+
| `InCodeEngine.taskHistory` | 当前单据授权读者按游标读取人工操作记录 |
|
|
65
|
+
| `approvalOperations()` | 显式启用框架持久站内提醒、申请人催办与已读,复用现有 worker |
|
|
63
66
|
|
|
64
67
|
领域绑定原语 `approval()` 从 `@yss123/cage-kernel` 导入;不要与本包的路由原语混淆。
|
|
65
68
|
|
|
@@ -67,8 +70,18 @@ AI 审批使用同一个 `step` 的 `kind: 'ai'` 选项,配置模型、材料
|
|
|
67
70
|
|
|
68
71
|
## 文档
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
人工节点可设置 `actions: { transfer: true, delegate: true, addSign: true }`,默认关闭并随流程实例保存。
|
|
74
|
+
委派未归还不能批准;前、后及并行加签在 any/all 模式下都必须全部通过。候选任务先领取再决定,任务操作及启用候选/流转节点的决定须携带 revision。
|
|
75
|
+
不能把任务管理理解为授予角色能力。升级需执行追加系统迁移,旧实例保持原政策。
|
|
76
|
+
|
|
77
|
+
通用时效通过 `export const approvalOps = approvalOperations()` 启用。人工 `step` 配置 `sla: { durationMinutes: 1440, remindBeforeMinutes: 60 }`,
|
|
78
|
+
`route` 第三参配置 `reminders: { requesterField: 'createdBy', cooldownMinutes: 15 }`,申请人字段必须进入 `approval.routable`。
|
|
79
|
+
框架负责持久提醒、去重、当前接收人授权、冷却和已读;Admin 提供“我的申请”和“流程提醒”,业务应用无需自行实现通知表或定时器。
|
|
80
|
+
节点时限采用自然分钟,加签、转办和领取不重置;尚无工作日历、自动升级、外部通知、并行网关或流程设计器。
|
|
81
|
+
|
|
82
|
+
- [人工审批任务流转、SDK 与边界](https://github.com/shunshunyue/cage/blob/main/docs/development/guides/approvals.md)
|
|
83
|
+
- [标准采购审批链路](https://github.com/shunshunyue/cage/blob/main/docs/development/examples/purchase-example.md)
|
|
84
|
+
- [AI 审批三态、转人工和授权](https://github.com/shunshunyue/cage/blob/main/docs/development/guides/intelligence.md)
|
|
72
85
|
- [采购审批完整源码](https://github.com/shunshunyue/cage/blob/main/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts)
|
|
73
86
|
|
|
74
87
|
源码与反馈:[GitHub](https://github.com/shunshunyue/cage)。许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
|
package/dist/bridge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 桥接:引擎的结论只能转成命令,以流程身份调用,走完整 12 关。引擎侧没有任何业务表的数据库权限。
|
|
2
|
-
import { cageError } from '@yss123/cage-kernel';
|
|
2
|
+
import { cageError, loadUserGrants, userCtx } from '@yss123/cage-kernel';
|
|
3
3
|
import { approvalRoleContext } from './ai.js';
|
|
4
4
|
/**
|
|
5
5
|
* 从流程绑定选择通过/驳回/退回命令,再为结论构造可信 process 上下文。
|
|
@@ -15,11 +15,15 @@ export async function applyOutcome(k, r, tx) {
|
|
|
15
15
|
throw new Error('[CAGE-APPR-006] 审批未绑定退回修改命令');
|
|
16
16
|
let ctx;
|
|
17
17
|
if (r.lastApprover) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// 待办决定已持有事务连接时复用它;同实例并发排队不能耗尽池后让锁持有者再等新连接。
|
|
19
|
+
const grants = await loadUserGrants(tx ?? k.app, r.lastApprover, r.tenantId);
|
|
20
|
+
const user = grants ? userCtx(grants) : null;
|
|
21
|
+
if (!user?.capabilities.has(k.registry.capabilityOf(cmd)) || !user.capabilities.has(`${binding.a.agg.name}.read`))
|
|
20
22
|
throw cageError('FORBIDDEN');
|
|
21
23
|
// 先核验真人对目标单据的权限;命令内声明的后台读依赖仍按已有流程身份完成业务校验。
|
|
22
|
-
await k.read.
|
|
24
|
+
const visible = await k.read.tx(user, read => read.selectFrom(`safe.${binding.a.agg.table.name}`).select('id').where('id', '=', r.businessKey).executeTakeFirst());
|
|
25
|
+
if (!visible)
|
|
26
|
+
throw cageError('NOT_FOUND');
|
|
23
27
|
ctx = k.processCtx({ process: r.process, tenantId: r.tenantId, onBehalfOf: r.lastApprover, capabilities: [k.registry.capabilityOf(cmd)] });
|
|
24
28
|
}
|
|
25
29
|
else
|
package/dist/bridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,OAAO,EAAE,SAAS,EAAyB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Transaction } from 'kysely';
|
|
2
|
+
/** 自然分钟时限;允许小数,精度至少一毫秒,最长一年,不包含工作日历或暂停扣时。 */
|
|
3
|
+
export interface ApprovalSla {
|
|
4
|
+
/** 从进入节点起计算的总办理分钟数,前后加签共用这一截止时间。 */
|
|
5
|
+
durationMinutes: number;
|
|
6
|
+
/** 在截止前多少分钟产生临期提醒;省略只提醒超时,必须小于总时限。 */
|
|
7
|
+
remindBeforeMinutes?: number;
|
|
8
|
+
}
|
|
9
|
+
/** 申请人催办政策随实例冻结;字段只标识申请人,不授予其审批结论能力。 */
|
|
10
|
+
export interface ApprovalReminderPolicy {
|
|
11
|
+
/** routable 快照中的单个用户 UUID 字段,运行时还需当前用户有效且可见业务单据。 */
|
|
12
|
+
requesterField: string;
|
|
13
|
+
/** 两次成功催办间隔的自然分钟数,同实例各节点共用冷却;无收件人不占用冷却。 */
|
|
14
|
+
cooldownMinutes: number;
|
|
15
|
+
}
|
|
16
|
+
/** 校验并冻结节点政策;未知键和不合理提前量明确报声明错误,不把配置失误静默当作关闭提醒。 */
|
|
17
|
+
export declare function approvalSla(value: ApprovalSla): Readonly<ApprovalSla>;
|
|
18
|
+
/** 校验申请人字段名与正数冷却;真正的字段存在性、routable 和公开性在注册表装配后再检查。 */
|
|
19
|
+
export declare function approvalReminderPolicy(value: ApprovalReminderPolicy): Readonly<ApprovalReminderPolicy>;
|
|
20
|
+
/**
|
|
21
|
+
* 在进入人工节点的同一事务登记固定截止时间;使用数据库时间,服务重启不会丢失或重新计时。
|
|
22
|
+
* next_notice_scan_at 是有界扫描的持久轮转位置,后续收件人变化可补投,不让已提醒的旧步骤长期占满扫描批次。
|
|
23
|
+
*/
|
|
24
|
+
export declare function initializeDeadline(tx: Transaction<any>, instanceId: string, step: number, policy: ApprovalSla): Promise<void>;
|
|
25
|
+
/** 前向迁移:时间只新增到节点,通知以来源/任务/接收人唯一;已读不删除历史,禁止修改已发布迁移。 */
|
|
26
|
+
export declare const APPROVAL_DEADLINES_MIGRATION: {
|
|
27
|
+
name: string;
|
|
28
|
+
sql: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 人工节点的自然时间办理政策及持久存储,由 route 校验声明、engine 在进入节点时写入。
|
|
3
|
+
* 时限属于整个节点:领取、转办、委派和前后加签均不重置;这里只计时,不替代领域结论或自动批准。
|
|
4
|
+
* 迁移在已发布审批表之上追加,旧实例没有时限或催办政策,升级不会自动启用新行为。
|
|
5
|
+
*/
|
|
6
|
+
import { declError } from '@yss123/cage-kernel';
|
|
7
|
+
import { sql } from 'kysely';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
const minutes = z.number().finite().min(1 / 60_000).max(525_600);
|
|
10
|
+
const slaSchema = z.strictObject({ durationMinutes: minutes, remindBeforeMinutes: minutes.optional() })
|
|
11
|
+
.refine(value => value.remindBeforeMinutes === undefined || Math.round(value.remindBeforeMinutes * 60_000) < Math.round(value.durationMinutes * 60_000));
|
|
12
|
+
const reminderSchema = z.strictObject({ requesterField: z.string().regex(/^[A-Za-z][A-Za-z0-9]*$/), cooldownMinutes: minutes });
|
|
13
|
+
/** 校验并冻结节点政策;未知键和不合理提前量明确报声明错误,不把配置失误静默当作关闭提醒。 */
|
|
14
|
+
export function approvalSla(value) {
|
|
15
|
+
const parsed = slaSchema.safeParse(value);
|
|
16
|
+
if (!parsed.success)
|
|
17
|
+
throw declError('CAGE-APPR-006', 'sla 须为有效自然分钟时限(至少一毫秒,最长一年),提前提醒必须小于总时限');
|
|
18
|
+
return Object.freeze(parsed.data);
|
|
19
|
+
}
|
|
20
|
+
/** 校验申请人字段名与正数冷却;真正的字段存在性、routable 和公开性在注册表装配后再检查。 */
|
|
21
|
+
export function approvalReminderPolicy(value) {
|
|
22
|
+
const parsed = reminderSchema.safeParse(value);
|
|
23
|
+
if (!parsed.success)
|
|
24
|
+
throw declError('CAGE-APPR-006', 'reminders 需要有效 requesterField 和自然分钟 cooldownMinutes(至少一毫秒,最长一年)');
|
|
25
|
+
return Object.freeze(parsed.data);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 在进入人工节点的同一事务登记固定截止时间;使用数据库时间,服务重启不会丢失或重新计时。
|
|
29
|
+
* next_notice_scan_at 是有界扫描的持久轮转位置,后续收件人变化可补投,不让已提醒的旧步骤长期占满扫描批次。
|
|
30
|
+
*/
|
|
31
|
+
export async function initializeDeadline(tx, instanceId, step, policy) {
|
|
32
|
+
const duration = Math.round(policy.durationMinutes * 60_000);
|
|
33
|
+
const before = policy.remindBeforeMinutes === undefined ? null : Math.round(policy.remindBeforeMinutes * 60_000);
|
|
34
|
+
await tx.updateTable('flow.approval_step').set({
|
|
35
|
+
due_at: sql `now() + ${duration} * interval '1 millisecond'`,
|
|
36
|
+
remind_at: before === null ? null : sql `now() + ${duration - before} * interval '1 millisecond'`,
|
|
37
|
+
next_notice_scan_at: sql `now() + ${duration - (before ?? 0)} * interval '1 millisecond'`,
|
|
38
|
+
}).where('instance_id', '=', instanceId).where('step', '=', step).execute();
|
|
39
|
+
}
|
|
40
|
+
/** 前向迁移:时间只新增到节点,通知以来源/任务/接收人唯一;已读不删除历史,禁止修改已发布迁移。 */
|
|
41
|
+
export const APPROVAL_DEADLINES_MIGRATION = {
|
|
42
|
+
name: 'cage:approval_deadlines_003',
|
|
43
|
+
sql: `
|
|
44
|
+
ALTER TABLE flow.approval_instance ADD COLUMN reminder_policy jsonb;
|
|
45
|
+
ALTER TABLE flow.approval_instance ADD COLUMN next_urge_at timestamptz;
|
|
46
|
+
ALTER TABLE flow.approval_step ADD COLUMN due_at timestamptz;
|
|
47
|
+
ALTER TABLE flow.approval_step ADD COLUMN remind_at timestamptz;
|
|
48
|
+
ALTER TABLE flow.approval_step ADD COLUMN next_notice_scan_at timestamptz;
|
|
49
|
+
ALTER TABLE flow.approval_step ADD CONSTRAINT approval_step_deadline_order CHECK (remind_at IS NULL OR (due_at IS NOT NULL AND remind_at < due_at));
|
|
50
|
+
CREATE INDEX approval_step_notice_scan_idx ON flow.approval_step(next_notice_scan_at, instance_id) WHERE next_notice_scan_at IS NOT NULL;
|
|
51
|
+
CREATE TABLE flow.approval_notice (
|
|
52
|
+
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
53
|
+
tenant_id uuid NOT NULL,
|
|
54
|
+
instance_id uuid NOT NULL REFERENCES flow.approval_instance(id),
|
|
55
|
+
task_id uuid NOT NULL REFERENCES flow.approval_task(id),
|
|
56
|
+
step integer NOT NULL,
|
|
57
|
+
recipient_id uuid NOT NULL,
|
|
58
|
+
source text NOT NULL,
|
|
59
|
+
kind text NOT NULL CHECK (kind IN ('due_soon','overdue','urge')),
|
|
60
|
+
actor_id uuid,
|
|
61
|
+
reason text,
|
|
62
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
63
|
+
read_at timestamptz,
|
|
64
|
+
UNIQUE(instance_id, step, source, task_id, recipient_id)
|
|
65
|
+
);
|
|
66
|
+
CREATE INDEX approval_notice_recipient_idx ON flow.approval_notice(tenant_id, recipient_id, id);
|
|
67
|
+
CREATE INDEX approval_notice_unread_idx ON flow.approval_notice(tenant_id, recipient_id, id) WHERE read_at IS NULL;
|
|
68
|
+
GRANT SELECT, INSERT, UPDATE ON flow.approval_notice TO cage_app;
|
|
69
|
+
GRANT USAGE, SELECT ON SEQUENCE flow.approval_notice_id_seq TO cage_app;
|
|
70
|
+
`,
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=deadlines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deadlines.js","sourceRoot":"","sources":["../src/deadlines.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 原生结构化审批执行器:实例、AND 分支、候选/直派任务及签署阶段存在 flow 模式,结论经桥接调用领域命令。
|
|
3
|
+
* HTTP、worker 和 AI 运行时共用本入口;按实例锁串行决定,前后签激活与最后结论共同事务提交。
|
|
4
|
+
* waiting 不是完成;任务选人不授予能力,最后命令拒绝(如超额度)会回滚本次决定及附带变化。
|
|
5
|
+
*/
|
|
1
6
|
import { type Kernel, type Ctx } from '@yss123/cage-kernel';
|
|
2
7
|
import { type ApprovalEngine } from '@yss123/cage-kernel/worker';
|
|
3
8
|
import { type Transaction } from 'kysely';
|
|
4
9
|
import { type Route } from './route.js';
|
|
10
|
+
import { type BranchLocation } from './execution.js';
|
|
5
11
|
import { type AiDecision } from './ai.js';
|
|
12
|
+
import { type TaskActionInput, type TaskActionPolicy } from './task-actions.js';
|
|
6
13
|
/** 对外待办摘要;step 为 0 基索引,vars 仅来自 routable 白名单,不包含整份领域对象。 */
|
|
7
14
|
export interface TaskView {
|
|
8
15
|
id: string;
|
|
@@ -12,23 +19,50 @@ export interface TaskView {
|
|
|
12
19
|
businessKey: string;
|
|
13
20
|
step: number;
|
|
14
21
|
title: string;
|
|
22
|
+
/** 所属并行组/分支路径;普通主线或旧顺序实例为空数组,不把索引大小当作实际执行先后。 */
|
|
23
|
+
branchPath: BranchLocation[];
|
|
15
24
|
status: string;
|
|
16
25
|
vars: Record<string, unknown>;
|
|
17
26
|
createdAt: string;
|
|
27
|
+
/** 节点总办理截止 UTC 时间;无 SLA 为 null,waiting 或转办不重置,不代表自动批准时间。 */
|
|
28
|
+
dueAt: string | null;
|
|
29
|
+
/** 待办版本,启用任务流转的节点提交决定时也必须携带。 */
|
|
30
|
+
revision: number;
|
|
31
|
+
/** 当前持有人;候选池待领取时为空。 */
|
|
32
|
+
assigneeId: string | null;
|
|
33
|
+
/** direct 直接分配,candidate 允许当前候选领取/持有人释放。 */
|
|
34
|
+
assignment: 'direct' | 'candidate';
|
|
35
|
+
/** 加签来源任务;旧并行加签和原始任务可为空。 */
|
|
36
|
+
parentTaskId: string | null;
|
|
37
|
+
/** 相对父任务的签署顺序;null 表示原票或旧版未记录签序。 */
|
|
38
|
+
signPosition: 'parallel' | 'before' | 'after' | null;
|
|
39
|
+
/** 实例允许的新加签位置;未开放为 [],第一批旧快照仅 parallel,不表示本任务可以再次加签。 */
|
|
40
|
+
signPositions: readonly ('parallel' | 'before' | 'after')[];
|
|
41
|
+
/** 委派原负责人;未委派或已转办时为空。 */
|
|
42
|
+
ownerId: string | null;
|
|
43
|
+
/** pending 表示受托处理中,只能归还;resolved 表示已归还原负责人。 */
|
|
44
|
+
delegation: 'pending' | 'resolved' | null;
|
|
45
|
+
/** original 是路由选人,additional 是节点前后/并行追加必签。 */
|
|
46
|
+
taskKind: 'original' | 'additional';
|
|
47
|
+
/** 实例快照中的动作政策,仅供界面显示;服务端仍会实时授权。 */
|
|
48
|
+
actions: Readonly<TaskActionPolicy>;
|
|
18
49
|
}
|
|
19
50
|
/**
|
|
20
|
-
*
|
|
51
|
+
* 有限无环路由的持久执行器:实例冻结展开步骤与依赖,人工任务和 AI 节点共享同一推进/结束边界。
|
|
21
52
|
* 以实例行锁串行化决定,最后结论与领域命令共同事务提交;命令失败时本次审批也不能算完成。
|
|
22
53
|
*/
|
|
23
54
|
export declare class InCodeEngine implements ApprovalEngine {
|
|
24
55
|
private readonly k;
|
|
25
56
|
readonly route: Route;
|
|
26
57
|
constructor(k: Kernel, route: Route);
|
|
27
|
-
/**
|
|
28
|
-
|
|
58
|
+
/**
|
|
59
|
+
* 执行候选领取/释放或本人待办转办/委派/归还/前后加签;必需任务版本与原因,失败不会部分更新。
|
|
60
|
+
* 分配或动作政策须显式启用,接收人须已有批准和读取权限;返回版本及新任务 ID,不推进领域状态。
|
|
61
|
+
*/
|
|
62
|
+
act(taskId: string, userId: string, tenantId: string, input: TaskActionInput): Promise<import("./task-actions.js").TaskActionResult>;
|
|
29
63
|
/** 登记当前节点与提交版本;AI 节点排队等待运行时领取,人工节点立即解析候选并创建待办。 */
|
|
30
64
|
private openStep;
|
|
31
|
-
/**
|
|
65
|
+
/** direct 逐人建立票,candidate 只建一项未领取任务;空候选明确报错,不跳过或默认通过。 */
|
|
32
66
|
private openTasks;
|
|
33
67
|
/**
|
|
34
68
|
* 在消费事务中创建新轮次:先取消旧执行者,再保存展开步骤和触发事件/提交版本。
|
|
@@ -52,44 +86,86 @@ export declare class InCodeEngine implements ApprovalEngine {
|
|
|
52
86
|
tenantId: string;
|
|
53
87
|
tx: Transaction<any>;
|
|
54
88
|
}): Promise<void>;
|
|
55
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* 从本人最近 200 条任务候选中按当前单据 read/范围过滤,撤权或停用后不继续披露任务摘要。
|
|
91
|
+
* vars 是历史 routable 摘要,始终去隐藏字段并掩码;需要明文时另经带审计的业务读接口获取。
|
|
92
|
+
*/
|
|
56
93
|
tasksOf(userId: string, tenantId: string, status?: string): Promise<TaskView[]>;
|
|
57
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* 查询最近 200 项未领取候选中的可见任务;当前候选身份与批准/read 权限均需成立。
|
|
96
|
+
* 列表不是占用承诺,领取必须再次锁定并校验;待领取任务不会混入 tasksOf/AI 的可决定清单。
|
|
97
|
+
*/
|
|
98
|
+
candidateTasks(userId: string, tenantId: string): Promise<TaskView[]>;
|
|
99
|
+
/** 共用安全摘要投影;候选规则在读取时解析,组织变化即时影响候选清单,历史变量仍始终掩码。 */
|
|
100
|
+
private listTasks;
|
|
101
|
+
/** 以业务对象读权限查看审批轮次;步骤标题取提交时的快照,隐藏内部 policy、提示词、工具历史和运行凭据。 */
|
|
58
102
|
history(aggregate: string, businessKey: string, ctx: Ctx): Promise<{
|
|
59
103
|
id: any;
|
|
60
104
|
process: string;
|
|
61
105
|
status: any;
|
|
62
106
|
currentStep: any;
|
|
107
|
+
currentSteps: any[];
|
|
108
|
+
definitionVersion: string | null;
|
|
63
109
|
createdAt: string;
|
|
64
110
|
updatedAt: string;
|
|
65
111
|
steps: {
|
|
66
112
|
index: any;
|
|
113
|
+
title: string;
|
|
114
|
+
branchPath: BranchLocation[];
|
|
115
|
+
dependsOn: number[];
|
|
67
116
|
kind: any;
|
|
68
117
|
status: any;
|
|
69
118
|
decision: any;
|
|
70
119
|
detailRestricted: boolean;
|
|
71
120
|
actor: any;
|
|
72
121
|
updatedAt: string;
|
|
122
|
+
dueAt: string | null;
|
|
73
123
|
}[];
|
|
74
124
|
}[]>;
|
|
125
|
+
/**
|
|
126
|
+
* 按业务对象当前 read/范围读取本流程的人工操作记录,跨轮次以递增 ID 游标分页。
|
|
127
|
+
* after 是服务端返回的十进制字符串,避免 bigint 转为 JS number 丢失精度;默认 50、最多 200 条。
|
|
128
|
+
* 历史意见仅供同一对象的授权读者共享,不返回任务变量或 AI 提示词;旧迁移前的动作不会伪造补录。
|
|
129
|
+
*/
|
|
130
|
+
taskHistory(aggregate: string, businessKey: string, ctx: Ctx, options?: {
|
|
131
|
+
after?: string;
|
|
132
|
+
limit?: number;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
items: {
|
|
135
|
+
id: string;
|
|
136
|
+
instanceId: any;
|
|
137
|
+
taskId: any;
|
|
138
|
+
actorId: any;
|
|
139
|
+
action: any;
|
|
140
|
+
reason: any;
|
|
141
|
+
details: any;
|
|
142
|
+
createdAt: string;
|
|
143
|
+
}[];
|
|
144
|
+
nextCursor: string | null;
|
|
145
|
+
}>;
|
|
75
146
|
/** 审批人做出决定;流程结束时在同一事务里执行结论命令 */
|
|
76
|
-
decide(taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected' | 'returned', comment?: string): Promise<{
|
|
147
|
+
decide(taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected' | 'returned', comment?: string, expectedRevision?: number): Promise<{
|
|
77
148
|
finished: boolean;
|
|
78
149
|
outcome?: string;
|
|
79
150
|
}>;
|
|
80
151
|
/** AI 助理的动作回执可与人工待办决定共用该事务,重试不重复审批。 */
|
|
81
152
|
/**
|
|
82
153
|
* 先按租户/受托人定位任务,再锁实例和任务并实时验证能力/单据可见性;过期待办不允许再次决定。
|
|
83
|
-
*
|
|
154
|
+
* 委派未归还或 waiting 时拒绝决定;候选/启用流转的节点要求 revision,避免责任或阶段变化后旧决定仍生效。
|
|
155
|
+
* 驳回/退回直接结束;all 等待全部,any 等任一原票及全部追加票;批准后激活已满足依赖的前后签。
|
|
84
156
|
* 外层拥有事务提交权,可与 AI 助理动作回执共同提交;finished:false 仅表示流程还需后续步骤。
|
|
85
157
|
*/
|
|
86
|
-
decideIn(tx: Transaction<any>, taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected' | 'returned', comment?: string): Promise<{
|
|
158
|
+
decideIn(tx: Transaction<any>, taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected' | 'returned', comment?: string, expectedRevision?: number): Promise<{
|
|
87
159
|
finished: boolean;
|
|
88
160
|
outcome?: string;
|
|
89
161
|
}>;
|
|
90
162
|
/** 先执行带版本的领域结论命令,成功后才关闭剩余待办并写流程终态,保证两边不会各自成功。 */
|
|
91
163
|
private finish;
|
|
92
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* 在实例锁内完成一项节点,再开启所有前驱已通过且尚无持久记录的节点;AND 汇合等待每条分支末端。
|
|
166
|
+
* 创建节点和最终领域结论都在当前事务内,失败回滚本次签署;重启只依赖数据库状态,唯一键阻止重复节点。
|
|
167
|
+
* current_step 仅保留最小活跃索引给旧读者,决定、AI 与提醒均以节点自身状态为准。
|
|
168
|
+
*/
|
|
93
169
|
private advance;
|
|
94
170
|
/** 只有仍然占据该节点的 run 能提交;调用方需在同一事务核验 AI run 租约。 */
|
|
95
171
|
/**
|