# 订单调整 snapshot / submit 契约重制——basic 承载订单信息+金额、行程 tab 定价化、submit 入出参大幅精简

> 端类型：管理后台
> 变更类型：修改接口（**破坏性**，前端需同步改造）
> 涉及接口：
> - `GET /v3/admin/order/{id}/adjustment/snapshot`（调整弹窗预填快照）
> - `POST /v3/admin/order/{id}/adjustment/submit`（调整统一提交）

## 背景

订单调整弹窗的预填快照与统一提交接口经一轮重制：snapshot 的 `basic` 改为每次必返并承载订单身份+金额、行程 tab 升级为带价展示、删除前端用不到的优惠/附加费列表；submit 入参删除若干无用/已不支持字段、行程改嵌套结构、出参精简为只剩成功标志。**两个接口的入出参结构均有破坏性变化，前端需按本文同步改造。**

---

## 一、`GET /v3/admin/order/{id}/adjustment/snapshot`（出参重制）

### 入参（不变）

| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|:---:|---|
| path | `id` | Long | 是 | 订单 ID |
| query | `scope` | String | 否 | 限定返回子域，逗号分隔，不传返全部。枚举：`BASIC`/`PEOPLE`/`SCHEDULE`/`ITINERARY`/`HOTEL_REQ`/`VEHICLE_REQ`（**`FEE` 已移除**） |

### 关键变更

1. **`basic` 每次必返**（不再受 `scope` 限制，传任意 scope 都返回 basic）。
2. **`basic` 新增订单身份字段**：`orderNo`/`productName`/`teamNo`/`productType`（只读展示）。
3. **订单金额从顶层移入 `basic`**：`orderAmount`/`surchargeAmount`/`discountAmount`/`receivableAmount`/`balanceAmount`。其中 `receivableAmount`（应收总额）为新增字段。
4. **删除顶层字段**：`discounts[]`、`surcharges[]`、`orderAmount`、`surchargeAmount`、`discountAmount`、`balanceAmount`（金额已并入 basic；优惠/附加费列表下线）。
5. **行程 `itinerary` 改嵌套带价**：原顶层 `nodes[]` 删除，节点改挂在 `days[].nodes[]` 下，并新增单价/数量/小计/本日合计。

### 出参 `basic`（OrderBasicVO）

| 字段 | 类型 | 说明 |
|---|---|---|
| `orderNo` | String | 订单号（只读）🆕 |
| `productName` | String | 产品名（快照，只读）🆕 |
| `teamNo` | String | 团号（订金支付后生成，未支付为 null，只读）🆕 |
| `productType` | String | 订单类型 CORE/ROUTE/CUSTOM/GROUP（只读）🆕 |
| `customerName` | String | 客户姓名（脱敏） |
| `customerPhone` | String | 客户手机（脱敏） |
| `emergencyContact` | String | 紧急联系人 |
| `customerRemark` | String | 客户备注 |
| `consultantRemark` | String | 顾问备注（内部） |
| `tags` | String[] | 订单标签 |
| `adultCount` / `childCount` / `youngChildCount` / `babyCount` | Integer | 成人/儿童/幼童/婴儿人数 |
| `orderAmount` | String(金额) | 订单基价（由顶层迁入）🔁 |
| `surchargeAmount` | String(金额) | 已有附加费合计（由顶层迁入）🔁 |
| `discountAmount` | String(金额) | 已有优惠合计（由顶层迁入）🔁 |
| `receivableAmount` | String(金额) | 应收总额 = 基价+附加费−优惠，≥0，取消单为 0 🆕 |
| `balanceAmount` | String(金额) | 待付尾款 = 应收−净已付，≥0，取消单为 0（由顶层迁入）🔁 |

> 金额字段均序列化为**字符串**（防 JS 精度丢失）。

### 出参 `itinerary`（ItineraryEditVO，嵌套带价）

