文件
hl-api-changelog/changelogs-v2/2026-09/30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md
T
API Changelog Bot和Claude Opus 5 e1ae777695
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): 订正 #8576/#8601/#8577 三份交接件的编造内容与 frontend_status
三份都是既有条目(#8576/#8601 由 da562f3 批次产出,#8577 单独产出),本次逐条对源码与
测试服实测记录核对后订正,不新增条目。

#8576
- frontend_status 由 not_required 改回 pending(ca26be4 批量置位)。依据:hl-ui
  origin/v2.1 确有 src/api/fleet/group-dispatch.js 消费 reconfigure / confirm 两个端点,
  而全仓 specWarnings 命中数为 0 —— 新字段目前无人渲染,车辆规格提醒对车务不可见。
  改判原因已按 FRONTEND_CONSUMPTION_STATUS_GUIDE 要求写进 status_note。
- 两处错误响应示例的 message 是编造的,换成 GroupDispatchAdminErrorCode 的真实模板。
- planVersion 出参类型 Integer/Long 统一为 Long(两张表)。

#8601
- 删掉四处「589535 适用于本端点」的错误断言。实证:RequirementService.java:4749 对
  resourceType=VEHICLE 硬编码 hasActiveAssignments=false,该码在车需求打回上结构性不可达。
- 编造的订单 ID 2099459272533323777 换成实测的 2105173274755534850(dispatch)与
  2105173313083080706(reject)。
- 错误码集合订正为 809000 / 809007(仅 dispatch)/ 582031 / 582083。

#8577
- 订正一处「POST requirement/confirm 零影响」的错误断言:doConfirm 与 confirm-check 共用
  已收窄的 classifyVehicleSubmission,该端点的 809122 触发条件同步收窄。
- 809121 / 809123 的错误响应示例换成测试服实测原文。
- frontend_status 保留 not_required,但把判定依据写进 status_note:三个码一律走拦截器透
  message、生产代码无一处按报文匹配、前端也无纯接送机户的规避需要撤除;同时列出五处现已
  陈旧的前端注释与一处 mock 报文,供 mmg 顺手清理。

门禁:validate-changelog-frontmatter.mjs --files 三个文件一次通过(PASS: 3 files)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 15:17:17 +08:00

41 KiB
原始文件 Blame 文件历史

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 8577 只提交了接送机的户不再被判「未提交用车需求」,809121/809122/809123 触发条件收窄且文案改写 admin wx(GIT) 修改接口 deployed verified not_required PR #8600 已 squash 合并 dev-v3(5b7074e691),hl-order-service-v3 dev-v3 分支已滚测试服。三个端点的请求体、响应体字段与错误码号全部未变,变的是 809121/809122/809123 的触发条件(收窄)与消息文案(去掉「行程」二字)。【frontend_status 取 not_required 的依据,2026-09-30 对 hl-ui origin/v2.1 逐处查证】三个码的展示一律走拦截器透 message,生产代码无一处按报文字符串匹配;纯接送机户在前端也没有任何规避(按钮禁用/提示)需要撤除,故无强制前端动作。⚠️ 但有注释级陈旧需顺手清:src/api/orderV2GroupBatch.js:921,957,958 与 GroupVehicleRequirementEditModal.vue:377,874 仍写着「TRAVEL / 行程用车需求」,三个码现已按 TRAVEL ∪ TRANSFER 判「已提交」;GroupVehicleRequirementSection.spec.js:1000 的 mock 报文同样陈旧(该用例不断言文案,不会红)。 2026-09-30 dev-v3

hl-order-service-v3: 只提交了接送机的户不再被判「未提交用车需求」

存放目录: changelogs-v2/2026-09/ 服务: hl-order-service-v3 (端口 8086) PR: #8600 Issue: #8577 日期: 2026-09-30 影响范围: 团期需求管理 Tab 的三个端点(保存正式用车需求 / 自动汇总草稿 / 整体确认预检)里 809121、809122、809123 的触发条件与消息文案


⚠️ 关键变化

  • 🔴 判据从「有没有提交行程用车(TRAVEL)」收窄为「两类用车需求(TRAVEL / TRANSFER)是不是一条都没有」。改前:某户只提交了接送机需求,团级保存、自动汇总、确认预检都把它当成「一条都没交」,整团被 809123 / 809121 / 809122 卡住,而这户其实已经明确表达过「只要接送机、不要行程车」,运营没有任何干净出路(唯一逃生舱是整团 waive 免车,那会把真需要行程车的户一起免掉)。改后:这户算已提交,三处一律放行。
  • 三个错误码的码值没变、字段没变,变的是什么时候抛(收窄)与消息文案(三条都去掉了「行程」二字):
    • 809121 团期 {0} 有 {1} 户缺少可汇总的行程用车需求… → …缺少可汇总的用车需求…
    • 809122 该户尚未提交行程用车需求,请先让定制师提交后再整团提交车务 → 该户尚未提交用车需求,…
    • 809123 {0}有 {1} 户尚未提交行程用车需求,暂不能保存正式用车需求:{2} → …尚未提交用车需求,…
    • 🔴 前端凡是对这三条报文做过关键词匹配 / 字符串包含判断的地方必须改(行程用车需求 这个子串在三条里都没了)。正确做法是按 code 分支,不要匹配 message 文本。
  • 🔴 809109「逐日覆盖」一个字都没改,仍然只认 TRAVEL。这是刻意的:本次分离的是「户级提交判定」与「行程覆盖判定」两件事,合并会把墙从 809123 挪到 809109,症状一模一样只是换个码。所以——只提交接送机的户不再被判未提交,也不要求被任何乘车分组覆盖;它结构上就在团级乘车分组之外,走逐户派车。
  • GroupVehicleDraftAggregator 的缺失原因文案 未提交行程用车需求 → 未提交用车需求。它出现在 809121 报文的逐户清单里(「户标识:原因」,顿号分隔),前端若展示过这个字符串同样受影响。
  • 「豁免户」exemptHouseholds 的语义边界也随之明确:只提交了接送机的户既不进未提交名单、也不进豁免名单——豁免解释的是「没提交的户为什么不拦」,而它本来就提交过。

一、背景

一户在团期里的用车需求有两类活跃行,互不替代:

类别 含义 派车路径
TRAVEL 团期行程用车 汇总进团级乘车分组,整团逐日配车
TRANSFER 接送机 逐户派车,结构上不进团级乘车分组

改前的三处判定都只查 TRAVEL。于是「只要接送机、不要行程车」这种完全合法的在团户(与 #7972 (A) 对 809114 的定案同源)被读成「什么都没交」。团级保存直接 809123 整份拒绝、自动汇总 809121 整团出不来草稿、确认预检 809122 逐户挂红——运营改不动、催不动(该户定制师已经交过了)、也绕不过去。

本次把判定拆成两个集合(travelSubmittedOrderIds / anySubmittedOrderIds),单源仍只有一份,在 GroupVehicleRequirementService#classifyVehicleSubmission(原名 classifyTravelSubmission),保存、预检、自动汇总三处共用:

  • 户级「交了没有」 → 用 anySubmittedOrderIds(两类任一即算交了)→ 管 809121 / 809122 / 809123;
  • 行程逐日覆盖 → 仍用 travelSubmittedOrderIds(只认 TRAVEL)→ 管 809109。

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 保存团期正式用车需求(全量替换) PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 错误码触发条件收窄 + 文案改写 809123 不再对「只提交接送机」的户触发;报文去掉「行程」
2 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft 错误码触发条件收窄 + 文案改写 809121 同上;缺失原因文案同步改写
3 整体确认需求缺失预检 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check 缺失项触发条件收窄 + 文案改写 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED(809122)同上

三、接口详情

1. 保存团期正式用车需求(全量替换) PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement

VO: GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO

使用场景

团期需求管理 Tab 的「正式用车需求」编辑弹窗点保存时调用,整份全量替换(未出现在本次提交里的分组会被移出当前版本)。权限点 group-batch:demand:confirm。本次改动只让 809123 少抛一类情况、并改了它的报文,请求体与响应体一个字段都没动。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期 ID
version Body Integer ❌ 乐观锁 首次保存传 null,后续必须回传上次 GET / PUT 拿到的值;不一致抛 809102
remark Body String ❌ @Size(max=500) 整份需求备注
groups Body Array ✅ @NotNull(不是 @NotEmpty)、@Valid 全部乘车分组;空数组是合法提交(有需车户时由 809103 拦),整团免车请改走 waive 端点
groups[].groupId Body Long ❌ - 既有分组主键;新增分组传 null。带上它 = 声明「就是库里那一组」,此时 groupCode 不得变更(改名抛 809104)
groups[].groupCode Body String ✅ @NotBlank,@Size(max=32) 分组键,直接作为车费 alloc_group
groups[].vehicleType Body String ✅ @NotBlank,@Size(max=64) 车型大类编码,不是自由文本;取值权威见 GET /internal/fleet/vehicle-types/category-names,不在字典内抛 809119
groups[].serviceStartDate Body String(yyyy-MM-dd) ✅ @NotNull 本组服务开始日
groups[].serviceEndDate Body String(yyyy-MM-dd) ✅ @NotNull 本组服务结束日(须不早于开始日)
groups[].seats Body Integer ❌ @Min(1) 该组单车座位数;刻意非必填(存量分组没有该值),与 count 必须同填或同空(809118),且须在该车型可选档位内(809124)
groups[].count Body Integer ❌ @Min(1) 该组车辆数量;同上
groups[].specialTags Body Array<String> ❌ 值须在字典 vehicle_special_demand 内 特殊诉求标签编码数组;含字典外编码整份拒绝(809117)
groups[].remark Body String ❌ @Size(max=500) 该组备注
groups[].days Body Array ✅ @NotEmpty,@Valid 逐日用车人数与成员,不能用单值人数代替
groups[].days[].tripDate Body String(yyyy-MM-dd) ✅ @NotNull 团期行程日,须落在本组服务日范围内且不缺日(809105 / 809106)
groups[].days[].headcount Body Integer ✅ @NotNull,@Min(1) 该组该日乘车人数(不是户数);小于当日成员户数抛 809110
groups[].days[].memberOrderIds Body Array<Long> ✅ @NotEmpty 该组该日实际乘车的子订单集合,须全属本团在团户(809107),同一户同一日只能属一个分组(809108)

出参 Result<GroupVehicleRequirementRespVO>

字段 类型 说明
requirementId String 正式用车需求 ID(雪花,字符串)
groupBatchId String 团期 ID(雪花,字符串)
status String 需求状态
version Integer 乐观锁版本,下次保存必须回传
remark String 整份需求备注
confirmedBy String 确认人
confirmedAt String(datetime) 确认时间
planRefreshState String 配车刷新状态(只读投影)
planRefreshReplayCount Integer 配车刷新重投次数
blockedStage String 被卡住的阶段
planRefreshStalled Boolean 配车刷新是否已停滞
planRefreshStalledReason String 停滞原因
planRefreshTimeoutAt String(datetime) 刷新超时时刻
planRefreshReplayExhausted Boolean 重投次数是否已用尽
groups Array 乘车分组回显
groups[].groupId / groupCode / vehicleType / vehicleTypeName String 分组主键(字符串)、分组键、车型大类编码、车型中文名(按归一 key 取)
groups[].serviceStartDate / serviceEndDate String(yyyy-MM-dd) 本组服务日范围
groups[].seats / count / totalSeatCount / maxHeadcount / remainingPassengerSeats Integer 单车座位数 / 车辆数 / 总座位 / 最大日人数 / 剩余可载客座位
groups[].specialTags[] Array code + name(中文名后端下发,前端不自己映射)
groups[].remark String 该组备注
groups[].days[] Array tripDate / headcount / memberOrderIds(字符串数组) / memberOrderCount
exemptHouseholds Array 豁免户(在团需车、两类需求都没有活跃行、但定制师提交不了的户);🔴 只提交了接送机的户不在这里——它已提交
exemptHouseholds[].orderId String 子订单 ID(雪花,字符串)
exemptHouseholds[].teamNo String 团号
exemptHouseholds[].orderNo String 子订单号
exemptHouseholds[].reason String ORDER_NOT_CUSTOMIZING / REQUIREMENT_FROZEN
exemptHouseholds[].reasonName String 豁免原因中文名(后端下发,前端不自己映射)

请求示例

{
  "version": 3,
  "remark": "9/13 起换大巴",
  "groups": [
    {
      "groupId": null,
      "groupCode": "BUS",
      "vehicleType": "bus",
      "serviceStartDate": "2026-09-12",
      "serviceEndDate": "2026-09-16",
      "seats": 19,
      "count": 1,
      "specialTags": ["CHILD_SEAT"],
      "remark": "含高速费",
      "days": [
        {
          "tripDate": "2026-09-12",
          "headcount": 9,
          "memberOrderIds": [2099459272533323777, 2099459272533323778]
        }
      ]
    }
  ]
}

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "requirementId": "2099459272533400001",
    "groupBatchId": "2099459272533000001",
    "status": "DRAFT",
    "version": 4,
    "remark": "9/13 起换大巴",
    "confirmedBy": null,
    "confirmedAt": null,
    "planRefreshState": null,
    "planRefreshStalled": false,
    "groups": [
      {
        "groupId": "1867000000009",
        "groupCode": "BUS",
        "vehicleType": "bus",
        "vehicleTypeName": "大巴客车",
        "serviceStartDate": "2026-09-12",
        "serviceEndDate": "2026-09-16",
        "seats": 19,
        "count": 1,
        "totalSeatCount": 19,
        "maxHeadcount": 9,
        "remainingPassengerSeats": 9,
        "specialTags": [{ "code": "CHILD_SEAT", "name": "儿童座椅" }],
        "remark": "含高速费",
        "days": [
          {
            "tripDate": "2026-09-12",
            "headcount": 9,
            "memberOrderIds": ["2099459272533323777", "2099459272533323778"],
            "memberOrderCount": 2
          }
        ]
      }
    ],
    "exemptHouseholds": []
  }
}

