# 审批引擎说明

## 核心流程节点

```
草稿(draft)
  │
  ├─[DCO类型内容]──────────────────────────────────────────► 结论(conclusion)
  │
  └─[普通内容]
       │
       ▼
  AI审核(ai_review)
       │
       ├─[拒绝]──► 草稿(draft) ← 退回发起人修改
       │
       └─[通过] ──► 路由判断(flow_rule规则)
                         │
              ┌──────────┴──────────┐
              │                     │
              ▼                     ▼
    四部门并行审核              专员审核
    (departmental_review)    (personnel_review)
              │                     │
              └─────────┬───────────┘
                        │
              ┌─────────┴─────────┐
              │                   │
           [拒绝]               [通过]
              │                   │
              ▼               ┌───┴───────────────┐
   提交人修改              ch_upload=no?      ch_upload=yes?
(submitter_revisions)         │                   │
              │            [直接结束]        待传输
              │           conclusion     (awaiting_deployment)
              │                               │
              └────────────────► 结论(conclusion)
```

---

## 各节点详情

| 节点 | 标识 | 操作人 | 说明 |
|------|------|--------|------|
| **草稿** | `draft` | 发起人 | 填写信息、上传素材，提交后进入 AI 审核 |
| **AI 审核** | `ai_review` | AI 系统账号 | 自动合规检查；管理员可 skip 此步骤 |
| **四部门并行审核** | `departmental_review` | NS / Legal / SRA / CA | 四部门同时审核，**全部通过**才算通过，**任一驳回**即推进到驳回 |
| **专员审核** | `personnel_review` | 审核专员(guding) | 单人/单部门审核 |
| **提交人修改** | `submitter_revisions` | 发起人 | 被驳回后修改，重提后智能跳过上轮已通过的审核人 |
| **待传输** | `awaiting_deployment` | material_owner | 通知素材负责人确认/延后传输到 Content Hub |
| **结论** | `conclusion` | - | 终态，可能是通过/驳回/中止 |

---

## 路由规则 (flow_rule)

通过 `pepsico_flow_rule` 表的公式动态决定走哪个审核路径：

- **`skip_approve`** 条件为真 → 跳过审核，AI 通过后直接 `conclusion`
- **`name`** 公式为真 → 走**专员审核** (`personnel_review`)
- **`name`** 公式为假 → 走**四部门并行** (`departmental_review`)

---

## 部门内多人审核规则

### 两个层级的多对多关系

审核涉及两个维度的"多"：

```
审批单
  └── 多个部门 (CA / NS / Legal / SRA) — 并行
        └── 每个部门有多个处理人 (handler_ca: [A, B, C])
              └── 每个处理人对每个素材逐一处理
                    └── pepsico_approval_task (一条记录 = 一人 × 一素材)
```

### 单素材的部门通过规则

判断逻辑在 `getMaterialStatus` (`src/methods/task.ts`)：

```
对于某部门的某素材:
  1. 如果 handler_xxx 人数 > 已提交 task 数量  →  pending (有人未处理)
  2. 遍历每一位处理人:
       如果该用户没有 approval_status == true 的 task  →  rejected
  3. 所有处理人都有 approved task  →  approved
```

**结论：部门内所有处理人必须全部通过，该素材才算该部门通过。任意一人驳回即驳回。**

### 审批单部门状态的汇总规则

判断逻辑在 `getUserReviewedAllMaterialsStatus` (`src/methods/material.ts`)，由 `approveMaterial` / `rejectMaterial` 触发：

```
每当某处理人完成一个素材的审核后:
  检查该审批单的所有未作废素材的 {dept}_approval_status:
    - 存在 null / '' / undefined 的素材  →  pending (继续等待)
    - 全部非空且有 false              →  rejected → 触发驳回流程
    - 全部为 true                     →  approved → 触发通过流程
```

### 处理人完成"当前步骤"的判断

在 `submitMaterialTask` (`src/methods/task.ts`) 中：

