FR-112 · 設計文件 · 2026-09-19(D1–D7 全數定案)
PM 要替一個查核項目多開一個任務,動線回到專案規劃頁:按「新增任務」、填名稱、存檔。新任務預設掛成平行分支——誰先做都行、全部做完這個查核項目才算過。流程圖編輯器降級為純粹的走向設計工具,屬性面板只留名稱,不再是填任務資料的地方。連帶修掉一個既有引擎缺口:合流閘道不等兄弟分支就放行。
狀態:設計定案(D1–D7 全數拍板),待開卡|日期:2026-09-19 討論稿(含選項與被排除方案的完整推演):
discussion.html開發用 worktree:三個 repo 各有.claude/worktrees/planning-task-parallel-flow,branch 一律feature/planning-task-parallel-flow
🔴 這份是設計決策,不是現況。 內文的行號、筆數與待辦停在 2026-09-19 定稿當時,之後實作改動的結果不回頭改寫這裡——現況一律看
FINAL-SPEC.md(實作完成後產出)。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-09-19 | 初版設計定案。D1–D6 拍板(皆採討論稿建議案);D4 依決策者指示鎖為「屬性面板只留名稱」,編輯器 UI 優化列為延伸項。 | FR-112 母案 |
| 2026-09-19 | 追加 D7(刪除任務兩入口對稱),並據此改寫 D6 ②:XML 少了 job_execution_uid 視為刪除意圖而非孤兒,存檔端點改為三向 diff(§5.8)。拆分為 4 子需求 16 子任務。 |
決策者追加拍板 |
| 2026-09-19 | §5.8 三向 diff 加第五列:一個節點對到多筆任務→擋下不靜默刪(DEV 有兩筆舊版編輯器殘留,B-2.3 驗收時發現)。 | 決策者裁示 |
| 2026-09-19 | D6 ① 改為至少保留一個任務:規劃頁刪到最後一筆擋下、編輯器存檔會刪到零也擋下。 | 決策者追加拍板 |
| 2026-09-20 | 補兩項裁示:①編輯器刪任務走範本能力守門、不要求專案管理人身分(§5.4);②輪次凍結反查不到時放行及其理由(§5.8)。另記存檔執行順序「先建後刪」與被刪 uid 回報 route 接 Drive 歸檔(§5.8)。 | 決策者 2026-09-20 裁(arc review CM-1977 → CM-1978) |
任務的所有資料以專案規劃頁為準存進資料庫,流程圖只留「先後與分支」這一件事;規劃頁補上「新增任務」按鈕,新任務預設掛成平行分支,該查核項目要全部任務做完才算通過。
userTask(一個方塊=一個任務),箭頭表示先後順序,菱形的叫「閘道」——平行閘道表示「這幾條同時開跑」、互斥閘道表示「二選一」。job_executions):資料庫裡真正存任務的地方——名稱、描述、負責人、狀態、證據、問卷、檢測工具設定都在這裡。任務的同一份資料存在兩個地方,而且會互相覆蓋:
| 存在哪 | 存了什麼 | 誰在寫 |
|---|---|---|
資料庫 job_executions(+ task_assignees 等關聯表) |
名稱、描述、指南、類型、狀態、時間、負責人、部門、設備、問卷、檢測工具 | 規劃頁存檔、後端流程引擎 |
| 流程圖 XML 的 camunda 屬性 | name、workDescription、guide、actionType、actionInfo、devices、departments、toolParams、completionMode、job_execution_uid |
流程圖編輯器存檔、後端 _sync_task_to_template_xml 回寫 |
編輯器存檔時會逐節點打 PUT 更新任務,而它手上只有五個欄位——送出的更新會蓋掉規劃頁設好的東西。程式碼裡的防禦註解就是病徵:「只在 actionInfo 非空時帶 surveys⋯避免把規劃頁設的問卷誤刪」「不帶 approval_required⋯不誤蓋規劃頁審查旗標」(FE WorkflowSetupEditor.vue:525-531)。
每加一個規劃頁專屬欄位,就要在編輯器同步邏輯補一條「這欄不要蓋」的例外
例外會愈來愈多,而漏掉一條的症狀是資料靜靜消失、沒有錯誤訊息。本案的解法不是把例外補齊,是把雙向寫入路徑整條拿掉。
同時,規劃頁根本不能新增任務——要多開一個任務,PM 得打開流程圖編輯器、從工具列拖方塊、手動接線、填一堆屬性。流程圖編輯器是工程師與流程設計者的工具,要 PM 為了「多派一件事」去學拖方塊、分辨平行閘道與互斥閘道,等於把工具的複雜度轉嫁給使用者。
PM 在規劃頁點一個查核項目
→ 按「新增任務」,只填名稱
→ 後端在該查核項目的流程圖上插一個方塊、接成平行分支、回寫任務編號(同一個交易內)
→ 任務面板出現、展開待填(負責人/問卷/檢測工具⋯全部在規劃頁填)
→ 專案啟動後,該查核項目底下所有平行任務同時可做
→ 每個負責人各自完成自己那條
→ 全部完成 → 該查核項目才算通過(只完成一部分不會提前放行)
流程圖編輯器(另一條動線,給要調走向的人用)
→ 改線(把平行拉成一條龍、加二選一分支)、拖新方塊(只能填名稱)、刪方塊
→ 存檔 → 後端三向 diff:沒編號的新方塊建成空任務/XML 裡消失的編號刪掉那筆任務/其餘只更新拓樸
→ 回規劃頁重抓,任務清單與圖一致
| 階段 | 做什麼 | 產出 | 完成怎麼判定(決策者親手檢查法) |
|---|---|---|---|
| ⓪ 開工前必驗 | 確認輪次推進/輪次回退讀的流程圖,與查核項目底下的收證據流程不是同一張 | 一頁查證結論(寫進母卡) | 開 stage_advance_service.py 與 stage_rollback_service.py,看它們讀的 workflow_template_xml 來自哪張範本;若與查核項目共用同一張,停下回報、整案範圍重估 |
| ① 流程圖改圖工具 | 共用套件補三支能力:把流程重排成標準平行、在合流閘道前掛一條新分支、判斷「這個閘道是合流嗎/兄弟分支都完成了嗎」 | jedi-flow-engine 新增/修正的函式 + 單元測試 | 跑套件測試:給一張「兩條平行分支、合流後還有一個任務」的流程圖,完成第一條分支後判定函式要回「還不能放行」;兩條都完成才回「可以」 |
| ② 後端新增/刪除任務 | 新增任務端點不再要求前端先給流程圖節點編號——後端自己在圖上補節點、接線、回寫任務編號;刪除從兩個入口都能做(規劃頁按刪除/編輯器刪方塊存檔,同一支刪除函式);合流等待判定接上兩個落點 | BE app service + repo 改動 | 用 Swagger 直接打新增任務端點(不帶 job_element_uid)→ 回 200;去資料庫看該查核項目的流程圖 XML,多了一個方塊、且方塊上帶著新任務的編號 |
| ③ 規劃頁按鈕 | 任務區塊補「新增任務」與「刪除任務」;凍結後按鈕停用並顯示原因 | FE ProjectPlanningView.vue |
規劃頁點任一查核項目 → 按「新增任務」→ 填名稱存檔 → 重新整理仍在 → 打開流程圖編輯器,看得到這個新方塊,而且是與原任務並排(平行)不是串在後面 |
| ④ 流程圖編輯器瘦身 | 屬性面板只留名稱;存檔不再由前端逐一打新增/更新任務端點;刪方塊的能力保留不鎖(刪了就是刪任務) | FE WorkflowSetupEditor.vue |
打開編輯器點一個方塊 → 右邊只剩名稱欄,沒有任務類型/問卷/設備/部門;拖一個新方塊出來存檔 → 回規劃頁看得到一筆新的空任務;刪掉一個方塊存檔 → 回規劃頁該任務消失 |
| ⑤ 驗收與回歸 | 專案啟動、輪次推進、批次完成、任務佇列四條既有動線在平行結構下重跑 | 手測清單 + e2e 回歸(測試 repo) | 起一個新專案 → 規劃頁加兩個任務 → 兩個都完成 → 該查核項目變「已完成」;只完成一個時不可以變完成 |
依賴:⓪是全案的前置查證;①是②的地基(②要呼叫①補的改圖函式);③④在②完成後可平行;⑤等③④都驗收過才做。
| # | 決策 | 定案 | 理由 | 被排除的方案 |
|---|---|---|---|---|
| D1 | 輪次凍結後還能不能新增任務 | 不能。新增/刪除任務只在規劃期開放;凍結後按鈕停用並在畫面上顯示原因(停用+留住提示,不彈 toast),後端同步擋下 | 輪次一旦離開規劃期,任務清單就是這一輪的稽核範圍;中途追加等於改變已在跑的稽核範圍,證據完整性與報表口徑都會受影響 | 「凍結後仍可新增」——「稽核到一半發現漏了一項」確實會發生,但那牽涉補件流程與報表口徑,屬另一期的題目,不在本案混做 |
| D2 | 使用者已把流程排成一條龍時,規劃頁新增的任務掛哪裡 | 掛成合流前的一條新平行分支,不動既有順序。圖上沒有合流閘道時,在結束事件前補一個 | 同時滿足兩件事:不動使用者刻意排的順序,又維持「新增的任務預設不相依」這個原則 | ①一律重排成標準平行——會擅自推翻使用者排的先後,而且沒有任何提示,是很難解釋的行為;②接在最後一個任務後面繼續串——與「新增預設平行」的方向相反 |
| D3 | 合流閘道等待修正要不要納入本案 | 納入,且兩個落點都修——complete_job() 與 complete_main_workflow_job()(app/flow_engine/service/workflow_execution_service.py)。拓樸判定函式放 jedi-flow-engine,兩處呼叫它 |
缺口本身是既有的,但本案讓平行結構從「幾乎不存在」變成「預設樣貌」,並明示保留改線能力——使用者只要在合流後接一個覆核任務就會踩到,而症狀(覆核任務提早跳成進行中)看起來像資料錯亂、不像流程設定問題,會極難查 | ①不修,另開一期——本案正是放大它的原因,留著等於明知會踩;②只修 complete_job() 一處——兩支是同一份寫法的兩個副本,只修一處會留下一條靜默錯誤路徑 |
| D4 | 流程圖編輯器的屬性面板留哪些欄位 | 只留名稱。工作描述、指南、任務類型、設備、部門全部移除,圖上既有屬性的讀取路徑一併停用 | 決策者原話:「回歸最簡單就好,不想讓編輯器做太複雜的編輯,目前的 UI 設計不是很好用,之後再來優化」。工程面理由同向:多留一個欄位就多一條雙向寫入路徑,而「在圖上看得到描述」的好處不值得為它保留第二份真相 | ①連同描述與指南一起留——它們會立刻變成第二份真相,規劃頁改了、圖上沒改,兩邊不一致;②面板全砍只剩唯讀——名稱是圖的可讀性根本,拿掉後圖上只剩節點編號 |
| D5 | 既有專案圖上的舊 camunda 屬性要不要清掉 | 不清,讀的時候一律忽略。同時 _sync_task_to_template_xml() 收斂成只寫 name |
清除 migration 要改動所有租戶的範本 XML,改壞了沒有回頭路(XML 沒有欄位層級的版本控制);而這些屬性留著不造成行為差異——只要沒有任何程式碼讀它,它就只是一段被忽略的文字。收斂寫入端是必要的,否則舊屬性會被持續刷新、永遠不會自然凋零 | 寫 migration 掃全部範本清屬性——唯一好處是「看起來乾淨」,不值得承擔改壞既有專案流程的風險 |
| D6 | 邊界情形 | ①至少保留一個任務——規劃頁刪到最後一筆擋下(GRC_412 系列前置條件錯誤,訊息:「查核項目至少要有一個任務」);編輯器存檔的三向 diff 若會把任務刪到零也整筆擋下、不存;②XML 少了 job_execution_uid =刪除意圖,不是錯誤——後端據此刪對應任務;「孤兒」的定義收窄為「圖上有 userTask、但該節點沒有 uid 屬性且 DB 查不到對應任務」,那視為新增;真正擋下存檔的只剩「uid 重複」與「節點從開始事件走不到」;③不設硬上限,但超過 20 筆在規劃頁給提示 |
①零任務會讓該查核項目的流程變成 開始 → 結束,一啟動就直接完成、沒有任何人經手就「通過」,稽核上站不住腳;「這項不需要做」應由專案範圍(reviewed-controls)表達,不是把任務刪光;②刪除在兩個入口都要能做(D7),編輯器刪方塊的唯一可觀察訊號就是「XML 裡少了那個 uid」——把它當錯誤擋下,等於宣告「圖上刪方塊無效」,與 D7 相反;③硬上限會在真實需求撞上時變成無解的阻塞,提示足以處理「圖擠成一團」的可讀性問題 |
①允許零任務(圖 開始 → 結束)——會產生無人經手即通過的查核項目;②「少了 uid =孤兒 → 擋下存檔」——會讓編輯器刪節點這條動線無法成立;③設硬上限(如 20 筆)擋下 |
| D7 | 刪除任務由哪個入口做 | 兩邊都可以,語意完全一致。規劃頁按刪除 → 走既有 DELETE 端點;編輯器刪掉 userTask 節點後存 XML → 後端 diff 出「XML 裡消失的 job_execution_uid」,對那些任務呼叫同一支 delete_job()。硬刪除(實查 flow_control_job_repo_impl.py:808 是 session.delete(job),不是改 CANCEL),守門沿用既有的 _require_manager(job_service.py:179) |
與新增對稱:新增已經是兩個入口同一支端點,刪除若只開一邊,使用者在圖上刪了方塊卻發現任務還在,是無法解釋的行為。共用同一支 delete_job() 是硬要求——它負責清五張關聯表與 snapshot 問卷,另寫一套必定漏刪,留下永遠查不到任務的孤兒列 |
①只開規劃頁一邊、編輯器刪節點擋下——與 D4「編輯器管走向」的定位矛盾(刪方塊就是改走向);②編輯器刪節點改成「標 CANCEL」——兩入口兩套語意,同一個動作在不同頁面產生不同結果 |
3涉及 repo
10寫進流程圖的 camunda 屬性
2引擎缺口落點(同一個 bug 兩處)
| 元件 | 現況 | 本案動作 |
|---|---|---|
api/flow_control/routes/job_route.py:170 ProjectJobCreateResource |
新增任務端點在(POST /grc/project/<uid>/assessment-object/<ao_uid>/jobs),註冊於 api/flow_control/__init__.py:62,117;只有流程圖編輯器在呼叫 |
端點保留,讓它接受不帶節點編號的呼叫 |
api/flow_control/serializers/job.py:322 job_element_uid |
allow_none=True, load_default=None——schema 本來就允許不帶 |
不改;改的是下游的 repo |
infra/flow_control/repository/flow_control_job_repo_impl.py:673 create_job() |
開頭 if not job_element_uid: return None(:698),路由層轉成 return_response(False, None),前端看不出發生什麼事 |
🔴 核心改動:沒帶編號時改成「後端自己在圖上插節點」,插完再往下走原邏輯 |
同檔 create_job() 冪等防護(:729) |
同一流程下同一 template_job_id 已有任務 → 更新後回傳、不重建 |
保留。後端自己插節點時編號必定是新的,不衝突 |
同檔 create_job() 初始狀態判定(:750) |
該流程下沒有未完成的任務 → 新任務開 PROCESSING;否則 TODO |
🔴 判定基準改為看「這個節點的上游是不是分叉閘道/開始事件」,是就開 PROCESSING(見 §5.5) |
同檔 _sync_task_to_template_xml()(:401) |
規劃頁存檔後單向回寫圖;定位方式「先找 job_execution_uid 屬性、退而求其次比對節點 id」;同時寫主表與翻譯表 |
保留為唯一的圖寫入路徑;寫入欄位收斂成只寫 name(D5) |
同檔 delete_job()(:779) |
刪任務時清五張關聯表,再呼叫 BpmnUtils.remove_user_task_from_xml() 移節點(註解寫「前端存檔通常已移除,這裡兜底」) |
從兜底升格為主要路徑——規劃頁刪除就是走這裡 |
同檔 _compute_can_revert_job()(:350-396) |
len(all_jobs)==1 直接可退;否則取 get_next_jobs() 的第 0 筆看是否進行中 |
🔴 改成「所有下一批任務都還沒開始才可以退回」;len(all_jobs)==1 那條捷徑一併重檢 |
app/flow_control/service/job_service.py:117 create_job() |
@transaction,第一件事 _require_manager(project_uid)(:94,走 assert_project_manager);接著處理問卷 snapshot、task_survey 笛卡爾積、檢測工具綁定與參數加密 |
守門與後續處理沿用不動;加一道規劃期守門(D1) |
app/flow_engine/service/workflow_execution_service.py:602 complete_job() |
取 get_next_jobs()(:668)後,:702 起的迴圈把每一個下一批任務直接設 PROCESSING,無兄弟分支檢查。無下一批時(:677)才檢查整條流程是否全完成 |
🔴 :702 迴圈前插合流等待判定;:677 那條分支語意正確,不動 |
同檔 complete_main_workflow_job()(:469) |
:569 迴圈是與上面一模一樣的寫法;輪次推進在用(stage_advance_service.py:382 呼叫) |
🔴 同樣插判定——兩處都要修(D3) |
同檔 _assert_round_not_frozen_for_workflow()(:155) |
輪次凍結 gate,目前只擋 revert_job(:794) |
新增/刪除任務接同一道 gate(D1) |
app/flow_control/service/prep_job_generation_service.py:101 generate() |
專案啟動時每個查核項目 clone 範本 → 建流程實例 → 建一筆任務,節點編號取 _first_user_task_id()(:93,正規表示式抓第一個 userTask,:54),再 inject_job_execution_uid() 注回圖 |
不改(範本仍是單任務)。在該函式旁留一句註解寫明陷阱:「只建第一個 userTask;多任務範本會靜默漏建;覆核輪 launch_reverify 會 re-clone 第一輪已改過的範本再跑本函式,本案讓 AO 可多任務後此缺口會實際發生」(延伸項見 §8) |
app/flow_control/service/job_batch_complete_service.py:86 |
批次完成:逐筆取 template_job_id 呼叫 complete_job() |
不改程式,但是風險集中點——批次完成會連續觸發引擎缺口,必測 |
app/flow_engine/service/stage_advance_service.py / stage_rollback_service.py |
輪次推進/回退,讀 workflow_template_xml |
流程圖不共用(D-4.1 已查證:輪次圖綁 project_audit_rounds.workflow_execution_uid,查核項目圖靠 workflow_execution_control_mapping,DEV 全庫零交集)。但 complete_main_workflow_job() 是輪次推進實際會跑的程式,B-2.7 動它屬刻意涵蓋;輪次圖只有互斥閘道、無平行閘道,新判定永遠放行、行為不變——回歸仍要走一次輪次推進(B-2.7 驗收已含) |
infra/readmodel/tasks/vw_user_job_queue.py |
任務佇列讀模型,template_job_id 當一般欄位存(:77),無順序假設 |
不受影響 |
| 元件 | 現況 | 本案動作 |
|---|---|---|
bpmn_uilts.py:276 get_next_jobs() |
走訪下一批節點:任務直接收;互斥閘道依條件挑一條;平行閘道(:326)把所有出線對應的任務全部收進來 |
展開行為不改。它分不出手上的閘道是「分叉」還是「合流」(BPMN 裡兩者都是 parallelGateway,差別只在入線/出線數量)——等待判定另寫一支,不塞進它 |
bpmn_uilts.py:213 get_next_elements() |
只看一條出線的目標節點類型 | 不改 |
bpmn_uilts.py:501 append_user_task_to_xml() |
在結束事件之前插一個新方塊,docstring 明寫「接線邏輯(線性流程)」 | 🔴 只會串成一條龍——本案要的平行掛法另新增函式,這支不動(既有 caller 仍在用) |
bpmn_uilts.py:574 inject_job_execution_uid() |
把任務編號寫進指定節點的 camunda 屬性,已存在就覆蓋 | 直接沿用 |
bpmn_uilts.py:613 remove_user_task_from_xml() |
移除節點並把線接回去(前 → 節點 → 後 變 前 → 後) |
擴充:平行分支被刪到只剩一條時,把分叉/合流閘道一併收掉 |
bpmn_generator.py:145 create_bpmn_with_user_tasks() |
已能產標準平行結構:開始 → 分叉 → N 個方塊並排 → 合流 → 結束(:216 起逐一建方塊與連線) |
直接重用為「重排成標準平行」的實作核心,不另起爐灶 |
bpmn_uilts.py:391 get_job_execution_order() / :447 get_jobs_by_layer() |
深度優先出順序/廣度優先分層 | 平行結構下「順序」意義不大,但不會壞,不動 |
| 元件 | 現況 | 本案動作 |
|---|---|---|
ProjectPlanningView.vue:2931 起 任務區塊 |
逐筆展開面板,欄位齊全;空狀態只顯示「沒有任務」文案(:2943),無新增按鈕 |
🔴 補「新增任務」;空狀態改成可從這裡開第一筆 |
ProjectPlanningView.vue:1772 saveTask() |
存單筆任務,含名稱必填、檢測工具必選、逐列參數必填、重複分派檢查 | 不動;新增任務走同一支存檔 |
ProjectPlanningView.vue:1923 openBpmnEditor() |
開新分頁到 /workflow/workflow-setup,帶範本編號/流程實例編號/專案編號/查核項目編號 |
保留;按鈕文案改成「編輯流程走向」以符合新定位 |
WorkflowSetupEditor.vue:402 onSaveClick() |
存 XML → syncJobExecutions() → 有新任務就 patchJobExecutionUids() → 再存一次 XML → 重新載入圖 |
🔴 砍掉後三步,只留第一步存 XML;新任務由後端在存檔時建 |
WorkflowSetupEditor.vue:489 syncJobExecutions() |
逐節點打 PUT/POST | 整支移除 |
WorkflowSetupEditor.vue:443 patchJobExecutionUids() |
用 DOMParser 直接改 XML 字串寫回任務編號 | 整支移除(改由後端回寫) |
WorkflowSetupEditor.vue:635 onSaveTaskProperties() +屬性面板(:1032-1045) |
面板有名稱/工作描述/指南/任務類型四欄,另把設備與部門寫進圖 | 🔴 只留名稱(D4) |
TaskSetupView.vue:524 |
也會開 /workflow/workflow-setup |
一併確認改動後行為正常 |
FlowTemplateEditorView.vue |
流程範本管理,是另一套系統 | 明確不在範圍 |
| 這件事 | 真相在哪 | 另一邊是什麼 |
|---|---|---|
| 任務叫什麼、描述、指南、類型、負責人、部門、設備、問卷、工具參數、狀態 | 資料庫 job_executions +關聯表 |
圖上的 name 是顯示副本,由後端單向寫;其餘屬性不再寫也不再讀 |
| 誰先誰後、誰跟誰平行、有沒有二選一分支 | 流程圖 XML | 資料庫不存拓樸 |
| 圖上這個方塊是哪一筆任務 | 圖上的 job_execution_uid 屬性(後端寫) |
— |
🔴 判準一句話:圖上的東西只有「拓樸」和「指回任務的編號」是真的,其他都是印出來給人看的。
任何一次改動只要讓某個欄位「從圖上讀回來當真值用」,就等於把第二份真相請回來。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
participant PM as PM(規劃頁)
participant API as 後端端點<br/>job_route.py:170
participant SVC as 任務服務<br/>job_service.py:117
participant REPO as 任務倉儲<br/>repo_impl.py:673
participant XML as 流程圖改圖<br/>jedi-flow-engine
participant DB as 資料庫
PM->>API: 按「新增任務」(只帶名稱,不帶節點編號)
API->>SVC: create_job(ao_uid, name, job_element_uid=None)
SVC->>SVC: 檢查專案 manager + 輪次是否仍在規劃期
SVC->>REPO: create_job(...)
REPO->>DB: 查這個查核項目的流程範本與流程實例
REPO->>XML: 依現有拓樸決定插法<br/>(重排成平行 / 掛新分支)
XML-->>REPO: 新 XML + 新節點編號
REPO->>DB: 建 job_executions 一筆(狀態依上游判定)
REPO->>XML: inject_job_execution_uid(新節點, 新任務編號)
REPO->>DB: 寫回範本 XML(主表+翻譯表,同一交易)
REPO-->>SVC: 任務資料
SVC-->>API: 任務資料
API-->>PM: 200,任務面板出現、預設展開待填
兩種入口走同一支端點,差別只在有沒有帶 job_element_uid:
| 入口 | 帶 job_element_uid 嗎 |
後端做什麼 |
|---|---|---|
| 規劃頁按「新增任務」 | 不帶 | 自己在圖上插節點、接線、回寫任務編號 |
| 流程圖編輯器拖新方塊、存檔 | 帶(前端存 XML 時圖上已有這個節點) | 直接用這個編號建一筆空任務、把編號回寫圖上 |
保留第二種入口是決策者明示的
在圖上拖方塊仍然可以開任務——只是屬性面板只能填名稱,其餘細節回規劃頁補。存檔後後端掃圖,發現沒有對應任務的 userTask 就建一筆空任務並回寫編號。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart TB
subgraph A["情境一:只有一個任務(專案啟動後的預設樣子)→ 重排成標準平行"]
direction LR
A1([開始]) --> A2[任務 1] --> A3([結束])
A3 -.新增任務 2.-> A4[" "]
A5([開始]) --> A6{{分叉}}
A6 --> A7[任務 1]
A6 --> A8[任務 2 新]
A7 --> A9{{合流}}
A8 --> A9
A9 --> A10([結束])
end
subgraph B["情境二:已是標準平行 → 分叉多一條出線、合流多一條入線"]
direction LR
B1([開始]) --> B2{{分叉}}
B2 --> B3[任務 1]
B2 --> B4[任務 2]
B3 --> B5{{合流}}
B4 --> B5
B5 --> B6([結束])
B6 -.新增任務 3.-> B7[" "]
B8{{分叉}} --> B9[任務 1]
B8 --> B10[任務 2]
B8 --> B11[任務 3 新]
B9 --> B12{{合流}}
B10 --> B12
B11 --> B12
end
subgraph C["情境三:使用者手動排成一條龍 → 掛成合流前的新平行分支,不動既有順序"]
direction LR
C1([開始]) --> C2[任務 1] --> C3[任務 2] --> C4{{合流}} --> C5([結束])
C4 -.新增任務 3.-> C6[" "]
C7[任務 1] --> C8[任務 2] --> C9{{合流}}
C10{{分叉}} --> C11[任務 3 新] --> C9
end
規則寫成白話:
bpmn_generator.py:145 的產圖邏輯,既有任務的 job_execution_uid 必須原樣保留。為什麼不是「一律重排成標準平行」
使用者若刻意把任務排成「先做 A 才能做 B」,一按新增就被重排回全平行,等於系統擅自推翻他的設計,而且沒有任何提示。
delete_job()| 入口 | 觸發 | 後端做什麼 |
|---|---|---|
| 規劃頁按刪除 | DELETE /…/jobs/<job_uid> |
job_service.delete_job() → 清 task_surveys 與 snapshot 問卷 → repo.delete_job() 清五張關聯表、移節點、硬刪 job |
| 編輯器刪方塊後存 XML | XML 存檔端點 | diff 出「舊 XML 有、新 XML 沒有」的 job_execution_uid → 對每一筆呼叫同一支 delete_job()(D7) |
🔴 兩個入口共用同一支 delete_job() 是硬要求,不可各寫一套
repo.delete_job()(flow_control_job_repo_impl.py:779)負責清 task_assignees、job_execution_org_units、job_execution_devices、job_execution_surveys、job_execution_comments 五張表——這四張綁定表的外鍵已從 CASCADE 改為軟參照,清理責任在這支函式裡,少刪一張就留下永遠查不到任務的孤兒列。另外 job_service.delete_job() 上面還有一層 _cleanup_task_surveys() 與 snapshot 問卷清理。
繞過去自己寫刪除,症狀是「任務不見了但關聯資料還在」,而且沒有錯誤訊息。
刪除語意與守門(實查,兩入口一致):
repo_impl.py:808 是 session.delete(job),不是改狀態成 CANCEL。job_service.py:179 的 _require_manager(project_uid)。現況沒有任何「已有進度不准刪」的檢查——PROCESSING/COMPLETED、已有證據、已指派負責人都刪得掉。本案沿用此規則、不新增限制(新增限制屬產品行為變更,不在本案;兩入口一致才是本案的要求)。本案另外加的只有 D1 的規劃期守門,兩入口同樣適用。module-frame.update),不要求專案管理人身分(決策者 2026-09-20 裁)。存檔端點只拿得到範本 uid,而範本反查專案在現況資料上多半查不到(DEV 上 11,710 個有任務的流程實例只有 910 個查得回專案),沒有 project_uid 就問不出「你是不是這個專案的管理人」;租戶隔離由 RLS 負責。job_route.py:161-166)。編輯器存檔這條路徑也要接上,否則同一個刪除動作在兩個入口對雲端資料夾的處置不同。圖的處理:
前 → 節點 → 後 接回 前 → 後(remove_user_task_from_xml() 現有行為,idempotent——編輯器那條路徑節點已經不在 XML 裡,呼叫它不會出錯)。平行分支上的任務應該都可以馬上開始做,不該排隊。判定基準從「該流程下有沒有未完成任務」改成看這個節點的上游:
| 新節點的上游是 | 初始狀態 |
|---|---|
開始事件,或分叉閘道(parallelGateway 出線數 > 1) |
PROCESSING |
另一個 userTask(使用者排的一條龍中段) |
TODO |
判定要用的拓樸資訊由套件的判定函式提供(§5.7),BE 不自己解析 XML。
🔴 不改這裡的症狀:平行下第二個以後的任務全部變 TODO 排隊
畫面上看起來是「有三個平行任務,但只有一個能做」——與本案要的「誰先做都行」完全相反,而且沒有錯誤訊息。
_compute_can_revert_job() 現行邏輯取 get_next_jobs() 的第 0 筆,平行結構下「下一批」有多筆,只看第一筆會誤判。改成:所有下一批任務都還沒開始,才可以退回;len(all_jobs)==1 那條捷徑在多任務後失效,一併重檢。
缺口:合流閘道不等兄弟分支——第一條分支完成,合流後面的任務就被啟動
complete_job()(workflow_execution_service.py:602)完成一個任務後拿 get_next_jobs()(:668),接著 :702 起的 for job in next_jobs: 迴圈把每一個都直接設成 PROCESSING,中間沒有任何「兄弟分支都完成了嗎」的檢查。
同一個缺口在兩個地方:complete_main_workflow_job()(同檔 :469)的 :569 迴圈是一模一樣的寫法,輪次推進在用。
成立條件:合流閘道後面還有 userTask。本案要產的標準結構(分叉 → N 任務 → 合流 → 結束)中合流後面是結束事件,get_next_jobs() 回空,走的是 :686 那條「檢查整條流程是否全部完成」的分支——那條是對的。所以預設結構不會立刻踩到,但本案會放大它:
job_batch_complete_service.py:86)連續呼叫 complete_job(),把時序放到最大。修法:在 jedi-flow-engine 新增拓樸判定函式,回答「這個任務的上游是不是合流閘道/該閘道所有入線分支的任務是否都已完成或取消」;complete_job() 與 complete_main_workflow_job() 兩處的迴圈在設 PROCESSING 之前先問它,答否就跳過不啟動。
判定放套件、呼叫點在主專案,是刻意的分界
拓樸語意(誰是分叉、誰是合流、兄弟分支有哪些)只有看得到整張圖的那一層能回答,放在套件;「任務算不算完成」是主專案的業務狀態,留在主專案。兩邊各自回答自己知道的事。
編輯器存 XML 時,後端拿新 XML 的 userTask 節點對照該流程實例現有的任務清單,逐一歸到下面四種處置之一。這張表是存檔端點的完整行為規則:
| 圖上的節點 | DB 有沒有對應任務 | 判定 | 後端做什麼 |
|---|---|---|---|
有 userTask、沒有 job_execution_uid 屬性 |
— | 新增意圖 | 建一筆空任務(名稱取圖上的 name)→ inject_job_execution_uid() 回寫編號 |
有 userTask、有 uid,DB 查得到 |
✅ | 保留 | 只更新拓樸(XML 照存),任務資料一個欄位都不動 |
| — | ✅ DB 有、但新 XML 裡找不到這個 uid | 刪除意圖(D6/D7) | 呼叫 delete_job() 刪掉它(同一支、同一守門,見 §5.4);若刪完會剩零任務 → 整筆存檔擋下 |
有 userTask、有 uid,但 DB 查不到(uid 失效) |
❌ | 錯誤 | 擋下存檔,指出是哪個節點帶了查不到的編號 |
一個 userTask 節點在 DB 對到多筆任務(同 template_job_id,舊版編輯器殘留) |
✅ 多筆 | 錯誤 | 擋下存檔,指出是哪個節點對到哪幾筆任務;不可判成刪除意圖靜默清掉(決策者 2026-09-19 裁) |
🔴 「XML 少了 uid」是刪除,不是孤兒
把它當錯誤擋下,等於宣告「在圖上刪方塊沒有用」——而編輯器的定位就是管走向,刪方塊正是改走向。孤兒的定義因此收窄成上表最後一列:帶著一個查不到的編號。
另外兩項驗證維持,任一不過就退回並指出哪個節點有問題:
| 檢查 | 為什麼 | 不擋的症狀 |
|---|---|---|
job_execution_uid 不重複 |
使用者可能複製貼上方塊,連帶複製別人的任務編號 | 兩個方塊指向同一筆任務,完成其中一個另一個跟著變狀態 |
| 每個任務節點從開始事件走得到 | 接線接錯、方塊沒連上 | 完成前一個任務時引擎找不到它,流程卡住 |
輪次凍結反查不到時放行(決策者 2026-09-20 裁):存檔端點只有範本 uid,輪次狀態要從流程實例反查(workflow_execution_control_mapping.round_id,或任一任務的指派人所屬專案的最新輪)。兩條都查不到就放行,不擋下。理由是查不到的多半是沒有輪次、沒有指派人的舊資料(DEV 上約占一半),那些本來就沒有輪次可凍結;拿不到就擋等於把規劃期的正常存檔一起擋掉,而使用者看到的只是「存不了」。代價是這類舊資料上的凍結守門形同不存在——可接受,因為凍結要保護的稽核範圍在那些資料上並不成立。
存檔時的執行順序是「先建、再寫圖、最後刪」,不可改成先刪。刪除的兩道守門(倉儲數任務筆數、套件數圖上節點)都是逐筆問「刪完還剩不剩」,只有上層那筆總帳知道新的會補回來。整批換掉(現有 2 筆、刪 2 筆、建 1 筆)先刪的話,刪到第二筆時新的還沒建、圖上也還沒有它,兩道守門都會把這筆合法存檔擋成「至少要留一個任務」。先寫圖還讓刪除走到「節點已不在圖上」的 no-op 分支——編輯器送上來的 XML 本來就沒有那些被刪的方塊。連帶要求:刪除讀圖一律原生 SQL 現讀,不可拿 ORM 載過的 WorkflowTemplate.xml(寫圖走原生 SQL,ORM 快取那份會過期,拿舊圖改再寫回會把剛建的新方塊整段蓋掉,且沒有錯誤訊息)。
刪掉哪些任務要回報給 route:編輯器刪方塊=刪任務,雲端資料夾的處置必須與規劃頁一致(見 §5.4),存檔服務因此回傳被刪任務的 uid 清單,由 route 比照規劃頁呼叫 Drive 歸檔(best-effort,沒接 Drive 的租戶跳過)。
整個 diff(建/刪/存 XML/雙寫翻譯表)必須在同一個交易內——中途失敗時圖與任務要一起回到存檔前的狀態,不可以出現「任務刪了但 XML 沒存」。
規劃頁走原生 SQL 讀主表、編輯器走倉儲的語系備援讀翻譯表。只更新主表,編輯器會永遠看到舊圖。
所有寫圖路徑(插節點、移節點、存檔時回寫 uid、專案建立時把任務編號寫回範本)都走倉儲的 write_template_xml() 同時寫主表與翻譯表,並且與建任務落在同一個交易內。專案建立那條路徑若漏了翻譯表,症狀是新專案一開編輯器直接存檔就被三向 diff 判成「任務全刪」而擋下。
| 還能做 | 不能做 |
|---|---|
| 改線(把平行拉成一條龍、加二選一分支、調整順序) | 填任務類型、問卷、設備、部門、工具參數(回規劃頁) |
| 拖新方塊進來(只填名稱) | 直接改任務的負責人與狀態 |
| 刪方塊(=刪任務,與規劃頁刪除等效,D7) | — |
存檔後前端只存一次 XML,取回後端回傳的新 XML 重新載入圖;規劃頁分頁在重新取得焦點時重抓任務清單。
jedi-flow-engine 的改動開發期一律走 poetry path dependency(主專案 pyproject.toml 取消 path 形式註解,改動後重啟 BE 即生效)。path 改動不 commit;功能整體完成後還原 pin 一起 commit。發版與推 Nexus 一律等決策者明示,runner 不自行 bump。
依賴鏈:D-4.1(開工前查證)→ A(套件改圖工具)→ B(BE)→ C(FE,B 完成後兩張可並行)→ D-4.2/D-4.3(驗收)
這是拆分表,不是派工單
實際開卡、切棒、派工要等決策者發令。跨 3 repo,各 repo 分開 commit;套件的 path dependency 改動不 commit。
| # | 子任務 | 驗收條件 | 依賴 | Repo |
|---|---|---|---|---|
| A-1.1 | 新增兩支插節點函式:①把單任務流程重排成標準平行(重用 bpmn_generator.py:145 產圖邏輯);②在合流閘道前掛一條新平行分支(無合流閘道時於結束事件前補一個) |
單一任務的圖 → 重排 → 產出「開始→分叉→2任務→合流→結束」,且既有任務的 job_execution_uid 原樣保留;標準平行圖 → 掛新分支 → 分叉出線數與合流入線數都 +1;一條龍圖 → 掛新分支 → 既有先後順序完全不變 |
— | jedi-flow-engine |
| A-1.2 | 擴充 remove_user_task_from_xml():平行分支刪到只剩一條時收掉分叉/合流閘道、接回線性;圖上只剩最後一個 userTask 時拒絕移除(拋錯,由呼叫端轉成前置條件錯誤) |
三分支平行圖 → 刪到剩一條 → 閘道消失、接回線性;對只剩一個任務的圖再呼叫 → 拋錯、XML 不變 | — | jedi-flow-engine |
| A-1.3 | 新增拓樸判定函式:①「這個節點的上游是不是分叉閘道/開始事件」(供初始狀態判定);②「這個節點的上游是不是合流閘道、該閘道所有入線分支各自的末端任務有哪些」(供合流等待判定)。全部補單元測試 | 給「兩條平行分支、合流後還有一個任務」的圖:查合流後任務 → 回傳兩條分支的末端任務編號;查分叉後任務 → 回傳「上游是分叉」。不動 get_next_jobs() 既有展開行為 |
— | jedi-flow-engine |
| # | 子任務 | 驗收條件 | 依賴 | Repo |
|---|---|---|---|---|
| B-2.1 | create_job() 不帶 job_element_uid 的入口:依現有拓樸選插法(§5.3)→ 插節點 → 建任務 → inject_job_execution_uid() → 主表+翻譯表雙寫,全部同一交易 |
Swagger 打新增端點不帶 job_element_uid → 200;資料庫查該範本 XML 多一個方塊且帶新任務編號;打開編輯器看得到新方塊(證明翻譯表也寫了) |
A-1.1、A-1.3 | BE |
| B-2.2 | 規劃頁刪除入口:delete_job() 升為刪除主路徑,清五張關聯表 → 移節點 → 必要時收閘道 → 雙寫。守門沿用既有 _require_manager,不新增「已有進度不准刪」限制,但新增「至少保留一個」前置條件(§5.4、D6) |
連續新增三筆 → 刪一筆 → 圖剩兩條分支、任務與五張關聯表都清乾淨;再刪到剩一筆 → 閘道收掉接回線性;對最後一筆按刪除 → 412 擋下、任務與圖不變;刪一筆 PROCESSING 的任務 → 照現況允許(行為與改動前一致) |
A-1.2 | BE |
| B-2.3 | 流程圖 XML 存檔端點做三向 diff(§5.8):①無 uid 的新節點 → 建空任務並回寫編號;②新 XML 裡消失的 uid → 呼叫同一支 delete_job() 刪任務(含 Drive 資料夾封存副作用,與規劃頁入口一致);③其餘只更新拓樸。uid 重複/uid 查不到/節點走不到/刪到零任務 → 擋下並指出原因。全部同一交易,回傳新 XML |
拖新方塊存檔 → 資料庫多一筆空任務、名稱是圖上打的;刪掉一個方塊存檔 → 對應任務與五張關聯表全部清乾淨(與規劃頁刪除結果逐欄相同);帶查不到的 uid → 被擋下且回應指得出是哪個節點;複製貼上重複 uid → 被擋下;交易中途失敗 → 圖與任務一起回到存檔前狀態 | A-1.3、B-2.2 | BE |
| B-2.4 | _sync_task_to_template_xml() 收斂成只寫 name;圖上舊屬性的讀取路徑停用(不清既有 XML) |
規劃頁改任務描述存檔 → 圖上 name 更新、其餘 camunda 屬性維持原值不被刷新;既有專案流程行為不變 |
— | BE |
| B-2.5 | 初始狀態判定(§5.5)與可否退回判定(§5.6)改依拓樸 | 同一查核項目連續新增三次 → 三筆任務全部是進行中(不是兩筆 TODO);平行下所有下一批任務都未開始才回「可退回」 | A-1.3 | BE |
| B-2.6 | 規劃期守門:新增/刪除任務接上 _assert_round_not_frozen_for_workflow(),凍結後回明確 error code 與訊息 |
輪次凍結後打新增端點 → 被擋、回傳可辨識的 error code(FE 據此顯示停用原因);規劃期打 → 正常 | — | BE |
| B-2.7 | 合流閘道等待修正兩落點:complete_job():702 與 complete_main_workflow_job():569 迴圈前插 A-1.3 的判定 |
「兩條平行分支、合流後接一個覆核任務」的圖:完成第一條 → 覆核任務不得變進行中;兩條都完成 → 才變進行中。兩支各驗一次(complete_main_workflow_job 走輪次推進動線) |
A-1.3 | BE |
| B-2.8 | prep_job_generation_service._first_user_task_id() 旁補註解寫明陷阱(只建第一個/覆核輪 re-clone 會複製此問題,見 §4.1);走 ddd-compliance-reviewer 掃本子需求全部 diff |
reviewer 無阻斷級發現;註解為一行、只寫陷阱不寫施工日誌 | B-2.1~B-2.7 | BE |
| # | 子任務 | 驗收條件 | 依賴 | Repo |
|---|---|---|---|---|
| C-3.1 | 規劃頁任務區塊補「新增任務」(空狀態與有任務時都有)與每筆的刪除(帶確認);新任務建立後自動展開、聚焦名稱欄;凍結時按鈕停用+留住原因提示(照 FR-110 做法,不彈 toast);超過 20 筆顯示提示 | 點查核項目 → 新增 → 填名稱存檔 → 重新整理仍在;刪除 → 確認後消失、重新整理不回來;凍結狀態按鈕停用且看得到原因(不是無聲失效) | B-2.1、B-2.2、B-2.6 | FE |
| C-3.2 | WorkflowSetupEditor.vue 屬性面板只留名稱(移除工作描述/指南/任務類型/設備/部門的編輯與寫入) |
點方塊 → 右邊只剩名稱欄;改名稱存檔 → 規劃頁該任務名稱跟著變 | B-2.4 | FE |
| C-3.3 | 編輯器存檔瘦身:onSaveClick() 只存一次 XML,移除 syncJobExecutions() 與 patchJobExecutionUids();刪節點能力保留不鎖,但存檔前提示「刪掉的方塊對應的任務會一併刪除」;顯示後端回的驗證訊息(哪個節點有問題);規劃頁分頁重新取得焦點時重抓任務清單;TaskSetupView.vue:524 進來的路徑一併驗 |
拖新方塊存檔 → 回規劃頁(不必手動重新整理)看得到新的空任務;刪方塊存檔 → 規劃頁對應任務消失且存檔前有提示;帶查不到 uid 的節點存檔 → 畫面出現明確訊息指出節點 | B-2.3 | FE |
| # | 子任務 | 驗收條件 | 依賴 | Repo |
|---|---|---|---|---|
| D-4.1 | 🔴 開工前查證:stage_advance_service.py 與 stage_rollback_service.py 讀的 workflow_template_xml 是否與查核項目底下的收證據流程同一張圖(唯讀查證,不改任何檔) |
產出一頁結論寫進母卡,明確回答「是/否」與依據(檔名+行號)。若為同一張圖 → 停下回報,整案範圍重估(get_next_jobs() 與初始狀態判定的改動會連帶影響輪次主流程) |
— | BE(唯讀) |
| D-4.2 | 端到端手測清單實跑(見 §7.2),批次一輪跑完再統一修 | 清單全項通過;有打折處(環境限制只能模擬)當場在回報中寫明,不報成通過 | C-3.1~C-3.3 | 手測 |
| D-4.3 | e2e 回歸與新情境(母卡收口動作,arc 完成後另開一棒) | 專案啟動建任務、輪次推進、批次完成、任務佇列四條既有動線在平行結構下綠燈;新增「兩個平行任務只完成一個不得放行」情境 | D-4.2 | compliance-manager-test |
子任務數:jedi-flow-engine 3、BE 9(B 八張 + D-4.1)、FE 3、test 1,另手測 1 張不綁 repo — 合計 16。
D-4.1 的查證結論必須是「輪次推進/回退讀的不是查核項目那張圖」。 若共用同一張,本案範圍要重新評估後再開卡。
name 更新、其餘舊屬性未被刷新。專案啟動建任務、輪次推進、輪次回退、批次完成、任務佇列——五條既有動線在平行結構下重跑。
| 項目 | 在做什麼 | 為什麼會長出來 | 狀態 |
|---|---|---|---|
| 編輯器 UI 優化/屬性面板擴充 | 流程圖編輯器的操作體驗重做,並重新評估屬性面板要不要放回類型之類的欄位 | 決策者原話:「目前的 UI 設計不是很好用,之後再來優化」——本案先把面板收到只剩名稱以止住第二份真相,體驗問題另案處理 | 未開案 |
| 稽核中補任務 | 輪次凍結後仍能追加任務的動線 | D1 把新增限制在規劃期,但「稽核到一半發現漏了一項」確實會發生;牽涉補件流程與報表口徑 | 未開案 |
| 多任務流程範本 | 讓流程範本本身就能定義多個任務 | prep_job_generation_service._first_user_task_id() 只抓第一個 userTask,範本做成多任務會靜默漏建 |
未開案(本案只留註解標註前提) |
| 覆核輪對多任務查核項目的任務生成 | 開覆核輪時,對已有多個平行任務的查核項目,每個方塊各建一筆任務 | 覆核輪 re-clone 第一輪範本後重跑 generate(),它只認第一個方塊——本案讓查核項目可多任務後,開覆核輪會出現「圖上三個方塊、資料庫一筆任務」且無錯誤訊息(D-4.1 查證發現)。決策者 2026-09-19 裁留延伸、不擴本案 |
未開案 |
| 圖上舊 camunda 屬性清理 | 掃全部範本清掉不再讀的屬性 | D5 選擇「不清、讀時忽略」,屬性會停在那裡不再增生 | 不排程(風險大於收益,除非有明確需要) |
閱讀順序:先看本頁 → 實作完成後看 FINAL-SPEC.md → 要追每一棒做了什麼看 handoff/FR-112-LOG.md。
| 你想知道 | 看哪份 |
|---|---|
| 為什麼要做、決策怎麼裁、被排除了什麼 | 本文件 |
| 完整的選項推演與業界對照 | discussion.html |
| 做完之後最後長什麼樣 | FINAL-SPEC.md(實作完成後產出) |
| 每一棒做了什麼、踩了什麼坑 | handoff/FR-112-LOG.md(待建) |
| 流程圖屬性與 API 欄位的對照 | FE docs/guides/grc-page-data-models.md:40-47 |