docs(changelog): 终止行程重复提交改一律581049(#5598 / PR #5604)
所有检测均成功
changelog-filename-gate / validate (push) Successful in 1s
所有检测均成功
changelog-filename-gate / validate (push) Successful in 1s
- COMPLETED 订单再次提交 terminate 由同正文 200 重放改为一律 581049「订单已终止,禁止重复提交」,零副作用 - 581049 文案由「终止行程重试与首次终止边界或请求不一致」改为「订单已终止,禁止重复提交」 - 首次终止(TRAVELLING→COMPLETED)行为不变;入参/出参字段结构不变 - 取代 04_5460 同正文重放契约,前端收到 581049 应视为订单已终止并改走查询接口取退款结果
这个提交包含在:
父节点
b6181f2c0a
当前提交
be67501c86
@ -0,0 +1,312 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "5598"
|
||||||
|
title: "终止行程重复提交改一律 581049:COMPLETED 订单再次提交不再重放首次结果"
|
||||||
|
consumer: "admin"
|
||||||
|
change_type: "修改接口"
|
||||||
|
author: "yst(GIT)"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "verified"
|
||||||
|
frontend_status: "pending"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "PR #5604 已合并 dev-v3 并部署 TEST,网关实测:COMPLETED 订单再次提交 terminate 一律返回 581049「订单已终止,禁止重复提交」(同正文也不再 200 重放)。前端如依赖 #5460 的同正文重放取回结果逻辑需适配。"
|
||||||
|
updated_at: "2026-08-06"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🔧【修改接口·管理后台】终止行程重复提交改一律 581049 (#5598)
|
||||||
|
|
||||||
|
> **PR**:[#5604](https://git.1814.love:8443/wx/HL/pulls/5604) | **服务**:`hl-order-service-v3` | **更新时间**:2026-08-06 | **消费端**:管理后台
|
||||||
|
|
||||||
|
## 1. 接口背景
|
||||||
|
|
||||||
|
终止行程(出行中提前结束订单)是**有副作用的写操作**:首次提交会把订单从 TRAVELLING 推进到 COMPLETED、生成终止退款单、写核单退款关联。此前(#5460)对"首次成功但响应丢失"的场景提供了幂等重放:同正文重放返回 200 和既有退款结果。实测中发现该重放语义让"重复提交"与"首次提交"边界模糊,且重放判定依赖请求指纹快照,对升级前的历史订单还要做语义等价兜底,复杂度高、收益低。
|
||||||
|
|
||||||
|
本次收紧为**状态机幂等**:订单一旦 COMPLETED(已终止/已完成),再次提交终止**一律拒绝**,返回 `581049「订单已终止,禁止重复提交」`,零副作用(不重写退款、不重发事件、不做指纹比对)。
|
||||||
|
|
||||||
|
**首次正常终止(TRAVELLING → COMPLETED)行为完全不变**,仍返回 200 和退款结果。
|
||||||
|
|
||||||
|
## 变更接口清单
|
||||||
|
|
||||||
|
| # | 接口名 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 终止行程(出行中) | `POST` | `/v3/admin/order/:id/terminate` | 修改 | COMPLETED 订单重复提交:同正文重放由 `200` 返回既有结果改为一律 `581049`;581049 文案由「终止行程重试与首次终止边界或请求不一致」改为「订单已终止,禁止重复提交」 |
|
||||||
|
|
||||||
|
入参字段结构、出参字段结构**均无变化**。
|
||||||
|
|
||||||
|
## 3. 接口详情
|
||||||
|
|
||||||
|
### 3.1 终止行程(出行中)
|
||||||
|
|
||||||
|
- **接口说明**:出行中因特殊原因提前终止订单,订单进入 COMPLETED 状态,由核单/结算流程继续处理;后端按权威数据重算退款金额。
|
||||||
|
- **使用场景**:管理后台订单详情页,出行中订单执行"终止行程"操作。
|
||||||
|
- **认证**:需要管理后台登录态和订单操作权限;房务角色不可操作。
|
||||||
|
- **幂等性**:**不再提供重放幂等**。首次提交成功即生效;订单 COMPLETED 后任何再次提交(无论正文是否与首次一致)一律返回 `581049`,零副作用。
|
||||||
|
- **限流**:未声明接口级独立限流规则。
|
||||||
|
|
||||||
|
## 4. 接口入参
|
||||||
|
|
||||||
|
### 4.1 路径参数 / Query 参数
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 说明与校验 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `id` | Path | Long | 是 | 订单 ID(路径写作 `:id`) |
|
||||||
|
|
||||||
|
无 Query 参数。
|
||||||
|
|
||||||
|
### 4.2 请求体字段
|
||||||
|
|
||||||
|
请求体类型:`OrderTerminateTripReqVO`(**本次无变化**)。
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明与校验 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `cancelReason` | String | 是 | 终止原因 + 调整说明(合并,不单列 adjustReason) |
|
||||||
|
| `endDayNumber` | Integer | 是 | 停在第几天(截断点,1-based),最小 1 |
|
||||||
|
| `lineUsages[]` | Array | 否 | 统一资源行已用判定列表;优先级高于旧分组数组,`lineKey` 来自 refund-preview 的 `refundLines[].lineKey` |
|
||||||
|
| `lineUsages[].lineKey` | String | 是(数组内) | 退款资源行 key,示例 `ITINERARY_NODE:97010:2` |
|
||||||
|
| `lineUsages[].used` | Boolean | 是(数组内) | 是否已使用 |
|
||||||
|
| `rooms[]` | Array | 否 | 住宿已用判定列表;兼容旧入参,新逻辑优先使用 `lineUsages` |
|
||||||
|
| `tickets[]` | Array | 否 | 门票/活动已用判定列表;兼容旧入参,`refId` = 行程节点 nodeId |
|
||||||
|
| `services[]` | Array | 否 | 服务已用判定列表;兼容旧入参,`refId` = 行程节点 nodeId |
|
||||||
|
| `supplies[]` | Array | 否 | 备品已用判定列表;兼容旧入参,`refId` 与预览返回一致 |
|
||||||
|
| `rooms[]/tickets[]/services[]/supplies[]` 元素 | Object | — | 结构为 refId Long 必填 + used Boolean 必填 |
|
||||||
|
| `vehicles[]` | Array | 否 | 用车每天已用判定列表;仅兼容接收,`used` 不参与计算,后端按车辆 serviceDate 与终止日权威重算 |
|
||||||
|
| `vehicles[].refId` | Long | 是(数组内) | 配车记录 ID(同段车跨天 refId 相同,靠 dayNumber 区分) |
|
||||||
|
| `vehicles[].dayNumber` | Integer | 是(数组内) | 第几天(1-based) |
|
||||||
|
| `vehicles[].used` | Boolean | 是(数组内) | 兼容字段:必填,但车辆退款金额不采用该值 |
|
||||||
|
| `adjustAmount` | Decimal | 否 | 人工调整额(允许负,默认 0;说明并入 cancelReason) |
|
||||||
|
|
||||||
|
## 5. 出参字段
|
||||||
|
|
||||||
|
响应类型:`Result<OrderTerminateTripRespVO>`(**本次无变化**)。
|
||||||
|
|
||||||
|
### 5.1 统一响应外层
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可空 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `code` | Integer | 否 | 成功为 `200`;失败见第 7 节 |
|
||||||
|
| `message` | String | 否 | 结果说明 |
|
||||||
|
| `data` | Object/null | 失败时为空 | 成功时为终止结果 |
|
||||||
|
| `traceId` | String | 是 | 链路追踪 ID |
|
||||||
|
| `success` | Boolean | 否 | `code=200` 时为 `true` |
|
||||||
|
|
||||||
|
### 5.2 成功响应 `data`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 可空 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `terminateRefundId` | String(Long) | 否 | 终止退款主表 ID,按字符串返回 |
|
||||||
|
| `baselineRefund` | String(Decimal) | 否 | 系统建议基线 = max(0, 已付金额 - 已用金额),按字符串返回 |
|
||||||
|
| `adjustAmount` | String(Decimal) | 否 | 人工调整额(入参回显,默认 0),按字符串返回 |
|
||||||
|
| `finalRefund` | String(Decimal) | 否 | 最终返还额 = max(0, baselineRefund + adjustAmount),按字符串返回 |
|
||||||
|
| `settlementRefundId` | String(Long) | 是 | 关联的核单退款结算 ID(核单模块写入后回填),按字符串返回 |
|
||||||
|
| `newStatus` | String | 否 | 终止后订单粗状态,固定 `COMPLETED` |
|
||||||
|
| `newFlowStatus` | String | 否 | 终止后流程细状态,固定 `PENDING_REVIEW` |
|
||||||
|
|
||||||
|
## 6. 枚举 / 数据字典
|
||||||
|
|
||||||
|
### 6.1 `data.newStatus`(订单粗状态,本接口相关取值)
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `TRAVELLING` | 出行中 | 终止前状态;只有该状态可首次提交终止 |
|
||||||
|
| `COMPLETED` | 已完成 | 终止后状态;该状态下再次提交一律 581049 |
|
||||||
|
|
||||||
|
### 6.2 `data.newFlowStatus`(流程细状态)
|
||||||
|
|
||||||
|
| 值 | 中文 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PENDING_REVIEW` | 待核单 | 终止成功后进入核单流程 |
|
||||||
|
|
||||||
|
本次无数值新增/删除/改义。
|
||||||
|
|
||||||
|
## 7. 错误码
|
||||||
|
|
||||||
|
| code | 含义 | 触发场景 |
|
||||||
|
|---|---|---|
|
||||||
|
| `200` | 终止成功 | 订单为 TRAVELLING 且校验通过;订单推进 COMPLETED 并生成终止退款 |
|
||||||
|
| `400` | 请求参数错误 | 缺 `cancelReason`/`endDayNumber`、数组内必填字段缺失等 |
|
||||||
|
| `581007` | 订单不存在 | `id` 对应订单不存在 |
|
||||||
|
| `581018` | 出行中取消仅适用于出行中订单 | 订单非 TRAVELLING 且非 COMPLETED(待出行/已取消等)时提交终止 |
|
||||||
|
| `581044` | 终止行程:结束日之前的资源必须标记为已使用 | 结束日前资源 used=false |
|
||||||
|
| `581047` | 终止行程:结束日不在订单行程范围内 | `endDayNumber` 越界 |
|
||||||
|
| `581048` | 终止行程:本地车辆费用服务日与订单行程不一致 | 车辆费用服务日与订单行程对不上 |
|
||||||
|
| **`581049`** | **订单已终止,禁止重复提交**(原「终止行程重试与首次终止边界或请求不一致」) | **订单已 COMPLETED 时再次提交终止,无论正文是否与首次一致** |
|
||||||
|
| `584100` | 车务车辆总车费暂时不可用,请稍后重试 | Fleet 车辆费用事实不可用,fail closed |
|
||||||
|
|
||||||
|
## 8. 示例
|
||||||
|
|
||||||
|
### 8.1 典型成功:出行中订单首次终止(行为不变)
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/2084280231786430466/terminate
|
||||||
|
Authorization: Bearer <管理后台访问令牌>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cancelReason": "客户高反送医终止",
|
||||||
|
"endDayNumber": 2,
|
||||||
|
"adjustAmount": 0,
|
||||||
|
"lineUsages": [
|
||||||
|
{
|
||||||
|
"lineKey": "HOTEL_ASSIGNMENT:96011:1",
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lineKey": "ITINERARY_NODE:97010:1",
|
||||||
|
"used": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicles": [
|
||||||
|
{
|
||||||
|
"refId": 9000004500000001,
|
||||||
|
"dayNumber": 1,
|
||||||
|
"used": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"terminateRefundId": "2084497785524011009",
|
||||||
|
"baselineRefund": "6500.00",
|
||||||
|
"adjustAmount": "0.00",
|
||||||
|
"finalRefund": "6500.00",
|
||||||
|
"settlementRefundId": "2084497785561759746",
|
||||||
|
"newStatus": "COMPLETED",
|
||||||
|
"newFlowStatus": "PENDING_REVIEW"
|
||||||
|
},
|
||||||
|
"traceId": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 边界情况:COMPLETED 订单重复提交(本次变化点)
|
||||||
|
|
||||||
|
**场景说明**:首次终止已成功(无论响应是否丢失),对同一订单再次提交终止——与首次正文完全相同或不同,结果一样。改前同正文重放会返回 200 和首次结果;改后一律 581049,零副作用。
|
||||||
|
|
||||||
|
**请求**:同 8.1(正文可与首次一致,也可不一致)。
|
||||||
|
|
||||||
|
**响应**(TEST 网关实测):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581049,
|
||||||
|
"message": "订单已终止,禁止重复提交",
|
||||||
|
"data": null,
|
||||||
|
"traceId": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 业务失败:结束日越界
|
||||||
|
|
||||||
|
**请求**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /v3/admin/order/2084280231786430466/terminate
|
||||||
|
Authorization: Bearer <管理后台访问令牌>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cancelReason": "客户临时改行程",
|
||||||
|
"endDayNumber": 99
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 581047,
|
||||||
|
"message": "终止行程:结束日不在订单行程范围内",
|
||||||
|
"data": null,
|
||||||
|
"traceId": null,
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 业务边界
|
||||||
|
|
||||||
|
- **适用场景**:订单为 TRAVELLING(出行中)时首次提交终止,走正常终止流程,返回 200 和退款结果。
|
||||||
|
- **不适用场景**:
|
||||||
|
- 订单已 COMPLETED(已终止/已完成):任何再次提交一律 `581049`,不产生任何副作用。
|
||||||
|
- 订单为其他状态(待出行/已取消等):`581018`。
|
||||||
|
- **特殊边界**:终止是有副作用操作,`581049` 不等于"重试成功";前端拿到 581049 后如需终止结果(terminateRefundId / finalRefund),应改走订单详情 / 退款相关查询接口取数,不要再重放 terminate。
|
||||||
|
- **金额口径不变**:车辆已用仍以车辆 serviceDate 与终止日权威判定,`vehicles[].used` 仅为兼容字段。
|
||||||
|
|
||||||
|
## 10. 修改前后对比
|
||||||
|
|
||||||
|
### 10.1 字段级对比
|
||||||
|
|
||||||
|
| 项 | 原来 | 现在 |
|
||||||
|
|---|---|---|
|
||||||
|
| 入参字段结构 | `OrderTerminateTripReqVO` 全字段 | 不变 |
|
||||||
|
| 出参字段结构 | `OrderTerminateTripRespVO` 全字段 | 不变 |
|
||||||
|
| 581049 错误文案 | 终止行程重试与首次终止边界或请求不一致 | **订单已终止,禁止重复提交** |
|
||||||
|
|
||||||
|
### 10.2 行为级对比
|
||||||
|
|
||||||
|
| 场景 | 原来(#5460) | 现在 |
|
||||||
|
|---|---|---|
|
||||||
|
| TRAVELLING 订单首次终止 | `200` 返回退款结果,订单推进 COMPLETED | **不变** |
|
||||||
|
| COMPLETED 订单**同正文**重放 | `200`,返回既有 terminateRefundId / baselineRefund / adjustAmount / finalRefund / settlementRefundId | **`581049` 订单已终止,禁止重复提交**,零副作用 |
|
||||||
|
| COMPLETED 订单**不同正文**重试 | `581049`(旧文案) | `581049`(新文案),行为不变 |
|
||||||
|
| 升级前已终止的历史订单重放 | 按快照语义等价校验,等价返 200 / 不等价 581049 | 一律 `581049` |
|
||||||
|
|
||||||
|
## 11. 影响评估 / 回滚
|
||||||
|
|
||||||
|
### 11.1 影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:**是,行为级破坏**。依赖"同正文重放返回 200 取回首次结果"的前端重试/补偿逻辑会失效——现在重放拿到的是 581049。
|
||||||
|
- **前端是否必须同步上线**:**建议同步**。适配方式:
|
||||||
|
1. 终止提交后若因超时/网络原因未收到响应,重试时收到 `581049` 应视为"订单已终止"(首次实际已成功),按成功路径收尾(刷新订单详情),不要当普通错误提示"提交失败"。
|
||||||
|
2. 如需展示首次终止的退款结果,收到 581049 后拉订单详情/退款查询接口取数,不再从重放响应获取。
|
||||||
|
3. 清理 #5460 时期按"同正文重放取回结果"编写的 workaround。
|
||||||
|
- 首次终止路径零变化,不重试的正常操作流程无感。
|
||||||
|
|
||||||
|
### 11.2 回滚方案
|
||||||
|
|
||||||
|
- 后端回滚即恢复 #5460 重放语义(同正文 200 / 不同正文 581049 旧文案)。前端兼容策略"581049 = 已终止,拉详情取数"在回滚后仍然成立(只是同正文重放会重新拿到 200),无需反向适配。
|
||||||
|
|
||||||
|
## 12. 注意事项
|
||||||
|
|
||||||
|
- `581049` 新文案是"订单已终止,禁止重复提交",**不要再按旧文案"边界或请求不一致"做文案匹配**;按 code 判断。
|
||||||
|
- 收到 `581049` 时订单已是 COMPLETED,不要引导用户"修改正文后重试"——任何正文都会被拒。
|
||||||
|
- 终止结果字段(terminateRefundId / finalRefund 等)只有首次终止的 200 响应返回;之后只能从查询类接口获取。
|
||||||
|
- `581018` 与 `581049` 分工:非 TRAVELLING 且未终止过 → 581018;已 COMPLETED → 581049。
|
||||||
|
|
||||||
|
|
||||||
|
## 验证证据
|
||||||
|
|
||||||
|
- `mvn -pl hl-order-service-v3 -am verify` 全绿(含重复终止一律 581049 的回归测试)。
|
||||||
|
- TEST 网关实测(2026-08-06):COMPLETED 订单再次提交 terminate 返回 `code=581049`、`message=订单已终止,禁止重复提交`、`success=false`,零副作用;TRAVELLING 订单首次终止仍 200 返回退款结果。
|
||||||
|
- 已部署 TEST(PR #5604 合并 dev-v3 后滚动发布)。
|
||||||
|
|
||||||
|
## 13. 关联 / 联系人
|
||||||
|
|
||||||
|
### 13.1 链接
|
||||||
|
|
||||||
|
- **Issue**:[#5598](https://git.1814.love:8443/wx/HL/issues/5598)
|
||||||
|
- **PR**:[#5604](https://git.1814.love:8443/wx/HL/pulls/5604)
|
||||||
|
- **Merge commit**:[047bb72951](https://git.1814.love:8443/wx/HL/commit/047bb72951)
|
||||||
|
- **被取代的契约**:[04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md](./04_5460_终止行程幂等重放契约恢复-修改接口-管理后台.md)(同正文重放语义本次起作废)
|
||||||
|
|
||||||
|
### 13.2 联系人
|
||||||
|
|
||||||
|
- **后端负责人**:@yst / yaosutu(腰苏图)
|
||||||
|
- **前端消费方**:管理后台订单详情 - 终止行程操作
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户