手冊首頁 / 專案與任務 / 專案摘要報表

專案摘要報表(Project Summary Report)

功能群:報表與儀表板|兩類產出與版本歷史機制先讀 功能群總覽

事實基準:2026-07-05 從 FE(views/project-summary-report/)/ BE(api/project_summary_report)code 掃出;表結構取自 db_schema.jsoncompliance schema)

變更紀錄:2026-07-09 FR-047 六項#5 —— 建立報告的專案下拉放寬到「進行中 + 已完成」(原僅已完成;與專案狀態機 B 案強互鎖)

1. 功能描述

專案摘要報表讓使用者為已完成的稽核專案撰寫一份富文本(TipTap)總結報告:列表管理、編輯、匯出 PDF、版本歷史與還原。一個報告綁一個專案(project_id),summary 欄以 HTML 字串存編輯器內容。

主要使用者:需為專案產出對外總結的人(實際 endpoint 僅 @jwt_required(),無角色細分,見 §3)。

角色速覽

角色 一句話
報告作者 撰寫 / 編輯 / 匯出報告(實際 endpoint 僅 jwt,無角色細分,見 §3)

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 報告清單(分頁 / 排序) 表格列出報告:名稱 / 專案 / 建立者 / 時間,皆可排序 列表頁 UC-PSR-01
2 關鍵字搜尋 依報告名稱搜尋 列表頁 §10
3 建立報告 已完成專案 + 報告名 → 進編輯頁 列表頁 dialog UC-PSR-01
4 編輯報告 TipTap 富文本編輯 summary(HTML) 編輯頁 UC-PSR-01
5 儲存 更新報告 + 寫版本歷史(type=U) 編輯頁 UC-PSR-01
6 匯出 PDF WeasyPrint 由 HTML 產 PDF 下載 編輯頁 UC-PSR-01
7 刪除報告 軟刪除(is_delete=1) 列表頁操作 §6.2
8 版本歷史 列出所有版本(type U/R) 歷史頁側欄 UC-PSR-02
9 載入歷史版本 唯讀檢視某版內容 歷史頁 UC-PSR-02
10 還原版本 套回某版 + 寫還原歷史(type=R) 歷史頁 UC-PSR-02

UC(§2)展開 建立→編輯→儲存→匯出(UC-PSR-01)與歷史還原(UC-PSR-02)核心路徑。TipTap 工具列細節見 §5。

2. Use Case

角色速覽

角色 一句話
報告作者 撰寫 / 編輯 / 匯出報告(實際 endpoint 僅 jwt,無角色細分,見 §3)

流程圖視覺慣例:菱形 = 判斷、橘底 = 例外、紅框 = 錯誤、綠框 = 成功終點、虛線 = 可選路徑。

UC-PSR-01 建立 → 編輯 → 儲存 → 匯出

項目 內容
角色 報告作者
前置條件 至少一個 status=completed 的專案(建立 dialog 只列已完成專案)
產出 / 後置條件 建立報告列;每次儲存寫一筆 type=U 歷史;匯出下載 {uid}_{timestamp}.pdf
uc_psr_01 UC-PSR-01 建立 → 編輯 → 儲存 → 匯出 PDF start 列表頁 [新增報告] pick 選已完成專案(GRC_PROJECTS_LIST filters.status=completed)+ 報告名 start->pick create POST /project-summary-report {project_uid, name, summary} 找不到專案 → 404 pick->create edit 轉編輯頁:TipTap 編輯 summary(HTML) create->edit save PUT /project-summary-report/{uid} 更新主檔 + 寫歷史 type=U edit->save export 匯出 PDF GET /project-summary-report/export/{uid} WeasyPrint:Jinja2 HTML → PDF(summary|safe) edit->export done 下載 {uid}_{timestamp}.pdf export->done
UC-PSR-01:選已完成專案建立 → TipTap 編輯 → 儲存寫 U 歷史 → WeasyPrint 匯出 PDF

UC-PSR-02 版本歷史 / 還原

