---
title: "結案報告與一包下載 — 設計定案 (FR-145)"
brand: "Guidant AI · **FR-145** 結案報告"
eyebrow: "FR-145 · 設計定案 v1 · 2026-10-17"
h1: "結案報告與一包下載：設計定案"
subtitle: "稽核報告掛稽核次、階段結案報告與專案總報告自動建立、六章文件版面、報告包含凍結證據與問卷 PDF、定稿與下載日誌"
lede: "稽核報告（Word／PDF／Excel）掛在每個稽核次後面，結案就能下載。「專案調查結果」改造成結案報告：輪次結案仍有不符合時自動建該輪的階段結案報告、專案完成時自動建專案總報告；六章只有「總結與建議」由人寫，其餘從稽核計畫、稽核結果、改善計畫即時算。下載報告包拿 Word、PDF、證據對照表、各稽核次報告與工作表、依控制族群／控制項／評估目標分資料夾的凍結證據（問卷填答轉 PDF）。定稿鎖總結記版號；Word 給顧問自行修改交付，系統只記下載日誌。本文件寫定案規則、為什麼這樣定、踩到會怎樣。"
chips: [
  {text: "D1～D16 定案（2026-10-17）", kind: ok},
  {text: "六張卡；1、2 可先派，3～5 等 FR-143", kind: accent},
  {text: "不動套件；一支主線 migration", kind: plain},
  {text: "陷阱：證據必須取凍結版本、縮範圍的「未稽核」≠「未判定」", kind: crit}
]
footer: "FR-145 · 設計文件 · 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/<套件>/<套件底線名>/`；三個 repo 都在 `feature/FR-139`。

名詞：**輪次**＝`compliance.project_audit_rounds` 一筆；**稽核次**＝`compliance.audit_round_passes` 一筆，各自一組 AP／AR／POA&M；**階段結案報告**＝一輪一份；**專案總報告**＝一專案一份；**定稿**＝鎖總結段、記版號、報告包改正式版；**報告包**＝zip；**證據對照表**＝報告包內的 Excel 索引；**凍結佐證**＝FR-142 結案時 `ar_close_service._freeze` 寫進 `assessment_observations` 的採用佐證，帶 `content-hash` 與 `frozen-at` props。

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

| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-17 | v1：D1～D16 定案；資料模型、事件掛點、讀模型、端點契約、報告包契約、六張卡、端到端驗收 | 討論稿定案 |

---

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

整案一句話：**稽核報告掛在稽核次後面結案即可下載；「專案調查結果」改成結案報告，一輪一份階段報告（輪次結案仍有不符合才建）、專案完成一份總報告，六章只有總結由人寫，其餘系統算；報告包含 Word／PDF、證據對照表、各稽核次報告與工作表、依控制結構分資料夾的凍結證據。**

