diff --git a/changelogs-v2/2026-09/22_8121_核单清单用车安排逐类判定包车接送机并存不再静默放行-修改接口-管理后台.md b/changelogs-v2/2026-09/22_8121_核单清单用车安排逐类判定包车接送机并存不再静默放行-修改接口-管理后台.md new file mode 100644 index 00000000..6f422bd4 --- /dev/null +++ b/changelogs-v2/2026-09/22_8121_核单清单用车安排逐类判定包车接送机并存不再静默放行-修改接口-管理后台.md @@ -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` + +外层信封 `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 +``` + +##### 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