# 订单增减项改造 — 新增统一端点 + 出参增 itemCode/itemName

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

---

## 1. 接口背景

订单详情页原「添加优惠」按鈕，升级为「订单增减项」：**既能加优惠（减项）又能加费用（增项）**。改造前项目名是自由文本输入；改造后改为**数据字典选项**，前端渲染 chip 单选。

本次变更要点：

- **新增** 统一端点 POST /v3/admin/order/{orderId}/adjustment，direction 字段区分减项/增项，前端只维护一套提交逻辑
- **新建** 两个数据字典 type（order_discount_item / order_surcharge_item），前端动态拉取渲染
- **增强** 优惠/附加费清单接口出参，discounts[]/surcharges[] 各新增 itemCode + itemName 字段
- 旧端点 POST /v3/admin/order/{orderId}/discounts 和 /surcharges 已标为 @Deprecated，前端改调 /adjustment；**撤销端点不变**

---

## 2. 变更清单

| # | 变更类型 | 接口 / 字段 | 影响 |
|---|----------|------------|------|
| 1 | ✨ 新增接口 | POST /v3/admin/order/{orderId}/adjustment | 统一增减项写入，替代旧两端点 |
| 2 | ✨ 新增字典 | order_discount_item（8 项） | 减项/优惠选项，前端动态拉取 |
| 3 | ✨ 新增字典 | order_surcharge_item（6 项） | 增项/费用选项，前端动态拉取 |
| 4 | ✨ 出参新增字段 | discounts[].itemCode / discounts[].itemName | 优惠/附加费清单接口 |
| 5 | ✨ 出参新增字段 | surcharges[].itemCode / surcharges[].itemName | 同上 |
| 6 | 🔧 旧端点废弃 | POST /v3/admin/order/{orderId}/discounts | @Deprecated，Swagger 隐藏，前端不再调用 |
| 7 | 🔧 旧端点废弃 | POST /v3/admin/order/{orderId}/surcharges | @Deprecated，Swagger 隐藏，前端不再调用 |

---

## 3. 接口详情

### 3.1 新增统一增减项端点

| 项 | 值 |
|---|---|
| 方法 | POST |
| 路径 | /v3/admin/order/{orderId}/adjustment |
| 接口名 | 订单增减项（统一端点，按 direction 分流） |
| 认证 | Bearer JWT（管理后台 token） |
| 幂等性 | 有（@Idempotent，同用户同订单重复提交返回首次结果） |
| 并发锁 | 有（@Lock4j，同订单并发请求串行执行） |
| Content-Type | application/json |

### 3.2 字典查询接口（走 user-service，路径与现有字典接口相同）

| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/dict/data/order_discount_item | 获取减项选项列表 |
| GET | /admin/dict/data/order_surcharge_item | 获取增项选项列表 |

返回格式示例：

前端渲染 chip 单选，提交时传 value（即 itemCode）。

---

## 4. 接口入参

### 4.1 路径参数

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

### 4.2 请求体字段（OrderAdjustmentReqVO）

| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| direction | String | 是 | 增减方向：REDUCE=减项/优惠，ADD=增项/附加费 | "REDUCE" |
| itemCode | String | 是 | 项目代码：REDUCE 时取 order_discount_item 的 value，ADD 时取 order_surcharge_item 的 value | "OLD_CUSTOMER" |
| amount | String | 是 | 金额（元），必须 > 0，传字符串防 JS 精度丢失 | "200.00" |
| remark | String | 否 | 备注，最多 500 字 | "春节活动老客户优惠" |

---

## 5. 出参字段

### 5.1 统一端点出参

direction=REDUCE 时返回优惠记录（OrderDiscountRespVO），direction=ADD 时返回附加费记录（OrderSurchargeRespVO）。

**direction=REDUCE 出参（优惠记录）**

| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
| id | String | 优惠记录主键 | "8800001234567" |
| orderId | String | 订单 ID | "12345678901234" |
| discountType | String | 优惠类型（统一端点写入时存 itemCode 值） | "OLD_CUSTOMER" |
| itemCode | String | **新增** 优惠项目 itemCode（字典 order_discount_item value） | "OLD_CUSTOMER" |
| itemName | String | **新增** 优惠项目中文名（字典 label 快照） | "老客户回访" |
| discountName | String | 优惠描述（与 itemName 相同，为字典中文名快照） | "老客户回访" |
| discountAmount | String | 优惠金额（元） | "200.00" |
| sourceType | String | 来源类型（统一端点固定 MANUAL） | "MANUAL" |
| sourceRefId | String/null | 来源关联 ID | null |
| status | String | 记录状态（写入后固定 ACTIVE） | "ACTIVE" |
| operatorId | String | 操作人 ID（JWT 派生） | "30001" |
| operatorName | String | 操作人姓名快照 | "李定制师" |
| createdAt | String | 写入时间（ISO 8601） | "2026-06-22T16:08:32" |
| orderDiscountAmountAfter | String | 写入后订单优惠总额镜像（本订单所有 ACTIVE 优惠合计） | "200.00" |

