# 【新增接口·管理后台】v3 traveler 模块 §2（出行人 + 大交通）

> **更新时间**: 2026-05-18
> **端类型**: 管理后台
> **设计文档版本**: v5.50（API-SPEC / SRS / DETAIL-DESIGN / DATABASE-SCHEMA 4 份 HTML 同步）

---

## 0. 模块全貌

| 子模块 | 含接口 | 接口数 | 状态 |
|---|---|---|---|
| **§2A 出行人 admin CRUD** | §2.1 列表 / §2.2 批量编辑 / §2.3 新增 / §2.4 软删 | **4** | ✅ |
| **§2B 大交通批次 admin CRUD** | §2.6.1 列表 / §2.6.2 新增 / §2.6.3 编辑 / §2.6.4 软删 | **4** | ✅ |
| **§2C 动词类操作** | §2.8 smart-parse（⚠️ 待新建，Issue #2526）/ §2.9 validate | **2** | §2.9 ✅，§2.8 ⏳ wx 实现中 |
| **§2 合计** | — | **10** | ✅ 本次推送（§2.8 字段先定，wx 实现后接口可立即对接） |

> **不在本 changelog 范围内**：§2.7 `GET /v3/internal/order/orders/{orderId}/travelers`（internal Feign 跨服务查出行人 + 解密审计）属于**后端 changelog 范畴**（受众=其他后端服务/运维，不是前端），将单独推到 `hl-backend-changelog` 仓库。

---

## 1. 接口背景

订单服务 v3 traveler 模块（hl-order-service-v3）管理订单关联的**出行人信息**（含敏感字段：身份证 / 手机 / 紧急联系人）+ **大交通批次**（接送站 / 航班 / 火车 / 自驾）。

业务边界：
- 出行人不在 §1.1 创单接口里传，创单后通过 §2 单独添加（v4.8 取消占位行模型）
- admin 端明文返回 idNo / phone / emergencyPhone（仅 B 端定制师可见，JWT 鉴权保护）
- 大交通通过 `order_transport_plan_traveler` 桥接表（M:N）关联出行人，支持「一家分两批到达」场景

本次推送 §2 模块管理后台 10 个接口，对应管理后台原型 F20 / F21 / F27（详情概览 + 出行人补全 + 接送站 + 大交通登记弹窗）。`§2.7 internal feign` 不在本文档范围（见 §0 说明）。

---

## 2. 变更清单

| # | § | 接口名 | 方法 | 路径 |
|---|---|--------|------|------|
| 1 | 2.1 | 出行人列表 | GET | `/v3/admin/order/{id}/traveler/list` |
| 2 | 2.2 | 出行人批量编辑（upsert） | POST | `/v3/admin/order/{id}/traveler/batch-edit` |
| 3 | 2.3 | 单个出行人新增 | POST | `/v3/admin/order/{id}/traveler/add` |
| 4 | 2.4 | 出行人软删 | POST | `/v3/admin/order/{id}/traveler/{travelerId}/delete` |
| 5 | 2.6.1 | 大交通批次列表 | GET | `/v3/admin/order/{id}/transport-plans` |
| 6 | 2.6.2 | 大交通批次新增 | POST | `/v3/admin/order/{id}/transport-plans` |
| 7 | 2.6.3 | 大交通批次编辑 | PUT | `/v3/admin/order/{id}/transport-plans/{planId}` |
| 8 | 2.6.4 | 大交通批次软删 | DELETE | `/v3/admin/order/{id}/transport-plans/{planId}` |
| 9 | 2.8 ⏳ | 出行人智能批量解析 | POST | `/v3/admin/order/{id}/traveler/smart-parse` |
| 10 | 2.9 | 出行人信息校验 | GET | `/v3/admin/order/{id}/traveler/validate` |

> §2.7 internal feign 接口不在本表（属后端 changelog）。

---

## 3. 接口详情

> 每个接口自包含：使用场景 / 入参 / 出参 / 错误码 / 业务边界 / 示例。
> 跨接口共享枚举集中在 §6。

---

### 3.1 §2.1 出行人列表

**路径**：`GET /v3/admin/order/{id}/traveler/list`
**使用场景**：详情页 Tab 1 出行人区块 / F27 出行人补全页
**认证**：JWT（admin 角色 + 公司隔离）
**敏感字段**：`idNo` / `phone` / `emergencyPhone` **明文返回**（admin JWT 鉴权保护）

#### 入参

`id` (path, Long) — 订单 ID