| 卡 | 做什麼 | 產出 | 完成怎麼判定（決策者親手做什麼、看到什麼） |
|---|---|---|---|
| **1** | 稽核作業清單每列「下載報告」下拉；BE 選檔端點；FR-142 Word 附件後加「改善計畫」一節 | FE 清單欄、新 route、`ar_docx_generator` 加節 | 稽核作業清單已結案列點「下載報告」選 Word，開檔最後一節是改善計畫表（任務、執行人員、到期日、措施）；無缺失稽核次該節印「本次稽核無不符合項目，無改善計畫」；矯正中印「改善計畫尚未送出」；稽核中列印「結案後開放」；Excel 有改善計畫給改善計畫工作表否則給稽核結果工作表 |
| **2** | migration 加欄；輪次結案／專案完成掛「建報告」；清單端點一專案一列＋子列；FE 清單頁重做；選單改名 | `scripts/sql/` 一支、`app/project_summary_report/` 事件與清單、FE `ProjectSummaryReportManage.vue` 重做 | DEV 跑一輪有缺失結案 → 清單該專案展開多一列「第 N 輪結案報告 待定稿」；跑一輪無缺失結案 → 只多「專案總報告 待定稿」；側選單顯示「結案報告」；既有舊列仍在、點開唯讀 |
| **3** | 階段結案報告讀模型（六章＋附件一）、報告頁 FE、存檔／定稿／修訂、Word／PDF、下載日誌 | 新 `closing_report_*` service、docx 範本、FE 報告頁 | 開報告頁六章數字與 DEV 該輪 AP／AR／POA&M 對得上（符合數、不符合清單、改善任務）；寫總結存檔→定稿後第 5 章唯讀、狀態「已定稿 v1」；下載 Word 開檔六章加附件一；側欄下載紀錄多一列；修訂→再定稿變 v2 |
| **4** | 報告包 zip：Word、PDF、證據對照表、各稽核次 Word 與 Excel、證據資料夾（凍結版本）、問卷填答 PDF；定稿版存檔 | `closing_report_bundle_service.py`、`survey_answer_pdf.py` | 下載 zip 解開：資料夾是 族群／控制項／評估目標；對照表每列路徑在 zip 內找得到；問卷佐證是 PDF 且題目答案齊；連結只在對照表；未採用佐證有列無檔；定稿後再下載檔名帶 `_v1` |
| **5** | 專案總報告：七章讀模型（各輪演進、跨多次未改善、最終符合度逐條、各輪總結帶入）、總報告包收各輪子包＋合併對照表 | 沿用卡 3／4 加 `final` 分支 | 專案完成後清單多「專案總報告 待定稿」；開頁第 2 章一列一次稽核、跨多次未改善列出判定軌跡；第 5 章帶入第 1 輪已定稿總結；zip 內有 `第1輪/`、`覆核輪/` 與合併對照表（多「輪次」欄） |
| **6** | FR-143 改善計畫工作表匯出加「列全部評估目標」開關（追加到 FR-143 卡 4 CM-2844，不另開） | `poam_workbook_*` | 匯出後符合與不適用的列也在、改善欄空 |

依賴：1、2 可先派（1 的改善計畫節讀 FR-143 CM-2841 的 POA&M，若 CM-2841 未落地先用「尚未送出」分支）；3 等 2 與 FR-143 CM-2842；4 等 3；5 等 3、4；6 隨 FR-143 CM-2844。

---

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

定案理由與被排除方案見討論稿 D1～D16，此處只列結論供實作對照。

| D | 結論 |
|---|---|
| D1 | 稽核報告掛稽核次；closed 或 remediation 可下載，auditing 印「結案後開放」 |
| D2 | Word＝FR-142 報告＋「改善計畫」節（三態：有／無／尚未送出）；Excel＝有 POA&M 給改善計畫工作表否則稽核結果工作表，一律列全部評估目標；PDF 同 Word |
| D3 | 階段結案報告綁 `audit_round_id` 一輪一份；專案總報告綁 `project_id` 一專案一份 |
| D4 | 輪次結案時最終稽核 not_met > 0 才建階段報告；= 0 專案完成只建總報告；覆核又沒過再開一輪照建 |
| D5 | 系統自動建，不設建立鈕；建立即 `draft` |
| D6 | 狀態 `draft`／`issued` 兩態；清單分段鈕依專案：待撰寫／已完成／尚未結案 |
| D7 | 清單一專案一列，展開子列依時間序，總報告固定最後 |
| D8 | 文件版面；階段六章＋附件一，總報告七章＋附件一；只有總結由人寫、不標撰寫者 |
| D9 | 階段報告定論取該輪最後一次稽核；總報告最終符合度逐評估目標取最後一次判定 |
| D10 | 存檔／定稿／下載（下拉：Word、PDF、zip、報告包內容）三分開；定稿後存檔隱藏、定稿變修訂 |
| D11 | Word 與 PDF 都給；每次下載記稽核事件；系統不介入後續 |
| D12 | 報告包結構與對照表欄位（見「報告包契約」）；問卷轉 PDF；連結只進對照表；證據取凍結版本 |
| D13 | 版本沿用 `project_summary_report_history`；定稿加 `version_no`／`issued`；還原只還總結段；定稿版 zip 存儲存後端 |
| D14 | 總報告定稿不擋各輪未定稿；第 5 章該輪標「尚未定稿」 |
| D15 | 能力點沿用 `grc.summary-report.*`：read／export 給 manager 與 auditor，update（存檔、定稿、修訂）只給 manager；稽核作業下載沿用 FR-142 守門 |
| D16 | Word 套 FR-141／142 同一份 docx 範本；編輯器沿用 TipTap；第 5 章 HTML 走既有 `bleach` 白名單 |

