# GRC 專案 API 規格

> **Blueprint prefix**：`/api/1.0/grc`
> **File**：`api/grc/routes/project_route.py`
> **Auth**：JWT Required（Header：`Authorization: Bearer <token>`）

---

## API 列表

| Method | URL | 說明 |
|--------|-----|------|
| `GET` | `/api/1.0/grc/projects/menu` | 專案選單（無分頁） |
| `POST` | `/api/1.0/grc/projects/list` | 專案列表（分頁 + 篩選） |
| `GET` | `/api/1.0/grc/project/<uid>` | 單筆專案詳細（含稽核系統、設備） |
| `PUT` | `/api/1.0/grc/project/<uid>` | 更新專案設定 |
| `DELETE` | `/api/1.0/grc/project/<uid>` | 刪除專案（軟刪除，僅 admin / owner） |
| `POST` | `/api/1.0/grc/project/<project_uid>/control-groups/list` | 控制群組列表（分頁） |
| `GET` | `/api/1.0/grc/project/<project_uid>/control-group/<group_uid>` | 單筆控制群組詳細 |
| `POST` | `/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/controls/list` | 控制項列表（分頁） |
| `GET` | `/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/control/<control_uid>` | 單筆控制項詳細 |
| `POST` | `/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/control/<control_uid>/assessment-objects/list` | AO 列表（分頁） |
| `POST` | `/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/control/<control_uid>/assessment-object/<ao_uid>/jobs/list` | AO 下的 Job 列表（分頁） |
| `GET` | `/api/1.0/grc/project/<project_uid>/job/<job_uid>` | 取得單筆 Job |
| `PUT` | `/api/1.0/grc/project/<project_uid>/job/<job_uid>` | 更新 Job（基本資料 + 指派人 / 部門 / 設備 / 問卷） |
| `DELETE` | `/api/1.0/grc/project/<project_uid>/job/<job_uid>` | 刪除 Job（硬刪除） |
| `POST` | `/api/1.0/grc/project/<project_uid>/assessment-object/<ao_uid>/jobs` | 新增 Job |
| `POST` | `/api/1.0/grc/jobs/my/list` | 取得目前登入使用者的所有 GRC 任務 |
| `GET` | `/api/1.0/grc/project/<project_uid>/task-setup/tree` | Task Setup 樹狀結構 |

---

## 1. GET `/api/1.0/grc/projects/menu`

> **Route class**：`ProjectMenuResource`
> **用途**：取得精簡專案列表，供前端下拉選單或導覽使用。

### Request

| 位置 | 欄位 | 必填 | 說明 |
|------|------|------|------|
| Query | `search` | 否 | 名稱關鍵字模糊搜尋 |

```
GET /api/1.0/grc/projects/menu?search=CMMC
```

### Response `200`

```json
{
  "status": true,
  "data": [
    { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "2026 CMMC 2.0 稽核", "status": "in_progress" },
    { "id": "660e9511-f30c-52e5-b827-557766551111", "name": "ISO 27001 評估", "status": "pending" }
  ]
}
```

### Response 欄位說明

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 專案 UID |
| `name` | string | 專案名稱 |
| `status` | string | 專案狀態（見 Status Enum） |

### 可見性規則

- 管理員：回傳所有未軟刪除專案
- 一般使用者：僅回傳自己為 owner 或 participant 的專案

---

## 2. POST `/api/1.0/grc/projects/list`

> **Route class**：`ProjectListResource`
> **用途**：取得分頁專案列表，支援排序、篩選，含 OSCAL AP 即時計算欄位。

### Request Body

```json
{
  "pager": { "page": 1, "page_size": 20, "with_total": true },
  "sort":  [{ "field": "name", "order": "asc" }],
  "filters": {
    "search": "CMMC",
    "status": "in_progress"
  }
}
```

#### `filters` 欄位說明

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `search` | string \| null | 否 | 專案名稱關鍵字（ilike） |
| `status` | string \| null | 否 | 篩選狀態（見 Status Enum） |

#### 可排序欄位（`sort[].field`）

| 值 | 對應欄位 |
|----|----------|
| `name` | `compliance.projects.name` |
| `start_date` | `compliance.projects.start_date` |
| `end_date` | `compliance.projects.end_date` |
| `status` | `compliance.projects.status` |

### Response `200`

```json
{
  "status": true,
  "data": {
    "meta": {
      "paging": true,
      "page": 1,
      "page_size": 20,
      "total": 3,
      "total_pages": 1,
      "has_next": false,
      "has_prev": false
    },
    "data": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "2026 CMMC 2.0 Level 2 稽核",
        "description": "DoD 供應鏈合規年度評估",
        "status": "in_progress",
        "completion_rate": 42,
        "control_group_count": 14,
        "total_controls": 110,
        "total_assessment_objects": 110,
        "start_date": "2026-01-01",
        "end_date": "2026-12-31",
        "framework": {
          "id": "fw-uid-0001",
          "name": "CMMC 2.0",
          "short_name": "CMMC"
        },
        "compliance_resource": {
          "uid": "ap-uid-0001",
          "name": "2026 CMMC AP"
        },
        "owner": {
          "id": "u-uid-0001",
          "name": "Alice Chen",
          "avatar_url": null
        },
        "participants": [
          {
            "id": "u-uid-0002",
            "name": "Bob Wang",
            "is_admin": false,
            "role": null
          }
        ]
      }
    ]
  }
}
```