項目 內容
角色 報告作者
前置條件 報告已有 ≥1 次儲存(產生歷史)
產出 / 後置條件 載入某版唯讀檢視;還原套回主檔並寫一筆 type=R 歷史(版本鏈只增不減)
uc_psr_02 UC-PSR-02 版本歷史檢視 / 還原 open 編輯頁 → 歷史 list POST /project-summary-report/histories {report_uid} → 版本清單(type U/R) open->list load 載入某版 GET /project-summary-report/history/{uid} → 唯讀填回 name/summary list->load q 還原此版?(非最新版) load->q revert POST /project-summary-report/history-revert {report_uid, history_uid} 套回主檔 + 寫新歷史 type=R q->revert done 轉編輯頁、內容為還原版 q->done 否(只檢視) revert->done
UC-PSR-02:歷史清單 → 載入檢視 → 還原(寫 R 歷史)

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
讀清單 / 讀報告 選單能力 @jwt_required();無角色細分 api/project_summary_report/routes/project_summary_report_route.py
建立 / 編輯 / 刪除 / 還原 選單能力 @jwt_required()無 manager / 角色守門 同上 + ..._history_route.py
匯出 PDF 選單能力 @jwt_required() ProjectSummaryReportExportReportRoute

⚠️ 已知缺口(維護時注意):所有寫入 / 匯出 endpoint 只有 JWT、無角色守門,也未檢查使用者對該專案是否有份——任何登入者可對任一專案建 / 改 / 刪報告。收進 §12。

4. 狀態機與前置條件

本頁無多階段狀態機(報告是簡單 CRUD)。gate:

條件 效果 出處
建立可選 in_progress / completed 專案 建立 dialog 的專案下拉 filters.status=['in_progress','completed'](原僅 completed,2026-07-09 六項#5 放寬) ProjectSummaryReportManage.vue:49
儲存(PUT) 更新主檔 + 自動寫 type=U 歷史 app/project_summary_report/service/project_summary_report_service.py(update 內寫歷史)
還原 套回快照 + 寫 type=R 歷史 ..._history_service.py::revert_data_from_cruise_project_report_history
刪除 軟刪除 is_delete=1 delete_project_summary_report

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝;截圖另有專責 session)。預計補到 assets/img/: ① 報告列表 psr-list.png ② TipTap 編輯頁(工具列 + 富文本) psr-editor.png ③ 歷史頁(唯讀 + 版本側欄) psr-history.png ④ 匯出 PDF 樣張 psr-export-pdf.png

版面骨架與區塊職責:

報告三態:列表頁 → 編輯頁(TipTap) → 歷史頁(唯讀 + 還原) 頁面切換:ProjectSummaryReportManage → …Form → …History 開啟:§6 API 規格(分頁清單 / 建立) 報告列表:關鍵字搜尋 + 分頁表格(名稱/專案/建立者/時間,皆可排序)+ 刪除操作 POST /project-summary-reports(pager+sort+filters)・ DELETE /project-summary-report/{uid} 審計欄位 enrich:created_user_name/updated_user_name(nickname) → §6 API 規格(清單/刪除完整欄位,點我開啟) 開啟:UC-PSR-01 建立 → 編輯 → 儲存 → 匯出 建立報告 dialog 只列 status=completed 專案 + 報告名稱 POST /project-summary-report → UC-PSR-01(點我開啟) 開啟:UC-PSR-01 建立 → 編輯 → 儲存 → 匯出 編輯頁:TipTap 富文本編輯 summary + 儲存 + 匯出 PDF PUT /project-summary-report/{uid}(自動寫 U 歷史) GET /project-summary-report/export/{uid}(WeasyPrint PDF) → UC-PSR-01(點我開啟) TipTap 工具列 H1-3/粗斜底刪/清單/勾選/引用/程式碼/圖片(base64)/表格/undo-redo 開啟:UC-PSR-02 版本歷史 / 還原 歷史頁:版本側欄(type U/R)+ 唯讀 TipTap 檢視 + 還原鈕 POST /project-summary-report/histories(清單) POST /project-summary-report/history-revert(寫 R 歷史) → UC-PSR-02(版本鏈只增不減,點我開啟) 開啟:專案清單 spec §6(建立 dialog 的專案下拉來源) 建立 dialog 專案下拉 → project-list.md §6(filters.status=completed,點我開啟) Socket 協作結構在但停用(initSocket() 已註解) 多人同時編輯同報告會後存覆蓋前存(無鎖);線上使用者頭像元件在但無實際連線 彩色=主要互動/明細區塊,整塊可點擊跳轉 §6/UC|灰=本頁結構|虛線框=dialog/停用中結構 API 僅列代表性 2~3 條,完整規格見 §6;三態切換與版本歷史機制見 §4/§10
專案摘要報表 wireframe:列表/建立 dialog/TipTap 編輯/歷史三態,彩色區塊可點跳 §6 API 規格與 UC-PSR-01/02

