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

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

## 1. 接口背景

出行人保存相关接口（补全出行信息 / 批量 upsert / 单个新增）原先要求前端显式传入 `travelerType`（成人 / 儿童 / 小童 / 幼童）。本次重构将出行人类型的计算权收归后端：**前端只需传 `birthday`（出生日期），后端按年龄段自动派生 `travelerType`**，前端入参不再接受 `travelerType` 字段（传了也会被忽略）。

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

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

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 补全出行信息 | PUT | /v3/admin/order/{id}/traveler-info | 入参破坏性变更 | 出行人对象移除 `travelerType`，`birthday` 改必填 |
| 2 | 批量 upsert 出行人 | POST | /v3/admin/order/{id}/traveler/batch-edit | 入参破坏性变更 | 出行人对象移除 `travelerType`，`birthday` 改必填 |
| 3 | 单个出行人新增 | POST | /v3/admin/order/{id}/traveler/add | 入参破坏性变更 | 请求体移除 `travelerType`，`birthday` 改必填 |

## 3. 接口详情

### 3.1 补全出行信息（PUT /v3/admin/order/{id}/traveler-info）

- **使用场景**：管理后台「补全出行信息」功能页，全量同步出行人列表 + 紧急联系人 + 备注，一次性提交。
- **认证**：需要 JWT（管理员角色）。
- **幂等性**：否（写入操作，全量覆盖出行人列表）。
- **限流**：无。

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

### 3.2 批量 upsert 出行人（POST /v3/admin/order/{id}/traveler/batch-edit）

- **使用场景**：管理后台出行人 Tab 批量录入 / 编辑出行人（id=null 新增，id 有值则更新）。
- **认证**：需要 JWT（管理员角色）。
- **幂等性**：否（写入操作）。
- **限流**：无。

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

### 3.3 单个出行人新增（POST /v3/admin/order/{id}/traveler/add）

- **使用场景**：管理后台出行人 Tab 单条新增出行人。
- **认证**：需要 JWT（管理员角色）。
- **幂等性**：否（写入操作）。
- **限流**：无。

请求体移除 `travelerType` 字段，`birthday` 改为必填。出参 TravelerVO 不变（仍含 travelerType / travelerTypeName）。

## 4. 接口入参

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

| 接口 | 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|------|
| PUT /v3/admin/order/{id}/traveler-info | id | String（Long） | 是 | 路径参数，订单 ID |
| POST /v3/admin/order/{id}/traveler/batch-edit | id | String（Long） | 是 | 路径参数，订单 ID |
| POST /v3/admin/order/{id}/traveler/add | id | String（Long） | 是 | 路径参数，订单 ID |

### 4.2 请求体字段

接口 1（PUT traveler-info）外层结构示例：

```json
{
  "travelers": [{}],
  "emergencyContact": "张三",
  "emergencyPhone": "13800138000",
  "remark": "备注"
}
```

接口 2（POST batch-edit）外层结构示例：

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

接口 3（POST add）请求体即为单个出行人对象（见下表）。

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

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 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 | 否 | 紧急联系人（接口 3 单独字段；接口 1/2 在请求体外层） |
| emergencyPhone | String | 否 | 紧急联系人电话（同上） |
| roomGroupNo | Integer | 否 | 房间分组编号（团期订单使用） |
| travelerType（已移除） | — | — | 入参已移除，传入将被忽略；后端按 birthday 自动派生 |

## 5. 出参（响应）

出参 VO 结构**不变**，travelerType 和 travelerTypeName 仍正常返回。

### 5.1 接口 2、3 出参（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 | 国籍 |

### 5.2 接口 1（PUT traveler-info）出参

Result<Void>，成功时 data 为 null。

## 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 | 出行人不存在 | batch-edit 中传了无效的出行人 id（更新场景） |
| 401 | 未认证 | 未携带或 JWT 过期 |
| 403 | 无权限 | 当前角色无此操作权限 |

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

### 8.1 典型成功 — batch-edit 批量保存（含 birthday，不传 travelerType）

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

请求体示例：

```json
{
  "travelers": [
    {
      "id": null,
      "name": "张三",
      "gender": "1",
      "birthday": "1990-05-20",
      "idType": "ID_CARD",
      "idNo": "110101199005201234"
    },
    {
      "id": null,
      "name": "张小宝",
      "gender": "1",
      "birthday": "2022-03-15",
      "idType": "BIRTH_CERT",
      "idNo": "P110101202203151234"
    }
  ]
}
```

响应示例：

```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": "YOUNG_CHILD",
      "travelerTypeName": "小童",
      "name": "张小宝",
      "birthday": "2022-03-15",
      "idType": "BIRTH_CERT",
      "idTypeName": "出生证明",
      "idCardMasked": "P1101012022****1234",
      "gender": "1"
    }
  ],
  "message": "ok",
  "success": true
}
```

### 8.2 边界情况 — 单个新增幼童（birthday 刚满 1 岁，派生 BABY）

请求体示例（POST /v3/admin/order/{id}/traveler/add）：

```json
{
  "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。
- **前端是否必须同步上线**：是。三个写入接口（traveler-info / batch-edit / add）调用时都需确保传入 birthday；原有 travelerType 入参代码可同步清理。

### 11.2 回滚方案

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

## 12. 注意事项

- 出行人类型选择器（下拉 / radio）如果仅用于控制 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
