# 订单待办事项（Todo）完整对接方案

> 日期: 2026-03-19 | 服务: hl-order-service

---

## 一、功能说明

订单支付成功后，系统自动生成一组「待办事项」，按顺序推进内部流程。每个待办有对应的负责角色，完成后自动推进到下一步。

### 待办流程（6步）

```
确认订单 → 配置保险 → 签订合同 → 安排住宿 → 安排车辆 → 确认清单
(定制师)   (定制师)   (定制师)   (房管)     (车管)     (定制师)
```

### 关联字典

**`order_todo_type`（订单待办类型）**：

| dict_value | dict_label | 序号 | 负责角色 |
|------------|-----------|------|---------|
| CONFIRM_ORDER | 确认订单 | 1 | CUSTOMIZER（定制师） |
| INSURANCE | 配置保险 | 2 | CUSTOMIZER（定制师） |
| CONTRACT | 签订合同 | 3 | CUSTOMIZER（定制师） |
| ARRANGE_ROOM | 安排住宿 | 4 | ROOM_MANAGER（房管） |
| ARRANGE_VEHICLE | 安排车辆 | 5 | VEHICLE_MANAGER（车管） |
| CONFIRM_CHECKLIST | 确认清单 | 6 | CUSTOMIZER（定制师） |
| PROCESS_REFUND | 处理退款 | 7 | CUSTOMIZER（定制师） |

**待办状态**：

| 值 | 含义 |
|----|------|
| PENDING | 待处理 |
| COMPLETED | 已完成 |
| CANCELLED | 已取消（订单取消/退款时自动取消） |

---

## 二、接口清单

| # | 方法 | 路径 | 说明 | 使用场景 |
|---|------|------|------|---------|
| 1 | GET | `/admin/order/{orderId}/todos` | 获取订单待办列表 | 订单详情页 → 待办面板 |
| 2 | PUT | `/admin/order/todo/{todoId}/complete` | 完成待办 | 点击「完成」/「去投保」按钮 |
| 3 | GET | `/admin/order/todo/my-list` | 我的待办列表 | 首页/工作台 → 待办列表 |
| 4 | GET | `/admin/order/todo/my-count` | 我的待办数量 | 首页/菜单 → 红点角标 |
| 5 | GET | `/admin/order/todo/customizer-list` | 定制师列表 | 更换定制师弹窗 → 下拉选择 |
| 6 | PUT | `/admin/order/{orderId}/customizer` | 更换定制师 | 更换定制师弹窗 → 确认 |
| 7 | GET | `/admin/order/todo/contract-eligible` | 可签合同订单列表 | 合同管理页 → 待签合同 |

---

## 三、接口详细定义

### 1. 获取订单待办列表

**接口**：`GET /admin/order/{orderId}/todos`

**使用场景**：订单详情页右侧「待办事项」面板，展示该订单所有待办项的时间线