---

## 3. GET `/api/1.0/grc/project/<uid>`

> **Route class**：`ProjectDetailResource`
> **用途**：依 UID 取得單筆專案完整資料，包含稽核系統（`audit_systems`）與設備（`devices`）。

### Request

無 Request Body，UID 從 URL path 取得。

```
GET /api/1.0/grc/project/550e8400-e29b-41d4-a716-446655440000
```

### Response `200`

```json
{
  "status": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "2026 CMMC 2.0 Level 2 稽核",
    "description": "DoD 供應鏈合規年度評估",
    "status": "in_progress",
    "completion_rate": 42,
    "control_group_count": 14,
    "total_controls": 110,
    "total_assessment_objects": 110,
    "start_date": "2026-01-01",
    "end_date": "2026-12-31",
    "framework": {
      "id": "fw-uid-0001",
      "name": "CMMC 2.0",
      "short_name": "CMMC"
    },
    "compliance_resource": {
      "uid": "ap-uid-0001",
      "name": "2026 CMMC AP"
    },
    "owner": {
      "id": "u-uid-0001",
      "name": "Alice Chen",
      "avatar_url": null
    },
    "participants": [
      {
        "id": "u-uid-0002",
        "name": "Bob Wang",
        "is_admin": false,
        "role": null
      }
    ],
    "audit_systems": [
      {
        "id": "a1b2c3d4-0001-0000-0000-000000000001",
        "name": "核心業務系統",
        "abbreviation": "CBS"
      },
      {
        "id": "b2c3d4e5-0002-0000-0000-000000000002",
        "name": "人資管理系統",
        "abbreviation": "HRM"
      }
    ],
    "devices": [
      {
        "id": "dev-uid-0001",
        "name": "SSO Gateway",
        "device_type": "hardware"
      },
      {
        "id": "dev-uid-0002",
        "name": "API Server",
        "device_type": "server"
      }
    ]
  }
}
```

### Response 欄位說明

#### 頂層欄位

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 專案 UID |
| `name` | string | 專案名稱 |
| `description` | string \| null | 專案描述 |
| `status` | string | 專案狀態（見 Status Enum） |
| `completion_rate` | integer | 完成率 0–100（OSCAL AP tasks 完成比例即時計算） |
| `control_group_count` | integer | AP 下的 control group 數量（即時計算） |
| `total_controls` | integer | AP 下的 control 總數（即時計算） |
| `total_assessment_objects` | integer | AP 下的 assessment task 總數（即時計算） |
| `start_date` | string `YYYY-MM-DD` \| null | 專案起始日期 |
| `end_date` | string `YYYY-MM-DD` \| null | 專案結束日期 |

#### `framework`

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | `OscalFramework.uid` |
| `name` | string | 框架全名（如 `CMMC 2.0`） |
| `short_name` | string | 框架代碼（如 `CMMC`） |

> 來源：`ModuleFrame.oscal_framework_version_uid` → `OscalFrameworkVersion` → `OscalFramework`
> 若專案無對應 module frame，回傳 `null`。

#### `compliance_resource`

| 欄位 | 型別 | 說明 |
|------|------|------|
| `uid` | string | `OscalAssessmentPlan.uid` |
| `name` | string | AP 名稱 |

> 來源：`compliance.project_assessment_plan_mapping` → `oscal.assessment_plans`

#### `owner`

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | `users.uid` |
| `name` | string | 使用者顯示名稱（`nickname`） |
| `avatar_url` | string \| null | 頭像 URL（目前固定 `null`） |

> 來源：`compliance.project_extensions.owner_id` → `public.users`

#### `participants[]`

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 使用者 UID |
| `name` | string | 使用者顯示名稱 |
| `is_admin` | boolean | 是否為管理員 |
| `role` | string \| null | 角色名稱 |

> 來源：`compliance.project_participants` JOIN `public.users`

#### `audit_systems[]`（稽核系統）

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 資訊系統 UID（`compliance.information_systems.uid`） |
| `name` | string | 系統全名 |
| `abbreviation` | string \| null | 系統縮寫 |

> 來源：`compliance.project_information_systems` → `compliance.information_systems`
> 為空陣列時表示此專案未關聯任何稽核系統。

#### `devices[]`（設備）

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | 設備 UID |
| `name` | string | 設備名稱 |
| `device_type` | string \| null | 設備類型（如 `hardware`、`server`、`software`） |

> 來源：`compliance.project_device_mapping` → `devices`
> 為空陣列時表示此專案未關聯任何設備。

---

### 錯誤情境