#### 出参（`Result<List<TravelerVO>>`，每条 16 字段）

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | String | 出行人 ID |
| `orderId` | String | 订单 ID |
| `travelerType` | String | 枚举见 §6.1（ADULT / CHILD / YOUNG_CHILD / BABY） |
| `name` | String? | 姓名（占位行为 null） |
| `gender` | String | 枚举见 §6.2（MALE / FEMALE / UNKNOWN） |
| `birthday` | LocalDate? | 出生日期 |
| `idType` | String | 枚举见 §6.3（ID_CARD / PASSPORT / BIRTH_CERT） |
| `idNo` | String? | 证件号（admin 明文） |
| `nationality` | String | 国籍（默认中国） |
| `race` | String | 民族（默认汉族） |
| `phone` | String? | 出行人手机（admin 明文） |
| `emergencyContact` | String? | 紧急联系人姓名 |
| `emergencyPhone` | String? | 紧急联系人电话（admin 明文） |
| `roomGroupNo` | Integer? | 同住分组号 |
| `profileStatus` | String | 枚举见 §6.4（PENDING / COMPLETED） |
| `transportPlanIds` | List\<Long\> | 关联的大交通批次 ID 列表 |

> ⚠️ **transportPlanIds 序列化规则**：后端字段类型 `List<Long>`，但 HL 全局 Jackson `NumberSerializer` 会自动把超过 JS 安全整数（2^53-1）的 Long 转 String 输出。大交通批次 ID 是雪花 ID（19 位），**实际响应中一定是 String 数组**；下面示例值 `80012345`（8 位短整数）仅为可读性目的，**生产值类似 `"80012345678901234"`**。前端按 String 数组接收。

#### 错误码

| code | 含义 |
|------|------|
| `581020` | 订单不存在 |
| `581021` | 无权访问该订单（公司隔离） |

#### 示例

**典型 - 请求**：

```http
GET /v3/admin/order/60123456789012/traveler/list
Authorization: Bearer {admin_jwt}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": [
    {
      "id": "70123456789012",
      "orderId": "60123456789012",
      "travelerType": "ADULT",
      "name": "张三",
      "gender": "MALE",
      "birthday": "1985-08-12",
      "idType": "ID_CARD",
      "idNo": "220103198508121234",
      "nationality": "中国",
      "race": "汉族",
      "phone": "13800002046",
      "emergencyContact": "李四",
      "emergencyPhone": "13900008888",
      "roomGroupNo": 1,
      "profileStatus": "COMPLETED",
      "transportPlanIds": ["80012345678901234", "80012345678901235"]
    },
    {
      "id": "70123456789013",
      "orderId": "60123456789012",
      "travelerType": "CHILD",
      "name": null,
      "gender": "UNKNOWN",
      "birthday": null,
      "idType": null,
      "idNo": null,
      "nationality": "中国",
      "race": "汉族",
      "phone": null,
      "emergencyContact": null,
      "emergencyPhone": null,
      "roomGroupNo": null,
      "profileStatus": "PENDING",
      "transportPlanIds": []
    }
  ],
  "msg": "success"
}
```

---

### 3.2 §2.2 出行人批量编辑（upsert 语义）

**路径**：`POST /v3/admin/order/{id}/traveler/batch-edit`
**使用场景**：F27 出行人补全 B 端代填，定制师一次性提交订单内 N 行出行人的字段修改/新增（`id=null` 新增 / `id` 非 null 更新）
**整体事务**：任一行字段校验失败 → 全部回滚

#### 入参（`TravelerBatchEditReqVO`）

| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|------|------|:----:|------|----------|
| `travelers` | List\<TravelerEditItem\> | ✅ | 待修改/新增出行人数组 | `@NotEmpty` `@Size(max=30)` |

`TravelerEditItem`（12 字段）：

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `id` | Long | ❌ | 出行人 ID（null=新增 / 非 null=更新） |
| `travelerType` | String | 新增必填 | 枚举见 §6.1（更新时 null 保留原值） |
| `name` | String | ❌ | 姓名 |
| `gender` | String | ❌ | 枚举见 §6.2 |
| `birthday` | LocalDate | ❌ | 出生日期 |
| `idType` | String | ❌ | 枚举见 §6.3 |
| `idNo` | String | ❌ | 证件号（明文传） |
| `nationality` | String | ❌ | 国籍（不可为空字符串） |
| `race` | String | ❌ | 民族（不可为空字符串） |
| `phone` | String | ❌ | 手机（明文传） |
| `emergencyContact` | String | ❌ | 紧急联系人姓名 |
| `emergencyPhone` | String | ❌ | 紧急联系人电话（明文传） |
| `roomGroupNo` | Integer | ❌ | 同住分组号 |

#### 出参（`Result<TravelerBatchEditRespVO>`）

| 字段 | 类型 | 说明 |
|------|------|------|
| `createdCount` | Integer | 本次新增的出行人数（`id=null` 行） |
| `updatedCount` | Integer | 本次更新的出行人数（`id` 非 null 行） |
| `completedCount` | Integer | 本次操作后变为 COMPLETED 的行数 |
| `pendingCount` | Integer | 仍为 PENDING 的行数 |
| `allCompleted` | Boolean | 该订单所有出行人是否已完善 |

#### 错误码

