---
title: "資源庫任務說明 — 設計定案 (FR-147)"
brand: "Guidant AI · **FR-147** 資源庫任務說明"
eyebrow: "FR-147 · 設計定案 v1 · 2026-10-17"
h1: "資源庫任務說明：設計定案"
subtitle: "顧問在資源庫替每條評估目標的預設任務寫名稱、描述、指南；啟動帶進任務；一份 Excel 兩邊共用"
lede: "合規資源庫的「控制項與評估目標」頁，每條評估目標（AO）底下多一段「任務說明」。專案還沒啟動時每條 AO 固定一個範本預設任務，顧問填它的名稱、描述、指南，三欄存在範本流程圖的方塊上；專案啟動時原封帶進任務；啟動後這一段改讀專案目前輪次的任務，PM 改過流程就看到多個，顧問改的三欄直接寫進任務。顧問不改流程，資源庫頁的流程編輯器入口收掉。一份 Excel 一列一任務、AO 欄合併儲存格，資源庫頁與專案規劃頁同一份格式。不開新表、不動 jedi 套件、不套 migration。本文件寫定案規則、為什麼這樣定、踩到會怎樣。"
chips: [
  {text: "D1～D4、D6 定案（2026-10-17）", kind: ok},
  {text: "三棒八張卡（v2 加 3c 版型），可平行", kind: accent},
  {text: "不開新表、不動套件、不套 migration", kind: plain},
  {text: "陷阱：圖的 XML 是可翻譯欄位要雙寫", kind: crit}
]
footer: "FR-147 · 設計文件 · 2026-10-17 · 討論稿：[discussion.md](discussion.md)　雛形：[prototype/index.html](prototype/index.html) · 沿革見討論稿與 git log"
---

> 🔴 **這份是設計決策，不是現況。** 內文停在 2026-10-17 定稿當時，實作改動不回頭改寫這裡。

> 狀態：**設計定案，待開卡派工**｜討論稿：[`discussion.md`](discussion.md)｜雛形：[`prototype/index.html`](prototype/index.html)

> 檔案路徑與行號以 2026-10-17 為準，查不到時以函式名重新定位。路徑慣例：BE 路徑相對 `compliance-manager-be/`；FE 路徑相對 `compliance-manager-fe/`；`pkg/<套件>/` 代表 `~/Projects/Jedicogy/module/jedi-python-package/<套件>/`。

名詞：**資源庫**＝合規資源庫（`compliance.module_frames`），一本只給一個專案用；**AO**＝評估目標，每條控制項底下的檢查項目，鍵是 `catalog_control_parts.part_id`；**範本流程圖**＝每條 AO 配的一張 BPMN 圖（`compliance.workflow_templates`，uid 寫在 AO `props` 的 `workflow-template-uid`），預設一個方塊；**範本預設任務**＝那個方塊，資源庫階段「任務」指的就是它；**任務**＝`compliance.job_executions` 一列，有 `name`／`description`／`guide`；**目前輪次**＝專案最新一輪（第 1 輪或最新覆核輪）。

## 變更紀錄 {#changelog nav="變更紀錄"}

| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-17 | v1：D1～D4、D6 定案；資料落點、讀寫規則、Excel 契約、三棒七張卡、端到端驗收 | — |
| 2026-10-17 | v2：決策者審雛形 v3 後加 **D7 資源庫編輯頁版型比照稽核計畫頁**（左樹到 AO、右欄一次一條），新增卡 3c、3b 改依賴 3c；雛形 v4 | CM-2872 |

---

## 分工概述（30 秒版） {#overview nav="概述"}

整案一句話：**資源庫頁每條 AO 底下多一段「任務說明」，顧問填預設任務的名稱、描述、指南；啟動帶進任務、啟動後直接改任務；一份 Excel 兩邊共用；顧問不改流程。**

