# 出行人补全去 travelerType:按生日自动推断 + 不符企微提醒定制师

> **服务**: hl-mp-service(8085)+ hl-order-service-v2(8084)
> **PR**: #1892
> **Issue**: #1889
> **日期**: 2026-05-08
> **影响范围**: 小程序补全出行人页 / 管理端出行人录入弹窗 / 管理端编辑订单弹窗 / 修订单接口 / 管理端创建订单接口

---

## ⚠️ 关键变化(C 端 + 管理端必读)

**前端不再传 `travelerType`**(请求体里有也会被后端忽略)。后端按 `birthday` 字典 `traveler_type_age_rule` 自动推断:

- 身份证证件:**`birthday` 由后端从证件号反推**,前端可不传(传了也以证件号为准)
- 非身份证证件(护照/港澳台/其他):**`birthday` 必传**,不传返 **400 错误码 `504006`** "非身份证证件请填写出生日期"
- 身份证号校验位失败:返 **400 错误码 `504007`** "身份证号校验失败"

**业务规则放宽**:补全阶段不再校验"该类型已加人数 + 1 不得超订单声明"(原 504002 TYPE_COUNT_EXCEEDED 路径已废弃)。如需声明与实际不符的拦截,改由企微提醒定制师在"编辑订单"弹窗调整 4 个 count(`adultCount/childCount/youngChildCount/babyCount`)。

---

## 一、背景

下单时已按类型选过人数(决定价格),补全时又按类型选人是冗余动作。"3 成人 1 儿童下单 + 4 个成人证件补全" 原会被强校验 400 阻断;现在 C 端用户可用 4 个空位顺序录入,后端自动按生日推断 type 写入 `OrderTraveler.travelerType` 列。

12301 报备、保险、合同、详情排序的 `travelerType` 全部以"实际生日推断"为准(写入数据库的值即为下游消费的值)。

---

## 二、变更接口清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | C 端新增订单出行人 | POST | `/mp/order/{orderId}/traveler/add` | 请求体删字段 | `travelerType` 废弃,后端自动推断 |
| 2 | C 端修改订单出行人 | PUT | `/mp/order/{orderId}/traveler/{travelerId}` | 请求体删字段 | 同上 |
| 3 | C 端修改订单 | PUT | `/mp/order/{orderId}` | 请求体删字段 | `travelers[].travelerType` 废弃 |
| 4 | 管理端新增订单出行人 | POST | `/admin/order/{orderId}/traveler/add` | 请求体删字段 | 同 #1 |
| 5 | 管理端修改订单出行人 | PUT | `/admin/order/{orderId}/traveler/{travelerId}` | 请求体删字段 | 同 #2 |
| 6 | 管理端创建订单 | POST | `/admin/order/create` | 请求体删字段 | `travelers[].travelerType` 废弃 |
| 7 | 管理端编辑订单 | PUT | `/admin/order/{orderId}` | **行为新增** | 4 个 count 任一被改,且出行人已补全过 → 触发企微提醒比对(若不一致) |
| 8 | 12301 团队上报预填 | GET | `/admin/order/{orderId}/team-report/prefill` | **行为变更** | `peopleNums`/`childNums` 改为按 `touristList` 实际推断派生(原按 OrderInfo 4 count) |

---

## 三、接口详情(关键变化)

### 1. C 端新增订单出行人 `POST /mp/order/{orderId}/traveler/add`

**VO**: `MpOrderTravelerSaveReqVO`

#### 入参变化

| 字段 | 旧 | 新 |
|------|----|----|
| `travelerType` | 可选,默认 ADULT | **删除**(后端忽略) |
| `birthday` | 可选 | **身份证场景可不传(后端反推);其他证件必传**,否则 400 `504006` |
| `name`、`idCardNo`、`idCardType`、`phone`、`gender`、`nationality`、`race`、`roomGroupNo`、`emergencyContact`、`emergencyPhone`、`email` | 不变 | 不变 |

#### 错误码新增

| 码 | 文案 | 触发 |
|---|---|---|
| 504006 | 非身份证证件请填写出生日期 | 非身份证证件未传 birthday |
| 504007 | 身份证号校验失败 | 身份证 18 位校验位错误 |
| 504002 | (原 TYPE_COUNT_EXCEEDED) | **本期已废弃**,不再返回 |

#### 请求示例(身份证)

```json
{
  "name": "张三",
  "idCardType": "ID_CARD",
  "idCardNo": "110101199001011234",
  "phone": "13800000000"
}
```

`birthday` 不传 → 后端从 `idCardNo` 反推 `1990-01-01` → 字典推断 `ADULT`。