**direction=ADD 出参（附加费记录）**

| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
| id | String | 附加费记录主键 | "8800009876543" |
| orderId | String | 订单 ID | "12345678901234" |
| itemCode | String | **新增** 增项项目 itemCode（字典 order_surcharge_item value） | "ROOM_UPGRADE" |
| itemName | String | **新增** 增项项目中文名（字典 label 快照） | "升级房型" |
| surchargeName | String | 附加费描述（与 itemName 相同） | "升级房型" |
| surchargeAmount | String | 附加费金额（元） | "300.00" |
| sourceType | String | 来源类型（统一端点固定 MANUAL） | "MANUAL" |
| sourceRefId | String/null | 来源关联 ID | null |
| status | String | 记录状态（写入后固定 ACTIVE） | "ACTIVE" |
| operatorId | String | 操作人 ID | "30001" |
| operatorName | String | 操作人姓名快照 | "李定制师" |
| createdAt | String | 写入时间（ISO 8601） | "2026-06-22T16:12:00" |
| orderSurchargeAmountAfter | String | 写入后订单附加费总额镜像（本订单所有 ACTIVE 附加费合计） | "300.00" |

### 5.2 优惠/附加费清单接口新增字段

接口：GET /v3/admin/order/{orderId}/discounts-surcharges（路径不变）

**discounts[] 新增字段**

| 字段 | 类型 | 说明 | 历史数据 |
|---|---|---|---|
| itemCode | String/null | 优惠项目 itemCode | null（早鸟、行程编辑等历史写入行） |
| itemName | String/null | 优惠项目中文名 | null（历史行兼容） |

**surcharges[] 新增字段**

| 字段 | 类型 | 说明 | 历史数据 |
|---|---|---|---|
| itemCode | String/null | 增项项目 itemCode | null（历史行兼容） |
| itemName | String/null | 增项项目中文名 | null（历史行兼容） |

历史行（早鸟自动写入、行程编辑产生的记录）itemCode/itemName 返回 null，前端渲染时降级显示 discountName/surchargeName 字段。

---

## 6. 枚举 / 数据字典

### 6.1 AdjustmentDirection 枚举（入参 direction 字段）

| 值 | 含义 | 落库位置 |
|---|---|---|
| REDUCE | 减项/优惠，订单总额减少 | order_discount 表 |
| ADD | 增项/附加费，订单总额增加 | order_surcharge 表 |

### 6.2 order_discount_item 字典（dictType=order_discount_item）

减项/优惠选项，前端调 GET /admin/dict/data/order_discount_item 动态渲染。

| value（itemCode，提交时传此值） | label（展示中文） | sort |
|---|---|---|
| OLD_CUSTOMER | 老客户回访 | 1 |
| DISTRIBUTION | 分销返点 | 2 |
| PEER_PRICE | 同行价 | 3 |
| FAMILY_PRICE | 亲友价 | 4 |
| COMPLAINT_COMP | 投诉补偿 | 5 |
| FESTIVAL | 生日节日 | 6 |
| GROUPON | 团购优惠 | 7 |
| OTHER | 其他 | 8 |

### 6.3 order_surcharge_item 字典（dictType=order_surcharge_item）

增项/费用选项，前端调 GET /admin/dict/data/order_surcharge_item 动态渲染。

| value（itemCode，提交时传此值） | label（展示中文） | sort |
|---|---|---|
| ROOM_UPGRADE | 升级房型 | 1 |
| ADD_ENTERTAINMENT | 加购娱乐项目 | 2 |
| ADD_VEHICLE | 增加用车 | 3 |
| EXTRA_PERSON | 超员补位 | 4 |
| ADD_ITINERARY | 行程加点 | 5 |
| OTHER | 其他 | 6 |

---

## 7. 错误码

| 错误码 | 含义 | 触发场景 |
|---|---|---|
| 200 | 成功 | — |
| 581306 | ADJUSTMENT_ITEM_INVALID：itemCode 不在对应字典范围内 | direction=REDUCE 时 itemCode 不在 order_discount_item；direction=ADD 时 itemCode 不在 order_surcharge_item；或字典服务不可用 |
| 400 | 参数校验失败 | direction/itemCode 为空，amount 为空或 ≤ 0，remark 超 500 字 |
| 404（内部业务码） | 订单不存在 | orderId 无对应订单 |

