# 管理后台 · 订单交通信息（接送站）5 个 CRUD + 飞常准查询 7 个

**日期**：2026-04-23
**影响**：**管理后台** 订单详情·行程安排·交通信息（接送站）板块
**PR**：#1285（/transport 路径别名）
**关联 Issue**：#1284（404 修复）

---

## 概述

统一管理后台「交通信息」板块的接口契约。**管理后台前端之前对接的是一份已废弃的 v1 小程序契约**（`legType / departureStation / travelDate / ...`），和后端现有新契约（`direction / departStation / departTime + arriveTime / ...`）完全不匹配，导致：

1. `POST /admin/order/{orderId}/transport` **404**（#1285 已修：Controller 同时支持 `/transport` 和 `/arrival` 两个前缀）
2. 字段反序列化不上 → **400 `方向不能为空`、`出行人列表不能为空`**

本次梳理：管理后台前端请**完全对齐小程序端 `/mp/order/{orderId}/arrival` 的 DTO 结构**（字段名、必填规则都一样，仅路径前缀不同）。同时后端已开放 `/admin/transport/*` 共 7 个飞常准查询接口，供前端自动补全航班/车次时刻。

---

## 一、订单交通批次 5 个 CRUD

### 基础信息

- **鉴权**：Admin Bearer token
- **创建者**：`creatorType=ADMIN`（定制师代录；C 端小程序同样接口 `creatorType=USER`）
- **DTO**：后端 `ArrivalPlanSaveReqVO`（和小程序 `MpArrivalPlanSaveReqVO` 完全一致，字段名一一对应）

### 路径表

**URL 支持两个前缀别名（PR #1285）**，任选其一，推荐 `/transport`（与 DB 表 `order_transport_plan` 对齐）：

| 功能 | 方法 | 路径 |
|---|---|---|
| 查全量批次 | GET | `/admin/order/{orderId}/transport` 或 `/admin/order/{orderId}/arrival` |
| 新增单批次 | POST | `/admin/order/{orderId}/transport` 或 `/admin/order/{orderId}/arrival` |
| 全量替换（多批次一次性提交） | POST | `/admin/order/{orderId}/transport/batch` 或 `/admin/order/{orderId}/arrival/batch` |
| 修改单批次 | PUT | `/admin/order/{orderId}/transport/plan/{planId}` 或 `/admin/order/{orderId}/arrival/plan/{planId}` |
| 删除单批次 | DELETE | `/admin/order/{orderId}/transport/plan/{planId}` 或 `/admin/order/{orderId}/arrival/plan/{planId}` |

---

### 1. 查全量批次

```
GET /admin/order/{orderId}/transport
```

#### 入参

| 参数 | 位置 | 类型 | 必填 |
|---|---|---|---|
| orderId | Path | Long | ✅ |

#### 出参 `Result<ArrivalListRespVO>`

| 字段 | 类型 | 说明 |
|---|---|---|
| orderId | Long | 订单ID |
| arrivals | `List<ArrivalPlanRespVO>` | 到达批次（接站） |
| departures | `List<ArrivalPlanRespVO>` | 离开批次（送站） |

`ArrivalPlanRespVO` 字段结构见第 2 节出参。

---

### 2. 新增单批次

```
POST /admin/order/{orderId}/transport
```

**创建者类型**：`creatorType=ADMIN`。

#### 入参

| 参数 | 位置 | 类型 | 必填 |
|---|---|---|---|
| orderId | Path | Long | ✅ |

**Body `ArrivalPlanSaveReqVO`**（完整字段表）：

| 字段 | 类型 | 必填规则 | 约束 | 说明 |
|---|---|---|---|---|
| **direction** | String | ✅ 始终必填 | - | 方向。`ARRIVAL`=到达/接站；`DEPARTURE`=离开/送站。字典 `transport_direction` |
| **transportType** | String | ✅ 始终必填 | - | 交通方式。`FLIGHT`=飞机，`TRAIN`=火车，`SELF_DRIVE`=自驾。字典 `transport_type`（本模块子集） |
| transportNo | String | FLIGHT/TRAIN 必填；SELF_DRIVE 不填 | ≤32 | 航班号/车次号，如 `CA1234` / `G71` |
| carrier | String | 可选 | ≤64 | 航司/铁路公司，如 `中国国航` |
| departStation | String | DEPARTURE+FLIGHT/TRAIN 必填；其他场景可选 | ≤64 | 出发站/机场 |
| arriveStation | String | ARRIVAL+FLIGHT/TRAIN 必填；其他场景可选 | ≤64 | 到达站/机场 |
| departTime | LocalDateTime | DEPARTURE+FLIGHT/TRAIN 必填；其他场景可选 | - | 出发时间，`yyyy-MM-dd HH:mm:ss` |
| arriveTime | LocalDateTime | ARRIVAL+FLIGHT/TRAIN 必填；其他场景可选 | - | 到达时间，`yyyy-MM-dd HH:mm:ss` |
| selfDrivePeriod | String | SELF_DRIVE 必填 | - | 自驾时段：`MORNING` / `AFTERNOON` / `EVENING`。字典 `self_drive_period` |
| selfDriveEta | LocalDateTime | 可选 | - | 自驾预计到达精确时间 |
| remark | String | 可选 | ≤255 | 用户备注 |
| **travelerIds** | `List<Long>` | ✅ 始终非空 | 至少 1 人 | 本批次涉及的出行人 ID 列表；每个 ID **必须属于本订单** |

