diff --git a/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md b/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md new file mode 100644 index 00000000..01f9695c --- /dev/null +++ b/changelogs-v2/2026-09/20_7443_调整订单车辆安排页同页提交行程与接送机两类用车需求-修改接口-管理后台.md @@ -0,0 +1,162 @@ +--- +schema: "hl-changelog/v2" +ticket: "7443" +title: "调整订单「车辆安排」页同页提交 行程用车(TRAVEL) + 接送机用车(TRANSFER) 两类需求" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-20" +status_note: "后端已合入 dev-v3(PR #8024,squash 提交 920f29d76)并部署测试服:deploy-status.sh 回读 hl-order-service-v3 = dev-v3 @ 920f29d76、BEHIND=0/N、STATE=ok,两实例滚动重启均 UP,判据 git merge-base --is-ancestor 920f29d76 920f29d76 = true。gateway_status=verified 的依据是四条真实网关调用而非源码推断:①读口双槽(含阳性对照:未提交需求的订单两个字段都是 null,证明不是恒有值)②一次 submit 同时提交两份 → HTTP 200,落库两条 active 行,fleet 分别为 bus/19座/1台 与 mpv/7座/2台 ③调整记录两条 label 原文不同 ④无大交通时 809002。🔴 一条必须连着读的限定:本文只验证了「提交」这一段。同一次实测观测到 TRANSFER 需求同步给车务的 outbox 命令持续失败(order_fleet_command_outbox command_type=RECONCILE,TRAVEL=SUCCEEDED 而 TRANSFER=PENDING/retry_count=2,last_error_message='Fleet 用车需求换版失败: code=605905, message=需求版本过期'),根因是 hl-fleet-service AssignmentService:11492 requireCurrentVehicleRequirementForMutation 取当前需求时不带 kind、恒取 TRAVEL,与 TRANSFER 的 requirementId 比对必然不等。即:前端按本文接完即可正常提交并回显,但提交出去的接送机需求在修复该缺陷之前到不了车务侧,端到端业务尚未打通。该缺陷已在修(属 #7990 那一族),修好后另发交接件,不影响本文的前端契约。" +updated_at: "2026-09-20" +base: "dev-v3" +--- + +# order-v3: 调整订单「车辆安排」页同页提交 行程用车 + 接送机用车 两类需求 + +> **服务**: hl-order-service-v3(adjustment 层,唯一变化点) +> **影响范围**: 管理后台「调整订单 → 车辆安排」页。此前该页**结构上只能提交行程用车**,本次补上接送机用车的读口与写口。 + +## 这次修的是什么 + +「车辆安排」页此前只有一个用车需求槽。页面上那张接送机卡片(航班号、抵离时间、接送备注) +来自 `vehicleTransportSummary`,它是**只读的大交通摘要**——没有车型、座位数、数量输入, +也没有对应的提交字段。于是业务上「接机用什么车、用几辆」**没有任何录入通道**。 + +后端的用车需求模型本身早就支持两类并存:表 `order_vehicle_requirement` 的 +`requirement_kind` 取 `TRAVEL`(行程用车,服务日=行程日)/ `TRANSFER`(接送机,服务日=航班日), +唯一键 `(order_id, active_kind)` ⇒ **同一订单两类 active 需求合法并存,各有自己的 `fleet` 与 `service_dates`**。 +缺口只在 adjustment 这一层把 kind 焊死成了 TRAVEL。本次把它透出来。 + +## 接口变更 + +### 1. `GET /v3/admin/order/{orderId}/adjustment/snapshot`(读) + +**新增响应字段 `transferRequirement`**,类型与既有 `vehicleRequirement` 完全相同。 + +| 字段 | 说明 | +|---|---| +| `vehicleRequirement` | **行为不变**,仍是 `TRAVEL`(行程用车)。⚠️ 字段名与类型均未改动,现网页面无需调整 | +| `transferRequirement` | **新增**,`TRANSFER`(接送机用车)。该订单没有接送机需求时为 `null` | + +两个字段的结构(同一个 VO): + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | String | 需求 id | +| `version` | Integer | 版本号 | +| `status` | String | 需求状态(如 `PENDING_REVIEW`) | +| `fleet` | Array | **车辆组合**,元素 `{vehicleType, seats, count}` —— 即「用什么车、几座、几辆」 | +| `specialTags` | Array<String> | 特殊诉求标签 | +| `pickupRequired` | Boolean | 是否需要平台接机/接站 | +| `dropoffRequired` | Boolean | 是否需要平台送机/送站 | +| `remark` | String | 备注 | +| `claimerId` / `claimerName` / `claimedAt` | - | 抢单人信息 | +| `vehicleType` / `requiredSeats` | - | ⚠️ **旧兼容字段,恒为 `null`**。车型与座位已迁入 `fleet`,不要再读这两个 | + +`vehicleTransportSummary`(大交通摘要)**保持原样不变**,仍是只读展示用。 + +### 2. `POST /v3/admin/order/{orderId}/adjustment/submit`(写) + +**`updates` 下新增 `transferRequirement`**,结构与既有 `updates.vehicleRequirement` 完全相同 +(`fleet` / `specialTags` / `pickupRequired` / `dropoffRequired` / `remark`)。 + +- 只传 `vehicleRequirement` → **行为与改动前逐字节一致**(行程用车) +- 只传 `transferRequirement` → 只提交接送机用车 +- **两个都传 → 一次提交落两条需求**,这正是「同页提交」要的效果 + +调整记录(`adjustment-record`)里两者会产出**可区分的两条** `VEHICLE_REQ` 条目: +`行程用车需求已调整` / `接送机用车需求已调整`。改动前恒为「车辆需求已调整」,两条无法区分。 + +🔴 **`serviceDates` 前端不传**。服务日由后端派生:`TRAVEL` 取行程日,`TRANSFER` 取**大交通的航班/车次日期** +(客人可能提前一天到、返程后一天走,所以允许落在行程日窗口之外)。 + +## 🔴 前端必须处理的前置:没有大交通时提交接送机需求会失败 + +`TRANSFER` 的服务日来自大交通,**空集合不会兜底成行程日**(那样会把航班日写错、 +派车日期落到需求单声明范围之外)。因此该订单没有录入大交通行程时, +提交 `transferRequirement` 会返回 **`809002`**。 + +⇒ **建议前端在没有大交通时把接送机那一段的输入置灰**,并提示「请先录入大交通行程」, +不要让用户填完再吃一个错误码。判断依据用同一份快照里的 `vehicleTransportSummary.hasPickupTime` +(为 `false` 时它还会给出 `emptyText`,当前文案是「暂无接送机时间」)。 + +⚠️ 两份同时提交而接送机这半抛 809002 时,**整笔调整回滚**(一次 submit = 一笔原子调整), +行程用车那半也不会落库。所以置灰比事后补救重要。 + +## 环境开关 + +`hl.order.requirement.transfer-kind-submit-enabled` +- **代码默认 `false`**;生产环境未开,提交 `kind=TRANSFER` 返 `809009` +- **测试服已置 `true`**(2026-09-19 15:33:32 发布,随 order-v3 重启生效) +- ⚠️ 该配置无 `@RefreshScope`,改完必须重启服务才生效 + +## 联调样本 + +| | | +|---|---| +| 订单 | `2101219133700952066`(订单号 `HL20260919155734730`,散客单) | +| 特征 | 已录大交通 2 条;`TRAVEL` 与 `TRANSFER` 两类需求**均已存在**,可直接用来验双槽回显 | + +## 实测读数(2026-09-20,测试服真实网关调用) + +后端版本:`hl-order-service-v3` @ **`920f29d76`**(PR #8024 squash 合入 dev-v3)。 + +**① 读口双槽 —— 带阳性对照** + +| 订单 | `vehicleRequirement` | `transferRequirement` | +|---|---|---| +| `2101566624467419137`(提交前) | `null` | `null` | +| `2101219133700952066`(两类都有) | `{id:2101219223517659138, fleet:[{mpv,7,1}]}` | `{id:2101219393722634242, fleet:[{mpv,7,1}]}` | + +⚠️ 第二行两者的 `fleet` 恰好相同(样本单本身如此),**所以"两个字段不同"这件事不能拿来证明按 kind 取数生效**; +真正证明它的是第一行那个全 `null` 的阳性对照,以及两者 `requirementId` 不同。 + +**② 同页提交(本文的核心)** + +``` +POST /v3/admin/order/2101566624467419137/adjustment/submit +updates.vehicleRequirement = {fleet:[{vehicleType:"bus", seats:19, count:1}]} +updates.transferRequirement = {fleet:[{vehicleType:"mpv", seats:7, count:2}]} +→ HTTP 200 {"code":200,"data":{"success":true}} +``` + +落库 `order_vehicle_requirement` 两条 active 行: + +| `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 派生——这是「前端不传日期」这条约定的硬证据。 + +**③ 调整记录可区分** + +`GET /v3/admin/order/{id}/adjustment-record` → 本次 `changeCount:2`,两条 `type=VEHICLE_REQ`, +`label` 分别是 `行程用车需求已调整` / `接送机用车需求已调整`。改动前两条 label 完全相同、事后分不出改的是哪一份。 + +**④ 无大交通的失败形态** + +订单 `2101567681155207169`(同产品、已付款、**无大交通**),只提交 `transferRequirement`: +→ HTTP **200** + `{"code":809002,"message":"接送机需求缺少服务日期,请先补齐大交通信息"}` + +(提交前已确认测试服 Nacos `hl.order.requirement.transfer-kind-submit-enabled: true`, +所以这个 809002 不是被开关挡住的假象。) + +## 🔴 本文的覆盖边界:只验到「提交」为止 + +同一次实测里观测到: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),已在修, +修好后另发交接件。**它不改变本文的前端契约**,可以并行开工。 + +⚠️ 写下这段是因为「四条实测全达成」这个汇总句**丢掉边界之后会变强**—— +会被读成「接送机用车已经端到端可用」,而那句话今天还不成立。