diff --git a/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md b/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md index 01f9695c..66777b0c 100644 --- a/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md +++ b/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md @@ -20,92 +20,403 @@ base: "dev-v3" # order-v3: 调整订单「车辆安排」页同页提交 行程用车 + 接送机用车 两类需求 > **服务**: hl-order-service-v3(adjustment 层,唯一变化点) +> **PR**: #8024 | **Issue**: #7443 | **合并提交**: `920f29d76` +> **日期**: 2026-09-20 > **影响范围**: 管理后台「调整订单 → 车辆安排」页。此前该页**结构上只能提交行程用车**,本次补上接送机用车的读口与写口。 -## 这次修的是什么 +--- -「车辆安排」页此前只有一个用车需求槽。页面上那张接送机卡片(航班号、抵离时间、接送备注) -来自 `vehicleTransportSummary`,它是**只读的大交通摘要**——没有车型、座位数、数量输入, -也没有对应的提交字段。于是业务上「接机用什么车、用几辆」**没有任何录入通道**。 +## ⚠️ 关键变化 -后端的用车需求模型本身早就支持两类并存:表 `order_vehicle_requirement` 的 -`requirement_kind` 取 `TRAVEL`(行程用车,服务日=行程日)/ `TRANSFER`(接送机,服务日=航班日), -唯一键 `(order_id, active_kind)` ⇒ **同一订单两类 active 需求合法并存,各有自己的 `fleet` 与 `service_dates`**。 -缺口只在 adjustment 这一层把 kind 焊死成了 TRAVEL。本次把它透出来。 +**接口契约本身已闭环(可正常提交、可双槽回显),但接送机(TRANSFER)需求同步给车务的链路目前恒失败,业务尚未端到端打通。** 前端可以按本文正常开工,但不要把"提交成功"等同于"车务已收到派车任务"。完整证据与根因见「六、边界行为」§「本文的覆盖边界」。 -## 接口变更 +--- -### 1. `GET /v3/admin/order/{orderId}/adjustment/snapshot`(读) +## 一、背景 -**新增响应字段 `transferRequirement`**,类型与既有 `vehicleRequirement` 完全相同。 +「车辆安排」页此前只有一个用车需求槽。页面上那张接送机卡片(航班号、抵离时间、接送备注)来自 `vehicleTransportSummary`,它是**只读的大交通摘要**——没有车型、座位数、数量输入,也没有对应的提交字段。于是业务上「接机用什么车、用几辆」**没有任何录入通道**。 -| 字段 | 说明 | -|---|---| -| `vehicleRequirement` | **行为不变**,仍是 `TRAVEL`(行程用车)。⚠️ 字段名与类型均未改动,现网页面无需调整 | -| `transferRequirement` | **新增**,`TRANSFER`(接送机用车)。该订单没有接送机需求时为 `null` | +后端的用车需求模型本身早就支持两类并存:表 `order_vehicle_requirement` 的 `requirement_kind` 取 `TRAVEL`(行程用车,服务日=行程日)/ `TRANSFER`(接送机,服务日=航班日),唯一键 `(order_id, active_kind)` ⇒ **同一订单两类 active 需求合法并存,各有自己的 `fleet` 与 `service_dates`**。缺口只在 adjustment 这一层把 kind 焊死成了 TRAVEL。本次把它透出来。 -两个字段的结构(同一个 VO): +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 调整订单预填快照查询 | GET | `/v3/admin/order/{id}/adjustment/snapshot` | 修改接口 | 新增响应字段 `transferRequirement`(结构与既有 `vehicleRequirement` 完全相同) | +| 2 | 调整订单统一提交 | POST | `/v3/admin/order/{id}/adjustment/submit` | 修改接口 | `updates` 新增 `transferRequirement`,支持一次提交同时落 TRAVEL + TRANSFER 两类用车需求 | + +--- + +## 三、接口详情 + +### 1. 调整订单预填快照查询 `GET /v3/admin/order/{id}/adjustment/snapshot` + +**VO**: `AdjustmentSnapshotRespVO`(无独立 ReqVO;入参为 path 变量 `id` + query 参数 `scope`) + +#### 使用场景 + +「车辆安排」弹窗打开时调用,一次性拉取行程用车(TRAVEL)与接送机用车(TRANSFER)两类需求的当前 active 版本、以及大交通摘要,用于双卡片回显。`scope` 传 `VEHICLE_REQ`(或不传,不传返全部子领域)时本组字段有值;`basic` 恒返回不受 `scope` 限制。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `id` | Path | Long | ✅ | 订单雪花 ID | 订单 ID | +| `scope` | Query | String | ❌ | 逗号分隔枚举:`BASIC`/`PEOPLE`/`SCHEDULE`/`ITINERARY`/`HOTEL_REQ`/`VEHICLE_REQ`;任一 token 非法返 `587003` | 限定返回子领域;不传返全部。`scope` 传了但不含 `VEHICLE_REQ` 时,`vehicleRequirement`/`transferRequirement`/`vehicleTransportSummary` 三个字段均不返回 | + +#### 出参 `Result` | 字段 | 类型 | 说明 | |---|---|---| -| `id` | String | 需求 id | +| `vehicleRequirement` | VehicleRequirementVO | 行程用车需求(`requirement_kind=TRAVEL`)。**字段名/类型均未变动**;该类别无活跃需求时为 `null` | +| `transferRequirement` | VehicleRequirementVO | **新增**。接送机用车需求(`requirement_kind=TRANSFER`)。该类别无活跃需求时为 `null` | +| `vehicleTransportSummary` | VehicleTransportSummaryVO | 大交通接送时间摘要,**结构与行为原样不变**,仍是只读展示 | +| `basic`/`travelers`/`schedule`/`itinerary`/`hotelRequirement`/`hotelDayDefaults`/`lockedDays`/`editableTabLocksHint` | - | 其余子领域字段,本次未改动,按各自既有 `scope` 规则返回 | + +`VehicleRequirementVO` 结构(`vehicleRequirement` 与 `transferRequirement` 复用同一 VO): + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | String | 需求 ID(雪花,序列化为字符串) | | `version` | Integer | 版本号 | -| `status` | String | 需求状态(如 `PENDING_REVIEW`) | +| `status` | String | 需求状态,枚举见「六.5、枚举 / 数据字典」 | | `fleet` | Array | **车辆组合**,元素 `{vehicleType, seats, count}` —— 即「用什么车、几座、几辆」 | -| `specialTags` | Array<String> | 特殊诉求标签 | -| `pickupRequired` | Boolean | 是否需要平台接机/接站 | -| `dropoffRequired` | Boolean | 是否需要平台送机/送站 | +| `specialTags` | Array<String> | 特殊诉求标签(字典 `vehicle_special_demand`) | +| `pickupRequired` | Boolean | 兼容回显字段;接机/接站以大交通摘要为准 | +| `dropoffRequired` | Boolean | 兼容回显字段;送机/送站以大交通摘要为准 | | `remark` | String | 备注 | -| `claimerId` / `claimerName` / `claimedAt` | - | 抢单人信息 | +| `claimerId` / `claimerName` / `claimedAt` | - | 车控人信息(已认领时有值) | | `vehicleType` / `requiredSeats` | - | ⚠️ **旧兼容字段,恒为 `null`**。车型与座位已迁入 `fleet`,不要再读这两个 | -`vehicleTransportSummary`(大交通摘要)**保持原样不变**,仍是只读展示用。 +`VehicleTransportSummaryVO` 结构(本次未改动,供接送机门控逻辑参考): -### 2. `POST /v3/admin/order/{orderId}/adjustment/submit`(写) +| 字段 | 类型 | 说明 | +|---|---|---| +| `hasPickupTime` | Boolean | 是否有可展示的接送机/站时间——前端据此判断能否提交接送机需求 | +| `displayText` | String | 汇总展示文案;无时间时为 `null` | +| `emptyText` | String | 空态提示;`hasPickupTime=false` 时返回,当前文案「暂无接送机时间」 | +| `arrivals` / `departures` | Array | 到达/离开接送时间明细 | -**`updates` 下新增 `transferRequirement`**,结构与既有 `updates.vehicleRequirement` 完全相同 -(`fleet` / `specialTags` / `pickupRequired` / `dropoffRequired` / `remark`)。 +#### 请求示例 -- 只传 `vehicleRequirement` → **行为与改动前逐字节一致**(行程用车) -- 只传 `transferRequirement` → 只提交接送机用车 -- **两个都传 → 一次提交落两条需求**,这正是「同页提交」要的效果 +```http +GET /v3/admin/order/2101219133700952066/adjustment/snapshot?scope=VEHICLE_REQ +``` -调整记录(`adjustment-record`)里两者会产出**可区分的两条** `VEHICLE_REQ` 条目: -`行程用车需求已调整` / `接送机用车需求已调整`。改动前恒为「车辆需求已调整」,两条无法区分。 +#### 响应示例 -🔴 **`serviceDates` 前端不传**。服务日由后端派生:`TRAVEL` 取行程日,`TRANSFER` 取**大交通的航班/车次日期** -(客人可能提前一天到、返程后一天走,所以允许落在行程日窗口之外)。 +实测节选(订单 `2101219133700952066`,摘自「八、测试环境已验证」①第二行,仅保留 `data` 内 `vehicleRequirement`/`transferRequirement` 两字段用于结构对照): -## 🔴 前端必须处理的前置:没有大交通时提交接送机需求会失败 +```json +{ + "vehicleRequirement": { + "id": "2101219223517659138", + "fleet": [{"vehicleType": "mpv", "seats": 7, "count": 1}] + }, + "transferRequirement": { + "id": "2101219393722634242", + "fleet": [{"vehicleType": "mpv", "seats": 7, "count": 1}] + } +} +``` -`TRANSFER` 的服务日来自大交通,**空集合不会兜底成行程日**(那样会把航班日写错、 -派车日期落到需求单声明范围之外)。因此该订单没有录入大交通行程时, -提交 `transferRequirement` 会返回 **`809002`**。 +⚠️ 以上两者 `fleet` 恰好相同是该样本单本身如此,**不能拿来证明按 kind 取数生效**;真正证明它的是下面「空数据 / 降级响应」的阳性对照,以及两者 `id` 不同。 -⇒ **建议前端在没有大交通时把接送机那一段的输入置灰**,并提示「请先录入大交通行程」, -不要让用户填完再吃一个错误码。判断依据用同一份快照里的 `vehicleTransportSummary.hasPickupTime` -(为 `false` 时它还会给出 `emptyText`,当前文案是「暂无接送机时间」)。 +#### 空数据 / 降级响应 -⚠️ 两份同时提交而接送机这半抛 809002 时,**整笔调整回滚**(一次 submit = 一笔原子调整), -行程用车那半也不会落库。所以置灰比事后补救重要。 +该订单没有接送机需求时 `transferRequirement` 为 `null`(不是空对象,也不是省略字段)。实测阳性对照:订单 `2101566624467419137`(提交前)两个字段**均为 `null`**——用于证明 `transferRequirement` 不是恒有值、按 kind 派生是真实生效的(若恒有值就无法与 `vehicleRequirement` 区分开)。 -## 环境开关 +```json +{ "code": 200, "data": { "vehicleRequirement": null, "transferRequirement": null }, "success": true } +``` + +#### 错误响应 + +```json +{ "code": 581007, "message": "订单不存在", "success": false, "data": null } +``` + +```json +{ "code": 587003, "message": "调整范围取值非法", "success": false, "data": null } +``` + +#### 业务边界 + +- 未登录/网关未透传 `X-Admin-Role` → 401(网关拦截),不进入本接口逻辑 +- 订单不存在 → `581007` +- `scope` 含非法枚举值 → `587003`,不会静默只返 `basic` +- `vehicleRequirement`/`transferRequirement` 为 `null` 是正常态(该类别当前无活跃需求),前端不应把 `null` 当异常处理 +- 两个字段结构完全对称,读取代码不应区分对待,唯一差异是各自的 `id`/`fleet`/`version` 等业务数据 +- `vehicleType`/`requiredSeats` 两个旧兼容字段**恒为 `null`**,车型信息一律读 `fleet` + +### 2. 调整订单统一提交 `POST /v3/admin/order/{id}/adjustment/submit` + +**VO**: `AdjustmentSubmitReqVO` → `AdjustmentSubmitRespVO` + +#### 使用场景 + +「车辆安排」弹窗点击保存时调用。前端在内存中收集本次改动的 `vehicleRequirement`(行程用车)与/或 `transferRequirement`(接送机用车),一次性提交;后端在单一事务内原子应用,任一子域校验失败则整体回滚(含已提交的另一半)。这是「调整订单」弹窗统一提交入口的一部分,本次只新增 `updates.transferRequirement` 这一个子字段,其余子域(`people`/`schedule`/`itinerary`/`hotelRequirement`)行为不变。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `id` | Path | Long | ✅ | 订单雪花 ID | 订单 ID | +| `updates` | Body | Object | ✅ | 至少一个子字段非 `null`,否则 `587012` | 各子域修改内容容器 | +| `updates.vehicleRequirement` | Body | `VehicleRequirementBodyVO` | ❌ | 未改子领域置 `null` | 行程用车需求完整新版本;只传它时行为与改动前逐字节一致 | +| `updates.vehicleRequirement.fleet` | Body | Array | 提交该需求时必填 | 元素 `{vehicleType, seats, count}`;`vehicleType` 取值见「六.5、枚举 / 数据字典」 | 车型组合 | +| `updates.vehicleRequirement.specialTags` | Body | Array<String> | ❌ | 字典 `vehicle_special_demand` | 通用特殊诉求标签 | +| `updates.vehicleRequirement.pickupRequired` / `.dropoffRequired` | Body | Boolean | ❌ | - | 兼容字段 | +| `updates.vehicleRequirement.remark` | Body | String | ❌ | - | 备注 | +| `updates.transferRequirement` | Body | `VehicleRequirementBodyVO` | ❌ | **新增**;子字段与 `updates.vehicleRequirement` 完全相同(`fleet`/`specialTags`/`pickupRequired`/`dropoffRequired`/`remark`) | 接送机用车需求完整新版本;🔴 **不含 `serviceDates`**——服务日由后端从大交通派生;该订单没有大交通时返 `809002`;开关未开时返 `809009` | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `success` | Boolean | 提交是否成功。响应体极简,不含业务数据;失败统一由全局异常处理器返回 `Result{code, message}` | + +#### 请求示例 + +与「八、测试环境已验证」②实测一致,补上 `updates` 外层包裹以符合 `AdjustmentSubmitReqVO` 结构: + +```json +{ + "updates": { + "vehicleRequirement": { + "fleet": [{"vehicleType": "bus", "seats": 19, "count": 1}] + }, + "transferRequirement": { + "fleet": [{"vehicleType": "mpv", "seats": 7, "count": 2}] + } + } +} +``` + +#### 响应示例 + +实测原文(HTTP 200,未展开顶层 `message`/`success` 字段): + +```json +{"code":200,"data":{"success":true}} +``` + +#### 空数据 / 降级响应 + +若 `updates` 整体所有子字段均为 `null`(既未改行程用车也未改接送机,也未改其它子域),后端判定为无实际修改,返回 `587012`(不落库、不产生调整记录)。只传 `vehicleRequirement` 或只传 `transferRequirement` 都是合法的部分提交,另一半保持不变——不是"全有全无"。 + +#### 错误响应 + +实测原文(订单无大交通时提交 `transferRequirement`): + +```json +{"code":809002,"message":"接送机需求缺少服务日期,请先补齐大交通信息"} +``` + +源码定义、未在本次实测中主动触发(测试服开关已开),按 `VehicleRequirementKindErrorCode`/`AdjustmentErrorCode` 源码消息模板给出: + +```json +{ "code": 809009, "message": "接送机用车需求尚未开放提交,请联系管理员确认开放时间(订单 2101566624467419137)", "success": false, "data": null } +``` + +```json +{ "code": 587012, "message": "变更内容为空,无实际修改", "success": false, "data": null } +``` + +```json +{ "code": 587002, "message": "订单已是终态,不可调整", "success": false, "data": null } +``` + +#### 业务边界 + +- 事务边界:`updates.vehicleRequirement` 与 `updates.transferRequirement` 在**同一个 `@Transactional` 事务**内应用;任一半失败(如接送机因缺大交通抛 `809002`)**整笔回滚**,已提交的另一半也不落库 +- `serviceDates` 前端不传;服务日由后端派生:`TRAVEL` 取行程日,`TRANSFER` 取大交通航班/车次日期(详见「四、契约约束与正确调用方式」) +- 开关 `hl.order.requirement.transfer-kind-submit-enabled` 关闭时提交 `transferRequirement` 返 `809009`;开关无 `@RefreshScope`,Nacos 热推不生效,需重启实例 +- 只传 `vehicleRequirement` → 行为与改动前逐字节一致;改动前的请求体不含 `transferRequirement` 字段,旧前端请求不受影响(向后兼容) +- `fleet[].vehicleType` 只接受 `suv`/`mpv`/`bus`/`sedan` 四个大类 key,后端兼容历史别名并归一 +- 调整记录 `adjustment-record` 会为两类需求各产出一条可区分的 `VEHICLE_REQ` 条目(`行程用车需求已调整` / `接送机用车需求已调整`),见「八、测试环境已验证」③ +- 🔴 提交成功仅代表**订单侧**需求已落库;同步给车务的 outbox 命令目前对 `TRANSFER` 恒失败,详见「六、边界行为」的覆盖边界说明——业务尚未端到端打通 + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。 + +### `serviceDates` 由后端派生,前端禁止传 + +`TRANSFER` 的服务日来自大交通,**空集合不会兜底成行程日**(那样会把航班日写错,派车日期落到需求单声明范围之外): + +- `TRAVEL`:服务日 = 行程日(`order_itinerary_day` 派生) +- `TRANSFER`:服务日 = 大交通的航班/车次日期(客人可能提前一天到、返程后一天走,所以允许落在行程日窗口之外) + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload | 结果 | +|---|---|---| +| ✅ 只提交行程用车 | `{"updates":{"vehicleRequirement":{"fleet":[...]}}}` | 200,行为与改动前一致 | +| ✅ 只提交接送机用车(订单已录大交通) | `{"updates":{"transferRequirement":{"fleet":[...]}}}` | 200,落 1 条 `TRANSFER` 需求 | +| ✅ 同页两类都提交 | `{"updates":{"vehicleRequirement":{...},"transferRequirement":{...}}}` | 200,落 2 条 active 需求(见「八」②实测) | +| ❌ 提交接送机用车但订单未录大交通 | `{"updates":{"transferRequirement":{"fleet":[...]}}}` | `809002`,整笔回滚(见「八」④实测) | +| ❌ 提交接送机用车但开关未开(生产默认) | 同上 | `809009` | +| ❌ 期望通过入参指定 `serviceDates` | `{"updates":{"transferRequirement":{"serviceDates":[...]}}}` | **无效**——该字段不在 `VehicleRequirementBodyVO` 定义内,传了也会被忽略,服务日仍按后端派生规则计算 | + +### 前端必须处理的前置:没有大交通时提交接送机需求会失败 + +因此该订单没有录入大交通行程时,提交 `transferRequirement` 会返回 **`809002`**。 + +⇒ **建议前端在没有大交通时把接送机那一段的输入置灰**,并提示「请先录入大交通行程」,不要让用户填完再吃一个错误码。判断依据用同一份快照里的 `vehicleTransportSummary.hasPickupTime`(为 `false` 时它还会给出 `emptyText`,当前文案是「暂无接送机时间」)。 + +⚠️ 两份同时提交而接送机这半抛 809002 时,**整笔调整回滚**(一次 submit = 一笔原子调整),行程用车那半也不会落库。所以置灰比事后补救重要。 + +### 错误码处置对照(🔴 两码分开处理,别混) + +| 错误码 | 触发条件 | 前端应做的下一步 | +|---|---|---| +| `809002` | 该订单尚未录入大交通行程,却提交了 `transferRequirement` | **去补大交通**——引导用户先在大交通模块录入行程,而不是重试提交 | +| `809009` | `hl.order.requirement.transfer-kind-submit-enabled` 开关未开(生产环境默认关闭) | **找后端开开关**——这不是数据问题,重试/补数据都无效,需要后端改配置并重启实例 | + +### 环境开关 `hl.order.requirement.transfer-kind-submit-enabled` - **代码默认 `false`**;生产环境未开,提交 `kind=TRANSFER` 返 `809009` - **测试服已置 `true`**(2026-09-19 15:33:32 发布,随 order-v3 重启生效) -- ⚠️ 该配置无 `@RefreshScope`,改完必须重启服务才生效 +- ⚠️ 该配置无 `@RefreshScope`(源码 `RequirementService` 注释确认),改完必须重启服务才生效,Nacos 热推不生效 -## 联调样本 +--- -| | | -|---|---| -| 订单 | `2101219133700952066`(订单号 `HL20260919155734730`,散客单) | -| 特征 | 已录大交通 2 条;`TRAVEL` 与 `TRANSFER` 两类需求**均已存在**,可直接用来验双槽回显 | +## 五、数据库行为 -## 实测读数(2026-09-20,测试服真实网关调用) +写接口 `POST .../adjustment/submit` 落库表 `order_vehicle_requirement`(INSERT-only 版本化模型,唯一键 `(order_id, active_kind)`,同订单同 kind 仅 1 行 `is_active=1`)。 -后端版本:`hl-order-service-v3` @ **`920f29d76`**(PR #8024 squash 合入 dev-v3)。 +实测②落库结果(订单 `2101566624467419137`): + +| `active_kind` | `fleet` | `service_dates` | +|---|---|---| +| `TRAVEL` | `[{"count":1,"seats":19,"vehicleType":"bus"}]` | `["2026-10-20","2026-10-21","2026-10-22"]`(行程三天) | +| `TRANSFER` | `[{"count":2,"seats":7,"vehicleType":"mpv"}]` | `["2026-10-20","2026-10-23"]`(**接机日 + 送机日**) | + +🔴 **两个 `service_dates` 一个都不是前端传的**,全部由后端按 kind 派生——这是「前端不传日期」这条约定的硬证据。 + +**同订单两类需求各占一行、互不覆盖**:`TRAVEL` 与 `TRANSFER` 各自维护自己的 `version`/`fleet`/`service_dates`,提交一类不影响另一类已落库的行(除非同次 submit 一起提交)。 + +**写入原子性**:`updates.vehicleRequirement` 与 `updates.transferRequirement` 在同一个 `@Transactional` 事务内落库;任一半失败(如 `809002`)**整笔回滚**,DB 层零写入(不是"先落一半再补")。 + +--- + +## 六、边界行为 + +- 未登录/网关未透传角色 → 401(网关拦截) +- 订单不存在 → `581007` +- `scope` 非法枚举值 → `587003` +- 订单已是终态(已结算/已取消)→ 提交返 `587002`,拒绝任何调整(含车辆需求) +- `updates` 全部子字段为空 → `587012` +- 老数据兼容:改动前落库的旧行仍被正确识别为 `requirement_kind=TRAVEL` 并映射进 `vehicleRequirement`,不会因为响应新增字段而出现兼容异常 + +### 🔴 本文的覆盖边界:只验到「提交」为止 + +同一次实测里观测到:TRANSFER 需求同步给车务的 outbox 命令**持续失败** +(`order_fleet_command_outbox`,`command_type=RECONCILE`:TRAVEL `SUCCEEDED`, +**TRANSFER `PENDING` / `retry_count=2` / `last_error_message="Fleet 用车需求换版失败: code=605905, message=需求版本过期"`**)。 + +⇒ **前端按本文接完即可正常提交并回显,但提交出去的接送机需求目前到不了车务侧。** +该缺陷在 fleet(`AssignmentService:11492` 取当前需求不带 kind、恒取 TRAVEL),已在修, +修好后另发交接件。**它不改变本文的前端契约**,可以并行开工。 + +⚠️ 写下这段是因为「四条实测全达成」这个汇总句**丢掉边界之后会变强**—— +会被读成「接送机用车已经端到端可用」,而那句话今天还不成立。 + +--- + +## 六.5、枚举 / 数据字典 + +### `status`(`VehicleRequirementVO.status`,源码 `RequirementStatus` 枚举,房/车需求共用单源) + +**所属字段**: `vehicleRequirement.status` / `transferRequirement.status` | **类型**: `String` + +| 值 | 中文 label | 说明 | +|----|------|------| +| `PENDING` | 待房务配 | 已提交,等待车控接单(核心+团期共用,进入抢单池) | +| `PROCESSING` | 配房中 | 已接单,处理中 | +| `DONE` | 配房完成 | 配车完成 | +| `PENDING_REVIEW` | 待审核 | 仅团期:定制师提交后等团期管理员审核 | +| `REJECTED_TO_CONSULTANT` | 驳回 | 已驳回定制师 | +| `REJECTED_TO_ADMIN` | 驳回 | 已驳回团期管理员(仅团期) | + +⚠️ label 是房务侧视角措辞(该枚举被房、车两域共用),车需求场景下前端应另行映射展示文案,不要直接渲染 label 原文。`AdjustmentSnapshotRespVO` 内嵌 `VehicleRequirementVO` 的 Swagger 注释写的是「PENDING / CLAIMED / DONE」——**该注释与源码枚举 `RequirementStatus` 不一致,`CLAIMED` 不是合法取值,请以本表(源码枚举)为准**。 + +### `fleet[].vehicleType`(`FleetItem.vehicleType`) + +**所属字段**: `vehicleRequirement.fleet[].vehicleType` / `transferRequirement.fleet[].vehicleType` / 提交体同路径 | **类型**: `String` + +| 值 | 说明 | +|----|------| +| `suv` | SUV | +| `mpv` | MPV(本文实测样本使用值) | +| `bus` | 大巴/中巴(本文实测样本使用值) | +| `sedan` | 轿车 | + +只选大类,不选具体车型;后端兼容历史别名并归一。 + +### `requirement_kind`(DB 列 `order_vehicle_requirement.requirement_kind`;不直接出现在 JSON 字段名中,通过 `vehicleRequirement`/`transferRequirement` 两个槽位体现) + +| 值 | 对应响应/提交字段 | 服务日口径 | +|----|------|------| +| `TRAVEL` | `vehicleRequirement` | 行程日(`order_itinerary_day` 派生) | +| `TRANSFER` | `transferRequirement` | 大交通航班/车次日期(允许落在行程日窗口之外) | + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `GET .../snapshot` 响应 `vehicleRequirement` | 唯一的用车需求槽 | 语义收窄为「行程用车」,**字段名/类型/取值规则不变** | +| `GET .../snapshot` 响应 `transferRequirement` | 不存在 | **新增**,接送机用车需求,无该需求时为 `null` | +| `POST .../submit` 请求体 `updates.vehicleRequirement` | 唯一的用车需求提交槽 | **字段名/类型/校验规则不变** | +| `POST .../submit` 请求体 `updates.transferRequirement` | 不存在 | **新增**,接送机用车需求提交槽 | +| `adjustment-record` 车辆需求变更项 `label` | 恒为「车辆需求已调整」 | 按类别区分为「行程用车需求已调整」/「接送机用车需求已调整」 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 接送机用车的录入通道 | 无——`vehicleTransportSummary` 只读展示,没有对应提交字段 | 有——`updates.transferRequirement` 可提交车型/座位/数量 | +| 一次提交能覆盖的用车类别 | 仅 `TRAVEL` | `TRAVEL`、`TRANSFER`,或两者同页一次提交(同一原子事务) | +| 调整记录中车辆需求变更的可辨识度 | 无法区分改的是哪一类 | 两条独立可辨识条目 | +| `TRANSFER` 服务日来源 | N/A(无此提交路径) | 后端从大交通派生,前端不传;无大交通时拒绝(`809002`) | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 否——`vehicleRequirement` 字段名/类型/语义零变更,旧前端请求体(不含 `transferRequirement`)行为与改动前逐字节一致 +- **前端是否必须同步上线**: 否,本次是纯新增字段/新增可选提交槽,前端可延后接入;接入前不会影响现网「行程用车」链路 +- **前端 workaround 清理点**: 无——此前接送机用车没有任何前端录入通道,不存在需要撤下的旧 workaround +- 🔴 **业务闭环限制**:接口契约本身已闭环(提交 + 回显),但 TRANSFER 需求同步给车务的链路当前恒失败(见「六、边界行为」覆盖边界),前端接入后用户能成功提交,但接送机需求实际派不出车,直到 fleet 侧缺陷修复为止 + +--- + +## 七、不影响范围 + +- **仅影响**: 管理后台「调整订单 → 车辆安排」弹窗的读口(`GET .../adjustment/snapshot`)与写口(`POST .../adjustment/submit`) +- **零影响**: + - `vehicleRequirement` 字段名/类型/语义**零变更**,现网只读行程用车那部分代码无需调整 + - `vehicleTransportSummary`(大交通摘要)**结构与语义原样不变**,仍只读展示 + - 用车需求生命周期写口 `VehicleRequirementAdminController`(`dispatch`/`reject`/`supplier-reject`/`submit`/`urgent`/`urgent/cancel`/`candidates`/`assign`)**均未改动**——本次 PR 只动了 `AdjustmentSnapshotRespVO`/`AdjustmentSubmitReqVO`/`AdjustmentService` 三个源文件(`git show --stat 920f29d76` 核实) + - Fleet 侧确认/重开内部写口(`OrderInternalForFleetController` 的 `.../vehicle/final-confirmation-reservations`、`.../release`、`.../vehicle/reopen-after-assignment-cancel`)响应结构**未动** + - 团期用车需求 reopen(`GroupBatchRequirementController` 的 `.../vehicle-requirement/reopen`)**未动** + - 「调整订单」弹窗其余子域(出行人/改期/行程/房需求)的接口与行为**完全不受影响** + - C 端接口、订单创建/详情读取接口**零影响** + - 历史数据无需迁移:存量订单的 `TRAVEL` 需求行结构不变;本次不产生任何 `TRANSFER` 迁移行 + +--- + +## 八、测试环境已验证 + +真实网关调用(2026-09-20,测试服),后端版本 `hl-order-service-v3` @ `920f29d76`(PR #8024 squash 合入 dev-v3): **① 读口双槽 —— 带阳性对照** @@ -114,8 +425,7 @@ base: "dev-v3" | `2101566624467419137`(提交前) | `null` | `null` | | `2101219133700952066`(两类都有) | `{id:2101219223517659138, fleet:[{mpv,7,1}]}` | `{id:2101219393722634242, fleet:[{mpv,7,1}]}` | -⚠️ 第二行两者的 `fleet` 恰好相同(样本单本身如此),**所以"两个字段不同"这件事不能拿来证明按 kind 取数生效**; -真正证明它的是第一行那个全 `null` 的阳性对照,以及两者 `requirementId` 不同。 +⚠️ 第二行两者的 `fleet` 恰好相同(样本单本身如此),**所以"两个字段不同"这件事不能拿来证明按 kind 取数生效**;真正证明它的是第一行那个全 `null` 的阳性对照,以及两者 `requirementId` 不同。 ✓ **② 同页提交(本文的核心)** @@ -133,12 +443,12 @@ updates.transferRequirement = {fleet:[{vehicleType:"mpv", seats:7, count:2}]} | `TRAVEL` | `[{"count":1,"seats":19,"vehicleType":"bus"}]` | `["2026-10-20","2026-10-21","2026-10-22"]`(行程三天) | | `TRANSFER` | `[{"count":2,"seats":7,"vehicleType":"mpv"}]` | `["2026-10-20","2026-10-23"]`(**接机日 + 送机日**) | -🔴 **两个 `service_dates` 一个都不是前端传的**,全部由后端按 kind 派生——这是「前端不传日期」这条约定的硬证据。 +🔴 **两个 `service_dates` 一个都不是前端传的**,全部由后端按 kind 派生——这是「前端不传日期」这条约定的硬证据。 ✓ **③ 调整记录可区分** `GET /v3/admin/order/{id}/adjustment-record` → 本次 `changeCount:2`,两条 `type=VEHICLE_REQ`, -`label` 分别是 `行程用车需求已调整` / `接送机用车需求已调整`。改动前两条 label 完全相同、事后分不出改的是哪一份。 +`label` 分别是 `行程用车需求已调整` / `接送机用车需求已调整`。改动前两条 label 完全相同、事后分不出改的是哪一份。 ✓ **④ 无大交通的失败形态** @@ -146,17 +456,27 @@ updates.transferRequirement = {fleet:[{vehicleType:"mpv", seats:7, count:2}]} → HTTP **200** + `{"code":809002,"message":"接送机需求缺少服务日期,请先补齐大交通信息"}` (提交前已确认测试服 Nacos `hl.order.requirement.transfer-kind-submit-enabled: true`, -所以这个 809002 不是被开关挡住的假象。) +所以这个 809002 不是被开关挡住的假象。) ✓ -## 🔴 本文的覆盖边界:只验到「提交」为止 +**联调样本**:订单 `2101219133700952066`(订单号 `HL20260919155734730`,散客单),已录大交通 2 条,`TRAVEL` 与 `TRANSFER` 两类需求均已存在,可直接用来验双槽回显。 -同一次实测里观测到:TRANSFER 需求同步给车务的 outbox 命令**持续失败** -(`order_fleet_command_outbox`,`command_type=RECONCILE`:TRAVEL `SUCCEEDED`, -**TRANSFER `PENDING` / `retry_count=2` / `last_error_message="Fleet 用车需求换版失败: code=605905, message=需求版本过期"`**)。 +--- -⇒ **前端按本文接完即可正常提交并回显,但提交出去的接送机需求目前到不了车务侧。** -该缺陷在 fleet(`AssignmentService:11492` 取当前需求不带 kind、恒取 TRAVEL),已在修, -修好后另发交接件。**它不改变本文的前端契约**,可以并行开工。 +## 十、相关文档 -⚠️ 写下这段是因为「四条实测全达成」这个汇总句**丢掉边界之后会变强**—— -会被读成「接送机用车已经端到端可用」,而那句话今天还不成立。 +- 关联 Issue: [wx/HL#7443](https://git.1814.love:8443/wx/HL/issues/7443) +- 关联 PR: [wx/HL#8024](https://git.1814.love:8443/wx/HL/pulls/8024)(squash 合并至 dev-v3 @`920f29d76`) +- fleet 侧 outbox 换版失败(`code=605905`)已在修,属 `#7990` 那一族,修好后另发交接件——不影响本文描述的前端契约 + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7443](https://git.1814.love:8443/wx/HL/issues/7443) +- **PR**: [#8024](https://git.1814.love:8443/wx/HL/pulls/8024) +- **Merge commit**: [920f29d76](https://git.1814.love:8443/wx/HL/commit/920f29d76a9a761bf89a85b43408fef71e7a52a2) + +### 联系人 + +- **后端负责人**: wx(GIT) +- **前端负责人**: mmg