| 卡 | 做什麼 | 產出 | 完成怎麼判定（決策者親手做什麼、看到什麼） |
|---|---|---|---|
| **2a** | 專案啟動時三欄從範本方塊帶進任務 | 方塊屬性讀寫 helper；任務產生改三欄來源 | DEV 把一本資源庫某條 AO 三欄用 API 填好，用它啟動專案，規劃頁那條 AO 的任務名稱、描述、指南就是填的；沒填的資源庫啟動後任務名稱仍是「控制碼 / AO 收證據」 |
| **2b** | 覆核輪新任務抄上一輪三欄 | 覆核輪承襲加三欄 | DEV 在第 1 輪任務填三欄後啟動覆核輪，新輪同一條 AO 的任務三欄一樣 |
| **3a** | 資源庫 AO 任務說明讀寫 API | 新 service、route、readmodel | 沒綁專案的資源庫：讀回一個任務、寫三欄再讀回一致；綁了專案的：讀回專案目前輪次任務、寫三欄後規劃頁任務也變 |
| **3c** | 資源庫編輯頁版型比照稽核計畫頁（D7） | 左樹到 AO、右欄單一 AO 表單 | 對照雛形 v4：左樹點控制項右邊只有控制項欄位，點 AO 右邊只有那條 AO；上一條／下一條；15 條 AO 的控制項不再整頁堆 |
| **3b** | 右欄 AO 表單底下加「任務說明」區塊、左樹圓點、收掉流程編輯器入口 | FE 新子元件掛進 AO 表單、拿掉一顆鈕 | 對照雛形畫面 1：AO 展開看到任務小卡三格、填存重整還在；已啟動的資源庫看到專案任務（多個時有流程帶、狀態與指派人唯讀）；AO 右邊沒有流程編輯器鈕 |
| **4a** | 任務說明 Excel 匯出匯入（資源庫端） | 共用 workbook builder、解析、預覽、確認 | 資源庫頁匯出開 Excel：一列一任務、多任務的 AO 前幾欄合併、三欄黃底；改兩格匯回預覽只標那兩格、確認後畫面更新 |
| **4b** | 專案規劃頁既有任務配置匯出匯入對齊 | 加描述指南兩欄、AO 合併、匯入收三欄 | 專案規劃頁匯出長相與資源庫一致（多指派、部門、設備欄）；改三欄匯回寫進任務 |
| **4c** | 資源庫頁匯出匯入對話框與預覽 | FE | 對照雛形匯入預覽 |

依賴：2a 先建 helper，3a 的「寫圖」用同一支；3c（版型）不依賴後端可與 2a 同時派；3b 等 3a 與 3c；其餘可平行。

```{.mermaid cap="圖 1 — 七張卡的依賴（箭頭＝必須先做）"}
%%{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 LR
    subgraph B2[棒 2 啟動與覆核輪]
      A2a[2a helper＋啟動帶三欄] --> A2b[2b 覆核輪抄三欄]
    end
    subgraph B3[棒 3 資源庫頁]
      A3a[3a 讀寫 API] --> A3b[3b FE 區塊＋收入口]
    end
    subgraph B4[棒 4 Excel]
      A4a[4a 資源庫端匯出匯入] --> A4c[4c FE 對話框]
      A4a --> A4b[4b 專案端對齊]
    end
    A2a -.方塊屬性 helper.-> A3a
    A3a -.讀取 readmodel.-> A4a
```

---

## 決策定案 {#decisions nav="決策"}