---

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

| 元件 | 現況（2026-10-17 實查） | 本案動作 |
|---|---|---|
| `compliance.project_summary_reports` | `infra/project_summary_report/models/project_summary_report.py`：uid、project_id、name、summary、is_delete | 卡 2 加 `report_type`、`audit_round_id`、`status`、`issued_version`、`issued_at`、`issued_user` |
| `compliance.project_summary_report_history` | `models/project_summary_report_history.py`：report_id、type（U／R）、name、summary 快照 | 卡 3 加 `version_no`、`issued`、`bundle_ref` |
| 端點 | `api/project_summary_report/__init__.py:23-31`：list（POST）、CRUD、export（PDF）、histories、history、history-revert | 卡 2 改 list 回模型；卡 3 加 `issue`／`revise`／`export?format=docx\|pdf`、下載日誌；卡 4 加 `bundle` |
| App service | `app/project_summary_report/service/project_summary_report_service.py`：`_require_participant`（:38）守門、CRUD（:69-176） | 卡 2 加 `create_for_round`／`create_for_project`、清單讀模型；卡 3 新檔 `closing_report_app_service.py`（讀模型）不往既有檔塞 |
| 側選單 | `ui_routes` id 15：`report-manage` `/project-summary-report` 群組 42 sort 70；子頁 74（tab-view）、75（history） | 卡 2 seed migration 改 name／i18n key 為 `closing-report`；路徑不動 |
| FE | `src/views/project-summary-report/`：Manage 329 行、Form 344、TabView 52、History 159、TipTapEditor 323 | 卡 2 重做 Manage；卡 3 新 `ClosingReportView.vue`（文件版面）取代 Form／TabView；History 沿用加定稿標記 |
| 稽核次結案與凍結 | `app/flow_control/service/ar_close_service.py:31` `close()`：守門→`_freeze`（:54）→`mark_closed`→推進站；凍結項寫 `assessment_observations` 經 `_observations.upsert_adopted`，每項 props 帶 `content-hash`、`frozen-at` | 卡 4 讀凍結項：`relevant-evidence[].href` 定位檔案／問卷／連結，`props` 取雜湊 |
| 輪次結案與專案完成 | `pkg/jedi-compliance-audit/app/service/audit_round_app_service.py`：`finalize_audit`（:470，`terminal=True` 才 closed）、`_sync_project_status`（:157，全輪 closed → `ProjectStatus.COMPLETED`，呼叫點 :283／:435／:490） | 卡 2 在主專案 `audit_round_route` 對應 app service 呼叫後掛 hook（不改套件）：輪次轉 closed → `create_for_round`；專案轉 COMPLETED → `create_for_project` |
| 稽核結果報告 | `ar_report_export_service.py`（135 行）組 `ArReportModel`；`ar_docx_generator.py`（192 行）：`generate`（:68）、`_append_record_table`（:85）橫式附件 | 卡 1 在 `_append_record_table` 之後加 `_append_poam_section`；model 加 `poam_rows`、`poam_state` |
| 稽核結果 Excel | `ar_workbook_export_service.py`：`FORMATS=("xlsx","docx","pdf","zip")`，`export(round_uid, fmt, …)` | 卡 1 新 route 依 POA&M 存在選 `poam_workbook_*`（FR-143 CM-2844）或此 |
| 稽核計畫匯出 | `ap_report_export_service.py`、`ap_workbook_export_service.py`，`GET /ap/<uid>/export?format=` | 卡 4 直接呼叫組 zip |
| 稽核作業清單 | FR-142.8 `稽核作業` 表格，歸屬 `project_participants` role in (auditor, manager, reviewer) | 卡 1 FE 加「稽核報告」欄 |
| 問卷填答 | `pkg/jedi-survey/app/service/question_answer_service.py:95` `get_answer_list_by_task_survey_uid`；凍結項 href `urn:guidant:task-survey:<survey_uid>`（`ar_objective_view.py:77`） | 卡 4 新 `survey_answer_pdf.py`：題目＋答案排表經 `render_pdf` |
| PDF 渲染 | `render_pdf`（WeasyPrint，`project_summary_report_route.py:171`）；`SspLibreOfficeConverter`（docx→pdf） | 卡 3 Word 經 LibreOffice 轉 PDF，與 FR-141／142 一致 |
| 稽核事件日誌 | 既有埋點（`reference_audit_event_logging_pattern`） | 卡 3 下載日誌 `closing_report_download` |
| 儲存後端 | 既有 storage 設定（FR-039 系列） | 卡 4 定稿版 zip 存檔 |

