# 【修改接口·小程序端】出行人类型改为由出生日期自动派生，birthday 必填、travelerType 入参移除 (#3976)

> **PR**: #3981 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-18

## 1. 接口背景

小程序客户出行人补全接口（batch-edit）原先要求客户端传入 `travelerType`（出行人类型枚举值）。本次调整将出行人类型的计算权收归后端：**客户端只需传 `birthday`（出生日期），后端按年龄段自动派生 `travelerType`**，入参不再接受 `travelerType` 字段（传了也会被忽略）。

`birthday` 同步从选填升级为**必填**，不传报 400。响应 VO 不变，`travelerType` / `travelerTypeName` 仍正常返回，展示层无需改动。

> 本次为破坏性入参变更：移除 `travelerType` 入参 + `birthday` 改必填。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 客户出行人补全 | POST | /v3/internal/mp/order/{id}/traveler/batch-edit | 入参破坏性变更 | 出行人对象移除 `travelerType`，`birthday` 改必填 |

## 3. 接口详情

### 3.1 客户出行人补全（POST /v3/internal/mp/order/{id}/traveler/batch-edit）

- **使用场景**：小程序用户自助补全出行信息时，批量 upsert 出行人列表（id=null 新增，id 有值则更新）。
- **认证**：需要微信登录态 JWT（C 端 token）。
- **幂等性**：否（写入操作）。
- **限流**：无。

入参中每个出行人对象移除 `travelerType` 字段，`birthday` 改为必填。响应 VO 不变。

## 4. 接口入参

### 4.1 路径参数 / Query 参数

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String（Long） | 是 | 路径参数，订单 ID |

### 4.2 请求体字段

请求体外层结构示例：

```json
{
  "travelers": [{}]
}
```

**出行人对象字段表**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | String（Long） | 否 | null 或不传=新增；有值=更新已有出行人 |
| name | String | 否 | 出行人姓名 |
| gender | String | 否 | 性别字典码（1=男 / 2=女 / 0=未知） |
| birthday | String | **是** | 出生日期，格式 yyyy-MM-dd，不能晚于今天。**本次改为必填** |
| idType | String | 否 | 证件类型枚举值，见第 6 节 |
| idNo | String | 否 | 证件号码（明文） |
| nationality | String | 否 | 国籍 |
| race | String | 否 | 民族 |
| phone | String | 否 | 手机号 |
| emergencyContact | String | 否 | 紧急联系人 |
| emergencyPhone | String | 否 | 紧急联系人电话 |
| roomGroupNo | Integer | 否 | 房间分组编号（团期订单使用） |
| travelerType（已移除） | — | — | 入参已移除，传入将被忽略；后端按 birthday 自动派生 |

## 5. 出参（响应）

响应 VO 结构**不变**，`travelerType` 和 `travelerTypeName` 仍正常返回。

### 5.1 响应列表元素（TravelerVO 关键字段）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 出行人记录 ID |
| orderId | String | 所属订单 ID |
| travelerType | String | 出行人类型枚举值（后端由 birthday 派生后返回） |
| travelerTypeName | String | 出行人类型中文名（成人 / 儿童 / 小童 / 幼童） |
| name | String | 出行人姓名 |
| birthday | String | 出生日期（yyyy-MM-dd） |
| idType | String | 证件类型枚举值 |
| idTypeName | String | 证件类型中文名 |
| idCardMasked | String | 证件号（脱敏后） |
| gender | String | 性别字典码 |
| nationality | String | 国籍 |

## 6. 枚举 / 数据字典

### 6.1 travelerType 派生规则（后端自动计算，前端仅需理解出参含义）

| 年龄段（按 birthday 计算） | travelerType 枚举值 | travelerTypeName |
|--------------------------|-------------------|-----------------|
| 0-1 岁（未满 2 周岁） | BABY | 幼童 |
| 2-6 岁（未满 7 周岁） | YOUNG_CHILD | 小童 |
| 7-17 岁（未满 18 周岁） | CHILD | 儿童 |
| 18 岁及以上 | ADULT | 成人 |

### 6.2 idType（证件类型字典：id_card_type）

| 枚举值 | 中文名 | 说明 |
|--------|--------|------|
| ID_CARD | 身份证 | 中国居民身份证 |
| PASSPORT | 护照 | 中外护照 |
| BIRTH_CERT | 出生证明 | 婴幼儿出生医学证明 |
| HK_MACAU | 港澳通行证 | 港澳居民来往内地通行证 |
| TAIWAN | 台胞证 | 台湾居民来往大陆通行证 |
| MILITARY | 军官证 | 军人证件 |

## 7. 错误码

| code | 含义 | 触发场景 |
|------|------|----------|
| 400 | 出生日期不能为空 | 出行人对象未传 birthday 或传 null |
| 400 | 出生日期不能晚于今天 | birthday 传入了未来日期 |
| 581201 | 订单不存在 | orderId 无效 |
| 581200 | 出行人不存在 | 传了无效的出行人 id（更新场景） |
| 401 | 未认证 | 未携带或微信 JWT 过期 |
| 403 | 无权限 | 当前用户无权操作该订单的出行人 |

## 8. 示例（3 组：典型 / 边界 / 异常）

### 8.1 典型成功 — 补全两位出行人（成人 + 儿童，含 birthday，不传 travelerType）

请求：POST /v3/internal/mp/order/2067178767255560193/traveler/batch-edit，Authorization: Bearer <mp-token>

请求体示例：

