專案摘要報表(Project Summary Report)
功能群:報表與儀表板|兩類產出與版本歷史機制先讀 功能群總覽
事實基準:2026-07-05 從 FE(
views/project-summary-report/)/ BE(api/project_summary_report)code 掃出;表結構取自db_schema.json(complianceschema)變更紀錄: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-02 版本歷史 / 還原
| 項目 | 內容 |
|---|---|
| 角色 | 報告作者 |
| 前置條件 | 報告已有 ≥1 次儲存(產生歷史) |
| 產出 / 後置條件 | 載入某版唯讀檢視;還原套回主檔並寫一筆 type=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
版面骨架與區塊職責:
狀態呈現:列表用 useTableQuery 後端分頁 / 排序 /
篩選(依 userInfo.uid + route 存查詢條件);編輯頁用
TipTapEditor(H1-3 / 粗斜底刪 / 清單 / 勾選 / 引用 / 程式碼
/ 圖片(base64 拖放貼上)/ 表格 / undo-redo);歷史頁 TipTap
editable=false。socket
目前停用——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/list(filters.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::ProjectSummaryReportsRoute、app/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}.pdf(application/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.py、app/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 圖(本頁讀寫範圍)
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翻譯表——summaryHTML 直接存主表,不走翻譯機制。
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. 邊界情況與已知坑
- 寫入 /
匯出無角色守門、不檢查專案歸屬(安全缺口):所有 endpoint 僅
@jwt_required(),未驗證使用者對該專案是否有份——任何登入者可對任一專案建 / 改 / 刪 / 匯出報告。修法應加專案參與或角色檢查。 summary存原始 HTML:TipTap 輸出 HTML 存text欄,匯出|safe原樣輸出、圖片以 base64 內嵌——注意 XSS / HTML 淨化與 PDF 體積(大量 base64 圖片會脹)。- 建立可選「進行中 + 已完成」專案(2026-07-09 FR-047
六項#5):原本建立 dialog 專案下拉
filters.status='completed'、只列已完成;已放寬為filters.status=['in_progress','completed'](BElist_projects對 status 陣列走IN (...)、零 BE 改動)。⚠️ 與專案狀態機 B 案強互鎖——B 案落地 + 回填後 DEV 才有in_progress專案,放寬方見效。 - socket 停用是刻意的:協作程式碼在但註解,別誤以為有即時同步;多人同時編輯同報告會後存覆蓋前存(無鎖)。
- 版本鏈只增不減:還原不刪後續版本,而是寫
type=R新歷史;type僅 U / R 兩值。 - 軟刪除:刪除 =
is_delete=1,不真刪;清單以is_delete=0過濾。誤刪需 DB 還原。 - 無專屬 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的專案才能建報告 - E2E:
compliance-manager-test/repo(Cucumber + Playwright);以摘要報表/summary report搜尋 feature 檔 - 相關文件:AI 儀表板見 AI 儀表板、GRC 儀表板見 專案儀表板;DB / API 全量見 GAI-SD-02/03