---

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

### ① 稽核次下載與改善計畫節（卡 1）

**端點**：`GET /audit-round/<round_uid>/pass/<pass_uid>/report?format=docx|pdf|xlsx`，守門沿用 `audit_round_route.py` 的 AR 匯出。

選檔邏輯（app service 層）：

```
pass.status in (closed, remediation) else 412 GRC_PASS_NOT_CLOSED
poam = poam_app_service.get_submitted(pass)        # FR-143 CM-2842；None＝尚未送出或無缺失
format xlsx:
    poam and poam.submitted → poam_workbook_export(pass, all_objectives=True)   # CM-2844 開關
    else                    → ar_workbook_export(pass)
format docx|pdf:
    model = ar_report_export_service.build(pass)
    model.poam_state = 'has' | 'none' | 'pending'    # none＝最終 not_met==0；pending＝有不符合但 POA&M 未送出
    model.poam_rows  = [(ctrl, task, owners, due, measure)]
    docx = ar_docx_generator.generate(model); pdf 經 SspLibreOfficeConverter
```

**`ar_docx_generator`**：`generate()` 在 `_append_record_table` 後呼叫 `_append_poam_section(doc, model)`：直式新節、標題「改善計畫」、`poam_state` 三態文案、`has` 時五欄表（控制項、改善任務、執行人員、到期日、改善措施），表頭重印沿用 `_repeat_header`。檔已 192 行，新節邏輯放新檔 `ar_docx_poam_section.py` 由 generator 一行呼叫。

**FE**：稽核作業清單加「稽核報告」欄，`closed`／`remediation` 放「下載報告」下拉（Word／PDF／Excel，Excel 項文字依後端回的 `workbook_kind` 顯示「改善計畫工作表」或「稽核結果工作表」）；`auditing` 印「結案後開放」。清單端點回每列加 `report_available: bool`、`workbook_kind: 'poam'|'ar'`。

### ② 資料模型與自動建立（卡 2）

**migration**（主線 `phase=active`，一支）：

```sql
ALTER TABLE compliance.project_summary_reports
  ADD COLUMN report_type varchar(20) NOT NULL DEFAULT 'legacy',   -- stage | final | legacy
  ADD COLUMN audit_round_id integer REFERENCES compliance.project_audit_rounds(id) ON DELETE CASCADE,
  ADD COLUMN status varchar(20) NOT NULL DEFAULT 'draft',          -- draft | issued
  ADD COLUMN issued_version integer NOT NULL DEFAULT 0,
  ADD COLUMN issued_at timestamptz,
  ADD COLUMN issued_user varchar(50);
CREATE UNIQUE INDEX uq_psr_round ON compliance.project_summary_reports(audit_round_id) WHERE audit_round_id IS NOT NULL AND is_delete = 0;
CREATE UNIQUE INDEX uq_psr_final ON compliance.project_summary_reports(project_id) WHERE report_type = 'final' AND is_delete = 0;
ALTER TABLE compliance.project_summary_report_history
  ADD COLUMN version_no integer, ADD COLUMN issued boolean NOT NULL DEFAULT false, ADD COLUMN bundle_ref varchar(500);
-- ui_routes id 15 name report-manage → closing-report（i18n key 跟著改）
```

