--- schema: "hl-changelog/v2" ticket: "7439" title: "终止退款·dayNumber 字段取值域放宽,不再截断接送机窗外服务日" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "e03170eaf52e44858ec04cfa5a41fa252a34ba50" target_release: "" verified_at: "2026-09-14" status_note: "后端已部署 8c699658a,接收端应对 dayNumber ≤0 或 > 行程天数的情况做边界处理,避免越界或错误渲染【前端 2026-09-14 交付 verified】MidTripRefundModal 接送机窗外服务日边界处理:≤0 标「接」、>tripDays 标「送」并按 departDate+(dayNumber-1) 还原真实日期,行程内恒 Day;day 格判断改 != null(0 接机不落图标分支);车辆组「含接送机日」提示;terminateRefund.js 适配器零改动(本就透传不截断不索引);保险行 null 不变。spec 13/13+terminateRefund 14/14,checkpoint 7 项全绿。ref=e03170ea。" 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` | 字段 | 类型 | 说明 | |---|---|---| | data.orderNo | String | 订单号 | | data.tripDays | Integer | 行程天数 | | data.departDate | LocalDate | 出发日(ISO 8601) | | data.days | List | 按天分组的退款资源(含已用/未用统计) | | data.days[].dayNumber | Integer | 第几天(1-based,接送机可能 ≤0 或 > tripDays) | | data.days[].items | List | 该天的资源列表 | | 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 ``` **实测响应**:`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