空数据 / 降级响应

该团期只有接送机户、没有任何行程用车户时,提交零分组不再被 809123 拦(本次改动的直接效果);若团里确实还有需车户,零分组仍由 809103 拦下。exemptHouseholds 为空时是空数组不是 null:

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "requirementId": "2099459272533400001",
    "groupBatchId": "2099459272533000001",
    "status": "DRAFT",
    "version": 1,
    "groups": [],
    "exemptHouseholds": []
  }
}

错误响应

809123(触发条件已收窄、文案已改写;{0} 是团期人话标识,{2} 按团号列户、无团号回落订单号、都缺时为「某子订单」,顿号分隔;以下为测试服实测原文):

{
  "code": 809123,
  "message": "团期「第22期 8月喀纳斯湖秋色三日游」有 1 户尚未提交用车需求,暂不能保存正式用车需求:26-6538",
  "success": false,
  "data": null
}

其余错误码一条都没变:809100 团期尚未形成正式用车需求 / 809101 状态不允许 / 809102 已被他人修改(乐观锁) / 809103 有需车户却零分组 / 809104 分组重复或试图改名 / 809105 逐日行不在本组服务日范围内或重复 / 809106 缺逐日用车人数 / 809107 成员不属于本团期 / 809108 同一户同一日属多个分组 / 809109 该子订单的某日没有被任何乘车分组覆盖(仍只认 TRAVEL) / 809110 用车人数小于当日成员户数 / 809111 团期状态不允许编辑 / 809115 已声明整团免车需先 withdraw / 809116 座位不足 / 809117 特殊诉求标签不在字典内 / 809118 座位数与车辆数须同填或同空 / 809119 车型不在车型字典内 / 809120 车型字典暂不可用 / 809124 座位数不在该车型可选档位内。