既有列 `report_type='legacy'`、`status='issued'`（視為已定稿、唯讀）。

**自動建立**：主專案 `app/project/service/`（呼叫 `audit_round_app_service.finalize_audit`／`close_round` 的那層）在套件回傳後，同一 `@transaction` 內呼叫 `closing_report_creator.on_round_closed(round)`：

```
if round.status != closed: return
not_met = ar_objective_app_service.final_not_met_count(round.last_pass)
if not_met > 0: create_for_round(round)            # stage，name=f"第 {round.round_no} 輪結案報告"
project = reload; if project.status == COMPLETED: create_for_project(project)   # final，name="專案總報告"
```

兩個 create 都 idempotent（查唯一索引已有就跳過）。不改套件；套件事件已有 `_sync_project_status` 寫 COMPLETED，主專案只需在呼叫後重讀。

**清單端點**：`POST /project-summary-reports`（既有）回模型改為一專案一列：

```json
{"project_uid","project","framework","manager","consultant","progress":{"round_no","stage_label","at"},
 "bucket":"pending|done|none","draft_count","issued_count","latest_issued_at",
 "reports":[{"uid","report_type","label","covers":[稽核次標籤],"status","not_met_final","issued_at","issued_version"}]}
```

`bucket` 由後端算；分段鈕筆數走 `meta.bucket_counts`。歸屬：`_require_participant` 既有邏輯（manager／auditor），reviewer 不列（D15）。

**FE**：`ProjectSummaryReportManage.vue` 重做為 DataTable 行展開（`expandedRows` 用物件 key）；子列欄照雛形；點子列進卡 3 報告頁。選單 i18n `report-manage` 改「結案報告」。

### ③ 階段結案報告讀模型與頁面（卡 3）

**讀模型** `closing_report_app_service.get_stage_report(report_uid)`，全部即時算：

| 章 | 資料來源與算法 |
|---|---|
| 封面／1 專案概況 | project、round、`audit_round_passes`（nature、seq、audit_start／end）、最後一次 AP 的 `terms-and-conditions`（受稽單位、部門、顧問、稽核人員）；期間＝第一次 AP audit_start ～ 輪次 closed_at |
| 2 整體符合度 | 每個 pass 的 AR findings 統計（met／not_met／na／total，符合率＝met／(total−na)）；族群矩陣＝findings 依 control family 分組 × passes，縮範圍未涵蓋為 null；定論＝最後一個 pass |
| 3 主要缺失與風險 | 最後一個 pass 的 finding state=not-satisfied，join risk characterizations severity（高→中→低排）、observation 稽核發現、remediation(recommendation) 稽核建議 |
| 4 改善計畫摘要 | FR-143 POA&M（CM-2842 讀端點）：poam-item → response(planned) → task（執行人員、到期日、改善措施）；無 POA&M 印「本輪最終稽核無不符合項目」 |
| 5 總結與建議 | `summary` 欄 HTML |
| 6 附件清單 | 卡 4 bundle manifest |
| 附件一 | 逐 part_id：各 pass 的 finding state（null＝未稽核），最終＝最後一個 pass |

**端點**：

| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | `/closing-report/<uid>` | 讀模型（六章＋附件一）＋ status／version／下載紀錄 |
| PUT | `/closing-report/<uid>/summary` | 存檔第 5 章（status=draft 才可；記 history type U） |
| POST | `/closing-report/<uid>/issue` | 定稿：守門 summary 非空→status=issued、issued_version+1、history 記 version_no／issued=true、卡 4 產定稿版 zip 存 `bundle_ref`。manager 守門 |
| POST | `/closing-report/<uid>/revise` | 修訂：status=draft（版號不退）。manager 守門 |
| GET | `/closing-report/<uid>/export?format=docx\|pdf` | 完整報告；draft 時 PDF 加「草稿」浮水印、檔名不帶版號；issued 帶 `_v<N>`；記事件 `closing_report_download` |

