docs(changelog-v2): 终止行程接口终态汇总(管理后台 #3556)
合并 #3556 系列 3 篇 + #3965 金额String化 为一份终态,供前端一处对接: - POST /v3/admin/order/{id}/terminate/refund-preview 终止退款预览 - POST /v3/admin/order/{id}/terminate 终止行程(执行) 点明: 旧路径 cancel/on-trip 已删返404; 金额+LongID 全String化; 终态 COMPLETED+PENDING_REVIEW(非CANCELLED); 581007/581018 错误码。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
这个提交包含在:
父节点
ede5dd5c30
当前提交
de1292823a
@ -0,0 +1,221 @@
|
||||
# 订单详情「终止行程」接口终态汇总(出行中订单)—— 管理后台
|
||||
|
||||
- 端类型:管理后台
|
||||
- 变更类型:修改接口(**终态汇总篇**,无新契约变化;合并 #3556 系列 3 篇 + #3965 金额 String 化)
|
||||
- 关联 Issue:#3556 PR:#3557(主改造)/ #3559(预览状态校验加固)/ #3965(金额 String 化)
|
||||
- 日期:2026-06-26
|
||||
|
||||
---
|
||||
|
||||
## ① 接口背景
|
||||
|
||||
「终止行程」用于**出行中(TRAVELLING)订单**提前中止:定制师按"停在第几天 + 逐项资源是否已用"提交,后端按实际成交价权威核算退款额、写入核单返还,订单转为「已完成→待核单」。
|
||||
|
||||
此前该能力的 changelog 分散在 3 篇(接口改造 / 退款预览新增 / 旧路径下线),且金额格式在 #3965 后由数字改为字符串。**本篇为一处对接的终态汇总**,前端按此一份即可对接,旧 3 篇仅作历史留档。
|
||||
|
||||
> ⚠️ 旧路径 `POST /v3/admin/order/{id}/cancel/on-trip` **已删除**,调用返回 404,必须改调 `/terminate`。
|
||||
> ⚠️ 所有 Long ID 与金额均**字符串化**返回(防 JS 精度丢失),请求传金额相关 ID 也用字符串。
|
||||
|
||||
---
|
||||
|
||||
## ② 变更清单(两个接口配套使用)
|
||||
|
||||
| # | 方法 | 路径 | 用途 |
|
||||
|---|---|---|---|
|
||||
| 1 | POST | `/v3/admin/order/{id}/terminate/refund-preview` | 终止退款预览(拿全资源清单,前端本地算退款) |
|
||||
| 2 | POST | `/v3/admin/order/{id}/terminate` | 终止行程(提交执行,落核单返还) |
|
||||
|
||||
统一响应 `Result<T>`:`{ code, message, data, success }`,`code=200` 成功。认证:管理端 JWT(`Authorization` 头)。
|
||||
|
||||
**对接流程**:点「终止行程」→ 调①拿整趟资源清单 → 前端按所选结束天本地标已用/可退并算退款 → 调②只回传 `used` 判定 + `adjustAmount`(不传金额单价,后端权威重算防篡改)。
|
||||
|
||||
---
|
||||
|
||||
## ③ 接口 1:终止退款预览
|
||||
|
||||
```
|
||||
POST /v3/admin/order/{id}/terminate/refund-preview
|
||||
```
|
||||
- 入参:仅 path `id`(订单 ID),**无请求体**。一次性拿整趟全部资源,结束天选择/已用标记/退款计算全在前端本地完成,不需每选一天再调。
|
||||
- 幂等:是(只读,不落库)。
|
||||
|
||||
### 出参 `Result<OrderTerminateRefundPreviewRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| paidAmount | String(金额) | 客户已付金额(前端算退款基线用) |
|
||||
| tripDays | Integer | 行程总天数(画时间轴) |
|
||||
| departDate | String(date) | 出发日期 `yyyy-MM-dd`(第 N 天日期 = 出发日 + N−1) |
|
||||
| tripCurrentDay | Integer | 当前到第几天(时间轴默认选中点;出发前/未知=1) |
|
||||
| rooms | Array<Item> | 住宿清单(每晚×每组房一行) |
|
||||
| tickets | Array<Item> | 门票清单(每景点每天一行) |
|
||||
| vehicles | Array<Item> | 用车清单(**整车按天展开,每天一行**,同段车 refId 相同) |
|
||||
| insurance | Array<Item> | 保险清单(整单 1 行,locked=true 锁定不退) |
|
||||
|
||||
**Item(TerminateRefundItemVO,四类资源行通用)**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| refId | String | 资源配单记录 ID(提交终止时按此回传已用判定;用车多天共用同一 refId 靠 dayNumber 区分;保险可能为 null) |
|
||||
| name | String | 资源名称快照(酒店名/景点名/车型/保险产品名) |
|
||||
| dayNumber | Integer | 对应第几天(保险为 null) |
|
||||
| dealPrice | String(金额) | 成交单价(住宿=每间每晚 / 门票=每张 / 用车=每天每辆日租 / 保险=保费) |
|
||||
| quantity | Integer | 数量(住宿=间数 / 门票=张数 / 用车=车辆数 / 保险=1) |
|
||||
| totalAmount | String(金额) | 本行小计 = dealPrice × quantity |
|
||||
| locked | Boolean | 是否锁定不退(保险恒 true,其余 false) |
|
||||
| lockedReason | String | 锁定原因(如"保险已生效不退";未锁定为 null) |
|
||||
|
||||
> 响应**不含**"是否已用"标记与"退款合计"——由前端按所选结束天(`dayNumber ≤ 结束天` 默认已用,之后默认可退)实时计算。
|
||||
|
||||
---
|
||||
|
||||
## ④ 接口 2:终止行程(执行)
|
||||
|
||||
```
|
||||
POST /v3/admin/order/{id}/terminate
|
||||
Content-Type: application/json
|
||||
```
|
||||
- 幂等:是(订单一旦终止变 COMPLETED,二次调用因状态非 TRAVELLING 返 581018)。
|
||||
|
||||
### 入参 Body(OrderTerminateTripReqVO)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 校验 |
|
||||
|---|---|:--:|---|---|
|
||||
| cancelReason | String | ✅ | 终止原因(调整额说明并入此处,不单列字段) | 非空 |
|
||||
| endDayNumber | Integer | ✅ | 停在第几天(截断点,1-based) | ≥ 1 ≤ 行程总天数 |
|
||||
| rooms | Array<{refId, used}> | ✅ | 住宿各行已用判定(列表非 null,可空数组) | — |
|
||||
| tickets | Array<{refId, used}> | ✅ | 门票各行已用判定 | — |
|
||||
| vehicles | Array<{refId, dayNumber, used}> | ✅ | 用车各行已用判定(同车跨天靠 dayNumber 区分) | — |
|
||||
| adjustAmount | String(金额) | ❌ | 人工调整额(±,正=多退 负=少退),默认 0 | — |
|
||||
|
||||
**rooms / tickets 行(ResourceUsedItem)**:`refId`(String,必填) + `used`(Boolean,必填 true=已用不退/false=未用可退)
|
||||
**vehicles 行(VehicleUsedItem)**:`refId`(String,必填) + `dayNumber`(Integer,必填≥1) + `used`(Boolean,必填)
|
||||
|
||||
> `refId` 全部取自接口①返回值。入参**只传 `used` 判定 + `adjustAmount`,不传任何金额单价**——退款额由后端按预览同源成交价权威重算。保险不在入参,后端自动按锁定不退处理。
|
||||
|
||||
### 出参 `Result<OrderTerminateTripRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| terminateRefundId | String | 终止退款主表 ID |
|
||||
| baselineRefund | String(金额) | 系统建议基线 = max(0, paidAmount − usedAmount) |
|
||||
| adjustAmount | String(金额) | 人工调整额(回显入参,默认 0) |
|
||||
| finalRefund | String(金额) | 最终返还额 = max(0, baselineRefund + adjustAmount),最低 0 |
|
||||
| settlementRefundId | String | 核单返还记录 ID(核单模块写入后回填) |
|
||||
| newStatus | String | 终止后订单粗状态(恒 COMPLETED) |
|
||||
| newFlowStatus | String | 终止后流程细状态(恒 PENDING_REVIEW) |
|
||||
|
||||
---
|
||||
|
||||
## ⑤ 枚举 / 数据字典
|
||||
|
||||
- newStatus:`COMPLETED` 已完成(终止后进完成态转核单,**不再是旧的 CANCELLED**)
|
||||
- newFlowStatus:`PENDING_REVIEW` 待核单(等核单录入与结算复核)
|
||||
- 资源分类:通过 rooms/tickets/vehicles/insurance 四数组区分,无独立 resourceType 字段
|
||||
- locked:true=锁定不退(保险恒 true)/ false=可退
|
||||
|
||||
---
|
||||
|
||||
## ⑥ 错误码
|
||||
|
||||
| code | 含义 | 触发场景 |
|
||||
|---|---|---|
|
||||
| 581007 | 订单不存在 | id 无对应订单 |
|
||||
| 581018 | 出行中取消仅适用于出行中订单 | 订单当前状态非 TRAVELLING(未出发/已终止/已完成,含二次终止) |
|
||||
|
||||
---
|
||||
|
||||
## ⑦ 示例
|
||||
|
||||
### 接口①响应(预览,节选)
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": {
|
||||
"paidAmount": "6000.00", "tripDays": 7, "departDate": "2026-05-12", "tripCurrentDay": 3,
|
||||
"rooms": [ {"refId":"96011","name":"拉萨瑞吉·大床房","dayNumber":1,"dealPrice":"1280.00","quantity":1,"totalAmount":"1280.00","locked":false,"lockedReason":null} ],
|
||||
"vehicles": [ {"refId":"98001","name":"丰田赛那 商务车","dayNumber":1,"dealPrice":"600.00","quantity":1,"totalAmount":"600.00","locked":false,"lockedReason":null} ],
|
||||
"insurance": [ {"refId":null,"name":"安联境内旅行险·尊享版","dayNumber":null,"dealPrice":"256.00","quantity":1,"totalAmount":"256.00","locked":true,"lockedReason":"保险已生效不退"} ]
|
||||
} }
|
||||
```
|
||||
|
||||
### 接口②请求(终止)
|
||||
```json
|
||||
{
|
||||
"cancelReason": "客户中途高反送医终止;林芝段酒店已付全款不可退,调整 -300",
|
||||
"endDayNumber": 3,
|
||||
"rooms": [ {"refId":"96011","used":true}, {"refId":"96014","used":false} ],
|
||||
"tickets": [ {"refId":"97001","used":true} ],
|
||||
"vehicles": [ {"refId":"98001","dayNumber":1,"used":true},{"refId":"98001","dayNumber":2,"used":true},{"refId":"98001","dayNumber":3,"used":true} ],
|
||||
"adjustAmount": "-300.00"
|
||||
}
|
||||
```
|
||||
### 接口②响应(终止)
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": {
|
||||
"terminateRefundId": "9610000001", "baselineRefund": "2264.00", "adjustAmount": "-300.00",
|
||||
"finalRefund": "1964.00", "settlementRefundId": "9620000001",
|
||||
"newStatus": "COMPLETED", "newFlowStatus": "PENDING_REVIEW" } }
|
||||
```
|
||||
### 边界(不填调整额,默认 0)
|
||||
```json
|
||||
请求: { "cancelReason":"客户主动终止","endDayNumber":5,"rooms":[],"tickets":[],"vehicles":[] }
|
||||
响应: { "code":200,"data":{ "baselineRefund":"0.00","adjustAmount":"0.00","finalRefund":"0.00","newStatus":"COMPLETED","newFlowStatus":"PENDING_REVIEW" } }
|
||||
```
|
||||
### 异常 —— 调旧路径(前端联调易踩)
|
||||
```
|
||||
POST /v3/admin/order/{id}/cancel/on-trip → HTTP 404 Not Found
|
||||
```
|
||||
旧路径后端无任何映射,不做 301/308 跳转,必须改调 `/terminate`。
|
||||
### 异常 —— 订单非出行中
|
||||
```json
|
||||
{ "code": 581018, "message": "出行中取消仅适用于出行中订单", "success": false }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⑧ 业务边界
|
||||
|
||||
- ✅ 适用:订单当前粗状态 = TRAVELLING(出行中)
|
||||
- ❌ 不适用:未出发订单走「取消订单」流程(`cancel/pre-trip`);已终止/已完成二次调用返 581018
|
||||
- ⚠️ finalRefund 最低 0,不会负数;退款额进核单返还由财务复核放款;保险恒不退
|
||||
- ⚠️ 终止后订单是「已完成(待核单)」而非「已取消」,列表/详情状态展示需按新枚举对齐
|
||||
|
||||
---
|
||||
|
||||
## ⑨ 修改前后对比(相对最早 cancel/on-trip 旧实现)
|
||||
|
||||
| 维度 | 改前 | 改后(终态) |
|
||||
|---|---|---|
|
||||
| 路径 | `POST /{id}/cancel/on-trip` | `POST /{id}/terminate`(旧路径 404) |
|
||||
| 退款预览 | 无独立预览 | `POST /{id}/terminate/refund-preview`(新增) |
|
||||
| 退款额来源 | 定制师手填一个数 | 后端按已用资源 + 成交价权威核算 |
|
||||
| 退款是否进核单 | 否(仅记录) | 是(写核单返还,财务复核放款) |
|
||||
| 入参 | returnAmount/returnRemark 手填 | endDayNumber + rooms/tickets/vehicles 已用判定 + adjustAmount |
|
||||
| 出参 | returnAmount 回显 | terminateRefundId/baselineRefund/adjustAmount/finalRefund/settlementRefundId/newFlowStatus |
|
||||
| 金额/ID 格式 | 数字 | **字符串(#3965 后)** |
|
||||
| 终止后状态 | CANCELLED 已取消 | COMPLETED 已完成 → PENDING_REVIEW 待核单 |
|
||||
|
||||
---
|
||||
|
||||
## ⑩ 影响评估 / 回滚
|
||||
|
||||
- 接口已稳定上线(#3557/#3559/#3965 均已合并部署),本篇为终态汇总,不含新后端改动,无回滚项。
|
||||
- 前端「终止行程」页面对接:① 进入调①拿资源清单 → ② 选结束天 + 勾选已用 + 可选调整额 → ③ 调②提交 → ④ 终态按「已完成/待核单」展示并刷新详情。
|
||||
- 旧 3 篇(`06_3556_终止行程接口改造`、`06_3556_终止退款预览`、`11_3556_出行中取消路径下线`)保留历史留档,对接以本篇为准。
|
||||
|
||||
---
|
||||
|
||||
## ⑪ 注意事项
|
||||
|
||||
- Long ID + 金额(terminateRefundId/baselineRefund/finalRefund/paidAmount/refId 等)均字符串化,请求/解析均按字符串。
|
||||
- 写操作需前端按钮防重复点击 + loading。
|
||||
- 必须先调预览拿 refId,再提交终止;提交不传金额单价(后端权威重算)。
|
||||
|
||||
---
|
||||
|
||||
## ⑫ 关联 / 联系人
|
||||
|
||||
- Issue:https://git.1814.love:8443/wx/HL/issues/3556
|
||||
- PR:https://git.1814.love:8443/wx/HL/pulls/3557 ・ https://git.1814.love:8443/wx/HL/pulls/3559 ・ https://git.1814.love:8443/wx/HL/pulls/3965
|
||||
- 历史留档:`changelogs-v2/2026-06/06_3556_终止行程接口改造-修改接口-管理后台.md` 等 3 篇
|
||||
- 后端负责人:腰苏图
|
||||
- 接口已部署测试服,Knife4j 可见(`/v3/admin/order/{id}/terminate`、`/terminate/refund-preview`)。
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户