狀態呈現:列表用 useTableQuery 後端分頁 / 排序 / 篩選(依 userInfo.uid + route 存查詢條件);編輯頁用 TipTapEditor(H1-3 / 粗斜底刪 / 清單 / 勾選 / 引用 / 程式碼 / 圖片(base64 拖放貼上)/ 表格 / undo-redo);歷史頁 TipTap editable=falsesocket 目前停用——ProjectSummaryReportForm.vue 有多人協作 socket 結構(join/edit/update)但 initSocket() 已註解(§11);線上使用者頭像元件在但無實際連線。

6. API 規格

Envelope:成功 {"status": true, "data": …, "meta": …}、失敗 {"status": false, "data": {}}(HTTP 4xx)。

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
清單 POST /project-summary-reports 分頁報告清單(pager + sort + filters) §6.2
建立 POST /project-summary-report 建立報告(綁專案) §6.2
GET /project-summary-report/{uid} 單一報告 §6.2
更新 PUT /project-summary-report/{uid} 更新 name / summary(寫 U 歷史) §6.2
刪除 DELETE /project-summary-report/{uid} 軟刪除 §6.2
匯出 GET /project-summary-report/export/{uid} WeasyPrint PDF 下載 §6.2
歷史 POST /project-summary-report/histories 版本清單(by report_uid) §6.2
歷史 GET /project-summary-report/history/{uid} 單一歷史快照 §6.2
歷史 POST /project-summary-report/history-revert 還原到某版(寫 R 歷史) §6.2
輔助 POST /grc/projects/listfilters.status=completed 建立 dialog 的專案下拉 project-list §6.2

6.2 核心 endpoint(欄位對照 serializer / service,出處標段末)

[POST] /project-summary-reports — 分頁清單

Request:

{
  "pager": {},
  "sort": {},
  "filters": {}
}

Response data(陣列,每項 = ProjectSummaryReportResponse):

欄位 型別 說明
uid string 報告唯一識別碼
name string 報告名稱
summary string 報告摘要(TipTap HTML)
project_uid string 綁定專案 UID
project_name string 專案名稱
created_user(_name) string 建立者 login_name(_name = enrich 後的 nickname)
created_at string 建立時間
updated_user(_name) string 更新者 login_name(_name = enrich 後的 nickname)
updated_at string 更新時間

meta 帶分頁。審計欄位 enrich _name(login_name → nickname)。出處:api/project_summary_report/routes/project_summary_report_route.py::ProjectSummaryReportsRouteapp/project_summary_report/service/project_summary_report_service.py::get_project_summary_reports_and_pager

[POST] /project-summary-report ・ [PUT] /project-summary-report/{uid} — 建立 / 更新

建立 Request:

{
  "project_uid": "string", // 
  "name": "string",        // 
  "summary": "string"      // 選填
}

找不到專案回 404。更新 Request:

{
  "name": "string",        // 
  "summary": "string"      // 選填
}

更新後自動寫 type=U 歷史。皆記 EventCode.CRUISE_PROJECT_UPDATED 稽核。出處:project_summary_report_route.py::ProjectSummaryReportRoute..._service.py::create/update_project_summary_report

[GET] /project-summary-report/export/{uid} — 匯出 PDF

