FR-112 · 設計文件 · 2026-09-19(D1–D7 全數定案)

任務在規劃頁上開,流程圖只管先後順序 — 設計定案

PM 要替一個查核項目多開一個任務,動線回到專案規劃頁:按「新增任務」、填名稱、存檔。新任務預設掛成平行分支——誰先做都行、全部做完這個查核項目才算過。流程圖編輯器降級為純粹的走向設計工具,屬性面板只留名稱,不再是填任務資料的地方。連帶修掉一個既有引擎缺口:合流閘道不等兄弟分支就放行。

D1–D7 全數定案 4 子需求 · 16 子任務 跨 3 repo:jedi-flow-engine/BE/FE 🔴 引擎缺口:合流閘道不等兄弟分支(兩落點都修) 範圍排除:流程範本管理頁

狀態:設計定案(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)

1. 需求背景與端到端流程

1.1 一句話

任務的所有資料以專案規劃頁為準存進資料庫,流程圖只留「先後與分支」這一件事;規劃頁補上「新增任務」按鈕,新任務預設掛成平行分支,該查核項目要全部任務做完才算通過。

1.2 給非工程讀者的三個名詞

  • 查核項目(AO):一條法規控制項底下再細分的檢查點,規劃頁左邊樹狀清單點到的最小單位。每個查核項目底下掛一串任務。
  • 流程圖(BPMN):用方塊與箭頭畫出來的工作流程。方塊叫 userTask(一個方塊=一個任務),箭頭表示先後順序,菱形的叫「閘道」——平行閘道表示「這幾條同時開跑」、互斥閘道表示「二選一」。
  • 任務資料表(job_executions):資料庫裡真正存任務的地方——名稱、描述、負責人、狀態、證據、問卷、檢測工具設定都在這裡。

1.3 要解的問題

任務的同一份資料存在兩個地方,而且會互相覆蓋:

存在哪 存了什麼 誰在寫
資料庫 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 為了「多派一件事」去學拖方塊、分辨平行閘道與互斥閘道,等於把工具的複雜度轉嫁給使用者。

1.4 端到端流程(做完之後長這樣)

PM 在規劃頁點一個查核項目
  → 按「新增任務」,只填名稱
  → 後端在該查核項目的流程圖上插一個方塊、接成平行分支、回寫任務編號(同一個交易內)
  → 任務面板出現、展開待填(負責人/問卷/檢測工具⋯全部在規劃頁填)
  → 專案啟動後,該查核項目底下所有平行任務同時可做
  → 每個負責人各自完成自己那條
  → 全部完成 → 該查核項目才算通過(只完成一部分不會提前放行)

流程圖編輯器(另一條動線,給要調走向的人用)
  → 改線(把平行拉成一條龍、加二選一分支)、拖新方塊(只能填名稱)、刪方塊
  → 存檔 → 後端三向 diff:沒編號的新方塊建成空任務/XML 裡消失的編號刪掉那筆任務/其餘只更新拓樸
  → 回規劃頁重抓,任務清單與圖一致

2. 分工概述(30 秒版)

階段 做什麼 產出 完成怎麼判定(決策者親手檢查法)
⓪ 開工前必驗 確認輪次推進/輪次回退讀的流程圖,與查核項目底下的收證據流程不是同一張 一頁查證結論(寫進母卡) 開 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) 起一個新專案 → 規劃頁加兩個任務 → 兩個都完成 → 該查核項目變「已完成」;只完成一個時不可以變完成

依賴:⓪是全案的前置查證;①是②的地基(②要呼叫①補的改圖函式);③④在②完成後可平行;⑤等③④都驗收過才做。


3. 決策定案(D1–D7)

# 決策 定案 理由 被排除的方案
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」——兩入口兩套語意,同一個動作在不同頁面產生不同結果

4. 現況接入點盤點

3涉及 repo

10寫進流程圖的 camunda 屬性

2引擎缺口落點(同一個 bug 兩處)

