本卡屬 FR-148(母卡見上方連結),第 ① 棒:地基與任務卡。做完後用 DEV 手灌一個三關任務,三個帳號就能在任務卡上走完送審→核可→結案(含退回)。後續五張卡都依賴本卡。
建「任務關卡」需要的資料表,寫送審/核可/退回的後端服務與端點,並在任務卡上畫出「開始 → 1 → 2 → 3 → 結案」的關卡進度、跟著當下那關的人變的按鈕、以及審核歷程彈窗。設定樣板的頁面與規劃頁派人是後面的卡,本卡先用 SQL 手灌一筆測試資料驗畫面。
全部放 compliance schema。表名用 review_step_*,中文介面叫「任務關卡」。
review_step_templates 樣板:id, uid, tenant_id, name, description, is_default bool, is_builtin bool, 審計欄位
- 部分唯一索引:每 tenant 最多一筆 is_default=true
- RLS:FOR ALL「超管或 app_tenant_allowed_for_session(tenant_id)」USING+WITH CHECK
review_step_template_items 樣板的關卡:id, template_id FK, seq (1 起), name, description, back_to_seqs int[]
- RLS 跟隨樣板
job_executions 加兩欄:review_step_no int NULL(目前第幾關;NULL=零關=現狀), review_template_id int NULL(來源樣板,只記錄)
job_review_steps 任務的關卡(套樣板時複製):id, job_execution_id FK, seq, name, description, back_to_seqs int[]
job_review_step_assignees 第 2 關以後的人:job_execution_id, seq (≥2), user_id, org_unit_id NULL;PK (job_execution_id, seq, user_id, org_unit_id)
job_review_step_logs 歷程:id, job_execution_id, seq, action(submit/approve/reject/reopen), to_seq NULL, user_id, note, created_at timestamptz
- 三張 job_* 跟隨 job_executions 所屬專案的 RLS
is_builtin=true、is_default=true)。新租戶建立時也要有——查新租戶初始化的既有掛點(grep 既有 seed 樣板/system_menus 的租戶初始化做法),先查再寫。task_assignees,不另存;所以零關任務、問卷任務完全不碰新表。.claude/rules/security/data-access.md;有 tenant_id 的由 test/security/database/test_rls_all_tenant_tables.py 自動管;跟隨型的三張 job_* 與 template_items 加進 test/security/database/test_project_scoped_tables_rls.py 清單。job_executions 兩欄改套件 /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-flow-engine/jedi_flow_engine/infra/models/job_execution.py(ORM 與 migration 要一致,FR-140 due_at 是先例);新表 ORM 放主專案 infra/flow_control/,不放套件。新開 app/flow_control/service/review_step_service.py(不要塞進既有 service),四個方法:
submit(job_uid) 第 1 關的人;關卡 1→2、寫歷程 submit、通知第 2 關的人
approve(job_uid, note) 當下那關的人;非最後一關:+1、寫歷程、通知下一關;
最後一關:寫歷程後呼叫 workflow_execution_service.complete_job()
reject(job_uid, to_seq, note) 當下那關的人;note 必填;to_seq 必須在該關 back_to_seqs 內;
關卡改 to_seq、寫歷程、通知 to_seq 那關的人
reset_to_first(job_execution) 內部用(卡⑤ 的重開 hook 會呼叫);caller 在 @transaction 內
assert_project_participant;再判「是否在當下那關的名單」(第 1 關查 task_assignees、其餘查 job_review_step_assignees),否則 ForbiddenError。任務非進行中、或零關任務呼叫這三支 → PreconditionFailedError。WHERE id=:id AND review_step_no=:expected,更新 0 列回 ConflictError。common/code/flow_control_error_code.py,命名 GRC_<狀態><序號>;目前最大 403=GRC_403069、409=GRC_409047、412=GRC_412069、400=GRC_400175,往下接。同步加 FE error-code.json 的 i18n。app/flow_engine/service/workflow_execution_service.py:1209 notify_user_todo_job(job_id, assignees=<該關的人>)(它只在任務進行中才寄;assignees 要有 user_name/user_email,比照 job_service.py:503 _notify_newly_added_assignees 組 SimpleNamespace)。不新開通知管線。api/flow_engine/routes/flow_engine_route.py:42 → complete_job()(workflow_execution_service.py:623)。對「review_step_no 非 NULL 且不是最後一關」的任務拋錯;review_step_service 自己呼叫時要能放行(內部參數或分開的內部方法)。不擋的話舊的「完成任務」按鈕會繞過關卡。workflow_execution_service.py 已超標,判斷邏輯寫在新檔、那裡只加一行呼叫。POST /grc/jobs/<job_uid>/review-steps/submit
POST /grc/jobs/<job_uid>/review-steps/approve body {note}
POST /grc/jobs/<job_uid>/review-steps/reject body {to_seq, note}
GET /grc/jobs/<job_uid>/review-steps → {template_name, current_seq,
steps:[{seq, name, description, back_to_seqs, assignees:[{user_uid, name, dept_name}]}],
logs:[{created_at, seq, step_name, action, to_seq, user_name, note}]}