| 編號 | 定案 | 理由 | 被排除 |
|---|---|---|---|
| **D1** | 啟動前三欄存範本流程圖的方塊上：名稱＝方塊 `name`，描述與指南＝方塊的 camunda 屬性 `task_description`／`task_guide` | 與既有掛任務編號（`job_execution_uid`）同一招；不開新表、不套 migration；複製資源庫連圖一起 clone 免費帶走；沒有第二份真相 | 另開「AO 任務說明」表：顧問刪方塊時表上殘留、複製要多抄、要 RLS 與 migration |
| **D2** | 啟動後顧問在資源庫頁改三欄，守門＝資源庫編輯權（能力點 `oscal.resource-library.update` ＋ `verify_module_frame_exists_by_uid(writable=True)` 歸屬守門 ＋ 稽核鎖 `assert_template_not_audit_locked`），不要求專案 manager；端點只開三欄。已綁專案時這三欄直接寫進專案目前輪次的任務，**非專案成員的顧問也能改**——這是本意，決策者 2026-10-22 再次確認（arc-review 重要級 3 曾列為疑慮） | 決策者裁「顧問只改三欄文字，流程歸 PM」；顧問通常不是專案成員；三欄是說明文字不是稽核範圖 | 要求同時是專案 manager：顧問根本進不來 |
| **D3** | 啟動後資源庫頁讀專案**目前輪次**的任務（wecm 按 round_id，與任務配置匯出同一條查法） | 覆核輪啟動後顧問改的應是新輪；舊輪已進稽核不該再動 | 固定第 1 輪 |
| **D4** | Excel 匯入對不到的列跳過、其餘照寫，預覽列出跳過數 | 與既有任務配置匯入一致；PM 改過任務後整份退回會永遠匯不進去 | 一列對不到整份退回 |
| **D6** | 資源庫頁每條 AO 的「流程編輯器」按鈕拿掉；資源庫階段每條 AO 固定一個範本預設任務，顧問只填三欄；流程一律啟動後 PM 在專案規劃頁改 | 決策者裁「不想讓顧問有這麼大權限」；現況那顆鈕設了也白設（啟動只認第一個方塊）；拿掉後資源庫永遠一 AO 一任務，畫面、Excel、啟動邏輯都不必處理多方塊 | 留著鈕並把啟動改成整張圖都長任務：權限方向相反，且動啟動與覆核輪共用核心 |
| **D7** | 資源庫編輯頁「適用控制項」頁籤版型改成與稽核計畫頁（FR-141 `ApObjectiveTree`＋`ApObjectiveAoForm`）相同：左樹群組 → 控制項 → **AO**（每條 AO 一節點，圓點標任務說明填了沒），右欄一次只顯示一個：點控制項顯示控制項欄位、**底下列這條控制項的 AO 清單可點跳過去**（圓點、編號、文字、任務數與有說明數），點 AO 顯示那條 AO 的全部欄位＋任務說明；上一條／下一條。兩個匯入預覽不動；專案規劃頁另案 | 決策者原話「有的 AO 一多這邊就爆掉了」「比照稽核計畫、稽核作業那樣」；現行頁右欄把一條控制項的全部 AO（DEV 最多 15 條）直向堆，加任務說明後更長 | 右欄上段固定控制項欄位、下段 AO（兩段都長）；三欄（多一欄，與稽核計畫頁不一致） |
| ~~D5~~ | 撤銷 | PM 在專案規劃頁改方塊名稱與任務列名稱的關係是現況既有行為（兩邊各自、不同步），與本案無關 | — |

---

## 現況接入點 {#inventory nav="接入點"}

| 接入點 | 現況 | 本案動作 | 卡 |
|---|---|---|---|
| 建資源庫初始化 AO 流程圖 `app/oscal/service/resource_library_app_service.py:_init_ao_workflows()`（L269） | 每條 AO 一張圖、一個方塊「Evidence upload」，uid 寫進 AO props | 不動；那個方塊就是範本預設任務 | — |
| 專案啟動長任務 `app/flow_control/service/prep_job_generation_service.py:generate()`（L122） | 只認第一個 userTask；`label = f"{cid} / {ao_part_id} 收證據"`（L241）寫死名稱；描述指南不帶 | 名稱改取方塊 name（空則退回原 label）；描述指南取方塊屬性 | 2a |
| 覆核輪長任務 同一支 `reclone_from_props=True` | 同上 | 名稱、描述、指南從上一輪同 AO 任務列抄；配對用 `reverify_inheritance_service` 的 `get_job_pairs` | 2b |
| 覆核輪承襲 `app/flow_control/service/reverify_inheritance_service.py:inherit_from_parent()`（L40） | 抄指派、部門、設備、證據、問卷，不抄三欄 | 配對結果多抄三欄 | 2b |
| 編輯器存檔三向比對 `app/flow_control/service/workflow_xml_sync_service.py:sync()`（L64） | 圖上沒編號的方塊建任務、沒在圖上的任務刪 | 不動；資源庫頁的寫圖不經它（只改屬性不增刪方塊） | — |
| 資源庫一對一 `infra/readmodel/oscal/resource_library_binding_query.py`（`ever_bound`／`has_live_project`） | 已有 | 3a 用它判「讀圖還是讀任務」；要再補一支「資源庫 → 專案 id 與目前輪次 id」 | 3a |
| 資源庫寫入守門 `domain/module_frame/service/module_frame_domain_service.py:verify_module_frame_exists_by_uid(writable=True)`（L79）、`assert_template_not_audit_locked`（L98） | 已有 | 3a 兩支寫入端點照走 | 3a |
| 任務三欄更新 `app/flow_control/service/job_service.py:update_job()`（L331） | 收三欄，守專案 manager | 不動；3a 啟動後寫三欄走 domain service `update_job` 只帶三欄、守門換 D2 | 3a |
| 圖 XML 雙寫 `infra/flow_control/repository/flow_control_job_repo_impl.py:write_template_xml()` | 主表與翻譯列一起寫 | 3a 寫圖用它 | 3a |
| FE 資源庫頁 `src/views/module_frame/ModuleFrameTemplateEditView.vue`（1,954 行） | AO 展開區 L1496～；流程編輯器鈕 L1479～1487 | 新區塊開子元件，原檔只加一行掛載、刪那顆鈕 | 3b |
| FE 任務表單 `src/components/grc/planning/AoTaskForm.vue`（L395～420） | 三格已存在 | 3b 照這三格的樣子刻 | 3b |
| 任務配置匯出匯入 `app/flow_control/service/job_import_service.py`（546 行） | 十欄、一任務一列、AO 欄不合併、匯入不收三欄 | 4b 加兩欄、合併、收三欄；加欄時把表頭常數與列組裝抽到共用 builder，不往裡塞 | 4a、4b |
| 匯出查詢 `infra/readmodel/tasks/job_export_query.py:get_export_data()`（L85） | 回 `job_name`，沒 description／guide | 加兩欄 | 4a |
| 改善計畫匯出 `app/flow_control/service/poam_workbook_export.py:_plan_sheet()`（L131～205） | AO 多列合併、列高平分、灰黃著色 | 合併與著色寫法借用 | 4a |
| 複製資源庫 `app/module_frame/service/resource_library_copy_service.py:_copy_ao_workflows()`（L102） | 連圖 clone | 不動，三欄在圖上自動帶走 | — |