```json
{
  "travelers": [
    {
      "id": null,
      "name": "张三",
      "gender": "1",
      "birthday": "1990-05-20",
      "idType": "ID_CARD",
      "idNo": "110101199005201234"
    },
    {
      "id": null,
      "name": "张小宝",
      "gender": "1",
      "birthday": "2019-08-10",
      "idType": "BIRTH_CERT",
      "idNo": "P110101201908101234"
    }
  ]
}
```

响应示例：

```json
{
  "code": 200,
  "data": [
    {
      "id": "2067178767255560201",
      "orderId": "2067178767255560193",
      "travelerType": "ADULT",
      "travelerTypeName": "成人",
      "name": "张三",
      "birthday": "1990-05-20",
      "idType": "ID_CARD",
      "idTypeName": "身份证",
      "idCardMasked": "110***********1234",
      "gender": "1"
    },
    {
      "id": "2067178767255560202",
      "orderId": "2067178767255560193",
      "travelerType": "CHILD",
      "travelerTypeName": "儿童",
      "name": "张小宝",
      "birthday": "2019-08-10",
      "idType": "BIRTH_CERT",
      "idTypeName": "出生证明",
      "idCardMasked": "P1101012019****1234",
      "gender": "1"
    }
  ],
  "message": "ok",
  "success": true
}
```

### 8.2 边界情况 — 幼童（birthday 未满 2 周岁，派生 BABY）

请求体示例：

```json
{
  "travelers": [
    {
      "id": null,
      "name": "李小婴",
      "gender": "2",
      "birthday": "2025-06-01",
      "idType": "BIRTH_CERT",
      "idNo": "P110101202506011234"
    }
  ]
}
```

响应示例：

```json
{
  "code": 200,
  "data": [
    {
      "id": "2067178767255560210",
      "orderId": "2067178767255560193",
      "travelerType": "BABY",
      "travelerTypeName": "幼童",
      "name": "李小婴",
      "birthday": "2025-06-01",
      "idType": "BIRTH_CERT",
      "idTypeName": "出生证明",
      "idCardMasked": "P1101012025****1234",
      "gender": "2"
    }
  ],
  "message": "ok",
  "success": true
}
```

### 8.3 业务失败 — birthday 缺失，报出生日期不能为空

请求体示例（缺少 birthday）：

```json
{
  "travelers": [
    {
      "name": "王五",
      "gender": "1"
    }
  ]
}
```

响应示例：

```json
{
  "code": 400,
  "data": null,
  "message": "出生日期不能为空",
  "success": false
}
```

## 9. 业务边界

- 适用：用户已登录且有权限操作该订单，且订单处于可编辑出行人的状态时可调用。
- 不适用：订单已完成（COMPLETED）或已取消（CANCELLED）后，出行人信息不可再写入。
- 特殊边界：birthday 为整周岁当天（如刚满 2 岁、7 岁、18 岁生日当天）时，当天即按新档位计算（满龄升档）。
- 特殊边界：如果小程序端表单中仍有 travelerType 字段的存储或传入，不影响功能（后端忽略），建议同步清理。
- 特殊边界：birthday 只允许不晚于今天的日期，传入未来日期（包括明天）报 400。
- 特殊边界：birthday 格式须为 yyyy-MM-dd，格式错误报 400。

## 10. 修改前后对比

### 10.1 字段级对比（入参）

| 字段 | 改前 | 改后 |
|------|------|------|
| travelerType（入参） | 选填，由客户端传入，控制出行人类型 | 已移除，传入被忽略 |
| birthday（入参） | 选填 | 必填，不传报 400 |

### 10.2 字段级对比（出参，不变）

| 字段 | 改前 | 改后 |
|------|------|------|
| travelerType（出参） | 返回客户端传入值 | 返回后端按 birthday 派生值（语义不变） |
| travelerTypeName（出参） | 返回中文名 | 不变 |
| birthday（出参） | 原样返回 | 不变 |

### 10.3 行为级对比

| 行为 | 改前 | 改后 |
|------|------|------|
| 出行人类型来源 | 客户端传入 travelerType，后端直接存储 | 后端按 birthday 自动派生，客户端无需传 |
| birthday 必填性 | 选填，不传不报错 | 必填，不传报 400 |

## 11. 影响评估 / 回滚

### 11.1 影响评估

- **是否破坏向后兼容**：是，birthday 改必填属破坏性变更。旧版小程序表单若未传 birthday，保存时报 400。
- **小程序是否必须同步上线**：是。调用 batch-edit 接口时，必须确保每个出行人对象带 birthday；原有 travelerType 入参代码可同步清理。

### 11.2 回滚方案

- **回滚方式**：revert PR #3981 并重新部署 hl-order-service-v3，恢复 travelerType 入参有效 + birthday 选填的旧行为。

## 12. 注意事项

- 出行人类型选择器如果仅用于控制 travelerType 入参，本次可直接移除，出行人类型由 birthday 派生后在响应中正常回显，展示无需调整。
- birthday 格式严格为 yyyy-MM-dd（如 1990-05-20），不接受时间戳或其他格式。
- 响应中的 travelerType / travelerTypeName 仍正常返回，出行人卡片上的类型标签展示逻辑无需改动。
- 年龄以服务器当天日期（北京时间）计算，生日当天视为满周岁。

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#3976](https://git.1814.love:8443/wx/HL/issues/3976)
- **PR**: [#3981](https://git.1814.love:8443/wx/HL/pulls/3981)
- **Merge commit**: [8209c5702](https://git.1814.love:8443/wx/HL/commit/8209c5702)

### 13.2 联系人

- **后端负责人**: @yaosutu