**Word**：新 `closing_report_docx_generator.py`，範本 `app/oscal/templates/closing/stage_report_template.docx`（從 FR-142 `ar_report_template.docx` 複製改章節）；docxtpl 渲染六章，附件一用 python-docx 橫式表（沿用 `ar_docx_generator._landscape_section`、`_repeat_header`）。第 5 章 HTML → docx 走既有 `bleach` 白名單後以 `html2docx` 風格轉段落（先查 SSP 匯出是否已有 HTML→docx helper，有就用）。

**FE** `ClosingReportView.vue`：文件版面照雛形 `round-closing-report.html`（封面、章節、表格、右側目次與下載紀錄）；頁首存檔／定稿／下載下拉；TipTap 第 5 章；定稿後唯讀＋修訂。超過 800 行拆子元件（封面、章節表、附件表、側欄）。

### ④ 報告包（卡 4）

`closing_report_bundle_service.build(report) -> BytesIO`：

```
<專案>_<第N輪|專案總報告>_結案報告[_v<N>]/
├─ 00-完整報告.docx、00-完整報告.pdf                 ← 卡 3 export
├─ 01-證據對照表.xlsx                               ← 本卡
├─ 第<seq>次<內部|外部>稽核/                        ← 每個 pass
│   ├─ 稽核計畫.docx                                ← ap_report_export_service
│   ├─ 稽核報告.docx                                ← 卡 1 選檔（含改善計畫節）
│   └─ 改善計畫工作表.xlsx | 稽核結果工作表.xlsx     ← 卡 1 選檔
└─ 證據/<族群代碼 族群名>/<控制項>/<評估目標>/
    ├─ <原檔名>                                     ← upload_files 以凍結 content-hash 對應版本
    └─ 問卷填答_<問卷名>.pdf                         ← survey_answer_pdf
```

**凍結項來源**：`assessment_observations` 該 AR result 的採用項（`_observations.list_adopted(ar_result_id)`，CM-2826 已有讀法），每項 `relevant-evidence[].href`：`/files/<uid>`→檔案、`urn:guidant:task-survey:<uid>`→問卷、其他→連結；`props` 取 `content-hash`、`frozen-at`。檔案以 `content-hash` 對 `upload_files.sha256` 取版本，取不到（檔被刪）在對照表標「檔案已不存在」不中斷。

**證據對照表**欄：稽核次、控制族群、控制項、評估目標、類型（檔案／問卷／連結）、名稱、包內路徑、雜湊／快照識別、採用（是／否）、備註。未採用項來自同 observation 的 `adopted=false` 紀錄，留列不收檔。

**問卷 PDF** `survey_answer_pdf.render(task_survey_uid) -> bytes`：`get_answer_list_by_task_survey_uid` 取題目與答案，HTML 表（問卷名、填答人、填答時間、題號／題目／答案）經 `render_pdf`。凍結時記的是 survey_uid，答案取當下最新一筆 history（問卷填答在稽核後不會再改，FR-142 D10 已鎖）。

**定稿版存檔**：issue 時 build 一次存儲存後端，`history.bundle_ref` 記路徑；歷史頁該筆可重下載。draft 下載即時 build 不存。

**大小**：證據檔總量超過 1 GB 時改背景工作（`background_jobs`）產檔後通知，本案先同步產、超過門檻回 413 提示；門檻進 config。

### ⑤ 專案總報告（卡 5）

同卡 3 讀模型加 `report_type=final` 分支：

