文件
hl-api-changelog/changelogs-v2/2026-09/23_8220_团期正式行程用车需求自动汇总草稿-新增接口-管理后台.md
T
2026-09-23 18:06:39 +08:00

18 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 8220 团期正式行程用车需求自动汇总草稿 admin jw(GIT) 新增接口 deployed verified verified mmg ee98b96654fd250159ee4a3b0067e5f1c88d3b75 v2.1 2026-09-23 后端已部署 TEST 并经网关实测;待 mmg 在编辑弹窗接入「自动汇总」+ 乘车户全选 + 人数随所选户汇总;前端已交付:编辑弹窗「自动汇总」灌草稿+四诊断展示+大类收敛(suv→suv2)+version 用草稿值+非 DRAFT 禁保存;spec 41 例全绿,checkpoint 13 项过 2026-09-23 dev-v3

团期正式行程用车需求自动汇总草稿(#8220)

服务: hl-order-service-v3(经网关调用,无需关心服务端口) PR: #8273 Issue: #8220 日期: 2026-09-23 影响范围: 管理后台团期详情「用车」Tab 的正式行程用车需求编辑弹窗


⚠️ 关键变化

新增只读端点,按各子订单已提交的行程用车(TRAVEL)需求,自动汇总出团级正式用车需求草稿。草稿形状与保存端点 PUT .../vehicle-requirement 的请求体完全一致,前端可以直接灌进编辑弹窗、原样保存。

四条会直接影响你怎么写代码的点,按严重度排:

  1. 🔴 草稿之外的四个诊断字段必须展示,不能只看 draft。droppedFleetItems 里是没进草稿的车型需求(一户报了多个车型时只保留主车型),不提示的话,管理员一保存,这些需求就从团级正式需求里消失了。另外三个字段(staleHeadcountOrders / paddedOrderDays / violations)见业务边界。
  2. 🔴 车型回显是归一后的大类 key(suv / bus / mpv / sedan),与 #8221 交接件里提过的是同一个问题:车型下拉的 value 是 fleet typeKey suv2,拿 suv 逐字去比会判成「非字典值,请重选」,并被前端校验拦住保存。判「是否字典值」要按归一后的大类比,否则所有 SUV 组汇总出来都会被弹窗拦下。
  3. 有户还没提交行程用车需求时,本端点直接返回业务错误 809121,报文逐户列出「订单号(orderId):原因」。这时不要尝试拼一份草稿,应提示管理员去催这些户。
  4. 成功码是 code: 200;业务失败也返回 HTTP 200,一律按 body.code 判。

一、背景(选填)

wx 2026-09-23 团期详情页「用车」Tab 反馈:「正式的行程用车需求要根据子订单的行程用车需求自动汇总 自动填写」,以及「得有个全选的按钮,根据选的乘车户自动把人数算出来」。此前编辑弹窗不拉取任何子订单需求数据,日期预填的是团期自己的出发/结束日,其余字段全靠手填。汇总规则由管理者于 2026-09-23 定案(#8220 评论)。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft 新增接口 只读,返回可原样保存的草稿与汇总诊断

三、接口详情

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

VO: GroupVehicleAggregateDraftRespVO

使用场景

管理员在编辑弹窗里点「自动汇总」时调用。拿到 data.draft 后填进弹窗,同时展示四个诊断字段;管理员确认后,把 draft 原样(或编辑后)交给 PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 保存。

弹窗里如果已经有编辑内容,点「自动汇总」前请二次确认「这将覆盖当前编辑内容」。本端点不管现在有没有正式需求,都返回完整汇总,要不要覆盖由前端交互决定。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ — 团期 ID(group_batch_id),🔴 不是产品班期 ID

出参 Result<GroupVehicleAggregateDraftRespVO>

字段 类型 说明
groupBatchId String(雪花 ID) 团期 ID
currentStatus String / null 当前正式需求状态;还没形成时为 null。只有 null 或 DRAFT 时才能保存(其余状态保存会报 809101 / 809115),前端据此决定「保存」按钮是否可用
draft Object 汇总草稿,与保存请求体同形,恒非 null
draft.version Integer / null 当前正式需求的乐观锁版本;未形成时为 null。保存时原样带上
draft.remark String / null 整份备注,汇总时恒为 null
draft.groups[] Array 乘车分组,没有可汇总内容时为空数组
draft.groups[].groupId Long / null 恒为 null(汇总产物一律是新组)
draft.groups[].groupCode String 车型大写,同车型按连续日期段拆组时第二段起加序号:BUS、BUS2…
draft.groups[].vehicleType String 归一后的车型大类 key:bus / suv / mpv / sedan
draft.groups[].serviceStartDate String(yyyy-MM-dd) 本组首日
draft.groups[].serviceEndDate String(yyyy-MM-dd) 本组末日
draft.groups[].seats Integer / null 组内各户保留车型项里最大的单车座位数;各户都没填座位时,与 count 一起为 null
draft.groups[].count Integer / null ceil(本组最忙那天的人数 / (seats − 1)),已扣除司机座
draft.groups[].specialTags String[] 组内各户特殊诉求编码的并集(去重、保持顺序)
draft.groups[].remark String / null 逐户「订单号: 定制师备注」和「订单号 另报 suv 7座×1」拼接而成,超过 500 字截断。仅供人眼留底
draft.groups[].days[] Array 逐日明细,正好铺满本组首日到末日
draft.groups[].days[].tripDate String(yyyy-MM-dd) 日期
draft.groups[].days[].headcount Integer 当天在组各户的实时人数之和
draft.groups[].days[].memberOrderIds String[](雪花 ID) 当天在组的子订单 ID。🔴 19 位雪花 ID,前端一律按字符串处理
droppedFleetItems[] Array 没进草稿的车型项,见业务边界第 1 条
droppedFleetItems[].orderId / orderNo String / String 所属子订单
droppedFleetItems[].vehicleType / seats / count String / Integer / Integer 被丢弃项(子订单原值)
droppedFleetItems[].keptVehicleType String 该户被归入的主车型
droppedFleetItems[].reason String NOT_PRIMARY_TYPE(非主车型)/ VEHICLE_TYPE_NOT_IN_DICT(车型不在车型字典内)
staleHeadcountOrders[] Array 实时人数与子订单需求提交时冻结的人数不一致的户:orderId / orderNo / frozenHeadcount / liveHeadcount
paddedOrderDays[] Array 为了覆盖该户「出发~返回」每一天而补进分组、但不在该户行程用车服务日里的日期:orderId / orderNo / dates[]
violations[] Array 草稿按保存时同一套校验预检出的问题:code / reason / detail / groupCode / tripDate / orderId。正常应为空数组

请求示例

GET /v3/admin/order/group-batch/2102692937584513025/vehicle-requirement/aggregate-draft
Authorization: Bearer <admin token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "2102692937584513025",
    "currentStatus": null,
    "draft": {
      "version": null,
      "remark": null,
      "groups": [
        {
          "groupId": null,
          "groupCode": "BUS",
          "vehicleType": "bus",
          "serviceStartDate": "2026-11-24",
          "serviceEndDate": "2026-11-26",
          "seats": 16,
          "count": 1,
          "specialTags": [],
          "remark": "HL20260923173356872: 8220 甲户多车型;HL20260923173356872 另报 suv 5座×1;HL20260923173401731: 8220 乙户",
          "days": [
            {
              "tripDate": "2026-11-24",
              "headcount": 5,
              "memberOrderIds": [
                "2102692937378992129",
                "2102692957587165185"
              ]
            },
            {
              "tripDate": "2026-11-25",
              "headcount": 5,
              "memberOrderIds": [
                "2102692937378992129",
                "2102692957587165185"
              ]
            },
            {
              "tripDate": "2026-11-26",
              "headcount": 5,
              "memberOrderIds": [
                "2102692937378992129",
                "2102692957587165185"
              ]
            }
          ]
        },
        {
          "groupId": null,
          "groupCode": "SUV",
          "vehicleType": "suv",
          "serviceStartDate": "2026-11-24",
          "serviceEndDate": "2026-11-26",
          "seats": 5,
          "count": 1,
          "specialTags": [],
          "remark": "HL20260923173405165: 8220 丙户",
          "days": [
            {
              "tripDate": "2026-11-24",
              "headcount": 2,
              "memberOrderIds": [
                "2102692971709370370"
              ]
            },
            {
              "tripDate": "2026-11-25",
              "headcount": 2,
              "memberOrderIds": [
                "2102692971709370370"
              ]
            },
            {
              "tripDate": "2026-11-26",
              "headcount": 2,
              "memberOrderIds": [
                "2102692971709370370"
              ]
            }
          ]
        }
      ]
    },
    "droppedFleetItems": [
      {
        "orderId": "2102692937378992129",
        "orderNo": "HL20260923173356872",
        "vehicleType": "suv",
        "seats": 5,
        "count": 1,
        "keptVehicleType": "bus",
        "reason": "NOT_PRIMARY_TYPE"
      }
    ],
    "staleHeadcountOrders": [],
    "paddedOrderDays": [],
    "violations": []
  },
  "success": true
}