**路径参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long(String) | 是 | 订单ID |

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "todoId": "1902000000000000001",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "CONFIRM_ORDER",
      "todoLabel": "确认订单",
      "sequence": 1,
      "status": "COMPLETED",
      "assigneeRoleKey": "CUSTOMIZER",
      "assigneeAdminId": "1800000000000000010",
      "completedBy": "1800000000000000010",
      "completedAt": "2026-03-19 10:30:00",
      "createTime": "2026-03-19 10:00:00"
    },
    {
      "todoId": "1902000000000000002",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "INSURANCE",
      "todoLabel": "配置保险",
      "sequence": 2,
      "status": "PENDING",
      "assigneeRoleKey": "CUSTOMIZER",
      "assigneeAdminId": "1800000000000000010",
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00"
    },
    {
      "todoId": "1902000000000000003",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "CONTRACT",
      "todoLabel": "签订合同",
      "sequence": 3,
      "status": "PENDING",
      "assigneeRoleKey": "CUSTOMIZER",
      "assigneeAdminId": "1800000000000000010",
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00"
    },
    {
      "todoId": "1902000000000000004",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "ARRANGE_ROOM",
      "todoLabel": "安排住宿",
      "sequence": 4,
      "status": "PENDING",
      "assigneeRoleKey": "ROOM_MANAGER",
      "assigneeAdminId": null,
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00"
    },
    {
      "todoId": "1902000000000000005",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "ARRANGE_VEHICLE",
      "todoLabel": "安排车辆",
      "sequence": 5,
      "status": "PENDING",
      "assigneeRoleKey": "VEHICLE_MANAGER",
      "assigneeAdminId": null,
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00"
    },
    {
      "todoId": "1902000000000000006",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "CONFIRM_CHECKLIST",
      "todoLabel": "确认清单",
      "sequence": 6,
      "status": "PENDING",
      "assigneeRoleKey": "CUSTOMIZER",
      "assigneeAdminId": "1800000000000000010",
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00"
    }
  ]
}
```

**返回字段说明**：

| 字段 | 类型 | 说明 |
|------|------|------|
| todoId | String | 待办ID |
| orderId | String | 订单ID |
| orderNo | String | 订单号 |
| todoType | String | 待办类型（字典：`order_todo_type`） |
| todoLabel | String | 待办名称（中文） |
| sequence | Integer | 排序序号（1-6，决定显示顺序） |
| status | String | 状态：`PENDING`=待处理 / `COMPLETED`=已完成 / `CANCELLED`=已取消 |
| assigneeRoleKey | String | 负责角色标识：`CUSTOMIZER` / `ROOM_MANAGER` / `VEHICLE_MANAGER` |
| assigneeAdminId | String | 负责管理员ID（可能为null，表示尚未分配具体人） |
| completedBy | String | 完成人ID（未完成时为null） |
| completedAt | String | 完成时间（未完成时为null），格式 `yyyy-MM-dd HH:mm:ss` |
| createTime | String | 创建时间，格式 `yyyy-MM-dd HH:mm:ss` |

---

### 2. 完成待办

**接口**：`PUT /admin/order/todo/{todoId}/complete`

**使用场景**：点击待办项的「完成」或「去投保」按钮

**路径参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| todoId | Long(String) | 是 | 待办ID |

**请求体**：无

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": null
}
```

**权限说明**：
- 需要对应角色权限，例如 `ARRANGE_ROOM` 待办需要 `ROOM_MANAGER` 角色
- `SUPER_ADMIN` 可以完成所有类型的待办
- 权限不足时返回 403

**业务逻辑**：
- 完成待办后，系统自动检查是否满足推进条件
- 所有6个待办全部完成后，内部流程状态自动推进到 `READY`（就绪）

---

### 3. 我的待办列表

**接口**：`GET /admin/order/todo/my-list`

**使用场景**：首页/工作台的「我的待办」列表，展示当前登录管理员需要处理的所有未完成待办

**请求参数**：无（根据登录用户的 adminId 和 roleKey 自动筛选）

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "todoId": "1902000000000000002",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "INSURANCE",
      "todoLabel": "配置保险",
      "sequence": 2,
      "status": "PENDING",
      "assigneeRoleKey": "CUSTOMIZER",
      "assigneeAdminId": "1800000000000000010",
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00",
      "productName": "嗨·冰雪3.0南线7日游",
      "contactName": "张三",
      "departureDate": "2026-04-01",
      "orderStatus": "PAID",
      "blocked": false,
      "blockedReason": null
    }
  ]
}
```

**返回字段说明**（在基础字段上增加关联信息）：

| 字段 | 类型 | 说明 |
|------|------|------|
| productName | String | 产品名称（关联查询） |
| contactName | String | 联系人姓名（关联查询） |
| departureDate | String | 出发日期（关联查询），格式 `yyyy-MM-dd` |
| orderStatus | String | 订单状态（字典：`order_status`） |
| blocked | Boolean | 是否被阻塞（前序待办未完成时为true） |
| blockedReason | String | 阻塞原因（如 "等待「配置保险」完成"） |

**角色筛选逻辑**：
- `SUPER_ADMIN`：看到所有未完成待办
- `CUSTOMIZER`：只看到 `CONFIRM_ORDER` / `INSURANCE` / `CONTRACT` / `CONFIRM_CHECKLIST` 类型的待办
- `ROOM_MANAGER`：只看到 `ARRANGE_ROOM` 类型的待办
- `VEHICLE_MANAGER`：只看到 `ARRANGE_VEHICLE` 类型的待办

---

### 4. 我的待办数量

**接口**：`GET /admin/order/todo/my-count`

**使用场景**：菜单栏/首页角标红点提醒

**请求参数**：无

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": 5
}
```