业务边界

  • 鉴权:权限点 group-batch:demand:confirm(与整体确认、按户打回、受控重开同码——它们动的是同一个 Tab 里的同一份数据);未登录由网关拦截返 401。
  • 全量替换语义:未出现在本次提交里的分组会被移出当前版本,不是增量补丁。
  • 🔴 判据变化只在户级:「这户交了没有」看两类任一;「行程逐日覆盖」(809109)仍只看 TRAVEL,没变。
  • 只提交接送机的户:不再被 809123 拦、也不要求被任何乘车分组覆盖,且不出现在 exemptHouseholds 里。
  • 豁免户不阻断:ORDER_NOT_CUSTOMIZING / REQUIREMENT_FROZEN 两类户不进 809123、不参与 809109,但必须在页面上提示出来(后端已逐户带原因下发)。
  • 错误码文案是可变的:message 只用于展示,判定一律按 code。
  • 乐观锁只挡同一瞬间的并发写:挡不住「A 读了 v3 去改、B 也读了 v3 改完先提交」这种跨请求覆盖。
  • 雪花 ID 一律是字符串(requirementId / groupBatchId / memberOrderIds[] / exemptHouseholds[].orderId)。

2. 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft

VO: 无请求体 → GroupVehicleAggregateDraftRespVO

