# 订单时间线 11 事件类型扩展 + 转单入参正式生效

- **变更类型**：修改接口
- **端类型**：管理后台
- **日期**：2026-06-26
- **服务**：hl-order-service-v3
- **关联 PR**：#4422 #4426 #4434 #4449 #4454 #4464
- **关联 Issue**：#4419 #4423 #4428 #4446 #4451 #4459
- **后端负责人**：yst

---

## ① 接口背景

订单时间线 Epic（6 PR 合并周期）带来两处接口变更：

1. **GET /v3/admin/order/{id}/status-log**：时间线出参 `eventType` 新增 11 个枚举值，覆盖增减项、合同、保险、退款、发票等业务动作，前端需按 eventType 渲染对应 label 与 content 文本。
2. **PUT /v3/admin/order/{id}**（修改订单字段）：原先 `targetConsultantId` + `transferReason` 两个字段静默忽略（后端收到但不执行），现在**正式生效**——传这两个字段即触发真实转单逻辑。⚠️ 若前端已在传这两个字段，行为将从「无效」变为「真改归属」。

---

## ② 变更清单

| 接口 | 变更类型 | 变更描述 |
|---|---|---|
| GET /v3/admin/order/{id}/status-log | ✨ 出参枚举扩展 | eventType 新增 11 个取值（详见 §⑥） |
| GET /v3/admin/order/{id}/status-log | ✨ 出参新增触发场景 | INFO_ADJUST、REFUND_RECEIVED 新增触发场景（枚举值本身不变） |
| PUT /v3/admin/order/{id} | ⚠️ 入参行为变更 | targetConsultantId + transferReason 从「静默忽略」变为「真转单」 |
| PUT /v3/admin/order/{id} | ✨ 新增错误码 | 581042「目标定制师不存在或已停用」 |

---

## ③ 接口详情

### 接口 A：GET /v3/admin/order/{id}/status-log

- **描述**：获取订单详情记录 Tab 的时间线列表
- **认证**：需要管理员 JWT（Bearer Token）
- **幂等性**：只读，天然幂等
- **限流**：无特殊限流

### 接口 B：PUT /v3/admin/order/{id}

- **描述**：修改订单字段（含转单字段）
- **认证**：需要管理员 JWT（Bearer Token）
- **幂等性**：非幂等；同一 targetConsultantId 重复调用不报错但无实际变化（归属已是目标定制师则不重复写时间线）
- **限流**：无特殊限流

---

## ④ 接口入参

### 接口 A（GET /v3/admin/order/{id}/status-log）

#### 4.1 路径参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 订单 ID（雪花 ID，字符串形式传入） |

无请求体。

### 接口 B（PUT /v3/admin/order/{id}）

#### 4.1 路径参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 订单 ID |

#### 4.2 请求体字段（仅列出本次变更相关字段，其余字段不变）

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| targetConsultantId | Long | 否 | 转单目标定制师的 adminId。不传或与当前一致则不转单；传入且与当前不同则触发真实转单。 |
| transferReason | String | 否 | 转单原因备注，配合 targetConsultantId 使用；targetConsultantId 未传时忽略 |

---

## ⑤ 出参字段

### 接口 A（GET /v3/admin/order/{id}/status-log）

出参结构未变，records 数组每项字段如下：

| 字段 | 类型 | 说明 |
|---|---|---|
| records | Array | 时间线记录列表，按 changedAt 降序 |
| records[].id | String | 记录 ID |
| records[].eventType | String | 事件类型枚举（见 §⑥ 完整枚举表） |
| records[].content | String | 事件描述文本（见 §⑥ content 样例，后端生成，直接渲染） |
| records[].changedAt | String | 事件发生时间，ISO8601 格式 |
| records[].operatorName | String | 操作人名称（系统触发时为「系统」） |
| records[].fromStatus | String | 事件发生时订单粗状态快照 |
| records[].toStatus | String | 事件发生后订单粗状态快照 |
| records[].fromStatusName | String | fromStatus 中文名 |
| records[].toStatusName | String | toStatus 中文名 |
| records[].extra | Object | 附加结构化数据，按 eventType 不同而不同（可为 null） |

