From 00a6f15b91c829bce874d5a633531dec1fae4266 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 19 Sep 2026 02:16:48 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#7442=20=E8=A1=A5=E7=99=BB?= =?UTF-8?q?=20reconfigure=20=E5=86=99=E5=8F=A3=E4=B8=8E=E5=B0=B1=E7=BB=AA?= =?UTF-8?q?=E5=9B=9E=E5=86=99=E4=B8=A4=E7=BA=A7=E5=88=A4=E5=AE=9A=EF=BC=88?= =?UTF-8?q?PR-A=20/=20PR-C1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这两个 PR 合入时都没写交接件,是排查 #7442 AC-22(要求交接件含全部端点契约) 时发现的既有缺口: - PR-A #7844 的 `POST /admin/fleet/group-dispatch/batches/{id}/reconfigure` 是本单最核心的写口,此前无任何 changelog 记录 - PR-C1 #7923 的就绪回写带身份两级判定同样缺 两份都按实际合入的代码写,不照工单原文(该单正文被订正过多次)。 准入按角色门禁写:`X-Admin-Role ∈ {VEHICLE_MANAGER, SUPER_ADMIN}` (`FleetAdminRoleGuardInterceptor`)——工单里写的权限点 `fleet:group-dispatch:write` 全仓零命中,只存在于一行 javadoc 注释里,不是落地的权限模型。 backend_status=deployed:两个提交均为 4cbccc26b 祖先,测试服七服务已回读确认。 gateway_status=verified:两份清单里的端点均已在测试服取得实测请求/响应。 Refs #7442 Co-Authored-By: Claude Opus 5 (1M context) --- ...½¦分组写口reconfigure补登-新增接口-管理后台.md | 322 ++++++++++++++ ...°±绪回写带身份两级判定补登-修改接口-管理后台.md | 409 ++++++++++++++++++ 2 files changed, 731 insertions(+) create mode 100644 changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md create mode 100644 changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md b/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md new file mode 100644 index 00000000..be6eeeac --- /dev/null +++ b/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md @@ -0,0 +1,322 @@ +--- +schema: "hl-changelog/v2" +ticket: "7442" +title: "团期配车分组写口 reconfigure(PR-A 补登)" +consumer: "admin" +author: "wx(GIT)" +change_type: "新增接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "本文档是补登,不是新上线通知。PR-A(#7844,merge commit 8eb8e13cdfd4a8cc004f95cfb53f7e4b81904461,2026-09-16 合入 dev-v3)首次交付本端点时漏写了交接件,导致 #7442 AC-22(要求『5 个新增接口 + 4 个改造接口的完整契约』)按字面一直不可能达成;本单是对这个缺口的补登。backend_status=deployed 的依据:8eb8e13cd 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于 8eb8e13cd 与后续 c6aa1224f,均已实测回读 commit 确认在链上)。gateway_status 先记 pending,网关实测由 #7442 取证车道另行补。本文档按端点当前(2026-09-19)的完整契约撰写,其中 requirementId/requirementVersion/demands[].assignments[].groupId 等分组相关字段是 PR-A 首次引入的核心内容;reconfigureWindowToken 字段与 602011/602012/602013 三个错误码是随后 PR-C2(#7957)追加到同一端点的字段,本文档一并如实标注,避免把 PR-C2 之后的完整契约错当 PR-A 原状描述。" +updated_at: "2026-09-19" +base: "dev-v3" +--- + +# fleet: 团期配车分组写口 reconfigure(PR-A 补登) + +> **存放目录**: changelogs-v2/{YYYY-MM}/ +> +> **服务**: hl-fleet-service (端口 8082) +> **PR**: [#7844](https://git.1814.love:8443/wx/HL/pulls/7844)(PR-A,2026-09-16 已合入;本端点此后又被 [#7957](https://git.1814.love:8443/wx/HL/pulls/7957) PR-C2 追加一个字段,详见下方标注) +> **Issue**: #7442(AC-22) +> **日期**: 2026-09-19(补登;端点实际上线于 2026-09-16) +> **影响范围**: 团期配车页——车务提交整团逐日配车计划的核心写口 + +--- + +## ⚠️ 关键变化 + +1. **这是补登,不是新功能上线通知**:本端点已在测试服跑了 3 天(2026-09-16 起),**mmg 可能已经在对接它**—— + 本文档只是把此前漏写的交接件补齐,不代表这是新上线的东西,请勿据此重新走一遍"新接口接入"流程。 +2. **本单是团级配车从"零调用方"到"有真实写口"的分水岭**:改前,`GroupDispatchStatus` 相关的团级配车引擎 + 虽已存在,但生产调用方为零,车务只能靠车管手工建单兜底;改后,车务可在团期配车页把整团逐日计划直接提交。 +3. **`reconfigureWindowToken` 字段与 602011/602012/602013 三个错误码不属于 PR-A**:它们是随后 PR-C2 + (2026-09-18 合入)追加到本端点的,服务「受控重开窗口」流程(见 + `19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md`)。本文档按端点**当前**的完整契约 + 撰写(两次改动都已部署),但在字段表/错误码表里标注了各自的来源批次,避免误以为这是 PR-A 一次性交付的。 +4. **`missingGroupCodes` 字段恒为空列表,不要依赖它判断缺组**:响应 `coverage.missingGroupCodes` + 在任何路径上都只能是 `[]`——真的缺组时走的是抛 602002 异常的路径,根本不产生响应体,「缺组」这个结论 + 永远不会通过这个字段表达出来。前端如果要展示"缺哪些组",请用捕获到的 602002 错误消息(点名到组), + 或改查 `GET /internal/fleet/dispatch/group-batch/{id}/coverage`(内部接口,见 + `19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md` 第 4 条,那个端点的同名字段才有 + 非空的可能)。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 整团逐日配车提交 | POST | `/admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure` | 新增(补登;PR-A 已部署 3 天) | 车务按乘车分组提交整团逐日配车计划,服务端与现状差量比对 | + +--- + +## 三、接口详情 + +### 1. 整团逐日配车提交 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure` + +**VO**: `GroupDispatchReconfigureReqVO → GroupDispatchReconfigureRespVO` + +#### 使用场景 + +车务在团期配车页提交整团逐日配车计划(差量重配:多删少补,旧记录软删留痕,新需求新写,不整团作废重配)。 +服务端以 order-v3 提供的权威团期基线(服务日、生命周期、权威乘车分组清单)校验:完整覆盖、无越界、无重复 +且团期可配才允许提交。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID | +| requirementId | Body | Long | 是 | - | 本次计划照着哪一份正式团级用车需求排;与基线不一致抛 602005 | +| requirementVersion | Body | Integer | 是 | - | 本次计划照着需求的哪一版排;落后于基线当前版本抛 602005(fail-closed,不接受"反正车没变") | +| clearAll | Body | Boolean | 否 | 默认 `false` | true=显式整团清零,软删该团全部活跃配车并释放车辆/司机占用,`demands` 可为空 | +| reconfigureWindowToken | Body | String | 条件必填 | - | 【**PR-C2 追加**】受控重开窗口令牌,团期仍在 `RESOURCE_PREPARING` 时可不传;团期过了资源准备阶段后必填,且必须与 order-v3 下发的窗口令牌一致,否则 602012 | +| demands | Body | Array | 条件必填 | `clearAll=false` 时非空 | 逐日配车需求;行程日期不可重复(600003) | +| demands[].tripDate | Body | LocalDate | 是 | - | 行程日期 | +| demands[].assignments | Body | Array | 是 | 至少一条 | 当日全部车/司机组合,非空(600004) | +| demands[].assignments[].groupId | Body | String | 是 | ≤64 字符 | 乘车分组键(=需求侧 `group_code`,如 `BUS`);空抛 602000,不在基线清单抛 602001 | +| demands[].assignments[].vehicleId | Body | Long | 是 | - | 派出车辆 ID;同日重复抛 600006(车辆被占) | +| demands[].assignments[].driverId | Body | Long | 否 | - | 派出司机 ID;可空=仅排车未排司机;同日重复抛 600007(司机被占) | +| demands[].assignments[].remark | Body | String | 否 | ≤200 字符 | 备注 | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| groupBatchId | String(雪花 ID) | 团期主订单 ID | +| requirementId | String(雪花 ID) | 本次计划所依据的正式团级用车需求 ID | +| requirementVersion | Integer | 本次计划所依据的需求版本 | +| planVersion | Long | 本次落库后的团期计划版本;幂等短路时为当前版本,不递增 | +| addedCount | Integer | 新增派车记录数 | +| removedCount | Integer | 软删派车记录数(减员) | +| keptCount | Integer | 保留未变派车记录数 | +| updatedCount | Integer | 就地更新(换组/换司机/改备注)派车记录数 | +| aliveCount | Integer | 提交后整团存活派车记录总数 | +| addedDispatchIds | Array\(雪花 ID) | 新增派车记录主键列表 | +| idempotentShortCircuit | Boolean | 本次是否被计划去重短路;`true`=计划与上次完全一致、本次未落库——**这是幂等成功,不是失败**,前端不要按错误提示 | +| coverage | Object | 按乘车分组的覆盖明细,见下 | +| coverage.groups[] | Array | 按组的覆盖明细 | +| coverage.groups[].groupCode | String | 分组键 | +| coverage.groups[].vehicleType | String | 车型文本/字典值 | +| coverage.groups[].requiredDates | Array\ | 本组权威服务日 | +| coverage.groups[].coveredDates | Array\ | 本次计划为本组实际排车的日期 | +| coverage.groups[].missingDates | Array\ | 本组缺失的服务日 | +| coverage.groups[].outOfRangeDates | Array\ | 本组越界的日期 | +| coverage.groups[].satisfied | Boolean | 本组是否已满足 | +| coverage.missingGroupCodes | Array\ | **恒为空列表**(见「⚠️ 关键变化」第 4 条),不要依赖它判断缺组 | +| coverage.wholeBatchSatisfied | Boolean | 全团行程日整体覆盖是否成立 | +| legacyGroupRowCount | Integer | 本团存活派车行里无分组键的历史行数(非错误,仅留痕,见 #7442 AC-32) | + +#### 请求示例 + +```json +POST /admin/fleet/group-dispatch/batches/1934567890123456800/reconfigure +{ + "requirementId": 1934567890123456789, + "requirementVersion": 3, + "clearAll": false, + "demands": [ + { + "tripDate": "2026-09-12", + "assignments": [ + {"groupId": "BUS", "vehicleId": 1001, "driverId": 2001, "remark": "大巴组"}, + {"groupId": "SUV", "vehicleId": 1002, "driverId": 2002} + ] + } + ] +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "1934567890123456800", + "requirementId": "1934567890123456789", + "requirementVersion": 3, + "planVersion": 7, + "addedCount": 2, + "removedCount": 0, + "keptCount": 0, + "updatedCount": 0, + "aliveCount": 2, + "addedDispatchIds": ["99011", "99012"], + "idempotentShortCircuit": false, + "coverage": { + "groups": [ + { + "groupCode": "BUS", + "vehicleType": "宇通33座大巴", + "requiredDates": ["2026-09-12"], + "coveredDates": ["2026-09-12"], + "missingDates": [], + "outOfRangeDates": [], + "satisfied": true + } + ], + "missingGroupCodes": [], + "wholeBatchSatisfied": true + }, + "legacyGroupRowCount": 0 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +`clearAll=true` 时 `demands` 可为空数组,属正常请求形态(整团清零),响应仍返回完整对象, +`removedCount` 反映本次软删的行数、`aliveCount=0`: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "1934567890123456800", + "requirementId": "1934567890123456789", + "requirementVersion": 3, + "planVersion": 8, + "addedCount": 0, + "removedCount": 2, + "keptCount": 0, + "updatedCount": 0, + "aliveCount": 0, + "addedDispatchIds": [], + "idempotentShortCircuit": false, + "coverage": { "groups": [], "missingGroupCodes": [], "wholeBatchSatisfied": false }, + "legacyGroupRowCount": 0 + }, + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 602002, + "message": "以下乘车分组整组未排车: SUV", + "data": null, + "success": false +} +``` + +可能的错误码: +- `600001` - 团期批次 ID 不能为空 +- `600002` - 逐日配车需求不能为空 +- `600003` - 逐日需求存在重复行程日期 +- `600004` - 单日排车列表不能为空 +- `600005` - 排车车辆 ID 不能为空 +- `600006` - 车辆已被占用 +- `600007` - 司机已被占用 +- `600008` - 团期配车已被并发修改,请刷新后重试 +- `600009` - 团期配车权威基线不可用,请稍后重试或检查团期状态 +- `600010` - 团期当前状态不可配车 +- `600011` - 配车计划未完整覆盖团期服务日 +- `602000` - 排车项缺少乘车分组(PR-A) +- `602001` - 乘车分组不存在于本团正式需求(PR-A) +- `602002` - 以下乘车分组整组未排车(PR-A,**点名到组**,不是笼统的"缺日") +- `602003` - 乘车分组的服务日未排满(PR-A) +- `602004` - 乘车分组排了本组服务范围外的日期(PR-A) +- `602005` - 用车需求已更新,请刷新后重新配车(PR-A) +- `602006` - 正式用车需求当前状态不允许配车(PR-A) +- `602009` - 无法取得本团的权威乘车分组清单(PR-A,**失败关闭**,两种成因:该团从没提交过正式用车需求,或需求存在但声明了整团免车) +- `602010` - 配车入参非法(PR-A) +- `602011` - 受控重开窗口内不允许整团清零配车(**PR-C2 追加**) +- `602012` - 受控重开窗口校验不通过(**PR-C2 追加**,缺/错/过期令牌) +- `602013` - 本次配车改动越出重开窗口授权范围(**PR-C2 追加**) +- `809100` / `809101` - order-v3 经 Feign 解包透出(无活跃需求 / 需求状态不允许) + +#### 业务边界 + +- **鉴权**: `X-Admin-Role` 须为 `VEHICLE_MANAGER` 或 `SUPER_ADMIN`(`FleetAdminRoleGuardInterceptor` 角色门禁,**不是权限点**——工单原文与部分源码 javadoc 写的"权限点 `fleet:group-dispatch:write`"在数据库层从未注册,全仓 `*.sql` 搜不到这个字符串,见 `19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md`「关键变化」第 2 条的详细核实过程) +- **防重与幂等是两件事,不要混读**:①10 秒内对同一份计划重复提交会被 `@Idempotent` 防重窗口**拒绝** + (返 100502「团期配车重配处理中,请勿重复提交」),前端按"稍后重试"处理;②窗口**之外**重复提交同一份 + 计划会正常受理并返回 `idempotentShortCircuit=true`——**那是成功**(计划未变、未落库、未产生新意图), + 不要按错误提示。这两条机制彼此独立:前者按 10 秒时间窗判重,后者按计划内容摘要(`planDigest`)判重, + 没有时间窗限制 +- **各组服务日范围可以不同**:A 组走全程、B 组只用三天车时,B 组在第四、五天没有排车行是**正确的**, + 不报缺日——这条既有口径未被 602003 削弱 +- **不校验"是否能确认"**:本端点只管排车,"确认整团配车"是另一个独立端点 + (`POST .../confirm`,见 `17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md`) + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。 + +| 场景 | 做法 | +|------|------| +| 提交计划前需要拿团期权威分组清单 | 该清单在基线里,本端点自己不暴露一个单独的查询口;前端通常从团期需求/配车总览页拿到 | +| 团期在 `RESOURCE_PREPARING` 阶段提交 | `reconfigureWindowToken` 可不传 | +| 团期已过 `RESOURCE_PREPARING`(`MATERIAL_PREPARING`/`PENDING_DEPARTURE`)提交 | 必须先经受控重开端点(`POST .../vehicle-requirement/reopen`)拿到 `windowToken` 并原样带回,否则 602012 | +| 需要整团清零 | 传 `clearAll=true`,`demands` 可省略 | +| 判断"提交成功但计划没变" | 看 `idempotentShortCircuit`,为 `true` 时是成功,不是失败 | + +--- + +## 五、数据库行为 + +| 操作 | 数据库影响 | +|------|----------| +| 正常提交(有增/删/改) | `fleet_group_dispatch` 差量写入:新增行 INSERT、软删行 `status` 置软删并留痕、就地更新行按需变更;`fleet_group_dispatch_plan.plan_version` CAS +1 | +| `clearAll=true` | 该团全部活跃 `fleet_group_dispatch` 行软删,释放车辆/司机占用,`vehicle_ready` 重置为 false | +| 幂等短路(`idempotentShortCircuit=true`) | 零写入,`plan_version` 不变 | + +--- + +## 六、边界行为 + +- **无权威分组清单时失败关闭**(602009):不会因为拿不到分组就放行一份没有分母的计划 +- **同日重复车辆/司机拒绝**(600006/600007):同一天同一辆车/同一名司机出现在两条排车项里直接拒绝,不做去重合并 +- **服务日部分覆盖不是缺陷**:各组服务日范围可以不同,短组在超出自己范围的日子没有排车行是合法的 + +--- + +## 七、不影响范围 + +- **仅影响**: 团期配车页的整团逐日提交动作 +- **零影响**: + - 确认整团配车 `POST .../confirm`(见 `17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md`) + - 受控重开/计划刷新流程的其余 4 个端点(见 `19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md`) + - 团期配车就绪判定、同团车辆共用关系(见 `19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md`) + +--- + +## 八、测试环境已验证 + +> ⚠️ 本节暂无网关实测数据,PR-A 虽已部署但本文档撰写时未另行发起网关请求;gateway_status 待 #7442 取证车道回填。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7442](https://git.1814.love:8443/wx/HL/issues/7442) +- 关联 PR: [wx/HL#7844](https://git.1814.love:8443/wx/HL/pulls/7844)(PR-A,本端点首次交付); + [wx/HL#7957](https://git.1814.love:8443/wx/HL/pulls/7957)(PR-C2,追加 `reconfigureWindowToken` 字段) +- 相关文档: + - `changelogs-v2/2026-09/17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md` + - `changelogs-v2/2026-09/19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md` + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7442](https://git.1814.love:8443/wx/HL/issues/7442) +- **PR**: [#7844](https://git.1814.love:8443/wx/HL/pulls/7844) +- **Merge commit**: [8eb8e13cdfd4a8cc004f95cfb53f7e4b81904461](https://git.1814.love:8443/wx/HL/commit/8eb8e13cdfd4a8cc004f95cfb53f7e4b81904461) + +### 联系人 + +- **后端负责人**: @wx diff --git a/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md b/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md new file mode 100644 index 00000000..937ac085 --- /dev/null +++ b/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md @@ -0,0 +1,409 @@ +--- +schema: "hl-changelog/v2" +ticket: "7442" +title: "团期配车就绪回写带身份 + 两级判定(PR-C1 补登)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "not_required" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "本文档是补登。PR-C1(#7923,merge commit 6f5b1b679f6b534081ca7b136e338cd3fffc1155,2026-09-18 合入 dev-v3)首次交付本次改动时同样漏写了交接件——这是排查 #7442 交接件缺口时顺带发现的第二处(第一处是 PR-A 的 reconfigure 端点,见 19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md)。backend_status=deployed 依据:6f5b1b679 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于本提交,链上包含)。gateway_status 先记 pending。frontend_status 记 not_required:本文档涉及的两个端点都是仅限内部 Feign 调用的接口,不面向 hl-ui,前端无需改动。⚠️ 本文档只覆盖 PR-C1(#7442)原始交付的两级判定部分;#7444 PR-1 随后在 vehicle-ready 端点同一事务内又追加了一步(团级正式需求 DISPATCHED→DONE),那部分内容已在 19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md 交付,本文档不重复。" +updated_at: "2026-09-19" +base: "dev-v3" +--- + +# fleet/order-v3: 团期配车就绪回写带身份 + 两级判定(PR-C1 补登) + +> **存放目录**: changelogs-v2/{YYYY-MM}/ +> +> **服务**: hl-fleet-service (端口 8082)、hl-order-service-v3 (端口 8083) +> **PR**: [#7923](https://git.1814.love:8443/wx/HL/pulls/7923) +> **Issue**: #7442(AC-22) +> **日期**: 2026-09-19(补登;改动实际上线于 2026-09-18) +> **影响范围**: fleet↔order-v3 内部 Feign 回调——团期配车就绪回填/重置两个端点新增身份判定,杜绝乱序/旧版回调污染就绪状态;**不面向 admin 前端,mmg 无需改动** + +--- + +## ⚠️ 关键变化 + +1. **这是补登,不是新功能上线通知**:本改动已部署 1 天以上,mmg 不需要做任何事——两个端点都是内部 + Feign 接口,从未面向前端开放。补登原因同 `19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md`: + #7442 AC-22 要求的交接件缺口排查时顺带发现的。 +2. **根因**:改前,`vehicle-ready`/`vehicle-ready-reset` 两个内部回调端点**只有路径参数**,没有任何版本信息。 + 这条回调经 fleet 侧 Outbox 异步投递,到达 order-v3 时活跃需求可能已经换了一版、fleet 的计划也可能已经 + 又重配了几轮——旧需求产出的就绪意图可能覆盖新需求的未就绪状态,旧计划版本产出的重置意图可能把刚配好车的 + 团打回未就绪。这两种烂法都**不报错**,只在数据上悄悄错。 +3. **两级判定是本次改动的核心**:请求体新增 `requirementId`/`requirementVersion`/`planVersion` 三个身份字段, + 提供方(order-v3)先比对需求身份(第一级,逐字相等),再比对同一需求版本内的计划版本大小(第二级), + 两级都通过才落库;不通过**一律返回 HTTP 200 + `applied=false` + `discardReason`**,不抛错误码——本端点 + 由 fleet 侧 Outbox 重试链路驱动,抛错等于让一条已经该丢弃的意图无限重投。 +4. **legacy 兼容窗口**:请求体声明为 `required=false`。PR-C1 上线前 fleet 已投出、尚未消费完的在途旧意图 + 没有 body,提供方对它们走 legacy 路径(行为与改动前逐字一致)——这是滚动上线的兼容窗口,不是校验豁免, + body 一旦非空,DTO 上的逐字段约束全部生效。 +5. **顺带交付了一条内部可靠性保证("快照顺序不变量"),不影响外部契约**:`AssignmentInsuranceOutboxWriter` + 与相关监听器调整了保险快照/Outbox 事件的写入顺序,确保就绪回调触发的下游动作按稳定顺序执行;这部分 + 纯内部实现细节,不产生任何可观察的接口字段变化,本文档不展开。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 回填配车就绪 | POST | `/v3/internal/group-batch/{groupBatchId}/vehicle-ready` | 修改(请求体由无到有,新增两级判定) | fleet 整团配车完成后回填 vehicle_ready=true,补登 | +| 2 | 重置配车就绪 | POST | `/v3/internal/group-batch/{groupBatchId}/vehicle-ready-reset` | 修改(请求体由无到有,新增两级判定) | fleet 清零/释放后重置 vehicle_ready=false,补登 | + +--- + +## 三、接口详情 + +### 1. 回填配车就绪 `POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready` + +⚠️ **[内部接口,不对前端开放]** 仅限 fleet-service 内部 Feign 调用。 + +**VO**: `GroupBatchVehicleReadyReqDTO → GroupBatchVehicleReadyRespDTO` + +#### 使用场景 + +fleet-service 整团配车完成后调用本端点回填 `vehicle_ready=true`。回填成功后内部自动检测四 ready 闸门, +满足则推进团期状态 `RESOURCE_PREPARING → MATERIAL_PREPARING`。本次改动前该端点只接受路径参数,无法辨别 +一条回调到底产自哪个需求版本、哪个计划版本;本次改动后必须携带身份,经两级判定才落库。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID | +| requirementId | Body | Long | 条件必填(body 整体 `required=false`) | - | 产出本次就绪意图的正式团级用车需求 ID(两级判定第一级) | +| requirementVersion | Body | Integer | 同上 | ≥1 | 产出本次就绪意图的需求版本 | +| planVersion | Body | Long | 同上 | ≥1 | 产出本次就绪意图的 fleet 团期级计划版本(两级判定第二级,同一需求内部单调递增) | +| sourceRefNo | Body | String | 否 | ≤64 | 幂等追溯号(fleet Outbox 记录 ID),仅用于日志对账,不参与判定 | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| applied | Boolean | 本次是否真的把团期 `vehicle_ready` 改成了本意图的方向 | +| discardReason | String | `applied=false` 时的原因常量:`IDENTITY_MISMATCH`/`REQUIREMENT_NOT_CONFIRMED`/`ALREADY_APPLIED`/`PLAN_VERSION_STALE` | +| discardCode | Integer | `applied=false` 且该原因有对应错误码时为 809205(身份不一致)/809206(计划版本落后),否则为 null | +| currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null | +| currentRequirementVersion | Integer | 提供方当前活跃需求版本 | +| currentPlanVersion | Long | 提供方已应用的最高计划版本 | +| batchStatus | String | 回填后的团期状态(可能已被四 ready 闸门推进) | + +#### 请求示例 + +```json +POST /v3/internal/group-batch/1934567890123456800/vehicle-ready +{ + "requirementId": 1934567890123456789, + "requirementVersion": 3, + "planVersion": 7, + "sourceRefNo": "880123" +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "applied": true, + "discardReason": null, + "discardCode": null, + "currentRequirementId": "1934567890123456789", + "currentRequirementVersion": 3, + "currentPlanVersion": 7, + "batchStatus": "MATERIAL_PREPARING" + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +身份不一致时**一律返 200**,不是错误响应: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "applied": false, + "discardReason": "IDENTITY_MISMATCH", + "discardCode": 809205, + "currentRequirementId": "1934567890123456789", + "currentRequirementVersion": 4, + "currentPlanVersion": 8, + "batchStatus": "MATERIAL_PREPARING" + }, + "success": true +} +``` + +请求体缺省(`required=false`,legacy 兼容路径)时行为与改动前逐字一致,不做两级判定: + +```json +POST /v3/internal/group-batch/1934567890123456800/vehicle-ready +``` + +#### 错误响应 + +真正可重试的故障(DB 不可用、CAS 被并发抢跑)仍以异常形式返回失败 Result,由 Outbox 退避重试: + +```json +{ + "code": 500, + "message": "数据库异常", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- **一律返 200**:本端点由 fleet 侧 Outbox 重试链路驱动,「需求已换版」「计划版本落后」这些结论再投多少次 + 都一样,用错误码表达会让这条意图无限重投;是否真的落库看 `applied`,没落库的原因看 `discardReason` +- **幂等性**:重投同一条 `sourceRefNo`,结果保持一致 +- **legacy 兼容窗口是过渡态,不是长期行为**:请求体缺省时的 legacy 路径服务的是 PR-C1 上线前已投出的 + 在途旧意图,不建议新代码依赖这条路径 +- **`discardReason=REQUIREMENT_NOT_CONFIRMED`/`ALREADY_APPLIED` 没有对应错误码**:前者是受控重开窗口期间 + fleet 重配发出的意图正常会落在的分支(预期路径不是异常);后者是 Outbox 重试的正常幂等形态 + +--- + +### 2. 重置配车就绪 `POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready-reset` + +⚠️ **[内部接口,不对前端开放]** 仅限 fleet-service 内部 Feign 调用。 + +**VO**: `GroupBatchVehicleReadyReqDTO → GroupBatchVehicleReadyRespDTO` + +#### 使用场景 + +fleet-service 整团清零或取消成团/流团释放占用后调用本端点重置 `vehicle_ready=false`。判定与「1. 回填配车就绪」 +**完全相同、无任何例外**:先判 `requirementId + requirementVersion` 与当前活跃需求完全一致(不一致丢弃, +`discardReason=IDENTITY_MISMATCH`),再判该需求版本下的 `planVersion` 不落后(落后即丢弃, +`discardReason=PLAN_VERSION_STALE`)。本次改动前该端点同样没有版本信息,旧的 reset 晚到会无条件把 +`vehicle_ready` 打回 false(例如:"清零 plan10 → 重配出 plan11 → plan11 已就绪 → 重放 plan10 的 reset" +会错误地把就绪状态打回 false)。 + +#### 入参字段表 + +字段结构与「1. 回填配车就绪」完全一致(唯一区别是本端点的置位方向固定为 `ready=false`,体现在服务端内部 +处理逻辑上,不是一个显式的请求字段): + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | 是 | - | 团期主订单 ID | +| requirementId | Body | Long | 条件必填(body 整体 `required=false`) | - | 产出本次重置意图的正式团级用车需求 ID(两级判定第一级) | +| requirementVersion | Body | Integer | 同上 | ≥1 | 产出本次重置意图的需求版本 | +| planVersion | Body | Long | 同上 | ≥1 | 产出本次重置意图的 fleet 团期级计划版本(两级判定第二级) | +| sourceRefNo | Body | String | 否 | ≤64 | 幂等追溯号(fleet Outbox 记录 ID),仅用于日志对账 | + +#### 出参字段表 + +字段结构与「1. 回填配车就绪」完全一致: + +| 字段 | 类型 | 说明 | +|------|------|------| +| applied | Boolean | 本次是否真的把团期 `vehicle_ready` 改成了 false | +| discardReason | String | `applied=false` 时的原因常量:`IDENTITY_MISMATCH`/`REQUIREMENT_NOT_CONFIRMED`/`ALREADY_APPLIED`/`PLAN_VERSION_STALE` | +| discardCode | Integer | `applied=false` 且该原因有对应错误码时为 809205/809206,否则为 null | +| currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null | +| currentRequirementVersion | Integer | 提供方当前活跃需求版本 | +| currentPlanVersion | Long | 提供方已应用的最高计划版本 | +| batchStatus | String | 重置后的团期状态 | + +#### 请求示例 + +```json +POST /v3/internal/group-batch/1934567890123456800/vehicle-ready-reset +{ + "requirementId": 1934567890123456789, + "requirementVersion": 3, + "planVersion": 10, + "sourceRefNo": "880130" +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "applied": true, + "discardReason": null, + "discardCode": null, + "currentRequirementId": "1934567890123456789", + "currentRequirementVersion": 3, + "currentPlanVersion": 10, + "batchStatus": "RESOURCE_PREPARING" + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +重放一条落后的 `plan10` reset(当前已推进到 `plan11` 且已就绪)会被丢弃,**不会把就绪状态打回 false**: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "applied": false, + "discardReason": "PLAN_VERSION_STALE", + "discardCode": 809206, + "currentRequirementId": "1934567890123456789", + "currentRequirementVersion": 3, + "currentPlanVersion": 11, + "batchStatus": "MATERIAL_PREPARING" + }, + "success": true +} +``` + +#### 错误响应 + +同「1. 回填配车就绪」: + +```json +{ + "code": 500, + "message": "数据库异常", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- **判定与置位方向完全一致、无任何例外**——起草阶段曾给本端点开过"`planVersion` 落后仍执行"的例外,最终 + 版本删除了这条例外:新增释放/清零本来就会产生一个更高的计划版本,合法的释放意图永远带着新版本到达, + 不需要豁免;反过来,带着落后版本到达的 reset 只可能是旧事件重放 +- **该团已无活跃需求时清零仍照常应用**:这是本方向唯一的口子——流团已经把需求失活,而释放回调仍必须能把 + 标志清掉 +- 其余边界(一律返 200、幂等性、legacy 兼容窗口)同「1. 回填配车就绪」 + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议(本单两个端点均为内部接口,无 UI 直接对接)。 + +| 场景 | 做法 | +|------|------| +| fleet 侧发起就绪回调 | 必须带上产生这条意图那一刻的 `requirementId`/`requirementVersion`/`planVersion`,不能只传 `groupBatchId` | +| 判断回调是否真的生效 | 看响应 `applied`,不要用 HTTP 状态码判断——不通过的判定同样返回 200 | +| 需要排查一条回调为什么没生效 | 看 `discardReason`,四种原因分别指向不同的处置方向(换版 / 窗口期正常分支 / 幂等重放 / 乱序过期),不要合并处理 | + +--- + +## 五、数据库行为 + +| 操作 | 数据库影响 | +|------|----------| +| 两级判定通过、`ready` 方向 | `order_group_batch.vehicle_ready → true`,可能连带推进 `batch_status` | +| 两级判定通过、`reset` 方向 | `order_group_batch.vehicle_ready → false` | +| 两级判定未通过(任一方向) | 零写入 | +| `GroupVehicleRequirementDO` 新增列 | 新增 `plan_version`/相关身份列,供两级判定读取当前已应用的最高计划版本(Flyway `V20260918_*__add_group_vehicle_requirement_add_vehicle_plan_version.sql`) | + +--- + +## 六、边界行为 + +- **legacy 无身份回调**:`req == null` 或 `req.requirementId == null` 时走改动前的行为,不做两级判定 +- **同需求版本内计划版本必须单调不落后**:落后判定只在同一需求版本内部比较,换了需求版本后 fleet 的计划 + 版本并不重置,不会拿跨需求的两个版本比大小 + +--- + +## 六.5 枚举 + +### discardReason(GroupBatchVehicleReadyRespDTO.discardReason) + +**所属字段**: `discardReason` | **类型**: `String` + +| 值 | 中文 | 对应错误码 | 说明 | +|----|------|------------|------| +| `IDENTITY_MISMATCH` | 身份不一致 | 809205 | 回调携带的需求身份与当前活跃需求不一致 | +| `REQUIREMENT_NOT_CONFIRMED` | 需求未在已确认档 | 无 | 受控重开窗口期间的正常路径,不是异常 | +| `ALREADY_APPLIED` | 已应用过 | 无 | Outbox 重试的正常幂等形态 | +| `PLAN_VERSION_STALE` | 计划版本落后 | 809206 | 同一需求版本内计划版本比已应用的旧,乱序到达的过期意图 | + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `vehicle-ready`/`vehicle-ready-reset` 请求体 | 无(仅路径参数) | 新增可选请求体 `requirementId`/`requirementVersion`/`planVersion`/`sourceRefNo`(legacy 兼容,非必填) | +| `vehicle-ready`/`vehicle-ready-reset` 响应体 | 仅 `Result` | 新增 `GroupBatchVehicleReadyRespDTO`:`applied`/`discardReason`/`discardCode`/`currentRequirementId`/`currentRequirementVersion`/`currentPlanVersion`/`batchStatus` | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 旧需求晚到的就绪回调 | 会把新需求的未就绪状态覆盖成就绪(污染) | 身份不一致直接丢弃,`applied=false` | +| 旧计划版本晚到的重置回调 | 会把刚配好车的团打回未就绪(污染) | 计划版本落后直接丢弃,`applied=false` | +| 调用方判断回调是否生效 | 只能靠 HTTP 200 推断(不可靠) | 必须读 `applied` 字段 | + +--- + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 否。请求体新增字段声明 `required=false`,legacy 无身份回调仍走改动前的行为路径;响应体从 `Result` 扩展为携带结构化结果,属于向后兼容的加字段 +- **前端是否必须同步上线**: 否。两个端点均为内部 Feign 接口,不面向 hl-ui +- **前端 workaround 清理点**: 无 + +--- + +## 七、不影响范围 + +- **仅影响**: fleet→order-v3 的就绪回调链路(两个内部端点) +- **零影响**: + - admin/mp 前端可见的任何端点 + - #7442 PR-A 的 `reconfigure` 写口(见 `19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md`) + - #7442 PR-B 的确认/回写链路(见 `17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md`) + - #7442 PR-C2 的受控重开窗口流程(见 `19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md`) + - #7444 PR-1 在本端点追加的 DISPATCHED→DONE 推进(见 `19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md`,本文档不重复该部分) + +--- + +## 八、测试环境已验证 + +> ⚠️ 本节暂无网关实测数据(两个端点为内部接口,不走网关,网关实测本就不适用);内部调用链的验证归属 fleet↔order-v3 集成测试,本文档撰写时未另行发起手工调用,gateway_status 记 pending 供 #7442 取证车道处理。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7442](https://git.1814.love:8443/wx/HL/issues/7442) +- 关联 PR: [wx/HL#7923](https://git.1814.love:8443/wx/HL/pulls/7923) +- 相关文档: + - `changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md` + - `changelogs-v2/2026-09/19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md`(本端点后续被追加 DISPATCHED→DONE 推进的部分) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7442](https://git.1814.love:8443/wx/HL/issues/7442) +- **PR**: [#7923](https://git.1814.love:8443/wx/HL/pulls/7923) +- **Merge commit**: [6f5b1b679f6b534081ca7b136e338cd3fffc1155](https://git.1814.love:8443/wx/HL/commit/6f5b1b679f6b534081ca7b136e338cd3fffc1155) + +### 联系人 + +- **后端负责人**: @wx