> ⚠️ **字段间联动**：字段必填规则由 Service 层按 `transportType + direction` 组合校验，不是单注解表达式。前端请按上表规则做前置校验，否则会 500。

#### 出参 `Result<ArrivalPlanRespVO>`

| 字段 | 类型 | 说明 |
|---|---|---|
| planId | Long | 批次ID |
| orderId | Long | 订单ID |
| direction | String | 方向 |
| directionLabel | String | 方向中文标签 |
| transportType | String | 交通方式 |
| transportTypeLabel | String | 交通方式中文标签 |
| transportNo | String | 航班号/车次号 |
| carrier | String | 航司/铁路公司 |
| departStation | String | 出发站 |
| arriveStation | String | 到达站 |
| departTime | LocalDateTime | 出发时间 |
| arriveTime | LocalDateTime | 到达时间 |
| selfDrivePeriod | String | 自驾时段 |
| selfDrivePeriodLabel | String | 自驾时段中文标签 |
| selfDriveEta | LocalDateTime | 自驾预计到达 |
| remark | String | 备注 |
| creatorType | String | `USER` / `ADMIN` |
| createTime | LocalDateTime | 创建时间 |
| travelers | `List<ArrivalPlanTravelerSimpleVO>` | 本批次涉及的出行人 |

**`ArrivalPlanTravelerSimpleVO`**：

| 字段 | 类型 | 说明 |
|---|---|---|
| travelerId | Long | 出行人 ID |
| name | String | 姓名 |
| travelerType | String | `ADULT` / `CHILD` / `YOUNG_CHILD` / `BABY` |

#### 请求示例（接站 ARRIVAL）

```json
{
  "direction": "ARRIVAL",
  "transportType": "FLIGHT",
  "transportNo": "CA1234",
  "carrier": "中国国航",
  "departStation": "上海虹桥T2",
  "arriveStation": "海拉尔东山",
  "departTime": "2026-07-10 08:30:00",
  "arriveTime": "2026-07-10 11:00:00",
  "remark": "带小孩,需要婴儿座椅",
  "travelerIds": [10001, 10002]
}
```

#### 请求示例（送站 DEPARTURE）

```json
{
  "direction": "DEPARTURE",
  "transportType": "FLIGHT",
  "transportNo": "CA5678",
  "carrier": "中国国航",
  "departStation": "海拉尔东山",
  "arriveStation": "上海虹桥T2",
  "departTime": "2026-07-15 14:00:00",
  "arriveTime": "2026-07-15 17:00:00",
  "travelerIds": [10001, 10002]
}
```

#### 请求示例（自驾 SELF_DRIVE）

```json
{
  "direction": "ARRIVAL",
  "transportType": "SELF_DRIVE",
  "selfDrivePeriod": "AFTERNOON",
  "selfDriveEta": "2026-07-10 15:30:00",
  "remark": "自驾从漠河过来",
  "travelerIds": [10001]
}
```

---

### 3. 全量替换（多批次一次性提交）

```
POST /admin/order/{orderId}/transport/batch
```

先软删当前所有批次，再按请求体批量创建。空数组 `[]` = 清空订单全部交通批次。

#### 入参

| 参数 | 位置 | 类型 | 必填 |
|---|---|---|---|
| orderId | Path | Long | ✅ |

Body：`List<ArrivalPlanSaveReqVO>`（每项结构同第 2 节）。

#### 出参

`Result<ArrivalListRespVO>`，结构同第 1 节。

#### 使用场景

管理后台「一次性代录全部交通」按钮。例如定制师拿到客户的去程和回程航班信息，一次性把 2 条（或多条，如一家分两批）全部提交。

---

