# API 变更通知

**更新时间**: 2026-05-08 15:00
**PR**: #1878 feat(order-v2): 到达/返程信息支持区分一起/多批两种模式 + #1887 修正接口描述

---

## 破坏性变更：POST /mp/order/{orderId}/arrival 语义彻底变更

### 变了什么（前端视角）

**单数接口从「追加一条」变成「该方向全量替换 1 条」。**

| 行为 | 旧逻辑（PR 前） | 新逻辑（PR 后） |
|------|----------------|----------------|
| 连续调 2 次 | 产生 2 条批次（追加） | 第 2 次会把第 1 次清掉，只保留最新 1 条 |
| 想分批到达 | 多次调单数接口 | 必须改用 /arrival/batch（提交多元素数组） |
| 已有批次被覆盖范围 | 无此逻辑 | 只清同一 direction 的旧批次，不影响另一方向 |

如果小程序之前通过多次调 POST /arrival 实现「分批到达」，现在必须改为调 POST /arrival/batch。

---

## Bug 修复：POST /mp/order/{orderId}/arrival/batch 不再误删另一方向

| 行为 | 旧行为（有 bug） | 新行为（已修复） |
|------|----------------|----------------|
| 提交返程多批 | 把已填的到达批次也一起删掉 | 只删 direction=DEPARTURE 的旧批次，ARRIVAL 不受影响 |
| 提交到达多批 | 把已填的返程批次也一起删掉 | 只删 direction=ARRIVAL 的旧批次，DEPARTURE 不受影响 |

如果前端之前因为这个 bug 做了「先查再保」之类的 workaround，可以清理掉。

---

## 新增：GET 接口返回 mode + modeLabel 字段

GET /mp/order/{orderId}/arrival 返回的每个批次对象新增 2 个字段：

| 字段 | 类型 | 说明 |
|------|------|------|
| mode | String | 模式枚举：TOGETHER 或 SEPARATE |
| modeLabel | String | 按方向拼出的中文标签（见下表） |

modeLabel 的 4 种值：

| direction | mode | modeLabel |
|-----------|------|----------|
| ARRIVAL | TOGETHER | 一起到达 |
| ARRIVAL | SEPARATE | 多批到达 |
| DEPARTURE | TOGETHER | 一起返程 |
| DEPARTURE | SEPARATE | 多批返程 |

前端可以用 arrivals[0]?.mode（或 departures[0]?.mode）来判断该方向当前 tab 模式，决定进入页面时默认选中哪个选项卡。

---

## 新增错误码：580010 BATCH_DIRECTION_MIXED

POST /mp/order/{orderId}/arrival/batch 增加新校验：请求数组里所有元素的 direction 必须一致（不能同一次 batch 里混提 ARRIVAL 和 DEPARTURE）。

| 错误码 | message |
|--------|--------|
| 580010 | 批量提交的批次必须同一方向（到达或返程） |


响应示例（错误）：

```json
{
  "code": 580010,
  "message": "批量提交的批次必须同一方向（到达或返程）",
  "data": null
}
```

---

## 涉及的接口汇总

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 查询订单全量批次 | GET | /mp/order/{orderId}/arrival | 字段新增 | 每个批次对象新增 mode + modeLabel |
| 2 | 新增交通批次 | POST | /mp/order/{orderId}/arrival | 语义破坏性变更 | 从追加改为该方向全量替换 1 条 |
| 3 | 全量替换批次(多批) | POST | /mp/order/{orderId}/arrival/batch | Bug 修复 + 新增校验 | 修方向隔离 bug；新增 direction 混用校验 |
| 4 | 修改交通批次 | PUT | /mp/order/{orderId}/arrival/plan/{planId} | 无变化 | - |
| 5 | 删除交通批次 | DELETE | /mp/order/{orderId}/arrival/plan/{planId} | 无变化 | - |

---

## 接口详细定义

### 1. GET /mp/order/{orderId}/arrival — 查询订单全量到达/离开批次

- **使用场景**：进入订单详情/行程页时查询，用返回的 arrivals[0]?.mode 和 departures[0]?.mode 决定各方向 tab 默认状态

- **路径参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long | 是 | 订单 ID |

- **响应字段（data 对象）**：

| 字段 | 类型 | 说明 |
|------|------|------|
| orderId | Long | 订单 ID |
| arrivals | Array | direction=ARRIVAL 的批次列表 |
| departures | Array | direction=DEPARTURE 的批次列表 |

arrivals/departures 内每个批次对象字段（MpArrivalPlanRespVO）：