---

## 詳細設計 {#design nav="設計"}

### 資料落點

- **啟動前**：三欄在範本流程圖第一個 userTask 方塊上。`name` 是方塊的 `name` 屬性；描述與指南是 `<camunda:properties>` 底下兩個 `<camunda:property name="task_description" value="…"/>`、`name="task_guide"`。
- **啟動後**：三欄在任務列 `job_executions.name`／`description`／`guide`。
- 兩個落點之間只有一條單向流：啟動時從圖帶進任務列。啟動後改任務列不回寫圖。

### 方塊屬性 helper（2a 建，3a 共用）

新檔 `app/flow_control/service/task_node_props.py`，純函式、不碰 DB：

- `read_first_task(xml) -> {node_id, name, description, guide} | None`：用 `BpmnUtils(xml).get_jobs()` 取第一個 userTask（與 `_first_user_task_id` 同一個「第一個」），從 `properties` 取兩個屬性。
- `write_first_task(xml, name, description, guide) -> xml`：只改第一個 userTask 的 `name` 與那兩個屬性；**不動**方塊 id、線、其他屬性（含 `job_execution_uid`）；屬性值為空就把該屬性拿掉，`<camunda:properties>` 清到空要整段拿掉（空節點會讓 `BpmnUtils` 解析炸，見 `flow_apply_to_tasks_service.py:_strip_uid` 註解）。
- 實作手法照 `flow_apply_to_tasks_service.py` 用 `xmltodict` parse／unparse。

### 專案啟動帶三欄（2a）

`prep_job_generation_service.py:generate()` 專案啟動模式（`reclone_from_props=False`）：clone 完範本後 `read_first_task(cloned.xml)`，`name` 有值就用它當 `label`，否則維持 `f"{cid} / {ao_part_id} 收證據"`；`description`／`guide` 直接寫進 `JobExecutionEntity`。workflow_execution 的 `name` 維持原 label（它是流程實例名稱，不是任務名稱）。

### 覆核輪抄三欄（2b）

`generate()` 覆核輪模式不讀圖（上一輪的圖方塊名稱是上一輪 clone 當下的，PM 之後改的任務名稱不在圖上）。改在 `reverify_inheritance_service.py:inherit_from_parent()` 的 `get_job_pairs` 迴圈裡，每對新舊任務把舊任務的三欄 UPDATE 到新任務（`infra` 層加一支 `clone_task_text_fields(pair)`，與 `clone_assignees` 同形）。

### 資源庫 AO 任務說明 API（3a）

新 `app/module_frame/service/ao_task_guidance_service.py`（`@transaction`，守門見 D2）：