| code | 含义 |
|------|------|
| `581020` | 订单不存在 |
| `581100` | travelers 列表为空 / 超过 30 |
| `581101` | 字段格式校验失败（idNo 校验和 / phone 格式） |
| `581110` | 更新时出行人 ID 不属于该订单 |
| `581111` | 已签电子合同后禁止改证件号 |
| `581112` | 新增时缺 travelerType |

#### 业务边界

- ✅ **upsert 语义**：单次请求可混合新增 + 更新
- ✅ **整体事务**：任一行失败回滚
- ⚠️ **size 上限 30**：超出走分批多次调用
- ⚠️ **`allCompleted=true` 信号**：所有出行人字段齐 → 触发"已完善"系统标签 / 进入下一阶段

#### 示例

**典型 - 请求**：

```http
POST /v3/admin/order/60123456789012/traveler/batch-edit
Authorization: Bearer {admin_jwt}
Content-Type: application/json

{
  "travelers": [
    {
      "id": 70123456789013,
      "name": "王小明",
      "gender": "MALE",
      "birthday": "2018-06-20",
      "idType": "ID_CARD",
      "idNo": "220103201806201234",
      "nationality": "中国",
      "race": "汉族",
      "phone": "13812342046",
      "roomGroupNo": 1
    },
    {
      "id": null,
      "travelerType": "BABY",
      "name": "王小宝",
      "gender": "FEMALE",
      "birthday": "2024-01-15"
    }
  ]
}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": {
    "createdCount": 1,
    "updatedCount": 1,
    "completedCount": 1,
    "pendingCount": 1,
    "allCompleted": false
  },
  "msg": "success"
}
```

**异常（证件号格式非法 581101） - 响应**：

```json
{ "code": 581101, "data": null, "msg": "证件号校验失败（idNo 校验位错误）" }
```

---

### 3.3 §2.3 单个出行人新增（占位补充）

**路径**：`POST /v3/admin/order/{id}/traveler/add`
**使用场景**：F27 改人数场景（如临时加 1 个未声明儿童）。**会同步 UPDATE `order_main` 对应人数字段**（adultCount / childCount 等）+ 触发后续：保险重算 / 房车需求需重提示

#### 入参（`TravelerCreateReqVO`，12 字段）

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `travelerType` | String | ✅ | 枚举见 §6.1 |
| `name` | String | ❌ | 姓名（可后填） |
| `gender` | String | ❌ | 枚举见 §6.2 |
| `birthday` | LocalDate | ❌ | 出生日期 |
| `idType` | String | ❌ | 枚举见 §6.3 |
| `idNo` | String | ❌ | 证件号（明文传） |
| `nationality` | String | ❌ | 国籍（默认中国） |
| `race` | String | ❌ | 民族（默认汉族） |
| `phone` | String | ❌ | 出行人手机（明文传） |
| `emergencyContact` | String | ❌ | 紧急联系人姓名 |
| `emergencyPhone` | String | ❌ | 紧急联系人电话 |
| `roomGroupNo` | Integer | ❌ | 同住分组号 |

#### 出参（`Result<TravelerVO>`）

字段同 §3.1 列表项，含新建行完整字段。

#### 错误码

| code | 含义 |
|------|------|
| `581020` | 订单不存在 |
| `581115` | 实际人数已等于 `order_main` 声明人数（v4.8 取消占位模型规则） |
| `581116` | 订单状态禁止加人（已结算 / 已取消） |
| `581117` | travelerType 与现有同住分组冲突 |

#### 业务边界

- ✅ **创单后单独添加**：v4.8 取消占位模型，按需 INSERT 新行
- ⚠️ **人数同步**：自动 UPDATE `order_main.<type>Count` +1
- ⚠️ **应用层数量检查**：实际人数 ≥ 声明人数 → `581115`，需要先调整人数声明再加人

#### 示例

**典型 - 请求**：

```http
POST /v3/admin/order/60123456789012/traveler/add
Authorization: Bearer {admin_jwt}
Content-Type: application/json

{
  "travelerType": "CHILD",
  "name": "王小明",
  "gender": "MALE",
  "birthday": "2018-06-20",
  "idType": "ID_CARD",
  "idNo": "220103201806201234",
  "phone": "13812342046",
  "roomGroupNo": 1
}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": {
    "id": "70123456789013",
    "orderId": "60123456789012",
    "travelerType": "CHILD",
    "name": "王小明",
    "gender": "MALE",
    "birthday": "2018-06-20",
    "idType": "ID_CARD",
    "idNo": "220103201806201234",
    "nationality": "中国",
    "race": "汉族",
    "phone": "13812342046",
    "emergencyContact": null,
    "emergencyPhone": null,
    "roomGroupNo": 1,
    "profileStatus": "COMPLETED",
    "transportPlanIds": []
  },
  "msg": "success"
}
```

**异常（人数已满 581115） - 响应**：