| 字段 | 类型 | 说明 |
|------|------|------|
| planId | Long | 批次 ID |
| orderId | Long | 订单 ID |
| direction | String | ARRIVAL/DEPARTURE |
| directionLabel | String | 到达/离开 |
| mode | String | **[本次新增]** TOGETHER/SEPARATE |
| modeLabel | String | **[本次新增]** 一起到达/多批到达/一起返程/多批返程 |
| transportType | String | FLIGHT/TRAIN/SELF_DRIVE |
| transportTypeLabel | String | 飞机/火车/自驾 |
| transportNo | String | 航班号/车次号（自驾为 null） |
| carrier | String | 航司/铁路公司 |
| departStation | String | 出发站/机场 |
| arriveStation | String | 到达站/机场 |
| departTime | String | 出发时间，格式 yyyy-MM-dd HH:mm:ss |
| arriveTime | String | 到达时间，格式 yyyy-MM-dd HH:mm:ss |
| selfDrivePeriod | String | 自驾时段（MORNING/AFTERNOON/EVENING），非自驾为 null |
| selfDrivePeriodLabel | String | 上午/下午/晚上，非自驾为 null |
| selfDriveEta | String | 自驾预计到达精确时间，非自驾为 null |
| remark | String | 用户备注 |
| creatorType | String | USER/ADMIN |
| createTime | String | 创建时间 |
| travelers | Array | 本批次出行人列表 |

travelers 内每个出行人字段：

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

- **响应示例（完整）**：
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "orderId": 700001,
    "arrivals": [
      {
        "planId": 800001,
        "orderId": 700001,
        "direction": "ARRIVAL",
        "directionLabel": "到达",
        "mode": "TOGETHER",
        "modeLabel": "一起到达",
        "transportType": "FLIGHT",
        "transportTypeLabel": "飞机",
        "transportNo": "CZ6255",
        "carrier": "南方航空",
        "departStation": "北京首都T3",
        "arriveStation": "海拉尔东山",
        "departTime": "2026-07-10 08:30:00",
        "arriveTime": "2026-07-10 11:00:00",
        "selfDrivePeriod": null,
        "selfDrivePeriodLabel": null,
        "selfDriveEta": null,
        "remark": "带小孩,需婴儿座椅",
        "creatorType": "USER",
        "createTime": "2026-07-01 10:00:00",
        "travelers": [
          {
            "travelerId": 10001,
            "name": "张三",
            "travelerType": "ADULT"
          }
        ]
      }
    ],
    "departures": [
      {
        "planId": 800002,
        "orderId": 700001,
        "direction": "DEPARTURE",
        "directionLabel": "离开",
        "mode": "SEPARATE",
        "modeLabel": "多批返程",
        "transportType": "TRAIN",
        "transportTypeLabel": "火车",
        "transportNo": "K209",
        "carrier": null,
        "departStation": "海拉尔",
        "arriveStation": "北京",
        "departTime": "2026-07-15 10:00:00",
        "arriveTime": "2026-07-16 08:00:00",
        "selfDrivePeriod": null,
        "selfDrivePeriodLabel": null,
        "selfDriveEta": null,
        "remark": null,
        "creatorType": "USER",
        "createTime": "2026-07-02 09:00:00",
        "travelers": [
          {
            "travelerId": 10001,
            "name": "张三",
            "travelerType": "ADULT"
          }
        ]
      }
    ]
  }
}
```

---

### 2. POST /mp/order/{orderId}/arrival — 新增交通批次（单条，该方向全量替换）

- **使用场景**：用户在「一起到达」或「一起返程」tab 填写单条信息并提交
- **语义重申**：调用此接口会把同 direction 的所有旧批次软删，再插入 1 条新批次。最终该方向只剩 1 条，mode 固定为 TOGETHER

- **路径参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long | 是 | 订单 ID |

- **请求参数（JSON Body）**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| direction | String | 是 | ARRIVAL=到达 / DEPARTURE=离开 |
| transportType | String | 是 | FLIGHT=飞机 / TRAIN=火车 / SELF_DRIVE=自驾 |
| transportNo | String | 条件必填 | 航班号/车次号；FLIGHT/TRAIN 必填，SELF_DRIVE 不填 |
| carrier | String | 否 | 航司/铁路公司，最长 64 字符 |
| departStation | String | 条件必填 | 出发站/机场；DEPARTURE+FLIGHT/TRAIN 必填；ARRIVAL 可选 |
| arriveStation | String | 条件必填 | 到达站/机场；ARRIVAL+FLIGHT/TRAIN 必填；DEPARTURE 可选 |
| departTime | String | 条件必填 | 出发时间；DEPARTURE+FLIGHT/TRAIN 必填；格式 yyyy-MM-dd HH:mm:ss |
| arriveTime | String | 条件必填 | 到达时间；ARRIVAL+FLIGHT/TRAIN 必填；格式 yyyy-MM-dd HH:mm:ss |
| selfDrivePeriod | String | 条件必填 | 自驾时段；SELF_DRIVE 必填；MORNING/AFTERNOON/EVENING |
| selfDriveEta | String | 否 | 自驾预计到达精确时间；SELF_DRIVE 可选；格式 yyyy-MM-dd HH:mm:ss |
| remark | String | 否 | 用户备注，最长 255 字符 |
| travelerIds | Array | 是 | 出行人 ID 列表，至少 1 人，必须属于本订单 |

- **请求示例（到达+飞机）**：
```json
{
  "direction": "ARRIVAL",
  "transportType": "FLIGHT",
  "transportNo": "CZ6255",
  "carrier": "南方航空",
  "departStation": "北京首都T3",
  "arriveStation": "海拉尔东山",
  "departTime": "2026-07-10 08:30:00",
  "arriveTime": "2026-07-10 11:00:00",
  "remark": "带小孩,需婴儿座椅",
  "travelerIds": [
    10001,
    10002
  ]
}
```

- **响应示例（完整）**：

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "planId": 800001,
    "orderId": 700001,
    "direction": "ARRIVAL",
    "directionLabel": "到达",
    "mode": "TOGETHER",
    "modeLabel": "一起到达",
    "transportType": "FLIGHT",
    "transportTypeLabel": "飞机",
    "transportNo": "CZ6255",
    "carrier": "南方航空",
    "departStation": "北京首都T3",
    "arriveStation": "海拉尔东山",
    "departTime": "2026-07-10 08:30:00",
    "arriveTime": "2026-07-10 11:00:00",
    "selfDrivePeriod": null,
    "selfDrivePeriodLabel": null,
    "selfDriveEta": null,
    "remark": "带小孩,需婴儿座椅",
    "creatorType": "USER",
    "createTime": "2026-07-01 10:00:00",
    "travelers": [
      {
        "travelerId": 10001,
        "name": "张三",
        "travelerType": "ADULT"
      },
      {
        "travelerId": 10002,
        "name": "李四",
        "travelerType": "CHILD"
      }
    ]
  }
}
```