示例说明:示例值取自测试环境一次真实调用,不构成可复现夹具。

空数据 / 降级响应

团里没有需要用车的户,也没有任何 TRAVEL 需求时,返回空草稿(groups 是空数组,不是 null):

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

车型字典(车队服务)不可用时不降级,返回 809120,见错误响应。

错误响应

有户缺少可汇总的行程用车需求(未提交 / 被打回未重提 / 车型都不在字典内 / 推不出日期 / 人数为 0):

{
  "code": 809121,
  "message": "团期 2102692937584513025 有 1 户缺少可汇总的行程用车需求,暂不能自动汇总:HL20260923173405165(2102692971709370370):未提交行程用车需求",
  "data": null,
  "success": false
}

车型字典暂不可用:

{
  "code": 809120,
  "message": "车队车型字典暂不可用,无法校验车型,请稍后重试",
  "success": false,
  "data": null
}

无权限(当前角色没有 group-batch:demand:confirm):

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

业务边界

  • 多车型户:一户的行程用车同时报了多个车型(如 bus×1 + suv×1)时,这一户只进「主车型」组。主车型取座位数×辆数最大的那一项,相等时取车型编码字典序小的,所以同一份数据每次汇总结果都相同。其余车型进 droppedFleetItems,🔴 前端必须醒目提示「以下车型需求未包含在草稿中」。这样做是因为同一户同一天只能属于一个分组(809108)。
  • 拆组:同一车型的日期如果断开(比如甲 10-0810-09、乙 10-1210-13),会拆成 BUS / BUS2 两组,不会产出某天零人的逐日行。
  • 补日:团级保存要求每个需车户「出发~返回」的每一天都被分组覆盖(809109)。子订单行程用车的服务日如果比这个范围窄,多出来的日期也会把该户算进车,并列在 paddedOrderDays 里。前端提示「以下户在这些日期原本未报用车,已按行程补入」。
  • 人数:逐日人数用订单实时人数,与户列表显示的人数一致。实时人数与子订单需求提交时冻结的人数不一致时,该户进 staleHeadcountOrders,说明那户的子订单用车需求已经过期,车务在子订单侧看到的还是旧值。
  • 座位与车数:count 按扣掉司机座的口径算,所以草稿在团级(809116)和子订单级两种座位校验下都成立。
  • violations 为空也不保证保存一定成功:currentStatus 不是 null 或 DRAFT、或者汇总之后有人改过正式需求(version 变了),保存照样会被拒。
  • 只汇总行程用车(TRAVEL),接送机(TRANSFER)不进团车。
  • 本端点零写入:不取锁、不进事务,调多少次都不影响数据。

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