```json
{ "code": 581115, "data": null, "msg": "实际人数已达声明上限，请先调整人数再添加" }
```

---

### 3.4 §2.4 出行人软删

**路径**：`POST /v3/admin/order/{id}/traveler/{travelerId}/delete`
**使用场景**：F27 改人数场景（如取消同行 1 人）
**关联**：同步 UPDATE `order_main` 对应人数 -1 + 解除其在 `order_transport_plan_traveler` 桥接表中所有关联
**约束**：**已签电子合同的订单禁止删人**

#### 入参

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `id` | Long | ✅ | 订单 ID（path） |
| `travelerId` | Long | ✅ | 出行人 ID（path） |

#### 出参（`Result<Boolean>`）

返回 `true` 表示软删成功。

#### 错误码

| code | 含义 |
|------|------|
| `581020` | 订单不存在 |
| `581110` | 出行人不属于该订单 |
| `581106` | 已签电子合同，禁止删除出行人（DB 段位让位：581119 已被 `TRAVELER_ID_CARD_DUPLICATE` 占用，文档原 581119 让位至此） |
| `581107` | 出行人是订单最后 1 位成人，禁止删除（DB 段位让位：581120 已被 `TRANSPORT_PLAN_NOT_FOUND` 占用，文档原 581120 让位至此） |

#### 业务边界

- ✅ **软删**：UPDATE `order_traveler.deleted=1`，不真删
- ✅ **级联解除桥接**：UPDATE `order_transport_plan_traveler.deleted=1`
- ❌ **已签合同禁删**：合同强约束 → `581106`
- ❌ **最后成人禁删**：保留至少 1 位成人 → `581107`

#### 示例

**典型 - 请求**：

```http
POST /v3/admin/order/60123456789012/traveler/70123456789013/delete
Authorization: Bearer {admin_jwt}
```

**典型 - 响应**：

```json
{ "code": 200, "data": true, "msg": "success" }
```

**异常（已签合同 581106） - 响应**：

```json
{ "code": 581106, "data": null, "msg": "订单已签电子合同，禁止删除出行人" }
```

---

### 3.5 §2.6.1 大交通批次列表

**路径**：`GET /v3/admin/order/{id}/transport-plans`
**使用场景**：F21 行程安排 Tab "接送站"区块
**业务模型**：一个订单可有 N 个批次（实现"一家分两批到达"），按 `direction` 分 ARRIVAL / DEPARTURE

#### 入参

`id` (path, Long) — 订单 ID

#### 出参（`Result<List<TransportPlanVO>>`）

每条 16 字段 + travelers 嵌套：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | String | 批次 ID |
| `orderId` | String | 订单 ID |
| `direction` | String | 枚举见 §6.5（ARRIVAL / DEPARTURE） |
| `mode` | String | 枚举见 §6.6（TOGETHER / SEPARATE） |
| `transportType` | String | 枚举见 §6.7（FLIGHT / TRAIN / SELF_DRIVE） |
| `transportNo` | String? | 航班号 / 车次号；SELF_DRIVE 时为空 |
| `carrier` | String? | 航司 / 铁路公司 |
| `departStation` | String? | 出发站 |
| `arriveStation` | String? | 到达站 |
| `departTime` | LocalDateTime? | 出发时间（FLIGHT/TRAIN 必有；SELF_DRIVE 为空） |
| `arriveTime` | LocalDateTime? | 到达时间（同上） |
| `selfDrivePeriod` | String? | 枚举见 §6.8（仅 SELF_DRIVE） |
| `selfDriveEta` | LocalDateTime? | 自驾预计到达时间（仅 SELF_DRIVE 可选） |
| `pickupRequired` | Boolean? | 是否需要接送 |
| `pickupRemark` | String? | 接送备注 |
| `travelers` | List\<{id, name}\> | 关联出行人简要（桥接表 JOIN） |
| `remark` | String? | 备注 |

#### 错误码

| code | 含义 |
|------|------|
| `581020` | 订单不存在 |
| `581021` | 无权访问该订单 |

#### 示例

**典型 - 请求**：

```http
GET /v3/admin/order/60123456789012/transport-plans
Authorization: Bearer {admin_jwt}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": [
    {
      "id": "80012345",
      "orderId": "60123456789012",
      "direction": "ARRIVAL",
      "mode": "TOGETHER",
      "transportType": "FLIGHT",
      "transportNo": "CA1234",
      "carrier": "中国国际航空",
      "departStation": "北京首都T3",
      "arriveStation": "长春龙嘉",
      "departTime": "2026-06-01T08:30:00",
      "arriveTime": "2026-06-01T10:15:00",
      "selfDrivePeriod": null,
      "selfDriveEta": null,
      "pickupRequired": true,
      "pickupRemark": "需在 T3 出口举牌接机",
      "travelers": [
        {"id": "70123456789012", "name": "张三"},
        {"id": "70123456789013", "name": "王小明"}
      ],
      "remark": "需要接机举牌"
    },
    {
      "id": "80012346",
      "orderId": "60123456789012",
      "direction": "DEPARTURE",
      "mode": "TOGETHER",
      "transportType": "SELF_DRIVE",
      "transportNo": null,
      "carrier": null,
      "departStation": null,
      "arriveStation": null,
      "departTime": null,
      "arriveTime": null,
      "selfDrivePeriod": "AFTERNOON",
      "selfDriveEta": "2026-06-05T15:00:00",
      "pickupRequired": false,
      "pickupRemark": null,
      "travelers": [
        {"id": "70123456789012", "name": "张三"}
      ],
      "remark": null
    }
  ],
  "msg": "success"
}
```