---

### 3. POST /mp/order/{orderId}/arrival/batch — 全量替换批次（多批模式）

- **使用场景**：用户在「多批到达」或「多批返程」tab，一次性提交该方向所有批次
- **Bug 修复**：旧版本会把订单全部旧批次删掉（含另一方向），新版本只删 body 第一条元素对应的 direction
- **新增校验**：body 数组里所有元素的 direction 必须一致，混提返回 code=580010

- **路径参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long | 是 | 订单 ID |

- **请求参数（JSON Body，数组）**：字段同上面单条接口，外层包数组

- **请求示例（多批返程）**：
```json
[
  {
    "direction": "DEPARTURE",
    "transportType": "FLIGHT",
    "transportNo": "CZ6256",
    "carrier": "南方航空",
    "departStation": "海拉尔东山",
    "arriveStation": "北京首都T3",
    "departTime": "2026-07-15 12:00:00",
    "arriveTime": "2026-07-15 15:30:00",
    "travelerIds": [
      10001
    ]
  },
  {
    "direction": "DEPARTURE",
    "transportType": "TRAIN",
    "transportNo": "K209",
    "departStation": "海拉尔",
    "arriveStation": "北京",
    "departTime": "2026-07-15 10:00:00",
    "arriveTime": "2026-07-16 08:00:00",
    "travelerIds": [
      10002
    ]
  }
]
```

