# 【修改接口·管理后台】终止行程·退款计算弹窗：预览补字段 + 提交下界校验 (#4548 #4563)

> **PR**: #4551 #4564 | **服务**: hl-order-service-v3 | **更新时间**: 2026-06-28

## 1. 接口背景

「终止行程·退款计算」弹窗（出行中订单点终止时打开）涉及两个接口：预览（拉资源清单 + 选结束日）、提交（确认终止 + 算退款）。本次两点优化：

1. **预览接口**：弹窗需要渲染「客户在哪天结束行程」的日期按钮排（第 1 天 / 第 2 天 …），以及头部展示订单基本信息（订单号 / 客户 / 产品 / 团号 / 定制师）。原先这些都要前端自己拼，现在后端直接给。
2. **提交接口**：原先后端不校验「结束日之前的资源是否标记已用」，可能把已经发生的天数算成可退款。现在加了下界校验兜底。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 终止退款预览 | POST | `/v3/admin/order/{id}/terminate/refund-preview` | 修改接口 | 出参新增 `days` 数组 + 5 个订单基本信息字段 + `outstandingBalance` 应收尾款 |
| 2 | 终止行程提交 | POST | `/v3/admin/order/{id}/terminate` | 修改接口 | 新增错误码 581044 + 入参约束「结束日前的资源必须标记已用」 + 新增必填入参 `onsiteBalance` 现场实收尾款 |

> 两接口入参主体结构不变，均为出参/校验增量，向后兼容。

---

## 3. 接口详情

### 3.1 终止退款预览（POST `/v3/admin/order/{id}/terminate/refund-preview`）

- **使用场景**：出行中订单点「终止行程」打开弹窗时调用，拉取资源清单、结束日选项、订单基本信息。
- **认证**：需要管理后台 JWT。
- **前置**：订单状态必须为「出行中（TRAVELLING）」，否则返回错误。
- **本次变更**：纯出参新增，无破坏性。

**入参**（不变）

| 位置 | 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|------|
| path | `id` | Long | ✅ | 订单 ID |

无请求体。

**出参新增字段**

| 字段 | 类型 | 说明 |
|------|------|------|
| `orderNo` | String | 订单号 |
| `customerName` | String | 客户名 |
| `productName` | String | 产品名称 |
| `batchNo` | String | 团号（团期订单有值；**核心订单为 `null`**） |
| `consultantName` | String | 定制师 |
| `outstandingBalance` | String | 应收尾款（= 应收总额 − 净已付，clamp ≥ 0；已取消单为 0）。"该收多少"，与提交时录入的现场实收 `onsiteBalance` 对照 |
| `days` | Array | 结束日期选项（见下表），范围 `1..tripCurrentDay`，**不含「未出行」** |

`days[]` 元素字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `dayNumber` | Integer | 第几天（从 1 开始） |
| `label` | String | 中文展示名，如「第 1 天」 |
| `date` | String(日期) | 该天日期（= 出发日期 + dayNumber - 1） |
| `isCurrent` | Boolean | 是否当前天（= tripCurrentDay），前端默认选中此项 |

> 原有字段 `paidAmount` / `tripDays` / `departDate` / `tripCurrentDay` / `rooms` / `tickets` / `vehicles` / `insurance` 全部保留不变。