| 端點 | 做什麼 |
|---|---|
| `GET /module-frame/<uid>/control/<control_code>/task-guidance` | 回這條控制項所有 AO 的任務清單：`{source: "template"|"project", round_uid?, aos: [{part_id, tasks: [{key, name, description, guide, status?, assignee_names?, order}]}]}`。`source` 由 `binding_query.ever_bound` 決定；template 模式每 AO 恰一筆，`key = "<part_id>|<node_id>"`；project 模式按目前輪次 wecm 撈該 AO 的 USER 任務、依流程順序（`BpmnUtils.get_job_execution_order`）排 `order`，`key = job_uid` |
| `PUT /module-frame/<uid>/task-guidance` | body `[{key, name, description, guide}]`，批次寫。template 模式：解析 key 找 AO 的圖 uid，`write_first_task` 後走 `write_template_xml` 雙寫；project 模式：走 flow_control domain `update_job` 只帶三欄，**不**經 `job_service.update_job`（那支守專案 manager） |

左樹計數（已填說明／任務數）由同一支 GET 的聚合版 `GET /module-frame/<uid>/task-guidance/summary` 回每條控制項 `{code, filled, total}`，一次 SQL／一次解析，不逐條打。

新 readmodel `infra/readmodel/module_frame/ao_task_guidance_query.py`：綁定資源庫 → 專案 id、目前輪次 id；按輪次撈 AO 任務（與 `job_export_query.py` 同一條 wecm 查法）。

### 資源庫頁區塊（3b）

新子元件 `src/components/grc/module-frame/AoTaskGuidanceSection.vue`，掛在 `ModuleFrameTemplateEditView.vue` AO 展開區「參考程序書」之後，一行 `<AoTaskGuidanceSection :module-frame-uid :control-code :readonly="isVersionView || writeDisabled()" />`。元件自己打 GET、三格 `@change` 累積、頁面儲存時 emit 批次 PUT。版本檢視（`isVersionView`）唯讀顯示三欄文字。

長相照雛形畫面 1：template 一張小卡；project 多張小卡＋上方流程帶，小卡右上角狀態徽章與指派人（唯讀）。刪掉 L1479～1487 的流程編輯器 `<button>` 與 `openBpmnEditor`（D6）。

### Excel 契約（4a、4b）

共用 builder `app/flow_control/service/task_guidance_workbook.py`：

| 欄 | 內容 | 匯入 |
|---|---|---|
| A（隱藏） | 比對鍵：template 模式 `<part_id>|<node_id>`，project 模式 job_uid | 讀 |
| B 控制項群組、C 控制項識別碼、D 控制項名稱、E 評估目標 | 灰底唯讀；同一 AO 多列時 B～E `merge_cells` | 不讀 |
| F 順序 | 流程順序 1..n | 不讀 |
| G 任務名稱、H 描述、I 指南 | 黃底可改 | 讀、只寫這三欄 |
| （專案端加）J 任務類型、K 指派人員、L 參與部門、M 設備 | 既有欄位保留 | 既有邏輯 |

另一頁「填寫說明」（照 `prototype/sample/make_sample_xlsx.py` 的六條）。合併、列高、著色借 `poam_workbook_export.py:_plan_sheet()` 的寫法。

匯入三段：`parse`（回每列的新舊值與變更旗標、跳過清單）→ FE 預覽只標變更格 → `confirm`（只寫有變更且 key 對得到的列；D4 跳過不報錯）。資源庫端端點 `POST /module-frame/<uid>/task-guidance/export`、`.../import/parse`、`.../import/confirm`；守門同 3a。

專案端 `job_import_service.py` 改用共用 builder 產前九欄，`_EXPORT_COLUMNS` 加 `description`／`guide`，`confirm_import` 多把三欄傳進 `update_job`。`job_export_query.py` 回 `description`／`guide`。

### 權限與鎖

- 資源庫端三支寫入（PUT、import/confirm）：`@require_capability("oscal.resource-library.update")` ＋ service 內 `verify_module_frame_exists_by_uid(uid, writable=True)` ＋ `assert_template_not_audit_locked`。
- 資源庫端讀取：資源庫讀權（既有）。
- 專案端匯出匯入：維持既有專案 manager 守門。
- 稽核中（FR-139 鎖）資源庫頁三欄唯讀，與既有欄位一致。

---

## 端到端驗收 {#acceptance nav="驗收"}