### 4. 修改单批次

```
PUT /admin/order/{orderId}/transport/plan/{planId}
```

**覆盖语义**：按字段全量覆盖，包括 `travelerIds`（替换旧关联）。

#### 入参

| 参数 | 位置 | 类型 | 必填 |
|---|---|---|---|
| orderId | Path | Long | ✅ |
| planId | Path | Long | ✅ |

Body：`ArrivalPlanSaveReqVO`（结构同第 2 节）。

#### 出参

`Result<ArrivalPlanRespVO>`。

---

### 5. 删除单批次

```
DELETE /admin/order/{orderId}/transport/plan/{planId}
```

**级联**：软删关联的出行人绑定行。

#### 入参

| 参数 | 位置 | 类型 | 必填 |
|---|---|---|---|
| orderId | Path | Long | ✅ |
| planId | Path | Long | ✅ |

#### 出参

`Result<Void>`。

---

## 二、航班/火车查询 7 个（`/admin/transport/*`）

数据源：飞常准官方 API，`resource-service` 侧做 Redis 缓存后透传。与小程序端 `/mp/transport/*` **底层共享同一批数据**。

**用途**：前端表单里用户输入航班号后，后端返回精确的起飞/到达时间、机场/航站楼等，**前端自动回填 `departTime / arriveTime / departStation / arriveStation`**，用户不必手动填时分秒。

### 路径表

| 功能 | 路径 | 核心入参 |
|---|---|---|
| 按航班号查航班 | `GET /admin/transport/flight/by-flight-no` | `flightNo`、`date` |
| 按机场三字码查航班 | `GET /admin/transport/flight/by-airport` | `dep`、`arr`、`date` |
| 按城市三字码查航班 | `GET /admin/transport/flight/by-city` | `depCity`、`arrCity`、`date` |
| 按车次号查火车（含经停站） | `GET /admin/transport/train/by-train-no` | `trainNo`、`date` |
| 按车次+出发/到达站查火车 | `GET /admin/transport/train/by-train-no-stations` | `trainNo`、`dep`、`arr`、`date` |
| 按出发/到达站查火车 | `GET /admin/transport/train/by-station` | `dep`、`arr`、`date` |
| 按出发/到达城市查火车 | `GET /admin/transport/train/by-city` | `depCity`、`arrCity`、`date` |

> ⚠️ 注意：admin 端路径风格和 mp 端**略有差异**（admin 端按航班号的叫 `/flight/by-flight-no`，mp 端叫 `/flight`；按车次号的叫 `/train/by-train-no`，mp 端叫 `/train`）。其他 5 个路径完全一致。

### 航班返回 `FlightVO` 字段

| 字段 | 类型 | 说明 |
|---|---|---|
| flightNo | String | 航班号 |
| airline | String | 航空公司名称 |
| category | String | `国内` / `国际` / `地区` |
| departAirportCode | String | 出发机场三字码 |
| arriveAirportCode | String | 到达机场三字码 |
| departAirport | String | 出发机场名称 |
| arriveAirport | String | 到达机场名称 |
| departCity | String | 出发城市 |
| arriveCity | String | 到达城市 |
| planDepartTime | String | 计划起飞时间 |
| planArriveTime | String | 计划到达时间 |
| actualDepartTime | String | 实际起飞时间（未起飞为 null） |
| actualArriveTime | String | 实际到达时间（未到达为 null） |
| status | String | `计划` / `起飞` / `到达` / `延误` / `取消` / `返航` / `备降` 等 |
| stopFlag | String | `0`=不经停，`1`=经停 1 次，`n`=经停 n 次 |
| shareFlag | String | `0`=否，`1`=是（共享航班） |
| shareFlightNo | String | 共享航班号 |
| departTerminal | String | 出发航站楼 |
| arriveTerminal | String | 到达航站楼 |
| boardGate | String | 登机口 |
| arriveExit | String | 到达出口 |
| departTimezone | String | 出发时区偏移（秒） |
| arriveTimezone | String | 到达时区偏移（秒） |

### 火车返回 `TrainVO` 字段