#### `200` — 找不到（回傳 status false）

```json
{
  "status": false,
  "data": {}
}
```

> 找不到專案，或使用者無可見權限時回傳此格式（非 404）。

---

## 4. PUT `/api/1.0/grc/project/<uid>`

> **Route class**：`ProjectDetailResource.put`
> **用途**：更新 GRC 專案設定。所有欄位均為選填；`audit_systems` / `participants` / `devices` 提供時**整組替換**，不提供（`null` / 未傳）時保持不動。

### Request Body

```json
{
  "name": "Updated Project Name",
  "description": "更新說明",
  "start_date": "2026-01-01",
  "end_date": "2026-12-31",
  "status": "in_progress",
  "owner_uid": "u-uid-0001",
  "audit_systems": [
    { "information_system_uid": "a1b2c3d4-0001-0000-0000-000000000001" },
    { "information_system_uid": "b2c3d4e5-0002-0000-0000-000000000002" }
  ],
  "participants": [
    { "user_uid": "u-uid-0002", "is_admin": false },
    { "user_uid": "u-uid-0003", "is_admin": true }
  ],
  "devices": [
    { "device_uid": "dev-uid-0001" }
  ]
}
```

#### 欄位說明

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `name` | string \| null | 否 | 專案名稱，有值才更新 |
| `description` | string \| null | 否 | 專案描述，有值才更新 |
| `start_date` | string `YYYY-MM-DD` \| null | 否 | 起始日期，有值才更新 |
| `end_date` | string `YYYY-MM-DD` \| null | 否 | 結束日期，有值才更新 |
| `status` | string \| null | 否 | 狀態（見 Status Enum），有值才更新 |
| `owner_uid` | string \| null | 否 | 新 owner 的使用者 UID，有值才更新 |
| `audit_systems` | array \| null | 否 | 稽核系統清單，提供時**全量替換**（`[]` 代表清空） |
| `participants` | array \| null | 否 | 參與者清單，提供時**全量替換**；owner 不可被移除 |
| `devices` | array \| null | 否 | 設備清單，提供時**全量替換**（`[]` 代表清空） |

#### 最簡更新（僅改名稱）

```json
{ "name": "New Project Name" }
```

#### 清空所有設備

```json
{ "devices": [] }
```

### Response `200`

回傳更新後的專案完整資料，格式與 `GET /api/1.0/grc/project/<uid>` 相同（含 `audit_systems`、`devices`、`participants`）。

```json
{
  "status": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Updated Project Name",
    "...": "..."
  }
}
```

### 參與者同步規則

```
new_participants = 傳入的 participants 清單
current_participants = 目前 DB 中的參與者

新增：new_participants 中不在 current 的 → add_project_participant()
移除：current 中不在 new_participants 的 → delete_project_participant()
保護：owner（project_extensions.owner_id）即使不在 new_participants，也不會被移除
```

> **注意**：owner 若想變更，需透過 `owner_uid` 欄位指定，不是從 participants 中判斷。

### 後端執行流程

```
PUT /api/1.0/grc/project/<uid>
│
├─ 可見性檢查（同 GET，非 admin 只能更新自己可見的專案）
├─ Step 1  更新基本資訊（compliance.projects）
│           name / description / start_date / end_date / status
├─ Step 2  更新 owner（compliance.project_extensions.owner_id）
│           owner_uid → users.id via UserDomainService
├─ Step 3  稽核系統替換（compliance.project_information_systems）
│           DELETE 全部舊的 → INSERT 新的（依 information_system_uid 查詢 id）
├─ Step 4  參與者同步（compliance.project_participants）
│           新增缺少的 → 刪除多餘的（owner 保護）
│           add_project_participant 同步更新 oscal_metadatas SSP parties
│           delete_project_participant 同步更新 oscal_metadatas SSP parties
├─ Step 5  設備替換（compliance.project_device_mapping）
│           新增缺少的 → ProjectDeviceMappingService.add()
│           刪除多餘的 → ProjectDeviceMappingService.delete()
│           同步更新 oscal.system_security_plan_system_implementations
└─ 回傳更新後的完整專案資料（同 GET）
```

### 錯誤情境

#### `200 status false` — 找不到或無權限

```json
{ "status": false, "data": {} }
```

---

## 5. DELETE `/api/1.0/grc/project/<uid>`

> **Route class**：`ProjectDetailResource.delete`
> **用途**：軟刪除 GRC 專案（設定 `project_extensions.deleted_at`）。刪除後專案立即從所有列表消失，但 OSCAL AP 資料完整保留。

### Request

無 Request Body，UID 從 URL path 取得。

```
DELETE /api/1.0/grc/project/550e8400-e29b-41d4-a716-446655440000
```

### 權限規則

| 身份 | 可刪除 |
|------|--------|
| `is_admin = true` | 所有專案 |
| `is_admin = false` AND `owner` | 自己擁有的專案 |
| `is_admin = false` AND 非 owner | ❌ 403 |