---

### 3.6 §2.6.2 大交通批次新增

**路径**：`POST /v3/admin/order/{id}/transport-plans`
**使用场景**：F21 大交通登记弹窗 - 新增
**桥接表维护**：同事务 INSERT `order_transport_plan_traveler`（每个 plan 必须关联至少 1 名出行人）

#### 入参（`TransportPlanReqVO`，11 字段）

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `direction` | String | ✅ | 枚举见 §6.5 |
| `transportType` | String | ✅ | 枚举见 §6.7 |
| `transportNo` | String | 条件 | 航班号 / 车次号；SELF_DRIVE 时为空 |
| `carrier` | String | ❌ | 航司 / 铁路公司 |
| `departStation` / `arriveStation` | String | ❌ | 出发 / 到达站 |
| `departTime` / `arriveTime` | LocalDateTime | 条件 | FLIGHT/TRAIN 必填；SELF_DRIVE 为空 |
| `selfDrivePeriod` | String | 条件 | 枚举见 §6.8，仅 SELF_DRIVE |
| `selfDriveEta` | LocalDateTime | ❌ | 仅 SELF_DRIVE 可选 |
| `travelerIds` | List\<Long\> | ✅ | 关联出行人 ID（至少 1 个） |
| `remark` | String | ❌ | 备注（≤500） |

#### 出参（`Result<TransportPlanVO>`）

字段同 §3.5 列表项。

#### 错误码

| code | 含义 |
|------|------|
| `581020` | 订单不存在 |
| `581140` | 字段组合非法（FLIGHT 缺航班号 / SELF_DRIVE 错带 transportNo 等） |
| `581141` | `travelerIds` 含订单外的出行人 |
| `581142` | 出发时间晚于到达时间 |
| `581143` | 同方向同一出行人已在另一 plan |

#### 业务边界

- ✅ **字段组合校验**：FLIGHT/TRAIN 需 transportNo + 出发/到达时间；SELF_DRIVE 不传 transportNo，可传 selfDrivePeriod + selfDriveEta
- ⚠️ **桥接约束**：同方向同一出行人只能在 1 个 plan，不同方向各 1 个

#### 示例

**典型（航班 ARRIVAL） - 请求**：

```http
POST /v3/admin/order/60123456789012/transport-plans
Authorization: Bearer {admin_jwt}
Content-Type: application/json

{
  "direction": "ARRIVAL",
  "transportType": "FLIGHT",
  "transportNo": "CA1234",
  "carrier": "中国国际航空",
  "departStation": "北京首都T3",
  "arriveStation": "长春龙嘉",
  "departTime": "2026-06-01T08:30:00",
  "arriveTime": "2026-06-01T10:15:00",
  "travelerIds": [70123456789012, 70123456789013],
  "remark": "需要接机举牌"
}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": {
    "id": "80012345",
    "orderId": "60123456789012",
    "direction": "ARRIVAL",
    "mode": "TOGETHER",
    "transportType": "FLIGHT",
    "transportNo": "CA1234",
    "carrier": "中国国际航空",
    "departStation": "北京首都T3",
    "arriveStation": "长春龙嘉",
    "departTime": "2026-06-01T08:30:00",
    "arriveTime": "2026-06-01T10:15:00",
    "selfDrivePeriod": null,
    "selfDriveEta": null,
    "pickupRequired": null,
    "pickupRemark": null,
    "travelers": [
      {"id": "70123456789012", "name": "张三"},
      {"id": "70123456789013", "name": "王小明"}
    ],
    "remark": "需要接机举牌"
  },
  "msg": "success"
}
```

**异常（自驾错带 transportNo 581140） - 响应**：

```json
{ "code": 581140, "data": null, "msg": "SELF_DRIVE 类型不应传 transportNo" }
```

---

### 3.7 §2.6.3 大交通批次编辑

**路径**：`PUT /v3/admin/order/{id}/transport-plans/{planId}`
**使用场景**：F21 大交通登记弹窗 - 编辑（全量覆盖含 travelerIds）
**事务**：UPDATE plan + DELETE+INSERT 重建桥接表