以 Jinja2 template(project_summary_report_template.html)渲染 report.name + report.summary|safe(原樣輸出 TipTap HTML)→ WeasyPrint write_pdf() → 附件下載 {uid}_{timestamp}.pdfapplication/pdf)。找不到報告回 404。出處:project_summary_report_route.py::ProjectSummaryReportExportReportRoute

歷史 endpoint

POST /project-summary-report/histories:body {report_uid} → 回歷史清單。回傳(陣列,每項):

欄位 型別 說明
uid string 歷史記錄 UID
name string 變更當下的報告名稱快照
summary string 變更當下的摘要快照(TipTap HTML)
type string 操作類型:U=更新 / R=還原
created_user string 變更者(login_name)
created_at string 變更時間

GET .../history/{uid}:單一快照。POST .../history-revert:body {report_uid, history_uid} → 套回主檔 + 寫 type=R 歷史。出處:api/project_summary_report/routes/project_summary_report_history_route.pyapp/project_summary_report/service/project_summary_report_history_service.py

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/project-summary-report/ProjectSummaryReportManage.vue 報告列表 + 建立 dialog + 刪除
src/views/project-summary-report/ProjectSummaryReportTabView.vue Tab 容器(包編輯 Form)
src/views/project-summary-report/ProjectSummaryReportForm.vue 編輯頁(TipTap + 儲存 + 匯出 + 歷史入口;socket 停用)
src/views/project-summary-report/ProjectSummaryReportHistory.vue 歷史頁(唯讀 + 版本側欄 + 還原)
src/views/project-summary-report/TipTapEditor.vue 富文本編輯器元件(無 API,純 v-model)
src/composables/useTableQuery.js 分頁 / 排序 / 篩選
src/config/router/index.js:202-226 route project-summary-report(+ -tab-view / -history
src/config/api/api.js endpoint 常數(PROJECT_SUMMARY_REPORT(S) / ..._EXPORT / ..._HISTORIES …)

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
清單 / CRUD api/project_summary_report/routes/project_summary_report_route.py app/project_summary_report/service/project_summary_report_service.py@transaction;update 內寫 U 歷史) infra/project_summary_report repo + jedi_project Project FK
匯出 同檔 ProjectSummaryReportExportReportRoute (route 內組 Jinja2 + WeasyPrint) compliance.project_summary_reports
歷史 / 還原 ..._history_route.py ..._history_service.py(revert 寫 R 歷史) compliance.project_summary_report_histories

DDD 提醒:app service @transaction 包住報告更新 + 歷史寫入(原子)。匯出的 Jinja2 + WeasyPrint 組裝在 route 層(HTML→PDF)——屬邊界輸出,非 DB 操作。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
compliance.project_summary_reports 讀 / 報告主檔(summary 存 HTML);刪除改 is_delete=1 §9.3
compliance.project_summary_report_histories 版本快照(type U=更新 / R=還原) §9.3
compliance.projects 建立時挑 status=completed 專案 project-list §9

9.2 ER 圖(本頁讀寫範圍)

psr_er 專案摘要報表讀寫範圍(compliance schema) proj compliance.projects 建立時挑 status=completed psr compliance.project_summary_reports 讀 / 寫 name · summary(HTML) · is_delete proj->psr project_id (FK CASCADE) hist compliance.project_summary_report_histories type U=更新 / R=還原 name/summary 快照 psr->hist report_id (FK CASCADE) 存檔/還原時寫快照
專案摘要報表讀寫:projects(唯讀)→ reports(主檔)→ histories(快照)

9.3 核心表欄位(取自 DEV DB dump,含 DB comment)

compliance.project_summary_reports — 報告主檔(10 欄)

欄位 型別 說明
uid varchar(50) 唯一識別碼
project_id int 專案 ID(→ compliance.projects.id,FK CASCADE)
name text 報告名稱
summary text|null 報告摘要(TipTap 輸出的 HTML 字串)
is_delete int 軟刪除標記(1 已刪 / 0 未刪)
created_user / updated_user varchar(50) 建立 / 更新者(login_name)

compliance.project_summary_report_histories — 版本歷史(8 欄)