> 查詢時 bypass 可見性限制（admin 可刪除任何專案，owner 可刪除自己的即使不在參與者清單）。

### Response `200` — 成功

```json
{
  "status": true,
  "data": {}
}
```

### 錯誤情境

| HTTP | 狀況 | Body |
|------|------|------|
| `404` | 專案不存在 | `{"status": false, "data": {}, "msg": "Not found"}` |
| `403` | 非 admin 且非 owner | `{"status": false, "data": {}, "msg": "Forbidden"}` |

### 後端執行流程

```
DELETE /api/1.0/grc/project/<uid>
│
├─ get_project(uid, is_admin=True)   ← bypass 可見性
│   找不到 → NotFound (404)
├─ not is_admin AND entity.owner_id != user_id → ForbiddenError (403)
└─ soft_delete_project(uid)
    └─ UPDATE project_extensions SET deleted_at = now()
        WHERE project_id = (SELECT id FROM projects WHERE uid = ?)
        AND deleted_at IS NULL
```

---

## 6. POST `/api/1.0/grc/project/<project_uid>/control-groups/list`

> **Route class**：`ProjectControlGroupListResource`
> **用途**：取得專案下的控制群組列表（分頁），含狀態分布與 URL 參數 `project_id`（為 project_uid，保持相容）。

### Request Body

```json
{
  "pager": { "page": 1, "page_size": 25, "with_total": true },
  "sort": [{ "field": "order_no", "order": "asc" }],
  "filters": {
    "keyword": "Access",
    "status": "in_progress"
  }
}
```

### Response `200`

```json
{
  "status": true,
  "data": {
    "meta": { "paging": true, "page": 1, "page_size": 25, "total": 14 },
    "data": [
      {
        "id": "group-uid-0001",
        "name": "Access Control",
        "code": "AC",
        "description": "存取控制相關要求",
        "order_no": 1,
        "status": "in_progress",
        "total_controls": 8,
        "completed_controls": 3,
        "completion_rate": 37,
        "status_breakdown": {
          "not_started": 5,
          "in_progress": 3,
          "completed": 0
        }
      }
    ]
  }
}
```

---

## 7. GET `/api/1.0/grc/project/<project_uid>/control-group/<group_uid>`

> **Route class**：`ProjectControlGroupDetailResource`
> **用途**：取得單筆控制群組完整資料。

### Response `200`

同上方列表單筆欄位，加上 `participant[]`（群組成員）。

---

## 8. POST `.../control-group/<group_uid>/controls/list`

> **完整 URL**：`/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/controls/list`
> **Route class**：`ProjectControlListResource`
> **用途**：取得控制群組下的控制項列表（分頁）。

### Request Body

```json
{
  "pager": { "page": 1, "page_size": 25, "with_total": true },
  "sort": [],
  "filters": { "keyword": null, "status": null }
}
```

### Response `200`

```json
{
  "status": true,
  "data": {
    "meta": { "paging": true, "page": 1, "page_size": 25, "total": 8 },
    "data": [
      {
        "id": "ctrl-uid-0001",
        "workflow_execution_id": 1024,
        "control_id": "AC.1.001",
        "name": "Limit system access to authorized users",
        "description": "...",
        "status": "in_progress",
        "total_ao": 3,
        "completed_ao": 1,
        "completion_rate": 33
      }
    ]
  }
}
```

> `workflow_execution_id`：此控制項對應的 workflow execution ID，更新 Job assignees 時須作為 `control_id` 傳入。

---

## 9. GET `.../control/<control_uid>`

> **完整 URL**：`/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/control/<control_uid>`
> **Route class**：`ProjectControlDetailResource`
> **用途**：取得單筆控制項完整資料。

---

## 10. POST `.../assessment-objects/list`

> **完整 URL**：`/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/control/<control_uid>/assessment-objects/list`
> **Route class**：`ProjectAOListResource`
> **用途**：取得控制項下的 Assessment Object 列表（分頁）。

### Response 欄位說明

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | `OscalAssessmentPlanTask.uid` |
| `code` | string | AO 代碼（如 `CMMC.AC.1.001.E1`） |
| `name` | string | AO 名稱 |
| `description` | string \| null | AO 描述 |
| `status` | string | `pending` / `in_progress` / `completed` |

---

## 11. POST `.../jobs/list`

> **完整 URL**：`/api/1.0/grc/project/<project_uid>/control-group/<group_uid>/control/<control_uid>/assessment-object/<ao_uid>/jobs/list`
> **Route class**：`ProjectJobListResource`
> **用途**：取得指定 Assessment Object 下的 Job 列表（分頁）。
>
> **注意**：URL 中的 `project_uid`、`group_uid`、`control_uid` 僅作為路徑參數存在，實際查詢只使用 `ao_uid`。

### Request Body

```json
{
  "pager": { "page": 1, "page_size": 100, "with_total": true },
  "sort": []
}
```

### Response `200`