| 章 | 算法 |
|---|---|
| 1 概況 | 全部輪次與 passes；最終狀態 |
| 2 各輪演進 | 每輪每 pass 統計一列；跨多次未改善＝同 part_id 連續 ≥2 個 pass not-satisfied，列判定軌跡與最終通過日 |
| 3 最終符合度 | 逐 part_id 取最後一次 finding（任一輪），族群分組 |
| 4 缺失與改善紀錄 | 全部曾 not-satisfied 的 part_id：首次發現 pass、POA&M 任務、覆核結果 |
| 5 各輪總結 | 各 stage 報告 `summary`（issued 才帶，draft 標「尚未定稿」） |
| 6 專案總結與建議 | 本報告 `summary` |
| 7 附件清單、附件一 | 附件一各欄＝全部 passes＋最終 |

總報告包：`第1輪/`、`覆核輪/` 各放該輪 stage 報告包內容（有定稿版用定稿版，否則即時 build）；`01-證據對照表.xlsx` 合併各輪加「輪次」欄；`00-專案總報告.docx／pdf`。

### ⑥ FR-143 Excel 開關（卡 6，追加 CM-2844）

`poam_workbook_export(..., all_objectives: bool = False)`：True 時列全部評估目標，符合／不適用列改善欄空、灰底唯讀；頁面匯出鈕旁勾選「列全部評估目標」預設勾；卡 1 呼叫固定 True。

---

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

1. **稽核次下載**：DEV 一個已結案有缺失的稽核次，稽核作業清單該列「下載報告」→ Word 最後一節「改善計畫」有表；同專案無缺失稽核次該節印「無」；矯正中列可下載、該節印「尚未送出」；稽核中列印「結案後開放」。Excel：有 POA&M 的下載到改善計畫工作表且含符合列、改善欄空；無 POA&M 的是稽核結果工作表。
2. **自動建立**：從 FR-140 流程開一輪，外稽留 2 條不符合結案 → 結案報告清單該專案展開多「第 1 輪結案報告 待定稿」；再開覆核輪全過 → 專案完成，多「專案總報告 待定稿」。另一專案一次全過結案 → 只有「專案總報告」。
3. **報告頁**：開第 1 輪報告，第 2 章各稽核次數字＝DEV AR 統計；族群矩陣第 2 次內稽未涵蓋族群印「未稽核」；第 3 章兩條不符合與風險等級、稽核發現與稽核報告一字不差；第 4 章任務與 FR-143 送出內容一致；附件一列數＝評估目標數。
4. **存檔／定稿**：第 5 章空白按定稿 → 擋；寫字存檔 → 歷史多一筆 U；定稿 → 狀態已定稿 v1、第 5 章唯讀、存檔鈕消失、定稿鈕變修訂、歷史該筆 issued；修訂→改字→定稿 → v2；歷史頁「還原到 v1」→ 第 5 章回 v1 文字、狀態仍 draft。
5. **下載與日誌**：draft 下載 PDF 有「草稿」浮水印、檔名無版號；issued 下載檔名 `_v2`；每次下載側欄「下載紀錄」多一列（人、時間、格式）；DB 稽核事件表多 `closing_report_download`。
6. **報告包**：issued 下載 zip 解開：結構照契約；對照表每列「包內路徑」在 zip 內存在；問卷佐證是 PDF 開得了、題目答案齊；連結類只在對照表；未採用項有列無檔；改一個 DEV 證據檔再下載 → 包內仍是舊版（雜湊相符）。
7. **總報告**：專案完成後開總報告，第 2 章四列（內稽 1、內稽 2、外稽 1、覆核）、跨多次未改善兩條軌跡；第 3 章合計符合 57 不符合 0 不適用 2；第 5 章帶入第 1 輪 v2 總結；zip 內 `第1輪/` 與 `覆核輪/`、合併對照表有「輪次」欄。
8. **權限**：auditor 帳號開報告頁可讀可下載、存檔與定稿 403；reviewer 帳號清單不列；viewer 403。
9. **舊資料**：既有「專案調查結果」列仍在清單（子列標「舊版」）、點開唯讀、無報告包。
10. **FR-142 既有**：`test/` 下 ar 相關測試與 FR-142 e2e 不壞；稽核結果頁「結案」行為不變。

