--- schema: "hl-changelog/v2" ticket: "7972" title: "接送机用车的免车闸与结算闸改为非对称判据" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "not_required" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "本次改动为后端行为调整(错误码判定条件变化),网关无需变更。gateway_status not_required 意味着网关侧无需部署(网关只转发,错误码处理由后端定义)。 前端实证维持 not_required(mmg 2026-09-20):809114/584131 全仓零命中,两码均走拦截器透后端 message 不建字典(同 8091xx 惯例);TRANSFER 前端不可达(开关未开,#7443 维持挂起),判据变化无可消费面;开放后须知(809114 只看 TRAVEL、584131 finalize 硬阻、免车不豁免 TRANSFER)已入前端 memory。" updated_at: "2026-09-20" base: "dev-v3" --- # order-v3: 接送机用车的免车闸与结算闸改为非对称判据 > **存放目录**: changelogs-v2/2026-09/ > > **服务**: hl-order-service-v3 > **PR**: #7976 > **Issue**: #7972 > **日期**: 2026-09-20 > **影响范围**: 管理后台团期用车需求编辑、整团确认、订单结算门禁 --- ## ⚠️ 关键变化 免车判据从「**两种需求(TRAVEL+TRANSFER)任一已进行就拒**」改为「**只看 TRAVEL,TRANSFER 独立判**」。 - **前端以为**:只有 TRANSFER(接送机)、没有 TRAVEL(团车)的户被 809114 拒绝免车 - **实际现在**:809114 放行这类户;584131 只针对 TRANSFER 本身判(不存在/DONE 放行,其余拒) **结算闸新增硬阻**:有 TRANSFER 且未完工的户在 finalize 时被 584131 拒绝,且**存量 PENDING 的 TRANSFER 会在本次上线后对应户 finalize 变 584131**(非回归,是改动预期行为)。 --- ## 一、背景 **只订接送机、不订团车** 是合法的在团户(存在真实订单),但被原判据的两支 OR 条件挡住。 免车意在「本团无需用车」,而只有 TRANSFER 的户仍有接送机需求,整团免车不能替接送机放行。 改为非对称判据后:整团免车只豁免 TRAVEL,接送机必须逐户推进到 DONE。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 保存团期用车需求 | PUT | `/admin/group-batch/{groupBatchId}/vehicle-requirement` | 行为变化 | 809114 放宽(不挡仅 TRANSFER 户) | | 2 | 整团确认 | POST | `/admin/group-batch/{groupBatchId}/vehicle-requirement/confirm` | 行为变化 | 同上 | | 3 | 订单结算 | POST | `/admin/order/{orderId}/submit` | 行为变化 | 新增 584131 拒绝条件 | | 4 | 读团期用车需求 | GET | `/admin/group-batch/{groupBatchId}/vehicle-requirement` | 行为变化 | 只读,无副作用 | --- ## 三、接口详情 ### 1. 保存团期用车需求 `PUT /admin/group-batch/{groupBatchId}/vehicle-requirement` **VO**: `GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO` #### 使用场景 管理员在团期用车需求编辑页保存草稿或点「整团免车」。 本次改动只影响点「整团免车」按钮时的 809114 门禁判定:只有 TRANSFER(接送机)、没有 TRAVEL(团车)的户现在能通过。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | | reason | Body | String | ✅ | 不含空格、不超 200 字 | 免车原因(写入备注留痕) | | operatorId | 上下文 | String | ✅ | 取决于登录用户 | 操作人 ID(管理员工号) | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | requirementId | Long | 正式需求聚合主键 | | status | String | 状态(DRAFT/CONFIRMED/DISPATCHED/DONE) | | groups | Array | 乘车分组列表,免车声明时为空数组 | | version | Integer | 乐观锁版本号 | | confirmedBy | String | 确认人 ID(免车时为当前操作人) | | confirmedAt | LocalDateTime | 确认时刻 | #### 请求示例 ```json { "reason": "只订接送机,不需要团车" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "requirementId": 7200000000000001, "groupBatchId": 7100000000000001, "status": "CONFIRMED", "groups": [], "version": 2, "confirmedBy": "admin001", "confirmedAt": "2026-09-20 10:30:00", "remark": "[2026-09-19 15:22:00 撤回] 改房型; [2026-09-20 10:30:00 免车] 只订接送机,不需要团车" }, "success": true } ``` #### 空数据 / 降级响应 无(必返回需求对象)。 #### 错误响应 ```json { "code": 809114, "message": "车务已开工(子订单 5100000000000001:PROCESSING),不能再声明整团免车", "success": false, "data": null } ``` #### 业务边界 - **809114 新判据**(仅 TRAVEL 分支):检查全部在团户的 active TRAVEL(非 TRANSFER)需求,若任一已进入 PENDING/PROCESSING/DONE,整团免车被拒 - **TRANSFER 不挡免车**:即使某户有 active TRANSFER 处于上述状态,809114 不再拦住,转由结算闸(584131)独立硬阻 - **幂等**:整团已经是免车态(零分组已确认版本)时重复点免车返回 200,不抛错、不推版本 - **无副作用**:本端点不调 fleet,不修改户级需求状态,只操作团期层面的正式需求聚合根 --- ### 2. 整团确认 `POST /admin/group-batch/{groupBatchId}/vehicle-requirement/confirm` **VO**: `无请求体 → GroupVehicleRequirementRespVO` #### 使用场景 整团确认页面完成预检后点确认按钮。整团确认作为保存需求的上游,同样需要过 809114 门禁(只看 TRAVEL)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | requirementId | Long | 正式需求聚合主键 | | status | String | 状态(DRAFT/CONFIRMED/DISPATCHED/DONE) | | groups | Array | 乘车分组列表 | | version | Integer | 乐观锁版本号 | | confirmedBy | String | 确认人 ID | | confirmedAt | LocalDateTime | 确认时刻 | #### 请求示例 ```json {} ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "requirementId": 7200000000000001, "status": "CONFIRMED", "groups": [ { "groupCode": "BUS", "memberCount": 4, "days": [ { "tripDate": "2026-10-01", "headcount": 4 } ] } ], "version": 1, "confirmedBy": "admin002", "confirmedAt": "2026-09-20 11:00:00" }, "success": true } ``` #### 空数据 / 降级响应 无。 #### 错误响应 ```json { "code": 809114, "message": "车务已开工(子订单 5100000000000002:DONE),不能再声明整团免车", "success": false } ``` #### 业务边界 - **与保存草稿共用同一把团级锁**:concurrency 写入时后到者抛 809102 - **状态转移**:确认后正式需求进入 CONFIRMED,版本 +1 --- ### 3. 订单结算 `POST /admin/order/{orderId}/submit` **VO**: `无独立请求体 → SettlementSubmitRespVO` #### 使用场景 管理员在订单结算页面点「提交核单」。本次改动新增 584131 硬阻:有 TRANSFER(接送机)且未完工的户无法 finalize。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | orderId | Path | Long | ✅ | - | 子订单 ID | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | orderId | Long | 子订单 ID | | status | String | finalize 后的订单状态 | #### 请求示例 ```json {} ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "orderId": 5100000000000001, "status": "SETTLED" }, "success": true } ``` #### 空数据 / 降级响应 无。 #### 错误响应(新增 584131) ```json { "code": 584131, "message": "接送机:PENDING(团期已声明整团免车,跳过用车户级 TRAVEL 闸;但接送机未安排完成)", "success": false } ``` #### 业务边界 **新增判据(584131,非对称)**: - **TRAVEL 分支**:若整团已声明免车(zero-group CONFIRMED 版本),TRAVEL 判定被短路,跳过需求检查 - **TRANSFER 分支(独立)**: - 不存在 active TRANSFER(未订接送机)→ 放行 - TRANSFER 状态 = DONE → 放行 - TRANSFER 状态 = PENDING/PROCESSING/其他 → 硬阻 584131,不受整团免车影响 **存量数据行为**:production 中停在 `PENDING` 的 TRANSFER 需求行(9 条已于 2026-09-19 走真实端点 reject 清掉),所属户的 finalize 在本次上线后会从 200 变 584131——这是本次改动的**预期行为**,不是回归。 --- ### 4. 读团期用车需求 `GET /admin/group-batch/{groupBatchId}/vehicle-requirement` **VO**: `无请求体 → GroupVehicleRequirementRespVO` #### 使用场景 编辑页首次打开或重新加载时回填当前需求。本接口是配车刷新状态在 admin 侧唯一的**无副作用观测口**(#7988),不取锁、不进事务、无任何写操作。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | requirementId | Long | 正式需求聚合主键 | | status | String | 状态(DRAFT/CONFIRMED/DISPATCHED/DONE) | | groups | Array | 乘车分组列表 | | version | Integer | 乐观锁版本号 | | confirmedBy | String | 确认人 ID | | confirmedAt | LocalDateTime | 确认时刻 | #### 请求示例 ```json {} ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "requirementId": 7200000000000001, "status": "CONFIRMED", "groups": [], "version": 2, "confirmedBy": "admin001", "confirmedAt": "2026-09-20 10:30:00", "planRefreshStatus": "FAILED", "planRefreshFailReason": "fleet timeout" }, "success": true } ``` #### 空数据 / 降级响应 ```json { "code": 200, "message": "成功", "data": null, "success": true } ``` #### 错误响应 ```json { "code": 589500, "message": "团期不存在", "success": false, "data": null } ``` #### 业务边界 - **零写入**:不锁、不改库、不进事务、不调 fleet、不推版本 - **存在性**:返回 null 表示「尚未形成正式需求」(正常),不区分「本该有但丢了」 --- ## 四、契约约束与正确调用方式 ### ✅ 正确 / ❌ 错误 payload 对照 | 场景 | 端点 | payload | 结果 | |------|------|---------|------| | ✅ 仅 TRANSFER,免车通过 | PUT `/admin/group-batch/{id}/vehicle-requirement` | `{"reason":"只订接送机"}` | 200,状态 CONFIRMED,groups=[] | | ✅ 仅 TRAVEL,整团确认通过 | POST `/admin/group-batch/{id}/vehicle-requirement/confirm` | (无体) | 200,状态 CONFIRMED,groups=[{BUS}] | | ❌ 仅 TRANSFER,TRAVEL 已 PROCESSING,免车拒绝 | PUT `/admin/group-batch/{id}/vehicle-requirement` | `{"reason":"..."}` | 409114「车务已开工」 | | ✅ 有 TRANSFER 已 DONE,finalize 通过 | POST `/admin/order/{id}/submit` | (无体) | 200 | | ❌ 有 TRANSFER 未完工,finalize 拒绝 | POST `/admin/order/{id}/submit` | (无体) | 584131「接送机未完成」 | ### 前后端协议 - **809114 的「拒绝户」变了**:从「TRAVEL 或 TRANSFER 任一进行」改为「仅看 TRAVEL 进行」。前端如有针对 809114 的兜底提示,需复查文案准确性 - **584131 是新条件**:不是对 809114 的替代,而是针对 TRANSFER 独立的硬阻(结算端) - **两道分离**:免车判定(团期层,809114)与结算判定(订单层,584131)是两套独立的门禁,分别对 TRAVEL 与 TRANSFER 行为 --- ## 五、数据库行为 ### 免车声明 | 前置条件 | 结果 | |----------|------| | 用户A: TRAVEL=PENDING, TRANSFER=null | 809114 拒绝(TRAVEL 已进行) | | 用户B: TRAVEL=null, TRANSFER=PENDING | 809114 放行(TRANSFER 不挡) → 免车声明成功 | | 用户C: TRAVEL=DONE, TRANSFER=DONE | 809114 拒绝(TRAVEL 已进行) | ### finalize 闸门 | 前置条件 | 结果 | |----------|------| | TRANSFER=null | 584131 放行 | | TRANSFER=DONE | 584131 放行 | | TRANSFER=PENDING | 584131 拒绝(新判据) | ### 版本化 - 整团免车时若目标版本是「带分组已确认」,会先失活旧版本(CAS),再插入一条「零分组已确认」的新版本 - `version` 字段自动 +1,乐观锁冲突返 809102 --- ## 六、边界行为 - 未登录 → 401(网关拦截) - 团期不存在 → 589500 - 正式需求不存在 → 809100 - 下游 fleet 超时 → 返 200,不阻断 finalize(结算与车配解耦) - 团期被锁(并发写) → 等待后重试 --- ## 六.5、枚举 ### VehicleRequirementStatus(用车需求状态) **所属字段**: `GroupVehicleRequirementRespVO.status` | **类型**: `String` | 值 | 中文 | 说明 | |-----|------|------| | `DRAFT` | 草稿 | 编辑中 | | `CONFIRMED` | 已确认 | 整团确认/免车声明后 | | `DISPATCHED` | 已派发 | fleet 配车完成 | | `DONE` | 已完成 | 订单 finalize 后 | | `PENDING_RECONFIRM` | 待重确认 | 被打回重新编辑后的中间态 | ### VehicleRequirementKind(需求类型) **所属字段**: 内部枚举,不直接暴露给前端 | **类型**: `String` | 值 | 中文 | 说明 | |-----|------|------| | `TRAVEL` | 团车 | 整团用车需求 | | `TRANSFER` | 接送机 | 机场接送 | --- ## 六.6、修改前后对比 ### 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 809114 判定 | 检查 TRAVEL 和 TRANSFER,任一进行则拒 | 只检查 TRAVEL,TRANSFER 不挡 | | 接送机只户的免车 | 被 809114 拒绝 | 809114 放行(若 TRANSFER 未完工则被 584131 拒) | | finalize 对 TRANSFER | 无专项检查 | 新增 584131:不存在/DONE 放行,其余拒 | | 整团免车对 TRANSFER | 整户豁免 | 不豁免,TRANSFER 仍需逐户到 DONE | | flow 推进(行程清单) | 只看 TRAVEL | 追加 TRANSFER 检查:即使整团免车也要看 TRANSFER | --- ## 六.7、影响评估 - **是否破坏向后兼容**:是(行为变化) - **前端是否必须同步上线**:否(后端改动前兼容,后端改动后错误码体验改善但不影响流程) - **前端 workaround 清理点**: - 若有针对 809114「TRANSFER 已进行」的兜底提示,改成只提「TRAVEL 已进行」 - 若有假设「整团免车豁免一切需求」的逻辑,补充 TRANSFER 的检查(与结算闸 584131 同口径) --- ## 七、不影响范围 - **仅影响**:管理后台团期车需求编辑、整团确认、订单结算三处门禁 - **零影响**: - 小程序(MP 端点) - 户级用车需求的创建/更新(只改 validation 判定,不改流程) - 价格、保险、酒店等其他模块 - 一期业务(dev 分支无此改动) --- ## 八、测试环境已验证 部署版本: `8cdb9ad97`(包含提交 572dc4037) ``` ✓ PUT /admin/group-batch/7100000000000001/vehicle-requirement {"reason":"只订接送机"} → 200 + status=CONFIRMED + groups=[] ✓ POST /admin/group-batch/7100000000000001/vehicle-requirement/confirm → 200 + CONFIRMED ✓ POST /admin/order/5100000000000001/submit (TRANSFER=DONE) → 200 ✓ POST /admin/order/5100000000000002/submit (TRANSFER=PENDING) → 584131 (新判据) ✓ GET /admin/group-batch/7100000000000001/vehicle-requirement → 200 + 当前需求 ``` --- ## 九、相关历史 PR | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|----------| | #7763 | #7441 PR-3 | 首次引入 584131 硬拦「缺团车需求」 | ✅ 有效(本次改动收紧范围,只看 TRANSFER) | | #8033 | #7988 | 补只读观测口(本端点) | ✅ 有效 | | **本 PR #7976** | **#7972** | 收口 809114/584131 的 kind 口径,非对称判定 | ✅ 最新 | --- ## 十、相关文档 - 关联 Issue: [wx/HL#7972](https://git.1814.love:8443/wx/HL/issues/7972) - 关联 PR: [wx/HL#7976](https://git.1814.love:8443/wx/HL/pulls/7976) - Merge commit: [wx/HL@572dc4037](https://git.1814.love:8443/wx/HL/commit/572dc4037) --- ## 关联 / 联系人 ### 链接 - **Issue**: [#7972](https://git.1814.love:8443/wx/HL/issues/7972) - **PR**: [#7976](https://git.1814.love:8443/wx/HL/pulls/7976) - **Merge commit**: [572dc4037](https://git.1814.love:8443/wx/HL/commit/572dc4037) ### 联系人 - **后端负责人**: @wx