--- schema: "hl-changelog/v2" ticket: "frontend" title: "团期详情「查看需求」页签重做为一键审核弹窗:页签改只读总览加唯一主按钮,审核打回确认收进三步弹窗,全部调用既有接口,后端零改动" consumer: "admin" author: "wx(GIT)" change_type: "前端优化" backend_status: "not_required" gateway_status: "not_required" frontend_status: "implemented" frontend_owner: "hl-admin" frontend_ref: "80d7ad8872b2938e4849d71f32007ff07c6f465d" target_release: "v2.1" verified_at: "2026-09-30" status_note: "后端零改动,17 个既有接口;三种提交模式的调用顺序、确认后接送机对账补放行、已确认态只可通过不可打回见正文四;4 项字段缺口本期不展示;前端已交付:页签只读总览+三步审核弹窗+对账补放行+审核变更只通过" updated_at: "2026-09-30" base: "dev-v3" --- # 团期需求: 「查看需求」页签重做为一键审核弹窗 > **服务**: hl-order-service-v3(本条后端零改动,涉及接口全部为既有接口) > **PR**: 无(纯前端改造) > **Issue**: 无(ticket=frontend) > **日期**: 2026-09-29 > **影响范围**: 管理后台 → 订单管理 → 团期列表 → 出团详情 →「查看需求」页签(`src/views/order-v2/batch/detail/components/RequirementTab.vue` 及其子组件) > **设计稿**: 「设计稿」https://claude.ai/artifact/R2b6RsiUvykdF9kBUdaQcC > **源码基线**: 后端 `origin/dev-v3@7b702f5c`;前端 hl-ui `origin/v2.1@cdfaf793` --- ## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写) - **页面结构变了**:原来是「汇总块 + 逐户块 + 正式用车需求块」平铺,顶部四个按钮;新设计是**只读总览**(状态条 + 三张汇总卡 + 逐晚用房 + 用车安排 + 逐户需求)加**唯一主按钮**「审核并确认需求」,点开是**三步审核弹窗**(①住房 ②接送机 ③行程用车)。 - **取消的按钮**:「打回选中户」「新增正式行程用车需求」「更多」「整团确认需求」(`RequirementTab.vue:53 / :68 / :77 / :86`)。**取消的只是按钮,能力不取消**:整团免车(waive)进弹窗③「整团不用车」;撤回(withdraw)与受控重开(reopen)仍须保留入口,位置前端自定(见四)。 - **四处以前容易想错的地方**(均已对源码核实): 1. **确认后不能逐户打回**。需求已确认之后还能出现「待审核」行的,只有团期过了资源准备阶段后的接送机变更;这时团期阶段不是 `RESOURCE_PREPARING`,逐户打回报 **589501**。所以已确认态的「审核变更」**只有「通过」没有「打回」**。 2. **整团确认不一定放行全部接送机**。整团免车时一条车需求都不放行(含待审接送机);`needs_vehicle≠1` 的户报了接送机也不在放行集合里。确认成功后必须用响应里的 `transferDispatchedOrderIds` 对账,没放行的另调 `transfer/batch-confirm`。 3. **批量打回端点不覆盖接送机**。`POST …/requirement/reject` 的 `VEHICLE` 只打回行程用车,且整批只收一条原因;新设计每户各有原因、要分 kind 打回,**不用它**,改逐户打回。 4. **逐户打回(任何 kind)都会清团级确认标记**。所以同一次提交里,打回必须排在 `requirement/confirm` **之前**调;顺序反了,刚写上的确认标记会被打回清掉。 --- ## 一、背景(选填) 运营反馈原页签按钮多、确认前要在四个板块之间来回切,确认失败时不知道卡在哪一户。新设计把「看」和「审」分开:页签只读,所有写操作收进一个弹窗,底部按打回情况自动切换三种提交模式。 **本条与今天两条既有条目的关系**(两条都已由前端交付,与本条不冲突): | 既有条目 | 前端提交 | 与本条的关系 | |------|------|------| | `changelogs-v2/2026-09/29_frontend_团期详情用车汇总行程用车与接送机改逐条列出-前端优化-管理后台.md` | `a73ef932` | 它定的**数据口径**(按 `kind` 拆行程用车与接送机、`statusName` 原文直显、不按 `countedInSummary` 过滤)继续有效;它改的「用车汇总」块在本条被新设计 A 的「用车安排」吸收。**布局以本条为准,字段口径以原条为准。** | | `changelogs-v2/2026-09/29_frontend_团期详情用车需求确认弹窗点确认不发请求-前端缺陷-管理后台.md` | `01acf773` | 它修的三处调用点(`VehicleHouseholdsSection.vue:334 / :387`、`RequirementTab.vue:588`)在新设计里随原组件一起删除;**新写的所有确认对话框同样必须用 `onPositiveClick`**,不要用 `onPositive`。 | **涉及文件(hl-ui `origin/v2.1@cdfaf793`,目录 `src/views/order-v2/batch/detail/components/`)**:本节与下文引用的前端行号、行数都取自这个快照,它早于上表两个前端提交 `a73ef932` / `01acf773`,按上表第二条,`:334 / :387 / :588` 三处在你们当前分支上应已改成 `onPositiveClick`,各文件行数也可能有出入;按函数名与按钮文案定位即可。 | 旧组件 | 行数 | 现调接口 | 去向 | |------|------|------|------| | `RequirementTab.vue` | 1055 | confirm-check / confirm / requirement-summary | **保留并重写**为新设计 A;`emit('contact', orderId)` 事件保留(`index.vue:163 → :705 onContactCustomizer` 打开会话抽屉) | | `RoomSummarySection.vue` | 174 | (父级传入 summary) | 并入 A 的住房卡与「逐晚用房」表 | | `VehicleSummarySection.vue` | 160 | (父级传入 summary) | 并入 A 的「用车安排」 | | `RoomHouseholdsSection.vue` | 362 | hotel-households | 并入弹窗①,同时给 A 的逐户表供住房列数据 | | `VehicleHouseholdsSection.vue` | 571 | vehicle-households / batch-confirm / 逐户 vehicle dispatch | 并入弹窗②③ 与已确认态「审核变更」 | | `RequirementRejectModal.vue` | 213 | 批量 reject | **删除**(新设计不用批量打回) | | `GroupVehicleRequirementSection.vue` | 800 | GET vehicle-requirement / reopen / waive / withdraw | 并入弹窗③;reopen / withdraw 入口位置前端自定 | | `GroupVehicleRequirementEditModal.vue` | 1048 | aggregate-draft / PUT vehicle-requirement / 车型字典 | 并入弹窗③ | | `HouseholdRequirementModal.vue` | 589 | room-plans | **保留**,由逐户表「详情」打开 | | `__tests__/*.spec.js` | — | — | 随组件增删调整 | API 封装全部已存在,不需要新增:`src/api/orderV2GroupBatch.js`(`:115` 详情、`:279` 状态流水、`:629` 预检、`:656` 确认、`:716` 汇总、`:748` 住房逐户、`:790` 用车逐户、`:819` 接送机批量放行、`:865` 取正式需求、`:889` 重开、`:929` 保存正式需求、`:963` 自动汇总、`:979` 撤回、`:997` 免车);`src/api/orderV2.js`(`:1543` 住房逐户打回、`:1561` 用车逐户打回、`:1577` 住房逐户下发、`:1597` 用车逐户下发)。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 调用方式变更(后端零改动) | 页签头部户数人数、阶段、确认标记 | | 2 | 团期状态流水 | GET | `/v3/admin/order/group-batch/{groupBatchId}/status-logs` | 调用方式变更(后端零改动) | 已确认态「时间 由 某人 确认」 | | 3 | 全团需求汇总 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement-summary` | 调用方式变更(后端零改动) | 三张汇总卡、逐晚用房、接送机汇总 | | 4 | 住房逐户记录 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/hotel-households` | 调用方式变更(后端零改动) | 逐户表住房列、弹窗① | | 5 | 用车逐户记录 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households` | 调用方式变更(后端零改动) | 逐户表车侧两列、弹窗②③、审核变更 | | 6 | 整团确认预检 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` | 调用方式变更(后端零改动) | 状态条预检、弹窗底部缺失提示、大交通声明提示 | | 7 | 取正式用车需求 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 调用方式变更(后端零改动) | 行程用车卡、用车安排矩阵、弹窗③ | | 8 | 正式用车需求自动汇总 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft` | 调用方式变更(后端零改动) | 弹窗③「按户需求重新汇总」 | | 9 | 保存正式用车需求 | PUT | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 调用方式变更(后端零改动) | 弹窗③「仅保存草稿」及确认前落草稿 | | 10 | 整团免车 | POST | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive` | 调用方式变更(后端零改动) | 弹窗③「整团不用车」 | | 11 | 撤回正式用车需求 | POST | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/withdraw` | 调用方式变更(后端零改动) | 已免车或已确认后改分组的前置动作 | | 12 | 整团确认需求 | POST | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` | 调用方式变更(后端零改动) | 模式一、模式二的核心提交 | | 13 | 住房逐户打回 | POST | `/v3/admin/order/{id}/hotel-requirement/reject` | 调用方式变更(后端零改动) | 弹窗①打回 | | 14 | 用车逐户打回 | POST | `/v3/admin/order/{id}/vehicle-requirement/reject` | 调用方式变更(后端零改动) | 弹窗②打回(kind=TRANSFER)、弹窗③打回(kind=TRAVEL) | | 15 | 住房逐户下发 | POST | `/v3/admin/order/{id}/hotel-requirement/dispatch` | 调用方式变更(后端零改动) | 已确认态「审核变更」住房通过 | | 16 | 用车逐户下发 | POST | `/v3/admin/order/{id}/vehicle-requirement/dispatch` | 调用方式变更(后端零改动) | 已确认态「审核变更」车侧通过 | | 17 | 接送机批量放行 | POST | `/v3/admin/order/group-batch/{groupBatchId}/requirement/transfer/batch-confirm` | 调用方式变更(后端零改动) | 确认后补放行接送机、已确认态接送机批量通过 | --- ## 三、接口详情 ### 1. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` **VO**: `(无请求体) → GroupBatchDetailRespVO` #### 使用场景 页签头部的户数、人数、阶段,以及「待确认态 / 已确认态」的判定。出团详情页 `index.vue` 已经在拉这个接口(封装 `getGroupBatchDetail`,`orderV2GroupBatch.js:115`),由父组件把结果传进页签即可,不必重复请求。弹窗提交成功后要重拉一次,因为 `requirementConfirmed` 会变。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID(`GroupBatchDetailRespVO.java:34`) | | batchNo | String | 班期编号(`:48`) | | batchStatus | String | 团期阶段码,枚举见六.5(`:58`) | | batchStatusName | String | 阶段中文名(`:61`) | | enrolledPeople | Integer | 活跃子订单人数合计,页头「N 人」与用车矩阵「在团人数」用它(`:130`) | | subOrderCount | Integer | 活跃子订单户数,页头「N 户」用它(`:152`) | | vehicleReady | Boolean | 配车完成或整团免车成立(`:164`)。**不是**「已下发车务」,别用它渲染已确认态的「用车已下发车务」 | | requirementConfirmed | Boolean | 需求整体确认标志(`:176`)。false=待确认态,true=已确认态 | | departDate | LocalDate | 出发日(`:248`) | | endDate | LocalDate | 结束日(`:251`) | 本页只用上表字段,其余字段不在本条范围。 #### 请求示例 ```http GET /v3/admin/order/group-batch/2100856430494973953 ``` #### 响应示例 按 VO 结构组装,取值为示意,不是测试服读数(只列本页用到的字段): ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2100856430494973953", "batchNo": "T26-8867", "batchStatus": "RESOURCE_PREPARING", "batchStatusName": "资源准备中", "enrolledPeople": 7, "subOrderCount": 2, "vehicleReady": false, "requirementConfirmed": false, "departDate": "2026-10-08", "endDate": "2026-10-10" }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 无空数据形态:团期不存在直接报 589500(`requireById`)。`enrolledPeople / subOrderCount` 在没有活跃子订单时为 0。 #### 错误响应 ```json { "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:view`(`GroupBatchQueryController.java:113`),另有数据级归属校验:定制师角色只能看自己名下的团期,否则 589507。 - 团期不存在报 589500「团期不存在」。 - `requirementConfirmed` 会被后端自动清回 false:资源准备阶段有户改住房或用车需求、有户转团时都会清(`GroupBatchService.java:2629` `reopenRequirementConfirmation`)。页面不要缓存这个值做判断,每次进入页签与每次弹窗提交后都以最新详情为准。 --- ### 2. 团期状态流水 `GET /v3/admin/order/group-batch/{groupBatchId}/status-logs` **VO**: `(无请求体) → List` #### 使用场景 已确认态状态条的「{时间} 由 {人} 确认」。取法:列表按时间**升序**、不分页,取 `eventType = BATCH_REQUIREMENT_CONFIRM` 的**最后一条**,用它的 `changedAt` 与 `operatorName`。封装 `getGroupBatchStatusLogs`(`orderV2GroupBatch.js:279`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result>` | 字段 | 类型 | 说明 | |------|------|------| | logId | Long(JSON 字符串) | 流水 ID | | groupBatchId | Long(JSON 字符串) | 团期 ID | | changeType | String | 变更大类 | | eventType | String | 事件类型,本页只认三个值,见六.5 | | eventTypeName | String | 事件中文名 | | fromStatus / fromStatusName | String | 变更前团期阶段(数据类事件可能为空) | | toStatus / toStatusName | String | 变更后团期阶段 | | content | String | 事件描述 | | reason | String | 原因(打回类事件为打回原因) | | operatorType | String | 操作人类型 | | operatorId | Long | 操作人 ID | | operatorName | String | 操作人姓名,状态条「由 某人 确认」用它 | | extra | Object | 扩展信息;逐户打回写入的 extra 只含本户 orderId | | changedAt | LocalDateTime | 事件时间,状态条时间用它 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2100856430494973953/status-logs ``` #### 响应示例 按 VO 结构组装,取值为示意,不是测试服读数: ```json { "code": 200, "message": "成功", "data": [ { "logId": "2105000000000000001", "groupBatchId": "2100856430494973953", "changeType": "DATA", "eventType": "BATCH_REQUIREMENT_CONFIRM", "eventTypeName": "需求整体确认", "fromStatus": null, "fromStatusName": null, "toStatus": null, "toStatusName": null, "content": "需求整体确认", "reason": null, "operatorType": "ADMIN", "operatorId": "2021059720172838914", "operatorName": "王骁", "extra": null, "changedAt": "2026-09-29 16:20:00" } ], "traceId": null, "success": true } ``` #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "data": [], "traceId": null, "success": true } ``` 团期不存在或没有任何流水时返回空数组。已确认但列表里找不到 `BATCH_REQUIREMENT_CONFIRM` 时,状态条**不展示**「时间 由 某人 确认」这一行,其余照常。 #### 错误响应 ```json { "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:view`,在 Service 入口校验(`GroupBatchStatusLogService.java:203`)。 - 时间线写入失败只记日志、不回滚业务,所以「已确认但没有确认流水」是可能出现的形态,前端必须容忍(见上节降级)。 - 取「最后一条」而不是「第一条」:确认 → 被打回或自动清标记 → 再确认,会留下多条 `BATCH_REQUIREMENT_CONFIRM`。 - 本条对「确认人取自状态流水」这一推导未在测试服实测。 --- ### 3. 全团需求汇总 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary` **VO**: `(无请求体) → GroupRequirementSummaryRespVO` #### 使用场景 新设计 A 的住房卡、接送机卡、逐晚用房表、特殊需求户数。进入页签调用一次,弹窗提交成功后重拉。封装 `getGroupRequirementSummary`(`orderV2GroupBatch.js:716`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | activeOrderCount | Integer | 在团子订单数 | | hotelRequirementCount | Integer | 住房需求条数 | | hotelNeededOrderCount | Integer | 需要住房的户数,住房卡「N/N 户已提交」的分母 | | hotelFlagMismatchOrderCount | Integer | 住房标记与需求不一致的户数 | | hotelSubmittedOrderCount | Integer | 已提交住房需求的户数,「N/N 户已提交」的分子 | | vehicleRequirementCount | Integer | 用车需求条数 | | dailyRoomBreakdown[] | Array | 逐晚用房,**只计已提交且未被打回的户,不含客户自订的晚;全团都自订的晚没有条目** | | dailyRoomBreakdown[].dayNumber | Integer | 第几晚 | | dailyRoomBreakdown[].stayDate | LocalDate | 入住日 | | dailyRoomBreakdown[].rooms[] | Array | 该晚按房型大类合计:`roomCategory` / `roomCategoryName` / `totalRoomCount` | | dailyRoomBreakdown[].hotels[] | Array | 该晚按酒店拆:`hotelId` / `hotelName` / `totalRoomCount` / `rooms[]`(同上三字段) | | vehicleSeatSummary[] | Array | 行程用车按车型合计(**只算 TRAVEL**):`vehicleType` / `vehicleTypeName` / `totalSeats` / `totalCount` | | orderSpecialTags[] | Array | 各户特殊需求:`orderId` / `teamNo` / `requirementType`(HOTEL / VEHICLE)/ `specialTags`(String 数组) | | transferSummary | Object | 接送机汇总,**恒不为 null** | | transferSummary.householdCount | Integer | 报了接送机的户数 | | transferSummary.pickupHouseholdCount | Integer | 要接机的户数 | | transferSummary.dropoffHouseholdCount | Integer | 要送机的户数 | | transferSummary.headcount | Integer | 接送机总人数 | | transferSummary.vehicleSeatSummary[] | Array | 接送机按车型:`vehicleType` / `vehicleTypeName` / `seats` / `count` | | transferSummary.serviceDates[] | LocalDate[] | 接送机涉及日期的并集(**不带方向**) | #### 请求示例 ```http GET /v3/admin/order/group-batch/2100856430494973953/requirement-summary ``` #### 响应示例 测试服实测原文(2026-09-29 16:38,经网关,格式化排版,内容未改): ```json { "code": 200, "message": "成功", "data": { "activeOrderCount": 2, "hotelRequirementCount": 2, "hotelNeededOrderCount": 2, "hotelFlagMismatchOrderCount": 0, "hotelSubmittedOrderCount": 2, "vehicleRequirementCount": 2, "dailyRoomBreakdown": [ { "dayNumber": 1, "stayDate": "2026-10-08", "rooms": [ { "roomCategory": "DELUXE", "roomCategoryName": "豪华房", "totalRoomCount": 1 }, { "roomCategory": "KING", "roomCategoryName": "豪华大床", "totalRoomCount": 1 }, { "roomCategory": "STANDARD", "roomCategoryName": "标间", "totalRoomCount": 1 } ], "hotels": [ { "hotelId": "2023714929877450753", "hotelName": "呼伦贝尔香格里拉大酒店", "totalRoomCount": 3, "rooms": [ { "roomCategory": "DELUXE", "roomCategoryName": "豪华房", "totalRoomCount": 1 }, { "roomCategory": "KING", "roomCategoryName": "豪华大床", "totalRoomCount": 1 }, { "roomCategory": "STANDARD", "roomCategoryName": "标间", "totalRoomCount": 1 } ] } ] }, { "dayNumber": 2, "stayDate": "2026-10-09", "rooms": [ { "roomCategory": "STANDARD", "roomCategoryName": "标间", "totalRoomCount": 3 } ], "hotels": [ { "hotelId": "3001000000000000002", "hotelName": "海拉尔海棠酒店", "totalRoomCount": 3, "rooms": [ { "roomCategory": "STANDARD", "roomCategoryName": "标间", "totalRoomCount": 3 } ] } ] } ], "vehicleSeatSummary": [ { "vehicleType": "bus", "vehicleTypeName": "大巴系列", "totalSeats": 38, "totalCount": 2 } ], "orderSpecialTags": [ { "orderId": "2100856430239121409", "teamNo": "26-7060", "requirementType": "HOTEL", "specialTags": ["禁烟"] }, { "orderId": "2102309919002943489", "teamNo": "26-2355", "requirementType": "HOTEL", "specialTags": ["含早餐", "景观房", "高楼层"] }, { "orderId": "2102309919002943489", "teamNo": "26-2355", "requirementType": "VEHICLE", "specialTags": ["儿童安全座椅"] } ], "transferSummary": { "householdCount": 2, "pickupHouseholdCount": 2, "dropoffHouseholdCount": 2, "headcount": 7, "vehicleSeatSummary": [ { "vehicleType": "suv", "vehicleTypeName": "SUV系列", "seats": 5, "count": 1 }, { "vehicleType": "mpv", "vehicleTypeName": "商务车", "seats": 7, "count": 1 } ], "serviceDates": ["2026-10-08", "2026-10-10", "2026-10-11"] } }, "traceId": null, "success": true } ``` 按这份实测数据,住房卡应显示:峰值 3 间/晚(两晚各 3 间取最大)、间夜 6、酒店 2 家;接送机卡应显示:2 户 · 7 人,接机 2 户、送机 2 户,SUV系列 5座 ×1 · 商务车 7座 ×1。 #### 空数据 / 降级响应 没有任何户提交时:`dailyRoomBreakdown`、`vehicleSeatSummary`、`orderSpecialTags` 为空数组;`transferSummary` 仍是对象,计数为 0、数组为空。 ```json { "code": 200, "message": "成功", "data": { "activeOrderCount": 2, "hotelRequirementCount": 0, "hotelNeededOrderCount": 2, "hotelFlagMismatchOrderCount": 0, "hotelSubmittedOrderCount": 0, "vehicleRequirementCount": 0, "dailyRoomBreakdown": [], "vehicleSeatSummary": [], "orderSpecialTags": [], "transferSummary": { "householdCount": 0, "pickupHouseholdCount": 0, "dropoffHouseholdCount": 0, "headcount": 0, "vehicleSeatSummary": [], "serviceDates": [] } }, "traceId": null, "success": true } ``` (按 VO 结构组装,取值为示意。) #### 错误响应 ```json { "code": 589500, "message": "团期不存在", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:view`(`GroupBatchRequirementController.java:91`);无权 589507。团期不存在 589500。 - 汇总口径是「在团」子订单(只排除已取消),被打回的户不计入逐晚用房。 - 逐晚用房**没有**全团自订晚的条目:晚数不能用 `dailyRoomBreakdown.length` 算,要用 `endDate − departDate`(见四「字段来源」)。 - `vehicleSeatSummary` 只含行程用车;接送机车型看 `transferSummary.vehicleSeatSummary`,两者不要相加。 --- ### 4. 住房逐户记录 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/hotel-households` **VO**: `(无请求体) → GroupHotelHouseholdsRespVO` #### 使用场景 逐户表的住房列、弹窗①的逐户审核列表、逐晚用房表的「城市」和「客户自订」备注。封装 `getGroupHotelHouseholds`(`orderV2GroupBatch.js:748`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID | | departDate / endDate | LocalDate | 出发日 / 结束日 | | householdCount | Integer | 需要住房的户数(与汇总 `hotelNeededOrderCount` 同口径) | | countedHouseholdCount | Integer | 计入汇总的户数 | | households[] | Array | 逐户,按 `orderNo` 升序 | | households[].orderId | Long(JSON 字符串) | 子订单 ID,逐户打回 / 下发的路径参数 `{id}` 用它 | | households[].orderNo | String | 订单号 | | households[].teamNo | String | 团号,可能为 null | | households[].customerName | String | 客户名 | | households[].participantCount | Integer | 该户人数 | | households[].consultantId / consultantName | Long / String | 定制师 | | households[].status | String | 住房需求状态,见六.5;**未提交为 null**;打回为 `REJECTED_*` | | households[].statusName | String | 状态中文名,直接显示 | | households[].countedInSummary | Boolean | 是否计入汇总;打回态为 false | | households[].remark | String | 备注 | | households[].specialTags | String[] | 特殊需求 | | households[].returnRemark / returnedAt | String / LocalDateTime | 最近一次打回原因与时间 | | households[].days[] | Array | 逐晚:`dayNumber` / `stayDate` / `customerSelfBooked` / `hotels[]` | | households[].days[].hotels[] | Array | `hotelId` / `hotelName` / `city` / `district` / `totalRoomCount` / `rooms[]` | | households[].days[].hotels[].rooms[] | Array | `roomTypeId` / `roomTypeName` / `roomCategory` / `roomCategoryName` / `roomCount` | #### 请求示例 ```http GET /v3/admin/order/group-batch/2101506167098511362/requirement/hotel-households ``` #### 响应示例 按 VO 结构组装,ID 取 VO 自带示例值,其余取值为示意,不是测试服读数: ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2101506167098511362", "departDate": "2026-10-08", "endDate": "2026-10-10", "householdCount": 1, "countedHouseholdCount": 1, "households": [ { "orderId": "2101506167043985410", "orderNo": "HL20260920101530112", "teamNo": "26-0480", "customerName": "张丽", "participantCount": 3, "consultantId": "2021059720172838914", "consultantName": "王骁", "status": "PENDING_REVIEW", "statusName": "待审核", "countedInSummary": true, "remark": "老人同行,尽量安排低楼层", "specialTags": ["禁烟"], "returnRemark": null, "returnedAt": null, "days": [ { "dayNumber": 1, "stayDate": "2026-10-08", "customerSelfBooked": false, "hotels": [ { "hotelId": "2023714929877450753", "hotelName": "呼伦贝尔香格里拉大酒店", "city": "呼伦贝尔", "district": "海拉尔区", "totalRoomCount": 2, "rooms": [ { "roomTypeId": "2023714929877450801", "roomTypeName": "高级双床房", "roomCategory": "STANDARD", "roomCategoryName": "标间", "roomCount": 2 } ] } ] } ] } ] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2101506167098511362", "departDate": "2026-10-08", "endDate": "2026-10-10", "householdCount": 0, "countedHouseholdCount": 0, "households": [] }, "traceId": null, "success": true } ``` (按 VO 结构组装。)需要住房但一次都没提交的户**也会列出**,`status` 为 null、`days` 为空。 #### 错误响应 ```json { "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:view`;团期不存在 589500。 - 被打回的户照样列出(`countedInSummary=false`、`status` 为 `REJECTED_TO_CONSULTANT`),用于「已打回」筛选;它不计入逐晚用房。 - 本列表的「计入汇总的户」逐晚加总 = 汇总接口的 `dailyRoomBreakdown`,两处数字应一致。 - `city` 只在这里有,汇总接口的 `hotels[]` 没有城市;逐晚用房表的城市按 `hotelId` 关联本接口取。 --- ### 5. 用车逐户记录 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households` **VO**: `(无请求体) → GroupVehicleHouseholdsRespVO` #### 使用场景 逐户表的「接送机」「行程用车」两列,弹窗②(接送机)与弹窗③下半「各户行程用车需求」,已确认态「审核变更」的车侧待审行。封装 `getGroupVehicleHouseholds(groupBatchId, kind)`(`orderV2GroupBatch.js:790`)。**不传 `kind` 一次返回两类**,前端按 `requirements[].kind` 拆;沿用既有条目按 kind 分两次取也可以。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | | kind | Query | String | ❌ | `TRAVEL` / `TRANSFER`,其他值报 809000 | 不传返回两类 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID | | departDate / endDate | LocalDate | 出发日 / 结束日 | | householdCount | Integer | 应报车的户数(含一条都没提交的户),行程用车卡「N/N 户已报」的分母 | | vehicleRowCount | Integer | 返回的需求行条数 | | countedHouseholdCount | Integer | 有活跃行程用车行的户数,「N/N 户已报」的分子 | | households[].orderId | Long(JSON 字符串) | 子订单 ID | | households[].orderNo / teamNo / customerName | String | 订单号 / 团号 / 客户名 | | households[].participantCount | Integer | 该户人数 | | households[].consultantId / consultantName | Long / String | 定制师 | | households[].countedInSummary | Boolean | 是否计入汇总 | | households[].status / statusName | String | 户级状态:**null=该户一条都没提交**;非 null 时取展示顺序首条(TRAVEL 优先)的状态 | | households[].requirements[] | Array | 0~2 条(每个 kind 至多一条);**被打回的行已失活,不在这里** | | requirements[].requirementId | Long(JSON 字符串) | 需求行 ID | | requirements[].kind / kindName | String | `TRAVEL` 行程用车 / `TRANSFER` 接送机 | | requirements[].status / statusName | String | `PENDING_REVIEW` / `PENDING` / `PROCESSING` / `DONE`;`statusName` 原文直显(TRAVEL 待审显示「待提交车务」) | | requirements[].fleet[] | Array | `vehicleType` / `vehicleTypeName` / `seats` / `count` | | requirements[].specialTags[] | Array | `code` / `name` | | requirements[].remark | String | 备注 | | requirements[].serviceDates | LocalDate[] | 服务日期;接送机的日期**不带方向** | | requirements[].headcount | Integer | 人数 | | requirements[].totalSeatCount | Integer | 总座位数 | | requirements[].remainingPassengerSeats | Integer | 扣司机座与人数后的剩余座位 | | requirements[].pickupRequired / dropoffRequired | Boolean | 是否接机 / 送机,**仅 TRANSFER 有值**,TRAVEL 为 null | | requirements[].returnRemark / returnedAt | String / LocalDateTime | 最近一次打回原因与时间 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2100856430494973953/requirement/vehicle-households ``` #### 响应示例 测试服实测原文(2026-09-29 16:38,经网关,未传 kind;备注字段为测试数据原样保留): ```json {"code":200,"message":"成功","data":{"groupBatchId":"2100856430494973953","departDate":"2026-10-08","endDate":"2026-10-10","householdCount":2,"vehicleRowCount":4,"countedHouseholdCount":2,"households":[{"orderId":"2100856430239121409","orderNo":"HL20260918155619496","teamNo":"26-7060","customerName":"王有亿","participantCount":3,"consultantId":"2021059720172838914","consultantName":"王骁","countedInSummary":true,"status":"PENDING_REVIEW","statusName":"待提交车务","requirements":[{"requirementId":"2100883541330944002","kind":"TRAVEL","kindName":"行程用车","status":"PENDING_REVIEW","statusName":"待提交车务","fleet":[{"vehicleType":"bus","vehicleTypeName":"大巴系列","seats":19,"count":1}],"specialTags":[],"remark":"#7949 AC-2 定制师提交","serviceDates":["2026-10-08","2026-10-09","2026-10-10"],"headcount":3,"totalSeatCount":19,"remainingPassengerSeats":15,"pickupRequired":null,"dropoffRequired":null,"returnRemark":null,"returnedAt":null},{"requirementId":"2102202475291549698","kind":"TRANSFER","kindName":"接送机","status":"PENDING_REVIEW","statusName":"待审核","fleet":[{"vehicleType":"suv","vehicleTypeName":"SUV系列","seats":5,"count":1}],"specialTags":[{"code":"儿童安全座椅","name":"儿童安全座椅"}],"remark":"444444","serviceDates":["2026-10-08","2026-10-11"],"headcount":3,"totalSeatCount":5,"remainingPassengerSeats":1,"pickupRequired":true,"dropoffRequired":true,"returnRemark":null,"returnedAt":null}]},{"orderId":"2102309919002943489","orderNo":"HL20260922161158291","teamNo":"26-2355","customerName":"王二麻子","participantCount":4,"consultantId":"2083111674486693889","consultantName":"刘畅","countedInSummary":true,"status":"PENDING_REVIEW","statusName":"待提交车务","requirements":[{"requirementId":"2104838140441280514","kind":"TRAVEL","kindName":"行程用车","status":"PENDING_REVIEW","statusName":"待提交车务","fleet":[{"vehicleType":"bus","vehicleTypeName":"大巴系列","seats":19,"count":1}],"specialTags":[{"code":"儿童安全座椅","name":"儿童安全座椅"}],"remark":"12121221","serviceDates":["2026-10-08","2026-10-09","2026-10-10"],"headcount":4,"totalSeatCount":19,"remainingPassengerSeats":14,"pickupRequired":null,"dropoffRequired":null,"returnRemark":null,"returnedAt":null},{"requirementId":"2104838140579692545","kind":"TRANSFER","kindName":"接送机","status":"PENDING_REVIEW","statusName":"待审核","fleet":[{"vehicleType":"mpv","vehicleTypeName":"商务车","seats":7,"count":1}],"specialTags":[{"code":"儿童安全座椅","name":"儿童安全座椅"}],"remark":"32","serviceDates":["2026-10-08","2026-10-10"],"headcount":4,"totalSeatCount":7,"remainingPassengerSeats":2,"pickupRequired":true,"dropoffRequired":true,"returnRemark":null,"returnedAt":null}]}]},"traceId":null,"success":true} ``` 注意同一户 TRAVEL 待审的 `statusName` 是「待提交车务」、TRANSFER 待审是「待审核」,两者 `status` 都是 `PENDING_REVIEW`。判断用 `status`,显示用 `statusName`。 #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2100856430494973953", "departDate": "2026-10-08", "endDate": "2026-10-10", "householdCount": 0, "vehicleRowCount": 0, "countedHouseholdCount": 0, "households": [] }, "traceId": null, "success": true } ``` (按 VO 结构组装。)应报车但一条都没提交的户会列出,`status` 为 null、`requirements` 为空数组。 #### 错误响应 ```json { "code": 809000, "message": "用车需求类别非法:BUS", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `用车需求类别非法:{0}` 组装。) #### 业务边界 - 判权 `group-batch:view`;团期不存在 589500;`kind` 非法 809000。 - **车侧没有「已打回」可读**:打回后该行失活、从 `requirements` 里消失,读数与「未提交」相同。逐户表「已打回」筛选只能覆盖住房侧(见四「字段缺口」)。 - 列表范围 = 在团户里 `needs_vehicle=1` 的户,并上有活跃用车需求行的户。下单时 `needs_vehicle` 恒写 true,所以实际上就是全部在团户;没报任何用车的户也在列表里,`status` 为 null、`requirements` 为 `[]`。 - 最多 500 户(超出截断),按 `orderNo` 升序(空值排最后),同值再按 `orderId`。 - 一户最多一条 TRAVEL、一条 TRANSFER。 --- ### 6. 整团确认预检 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` **VO**: `(无请求体) → GroupBatchRequirementCheckRespVO` #### 使用场景 状态条「预检通过,可以确认 / 还有 N 项未就绪」、弹窗底部 `missingText`、大交通声明提示条。进入页签调用一次;弹窗打开时、弹窗③保存草稿或免车后各重拉一次。封装 `confirmGroupBatchRequirementCheck`(`orderV2GroupBatch.js:629`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID | | batchStatus / batchStatusName | String | 团期阶段 | | ready | Boolean | `missing` 为空 且 `vehicleMissing` 为空 且 阶段为 `RESOURCE_PREPARING` 时为 true | | missing[] | Array | 住房侧缺失:`orderId` / `teamNo` / `orderNo` / `customerName` / `consultantId` / `consultantName` / `reason` / `reasonName` / `dayNumber` / `segmentIndex` / `expectedNights` / `actualNights`;`reason` 见六.5 | | checkedResourceTypes | String[] | 本次检查了哪些资源类型 | | vehicleWaived | Boolean | 是否已声明整团免车 | | vehicleMissing[] | Array | 车侧违规:`reason` / `groupCode` / `tripDate` / `orderId` / `teamNo` / `orderNo` / `detail`;`reason` 见六.5。免车时恒为空 | | vehicleExemptHouseholds | Array | 豁免用车的户 | | groupVehicleRequirementId / Status / Version | Long / String / Integer | 当前活跃正式用车需求;没有时为 null | | transferSubmitEnabled | Boolean | 接送机提交开关状态 | | transferDeclaredWithoutRequirement[] | Array | 大交通里声明了接送、但没报接送机需求的户:`orderId` / `teamNo` / `orderNo` / `customerName` / `consultantId` / `consultantName` / `pickupRequired` / `dropoffRequired` / `pickupRemark`。**只是提示,不进 ready、不阻断确认** | #### 请求示例 ```http GET /v3/admin/order/group-batch/2104327837991518210/requirement/confirm-check ``` #### 响应示例 测试服实测原文(2026-09-27,整团免车的团期): ```json {"code":200,"message":"成功","data":{"groupBatchId":"2104327837991518210","batchStatus":"RESOURCE_PREPARING","batchStatusName":"资源准备中","ready":true,"missing":[],"checkedResourceTypes":["HOTEL","VEHICLE"],"vehicleWaived":true,"vehicleMissing":[],"vehicleExemptHouseholds":[],"groupVehicleRequirementId":"2104331168285671426","groupVehicleRequirementStatus":"CONFIRMED","groupVehicleRequirementVersion":1,"transferSubmitEnabled":true,"transferDeclaredWithoutRequirement":[]},"traceId":null,"success":true} ``` #### 空数据 / 降级响应 没有降级形态。团期阶段不对时照常返回 200,`ready=false`,`missing` 与 `vehicleMissing` 可能都为空——此时 `ready=false` 的原因就是阶段,按 `batchStatusName` 提示。 #### 错误响应 ```json { "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:demand:confirm`(`GroupBatchRequirementController.java:160`)。只有 view 权限的角色调它会 589507,页签要能在拿不到预检时照常渲染只读部分,并隐藏主按钮。 - 车侧检查顺序:团级六条(809100 / 809101 / 809103~809110 对应的原因)→ 户级未提交行程用车(`HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED`)→ 待放行接送机未回填服务日(`TRANSFER_SERVICE_DATES_NOT_BACKFILLED`)→ 接送机窗不全(`TRANSFER_WINDOW_INCOMPLETE`)→ 户车型与分组车型不符(`MEMBER_GROUP_MISMATCH`)。 - 整团免车时车侧检查整段跳过(`vehicleMissing` 恒空,809007 也不查),`transferDeclaredWithoutRequirement` 不受免车影响照常给。 - 确认时后端按同一套规则再算一遍,预检通过不等于确认一定成功(中间可能有户改了需求),确认失败按错误码处理。 --- ### 7. 取正式用车需求 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` **VO**: `(无请求体) → GroupVehicleRequirementRespVO` #### 使用场景 行程用车卡(状态、组数、逐日覆盖、车型)、「用车安排」的分组×日期矩阵、弹窗③打开时的初始草稿。封装 `getGroupVehicleRequirement`(`orderV2GroupBatch.js:865`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | requirementId | Long(JSON 字符串) | 正式需求 ID | | groupBatchId | Long(JSON 字符串) | 团期 ID | | status | String | 见六.5 `GroupVehicleRequirementStatus` | | version | Integer | 版本号,PUT 时原样带回 | | remark | String | 整份备注 | | confirmedBy / confirmedAt | String / LocalDateTime | 确认人 / 确认时间 | | planRefreshState / planRefreshReplayCount / blockedStage / planRefreshStalled / planRefreshStalledReason / planRefreshTimeoutAt / planRefreshReplayExhausted | — | 受控重开链路专用,普通流程为空,本页不展示 | | groups[] | Array | 乘车分组;**`status=CONFIRMED` 且 `groups` 为空 = 整团免车** | | groups[].groupId / groupCode | Long / String | 分组 ID / 分组编码(如 BUS、BUS2) | | groups[].vehicleType / vehicleTypeName | String | 车型 | | groups[].seats / count | Integer | 单车座位 / 车辆数 | | groups[].serviceStartDate / serviceEndDate | LocalDate | 本组服务起止 | | groups[].specialTags[] | Array | `code` / `name` | | groups[].remark | String | 组备注 | | groups[].totalSeatCount / maxHeadcount / remainingPassengerSeats | Integer | 总座位 / 最大单日人数 / 剩余座位 | | groups[].days[] | Array | 逐日:`tripDate` / `headcount` / `memberOrderIds` / `memberOrderCount` | | exemptHouseholds | Array | 豁免用车的户 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2100856430494973953/vehicle-requirement ``` #### 响应示例 按 VO 结构组装,取值为示意,不是测试服读数: ```json { "code": 200, "message": "成功", "data": { "requirementId": "2104900000000000001", "groupBatchId": "2100856430494973953", "status": "DRAFT", "version": 1, "remark": null, "confirmedBy": null, "confirmedAt": null, "planRefreshState": null, "planRefreshReplayCount": null, "blockedStage": null, "planRefreshStalled": null, "planRefreshStalledReason": null, "planRefreshTimeoutAt": null, "planRefreshReplayExhausted": null, "groups": [ { "groupId": "2104900000000000011", "groupCode": "BUS", "vehicleType": "bus", "serviceStartDate": "2026-10-08", "serviceEndDate": "2026-10-10", "vehicleTypeName": "大巴系列", "seats": 19, "count": 1, "specialTags": [{ "code": "儿童安全座椅", "name": "儿童安全座椅" }], "remark": null, "totalSeatCount": 19, "maxHeadcount": 7, "remainingPassengerSeats": 11, "days": [ { "tripDate": "2026-10-08", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"], "memberOrderCount": 2 }, { "tripDate": "2026-10-09", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"], "memberOrderCount": 2 }, { "tripDate": "2026-10-10", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"], "memberOrderCount": 2 } ] } ], "exemptHouseholds": [] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "data": null, "traceId": null, "success": true } ``` 还没形成正式需求时 `data=null`:行程用车卡显示「未形成正式需求」,矩阵不画。 #### 错误响应 ```json { "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:demand:confirm`(**不是 view**)。只有 view 权限的角色看页签时这里会 589507,行程用车卡与用车矩阵要降级为「无权查看」而不是整页报错。 - 整团免车的判定只能用「`status=CONFIRMED` 且 `groups` 为空」,没有单独的布尔字段;预检接口的 `vehicleWaived` 可以作为交叉核对。 - `version` 是乐观锁,编辑前取、保存时带回,页面停留期间被别人改过会 809102。 --- ### 8. 正式用车需求自动汇总 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft` **VO**: `(无请求体) → GroupVehicleAggregateDraftRespVO` #### 使用场景 弹窗③「按户需求重新汇总」,以及还没有正式需求时打开弹窗③的首次自动汇总。只计算、不落库;返回的 `draft` 可以原样作为 PUT 的请求体。封装 `getGroupVehicleAggregateDraft`(`orderV2GroupBatch.js:963`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID | | currentStatus | String | 当前活跃正式需求状态,没有时为 null | | draft | GroupVehicleRequirementSaveReqVO | 汇总出的草稿,结构同第 9 节请求体,可原样 PUT | | droppedFleetItems[] | Array | 汇总时被舍弃的车型:`orderId` / `teamNo` / `orderNo` / `vehicleType` / `seats` / `count` / `keptVehicleType` / `reason` | | 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`(Integer,809 段码)/ `reason` / `detail` / `groupCode` / `tripDate` / `orderId` / `teamNo` | | exemptHouseholds | Array | 豁免用车的户 | #### 请求示例 ```http GET /v3/admin/order/group-batch/2100856430494973953/vehicle-requirement/aggregate-draft ``` #### 响应示例 按 VO 结构组装,取值为示意,不是测试服读数: ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2100856430494973953", "currentStatus": null, "draft": { "version": null, "remark": null, "groups": [ { "groupId": null, "groupCode": "BUS", "vehicleType": "bus", "serviceStartDate": "2026-10-08", "serviceEndDate": "2026-10-10", "seats": 19, "count": 1, "specialTags": ["儿童安全座椅"], "remark": null, "days": [ { "tripDate": "2026-10-08", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"] }, { "tripDate": "2026-10-09", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"] }, { "tripDate": "2026-10-10", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"] } ] } ] }, "droppedFleetItems": [], "staleHeadcountOrders": [], "paddedOrderDays": [], "seatOptionAdjusted": [], "violations": [], "exemptHouseholds": [] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 没有降级形态:车型字典读取失败时直接报 809120(见下),有户还没提交行程用车时报 809121,都不会返回半份草稿。 #### 错误响应 ```json { "code": 809121, "message": "团期 「第3期 10月8日出发团」 有 1 户缺少可汇总的行程用车需求,暂不能自动汇总:26-2355:未提交行程用车需求", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `团期 {0} 有 {1} 户缺少可汇总的行程用车需求,暂不能自动汇总:{2}` 组装。`{0}` 是「第N期 班期名」外加一对直角引号,两者都缺时为「(未命名)」;`{2}` 是逐户「团号(缺则订单号):原因」,多户用顿号分隔。) #### 业务边界 - 判权 `group-batch:demand:confirm`。 - 809120:车型字典读取失败,提示用户重试即可。 - 809121:有户的行程用车汇总不出来,不能汇总。原因有四种:未提交行程用车需求、车型均不在车型字典内、推不出用车日期、订单人数为 0。弹窗③下半的逐户列表会显示是哪几户,message 里也逐户点名。 - `violations` 非空时照样可以 PUT 成草稿,但确认会被同样的规则挡住;弹窗③要把 `violations` 逐条显示在对应分组 / 日期旁。 - 只读、无副作用,可以反复调用。 --- ### 9. 保存正式用车需求 `PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` **VO**: `GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO` #### 使用场景 弹窗③「仅保存草稿」;模式一 / 模式二确认前,如果弹窗③里改过分组或还没有正式需求,先 PUT 落成草稿再调 confirm。封装 `saveGroupVehicleRequirement`(`orderV2GroupBatch.js:929`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | | version | Body | Integer | ❌ | 首次保存传 null;之后必须等于当前版本 | 乐观锁 | | remark | Body | String | ❌ | ≤500 | 整份备注 | | groups | Body | Array | ✅ | `@NotNull`,空数组合法 | 乘车分组 | | groups[].groupId | Body | Long | ❌ | 新组为 null | 已有组带回原 ID | | groups[].groupCode | Body | String | ✅ | ≤32,不可改名(改名 = 删旧组加新组) | 分组编码 | | groups[].vehicleType | Body | String | ✅ | ≤64,须在车型字典内 | 车型 | | groups[].serviceStartDate / serviceEndDate | Body | LocalDate | ✅ | `@NotNull` | 本组服务起止 | | groups[].seats | Body | Integer | ❌ | `@Min(1)`,须在该车型可选档位内;与 count 同填或同空 | 单车座位 | | groups[].count | Body | Integer | ❌ | `@Min(1)`;与 seats 同填或同空 | 车辆数 | | groups[].specialTags | Body | String[] | ❌ | 须在字典内 | 特殊诉求 | | groups[].remark | Body | String | ❌ | ≤500 | 组备注 | | groups[].days | Body | Array | ✅ | `@NotEmpty` | 逐日 | | groups[].days[].tripDate | Body | LocalDate | ✅ | 在本组服务日范围内、不重复 | 日期 | | groups[].days[].headcount | Body | Integer | ✅ | `@Min(1)`,不小于当日成员户数 | 当日人数 | | groups[].days[].memberOrderIds | Body | Long[] | ✅ | `@NotEmpty`;须属本团;同户同日只能在一个组 | 当日成员子订单 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | status | String | 保存后恒为 `DRAFT` | | version | Integer | 已 +1,下次保存用它 | | groups / 其余字段 | — | 同第 7 节 | #### 请求示例 ```json { "version": 1, "remark": null, "groups": [ { "groupId": "2104900000000000011", "groupCode": "BUS", "vehicleType": "bus", "serviceStartDate": "2026-10-08", "serviceEndDate": "2026-10-10", "seats": 19, "count": 1, "specialTags": ["儿童安全座椅"], "remark": null, "days": [ { "tripDate": "2026-10-08", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"] }, { "tripDate": "2026-10-09", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"] }, { "tripDate": "2026-10-10", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"] } ] } ] } ``` #### 响应示例 按 VO 结构组装,取值为示意:结构同第 7 节响应示例,`status` 为 `DRAFT`、`version` 为 2。 ```json { "code": 200, "message": "成功", "data": { "requirementId": "2104900000000000001", "groupBatchId": "2100856430494973953", "status": "DRAFT", "version": 2, "remark": null, "confirmedBy": null, "confirmedAt": null, "groups": [ { "groupId": "2104900000000000011", "groupCode": "BUS", "vehicleType": "bus", "vehicleTypeName": "大巴系列", "serviceStartDate": "2026-10-08", "serviceEndDate": "2026-10-10", "seats": 19, "count": 1, "totalSeatCount": 19, "maxHeadcount": 7, "remainingPassengerSeats": 11, "days": [ { "tripDate": "2026-10-08", "headcount": 7, "memberOrderIds": ["2100856430239121409", "2102309919002943489"], "memberOrderCount": 2 } ] } ], "exemptHouseholds": [] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 `groups: []` 是合法请求,但本团有需要用车的户时报 809103;整团不用车应走第 10 节免车,不要 PUT 空分组。 #### 错误响应 ```json { "code": 809102, "message": "正式用车需求已被他人修改(提交版本 1,当前版本 2),请刷新后重试", "data": null, "traceId": null, "success": false } ``` (按错误码模板组装。) #### 业务边界 - 判权 `group-batch:demand:confirm`;阶段须为 `RESOURCE_PREPARING`,否则 589501。 - 错误按后端校验顺序:先在事务外做字典校验 809117(特殊标签)/ 809120(车型字典读不到)/ 809119(车型不在字典内),这三个排在阶段守卫之前 → 团期不存在 589500 → 阶段 589501 → 809115(已免车,须先撤回)→ 809101(当前不是 DRAFT,比如已 CONFIRMED,须先撤回)→ 809102(版本不对)→ 809104(分组重复或改名)→ 809123(有户还没提交行程用车)→ 分组 / 座位 / 档位类只抛第一条(809103、809105~809110、809116、809118、809124)→ 写入时版本被并发推进,仍报 809102。 - 入参校验失败返回 `code=400`,`message` 为注解原文(如「提交备注不能超过 500 字」这类),HTTP 仍是 200。 - 幂等 5 秒,同团重复提交返回 `code=100502`「正式用车需求保存中,请勿重复提交」。 - 保存只落草稿,不下发车务;下发由第 12 节确认完成。 --- ### 10. 整团免车 `POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive` **VO**: `GroupVehicleRequirementWaiveReqVO → GroupVehicleRequirementRespVO` #### 使用场景 弹窗③「整团不用车」。整团不需要平台用车的团期,**不免车就确认不了**(没有正式需求时 confirm 报 809100)。封装 `waiveGroupVehicleRequirement`(`orderV2GroupBatch.js:997`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | | reason | Body | String | ✅ | `@NotBlank`「免车原因不能为空」;≤200「免车原因不能超过 200 字」 | 免车原因 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | status | String | 恒为 `CONFIRMED` | | groups | Array | 恒为空数组 | | 其余字段 | — | 同第 7 节 | #### 请求示例 ```json { "reason": "客户自行包车,全程不需要平台用车" } ``` #### 响应示例 按 VO 结构组装,取值为示意: ```json { "code": 200, "message": "成功", "data": { "requirementId": "2104331168285671426", "groupBatchId": "2104327837991518210", "status": "CONFIRMED", "version": 1, "remark": "客户自行包车,全程不需要平台用车", "confirmedBy": "2021059720172838914", "confirmedAt": "2026-09-27 10:12:00", "groups": [], "exemptHouseholds": [] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 已经免车的团期再调一次按幂等成功处理,返回同样的免车态。 #### 错误响应 ```json { "code": 809114, "message": "车务已开工(2026-10-08:BUS),不能再声明整团免车", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `车务已开工({0}:{1}),不能再声明整团免车` 组装。) #### 业务边界 - 判权 `group-batch:demand:confirm`;阶段须为 `RESOURCE_PREPARING / TRIP_FINISHED / REVIEWING` 之一(`GroupBatchService.java:2468`),否则 589501。 - 带分组的活跃正式需求须为 `DRAFT` 或 `CONFIRMED`,否则 809101(零写入);带分组时失活旧版本、换一份零分组的免车版本;零分组且已是确认态时按幂等成功返回;车务已开工 809114(在写入之前)。 - 免车只是把正式需求置成「已确认且无分组」,**不会**整团确认需求;弹窗仍要走第 12 节 confirm 才算确认。 - 免车后 confirm 不放行任何车需求(含待审接送机),见四「对账补放行」。 - 免车后要改回有车:先第 11 节撤回,再 PUT 分组,否则 PUT 报 809115。 - 幂等 5 秒,重复提交返回 `code=100502`「整团免车声明处理中,请勿重复提交」。 --- ### 11. 撤回正式用车需求 `POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/withdraw` **VO**: `GroupVehicleRequirementWithdrawReqVO → GroupVehicleRequirementRespVO` #### 使用场景 两种情况必须先撤回:①已免车,想改成有车;②正式需求已确认(`CONFIRMED` / `DISPATCHED`),想改分组。原来挂在「更多」菜单里,按钮取消后入口位置前端自定(建议放弹窗③分组区右上角,仅在 `status ∈ {CONFIRMED, DISPATCHED}` 时出现)。封装 `withdrawGroupVehicleRequirement`(`orderV2GroupBatch.js:979`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | | reason | Body | String | ✅ | `@NotBlank`「撤回原因不能为空」;≤200「撤回原因不能超过 200 字」 | 撤回原因 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | status | String | 恒为 `DRAFT` | | version | Integer | 已 +1 | | confirmedBy / confirmedAt | String / LocalDateTime | 已清空 | | 其余字段 | — | 同第 7 节 | #### 请求示例 ```json { "reason": "客户临时增加一天包车,需要重新分组" } ``` #### 响应示例 按 VO 结构组装,取值为示意: ```json { "code": 200, "message": "成功", "data": { "requirementId": "2104331168285671426", "groupBatchId": "2104327837991518210", "status": "DRAFT", "version": 2, "remark": "客户临时增加一天包车,需要重新分组", "confirmedBy": null, "confirmedAt": null, "groups": [], "exemptHouseholds": [] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 没有正式需求时不是空数据,而是报 809100。 #### 错误响应 ```json { "code": 809101, "message": "正式用车需求当前状态 DRAFT 不允许本次操作,期望 [CONFIRMED, DISPATCHED]", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `正式用车需求当前状态 {0} 不允许本次操作,期望 {1}` 组装。`{1}` 是后端集合的 `toString()`,方括号里两个值的先后顺序不固定,前端原样显示 message,不要解析它。) #### 业务边界 - 判权 `group-batch:demand:confirm`;**无团期阶段守卫**。 - 撤回本身不受团期阶段限制,但撤回之后的第 9 节 PUT 与第 12 节 confirm 只在 `RESOURCE_PREPARING` 可用,其他阶段报 589501。团期已过 `RESOURCE_PREPARING` 时,撤回入口要提示这一点。 - 状态须为 `CONFIRMED` 或 `DISPATCHED`,否则 809101;没有正式需求 809100。 - 撤回后:回到 `DRAFT`、`version+1`、原因追加进 `remark`、清确认人与确认时间、复位团期 `vehicleReady`。 - 撤回不清团级 `requirementConfirmed`;改完分组后要再走一次第 12 节 confirm,正式需求才会回到 `CONFIRMED`。 - 幂等 5 秒,重复提交返回 `code=100502`「正式用车需求撤回中,请勿重复提交」。 --- ### 12. 整团确认需求 `POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` **VO**: `(无请求体) → GroupBatchRequirementConfirmRespVO` #### 使用场景 弹窗底部模式一「确认需求并下发」、模式二「打回 N 户并确认其余」的核心一步。一次调用把住房、行程用车、接送机的待审行一起放行,并把正式用车需求推到已确认。封装 `confirmGroupBatchRequirement`(`orderV2GroupBatch.js:656`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数;**无请求体** | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID | | requirementConfirmed | Boolean | 恒为 true | | dispatchedOrderIds | Long[] | 住房侧本次由待审放行的户 | | skippedOrderIds | Long[] | 住房侧本次未动的户(已是 PENDING / PROCESSING / DONE) | | dispatchedCount | Integer | = `dispatchedOrderIds` 长度 | | vehicleDispatchedOrderIds | Long[] | 行程用车(TRAVEL)本次放行的户 | | transferDispatchedOrderIds | Long[] | 接送机(TRANSFER)本次放行的户,**对账补放行就用它** | | vehicleSkippedOrderIds | Long[] | 车侧本次未动的户(两类合并去重);免车时为空数组 | | vehicleDispatchedCount | Integer | 车侧放行的**条数**(不是户数)= 上面两个列表长度之和 | | groupVehicleRequirementId | Long | 正式需求 ID;整团免车且无正式需求时为 null | | groupVehicleRequirementStatus | String | 确认后正式需求的**实际状态**:`DRAFT / PENDING_RECONFIRM → CONFIRMED`;`DISPATCHED / DONE` 原样返回 | | groupVehicleRequirementVersion | Integer | 确认后版本号 | | planRefreshState / planRefreshOutboxId / planRefreshReplayKind / planRefreshReplayCount / blockedStage | — | 受控重开链路专用,普通确认为 null | | advanceUnblocked | Boolean | 恒为 false | #### 请求示例 ```http POST /v3/admin/order/group-batch/2100856430494973953/requirement/confirm ``` #### 响应示例 按 VO 结构组装,取值为示意,不是测试服读数: ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2100856430494973953", "requirementConfirmed": true, "dispatchedOrderIds": ["2100856430239121409", "2102309919002943489"], "skippedOrderIds": [], "dispatchedCount": 2, "vehicleDispatchedOrderIds": ["2100856430239121409", "2102309919002943489"], "transferDispatchedOrderIds": ["2100856430239121409", "2102309919002943489"], "vehicleSkippedOrderIds": [], "vehicleDispatchedCount": 4, "groupVehicleRequirementId": "2104900000000000001", "groupVehicleRequirementStatus": "CONFIRMED", "groupVehicleRequirementVersion": 3, "planRefreshState": null, "planRefreshOutboxId": null, "planRefreshReplayKind": null, "planRefreshReplayCount": null, "blockedStage": null, "advanceUnblocked": false }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 没有降级形态:要么整体成功,要么报错且**零写入**。 #### 错误响应 ```json { "code": 589533, "message": "仍有 1 户未提交需求或需求不完整,无法整体确认,请先查看缺失清单", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `仍有 {0} 户未提交需求或需求不完整,无法整体确认,请先查看缺失清单` 组装。) #### 业务边界 - 判权 `group-batch:demand:confirm`。 - 后端执行顺序(`GroupBatchRequirementService.java:505-584`,同一事务,任一步失败整团零写入):①阶段守卫 589501 → ②住房缺失 589533 → ③车侧违规,抛第一条对应的 809 段码(809100 / 809101 / 809103~809110 / 809122 / 809007 / 809126 / 809125 等)→ ④写确认标记与时间线 → ⑤住房待审户放行 → ⑥正式用车需求推到已确认 → ⑦行程用车与接送机待审行放行 → 提交后通知房务。 - **整团免车时 ⑥⑦ 都不做**:待审的行程用车与接送机一条都不放行(`:518-520`、`:564`)。 - **放行集合只含在团、`needs_vehicle=1`、未退团的户**(`:1206-1211`):`needs_vehicle≠1` 的户报了接送机,这里也不放行。 - 上两条的后果:确认成功后前端必须用 `transferDispatchedOrderIds` 对账,见四「对账补放行」。 - 同一团期的确认与逐户打回共用一把团级锁。 - 幂等 5 秒,重复提交返回 `code=100502`「需求确认处理中,请勿重复提交」。 - 受控重开链路(正式需求处于 `PENDING_RECONFIRM`)下还可能报 809204(车务配车尚未覆盖完整)、809210(刷新重试次数已达上限);普通确认不会遇到。 --- ### 13. 住房逐户打回 `POST /v3/admin/order/{id}/hotel-requirement/reject` **VO**: `RejectReqVO → Void` #### 使用场景 弹窗①某户选「打回」并填了原因,提交时逐户调用。封装 `rejectHotelRequirement(orderId, payload)`(`orderV2.js:1543`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | id | Path | Long | ✅ | 子订单 ID(取 hotel-households 的 `orderId`) | 路径参数 | | returnRemark | Body | String | ✅ | `@NotBlank`「打回/驳回备注不能为空」;≤500「打回/驳回备注不能超过 500 字」 | 打回原因,定制师可见 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | data | null | 成功无返回体 | #### 请求示例 ```json { "returnRemark": "第 2 晚房型与人数对不上,3 人只订了 1 间大床,请核对后重新提交" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 无返回体,成功即 `data=null`。 #### 错误响应 ```json { "code": 589535, "message": "子订单 26-2355 已分房,请先由房务调整配房后再打回", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `子订单 {0} 已分房,请先由房务调整配房后再打回` 组装。) #### 业务边界 - 判权 `group-batch:demand:confirm`;团期阶段须为 `RESOURCE_PREPARING`,否则 589501(零写入)。 - 错误按后端校验顺序:订单不存在 581007 → 非团单 582083 → 阶段 589501 → 该户已有配房 589535(要房务先调整配房)→ 订单无有效需求行 582031 → 源状态不是 `PENDING_REVIEW` / `PENDING` 582083 → 并发改动 582083。 - 成功后:该户住房需求退回定制师(`REJECTED_TO_CONSULTANT`),**清团级确认标记**,写时间线 `BATCH_REQUIREMENT_REJECT`,给定制师生成返工待办并发站内信。 - 与整团确认共用团级锁;每户一次调用,各自独立提交,已成功的打回不会因为后面某户失败而回滚。 --- ### 14. 用车逐户打回 `POST /v3/admin/order/{id}/vehicle-requirement/reject` **VO**: `RejectReqVO → Void` #### 使用场景 弹窗②接送机打回(`kind=TRANSFER`)、弹窗③下半各户行程用车打回(`kind=TRAVEL`)。封装 `rejectVehicleRequirement(orderId, payload, kind)`(`orderV2.js:1561`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | id | Path | Long | ✅ | 子订单 ID | 路径参数 | | kind | Query | String | ❌ | `TRAVEL` / `TRANSFER`,**不传按 TRAVEL**;其他值 809000 | 打回哪一类;接送机必须显式传 `TRANSFER` | | returnRemark | Body | String | ✅ | `@NotBlank`「打回/驳回备注不能为空」;≤500「打回/驳回备注不能超过 500 字」 | 打回原因 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | data | null | 成功无返回体 | #### 请求示例 ```http POST /v3/admin/order/2102309919002943489/vehicle-requirement/reject?kind=TRANSFER Content-Type: application/json { "returnRemark": "送机日期与大交通返程不一致,请按返程航班重新提交" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 无返回体,成功即 `data=null`。 #### 错误响应 ```json { "code": 589501, "message": "团期状态不允许当前操作", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:demand:confirm`;团期阶段须为 `RESOURCE_PREPARING`,否则 589501(零写入)。**已确认态「审核变更」出现时团期已过这个阶段,所以那里不能打回。** - 错误按后端校验顺序:订单不存在 581007 → 非团单 582083 → 阶段 589501 → 该户用车需求正在最终确认 582092 → 订单无有效需求行 582031 → 源状态不是 `PENDING_REVIEW` / `PENDING` 582083 → 并发改动 582083。车侧没有 589535。 - `kind` 只打回指定的那一类:打回接送机不影响该户行程用车,反之亦然。 - 成功后:该行退回定制师并失活(从 vehicle-households 里消失),**不论 kind 都会清团级确认标记**,写时间线 `BATCH_REQUIREMENT_REJECT`。 - 打回 TRAVEL 后该户缺行程用车,整团确认会被 809122 挡住;打回 TRANSFER 不挡确认(推理成立,未在测试服实测),该户可能出现在预检的 `transferDeclaredWithoutRequirement` 提示里。 --- ### 15. 住房逐户下发 `POST /v3/admin/order/{id}/hotel-requirement/dispatch` **VO**: `DispatchReqVO → Void` #### 使用场景 已确认态「审核变更」里住房行点「通过」。首次确认不用它(confirm 已一并放行住房)。封装 `dispatchHotelRequirement(orderId, payload)`(`orderV2.js:1577`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | id | Path | Long | ✅ | 子订单 ID | 路径参数 | | dispatchRemark | Body | String | ❌ | ≤500「提交备注不能超过 500 字」 | 下发备注;**请求体必须有**,可传 `{}` | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | data | null | 成功无返回体 | #### 请求示例 ```json { "dispatchRemark": "" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 无返回体,成功即 `data=null`。 #### 错误响应 ```json { "code": 582083, "message": "需求状态不允许此操作,请检查当前状态", "data": null, "traceId": null, "success": false } ``` #### 业务边界 - 判权 `group-batch:demand:confirm`;**无团期阶段守卫**。 - 守卫顺序:订单不存在 581007 → 非团单 582083 → 团期未成团 589552 / 未建团 589553 → 无有效需求行 582031 → 当前不是 `PENDING_REVIEW` 582083 → 并发更新失败 582083。 - 成功后:`PENDING_REVIEW → PENDING`,写下发备注,房控状态置待处理,写时间线,该团配房完成标记失效。 - 按源码,资源准备阶段有户改住房会把整团打回「需求待确认」,所以已确认态下住房待审行正常不会出现;页面按数据渲染即可,出现就按本接口处理。 --- ### 16. 用车逐户下发 `POST /v3/admin/order/{id}/vehicle-requirement/dispatch` **VO**: `DispatchReqVO → Void` #### 使用场景 已确认态「审核变更」里车侧行点「通过」:接送机传 `kind=TRANSFER`,行程用车传 `kind=TRAVEL`。多户接送机一起通过时可以改用第 17 节批量放行。封装 `dispatchVehicleRequirement(orderId, payload, kind)`(`orderV2.js:1597`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | id | Path | Long | ✅ | 子订单 ID | 路径参数 | | kind | Query | String | ❌ | `TRAVEL` / `TRANSFER`,**不传按 TRAVEL**;其他值 809000 | 下发哪一类;接送机必须显式传 `TRANSFER` | | dispatchRemark | Body | String | ❌ | ≤500「提交备注不能超过 500 字」 | 下发备注;请求体必须有,可传 `{}` | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | data | null | 成功无返回体 | #### 请求示例 ```http POST /v3/admin/order/2102309919002943489/vehicle-requirement/dispatch?kind=TRANSFER Content-Type: application/json { "dispatchRemark": "" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": null, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 无返回体,成功即 `data=null`。 #### 错误响应 测试服实测原文(2026-09-29 16:38,经网关,用不存在的订单 ID 探错误信封): ```json {"code":581007,"message":"订单不存在","data":null,"traceId":null,"success":false} ``` #### 业务边界 - 判权 `group-batch:demand:confirm`;**无团期阶段守卫**,已确认态也能调。 - 守卫顺序:该户用车需求正在最终确认 582092「用车需求正在最终确认,请稍后重试」(最先)→ 订单不存在 581007 → 非团单 582083 → 团期未成团 589552 / 未建团 589553 → 无有效需求行 582031 → 当前不是 `PENDING_REVIEW` 582083 → 接送机服务日期未回填 809007(在状态更新之前)→ 并发更新失败 582083。 - 成功后:`PENDING_REVIEW → PENDING`,写下发备注,车控状态置待处理,软删该行旧的实配记录,写时间线。 - `kind` 传错类别会动错行:接送机不传 `kind` 会按 TRAVEL 去找行程用车行。 --- ### 17. 接送机批量放行 `POST /v3/admin/order/group-batch/{groupBatchId}/requirement/transfer/batch-confirm` **VO**: `TransferBatchConfirmReqVO → TransferBatchConfirmRespVO` #### 使用场景 两处:①模式一 / 模式二 confirm 成功后,把弹窗②判为通过、但不在 `transferDispatchedOrderIds` 里的户补放行;②已确认态「审核变更」里接送机多户一起通过。封装 `batchConfirmTransferRequirements`(`orderV2GroupBatch.js:819`)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | 团期 ID | 路径参数 | | orderIds | Body | Long[] | ✅ | `@NotEmpty`「请至少选择一个要确认的子订单」;≤200「单次确认不超过 200 户」 | 要放行接送机的子订单 | | dispatchRemark | Body | String | ❌ | ≤500「确认备注不能超过 500 字」 | 放行备注 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | groupBatchId | Long(JSON 字符串) | 团期 ID | | requestedCount | int | 请求户数 | | successCount | int | 成功户数 | | failedCount | int | 失败户数 | | succeededOrderIds | Long[] | 成功的户 | | failed[] | Array | 失败明细:`orderId` / `teamNo` / `errorCode`(Integer)/ `reason` | #### 请求示例 ```json { "orderIds": ["2100856430239121409", "2102309919002943489"], "dispatchRemark": "" } ``` #### 响应示例 按 VO 结构组装,取值为示意: ```json { "code": 200, "message": "成功", "data": { "groupBatchId": "2100856430494973953", "requestedCount": 2, "successCount": 1, "failedCount": 1, "succeededOrderIds": ["2100856430239121409"], "failed": [ { "orderId": "2102309919002943489", "teamNo": "26-2355", "errorCode": 582083, "reason": "需求状态不允许此操作,请检查当前状态" } ] }, "traceId": null, "success": true } ``` #### 空数据 / 降级响应 **允许部分成功**:`code=200` 不代表全部成功,必须看 `failedCount`,大于 0 时把 `failed[]` 逐户列给用户。 #### 错误响应 ```json { "code": 589597, "message": "子订单 26-2355 不属于本团期", "data": null, "traceId": null, "success": false } ``` (按错误码模板 `子订单 {0} 不属于本团期` 组装。) #### 业务边界 - 判权 `group-batch:demand:confirm`;团期不存在 589500;团期已取消 589501;其他阶段都允许。 - 有任一户不属于本团期,**整批拒绝**报 589597,一户都不放行。 - 逐户失败(状态不对、服务日期未回填等)进 `failed[]`,不影响其他户。 - 幂等 120 秒,按「团期 + 这批 orderIds」判重,重复提交返回 `code=100502`「接送机需求确认处理中,请勿重复提交」。 --- ## 四、契约约束与正确调用方式(接口类必写) > 本节只写后端接受 / 拒绝的规则与前端必须遵守的调用顺序。 ### ✅ 正确 / ❌ 错误 payload 对照 | 场景 | payload | |------|---------| | ✅ 接送机逐户打回 | `POST /v3/admin/order/{id}/vehicle-requirement/reject?kind=TRANSFER` + `{ "returnRemark": "原因" }` | | ❌ 接送机打回不带 kind | `POST /v3/admin/order/{id}/vehicle-requirement/reject` → 按 TRAVEL 打回了该户的**行程用车** | | ❌ 打回不带原因 | `{ "returnRemark": "" }` → `code=400`「打回/驳回备注不能为空」 | | ✅ 逐户下发无备注 | `{}` 或 `{ "dispatchRemark": "" }` | | ❌ 逐户下发不带请求体 | 无 body → 请求体缺失,接口不受理 | | ✅ 首次保存正式需求 | `{ "version": null, "groups": [ … ] }` | | ❌ 改完不带版本 | 已有版本时传 `version: null` 或旧版本 → 809102 | | ✅ 整团不用车 | `POST …/vehicle-requirement/waive` + `{ "reason": "原因" }` | | ❌ 整团不用车走空分组 | `PUT …/vehicle-requirement` + `{ "groups": [] }`(本团有需要用车的户)→ 809103 | | ✅ 整团确认 | `POST …/requirement/confirm`,无请求体 | | ❌ 用批量打回处理接送机 | `POST …/requirement/reject` 的 `VEHICLE` 只覆盖 TRAVEL,只有接送机的户报 589534 | ### 切换状态时的必要动作 **弹窗底部三种提交模式与调用顺序**(按顺序串行调用,**任一步失败就停下、提示错误、重拉页签数据**;已成功的逐户打回不回滚): | 模式 | 触发条件 | 按钮文案 | 调用顺序 | |------|------|------|------| | 一 | 三步里没有任何「打回」 | 确认需求并下发 | ① 弹窗③改过分组或还没有正式需求 → `PUT vehicle-requirement`;选了整团不用车 → `waive`(二选一,都不需要就跳过)→ ② `POST requirement/confirm` → ③ 对账补放行(见下) | | 二 | 只有弹窗②(接送机)有打回 | 打回 N 户并确认其余 | ① 逐户 `vehicle-requirement/reject?kind=TRANSFER` → ② 同模式一的 PUT / waive → ③ `POST requirement/confirm` → ④ 对账补放行 | | 三 | 弹窗①(住房)或弹窗③(行程用车)有任一打回 | 打回 N 项 | 逐户 `hotel-requirement/reject`、`vehicle-requirement/reject?kind=TRAVEL`,弹窗②的接送机打回(`kind=TRANSFER`)一并提交;**不调 confirm** | - **打回一律排在 confirm 之前**:逐户打回会清团级确认标记,排在后面会把刚写上的确认清掉。 - **模式三不调 confirm 的原因**:住房打回后该户算缺失(589533),行程用车打回后该户缺行程用车(809122),确认必失败。 - **模式三不提交弹窗③的分组编辑**:打回行程用车后 PUT 会被 809123 挡住。要保留分组编辑,让用户先点「仅保存草稿」(PUT)再提交打回;弹窗里要提示这一点。 - **有打回项没填原因时禁用提交按钮**(`returnRemark` 必填、≤500)。免车原因同理(必填、≤200)。 - N 的口径:模式二的 N = 接送机打回户数;模式三的 N = 三步打回行数合计(同一户打了住房和行程用车算 2 项)。 **对账补放行(模式一、模式二 confirm 成功后必做)**: 1. 取弹窗②里判为「通过」、且该行 `status = PENDING_REVIEW` 的户,记为 A。 2. 从 A 里去掉 `confirm` 响应 `transferDispatchedOrderIds` 里已有的户,剩下的记为 B。 3. B 非空 → `POST requirement/transfer/batch-confirm`,`orderIds = B`;看 `failedCount`,失败户逐条提示。 4. 会出现 B 非空的两种情况:整团免车(confirm 一条车需求都不放行);该户 `needs_vehicle≠1`(不在放行集合)。 **已确认态(`requirementConfirmed=true`)的「审核变更(N)」**: - N = 住房与车侧所有 `status = PENDING_REVIEW` 的行数。 - 只提供「通过」:住房 → 第 15 节逐户下发;接送机 → 第 16 节 `kind=TRANSFER` 或第 17 节批量;行程用车 → 第 16 节 `kind=TRAVEL`。 - 「打回」只在 `batchStatus = RESOURCE_PREPARING` 时显示;按源码,这个阶段有户改需求会自动清确认标记、页签回到待确认态,所以已确认态下「打回」实际不会出现。 **主按钮可用性**: - `batchStatus ≠ RESOURCE_PREPARING` 且未确认:确认与打回都会 589501,主按钮置灰并显示 `batchStatusName`。 - 预检 589507(无 demand:confirm 权限):隐藏主按钮、只读展示。 - 预检 `ready=false` 但阶段正确:主按钮**仍可点**(用户要进弹窗去打回),弹窗里模式一 / 模式二的提交按钮按 `missing + vehicleMissing` 条数给出 `missingText` 并禁用。 **原「更多」菜单里三项能力的去处**: - 整团免车(waive)→ 弹窗③「整团不用车」。 - 撤回(withdraw)→ 前端自定入口,仅 `status ∈ {CONFIRMED, DISPATCHED}` 时显示;已免车改有车、已确认改分组都必须先撤回。 - 受控重开(`POST …/vehicle-requirement/reopen`,封装 `reopenGroupVehicleRequirement`,`orderV2GroupBatch.js:889`)→ 前端自定入口,契约不变。 **字段来源与前端算法(设计 A 页签主体)**: | 展示元素 | 来源 / 算法 | |------|------| | 页头 N 户 N 人 | 详情 `subOrderCount` / `enrolledPeople` | | 状态条「需求待确认 / 需求已确认」 | 详情 `requirementConfirmed` | | 状态条「N 户已全部提交」 | 汇总 `hotelSubmittedOrderCount == hotelNeededOrderCount` 且 用车逐户 `countedHouseholdCount == householdCount` | | 状态条「预检通过,可以确认」 | 预检 `ready`;为 false 时显示 `missing.length + vehicleMissing.length` 条未就绪 | | 已确认态「时间 由 某人 确认」 | 状态流水最后一条 `BATCH_REQUIREMENT_CONFIRM` 的 `changedAt` / `operatorName`(未实测) | | 已确认态「审核变更(N)」列表 | 状态条下方逐行列出 `status = PENDING_REVIEW` 的行:`customerName` + 行的 `kindName`(住房行写「住房」)+ 该行内容;每行一个「通过」,右上「全部通过(N)」。接口不区分新报与修改,界面不写「新报 / 改了」 | | 已确认态「N 户已下发房务」 | 住房逐户 `status ∈ {PENDING, PROCESSING, DONE}` 的户数 | | 已确认态「N 户已下发车务」(接送机) | 用车逐户 TRANSFER 行 `status ∈ {PENDING, PROCESSING, DONE}` 的户数 | | 已确认态「正式需求已确认 N 组」 | 正式需求 `status` + `groups.length` | | 住房卡「待审核 N」 | 住房逐户 `status = PENDING_REVIEW` 的户数 | | 住房卡「间/晚(峰值)」 | 每晚 `dailyRoomBreakdown[].rooms[].totalRoomCount` 求和,取各晚最大值 | | 住房卡「N 晚」 | `endDate − departDate`(天数差),**不用** `dailyRoomBreakdown.length` | | 住房卡「间夜」 | 全部晚的 `totalRoomCount` 总和 | | 住房卡「N 家酒店」 | `dailyRoomBreakdown[].hotels[].hotelId` 去重计数 | | 住房卡「N 晚有 N 户客户自订」 | 住房逐户 `days[].customerSelfBooked=true`:按 `stayDate` 去重得晚数,按 `orderId` 去重得户数 | | 住房卡「N/N 户已提交」 | 汇总 `hotelSubmittedOrderCount / hotelNeededOrderCount` | | 住房卡「N 户有特殊需求」 | 汇总 `orderSpecialTags` 中 `requirementType=HOTEL` 的 `orderId` 去重计数 | | 接送机卡「待审核 N」 | 用车逐户 TRANSFER 行 `status = PENDING_REVIEW` 的户数 | | 接送机卡「N 户 · N 人」 | `transferSummary.householdCount / headcount` | | 接送机卡「接机 N 户 N 人 · 送机 N 户 N 人」 | 户数用 `pickupHouseholdCount / dropoffHouseholdCount`;人数 = 用车逐户 TRANSFER 行按 `pickupRequired=true` / `dropoffRequired=true` 分别对 `headcount` 求和;**日期不配方向**(见缺口) | | 接送机卡车型 | `transferSummary.vehicleSeatSummary[]` → `{vehicleTypeName} {seats}座 ×{count}` | | 行程用车卡「正式需求 · 草稿」 | 正式需求 `status` 映射中文(六.5);`data=null` 显示「未形成正式需求」;`CONFIRMED` 且 `groups` 为空显示「整团不用车」 | | 行程用车卡「N 组 · 逐日覆盖 x/y 天」 | N = `groups.length`;x = `groups[].days[].tripDate` 去重后落在 `departDate..endDate` 内的天数;y = `endDate − departDate + 1` | | 行程用车卡车型(日期) | `groups[]` → `{vehicleTypeName} {seats}座 ×{count}({serviceStartDate}–{serviceEndDate})` | | 行程用车卡「N/N 户已报」 | 用车逐户 `countedHouseholdCount / householdCount` | | 大交通声明提示 | 预检 `transferDeclaredWithoutRequirement[]`:`customerName` + 声明了接机(`pickupRequired`)/ 送机(`dropoffRequired`)+ 定制师 `consultantName`;「联系定制师」→ `emit('contact', orderId)` | | 逐晚用房表 晚次 / 日期 | 以 `departDate..endDate−1` 逐晚生成行,按 `stayDate` 关联 `dailyRoomBreakdown`;没有条目的晚在备注列标「全团客户自订」或「无已提交需求」(用住房逐户 `customerSelfBooked` 区分) | | 逐晚用房表 城市 · 酒店 | `hotels[].hotelName`;城市按 `hotelId` 关联住房逐户 `days[].hotels[].city` | | 逐晚用房表 房型列 | **按数据里出现的 `roomCategory` 动态生成列**,列头用 `roomCategoryName`;设计稿里的「标间 / 大床 / 亲子 / 家庭」四列只是示意,实测数据里出现的是 DELUXE / KING / STANDARD | | 逐晚用房表 合计 / 间夜合计 | 行合计 = 该晚 `rooms[].totalRoomCount` 之和;末行按列求和 | | 用车安排 行程用车矩阵 | 行 = `groups[]`(`groupCode` + 车型);列 = `departDate..endDate`;格 = 该组该日 `days[].headcount`,无则空 | | 用车安排「在团人数」 | 详情 `enrolledPeople` | | 用车安排 接送机 | 按方向分两行:接机行取用车逐户 TRANSFER 行里 `pickupRequired=true` 的户,送机行取 `dropoffRequired=true` 的户;每行列客户名、人数(`headcount` 求和)、车型(按 `fleet` 计数)。标题旁单列服务日期 `transferSummary.serviceDates`,**不与方向配对** | | 逐户表 数据集 | 住房逐户 ∪ 用车逐户,按 `orderId` 合并 | | 逐户表 筛选「全部」 | 合并后户数 | | 逐户表 筛选「待审核」 | 住房 `status` 或任一车侧行 `status` 为 `PENDING_REVIEW` 的户 | | 逐户表 筛选「已打回」 | 住房 `status` 为 `REJECTED_TO_CONSULTANT` / `REJECTED_TO_ADMIN` 的户(车侧见缺口) | | 逐户表 筛选「未提交」 | 住房 `status=null` 或 用车逐户户级 `status=null` 的户 | | 逐户表 筛选「有特殊需求」 | 住房 `specialTags` 非空 或 任一车侧行 `specialTags` 非空 | | 逐户表 搜索 | 前端本地过滤 `customerName` / `teamNo` / `orderNo` / `consultantName` | | 逐户表 户 / 人数 / 定制师 | `customerName`(副行 `teamNo`,null 显示 —)/ `participantCount` / `consultantName` | | 逐户表 住房 | `days[].hotels[].hotelName` 去重拼接;副行 晚数与房数合计 | | 逐户表 接送机 | TRANSFER 行:接 / 送标记 + `serviceDates` + `fleet`;无 TRANSFER 行且在 `transferDeclaredWithoutRequirement` 里 →「未报 · 大交通声明了接机 / 送机」;不在用车逐户列表里 →「不需要」;其余 → — | | 逐户表 行程用车 | TRAVEL 行 `fleet` + `serviceDates`;不在用车逐户列表里 →「不需要」 | | 逐户表 特殊需求 | 住房 `specialTags` ∪ 车侧 `specialTags[].name` | | 逐户表 备注 | 住房 `remark` 与车侧 `remark` 分行显示 | | 逐户表 状态 | 住房与车侧各显示 `statusName` 原文;户级徽标按「已打回 > 待审核 > 未提交 > 已下发」取最高 | | 逐户表 详情 | 打开保留的 `HouseholdRequirementModal` | | 逐户表 联系定制师 | `emit('contact', orderId)` → `index.vue:705` | **字段来源与前端算法(设计 B 审核弹窗)**: | 弹窗元素 | 来源 / 算法 | |------|------| | 步骤条 ①住房 ②接送机 ③行程用车 | 各步待审行数与已打回数由前端统计 | | ① 逐户列表 | 住房逐户 `households[]`;`status ∈ {PENDING_REVIEW, PENDING}` 的户可选「通过 / 打回」,其余只读 | | ①「全部重置为通过」 | 前端清空本步所有打回标记与原因 | | ①「本次下发」 | 本步判为通过且 `status=PENDING_REVIEW` 的户,按 `days[].hotels[].rooms[].roomCount` 求和得间夜 | | ② 逐户列表 | 用车逐户 TRANSFER 行:接机 `pickupRequired` / 送机 `dropoffRequired` / 人数 `headcount` / 车型 `fleet` / `specialTags` / `remark` | | ② 大交通声明提示 | 预检 `transferDeclaredWithoutRequirement[]` | | ③ 正式需求编辑区 | 有正式需求用第 7 节 `groups`;没有或点「按户需求重新汇总」用第 8 节 `draft`;`violations` 逐条标在对应组 / 日期旁 | | ③「添加分组」 | 前端在 `groups` 里新增一组(`groupId=null`,`groupCode` 不可与已有组重复) | | ③ 逐日覆盖 `coverText` | 同设计 A「逐日覆盖 x/y 天」 | | ③「整团不用车」 | 第 10 节 waive(需填原因) | | ③「仅保存草稿」 | 第 9 节 PUT,成功后用返回的 `version` 覆盖本地 | | ③ 各户行程用车需求 | 用车逐户 TRAVEL 行;打回走第 14 节 `kind=TRAVEL` | | 底部 `missingText` | 预检 `missing[]` 与 `vehicleMissing[]` 条数;`reasonName` / `detail` 逐条可展开 | | 底部 `submitLabel` | 按上面三种模式切换 | **字段缺口(本期不展示)**: | 设计稿元素 | 为什么没有 | 本期处理 | |------|------|------| | 户级变更前后差异(「第 4 晚加 1 间大床」)与「新报 / 修改」的区分 | 所有接口只返回当前版本,不返回上一版或差异 | 本期不展示,只列「某户 住房 / 接送机 / 行程用车 待审核」 | | 接送机按日期拆方向(「10/03 接机 5 户 16 人 · 10/08 送机 5 户 16 人」) | `serviceDates` 不带方向,接口没有「哪一天是接机」 | 本期不展示日期与方向的配对;按方向显示户数与人数,日期单列 | | 大交通声明的接送日期 | `transferDeclaredWithoutRequirement` 只有方向标记与 `pickupRemark` | 本期不展示日期 | | 车侧「已打回」 | 车侧打回后该行失活,不再返回,与「未提交」读数相同 | 本期「已打回」筛选只覆盖住房侧 | --- ## 五、数据库行为(涉及写操作时必写) 本条后端零改动,下表只列前端各提交动作在后端产生的既有写入,供前端判断「失败时有没有部分写入」。 | 前端动作 | 后端写入 | 失败时 | |----------|---------------------|---------------------| | `PUT vehicle-requirement` | 正式用车需求新版本落为 `DRAFT`,`version+1` | 零写入 | | `waive` | 正式用车需求置为 `CONFIRMED` 且无分组,记免车原因 | 零写入 | | `withdraw` | 正式用车需求回 `DRAFT`、`version+1`、原因追加进备注、清确认人与时间、复位团期 `vehicleReady` | 零写入 | | `requirement/confirm` | 团期 `requirementConfirmed=true` + 时间线 `BATCH_REQUIREMENT_CONFIRM`;住房待审行 → `PENDING`;正式需求 → `CONFIRMED`;车侧待审行 → `PENDING`(免车时后两项不做) | 同一事务,整团零写入 | | 逐户 `reject`(住房 / 用车) | 该户该类需求行 → `REJECTED_TO_CONSULTANT` 并写打回原因;清团级 `requirementConfirmed`;时间线 `BATCH_REQUIREMENT_REJECT` | 该户零写入;**此前已成功的其他户不回滚** | | 逐户 `dispatch`(住房 / 用车) | 该户该类需求行 `PENDING_REVIEW → PENDING`,写下发备注,房控 / 车控置待处理 | 该户零写入 | | `transfer/batch-confirm` | 成功户的接送机行 → `PENDING` | 逐户独立,部分成功 | --- ## 六、边界行为 - 未登录 → 网关拦截,返回信封 `code=401`。 - 无团期权限 → 589507;只有 `group-batch:view` 的角色能看汇总、逐户、详情、流水,看不了预检与正式用车需求(这两个要 `group-batch:demand:confirm`),页签要能部分降级。 - 团期不存在 → 589500(状态流水除外,它返回空数组)。 - 团期阶段不是 `RESOURCE_PREPARING` → 确认、逐户打回、保存正式需求都是 589501;逐户下发、接送机批量放行、撤回不受阶段限制。 - 同一团期两个人同时操作:确认与逐户打回共用团级锁;正式需求靠 `version` 乐观锁(809102);各写接口有 5 秒(接送机批量 120 秒)幂等窗口,重复点击返回 `code=100502`,前端提交期间锁按钮即可。 - 入参校验失败 → HTTP 200、`code=400`,`message` 为注解原文,多条用「; 」连接。 - 所有 ID(`groupBatchId`、`orderId`、`requirementId` 等)在 JSON 里是字符串,前端不要转 Number。 --- ## 六.5、枚举 / 数据字典(接口出现枚举时必写) ### batchStatus(com.hulalv.order.groupbatch.enums.GroupBatchStatus) **所属字段**: `GroupBatchDetailRespVO.batchStatus`、`GroupBatchRequirementCheckRespVO.batchStatus` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `RECRUITING` | 招募中 | 确认 / 打回 589501 | | `RESOURCE_PREPARING` | 资源准备中 | **唯一**可整团确认、可打回、可保存正式用车需求的阶段 | | `MATERIAL_PREPARING` | 物料准备中 | 确认 / 打回 589501 | | `PENDING_DEPARTURE` | 待出发 | 同上 | | `TRAVELLING` | 出行中 | 同上 | | `TRIP_FINISHED` | 出行完毕 | 同上;可免车 | | `REVIEWING` | 核单中 | 同上;可免车 | | `SETTLED` | 已结算 | 同上 | | `CANCELLED` | 已取消 | 同上;接送机批量放行也拒 | ### status(com.hulalv.order.requirement.enums.RequirementStatus) **所属字段**: 住房逐户 `households[].status`、用车逐户 `households[].status` 与 `requirements[].status` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `PENDING_REVIEW` | 待审核 | 定制师已提交、团期管理员未放行;车侧 TRAVEL 的 `statusName` 显示「待提交车务」 | | `PENDING` | 待房务配 | 已放行;车侧 `statusName` 以接口返回为准 | | `PROCESSING` | 配房中 | 房务 / 车务处理中 | | `DONE` | 配房完成 | 处理完成 | | `REJECTED_TO_CONSULTANT` | 驳回 | 已打回定制师;只在住房逐户出现,车侧打回行不返回 | | `REJECTED_TO_ADMIN` | 驳回 | 被下游驳回到团期管理员 | | `null` | — | 该户一条都没提交 | 显示一律用接口返回的 `statusName`,不要用本表中文自行翻译(车侧与住房侧同一状态的中文不同)。 ### kind(com.hulalv.order.requirement.enums.VehicleRequirementKind) **所属字段**: 用车逐户 `requirements[].kind`;逐户打回 / 下发的 Query `kind` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `TRAVEL` | 行程用车 | Query 不传时的默认值 | | `TRANSFER` | 接送机 | 必须显式传 | ### status(com.hulalv.order.groupbatch.enums.GroupVehicleRequirementStatus) **所属字段**: `GroupVehicleRequirementRespVO.status`、预检 `groupVehicleRequirementStatus`、确认响应 `groupVehicleRequirementStatus` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `DRAFT` | 草稿 | 可 PUT;确认时推到 `CONFIRMED` | | `CONFIRMED` | 已确认 | `groups` 为空即整团免车;改分组须先撤回 | | `DISPATCHED` | 已发车务 | 改分组须先撤回 | | `DONE` | 配车完成 | — | | `PENDING_RECONFIRM` | 待重新确认 | 受控重开链路;确认时推到 `CONFIRMED` | | `CANCELLED` | 已取消 | 流团时关闭并失活,之后取正式用车需求读不到(`data` 为 null) | ### missing[].reason(GroupBatchRequirementCheckRespVO.MissingItem) **所属字段**: 预检 `missing[].reason`(中文见同行 `reasonName`) | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `NOT_SUBMITTED` | 未提报 | 该户需要住房但没有有效住房需求(含被打回后未重提) | | `ROOM_CATEGORY_MISSING` | 缺房型或房数 | 某晚某段缺房型大类或房数小于 1,附 `dayNumber` / `segmentIndex` | | `INVALID_REQUIREMENT` | 需求结构异常 | 逐晚数据为空或结构不完整 | | `NIGHTS_MISMATCH` | 住宿晚数对不上 | 附 `expectedNights` / `actualNights` | | `DAY_NUMBER_INVALID` | 晚序号异常 | 晚序号跳号、重复或越界,附 `expectedNights` / `actualNights` | ### vehicleMissing[].reason(GroupBatchRequirementCheckRespVO) **所属字段**: 预检 `vehicleMissing[].reason`(无中文名字段,展示用 `detail`) | **类型**: `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 | | `HEADCOUNT_LESS_THAN_MEMBERS` | 当日人数小于成员户数 | 对应 809110 | | `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED` | 该户没交行程用车 | 对应 809122,只带 `orderId` / `orderNo`,处置是催定制师提交 | | `MEMBER_GROUP_MISMATCH` | 户车型与分组车型不符 | 对应 809125,处置是重新汇总并保存 | | `TRANSFER_SERVICE_DATES_NOT_BACKFILLED` | 接送机服务日期未回填 | 对应 809007,只带 `orderId` | | `TRANSFER_WINDOW_INCOMPLETE` | 接送机窗没盖住大交通日期 | 对应 809126,带 `tripDate`,处置是让定制师重新提交接送机需求 | | `GROUP_SPEC_INCOMPLETE` | 座位数与车辆数没同填 | 对应 809118(源码常量 `GroupVehicleRequirementService.java:229`,VO 注释未列) | | `GROUP_SEATS_INSUFFICIENT` | 座位不够 | 对应 809116(同上 `:232`) | | `GROUP_SEATS_NOT_IN_OPTIONS` | 座位不在可选档位 | 对应 809124(同上 `:235`) | | `GROUP_SEAT_OPTIONS_UNAVAILABLE` | 档位读取失败 | 复用 809120 语义(同上 `:238`) | 上表「中文」列是本条给的说明性译名,接口不返回;遇到表外的值按 `detail` 原文展示。 ### eventType(com.hulalv.order.groupbatch.enums.GroupBatchLogEventType,本页只用三个) **所属字段**: `GroupBatchStatusLogItemVO.eventType` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `BATCH_REQUIREMENT_CONFIRM` | 需求整体确认 | 已确认态「时间 由 某人 确认」取最后一条 | | `BATCH_REQUIREMENT_REJECT` | 需求打回 | 批量或逐户打回;逐户时 `extra` 只含本户 orderId | | `BATCH_REQUIREMENT_REOPENED` | 需求重开待确认 | 有户改需求或转团,后端自动清确认标记 | ## 六.6、修改前后对比(修改/删除类接口必写,新增跳过) ### 字段级对比 | 字段 | 改前 | 改后 | |------|------|------| | 所有接口的请求与响应字段 | 不变 | 不变(后端零改动) | | vehicle-households 的 `kind` 调法 | 父组件按 `TRAVEL` / `TRANSFER` 分两次取 | 可不传 `kind` 一次取两类,按 `requirements[].kind` 拆;分两次取仍然有效 | ### 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 打回 | 顶部「打回选中户」→ 批量打回端点,整批一条原因,接送机打不了 | 弹窗内逐户逐类打回,每户各自原因,接送机 `kind=TRANSFER` | | 整团确认 | 顶部「整团确认需求」直接 confirm | 弹窗底部按三种模式串行调用,确认后对账补放行接送机 | | 正式行程用车需求 | 「新增正式行程用车需求」按钮 + 独立编辑弹窗 | 收进弹窗③,确认前按需 PUT | | 免车 / 撤回 / 重开 | 「更多」菜单 | 免车进弹窗③;撤回与重开入口前端自定 | | 接送机放行 | 用车逐户块勾选 + 批量放行按钮 | 首次确认随 confirm 放行并对账补放行;已确认态走「审核变更」 | | 已确认后的待审行 | 可在逐户块逐条下发 | 「审核变更(N)」只提供通过 | ## 六.7、影响评估(修改/删除类必写) - **是否破坏向后兼容**: 否(后端零改动,旧页签照常可用) - **前端是否必须同步发版**: 否 - **前端 workaround 清理点**: `RequirementRejectModal.vue` 及批量打回封装 `rejectGroupBatchRequirement` 的调用点(`orderV2GroupBatch.js:679`,封装本身可留);`RequirementTab.vue:53 / :68 / :77 / :86` 四个按钮与 `:588` 整团确认对话框;`VehicleHouseholdsSection.vue` 的勾选与批量放行按钮(并入弹窗②与审核变更) ## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面) - **仅影响**: 出团详情「查看需求」页签及其子组件 - **零影响**: - 后端全部接口与数据(零改动) - 出团详情其他页签(整团总览、子订单、配房明细、行程、用餐、操作记录等) - 定制师侧提交住房 / 用车 / 接送机需求的页面 - 房务、车务侧的工作台 - `HouseholdRequirementModal`(保留原样,只是改由逐户表「详情」打开) --- ## 八、测试环境已验证 测试服网关实测,带 ✓ 的为本条示例所用原文: ``` GET /v3/admin/order/group-batch/2100856430494973953/requirement-summary → 200 + 两晚逐晚用房 + 接送机汇总 ✓(2026-09-29 16:38) GET /v3/admin/order/group-batch/2100856430494973953/requirement/vehicle-households → 200 + 2 户 4 行(不传 kind 两类同返)✓(2026-09-29 16:38) POST /v3/admin/order/1/vehicle-requirement/dispatch?kind=TRANSFER → 581007 订单不存在(错误信封形态)✓(2026-09-29 16:38) GET /v3/admin/order/group-batch/2104327837991518210/requirement/confirm-check → 200 + vehicleWaived=true + ready=true ✓(2026-09-27) ``` 验证团期: `groupBatchId=2100856430494973953`(2 户,出发 2026-10-08,资源准备中);免车团期 `groupBatchId=2104327837991518210`。 其余接口的示例按 VO 结构组装、取值为示意,已在各节示例上方逐一标注;「确认人取自状态流水」「打回接送机不挡确认」两条是按源码推理,未在测试服实测,同样已在对应小节标注。 --- ## 十、相关文档 - 设计稿: 「设计稿」https://claude.ai/artifact/R2b6RsiUvykdF9kBUdaQcC - 同日既有条目: `changelogs-v2/2026-09/29_frontend_团期详情用车汇总行程用车与接送机改逐条列出-前端优化-管理后台.md` - 同日既有条目: `changelogs-v2/2026-09/29_frontend_团期详情用车需求确认弹窗点确认不发请求-前端缺陷-管理后台.md` - 后端源码(`origin/dev-v3@7b702f5c`,`hl-order-service-v3`): `GroupBatchRequirementController.java`、`GroupBatchQueryController.java`、`HotelRequirementAdminController.java`、`VehicleRequirementAdminController.java`、`GroupBatchRequirementService.java`、`GroupVehicleRequirementService.java`、`GroupBatchService.java` - 前端源码(hl-ui `origin/v2.1@cdfaf793`): `src/views/order-v2/batch/detail/components/`、`src/api/orderV2GroupBatch.js`、`src/api/orderV2.js` ## 关联 / 联系人 ### 链接 - **Issue**: 无(ticket=frontend) - **PR**: 无(后端零改动) - **前端提交**: 前端完成后填入 frontmatter `frontend_ref` ### 联系人 - **后端负责人**: @wx - **前端负责人**: @mmg