### 接口 B（PUT /v3/admin/order/{id}）

出参结构不变，仍返回 `Result<Void>`（成功时 data 为 null，code=200）。

---

## ⑥ 枚举 / 数据字典

### eventType 完整枚举表（含本次新增）

| eventType 值 | label | content 文本样例 | 是否本次新增 |
|---|---|---|---|
| ORDER_CREATED | 创建订单 | 创建订单 | 否 |
| ORDER_CONFIRMED | 确认订单 | 确认订单 | 否 |
| ORDER_CANCELLED | 订单取消 | 订单取消 | 否 |
| ORDER_TRIP_STARTED | 出行开始 | 出行开始 | 否 |
| ORDER_TRIP_TERMINATED | 终止行程 | 终止行程 | 否 |
| ORDER_COMPLETED | 订单完成 | 订单完成 | 否 |
| PAYMENT_RECEIVED | 收款记录 | 收款 ¥6000（微信支付） | 否 |
| PAYMENT_DEPOSIT | 定金收款 | 定金收款 ¥2000 | 否 |
| TRAVELER_CHANGED | 出行人变更 | 新增出行人：张三 / 移除出行人：李四 | 否 |
| INFO_ADJUST | 订单信息调整 | 修改客户信息 | 否（新增触发场景：改客户姓名/电话/紧急联系人/备注） |
| REFUND_RECEIVED | 退款到账 | ¥6000 已到账（手动完成） | 否（新增触发场景：退款手动完成） |
| ADJUSTMENT_ADD | 新增增减项 | 新增优惠 -¥500：VIP优惠 / 新增附加费 +¥500：船票升舱 | **是** |
| ADJUSTMENT_REVERSE | 撤销增减项 | 撤销优惠 ¥500：录错 / 撤销附加费 ¥300：录错 | **是** |
| CONSULTANT_TRANSFER | 转单 | 转单：张三→李四 | **是** |
| CONTRACT_CREATED | 创建合同 | 已创建合同：HT202601001 / 已报备合同：HT202601001 | **是** |
| CONTRACT_INVALIDATED | 作废合同 | 已作废合同：HT202601001 | **是** |
| INSURANCE_PURCHASED | 投保 | 已投保 保游金标准方案（3人） | **是** |
| INSURANCE_CANCELLED | 退保 | 已退保：BY2026001234 | **是** |
| REFUND_REVIEWED | 退款审核 | 退款审核通过 ¥6000 / 退款审核驳回：金额超出实付 | **是** |
| REFUND_EXECUTING | 退款发起 | 退款发起 ¥6000 | **是** |
| INVOICE_APPLIED | 申请开票 | 申请开票：呼籁旅行科技有限公司 | **是** |

> **eventType 分类说明**：
> - ADJUSTMENT_ADD / ADJUSTMENT_REVERSE：DATA 类（订单内数据变更），fromStatus/toStatus 为当时订单粗状态快照，不代表状态发生了迁移。
> - CONSULTANT_TRANSFER：DATA 类，归属变更不触发订单状态迁移。
> - CONTRACT_CREATED / CONTRACT_INVALIDATED / INSURANCE_PURCHASED / INSURANCE_CANCELLED / REFUND_REVIEWED / REFUND_EXECUTING / INVOICE_APPLIED：DATA 类，均为业务动作记录。

---

## ⑦ 错误码

### 接口 B（PUT /v3/admin/order/{id}）新增错误码

| 错误码 | message | 触发场景 |
|---|---|---|
| 581042 | 目标定制师不存在或已停用 | targetConsultantId 对应的 admin 用户不存在，或该用户账号已被停用 |

### 已有错误码（保持不变，供参考）

| 错误码 | message | 说明 |
|---|---|---|
| 581001 | 订单不存在 | id 对应订单不存在 |
| 400 | 参数校验失败 | 请求体字段类型/格式错误 |

---

## ⑧ 示例

### 8.1 典型成功示例

#### 接口 A：获取时间线（含新枚举值）