4.1 後端(compliance-manager-be)

元件 現況 本案動作
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),無順序假設 不受影響

4.2 共用套件(jedi-flow-engine,現版 1.1.2)

元件 現況 本案動作
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() 深度優先出順序/廣度優先分層 平行結構下「順序」意義不大,但不會壞,不動

4.3 前端(compliance-manager-fe)

元件 現況 本案動作
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 流程範本管理,是另一套系統 明確不在範圍

5. 詳細設計

5.1 原則:一件事只有一個地方說了算

這件事 真相在哪 另一邊是什麼
任務叫什麼、描述、指南、類型、負責人、部門、設備、問卷、工具參數、狀態 資料庫 job_executions +關聯表 圖上的 name 是顯示副本,由後端單向寫;其餘屬性不再寫也不再讀
誰先誰後、誰跟誰平行、有沒有二選一分支 流程圖 XML 資料庫不存拓樸
圖上這個方塊是哪一筆任務 圖上的 job_execution_uid 屬性(後端寫) —

🔴 判準一句話:圖上的東西只有「拓樸」和「指回任務的編號」是真的,其他都是印出來給人看的。

任何一次改動只要讓某個欄位「從圖上讀回來當真值用」,就等於把第二份真相請回來。

5.2 新增任務:兩種入口,同一支端點

