From 4088b2e53b3c284681fb8533b30cf10c1f623a46 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Wed, 16 Sep 2026 03:24:27 +0800 Subject: [PATCH] =?UTF-8?q?docs(order-v3):=20#7441=20=E5=9B=A2=E6=9C=9F?= =?UTF-8?q?=E6=95=B4=E5=9B=A2=E7=A1=AE=E8=AE=A4=E6=8E=A5=E8=BD=A6=E4=BE=A7?= =?UTF-8?q?=20+=20=E6=95=B4=E5=9B=A2=E5=85=8D=E8=BD=A6=E6=88=B7=E7=BA=A7?= =?UTF-8?q?=E4=B8=8E=E5=9B=A2=E7=BA=A7=E9=97=A8=E7=A6=81=E8=81=94=E5=8A=A8?= =?UTF-8?q?=20changelog=EF=BC=88=E5=B7=B2=E5=90=88=20dev-v3=20f0277a14f=20?= =?UTF-8?q?=E5=B9=B6=E9=83=A8=E7=BD=B2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 16_7441_团期整团确认接车侧:confirm-check / confirm 车侧字段、ready 语义变化、809100/809103/809108/809112/589533 实测 - 16_7441_团期整团免车户级与团级门禁联动:14 个契约不变行为变化端点,免车声明与团级 vehicleReady 联动;已实测与仅代码核对分列 Refs #7441 Co-Authored-By: Claude Opus 5 (1M context) --- ...团免车户级与团级门禁联动-修改接口-管理后台.md | 1362 +++++++++++++++++ ...41_团期整团确认接车侧-修改接口-管理后台.md | 517 +++++++ 2 files changed, 1879 insertions(+) create mode 100644 changelogs-v2/2026-09/16_7441_团期整团免车户级与团级门禁联动-修改接口-管理后台.md create mode 100644 changelogs-v2/2026-09/16_7441_团期整团确认接车侧-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/16_7441_团期整团免车户级与团级门禁联动-修改接口-管理后台.md b/changelogs-v2/2026-09/16_7441_团期整团免车户级与团级门禁联动-修改接口-管理后台.md new file mode 100644 index 00000000..5fe29e8f --- /dev/null +++ b/changelogs-v2/2026-09/16_7441_团期整团免车户级与团级门禁联动-修改接口-管理后台.md @@ -0,0 +1,1362 @@ +--- +schema: "hl-changelog/v2" +ticket: "7441" +title: "团期整团免车认户级与团级门禁联动——确认行程清单/待办/详情看板/物资门/合同/预支/出发门②/fleet待配车清单" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-16" +status_note: "本单(#7441 PR-2d + PR-2e)已由 PR #7778 squash 合入 dev-v3(合并提交 f0277a14f),测试服 order-v3 于 2026-09-16 01:21 部署该提交。确认行程清单(户丙残留需求+撤回对照)、确认行程、取消成团(589502 豁免)、成团(vehicleReady 回填)四个端点经真实 waive 声明取证;团期详情/手工复判物料门/手工复判出发门②/团期合同保险面板/发起预支五项,本轮用 fleet 真实回调置位(非 waive 声明)验证同一份 vehicleReady 读取口径生效,未单独用『已声明免车』的团复测这五个端点。分页查询我的订单待办、手动开合同/保险、团期看板、fleet 待配车清单、内部候选查询共 5 个端点本轮未经网关实测,按源码核对列示。第八节已逐条标注覆盖边界。" +updated_at: "2026-09-16" +base: "dev-v3" +--- + +# order-v3: 团期整团免车认户级与团级门禁联动 + +> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3;含一个 `/admin/fleet/**` 端点与一个 `/v3/internal/**` 端点,按仓规范例外同样归本目录) +> +> **服务**: hl-order-service-v3 (端口 8083) + hl-fleet-service (端口 8085,仅消费方,代码不改) +> **PR**: [#7778](https://git.1814.love:8443/wx/HL/pulls/7778)(squash 合入 dev-v3,合并提交 `f0277a14f`,同批含 PR-4,另有一份 changelog 覆盖) +> **Issue**: #7441 +> **日期**: 2026-09-16 +> **影响范围**: 「本团无需用车」声明(`waive`,已上线)从此不再是一个只写车需求表的孤立动作——14 个既有管理后台/车管后台/内部端点的响应值会随之联动变化,全部契约(方法/路径/参数/响应结构/错误码)不变,只是返回的**值**不同 + +--- + +## ⚠️ 关键变化(本版与上版行为不同,必读) + +1. **免车团从此在多个页面「表现为已就绪」,而改前只有车需求页知道**。声明「本团无需用车」(`waive`)改前只写车需求表本身,本单起同一次调用会连带让下面 14 个既有端点的返回值发生变化——**这些端点自身的契约(方法/路径/参数/响应结构/错误码)一个字节都没改**,变的只是数据。 +2. **`vehicleReady` 字段语义扩大**:团期详情 `GET .../group-batch/{groupBatchId}` 与看板 `GET .../group-batch/board` 的 `vehicleReady` 字段,改前只表示「fleet 真配车就绪」,本单起表示「fleet 真就绪 **或** 已声明整团免车」。字段名、类型、路径**都不变**,前端如果把这个字段渲染成「已配车」而不是更准确的「车侧无阻塞」,含义会跟着变化。 +3. **确认行程清单 `VEHICLE_DONE` 项对免车团的户直接判通过**,哪怕该户从没提交过用车需求、或提交后还卡在草稿/待审核。 +4. **「用车需求 · 待提交」待办对免车团的户不再挂起**,`waive` 成功后会自动完成;撤回免车(`withdraw`)后按原规则重新挂起。 +5. **物资门、出发门②、合同出具、预支、fleet 待配车清单全部随 `vehicleReady` 联动**——免车团会像「已配车」一样打开这些门,不再单独卡在车侧。 +6. **操作顺序有硬约束:先撤回免车、再配车**。若在「已声明免车」期间 fleet 又真的按团期 ID 配了车,之后撤回免车会把 `vehicle_ready` 清成 `false`,即使 fleet 那边已经就绪,团会重新卡门直到 fleet 再回调一次——这是已知缺口(见「业务边界」),不是本单交付范围(`#7442` 收口)。 +7. **撤回免车(`withdraw`)不回退团期状态**:已经因免车推进到物资准备中及之后阶段的团,撤回免车后团期阶段本身不回退,但会被下游门(出发门②等)重新拦住。 + +--- + +## 一、背景 + +「本团无需用车」声明(`POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive`)与撤回(`POST .../vehicle-requirement/withdraw`)两个端点已上线(`#7441` 更早的 PR)。声明免车此前只在车需求表里落一条零分组的已确认记录,**下游所有读口都不认它**:确认行程清单里车侧项恒判不通过、待办永远挂着「用车需求 · 待提交」、团级 `vehicle_ready` 恒为 `false`,导致物资准备、出发门②、合同出具、预支、fleet 待配车清单全部把免车团当成「车没配好」卡住——管理员点了免车却什么都没打开。 + +本单(`#7441` PR-2d + PR-2e)让这些既有读口都认整团免车声明:PR-2d 补户级(确认行程清单、待办、订单状态流转),PR-2e 补团级(`vehicle_ready` 本身及其下游七道门)。**新增一个只读判定服务作为唯一真源**,结算闸(`#7441` PR-3,已上线)、本单新增的户级/团级判定共用同一份「是否已声明整团免车」的判断,不各自重写一份。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 5 项 checklist 校验(确认订单前置) | GET | `/v3/admin/order/{id}/confirm-checklist` | 返回值变化 | `items[code=VEHICLE_DONE]` 对免车团的户直接判通过 | +| 2 | 确认行程 | POST | `/v3/admin/order/{id}/confirm-itinerary` | 返回值变化 | 免车团的户不再因车侧未就绪报 581036 | +| 3 | 分页查询我的订单待办 | GET | `/v3/admin/order-todos/my/page` | 返回值变化 | 免车团的户「用车需求 · 待提交」待办自动完成,撤回后重开 | +| 4 | 取消成团 | POST | `/v3/admin/order/group-batch/{groupBatchId}/cancel-group` | 返回值变化 | 免车团不再因车项被判「已派单资源」而拦 589502 | +| 5 | 成团 | POST | `/v3/admin/order/group-batch/{groupBatchId}/group` | 返回值变化 | 免车声明仍有效时成团同时置 `vehicleReady=true` | +| 6 | 手工复判物料门 | POST | `/v3/admin/order/group-batch/{groupBatchId}/recheck-material-gate` | 返回值变化 | 免车团车项视为已过,`blockedGate` 不再落在车侧 | +| 7 | 手工复判进入待出发硬门 | POST | `/v3/admin/order/group-batch/{groupBatchId}/recheck-departure-gate` | 返回值变化 | 门②(房车导摄四项配齐)对免车团通过 | +| 8 | 团期合同保险面板 | GET | `/v3/admin/order/group-batch/{groupBatchId}/contracts` | 返回值变化 | 免车团 `issuable` 不再被车项卡住 | +| 9 | 手动开合同/保险 | POST | `/v3/admin/order/group-batch/{groupBatchId}/contracts/issue` | 返回值变化 | 免车团不再因车项报 589548 | +| 10 | 发起团期预支 | POST | `/v3/admin/order/group-batch/{groupBatchId}/advance` | 返回值变化 | 免车团不再因车项报 589542 | +| 11 | 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 字段语义变化 | `vehicleReady` 含义扩大为「配车完成或整团免车」 | +| 12 | 团期看板 | GET | `/v3/admin/order/group-batch/board` | 字段语义变化 | 同上 | +| 13 | 团期待配车候选查询(内部) | POST | `/v3/internal/group-batch/vehicle-dispatch-candidates` | 返回值变化 | 免车团不再落在候选集合里;仅限 fleet Feign 调用 | +| 14 | 待配车团期清单(车管后台) | GET | `/admin/fleet/group-dispatch/pending-batches` | 返回值变化 | 免车团从清单中消失,撤回免车后重新出现;经「13」间接取数 | + +--- + +## 三、接口详情 + +### 1. 5 项 checklist 校验(确认订单前置) `GET /v3/admin/order/{id}/confirm-checklist` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +订单详情页点「确认订单」前调用,展示 5 项前置检查(出行人信息/支付/住宿/用车/合同模板)的通过情况,用于决定弹出确认预览还是逐项报错清单。本单只影响 `code=VEHICLE_DONE` 这一项,其余 4 项不变。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| id | Path | Long | 是 | - | 订单 ID(不变) | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| allPassed | Boolean | 是否全部通过(唯一开关):true 时 items=null、preview 有值;false 时 items 有值、preview=null(不变) | +| items | List | 5 项详细结果(仅 allPassed=false 时返回):code/checkName/passed/failReason(结构不变) | +| preview | PreviewVO | 确认弹框预览数据(仅 allPassed=true 时返回,结构不变,本单未改) | + +`items[]` 中 `code=VEHICLE_DONE` 一项:改前 `needsVehicle=true` 时要求订单镜像与当前用车需求同时为 DONE 才 passed=true;本单起在这之前新增一次判断——若该订单所在团已声明整团免车,直接 passed=true,不再检查需求行状态。 + +#### 请求示例 + +```http +GET /v3/admin/order/2098979230573330434/confirm-checklist HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团的户(其余 4 项均已满足): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "allPassed": true, + "items": null, + "preview": { + "departureDate": "2026-09-20", + "totalPeopleCount": 4, + "driverName": null, + "driverPhoneMasked": null, + "hotels": [], + "staffs": [], + "contractAutoAction": null, + "insuranceAutoAction": null + } + } +} +``` + +对照:同一户在撤回免车后、且用车需求仍未完成时: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "allPassed": false, + "items": [ + { + "code": "VEHICLE_DONE", + "checkName": "用车安排", + "passed": false, + "failReason": "用车需求尚未完成" + } + ], + "preview": null + } +} +``` + +#### 空数据 / 降级响应 + +本端点全程同步内存/DB 读取,不经 Feign/MQ,不产生降级分支;`items`/`preview` 互斥由 `allPassed` 决定,两者不会同时为空或同时有值。 + +```json +{ "code": 200, "success": true, "data": { "allPassed": true, "items": null } } +``` + +#### 错误响应 + +本单不新增错误码,沿用既有: + +```json +{ + "code": 404, + "message": "订单不存在", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 免车判定读的是「该户所在团是否已声明整团免车」(经统一判团门面解析,不读订单自身的团期 ID 冗余列),与本单其余端点共用同一份只读判定,不会出现「这个端点认免车、那个端点不认」的分叉。 +- 判定顺序:`needsVehicle=false` 时最先短路(不查库);免车判定其次;仍不通过才走原有的需求状态/完成来源三分支校验。 +- 非免车团、或免车团但 `needsVehicle=false` 的户,行为与改前逐字节一致。 + +--- + +### 2. 确认行程 `POST /v3/admin/order/{id}/confirm-itinerary` + +**VO**: `ConfirmItineraryReqVO(可空)→ Result` + +#### 使用场景 + +订单详情页「确认订单」按钮,把订单从定制阶段推进到下一阶段。内部先跑与上条相同的 5 项 checklist,`allPassed=false` 时拒绝推进并报 581036。本单不改该端点的调用方式,只是 `VEHICLE_DONE` 判定结果变化会连带影响它是否报错。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| id | Path | Long | 是 | - | 订单 ID(不变) | + +(请求体可空,字段本单未改,不重复列出。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | OrderTransitionRespVO | 结构不变,本单未改 | + +#### 请求示例 + +```http +POST /v3/admin/order/2098979230573330434/confirm-itinerary HTTP/1.1 +Authorization: Bearer {token} +Content-Type: application/json + +{} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { "orderId": "2098979230573330434", "orderStatus": "CONFIRMED" } +} +``` + +#### 空数据 / 降级响应 + +不涉及空态;失败直接返错误码。 + +```json +{ "code": 200, "success": true, "data": {} } +``` + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| 581036 | ORDER_CONFIRM_CHECKLIST_NOT_PASSED | 5 项 checklist 任一未通过 | 不变(码本身不变),触发条件因 VEHICLE_DONE 判定变化而收窄——免车团的户不再单独因车侧报这个码 | + +```json +{ + "code": 581036, + "message": "确认订单前置校验未通过,请查看具体缺失项", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 免车团的户此前恒因 VEHICLE_DONE=false 报 581036,本单起不再因车侧报错,其余 4 项照常校验,任一不满足仍会报 581036。 +- 判权、事务边界、幂等策略本单未改。 + +--- + +### 3. 分页查询我的订单待办 `GET /v3/admin/order-todos/my/page` + +**VO**: `OrderTodoPageReqVO(Query)→ Result>` + +#### 使用场景 + +定制师工作台「我的待办」列表,`todoType=ASSIGN_VEHICLE` 是其中一类(房型需求 `ASSIGN_ROOM` 同构)。本单只影响该类待办对免车团的户是否出现在「待处理」筛选下。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| status | Query | String | 否 | PENDING/COMPLETED/CANCELLED | 待办状态(不变) | +| todoSource | Query | String | 否 | SYSTEM/MANUAL | 待办来源(不变) | +| orderId | Query | Long | 否 | - | 订单 ID(不变) | +| fromDate | Query | LocalDate | 否 | - | 开始日期(不变) | +| toDate | Query | LocalDate | 否 | - | 结束日期(不变) | +| keyword | Query | String | 否 | - | 标题/订单号关键词(不变) | +| pageNo/pageSize | Query | Integer | 否 | 分页参数继承 PageParam | 不变 | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].todoType | String | 待办类型,如 ASSIGN_VEHICLE(不变) | +| records[].todoLabel | String | 标题,如「用车需求 · 待提交」(不变) | +| records[].status | String | PENDING/COMPLETED/CANCELLED(本单:免车团户级 ASSIGN_VEHICLE 待办自动转 COMPLETED) | +| records[].completedAt | LocalDateTime | 完成时间(本单:免车声明成立时自动写入) | +| (其余字段结构不变,见既有字段集合) | - | - | + +#### 请求示例 + +```http +GET /v3/admin/order-todos/my/page?status=PENDING&pageNo=1&pageSize=20 HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团声明成立后,该户的 ASSIGN_VEHICLE 待办不再出现在 `status=PENDING` 筛选结果里(因为已自动完成): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "records": [], + "total": 0, + "page": 1, + "pageSize": 20 + } +} +``` + +撤回免车后同一户重新出现: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "records": [ + { + "todoId": "1900000000000000099", + "orderId": "2098979230573330434", + "orderNo": "HL202609200001", + "todoType": "ASSIGN_VEHICLE", + "todoTypeName": "用车需求", + "todoLabel": "用车需求 · 待提交", + "status": "PENDING", + "statusName": "待处理" + } + ], + "total": 1, + "page": 1, + "pageSize": 20 + } +} +``` + +#### 空数据 / 降级响应 + +无匹配待办时 `records` 为空数组,`total=0`,不是错误。 + +```json +{ "code": 200, "success": true, "data": { "records": [], "total": 0, "page": 1, "pageSize": 20 } } +``` + +#### 错误响应 + +本单不新增错误码。 + +```json +{ "code": 401, "message": "未登录或登录已过期", "success": false, "data": null } +``` + +#### 业务边界 + +- 只在待办当前处于「需要定制师动作」(空白或被打回)状态时才查一次免车判定,镜像状态已是「完成」/「已放行」时不受影响,不会为每次对账都多查一次。 +- 免车判定与「订单是否需要用车」(`needsVehicle`)是与的关系:`needsVehicle=false` 的户本来就不挂车侧待办,免车判定不改变这类户的行为。 +- 撤回免车(`withdraw`)后,对账逻辑会按原规则重新判断是否需要挂起该待办,不是立即强制重开——若该户此时用车需求确实还未完成,会重新出现。 + +--- + +### 4. 取消成团 `POST /v3/admin/order/group-batch/{groupBatchId}/cancel-group` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +团期详情页「取消成团」按钮,把已成团(RESOURCE_PREPARING)的团期打回招募中(RECRUITING)。判权(`group-batch:manage`)与灰度开关见 `#7608` changelog,本单不改。R10 守卫要求「无已确认子订单 且 无已派单资源」,四项资源里车项此前恒读 `vehicle_ready` 原值;本单起对车项额外剥离「因整团免车而置位」的情形。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | + +(无请求体。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | null | 结构不变,Result | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/1867000000002/cancel-group HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无列表/分页语义,不存在空数据形态。灰度开关降级说明见 `#7608` changelog,本单未改。 + +```json +{ "code": 200, "success": true, "data": null } +``` + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| 589507 | GROUP_BATCH_PERMISSION_DENIED | 无 group-batch:manage 权限(灰度开关开时) | 不变,见 #7608 | +| 589500 | GROUP_BATCH_NOT_FOUND | 团期不存在 | 不变 | +| 589501 | GROUP_BATCH_STATUS_INVALID | 团期不在 RESOURCE_PREPARING | 不变 | +| 589502 | GROUP_BATCH_CANCEL_GROUP_BLOCKED | 有已确认子订单,或住宿/导游/摄影任一已派单,或车项已派单(免车团不计入车项) | 本单起:免车团不再仅因车项被拦 | + +```json +{ + "code": 589502, + "message": "取消成团被阻塞(有已确认子订单或已派单资源)", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 免车团(已声明整团免车、`vehicle_ready=true` 系因此置位)取消成团时,车项不计入「已派单资源」判断;若住宿/导游/摄影三项及已确认子订单数也满足,本次改动后可以取消成团,改前恒被拦。 +- 判定只在 `vehicle_ready=true` 时才去查是否为免车置位(false 时车项本就不算已派单,查了也不改结论),不额外增加常态开销。 +- 取消成团后免车声明本身**不会**被清除(只有整团流团才会关闭正式需求);`vehicle_ready` 随取消成团一并清零,若免车团再次成团,见下条第 5 项。 +- 若团期是「fleet 真配了车、车项 ready=true 且并非免车声明置位」,本单行为与改前完全一致,仍会被拦。 + +--- + +### 5. 成团 `POST /v3/admin/order/group-batch/{groupBatchId}/group` + +**VO**: `GroupBatchFormReqVO(可空)→ Result` + +#### 使用场景 + +团期从招募中(RECRUITING)人工/自动推进到资源准备中(RESOURCE_PREPARING)。改前对导游/摄影两项按 `needs=false` 免闸置位;本单起对车项同样按「该团是否仍有有效的整团免车声明」做免闸置位——这只在「取消成团 → 再成团」这条路径上才有意义(免车只能在成团之后声明)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | + +(请求体字段本单未改,不重复列出。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | null | 结构不变,Result | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/1867000000002/group HTTP/1.1 +Authorization: Bearer {token} +Content-Type: application/json + +{} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +不涉及空态。 + +```json +{ "code": 200, "success": true, "data": null } +``` + +#### 错误响应 + +本单不新增错误码,沿用既有团期状态类错误码(未改)。 + +```json +{ + "code": 589501, + "message": "团期状态不允许当前操作", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 只在「该团存在未被关闭的整团免车声明」时才会在成团事务内额外置 `vehicle_ready=true`;首次成团(此前从未声明过免车)不受影响。 +- 这一步是「取消成团 → 再成团」闭环的必要补丁:改前若不补这一步,免车团被取消成团后再次成团,`vehicle_ready` 会永远停在 false,物资准备等门会被重新卡住,即使免车声明本身还在。 +- 与导摄免闸置位(`needs=false` 场景)同一次事务、同一份实现风格,互不影响。 + +--- + +### 6. 手工复判物料门 `POST /v3/admin/order/group-batch/{groupBatchId}/recheck-material-gate` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +运维/管理员定点解卡用,手工复判「资源准备中 → 物料准备中」四项资源就绪门(住宿/车/导游/摄影),不必等 5 分钟一次的兜底扫描任务。判权 `group-batch:manage`。本单起「车」这一项读的是联动后的 `vehicle_ready`,免车团视为该项已过。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | + +(无请求体。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| advanced | Boolean | 本次调用是否把团期推进到了物料准备中(结构不变) | +| currentStatus | String | 复判后团期状态码(结构不变) | +| currentStatusName | String | 复判后团期状态中文名(结构不变) | +| blockedGate | String | 未推进时挡住的门:RESOURCE_NOT_READY=房车导摄四项未齐 / CONTRACT_INSURANCE_NOT_READY=合同或保险未出齐;已推进或被并发推进时为 null(本单:免车团不会因车项落在 RESOURCE_NOT_READY) | +| blockedOrderId | Long(String) | blockedGate=CONTRACT_INSURANCE_NOT_READY 时第一个挡住的子订单 ID(结构不变) | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/1867000000002/recheck-material-gate HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团、住宿导摄也齐、合同保险未齐: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "advanced": false, + "currentStatus": "RESOURCE_PREPARING", + "currentStatusName": "资源准备中", + "blockedGate": "CONTRACT_INSURANCE_NOT_READY", + "blockedOrderId": "2098979230573330434" + } +} +``` + +对照:同一团合同保险也齐全: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "advanced": true, + "currentStatus": "MATERIAL_PREPARING", + "currentStatusName": "物料准备中", + "blockedGate": null, + "blockedOrderId": null + } +} +``` + +#### 空数据 / 降级响应 + +幂等设计:重复调用第二次因状态已变,`advanced=false`、`blockedGate` 按当前实际状态给出,不是错误。 + +```json +{ "code": 200, "success": true, "data": { "advanced": false, "blockedGate": null } } +``` + +#### 错误响应 + +本单不新增错误码。 + +```json +{ + "code": 401, + "message": "未登录或登录已过期", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 车项判断委托给与「三、11/12」团期详情/看板同一份 `vehicleReady` 读取(配车完成或整团免车),本节不重复实现一份判断。 +- 免车团若住宿/导游/摄影任一未齐,仍会报 `blockedGate=RESOURCE_NOT_READY`,只是车项本身不再是卡点。 + +--- + +### 7. 手工复判进入待出发硬门 `POST /v3/admin/order/group-batch/{groupBatchId}/recheck-departure-gate` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +运维/管理员定点解卡用,手工复判「物资准备中 → 待出发」七项硬门(团期已成团/房车导摄四项配齐/物资已确认/子订单已确认/出行人证件完整/合同保险全齐/主报账人已设)。判权 `group-batch:manage`。本单只影响门②「房车导摄四项配齐」(`gateCode=RESOURCE_READY`)对免车团的判定。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | + +(无请求体。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| advanced | Boolean | 本次是否推进到「待出发」(结构不变) | +| notApplicable | Boolean | 团期不存在或当前状态不是「物资准备中」时为 true,此时 gates 为空数组(结构不变) | +| currentStatus | String | 复判时刻团期状态码(结构不变) | +| currentStatusName | String | 复判时刻团期状态中文名(结构不变) | +| blockedGateNames | String | 未通过的门中文名逗号串;全通过或未求值时为空串(本单:免车团不会再含「房车导摄四项配齐」) | +| gates[] | List | 七项硬门逐项结果,字段 gateCode/gateName/passed/failReason/blockedOrderId(结构不变) | + +`gates[]` 中 `gateCode=RESOURCE_READY`(门②)一项:改前读四项资源 ready 位原值;本单起车项同导摄免闸位同口径——免车团该子判定视为已过。 + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/1867000000002/recheck-departure-gate HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团、门②之外还有一门未过(如合同保险): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "advanced": false, + "notApplicable": false, + "currentStatus": "MATERIAL_PREPARING", + "currentStatusName": "物资准备中", + "blockedGateNames": "合同保险全齐", + "gates": [ + { "gateCode": "RESOURCE_READY", "gateName": "房车导摄四项配齐", "passed": true, "failReason": null, "blockedOrderId": null }, + { "gateCode": "CONTRACT_INSURANCE", "gateName": "合同保险全齐", "passed": false, "failReason": "子订单 2098979230573330434 合同状态 未出具(需 SIGNED)", "blockedOrderId": "2098979230573330434" } + ] + } +} +``` + +对照:撤回免车后同一团重新调用,门②失败: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "advanced": false, + "notApplicable": false, + "currentStatus": "MATERIAL_PREPARING", + "currentStatusName": "物资准备中", + "blockedGateNames": "房车导摄四项配齐", + "gates": [ + { "gateCode": "RESOURCE_READY", "gateName": "房车导摄四项配齐", "passed": false, "failReason": null, "blockedOrderId": null } + ] + } +} +``` + +#### 空数据 / 降级响应 + +团期不存在或不在「物资准备中」时 `notApplicable=true`、`gates` 为空数组,不是错误。 + +```json +{ "code": 200, "success": true, "data": { "notApplicable": true, "gates": [] } } +``` + +#### 错误响应 + +本单**零新增错误码**(沿用既有设计:状态不符返回回执而非抛错)。 + +```json +{ + "code": 401, + "message": "未登录或登录已过期", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 门②委托给与「三、6」同一份四项资源就绪判定,车项同样认整团免车。 +- 七门全部通过会真的把团期推进到「待出发」,取证/联调时若不希望真推进,需确保至少一门未过。 +- 撤回免车后,门②对该团重新按 `vehicle_ready=false` 判定不通过,直到重新免车或 fleet 真配车。 + +--- + +### 8. 团期合同保险面板 `GET /v3/admin/order/group-batch/{groupBatchId}/contracts` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +团期详情页「合同保险」Tab 面板,展示顶部三格统计与逐户明细,`issuable` 控制「手动开合同/保险」按钮是否可用。判权 `group-batch:view`(读,未改)。本单只影响 `issuable` 对免车团的取值。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | + +(无请求体。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| issuable | Boolean | 是否可以出具合同/保险(本单:免车团车项不再单独卡住该值) | +| currentStatus | String | 团期状态存储值(issuable=false 时前端可据此提示还差什么,结构不变) | +| (其余逐户明细字段) | - | 结构不变,本单未改 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/1867000000002/contracts HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团、房导摄三项也齐: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "issuable": true, + "currentStatus": "RESOURCE_PREPARING" + } +} +``` + +#### 空数据 / 降级响应 + +不涉及空态;团期不存在直接报错误码。 + +```json +{ "code": 200, "success": true, "data": { "issuable": false, "currentStatus": "RESOURCE_PREPARING" } } +``` + +#### 错误响应 + +本单不新增错误码。 + +```json +{ + "code": 589500, + "message": "团期不存在", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- `issuable` 的四项资源判定与「三、6/7」同一份实现委托,车项同口径认整团免车。 + +--- + +### 9. 手动开合同/保险 `POST /v3/admin/order/group-batch/{groupBatchId}/contracts/issue` + +**VO**: `GroupBatchContractIssueReqVO → Result` + +#### 使用场景 + +团期详情页「合同保险」Tab 逐户手动开具,已出具的户自动跳过。判权 `group-batch:contract:issue`(未改)。本单只影响四项资源就绪前置(`589548`)对免车团的车项判断。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | +| orderIds | Body | List\ | 否 | 为空表示全部尚未出具的户 | 不变 | +| target | Body | String | 是 | CONTRACT/INSURANCE/BOTH | 出具目标(不变) | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | GroupBatchIssueResultVO | 结构不变,本单未改 | + +#### 请求示例 + +```json +{ "orderIds": [2098979230573330434], "target": "CONTRACT" } +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { "successCount": 1, "skippedCount": 0, "failedCount": 0 } +} +``` + +#### 空数据 / 降级响应 + +不涉及空态。 + +```json +{ "code": 200, "success": true, "data": { "successCount": 0, "skippedCount": 0, "failedCount": 0 } } +``` + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| 589548 | 字面量码(团期未达四项配齐门) | 房/车/导/摄四项未配齐 | 免车团不再因车项报这个码 | + +```json +{ + "code": 589548, + "message": "房/车/导/摄四项配齐后才能出具合同与保险", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 前置判定与「三、6/7/8」同一份四项资源就绪委托,车项同口径认整团免车。 +- 作废重开端点 `POST .../contracts/reissue` 复用同一份服务实现,行为同构(本清单未单列,契约与本条一致)。 + +--- + +### 10. 发起团期预支 `POST /v3/admin/order/group-batch/{groupBatchId}/advance` + +**VO**: `CreateGroupBatchAdvanceReqVO → Result` + +#### 使用场景 + +团期详情页发起团期级预支,创建一条待站内财务审批的预支单。判权 `group-batch:finance:advance`(未改)。双前置闸门:团期状态门(589541,未改)+ 四项资源就绪门(589542)。本单只影响后者对免车团车项的判断。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | +| (请求体五字段) | Body | - | - | 与订单级预支一致 | 结构不变,本单未改 | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| advanceId | Long(String) | 预支单 ID(结构不变) | +| status | String | 预支单状态(结构不变) | + +#### 请求示例 + +```json +{ "amount": 5000.00, "borrowerType": "DRIVER", "voucherUrls": [] } +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { "advanceId": "1900000000000000123", "status": "PENDING_APPROVAL" } +} +``` + +#### 空数据 / 降级响应 + +不涉及空态。 + +```json +{ "code": 200, "success": true, "data": { "advanceId": null } } +``` + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| 589541 | 字面量码(团期阶段门) | 团期不在物料准备中/待出发/出行中 | 不变 | +| 589542 | 字面量码(四项资源就绪门) | 房/车/导/摄四项未全就绪 | 免车团不再因车项报这个码 | + +```json +{ + "code": 589542, + "message": "房 / 车 / 导 / 摄四项配齐后才能预支", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 前置判定与「三、6/7/8/9」同一份四项资源就绪委托,车项同口径认整团免车。 +- 金额上限走团期统一池(团期级与子订单级共扣一池),本单未改。 + +--- + +### 11. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +团期详情页整页数据源,`vehicleReady` 是「四项资源就绪」展示区块的一个字段。本单只改这一个字段的**含义**,字段名/类型/路径不变。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID(不变) | + +(无请求体。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| hotelReady | Boolean | 配房完成标志(不变) | +| vehicleReady | Boolean | **语义扩大**:改前=「fleet 团级配车就绪」;改后=「配车完成或整团免车」,字段名/类型/路径均不变 | +| guideReady | Boolean | 导游完成标志(不变) | +| photographerReady | Boolean | 摄影完成标志(不变) | +| (其余团期详情字段) | - | 结构不变,本单未改 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/1867000000002 HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团(未经 fleet 真配车,仅声明免车): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "groupBatchId": "1867000000002", + "hotelReady": true, + "vehicleReady": true, + "guideReady": true, + "photographerReady": true + } +} +``` + +#### 空数据 / 降级响应 + +不涉及空态;团期不存在报 589500。 + +```json +{ "code": 589500, "success": false, "data": null } +``` + +#### 错误响应 + +本单不新增错误码。 + +```json +{ + "code": 589500, + "message": "团期不存在", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- `vehicleReady=true` **不能**再简单等同于「fleet 已经排好车」——它现在是「车侧不再阻塞下游门」的合并信号。若前端页面需要精确区分「真配车」与「声明免车」两种情况分别展示不同图标/文案,本单不提供额外字段,需要的话应在另一张单里新增专用字段(本单只按既定契约扩大既有字段含义,不新增字段)。 +- 撤回免车后该字段随之回落为 fleet 侧真实状态(若 fleet 尚未真配车则为 false)。 + +--- + +### 12. 团期看板 `GET /v3/admin/order/group-batch/board` + +**VO**: `无请求体(Query: productId 必填, scope 可选)→ Result>` + +#### 使用场景 + +团期管理看板,按产品维度列出各团期的资源就绪概览。`vehicleReady` 字段含义变化与「三、11」完全一致,只是这里是列表形态。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productId | Query | Long | 是 | - | 产品 ID(不变) | +| scope | Query | String | 否 | - | 范围过滤(不变) | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| [].hotelReady | Boolean | 配房完成标志(不变) | +| [].vehicleReady | Boolean | **语义扩大**:同「三、11」,配车完成或整团免车 | +| [].guideReady | Boolean | 导游就绪标志(不变) | +| (其余看板项字段) | - | 结构不变,本单未改 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/board?productId=2045390643479412737 HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": [ + { "groupBatchId": "1867000000002", "hotelReady": true, "vehicleReady": true, "guideReady": true, "photographerReady": true } + ] +} +``` + +#### 空数据 / 降级响应 + +该产品无团期时返回空数组,不是错误。 + +```json +{ "code": 200, "success": true, "data": [] } +``` + +#### 错误响应 + +本单不新增错误码。 + +```json +{ + "code": 400, + "message": "productId 不能为空", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 同「三、11」的边界说明:`vehicleReady=true` 不再等同「真配车」,前端如需区分请勿在本字段上做强假设。 + +--- + +### 13. 团期待配车候选查询(内部) `POST /v3/internal/group-batch/vehicle-dispatch-candidates` + +**VO**: `GroupBatchVehicleDispatchCandidateReqDTO(hl-common,Body)→ Result>` + +#### 使用场景 + +hl-fleet-service 经内部 Feign 调用,查询「需求已确认但车未就绪」的团期候选,供车管后台「待配车」清单(见下条「14」)间接消费。仅限内部 Feign 调用,不经网关暴露给前端;本单不改请求体、响应结构、错误码,只改过滤条件命中的团期集合——免车团声明成立后不再落在候选集合里。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| requirementConfirmed | Body | Boolean | 否 | - | 过滤「整团需求是否已确认」(不变) | +| vehicleReady | Body | Boolean | 否 | - | 过滤「配车是否已就绪」;取值来源已随本单变化——免车团声明成立后该值联动为已就绪(参数本身不变) | +| (其余分页/日期过滤字段) | Body | - | - | 结构不变 | 本单未改 | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].groupBatchId | Long | 团期主订单 ID(不变) | +| records[].vehicleReady | Boolean | 联动后的配车/免车就绪值(字段不变,取值随本单变化) | +| (其余候选字段) | - | 结构不变,本单未改 | + +#### 请求示例 + +```json +{ "requirementConfirmed": true, "vehicleReady": false, "page": 1, "pageSize": 50 } +``` + +#### 响应示例 + +免车团声明成立前出现在候选里,成立后同一次查询消失(撤回免车后重新出现): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { "records": [ { "groupBatchId": 1867000000003, "vehicleReady": false } ], "total": 1 } +} +``` + +#### 空数据 / 降级响应 + +无匹配候选时 `records` 为空数组,不是错误。 + +```json +{ "code": 200, "success": true, "data": { "records": [], "total": 0 } } +``` + +#### 错误响应 + +本单不新增错误码,沿用既有内部校验错误码。 + +```json +{ "code": 809401, "message": "请求参数非法", "success": false, "data": null } +``` + +#### 业务边界 + +- 本端点自身代码零改动,过滤条件 `vehicleReady` 读取的列值来源已随本单变化(见「⚠️ 关键变化」第 2 条);免车团声明成立后不落在候选集合里,与「14、待配车团期清单(车管后台)」的表现联动一致(该清单经本端点间接取数)。 +- 仅限内部 Feign 调用,不经网关暴露;调用方为 `hl-fleet-service`。 +- 本单本轮未对本端点单独做网关/直连取证(内部 Feign 端点),行为按源码核对(过滤条件复用既有查询实现,仅联动字段来源变化)与「14」端点车管后台侧的间接观测印证,见第八节说明。 + +--- + +### 14. 待配车团期清单(车管后台) `GET /admin/fleet/group-dispatch/pending-batches` + +**VO**: `GroupDispatchPendingBatchPageReqVO(Query)→ Result>` + +> 服务:hl-fleet-service(本单车管后台自身代码零改动,清单变化是因为它查询的团期主数据固定按 `requirementConfirmed=true 且 vehicleReady=false` 过滤,而 `vehicleReady` 的取值来源已随本单变化——见 `#7440` changelog 获取该端点完整字段与错误码定义,本节只描述本单带来的**数据**变化)。 + +#### 使用场景 + +车务后台「待配车」列表,展示需求已确认但车还没配好的团期。判权/网关路由本单未改(沿用 `/admin/fleet/**` → `hl-fleet-service` 既有路由)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| departDateFrom | Query | LocalDate | 否 | 出发日下界 | 不变 | +| departDateTo | Query | LocalDate | 否 | 出发日上界 | 不变 | +| keyword | Query | String | 否 | 团号/团名模糊,≤50 字符 | 不变 | +| dispatchProgress | Query | String | 否 | NOT_STARTED/PARTIAL/FULL | 不变 | +| page | Query | Integer | 否 | 默认 1 | 不变 | +| pageSize | Query | Integer | 否 | 默认 20,1-100 | 不变 | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].groupBatchId | Long(String) | 团期主订单 ID(不变) | +| records[].requirementConfirmed | Boolean | 整团需求是否已确认(不变,本单不改这一维过滤条件) | +| records[].vehicleReady | Boolean | 配车是否已就绪(不变;但清单本身的过滤条件用的是同一个联动后的值,免车团不会出现在这里) | +| (其余字段:batchNo/batchName/departDate/dispatchProgress 等) | - | 结构不变,本单未改 | + +#### 请求示例 + +```http +GET /admin/fleet/group-dispatch/pending-batches?page=1&pageSize=20 HTTP/1.1 +Authorization: Bearer {token} +``` + +#### 响应示例 + +免车团在声明免车之前出现在清单里;声明免车后同一次分页查询里该团消失(`vehicleReady` 过滤条件命中,不再满足「未就绪」): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "records": [ + { "groupBatchId": "1867000000003", "batchNo": "GB-26-0920-01", "requirementConfirmed": true, "vehicleReady": false, "dispatchProgress": "NOT_STARTED" } + ], + "total": 1, + "page": 1, + "pageSize": 20 + } +} +``` + +撤回免车后该团重新出现在清单里。 + +#### 空数据 / 降级响应 + +无匹配团期时 `records` 为空数组;order-v3 团期基线不可达时返回 600012 而非静默空列表(不变,见 `#7440` changelog)。 + +```json +{ "code": 200, "success": true, "data": { "records": [], "total": 0 } } +``` + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| 600012 | 字面量码(团期配车基线不可达) | order-v3 降级或返错 | 不变,见 #7440 | +| 600013 | 字面量码(排班查询参数非法) | 日期倒置/分页越界/关键词超长/进度枚举非法 | 不变 | +| 401 | 未登录 | 网关拦截 | 不变 | + +```json +{ + "code": 600012, + "message": "团期配车基线不可达,请稍后重试", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 本端点自身逻辑与判权一个字都没改,「免车团消失/重现」是团期主数据源头(`vehicleReady`)联动的结果,不是本端点新增的过滤规则。 +- 车务侧若之前依赖「这份清单里没有的团=不用管」的假设,需要知道免车团现在也会从这份清单里消失,但**不代表该团不需要任何车侧关注**(结算侧仍按免车声明整户跳过车侧结算,业务上是自洽的)。 +- 单团配车总览 `GET .../batches/{groupBatchId}/overview` 与资源排班 `GET .../resource-schedule` 两个同 Controller 端点,本单未改,不受影响。 + +--- + +## 四、契约约束与正确调用方式 + +### 正确 / 错误 调用结果对照 + +| 场景 | 结果 | +|------|------| +| 前端按「`vehicleReady=true` 就是 fleet 已经排好车」渲染专属「已排车」图标(错误) | 本单起该字段也会因整团免车而为 true,按此假设渲染会误导用户 | +| 前端把「免车团从 fleet 待配车清单消失」当成「该团车务侧完全不需要关注」(错误) | 清单过滤只是不再要求配车,业务上团仍然是「免车」而非「未处理」,不应被解读为异常 | +| 先撤回免车、再让 fleet 配车(正确) | `vehicleReady` 由撤回复位为 false,之后 fleet 回调真实置位,两者不冲突 | +| 免车期间先让 fleet 按团期 ID 配车、之后再撤回免车(不推荐,已知缺口) | 撤回会把 `vehicle_ready` 清成 false,即使 fleet 已真就绪,团会重新卡门直到 fleet 再回调一次;见「业务边界」 | + +### 切换状态时的必要动作 + +前端不需要为本单改动任何请求参数——14 个端点的入参/路径全部不变,只是响应值会随团期是否声明免车而不同。前端唯一需要做的是**不要对 `vehicleReady`(及其下游门的通过/拦截结果)做「必然等于 fleet 已配车」的强假设**。 + +--- + +## 五、数据库行为 + +免车声明(`waive`)与撤回(`withdraw`)本身的写操作在此前的 changelog 已交代,本单不改这两个端点的请求/响应契约。本单新增的外部可观察写行为是: + +- 声明整团免车成功后,同一次调用内团级「配车/免车就绪」标志被置为已就绪,随之在同一把团期锁内尝试推进团期到「物料准备中」(推进失败只记日志,不影响本次调用的成功返回,由既有兜底扫描任务补推);同时对在团户逐一核对「是否只差车」,满足的户从「资源准备中」推进到「待确认」,对应的「用车需求 · 待提交」待办同步标记完成。 +- 撤回整团免车成功后,团级「配车/免车就绪」标志被复位(不影响团期主状态本身,团期不会因此回退阶段);对满足条件(未确认行程、且车侧确实未完成)的在团户,逐一从「待确认」回退到「资源准备中」,对应待办重新挂起;已经确认行程的户不回退(结算环节仍会因车侧未就绪而拦截)。 +- fleet 团级配车清零回调(既有端点,本单不改契约)若发生在「免车声明仍然有效」期间,本单起会在同一次回调里把团级就绪标志重新置回已就绪(免车声明优先于清零),避免团被误伤打回。 +- 以上写行为均随各自既有事务提交,不额外引入 Feign/MQ 同步写。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截,14 个端点均未改) +- 无权限 → 各端点沿用既有判权码,未改 +- 团期/订单不存在 → 各端点沿用既有错误码,未改 +- 免车团 → 见上文逐条端点的「本单」列 +- 非免车团 → 14 个端点行为与改前逐字节一致 +- 撤回免车后仍处于阻塞窗口(fleet 真配车发生在免车声明期间、之后又撤回)→ 已知缺口,见「业务边界」,须待 `#7442` 收口 +- 老数据兼容:存量团期若从未使用过 `waive`/`withdraw`,14 个端点行为完全不受影响 + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `GET .../group-batch/{groupBatchId}` 与 `board` 的 `vehicleReady` | 语义:fleet 团级配车就绪 | 语义:配车完成或整团免车(字段名/类型/路径不变) | +| `confirm-checklist` 的 `items[VEHICLE_DONE].passed` | 免车团的户恒 false(除非需求行也 DONE) | 免车团的户直接 true | +| `order-todos` 的 `ASSIGN_VEHICLE` 待办 | 免车团的户恒挂起 | 免车团的户自动完成,撤回后重开 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 免车团取消成团(R10 守卫) | 车项恒计入「已派单资源」,常被拦 589502 | 车项不计入(若住宿/导游/摄影/已确认子订单也满足,可以取消) | +| 免车团再次成团 | `vehicleReady` 停在 false | 免车声明仍有效时同步置 true | +| 免车团手工复判物料门 | `blockedGate` 常落在 RESOURCE_NOT_READY(车项未过) | 车项不再是卡点 | +| 免车团出发门②(房车导摄四项配齐) | 恒 fail | 通过(其余三项齐备时) | +| 免车团合同出具/预支 | 恒因四项未齐报错 | 车项不再单独卡住 | +| 免车团出现在 fleet 待配车清单 | 恒出现 | 消失(撤回免车后重现) | + +--- + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是。14 个既有端点的**返回值**在「团期已声明整团免车」这一数据条件下会发生变化(契约结构不变)。这是本单的设计目的:让「本团无需用车」声明真正在各处生效,而不是只写一张孤立的表。 +- **前端是否必须同步上线**: 视情况。若前端此前对这些字段/门禁结果没有做「必然等于 fleet 真配车」的强假设,代码可以零改动;若做了这类假设(例如按 `vehicleReady` 渲染专属「已排车」图标、或把 fleet 待配车清单当成车务全量待办清单),需要同步调整文案/图标含义。 +- **前端 workaround 清理点**: 若前端此前因为「免车团车侧四项门永远打不开」而对这几个页面做过特殊跳过逻辑(例如手动提示运营去走流团),本单合并部署后可以清理——四项门本身会随免车声明正常打开。 + +--- + +## 七、不影响范围 + +- **仅影响**: 上述 14 个端点在「该团已声明整团免车」这一数据条件下的返回**值**;14 个端点自身的方法/路径/参数/响应结构/错误码码值与文案**全部不变**。 +- **零影响**: + - 非免车团在全部 14 个端点上的行为——完全不变。 + - `POST .../vehicle-requirement/waive`、`POST .../vehicle-requirement/withdraw` 自身的请求/响应契约——本单不改,只是它们产生的结果被下游读口消费。 + - `#7441` PR-4(整团确认接车侧,`confirm-check`/`confirm`)——另一份独立 changelog 覆盖,与本单是并列关系,PR-4 需先于本单合并。 + - `#7441` PR-3 已上线的结算闸(`584131`/`584100`)与户级团车完成回写(PR-2/2b/2c 已上线)——本单不改其契约,只是新增了一个共用的只读免车判定服务,供本单与既有结算闸共同使用。 + - `hl-common-*`、`hl-gateway` 路由——本单只改 `hl-order-service-v3`,`hl-fleet-service` 零代码改动(仅消费方数据变化);`/v3/admin/**`、`/admin/fleet/**` 路由沿用既有通配,未新增路由配置。 + - 权限点——未新增。 + +--- + +## 八、测试环境已验证 + +**取证环境**:order-v3 = dev-v3 `f0277a14f`(2026-09-16 01:21 部署,含 PR-4 与本单 PR-2d/2e 全部改动),经 `hl-gateway` 网关真实调用。 + +**已声明整团免车的团,经真实 `waive` 调用后取证(本单核心新增联动,见工单 #7441 验收评论 54939(AC-29c)与 54942(AC-36e / AC-36g / AC-36h / AC-36i)**: + +1. `GET /v3/admin/order/{id}/confirm-checklist`:免车团某户残留一条 `PENDING_REVIEW` 用车需求时,`items[code=VEHICLE_DONE].passed=true`;撤回免车(`withdraw`)后对同一订单发起同一请求,变为 `passed=false`,`failReason="用车需求未完成(镜像: PENDING_REVIEW,当前需求: PENDING_REVIEW)"`——两次调用是同一订单、同一路径的真实前后对照。 +2. `POST /v3/admin/order/{id}/confirm-itinerary`:免车团内两户分别调用均成功(`code=200`),实际响应字段为 `{success, oldStatus, newStatus, oldFlowStatus, newFlowStatus, triggeredEvents}`(例:`oldStatus=CUSTOMIZING` → `newStatus=PENDING_DEPARTURE`,`triggeredEvents=["ASYNC_CONTRACT_GENERATE","ASYNC_INSURANCE_ISSUE"]`),未报 581036。 +3. `POST /v3/admin/order/group-batch/{groupBatchId}/cancel-group`:免车团(活跃 `waive` 声明仍在)调用成功(`code=200`);对照同一批次里非免车团(车侧经 fleet 真实回调就绪)调用同一端点,返回 `code=589502`,`message="取消成团被阻塞(有已确认子订单或已派单资源)"`——两次调用坐实「免车团车项不计入已派单资源」。 +4. `POST /v3/admin/order/group-batch/{groupBatchId}/group`:免车团取消成团后(`waive` 声明仍在)再次成团,`code=200`;紧接 `SELECT batch_status, vehicle_ready FROM order_group_batch` 显示 `vehicle_ready` 由取消成团时的 `0` 回填为 `1`,坐实「免车声明仍有效时成团同步置位」。 + +**`vehicleReady` 联动读取机制经真实数据验证,但本轮取证用的是 fleet 真实回调置位而非 `waive` 声明——同一字段来源,未针对『已声明免车』这一具体数据源单独复测以下 5 个端点**: + +5. `GET /v3/admin/order/group-batch/{groupBatchId}`:fleet 回调前 `vehicleReady=false`,回调后同一团 `vehicleReady=true`,其余字段不变;免车团路径本身未经该端点单独复测。 +6. `POST .../recheck-material-gate`:`blockedReason` 文案由「车=未配置」变为「车=已配置」,`blockedGate` 仍因房未配齐落在 `RESOURCE_NOT_READY`——证明车项判断已跟随联动字段,未取得车项放行后 `blockedGate` 变为合同保险门的完整对照(该团房侧全程未配置)。 +7. `POST .../recheck-departure-gate`:该团尚未进入「物资准备中」,两次调用均 `notApplicable=true`、`gates=[]`,未取得门②真实通过/拦截的对照样本。 +8. `GET .../contracts`:`issuable` 在车项就绪前后均为 `false`(该团房侧未配置,`issuable` 由四项资源共同决定),未取得车项单独放行到 `issuable=true` 的样本。 +9. `POST .../advance`:调用返回 `589541`(团期阶段门,该团仍在 `RESOURCE_PREPARING`,未到「物料准备中」),车项四项资源门(`589542`)未被触发到,本轮未取得该错误码退场的直接对照。 + +**本轮未经网关实测,按源码核对列示(仅代码核对)**:`GET /v3/admin/order-todos/my/page`(待办自动完成/重开)、`POST .../contracts/issue`(手动开具豁免 589548)、`GET /v3/admin/order/group-batch/board`(看板字段语义)、`POST /v3/internal/group-batch/vehicle-dispatch-candidates`(内部候选过滤,见「三、13」业务边界)、`GET /admin/fleet/group-dispatch/pending-batches`(车管后台清单,见「三、14」业务边界)——以上 5 个端点的字段与行为已按源码逐一核对(见各自「出参字段表」),示例响应仍是按源码拼装的合理构造,不是实测原文。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7441](https://git.1814.love:8443/wx/HL/issues/7441) +- 关联 PR: [#7778](https://git.1814.love:8443/wx/HL/pulls/7778)(squash 合入 dev-v3,合并提交 `f0277a14f`) +- 前置依赖:`#7441` PR-4(整团确认接车侧,同一个 PR #7778 内,另一份 changelog 覆盖)、`#7441` PR-1/PR-2/PR-2b/PR-2c/PR-3(免车声明端点、结算闸,均已合并 dev-v3)、`#7608`(取消成团判权改造,本单未改判权逻辑)、`#7440`(车管后台待配车团期清单首次交付,本单只改其过滤结果,不改其契约) +- 后续计划:`#7442`(团期配车写口通电)需收口「免车期间 fleet 真配车、之后撤回免车导致重新卡门」这一已知缺口(见「⚠️ 关键变化」第 6 条) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7441](https://git.1814.love:8443/wx/HL/issues/7441) +- **PR**: [#7778](https://git.1814.love:8443/wx/HL/pulls/7778) +- **Merge commit**: `f0277a14f` + +### 联系人 + +- **后端负责人**: @wx + diff --git a/changelogs-v2/2026-09/16_7441_团期整团确认接车侧-修改接口-管理后台.md b/changelogs-v2/2026-09/16_7441_团期整团确认接车侧-修改接口-管理后台.md new file mode 100644 index 00000000..32374726 --- /dev/null +++ b/changelogs-v2/2026-09/16_7441_团期整团确认接车侧-修改接口-管理后台.md @@ -0,0 +1,517 @@ +--- +schema: "hl-changelog/v2" +ticket: "7441" +title: "团期整团确认改造接入车侧——预检/确认新增车侧字段,checkedResourceTypes 扩至用车,免车团确认不放行车侧" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-16" +status_note: "本单(#7441 PR-4)已由 PR #7778 squash 合入 dev-v3(合并提交 f0277a14f),测试服 order-v3 于 2026-09-16 01:21 部署该提交。confirm-check/confirm 两端点的核心场景(vehicleWaived 逃生口、ready 语义变化、809100/809103/809108/589533 收窄、重复确认幂等、PENDING_RECONFIRM 推进、CAS 回滚 809112)均经 hl-gateway 网关真实调用验证,见第八节。TRAVEL+TRANSFER 同户两类需求并存的场景(AC-13/14/21)因测试环境 TRANSFER 写侧全局开关关闭(809009,#7443 未上线的既有开关)未能验证,按源码核对列示,第八节已如实说明覆盖边界。" +updated_at: "2026-09-16" +base: "dev-v3" +--- + +# order-v3: 团期整团确认改造接入车侧 + +> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3) +> +> **服务**: hl-order-service-v3 (端口 8083) +> **PR**: [#7778](https://git.1814.love:8443/wx/HL/pulls/7778)(squash 合入 dev-v3,合并提交 `f0277a14f`,同批含 PR-2d/PR-2e,另有一份 changelog 覆盖) +> **Issue**: #7441 +> **日期**: 2026-09-16 +> **影响范围**: 团期需求页「整体确认前预检」`GET .../requirement/confirm-check` 与「整体确认需求」`POST .../requirement/confirm` 两个既有管理后台端点,响应新增车侧字段,`confirm` 新增 4 个车侧错误码 + +--- + +## ⚠️ 关键变化(本版与上版行为不同,必读) + +1. **`checkedResourceTypes` 从 `["HOTEL"]` 变为 `["HOTEL","VEHICLE"]`**——`#7535` 当时明确承诺「扩到用车是另一张单」,本单就是那张单。若前端此前按「该数组恒为 `["HOTEL"]`」写死判断,这里会翻转。 +2. **`ready` 的必要条件变多**:改前 `ready = missing.isEmpty() && 阶段可确认`;改后 `ready = missing.isEmpty() && vehicleMissing.isEmpty() && 阶段可确认`。**同一个团可能出现 `missing` 为空数组但 `ready=false`** 的情况,此时必须读新增的 `vehicleMissing` 才能知道原因,不能再假设「`missing` 空即可确认」。 +3. **确认响应新增 7 个车侧字段,3 个旧字段语义保持不变**(`dispatchedOrderIds`/`skippedOrderIds`/`dispatchedCount` 仍然只统计住宿,不含车)。 +4. **`groupVehicleRequirementStatus` 不再恒为 `CONFIRMED`**:车侧已进入 `DISPATCHED` 或 `DONE` 的团再次确认时该字段回**原状态**(不倒退),这是正常态,不要渲染成异常。 +5. **免车团(`vehicleWaived=true`)整团确认不放行车侧**:`vehicleDispatchedCount=0`,三个车侧 ID 列表为空数组,正式需求不推进——这不是漏放,是设计如此。 +6. **`589533` 触发条件收窄为「只管住宿」**:改前住宿或车任一缺失都可能报 `589533`;本单起车侧缺失改抛 809 段专属码,`589533` 的 `{0}` 只统计住宿缺失户数。 +7. **背景信息(非本单改动,供理解字段含义)**:团期管理员可在需求页对整团声明「本团无需用车」(`POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/waive`,已上线)或撤销声明(`POST .../vehicle-requirement/withdraw`,已上线)。本单不改这两个端点的契约,只是预检/确认从此会读取它们产生的结果。声明免车曾经在「已有分组仍要声明免车」时报错码 `809113`;**该码自 `#7441` PR-3(已合并 dev-v3)起停用不再抛出**(改为整份换版为免车版本),本单起进一步不出现 `vehicleMissing` 意义上的相关缺失项,前端若还留有 `809113` 专属提示文案,可以确认无需再触发但不必删除(错误码本身仍占位保留)。 + +--- + +## 一、背景 + +`#7210` 交付的「整体确认」原本只校验、只放行**住宿**:预检 `GET .../requirement/confirm-check` 只看住宿缺失清单,确认 `POST .../requirement/confirm` 只推进住宿需求。团期正式**车**需求(分组 × 逐日 × 成员,由 `PUT/GET .../vehicle-requirement` 两个已上线端点维护)与「本团无需用车」声明(`waive`/`withdraw`,已上线)此前完全不接入这两个端点——车侧需要逐单在订单详情页另行放行,管理员在团期需求页看不到车侧是否齐备。 + +本单(`#7441` PR-4)让这两个已有端点在住宿之外**对称接入车侧**:预检同时给出车侧缺失清单,确认在住宿放行完成后,如果不是免车团,再推进正式车需求并批量放行在团户的车需求。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 整体确认前的缺失预检 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` | 响应新增字段 | 新增 5 个顶层字段 + 车侧缺失清单;`ready`/`checkedResourceTypes` 语义变化 | +| 2 | 整体确认需求(放行住宿+车) | POST | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` | 响应新增字段 + 新增错误码 | 车侧校验接入;免车团跳过车侧放行;新增 7 个响应字段 + 4 个车侧错误码;`589533` 收窄为只管住宿 | + +--- + +## 三、接口详情 + +### 1. 整体确认前的缺失预检 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +团期需求页进入「查看需求」Tab 时调用,以及点击「确认」按钮前调用,用于据 `ready` 置灰按钮、据 `missing`/`vehicleMissing` 展示缺哪些户/哪些车侧问题。只读,零副作用,可任意重复调用。本单起该端点同时覆盖住宿与车侧两类资源。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期聚合主键(不变) | + +(无查询参数、无请求体,本单未改动。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| groupBatchId | Long(序列化为 String) | 团期聚合主键(不变) | +| batchStatus | String | 团期当前状态码(不变) | +| batchStatusName | String | 团期当前状态中文名(不变) | +| ready | Boolean | 语义变化:是否可以整体确认,改为 missing 为空 且 vehicleMissing 为空 且团期处于可确认阶段 | +| missing | List | 改名为「住宿缺失清单」(字段名不变,含义收窄为只描述住宿),结构不变 | +| checkedResourceTypes | List | 取值变化:恒为 ["HOTEL","VEHICLE"](改前恒为 ["HOTEL"]),服务端常量,非按团期配置动态算出 | +| 🆕 vehicleWaived | Boolean | 整团都不需要车时为 true,此时车侧校验整体跳过、vehicleMissing 恒为空数组。这是合法逃生口,不是异常 | +| 🆕 vehicleMissing | List | 车侧缺失清单(按校验顺序全部列出,不按户合并);vehicleWaived=true 时为空数组 | +| 🆕 groupVehicleRequirementId | Long(序列化为 String) | 当前活跃正式用车需求主键;无活跃正式需求时为 null | +| 🆕 groupVehicleRequirementStatus | String | 当前活跃正式用车需求状态;无则 null。取值 DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM——DISPATCHED 与 DONE 同样属于预检可通过的正常状态,不要渲染成异常 | +| 🆕 groupVehicleRequirementVersion | Integer | 当前活跃正式用车需求版本号;无则 null | + +missing[](MissingItem,结构不变,仅补充在本节自包含): + +| 字段 | 类型 | 说明 | +|------|------|------| +| orderId | Long(序列化为 String) | 子订单 ID | +| orderNo | String | 子订单号 | +| customerName | String | 客户姓名 | +| consultantId | String | 定制师 adminId | +| consultantName | String | 定制师姓名快照 | +| reason | String | NOT_SUBMITTED/ROOM_CATEGORY_MISSING/INVALID_REQUIREMENT/NIGHTS_MISMATCH/DAY_NUMBER_INVALID | +| reasonName | String | 缺失原因中文名 | +| dayNumber | Integer | 第几晚(仅 ROOM_CATEGORY_MISSING 有值) | +| segmentIndex | Integer | 第几段(仅 ROOM_CATEGORY_MISSING 有值) | +| expectedNights | Integer | 应住晚数(仅 NIGHTS_MISMATCH/DAY_NUMBER_INVALID 有值) | +| actualNights | Integer | 实际填写晚数(仅 NIGHTS_MISMATCH/DAY_NUMBER_INVALID 有值) | + +🆕 vehicleMissing[](VehicleMissingItem): + +| 字段 | 类型 | 说明 | +|------|------|------| +| reason | String | 取值见「六.5」,与 809 段错误码/809007 一一对应 | +| groupCode | String | 涉及的乘车分组编码;无分组维度时为 null | +| tripDate | LocalDate | 涉及的日期;无日期维度时为 null | +| orderId | Long(序列化为 String) | 涉及的子订单 ID;无订单维度时为 null | +| orderNo | String | 子订单号快照 | +| detail | String | 人话描述,与整团确认时抛出的错误报文逐字相同,可直接展示 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/1867000000001/requirement/confirm-check HTTP/1.1 +Authorization: Bearer {token} +``` + +(无请求体,仅 Path 参数 groupBatchId。) + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "groupBatchId": "1867000000001", + "batchStatus": "RESOURCE_PREPARING", + "batchStatusName": "资源准备中", + "ready": false, + "missing": [], + "checkedResourceTypes": ["HOTEL", "VEHICLE"], + "vehicleWaived": false, + "vehicleMissing": [ + { + "reason": "ORDER_DAY_UNCOVERED", + "groupCode": null, + "tripDate": null, + "orderId": "60123456789001", + "orderNo": "HL2606010001", + "detail": "子订单 60123456789001 的 2026-09-13 没有被任何乘车分组覆盖" + } + ], + "groupVehicleRequirementId": "1868000000001", + "groupVehicleRequirementStatus": "DRAFT", + "groupVehicleRequirementVersion": 3 + } +} +``` + +免车团示例(vehicleWaived=true): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "groupBatchId": "1867000000002", + "batchStatus": "RESOURCE_PREPARING", + "batchStatusName": "资源准备中", + "ready": true, + "missing": [], + "checkedResourceTypes": ["HOTEL", "VEHICLE"], + "vehicleWaived": true, + "vehicleMissing": [], + "groupVehicleRequirementId": "1868000000005", + "groupVehicleRequirementStatus": "CONFIRMED", + "groupVehicleRequirementVersion": 1 + } +} +``` + +#### 空数据 / 降级响应 + +团期尚未提交任何正式车需求(未编辑过 PUT .../vehicle-requirement、也未 waive)时,车侧三个身份字段(groupVehicleRequirementId/Status/Version)均为 null,vehicleMissing 按住宿同款缺失校验给出实际内容(不是空数组,除非该团确实零缺失或已免车)。本端点全程同步内存/DB 读取,不经 Feign/MQ,不产生降级分支。 + +```json +{ "code": 200, "success": true, "data": { "groupVehicleRequirementId": null, "groupVehicleRequirementStatus": null, "groupVehicleRequirementVersion": null, "vehicleMissing": [] } } +``` + +#### 错误响应 + +沿用既有码,本单未新增: + +```json +{ + "code": 589500, + "message": "团期不存在", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 判权沿用既有 group-batch:demand:confirm(GroupBatchPermissionGuard.PERMISSION_DEMAND_CONFIRM),本单未改。 +- 车侧校验集合口径与保存草稿(PUT .../vehicle-requirement)、整团确认(见下条)完全共用一份校验内核,三处同一份数据得到同一个结论——本端点的 vehicleMissing 为空当且仅当真去点确认不会报车侧错误码(并发写入除外)。 +- vehicleWaived=true 时车侧校验整体跳过,与是否有历史违规无关。 +- 老数据兼容:本单不改任何已有字段的类型或序列化方式,存量前端若忽略新字段仍可正常渲染住宿部分;但 checkedResourceTypes 与 ready 的取值/语义已变,见「⚠️ 关键变化」。 + +--- + +### 2. 整体确认需求(放行住宿+车) `POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` + +**VO**: `无请求体 → Result` + +#### 使用场景 + +团期需求页点击「确认」按钮时调用。改前只校验并放行住宿;本单起在住宿放行成功之后,非免车团额外推进正式车需求并批量放行在团户的车需求(行程用车 TRAVEL + 接送机 TRANSFER 两类)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期聚合主键(不变) | + +(无请求体,本单未改动。) + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| groupBatchId | Long(序列化为 String) | 团期聚合主键(不变) | +| requirementConfirmed | Boolean | 团期需求整体确认标记,成功后恒 true(不变) | +| dispatchedOrderIds | List(String) | 语义不变:仍只统计住宿,本次放行的住宿子订单 | +| skippedOrderIds | List(String) | 语义不变:仍只统计住宿 | +| dispatchedCount | Integer | 语义不变:仍只统计住宿(= dispatchedOrderIds.size()) | +| 🆕 vehicleDispatchedOrderIds | List(String) | 本次由「待审核」放行的【行程用车 TRAVEL】需求所属子订单 | +| 🆕 transferDispatchedOrderIds | List(String) | 本次由「待审核」放行的【接送机 TRANSFER】需求所属子订单 | +| 🆕 vehicleSkippedOrderIds | List(String) | 车侧本次未动的子订单(两类合并去重;该户该类车需求已非「待审核」)。整团免车时为空数组 | +| 🆕 vehicleDispatchedCount | Integer | 车侧本次放行的需求条数 = vehicleDispatchedOrderIds.size() + transferDispatchedOrderIds.size()。⚠️ 是条数不是户数,一户两类都放行计 2 | +| 🆕 groupVehicleRequirementId | Long(序列化为 String) | 本次确认所对应的正式车需求主键;整团免车且无正式需求(存量判据)时为 null | +| 🆕 groupVehicleRequirementStatus | String | 确认后正式车需求的实际状态,不是恒为 CONFIRMED:源状态 DRAFT/PENDING_RECONFIRM → 回 CONFIRMED;CONFIRMED 保持 CONFIRMED;DISPATCHED/DONE 回原值(车侧不动、状态不倒退,属正常态)。免车且无正式需求时为 null | +| 🆕 groupVehicleRequirementVersion | Integer | 确认后正式车需求版本号(推进到 CONFIRMED 时已 +1,原样返回时不变);免车且无正式需求时为 null | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/1867000000001/requirement/confirm HTTP/1.1 +Authorization: Bearer {token} +``` + +(无请求体,仅 Path 参数 groupBatchId。) + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "groupBatchId": "1867000000001", + "requirementConfirmed": true, + "dispatchedOrderIds": ["60123456789001", "60123456789002", "60123456789003"], + "skippedOrderIds": [], + "dispatchedCount": 3, + "vehicleDispatchedOrderIds": ["60123456789001", "60123456789002", "60123456789003", "60123456789004", "60123456789005"], + "transferDispatchedOrderIds": [], + "vehicleSkippedOrderIds": [], + "vehicleDispatchedCount": 5, + "groupVehicleRequirementId": "1868000000001", + "groupVehicleRequirementStatus": "CONFIRMED", + "groupVehicleRequirementVersion": 2 + } +} +``` + +免车团响应示例(vehicleDispatchedCount=0): + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "groupBatchId": "1867000000002", + "requirementConfirmed": true, + "dispatchedOrderIds": ["60123456789006"], + "skippedOrderIds": [], + "dispatchedCount": 1, + "vehicleDispatchedOrderIds": [], + "transferDispatchedOrderIds": [], + "vehicleSkippedOrderIds": [], + "vehicleDispatchedCount": 0, + "groupVehicleRequirementId": "1868000000005", + "groupVehicleRequirementStatus": "CONFIRMED", + "groupVehicleRequirementVersion": 1 + } +} +``` + +#### 空数据 / 降级响应 + +不存在空态:本端点是写操作,要么全部成功返回上述结构,要么整个事务回滚并抛错误码。没有部分成功或静默降级的分支。 + +```json +{ "code": 200, "success": true, "data": { "vehicleDispatchedOrderIds": [], "transferDispatchedOrderIds": [], "vehicleSkippedOrderIds": [], "vehicleDispatchedCount": 0 } } +``` + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| 589501 | GROUP_BATCH_STATUS_INVALID | 团期非可确认阶段 | 不变 | +| 589533 | GROUP_BATCH_REQUIREMENT_INCOMPLETE | 语义收窄:只在住宿缺失时抛,{0} 仍是住宿缺失户数 | 收窄 | +| 🆕 809100 | GROUP_VEHICLE_REQUIREMENT_NOT_FOUND | 有在团需车户却无正式车需求 | 本单起会抛 | +| 🆕 809101 | GROUP_VEHICLE_REQUIREMENT_STATUS_INVALID | 正式车需求状态不在 {DRAFT,CONFIRMED,DISPATCHED,DONE,PENDING_RECONFIRM},或推进 CAS 落空 | 本单起会抛 | +| 🆕 809103-809110 | 车侧八条逐日/成员/人数校验 | 见「六.5」 | 本单起会抛 | +| 🆕 809007 | TRANSFER_SERVICE_DATES_NOT_BACKFILLED | 待放行的接送机需求未回填服务日;{0} 为该需求主键(数字,非字符串) | 本单起会抛 | +| 🆕 809112 | GROUP_VEHICLE_DISPATCH_CAS_FAILED | 放行某户车需求时并发冲突(CAS 落空),整团事务回滚,零写入 | 本单起会抛 | + +```json +{ + "code": 589533, + "message": "仍有 2 户未提交需求或需求不完整,无法整体确认,请先查看缺失清单", + "success": false, + "data": null +} +``` + +车侧违规示例(该团抛第一条命中的违规,取决于哪条违规先被发现): + +```json +{ + "code": 809109, + "message": "子订单 60123456789001 的 2026-09-13 没有被任何乘车分组覆盖", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 顺序不可调换:① 阶段守卫(589501)→ ② 住宿缺失校验(589533,零写入)→ ③ 车侧校验(免车团整体跳过,否则抛第一条违规,零写入)→ ④ 置团级标记 + 写团级时间线 → ⑤ 逐户住宿放行 → ⑥ 非免车团:推进正式车需求 → ⑦ 非免车团:逐户放行车需求两类 → ⑧ 事务提交后异步通知房务。全部在同一个事务里,任一步失败整团零写入(809112 整团回滚即靠这一点)。 +- 住宿排在车侧之前:两边都缺时仍报 589533,与改造前一致;只有住宿齐备、车侧有问题时才会看到 809 段码。 +- 免车团(vehicleWaived=true)跳过 ⑥⑦ 两步:正式需求不推进(版本不 +1)、逐户车需求不放行(在团户残留的待审核车需求本次不动,需要就走逐单放行入口)。 +- 809007 排在放行动作之前抛:预检阶段(上一条端点)已经把这类需求列进 vehicleMissing,ready=true 时不会再遇到本码,两者结构性一致。 +- 重复确认不失败:正式车需求已是 CONFIRMED 时重复确认走幂等成功,不抛 809101;已放行的车需求本次计入 vehicleSkippedOrderIds 而不是报错——与住宿侧口径一致。 +- 只改住宿的再次确认必须成功:车侧已 DISPATCHED/DONE、只有住宿需求被打回重提时,再次点整团确认——正式车需求保持原状态不倒退,车侧逐户按「已放行」计入 vehicleSkippedOrderIds(vehicleDispatchedCount=0),住宿正常放行。改前这一场景会被状态白名单直接拒掉,团再也确认不了;本单起这条路走通。 +- 本端点不取车侧专属锁:与住宿放行共用团期需求锁,车侧两步不额外加锁(避免自锁)。 +- 判权沿用既有 group-batch:demand:confirm,本单未改。 + +--- + +## 四、契约约束与正确调用方式 + +### 正确 / 错误 调用结果对照 + +| 场景 | 结果 | +|------|------| +| 预检 ready=true 后立即点确认,期间无其他人并发改动(正确) | confirm 成功,不因校验类错误码失败(CAS 类失败如 809101/809112 不在此等价关系内) | +| 前端只判断 missing.isEmpty() 就以为可以确认(错误) | 车侧仍可能缺失,点确认会报 809 段码;必须同时判 vehicleMissing.isEmpty()(或直接看 ready) | +| 前端按「checkedResourceTypes 恒为 ["HOTEL"]」写死判断(错误) | 本单起恒为 ["HOTEL","VEHICLE"],写死判断会得出错误结论 | +| 前端按「groupVehicleRequirementStatus 恒为 CONFIRMED」渲染确认结果(错误) | 车侧已 DISPATCHED/DONE 时该字段回原值,不是 CONFIRMED;按恒等判断会误判为异常 | + +### 切换状态时的必要动作 + +前端渲染「整体确认」按钮的可用性时,必须把 vehicleMissing.isEmpty() 并入判断(或直接读 ready,不要自己用 missing.isEmpty() 重新计算);确认成功后的提示文案不能只读旧三个字段(否则车侧放行结果对用户不可见)。 + +--- + +## 五、数据库行为 + +预检端点全程只读,不产生任何写入。确认端点在同一个事务里,除既有的「置团级确认标记 + 住宿放行」外,本单额外做两件事:①非免车团把当前活跃正式车需求的状态从「待确认」推进为「已确认」(若已经是「已确认」及之后的状态则保持不变,不会倒退);②非免车团把在团户的行程用车与接送机需求从「待审核」批量放行为「待处理」。任一步失败(含并发写入冲突)都会让本次确认动作(含住宿放行)整体回滚,不会出现部分成功。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- 无权限 → 沿用既有 group-batch:demand:confirm 判权(未改) +- 团期不存在 → 589500(未改) +- 团期非可确认阶段 → 589501(未改) +- 住宿缺失 → 589533(收窄为只管住宿) +- 车侧缺失(非免车团)→ 809 段码,见「三、2」错误响应表 +- 免车团 → 车侧校验/放行整体跳过,不产生任何车侧相关错误 +- 老数据兼容:存量团期第一次读取本端点时,若从未编辑过车需求,groupVehicleRequirementId/Status/Version 均为 null,不异常 + +--- + +## 六.5、枚举 + +### vehicleMissing[].reason(服务端内部校验原因码,无独立 Java 枚举类,值见下表) + +**所属字段**: `vehicleMissing[].reason` | **类型**: `String` + +| 值 | 对应错误码 | 说明 | +|----|------|------| +| GROUP_REQUIREMENT_NOT_FOUND | 809100 | 有在团需车户却无正式车需求 | +| GROUP_REQUIREMENT_STATUS_INVALID | 809101 | 正式车需求状态不在合法集合 | +| NO_GROUP | 809103 | 本团存在需要用车的子订单,却零乘车分组 | +| GROUP_CODE_INVALID | 809104 | 乘车分组重复或试图改名 | +| DAY_OUT_OF_GROUP_RANGE | 809105 | 分组逐日行不在本组服务日范围内或重复提交 | +| DAY_GAP_IN_GROUP_RANGE | 809106 | 分组缺少某日的用车人数 | +| MEMBER_FOREIGN_ORDER | 809107 | 子订单不属于本团期,不能作为乘车成员 | +| MEMBER_DUPLICATE_DAY | 809108 | 子订单在同一日同时属于两个分组 | +| ORDER_DAY_UNCOVERED | 809109 | 子订单某日没有被任何乘车分组覆盖 | +| HEADCOUNT_LESS_THAN_MEMBERS | 809110 | 分组当日用车人数小于当日成员户数 | +| 🆕 TRANSFER_SERVICE_DATES_NOT_BACKFILLED | 809007 | 待放行的接送机需求未回填服务日;只带 orderId,groupCode/tripDate 为 null;处置是先回填服务日再确认;复用 #7439 既有码,非本单新增 | + +### groupVehicleRequirementStatus(com.hulalv.order.groupbatch.enums.GroupVehicleRequirementStatus) + +**所属字段**: `confirm-check`/`confirm` 响应的 `groupVehicleRequirementStatus` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| DRAFT | 草稿 | 尚未提交审核 | +| CONFIRMED | 已确认 | 整团确认推进后的常见终态 | +| DISPATCHED | 已放行车务 | 团车已开始配车;确认时保持不动,不倒退 | +| DONE | 已完成 | 团车配完;确认时保持不动,不倒退 | +| PENDING_RECONFIRM | 待重确认 | 再次确认时可推进到 CONFIRMED | + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| confirm-check.checkedResourceTypes | 恒 ["HOTEL"] | 恒 ["HOTEL","VEHICLE"] | +| confirm-check.ready | missing.isEmpty() && 阶段可确认 | missing.isEmpty() && vehicleMissing.isEmpty() && 阶段可确认 | +| confirm-check 车侧字段 | 不存在 | 新增 vehicleWaived/vehicleMissing/groupVehicleRequirementId/Status/Version 5 个 | +| confirm.dispatchedOrderIds/skippedOrderIds/dispatchedCount | 语义为「住宿」 | 语义不变,仍只统计住宿 | +| confirm 车侧字段 | 不存在 | 新增 7 个:见出参字段表 | +| 589533 触发条件 | 住宿或车任一缺失 | 只在住宿缺失时触发 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 团期需求页点「确认」,住宿齐、车不齐 | 200 成功(车侧无感) | 809 段码阻断(非免车团) | +| 车侧已 DISPATCHED/DONE,住宿被打回重提后再次确认 | 809101 阻断,团再也确认不了 | 200 成功,车侧保持原状态、住宿正常放行 | +| 免车团点「确认」 | (本端点改造前免车概念不影响本端点) | 200 成功,车侧三个 ID 列表为空、vehicleDispatchedCount=0 | +| 预检时车侧有缺失 | ready 不受车侧影响(预检本不检查车) | ready=false,需读 vehicleMissing | + +--- + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是。此前 ready=true(只看住宿)的部分团在本单合并部署后,若车侧未齐备会变成 ready=false;589533 触发条件收窄,改前依赖它同时报告车缺失的前端提示会失真。 +- **前端是否必须同步上线**: 是。按「⚠️ 关键变化」逐条改:checkedResourceTypes 判断、ready/vehicleMissing 联合判断、确认响应新增字段的展示、groupVehicleRequirementStatus 不再恒 CONFIRMED 的容错。 +- **前端 workaround 清理点**: 无(新增字段与语义收紧,非清理旧逻辑)。 + +--- + +## 七、不影响范围 + +- **仅影响**: `GET .../requirement/confirm-check` 与 `POST .../requirement/confirm` 两个既有端点的响应体与 `confirm` 的错误码集合。 +- **零影响**: + - 路径、Path 参数、请求体(均无请求体)——完全不变。 + - 判权码 group-batch:demand:confirm——未改。 + - PUT/GET .../vehicle-requirement、POST .../vehicle-requirement/withdraw、POST .../vehicle-requirement/waive 四个已上线端点自身的契约——本单不改,只是被读取结果。 + - POST .../requirement/reject(按户打回)、GET .../requirement-summary(全团需求汇总)——本单未改,按户打回逐户需求的语义完全不动。 + - POST /v3/admin/order/{id}/vehicle-requirement/dispatch(逐单放行)——保留,与本单整团路径共用同一份副作用实现,未新增未删除。 + - hl-common-*、hl-fleet-service、hl-gateway 路由——本单只改 hl-order-service-v3,/v3/admin/** 路由沿用既有通配,未新增路由配置。 + +--- + +## 八、测试环境已验证 + +**取证环境**:order-v3 = dev-v3 `f0277a14f`(2026-09-16 01:21 部署,含本单 PR-4 全部改动),经 hl-gateway 网关真实调用,见工单 #7441 验收评论 54924(AC-8/9/10/11/12/15/18)与 54939(AC-16)。 + +`GET .../requirement/confirm-check`: +- 免车逃生口(`vehicleWaived=true`):`ready=true`、`missing=[]`、`vehicleMissing=[]`,车侧三个身份字段均为 `null`(团 2099918391610314754)。 +- 车侧正式需求缺失(`vehicleWaived=false`):`vehicleMissing=[{"reason":"GROUP_REQUIREMENT_NOT_FOUND","detail":"团期 2099918391610314754 尚未形成正式用车需求"}]`。 +- `ready` 语义变化:房齐备、车零分组的团返回 `ready=false`、`missing=[]`、`vehicleMissing=[{"reason":"NO_GROUP",...}]`——「`missing` 空但 `ready=false`」的形态经真实调用坐实。 +- 同日同户重复归属(数据经 SQL 直接向 `order_group_vehicle_group`/`order_group_vehicle_group_day` 造重叠行,读侧取证):`vehicleMissing=[{"reason":"MEMBER_DUPLICATE_DAY","groupCode":"GB","tripDate":"2026-10-24","orderId":"2099919145398046721","orderNo":"HL20260916015153509"}]`。 +- 房侧缺失清单:`missing` 含两条真实户(`orderId` 2099918640269627394 / 2099918650218516481,`reason=NOT_SUBMITTED`)。 + +`POST .../requirement/confirm`: +- 免车团确认成功:`code=200`、`vehicleDispatchedCount=0`、`groupVehicleRequirementId=null`。 +- 车侧需求缺失:`code=809100`,`message="团期 2099918596187598850 尚未形成正式用车需求"`。 +- 房齐车零分组:`code=809103`,`message="本团存在需要用车的子订单,至少要提交一个乘车分组"`。 +- 房缺 2 户、车侧已免车:`code=589533`,`message="仍有 2 户未提交需求或需求不完整,无法整体确认,请先查看缺失清单"`(`{0}=2` 坐实)。 +- 同日同户重复归属:三入口(`PUT`/`confirm-check`/`confirm`)均报 `code=809108`,`message="子订单 2099919145398046721 在 2026-10-24 同时属于分组 GA、GB,同一户同一日只能属于一个分组"`——`PUT` 由业务接口直接触发重叠数据被拒;`confirm-check`/`confirm` 因 `PUT` 会在写入前就拒绝重叠数据、结构上无法经业务路径把该状态存进库,按 SQL 造重叠行后走读侧验证。 +- 重复确认幂等:单户团 T0 `confirm` 成功(`vehicleDispatchedOrderIds=["2099919607505596417"]`、`vehicleDispatchedCount=1`、`groupVehicleRequirementStatus=CONFIRMED`、`version=2`),间隔 >5 秒后 T1 再次 `confirm` 成功且不抛 809101(`vehicleSkippedOrderIds=["2099919607505596417"]`、`vehicleDispatchedCount=0`,状态与版本不变)。 +- `PENDING_RECONFIRM → CONFIRMED`:正式需求经 SQL 置为 `PENDING_RECONFIRM` 后再次 `confirm`,成功且 `groupVehicleRequirementStatus=CONFIRMED`(`version=3`)。 +- CAS 落空整团回滚:并发导致放行第 3 户车需求时 CAS 落空,抛 `809112`,前 2 户车需求状态、房侧放行结果、正式需求状态、团期确认标记全部回滚,零写入(评论 54939)。 + +**仅代码核对,未经该场景网关验证**:AC-13/14/21 要求的「同一户同时提交 TRAVEL + TRANSFER 两类需求、`confirm` 一次响应内房 3 户 + 车 5 条同时出现」这一组合场景,因测试环境 TRANSFER 写侧全局开关关闭(既有错误码 809009,`#7443` 派车侧尚未上线的独立开关,与本单代码无关)未能取得;待该开关在测试环境临时打开后补测,结果回写工单 #7441 验收评论。已取得的替代证据:全员仅提交 TRAVEL 需求的团确认成功,`vehicleDispatchedOrderIds` 含 3 户、`transferDispatchedOrderIds=[]`、`vehicleDispatchedCount=3`——证明 TRAVEL 单类路径工作正常,但未覆盖两类需求同户并存、以及打回其中一类后另一类不受影响(`POST .../vehicle-requirement/reject?kind=TRANSFER`)的场景;相关字段的类型与计算口径已按源码核对(见「出参字段表」与「六.6」),这部分示例响应仍是按源码拼装的合理构造,不是该组合场景的实测原文。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7441](https://git.1814.love:8443/wx/HL/issues/7441) +- 关联 PR: [#7778](https://git.1814.love:8443/wx/HL/pulls/7778)(squash 合入 dev-v3,合并提交 `f0277a14f`) +- 前置依赖:`#7535`(首次引入 checkedResourceTypes,本单是其承诺的「扩到用车」那张单)、`#7210`(confirm-check/confirm 首次交付,只管住宿)、`#7441` PR-1/PR-2/PR-2b/PR-2c/PR-3(团期正式车需求声明、整团免车、团车完成回写、结算闸,均已合并 dev-v3,本单依赖它们提供的数据但不改它们的契约) +- 关联文档:本单合并部署后的户级/团级联动效果(确认行程清单、待办、团期详情看板、物资门等)见同批另一份 changelog(`#7441` PR-2d/PR-2e,同一个 PR #7778) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7441](https://git.1814.love:8443/wx/HL/issues/7441) +- **PR**: [#7778](https://git.1814.love:8443/wx/HL/pulls/7778) +- **Merge commit**: `f0277a14f` + +### 联系人 + +- **后端负责人**: @wx +