1. DEV 建一本新資源庫（或用沒綁專案的既有庫），資源庫頁某控制項展開 AO，看到一張「任務說明」小卡、名稱「Evidence upload」、描述指南空白；AO 右邊沒有流程編輯器鈕。
2. 填三欄存檔、重整還在；另一條 AO 不受影響。左樹該控制項計數變 1/N。
3. 匯出任務說明 Excel，開檔：每 AO 一列、三欄黃底、第一列是剛填的；改另一條 AO 兩格匯回，預覽只標兩格、確認後畫面更新。
4. 用這本資源庫啟動專案：規劃頁該 AO 的任務名稱、描述、指南＝資源庫填的；沒填的 AO 任務名稱仍是「控制碼 / AO 收證據」。
5. PM 在專案規劃頁把一條 AO 流程改成三站；回資源庫頁同一條 AO 看到三張小卡、流程帶三格、狀態與指派人唯讀；改第二張的指南存檔，規劃頁該任務指南同步。
6. 資源庫匯出 Excel：那條 AO 三列、B～E 合併；專案規劃頁任務配置匯出長相一致且多指派、部門、設備欄。
7. 啟動覆核輪：新輪同一條 AO 的任務三欄＝上一輪。
8. 覆核輪送審鎖寫後資源庫頁三欄唯讀。

---

## 拆分表 {#cards nav="拆卡"}

| 卡 | 做什麼 | 碰哪裡 | 依賴 | 驗收 |
|---|---|---|---|---|
| 2a | 方塊屬性 helper＋專案啟動帶三欄 | `task_node_props.py` 新檔；`prep_job_generation_service.py` L229～252 | — | 驗收 4 |
| 2b | 覆核輪抄三欄 | `reverify_inheritance_service.py`、`reverify_clone_query` 加 `clone_task_text_fields` | 2a | 驗收 7 |
| 3a | 任務說明讀寫 API＋summary＋readmodel | `ao_task_guidance_service.py` 新、route、`ao_task_guidance_query.py` 新 | 2a helper | 驗收 1、2、5 的 API 面 |
| 3c | 版型比照稽核計畫頁：左樹到 AO、右欄單一控制項或 AO 表單（D7） | 新 `MfControlTree.vue`、`MfControlForm.vue`、`MfAoForm.vue`（從 `ModuleFrameTemplateEditView.vue` L1173～1560 抽出）；原檔變薄 | — | 左樹點控制項／AO 右欄各只顯示一個；既有欄位與存檔行為不變 |
| 3b | AO 表單底下任務說明子元件、左樹圓點、收流程編輯器入口 | `AoTaskGuidanceSection.vue` 新，掛進 `MfAoForm.vue` | 3a、3c | 驗收 1、2、5、8 |
| 4a | Excel 共用 builder、資源庫端匯出／parse／confirm | `task_guidance_workbook.py` 新、route、`job_export_query.py` 加兩欄 | 3a readmodel | 驗收 3、6 前半 |
| 4b | 專案端任務配置匯出匯入對齊 | `job_import_service.py`（抽共用、不往裡塞） | 4a | 驗收 6 後半 |
| 4c | FE 匯出匯入對話框與預覽 | FE | 3b、4a | 驗收 3 |

---

## 坑與陷阱 {#pitfalls nav="坑"}

| 坑 | 在哪 | 怎麼避 |
|---|---|---|
| 圖的 XML 是可翻譯欄位，只寫主表編輯器看不到 | `prep_job_generation_service.py` L262 註解 | 寫圖一律 `write_template_xml` 雙寫 |
| 資源庫頁寫方塊屬性時動到 `job_execution_uid` 或方塊 id | 三向比對 `workflow_xml_sync_service.py` L94 會判成新任務或查不到 | helper 只改 name 與兩個自家屬性，其餘原樣 |
| 空的 `<camunda:properties/>` 讓解析炸 | `flow_apply_to_tasks_service.py:_strip_uid` | 清到空整段拿掉 |
| 啟動後走 `job_service.update_job` 會擋顧問 | 它守專案 manager | 3a 走 domain `update_job` 只帶三欄，守門換 D2 |
| 資源庫側行為要用資源庫入口驗 | FR-139 T-24 教訓 | 驗收用 `/module-frame/...` 端點，不拿專案端點頂 |
| `ModuleFrameTemplateEditView.vue` 超標 | 1,954 行 | 新區塊必開子元件，原檔只加一行、刪一顆鈕 |
| `job_import_service.py` 超標邊緣 | 546 行 | 4b 把表頭與列組裝抽到共用 builder，淨行數不增 |
| 改 service 後共用 BE 跑舊程式 | 2026-10-15 教訓 | 驗收前先重啟 |