✅ 正确 / ❌ 错误请求对照

场景 请求 预期(HTTP 恒 200)
✅ 各户都已提交 GET .../{groupBatchId}/vehicle-requirement/aggregate-draft code: 200,draft.groups 非空
✅ 原样保存 PUT .../{groupBatchId}/vehicle-requirement,请求体 = data.draft code: 200
❌ 有户未提交 同上 GET code: 809121,报文列出未提交的户
❌ 路径传成产品班期 ID GET .../{productBatchId}/vehicle-requirement/aggregate-draft 团期不存在的错误码(589500)

前端「乘车户全选 + 人数自动汇总」的取数口径(wx 反馈第 ③ 条)

  • 户清单与每户人数:用既有的 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households?kind=TRAVEL,人数取 households[].participantCount。它与本端点草稿里的逐日人数同源,都是订单实时人数。
  • 🔴 不要用分页的团期订单列表做「全选」:那个列表单页上限 200,户数超过 200 时会漏选。vehicle-households 不分页,单次最多 500 户。
  • 某天的人数 = 当天勾选各户的 participantCount 之和。汇总草稿里的 days[].headcount 就是按这个口径算的,可以直接对照。

五、数据库行为

本接口只读,没有任何数据库写入,不新增表、不改表结构,也没有 Flyway 脚本。测试环境实测:调用前后,团级正式需求相关表与子订单用车需求表的行数和 update_time 都没有变化(见第八节)。