使用场景

编辑弹窗点「自动汇总」时调用,按各子订单的活跃 TRAVEL 需求汇总出一份团级草稿,只读零写入,返回的 draft 可原样 PUT 给上面那个保存端点。权限点与编辑弹窗取数口同码 group-batch:demand:confirm。本次改动只让 809121 少抛一类情况并改了它的报文与缺失原因文案。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期 ID

出参 Result<GroupVehicleAggregateDraftRespVO>

字段 类型 说明
groupBatchId String 团期 ID(雪花,字符串)
currentStatus String 当前正式需求状态
draft Object 汇总出的草稿,结构与保存端点的请求体逐字段相同,可原样 PUT
droppedFleetItems Array 多车型户被丢弃的车型项:orderId / teamNo / orderNo / vehicleType / seats / count / keptVehicleType / reason(VEHICLE_TYPE_NOT_IN_DICT 等)
staleHeadcountOrders Array 冻结人数与实时人数不一致的户:orderId / teamNo / orderNo / frozenHeadcount / liveHeadcount
paddedOrderDays Array 为覆盖出发~返回而补进分组的日期:orderId / teamNo / orderNo / dates[]
seatOptionAdjusted Array 座位档被兜底调整的组/户:groupCode / orderId / teamNo / orderNo / vehicleType / originalSeats / adoptedSeats / seatOptions[] / reason
violations Array 草稿已先跑过与保存同一份逐日校验的结果:code(对应 809xxx) / reason / detail / groupCode / tripDate / orderId / teamNo
exemptHouseholds Array 豁免户(结构同上一个端点);🔴 只提交了接送机的户不在这里,也不在草稿里

请求示例

GET /v3/admin/order/group-batch/2099459272533000001/vehicle-requirement/aggregate-draft

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "groupBatchId": "2099459272533000001",
    "currentStatus": "DRAFT",
    "draft": {
      "version": 3,
      "remark": null,
      "groups": [
        {
          "groupId": null,
          "groupCode": "BUS",
          "vehicleType": "bus",
          "serviceStartDate": "2026-09-12",
          "serviceEndDate": "2026-09-16",
          "seats": 19,
          "count": 1,
          "specialTags": [],
          "remark": null,
          "days": [
            {
              "tripDate": "2026-09-12",
              "headcount": 9,
              "memberOrderIds": [2099459272533323777]
            }
          ]
        }
      ]
    },
    "droppedFleetItems": [],
    "staleHeadcountOrders": [],
    "paddedOrderDays": [],
    "seatOptionAdjusted": [],
    "violations": [],
    "exemptHouseholds": []
  }
}

空数据 / 降级响应

团里只有接送机户、没有任何可汇总的行程用车户时,草稿分组为空数组而不再抛 809121(本次改动的直接效果):

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "groupBatchId": "2099459272533000001",
    "currentStatus": "DRAFT",
    "draft": { "version": null, "remark": null, "groups": [] },
    "droppedFleetItems": [],
    "staleHeadcountOrders": [],
    "paddedOrderDays": [],
    "seatOptionAdjusted": [],
    "violations": [],
    "exemptHouseholds": []
  }
}