| 路径 | 字段 | 类型 | 说明 |
|---|---|---|---|
| days[] | `id`/`dayNumber`/`dayDate`/`theme` | - | 行程天信息 |
| days[] | `nodes[]` | 数组 | 当天节点（**已过滤 HOTEL 节点**）🆕 |
| days[] | `dayTotal` | 金额 | 本日合计 = Σ节点 subtotal，后端派生只读 🆕 |
| days[].nodes[] | `id`/`dayId`/`nodeType`/`title`/`startTime`/`sortOrder` | - | 节点信息 |
| days[].nodes[] | `unitPrice` | 金额 | 单价（叙事展示；非 SCENIC 节点当前为 0）🆕 |
| days[].nodes[] | `quantity` | Integer | 数量（默认 1）🆕 |
| days[].nodes[] | `subtotal` | 金额 | 小计 = unitPrice×quantity，后端派生只读 🆕 |

> ⚠️ 原顶层 `itinerary.nodes[]` 已删除，全部迁入 `days[].nodes[]`。
> 其余子域 `travelers` / `schedule` / `hotelRequirement` / `hotelDayDefaults` / `vehicleRequirement` / `lockedDays` / `editableTabLocksHint` 不变。

### snapshot 出参示例（节选）

```json
{
  "code": 200,
  "data": {
    "basic": {
      "orderNo": "HL202608010001",
      "productName": "长白山深度5日游",
      "teamNo": "GB202608001",
      "productType": "CORE",
      "customerName": "张*",
      "adultCount": 4,
      "orderAmount": "12000.00",
      "surchargeAmount": "800.00",
      "discountAmount": "200.00",
      "receivableAmount": "12600.00",
      "balanceAmount": "3000.00"
    },
    "itinerary": {
      "days": [
        {
          "id": "1001", "dayNumber": 1, "dayDate": "2026-08-01", "theme": "天池徒步",
          "nodes": [
            { "id": "2001", "nodeType": "SCENIC", "title": "长白山天池门票", "unitPrice": "128.00", "quantity": 1, "subtotal": "128.00" },
            { "id": "2002", "nodeType": "RESTAURANT", "title": "午餐", "unitPrice": "0", "quantity": 1, "subtotal": "0" }
          ],
          "dayTotal": "128.00"
        }
      ]
    }
  }
}
```

---

## 二、`POST /v3/admin/order/{id}/adjustment/submit`（入参 + 出参重制）

### 入参 `AdjustmentSubmitReqVO`

| 字段 | 类型 | 必填 | 说明 |
|---|---|:---:|---|
| `updates` | 对象 | 是 | 各子域改动（按需填，未改置 null，但不能全 null） |

> ⚠️ **删除入参字段**：`editReason`（原必填）、`tabLocks`、`confirmDiffHash`。这三个一律不再传。

### `updates`（AdjustmentUpdatesVO）—— 现仅 5 个子域

| 子域 | 类型 | 说明 |
|---|---|---|
| `travelers` | 对象 | 出行人增删改：`add[]` / `update[]`（含 id）/ `remove[]`（id 列表） |
| `schedule` | 对象 | 改期：`departDate`（新出发日 yyyy-MM-dd，本子域存在时必填，不可同原值） |
| `itinerary` | 对象 | 行程：`days[].nodes[]`（见下） |
| `hotelRequirement` | 对象 | 房需求完整新版本：`totalRoomCount`/`roomTypeSummary`/`specialTags[]`/`remark`/`days[]` |
| `vehicleRequirement` | 对象 | 车需求完整新版本：`vehicleType`/`requiredSeats`/`remark`/`fleet[]` |

> ⚠️ **删除子域**：`basic`（订单基础信息不再在调整弹窗修改）、`feeChanges`（手动加优惠/附加费下线）。

### `updates.itinerary` 行程入参

| 路径 | 字段 | 类型 | 说明 |
|---|---|---|---|
| days[] | `id`/`dayNumber` | - | 定位天；`days` 数量比现有多/少 → 后端差量增/减天 |
| days[].nodes[] | `id` | Long | 定位要改的节点；为 null 视为新建（本期不支持，忽略） |
| days[].nodes[] | `unitPrice` / `quantity` | - | 传了即改单价/数量（patch，不传不动） |
| days[].nodes[] | `deleted` | Boolean | true=删除该节点 |

