docs(changelog): #8121 核单清单「用车安排」逐类判定,包车+接送机并存不再静默放行
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>
这个提交包含在:
API Changelog Bot
2026-09-22 06:24:22 +08:00
共同撰写人 Claude Opus 5
父节点 4be46a6531
当前提交 5140667d28
@@ -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&lt;ChecklistItemVO&gt; | `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&lt;HotelSummaryVO&gt; | 元素为 `{cityName, hotelName}`;按 `dayNumber` 升序,相邻「城市+酒店」相同的去重;无配房时为空数组 | `OrderDetailService.java:1151-1182` |
| `staffs` | List&lt;StaffItemVO&gt; | 本单人员:`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