#### 入参

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `id` | Long | ✅ | 订单 ID（path） |
| `planId` | Long | ✅ | 批次 ID（path） |
| Body | `TransportPlanReqVO` | ✅ | 同 §3.6 字段（全量覆盖语义） |

#### 出参（`Result<TransportPlanVO>`）

字段同 §3.5。

#### 错误码

| code | 含义 |
|------|------|
| `581140` ~ `581143` | 同 §3.6 字段组合校验 |
| `581144` | plan 不存在 / 不属于该订单 |

#### 业务边界

- ⚠️ **全量覆盖语义**：所有 Req 字段都会替换原值，包括 `travelerIds`
- ⚠️ **桥接表重建**：旧 `order_transport_plan_traveler` 行 DELETE，按新 `travelerIds` INSERT

#### 示例

**典型 - 请求**：

```http
PUT /v3/admin/order/60123456789012/transport-plans/80012345
Authorization: Bearer {admin_jwt}
Content-Type: application/json

{
  "direction": "ARRIVAL",
  "transportType": "FLIGHT",
  "transportNo": "CA1235",
  "carrier": "中国国际航空",
  "departStation": "北京首都T3",
  "arriveStation": "长春龙嘉",
  "departTime": "2026-06-01T10:30:00",
  "arriveTime": "2026-06-01T12:15:00",
  "travelerIds": [70123456789012],
  "remark": "改签后的航班"
}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": {
    "id": "80012345",
    "direction": "ARRIVAL",
    "transportType": "FLIGHT",
    "transportNo": "CA1235",
    "departTime": "2026-06-01T10:30:00",
    "arriveTime": "2026-06-01T12:15:00",
    "travelers": [{"id": "70123456789012", "name": "张三"}],
    "remark": "改签后的航班"
  },
  "msg": "success"
}
```

---

### 3.8 §2.6.4 大交通批次软删

**路径**：`DELETE /v3/admin/order/{id}/transport-plans/{planId}`
**使用场景**：F21 大交通登记弹窗 - 删除
**事务**：UPDATE `order_transport_plan.deleted=1` + UPDATE 桥接表 deleted=1

#### 入参

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `id` | Long | ✅ | 订单 ID（path） |
| `planId` | Long | ✅ | 批次 ID（path） |

#### 出参（`Result<Boolean>`）

返回 `true` 表示软删成功。

#### 错误码

| code | 含义 |
|------|------|
| `581144` | plan 不存在 / 不属于该订单 |

#### 示例

**典型 - 请求**：

```http
DELETE /v3/admin/order/60123456789012/transport-plans/80012345
Authorization: Bearer {admin_jwt}
```

**典型 - 响应**：

```json
{ "code": 200, "data": true, "msg": "success" }
```

---

### 3.9 §2.8 出行人智能批量解析 ⏳

