專案基本資訊與參與人員(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) |
3. 權限矩陣
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進頁 / 讀 | 專案可見者 | 可見性(admin / owner / 任一層參與者,否則
GRC_403053) |
infra/grc/repository/grc_project_repo_impl.py:318-331(get_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-only(
GRC_403002),此 Tab2/3 卻無守門,同頁兩個寫入面守門強度不一致——改權限兩端一起補。
4. 狀態機與前置條件
本頁無獨立狀態機。基本資訊 /
參與人員編輯不受輪次階段限制(與 SSP 編輯的 planning
gate 不同)——只要是專案可見者、FE canEdit 為真即可存。
5. UI 設計
版面骨架(Tab2 基本資訊 + Tab3 參與人員兩節,共用 PUT,含安全 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(ProjectUpdateRequestSchema,Meta.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 /
viewer(project.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_service(enforce_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. 邊界情況與已知坑
- ⚠️ 基本資訊 / 參與人員 PUT
無角色守門(安全相關):
PUT /grc/project/{uid}(Tab2/3 存檔)BE 只做可見性檢查、無角色守門——任一參與者(含 viewer)繞過 FEcanEdit直接打 API 就能改名 / 覆寫參與人員。update_project的 participant 同步刻意帶enforce_role=False,註解說「授權由 update_project 自身把關」但實際上update_project沒把關——屬未補的 follow-up 缺口。對比 Tab5 雲端 verify-and-repair 是嚴格 manager-only(GRC_403002),同頁兩個寫入面守門強度不一致;改權限兩端一起補。(自原專案設定頁 §3 遷入) - participants 全量覆寫:送
participantslist 即整組取代(送[]清空);null / 省略才是不動。FE 若只想改一個人的角色也要送完整 list,漏人會被移除(owner 除外)。 - owner 保護只在 BE、FE 無提示:owner(專案負責人)永遠保留,即使 FE 送的 list 不含 owner;但 ParticipantPicker 沒有 owner 的 disabled / lock UI,使用者可能誤以為移除成功(實際被 BE 忽略)。
- Drive 改名 best-effort:改名不阻塞存檔;無 Drive 整合或改名失敗都只 log 不報錯——使用者不會知道 Drive 那端沒改到。
- date 欄位時區:start_date / end_date BE 存 timestamp、序列化留意 date-only 顯示時區偏移(見 _overview 時區慣例)。