| 字段 | 类型 | 说明 |
|---|---|---|
| trainNo | String | 车次 |
| departStation | String | 出发车站 |
| arriveStation | String | 到达车站 |
| shutdown | String | `0`=否，`1`=停运 |
| planDepartTime | String | 计划出发时间 |
| planArriveTime | String | 计划到达时间 |
| estimatedDepartTime | String | 预计出发时间（仅 `by-train-no-stations` 返回） |
| estimatedArriveTime | String | 预计到达时间（仅 `by-train-no-stations` 返回） |
| actualDepartTime | String | 实际出发时间（仅 `by-train-no-stations` 返回） |
| actualArriveTime | String | 实际到达时间（仅 `by-train-no-stations` 返回） |
| departStatus | String | 出发状态：`计划`/`正点`/`晚点`/`出发`（仅 `by-train-no-stations`） |
| arriveStatus | String | 到达状态：`计划`/`正点`/`晚点`/`到达`（仅 `by-train-no-stations`） |
| duration | Integer | 运行时长（分钟） |
| stops | `List<TrainStopVO>` | 经停站列表（仅 `by-train-no` 返回） |

**`TrainStopVO`**：

| 字段 | 类型 | 说明 |
|---|---|---|
| stationName | String | 车站名称 |
| arriveTime | String | 到站时间 |
| departTime | String | 发车时间 |

### 边界

- 查不到数据（航班号无效/停运）：返回 `data: []`
- 飞常准上游异常/超时：500，`message` 含上游错误码
- 日期格式：`yyyy-MM-dd`，格式错误返回 500
- 所有三字码不区分大小写（内部 `toUpperCase`）

---

## 三、字典依赖

| 字典类型 | 值 |
|---|---|
| `transport_direction` | `ARRIVAL` / `DEPARTURE` |
| `transport_type` | `FLIGHT` / `TRAIN` / `SELF_DRIVE`（本模块子集） |
| `self_drive_period` | `MORNING` / `AFTERNOON` / `EVENING` |
| `creator_type` | `USER` / `ADMIN` |
| `traveler_type` | `ADULT` / `CHILD` / `YOUNG_CHILD` / `BABY` |

所有 `xxxLabel` 字段由后端透传中文展示值，前端无需再次映射。

---

## 四、前端迁移指南（重要 ⚠️）

### 字段改名对照表

管理后台前端**之前在用 v1 小程序遗留契约**，请按下表迁移：

| 旧契约（v1 遗留） | 新契约（当前） | 说明 |
|---|---|---|
| `legType` (`OUTBOUND` / `RETURN`) | **`direction`** (`ARRIVAL` / `DEPARTURE`) | **字段名+值域都变了**。`OUTBOUND` 去程=客户到达目的地 → `ARRIVAL`；`RETURN` 回程=客户从目的地离开 → `DEPARTURE` |
| `departureStation` | **`departStation`** | 去掉 `ure` 词尾 |
| `arrivalStation` | **`arriveStation`** | 去掉 `al` 词尾 |
| `travelDate`（一个日期） | **`departTime` + `arriveTime`**（两个 LocalDateTime） | 拆成出发时间和到达时间，两者都要有时分秒 |
| 缺失 | **`travelerIds`（必填）** | 新增必填字段，至少 1 人，必须属于本订单 |

### 推荐 UX

用户填写交通信息时：

1. **方向**：单选 `接站 (ARRIVAL)` / `送站 (DEPARTURE)`
2. **交通方式**：单选 `飞机 (FLIGHT)` / `火车 (TRAIN)` / `自驾 (SELF_DRIVE)`
3. **航班号/车次号**：输入后立即调 `GET /admin/transport/flight/by-flight-no` 或 `GET /admin/transport/train/by-train-no`
4. 从返回列表让用户选具体班次，**自动回填** `departStation / arriveStation / departTime / arriveTime / carrier`
5. 自驾场景：改为输入 `selfDrivePeriod` + 可选 `selfDriveEta`
6. **出行人选择**：多选框，默认全选订单所有出行人（用户可取消部分），最少 1 人
7. 提交时按上表 DTO 校验格式

### 参考实现

小程序端已上线该 UI，表单交互和字段校验可**直接参考 `MpArrivalController` 的使用**（changelog: `2026-04-21_mp-order-arrival.md`）。唯一差别是路径前缀 `/admin` 和 `creatorType=ADMIN`。

---

## 五、关联 PR 与历史

- #1285（本次）：Controller 同时支持 `/transport` 和 `/arrival` 两个前缀别名。修 404。
- #1262（2026-04-23）：配房差价 execute 自动写 `order_surcharge` / `order_discount`（source=ROOM_UPGRADE），与本模块无关但同日相邻。
- 2026-04-21 MP 端 `/mp/order/{orderId}/arrival` 已上线，DTO 结构与本次 admin 端一致。

**待废弃**：`MpOrderTransportController`（`/mp/order/{orderId}/transport`，v1 遗留）已标 `@Deprecated`，请**不要再参考它的字段命名**。管理后台前端之前误以此为契约，本次必须迁移。
