文件
hl-api-changelog/changelogs-v2/2026-09/24_8320_调整订单接送机不用声明-修改接口-管理后台.md
Mimingguang 347b8ee929
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): #8320 前端已交付 verified(ref=f8c389c1)
2026-09-24 12:40:32 +08:00

25 KiB

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 8320 调整订单:团期子订单未录大交通时提交用车需求须显式确认「不用接送机」(新错误码 587044) admin wx(GIT) 修改接口 deployed verified verified mmg f8c389c1a1230316163bac80079675fb3c1a26ad v2.1 2026-09-24 已合 dev-v3(PR #8326,bc197bc07)并部署测试服,deploy-status.sh 回读 hl-order-service-v3=bc197bc07、BEHIND 0/N、STATE ok。网关实测五组全过:①无大交通不带声明提交团期单车辆需求 → 587044 且整笔零写入(order_main/需求/调整记录/状态日志前后快照逐项相等)②两方向声明 true + 同一份 vehicleRequirement → 200,order_main 两列置 1,snapshot 回显 transferDeclaration=true/true、transferTransportPresent=false/false ③该方向补大交通批次后声明不生效,车务读侧仍 required=true/READY<接客信息已齐> ④同一张单同一时刻:已声明不用且无批次的方向 required=false/NOT_REQUIRED/<无需送客>,未声明且无批次的方向 required=true/MISSING/<待补接客信息>(阴性对照)⑤接机有批次、送机无批次时提交非空 transferRequirement 未声明送机 → 587044<不用送机>且零写入;散客单同 payload 不带声明 → 200 正常落库(口径 4 成立)。定向单测 14 类 386 例全绿 + ArchUnit/错误码门禁绿。 | 2026-09-24 mmg 交付:调整订单车辆安排页新增「接送机确认」卡(仅团期子订单+该方向无大交通渲染不用接机/送机必选确认,有批次不渲染),snapshot 回显声明+基线已声明无批次锁定禁撤销,前置闸报文与 587044 逐字一致本地拦,提交整份回传 transferDeclaration 自动弃快捷写口走统一提交;FunItemAdjustModal spec 46 例全绿(新增 6 例) 2026-09-24 dev-v3

调整订单:团期子订单未录大交通时提交用车需求须显式确认「不用接送机」

服务: hl-order-service-v3 PR: #8326 | Issue: #8320 | 合并提交: bc197bc07 日期: 2026-09-24 影响范围: 管理后台「订单详情 → 调整订单 → 车辆安排」。仅团期子订单受影响,散客单行为完全不变。


⚠️ 关键变化

接送机那一槽多了一条硬闸,前端不做就会撞 587044 整笔提交失败。

  1. 团期子订单(下单时带 productBatchId 的订单)在「车辆安排」页提交时,如果某个方向一条大交通批次都没有,该方向就必须在提交体里显式确认「不用接送机」(true),否则整笔被拒,返回 587044,一个字都不落库。有批次的方向不需要确认(沿用大交通,声明对它无效)。两个方向各自判定。
  2. 确认之后,车务侧那一格的文案会变:该方向由「待补接客信息 / 待补送客信息」变成「无需接客 / 无需送客」。不确认则维持「待补」——这是有意的,不要把「客人还没填大交通」读成「不用接送机」。
  3. 要接送机的路径没有变:需要接送机就必须先把大交通录进去(接送机用车需求的服务日只能从大交通航班日派生)。本次没有新增「需要但航班未定」这种第三态。

一、背景

「车辆安排」页的接送机用车槽,服务日由服务端从大交通批次派生。客人没录大交通时:

  • 订单侧算出来的接送要求是「未知」,车务侧按「需要」处理,于是看板/配车页恒显示「待补接客信息 / 待补送客信息」;
  • 而车务侧的接送机状态里本来就有「无需接客 / 无需送客」这一档,订单侧却没有任何入口能把它置上。

结果是「客人还没填大交通」与「这单就是不用接送机」在数据上完全一样,接送机缺口永远关不掉。本版增加一个订单级的显式声明来承载「就是不用」这个语义,并在提交侧设闸,逼定制师傅表态。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 调整订单预填快照查询 GET /v3/admin/order/{id}/adjustment/snapshot 修改接口 出参新增 transferDeclaration 与 transferTransportPresent
2 调整订单统一提交 POST /v3/admin/order/{id}/adjustment/submit 修改接口 updates 新增 transferDeclaration;某方向无大交通未表态时返回新码 587044

读侧(车务看板 / 团期配车)路径与响应结构均未变,只有接送机那一档的取值会随声明变化,见「六.6、修改前后对比」。


三、接口详情

1. 调整订单预填快照查询 GET /v3/admin/order/{id}/adjustment/snapshot

VO: AdjustmentSnapshotRespVO(入参为 path 变量 + query,无独立 ReqVO)

使用场景

管理后台打开「调整订单」弹窗时调用一次,一次性拉取各子领域当前值用于回显。本次相关的是「车辆安排」页:需要知道(a)接送机不用声明的当前值,(b)两个方向是否已有大交通批次——后者决定前端要不要渲染「不用接送机」的必选确认。

入参

字段 位置 类型 必填 约束 说明
id Path Long ✅ 订单雪花 ID(字符串传输,别转 Number) 订单 ID
scope Query String ❌ 逗号分隔:BASIC/PEOPLE/SCHEDULE/ITINERARY/HOTEL_REQ/VEHICLE_REQ;任一 token 非法返 587003 限定返回子领域;不传返全部。传了但不含 VEHICLE_REQ 时,本次两个新字段不返回

出参 Result<AdjustmentSnapshotRespVO>

本次新增字段(其余字段结构与取值规则一律不变):

字段 类型 说明
transferDeclaration.pickupNotRequired Boolean 新增。接机方向是否已声明「本单不用接机」。未声明返回 false(不是 null)
transferDeclaration.dropoffNotRequired Boolean 新增。送机方向是否已声明「本单不用送机」。未声明返回 false
transferTransportPresent.pickup Boolean 新增。接机方向是否已存在大交通批次。true 时该方向不需要「不用接送机」确认,且声明对它不生效
transferTransportPresent.dropoff Boolean 新增。送机方向是否已存在大交通批次

其余字段(vehicleRequirement / transferRequirement / vehicleTransportSummary / basic 等)结构与取值均未变。

请求示例

GET /v3/admin/order/2101219133700952066/adjustment/snapshot?scope=VEHICLE_REQ

响应示例

{
  "code": 0,
  "message": "success",
  "data": {
    "vehicleRequirement": { "...": "结构未变,略" },
    "transferRequirement": null,
    "vehicleTransportSummary": {
      "hasPickupTime": false,
      "displayText": null,
      "emptyText": "暂无接送机时间",
      "arrivals": [],
      "departures": []
    },
    "transferDeclaration": { "pickupNotRequired": false, "dropoffNotRequired": false },
    "transferTransportPresent": { "pickup": false, "dropoff": false }
  },
  "success": true
}

空数据 / 降级响应

  • 订单没有大交通批次:vehicleTransportSummary.hasPickupTime=false、emptyText="暂无接送机时间"、arrivals/departures 均为 [];transferTransportPresent 两个方向都是 false。这是正常态,不是降级。
  • 订单从未表过态:transferDeclaration 两个字段都是 false(表示「未声明」,不是「声明了需要」)。
  • 老订单 / 存量数据:两列默认 0,读出来与「从未表态」完全一致,不会异常。
  • scope 不含 VEHICLE_REQ:本组三个字段(含本次两个)整体不返回。

错误响应

{ "code": 587003, "message": "调整范围取值非法", "data": null, "success": false }

业务边界

  • 只读接口,无副作用;不因订单状态(已出行、已结算)而拒绝。
  • transferTransportPresent 判的是「该方向有没有大交通批次」,不看批次的 pickupRequired;与提交闸、车务读侧回落同一句判据。
  • 订单 ID 传错/不存在 → 既有 581007「订单不存在」,本次未改。

2. 调整订单统一提交 POST /v3/admin/order/{id}/adjustment/submit

VO: AdjustmentSubmitReqVO → AdjustmentSubmitRespVO

使用场景

「调整订单」弹窗内收集齐所有子领域改动后一次性提交;「车辆安排」页保存行程用车 / 接送机用车需求、以及本次新增的接送机不用声明,都走这一个入口。单事务原子应用:任一步失败整笔回滚。

入参

字段 位置 类型 必填 约束 说明
id Path Long ✅ 订单雪花 ID 订单 ID
updates Body Object ✅ 各子领域容器,不可全 null 未改的子领域置 null
updates.transferDeclaration Body Object ❌ 本次新增 接送机不用声明,见下
updates.transferDeclaration.pickupNotRequired Body Boolean ❌ true=本单不用接机;false=需要(或撤销声明) 接机方向表态
updates.transferDeclaration.dropoffNotRequired Body Boolean ❌ true=本单不用送机 送机方向表态
updates.vehicleRequirement Body Object ❌ 结构未变(fleet 为空数组按「未提交」处理) 行程用车需求
updates.transferRequirement Body Object ❌ 结构未变;服务日由服务端派生 接送机用车需求
其余 updates.* Body Object ❌ 全部未变 出行人 / 改期 / 行程 / 房需求

出参 Result<AdjustmentSubmitRespVO>

字段 类型 说明
success Boolean 恒 true(失败走错误码)

结构未变。

请求示例

无大交通、只提交行程用车、不带声明(会被拒,用于说明闸的位置):

{
  "updates": {
    "vehicleRequirement": {
      "specialTags": [],
      "remark": "按原方案",
      "fleet": [ { "vehicleType": "SUV系列", "seats": 5, "count": 1 } ]
    }
  }
}

两个方向都确认「不用」:这是无大交通时唯一能被接受的形态

{
  "updates": {
    "vehicleRequirement": {
      "specialTags": [],
      "remark": "按原方案",
      "fleet": [ { "vehicleType": "SUV系列", "seats": 5, "count": 1 } ]
    },
    "transferDeclaration": { "pickupNotRequired": true, "dropoffNotRequired": true }
  }
}

只改声明、不动其他子领域(有效提交,不会被 587033 拦):

{ "updates": { "transferDeclaration": { "pickupNotRequired": true, "dropoffNotRequired": false } } }

响应示例

{ "code": 0, "message": "success", "data": { "success": true }, "success": true }

空数据 / 降级响应

  • updates.transferDeclaration 传空对象 {}(两个字段都 null)或整体不传:按「本次没对车辆子领域表态」处理,不触发 587044。
  • 散客单(无 productBatchId)传了 transferDeclaration:不写入、不报错,静默忽略。
  • 该方向已有大交通批次时传了声明:不写入、不报错,接送要求仍按批次 pickupRequired 判。

错误响应

{ "code": 587044, "message": "本单未录入大交通,请先确认「不用接机」,或先在大交通模块录入行程后再提交", "data": null, "success": false }

(方向占位符取「接机」或「送机」,两个方向各自判定,只报先撞上的那一个。)

{ "code": 587033, "message": "未检测到有效变更,无需提交", "data": null, "success": false }

业务边界

  • 只对团期子订单生效:散客单行为与本次之前逐字相同。
  • 只在本次提交真的带了车辆子领域有效载荷时判定:非空 fleet、或 transferDeclaration 任一方向非 null。改房务、改行程、加出行人、改期这些提交不会被 587044 拦。
  • 整笔零写入:587044 与其它守卫一样在任何 DB 写之前抛出,不存在「声明写了、需求没写」的半截状态。
  • 覆盖写 + 值级 no-op:字段为 null 表示「本次不动这个方向」;两个方向的值与现值都相同时不写库、不产调整记录项。
  • 两个方向独立:可以只声明接机不用、送机照旧(送机若已补录大交通则不受影响)。
  • 幂等/并发:沿用本接口既有的订单级锁与「提交期间订单被改」守卫(587043),本次未改。

四、契约约束与正确调用方式

本节只写后端接受 / 拒绝 payload 的规则。

✅ 正确 / ❌ 错误 payload 对照

前提:订单是团期子订单,且两个方向都没有大交通批次。

场景 payload 结果
✅ 只提交行程用车 + 两个方向都确认不用 {"updates":{"vehicleRequirement":{"fleet":[{"vehicleType":"SUV系列","seats":5,"count":1}]},"transferDeclaration":{"pickupNotRequired":true,"dropoffNotRequired":true}}} 200
✅ 只提交声明(不动车辆) {"updates":{"transferDeclaration":{"pickupNotRequired":true,"dropoffNotRequired":true}}} 200
❌ 只提交行程用车、不带声明 {"updates":{"vehicleRequirement":{"fleet":[...]}}} 587044
❌ 只声明了一个方向(另一个方向无批次) {"updates":{"vehicleRequirement":{"fleet":[...]},"transferDeclaration":{"pickupNotRequired":true,"dropoffNotRequired":null}}} 587044(报「送机」)
✅ 改房务 / 改行程 / 改期(完全不带车辆子领域字段) {"updates":{"hotelRequirement":{...}}} 200(不判 587044)
⚠️ 混方向:接机有批次、送机没有 {"updates":{"transferRequirement":{"fleet":[...]}}}(未声明送机) 587044——即便接送机需求能派生出服务日也一样拦,防「带着没表过态的方向落库」
✅ 散客单不带声明 {"updates":{"vehicleRequirement":{"fleet":[...]}}} 200(散客单不判)
✅ 该方向已有大交通批次 传或不传声明 200,声明不生效

切换状态时的必要动作

  • transferDeclaration 是逐方向覆盖写:只传想动的方向,另一个方向传 null 或干脆不传(不要因为「表单里没这个控件」就传 false,那会被当成「本方向需要接送机」,在无批次时被 587044 拒绝)。
  • 未进入 / 未编辑「车辆安排」页时,整个 transferDeclaration 都不要回传(连空对象也建议不传)——只要出现 true/false 就视为对车辆子领域表态。
  • 需要接送机时不要试图用声明绕过:接送机用车需求的服务日只能来自大交通,先录大交通。
  • 判据只看本次请求体,不回看库里已存的声明:某方向无大交通时,本次提交必须带上该方向的表态。前端如果只回传「变化过的字段」(例如只带 transferRequirement),即使客人此前已勾过「不用接送机」也会被 587044 挡住。正确做法:车辆安排页每次提交都把 transferDeclaration 按当前控件状态整份回传。

五、数据库行为

涉及写操作:order_main 新增两列,语义为「该方向已显式声明不用接送机」。

前端提交 updates.transferDeclaration pickup_not_required 列 dropoff_not_required 列
{"pickupNotRequired":true,"dropoffNotRequired":true} 1 1
{"pickupNotRequired":true,"dropoffNotRequired":null} 1 保持原值(不动)
{"pickupNotRequired":false,"dropoffNotRequired":false} 0 0
不传 / 空对象 保持原值 保持原值
  • 列类型 TINYINT(1) NOT NULL DEFAULT 0;存量数据不做任何回填,全部为 0(未声明)。
  • 该方向已有大交通批次时即使传了 true 也不写库(声明对该方向不生效)。
  • 提交成功时同时写一条调整记录项(类型为车辆需求类,文案「接送机不用声明已更新」,带方向的中文前后值)。

六、边界行为

  • 未登录 / 无权限 → 网关拦截(401/403),本次未改。
  • 订单不存在 → 既有 581007。
  • 订单已是终态 → 既有 587002「订单已是终态,不可调整」。
  • 提交期间订单被并发改动 → 既有 587043,重新拉取后重试。
  • 老前端(不认识这两个新字段、也不回传 transferDeclaration):团期子订单只要提交车辆子领域就会被 587044 拦住——这是有意的硬闸,前端必须同步上线。
  • 老数据兼容:存量订单两列为 0,读侧与「从未表态」一致;车务侧该方向仍显示「待补」,不会被静默读成「无需接送」。
  • 大交通批次 direction 为空的历史行:与订单侧既有的方向切分口径一致(该行不归入任何方向)。

六.5、枚举 / 数据字典

接送机 readiness(车务读侧)

所属字段: 车务看板 / 配车页 readiness.statusCode(本次未改路径与结构,仅取值可能变化)

值 中文 说明
NOT_REQUIRED 无需接客 / 无需送客 该方向有大交通批次且批次 pickupRequired=false,或该方向无批次但订单已声明「不用接送机」
MISSING 待补接客信息 / 待补送客信息 该方向需要接送但信息不齐;无大交通且未声明也算这一档(fail-closed)
PARTIAL 接客/送客信息部分缺失 有多条批次,部分缺时刻或站点
READY 接客/送客信息已齐 全部批次时刻与站点齐全
SOURCE_UNAVAILABLE 订单信息暂不可用 订单上下文取数失败(降级态)

六.6、修改前后对比

字段级对比

字段 改前 改后
snapshot 出参 transferDeclaration 不存在 {pickupNotRequired:Boolean, dropoffNotRequired:Boolean},未声明为 false
snapshot 出参 transferTransportPresent 不存在 {pickup:Boolean, dropoff:Boolean}
submit 入参 updates.transferDeclaration 不存在 新增,两个方向可选
updates 其余字段 / 出参结构 - 未变

行为级对比

行为 改前 改后
团期子订单无大交通、只提交行程用车 200,接送机不留任何结论 587044,整笔零写入
团期子订单无大交通、确认两个方向不用后提交 无此路径 200,订单落两列声明
车务侧某方向(无大交通) 恒「待补接客信息 / 待补送客信息」 已声明 ⇒「无需接客 / 无需送客」;未声明仍「待补」
车务侧某方向(有大交通批次) 按批次 pickupRequired 判 不变
散客单 - 完全不变
809002 / 809009 接送机用车需求的服务日与开关校验 触发条件均未改(但无大交通且未表态时,会先撞 587044)

六.7、影响评估

  • 是否破坏向后兼容: 否(纯新增字段;但行为上对团期子订单是硬闸——老前端提交车辆子领域会撞 587044)
  • 前端是否必须同步上线: 是(团期子订单的「车辆安排」页必须按 transferTransportPresent 渲染必选确认并回传 transferDeclaration)
  • 前端 workaround 清理点: 无

六.8、已知约束(实测确认,非缺陷,但前端要知道)

  1. 声明的撤销只在「该方向已有大交通批次」时可达。该方向一条批次都没有时,把声明清回 false 会被 587044 拒绝(实测:两方向都清 false → 587044「不用接机」;只清送机、接机留 true → 587044「不用送机」)。要改回「需要接送机」,先录该方向的大交通——这也是接送机服务能落地的必经步骤。
  2. 团期子订单的用车需求提交后是「待审核」,不即时进车务池:需由团期管理员执行提交车务动作后才会出现在车务看板。这不是本版引入的行为,但会影响「提交完立刻看车务看板」的验收动作。
  3. 大交通批次新增接口有 3 秒幂等窗口,同一订单连续两次新增会返回 100502「大交通批次新增处理中」,与本次改动无关。

七、不影响范围

  • 仅影响: 管理后台「订单详情 → 调整订单 → 车辆安排」的团期子订单提交路径;以及车务侧接送机 readiness 的取值。
  • 零影响:
    • 散客单(无 productBatchId)的一切调整行为
    • 「调整订单」的其余 tab(出行人 / 改期 / 行程 / 房需求)
    • 接送机用车需求(TRANSFER)的服务日派生、提交开关、809002 / 809009 的触发条件
    • 大交通模块自身的读写接口
    • 行程单 / H5 / 小程序
    • 网关路由(无新增,均在既有 /v3/admin/** 通配下)
    • 历史数据(存量两列为 0,不迁移)

八、测试环境已验证

被测服务 hl-order-service-v3:dev-v3 @ bc197bc07,deploy-status.sh 回读 BEHIND 0/N、STATE ok。

被测服务部署:`hl-order-service-v3` = `dev-v3 @ bc197bc07`,`BEHIND 0/N`、`STATE ok`。
网关 `hl-gateway` 本次无路由改动;`hl-fleet-service` 现网版本虽落后若干提交,但落后的提交全是 order-v3 的、
未触及 fleet 代码,其接送就绪态判据与本次工作树逐字相同,故车务读口读数有效。

实测用**自建的团期子订单测试单**(客户备注带 `HLTEST[8320]` 标记,未碰任何真实订单):

```text
① 无大交通 + 不带 transferDeclaration 提交行程用车
   POST /v3/admin/order/{orderId}/adjustment/submit → HTTP 200,code=587044
   "本单未录入大交通,请先确认「不用接机」,或先在大交通模块录入行程后再提交"          ✓
   零写入核对(提交前后同一次 SQL 快照逐项相等):
     order_main 两列仍为 0 / order_vehicle_requirement 无新版本 /
     order_transport_plan 无变化 / order_adjustment_record 空 / order_status_log 计数不变 ✓

② 同一 payload + transferDeclaration={pickupNotRequired:true,dropoffNotRequired:true}
   POST .../adjustment/submit → {"code":200,"data":{"success":true}}                     ✓
   GET  .../adjustment/snapshot?scope=VEHICLE_REQ
     "transferDeclaration":{"pickupNotRequired":true,"dropoffNotRequired":true}
     "transferTransportPresent":{"pickup":false,"dropoff":false}                         ✓
   order_main: pickup_not_required=1 dropoff_not_required=1                              ✓
   order_adjustment_record: label="接送机不用声明已更新"
     before="接机:需要接送,送机:需要接送" after="接机:不用接送,送机:不用接送"        ✓

③ 给该方向补一条 ARRIVAL 大交通批次后再提交(仍带声明 true)
     snapshot "transferTransportPresent":{"pickup":true,"dropoff":false}                 ✓
     车务读侧 pickupSummary: required=true, statusCode=READY, "接客信息已齐"             ✓(声明不覆盖批次)

④ 车务读侧阳性 + 阴性对照(同一张单、同一时刻,两个方向都没有批次)
     pickupSummary  : 未声明(0)     → required=true,  statusCode=MISSING,       "待补接客信息" ✓
     dropoffSummary : 已声明不用(1) → required=false, statusCode=NOT_REQUIRED,  "无需送客"     ✓

⑤ 混方向:接机有批次、送机无批次,提交非空 transferRequirement 且不声明送机
   → code=587044 "…请先确认「不用送机」…",整笔零写入                                  ✓

⑥ 散客单(productBatchId=null)不带声明提交同一份用车需求
   → {"code":200,"data":{"success":true}},正常落库;snapshot transferDeclaration={false,false} ✓

(车务读侧入口:管理端 GET /admin/fleet/board/orders——fleet 管理端路径没有 /v3 前缀; /v3/internal/** 经网关直连返 403 属设计内,未作为证据。)


---

## 九、相关历史 PR

| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| #8024 | #7443 | 调整订单车辆安排页同页提交行程用车 + 接送机用车两类需求 | ✅ 有效,本版在其上加闸 |
| #7439 | #7439 | 用车需求分家(TRAVEL / TRANSFER),接送机服务日由大交通派生 | ✅ 有效 |
| #8153 | #8153 | 声明了接送机却无 TRANSFER 需求行的户在确认预检里报出 | ✅ 有效 |
| **本 PR #8326** | **#8320** | 补「不需要接送机」这一侧的显式声明 + 提交闸 | ✅ 最新 |

---

## 十、相关文档

- 关联 Issue: [wx/HL#8320](https://git.1814.love/wx/HL/issues/8320)
- 关联 PR: [wx/HL#8326](https://git.1814.love/wx/HL/pulls/8326)
- 团期车务实施单: `HL-v3/docs/group/实施单/06-团期车务.html`(接送机 readiness 口径本次补了一句)

## 关联 / 联系人

### 链接

- **Issue**: [#8320](https://git.1814.love/wx/HL/issues/8320)
- **PR**: [#8326](https://git.1814.love/wx/HL/pulls/8326)
- **Merge commit**: [bc197bc07](https://git.1814.love/wx/HL/commit/bc197bc07)

### 联系人

- **后端负责人**: @wx