#### 请求示例(护照,birthday 必传)

```json
{
  "name": "John Doe",
  "idCardType": "PASSPORT",
  "idCardNo": "E12345678",
  "birthday": "2020-06-01"
}
```

不传 `birthday` 则 400 `504006`。

#### 出参

不变。响应仍含 `travelerType` + `travelerTypeLabel`(后端自动写入)。

---

### 7. 管理端编辑订单 `PUT /admin/order/{orderId}` —— 行为新增

**VO**: `OrderEditReqVO`

`adultCount/childCount/youngChildCount/babyCount` 4 字段已支持(无变化),编辑订单弹窗"出行人数"区域 UI 已存在。

**新增触发**:任一 count 字段非空提交,且订单 `processStatus` 已过 `PENDING_TRAVELER_INFO`(即出行人已补全过) → afterCommit 比对声明 vs 实际 → 不一致触发企微提醒定制师。

**何时收到通知**(同样规则也适用接口 #1-#6 补全完成时):
- declared(OrderInfo 4 count)与 actual(OrderTraveler 实际生日推断 type 分布)不一致
- `OrderInfo.customizerId` 非空,且该 admin 配置了 `wechatUserid`
- 上述两条均满足时 → 企微推送给定制师 + 自动落 `notification_log`(管理后台"日志监控 → 企微客户提醒日志"页面可查)

**通知文案**:

```
【出行人类型不符提醒】
订单号:HL20260508xxx
产品:草原 5 日游
联系人:张三 13800000000
出发日期:2026-06-01
下单声明:3 成人 1 儿童 0 幼儿 0 婴儿
实际证件:4 成人 0 儿童 0 幼儿 0 婴儿
请到管理端订单详情核实并按需调整人数。
```

---

### 8. 12301 团队上报预填 `GET /admin/order/{orderId}/team-report/prefill` —— 行为变更

**字段**: `peopleNums` / `childNums`

| 字段 | 旧来源 | 新来源 |
|---|---|---|
| `peopleNums` | `adultCount + childCount + youngChildCount + babyCount`(OrderInfo) | `touristList.size()`(实际推断) |
| `childNums` | `childCount + youngChildCount + babyCount`(OrderInfo) | `touristList.stream().filter(t -> isChild==1).count()` |

**理由**:声明与实际不符时(用户用错位证件),走 OrderInfo 会导致 12301 平台校验"成人+儿童≠总人数"失败。新口径全部按实际生日推断,自洽通过 12301 校验。

---

## 四、前端改动建议(mmg)

### 小程序(C 端)
- 补全出行人页:**移除"成人/儿童/小童/幼儿" Tab/分组**,按下单总人数(`adult+child+young+baby = N`)开 N 个空位顺序录入
- 补全表单:**身份证证件 birthday 可不显示**(后端反推);**护照/港澳台/其他证件 birthday 输入框必填**,400 错误码 504006 文案直接展示
- 详情页:`travelerType` + `travelerTypeLabel` 由后端返回,前端继续展示(label 仍是"成人/儿童/幼儿/婴儿",前端可自映射为 UI 用的"成人/儿童/小童/幼儿")

### 管理端
- 录入/编辑出行人弹窗:**移除"出行人类型"下拉**(后端自动推断)
- 创建订单:`travelers[].travelerType` 不传(传了后端忽略)
- 编辑订单弹窗"出行人数"4 个 count 输入框**已存在**,**本期无需改 UI** — 定制师收到企微通知后直接进入此弹窗调整即可

### 错误码处理
- `504006 BIRTHDAY_REQUIRED` → toast/dialog 显示文案"非身份证证件请填写出生日期"
- `504007 ID_CARD_INVALID` → toast/dialog 显示文案"身份证号校验失败"

---

## 五、向后兼容

- 老 App 仍传 `travelerType` 字段:**Jackson 默认忽略未知字段**(项目已设),不会 400
- `OrderTraveler.traveler_type` 列保留,Resolver 自动写入,下游消费无变化
- `TravelerType` 枚举(ADULT/CHILD/YOUNG_CHILD/BABY)保留,前端 label 三套命名不一致是已有问题,后续单独治理

---

## 六、@mmg 关注点

1. 补全页 UI 改造(分组 → 顺序录入)
2. 非身份证场景 birthday 输入框必填校验
3. 管理端三处弹窗去掉"出行人类型"下拉
4. 错误码 504006/504007 文案接入
5. 编辑订单弹窗 UI 不动,仅文案可优化(可选:提示"如收到出行人类型不符提醒,请在此调整人数")