```json
{
  "status": true,
  "data": {
    "meta": { "paging": true, "page": 1, "page_size": 100, "total": 2 },
    "data": [
      {
        "id": "job-uid-0001",
        "name": "Upload MFA Configuration Evidence",
        "description": "Verify that MFA is implemented for all privileged accounts.",
        "guide": "Upload screenshots showing MFA is enabled on all administrator accounts.",
        "task_type": "general",
        "status": "not_started",
        "template_job_id": "UserTask_3a9f1c2b",
        "assignees": [
          { "id": "u-uid-0001", "name": "Alice Chen", "avatar": "AC" }
        ],
        "departments": [
          { "id": "dept-uid-0001", "name": "IT Security" }
        ],
        "devices": [
          { "id": "dev-uid-0001", "name": "SSO Gateway", "device_type": "hardware" }
        ],
        "questionnaires": []
      }
    ]
  }
}
```

### Response 欄位說明

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | `job_executions.uid` |
| `name` | string | Job 名稱 |
| `description` | string \| null | Job 描述 |
| `guide` | string \| null | 操作指引（儲存於 `job_executions.guide`） |
| `task_type` | string | `general` / `questionnaire` |
| `status` | string | `not_started` / `in_progress` / `completed`（由 JobExecution.status enum 轉換） |
| `template_job_id` | string \| null | `job_executions.template_job_id`（BPMN 節點 ID），搭配 `workflow_execution_uid` 呼叫完成/退回任務 API |
| `assignees[].id` | string | `users.uid` |
| `assignees[].name` | string | 使用者顯示名稱 |
| `assignees[].avatar` | string \| null | 名字縮寫（如 `"AC"`） |
| `departments[].id` | string | `org_units.uid` |
| `departments[].name` | string | 部門名稱 |
| `devices[].id` | string | `devices.uid` |
| `devices[].name` | string | 設備名稱 |
| `devices[].device_type` | string \| null | 設備類型 |
| `questionnaires[].id` | string | `surveys.uid` |
| `questionnaires[].name` | string | 問卷名稱 |
| `questionnaires[].question_count` | integer | 題目數量 |

### 資料來源

| 資料 | 資料表 |
|------|--------|
| Jobs | `job_executions` WHERE `workflow_execution_id` = (由 `ao_uid` 查 `assessment_plan_task_workflow_execution_mapping`)，且 `type = 'USER'` |
| Assignees | `compliance.task_assignees` JOIN `users` |
| Departments | `compliance.job_execution_org_units` JOIN `org_units` |
| Devices | `compliance.job_execution_devices` JOIN `devices` |
| Questionnaires | `compliance.job_execution_surveys` JOIN `surveys` |

---

## 12. PUT `/api/1.0/grc/project/<project_uid>/job/<job_uid>`

> **Route class**：`ProjectJobDetailResource`
> **用途**：更新 GRC Job 基本資訊、指派人、部門、設備、問卷（陣列欄位為**全量替換**）。

### Request Body

```json
{
  "name": "Upload MFA Evidence",
  "description": "Verify that MFA is implemented.",
  "guide": "Upload screenshots showing MFA is enabled on all administrator accounts.",
  "task_type": "general",
  "control_uid": "wf-ctrl-uid-0001",
  "assignee_uids": ["u-uid-0001", "u-uid-0002"],
  "department_uids": ["dept-uid-0001"],
  "device_uids": ["dev-uid-0001"],
  "survey_uids": ["survey-uid-0001"]
}
```

#### 欄位說明

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `name` | string \| null | 否 | Job 名稱，有值才更新 |
| `description` | string \| null | 否 | Job 描述，有值才更新 |
| `guide` | string \| null | 否 | 操作指引，有值才更新 |
| `task_type` | `"general"` \| `"questionnaire"` \| null | 否 | 任務類型，有值才更新 |
| `control_uid` | string \| null | 否 | 控制項層級的 `workflow_execution.uid`；**可省略**，系統先嘗試 uid 查詢 id，失敗時再從 `job → workflow_execution.parent_id` 自動推算；兩者皆失敗才略過 assignee 更新 |
| `assignee_uids` | string[] \| null | 否 | 指派人 UID 清單；提供時**全量替換**（`[]` = 清空，不需 `control_id`），`null` = 不動 |
| `department_uids` | string[] \| null | 否 | 部門 UID 清單；提供時**全量替換** |
| `device_uids` | string[] \| null | 否 | 設備 UID 清單；提供時**全量替換** |
| `survey_uids` | string[] \| null | 否 | 問卷 UID 清單；提供時**全量替換** |

> **`control_id` 取得方式**：呼叫 `POST .../controls/list` 時，回應中每筆控制項包含 `workflow_execution_id` 欄位，即為此處的 `control_id`。

#### 替換語意（陣列欄位共用）

| 傳入值 | 行為 |
|--------|------|
| `null`（未傳） | **略過**，保持現有關聯不動 |
| `[]`（空陣列） | **清空**所有現有關聯 |
| `["uid1", "uid2"]` | 刪除全部舊關聯，寫入新關聯 |