> 字典服务（user-service）不可用时，后端降级返回空选项集，任何 itemCode 均触发 581306 拒绝（保守策略）。

---

## 8. 示例

### 8.1 典型成功——减项（老客户优惠 200 元）

请求：



响应：



### 8.2 典型成功——增项（升级房型 300 元）

请求：



响应：



### 8.3 业务失败——itemCode 不合法（错误码 581306）

请求：



响应：



> "VIP" 是旧端点历史枚举值，不在 order_discount_item 字典，统一端点拒绝。

---

## 9. 业务边界

**适用**：
- 订单处于任何状态均可操作（无订单状态限制）
- 一笔订单可叠加多条优惠/附加费记录，每条独立成行

**不适用**：
- 旧端点（/discounts、/surcharges）已废弃，前端不应再调用

**特殊边界**：
- 减项/增项金额均无上限校验，业务人员自行把握
- 历史行（早鸟写入、行程编辑产生）itemCode/itemName 返回 null，前端渲染时降级显示 discountName/surchargeName
- 同一请求遭并发时（@Lock4j），后到的排队等待，不会重复写入

---

## 10. 修改前后对比

### 10.1 写入端点

| 场景 | 改造前 | 改造后 |
|---|---|---|
| 加优惠 | POST /{orderId}/discounts，discountName 自由文本 | POST /{orderId}/adjustment，direction=REDUCE + itemCode 字典选项 |
| 加附加费 | POST /{orderId}/surcharges，surchargeName 自由文本 | POST /{orderId}/adjustment，direction=ADD + itemCode 字典选项 |

### 10.2 出参字段变化（清单接口）

| 位置 | 改造前 | 改造后 |
|---|---|---|
| discounts[n] | 无 itemCode / itemName 字段 | 新增 itemCode、itemName（统一端点写入时有值，历史行 null） |
| surcharges[n] | 无 itemCode / itemName 字段 | 新增 itemCode、itemName（同上） |

### 10.3 项目名来源变化

| 改造前 | 改造后 |
|---|---|
| discountName/surchargeName 由用户自由输入文本 | 由 itemCode 查字典 label 后端回填，存入 discountName/surchargeName 快照列 |

---

## 11. 影响评估 / 回滚

**前端需同步操作**：

1. 订单增减项弹窗的原因/项目 chips 改为动态调字典接口渲染（/admin/dict/data/order_discount_item 和 order_surcharge_item），不再硬编码
2. 提交时调 /adjustment，传 direction + itemCode + amount + remark
3. 清单接口（discounts/surcharges 列表）新增 itemCode/itemName 字段，建议优先显示 itemName，null 时降级显示 discountName/surchargeName

**破坏兼容性**：旧端点仍保留可调（非破坏性），但 Swagger 已隐藏，前端代码应切换。

**上线顺序建议**：user-service 须先于或同时于 order-v3 部署（字典须先存在）；建议先上线后端，再上线前端。

**回滚方案**：
- 后端：重新部署旧版 hl-order-service-v3 + hl-user-service（旧端点仍在）
- 前端：切回调旧两端点即可

---

## 12. 注意事项

1. **user-service 先行**：order-v3 /adjustment 端点运行时调 user-service 拉字典，若字典不存在，任何 itemCode 均触发 581306 拒绝。两服务须同批或 user-service 先上。
2. **itemCode 大小写敏感**：传 "OLD_CUSTOMER" 不能传 "old_customer"。
3. **amount 传字符串**：防 JS 数值精度丢失，传 "200.00" 而非数字 200.00。
4. **字典 value 不可随意修改**：value 已写入历史快照字段，运营改 value 会导致历史数据 itemCode 无法回查中文名（itemName 返回 null，discountName 仍有值可降级）。
5. **多条叠加**：多次调 /adjustment 每次写一条新记录，清单接口可见全部明细，镜像值为当前全部 ACTIVE 之和。

---

## 13. 关联 / 联系人

- **Issue**: https://git.1814.love:8443/wx/HL/issues/4205
- **PR**: https://git.1814.love:8443/wx/HL/pulls/4207
- **Commit**: https://git.1814.love:8443/wx/HL/commit/3a3577aa9de39857d66c3e1811420ce175956f0a
- **后端负责人**: 腰苏图（yaosutu）
