文件
hl-api-changelog/changelogs-v2/2026-09/30_8544_团期用车需求读口补中文名汇总草稿改读侧VO并列下发团级已确认座位-修改接口-管理后台.md
T
2026-10-01 00:31:15 +08:00

45 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 8544 团期正式用车需求读口补状态中文名、汇总草稿改用读侧 VO、需求汇总并列下发团级已确认座位、子订单列表逐类下发用车需求行(#8544 #8545 #8619) admin wx(GIT) 修改接口 deployed not_required implemented hl-admin(claude-opus-4-8) bc9c13aaa39bab96e132a3a42515c821fa74a269 v2.1 2026-10-01 PR #8625(覆盖 #8544 #8545)与 PR #8624(覆盖 #8619)均已 squash 合并 dev-v3(5f2f86bc9 / ce7cd238e9),hl-order-service-v3 dev-v3 分支已滚测试服(HEAD 5f2f86bc9,jar mtime 2026-09-30 08:19:48,两实例 Nacos 健康)。四个 GET 端点响应体的新增/变更字段均实测通过;PUT / withdraw / waive 三个写端点与 GET 共用同一个 GroupVehicleRequirementRespVO 类(源码级共用,非各自派生),因此 #8544 新增的 4 个 *Name 字段在这三个端点的响应里同样存在,字段语义与本文档「三、接口详情」第 2 条完全一致。;前端已交付:正式用车需求三处(Section/汇总卡/状态条)删本地 code→中文 map 改读后端 statusName/planRefreshStalledReasonName(原码仅防空白兜底),aggregate-draft 灌表单用 vehicleType key 零改动,62 例定向测试全绿(hl-admin 8926ebff);续:#8545 座位两口径并列行(已报 vs 已定,集合外车型 totalSeats=0 行显逐户未报)与 #8619 逐类清单段也已交付(hl-admin 850abddc/bc9c13aa,frontend_ref 更新为最终哈希) 2026-09-30 dev-v3

hl-order-service-v3: 团期正式用车需求读口补状态中文名、汇总草稿改用读侧 VO、需求汇总并列下发团级已确认座位、子订单列表逐类下发用车需求行

存放目录: changelogs-v2/2026-09/ 服务: hl-order-service-v3 (端口 8086) PR: #8625(#8544 #8545)、#8624(#8619) Issue: #8544、#8545、#8619 日期: 2026-09-30 影响范围: 团期需求管理 Tab 下 4 个 GET 端点(全团需求汇总 / 读正式用车需求 / 自动汇总草稿 / 子订单列表)的响应体字段;PUT / withdraw / waive 三个写端点共用的响应体同步补齐字段(契约见「四」,未单开详情块)


⚠️ 关键变化

  • #8544:GroupVehicleRequirementRespVO(PUT / GET / withdraw / waive 四端点共用的响应体)新增 4 个只读中文名字段——statusName / planRefreshStateName / blockedStageName / planRefreshStalledReasonName,分别配对既有的 status / planRefreshState / blockedStage / planRefreshStalledReason。认不出的编码一律给 null,不回落成编码原文(与本 VO 既有的 SpecialTagItem.name / GroupItem.vehicleTypeName 同口径)。
  • 🔴 这条 null 规则与看板列表的团期状态中文名口径是两套、刻意不同:GroupBatchConverter#resolveBatchStatusName(团期列表页用)未知码会回落原编码;本次这 4 个字段所在的正式用车需求读口未知码给 null。前端不要把两处的「未知码兜底逻辑」当成同一套抄。
  • #8544:GET .../vehicle-requirement/aggregate-draft 的 draft 字段类型从写侧 GroupVehicleRequirementSaveReqVO 改为新的只读读侧类型 GroupVehicleRequirementDraftRespVO。JSON 形状基本不变(由 GroupVehicleRequirementDraftRespVOFieldParityTest 钉住与写侧字段集一致),唯一新增字段是 groups[].vehicleTypeName(只读车型中文名,不参与保存,原样 PUT 回去时被忽略);该 VO 不挂任何校验注解(@NotNull/@NotEmpty/@Size 等),因为草稿可能违反若干条保存态约束,违规项另在 violations 里列出。
  • #8544:draft.groups[].remark(分组备注拼接文案)的逐户标签优先级改为 团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户",不再回落雪花订单 ID(原来兜底到裸数字 orderId 的情况,现在只有当团号/客户名/订单号三者都拿不到时才会出现,落成字面文案"未知户")。前端如果对这段 remark 文本做过正则匹配、高亮、或按"看起来像一串数字"识别订单号,需要同步更新——这段文本此后不会再出现裸数字订单 ID。
  • #8545:GET .../requirement-summary 的 vehicleSeatSummary[] 新增 confirmedSeats / confirmedCount(均为 int,尚未整团确认时恒为 0,不是 null)。这是"团级已确认"口径,与既有的 totalSeats / totalCount("逐户已提交"口径)并列下发、允许不相等——两者不等是常态不是缺陷,任何断言两者相等的前端逻辑都是错的。
  • 🔴 #8545:vehicleSeatSummary[] 可能新增一行只有 confirmedSeats/confirmedCount 非零、totalSeats/totalCount 恒为 0 的车型条目——发生在车务把车型整团定成了逐户报的车型集合之外的某个车型时(例如逐户都报 mpv,车务整团定成 bus)。前端渲染这张表时不能假设"有座位数就等于有逐户报",要按 4 个数字各自判断。
  • #8619:GET .../orders 的 vehicleRequirementStatus / vehicleRequirementKind 口径从"只看行程用车(TRAVEL)"放宽为"该户展示序首条活跃用车需求行"(行程用车优先、其次接送机)。🔴 改前只提交了接送机需求的户,这两列恒为 null、vehicleRequirementStatusName 被渲染成"未提交"——这是一个错误的展示(该户其实已提交、可能已在审/已派车/已完成),本次已修复。有行程用车需求行的户这两列读数不变。
  • #8619:vehicleRequirementKind 的实际取值域从恒为 TRAVEL 放宽为 {TRAVEL, TRANSFER}——VehicleRequirementKind 枚举本身没有新增第三个值,仍然只有这两个;变的是这一个响应字段过去被上游按 TRAVEL 过滤取行、现在按展示序取行,所以能观测到 TRANSFER。前端如果曾按"这一列永远是 TRAVEL"写死过图标/文案分支,需要按实际值渲染。
  • #8619:orders 新增 vehicleRequirements[](该户全部活跃用车需求行,恒非 null,0~2 条,行程用车在前、接送机在后)。一户同时提交了行程用车与接送机时,两条需求行状态可能各自不同(例如 TRAVEL 已 DONE、TRANSFER 还在 PENDING_REVIEW),此时上面那组单值字段(vehicleRequirementStatus/vehicleRequirementKind)只能表达其中一条——按类别判断状态必须读这个数组,不要只读单值字段。
  • orders 端点其余 25 个既有字段(hotelRequirementStatus、totalPrice、travelers 等)本次未变,已由 changelog 30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md 完整记录,此处不重复。

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 全团需求汇总 GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary 修改 vehicleSeatSummary[] 新增 confirmedSeats/confirmedCount(#8545)
2 读团期正式用车需求 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 修改 响应体新增 4 个 *Name 字段(#8544),PUT/withdraw/waive 共用同一响应体同步生效
3 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft 修改 draft 字段改用只读读侧 VO,新增 groups[].vehicleTypeName(#8544)
4 团期下子订单列表 GET /v3/admin/order/group-batch/{groupBatchId}/orders 修改 vehicleRequirementKind 取值域放宽、新增 vehicleRequirements[](#8619)

三、接口详情

1. 全团需求汇总 GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary

VO: GroupRequirementSummaryRespVO

使用场景

团期需求管理 Tab 打开时拉取的"全团需求汇总"卡片,展示逐日房间合计与大巴座位合计。本次变更只影响座位合计部分——vehicleSeatSummary[] 新增团级已确认口径的两个字段,供页面并列展示"定制师报了多少座"与"车务最后定了多少座"。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long 是 - 团期聚合主键

出参字段表

字段 类型 说明
activeOrderCount int 在团子订单数(仅排除 CANCELLED,含 COMPLETED),未变
hotelRequirementCount int 有 active 用房需求记录的子订单数(兼容保留字段),未变
hotelNeededOrderCount int 需要订房的户数,未变
hotelFlagMismatchOrderCount int needsHotel≠true 却已提交有效用房需求的户数,未变
hotelSubmittedOrderCount int 已提交有效用房需求且计入 dailyRoomBreakdown 的户数,未变
vehicleRequirementCount int 已提交用车需求的子订单数,未变
dailyRoomBreakdown array 逐日房间汇总,本次未变
vehicleSeatSummary array 大巴座位汇总(按车型),本次新增字段见下表
orderSpecialTags array 各子订单 specialTags,本次未变
transferSummary object 接送机汇总(#8151 既有字段),本次未变

vehicleSeatSummary[] 单项字段:

字段 类型 说明
vehicleType string 车型大类编码(suv/mpv/bus/sedan;缺失时为"未知"),未变
vehicleTypeName string 车型大类中文名;查不到或车队服务不可用时为 null,未变
totalSeats int 合计座位数(seats × count 之和);统计基数为"逐户已提交"的行程用车需求,未变
totalCount int 合计车辆台数;统计基数为"逐户已提交"的行程用车需求,未变
confirmedSeats 🆕 int 合计座位数(团级已确认的正式需求口径);尚未整团确认时为 0,与 totalSeats 不等是常态(#8545)
confirmedCount 🆕 int 合计车辆台数(团级已确认的正式需求口径);尚未整团确认时为 0(#8545)

请求示例

GET /v3/admin/order/group-batch/2104839654727618562/requirement-summary

响应示例

以下为测试环境实测响应(团期批次 2104840641651556353,2026-09-30 采集;仅展示 vehicleSeatSummary[0],其余字段结构未变不重复列出):

{
  "code": 200,
  "message": "成功",
  "data": {
    "vehicleSeatSummary": [
      {
        "vehicleType": "mpv",
        "vehicleTypeName": "商务车",
        "totalSeats": 14,
        "totalCount": 2,
        "confirmedSeats": 7,
        "confirmedCount": 1
      }
    ]
  },
  "traceId": null,
  "success": true
}

空数据 / 降级响应

  • 团期批次不存在:返回业务错误(见"错误响应"),data 为 null。
  • 全团无任何用车需求:vehicleSeatSummary 为空数组,不是 null。
  • 团级从未整团确认过(或已被重开成 PENDING_RECONFIRM):既有行的 confirmedSeats/confirmedCount 均为 0,不追加新行;此时该数组与改动前逐户口径的行完全一致。
  • 车务整团定的车型不在逐户已提交的车型集合内:追加一行 totalSeats=0/totalCount=0、confirmedSeats/confirmedCount 非零的记录,vehicleTypeName 仍会被正常补全。

错误响应

{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}

业务边界

  • confirmedSeats/confirmedCount 与 totalSeats/totalCount 是两个独立基数,前端不得假设两者相等或用其中一个推算另一个。
  • 权限码为 group-batch:view。

2. 读团期正式用车需求 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement

VO: GroupVehicleRequirementRespVO

使用场景

团期正式用车需求编辑页/详情页打开时的回填读口,同时是"配车计划刷新是否死了"在 admin 侧唯一无副作用的观测口(#7988)。PUT(保存)、withdraw(撤回)、waive(免车)三个写端点返回的响应体与本端点完全一致(同一个 Java 类),前端可以对四个端点复用同一套渲染逻辑。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long 是 - 团期聚合主键

出参字段表

字段 类型 说明
requirementId string(Long 转字符串) 正式需求主键,未变
groupBatchId string(Long 转字符串) 团期聚合主键,未变
status string 状态:DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM/CANCELLED,未变
statusName 🆕 string 状态中文名(草稿/已确认/已发车务/配车完成/待重新确认/已取消);status 为 null 或认不出的编码时为 null,不回落编码原文(#8544)
version int 乐观锁版本号,未变
remark string 整份备注;撤回/免车会把操作追加进来(不覆盖原备注),超 500 字从头部截并以 … 开头,未变
confirmedBy string 整份确认人,DRAFT 时为 null,未变
confirmedAt string(LocalDateTime) 整份确认时间,DRAFT 时为 null,未变
planRefreshState string 配车刷新状态原值:null=从未登记过刷新(多数团期正常态)/PENDING/DONE/FAILED,未变
planRefreshStateName 🆕 string 配车刷新状态中文名(刷新中/刷新完成/刷新失败);为 null 或认不出的编码时为 null(#8544)
planRefreshReplayCount int 人工受控重投累计次数(管理员点确认触发,不含自动重试),上限 5,未变
blockedStage string 团期阻断阶段快照:null=未阻断;非空为受控重开发生那一刻的团期状态码(GroupBatchStatus 全域,常见 RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE),未变
blockedStageName 🆕 string 阻断阶段中文名(如 资源准备中/物料准备中/待出发);为 null 或认不出的编码时为 null(#8544)
planRefreshStalled boolean 刷新是否已停滞、不会自愈(恒非 null);true=必须有人处置,未变
planRefreshStalledReason string 停滞归因:STATE_FAILED/COMMAND_FAILED/TIMEOUT;未停滞时为 null,未变
planRefreshStalledReasonName 🆕 string 停滞归因中文名(刷新已被判死/刷新命令已失败/刷新超时);未停滞或认不出编码时为 null(#8544)
planRefreshTimeoutAt string(LocalDateTime) 本轮刷新超时时刻;仅 PENDING 且已登记发起时刻时有值,未变
planRefreshReplayExhausted boolean 人工重投额度是否已耗尽(恒非 null),未变
groups array 全部乘车分组(整团免车态为空数组),结构未变,见下表
exemptHouseholds array 仅保存草稿(PUT)响应填充,其余端点(含本 GET)恒为 null,未变

groups[] 单项字段(未变,随 VO 一起下发本次新增的 4 个 *Name 兄弟字段):

字段 类型 说明
groupId string(Long) 分组主键
groupCode string 分组键,直接作为车费 alloc_group
vehicleType string 车型文本/字典值
vehicleTypeName string 车型中文名(既有字段,非本次新增)
serviceStartDate / serviceEndDate string(LocalDate) 本组服务起止日
seats / count int 该组单车座位数/车辆数量;存量分组为 null 表示待填
specialTags array 该组特殊诉求标签(code + name)
remark string 该组备注/其他诉求;存量分组为 null
totalSeatCount int 总座位数 = seats × count
maxHeadcount int 该组 days 里的最大用车人数
remainingPassengerSeats int 余座(扣司机位后的可乘座位 − 最大用车人数)
days array 逐日用车人数与成员

请求示例

GET /v3/admin/order/group-batch/2104839654727618562/vehicle-requirement

响应示例

以下为测试环境实测响应(团期批次 2104840641651556353,2026-09-30 采集)。planRefreshState / blockedStage / planRefreshStalledReason 三列在库中为 NULL,对应 *Name 实测为 null:

{
  "code": 200,
  "message": "成功",
  "data": {
    "status": "DONE",
    "statusName": "配车完成",
    "planRefreshState": null,
    "planRefreshStateName": null,
    "blockedStage": null,
    "blockedStageName": null,
    "planRefreshStalledReason": null,
    "planRefreshStalledReasonName": null
  },
  "traceId": null,
  "success": true
}

空数据 / 降级响应

  • 团期尚未形成正式需求(从未 PUT 过):data 为 null(源码 @ApiOperation 明确标注"未形成时返回 null"),不是报错。
  • planRefreshState/blockedStage/planRefreshStalledReason 三列 DB 为 NULL(从未走过受控重开,绝大多数团期的正常态):对应的 3 个 *Name 字段一律为 null,不下发默认中文名。
  • groups 为空数组场景:整团免车态(waive 后)。

错误响应

{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}

业务边界

  • 4 个 *Name 字段是只读派生展示字段,不接受回传;PUT 请求体仍用原编码字段(GroupVehicleRequirementSaveReqVO 未新增字段)。
  • 认不出的编码 → 对应 *Name 为 null,不回落原编码——这与团期列表页 GroupBatchConverter#resolveBatchStatusName(未知码回落原码)是刻意不同的两套口径,不要混用同一套前端兜底逻辑。
  • 本端点权限码为 group-batch:demand:confirm,与 PUT/withdraw/waive/aggregate-draft 四个端点同码。
  • 本端点后续不会引入任何写操作(#7988 明确约束),可放心作为无副作用轮询口使用。

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

VO: GroupVehicleAggregateDraftRespVO(外层诊断字段未变;draft 字段类型改为 GroupVehicleRequirementDraftRespVO)

使用场景

团期正式用车需求编辑弹窗首次打开、或点"重新汇总"时调用:按各子订单已提交的活跃行程用车需求自动生成一份分组草稿,draft 可原样 PUT 回 /vehicle-requirement 保存。本次变更只影响 draft 内部的类型与新增字段,外层的 droppedFleetItems/staleHeadcountOrders/paddedOrderDays/seatOptionAdjusted/violations/exemptHouseholds 六个诊断字段结构未变。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long 是 - 团期聚合主键

出参字段表

字段 类型 说明
draft object 🆕类型变更 汇总草稿,类型由写侧 GroupVehicleRequirementSaveReqVO 改为只读读侧 GroupVehicleRequirementDraftRespVO,见下表
droppedFleetItems array 多车型户被丢弃的车型,未变
staleHeadcountOrders array 人数已过期的户,未变
paddedOrderDays array 为覆盖出发~返回而补进的日期,未变
seatOptionAdjusted array 座位被兜底调整到车型可选档位的户,未变
violations array 草稿违反保存态校验的逐条诊断,未变
exemptHouseholds array 提交不了的豁免户(订单不在定制中/团期冻结且未被打回),未变

draft(GroupVehicleRequirementDraftRespVO)字段:

字段 类型 说明
version int 乐观锁版本号;原样回传给 PUT;尚未形成正式需求时为 null
remark string 整份需求备注,汇总草稿恒为 null
groups array 全部乘车分组,恒非 null,无可汇总内容时为空数组;见下表

draft.groups[] 字段:

字段 类型 说明
groupId string(Long) 既有分组主键,汇总草稿恒为 null
groupCode string 分组键,直接作为车费 alloc_group
vehicleType string 车型大类编码(归一后的 key)
vehicleTypeName 🆕 string 车型大类中文名(只读,不参与保存);字典查不到时为 null,不回落编码原文(#8544,本类相对写侧唯一新增字段)
serviceStartDate / serviceEndDate string(LocalDate) 本组服务起止日
seats / count int 该组单车座位数(存量/无档位可取时可能为 null)/车辆数量
specialTags array(string) 该组特殊诉求标签编码数组
remark string 该组备注;汇总时按"团号(客户名): 备注"拼接,超 500 字从尾部截断
days array 逐日用车人数与成员,见下表

draft.groups[].days[] 字段:

字段 类型 说明
tripDate string(LocalDate) 团期行程日
headcount int 该组该日用车人数(乘车人数,非户数)
memberOrderIds array(string) 该组该日实际乘车的子订单集合

请求示例

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

响应示例

以下为测试环境实测响应(团期批次 2104840641651556353,2026-09-30 采集;days 实测为 7 天逐日行,此处只保留首日,其余日同形):

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "2104840641651556353",
    "currentStatus": "DONE",
    "draft": {
      "version": 3,
      "remark": null,
      "groups": [
        {
          "groupId": null,
          "groupCode": "MPV",
          "vehicleType": "mpv",
          "vehicleTypeName": "商务车",
          "serviceStartDate": "2026-11-11",
          "serviceEndDate": "2026-11-17",
          "seats": 7,
          "count": 1,
          "specialTags": [],
          "remark": "26-3682(董海涛): 结伴出行客户,整团统一 7 座商务车,已与客户确认路线;26-0805(谢丽萍): 9月30日至10月3日行程用车,成人4人其中1位长者,行李较多需大后备箱",
          "days": [
            {
              "tripDate": "2026-11-11",
              "headcount": 4,
              "memberOrderIds": ["2104840641597030402", "2104840685708525570"],
              "memberOrderCount": 2
            }
          ]
        }
      ]
    },
    "violations": []
  },
  "traceId": null,
  "success": true
}

groups[].remark 的一户标识格式是**「团号(客户名)」**(26-3682(董海涛)),多户合并进同一车型组时以 ; 连接。本次改动前该位置拼的是 19 位订单 ID,因此:已确认落库的存量团期,其 GET .../vehicle-requirement 返回的 groups[].remark 仍是旧格式(HL2026… 订单号前缀)——本单只改新生成的汇总草稿,不回写历史数据。前端不要按固定格式解析 remark,它是给运营看的自由文本。

空数据 / 降级响应

  • 团期批次不存在:返回业务错误(见"错误响应")。
  • 全团无可汇总的行程用车需求:draft.groups 为空数组。
  • 车型字典不可用:vehicleTypeName 降级为 null,groups 其余字段照常下发(不阻断整个响应)。

错误响应

{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
{"code": 809120, "message": "车队车型字典暂不可用,无法校验车型,请稍后重试", "success": false, "data": null}

有在团需车户缺少可汇总用车需求时(809121,触发条件已被 #8577 收窄——只提交了接送机的户不再算缺少):

{"code": 809121, "message": "团期 「第3期 10月8日出发团」 有 1 户缺少可汇总的用车需求,暂不能自动汇总:26-0480:未提交用车需求", "success": false, "data": null}

业务边界

  • draft 是只读展示体,不挂任何 @NotNull/@NotEmpty/@Size 校验注解;违反保存态约束的地方在 violations 里另行列出,不要用 draft 自身的字段是否为空来判断能不能保存。
  • draft 除 groups[].vehicleTypeName 外的字段集与写侧 GroupVehicleRequirementSaveReqVO 完全一一对应(由专门的字段一致性单测钉住),可以原样 PUT 回 /vehicle-requirement;vehicleTypeName 回传时会被后端忽略,车型以 vehicleType 编码为准。
  • draft.groups[].remark 的拼接标签口径:团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户",不再回落裸雪花订单 ID。
  • 权限码与「2」相同(group-batch:demand:confirm),本端点同样不引入任何写操作。

4. 团期下子订单列表 GET /v3/admin/order/group-batch/{groupBatchId}/orders

VO: GroupBatchOrderItemRespVO(30 个字段中本次只变更 4 个,见下表标注)

使用场景

团期详情页"子订单"Tab 的列表数据源,每户一行。本次变更聚焦用车需求相关的 4 个字段,其余 26 个字段(hotelRequirementStatus、totalPrice、travelers 等)未变,已由 30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md 完整记录。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long 是 - 团期聚合主键
page Query Integer 否 缺省 1,<1 归一为 1 页码
pageSize Query Integer 否 缺省 20,<1 归一为 20,>200 截断为 200 每页条数
includeTravelers Query Boolean 否 缺省 true 是否附出行人明细(证件号/手机号一律不返回)
includeNeeds Query Boolean 否 缺省 true 是否附房数/房型/特殊需求
includeCancelled Query Boolean 否 缺省 false 是否含已取消子订单

出参字段表(仅列本次变更相关字段)

字段 类型 说明
vehicleRequirementStatus string 车需求状态:口径从"只看行程用车"改为"该户展示序首条活跃行"(行程用车优先、其次接送机)。该户一条活跃车需求行都没有时为 null(#8619)
vehicleRequirementStatusName string 状态中文名;未知码回落原 code(与 vehicleRequirementKindName 不同口径);code 为 null 时按该户 needsVehicle 分叉——true 出"未提交"、否则为 null。#8619 起"未提交"只在两类需求都没报时出现
vehicleRequirementKind string 本行车需求的用车类别:TRAVEL/TRANSFER。#8619 起不再恒为 TRAVEL——只报了接送机的户在这里下发 TRANSFER;无 active 行时为 null
vehicleRequirementKindName string 类别中文名:行程用车/接送机;认不出的类别给 null,不回落编码(与 vehicleRequirementStatusName 不同口径)
vehicleRequirements 🆕 array 该户全部活跃用车需求行(0~2 条:TRAVEL/TRANSFER 各至多一条),恒非 null,行程用车在前、接送机在后(#8619),见下表

vehicleRequirements[] 单项字段:

字段 类型 说明
kind string 需求类别:TRAVEL=行程用车 / TRANSFER=接送机
kindName string 类别中文名;认不出的类别给 null,不回落编码
status string 该条需求状态:PENDING/PROCESSING/DONE/PENDING_REVIEW/REJECTED_TO_CONSULTANT/REJECTED_TO_ADMIN
statusName string 状态中文名;PENDING_REVIEW 按 kind 分两套文案:TRAVEL="待提交车务"、TRANSFER="待审核"

请求示例

GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=20

响应示例

以下为测试环境实测响应(团期批次 2104839654727618562,2026-09-30 采集)。每条 records[] 实测另有 26 个本次未变的字段,此处只保留与本单相关的 5 个,其余省略(完整字段集见 changelog 30_8543):

{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "orderId": "2104839654652121090",
        "vehicleRequirementStatus": "DONE",
        "vehicleRequirementStatusName": "配车完成",
        "vehicleRequirementKind": "TRAVEL",
        "vehicleRequirementKindName": "行程用车",
        "vehicleRequirements": [
          {"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
          {"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING", "statusName": "待车队配"}
        ]
      },
      {
        "orderId": "2104839686486888449",
        "vehicleRequirementStatus": "DONE",
        "vehicleRequirementStatusName": "配车完成",
        "vehicleRequirementKind": "TRAVEL",
        "vehicleRequirementKindName": "行程用车",
        "vehicleRequirements": [
          {"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"}
        ]
      },
      {
        "orderId": "2104839729176514562",
        "vehicleRequirementStatus": "DONE",
        "vehicleRequirementStatusName": "配车完成",
        "vehicleRequirementKind": "TRAVEL",
        "vehicleRequirementKindName": "行程用车",
        "vehicleRequirements": [
          {"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
          {"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING_REVIEW", "statusName": "待审核"}
        ]
      }
    ],
    "total": 3,
    "page": 1,
    "pageSize": 20
  },
  "traceId": null,
  "success": true
}

上例里三户的 vehicleRequirements 长度分别为 2 / 1 / 2,vehicleRequirementStatus 与 Kind 取的都是展示序首条(TRAVEL 优先),因此三户顶层都是 TRAVEL;接送机那一行的真实状态只在 vehicleRequirements[] 里看得到。

空数据 / 降级响应

  • 团期批次不存在:返回业务错误(见"错误响应")。
  • 团期下暂无子订单(或 includeCancelled=false 时全部已取消):records 为空数组,total=0。
  • 该户一条活跃用车需求行都没有:vehicleRequirementStatus/vehicleRequirementKind 均为 null,vehicleRequirements 为空数组(不是 null);vehicleRequirementStatusName 按该户 needsVehicle 分叉。
  • 该户只提交了接送机需求(#8619 修复的场景):vehicleRequirementKind 下发 TRANSFER、vehicleRequirementStatus 下发该行程的真实状态,不再是 null/"未提交"。

错误响应

{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}

业务边界

  • 按用车类别判断状态一律读 vehicleRequirements[],不要只读 vehicleRequirementStatus/vehicleRequirementKind 单值字段——一户两类需求并存时单值字段只能表达展示序首条。
  • vehicleRequirementKindName 与 vehicleRequirementStatusName 是两套不同的未知码兜底口径(前者给 null,后者回落原码),不要用同一段兜底逻辑处理。
  • 🔴 同一个 status 编码在不同 kind 上的中文名不同,前端不能自建 code→中文 映射表:PENDING_REVIEW 在 TRAVEL 行下发「待提交车务」(推走它的是团期管理员整团一次的「提交车务」,该状态上没有逐户审核动作),在 TRANSFER 行下发「待审核」(有逐户审核动作)——这是 #8218 起的刻意分叉,声明见 GroupVehicleHouseholdsRespVO/GroupBatchOrderItemRespVO 的 @ApiModelProperty。两侧均有实测样本:批次 2104839654727618562 的 2104839729176514562 户 TRANSFER 行 = PENDING_REVIEW/「待审核」;批次 2104840641651556353 的 2104840685708525570 户 TRAVEL 行 = PENDING_REVIEW/「待提交车务」。一律直接渲染后端下发的 statusName。
  • 权限码为 group-batch:view。

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

  • 4 个新增 *Name 字段(#8544)是只读展示字段:statusName/planRefreshStateName/blockedStageName/planRefreshStalledReasonName 只在响应体里出现,PUT 请求体 GroupVehicleRequirementSaveReqVO 未新增任何字段,回传这些字段会被忽略。
  • PUT /vehicle-requirement、POST /vehicle-requirement/withdraw、POST /vehicle-requirement/waive 三个写端点与本文档「三、2」共用完全同一个 GroupVehicleRequirementRespVO 类:调用这三个端点后,响应体里同样带有 4 个新增 *Name 字段,字段语义、null 规则与「三、2」逐字一致,无需前端另写一套解析。
  • aggregate-draft 的 draft 字段类型变更(#8544):TypeScript/接口类型定义如果之前直接复用了 GroupVehicleRequirementSaveReqVO 的类型作为 draft 的类型,需要改成新类型(多一个只读字段 groups[].vehicleTypeName,其余字段名与类型逐一相同)。JSON 结构层面对已有解析代码零破坏,只有严格 schema 校验(如果有)需要放开这个新字段。
  • 未知/认不出的编码统一规则(本次涉及的所有 *Name 字段):statusName/planRefreshStateName/blockedStageName/planRefreshStalledReasonName/draft.groups[].vehicleTypeName/vehicleRequirementKindName/vehicleRequirements[].kindName 这一组字段认不出编码一律给 null,不回落原编码。这与 orders 端点的 vehicleRequirementStatusName(未知码回落原码,#8543/#8619 未改动此口径)以及团期列表页的团期状态中文名(同样回落原码)是刻意不同的两套口径,前端不要用同一段兜底组件处理。
  • vehicleSeatSummary[].confirmedSeats/confirmedCount 与 totalSeats/totalCount 不相等是设计上允许的常态(#8545),不要写断言校验两者相等,也不要用其中一组数字反推另一组。
  • orders 端点判断某户是否提交了某类用车需求,一律遍历 vehicleRequirements[] 按 kind 过滤,不要依赖单值字段 vehicleRequirementStatus/vehicleRequirementKind(#8619 起单值字段只表达展示序首条,可能丢失第二类需求的状态)。

六、边界行为

  • 未登录访问:网关拦截,不进入本文档描述的业务逻辑。
  • 权限不足(当前角色未获授团期权限,或该团期不在本人名下):所有 4 个端点统一报 589507。
  • 团期不存在:所有 4 个端点统一报 589500。
  • GET .../vehicle-requirement 团期尚未形成正式需求:data 为 null,HTTP 200,不是错误。
  • planRefreshState/blockedStage/planRefreshStalledReason 三列 DB 为 NULL(绝大多数团期的正常态,从未走过受控重开):对应 3 个 *Name 字段一律为 null。
  • vehicleSeatSummary[]:团级从未确认过时 confirmedSeats/confirmedCount 恒为 0(不是 null);车务定的车型在逐户报的车型集合之外时会新增一行 totalSeats=0/totalCount=0 的记录。
  • orders 端点 vehicleRequirements[]:该户没有任何活跃用车需求行时为空数组(不是 null)。

六.5、枚举

所属字段:GroupVehicleRequirementRespVO.status / statusName

值 中文名 说明
DRAFT 草稿 团期管理员正在汇总逐户需求、编辑分组与逐日人数
CONFIRMED 已确认 整份确认通过,等待发车务
DISPATCHED 已发车务 已推送到车务侧,等待配车
DONE 配车完成 车务侧已完成整团配车
PENDING_RECONFIRM 待重新确认 确认后团期人数/成员发生变化,需要重新确认整份
CANCELLED 已取消 -

所属字段:GroupVehicleRequirementRespVO.planRefreshState / planRefreshStateName

值 中文名 说明
null(列值 NULL) (无中文名,字段为 null) 从未登记过任何刷新,多数团期的正常态
PENDING 刷新中 刷新命令已登记,等 fleet 刷完
DONE 刷新完成 就绪回调已通过判定并应用,本轮刷新闭环
FAILED 刷新失败 已耗尽重试预算或被 fleet 判定性终结;不放行团期,出团门②持续拒绝

所属字段:GroupVehicleRequirementRespVO.planRefreshStalledReason / planRefreshStalledReasonName

值 中文名 说明
STATE_FAILED 刷新已被判死 plan_refresh_state 已经是 FAILED
COMMAND_FAILED 刷新命令已失败 状态列仍是 PENDING,但刷新命令已终态失败(回写钩子未落成)
TIMEOUT 刷新超时 状态列仍是 PENDING、命令也未判死,但已超过本轮窗口时限(缺省 120 分钟)

所属字段:GroupVehicleRequirementRespVO.blockedStage / blockedStageName

取值域是团期状态枚举 GroupBatchStatus(既有枚举,本次未新增值)的全域,本次只是给这个已有编码字段配了中文名。常见值举例:

值 中文名 说明
RESOURCE_PREPARING 资源准备中 -
MATERIAL_PREPARING 物料准备中 -
PENDING_DEPARTURE 待出发 -
(其余 GroupBatchStatus 取值) 对应中文名 认不出的编码给 null

所属字段:orders[].vehicleRequirementKind / vehicleRequirementKindName、orders[].vehicleRequirements[].kind / kindName

VehicleRequirementKind 枚举本身固定只有 2 个值(本次未新增第三个值,放宽的是响应字段的实际观测取值范围,不是枚举定义):

值 中文名 说明
TRAVEL 行程用车 服务日冻结为行程日
TRANSFER 接送机 服务日取航班/车次日期,允许落在行程日窗外

六.6、修改前后对比

项 改前 改后
GroupVehicleRequirementRespVO 状态/刷新状态/阻断阶段/停滞归因 仅下发编码,前端自行映射中文 新增 4 个 *Name 字段直接下发中文名;认不出编码给 null
aggregate-draft.draft 字段类型 写侧 GroupVehicleRequirementSaveReqVO(挂校验注解,多余的约束会渲染进 Swagger) 只读读侧 GroupVehicleRequirementDraftRespVO(不挂校验注解,多一个只读字段 vehicleTypeName)
draft.groups[].remark 逐户标签兜底 团号(客户名)→ 团号 → 客户名 → 订单号(裸雪花 ID) 团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户"(不再回落裸雪花 ID)
requirement-summary.vehicleSeatSummary[] 只有 totalSeats/totalCount(逐户已提交口径) 并列新增 confirmedSeats/confirmedCount(团级已确认口径),可能新增车型行
orders[].vehicleRequirementStatus/Kind 只按 TRAVEL 过滤取行;只提交接送机的户恒为 null/"未提交" 取展示序首条活跃行(TRAVEL 优先、TRANSFER 次之);只提交接送机的户下发真实状态
orders[].vehicleRequirements 不存在该字段 新增,0~2 条,逐类下发状态

六.7、影响评估

  • 前端必改:如果曾对 draft.remark 文本做正则匹配来提取订单号或高亮逐户标签,需要兼容新的"未知户"文案且不再假设会出现裸数字订单 ID。并且请不要按任何固定格式解析 remark——本单只改新生成的汇总草稿,已确认落库的存量团期其 GET .../vehicle-requirement 返回的 groups[].remark 仍是改前格式(订单号前缀,形如 HL20260929154809598: …),不做历史数据回写。该字段是给运营看的自由文本,两种格式会长期并存。
  • 前端必改:如果曾假设 orders[].vehicleRequirementKind 恒为 TRAVEL 来做图标/文案硬编码分支,需要改为按实际值(TRAVEL/TRANSFER)渲染,并优先改用 vehicleRequirements[] 数组按类判断。
  • 前端可选增强:可以直接展示后端下发的 4 个新增中文名字段,替换掉前端此前自行维护的编码→中文映射表(如果有)。
  • 前端需知悉但不需要立即改:vehicleSeatSummary[] 新增的两个字段、可能新增的车型行,只在页面展示这两个数字时才需要处理;不展示则忽略即可,不影响既有渲染。
  • 零风险:所有新增字段都是在既有 JSON 对象上新增键,未删除、未改名任何既有字段(aggregate-draft.draft 虽改了后端类型,但 JSON 键集合与既有字段类型未变,由专门单测钉住);未做严格 schema 校验的前端代码可无感兼容。

七、不影响范围

  • GroupVehicleRequirementSaveReqVO(PUT 请求体)字段集未变,仍是原有编码字段集,本次新增的只读字段不参与保存也不会被写侧读取。
  • GroupVehicleRequirementDraftRespVO 除 groups[].vehicleTypeName 外的全部字段(顶层 version/remark、groups[] 内 groupId/groupCode/vehicleType/serviceStartDate/serviceEndDate/seats/count/specialTags/days)未变,由 GroupVehicleRequirementDraftRespVOFieldParityTest 钉住与写侧字段集一致。
  • VehicleRequirementKind 枚举定义本身未变,仍只有 TRAVEL/TRANSFER 两个值。
  • 团期用房相关端点(hotel-households 等)不在本次改动范围内。
  • orders 端点除本文档列出的 4 个字段外,其余 26 个字段未变(详见 changelog 30_8543)。
  • 809121/809122/809123 三个错误码的触发条件收窄与文案改写属于 #8577,不在本次三张工单范围内,详见 changelog 30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md。

八、测试环境已验证

  • hl-order-service-v3 dev-v3 分支已部署测试网关,HEAD 6373e5cf2(git merge-base --is-ancestor 核实本单提交 5f2f86bc96 已在其祖先链内),jar mtime 2026-09-30 09:27:12,两个实例 Nacos 健康检查均为 healthy。
  • 以下四条读口实测取样自团期批次 2104840641651556353(vehicleRequirements[] == [] 一条取样自 2104830530031919106),2026-09-30 采集。
  • GET .../requirement-summary 实测:vehicleSeatSummary[0] = {"vehicleType":"mpv","vehicleTypeName":"商务车","totalSeats":14,"totalCount":2,"confirmedSeats":7,"confirmedCount":1}。
  • GET .../vehicle-requirement/aggregate-draft 实测:draft.groups 非空,含 vehicleTypeName: "商务车";groups[0].remark 为「团号(客户名)」格式(26-3682(董海涛): …;26-0805(谢丽萍): …),未出现裸雪花订单 ID。
  • GET .../orders 实测:2 条子订单,每条 vehicleRequirements 键均存在且非 null,长度分别为 2 与 1。另在一个无活跃用车需求行的户上实测该键为空数组 [] 而不是 null(与 GroupBatchConverter.toVehicleRequirementItems() 从 new ArrayList<>() 起手、无 null 分支一致)。
  • GET .../vehicle-requirement 实测:顶层键包含 status/statusName/planRefreshState/planRefreshStateName/blockedStage/blockedStageName/planRefreshStalledReason/planRefreshStalledReasonName 共 8 个;观测样本 status="DONE" → statusName="配车完成";另外三个 DB 列为 NULL,对应 *Name 字段均实测为 null。
  • 「认不出的编码 → null」这条行为本轮没有实测样本:候选团期的 plan_refresh_state / blocked_stage / plan_refresh_stalled_reason 三列在库中全为 NULL,测试环境里造不出一个库内存着字典外编码的自然样本。该行为由源码与单测两侧钉住:GroupVehiclePlanRefreshState.labelOf()、GroupBatchStatus.descOf() 对未命中编码返回 null(不回落编码原文)。前端按 null 兜底渲染即可。
  • 单元测试:hl-order-service-v3 模块 Tests run: 561, Failures: 0, Errors: 0, Skipped: 0,38 个相关测试类逐类点名核对报告文件均存在(38/38 命中);nested_selector_census 退出码 0(无 @Nested 类被静默漏跑)。
  • ArchUnit/架构门禁测试:Tests run: 167,全绿。

十、相关文档

  • docs/CODE_RULES.md §3(VO 命名与读写侧分离约定)
  • changelog 30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md(orders 端点其余字段的完整文档,及 vehicleRequirementKind 字段本次改动前的基线状态)
  • changelog 30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md(PUT/aggregate-draft/confirm-check 三处 809121/809122/809123 错误码本次收窄的完整文档)

关联 / 联系人

  • 关联 Issue:#8544、#8545、#8619
  • 关联 PR:#8625(#8544 #8545)、#8624(#8619)
  • 联系人:wx