**响应示例**（典型成功，团期订单 6 天行程、当前到第 3 天）

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "orderNo": "HL202605120001",
    "customerName": "张三",
    "productName": "西藏拉林环线 6 日定制",
    "batchNo": "GB20260512-008",
    "consultantName": "李顾问",
    "paidAmount": "6000.00",
    "outstandingBalance": "0.00",
    "tripDays": 6,
    "departDate": "2026-05-12",
    "tripCurrentDay": 3,
    "days": [
      { "dayNumber": 1, "label": "第 1 天", "date": "2026-05-12", "isCurrent": false },
      { "dayNumber": 2, "label": "第 2 天", "date": "2026-05-13", "isCurrent": false },
      { "dayNumber": 3, "label": "第 3 天", "date": "2026-05-14", "isCurrent": true }
    ],
    "rooms": [],
    "tickets": [],
    "vehicles": [],
    "insurance": []
  },
  "success": true
}
```

> 核心订单（非团期）`batchNo` 为 `null`，其余结构一致。

**业务边界**

- ✅ 订单状态 = 出行中（TRAVELLING）时可调。
- ❌ 非出行中订单调用 → 返回「出行中取消仅适用于出行中订单」错误。
- ⚠️ `days` 不含「未出行（第 0 天）」：未出行属于「取消订单」链路，不在终止行程范围内。

---

### 3.2 终止行程提交（POST `/v3/admin/order/{id}/terminate`）

- **使用场景**：弹窗内确认终止，提交各资源「是否已用」+ 结束天，后端算退款并终止订单。
- **认证**：需要管理后台 JWT。
- **本次变更**：新增一条下界校验 + 对应错误码；入参字段结构不变。

**入参约束变更（重点）**

新增校验：**结束日之前（`dayNumber < endDayNumber`）的资源行，`used` 必须为 `true`**；否则返回错误码 **581044**。

- `dayNumber < endDayNumber`（结束日之前已发生的天）→ `used` 必须 `true`（不可退）。
- `dayNumber >= endDayNumber`（结束当天及之后）→ `used` 自由（默认 `false`，也可手动标 `true`，如当天酒店已入住）。

> 与现有前端交互一致：前端选定结束天后「自动标记前 N 天资源为已用」，天然满足此约束，正常不会触发 581044。仅当传入与结束天矛盾的 `used` 时才会被拒。

**入参字段表**（结构不变，列出供对照）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `cancelReason` | String | ✅ | 终止原因 + 调整说明 |
| `endDayNumber` | Integer | ✅ | 停在第几天（≥ 1） |
| `rooms` | Array | ✅ | 住宿已用判定 `[{refId, used}]` |
| `tickets` | Array | ✅ | 门票已用判定 `[{refId, used}]` |
| `vehicles` | Array | ✅ | 用车每天已用判定 `[{refId, dayNumber, used}]` |
| `adjustAmount` | Decimal | ❌ | 人工调整额（默认 0，允许负） |
| `onsiteBalance` | Decimal | ✅ | **现场实收尾款**（定制师终止时现场实收的尾款，必填）。纯记录：不参与退款计算、不影响已付金额，仅留存供核单/财务对照（与预览的 `outstandingBalance` 应收尾款相对） |

**错误码**

| code | 含义 | 触发场景 |
|------|------|----------|
| 581044 | 终止行程：结束日之前的资源必须标记为已使用 | 提交时存在 `dayNumber < endDayNumber` 的资源行被标 `used=false` |

**示例**

8.1 业务失败（异常）——触发 581044

**请求**（结束天=2，但第 1 天住宿被标未用）：

```json
{
  "cancelReason": "QA测试",
  "endDayNumber": 2,
  "rooms": [ { "refId": "96011", "used": false } ],
  "tickets": [],
  "vehicles": []
}
```

**响应**：

```json
{
  "code": 581044,
  "message": "终止行程：结束日之前的资源必须标记为已使用",
  "success": false
}
```

> 触发 581044 时订单不会被终止、不产生退款（校验在落库前拦截）。

**业务边界**

- ✅ 结束当天及之后的资源可自由标记已用 / 未用。
- ❌ 结束日之前的资源标记未用 → 581044。
- ⚠️ 保险行不参与此校验（保险整单不可退，由后端固定处理，无需前端传入）。

---

## 11. 影响评估

- **是否破坏向后兼容**：否。预览为出参增量；提交为新增校验，与现有「前 N 天自动标已用」前端交互一致，正常流程不受影响。
- **前端是否必须同步上线**：否（预览新字段不消费则忽略；提交保持现有标记逻辑即可）。建议前端接入 `days` 后撤掉自行用 `tripDays + departDate` 构造日期按钮的逻辑。

## 12. 注意事项

- 前端可清理 workaround：原先用 `tripDays + departDate` 循环构造结束日按钮、自行算每天日期的逻辑，可改为直接渲染后端返回的 `days` 数组。
- `batchNo` 在核心订单为 `null`，前端头部展示需做空值兜底。

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#4548](https://git.1814.love:8443/wx/HL/issues/4548) / [#4563](https://git.1814.love:8443/wx/HL/issues/4563)
- **PR**: [#4551](https://git.1814.love:8443/wx/HL/pulls/4551) / [#4564](https://git.1814.love:8443/wx/HL/pulls/4564)
- **Merge commit**: [a66e78143](https://git.1814.love:8443/wx/HL/commit/a66e78143) / [37e52315d](https://git.1814.love:8443/wx/HL/commit/37e52315d)

### 13.2 联系人

- **后端负责人**: @yst
- **前端对接（管理后台）**: 终止行程·退款计算弹窗