### 出参 `AdjustmentSubmitRespVO`（精简）

| 字段 | 类型 | 说明 |
|---|---|---|
| `success` | Boolean | 提交是否成功 |

> ⚠️ 出参从原 15 字段精简为**仅 `success`**。删除：`appliedScopes`/`affectedRows`/`newHotelRequirementId`/`newHotelVersion`/`newVehicleRequirementId`/`newVehicleVersion`/`assignmentDeletedCount`/`statusLogId`/`changedDims`/`affectedDays`/`priceDelta`/`newDepartDate`/`newReturnDate`/`newFlowStatus`。
> **失败时**统一走全局异常：HTTP 200 + `Result{code: 587xxx, message: "..."}`，前端按已有错误码机制读 `code`/`message`。

### submit 入参示例

```json
{
  "updates": {
    "schedule": { "departDate": "2026-09-01" },
    "itinerary": {
      "days": [
        { "id": "1001", "dayNumber": 1, "nodes": [
            { "id": "2001", "unitPrice": "150.00", "quantity": 2 },
            { "id": "2003", "deleted": true }
        ]}
      ]
    },
    "travelers": { "remove": [70001005] }
  }
}
```

### submit 成功出参示例

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

### submit 失败出参示例（如：减员致有效总额低于已付）

```json
{ "code": 587032, "message": "调整后总额低于已支付金额，不允许本次调整", "data": null }
```

---

## 三、错误码（submit 失败可能返回，前端按 code/message 展示）

| code | message | 含义 |
|---|---|---|
| 587002 | 订单已是终态，不可调整 | 订单 COMPLETED/CANCELLED 不可调整 |
| 587012 | updates 所有子领域均为空，无实际修改 | 未提交任何子域 |
| 587032 | 调整后总额低于已支付金额，不允许本次调整 | 防超付 |
| 587034 | 已出行，出行人不可调整 | 出行中改出行人被拦 |
| 587035 | 行程已确认，出发日期不可修改 | 改期窗口已关 |
| 587036 | 团期子订单不支持通过调整弹窗增删出行人，请前往团期管理台操作 | 团期出行人增删拦截 |

> 注：上表为常见项，完整错误码以后端 IErrorCode 段位为准；前端只需读 `code`/`message`。

---

## 四、业务边界 / 注意

- **团期(GROUP)订单不能在调整弹窗改人数**：团期增删出行人被拦截，且基础信息子域已删，故团期人数变更不在本弹窗。非团期订单通过增减出行人(`travelers`)改人数。
- **行程单价为叙事展示价**，不进结算、不影响订单应付总额（改单价不触发算价）。
- **HOTEL 节点**不在 `itinerary.days[].nodes[]` 出现（配房默认值另走 `hotelDayDefaults`）。
- 人数/天数/行程改动触发的**自动价差**仍会写入订单加费/优惠（后端处理，前端无需关心）。

---

## 五、影响评估 / 前端改造点

1. snapshot：金额与订单号/产品名/团号读取改从 `data.basic.*`（原顶层金额字段已无）；不再渲染 `discounts`/`surcharges` 列表。
2. snapshot：行程 tab 改读 `itinerary.days[].nodes[]`（含单价/数量/小计/本日合计），原顶层 `itinerary.nodes[]` 不存在。
3. submit：请求体去掉 `editReason`/`tabLocks`/`confirmDiffHash`/`updates.basic`/`updates.feeChanges`；行程改提交 `days[].nodes[]`。
4. submit：响应只读 `data.success`（其余字段已删）；失败读 `code`/`message`。

---

## 六、关联

- Issue：#4488（行程 tab 定价化）/ #4498（应收总额）/ #4502（金额挪 basic + basic 恒返 + 身份字段）/ #4509（submit 删 editReason/tabLocks）/ #4516（submit 删 confirmDiffHash/basic/feeChanges + 出参精简）
- PR：#4497 / #4500 / #4504 / #4521
- 负责人：腰苏图（yst）