---

### 5. 定制师列表

**接口**：`GET /admin/order/todo/customizer-list`

**使用场景**：更换定制师弹窗中的下拉选择框

**请求参数**：无

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "adminId": "1800000000000000010",
      "realName": "王骁",
      "avatar": "https://oss.example.com/avatar/wx.jpg"
    },
    {
      "adminId": "1800000000000000011",
      "realName": "李明",
      "avatar": "https://oss.example.com/avatar/lm.jpg"
    }
  ]
}
```

**返回字段说明**：

| 字段 | 类型 | 说明 |
|------|------|------|
| adminId | String | 管理员ID |
| realName | String | 姓名 |
| avatar | String | 头像URL |

---

### 6. 更换定制师

**接口**：`PUT /admin/order/{orderId}/customizer`

**使用场景**：更换定制师弹窗 → 确认按钮

**路径参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long(String) | 是 | 订单ID |

**请求体**：
```json
{
  "customizerId": "1800000000000000011"
}
```

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| customizerId | Long(String) | 是 | 目标定制师的管理员ID，从定制师列表接口获取 |

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": null
}
```

**业务逻辑**：
- 更换后，该订单中所有 `CUSTOMIZER` 角色的待办自动转移给新定制师
- 订单时间线会记录「更换定制师」操作

---

### 7. 可签合同订单列表

**接口**：`GET /admin/order/todo/contract-eligible`

**使用场景**：合同管理页面，展示保险已完成、可以进入签合同环节的订单列表

**请求参数**：无

**响应示例**：
```json
{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "todoId": "1902000000000000003",
      "orderId": "1901000000000000001",
      "orderNo": "HL202603191600255172",
      "todoType": "CONTRACT",
      "todoLabel": "签订合同",
      "sequence": 3,
      "status": "PENDING",
      "assigneeRoleKey": "CUSTOMIZER",
      "assigneeAdminId": "1800000000000000010",
      "completedBy": null,
      "completedAt": null,
      "createTime": "2026-03-19 10:00:00",
      "productName": "嗨·冰雪3.0南线7日游",
      "contactName": "张三",
      "departureDate": "2026-04-01",
      "orderStatus": "PAID",
      "blocked": false,
      "blockedReason": null
    }
  ]
}
```

> 返回字段与「我的待办列表」相同（`OrderTodoVO`）

---

## 四、前端页面布局

### 1. 订单详情页 → 待办事项面板

在订单详情页右侧（或底部），使用**时间线组件**展示待办进度：

```
┌──────────────────────────────────────────────┐
│ 待办事项                                       │
│                                              │
│ ✅ 确认订单          已完成 2026-03-19 10:30    │
│                                              │
│ ⏳ 配置保险          [ 去投保 ]                │
│                                              │
│ ○ 签订合同           (等待上一步完成)           │
│                                              │
│ ○ 安排住宿           (等待上一步完成)           │
│                                              │
│ ○ 安排车辆           (等待上一步完成)           │
│                                              │
│ ○ 确认清单           (等待上一步完成)           │
│                                              │
│ [更换定制师]                                   │
└──────────────────────────────────────────────┘
```

### 2. 待办项状态展示规则

| 状态 | 图标 | 按钮 | 说明文字 |
|------|------|------|---------|
| `COMPLETED` | ✅ 绿色勾 | 无 | 显示 `completedAt` 完成时间 |
| `PENDING` + 非阻塞 | ⏳ 黄色 | 显示操作按钮 | 当前可操作项 |
| `PENDING` + 阻塞 | ○ 灰色 | 无 | 显示 `blockedReason`（如"等待上一步完成"） |
| `CANCELLED` | ❌ 红色 | 无 | 显示"已取消" |

### 3. 操作按钮规则

| 待办类型 | 按钮文案 | 按钮类型 | 点击行为 |
|---------|---------|---------|---------|
| CONFIRM_ORDER | 完成 | primary | 调用完成待办接口 |
| INSURANCE | 去投保 | warning | 跳转到保险配置页面（或弹窗配置后调用完成接口） |
| CONTRACT | 去签合同 | primary | 跳转到合同签署页面（或弹窗操作后调用完成接口） |
| ARRANGE_ROOM | 完成 | primary | 调用完成待办接口（仅ROOM_MANAGER角色可见） |
| ARRANGE_VEHICLE | 完成 | primary | 调用完成待办接口（仅VEHICLE_MANAGER角色可见） |
| CONFIRM_CHECKLIST | 完成 | primary | 调用完成待办接口 |

