# 会议出勤（Meeting Attendance）- 状态机文档

> **版本**: v1.0
> **最后更新**: 2026-03-02
> **维护者**: 待定

---

## ✅ 机器读取区（必填）

### 核心对象

| 字段 | 内容 |
|------|------|
| 对象类型 | Meeting |
| 状态字段 | `status` |
| 数据表 | `meeting_attendance.meetings` |

### 状态列表

| 状态代码 | 说明 | 终态 |
|---------|------|------|
| SCHEDULED | 会议未开始 | ❌ |
| IN_PROGRESS | 会议进行中 | ❌ |
| COMPLETED | 会议已结束 | ✅ |
| CANCELLED | 会议已取消 | ✅ |

### 合法流转

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| SCHEDULED | IN_PROGRESS | 时间到达开始时间 | 系统 | now 在 [startTime, endTime] |
| IN_PROGRESS | COMPLETED | 时间超过结束时间 | 系统 | now > endTime |
| SCHEDULED | CANCELLED | 取消会议 | 管理员/会议管理员 | 无 |
| IN_PROGRESS | CANCELLED | 取消会议 | 管理员/会议管理员 | 允许中途取消 |
| SCHEDULED/IN_PROGRESS | CANCELLED | Outlook 同步取消/删除 | 系统同步任务 | 会议已纳管且同步来源邮箱可用 |

---

### 核心对象（出勤）

| 字段 | 内容 |
|------|------|
| 对象类型 | Attendance |
| 状态字段 | `status` |
| 数据表 | `meeting_attendance.attendances` |

### 状态列表

| 状态代码 | 说明 | 终态 |
|---------|------|------|
| NOT_CHECKED_IN | 未签到 | ❌ |
| ON_SITE | 现场签到 | ❌ |
| ONLINE | 线上签到 | ❌ |
| LATE | 迟到 | ❌ |
| ABSENT | 缺席 | ✅ |
| PTO | 请假 | ✅ |
| BUSINESS_CONFLICT | 业务冲突 | ✅ |
| MEETING | 会议冲突 | ✅ |

### 合法流转

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| NOT_CHECKED_IN | ON_SITE/ONLINE | 扫码/手动签到 | 参会人 | 会议开始前15分钟内 |
| NOT_CHECKED_IN | LATE | 迟到签到 | 参会人 | 会议开始后8分钟 |
| NOT_CHECKED_IN | ABSENT | 会议结束标记缺席 | 管理员/会议管理员 | 会议已完成 |
| 任意 | 任意 | 手动调整出勤状态 | 管理员/会议管理员 | 有权限操作 |

---

### 核心对象（Outlook 纳管绑定）

| 字段 | 内容 |
|------|------|
| 对象类型 | OutlookMeetingBinding |
| 状态字段 | `manageStatus` + `syncMode` |
| 数据表 | `platform_meeting_attendance.outlook_sync_bindings` |

### 纳管状态（manageStatus）

| 状态代码 | 说明 | 终态 |
|---------|------|------|
| PENDING_SELECTION | 候选会议，未纳管 | ❌ |
| MANAGED | 已纳管并同步 | ❌ |
| SYNC_ERROR | 同步失败待重试 | ❌ |
| DISABLED | 停止纳管 | ✅ |

### 同步模式（syncMode）

| 状态代码 | 说明 | 终态 |
|---------|------|------|
| AUTO | 默认自动同步，Outlook 更新可覆盖本地 | ❌ |
| LOCKED_BY_LOCAL_EDIT | 已转为本地维护，继续记录官方源但不再覆盖本地 | ❌ |

### 合法流转

#### manageStatus

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| PENDING_SELECTION | MANAGED | 管理员选择纳管 | 管理员/会议管理员 | 当前管理员邮箱可用于同步 |
| MANAGED | SYNC_ERROR | 同步任务失败 | 系统 | 超过单次处理容错 |
| SYNC_ERROR | MANAGED | 重试同步成功 | 系统 | 数据校验通过 |
| MANAGED | DISABLED | 管理员取消纳管 | 管理员/会议管理员 | 无 |
| MANAGED | MANAGED | 强制接管绑定 | 管理员/会议管理员 | 有同步页面权限，接管后更新绑定人/来源邮箱/syncFrom并记录审计 |

#### syncMode

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| AUTO | LOCKED_BY_LOCAL_EDIT | 单次会议人工修改 | 管理员/会议管理员 | 修改标题/时间/地点/状态/参会名单任一字段 |
| LOCKED_BY_LOCAL_EDIT | LOCKED_BY_LOCAL_EDIT | Outlook 同步到达但跳过覆盖 | 系统 | 继续记录官方源版本与差异，不更新本地 meeting/attendees |

---

### 核心对象（系列单次排除）