车型字典取不到时不静默降级,抛 809120 让运营重试(避免把一整份草稿的车型全判成非法)。

错误响应

809121(触发条件已收窄、文案已改写;{0} 是团期名标识,{2} 是「户标识:原因」顿号分隔的清单,原因文案里的 未提交行程用车需求 已改为 未提交用车需求;以下为测试服实测原文):

{
  "code": 809121,
  "message": "团期 「第22期 8月喀纳斯湖秋色三日游」 有 1 户缺少可汇总的用车需求,暂不能自动汇总:26-6538:未提交用车需求",
  "success": false,
  "data": null
}

其余错误码未变:809120 车队车型字典暂不可用 / 809111 团期状态不允许 / 809100 团期尚未形成正式用车需求(视链路)。

业务边界

  • 鉴权:权限点 group-batch:demand:confirm;未登录由网关拦截返 401。
  • ⛔ 本端点零写入,可安全重复调用;draft 是「按现有子订单需求草稿长什么样」,不保证保存一定能过——预跑的校验结果在 violations。
  • 收窄后的 809121 判据:需车户「两类用车需求一条都没有」才算信息缺失;只提交接送机的户不算缺少,也不会出现在草稿里(团车草稿只汇总 TRAVEL,它本就没有位置)。
  • 缺失原因文案已改:未提交行程用车需求 → 未提交用车需求(另有 车型均不在车型字典内、服务日推不出、人数为 0 三类未变)。
  • 诊断字段必须展示:droppedFleetItems / staleHeadcountOrders / paddedOrderDays / seatOptionAdjusted 都是「草稿与用户预期可能不一致」的位置,静默吞掉会让运营看到一份自己没想要的草稿。
  • 雪花 ID 一律是字符串。

3. 整体确认需求缺失预检 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check

VO: 无请求体 → GroupBatchRequirementCheckRespVO

使用场景

「查看需求」Tab 进入时与点「确认」前调用,据 ready 置灰确认按钮、据 missing / vehicleMissing 展示缺哪几户。只读无副作用。权限点 group-batch:demand:confirm。本次改动只让缺失项 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED(809122)少产出一类情况并改了它的报文。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期 ID

出参 Result<GroupBatchRequirementCheckRespVO>

字段 类型 说明
groupBatchId String 团期 ID(雪花,字符串)
batchStatus / batchStatusName String 团期状态编码与中文名
ready Boolean 是否可以整体确认(置灰按钮用)
missing Array 房侧缺失户:orderId / teamNo / orderNo / customerName / consultantId / consultantName / reason / reasonName / dayNumber / segmentIndex / expectedNights / actualNights
checkedResourceTypes Array<String> 恒为 ["HOTEL","VEHICLE"];文案已更新为「车侧逐户查用车需求行是否提交(#8577 起行程用车与接送机任一有即算已提交)」
vehicleWaived Boolean 是否已声明整团免车
vehicleMissing Array 车侧缺失项,见下
vehicleMissing[].reason String GROUP_REQUIREMENT_NOT_FOUND / GROUP_REQUIREMENT_STATUS_INVALID / NO_GROUP / GROUP_CODE_INVALID / DAY_OUT_OF_GROUP_RANGE / DAY_GAP_IN_GROUP_RANGE / MEMBER_FOREIGN_ORDER / MEMBER_DUPLICATE_DAY / ORDER_DAY_UNCOVERED / HEADCOUNT_LESS_THAN_MEMBERS / HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED / MEMBER_GROUP_MISMATCH / TRANSFER_SERVICE_DATES_NOT_BACKFILLED / TRANSFER_WINDOW_INCOMPLETE
vehicleMissing[].groupCode String 涉及的乘车分组编码;无分组维度时 null
vehicleMissing[].tripDate String(yyyy-MM-dd) 涉及的日期;无日期维度时 null
vehicleMissing[].orderId String 涉及的子订单 ID(雪花,字符串);无订单维度时 null
vehicleMissing[].teamNo / orderNo String 团号 / 子订单号快照
vehicleMissing[].detail String 人话描述,与整团确认时抛出的错误报文逐字相同,可直接展示
vehicleExemptHouseholds Array 车侧豁免户(结构同前两个端点);🔴 只提交了接送机的户不在这里
groupVehicleRequirementId String 团级正式用车需求 ID(雪花,字符串)
groupVehicleRequirementStatus String 团级正式用车需求状态
groupVehicleRequirementVersion Integer 团级正式用车需求版本
transferSubmitEnabled Boolean 接送机提交灰度开关当前状态
transferDeclaredWithoutRequirement Array 声明了接送机却没有活跃 TRANSFER 行的户:orderId / teamNo / orderNo / customerName / consultantId / consultantName / pickupRequired / dropoffRequired / pickupRemark

请求示例

GET /v3/admin/order/group-batch/2099459272533000001/requirement/confirm-check

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "groupBatchId": "2099459272533000001",
    "batchStatus": "RESOURCE_PREPARING",
    "batchStatusName": "资源准备中",
    "ready": false,
    "missing": [],
    "checkedResourceTypes": ["HOTEL", "VEHICLE"],
    "vehicleWaived": false,
    "vehicleMissing": [
      {
        "reason": "HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED",
        "groupCode": null,
        "tripDate": null,
        "orderId": "2099459272533323779",
        "teamNo": "26-0482",
        "orderNo": "HL2606010003",
        "detail": "该户尚未提交用车需求,请先让定制师提交后再整团提交车务"
      }
    ],
    "vehicleExemptHouseholds": [],
    "groupVehicleRequirementId": "2099459272533400001",
    "groupVehicleRequirementStatus": "DRAFT",
    "groupVehicleRequirementVersion": 4,
    "transferSubmitEnabled": true,
    "transferDeclaredWithoutRequirement": []
  }
}