> **`assignee_uids` 特別說明**：`control_id` 可省略，系統自動從 `job → workflow_execution.parent_id` 推算控制項層級 ID。僅在系統無法自動推算時才會略過 assignee 更新。

#### 最簡更新（僅改名稱）

```json
{ "name": "New Job Name" }
```

#### 清空所有指派人

```json
{ "assignee_uids": [], "control_id": 1024 }
```

### Response `200`

```json
{
  "status": true,
  "data": {
    "id": "job-uid-0001",
    "name": "Upload MFA Evidence",
    "description": "Verify that MFA is implemented.",
    "guide": "Upload screenshots showing MFA is enabled on all administrator accounts.",
    "task_type": "general",
    "status": "not_started",
    "assignees": [
      { "id": "u-uid-0001", "name": "Alice Chen", "avatar": "AC" },
      { "id": "u-uid-0002", "name": "Bob Wang", "avatar": "BW" }
    ],
    "departments": [
      { "id": "dept-uid-0001", "name": "IT Security" }
    ],
    "devices": [
      { "id": "dev-uid-0001", "name": "SSO Gateway", "device_type": "hardware" }
    ],
    "questionnaires": [
      { "id": "survey-uid-0001", "name": "MFA Compliance Survey", "question_count": 12 }
    ]
  }
}
```

### 後端執行流程

```
PUT /api/1.0/grc/project/<project_uid>/job/<job_uid>
│
├─ Step 1  查找 job_execution WHERE uid = job_uid AND type = 'USER'
│           找不到 → 404
├─ Step 2  更新 scalar 欄位（有值才更新）
│           name / description / guide / task_type → job_executions
├─ Step 3  替換 assignees（assignee_uids != null）
│           assignee_uids=[]  → DELETE task_assignees WHERE task_id = job.id
│           assignee_uids=[..] → 自動推算 control_id（若未傳）：
│                                  job.workflow_execution_id → WorkflowExecution.parent_id
│                            → DELETE task_assignees WHERE task_id = job.id
│                            → INSERT 新 assignees（project_id + resolved_control_id + user_id）
├─ Step 4  替換 departments（department_uids != null）
│           DELETE compliance.job_execution_org_units WHERE job_execution_id = job.id
│           INSERT 新 org_unit 關聯
├─ Step 5  替換 devices（device_uids != null）
│           DELETE compliance.job_execution_devices WHERE job_execution_id = job.id
│           INSERT 新 device 關聯
├─ Step 6  替換 surveys（survey_uids != null）
│           DELETE compliance.job_execution_surveys WHERE job_execution_id = job.id
│           INSERT 新 survey 關聯
├─ flush + expire(job)
└─ get_job(job_uid) → 回傳最新完整資料
```

### 錯誤情境

#### `404` — Job 找不到

```json
{ "status": false, "data": {}, "msg": "Job not found" }
```

---

## 13. GET `/api/1.0/grc/project/<project_uid>/job/<job_uid>`

> **Route class**：`ProjectJobDetailResource.get`
> **用途**：依 UID 取得單筆 GRC Job 完整資料（含 assignees / departments / devices / surveys）。

### Request

無 Request Body，UID 從 URL path 取得。

```
GET /api/1.0/grc/project/550e8400-e29b-41d4-a716-446655440000/job/job-uid-0001
```

### Response `200`

```json
{
  "status": true,
  "data": {
    "id": "job-uid-0001",
    "name": "Upload MFA Configuration Evidence",
    "description": "Verify that MFA is implemented for all privileged accounts.",
    "guide": "Upload screenshots showing MFA is enabled on all administrator accounts.",
    "job_type": "general",
    "status": "not_started",
    "template_job_id": "UserTask_3a9f1c2b",
    "assignees": [
      { "id": "u-uid-0001", "name": "Alice Chen", "avatar": null }
    ],
    "departments": [
      { "id": "dept-uid-0001", "name": "IT Security" }
    ],
    "devices": [
      { "id": "dev-uid-0001", "name": "SSO Gateway", "device_type": "hardware" }
    ],
    "surveys": [
      { "id": "survey-uid-0001", "name": "MFA Compliance Survey", "question_count": 12 }
    ]
  }
}
```

### 錯誤情境

| HTTP | 狀況 |
|------|------|
| `404` | `{"status": false, "data": {}, "msg": "Job not found"}` |

---

## 14. POST `/api/1.0/grc/project/<project_uid>/assessment-object/<ao_uid>/jobs`

> **Route class**：`ProjectJobCreateResource`
> **用途**：在指定 Assessment Object 下新增一筆 Job。

### Request Body

```json
{
  "name": "Upload MFA Evidence",
  "description": "Verify that MFA is implemented.",
  "guide": "Upload screenshots showing MFA is enabled on all administrator accounts.",
  "job_type": "general",
  "control_uid": "wf-ctrl-uid-0001",
  "assignee_uids": ["u-uid-0001"],
  "department_uids": ["dept-uid-0001"],
  "device_uids": ["dev-uid-0001"],
  "survey_uids": []
}
```