欄位 型別 說明
uid varchar(50) 歷史記錄 ID
report_id int 報告 ID(→ project_summary_reports.id,FK CASCADE)
type varchar(10) 操作類型:U=更新 / R=還原
name / summary text|null 變更當下的名稱 / 摘要快照
created_user / created_at varchar(50) / timestamp 變更者 / 時間

_trans 翻譯表——summary HTML 直接存主表,不走翻譯機制。

10. 頁面邏輯與資料對應

載入時序:列表頁 POST /project-summary-reports(enrich 暱稱);編輯頁 GET /project-summary-report/{uid} 填 name + summary 進 TipTap;歷史頁 POST /.../histories 列版本。

關鍵欄位對應(API ↔︎ 畫面):

畫面元素 FE state API 欄位
報告名輸入 report.name name
TipTap 內容 report.summary(HTML) summary
建立時選專案 下拉(completed) project_uid
專案名欄 project_name
版本徽章 history.type(U/R)

儲存流程:編輯頁「儲存」= PUT(寫 U 歷史);「匯出」= blob 下載 PDF;歷史「還原」確認後 POST history-revert(寫 R 歷史)→ 轉編輯頁。

錯誤對應:本模組無專屬 error code,走通用 404(報告 / 專案不存在)→ toast。

11. 背景行為與外部依賴

類型 內容
匯出 WeasyPrint(Python)由 Jinja2 HTML template 產 PDF;summary|safe 原樣輸出 TipTap HTML
通知 建立 / 更新記 EventCode.CRUISE_PROJECT_UPDATED 稽核事件(非使用者通知)
Socket 結構在但停用ProjectSummaryReportForm.vue 有多人協作 socket(SOCKET_PROJECT_SURVEY_REPORT,join/edit/update),initSocket() 已註解——目前無即時協作
jedi-* 套件 jedi-common@transaction / auth context / 分頁 spec)、jedi-project(Project FK model)
系統參數 無 feature flag

12. 邊界情況與已知坑

  1. 寫入 / 匯出無角色守門、不檢查專案歸屬(安全缺口):所有 endpoint 僅 @jwt_required(),未驗證使用者對該專案是否有份——任何登入者可對任一專案建 / 改 / 刪 / 匯出報告。修法應加專案參與或角色檢查。
  2. summary 存原始 HTML:TipTap 輸出 HTML 存 text 欄,匯出 |safe 原樣輸出、圖片以 base64 內嵌——注意 XSS / HTML 淨化與 PDF 體積(大量 base64 圖片會脹)。
  3. 建立可選「進行中 + 已完成」專案(2026-07-09 FR-047 六項#5):原本建立 dialog 專案下拉 filters.status='completed'、只列已完成;已放寬為 filters.status=['in_progress','completed'](BE list_projects 對 status 陣列走 IN (...)、零 BE 改動)。⚠️ 與專案狀態機 B 案強互鎖——B 案落地 + 回填後 DEV 才有 in_progress 專案,放寬方見效。
  4. socket 停用是刻意的:協作程式碼在但註解,別誤以為有即時同步;多人同時編輯同報告會後存覆蓋前存(無鎖)。
  5. 版本鏈只增不減:還原不刪後續版本,而是寫 type=R 新歷史;type 僅 U / R 兩值。
  6. 軟刪除:刪除 = is_delete=1,不真刪;清單以 is_delete=0 過濾。誤刪需 DB 還原。
  7. 無專屬 error code:本模組不定義 error code,錯誤走通用 404 / 400;新增業務錯誤時需依命名規則補(見 CLAUDE.md error code 規範)。

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)。匯出需 WeasyPrint 相依(系統字型 / cairo)
  • 測試帳號:dev 環境登入帳號密碼見 .env / 部署文件
  • 導航路徑:側邊選單「報表」→ 專案摘要報表(/project-summary-report
  • 前置資料:需至少一個 status=completed 的專案才能建報告
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);以 摘要報表 / summary report 搜尋 feature 檔
  • 相關文件:AI 儀表板見 AI 儀表板、GRC 儀表板見 專案儀表板;DB / API 全量見 GAI-SD-02/03