空数据 / 降级响应

全部就绪时 ready=true,三个清单都是空数组不是 null:

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "groupBatchId": "2099459272533000001",
    "batchStatus": "RESOURCE_PREPARING",
    "batchStatusName": "资源准备中",
    "ready": true,
    "missing": [],
    "checkedResourceTypes": ["HOTEL", "VEHICLE"],
    "vehicleWaived": false,
    "vehicleMissing": [],
    "vehicleExemptHouseholds": [],
    "transferDeclaredWithoutRequirement": []
  }
}

错误响应

本端点是只读预检,把缺失列成清单而不是抛码;仍可能出现的错误只有权限与团期不存在两类:

{
  "code": 403,
  "message": "无权限执行该操作",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:权限点 group-batch:demand:confirm;未登录由网关拦截返 401。
  • ⛔ 只读无副作用,可随页面进入反复调用。
  • 它是缺失明细的唯一来源:整团确认失败时抛出的 589533 只带汇总户数,逐户明细只能从本端点取。
  • 收窄后的 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED 判据:在团需车户两类用车需求都没提交才产出;只提交接送机的户不产出,也不进 vehicleExemptHouseholds。
  • detail 与错误报文逐字相同:所以它也跟着改了文案(行程用车需求 → 用车需求),前端不要做子串匹配。
  • ORDER_DAY_UNCOVERED(809109)仍只认 TRAVEL:只提交接送机的户不会因为「没被任何乘车分组覆盖」出现在这里。
  • transferDeclaredWithoutRequirement 里两个 flag 都为 false 是合法组合:该户的声明落在 direction 为空或不在 ARRIVAL/DEPARTURE 两值内的批次上,仍确实声明了接送机,前端照常展示、不要过滤掉。
  • 雪花 ID 一律是字符串。

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

本节只写后端接受 / 拒绝 payload 的规则,不写 UI 渲染建议。

✅ 正确 / ❌ 错误 payload 对照(保存端点)

场景 payload
✅ 首次保存(无版本) { "version": null, "groups": [ { "groupCode": "BUS", "vehicleType": "bus", "serviceStartDate": "2026-09-12", "serviceEndDate": "2026-09-16", "days": [ { "tripDate": "2026-09-12", "headcount": 9, "memberOrderIds": [2099459272533323777] } ] } ] }
✅ 改既有组(带 groupId,groupCode 不变) { "version": 3, "groups": [ { "groupId": 1867000000009, "groupCode": "BUS", ... } ] }
✅ 座位与车辆数同空(存量分组) { ..., "seats": null, "count": null }
✅ 团里只有接送机户 → 提交零分组 { "version": null, "groups": [] } → 200(改前该团常因某户「只交了接送机」撞 809123)
❌ groups 传 null { "version": 3, "groups": null } → 400「乘车分组列表不能为 null(整团免车请改用 waive 端点)」
❌ 带 groupId 却改了 groupCode { "groupId": 1867000000009, "groupCode": "BUS2", ... } → 809104
❌ 只填 seats 不填 count { "seats": 19, "count": null } → 809118
❌ 车型填自由文本 { "vehicleType": "35座大巴" } → 809119

前端必须做的一处改动

  • 🔴 凡是对 809121 / 809122 / 809123 的 message(或预检 vehicleMissing[].detail)做过字符串包含判断的地方,一律改成按 code / reason 分支。三条报文里的 行程用车需求 已改为 用车需求,旧的子串匹配会静默失配(不报错,只是那条分支再也不进)。
  • 其余全部字段、校验规则、请求格式不变,不需要任何别的适配。

五、数据库行为

只有保存端点(PUT)是写端点,本次改动没有任何表结构或写入语义变化——变的是写之前那道户级阻断的判据。

场景 改前 改后
某户只有活跃 TRANSFER 行,团级 PUT 提交 809123 整份拒绝,零写入 正常落库(该户不需要被任何分组覆盖)
某户两类都没有活跃行且提交得了 809123 整份拒绝,零写入 未变,仍 809123 零写入
某户两类都没有活跃行但提交不了(豁免户) 不阻断,列入 exemptHouseholds 未变
正常提交 全量替换:本次未出现的分组移出当前版本、版本号 +1 未变

失败零写入:809123 抛在乐观锁比对与分组改名守卫之后、任何写入之前,整份拒绝不留半份数据。

自动汇总(GET)与确认预检(GET)两个端点零写入,本次未改变这一点。


六、边界行为

  • 未登录 → 401(网关拦截)。
  • 权限点 group-batch:demand:confirm 缺失 → 403。
  • 团期尚未形成正式用车需求 → 809100(报文用「该团期」,不带雪花 id)。
  • 正式需求已被他人修改 → 809102,带提交版本与当前版本。
  • 团期已过配置阶段 → 809111。
  • 已声明整团免车又提交分组 → 809115(需先 withdraw 回草稿)。
  • 车队车型字典不可用 → 809120(不静默降级,让运营重试)。
  • 老数据兼容:存量分组没有 seats / count,编辑时原样回传 null 不会 400;库里被 V20260924_402 归一过的车型可正常回显,归一认不出的历史自由文本原样保留,但再提交一次仍会被 809119 拒——编辑态请把字典外的当前值显式标出提示重选,不要渲染成空。
  • 推不出服务日的户(departDate / returnDate 任一为空)跳过 809109 覆盖判定(已知盲区,不是遗漏)。

六.5、枚举 / 数据字典

reason(车侧缺失项原因码)

所属字段: GroupBatchRequirementCheckRespVO.vehicleMissing[].reason | 类型: String

值 中文 说明
GROUP_REQUIREMENT_NOT_FOUND 团级正式需求未形成 对应 809100
GROUP_REQUIREMENT_STATUS_INVALID 团级正式需求状态不允许 对应 809101
NO_GROUP 有需车户却零分组 对应 809103
GROUP_CODE_INVALID 分组编码重复或改名 对应 809104
DAY_OUT_OF_GROUP_RANGE 逐日行不在本组服务日范围内 对应 809105
DAY_GAP_IN_GROUP_RANGE 本组服务日范围内缺日 对应 809106
MEMBER_FOREIGN_ORDER 成员不属于本团期 对应 809107
MEMBER_DUPLICATE_DAY 同一户同一日属多个分组 对应 809108
ORDER_DAY_UNCOVERED 该户某日未被任何分组覆盖 对应 809109;🔴 仍只认 TRAVEL,本次未改
HEADCOUNT_LESS_THAN_MEMBERS 用车人数小于当日成员户数 对应 809110
HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED 该户尚未提交用车需求 对应 809122;🔴 #8577 收窄:行程用车与接送机任一有即不报。只带 orderId / orderNo,处置是催该户定制师提交
MEMBER_GROUP_MISMATCH 该户车型与覆盖它的分组车型不符 对应 809125;排在户级未提交之后(交都没交的户没有车型可比)
TRANSFER_SERVICE_DATES_NOT_BACKFILLED 待放行的接送机需求未回填服务日 对应 809007,只带 orderId
TRANSFER_WINDOW_INCOMPLETE 接送机需求窗没盖住大交通派生日期 对应 809126,带 orderId / orderNo 与首个越窗日期

reason(团级用车需求豁免户原因码)

所属字段: exemptHouseholds[].reason、vehicleExemptHouseholds[].reason | 类型: String

值 中文 说明
ORDER_NOT_CUSTOMIZING 订单不在定制中 定制师提交会被 582017 拒,所以该户不算「没交」
REQUIREMENT_FROZEN 团期已过资源准备、需求已冻结且该户未被打回 定制师提交会被 589536 拒

🔴 只提交了接送机的户不属于任何一档——它已提交,既不进未提交名单也不进豁免名单。

用车需求类别(判定用,不直接出现在本次三个响应的字段里)

值 中文 在本次判定中的角色
TRAVEL 团期行程用车 汇总进团级乘车分组;809109 逐日覆盖只认它
TRANSFER 接送机 走逐户派车、结构上在团级乘车分组之外;#8577 起它也算「已提交用车需求」,参与 809121 / 809122 / 809123 的判定

六.6、修改前后对比

字段级对比

字段 改前 改后
三个端点的全部请求字段 — 未变(一个都没动)
三个端点的全部响应字段 — 未变(无新增、无删除、无改名、无类型变化)
checkedResourceTypes 的字段说明文案 「车侧逐户查行程用车需求行是否提交」 「车侧逐户查用车需求行是否提交(#8577 起行程用车与接送机任一有即算已提交)」
vehicleMissing[].reason 的取值集合 14 个 未变(仍 14 个,只是其中一个的触发条件收窄)
exemptHouseholds 的成员判据 在团需车 ∧ 无 active TRAVEL ∧ 提交不了 在团需车 ∧ 两类都无 active 行 ∧ 提交不了

行为级对比

行为 改前 改后
某户只提交了接送机,团级 PUT 保存 809123 整份拒绝,运营无干净出路 正常保存
某户只提交了接送机,点自动汇总 809121 整团出不来草稿 正常出草稿(该户不进草稿,也不进缺失清单)
某户只提交了接送机,进确认预检 该户挂 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED,ready=false 不产出该缺失项
某户只提交了接送机,点整体确认(POST .../requirement/confirm,真实派车/写库) 809122 抛出,确认失败、零写入 正常确认,该户随团一起派车(前提其余条件都满足)
某户只提交了接送机,是否要求被乘车分组覆盖 会走到 809109 不要求(它结构上在团级分组之外)
809121 报文 团期 {0} 有 {1} 户缺少可汇总的**行程**用车需求… …缺少可汇总的用车需求…
809122 报文 该户尚未提交**行程**用车需求,… 该户尚未提交用车需求,…
809123 报文 {0}有 {1} 户尚未提交**行程**用车需求,… {0}有 {1} 户尚未提交用车需求,…
汇总缺失原因文案 未提交行程用车需求 未提交用车需求
809109 逐日覆盖的判据 只认 TRAVEL 未变,仍只认 TRAVEL
两类都没提交的户 三处照旧阻断 未变

六.7、影响评估

  • 是否破坏向后兼容: 否(无字段增删改;只是三个错误码少抛一类情况、报文文案改写)
  • 前端是否必须同步上线: 否;但若前端对这三条报文做过字符串包含判断,必须改(改成按 code / reason 分支),否则那条分支会静默失配
  • 前端 workaround 清理点: 若为绕开「纯接送机户卡住整团」在页面上加过提示、屏蔽过确认按钮、或引导过运营去整团免车,可以撤掉

七、不影响范围

  • 仅影响: 团期需求管理 Tab 的三个端点里 809121 / 809122 / 809123 的触发条件与报文文案。
  • ⚠️ POST .../requirement/confirm(整体确认,真实派车/写库)不是零影响:它的响应字段结构、派车/写库机制本身未改一行代码,但它内部同样经 GroupBatchRequirementService.doConfirm(GroupBatchRequirementService.java:562)调 loadVehicleSnapshot,后者第 1240-1241 行直接调用本次收窄后的 classifyVehicleSubmission 来判 809122——即它对 809122 的实际触发条件与 confirm-check 端点同步收窄:纯接送机户此前会在这里被 809122 拦下、零写入(回归用例 GroupBatchRequirementServiceConfirmVehicleTest#doConfirm_householdWithoutTravelRequirement_throws809122 钉住的正是「两类都没提交」仍会拦的边界),现在能正常通过、随团一起派车,见六.6 行为级对比。前端若曾据「纯接送机户点确认必失败」写过分支或禁用逻辑,需按该行为级对比同步调整。
  • 零影响:
    • 809109 逐日覆盖判定(仍只认 TRAVEL)
    • POST .../requirement/confirm 的响应字段结构(GroupBatchRequirementConfirmRespVO 字段清单未变)与派车 / 写库机制(doConfirm 方法体本身未改)
    • 受控重开、整份撤回、整团免车、按户打回四个端点
    • 接送机批量确认 POST .../requirement/transfer/batch-confirm
    • 户级用车需求的提交 / 编辑 / 打回链路
    • 车务侧(hl-fleet-service)的配车、派单、就绪判定
    • 历史数据:不做任何迁移,存量团期下次调用时按新判据生效

八、测试环境已验证

  • 代码事实(对 origin/dev-v3 逐一查证):
    • 合并提交 5b7074e691(PR #8600 squash 合并进 dev-v3),17 文件 / +559 −137。
    • GroupVehicleRequirementErrorCode 三条 IErrorCode.of 的字面量 diff 已逐字核对(809121 / 809122 / 809123 各去掉「行程」二字),码值与常量名未变。
    • GroupVehicleDraftAggregator.MISSING_NOT_SUBMITTED 由 未提交行程用车需求 改为 未提交用车需求;Household record 新增 boolean transferSubmitted 位,判缺失处改为 household.needsVehicle() && !household.transferSubmitted()。
    • classifyTravelSubmission 更名为 classifyVehicleSubmission,VehicleSubmission 内 travelSubmittedOrderIds 与 anySubmittedOrderIds 是两个分开的字段——809109 用前者、三码用后者,合并会把墙挪到 809109。
    • 三个端点的 Controller 签名、@RequestBody VO、响应 VO 字段清单逐一核对,确认零字段变化。
    • 回归钉在 GroupVehicleRequirementValidateTest#save_frozenRejectedButTransferSubmitted_noLongerThrows809123 等用例上(本 PR 新增 / 改写测试 6 个文件、+400 余行)。
  • 部署:hl-order-service-v3 的 dev-v3 分支已滚到测试服,三个端点走管理端网关 /v3/admin/order/** 既有路由,无新增路由。
PUT  /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement                → 200 ✓(纯接送机户不再触发 809123)
GET  /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft → 200 ✓(纯接送机户不再触发 809121)
GET  /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check           → 200 ✓(不再产出 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED)

九、相关历史 PR

PR Issue 说明 是否仍有效
— #7441 团期正式用车需求首次落地(809100-809115 段) ✅ 有效
— #8219 有户未提交时阻断团级 PUT,新开 809123 与豁免户机制 ✅ 有效(本单在其基础上收窄判据)
— #8220 自动汇总草稿端点与 809121 ✅ 有效
— #8249 预检加户级 809122 ✅ 有效
— #8306 报文按团号列户、不出现雪花 id ✅ 有效
本 PR #8600 #8577 户级提交判定与行程覆盖判定分离,三码收窄 + 文案改写 ✅ 最新

十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @wx