#### 欄位說明

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `name` | string | **是** | Job 名稱 |
| `description` | string \| null | 否 | Job 描述 |
| `guide` | string \| null | 否 | 操作指引 |
| `job_type` | `"general"` \| `"questionnaire"` \| null | 否 | 任務類型，預設 `"general"` |
| `control_uid` | string \| null | 否 | 控制項層級的 `workflow_execution.uid`；省略時略過 assignee 寫入 |
| `assignee_uids` | string[] \| null | 否 | 指派人 UID 清單 |
| `department_uids` | string[] \| null | 否 | 部門 UID 清單 |
| `device_uids` | string[] \| null | 否 | 設備 UID 清單 |
| `survey_uids` | string[] \| null | 否 | 問卷 UID 清單 |

### Response `200`

回傳新建立的 Job 完整資料，格式與 GET 單筆相同。

```json
{
  "status": true,
  "data": {
    "id": "job-uid-new-0001",
    "name": "Upload MFA Evidence",
    "description": "Verify that MFA is implemented.",
    "guide": "Upload screenshots showing MFA is enabled on all administrator accounts.",
    "job_type": "general",
    "status": "not_started",
    "template_job_id": null,
    "assignees": [
      { "id": "u-uid-0001", "name": "Alice Chen", "avatar": null }
    ],
    "departments": [],
    "devices": [],
    "surveys": []
  }
}
```

> **`template_job_id`**：手動新增的 Job 使用 UUID4 作為 `template_job_id`（非 BPMN 節點 ID）。

### 後端執行流程

```
POST /api/1.0/grc/project/<project_uid>/assessment-object/<ao_uid>/jobs
│
├─ 查找 ao_uid → assessment_plan_task_workflow_execution_mapping → workflow_execution_id
│   找不到 → 回傳 status false
├─ 建立 JobExecution（status=PROCESSING, type=USER, template_job_id=uuid4）
├─ 寫入 task_assignees（需 control_uid 解析 control_id；省略則略過）
├─ 寫入 job_execution_org_units
├─ 寫入 job_execution_devices
├─ 寫入 job_execution_surveys
└─ 回傳 get_job(new_uid)
```

### 錯誤情境

```json
{ "status": false, "data": null }
```

> AO 找不到或 workflow_execution 對應不到時回傳此格式。

---

## 15. DELETE `/api/1.0/grc/project/<project_uid>/job/<job_uid>`

> **Route class**：`ProjectJobDetailResource.delete`
> **用途**：硬刪除指定 Job（同時刪除所有關聯資料）。

### Request

無 Request Body，UID 從 URL path 取得。

```
DELETE /api/1.0/grc/project/550e8400-e29b-41d4-a716-446655440000/job/job-uid-0001
```

### Response `200`

```json
{
  "status": true,
  "data": true
}
```

### 後端執行流程

```
DELETE → job_execution WHERE uid = job_uid
  ├─ DELETE task_assignees WHERE task_id = job.id
  ├─ DELETE job_execution_org_units WHERE job_execution_id = job.id
  ├─ DELETE job_execution_devices WHERE job_execution_id = job.id
  ├─ DELETE job_execution_surveys WHERE job_execution_id = job.id
  └─ DELETE job_executions WHERE id = job.id
```

### 錯誤情境

| HTTP | 狀況 |
|------|------|
| `404` | `{"status": false, "data": {}, "msg": "Job not found"}` |

---

## 16. POST `/api/1.0/grc/jobs/my/list`

> **Route class**：`MyJobListResource`
> **用途**：取得目前登入使用者的所有 GRC 任務（DB 層過濾 + 排序 + 分頁，資料來源為 `vw_user_job_queue`）。
> **注意**：已固定排除 `project.status = 'archived'` 的專案資料。

### Request Body

```json
{
  "pager": { "page": 1, "page_size": 25, "with_total": true },
  "sort": [{ "field": "job_name", "order": "asc" }],
  "filters": {
    "keyword": "MFA",
    "status": "not_started",
    "exclude_status": null,
    "project_uid": "550e8400-e29b-41d4-a716-446655440000",
    "group_keyword": "Access",
    "control_code": "AC.1",
    "ao_keyword": "Examine"
  }
}
```

#### `filters` 欄位說明

| 欄位 | 型別 | 說明 |
|------|------|------|
| `keyword` | string \| null | 跨欄位模糊搜尋（job 名稱、專案名稱、控制群組名稱、控制項編號、AO 描述） |
| `status` | string \| null | Job 狀態篩選（`not_started` / `in_progress` / `completed`），與 `exclude_status` 擇一使用 |
| `exclude_status` | string \| null | 排除指定狀態，與 `status` 擇一使用 |
| `project_uid` | string \| null | 篩選特定專案（精確比對） |
| `group_keyword` | string \| null | 控制項群組名稱／描述模糊搜尋 |
| `control_code` | string \| null | 控制項編號模糊搜尋（如 `"AC.1"` 會匹配 `AC.1.001`） |
| `ao_keyword` | string \| null | AO 代碼或描述模糊搜尋 |

