管理覆核群組、範本與投遞
情境:公司要由財務主管覆核高金額訂單,並在流程結束後可靠地通知 ERP。
前置條件
公司 manager 才能授予 Review module 存取;manager 角色可管理群組與範本,creator 角色可建立流程。被指派的公司成員可處理自己的 assignment。以下所有 private 請求使用使用者 access token,而且 Review module 是 company-wide,路徑不帶 custom-table scope。
這是管理端流程;要讓資料表的 create/update/delete 自動進入覆核,請接著使用核准流程設定 require_approval 規則與範本。
步驟
1. 授予 Review manager
company manager 可透過 review.access.grant 對一位使用者授予 manager。也可改用 department_id 授權整個部門,但同一個 grant 只能提供 user_id 或 department_id 其中一個。
POST /private/module/review/access
Content-Type: application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"role_type": "manager"
}保留回應的 access.id;撤權時 review.access.revoke 使用的是這個 ID,不是 user ID。
2. 建立 reviewer group
以 Review manager 呼叫 review.groups.create 建立「訂單覆核人員」。member_ids 必須都是同公司使用者;群組可集中管理未來流程的 reviewer 集合。
POST /private/module/review/groups
Content-Type: application/json
{
"name": "訂單覆核人員",
"description": "負責審核高金額訂單",
"member_ids": [
"33333333-3333-4333-8333-333333333333"
]
}以下假設 group ID 是 44444444-4444-4444-8444-444444444444。群組成員更新會影響未來 assignment;已建立流程保留建立當下的 gate/assignment snapshot。
群組成員資格刻意寬鬆:這個端點只擋已刪除的使用者。未驗證或已到期的帳號仍可被加入,也會出現在 member_ids 中。「是群組成員」與「算得上 reviewer」是兩件事——見下方誰才算 reviewer。
3. 建立可重用 template
在 review.templates.create 中,gate 依陣列順序執行。範例只有一關,群組中任一人核准即可通過;若要每個符合者都同意,使用適合的 condition/mode。
POST /private/module/review/templates
Content-Type: application/json
{
"name": "高金額訂單覆核",
"description": "財務主管單關覆核",
"require_signature": false,
"gates": [
{
"name": "主管覆核",
"condition": {
"type": "group",
"group_id": "44444444-4444-4444-8444-444444444444",
"mode": "any"
}
}
]
}以下假設 template ID 是 55555555-5555-4555-8555-555555555555。範本更新只影響之後建立的流程;既有流程不會被悄悄改寫。資料表 require_approval 規則也可引用這個 template。
直接指名一個人
condition tree 接受三種節點:組合節點 {"type": "and"|"or", "children": [...]}(2–20 個 children)、上面那種 group leaf,以及 user leaf {"type": "user", "user_id": "<36 字元 uuid>"}。指定單一核准人不必再先建一個只有一人的群組。user leaf 可以裸著當整個 condition,也可以掛在任何 and/or 之下、與 group leaf 自由混用;template 與 inline process gate 都支援,而且 template 會原樣帶進 process snapshot。
{
"name": "負責人簽核",
"condition": {
"type": "or",
"children": [
{
"type": "group",
"group_id": "44444444-4444-4444-8444-444444444444",
"mode": "any"
},
{
"type": "user",
"user_id": "33333333-3333-4333-8333-333333333333"
}
]
}
}該使用者會成為單人選舉人團,拿到剛好一筆 assignment。只有會回傳 group_ids 的地方——建立與詳情回應的 gates[].assignments[]——純直接 reviewer 的 group_ids 會是 [];引擎內部用來標記虛擬選舉人團的鍵在序列化前就被剝掉,永遠不會外流給客戶端。列表回應、pending queue、audit changes 與 webhook body 根本沒有 group_ids 這個欄位,不要在那些地方去找它。同時被 group leaf 與 user leaf 指名的人仍然只有一筆 assignment,其 group_ids 只列出真實的 group id,而他那一票同時算給他所代表的每一個 leaf。
condition 物件是嚴格的
三種節點都會在任何業務驗證之前,以 422 拒絕未知的鍵。允許的鍵集合剛好是 {type, group_id, mode}、{type, user_id} 與 {type, children}。user leaf 沒有 mode,所以 {"type": "user", "user_id": "…", "mode": "all"} 是 422——過去這會被接受、多餘的鍵被默默丟掉,讓呼叫端以為自己送出的限制生效了,其實沒有。重新提交任何已儲存或程式產生的 condition JSON 前,先把多餘的鍵清掉。
每個 gate 的上限是:最多 20 個 leaf(group leaf 與 user leaf 合併計算)、深度最多 5、每個 and/or 節點最多 20 個 children,以及 gate 聯集最多 500 位 reviewer。超過 leaf 上限是 Pydantic 驗證 422,訊息落在 detail[].msg,內容是 Value error, condition has 21 leaves, exceeds max 20——不是單一字串的 detail。超過 reviewer 聯集上限則確實是單一字串:gate 1 would assign 501 reviewers, exceeds max 500。
誰才算 reviewer
Reviewer 資格現在與認證邊界完全一致。同一個判定式同時管建立時驗證與 gate 啟用時的 snapshot,兩者不可能各說各話。一位使用者要四個條件全中才算數:未刪除、未停用、verified 為 true、未到期——role -2(部門虛擬管理員)豁免到期檢查,因為 get_current_user 也豁免它。
建立時由兩個 422 強制執行,review.templates.create/review.templates.update 與 review.processes.create 都適用:
review groups have no active members: ['44444444-4444-4444-8444-444444444444']
review users not found or inactive: ['33333333-3333-4333-8333-333333333333']第一則現在也會在「群組成員全部未驗證或全部已到期」時觸發。第二則涵蓋 user_id 不存在、已軟刪、已停用、未驗證、已到期或屬於其他公司;最多只列出前五個。在此之前,這種 template 會以 200 通過驗證、第一關照樣啟用,並替永遠無法認證的人開出 assignment——流程卡在 in_review 且沒有任何可達的結局,requester 唯一的出路是取消它。混合群組則只是把不合格成員排除在 snapshot 之外:他們拿不到 assignment,也收不到 review_assigned 通知,而 all 模式的法定人數縮到真正能動作的那些人。
4. 建立流程並設定 terminal webhook
review.processes.create 沒有 draft 狀態:建立成功就成為 in_review、啟用第一個 gate 並產生通知。template_id 與 inline gates 二選一。Webhook 的 events 只列出需要通知的 terminal states。
POST /private/module/review/processes
Content-Type: application/json
{
"title": "訂單 ORD-1042 變更覆核",
"description": "確認折扣與付款條件",
"content": {
"table_id": "66666666-6666-4666-8666-666666666666",
"operation": "update",
"record_id": "77777777-7777-4777-8777-777777777777"
},
"blob_ids": [],
"template_id": "55555555-5555-4555-8555-555555555555",
"webhook": {
"url": "https://example.com/review-events",
"method": "POST",
"headers": {
"X-Review-Source": "teamsync"
},
"body": {
"process_id": "$review.id",
"status": "$review.status",
"content": "$review.content"
},
"events": ["approved", "rejected", "cancelled"]
}
}保留 process.id。Webhook 是投遞通知,不是流程真相來源;ERP 收到後仍應以 process ID 做 idempotent 處理。
上例是 generic manual Review process。由 custom-table require_approval gate 建立的 process,其 title 為 表格「<name>」<operation>審核,而 content 會補上 change_type_label、table: {id, name, scope, scope_label, scope_id, scope_name},以及 content-level requester。內部 requester 含 user name 與 department;external client 含 platform、在地化 channel label 與 chatroom id/name。這個 content-level requester 是 change provenance,並不是 process list 上的 ReviewProcessSummary.requester。完整代表 payload 見審批流程指南。
5. 讓 reviewer 從自己的 pending queue 決策
review.assignments.pending 只會讓每位 reviewer 看見自己的 active assignments。
GET /private/module/review/assignments/pending?skip=0&limit=50將回應中的 assignment_id 交給 review.assignments.decision 決策。拒絕時需要非空 comment;require_signature: true 時要先上傳 signature blob 並提供其 ID。
POST /private/module/review/assignments/88888888-8888-4888-8888-888888888888/decision
Content-Type: application/json
{
"decision": "approved",
"comment": "金額與付款條件正確"
}gate 通過後會啟用下一關;最後一關通過時 process 變成 approved。任何終止性拒絕會變成 rejected。
Decision 帶簽名時,process detail 會在該 assignment 同時回傳 signature_blob_id 與內嵌 signature_blob。內嵌 BlobInfo 含 id、created_at、url、content_type、filename、tags 與 thumbnail,前端不必再查一次 blob 就能渲染簽名。Pending、無需簽名或 blob row 已不存在時會是 null;把 ID 當成 durable reference,並對內嵌物件做 null check。
一張選票最後會停在 pending、approved、denied 或 obsolete 其中之一。obsolete 的意思是從未投票——gate 或流程在沒有這位 reviewer 的情況下就結束了,於是他的列被作廢。真正投出去的票一律保留自己的決定,包含那張讓 gate 定案的票。這件事以前並不成立:讓 gate 通過或失敗的那張核准/拒絕,會被寫回成 obsolete,於是 review.processes.get 在 gates[].assignments[] 中把決策者顯示為 status: "obsolete"——comment、decided_at 與 signature_blob_id 都還在,唯獨決定本身被抹掉——而 audit log 仍記錄著 decision_approved/decision_denied。流程詳情與稽核軌跡對「是誰結掉這關」說法不一致。所有讓 gate 定案的票都中招:單人 gate 丟失每一次核准、any 模式丟失核准者、all 模式丟失最後一位核准者,讓 gate 失敗的拒絕則丟失拒絕者。取消走同一條規則——只作廢仍為 pending 的票,已投出的維持原狀。
當一關已經沒有人可以審
資格是在每一關啟用時對當下成員重新判定,不是在流程建立時判定一次。若某個決定性 group leaf 的成員(或唯一的直接 reviewer)在這期間全部變成未驗證、已到期、已停用或已刪除,該 leaf 的選舉人團為空,這一關會在啟用時直接失敗,而不是無限期掛著;流程以 rejected 結束,completed_at 有值、current_gate_order 為 null。Audit log 會有一列 gate_failed,其 changes.reason 是 gate_unsatisfiable。第二關以後的 gate 走的正是這條路,因為它們的選舉人團要等前一關通過才會 snapshot。以前這些人照樣被 snapshot,流程就永遠停在 in_review。
or 節點只要還有一個可滿足的兄弟節點,行為就不同:這一關照常啟用,無法審核的那個人單純拿不到 assignment。
6. 追蹤、稽核或取消流程
requester、參與者與 manager 依各自可見性透過 review.processes.get 讀取 process;管理調查再沿著 review.processes.audit 檢查稽核 log。
用 review.processes.list 建立相對於呼叫者的 inbox tabs:
GET /private/module/review/processes?role=requested&status=in_review&skip=0&limit=50
GET /private/module/review/processes?role=assigned&status=in_review&skip=0&limit=50role 只接受 requested 或 assigned,即使 caller 是 manager 也仍然相對於他本人。省略時回完整可見集合:manager 看全公司,其他使用者看「自己送出」與「目前或曾持有 ballot」的聯集。每筆 summary 會各自回 requested_by_me 與 assigned_to_me,兩者可能同時為 true。只有 requester_id 仍能在呼叫者公司內解析成使用者時,才會填入 requester: {id, name, department_id, department_name};否則 requester 為 null,包括 client/AI 送審,以及送審人已無法在該 tenant 解析的歷史真人流程。若當初有記錄,client/AI 的來源身分仍保留在 process content。Tabs、badges 與送審人標籤應直接用這些欄位,不要為列表每列再打一次 detail request。
GET /private/module/review/processes/99999999-9999-4999-8999-999999999999GET /private/module/review/processes/99999999-9999-4999-8999-999999999999/audit?skip=0&limit=50仍為 in_review 時,requester 或 manager 可透過 review.processes.cancel 取消;取消也是 terminal state。
POST /private/module/review/processes/99999999-9999-4999-8999-999999999999/cancel7. 檢查 webhook deliveries 並安全 retry
review.processes.deliveries 只會在設定 webhook 且流程進入 terminal state 後回傳 delivery;requester 或 manager 可列出它們。
GET /private/module/review/processes/99999999-9999-4999-8999-999999999999/deliveries?skip=0&limit=50對 failed,或 pending/sending 超過 10 分鐘的 delivery 呼叫 review.deliveries.retry;伺服器會以 atomic claim 防止兩位管理者同時重送。
POST /private/module/review/deliveries/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/retryretry 回應為 pending 只代表重新排入;再次列出 deliveries,直到 delivered 或出現可診斷的 failed。
你會看到什麼
manager access 建立後可維護 group/template;流程建立後立即有 active assignment;決策會推進 gate 並留下 audit;terminal state 會建立 delivery,失敗投遞可由同一個 delivery ID 安全重試。
常見錯誤
請直接查看存取授權錯誤表、群組建立錯誤表、範本建立錯誤表、流程建立錯誤表、決策錯誤表與投遞重試錯誤表。
試試看
在 API Playground 用測試公司依序執行 review.access.grant、review.groups.create、review.templates.create、review.processes.create、review.assignments.pending、review.assignments.decision 與 delivery 查詢;正式 webhook 前先使用可觀察的測試接收端。