diff --git a/changelogs-v2/2026-09/24_8329_团期配车确认重复调用不再返602005-修改接口-管理后台.md b/changelogs-v2/2026-09/24_8329_团期配车确认重复调用不再返602005-修改接口-管理后台.md new file mode 100644 index 00000000..5fbfc4ff --- /dev/null +++ b/changelogs-v2/2026-09/24_8329_团期配车确认重复调用不再返602005-修改接口-管理后台.md @@ -0,0 +1,292 @@ +--- +schema: "hl-changelog/v2" +ticket: "8329" +title: "团期配车确认重复调用不再返 602005:需求版本校验下沉到幂等分支之后,回写推进版本不再让重复确认变成错误" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "v2.1" +verified_at: "" +status_note: "PR #8332 squash 合并 dev-v3(8675e1ff0)。部署:hl-fleet-service dev-v3 @ 2b4656424(2026-09-24 14:03 滚动部署完成;同批 order-v3 亦为 2b4656424)。测试服网关从零实测(自造团期 groupBatchId=2103003304906936322,正式需求 2103003334258675714 v2,3 条配车行):① reconfigure(requirementVersion=2)→ 200;② confirm 第一次(requirementVersion=2)→ 200,confirmedCount=3、alreadyConfirmedCount=0、requirementAdvanceIntent=CONFIRMED_TO_DISPATCHED,回写把正式需求推到 status=DONE、version=3;③ 用提交时的旧版本再 confirm 一次(requirementVersion=2)→ http 200、code=200、message=成功,confirmedCount=0、alreadyConfirmedCount=3。改前同一形态返 602005「用车需求已更新, 请刷新后重新配车(提交 …/v2, 当前 …/v3)」。定向单测 81 绿(GroupDispatchConfirmTest 23 / GroupDispatchServiceTest 58),fleet spotless:check 901 files clean。闸门按「基线需求状态已是 DISPATCHED/DONE(即本端点回写已落地)」放行,状态停在 CONFIRMED/PENDING_RECONFIRM 时仍 fail-closed 抛 602005。" +updated_at: "2026-09-24" +base: "dev-v3" +--- + +# fleet 团期配车: 确认重复调用不再返 602005(版本校验下沉) + +> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/` +> +> **服务**: hl-fleet-service(dispatch 域) +> **PR**: https://git.1814.love/wx/HL/pulls/8332 +> **Issue**: #8329 +> **日期**: 2026-09-24 +> **影响范围**: 车务「团期配车 → 确认配车」按钮的重复点击 / 刷新后重试 + +--- + +## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写) + +- **重复确认不再返 602005**:整团配车确认成功后,本次确认会**推进正式用车需求版本**(回写);此前这个自己造成的版本前进会让「再点一次确认」被判成「需求已更新」,返回 602005 要求刷新重配——而刷新后拿到的还是同一个已完成的团。现在这一档返回 **200**,并以 `confirmedCount=0` / `alreadyConfirmedCount=N` 表达「本来就是已确认状态」。 +- **602005 仍然存在,且判据更精确**:只有当「本次端点的回写**尚未落地**」而版本/状态又对不上时才抛(fail-closed),即真正的「业务侧改了需求,你拿的是旧版本」。 +- **602007 的触发面变化**:本团一行配车行都没有(`assignedCount=0` 且 `alreadyConfirmedCount=0`)时抛 602007「本团没有可确认的配车行」,**不再**被版本校验抢先报成 602005。 +- **602005 报文的两个占位符统一成 `id=…/v…` 形态**:前端**只应按错误码 602005 处置,不要解析 {0} / {1}**。 + +--- + +## 一、背景(选填) + +确认配车是定稿动作,它认下的是「这一版需求对应的这套车」,因此入参带了 `requirementId` + `requirementVersion` 作为需求身份。问题是这个端点自己有副作用:确认成功会把正式需求从 `CONFIRMED` 回写成 `DISPATCHED`,版本因此前进。改前版本校验排在幂等分支**之前**、且在事务外先跑,于是「重复确认」这条正常操作被自己造成的版本前进判成了错误。实测:`POST .../confirm {requirementId:2102982327141556225, requirementVersion:2}` → `code=602005`「用车需求已更新, 请刷新后重新配车(提交 2102982327141556225/v2, 当前 2102982327141556225/v3)」,而同刻回读需求已是 `v3`——前进正是首次确认自己的回写。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 确认整团配车 | POST | `/admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` | 行为变更(校验时机 + 报文占位符口径) | 重复确认返 200;602005 只在「本端点回写未落地」时抛 | + +--- + +## 三、接口详情 + +### 1. 确认整团配车 `POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` + +**VO**: `GroupDispatchConfirmReqVO → GroupDispatchConfirmRespVO` + +#### 使用场景 + +车务在团期配车页点「确认配车」,把本团已排的车一次定稿。**没有防重提交时间窗**:连点多少次都是同一个形态,不会出现「请稍后重试」。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | +| requirementId | Body | Long | ✅ | 须等于本团当前正式需求 | 不等于抛 602005(不受本次改动放宽) | +| requirementVersion | Body | Integer | ✅ | 本次确认所依据的版本 | **语义细化(本单)**:版本不一致时——本团仍有「已派车」行待确认,或基线需求状态还停在 CONFIRMED / PENDING_RECONFIRM ⇒ 602005;基线需求状态已是 DISPATCHED / DONE(本端点回写已落地)⇒ 不抛,按 confirmedCount=0 返 200 | +| remark | Body | String | ❌ | ≤200 | 仅日志留痕,**不写进配车行**(配车行 remark 参与 planDigest,刷进去会让下次重配误判「计划有变更」) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| confirmedCount | Integer | 本次真正由 ASSIGNED 转 CONFIRMED 的行数;重复确认时为 0 | +| alreadyConfirmedCount | Integer | 本次之前就已是 CONFIRMED 的行数 | +| requirementId / requirementVersion | String / Integer | 本次提交的需求身份(照原样回显) | +| planVersion | Integer | 配车计划版本 | +| requirementAdvanceIntent | String | 回写意图,如 `CONFIRMED_TO_DISPATCHED` | +| coverage | Object | 按组覆盖读数(groups / missingGroupCodes / satisfied) | + +#### 请求示例 + +```json +{ + "requirementId": "2103003334258675714", + "requirementVersion": 2, + "remark": "与地接确认车辆无误" +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2103003304906936322", + "confirmedCount": 0, + "alreadyConfirmedCount": 3, + "requirementId": "2103003334258675714", + "requirementVersion": 2, + "planVersion": 2, + "requirementAdvanceIntent": "CONFIRMED_TO_DISPATCHED", + "coverage": { "satisfied": true, "missingGroupCodes": [] } + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 本团没有任何配车行时**不是**空成功,而是 602007(见「错误响应」)。 +- 本团有配车行但已全部 CONFIRMED:200 + `confirmedCount=0`、`alreadyConfirmedCount=N`。 + +```json +{ "code": 200, "message": "成功", "data": { "confirmedCount": 0, "alreadyConfirmedCount": 3 }, "success": true } +``` + +#### 错误响应 + +```json +{ + "code": 602005, + "message": "用车需求已更新, 请刷新后重新配车(提交 id=2103003334258675714/v2, 当前 id=2103003334258675714/v4)", + "success": false, + "data": null +} +``` + +```json +{ + "code": 602007, + "message": "本团没有可确认的配车行, 请先提交配车计划", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- **真实的判定顺序(改后)**:事务外只做需求 **ID** 校验(602005 的一种形态);进事务、取团锁、锁内重读配车行后依次判 —— 行数都为 0 ⇒ 602007;**本端点回写是否已落地**(基线需求状态 ∈ DISPATCHED / DONE)⇒ 落地则跳过版本/状态校验(幂等重放),未落地则照旧 fail-closed 抛 602005 / 602006;随后做按组覆盖校验(602008),再 CAS 确认。 +- **跳过的条件不是「没有待确认行」,而是「本端点的回写已经落地」**:状态停在 CONFIRMED / PENDING_RECONFIRM 而版本不等 ⇒ 推进版本的不是我们,是业务侧改了需求,必须照旧 602005。**不要按 assignedCount==0 一刀切。** +- **需求被整份换过**(`requirementId` 都变了)时拿不到「提交版本」,报文只有 ID 那一半;两支统一拼 `id=…` 前缀。 +- 重配端点(`reconfigure`)**未变**:仍整份调用身份校验。 + +--- + +## 四、契约约束与正确调用方式(接口类必写) + +- **前端按码处置,不解析报文**:602005 = 需求被业务侧改过,刷新重配;200 且 `confirmedCount=0` = 本来就是已确认,无需任何动作。 +- **不要把「重复确认」当成需要防抖的操作**:本端点没有防重提交时间窗,按钮可以做防抖,但服务端保证重复调用不报错。 +- 确认成功后若要改车,走重配端点(`reconfigure`)而不是再点确认。 + +### ✅ 正确 / ❌ 错误处置对照 + +```text +✅ 200 + confirmedCount=0 → 已是确认态,直接展示「已确认」,不要提示失败 +✅ 602005 → 提示「需求已更新,请刷新后重新配车」 +❌ 把 200 + confirmedCount=0 渲染成错误或重复提交警告 +❌ 发现 602005 后原地重试同一个 requirementVersion(版本不会自己回来) +``` + +### 切换状态时的必要动作 + +- 重配(`reconfigure`)成功后配车行回到 ASSIGNED/待确认,需要再点一次确认;本单未改这条链路。 + +--- + +## 五、数据库行为(涉及写操作时必写) + +- 无表结构变更、无 Flyway。 +- 确认成功的写入是「把本团存活配车行由 ASSIGNED 置 CONFIRMED」+「事务内意图记录回写需求状态」;重复确认时 CAS 命中 0 行,**零写入**。 +- 602005 / 602006 / 602007 / 602008 都在写库之前抛出,可回读需求版本与配车行状态确认未变。 + +--- + +## 六、边界行为 + +- 并发下的保护未变:确认前取团级变更锁并在锁内重读配车行,命中行数与 assignedCount 不符即判并发。 +- 受控重开窗口(602012 / 602013)语义未变。 +- 需求被整份换过仍抛 602005,不受本次放宽影响。 + +--- + +## 六.5、枚举 / 数据字典(接口出现枚举时必写) + +| 码 | 触发(改后) | 报文 | +|---|---|---| +| 602005 | requirementId 不等于基线;或本端点回写未落地(需求状态非 DISPATCHED/DONE)而 requirementVersion 不等于基线当前版本 | 用车需求已更新, 请刷新后重新配车(提交 {0}, 当前 {1}),两个占位符均为 id=需求ID/v版本 | +| 602006(不变) | 需求状态不允许配车(回写未落地时的状态判据) | 正式用车需求当前状态不允许配车: {0} | +| 602007(触发面变更) | 本团 assignedCount=0 且 alreadyConfirmedCount=0 | 本团没有可确认的配车行, 请先提交配车计划 | +| 602008(不变) | 库里现存计划仍不满足按组覆盖 | 配车尚未覆盖完整, 不能确认: {0} | + +--- + +## 六.6、修改前后对比(修改/删除类接口必写,新增跳过) + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| 请求 / 响应字段 | — | 无新增、无删除,结构不变 | +| 602005 的 {0} | 「换版」支不带前缀词 | 两支统一为 id=…/v… | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 首次确认成功 | 200,confirmedCount=N | 200,confirmedCount=N(不变) | +| 需求版本已被本端点回写推进后再确认(旧版本) | **602005**(要求刷新重配) | **200**,confirmedCount=0、alreadyConfirmedCount=N | +| 业务侧改了需求、本团仍有待确认行 | 602005 | 602005(不变,fail-closed) | +| 业务侧改了需求、本团早已确认完(回写已落地) | 602005 | 200(幂等重放) | +| 本团一行配车行都没有 | 可能先被版本校验报成 602005 | 602007 | + +--- + +## 六.7、影响评估(修改/删除类必写) + +- **是否破坏向后兼容**:不破坏——只把「自己造成的版本前进」这一档从错误放宽成幂等成功;报文占位符口径变化不影响按码处置的前端。 +- **前端是否必须同步上线**:不必须。建议把 confirmedCount=0 渲染成「已确认」而不是失败。 +- **存量数据影响**:无迁移、无重算。 +- **上线风险**:放宽的闸门以「基线需求状态已是 DISPATCHED / DONE」为唯一理由,状态未落地时判据与改前完全一致,因此不会掩盖「业务侧改需求」这一真实冲突。 + +--- + +## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面) + +- **仅影响**:`POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm` 的版本/状态校验时机与 602005 报文占位符形态。 +- **零影响**: + - 重配端点 `POST .../reconfigure`(仍整份校验身份)。 + - 602006 / 602008 / 602012 / 602013 的判据与报文。 + - 按组覆盖校验、并发锁、受控重开窗口。 + - order-v3 侧对回写意图的幂等消费口径。 + - readiness / overview / 配车行数据结构、数据库、网关路由。 + +--- + +## 八、测试环境已验证 + +**部署读数**:`hl-fleet-service` dev-v3 @ `2b4656424`,2026-09-24 14:03 滚动部署完成。 + +**网关真实请求与响应**(`https://api.test.1814.love`,自造团期 `groupBatchId=2103003304906936322`,正式需求 `2103003334258675714`,3 条配车行): + +```text +① POST /admin/fleet/group-dispatch/batches/2103003304906936322/reconfigure + {requirementId:2103003334258675714, requirementVersion:2, demands:[3 天 × BUS × 1 辆]} + → http 200, code=200;addedCount=3、aliveCount=3 ✓ + +② POST .../confirm {requirementId:2103003334258675714, requirementVersion:2} + → http 200, code=200, confirmedCount=3, alreadyConfirmedCount=0, + requirementAdvanceIntent=CONFIRMED_TO_DISPATCHED ✓ + 回读 GET /v3/admin/order/group-batch/2103003304906936322/vehicle-requirement + → status=DONE, version=3(版本被本次回写推进)✓ + +③ POST .../confirm {requirementId:2103003334258675714, requirementVersion:2} ← 用提交时的旧版本重复确认 + → http 200, code=200, message=成功, confirmedCount=0, alreadyConfirmedCount=3 ✓(改前此处为 602005) +``` + +**定向测试逐类读数**(`mvn -o -pl hl-fleet-service -am test -Dtest='GroupDispatchConfirmTest,GroupDispatchServiceTest' -DfailIfNoTests=false -Dhl.surefire.failIfNoTests=false`,81 绿): + +| 测试类 | Tests run | +|---|---| +| GroupDispatchServiceTest | 58 | +| GroupDispatchConfirmTest | 23 | + +fleet `spotless:check`:901 files clean、0 needs changes。 + +--- + +## 十、相关文档 + +- 确认配车端点与 `GroupDispatchConfirmReqVO` 原始设计:#7442 +- 受控重开窗口(602012 / 602013):#8308 系列 → `changelogs-v2/2026-09/` +- 团期配车就绪检查:#8294 → `changelogs-v2/2026-09/24_8294_团期配车就绪检查座位不足黄牌扣司机座,新增-passengerSeatTotal-修改接口-管理后台.md` + +--- + +## 关联 / 联系人 + +### 链接 + +- Issue: https://git.1814.love/wx/HL/issues/8329 +- 后端 PR: https://git.1814.love/wx/HL/pulls/8332 + +### 联系人 + +- 后端: wx diff --git a/changelogs-v2/2026-09/24_8330_团期整团确认预检新增车型与分组不符原因MEMBER_GROUP_MISMATCH与809125-修改接口-管理后台.md b/changelogs-v2/2026-09/24_8330_团期整团确认预检新增车型与分组不符原因MEMBER_GROUP_MISMATCH与809125-修改接口-管理后台.md new file mode 100644 index 00000000..778907d5 --- /dev/null +++ b/changelogs-v2/2026-09/24_8330_团期整团确认预检新增车型与分组不符原因MEMBER_GROUP_MISMATCH与809125-修改接口-管理后台.md @@ -0,0 +1,348 @@ +--- +schema: "hl-changelog/v2" +ticket: "8330" +title: "团期整团确认预检新增车侧缺失原因 MEMBER_GROUP_MISMATCH(809125):户级报的车型与覆盖它的乘车分组车型不符时拒绝放行" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "v2.1" +verified_at: "" +status_note: "PR #8334 squash 合并 dev-v3(39d21cd4f)。部署:hl-order-service-v3 dev-v3 @ 2b4656424(2026-09-24 14:02 滚动部署两实例)。测试服网关实测(groupBatchId=2102981652823302146,生效正式需求 CONFIRMED v4:BUS=[丙]、MPV=[甲,乙];甲已把户级 TRAVEL 需求改成 bus 并放行):GET requirement/confirm-check → ready=false,vehicleMissing 新增一条 reason=MEMBER_GROUP_MISMATCH、orderId=甲、detail=该户报的车型为 bus,但覆盖它的乘车分组(MPV)车型为 mpv,没有车型相符的分组,请重新汇总后保存正式用车需求;同刻 POST requirement/confirm 在原 809109 上先被拒(该团期另有未覆盖户)。改前同一位置 vehicleMissing=[]、ready=true。定向单测 141 绿(CheckVehicleTest 20 / ConfirmVehicleTest 18 / GroupBatchRequirementServiceTest 73 / GroupVehicleRequirementConfirmCheckTest 30)。判据保守 fail-open:户级车型集合为空、分组车型归一失败、无活跃正式需求一律跳过,历史自由文本脏值不会误报。" +updated_at: "2026-09-24" +base: "dev-v3" +--- + +# order-v3 团期需求: 新增 MEMBER_GROUP_MISMATCH(809125)车型-分组一致性预检 + +> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/` +> +> **服务**: hl-order-service-v3(团期需求域,groupbatch 包) +> **PR**: https://git.1814.love/wx/HL/pulls/8334 +> **Issue**: #8330 +> **日期**: 2026-09-24 +> **影响范围**: 管理后台「团期详情 → 查看需求」Tab 的整团确认预检与整团确认 + +--- + +## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写) + +- **新增车侧缺失原因 `MEMBER_GROUP_MISMATCH` 与错误码 809125**:户级 TRAVEL 需求改成另一种车型并放行后,若该户仍落在**车型不符**的分组里,整团确认预检会点名该户,`ready=false`;整团确认也会被拒。 +- **这是「补盲区」而不是「加新规则」**:既有的 809109 只问「该户的每一天有没有落在**某个**分组里」,不问「那个分组是不是这户要的车型」。户改了车型却仍在原分组 `memberOrderIds` 里时,809109 恒通过,一份与户级现状不符的正式需求会被放给车务。 +- **判据保守、fail-open**:只有当「该户报的车型集合」与「覆盖它的分组车型集合」**交集为空**时才报;任一集合为空(没报车型 / 不在任何分组里 / 分组车型归一不出来)一律跳过。历史脏值(自由文本车型、车型列未填的存量分组)不会被误报成不一致。代价是「说不清」的数据继续不报——漏报只是少一道提醒,误报会卡死整团确认。 +- **不自动改数据、不新增状态**:不改 `GroupVehicleCoverage`、不改车辆需求状态机、不回退已配车辆、不动 fleet 侧 `readiness`。处置是把不一致摆给团期管理员,由管理员「重新汇总并保存正式用车需求」收敛。 +- **前端必须展示新 reason**:`vehicleMissing[]` 目前只有 `OrderNo` 与 `orderId`,页面已把 `orderNo` 拼在 `detail` 之前,报文里不再带雪花 id;新 reason 的处置动作已写死在 `detail` 尾句。 + +--- + +## 一、背景(选填) + +团期需求是「户级提交 → 团期管理员审核放行 → 团级汇总成正式需求 → 车务按正式需求配车」四段结构。户级需求在配置完成后仍会被改(客人换车型、加人加车、临时改行程),这是常态。房务侧对这条路径已有完整回退(`BATCH_ROOM_PLAN_BASELINE_RECONFIRMED` + `hotelReady` 回落 + 逐日 mismatch + 分房 stale),车务侧此前缺这一半:实测把某户由 `mpv 7 座` 改成 `bus 16 座` 并放行后,生效版本仍写着该户在 MPV 组,而 `aggregate-draft` 已把它算进 BUS 组,两份数字互相矛盾却全程零提示,`readiness` 仍 `ready=true`。本单落**方案②**(零 DDL、只做可见性),方案①(新增「过时」标记 + fleet 硬拦 + 配车完成回退)改动面大,留待产品决策。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 整团确认预检 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` | 响应新增枚举值 | `vehicleMissing[].reason` 新增 `MEMBER_GROUP_MISMATCH`,对应 809125 | +| 2 | 整团确认 | POST | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` | 行为变更(同一判据) | 与预检同源,存在该缺失时整团确认被拒(不再静默通过) | + +--- + +## 三、接口详情 + +### 1. 整团确认预检 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` + +**VO**: `GroupBatchRequirementCheckRespVO` + +#### 使用场景 + +团期详情「查看需求」Tab 打开或点「整团确认」前的只读预检。权限 `group-batch:demand:confirm`。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期聚合主键(不是产品班期 ID) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| ready | Boolean | 是否可整体确认;本次起在「车型-分组不一致」时为 false | +| vehicleMissing[] | Array | 车侧缺失项;本次新增 `MEMBER_GROUP_MISMATCH` | +| vehicleMissing[].reason | String | 原因码,取值见「六.5」 | +| vehicleMissing[].orderId / orderNo | String / String | 涉及的子订单(雪花 ID 按字符串处理) | +| vehicleMissing[].groupCode / tripDate | String / LocalDate | 本原因这两项恒为 null(分歧在「组选错了」,不在某一天) | +| vehicleMissing[].detail | String | 人话描述,与整团确认抛出的错误报文逐字相同,可直接展示 | +| groupVehicleRequirementId / Status / Version | String / String / Integer | 当前生效正式需求的身份,`detail` 里照旧不出现 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2102981652823302146/requirement/confirm-check +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2102981652823302146", + "batchStatus": "RESOURCE_PREPARING", + "batchStatusName": "资源准备中", + "ready": false, + "missing": [], + "checkedResourceTypes": ["HOTEL", "VEHICLE"], + "vehicleWaived": false, + "vehicleMissing": [ + { + "reason": "MEMBER_GROUP_MISMATCH", + "groupCode": null, + "tripDate": null, + "orderId": "2102981652680695809", + "orderNo": "HL20260924124112051", + "detail": "该户报的车型为 bus,但覆盖它的乘车分组(MPV)车型为 mpv,没有车型相符的分组,请重新汇总后保存正式用车需求" + } + ], + "vehicleExemptHouseholds": [], + "groupVehicleRequirementId": "2102982327141556225", + "groupVehicleRequirementStatus": "CONFIRMED", + "groupVehicleRequirementVersion": 4 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 一切正常时 `vehicleMissing: []`、`ready: true`,接口仍 200。 +- 该户没报车型、不在任何分组里、分组车型归一不出来(自由文本 / 车型列未填)时**跳过本判据**,不报 809125(fail-open)。 +- 团期还没有生效中的正式需求时不报本判据。 + +```json +{ "code": 200, "message": "成功", "data": { "ready": true, "vehicleMissing": [] }, "success": true } +``` + +#### 错误响应 + +本端点只读,业务失败以 `result` 包裹在 HTTP 200 内;本单不新增抛错分支。 + +```json +{ "code": 200, "message": "成功", "data": { "ready": false, "vehicleMissing": [{ "reason": "MEMBER_GROUP_MISMATCH" }] }, "success": true } +``` + +#### 业务边界 + +- 判据是「该户 `fleet[].vehicleType` 归一后的集合」与「逐日覆盖它的那些分组的 `vehicleType` 集合」**交集为空**;只要有一个分组车型相符就不报。 +- 与 809109 互补且不重叠:809109 管「不在任何分组里」,809125 管「在分组里但组选错了」。 +- 一个户最多报一条(不分天、不分组)。 +- 报文不含雪花 id,由页面用清单行自带的 `orderNo` 定位;`detail` 尾句已写死处置动作。 + +--- + +### 2. 整团确认 `POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` + +**VO**: `GroupBatchRequirementCheckRespVO`(确认口无请求体,与预检同源判据) + +#### 使用场景 + +团期管理员点「整团确认」,把各户需求汇总成正式需求并放行给车务。权限同上。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | + +#### 出参 `Result<...>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementConfirmed | Boolean | 本次是否完成整体确认;被拒时不返回该结构 | +| vehicleDispatchedOrderIds | Array<String> | 本次被放行的户 | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/2102981652823302146/requirement/confirm +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "requirementConfirmed": true }, "success": true } +``` + +#### 空数据 / 降级响应 + +确认链路与预检读同一份快照;预检不报 809125 时确认也不会报。 + +```json +{ "code": 200, "message": "成功", "data": { "requirementConfirmed": false }, "success": true } +``` + +#### 错误响应 + +```json +{ + "code": 809125, + "message": "该户报的车型为 bus,但覆盖它的乘车分组(MPV)车型为 mpv,没有车型相符的分组,请重新汇总后保存正式用车需求", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 确认按「第一条缺失」抛出,异常顺序为:团级六条 → 户级未提交(809122)→ 接送机未回填(809007)→ **车型不符(809125)**。排在 809122 之后是因为「重新汇总」本身会被未提交户的 809121 挡住,先催未提交的户才有意义。 +- 本原因不改变任何既有场景下「确认抛第一条」抛出的那一码。 +- 整团确认成功后正式需求会被重新汇总保存,管理员再次触发时本原因自然消失(状态收敛)。 + +--- + +## 四、契约约束与正确调用方式(接口类必写) + +- `reason` 是**稳定的机器可判值**,前端按它分支渲染;`detail` 只用于直接展示,**不要解析 `detail`**。 +- `vehicleMissing` 的顺序即处置顺序:先处理靠前的条目再刷新预检。 +- 页面在 `ready=false` 时不得放开「整团确认」按钮;即便放开,服务端也会以同一判据返回 809125。 + +### ✅ 正确 / ❌ 错误处置对照 + +```text +✅ ready=false 且含 MEMBER_GROUP_MISMATCH → 走「自动汇总」→ 检查车型分组 → 保存正式用车需求 → 重新预检 +❌ ready=false 却直接点整团确认 → 809125(HTTP 200 业务失败) +❌ 只改团级分组的 remark / 座位数就再保存 → 分组归属不变,本原因不会消失 +``` + +### 切换状态时的必要动作 + +- 重新汇总并保存后正式需求进入 DRAFT 且 version +1,车务需按新版本重新配车确认(既有语义,未变)。 + +--- + +## 五、数据库行为(涉及写操作时必写) + +- 无表结构变更、无 Flyway。 +- 本原因的判定发生在**写库之前**:整团确认抛 809125 时零写入(正式需求版本、状态、分组均不变,可回读确认)。 + +--- + +## 六、边界行为 + +- 存量数据不批量重算:历史自由文本车型 / 车型列未填的分组继续不报(fail-open)。 +- 判据只在团级读面(`confirm-check` / `confirm`)生效;车务 `readiness` 与配车行不动。 +- 车主车型与乘车分组都是多值时取集合,任一相交即视为一致。 + +--- + +## 六.5、枚举 / 数据字典(接口出现枚举时必写) + +| reason | 错误码 | 触发 | 报文 | +|---|---|---|---| +| `MEMBER_GROUP_MISMATCH` | 809125 | 该户报的车型与覆盖它的分组车型无交集 | 该户报的车型为 {0},但覆盖它的乘车分组({1})车型为 {2},没有车型相符的分组,请重新汇总后保存正式用车需求 | +| `ORDER_DAY_UNCOVERED`(不变) | 809109 | 该户某天不在任何分组里 | 该子订单的 {0} 没有被任何乘车分组覆盖 | +| `HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED`(不变) | 809122 | 户级根本没提交 | 见既有条目 | + +--- + +## 六.6、修改前后对比(修改/删除类接口必写,新增跳过) + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `confirm-check` 请求/响应字段 | — | 无新增、无删除;`vehicleMissing[].reason` 多一个取值 | +| `vehicleMissing` 条数 | — | 最多每户多一条 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 户改车型并放行、该户仍在旧车型分组里 | 预检 `ready=true`、`vehicleMissing=[]`;整团确认静默通过 | 预检 `ready=false` 并点名该户;整团确认 809125 拒绝 | +| 户改车型但新车型有相符分组 | 通过 | 通过(不变) | +| 户没报车型 / 分组车型归一不出来 | 通过 | 通过(fail-open 跳过) | +| 团级分组与户级完全一致 | 通过 | 通过(不变) | + +--- + +## 六.7、影响评估(修改/删除类必写) + +- **是否破坏向后兼容**:响应结构不变,但**同一场景下 `ready` 可能由 true 变 false、`confirm` 可能由 200 变 809125**。凡「户级车型与覆盖分组车型无交集」的团期都会被拒,直到管理员重新汇总保存。 +- **前端是否必须同步上线**:**建议同批**。不同步时新 reason 会以未知值出现在清单里(`detail` 已是完整人话,仍可展示),但页面若只看 `ready` 就足以正确禁用确认按钮。 +- **存量数据影响**:不批量重算、不迁移;只有下一次预检 / 确认才会暴露。 +- **上线风险**:判据 fail-open,历史脏值不会误报;但车型与分组确实不一致的团期会在确认时被拦,需要管理员走一次「重新汇总并保存」。 + +--- + +## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面) + +- **仅影响**:`GET .../requirement/confirm-check` 的 `ready` / `vehicleMissing`;`POST .../requirement/confirm` 的拒绝条件。 +- **零影响**: + - 车务 `GET /admin/fleet/group-dispatch/batches/{gb}/readiness`(方案①范围,本单不做)。 + - 配车行、已配车辆、「已配车辆不动」语义。 + - 团级六条既有校验、809116 / 809118 / 809119 / 809120 / 809121 / 809122 / 809007 等其余判据与报文。 + - `GET .../vehicle-requirement`、`aggregate-draft`、`vehicle-households`、`requirement-summary`。 + - 子订单级用车需求与 `GroupVehicleCoverage`。 + - 数据库、网关路由、fleet 服务代码。 + +--- + +## 八、测试环境已验证 + +**部署读数**:`hl-order-service-v3` dev-v3 @ `2b4656424`,2026-09-24 14:02 滚动部署两实例(8086/8186 均 UP)。 + +**网关真实请求与响应**(`https://api.test.1814.love`,`groupBatchId=2102981652823302146`,生效正式需求 CONFIRMED v4:BUS=[丙]、MPV=[甲,乙];甲 `2102981652680695809` 的户级 TRAVEL 需求已改成 `bus 16 座` 并放行): + +```text +① GET /v3/admin/order/group-batch/2102981652823302146/requirement/confirm-check + → http 200, code=200, ready=false + vehicleMissing 含一条: + reason=MEMBER_GROUP_MISMATCH, orderId=2102981652680695809, orderNo=HL20260924124112051 + detail=该户报的车型为 bus,但覆盖它的乘车分组(MPV)车型为 mpv,没有车型相符的分组,请重新汇总后保存正式用车需求 ✓ + +② 同刻 POST .../requirement/confirm → 被既有 809109 先拦(该团期另有一户不在任何分组里), + 说明 809125 已并入同一条缺失链且不改变「确认抛第一条」的既有语义 ✓ + +改前同位置读数(2026-09-24 13:03,同一团期同一数据):ready=true、vehicleMissing=[] ✓ +``` + +**定向测试逐类读数**(`mvn -o -pl hl-order-service-v3 -am test -Dtest='…' -DfailIfNoTests=false -Dhl.surefire.failIfNoTests=false`,4 个类合计 141 绿): + +| 测试类 | Tests run | +|---|---| +| GroupVehicleRequirementConfirmCheckTest | 30 | +| GroupBatchRequirementServiceTest | 73 | +| GroupBatchRequirementServiceCheckVehicleTest | 20 | +| GroupBatchRequirementServiceConfirmVehicleTest | 18 | + +--- + +## 十、相关文档 + +- 户级未提交(809122)先例:#8249 → `changelogs-v2/2026-09/` +- 逐日覆盖校验 809109:#8235 → `changelogs-v2/2026-09/` +- 团级座位档位校验 809124:#8311 → `changelogs-v2/2026-09/24_8311_团期分组座位数改档位下拉与809124校验-修改接口-管理后台.md` +- 房侧同源回退(对照实现):`BATCH_ROOM_PLAN_BASELINE_RECONFIRMED` + +--- + +## 关联 / 联系人 + +### 链接 + +- Issue: https://git.1814.love/wx/HL/issues/8330 +- 后端 PR: https://git.1814.love/wx/HL/pulls/8334 + +### 联系人 + +- 后端: wx diff --git a/changelogs-v2/2026-09/24_8331_接送机需求窗不跟随新增大交通方向时整团确认可预检与605062报文点名-修改接口-管理后台.md b/changelogs-v2/2026-09/24_8331_接送机需求窗不跟随新增大交通方向时整团确认可预检与605062报文点名-修改接口-管理后台.md new file mode 100644 index 00000000..8fbf0ebf --- /dev/null +++ b/changelogs-v2/2026-09/24_8331_接送机需求窗不跟随新增大交通方向时整团确认可预检与605062报文点名-修改接口-管理后台.md @@ -0,0 +1,427 @@ +--- +schema: "hl-changelog/v2" +ticket: "8331" +title: "接送机需求窗不跟随新增大交通方向:整团确认预检新增 TRANSFER_WINDOW_INCOMPLETE(809126),605062 报文改为点名版本、越窗日期与处置方" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "v2.1" +verified_at: "" +status_note: "PR #8335 squash 合并 dev-v3(2b4656424)。部署:hl-order-service-v3 + hl-fleet-service dev-v3 @ 2b4656424,2026-09-24 14:02—14:03 滚动部署(order-v3 两实例 8086/8186 均 UP,fleet 两实例滚动完成)。测试服网关实测(groupBatchId=2102981652823302146;甲 2102981652680695809 的接送机需求 2102989264394563586 窗内只有 2027-02-03,之后补录了 2027-02-05 返程大交通):① GET requirement/confirm-check → ready=false,vehicleMissing 新增 reason=TRANSFER_WINDOW_INCOMPLETE、tripDate=2027-02-05、detail=该户接送机需求窗仅含 2027-02-03,大交通送机 2027-02-05 未进窗,请定制师重新提交接送机需求后再派车(改前该位置 vehicleMissing=[]);② POST /admin/fleet/assignments 派 2027-02-05 送机车 → code=605062,报文已改为:派车日期 2027-02-05 越出当前接送机需求日期窗(版本 v1,窗内服务日 2027-02-03):请先调整或取消这些越窗槽位,或让定制师重新提交接送机需求换版后再派车(改前为「存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续」)。定向单测:order-v3 157 绿、fleet AssignmentServiceTest 565 绿,fleet spotless:check 901 files clean。只对「提交得了的户」报(与 809122 同一份豁免判据取交集),避免对没有提交入口的户把整团确认永久卡死。" +updated_at: "2026-09-24" +base: "dev-v3" +--- + +# order-v3 + fleet 接送机: 需求窗缺方向预检(809126)与 605062 可执行报文 + +> **存放目录**: 二期(v3) → `changelogs-v2/2026-09/` +> +> **服务**: hl-order-service-v3(团期需求域 + requirement 域)· hl-fleet-service(assignment 域) +> **PR**: https://git.1814.love/wx/HL/pulls/8335 +> **Issue**: #8331 +> **日期**: 2026-09-24 +> **影响范围**: 管理后台「团期详情 → 查看需求」Tab 的整团确认预检与整团确认;车务「派车」写入口的 605062 报文 + +--- + +## ⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写) + +- **新增车侧缺失原因 `TRANSFER_WINDOW_INCOMPLETE` 与错误码 809126**:某户的大交通派生出了新的方向日期(典型:先报到达、后补返程),而该户接送机需求的 `service_dates` 里没有这一天时,整团确认预检点名该户并 `ready=false`。 +- **605062 报文重写(码值与判据不变)**:由「存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续」改为点名**哪一版需求、哪一天越窗、窗内有哪些服务日、谁该做什么**。**这是一个既有错误码的报文变更,前端/QA 的文案断言需要同步更新。** +- **只对「提交得了的户」报**:候选户与 809122 那份豁免判据取交集(订单在定制中且团期阶段开放提交,或该户最新需求被打回)。提交不了的户此刻在系统里没有提交入口,报出来只会把整团确认**永久卡死**(户补不了、团也确认不了)。 +- **不自动改需求数据、不自动换版**:换版要重走提交链(服务日重新派生、状态回待审核、记录换版原因),由定制师发起才是这个事实的属主该做的事。方案①(新方向大交通落库即自动换版)与 `requirementVersion` 身份校验、已派车行 superseded 处置耦合,留待产品决策。 +- **成功路径仍要求定制师重新提交**:本次只保证「不一致在整团确认前被看见 + 车务拿到的 605062 可执行」。 + +--- + +## 一、背景(选填) + +大交通天然是分次录入的(先定去程、回程后补)。接送机的服务日只在**提交那一刻**由大交通派生一次、随后冻结在需求行上;补录一个**此前完全不存在方向**的大交通与「把已有航班改签」是两件事——前者是新增缺口,不是对已冻结窗口的修改。此前的现场是:车务看板按大交通报出「有送机缺口」,车务去派送机车却被 605062 拦死,报文既不点名越窗日期也不指出处置方,车务在派车侧怎么调整都推不动。本单落**方案②**(零 DDL、只做可见性与报文)。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 整团确认预检 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` | 响应新增枚举值 | `vehicleMissing[].reason` 新增 `TRANSFER_WINDOW_INCOMPLETE`,对应 809126 | +| 2 | 整团确认 | POST | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` | 行为变更(同一判据) | 存在该缺失时整团确认被拒 | +| 3 | 创建派车(单槽位占位) | POST | `/admin/fleet/assignments` | 报文变更(码值与判据不变) | 605062 报文改为点名版本、越窗日期、窗内服务日与处置方 | + +--- + +## 三、接口详情 + +### 1. 整团确认预检 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` + +**VO**: `GroupBatchRequirementCheckRespVO` + +#### 使用场景 + +团期详情「查看需求」Tab 打开或点「整团确认」前的只读预检。权限 `group-batch:demand:confirm`。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期聚合主键(不是产品班期 ID) | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| ready | Boolean | 是否可整体确认;本次起在「接送机需求窗缺方向」时为 false | +| vehicleMissing[] | Array | 车侧缺失项;本次新增 `TRANSFER_WINDOW_INCOMPLETE` | +| vehicleMissing[].reason | String | 原因码,取值见「六.5」 | +| vehicleMissing[].tripDate | LocalDate | 本原因下为**首个越窗日期**(大交通派生出来、却不在需求窗内的那天) | +| vehicleMissing[].orderId / orderNo | String / String | 涉及的子订单 | +| vehicleMissing[].groupCode | String | 本原因恒为 null(接送机需求没有乘车分组维度) | +| vehicleMissing[].detail | String | 人话描述,与整团确认抛出的报文逐字相同,已含「请定制师重新提交接送机需求后再派车」 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2102981652823302146/requirement/confirm-check +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "groupBatchId": "2102981652823302146", + "ready": false, + "vehicleMissing": [ + { + "reason": "TRANSFER_WINDOW_INCOMPLETE", + "groupCode": null, + "tripDate": "2027-02-05", + "orderId": "2102981652680695809", + "orderNo": "HL20260924124112051", + "detail": "该户接送机需求窗仅含 2027-02-03,大交通送机 2027-02-05 未进窗,请定制师重新提交接送机需求后再派车" + } + ], + "groupVehicleRequirementId": "2102982327141556225", + "groupVehicleRequirementStatus": "CONFIRMED", + "groupVehicleRequirementVersion": 4 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +- 全部一致时 `vehicleMissing: []`、`ready: true`。 +- 该户**提交不了**(订单不在定制中,或团期阶段已关提交且最新需求未被打回)时**跳过**本判据,不报 809126。 +- 该户没有活跃接送机需求、或大交通没有派生任何方向日期时不报。 +- 接送机需求窗与派生日期完全一致时不报。 + +```json +{ "code": 200, "message": "成功", "data": { "ready": true, "vehicleMissing": [] }, "success": true } +``` + +#### 错误响应 + +本端点只读,业务结果包在 HTTP 200 内。 + +```json +{ "code": 200, "message": "成功", "data": { "ready": false, "vehicleMissing": [{ "reason": "TRANSFER_WINDOW_INCOMPLETE" }] }, "success": true } +``` + +#### 业务边界 + +- 判据 =「当前大交通按方向派生的日期集合」−「需求行冻结的 `service_dates`」非空;只取**首个**越窗日期进 `tripDate`,多条时 `detail` 里顿号分隔。 +- 判据取「在团户」而不是「放行集合」:接送机需求是户级自己报的,与团级 `needs_vehicle` 无关。 +- 与 809007(待放行接送机需求未回填服务日)互斥且不重叠:809007 管「一行服务日都没有」,809126 管「有窗但窗比大交通旧」。 +- **同方向改签不报**:判据落在「缺的方向/日期」上,不是「大交通变了」。 + +--- + +### 2. 整团确认 `POST /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm` + +**VO**: `GroupBatchRequirementCheckRespVO`(确认口无请求体,与预检同源判据) + +#### 使用场景 + +团期管理员点「整团确认」,把各户需求汇总成正式需求并放行给车务。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 | + +#### 出参 `Result<...>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| requirementConfirmed | Boolean | 本次是否完成整体确认;被拒时不返回该结构 | +| vehicleDispatchedOrderIds | Array<String> | 本次被放行的户 | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/2102981652823302146/requirement/confirm +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "requirementConfirmed": true }, "success": true } +``` + +#### 空数据 / 降级响应 + +与预检读同一份快照;预检不报 809126 时确认也不会报。 + +```json +{ "code": 200, "message": "成功", "data": { "requirementConfirmed": false }, "success": true } +``` + +#### 错误响应 + +```json +{ + "code": 809126, + "message": "该户接送机需求窗仅含 2027-02-03,大交通送机 2027-02-05 未进窗,请定制师重新提交接送机需求后再派车", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- 异常顺序:团级六条 → 户级未提交(809122)→ 接送机未回填(809007)→ **接送机窗不完整(809126)** → 车型不符(809125)。 +- 「车型不符(809125)」排在 809126 之后:前者的处置是管理员**重新汇总**,而户级还在改(定制师要重提接送机需求)时先汇总,改完还得再汇总一次。 + +--- + +### 3. 创建派车(单槽位占位) `POST /admin/fleet/assignments` + +**VO**: `AssignmentCreateReqVO`(沿用既有请求体,本单未改结构) + +#### 使用场景 + +车务按看板缺口派车。本单只改**被拒时的报文**,不改编排、判据与码值。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Body | Long | ✅ | 在团户 | 子订单 | +| requirementId | Body | Long | ✅ | 该户当前活跃需求 | 与 `requirementVersion` 共同构成需求身份 | +| startDate / endDate | Body | LocalDate | ✅ | `yyyy-MM-dd` | 本次派车的服务日段(逐日行) | +| headcount | Body | Integer | ✅ | ≥1 | 用车人数 | +| requestId | Body | String | ✅ | 幂等标识 | 重复提交同值即幂等 | + +#### 出参 `Result<...>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| (结构未变) | — | 本单只改业务失败时的 605062 报文 | + +#### 请求示例 + +```json +{ + "orderId": "2102981652680695809", + "requirementId": "2102989264394563586", + "vehicleId": "2085284111341023234", + "driverId": "2089691308707733506", + "startDate": "2027-02-05", + "endDate": "2027-02-05", + "headcount": 2, + "sendItinerarySms": false, + "confirmCrossResident": true, + "requestId": "GRE2E-win-0205" +} +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "assignmentId": "2103003339824435202" }, "success": true } +``` + +#### 空数据 / 降级响应 + +- 需求窗内派车:正常 200,报文未变。 +- 该户当天已有在途槽位:仍走既有 605003 / 605004 等码,本单未改。 + +```json +{ "code": 200, "message": "成功", "data": { "addedCount": 1 }, "success": true } +``` + +#### 错误响应 + +```json +{ + "code": 605062, + "message": "派车日期 2027-02-05 越出当前接送机需求日期窗(版本 v1,窗内服务日 2027-02-03):请先调整或取消这些越窗槽位,或让定制师重新提交接送机需求换版后再派车", + "success": false, + "data": null +} +``` + +#### 业务边界 + +- **四个实参**:{0}=越窗派车日期(升序、顿号分隔)、{1}=需求类别的人话名(接送机需求 / 行程用车需求)、{2}=需求版本号、{3}=当前需求窗内的服务日。全部由抛点拼成**字符串**传入——数字与日期直接交给 `MessageFormat` 会被本地化或插入千分位(版本号会变成 1,234)。 +- **两个入口共用同一份报文**:「在途槽位行越窗」与「请求日期段越窗」现在从同一个构造点出来,不会出现同一次越窗、两个入口不同指引。 +- **判据与码值不变**:仍然只按日期窗拦截,车型 / 数量 / 人数不符一律不拦。 + +--- + +## 四、契约约束与正确调用方式(接口类必写) + +- `reason` 是稳定的机器可判值;`detail` 直接展示,**不要解析**。 +- 605062 是「车务侧无解」的码:**不要**在页面引导车务反复调整槽位,正确下一步是让定制师重新提交接送机需求(换版)后再派车。新报文已把这句话写进 message。 +- 需求版本会随换版 +1,换版后车务必须按新版本重新配车确认(既有语义)。 + +### ✅ 正确 / ❌ 错误处置对照 + +```text +✅ 预检 ready=false 含 TRANSFER_WINDOW_INCOMPLETE → 通知该户定制师重新提交接送机需求 → 团期管理员重新整团确认 → 车务再派车 +❌ 车务反复改派车日期:需求窗没变,改到哪一天都还是 605062 +❌ 把需求窗当成可以手工改的字段:需求服务日由大交通派生,只能通过重新提交换版 +``` + +### 切换状态时的必要动作 + +- 定制师换版后,该户需求进入 PENDING_REVIEW,团期管理员需重新整团确认放行;届时 `requirementVersion` 变化,车务侧旧版本身份校验会失败(既有 602005 语义)。 + +--- + +## 五、数据库行为(涉及写操作时必写) + +- 无表结构变更、无 Flyway。 +- 809126 在**写库之前**抛出:整团确认被拒时零写入(正式需求版本与状态不变,可回读确认)。 +- 605062 在**写库之前**抛出:没有任何派车行落库。 + +--- + +## 六、边界行为 + +- 判据按「当前大交通派生的日期 − 需求窗」求差,同方向改签(日期变了但方向仍齐)不报——这是刻意保留「冻结不覆盖」的既有定案。 +- 只对「提交得了的户」报;提交不了的户不报,避免整团确认被永久卡死。 +- 多条越窗日期时 `tripDate` 取首个,`detail` / 805062 报文列出全部。 + +--- + +## 六.5、枚举 / 数据字典(接口出现枚举时必写) + +| reason / 错误码 | 触发 | 报文 | +|---|---|---| +| `TRANSFER_WINDOW_INCOMPLETE` / 809126 | 该户大交通派生出的日期不在活跃接送机需求窗内,且该户此刻提交得了 | 该户接送机需求窗仅含 {0},大交通{1} 未进窗,请定制师重新提交接送机需求后再派车 | +| 605062(报文变更,码值不变) | 派车日期越出当前需求窗 | 派车日期 {0} 越出当前{1}日期窗(版本 v{2},窗内服务日 {3}):请先调整或取消这些越窗槽位,或让定制师重新提交{1}换版后再派车 | +| `TRANSFER_SERVICE_DATES_NOT_BACKFILLED`(不变) / 809007 | 待放行接送机需求一行服务日都没有 | 既有报文 | + +--- + +## 六.6、修改前后对比(修改/删除类接口必写,新增跳过) + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| `confirm-check` 请求/响应字段 | — | 无新增、无删除;`vehicleMissing[].reason` 多一个取值 | +| 605062 `message` | 存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续 | 派车日期 {0} 越出当前{1}日期窗(版本 v{2},窗内服务日 {3}):请先调整或取消这些越窗槽位,或让定制师重新提交{1}换版后再派车 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| 先提接送机需求、后补录新方向大交通 | 预检 `ready=true`;车务按看板缺口派车被 605062 拦死且报文无出路 | 预检 `ready=false` 点名该户;605062 报文点名版本、越窗日期与处置方 | +| 同方向改签 | 不换版、需求窗不变 | 不变(判据落在「缺的方向/日期」) | +| 提交不了的户窗不完整 | 无提示 | 仍无提示(刻意 fail-open,避免永久卡死) | + +--- + +## 六.7、影响评估(修改/删除类必写) + +- **是否破坏向后兼容**:`confirm-check` 响应结构不变但**同一场景 `ready` 可能由 true 变 false**;605062 **报文文本变更**,凡断言旧文案的自动化脚本需同步。 +- **前端是否必须同步上线**:**建议同批**。新 reason 未适配时仍可由 `ready=false` 正确禁用确认按钮;605062 报文是纯文案替换,页面直接透传即可。 +- **存量数据影响**:不批量重算、不改数据;只有下一次预检 / 确认 / 派车才会暴露。 +- **上线风险**:对「窗比大交通旧且该户提交得了」的团期会在整团确认时被拦,需要定制师换版一次;提交不了的户不受影响。 + +--- + +## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面) + +- **仅影响**:`GET .../requirement/confirm-check` 的 `ready` / `vehicleMissing`;`POST .../requirement/confirm` 的拒绝条件;605062 的报文文本。 +- **零影响**: + - 605062 的**判据与码值**(仍只拦日期越窗;车型 / 数量 / 人数不拦)。 + - 派车 / 排车 / 看板的缺口计算与 `overview` 字段。 + - 需求数据本身:不自动换版、不写 `service_dates`。 + - 团级六条既有校验与 809116 / 809118 / 809119 / 809120 / 809121 / 809122 / 809007 等其余判据与报文。 + - 数据库、网关路由。 + +--- + +## 八、测试环境已验证 + +**部署读数**:`hl-order-service-v3` 与 `hl-fleet-service` 均 dev-v3 @ `2b4656424`,2026-09-24 14:02—14:03 滚动部署完成。 + +**网关真实请求与响应**(`https://api.test.1814.love`,`groupBatchId=2102981652823302146`;甲 `2102981652680695809` 的接送机需求 `2102989264394563586` 窗内只有 `2027-02-03`,之后补录了 `2027-02-05` 返程大交通): + +```text +① GET .../requirement/confirm-check + → http 200, code=200, ready=false + vehicleMissing 含一条: + reason=TRANSFER_WINDOW_INCOMPLETE, tripDate=2027-02-05, + orderId=2102981652680695809, orderNo=HL20260924124112051 + detail=该户接送机需求窗仅含 2027-02-03,大交通送机 2027-02-05 未进窗,请定制师重新提交接送机需求后再派车 ✓ + +② POST /admin/fleet/assignments(派 2027-02-05 送机车) + → http 200, code=605062 + message=派车日期 2027-02-05 越出当前接送机需求日期窗(版本 v1,窗内服务日 2027-02-03): + 请先调整或取消这些越窗槽位,或让定制师重新提交接送机需求换版后再派车 ✓ + +改前读数(2026-09-24 13:1x,同一团期同一数据): + confirm-check → ready=true、vehicleMissing=[] + assignments → code=605062,message=存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续 ✓ +``` + +**定向测试逐类读数**: + +| 模块 / 测试类 | Tests run | +|---|---| +| hl-order-service-v3(5 个类合计) | 157 | +| ├ GroupBatchRequirementServiceTest | 73 | +| ├ GroupVehicleRequirementConfirmCheckTest | 30 | +| ├ GroupBatchRequirementServiceCheckVehicleTest | 27 | +| ├ GroupBatchRequirementServiceConfirmVehicleTest | 19 | +| └ TransferServiceDatesResolverTest | 8 | +| hl-fleet-service / AssignmentServiceTest | 565 | + +fleet `spotless:check`:901 files clean、0 needs changes。 + +--- + +## 十、相关文档 + +- 户级未提交(809122)先例:#8249 → `changelogs-v2/2026-09/` +- 车型与分组不符(809125,同日并入):#8330 → `changelogs-v2/2026-09/24_8330_团期整团确认预检新增车型与分组不符原因MEMBER_GROUP_MISMATCH与809125-修改接口-管理后台.md` +- 605062 越窗门禁来源:#5810 / #8235 +- 接送机需求提交与 `transfer/batch-confirm`:#8202 / #8152 + +--- + +## 关联 / 联系人 + +### 链接 + +- Issue: https://git.1814.love/wx/HL/issues/8331 +- 后端 PR: https://git.1814.love/wx/HL/pulls/8335 + +### 联系人 + +- 后端: wx