diff --git a/changelogs/2026-05/08_feat_traveler_auto_type_resolve.md b/changelogs/2026-05/08_feat_traveler_auto_type_resolve.md new file mode 100644 index 0000000..2e07389 --- /dev/null +++ b/changelogs/2026-05/08_feat_traveler_auto_type_resolve.md @@ -0,0 +1,173 @@ +# 出行人补全去 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 不动,仅文案可优化(可选:提示"如收到出行人类型不符提醒,请在此调整人数")