%%{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,任務面板出現、預設展開待填
圖 1 — 規劃頁新增任務的端到端時序

兩種入口走同一支端點,差別只在有沒有帶 job_element_uid:

入口 帶 job_element_uid 嗎 後端做什麼
規劃頁按「新增任務」 不帶 自己在圖上插節點、接線、回寫任務編號
流程圖編輯器拖新方塊、存檔 帶(前端存 XML 時圖上已有這個節點) 直接用這個編號建一筆空任務、把編號回寫圖上

保留第二種入口是決策者明示的

在圖上拖方塊仍然可以開任務——只是屬性面板只能填名稱,其餘細節回規劃頁補。存檔後後端掃圖,發現沒有對應任務的 userTask 就建一筆空任務並回寫編號。

5.3 插節點規則:看現在的圖長什麼樣決定怎麼插

%%{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
圖 2 — 三種情境下新增任務後的拓樸變化

規則寫成白話:

  1. 圖上只有一個任務 → 重排成標準平行:開始 → 分叉 → 兩個任務並排 → 合流 → 結束。重用 bpmn_generator.py:145 的產圖邏輯,既有任務的 job_execution_uid 必須原樣保留。
  2. 圖上已是標準平行結構 → 分叉閘道多一條出線、合流閘道多一條入線,新方塊掛中間。
  3. 使用者已排成一條龍或加了二選一分支 → 不動他排好的順序,把新任務掛成「合流前的一條新平行分支」;圖上沒有合流閘道時,在結束事件前補一個(D2)。

為什麼不是「一律重排成標準平行」

使用者若刻意把任務排成「先做 A 才能做 B」,一按新增就被重排回全平行,等於系統擅自推翻他的設計,而且沒有任何提示。

5.4 刪除任務:兩個入口、同一支 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 負責。
  • Drive 資料夾封存是 route 層的 best-effort 副作用(job_route.py:161-166)。編輯器存檔這條路徑也要接上,否則同一個刪除動作在兩個入口對雲端資料夾的處置不同。

圖的處理:

  • 一般情形:前 → 節點 → 後 接回 前 → 後(remove_user_task_from_xml() 現有行為,idempotent——編輯器那條路徑節點已經不在 XML 裡,呼叫它不會出錯)。
  • 平行分支刪到只剩一條:分叉與合流閘道只剩單進單出 → 把兩個閘道一併收掉、接回線性。
  • 刪到只剩最後一個:擋下,不允許刪到零(D6)。規劃頁的刪除鈕在只剩一筆時停用並顯示原因;編輯器存檔若 diff 結果會刪到零,整筆存檔拒絕、XML 與任務都不動。

5.5 初始狀態判定(R3)

平行分支上的任務應該都可以馬上開始做,不該排隊。判定基準從「該流程下有沒有未完成任務」改成看這個節點的上游:

新節點的上游是 初始狀態
開始事件,或分叉閘道(parallelGateway 出線數 > 1) PROCESSING
另一個 userTask(使用者排的一條龍中段) TODO

判定要用的拓樸資訊由套件的判定函式提供(§5.7),BE 不自己解析 XML。

🔴 不改這裡的症狀:平行下第二個以後的任務全部變 TODO 排隊

畫面上看起來是「有三個平行任務,但只有一個能做」——與本案要的「誰先做都行」完全相反,而且沒有錯誤訊息。

5.6 可否退回判定(R4)

_compute_can_revert_job() 現行邏輯取 get_next_jobs() 的第 0 筆,平行結構下「下一批」有多筆,只看第一筆會誤判。改成:所有下一批任務都還沒開始,才可以退回;len(all_jobs)==1 那條捷徑在多任務後失效,一併重檢。

5.7 🔴 合流閘道等待判定(D3)

缺口:合流閘道不等兄弟分支——第一條分支完成,合流後面的任務就被啟動

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 那條「檢查整條流程是否全部完成」的分支——那條是對的。所以預設結構不會立刻踩到,但本案會放大它:

  1. 編輯器保留改線能力,使用者只要在合流後接一個「主管覆核」任務,缺口立刻成立。
  2. 批次完成(job_batch_complete_service.py:86)連續呼叫 complete_job(),把時序放到最大。

修法:在 jedi-flow-engine 新增拓樸判定函式,回答「這個任務的上游是不是合流閘道/該閘道所有入線分支的任務是否都已完成或取消」;complete_job() 與 complete_main_workflow_job() 兩處的迴圈在設 PROCESSING 之前先問它,答否就跳過不啟動。

判定放套件、呼叫點在主專案,是刻意的分界

拓樸語意(誰是分叉、誰是合流、兄弟分支有哪些)只有看得到整張圖的那一層能回答,放在套件;「任務算不算完成」是主專案的業務狀態,留在主專案。兩邊各自回答自己知道的事。

5.8 流程圖存檔端點:三向 diff

編輯器存 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 沒存」。

5.9 🔴 範本 XML 是可翻譯欄位,寫圖必須雙寫(R7)

規劃頁走原生 SQL 讀主表、編輯器走倉儲的語系備援讀翻譯表。只更新主表,編輯器會永遠看到舊圖。

所有寫圖路徑(插節點、移節點、存檔時回寫 uid、專案建立時把任務編號寫回範本)都走倉儲的 write_template_xml() 同時寫主表與翻譯表,並且與建任務落在同一個交易內。專案建立那條路徑若漏了翻譯表,症狀是新專案一開編輯器直接存檔就被三向 diff 判成「任務全刪」而擋下。

5.10 編輯器鎖定範圍

還能做 不能做
改線(把平行拉成一條龍、加二選一分支、調整順序) 填任務類型、問卷、設備、部門、工具參數(回規劃頁)
拖新方塊進來(只填名稱) 直接改任務的負責人與狀態
刪方塊(=刪任務,與規劃頁刪除等效,D7) —

存檔後前端只存一次 XML,取回後端回傳的新 XML 重新載入圖;規劃頁分頁在重新取得焦點時重抓任務清單。

5.11 套件開發方式

jedi-flow-engine 的改動開發期一律走 poetry path dependency(主專案 pyproject.toml 取消 path 形式註解,改動後重啟 BE 即生效)。path 改動不 commit;功能整體完成後還原 pin 一起 commit。發版與推 Nexus 一律等決策者明示,runner 不自行 bump。


6. 拆分(4 子需求 × 16 子任務)

依賴鏈:D-4.1(開工前查證)→ A(套件改圖工具)→ B(BE)→ C(FE,B 完成後兩張可並行)→ D-4.2/D-4.3(驗收)

這是拆分表,不是派工單

實際開卡、切棒、派工要等決策者發令。跨 3 repo,各 repo 分開 commit;套件的 path dependency 改動不 commit。

FR-112.A jedi-flow-engine:BPMN 拓樸改寫工具 — 依賴:D-4.1

# 子任務 驗收條件 依賴 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

FR-112.B compliance-manager-be:新增/刪除、守門與引擎修正 — 依賴:FR-112.A

# 子任務 驗收條件 依賴 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

FR-112.C compliance-manager-fe:規劃頁按鈕與編輯器瘦身 — 依賴:FR-112.B

# 子任務 驗收條件 依賴 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

FR-112.D 驗收與回歸 — D-4.1 最先做,D-4.2/D-4.3 最後做

# 子任務 驗收條件 依賴 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。


7. 端到端驗收

7.1 前置條件(不過就不開工)

D-4.1 的查證結論必須是「輪次推進/回退讀的不是查核項目那張圖」。 若共用同一張,本案範圍要重新評估後再開卡。

7.2 手測清單(決策者親手可驗)

  1. 起一個新專案 → 規劃頁點任一查核項目 → 按「新增任務」→ 填名稱存檔 → 重新整理仍在。
  2. 打開流程圖編輯器 → 新方塊與原任務並排(平行),不是串在後面。
  3. 同一查核項目連續新增到三筆 → 三筆全部是進行中,沒有任何一筆在排隊。
  4. 只完成其中一個 → 該查核項目不可以變已完成;全部完成 → 才變已完成。
  5. 批次完成一次勾兩個平行任務 → 狀態正確,沒有任務提前跳成進行中。
  6. 在編輯器把流程改成「兩條平行 → 合流 → 主管覆核」→ 完成第一條 → 覆核任務不得變進行中;兩條都完成 → 才變。
  7. 編輯器點方塊 → 右邊只剩名稱欄,沒有任務類型/問卷/設備/部門。
  8. 編輯器拖新方塊存檔 → 回規劃頁看得到一筆新的空任務,名稱是圖上打的那個。
  9. 編輯器刪掉一個方塊存檔 → 回規劃頁該任務消失,且五張關聯表都清乾淨(與規劃頁刪除結果相同)。
  10. 手改 XML 讓某節點帶一個查不到的 uid → 存檔被擋下,訊息指得出是哪個節點。
  11. 規劃頁刪任務 → 圖上方塊消失;刪到剩一筆 → 閘道收掉、刪除鈕停用,且不必把滑鼠移過去就能在按鈕旁看到「查核項目至少要有一個任務」;編輯器把最後一個方塊刪掉存檔 → 被擋下、圖與任務不變,訊息顯示中文(不是 error code)。
  12. 輪次凍結後 → 新增/刪除按鈕停用,畫面看得到原因;直接打後端端點也被擋。
  13. 規劃頁新增一個「檢測工具執行」型任務 → 參數確實加密落庫(查 DB 欄位不是明文)。
  14. 規劃頁改任務描述存檔 → 圖上 name 更新、其餘舊屬性未被刷新。
  15. 既有專案(圖上帶完整舊屬性)打開規劃頁與編輯器 → 行為正常,無錯誤。

7.3 回歸範圍

專案啟動建任務、輪次推進、輪次回退、批次完成、任務佇列——五條既有動線在平行結構下重跑。


8. 延伸 FR 清單

項目 在做什麼 為什麼會長出來 狀態
編輯器 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。


9. 這份文件的定位

你想知道 看哪份
為什麼要做、決策怎麼裁、被排除了什麼 本文件
完整的選項推演與業界對照 discussion.html
做完之後最後長什麼樣 FINAL-SPEC.md(實作完成後產出)
每一棒做了什麼、踩了什麼坑 handoff/FR-112-LOG.md(待建)
流程圖屬性與 API 欄位的對照 FE docs/guides/grc-page-data-models.md:40-47