### 4. 权限控制

- 当前登录用户的 `roleKey` 不匹配待办的 `assigneeRoleKey` 时，**不显示操作按钮**
- `SUPER_ADMIN` 可以看到并操作所有待办的按钮
- 具体映射：
  - `CUSTOMIZER` → 可操作 CONFIRM_ORDER / INSURANCE / CONTRACT / CONFIRM_CHECKLIST
  - `ROOM_MANAGER` → 可操作 ARRANGE_ROOM
  - `VEHICLE_MANAGER` → 可操作 ARRANGE_VEHICLE

---

## 五、首页待办角标

### 实现要点

1. **页面加载时**调用 `GET /admin/order/todo/my-count` 获取待办数量
2. 如果 `data > 0`，在菜单项（如「订单管理」）右侧显示红点或数字角标
3. 点击菜单进入「我的待办」列表页

### 我的待办列表页

调用 `GET /admin/order/todo/my-list`，展示为卡片或表格：

```
┌──────────────────────────────────────────────────────────────┐
│ 我的待办 (5)                                                   │
├──────────────────────────────────────────────────────────────┤
│ 📋 配置保险 | HL202603191600255172 | 嗨·冰雪3.0南线7日游       │
│    联系人: 张三 | 出发: 2026-04-01 | [ 去投保 ] [ 查看订单 ]    │
├──────────────────────────────────────────────────────────────┤
│ 📋 签订合同 | HL202603191800123456 | 海岛亲子5日游              │
│    联系人: 李四 | 出发: 2026-04-15 | ⚠️ 等待「配置保险」完成     │
├──────────────────────────────────────────────────────────────┤
│ ...                                                          │
└──────────────────────────────────────────────────────────────┘
```

---

## 六、更换定制师

### 实现要点

1. 订单详情页待办面板底部放「更换定制师」按钮
2. 点击弹出弹窗，调用 `GET /admin/order/todo/customizer-list` 加载定制师列表
3. 下拉选择目标定制师（显示 `realName`，值 `adminId`）
4. 确认后调用 `PUT /admin/order/{orderId}/customizer`
5. 成功后刷新待办列表

---

## 七、调用示例

```javascript
import { http } from '@/utils/request'

// 1. 获取订单待办列表（订单详情页加载时）
const todos = await http.get(`/order/${orderId}/todos`)
// todos = [{ todoId, todoType, todoLabel, status, completedAt, ... }]

// 2. 完成待办（点击「完成」按钮）
await http.put(`/order/todo/${todoId}/complete`)

// 3. 获取我的待办列表（首页/工作台）
const myTodos = await http.get('/order/todo/my-list')
// myTodos = [{ todoId, orderNo, todoLabel, productName, contactName, departureDate, blocked, ... }]

// 4. 获取待办数量（菜单角标）
const countRes = await http.get('/order/todo/my-count')
const todoCount = countRes  // Number

// 5. 获取定制师列表（更换定制师弹窗）
const customizers = await http.get('/order/todo/customizer-list')
// customizers = [{ adminId, realName, avatar }]

// 6. 更换定制师
await http.put(`/order/${orderId}/customizer`, { customizerId: targetAdminId })

// 7. 获取可签合同订单（合同管理页）
const eligibleOrders = await http.get('/order/todo/contract-eligible')
```

---

## 八、待办生命周期

```
订单支付成功
  ↓
系统自动创建6个待办（status=PENDING）
  ↓
定制师逐步完成待办（PENDING → COMPLETED）
  ↓
所有待办完成 → 内部流程推进到 READY（就绪）
  ↓
如果订单取消/退款 → 所有未完成待办自动取消（status=CANCELLED）
```

**注意事项**：
- 待办按 `sequence` 排序显示（1→6）
- 虽然显示顺序固定，但 ARRANGE_ROOM(4) 和 ARRANGE_VEHICLE(5) 可以并行完成（不互相阻塞）
- 只有 INSURANCE 完成后，CONTRACT 才能操作（有依赖关系）
- 前端拿到 `blocked: true` 的待办不显示操作按钮，显示 `blockedReason` 文字即可