- **响应示例（完整）**：

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "orderId": 700001,
    "arrivals": [],
    "departures": [
      {
        "planId": 800003,
        "orderId": 700001,
        "direction": "DEPARTURE",
        "directionLabel": "离开",
        "mode": "SEPARATE",
        "modeLabel": "多批返程",
        "transportType": "FLIGHT",
        "transportTypeLabel": "飞机",
        "transportNo": "CZ6256",
        "carrier": "南方航空",
        "departStation": "海拉尔东山",
        "arriveStation": "北京首都T3",
        "departTime": "2026-07-15 12:00:00",
        "arriveTime": "2026-07-15 15:30:00",
        "selfDrivePeriod": null,
        "selfDrivePeriodLabel": null,
        "selfDriveEta": null,
        "remark": null,
        "creatorType": "USER",
        "createTime": "2026-07-01 11:00:00",
        "travelers": [
          {
            "travelerId": 10001,
            "name": "张三",
            "travelerType": "ADULT"
          }
        ]
      },
      {
        "planId": 800004,
        "orderId": 700001,
        "direction": "DEPARTURE",
        "directionLabel": "离开",
        "mode": "SEPARATE",
        "modeLabel": "多批返程",
        "transportType": "TRAIN",
        "transportTypeLabel": "火车",
        "transportNo": "K209",
        "carrier": null,
        "departStation": "海拉尔",
        "arriveStation": "北京",
        "departTime": "2026-07-15 10:00:00",
        "arriveTime": "2026-07-16 08:00:00",
        "selfDrivePeriod": null,
        "selfDrivePeriodLabel": null,
        "selfDriveEta": null,
        "remark": null,
        "creatorType": "USER",
        "createTime": "2026-07-01 11:00:00",
        "travelers": [
          {
            "travelerId": 10002,
            "name": "李四",
            "travelerType": "CHILD"
          }
        ]
      }
    ]
  }
}
```

---

## 枚举 / 字典值（完整）

### transport_direction（方向）

| 值 | 中文 | 说明 |
|----|------|------|
| ARRIVAL | 到达 | 游客到达目的地 |
| DEPARTURE | 离开 | 游客离开目的地（返程） |

### mode（模式，本次新增）

| 值 | 中文 | 说明 |
|----|------|------|
| TOGETHER | 一起 | 该方向所有出行人同一批次 |
| SEPARATE | 多批 | 该方向出行人分多个批次 |

### modeLabel（按 direction + mode 拼，本次新增）

| direction | mode | modeLabel |
|-----------|------|-----------|
| ARRIVAL | TOGETHER | 一起到达 |
| ARRIVAL | SEPARATE | 多批到达 |
| DEPARTURE | TOGETHER | 一起返程 |
| DEPARTURE | SEPARATE | 多批返程 |

### transport_type（交通方式）

| 值 | 中文 |
|----|------|
| FLIGHT | 飞机 |
| TRAIN | 火车 |
| SELF_DRIVE | 自驾 |

### self_drive_period（自驾时段）

| 值 | 中文 | 时间范围 |
|----|------|---------|
| MORNING | 上午 | 06:00-12:00 |
| AFTERNOON | 下午 | 12:00-18:00 |
| EVENING | 晚上 | 18:00 以后 |

### creator_type（创建者类型）

| 值 | 中文 |
|----|------|
| USER | 用户自填 |
| ADMIN | 定制师代录 |

### traveler_type（出行人类型）

| 值 | 中文 |
|----|------|
| ADULT | 成人 |
| CHILD | 儿童 |
| YOUNG_CHILD | 幼儿 |
| BABY | 婴儿 |

---

## 错误码（本模块完整）

| code | message | 触发场景 |
|------|---------|---------|
| 580001 | 订单当前状态不可修改交通信息: {状态} | 订单不在可编辑状态 |
| 580002 | 出行人列表不能为空 | travelerIds 为空 |
| 580003 | 以下出行人不属于本订单: {ids} | travelerIds 包含不属于此订单的 ID |
| 580004 | 航班号/车次号不能为空 | FLIGHT/TRAIN 时 transportNo 未填 |
| 580005 | 到达机场/车站不能为空 | ARRIVAL+FLIGHT/TRAIN 时 arriveStation 未填 |
| 580006 | 到达时间不能为空 | ARRIVAL+FLIGHT/TRAIN 时 arriveTime 未填 |
| 580007 | 出发机场/车站不能为空 | DEPARTURE+FLIGHT/TRAIN 时 departStation 未填 |
| 580008 | 出发时间不能为空 | DEPARTURE+FLIGHT/TRAIN 时 departTime 未填 |
| 580009 | 自驾时段不能为空 | SELF_DRIVE 时 selfDrivePeriod 未填 |
| 580010 | 批量提交的批次必须同一方向（到达或返程） | batch 接口 body 内 direction 混用（本次新增） |

---

## 业务规则 / 校验规则

1. POST /arrival（单数）：提交即清空同 direction 旧批次，再插入 1 条。最终该方向只有 1 条批次，mode 固定为 TOGETHER。
2. POST /arrival/batch：提交即清空同 direction 旧批次，再批量插入。有 2 条及以上时 mode 为 SEPARATE；仅 1 条时 mode 也是 SEPARATE（因为用户选择了「多批」入口）。
3. 两个接口只影响自己提交的 direction，不互相干扰（本次 bug 修复要点）。
4. PUT /arrival/plan/{planId} 修改单条，不影响其他批次，不改变 mode。
5. DELETE /arrival/plan/{planId} 只软删单条。
6. 以上操作均需订单处于可编辑状态，否则返回 580001。

---

## 前端实现建议



---

## 补充说明

- GET 接口新增 mode/modeLabel 字段，向后兼容（纯新增，无删改名），已有代码不受影响
- POST 单数接口语义变更属于破坏性变更，旧版用多次调单数接口实现分批的需改用 batch
- 服务重启：需重启 hl-order-service-v2 和 hl-mp-service 后生效