| 字段 | 内容 |
|------|------|
| 对象类型 | OutlookSeriesOccurrenceExclusion |
| 状态字段 | N/A（记录型对象） |
| 数据表 | `platform_meeting_attendance.outlook_series_occurrence_exclusions` |

### 生命周期（记录型）

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| 无记录 | 已排除 | 新增排除实例 | 管理员/会议管理员 | 绑定必须是 seriesMaster 且实例属于该系列 |
| 已排除 | 无记录 | 取消排除 | 管理员/会议管理员 | 排除记录存在 |

---

### 核心对象（议程项上传任务）

| 字段 | 内容 |
|------|------|
| 对象类型 | MeetingAgendaItemUploadTask |
| 状态字段 | `status`（枚举 `UploadTaskStatus`） |
| 数据表 | `meeting_attendance.meeting_agenda_item_upload_tasks` |

### 状态列表

| 状态代码 | 说明 | 终态 |
|---------|------|------|
| PENDING | 任务待处理，assignee 未上传 | ❌ |
| UPLOADED | assignee 上传成功，任务完成 | ✅ |
| CANCELLED | 管理员主动取消 | ✅ |

### 合法流转

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| PENDING | UPLOADED | assignee 通过 `POST /agenda-items/:itemId/attachments` 上传成功 | 系统（auto-flip） | assignee = 当前用户；attachment 写入成功 |
| PENDING | CANCELLED | 主动取消任务 | 管理员 / creator | 通过 `PATCH /agenda-items/:itemId/upload-tasks/:taskId` |
| UPLOADED | 任意 | 禁止 | — | 终态，不允许回退；如需恢复请重新分配新任务 |
| CANCELLED | 任意 | 禁止 | — | 终态，不允许回退；如需恢复请重新分配新任务 |

---

## 🧭 人类阅读区（可选）

### 状态流转图（会议）

```mermaid
stateDiagram-v2
    [*] --> SCHEDULED: 创建会议
    SCHEDULED --> IN_PROGRESS: 到达开始时间
    IN_PROGRESS --> COMPLETED: 到达结束时间
    SCHEDULED --> CANCELLED: 取消
    IN_PROGRESS --> CANCELLED: 取消
```

### 状态流转图（出勤）

```mermaid
stateDiagram-v2
    [*] --> NOT_CHECKED_IN: 创建参会记录
    NOT_CHECKED_IN --> ON_SITE: 现场签到
    NOT_CHECKED_IN --> ONLINE: 线上签到
    NOT_CHECKED_IN --> LATE: 迟到签到
    NOT_CHECKED_IN --> ABSENT: 会议结束标记缺席
    ON_SITE --> ABSENT: 管理员调整
    ONLINE --> ABSENT: 管理员调整
    LATE --> ABSENT: 管理员调整
```

### 状态流转图（议程项上传任务）

```mermaid
stateDiagram-v2
    [*] --> PENDING: 管理员分配任务
    PENDING --> UPLOADED: assignee 上传成功（auto-flip）
    PENDING --> CANCELLED: 管理员主动取消
    UPLOADED --> [*]
    CANCELLED --> [*]
```

> 说明：`UPLOADED` 和 `CANCELLED` 均为终态。若需要再次让某人上传，应**重新分配一个新任务**，而不是回退既有任务状态。

---

### 软删 + 物理删生命周期（议程段 / 项 / 任务 / 附件，v1.0 通用）

5 张新表所有删除一律走"软删 + 30 天物理清理"两段生命周期：

| 状态代码 | 说明 | 终态 |
|---------|------|------|
| active | `deletedAt IS NULL`，对外可见 | ❌ |
| deleted | `deletedAt IS NOT NULL`，list/find 默认过滤 | ❌ |
| physically_removed | cron 物理 DELETE + unlink 文件（仅 attachment） | ✅ |

**合法流转**：

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| active | deleted | 用户/管理员触发 DELETE | 管理员 / uploader 本人 | service 层 `UPDATE deletedAt = now()` |
| deleted | active | admin 手工恢复（30 天窗口） | Administrator | `UPDATE deletedAt = NULL`；不通过常规 UI 暴露 |
| deleted | physically_removed | cron 扫 `deletedAt < now() - 30d` | 系统（cron） | env `MEETING_ATTACHMENT_PHYSICAL_DELETE_DAYS`，默认 30；attachment 同时 unlink 底层文件 |

**cascade soft-delete**：删除议程段 → service 层显式在同事务里软删段下所有 item / task / attachment（不依赖 FK `onDelete: Cascade`）。

```mermaid
stateDiagram-v2
    [*] --> active: 创建
    active --> deleted: DELETE (deletedAt 标记)
    deleted --> active: admin 30d 内恢复
    deleted --> physically_removed: cron 30d 后物理删 + unlink
    physically_removed --> [*]
```