#### `sort[].field` 可排序欄位

| 值 | 說明 |
|----|------|
| `job_name` | Job 名稱 |
| `job_status` | Job 狀態 |
| `job_type` | Job 類型（`general` / `questionnaire`） |
| `project_name` | 專案名稱 |
| `group_name` | 控制項群組名稱 |
| `control_no` | 控制項編號 |
| `ao_code` | 評估物件代碼 |

### Response `200`

```json
{
  "status": true,
  "data": {
    "meta": { "paging": true, "page": 1, "page_size": 25, "total": 5 },
    "data": [
      {
        "id": "job-uid-0001",
        "name": "Upload MFA Evidence",
        "task_type": "general",
        "status": "not_started",
        "project_uid": "550e8400-e29b-41d4-a716-446655440000",
        "project_name": "2026 CMMC 2.0 Level 2 稽核",
        "control_group_id": "group-uid-0001",
        "control_group_code": "AC",
        "control_group": "Access Control",
        "control_uid": "ctrl-uid-0001",
        "control_code": "AC.1.001",
        "control_name": "Limit system access to authorized users",
        "ao_code": "CMMC.AC.1.001.E1",
        "ao_name": "Examine: Access control policy",
        "ao_id": "ao-uid-0001",
        "workflow_execution_uid": "88072e77-56f2-4d4c-9197-f83a454ffa89"
      }
    ]
  }
}
```

### Response 欄位說明（My Jobs）

| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | string | `job_executions.uid` |
| `name` | string | Job 名稱 |
| `task_type` | string | `general` / `questionnaire` |
| `status` | string | `not_started` / `in_progress` / `completed` |
| `project_uid` | string \| null | 所屬專案 UID |
| `project_name` | string \| null | 所屬專案名稱 |
| `control_group_id` | string \| null | 控制群組 UID |
| `control_group_code` | string \| null | 控制群組代碼（`group_name`） |
| `control_group` | string \| null | 控制群組說明（`group_description`） |
| `control_uid` | string \| null | 控制項 UID |
| `control_code` | string \| null | 控制項編號 |
| `control_name` | string \| null | 控制項名稱 |
| `ao_code` | string \| null | Assessment Object 代碼 |
| `ao_name` | string \| null | Assessment Object 說明 |
| `ao_id` | string \| null | Assessment Object UID |
| `workflow_execution_uid` | string \| null | 對應的 `workflow_executions.uid`，供前端呼叫完成/退回任務 API 使用 |

> **`workflow_execution_uid` 來源**：`job_executions.workflow_execution_id` → `workflow_executions.uid`，由 `get_user_task_queue` SP 回傳。

---

## Status Enum

| 值 | 說明 |
|----|------|
| `pending` | 待開始 |
| `in_progress` | 進行中 |
| `completed` | 已完成 |
| `suspended` | 暫停中 |
| `archived` | 已封存 |

---

## 可見性規則（所有 GRC 專案 API）

```
若 is_admin = true  → 可見所有專案
若 is_admin = false → 僅可見：
    ① 自己為 owner（project_extensions.owner_id = user.id）
    ② 自己為 participant（project_participants.user_id = user.id）
```

---

## 後端資料來源總覽（GET `/project/<uid>`）

| 欄位 | 資料來源 | 查詢方式 |
|------|----------|----------|
| 基本資訊 | `compliance.projects` + `project_extensions` | JOIN |
| `completion_rate` | `oscal.assessment_plan_tasks` | correlated subquery |
| `control_group_count` | `oscal.assessment_plan_groups` | correlated subquery |
| `total_controls` | `oscal.assessment_plan_controls` | correlated subquery |
| `total_assessment_objects` | `oscal.assessment_plan_tasks` | correlated subquery（同 completion_rate 分子） |
| `framework` | `module_frames` → `oscal_framework_versions` → `oscal_frameworks` | sequential fetch |
| `compliance_resource` | `project_assessment_plan_mapping` → `assessment_plans` | sequential fetch |
| `owner` | `project_extensions.owner_id` → `users` | single fetch |
| `participants` | `project_participants` JOIN `users` | `ProjectParticipantService` |
| `audit_systems` | `project_information_systems` → `information_systems` | batch IN query |
| `devices` | `project_device_mapping` → `devices` | `ProjectDeviceMappingService` |

---

## 相關 API

| 說明 | API |
|------|-----|
| 啟動新專案 | `POST /api/1.0/oscal-project/start` |
| 取得稽核系統選單 | `GET /api/1.0/information-systems/menu` |
| 取得部門選單（供 Job 指派部門） | `GET /api/1.0/org-units/menu` |
| 取得設備選單（供 Job 指派設備） | `GET /api/1.0/devices/menu` |
| 取得問卷選單（供 Job 指派問卷） | `GET /api/1.0/surveys/menu` |
