# 订单财务明细查询（优惠/增项清单）— 接口说明 + 变更预览历史数据源用法

- **日期**: 2026-06-22
- **端类型**: 管理后台
- **服务**: hl-order-service-v3（端口 8086）
- **接口路径前缀**: /v3/admin/order
- **Issue**: [#4205](https://git.1814.love:8443/wx/HL/issues/4205)
- **PR**: [#4207](https://git.1814.love:8443/wx/HL/pulls/4207)
- **后端负责人**: 腰苏图（yaosutu）

---

## 1. 接口背景

订单增减项弹窗（POST /v3/admin/order/{orderId}/adjustment）需要在打开时先展示该订单的历史增减项明细与合计，让定制师在看到存量数据的前提下再填写本次变更金额。

本接口 `GET /v3/admin/order/{orderId}/discount-surcharge/list` 承担这一**历史数据源**角色：

- 打开弹窗 → 前端调本接口（`includeReversed=false`）→ 渲染历史明细列表 + 合计
- 定制师填入本次金额 → 前端用 `summary.activeDiscountTotal` / `activeSurchargeTotal` + 本次 amount 实时推算新综合价/应收尾款（本地计算，不调接口）
- 定制师确认提交 → 调 POST /adjustment → 响应 `discountAmountAfter` / `surchargeAmountAfter` 即最新合计，直接刷新 UI，不必重查本接口

与 `22_4205_订单增减项改造-新增接口-管理后台.md`（写入端 /adjustment）配套阅读，两份合起来构成完整的增减项弹窗对接说明。

---

## 2. 变更清单

| # | 变更类型 | 接口 / 字段 | 说明 |
|---|----------|------------|------|
| 1 | ✨ 新增字段 | discounts[].itemCode | 优惠字典编码（来自 order_discount_item 字典） |
| 2 | ✨ 新增字段 | discounts[].itemName | 优惠字典中文名（如：老客户回馈、临时优惠） |
| 3 | ✨ 新增字段 | surcharges[].itemCode | 增项字典编码（来自 order_surcharge_item 字典） |
| 4 | ✨ 新增字段 | surcharges[].itemName | 增项字典中文名（如：加项景点、用车费用） |
| 5 | 📝 接口说明 | GET /v3/admin/order/{orderId}/discount-surcharge/list | 补充变更预览场景下的标准调用方式 |

> 本文件重点说明**列表查询**接口的字段含义与调用场景。写入端（POST /adjustment）见同目录另一份 changelog。

---

## 3. 接口详情

| 项 | 值 |
|---|---|
| 方法 | GET |
| 路径 | /v3/admin/order/{orderId}/discount-surcharge/list |
| 接口名 | 查询订单优惠+附加费清单 |
| 认证 | Bearer JWT（管理后台 token） |
| 幂等性 | 只读，天然幂等 |
| 限流 | 无特殊限流 |
| Content-Type | 无请求体 |

---

## 4. 接口入参

### 4.1 路径参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| orderId | Long | 是 | 订单 ID（雪花 ID，前端以字符串传输防精度丢失） |

### 4.2 Query 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| includeReversed | Boolean | 否 | true | false=仅返 ACTIVE 有效项；true=含 REVERSED/REVERSAL 历史撤销行。**变更预览弹窗固定传 false** |
| category | String | 否 | ALL | 暂未启用，固定传 ALL 或直接不传 |

---

## 5. 出参字段

### 5.1 顶层结构

| 字段 | 类型 | 说明 |
|------|------|------|
| orderId | Long | 订单 ID |
| discounts | DiscountVO[] | 历史优惠/减项列表（按 created_at 升序） |
| surcharges | SurchargeVO[] | 历史增项/附加费列表（按 created_at 升序） |
| summary | SummaryVO | 聚合统计 |

### 5.2 discounts[] 字段（DiscountVO）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 主键（雪花 ID，JSON 序列化为字符串） |
| discountType | String | 优惠类型，固定值 MANUAL |
| itemCode | String | 优惠字典编码（如 LOYAL_CUSTOMER），来自 order_discount_item；存量历史行可能为 null |
| itemName | String | 优惠字典中文名（如 老客户回馈）；存量历史行可能为 null，用 discountName 兜底 |
| discountName | String | 优惠描述文本 |
| discountAmount | String | 金额（元，BigDecimal 序列化为字符串，正数） |
| sourceType | String | 来源类型：MANUAL=手动录入 |
| sourceRefId | Long | 来源关联 ID，MANUAL 时为 null |
| status | String | 行状态（ACTIVE / REVERSED / REVERSAL），见 6.1 节 |
| reverseRefId | Long | 指向被撤销的原行 ID，仅 REVERSAL 行有值，ACTIVE 时 null |
| operatorId | Long | 操作人 ID |
| operatorName | String | 操作人姓名快照（写入时取，历史不变） |
| createdAt | String | 写入时间（ISO-8601，如 2026-05-10T16:08:32） |

### 5.3 surcharges[] 字段（SurchargeVO）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 主键（字符串序列化） |
| itemCode | String | 增项字典编码（如 EXTRA_ATTRACTION），来自 order_surcharge_item；存量历史行可能为 null |
| itemName | String | 增项字典中文名（如 加项景点）；存量历史行可能为 null，用 surchargeName 兜底 |
| surchargeName | String | 增项描述文本 |
| surchargeAmount | String | 金额（元，String 序列化，正数） |
| sourceType | String | 来源类型：MANUAL=手动 / ITINERARY_EDIT=行程编辑联动 |
| sourceRefId | Long | 来源关联 ID（MANUAL 时 null，ITINERARY_EDIT 时为行程编辑记录 ID） |
| status | String | 行状态（ACTIVE / REVERSED / REVERSAL） |
| reverseRefId | Long | 指向被撤销的原行 ID，仅 REVERSAL 行有值 |
| operatorId | Long | 操作人 ID |
| operatorName | String | 操作人姓名快照 |
| createdAt | String | 写入时间（ISO-8601） |

### 5.4 summary 字段（SummaryVO）

| 字段 | 类型 | 说明 |
|------|------|------|
| activeDiscountTotal | String | 当前生效优惠合计（仅 ACTIVE 行 SUM，String） |
| activeSurchargeTotal | String | 当前生效增项合计（仅 ACTIVE 行 SUM，String） |
| discountAmountMirror | String | order_main.discount_amount 的镜像值，与 activeDiscountTotal 正常相等，双校验用 |
| surchargeAmountMirror | String | order_main.surcharge_amount 的镜像值 |

**变更预览推算公式（前端本地计算，不调接口）：**

```
新优惠合计 = activeDiscountTotal + 本次 discountAmount（减项为正数）
新增项合计 = activeSurchargeTotal + 本次 surchargeAmount
新综合价   = orderAmount - 新优惠合计 + 新增项合计
应收尾款   = 新综合价 - 已收款（前端自有字段）
```

> 必须用 BigNumber / Decimal.js 运算，禁止 JS 原生浮点。

---

## 6. 枚举 / 数据字典

### 6.1 status 枚举（discounts[].status / surcharges[].status）

| 值 | 含义 | 前端渲染建议 |
|----|------|------------|
| ACTIVE | 当前有效 | 正常渲染 |
| REVERSED | 已被撤销（原行被抹除） | 仅 includeReversed=true 时返回；渲染删除线 |
| REVERSAL | 反向冲销行（撤销动作本身） | 仅 includeReversed=true 时返回；灰显或隐藏 |

> 变更预览弹窗传 includeReversed=false，后端不返 REVERSED/REVERSAL 行，前端无需再过滤。

### 6.2 sourceType 枚举

| 值 | 来源场景 |
|----|--------|
| MANUAL | 定制师在订单增减项弹窗手动录入 |
| ITINERARY_EDIT | 行程编辑模块联动产生的费用增减 |

### 6.3 order_discount_item 字典（减项选项）

> 通过 GET /admin/dict/data/order_discount_item 动态获取；以下为发版参考值。

| dictValue（itemCode） | dictLabel（itemName） |
|----------------------|----------------------|
| LOYAL_CUSTOMER | 老客户回馈 |
| PROMO_CODE | 优惠码减免 |
| GROUP_DISCOUNT | 团体优惠 |
| HOLIDAY_PROMO | 节假日促销 |
| REFERRAL | 推荐奖励 |
| COMPLAINT_COMP | 投诉补偿 |
| MANUAL_ADJUST | 临时调价 |
| OTHER_DISCOUNT | 其他优惠 |

### 6.4 order_surcharge_item 字典（增项选项）

| dictValue（itemCode） | dictLabel（itemName） |
|----------------------|----------------------|
| EXTRA_ATTRACTION | 加项景点 |
| VEHICLE_FEE | 用车费用 |
| ACCOMMODATION | 住宿升级 |
| CATERING | 餐饮加项 |
| SERVICE_UPGRADE | 服务升级 |
| OTHER_SURCHARGE | 其他增项 |

---

## 7. 错误码

| 错误码 | HTTP 状态 | 含义 | 前端处理建议 |
|--------|-----------|------|------------|
| 200 | 200 | 正常（discounts/surcharges 为空数组也返 200） | 渲染空列表 |
| 587001 | 404 | 订单不存在 | 关闭弹窗并 toast 订单不存在 |

---

## 8. 示例

### 8.1 典型成功（弹窗打开，includeReversed=false）

请求：
```
GET /v3/admin/order/1234567890001/discount-surcharge/list?includeReversed=false
Authorization: Bearer <admin-token>
```

响应：
```json
{
  "code": 200,
  "data": {
    "orderId": 1234567890001,
    "discounts": [
      {
        "id": "8800001234567",
        "discountType": "MANUAL",
        "itemCode": "LOYAL_CUSTOMER",
        "itemName": "老客户回馈",
        "discountName": "老客户回馈减免 200",
        "discountAmount": "200.00",
        "sourceType": "MANUAL",
        "sourceRefId": null,
        "status": "ACTIVE",
        "reverseRefId": null,
        "operatorId": 30001,
        "operatorName": "李定制师",
        "createdAt": "2026-05-10T16:08:32"
      }
    ],
    "surcharges": [
      {
        "id": "8800009876543",
        "itemCode": "EXTRA_ATTRACTION",
        "itemName": "加项景点",
        "surchargeName": "加项景点：羊卓雍措一日游",
        "surchargeAmount": "240.00",
        "sourceType": "MANUAL",
        "sourceRefId": null,
        "status": "ACTIVE",
        "reverseRefId": null,
        "operatorId": 30001,
        "operatorName": "李定制师",
        "createdAt": "2026-05-10T16:12:10"
      }
    ],
    "summary": {
      "activeDiscountTotal": "200.00",
      "activeSurchargeTotal": "240.00",
      "discountAmountMirror": "200.00",
      "surchargeAmountMirror": "240.00"
    }
  }
}
```

本次新增减项 100 元时，弹窗变更预览本地推算：

```
新优惠合计 = 200.00 + 100.00 = 300.00
新增项合计 = 240.00（本次无增项）
新综合价   = orderAmount - 300.00 + 240.00
```

### 8.2 边界情况（无历史增减项）

请求：
```
GET /v3/admin/order/9999999999001/discount-surcharge/list?includeReversed=false
```

响应：
```json
{
  "code": 200,
  "data": {
    "orderId": 9999999999001,
    "discounts": [],
    "surcharges": [],
    "summary": {
      "activeDiscountTotal": "0.00",
      "activeSurchargeTotal": "0.00",
      "discountAmountMirror": "0.00",
      "surchargeAmountMirror": "0.00"
    }
  }
}
```

> 空数组时渲染「暂无历史增减项」；summary 全零，变更预览公式仍正确。

### 8.3 业务失败（订单不存在）

请求：
```
GET /v3/admin/order/0000000000001/discount-surcharge/list
```

响应：
```json
{
  "code": 587001,
  "msg": "订单不存在"
}
```

---

## 9. 业务边界

### 适用场景

- 订单增减项弹窗打开时，拉历史明细和合计用于变更预览（传 includeReversed=false）
- 订单详情页财务明细 Tab 展示完整流水（传 includeReversed=true）

### 不适用场景

- 本接口只读；录入新增减项必须调 POST /v3/admin/order/{orderId}/adjustment
- 不适合用作提交后刷新数据源；POST /adjustment 响应已携带 discountAmountAfter/surchargeAmountAfter，直接用

### 特殊边界

- discountAmountMirror / surchargeAmountMirror 是 order_main 持久列快照；极端并发下与 activeTotal 可能短暂不一致，以 Mirror 值为账面权威
- includeReversed=true 时，REVERSED 与 REVERSAL 两种行都返回，前端可按 status 差异渲染（删除线 / 灰色）

---

## 10. 修改前后对比

### 字段级对比

| 字段 | 改前 | 改后 | 影响 |
|------|------|------|------|
| discounts[].itemCode | 不存在 | 新增 String | 前端可渲染字典标签 chip |
| discounts[].itemName | 不存在 | 新增 String | 前端直接展示中文名，无需自行查字典 |
| surcharges[].itemCode | 不存在 | 新增 String | 同上 |
| surcharges[].itemName | 不存在 | 新增 String | 同上 |

### 行为级对比

| 维度 | 改前 | 改后 |
|------|------|------|
| discountName/surchargeName | 纯自由文本 | 由字典 itemName 生成，标准化 |
| 变更预览历史合计 | 无此字段，弹窗无法预算 | summary.activeDiscountTotal/activeSurchargeTotal 支撑弹窗实时推算 |

---

## 11. 影响评估 / 回滚

### 兼容性

- discounts[] / surcharges[] 新增 itemCode / itemName 为纯增量字段，不影响已渲染旧字段
- 存量历史记录 itemCode/itemName 为 null，前端用 discountName/surchargeName 兜底

### 前端同步上线

- 变更预览弹窗对接：打开时调本接口（includeReversed=false），拿 summary 用于预览推算
- 现有财务明细渲染：按需接入 itemCode/itemName，新旧字段共存，可渐进迁移

### 回滚方案

- 后端回滚后 itemCode/itemName 不返回，前端渲染降级为 discountName/surchargeName，无破坏
- 无 DDL 变更，回滚零风险

---

## 12. 注意事项

1. **金额全部 String**：discountAmount、surchargeAmount、summary 四个合计字段均为字符串。前端必须用 BigNumber/Decimal.js 运算，禁止 JS 原生浮点。

2. **includeReversed 必须显式传 false**：变更预览弹窗调用时必须明确传 includeReversed=false，后端默认值为 true，不传则返回撤销历史行，导致合计计算错误。

3. **提交后不必重查本接口**：POST /adjustment 成功响应直接携带 discountAmountAfter / surchargeAmountAfter 最新合计，前端用这两个字段刷新 UI，无需再调 list。

4. **itemCode/itemName 存量行为 null**：早于本次改造写入的记录 itemCode/itemName 为 null，前端渲染时以 discountName/surchargeName 兜底。

5. **变更预览推算是前端本地计算**：不需要向后端预查，直接用 summary + 本次输入金额在前端推算，减少往返。

---

## 13. 关联 / 联系人

| 项 | 值 |
|---|---|
| Issue | [#4205 订单增减项改造](https://git.1814.love:8443/wx/HL/issues/4205) |
| PR（写入端 /adjustment） | [#4207 订单增减项改造](https://git.1814.love:8443/wx/HL/pulls/4207) |
| 配套 changelog（写入端） | changelogs-v2/2026-06/22_4205_订单增减项改造-新增接口-管理后台.md |
| 后端负责人 | 腰苏图（yaosutu） |
| Gitea 仓库 | https://git.1814.love:8443/wx/HL |
