--- schema: "hl-changelog/v2" ticket: "5810" title: "换版槽位人工制:新增需求语句 requirementFleetText + 派车日期门禁 605062 + 槽位统一原样渲染(取代 #5788 全部旧数据形态)" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "not_required" frontend_status: "implemented" frontend_owner: "mmg" frontend_ref: "b6f9c44a" target_release: "" verified_at: "2026-08-11" status_note: "前端已实现(b6f9c44a):删 RequirementSupersededCard 组件+utils/requirementSuperseded.js+两 spec,AssignModal/OrderDrawer 摘除标旧块/supersededSlots computed/删除守卫/样式;batchAssignmentSlots 删 superseded 合并/收集/返回(保留 canDelete/deleteBlockReason 合并),槽位统一以 vehicleSlots 为源原样渲染能力不降级;槽位头两处「建议{车型}」chip 删除(改派卡头+派车卡头,派车卡头保留「全程」标记),删 slotChangeRequirementLabel/slotRequiredVehicleLabel;新增 requirementFleetText 常驻需求语句(后端权威文案,null 不渲染),挂原 SupersededCard 位(弹窗级+详情级)。605062 实证:confirm 走全局拦截器透传 msg、batch 走 silentError+catch message.error(error.message),msg 已含引导语,统一透传即满足,前端零改动。保持 housekeeper requirementSuperseded 独立契约/BoardSlotSummary/司机建议/OrderDrawer「需求:」行。fleet/board 35 spec 427 全过,checkpoint 8 文件全绿。注:配套 11_5810b(格式改车队组成写法)与 11_frontend(Step2 需求展示区布局+统一选车弹窗空白缺陷)为独立待办,本字段原样渲染不解析、对格式变更零改动即受益。" updated_at: "2026-08-11" base: "dev-v3" --- # 车务:换版槽位人工制 —— 需求语句 + 派车日期门禁 + 槽位统一原样渲染(#5810) > **服务**: hl-fleet-service (8087/8187) > **PR**: #5815 > **Issue**: #5810(取代 #5788 的前端契约) > **日期**: 2026-08-10 后端部署完成 > **影响范围**: 派单看板订单详情 `GET /admin/fleet/board/orders/{orderId}`(新增 1 字段 + 3 个字段语义退化为恒空)、派车推进类接口新增错误码 605062 > **一句话**: 换版后端不再自动增删槽位、不再区分新旧行,前端也不要再区分——**槽位有什么就原样画什么**;需求变成一句提示语;唯一会拦住车务的是「派车日期不在需求日期窗内」。 --- ## 0. 先读这段:为什么推翻 #5788 的做法 #5788(后端 #5784/#5796/#5798/#5802)的老机制是:定制师改用车需求 → 后端按「车型+座位」签名匹配,**匹配上的旧派车行沿用、匹配不上的标 `superseded`(旧数据)、缺的自动补未派占位、多的自动取消**。于是页面上出现了「新槽位 + 旧数据行」两类东西,前端为了表达它反复返工三轮(窄条 → 卡片 → 单独区块),wx 每一轮都不满意。 **2026-08-10 wx 最终口径(本单实现的就是这个)**: > 「槽位的数据配置啥就是啥,不自动清除数据,不自动增加/减少槽位。只在改出发日期的时候做日期判断,日期不对没法下一步。」 所以后端把整套「签名匹配 / 自动补位 / 自动取消 / superseded 标记」全部删掉,换成**整槽平移**:换版时把该订单下所有未删派车行(含已完成 completed,只排除 canceled)原封不动地重新绑定到新需求 ID 上——槽位数、车辆、司机、日期、价格、状态全不动,只是"户口"迁到新需求名下。存量带 `superseded` 标记的历史行也已由 Flyway 一次性迁移重绑完毕。 **给前端的直接含义**:**再也没有"旧数据行"这个概念了。** 所有槽位都是当前需求下的普通槽位,一视同仁地渲染成普通槽位卡。 --- ## 1. 新增字段:`requirementFleetText`(需求语句) ### 接口 `GET /admin/fleet/board/orders/{orderId}` —— **根级新增**(不在 `vehicleSlots` 里): | 字段 | 类型 | 可空 | 说明 | |------|------|------|------| | `requirementFleetText` | string | 是(需求不可达时 `null`) | 当前有效用车需求的车型语句 | ### 取值规则(后端已实现,前端直接展示即可,**不要自己再拼**) - 格式:车型项按 `{车型中文标签}×{数量}({座位数}座)` 生成,多项用中文顿号 `、` 连接 - 实例(测试服真实返回): - 26-9313 改需求后 → `"商务车×1(7座)、SUV×1(5座)"` - 单车型单辆 → `"商务车×1(7座)"` - 车型标签走字典 normalize,字典查不到时回退车型原始编码;数量 ≤0 按 1 计;座位数缺失时省略 `(N座)` 后缀 ### 前端怎么用 1. **改版提示横幅**:文案 `需求已改版,请对照新需求人工调整` + 紧跟这句 `requirementFleetText`。 ⚠️ 注意:横幅的**触发条件不再是** `requirementChangePendingCount > 0`(该字段现在恒 0,见 §2)。是否显示横幅由前端自行决定(例如:本次进入派车页时对比上次看到的 `requirementVersion`,或干脆常驻显示"当前需求:xxx")。**推荐做法**:不做横幅态,直接在派车页/订单概览常驻一行「当前需求:商务车×1(7座)、SUV×1(5座)」,这样任何时候车务都能对照,不依赖任何"变更"状态。 2. **订单概览**:同一句展示,位置见 wx 截图指向的区域(需求信息块)。 3. `null` 时该行整体不渲染(不要显示"当前需求:null"或空冒号)。 --- ## 2. ⚠️ 必须删除的旧渲染分支(#5788 遗留) 以下三个字段**契约保留不删**(兼容期,避免前端 500),但**语义已死**,值恒为空: | 字段 | 位置 | #5810 后的值 | 前端处理 | |------|------|-------------|---------| | `requirementChangePendingCount` | 根级 | **恒 `0`** | 删掉依赖它的徽章/横幅触发逻辑 | | `supersededAssignments` | 根级 | **恒 `[]`** | 删掉整个"旧数据区块/列表"渲染 | | `vehicleSlots[].superseded` | 槽位 | **恒 `false`** | 删掉 `superseded ? 旧数据卡 : 普通卡` 的三元分支 | | `vehicleSlots[].supersededByRequirementId` | 槽位 | **恒 `null`** | 无用 | | `vehicleSlots[].requirementId` | 槽位 | 恒 == 根级 `requirementId` | 无需比对 | **具体到 mmg 已写的代码**(按 #5788 三轮返工的产物): - `SupersededSlotCard`(064d0be2 的旧数据槽位卡组件)→ **整个组件删除** - "标旧窄条"(a75efc05/b31ac7c3 的窄条渲染)→ **删除** - 任何 `slot.superseded === true` 的判断、任何把 `supersededAssignments` 单独成区/成行的代码 → **删除** - 槽位列表数据源:**只用 `vehicleSlots`**,按后端返回顺序原样渲染,每一项都是普通槽位卡(可派车/可换车/可取消/可删除,能力与普通槽位完全一致) **验收标准**:改需求前后,页面槽位卡数量与内容**完全不变**(除非车务自己动手加/删/改)。26-9313 实测返回 3 个槽位(原商务车 E5555 行、原商务车 S6666 行、新 SUV 空槽),三个都是普通卡,没有任何"旧"标记。 --- ## 3. ⚠️ 槽位头去掉「建议 {车型}」标签 槽位头当前显示的「建议 商务车」「建议 SUV」这类标签 —— **去掉**。 原因:槽位人工制下车务爱派什么车派什么车,后端不再按车型校验(车型/数量/人数不符**不拦截**,见 §4),"建议"标签只会让车务误以为必须匹配。车型诉求统一由 §1 的 `requirementFleetText` 在横幅/概览表达一次即可。 字段层面:`vehicleSlots[].requiredVehicleType` / `requiredVehicleTypeLabel` / `requiredSeats` **契约保留**(后端仍返回真实值,其它场景可能用到),但**不要再渲染成槽位头的"建议 xx"标签**。 --- ## 4. 新增错误码 605062:派车日期与需求不符(唯一硬门禁) ### 触发场景 定制师**改了出发日期/行程天数**后,需求的日期窗变了,但车务原来配的车还挂在旧日期上。此时车务想推进(发送给司机 / 确认执行 / 提交最终方案),后端拦截: ```json { "code": 605062, "msg": "存在派车日期与当前用车需求不符的槽位,请先调整或取消后再继续", "data": null } ``` ### 哪些接口会返回 | 接口 | 说明 | |------|------| | `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` | 需求级确认执行(preflight + 锁内双重校验) | | `POST /admin/fleet/assignments/batch` | 批量提交最终实派方案(提交入参日期 + 既有在途行双向校验) | | 逐日方案提交(`batch` 走 `dailyPlan` 入参) | 同上 | ### 前端处理 1. `msg` 已是**完整可直接展示的中文**,直接弹给用户即可,不要自己另写文案。 2. 建议在提示后附操作引导:`请在槽位列表中删除或改期日期不符的槽位后重试`。 3. **不需要**前端预先算日期做本地拦截(后端是权威,且前端算容易与后端口径不一致);如果想给视觉提示,可用 `vehicleSlots[].serviceStartDate/serviceEndDate` 与订单 `departDate/endDate` 比对给个黄色角标,但**不要**据此禁用按钮。 ### 不拦截的情况(明确告知,避免前端"帮倒忙"加校验) - **加天数**(需求日期窗**扩大**):旧派车行日期仍落在扩大后的窗内 → **完全放行**,且**不强制车务把新增的天派满** - **车型/数量/人数与需求不符** → **不拦截**(降级为提示语句,即 §1 的 `requirementFleetText`) - 已完成(`completed`)的历史行日期越窗 → **不拦截**(终态行无法调整,不能卡死推进) --- ## 5. 行为变化速查(后端换版语义,供前端理解页面为什么这样) | 场景 | #5788 老行为 | #5810 新行为 | |------|-------------|-------------| | 定制师改车型(2商务 → 1商务+1SUV) | 匹配上的沿用,匹配不上的标旧,缺的自动补占位 | **全部行原样平移**,槽位数不变;SUV 需求只体现在语句里 | | 定制师改日期 | 同上 + 旧日期行标旧 | 全部行原样平移(日期不动);推进时 **605062** 拦截,车务手动改期/取消后放行 | | 定制师加天数 | 自动补新天占位 | 不自动补;车务可自行补派新增天(不补也能推进) | | 定制师减车辆数 | 自动取消多余未派占位 | **不自动取消**;多出来的槽位由车务自己删(删除能力 #5572 已全放开) | | 旧存量 superseded 行 | 特殊形态 | Flyway 已一次性迁移重绑为普通行 | --- ## 6. 联调数据(测试服可直接复现) | 场景 | 订单 | 说明 | |------|------|------| | 改车型后的三槽位原样 | 团号 `26-9313`(orderId `2086270153414103041`) | 详情返回 `requirementFleetText="商务车×1(7座)、SUV×1(5座)"`,`vehicleSlots` 3 项、`superseded` 全 false、`supersededAssignments=[]` | | 605062 拦截 / 放行 | 订单 `2086697882311680002` | 已被本次实测改到 08-28~31 且已确认执行完毕;如需重现 605062,请改该单出发日期后再点确认执行 | 后端实测记录:改车型场景槽位零变动;改日期场景 confirm 返 605062、取消越窗行并窗内重派后 confirm 返 200;加天数场景不拦截,且新增天用他单占用车被 605001 正确拒绝、precheck 如实返回车+司机双 conflicts。 --- ## 7. 不变的部分(无需改动) - `vehicleSlots` 其余字段(`assignmentSlotId`/`fleetItemIndex`/`assignmentGroupId`/`slotStatus`/`canDelete` 等)语义全不变 - 候选查询 `POST /admin/fleet/assignments/candidates` 的 `excludeAssignmentId` 语义不变(#5802 宽容口径保留:同订单不命中的排除项后端忽略并记 WARN,不再报"参数非法") - 派车/改派/取消/增删槽位所有既有接口契约不变 - 网关路由无新增 --- ## 8. 前端 checklist(做完请把 frontmatter 的 `frontend_status` 改为 `implemented` 并填 `frontend_ref`) - [ ] 详情响应新增消费 `requirementFleetText`,在派车页/订单概览展示为一行需求语句(null 不渲染) - [ ] 删除 `SupersededSlotCard` 组件及所有"旧数据"分支渲染(窄条/区块/角标) - [ ] 删除对 `supersededAssignments`、`requirementChangePendingCount`、`slot.superseded` 的一切依赖 - [ ] 槽位列表数据源统一为 `vehicleSlots`,按序原样渲染为普通槽位卡,能力(派车/换车/取消/删除)不因来源不同而降级 - [ ] 槽位头去掉「建议 {车型}」标签 - [ ] 派车推进类接口(confirm / batch)接住 `605062`,直接展示后端 `msg` + 操作引导 - [ ] 不新增任何前端侧日期硬拦截(不 disable 按钮)