请求：
```
GET /v3/admin/order/1234567890123456789/status-log
Authorization: Bearer <admin-token>
```

响应：
```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "id": "9876543210",
        "eventType": "ADJUSTMENT_ADD",
        "content": "新增优惠 -¥500：VIP优惠",
        "changedAt": "2026-06-25T14:30:00+08:00",
        "operatorName": "李定制",
        "fromStatus": "CONFIRMED",
        "toStatus": "CONFIRMED",
        "fromStatusName": "已确认",
        "toStatusName": "已确认",
        "extra": null
      },
      {
        "id": "9876543211",
        "eventType": "CONSULTANT_TRANSFER",
        "content": "转单：张三→李四",
        "changedAt": "2026-06-25T10:00:00+08:00",
        "operatorName": "系统",
        "fromStatus": "CONFIRMED",
        "toStatus": "CONFIRMED",
        "fromStatusName": "已确认",
        "toStatusName": "已确认",
        "extra": null
      },
      {
        "id": "9876543212",
        "eventType": "INSURANCE_PURCHASED",
        "content": "已投保 保游金标准方案（3人）",
        "changedAt": "2026-06-24T16:20:00+08:00",
        "operatorName": "系统",
        "fromStatus": "CONFIRMED",
        "toStatus": "CONFIRMED",
        "fromStatusName": "已确认",
        "toStatusName": "已确认",
        "extra": null
      }
    ]
  }
}
```

#### 接口 B：转单成功

请求：
```
PUT /v3/admin/order/1234567890123456789
Authorization: Bearer <admin-token>
Content-Type: application/json

{
  "targetConsultantId": 987654321,
  "transferReason": "原定制师休假，转交李四跟进"
}
```

响应：
```json
{
  "code": 200,
  "data": null
}
```

---

### 8.2 边界情况示例

#### 转单字段与当前归属相同（不触发转单，静默成功）

请求（当前归属已是 adminId=123456789）：
```json
{
  "targetConsultantId": 123456789,
  "transferReason": "测试"
}
```

响应：
```json
{
  "code": 200,
  "data": null
}
```

不会写入 CONSULTANT_TRANSFER 时间线记录。

#### 不传转单字段（其他字段正常修改，归属不变）

请求：
```json
{
  "customerRemark": "客户备注更新"
}
```

响应：
```json
{
  "code": 200,
  "data": null
}
```

---

### 8.3 业务失败示例

#### targetConsultantId 目标定制师不存在或已停用

请求：
```json
{
  "targetConsultantId": 999999999,
  "transferReason": "转单测试"
}
```

响应：
```json
{
  "code": 581042,
  "msg": "目标定制师不存在或已停用",
  "data": null
}
```

---

## ⑨ 业务边界

### 适用场景

- 接口 A：任何状态的订单均可查询时间线（只读）。
- 接口 B 转单：订单状态为非终态（未取消、未完成）时可转单；已取消或已完成订单会被业务守卫拦截。

### 不适用场景

- 不可通过时间线接口触发业务动作，时间线为只读查询。
- 转单不可将订单归属给已停用的管理员账号（报 581042）。
- transferReason 在 targetConsultantId 未传时无意义，后端忽略。

### 特殊边界

- ADJUSTMENT_ADD / ADJUSTMENT_REVERSE 的 fromStatus/toStatus 是当时订单粗状态的快照，这两个事件本身**不改变订单状态**，前端时间线渲染不需要显示状态迁移箭头。
- CONSULTANT_TRANSFER 同上，仅归属变更，不改变订单主状态。
- 时间线记录不可删除，历史订单在本次上线前发生的对应操作无时间线记录属正常现象（该类事件在此次上线前未记录）。
- targetConsultantId 传 null 或不传，后端视为「不转单」，与历史行为完全一致。

---

## ⑩ 修改前后对比

### 接口 A：eventType 枚举值扩展