```
当处理人完成某素材后，检查:
  "该用户还有多少未处理的素材（排除已作废）" vs "该用户还有多少未完成的 task"

  如果 processed.length == pendingMaterialList.length
    → 该用户已完成所有素材
    → 更新 pepsico_approval_process 为 approved/rejected
    → 将用户加入 person_done
    → 从 person_pending_user 中移除该用户
```

### 角色判定：同一人属于哪个部门？

`getApproverField` (`src/methods/material.ts`) 通过以下顺序判断用户角色：

1. 当前步骤是 `departmental_review` 时，依次检查用户是否在 `ca_reviewer / handler_ca`、`sra_reviewer / handler_sra`、`ns_reviewer / handler_ns`、`legal_reviewer / handler_legal` 中
2. 再检查 `pepsico_approval_process` 中该用户的 `type` 字段（Kids 优先）
3. Kids 单独判断：`child_files == 'yes'` 且部门状态尚未完成

> **注意：** 当前实现中一个用户同一步骤只映射到第一个匹配的部门角色（`if / else if` 链），如果同一人被同时配置到两个部门则只取第一个。

### 完整的"部门通过"链路

```
处理人 A 审核素材 X → pepsico_approval_task (A, X, ca, approved)
处理人 B 审核素材 X → pepsico_approval_task (B, X, ca, approved)
                        ↓
              getMaterialStatus: A + B 都通过 → approved
                        ↓
              pepsico_material.ca_approval_status = true
                        ↓
              getUserReviewedAllMaterialsStatus: 所有素材都 true?
                        ↓ 是
              pepsico_material_approval.ca_approval_status = true
                        ↓
              approvalContent: 检查 CA/NS/Legal/SRA 四部门是否全部完成
                        ↓ 全部完成
              advanceApproval → 推进到下一步
```

---

## Kids 特殊审核

当 `child_files == 'yes'` 时，无论走哪条路径，都**额外附加 Kids 审核人**：

- 四部门审核时：Kids 与四部门并行
- 专员审核时：Kids 与专员并行
- 必须专员 + Kids **都完成**才能推进步骤
- Kids 采用逐素材审核模式，不自动推进审批单状态，需通过 `submitKidsContent` 显式推进

---

## 外部用户流程

`is_external == true` 时，草稿提交后**不走审核流**，而是将 owner 转移给内部负责人，由内部人员重新正式提交。

---

## 传输节点 (awaiting_deployment)

仅当 `ch_upload != 'no'` 时才会出现。`material_owner` 可选择：

- **确认传输** (`confirmDeployment`) → 推进到 `conclusion` + 推送 Content Hub
- **延后传输** (`postponeDeployment`) → 标记 `launch=postpone`，记录延后日期

---

## 中止能力

发起人、空间管理员、或当前待审批人均可**中止审批** (`terminate`)，直接进入 `conclusion`，`review_status = terminated`。

---

## 重审优化（submitter_revisions 后）

提交人修改后重新提交时，引擎会检查各部门上轮是否已通过：

- **上轮已通过**的审核人 → 自动跳过，不再分配任务
- **上轮驳回或未处理**的审核人 → 重新分配
- **所有审核人上轮均已通过** → 直接进入 `conclusion`

---

## 关键数据字段

| 字段 | 说明 |
|------|------|
| `current_step` | 当前所处节点 |
| `review_status` | 整体审核状态：`draft/under_review/approved/rejected/terminated` |
| `person_pending_approve` | 当前待审批人员列表 |
| `person_pending_user` | 以用户维度的待处理人员（同一用户在多角色时去重处理） |
| `person_approved` | 历史已处理人员 |
| `person_done` | 本轮已完成审批人员（重审时清空） |
| `handler_*` | 各部门本轮实际处理人（CA/NS/Legal/SRA/Kids/guding） |
| `display_handler_*` | 各部门历史处理人（含历史轮次，用于展示） |
| `{dept}_approval_status` | 各部门审核结果：`true=通过 / false=驳回 / ''=审核中` |
