docs(changelog): #8121 核单清单「用车安排」逐类判定,包车+接送机并存不再静默放行
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
按 origin/dev-v3 源码逐条重建出参、ChecklistItemVO 结构、三段响应示例、 空数据降级、错误响应与业务边界六节:原稿的 orderId、PAYMENT_DONE 码值、 localhost:8033 主机头与 404/401/403 状态码均无源码依据,已按 OrderDetailService / ConfirmChecklistRespVO / GlobalExceptionHandler 的实际实现订正。 错误响应改为 HTTP 200 + body code(581007 订单不存在 / 581008 非可见角色 / 581045 房务角色),与 CODE_RULES §10「业务失败走 200」一致; 越权校验两道门(OrderController:254 assertNotHouseRole、 OrderDetailService:785 assertOrderReadable)按源码写实,并记入 「角色为空时 assertNotHouseRole 放行」这一已知缺口。 第八章换为五单真实读数,并照写「行程用车未就绪分支本轮无活体读数」这一覆盖边界。 Refs #8121 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,360 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8121"
|
||||
title: "核单清单「用车安排」逐类判定,包车+接送机并存不再静默放行"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "backend_status: deployed - hl-order-service-v3 COMMIT=7811104b4 BEHIND=0 STATE=ok(2026-09-22 AC-3 取证); gateway_status: not_required - 本单零网关改动,GET /v3/admin/order/{orderId}/confirm-checklist 是既有路由,走 hl-gateway 既有 /v3/admin/** 通配断言(application.yml:220-223),无新增 /admin/ 端点需要配路由; frontend_status: pending - 前端适配情况未知,后端不代填"
|
||||
updated_at: "2026-09-22"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# order-v3: 核单清单「用车安排」逐类判定,包车+接送机并存不再静默放行
|
||||
|
||||
> **存放目录**: 二期(v3) → `changelogs-v2/{YYYY-MM}/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3 (端口 8033)
|
||||
> **Issue**: [#8121](https://git.1814.love:8443/wx/HL/issues/8121)
|
||||
> **日期**: 2026-09-22
|
||||
> **影响范围**: 管理后台「订单详情」→「确认核单」清单;接送机需求与行程用车并存的订单
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**缺陷修复**:同一订单同时买「行程用车(包车)」和「接送机」时,之前只要包车那类先办完,整个 `VEHICLE_DONE` 项就判 `passed=true` —— 接送机零派车也照样放行,**没有异常、没有错误码,清单上是一个绿勾,订单就这么被确认掉了**。修复后两类需求各自独立判定,任一类未就绪则整体 `passed=false`,且 `failReason` 点名是哪一类。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
同一张订单可以包含多个用车需求类别(行程用车、接送机)。原判定逻辑使用"单值投影"——只选中其中一类进行判定,导致当两类并存且其中一类已完成时,另一类的未就绪状态被静默忽略。核单清单是车务部门唯一的缺陷定位入口,绿勾放行的订单实际上出数不全,造成订单被错误地推入后续流程。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 订单确认核单清单 | GET | `/v3/admin/order/{orderId}/confirm-checklist` | 响应字段语义变化 | `VEHICLE_DONE` 改为逐类判定(两类并存时不再静默判过);`items[*].failReason` 未就绪时点名用车类别。响应结构不变 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 订单确认核单清单 `GET /v3/admin/order/{orderId}/confirm-checklist`
|
||||
|
||||
**VO**: `OrderDetailService#getConfirmChecklist(Long) → ConfirmChecklistRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台订单详情页面,确认前的最终核对清单。车务部门通过此清单判断订单是否已具备所有必要条件(付款、房间、用车、合同等),逐项绿勾后方可点击「确认」按钮推动订单进入后续流程。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `orderId` | Path | Long | ✅ | 订单必须存在 | 订单 ID |
|
||||
|
||||
#### 出参 `Result<ConfirmChecklistRespVO>`
|
||||
|
||||
外层信封 `Result`:`code`(成功恒 200)、`message`、`data`、`traceId`、`success`(由 `isSuccess()` 序列化,等价于 `code == 200`),依据 `hl-common/hl-common-core/src/main/java/com/hulalv/common/result/Result.java:21-45`。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `allPassed` | Boolean | 是否五项全部通过,是 `items` / `preview` 的唯一开关(`ConfirmChecklistRespVO.java:23-24`、`OrderDetailService.java:424-427`) |
|
||||
| `items` | List<ChecklistItemVO> | `allPassed=false` 时返回**全部 5 项**(含 `passed=true` 的项,不是只返回失败项);`allPassed=true` 时为 `null`(`OrderDetailService.java:429`) |
|
||||
| `preview` | PreviewVO | `allPassed=true` 时返回确认弹框预览;`allPassed=false` 时为 `null`(`OrderDetailService.java:430-432`) |
|
||||
|
||||
##### PreviewVO 结构(仅 `allPassed=true` 时有值)
|
||||
|
||||
| 字段 | 类型 | 说明 | 源码依据 |
|
||||
|------|------|------|----------|
|
||||
| `departureDate` | LocalDate | 出发日期,取订单 `departDate` | `OrderDetailService.java:1116` |
|
||||
| `totalPeopleCount` | Integer | 总出行人数:优先按 `order_traveler` 实际条数;为 0 时回落「成人+儿童+幼儿+婴儿」 | `OrderDetailService.java:1117-1124` |
|
||||
| `driverName` | String | 司机姓名。团车路径(车在团级承担、逐户无配车行)该栏留空 | `OrderDetailService.java:1127-1147` |
|
||||
| `driverPhoneMasked` | String | 司机手机(脱敏) | 同上 |
|
||||
| `hotels` | List<HotelSummaryVO> | 元素为 `{cityName, hotelName}`;按 `dayNumber` 升序,相邻「城市+酒店」相同的去重;无配房时为空数组 | `OrderDetailService.java:1151-1182` |
|
||||
| `staffs` | List<StaffItemVO> | 本单人员:`assignmentId` / `staffId`(两个 Long 均序列化成字符串)/ `staffName` / `staffPhone`(已脱敏)/ `staffRole` / `staffRoleName` / `isPrimaryReporter` | `ConfirmChecklistRespVO.java:83-107`、`OrderDetailService.java:1209-1230` |
|
||||
| `contractAutoAction` | 对象 | `{planName, autoSign}`,`autoSign` 恒 `true`(确认后自动发起线上签署);无 ACTIVE 合同方案时整个对象缺省 | `OrderDetailService.java:1187-1194` |
|
||||
| `insuranceAutoAction` | 对象 | `{planName, peopleCount, effectiveDescription}`,`effectiveDescription` 是固定文案「出发前 24h 内生效」;无 ACTIVE 保险方案时整个对象缺省 | `OrderDetailService.java:1198-1205` |
|
||||
|
||||
> `preview` **没有** `rooms` / `drivers` / `notificationList` 这三个字段:通知块已于 #4844 删除(`OrderDetailService.java:1232-1233`),酒店与司机分别是上表的 `hotels` 与 `driverName` / `driverPhoneMasked`。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/2101908228545937410/confirm-checklist HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
##### ChecklistItemVO 结构
|
||||
|
||||
| 字段 | 类型 | 说明 | 源码依据 |
|
||||
|------|------|------|----------|
|
||||
| `code` | String | 检查项代码,5 个固定取值(见下表) | `ConfirmChecklistRespVO.java:36-38` |
|
||||
| `checkName` | String | 检查项中文名(见下方 ⚠️) | `ConfirmChecklistRespVO.java:40-41` |
|
||||
| `passed` | Boolean | 该项是否通过 | `ConfirmChecklistRespVO.java:43-44` |
|
||||
| `failReason` | String | 未通过原因;通过时不赋值(为 `null`) | `ConfirmChecklistRespVO.java:46-47` |
|
||||
|
||||
`items` 的 5 项及其数组顺序(`OrderDetailService.java:392-422` 按此顺序 `add`):
|
||||
|
||||
| # | `code` | `checkName` | 源码依据 |
|
||||
|---|--------|-------------|----------|
|
||||
| 1 | `PAYMENT_OK` | 款项校验 | `OrderDetailService.java:394-395` |
|
||||
| 2 | `TRAVELER_COMPLETE` | 出行人信息 | `OrderDetailService.java:897`(code)/ `:405`(checkName) |
|
||||
| 3 | `HOTEL_DONE` | 房型安排 | `OrderDetailService.java:946` / `:410` |
|
||||
| 4 | `VEHICLE_DONE` | 用车安排 | `OrderDetailService.java:1009` / `:416` |
|
||||
| 5 | `CONTRACT_TEMPLATE_OK` | 合同方案配置 | `OrderDetailService.java:1089` / `:421` |
|
||||
|
||||
⚠️ **`checkName` 不要当必有字段用**:源码对五项都调了 `setCheckName`(行号见上表),但第八章那条逐字实测读数里**只有 `VEHICLE_DONE` 带 `checkName`**,其余四项该键缺省——两者对不上,成因本轮没有查清。展示层请以 `code` 为主键并用它兜底文案,`checkName` 只作可选补充。
|
||||
|
||||
#### 响应示例①:全通过(`allPassed=true`|格式示意)
|
||||
|
||||
`items` 置 `null`,`preview` 有值。下面的**字段名取自 VO 源码**,值用的是 `@ApiModelProperty` 上的示例值(`ConfirmChecklistRespVO.java:55-142`),不是某一单的实测读数:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"allPassed": true,
|
||||
"items": null,
|
||||
"preview": {
|
||||
"departureDate": "2026-05-30",
|
||||
"totalPeopleCount": 14,
|
||||
"driverName": "扎西师傅",
|
||||
"driverPhoneMasked": "1398761****",
|
||||
"hotels": [{ "cityName": "拉萨", "hotelName": "瑞吉度假酒店" }],
|
||||
"staffs": [
|
||||
{
|
||||
"assignmentId": "1234567890123456789",
|
||||
"staffId": "9876543210987654321",
|
||||
"staffName": "扎西师傅",
|
||||
"staffPhone": "1398761****",
|
||||
"staffRole": "DRIVER",
|
||||
"staffRoleName": "司机",
|
||||
"isPrimaryReporter": true
|
||||
}
|
||||
],
|
||||
"contractAutoAction": { "planName": "标准跟团方案 v3.2", "autoSign": true },
|
||||
"insuranceAutoAction": {
|
||||
"planName": "安联境内旅行险 · 尊享版",
|
||||
"peopleCount": 14,
|
||||
"effectiveDescription": "出发前 24h 内生效"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例②:接送机未就绪(实测原文,逐字)
|
||||
|
||||
订单 `2101908228545937410`(行程用车 DONE + 接送机 PENDING),2026-09-22 测试服 `COMMIT=7811104b4` 只读 GET 的原始读数:
|
||||
|
||||
```json
|
||||
{"code":200,"data":{"allPassed":false,"items":[{"code":"PAYMENT_OK","passed":true},{"code":"TRAVELER_COMPLETE","passed":true},{"code":"HOTEL_DONE","passed":false,"failReason":"用房需求未完成(当前: PENDING)"},{"code":"VEHICLE_DONE","checkName":"用车安排","passed":false,"failReason":"接送机需求未完成(镜像: DONE,当前需求: PENDING)"},{"code":"CONTRACT_TEMPLATE_OK","passed":true}],"preview":null}}
|
||||
```
|
||||
|
||||
照着它写代码要注意两点:
|
||||
|
||||
- `items` 里**五项都在**,通过的项也在数组里(`passed=true`)——不要按「出现在数组里就是失败项」渲染,要按 `passed=false` 筛。
|
||||
- 这条读数的信封只有 `code` 与 `data` 两个键;`Result` 本身还带 `message` / `success` / `traceId`(`Result.java:21-45`)。按 `data` 取值、按 `code` 判成败即可,不要依赖信封里某个键一定出现。
|
||||
|
||||
#### 响应示例③:行程用车未就绪(**格式示意,本轮无活体读数**)
|
||||
|
||||
第八章五单覆盖的是「接送机侧未就绪」与「两类均就绪」,**「行程用车未就绪」这一分支本轮没有活体读数**。下面只给 `items` 数组里 `VEHICLE_DONE` 那一项的片段,文案按源码字符串模板(`OrderDetailService.java:1060-1061`)逐字拼出,同轮其余四项照常返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "VEHICLE_DONE",
|
||||
"checkName": "用车安排",
|
||||
"passed": false,
|
||||
"failReason": "行程用车需求未完成(镜像: PENDING,当前需求: PENDING)"
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
**无空数据场景**:`data` 恒是一个 `ConfirmChecklistRespVO` 对象,`allPassed` 必有值,`items` 与 `preview` 按 `allPassed` 互斥其一为 `null`(`OrderDetailService.java:427-432`)。
|
||||
|
||||
**无降级分支**:调用链只读 order-v3 同进程的数据(活跃用车需求、配车记录、整团免车判定、房态、行程日、合同方案、保险方案、人员候选、出行人计数),不经跨服务 Feign,所以没有「下游挂了返回兜底值」这种形态。
|
||||
|
||||
订单不存在不是空数据,走错误响应(HTTP 200 + body `code=581007`),见下节。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
**HTTP 状态恒 200,成败看 body 里的 `code`。** 两处依据:
|
||||
|
||||
- 业务异常 `BusinessException` 由全局 advice 处理,处理方法上标的是 `@ResponseStatus(HttpStatus.OK)`,body 为 `Result.error(code, message)`(`hl-common/hl-common-log/src/main/java/com/hulalv/common/exception/GlobalExceptionHandler.java:179-205`)。
|
||||
- 网关鉴权失败同样是 HTTP 200,401 / 403 只出现在 body 的 `code` 里(`hl-gateway/src/main/java/com/hulalv/gateway/filter/JwtAuthFilter.java:543-563`,该写入器注释原文:「HTTP status 永远 200 (项目铁规)」)。
|
||||
|
||||
| body `code` | message | 触发条件 | 源码依据 |
|
||||
|---|---|---|---|
|
||||
| `200` | 成功 | 正常返回 | `Result.java:47-53` |
|
||||
| `581007` | 订单不存在 | `orderId` 查不到(判定在权限校验之前) | `OrderCoreErrorCode.java:17-19`、`OrderDetailService.java:778-782` |
|
||||
| `581008` | 无权查看此订单 | 非 ADMIN / SUPER_ADMIN / VEHICLE_MANAGER,且当前操作人不是本单定制师(`adminId != consultantId`) | `OrderCoreErrorCode.java:21-23`、`OrderViewGuard.java:99-102` 与 `:128-137` |
|
||||
| `581045` | 房务角色无权查看订单详情,房务仅可配房 | 角色为 ROOM_MANAGER / house_keeper_lead,Controller 入口即拒 | `OrderController.java:250-256`、`OrderViewGuard.java:67-79`、`OrderCoreErrorCode.java:211-213` |
|
||||
| `401` | 网关鉴权文案 | 未登录 / token 失效,请求被网关 JwtAuthFilter 拦在本服务之外 | `JwtAuthFilter.java:543-545` |
|
||||
|
||||
错误 body 的关键字段(以订单不存在为例):
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **鉴权是两道门**:① Controller 入口 `OrderViewGuard.assertNotHouseRole()`,房务管理员 / 房务组长 → 581045(`OrderController.java:250-256`);② `requireOrderById` 内 `OrderViewGuard.assertOrderReadable(entity)`,ADMIN / SUPER_ADMIN / VEHICLE_MANAGER 放行,其余角色必须是本单定制师,否则 581008(`OrderDetailService.java:778-787`、`OrderViewGuard.java:99-102`)。
|
||||
- **零角色账号可读**:网关没透传 `X-Admin-Role` 时(零角色 admin 账号签发的 token),`assertNotHouseRole` 按「无角色 → 放行」处理(`OrderViewGuard.java:67-72` 与该类 javadoc 的「已知例外」段)。前端不要把本接口能否调通当作角色可见性的判据。
|
||||
- **资源不存在**:`orderId` 查不到 → HTTP 200 + `code=581007`,不返回空对象(`OrderDetailService.java:778-782`)。
|
||||
- **只读**:入口标 `@Transactional(readOnly = true)`(`OrderDetailService.java:369`);本轮逐个核过它直接调用的协作方法(活跃用车需求、配车记录、整团免车判定、房态、行程日、合同方案、保险方案、人员候选、出行人计数),方法体内都是查询口,未见写库分支。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 前端须知的 `failReason` 三个分支及其**精确拼法**
|
||||
|
||||
本接口响应的 `failReason` 字段有三种不同的文案模板。**分支 2 与分支 3 的前缀拼法不同**,前端若对此字符串做精确匹配,需要逐一处理:
|
||||
|
||||
| 分支 | 触发条件 | failReason 格式 | 示例 |
|
||||
|------|---------|-------------|------|
|
||||
| 1 | 订单未提交任何用车需求 | `未提交用车需求` | `未提交用车需求` |
|
||||
| 2 | 镜像或需求 status 非 DONE | `{类别中文名}需求未完成(镜像: {镜像值},当前需求: {需求值})` | `行程用车需求未完成(镜像: PENDING,当前需求: PENDING)` / `接送机需求未完成(镜像: DONE,当前需求: PENDING)` |
|
||||
| 3 | 无有效配车记录 | `{类别中文名}未找到有效配车记录` | `行程用车未找到有效配车记录` / `接送机未找到有效配车记录` |
|
||||
|
||||
**关键限定一**:分支 2 的前缀是 `{类别中文名}需求未完成…`,分支 3 的前缀是 `{类别中文名}未找到有效配车记录`(无"需求"二字)。如果前端要按类别名跳转到对应 Tab,正则 `^{类别中文名}` 能覆盖两个分支,但若要区分**具体原因**,需要分别匹配两个不同的字符串模式。
|
||||
|
||||
### 前端须知:`failReason` 指错的特殊情形
|
||||
|
||||
**关键限定二**(已知缺口,不是本次改动遗漏,而是现有设计的短路条件决定的):
|
||||
|
||||
> **当且仅当**订单的镜像字段 `order_main.vehicle_control_status` **不是 DONE**、**且**该订单存在「行程用车」类活跃需求时,`failReason` 恒点名「行程用车」—— **即使真正落后的是接送机**。
|
||||
|
||||
**原因**:判定短路条件是 `!镜像DONE || !该类自己DONE`(OR),镜像非 DONE 时循环在第一条就返回,而活跃需求列表按枚举声明序排(TRAVEL 先于 TRANSFER),所以点名永远是行程用车。
|
||||
|
||||
**反过来的可信范围**(前端可用):
|
||||
- 镜像 `vehicle_control_status` **为 DONE** 时,每一类按**自己的** status 独立判定,`failReason` **点名准确**。实测读数:`接送机需求未完成(镜像: DONE,当前需求: PENDING)`。
|
||||
- 订单**不存在**行程用车类活跃需求时(纯接送机单),循环第一条就是接送机,点名同样准确。
|
||||
|
||||
**可操作的建议**:想用 `failReason` 做「跳转到对应 Tab」的深链是可行的,但**在镜像非 DONE 且存在行程用车需求这个窗口内需要额外逻辑** —— 比如先读全量需求列表,找出真正未就绪的那一类;或者让车务点击清单项后弹框让他选择哪一类的问题。**不要写成「这个字段永远不可信」**,那会让前端整个放弃深链功能,而大多数情形(镜像 DONE 或无行程用车需求)点名是准确的。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- **全通过时 `items` 为 `null`**(**既有契约,不是本单改动**:该写法自 2026-07-09 `a2b5e8cb3` 起就在,`OrderDetailService.java:429`):前端按 `allPassed` 判分支,`items === null` 时只读 `preview`,不要无条件遍历 `items`。
|
||||
- **未通过时 `items` 是全量 5 项**:含 `passed=true` 的项,失败项靠 `passed=false` 筛(`OrderDetailService.java:392-422`,第八章实测读数同)。
|
||||
- **未登录**:网关 JwtAuthFilter 拦截,HTTP 200 + body `code=401`(`JwtAuthFilter.java:543-563`)。
|
||||
- **资源不存在**:HTTP 200 + body `code=581007`。
|
||||
- **权限不足**:HTTP 200 + body `code=581008`(非本单定制师)或 `code=581045`(房务角色)。
|
||||
- **不需要用车 / 整团免车**:`VEHICLE_DONE` 直接 `passed=true` 且无 `failReason`——订单 `needs_vehicle=false`(`OrderDetailService.java:1011-1013`),或所在团已声明整团免车(`OrderDetailService.java:1016-1018`)。
|
||||
- **需要用车但一条需求都没提**:`VEHICLE_DONE` `passed=false`、`failReason="未提交用车需求"`,这是正常返回不是异常(`OrderDetailService.java:1023-1026`)。
|
||||
- **下游服务降级**:无,调用链全在 order-v3 同进程,不经 Feign。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举
|
||||
|
||||
### 用车类别(`VehicleRequirementKind`)
|
||||
|
||||
**所属字段**: `failReason` 中的类别中文名前缀 | **类型**: `String`
|
||||
|
||||
| 枚举值 | 中文名 | 说明 |
|
||||
|-------|-------|------|
|
||||
| `TRAVEL` | 行程用车 | 团期行程用车,服务日冻结为行程日 |
|
||||
| `TRANSFER` | 接送机 | 接送机,服务日取航班/车次日期 |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
「改前」指的是本次提交 `7811104b4` 的父提交(#8056 已合入之后的状态),不是更早的历史版本。
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 同一订单同时有行程用车 + 接送机,行程用车已 DONE、接送机未就绪 | `VEHICLE_DONE` `passed=true`(**错误**),清单放行 | `passed=false`,`failReason` 点名接送机(需求未 DONE 走分支 2、缺配车记录走分支 3),清单拦截 |
|
||||
| `failReason` 文案 | 无类别前缀:`用车需求未完成(镜像: X,当前需求: Y)` / `未找到有效配车记录` | 两个失败分支都带类别中文名前缀,拼法见第四章三分支表 |
|
||||
|
||||
本次改动只落在上面两行:`items` / `preview` 的互斥语义、5 项的 `code` 与 `checkName`、`preview` 的字段集合**都没有变**(依据:`7811104b4` 只改了 `OrderDetailService` / `VehicleRequirementKind` / `RequirementService` 与一份单测,`ConfirmChecklistRespVO` 与 `OrderController` 零改动)。
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **响应结构**:无变化。`allPassed` / `items` / `preview` 的字段与互斥语义、5 项的 `code` 与 `checkName` 均与改前一致。
|
||||
- **是否破坏向后兼容**:只在 `failReason` 文案上。改后两个失败分支的文案前面多了类别中文名(`行程用车` / `接送机`),前端若对该字符串做**精确匹配**会失配;只当文案展示则不受影响。
|
||||
- **前端是否必须同步上线**:仅当前端对 `failReason` 做了精确匹配或前缀判断时需要同步;纯展示场景无需改动。
|
||||
- **判定口径收紧**:两类需求并存的订单,改前可能 `passed=true`、改后 `passed=false`。前端不用改代码,但页面上会看到以前能确认的单现在被清单拦住——这是本次修复的预期结果。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 管理后台订单详情「确认核单」清单页面
|
||||
- **零影响**:
|
||||
- 订单创建接口
|
||||
- 订单列表、详情查询(非确认流程)
|
||||
- 支付、房间、合同等其他核单项
|
||||
- 前端对用车需求的编辑操作(创建、修改、删除需求的接口无改)
|
||||
- 历史数据(已确认的订单数据无回溯影响)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
2026-09-22 在测试服(hl-order-service-v3 `COMMIT=7811104b4`、`BEHIND=0`、`STATE=ok`)用只读 GET 取证,五单:
|
||||
|
||||
| 订单 | 形态 | `VEHICLE_DONE.passed` | `failReason` 实测原文 |
|
||||
|---|---|---|---|
|
||||
| `2101908228545937410` | 行程用车 DONE + 接送机 PENDING | `false` | `接送机需求未完成(镜像: DONE,当前需求: PENDING)` |
|
||||
| `2098374030837829634` | 两类需求都已派车 | `true` | `null` |
|
||||
| `2101146798373339137` | 全项就绪 | — | 整个 `items` 为 `null` |
|
||||
| `2087157633055064066` | 只有行程用车(TRAVEL-only) | `true` | `null` |
|
||||
| `2101018930892972033` | 只有接送机(TRANSFER-only) | `true` | `null` |
|
||||
|
||||
`2101908228545937410` 的完整响应(逐字原始读数):
|
||||
|
||||
```json
|
||||
{"code":200,"data":{"allPassed":false,"items":[{"code":"PAYMENT_OK","passed":true},{"code":"TRAVELER_COMPLETE","passed":true},{"code":"HOTEL_DONE","passed":false,"failReason":"用房需求未完成(当前: PENDING)"},{"code":"VEHICLE_DONE","checkName":"用车安排","passed":false,"failReason":"接送机需求未完成(镜像: DONE,当前需求: PENDING)"},{"code":"CONTRACT_TEMPLATE_OK","passed":true}],"preview":null}}
|
||||
```
|
||||
|
||||
**这五单覆盖不到的地方**:它们全是「接送机侧未就绪」或「两类均就绪」,**「行程用车未就绪」那一分支本轮没有活体读数**。第三章「响应示例③」里该分支的文案是按源码字符串模板(`OrderDetailService.java:1060-1061`)拼出的**格式示意**,不是实测读数;前端要对它做精确匹配的话,以源码模板为准。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
本单契约的源码落点(`origin/dev-v3`,供对照取证):
|
||||
|
||||
- 接口入口:`hl-order-service-v3/src/main/java/com/hulalv/order/core/controller/admin/OrderController.java:250-256`
|
||||
- 清单装配:`hl-order-service-v3/src/main/java/com/hulalv/order/core/service/OrderDetailService.java:369-432`
|
||||
- 用车逐类判定与 `failReason` 拼法:同上文件 `:1006-1080`
|
||||
- 响应 VO:`hl-order-service-v3/src/main/java/com/hulalv/order/core/controller/admin/vo/ConfirmChecklistRespVO.java`
|
||||
- 错误码:`hl-order-service-v3/src/main/java/com/hulalv/order/errorcode/OrderCoreErrorCode.java:17-23`、`:211-213`
|
||||
- 权限守卫:`hl-order-service-v3/src/main/java/com/hulalv/order/core/guard/OrderViewGuard.java:67-79`、`:99-137`
|
||||
- 用车类别枚举:`hl-order-service-v3/src/main/java/com/hulalv/order/requirement/enums/VehicleRequirementKind.java`
|
||||
|
||||
工单与提交链接见下方「关联 / 联系人」。
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8121](https://git.1814.love:8443/wx/HL/issues/8121)
|
||||
- **Commit**: [7811104b4](https://git.1814.love:8443/wx/HL/commit/7811104b4)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户