---

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

| 卡 | Notion | 做什麼 | 主要檔 | 依賴 | 規模 |
|---|---|---|---|---|---|
| 1 | CM-2855 | 稽核作業清單「下載報告」＋選檔端點＋Word 改善計畫節 | `api/project/routes/audit_round_route.py`、新 `ar_docx_poam_section.py`、FE 清單元件 | FR-143 CM-2841／2842（POA&M 讀）；未落地先走 pending 分支 | 中 |
| 2 | CM-2856 | migration、自動建立 hook、清單端點、FE 清單重做、選單改名 | `scripts/sql/2026-10-17-closing-report-columns.sql`、`app/project_summary_report/`、`app/project/service/`（hook）、FE Manage | — | 中 |
| 3 | CM-2857 | 階段報告讀模型、端點、Word／PDF、FE 報告頁、下載日誌 | 新 `app/closing_report/`、docx 範本、FE `ClosingReportView.vue` | 2、FR-143 CM-2842 | 大 |
| 4 | CM-2858 | 報告包：zip、證據對照表、證據資料夾、問卷 PDF、定稿版存檔 | `closing_report_bundle_service.py`、`survey_answer_pdf.py` | 3 | 大 |
| 5 | CM-2859 | 專案總報告七章、總報告包 | 卡 3／4 加 final 分支 | 3、4 | 中 |
| 6 | CM-2860 | FR-143 Excel 列全部開關 | `poam_workbook_*` | FR-143 CM-2844 | 小 |

序列組：卡 1 與卡 2 可平行；卡 3 之後序列。卡 1 與卡 3 都動 `ar_docx_generator` 周邊，卡 3 等卡 1 合回。

---

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

- **證據必須取凍結版本**：`assessment_observations` 採用項 props 的 `content-hash` 才是報告包要用的；用 `upload_files` 現在的檔會在結案後被換。對應版本取不到要在對照表標出，不中斷打包。
- **問卷 href 是 `urn:guidant:task-survey:<uid>`**（`ar_objective_view.py:77`），不是檔案路徑；分類型時先判 scheme。
- **縮範圍的 null ≠ 未判定**：第 2 次內稽未涵蓋的評估目標在附件一與族群矩陣印「未稽核」，有 finding 但 state 空才是「未判定」。讀模型兩者分開欄位。
- **改善計畫章節三態**：`has`（POA&M 已送出）、`none`（最終 not_met==0）、`pending`（有不符合但未送出）。判 `none` 看 AR 不看 POA&M。
- **自動建立要 idempotent**：輪次結案事件可能重入（覆核輪 `close_round` 與 `finalize_audit` 都會 `_sync_project_status`），用唯一索引擋重複，不靠流程保證只叫一次。
- **hook 放主專案不放套件**：`_sync_project_status` 在 jedi-compliance-audit；本案在主專案呼叫套件 app service 的那層之後掛，避免動套件發版。
- **既有列 `report_type` 要有預設**：`NOT NULL DEFAULT 'legacy'`，否則 migration 在有資料的庫炸。
- **`ar_docx_generator` 已 192 行、`ProjectSummaryReportManage.vue` 329 行**：新節與新頁一律新檔，不往裡塞（CLAUDE.md 800／60 行規則）。
- **PDF 浮水印**：LibreOffice 轉出的 PDF 加浮水印要在 docx 範本層做（頁首浮水印文字框 + 條件），不是事後蓋 PDF。
- **下載日誌 payload 不含內容**：只記識別、格式、版本（資安 log 規則）。
- **DataTable 行展開**：PrimeVue 3.53 `expandedRows` 用物件 `{[uid]: true}`，不是陣列。
- **報告包大小**：證據總量大時同步產會逾時；門檻進 config，超過回 413，背景產檔列為後續。
- **本卡 2 新增主線 migration 1 支**：回寫時明講「出貨基線待重產」。