| 维度 | 修改前 | 修改后 |
|---|---|---|
| eventType 取值域 | 11 个基础值（ORDER_CREATED 等） | 在原有基础上新增 11 个值：ADJUSTMENT_ADD / ADJUSTMENT_REVERSE / CONSULTANT_TRANSFER / CONTRACT_CREATED / CONTRACT_INVALIDATED / INSURANCE_PURCHASED / INSURANCE_CANCELLED / REFUND_REVIEWED / REFUND_EXECUTING / INVOICE_APPLIED |
| INFO_ADJUST 触发场景 | 仅原有场景 | 新增：改客户姓名/电话/紧急联系人/备注时写入 |
| REFUND_RECEIVED 触发场景 | 仅原有场景 | 新增：退款手动标记完成时写入 |
| 出参字段结构 | 不变 | 不变 |

### 接口 B：转单字段行为变更

| 维度 | 修改前 | 修改后 |
|---|---|---|
| targetConsultantId 传非空值行为 | **静默忽略**（后端接收但不执行任何操作） | **正式生效**：触发转单逻辑，改变订单归属定制师 |
| transferReason 传值行为 | **静默忽略** | 正式生效：记录转单备注，写入 CONSULTANT_TRANSFER 时间线 |
| 转单时间线记录 | 无 | 自动写入 CONSULTANT_TRANSFER 事件 |
| 错误码 581042 | 不存在 | **新增**，targetConsultantId 校验失败时返回 |

---

## ⑪ 影响评估 / 回滚

### ⚠️ 破坏兼容风险（高优先级确认）

**接口 B 转单字段行为变更是破坏性变更。**

请前端确认：
1. 现有「修改订单」表单是否已在传 `targetConsultantId` 字段？
   - 若**已在传**：上线后该字段会真实改变订单归属，需评估是否符合预期，或在前端侧暂时移除该字段传入，等转单 UI 正式上线再接入。
   - 若**未传**：无影响。
2. `transferReason` 同上评估逻辑。

### 前端同步上线要求

- **接口 A 时间线**：新枚举值前端若不处理，时间线条目的 label/icon 会命中 fallback。建议按 §⑥ 枚举表逐一补充渲染逻辑（content 文本由后端返回，直接展示即可）。
- **接口 B 转单**：确认现有表单是否已传转单字段，按需调整后再上线。

### 回滚方案

- 后端可通过 Nacos 开关关闭时间线新事件写入（不影响已有记录）。
- 接口 B 转单生效逻辑如需紧急回滚，联系后端 yst 单独发布回滚版本。

---

## ⑫ 注意事项

1. **时间线 content 文本由后端生成**，前端直接渲染 records[].content 字符串，无需前端拼接或翻译枚举值。
2. **eventType 枚举将随业务持续扩展**，前端建议用 map/switch 处理已知值，对未知 eventType 做 fallback 渲染（展示 content 文本而不崩溃）。
3. **INFO_ADJUST 和 REFUND_RECEIVED 并非新枚举值**，只是新增了触发场景。历史订单在本次上线前对应操作无时间线记录，属预期行为。
4. **接口 B 转单字段传 null 或不传**等同于「不转单」，与历史行为一致。
5. **转单不改变订单主状态**，前端进度条/状态显示无需因转单刷新。

---

## ⑬ 关联 / 联系人

### Issue

- [#4419](https://git.1814.love:8443/wx/HL/issues/4419)
- [#4423](https://git.1814.love:8443/wx/HL/issues/4423)
- [#4428](https://git.1814.love:8443/wx/HL/issues/4428)
- [#4446](https://git.1814.love:8443/wx/HL/issues/4446)
- [#4451](https://git.1814.love:8443/wx/HL/issues/4451)
- [#4459](https://git.1814.love:8443/wx/HL/issues/4459)

### PR

- [#4422](https://git.1814.love:8443/wx/HL/pulls/4422)
- [#4426](https://git.1814.love:8443/wx/HL/pulls/4426)
- [#4434](https://git.1814.love:8443/wx/HL/pulls/4434)
- [#4449](https://git.1814.love:8443/wx/HL/pulls/4449)
- [#4454](https://git.1814.love:8443/wx/HL/pulls/4454)
- [#4464](https://git.1814.love:8443/wx/HL/pulls/4464)

### 后端负责人

yst（腰苏图）