六、边界行为

  • 鉴权:需要 group-batch:demand:confirm,与 GET/PUT .../vehicle-requirement 同一个权限码。不带 token 时网关返回 401;角色没有该权限时返回 code: 589507。
  • 网关路由:沿用现有的 - Path=/v3/admin/** → lb://hl-order-service-v3,本单不新增路由。
  • 团期不存在 → 589500。
  • 有户缺少可汇总需求 → 809121;车型字典不可用 → 809120。

六.5 枚举 / 数据字典

droppedFleetItems[].reason

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

值 中文 说明
NOT_PRIMARY_TYPE 非主车型 该户多车型,只保留主车型
VEHICLE_TYPE_NOT_IN_DICT 车型不在字典 车型归一后不在车队车型字典内

六.6 修改前后对比

无,本接口为新增。


六.7 影响评估

  • 破坏兼容:否,新增接口
  • 前端同步上线要求:否,不接入也不影响现有编辑与保存流程
  • 新增错误码:809121(有户缺少可汇总的行程用车需求)

七、不影响范围

  • 仅影响:团期正式行程用车需求编辑弹窗的「自动汇总」入口
  • 零影响:
    • 正式用车需求的保存 / 读取 / 撤回 / 免车 / 确认(校验语义一处未改)
    • 子订单用车需求的提交与审核
    • 接送机(TRANSFER)链路

八、测试环境已验证

真实网关调用(https://api.test.1814.love),hl-order-service-v3 已部署 dev-v3 @ 9b60bc62b(本单合并提交,两个实例都在 17:21 滚动重启)。夹具全部自建:产品班期 2102692899017912323,团期 2102692937584513025,三户订单甲 / 乙 / 丙。

GET .../2102692937584513025/vehicle-requirement/aggregate-draft   (三户都没提交 TRAVEL)
  → 809121,报文列出 3 户 ✓;甲户提交后列 2 户 ✓;乙户提交后列 1 户 ✓
GET .../aggregate-draft   (三户都已提交:甲 bus 16×1 + suv2 5×1、乙 bus 12×1、丙 suv2 5×1)
  → 200:BUS 组(甲 + 乙,seats=16,count=1,每天 5 人)+ SUV 组(丙);
    droppedFleetItems = 甲户 suv 5×1(NOT_PRIMARY_TYPE);violations = [] ✓
PUT .../2102692937584513025/vehicle-requirement   请求体 = data.draft 原样
  → 200,version=1;GET 回读后逐字段比对与草稿一致 ✓(再汇总一次、带 version=1 原样保存 → 200,version=2 ✓)
零写入:调汇总前后各读一次 order_group_vehicle_requirement / _group / _group_day / order_vehicle_requirement
  的全表行数与 MAX(update_time),以及本团相关行 → 两轮前后完全一致 ✓
丙户另外提交了 TRANSFER(mpv,11-23 接机)→ 草稿里没有 mpv,也没有 11-23 ✓
不带 token → code 401「缺少有效的 Authorization 头」 ✓
定制师角色(无 group-batch:demand:confirm)→ code 589507 ✓

九、相关历史 PR

  • #8221(车型字典与存量车型归一):本端点输出的车型同样要通过 809119

十、相关文档


关联 / 联系人

链接

联系人

  • 后端负责人: @jw