手冊首頁 / 專案與任務 / 專案基本資訊與參與人員

專案基本資訊與參與人員(Basic Info & Participants)

功能群:專案管理|上層見 專案規劃頁(本頁=其 Tab2「基本資訊」與 Tab3「參與人員」,共用同一支 PUT /grc/project

事實基準:2026-07-06 從 FE / BE code 掃出;表結構對 db_schema.json、endpoint 對 routes.json、欄位對 marshmallow serializer + service 逐條核|變更紀錄:2026-07-06 FR-047 從 project-planning.md 拆出(原專案設定頁內容)

1. 功能描述

本頁涵蓋專案規劃頁的 Tab2「基本資訊」(專案名稱 / 描述 / 起訖日期 / 狀態 / 負責人)與 Tab3「參與人員」(專案層角色指派)。兩個 tab 是不同畫面、但共用同一支 PUT /grc/project:基本資訊改欄位、參與人員送 participants list 做全量覆寫。原「專案設定」頁已整併進規劃頁這兩個 tab。專案改名會 best-effort 觸發 Drive 資料夾同步改名。

⚠️ 安全提醒:此 endpoint BE 只做可見性檢查、無角色守門——任一參與者(含 viewer)繞過 FE 都能改基本資訊與參與人員(見 §12 坑 1)。

主要使用者:專案 manager(FE canEdit 判定;BE 未強制)。

角色速覽(完整定義見 _overview §3):

角色 一句話
manager 專案經理 — FE 允許編輯基本資訊 / 參與人員的角色
參與者 reviewer / auditor / viewer — FE 唯讀(但 BE 無守門,見 §12)

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

# 功能 說明 位置 詳述
1 專案基本資訊編輯 名稱 / 描述 / 起訖日期(改名觸發 Drive 資料夾同步改名) Tab2 UC-PP-09
2 專案參與人員 專案層角色(manager / reviewer / auditor / viewer)指派,全量覆寫、owner 保護 Tab3 UC-PP-09

UC(§2)展開 UC-PP-09,涵蓋兩 tab 共用 endpoint、participants 全量覆寫 + owner 保護、無角色守門三個關鍵分支。

2. Use Case

角色速覽(同 §1)。流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-PP-09 編輯專案基本資訊與參與人員(Tab2 / Tab3)

項目 內容
角色 專案 manager(FE canEdit 判定);⚠️ BE PUT /grc/project 無角色守門,任一參與者繞過 FE 直接打 API 都寫得動(見 §3 / §12 坑 1)
前置條件 已登入;為專案可見者(非參與者 GET 回 GRC_403053
產出 / 後置條件 compliance.projects 基本資訊(name / description / 起訖 / status)+ project_participants(送 list = 全量覆寫、owner 受保護不被移除)更新;名稱變更 → best-effort Drive 資料夾改名(§11)
uc_ps_01 start edit 編輯基本資訊 / 參與人員 (FE canEdit:僅專案 manager 顯示可編) start->edit save PUT /grc/project/{uid} (欄位選填;participants 送 list = 全量覆寫) edit->save vis BE 可見性:呼叫者看得到本專案? (owner 或任一 participant) save->vis none update_project 回 None → 封包 {status:false} vis->none norole ⚠️ BE 無角色守門:任一參與者 (含 viewer)都能寫入 (enforce_role=False) vis->norole owner participants 全量覆寫: owner 受保護不被刪除 norole->owner rename 名稱變更 → best-effort Drive 資料夾改名佇列 owner->rename done 存檔成功(基本資訊 → 跳回總覽頁) rename->done
UC-PP-09 流程:編輯基本資訊/參與人員→PUT /grc/project→BE 只做可見性檢查(⚠️ 無角色守門)→participants 全量覆寫、owner 保護→名稱變更觸發 Drive 佇列改名

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
進頁 / 讀 專案可見者 可見性(admin / owner / 任一層參與者,否則 GRC_403053 infra/grc/repository/grc_project_repo_impl.py:318-331get_project_by_uid
編輯基本資訊 / 參與人員(PUT /grc/project canEdit:本人 role=manager ⚠️ 只做可見性檢查、無角色守門——任一參與者(含 viewer)繞過 FE 都寫得動(participant 同步刻意帶 enforce_role=False app/grc/service/project_service.py::update_project(無 role 檢查,:336-478

⚠️ 安全 finding(§12 坑 1):同頁 Tab5 雲端 verify-and-repair 是嚴格 manager-onlyGRC_403002),此 Tab2/3 卻無守門,同頁兩個寫入面守門強度不一致——改權限兩端一起補。

4. 狀態機與前置條件

本頁無獨立狀態機。基本資訊 / 參與人員編輯不受輪次階段限制(與 SSP 編輯的 planning gate 不同)——只要是專案可見者、FE canEdit 為真即可存。

5. UI 設計

版面骨架(Tab2 基本資訊 + Tab3 參與人員兩節,共用 PUT,含安全 finding):

專案規劃頁 Tab[2] 基本資訊 + Tab[3] 參與人員(同一支 PUT /grc/project) Tab[2] 基本資訊(ProjectBasicInfoPanel) 名稱 InputText*(FE 必填) 描述 Textarea 起始日 Calendar* ・ 結束日 Calendar*(end ≥ start) 框架 / 範本 — 唯讀文字(不可改) PUT /grc/project/{uid} 名稱變更 → best-effort 觸發 Drive 資料夾改名 全部欄位選填(allow_none);未送不動 Tab[3] 參與人員(ProjectParticipantsPanel + ParticipantPicker) 使用者搜尋 + 已選清單(Avatar + nickname) 每列 role Dropdown:manager / reviewer /  auditor / viewer(severity 上色) 移除鈕(owner 無特殊 UI 保護) PUT /grc/project/{uid}(participants 全量覆寫) 送 list = 整組取代([] 清空);owner 受保護不被移除 FE 前置:participants 非空 且 至少一位 manager ⚠️ 安全 finding(§12 坑 1) BE 只做可見性檢查、無角色守門——任一參與者 (含 viewer)繞過 FE 直打 API 即可改名 / 覆寫參與人員 participant 同步刻意帶 enforce_role=False(授權缺口) 非參與者可見性 → GRC_403053 兩節共用 PUT /grc/project payload name / description / start_date / end_date / status / owner_uid / participants[{user_uid, role}] role 4 值;status: pending/in_progress/completed/suspended/archived 藍=基本資訊 | 粉=參與人員(兩節同一支 PUT)|紅虛線框=無角色守門安全 finding(坑 1)
專案基本資訊與參與人員版面骨架:Tab2 基本資訊表單+Tab3 參與人員清單+共用 PUT /grc/project payload+無角色守門安全 finding

📸 截圖待補(亮色模式):Tab2 基本資訊表單 planning-basic-info.png、Tab3 參與人員清單 planning-participants.png

Tab2 基本資訊ProjectBasicInfoPanel.vue):

欄位 型別 必填 備註
name InputText 是(FE) 變更觸發 Drive 改名
description Textarea
start_date / end_date Calendar(yy-mm-dd 是(FE) end min-date = start
framework / module_frame 唯讀文字 不可改

Tab3 參與人員ProjectParticipantsPanel.vue + ParticipantPicker.vue):使用者搜尋 + 已選清單(User Avatar + nickname);每列 role Dropdown(4 值,severity 上色:manager=success / reviewer=warning / auditor=danger / viewer=info);移除鈕。owner 無特殊 UI 保護(保護全在 BE)。FE 前置驗證:participants 非空且至少一位 manager。

狀態呈現:無 socket,存檔後重查;儲存成功 / 失敗 toast。

6. API 規格

Envelope:成功 {"status": true, "data": …}、失敗 {"error_code": "…", "msg": "…"}

6.1 總清單

分類 Method + Path 說明 完整規格
載入 GET /grc/project/{uid} 專案基本資料 + 參與人員(進頁 + 權限判定) GAI-SD-02
編輯 PUT /grc/project/{uid} Tab2/3 共用(基本資訊 + participants 全量覆寫) §6.2

6.2 核心 endpoint

[PUT] /grc/project/{project_uid}

Tab2 / Tab3 共用。全部欄位選填allow_none,未送不動);participants 送 list 即全量覆寫。Request(ProjectUpdateRequestSchemaMeta.unknown=EXCLUDE):

{
  "name": "專案名稱",
  "description": "…",
  "start_date": "2026-01-01",
  "end_date": "2026-12-31",
  "status": "in_progress",
  "owner_uid": "<user uid>",
  "participants": [
    { "user_uid": "<user uid>", "role": "manager" }
  ]
}
欄位 型別 必填 說明
name string 專案名稱;變更 → best-effort 觸發 Drive 資料夾改名(§11)
description string 專案描述
start_date / end_date date(%Y-%m-%d) 專案期程(FE 端兩欄皆必填、end ≥ start)
status string pending / in_progress / completed / suspended / archived_STATUS_VALUES
owner_uid string 變更負責人
participants array|null [{user_uid(必填), role(預設 viewer)}];送 list = 全量覆寫[] 清空);null/省略 = 不動;owner 受保護不被移除

role 允許值:manager / reviewer / auditor / viewerproject.py:81)。出處:serializer api/grc/serializers/project.py:88-117(participant item :79-81)、service app/grc/service/project_service.py:336-478(owner 保護 :429-469)、route api/grc/routes/project_route.py:168-212(Drive 改名 :203-209)。

Response data:專案 DTO(含 enrich 後的 participants[])。錯誤碼:GRC_404001(專案不存在)/ GRC_403053(非參與者可見性)/ GRC_409001(參與者已存在)/ GRC_404008(參與者不存在)。

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

檔案 角色
src/components/grc/project/ProjectBasicInfoPanel.vue Tab2 基本資訊表單
src/components/grc/project/ProjectParticipantsPanel.vue Tab3 參與人員面板(前置驗證:非空 + 至少一 manager)
src/components/grc/ParticipantPicker.vue 參與人員 picker(role dropdown + 搜尋 + 移除;無 owner UI 保護)

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

鏈路 Route App Service 底層
專案更新 api/grc/routes/project_route.py::ProjectDetailResource.put app/grc/service/project_service.py::update_project(participant 全量覆寫、owner 保護、改名回報 Drive rename) jedi_project domain + project_participant_serviceenforce_role=False

⚠️ DDD 提醒:update_project 只做可見性檢查、無角色守門(別比照為權限典範);participant 同步刻意 enforce_role=False,註解稱「授權由 update_project 把關」但實際沒把關(§12 坑 1)。

9. DB

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

讀寫 說明 詳述
compliance.projects 讀 / 專案主檔(name / description / 起訖 / status) §9.3
compliance.project_participants 讀 / (全量覆寫) 專案層參與者角色 §9.3
compliance.project_extensions owner_id(owner 保護判定來源) GAI-SD-03

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

compliance.projects — 專案主檔

欄位 型別 說明
uid uuid 對外識別碼
name / description varchar(255) / text 標題 / 描述
start_date / end_date timestamp 專案起訖
status varchar(50) 專案狀態(_STATUS_VALUES
org_unit_id / tenant_id integer 租戶 / 組織(RLS)

compliance.project_participants — 專案參與者

欄位 型別 說明
project_id int → compliance.projects.id
user_id int 參與者
role varchar(50) manager / reviewer / auditor / viewer(DB comment)

PK (project_id, user_id);全量覆寫時 owner(project_extensions.owner_id 對應的 user)永遠保留,即使 FE 送的 list 不含 owner。

10. 頁面邏輯與資料對應

畫面元素 FE state API 欄位
基本資訊表單 ProjectBasicInfoPanel form name / description / start_date / end_date
參與人員清單 ParticipantPicker selected participants: [{user_uid, role}]
role 徽章 picker row severity role 4 值(manager=success…)

儲存流程:Tab2 送 PUT(僅基本資訊欄);Tab3 送 PUT(participants 全量覆寫)→ 成功後重查。owner 保護:service 比對現有 vs 新清單,p_uid != owner_user_uid 才允許刪除(project_service.py:464-469)。錯誤對應GRC_403053(非參與者)/ GRC_404001(專案不存在)→ toast。

11. 背景行為與外部依賴

類型 內容
socket (存檔後重查)
Drive 同步 專案改名 → try_enqueue_rename_folder(best-effort,無 Drive 整合則靜默跳過;失敗只 log 不擋存檔)
owner 保護 全量覆寫時 owner 不被移除(BE 邏輯,FE 無 UI 提示)
jedi-* jedi-project(專案 domain)、jedi-auth(JWT / user)

12. 邊界情況與已知坑

  1. ⚠️ 基本資訊 / 參與人員 PUT 無角色守門(安全相關)PUT /grc/project/{uid}(Tab2/3 存檔)BE 只做可見性檢查、無角色守門——任一參與者(含 viewer)繞過 FE canEdit 直接打 API 就能改名 / 覆寫參與人員。update_project 的 participant 同步刻意帶 enforce_role=False,註解說「授權由 update_project 自身把關」但實際上 update_project 沒把關——屬未補的 follow-up 缺口。對比 Tab5 雲端 verify-and-repair 是嚴格 manager-onlyGRC_403002),同頁兩個寫入面守門強度不一致;改權限兩端一起補。(自原專案設定頁 §3 遷入)
  2. participants 全量覆寫:送 participants list 即整組取代(送 [] 清空);null / 省略才是不動。FE 若只想改一個人的角色也要送完整 list,漏人會被移除(owner 除外)。
  3. owner 保護只在 BE、FE 無提示:owner(專案負責人)永遠保留,即使 FE 送的 list 不含 owner;但 ParticipantPicker 沒有 owner 的 disabled / lock UI,使用者可能誤以為移除成功(實際被 BE 忽略)。
  4. Drive 改名 best-effort:改名不阻塞存檔;無 Drive 整合或改名失敗都只 log 不報錯——使用者不會知道 Drive 那端沒改到。
  5. date 欄位時區:start_date / end_date BE 存 timestamp、序列化留意 date-only 顯示時區偏移(見 _overview 時區慣例)。

13. 開發與驗證

  • 跑起來:起 BE + FE;登入見 .env / 部署文件測試帳號
  • 導航:專案規劃頁 → Tab2「基本資訊」/ Tab3「參與人員」
  • 前置資料:一個可見專案;測 participants 全量覆寫(漏人被移除)、owner 保護(送不含 owner 的 list 仍保留)、改名觸發 Drive(需 tenant 有 CONNECTED Drive integration)
  • 相關:雲端整合(嚴格 manager-only 對照)見 專案雲端整合;專案清單的 PUT 同 endpoint 見 專案清單 §6.3