docs(changelog): #7439 dayNumber 取值域放宽,用车行可落在行程窗外
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
Refs #7439 / PR #7639 (8c699658a) - TerminateRefundItemVO.dayNumber 不再保证落在 [1, tripDays]:接送机服务日常在行程窗外, 该值是服务日的无损编码,后端不再截断;原先这类订单被 581048 拒掉,现在正常返回。 - 前端凡是拿 dayNumber 直接当下标 / 做 1..N 循环的地方必须先做边界判断。 - 第八节为测试服实测:order-v3 @ dev-v3/8c699658a,经网关调 refund-preview, 同一响应实测到 dayNumber=0(接机,出发前一天)与 dayNumber=5(送机,>tripDays=3), 保险行仍 null,581048 未出现;取证前后 deploy-status 的 commit 一致。
这个提交包含在:
@@ -0,0 +1,278 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7439"
|
||||
title: "终止退款·dayNumber 字段取值域放宽,不再截断接送机窗外服务日"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-13T22:23:57+08:00"
|
||||
status_note: "后端已部署 8c699658a,接收端应对 dayNumber ≤0 或 > 行程天数的情况做边界处理,避免越界或错误渲染"
|
||||
updated_at: "2026-09-13"
|
||||
base: "dev-v3"
|
||||
generated: "2026-09-13T22:26:36+08:00"
|
||||
---
|
||||
|
||||
# 终止退款·dayNumber 字段取值域放宽
|
||||
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #7639
|
||||
> **Issue**: #7439
|
||||
> **日期**: 2026-09-13
|
||||
> **影响范围**: 管理后台终止行程退款预览接口,用车行的天序号编码
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**接送机服务日现已正常返回,不再被截断丢弃**。
|
||||
|
||||
1. 终止退款预览接口出参中,用车行的 `dayNumber` **不再保证落在 `[1, 行程天数]` 内**
|
||||
2. **接送机场景**(提前一天接机 → `dayNumber ≤ 0`;返程后一天送机 → `dayNumber > 行程天数`)现已正常返回真实值
|
||||
3. 原先这类订单因服务日越界会被 **581048 拒掉**,现在正常预览返回
|
||||
4. **前端需对 dayNumber 做边界处理**,避免用它直接索引"第几天"数组而越界;保险行 `dayNumber` **仍为 null**(无变化)
|
||||
|
||||
## 一、背景
|
||||
|
||||
接送机服务是订单行程的边界服务:出发前接机服务日早于出发日,返程后送机服务日晚于行程结束日。
|
||||
原先后端按 `[1, 行程天数]` 硬截断这类服务日,致使真实服务日信息丢失。
|
||||
|
||||
现改为:`dayNumber` 是服务日相对出发日的 **1-based 编码**,其逆运算 `serviceDate = departDate + (dayNumber - 1)` 可精确还原服务日。
|
||||
前端回传释放行时会用这个值,所以后端**不再截断、不置空**,由前端按业务场景判断是否显示/处理。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 终止行程·退款预览 | POST | `/{id}/terminate/refund-preview` | 响应字段取值域放宽 | 用车行 `dayNumber` 不再截断 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 终止行程·退款预览 `POST /{id}/terminate/refund-preview`
|
||||
|
||||
**VO**: `没有请求体 → OrderTerminateRefundPreviewRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台出行中订单详情页,用户点击「终止行程」时调用,预览该订单所有资源(住宿/门票/用车/保险/备品等)的退款金额,供用户和管理员确认。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|:---:|---|---|
|
||||
| id | Path | Long | ✅ | Snowflake ID | 订单 ID |
|
||||
|
||||
#### 出参 `Result<OrderTerminateRefundPreviewRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| data.orderNo | String | 订单号 |
|
||||
| data.tripDays | Integer | 行程天数 |
|
||||
| data.departDate | LocalDate | 出发日(ISO 8601) |
|
||||
| data.days | List<TerminateDayVO> | 按天分组的退款资源(含已用/未用统计) |
|
||||
| data.days[].dayNumber | Integer | 第几天(1-based,接送机可能 ≤0 或 > tripDays) |
|
||||
| data.days[].items | List<TerminateRefundItemVO> | 该天的资源列表 |
|
||||
| data.days[].items[].dayNumber | Integer | 资源对应的天序号 ⚠️ **本次变更重点** |
|
||||
| data.days[].items[].categoryCode | String | 资源分类(ROOM/TICKET/SERVICE/VEHICLE/INSURANCE/SUPPLIES) |
|
||||
| data.days[].items[].name | String | 资源名称(酒店名/车型/景点等) |
|
||||
| data.days[].items[].dealPrice | BigDecimal | 单位价格(住宿=元/间·晚;用车=日费) |
|
||||
| data.days[].items[].quantity | Integer | 数量(住宿=间数;用车=车辆数) |
|
||||
| data.days[].items[].totalAmount | BigDecimal | 行合计 = dealPrice × quantity |
|
||||
| data.days[].items[].locked | Boolean | 是否锁定不退(保险等 = true) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/order/123456789/terminate/refund-preview
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"orderNo": "ORD-2026090100001",
|
||||
"tripDays": 3,
|
||||
"departDate": "2026-09-15",
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 0,
|
||||
"items": [
|
||||
{
|
||||
"lineKey": "VEHICLE_ASSIGNMENT:666:1",
|
||||
"categoryCode": "VEHICLE",
|
||||
"categoryName": "用车",
|
||||
"sourceType": "VEHICLE_ASSIGNMENT",
|
||||
"sourceId": "666",
|
||||
"name": "丰田 RAV4",
|
||||
"dayNumber": 0,
|
||||
"dealPrice": "380.00",
|
||||
"quantity": 1,
|
||||
"totalAmount": "380.00",
|
||||
"defaultUsed": false,
|
||||
"locked": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "订单不存在或不在出行中状态",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 581048,
|
||||
"message": "终止行程:本地车辆费用服务日与订单行程不一致",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权**: OrderViewGuard 排除房务角色;其他所有角色(产品/运营/财务等)均可调用
|
||||
- **车辆已用状态**: 按 Order 当前 DAILY_V3 的 serviceDate 与实际终止日对比,前端显示仅供参考,提交时后端权威重算
|
||||
- **服务日合法性**: 原先 dayNumber < 1 或 > tripDays 时会抛 581048;现已移到 OrderVehicleDailyFeeSnapshotProvider 按车型分流验证
|
||||
- **保险**: 始终 locked=true,dayNumber=null,不可退
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
此接口返回的 `dayNumber` **仅是原始编码,不保证在行程窗内**。消费方必须按以下流程处理:
|
||||
|
||||
| dayNumber 情况 | 含义 | 前端处理建议 |
|
||||
|---|---|---|
|
||||
| ≤ 0 | 接送机:出发前服务 | 标记为"接机";不索引"第几天"数组 |
|
||||
| 1 ~ tripDays | 行程内 | 正常按"第 dayNumber 天"渲染 |
|
||||
| > tripDays | 接送机:返程后服务 | 标记为"送机";不索引"第几天"数组 |
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
此接口为**读接口**,无数据库写操作。返回的是现存订单快照中的车辆事实编码。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- **未登录** → 401(网关拦截)
|
||||
- **无权限(房务角色)** → 403(OrderViewGuard 拦截)
|
||||
- **订单不存在** → 404
|
||||
- **订单非出行中** → 404
|
||||
- **下游数据缺失** → 返 `[]` 对应项,不 500(降级处理)
|
||||
- **老数据兼容** → 旧字段为 null,不异常
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| dayNumber(用车) | 强制落在 `[1, tripDays]`;越界时异常拒绝(581048) | 无约束;可以 ≤0 或 > tripDays;精确编码服务日 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 接送机订单预览 | 被 581048 拒绝 | 正常返回,dayNumber 准确反映接送日期 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 是
|
||||
- **前端是否必须同步上线**: 是
|
||||
- **前端 workaround 清理点**: 原先对接送机订单做的排除逻辑可撤销;dayNumber 直接索引改为先检查边界
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 管理后台终止行程退款预览接口中用车行
|
||||
- **零影响**:
|
||||
- 订单详情/订单列表接口
|
||||
- 住宿/门票/保险/备品的 dayNumber
|
||||
- 订单创建/修改接口
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**部署**:`hl-order-service-v3` @ `dev-v3 / 8c699658a`(PR #7639 的 merge commit),
|
||||
两实例 8086 / 8186 滚动完成并 UP,Nacos(`namespaceId=test`)两实例 `healthy:true`。
|
||||
取证**前后各查一次** `deploy-status.sh`,commit 均为 `8c699658a`、`BEHIND=0/N` ⇒ 取证期间无人换分支。
|
||||
|
||||
**实测请求**(路径取自源码 `OrderController.java:203`,不是照文档猜的):
|
||||
|
||||
```
|
||||
POST https://api.test.1814.love:9443/v3/admin/order/8880000000000000001/terminate/refund-preview
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**实测响应**:`HTTP 200`,`code=200`,`success=true`。订单 `tripDays=3`、`departDate=2026-10-01`。
|
||||
`refundLines` 里六行的 `dayNumber` 实测值:
|
||||
|
||||
| 行 | 类别 | 服务日 | `dayNumber` 实测 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| ceB00001 | 用车(接机) | 2026-09-30(出发**前**1 天) | **`0`** | 🔴 **≤0,改动前会被 581048 拒掉** |
|
||||
| ceA00001 | 用车 | 2026-10-01 | `1` | 行程内 |
|
||||
| ceA00002 | 用车 | 2026-10-02 | `2` | 行程内 |
|
||||
| ceA00003 | 用车 | 2026-10-03 | `3` | 行程内 |
|
||||
| ceB00002 | 用车(送机) | 2026-10-05(返程**后**2 天) | **`5`** | 🔴 **> tripDays=3,改动前会被 581048 拒掉** |
|
||||
| 旅行险 | 保险 | — | **`null`** | 保险行仍为 null,此条未变 |
|
||||
|
||||
⇒ **本次改动实际放行的行为已被真实触发并观测到**:同一响应里同时出现 `0` 与 `5`,
|
||||
不是全部落在 `[1, 3]` 内;**`581048` 未出现**。
|
||||
|
||||
**取证用数据说明(供排查时识别,勿动)**:该订单为本次验证**专门新建**的独占单
|
||||
(`order_id=8880000000000000001` / `order_no=HL88800000000001`),未改动任何既有订单;
|
||||
`order_main` / `order_vehicle_requirement` / `order_vehicle_assignment` / `insurance_order`
|
||||
的备注字段均带 `#7439 AC-27` 标记,可直接检索。
|
||||
|
||||
**本节的已知边界(不外推)**:
|
||||
- 未做前端页面联调——本节只验到接口层。
|
||||
- 未调用有副作用的终止提交接口(`processTermination`),只验了只读的 `refund-preview`。
|
||||
- 该测试单由 SQL 直插创建(DAILY_V3 派车快照涉十余项耦合 CHECK,走真实抢单/派车链路超出本次范围),
|
||||
**未经"定制→支付→出发"业务流程产出**。
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|-----------|
|
||||
| **本 PR #7639** | **#7439** | 放宽 dayNumber 取值域 | ✅ 最新 |
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 端点定义: `hl-order-service-v3/.../controller/admin/OrderController.java:203`
|
||||
- 改动点: `hl-order-service-v3/.../settlement/service/TerminateRefundService.java` 的 `vehicleDayNumber`
|
||||
- 字段定义: `hl-order-service-v3/.../controller/admin/vo/TerminateRefundItemVO.java` 的 `dayNumber`
|
||||
- 鉴权: `com.hulalv.order.core.guard.OrderViewGuard#assertNotHouseRole`(房务/组长 → 581045)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7439](https://git.1814.love:8443/wx/HL/issues/7439)
|
||||
- **PR**: [#7639](https://git.1814.love:8443/wx/HL/pulls/7639)
|
||||
- **Merge commit**: [8c699658a](https://git.1814.love:8443/wx/HL/commit/8c699658a)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户