**路径**：`POST /v3/admin/order/{id}/traveler/smart-parse`
**使用场景**：F27 出行人补全 / 一键导入弹窗。定制师粘贴多行文本（姓名+证件号+手机号），后端解析为结构化出行人列表，校验后批量入库
**状态**：⏳ **wx 待实现**（Issue [#2526](https://git.1814.love:8443/wx/HL/issues/2526)）。本节字段先定，wx 实现完即可对接

#### 入参（`TravelerSmartParseReqVO`）

| 字段 | 类型 | 必填 | 说明 |
|------|------|:----:|------|
| `id` | Long | ✅ | 订单 ID（path） |
| `rawText` | String | ✅ | 粘贴的多行原文（≤ 20000 字符） |
| `dryRun` | Boolean | ❌ | true=仅解析不入库（默认 false=解析+入库） |

#### 出参（`Result<TravelerSmartParseRespVO>`）

| 字段 | 类型 | 说明 |
|------|------|------|
| `successCount` | Integer | 解析成功并入库的行数 |
| `failCount` | Integer | 解析失败的行数 |
| `successList` | List\<TravelerVO\> | 成功创建的出行人列表（结构同 §3.1）；dryRun=true 时不含 id |
| `failures` | List\<FailureVO\> | 失败明细：`{lineIndex, maskedSnippet, reason}` |

#### 错误码

| code | 含义 |
|------|------|
| `581131` | 原文超长（> 20000 字符）（DB 段位让位：原文档 581120 已被 `TRANSPORT_PLAN_NOT_FOUND` 占用） |
| `581132` | 原文为空或全行无法解析（DB 段位让位：原文档 581121 改至此） |
| `100501` | 限流触发（10 次/分钟）—— 走 HL 全局 `CommonErrorCode.RATE_LIMITED`，不在 traveler 段位 |
| `581134` | 订单状态不允许批量导入（已结算 / 已取消）（DB 段位让位：原文档 581123 改至此） |

#### 业务边界 + 审计安全

- ✅ **限流**：每 admin 10 次/分钟（`@RateLimiter(count=10, time=60)`）
- ✅ **PII 不落审计**：接口**不挂** `@OperationLog`（避免明文 idNo/phone 写入 audit_log）
- ✅ **错误行脱敏**：`failures[].maskedSnippet` 严格脱敏（idNo 前 6 后 4、phone 前 3 后 4）
- ⚠️ **dryRun=true**：纯解析不落库，前端可用作"试解析预览"
- ⚠️ **dryRun=false**：解析+入库一次完成，INSERT N 行 `order_traveler` + UPDATE `order_main.<type>Count` 同事务

#### 示例

**典型 - 请求**：

```http
POST /v3/admin/order/60123456789012/traveler/smart-parse
Authorization: Bearer {admin_jwt}
Content-Type: application/json

{
  "rawText": "张三 350101199001011234 13800138000\n李四 13900139000 110101199201021235\n王五 110101****1234 138****8765",
  "dryRun": false
}
```

**典型 - 响应**：

```json
{
  "code": 200,
  "data": {
    "successCount": 2,
    "failCount": 1,
    "successList": [
      {"id": "70123456789014", "name": "张三", "travelerType": "ADULT", "profileStatus": "COMPLETED"},
      {"id": "70123456789015", "name": "李四", "travelerType": "ADULT", "profileStatus": "COMPLETED"}
    ],
    "failures": [
      {"lineIndex": 3, "maskedSnippet": "王五 110101****1234 138****8765", "reason": "身份证号校验失败"}
    ]
  },
  "msg": "success"
}
```

**异常（限流 100501） - 响应**：

```json
{ "code": 100501, "data": null, "msg": "请求过于频繁，请稍后再试" }
```

---

### 3.10 §2.9 出行人信息校验

**路径**：`GET /v3/admin/order/{id}/traveler/validate`
**使用场景**：支付前 / 锁单前置校验（同 §1.8 confirm-checklist 的 `TRAVELER_COMPLETE` 项底层依赖）
**校验维度**：(1) 实际出行人数 = 订单声明人数；(2) 每个出行人字段齐全（姓名+身份证+手机+证件类型）

#### 入参

`id` (path, Long) — 订单 ID

#### 出参（`Result<TravelerValidateRespVO>`）

| 字段 | 类型 | 说明 |
|------|------|------|
| `passed` | Boolean | 是否全部通过 |
| `declaredCount` | Integer | 订单声明人数（含所有人群类型） |
| `actualCount` | Integer | 实际 `order_traveler` 行数（未软删） |
| `countMismatch` | Boolean | 人数是否不一致 |
| `incompleteList` | List\<IncompleteTravelerVO\> | 字段不完整的出行人 |

`IncompleteTravelerVO`：

| 字段 | 类型 | 说明 |
|------|------|------|
| `travelerId` | String | 出行人 ID |
| `name` | String | 姓名（脱敏，如 `张*`） |
| `missingFields` | List\<String\> | 缺失字段名列表（如 `["idNo", "phone"]`） |

#### 错误码

| code | 含义 |
|------|------|
| `581124` | 订单不存在 |

#### 业务边界

- ✅ **只读校验**：不入库、不改状态
- ⚠️ **`incompleteList[].name` 脱敏**：响应里姓名仅留首字（如 `张*`），不暴露完整姓名
- ⚠️ **必填判定**：缺 `name` / `idType` / `idNo` / `phone` 任一即进 `incompleteList`

#### 示例

**典型（未通过） - 请求**：

```http
GET /v3/admin/order/60123456789012/traveler/validate
Authorization: Bearer {admin_jwt}
```

**典型（未通过） - 响应**：

```json
{
  "code": 200,
  "data": {
    "passed": false,
    "declaredCount": 3,
    "actualCount": 2,
    "countMismatch": true,
    "incompleteList": [
      {"travelerId": "70123456789013", "name": "张*", "missingFields": ["idNo", "phone"]}
    ]
  },
  "msg": "success"
}
```

**典型（通过） - 响应**：

```json
{
  "code": 200,
  "data": {
    "passed": true,
    "declaredCount": 3,
    "actualCount": 3,
    "countMismatch": false,
    "incompleteList": []
  },
  "msg": "success"
}
```

---

## 6. 枚举 / 数据字典

> 跨接口共享枚举集中列出，按字段分组，标注「使用字段在哪里」。

### 6.1 travelerType（出行人类型）

**使用字段**：§3.1 / §3.2 / §3.3 / §3.9 出入参 `travelerType`

| 值 | 说明 |
|----|------|
| `ADULT` | 成人 |
| `CHILD` | 儿童（有床） |
| `YOUNG_CHILD` | 幼儿（无床/占座） |
| `BABY` | 婴儿（无座） |

### 6.2 gender（性别）

**使用字段**：§3.1 / §3.2 / §3.3 / §3.9 出入参 `gender`

| 值 | 说明 |
|----|------|
| `MALE` | 男 |
| `FEMALE` | 女 |
| `UNKNOWN` | 未填 |

### 6.3 idType（证件类型）

**使用字段**：§3.1 / §3.2 / §3.3 / §3.9 出入参 `idType`

| 值 | 说明 |
|----|------|
| `ID_CARD` | 居民身份证 |
| `PASSPORT` | 护照 |
| `BIRTH_CERT` | 出生证明 |

### 6.4 profileStatus（资料完善状态）

**使用字段**：§3.1 / §3.3 / §3.9 出参 `profileStatus`

| 值 | 说明 |
|----|------|
| `PENDING` | 字段未齐全 |
| `COMPLETED` | 字段已齐全 |

### 6.5 direction（大交通方向）

**使用字段**：§3.5 / §3.6 / §3.7 出入参 `direction`

| 值 | 说明 |
|----|------|
| `ARRIVAL` | 到达（去程） |
| `DEPARTURE` | 返程（离开） |

### 6.6 mode（大交通模式）

**使用字段**：§3.5 出参 `mode`

| 值 | 说明 |
|----|------|
| `TOGETHER` | 一起到达 / 返程 |
| `SEPARATE` | 分批到达 / 返程（一家分两批场景） |

### 6.7 transportType（交通工具类型）

**使用字段**：§3.5 / §3.6 / §3.7 出入参 `transportType`

| 值 | 说明 |
|----|------|
| `FLIGHT` | 航班 |
| `TRAIN` | 火车 |
| `SELF_DRIVE` | 自驾 |

### 6.8 selfDrivePeriod（自驾时段）

**使用字段**：§3.5 / §3.6 / §3.7 出入参 `selfDrivePeriod`（仅 SELF_DRIVE 类型）

| 值 | 说明 |
|----|------|
| `MORNING` | 上午 |
| `AFTERNOON` | 下午 |
| `EVENING` | 晚上 |

### 6.9 missingFields（缺失字段名）

**使用字段**：§3.10 出参 `incompleteList[].missingFields[]`

可能值（与 §3.1 字段名一致）：`name` / `idType` / `idNo` / `phone`

---

## 11. 影响评估

- **是否破坏向后兼容**：否（v3 全新二期，前端 v3 项目仓库首次消费）
- **前端是否必须同步上线**：是
- **本次推送范围**：§2 模块管理后台 10 接口（含 §2.8 smart-parse 字段先定，wx 实现后立即可对接）。§2.7 internal feign 走后端 changelog 仓库，不在本表。

---

## 12. 注意事项

- **§2.8 smart-parse 待 wx 新建（Issue [#2526](https://git.1814.love:8443/wx/HL/issues/2526)）**：前端可先按本文字段对接 UI，wx 实现完接口立即可调；接口字段已确定不会变
- **敏感字段明文返回**：§2.1 / §2.3 / §2.8 / §2.9 admin 接口返回 `idNo` / `phone` / `emergencyPhone` 明文，前端拿到后**不要**写本地 log / 不要塞 URL query；§2.9 `incompleteList[].name` 已脱敏
- **桥接表约束**：§3.6 / §3.7 大交通批次新增/编辑时同方向同一出行人只能在 1 个 plan（错则 `581143`）
- **删人合同强约束**：§3.4 已签电子合同后禁止删人 → `581106`
- **`/v3/admin/**` 公司隔离**：所有 admin 接口均带跨公司隔离校验，跨公司访问返 `581021`，前端无需自行过滤

---

## 13. 关联

- **API 设计文档**: `docs/order-v3/api/API-SPEC-V5.50.html` §2.1 ~ §2.9（v5.50 阶段已对齐 v3 代码现状）
- **SRS 业务规格**: `docs/order-v3/srs/order-cloud-v3-srs-v5.50.html` §1.0i 出行人模型 / §2 大交通
- **数据库 Schema**: `docs/order-v3/database/DATABASE-SCHEMA-V5.50.html`（`order_traveler` / `order_transport_plan` / `order_transport_plan_traveler` / `order_decrypt_audit_log`）
- **§1 总 changelog**（订单核心模块）: `changelogs-v2/2026-05/18_§1_订单核心模块-新增接口-管理后台.md`（详情主聚合 §1.3.1 引用本模块 §2.1 TravelerVO 字段口径）
- **§2.7 internal feign 单独走后端 changelog**: 待推 `hl-backend-changelog/.../18_§2.7_traveler-internal-feign-新增接口.md`（受众=合同 / 保险服务）
- **关联 Issue**：[#2517](https://git.1814.love:8443/wx/HL/issues/2517) ~ [#2527](https://git.1814.love:8443/wx/HL/issues/2527) 11 个（含 §2.7 [#2525](https://git.1814.love:8443/wx/HL/issues/2525) internal feign 单独走后端 changelog），全部 assign wx；§2.8 [#2526](https://git.1814.love:8443/wx/HL/issues/2526) 为 0→1 新建
- **后端负责人**: @yaosutu / 实施 @wx
