docs(changelog): #5849 前端补完——两行最小改动、立即绕过办法、新增槽位 excludeAssignmentId 误传
所有检测均成功
changelog-filename-gate / validate (push) Successful in 2s

这个提交包含在:
API Changelog Bot 2026-08-11 18:37:55 +08:00
父节点 738e239a07
当前提交 45ea603be0

查看文件

@ -162,11 +162,42 @@ export function dailyVehiclePlanCandidateEvidenceMatches(cell = {}) {
这正是 wx 说的那句话的反面:「定制师的需求变了,但车务可以不改派车」。 这正是 wx 说的那句话的反面:「定制师的需求变了,但车务可以不改派车」。
### 3.4 与现场完全对上 ### 3.4 为什么偏偏是「1/3」、且只有一天挂橙标
26-3698 三个日格里,8/17 有证据wx 在本次会话里动过这一天,触发了候选查询并写戳,8/18、8/19 是回显数据、证据为 `null`**「1/3 个日格已完成」**,槽位因此算未完成 → **「已完成 0/1 个最终方案槽位」** → 「下一步」置灰。 两条机制叠加,**不是「中间那天特殊」**
> 补充:先前怀疑的「接机/参与勾选」不是原因——`pickupParticipant` 只参与「某服务日要求接机则至少一辆车参与接机」这条独立校验,不进日格完成度判定。 1. **只有 1 格能拿到证据**:弹窗初始化时自动激活「第一个未完成的用车格」(`AssignModal.vue` 约 :3536-3541,`dailyPlan` 按日期升序),即 **8/17**;picker 对该单日拉候选并**只回写这一格**的证据(约 :2418-2481。8/18、8/19 从未被激活,永远拿不到证据。
2. **两天都不合格,但只显示第一条**`validateDailyVehiclePlan` 返回**第一条**错误;能定位到具体格时 `dailyPlanGlobalValidationError` 返回空串(约 :1820-1836,改由矩阵按 `serviceDate` + 槽位号渲染**单行**橙标(`DailyVehiclePlanMatrix.vue` 约 :296-301。所以只有 8/18 挂标,8/19 同样不合格。另一单表现为 8/21 是同理。
**「1/3 个日格已完成」**(只有自动激活的那格有证据)→ 槽位算未完成 → **「已完成 0/1 个最终方案槽位」** → 「下一步」置灰。
> 排除项:「接机/参与」勾选**不是**原因——`pickupParticipant` 只参与「某服务日要求接机则至少一辆车参与接机」这条独立校验,`isDailyVehiclePlanCellComplete` 通篇不读该字段。
### 3.5 按钮与两个读数的完整链路(供定位)
```js
// AssignModalFooter.vue 约 :107
:disabled="!selectionReady || submitting"
// AssignModal.vue 约 :1837-1846
selectionReady = batchMode
? selectedSlots.length > 0 && !selectionBlockedReason && !dailyPlanValidationError
: ...
// 「N/M 个日格已完成」 约 :1526
dailyPlan.filter(isDailyVehiclePlanCellComplete).length
// 「已完成 N/M 个最终方案槽位」 约 :2416
slot.selectionReady = cells.every(isDailyVehiclePlanCellComplete)
```
三者全部本地计算,**Step2 →Step3 全程零 HTTP**`useAssignFlow.js` 约 :685-701 的 `onStep2Next` 只做 `step.value = 3`)。
---
## 🚑 立即可用的绕过(不需要发版)
抽屉里用「**统一选择车辆 / 司机**」→「**应用到槽位全部日期**」。该路径会给整槽**所有可编辑用车日格批量写入证据**`AssignModal.vue` 约 :2122-2127,成功提示「已应用到当前槽位 N 个用车日格」),一次打满 3/3,「下一步」即可点。
--- ---
@ -174,11 +205,26 @@ export function dailyVehiclePlanCandidateEvidenceMatches(cell = {}) {
**核心认识:客户端「候选证据」是对后端已有保证的重复设卡。** 提交时后端在锁内会重新校验资源可用性、档期冲突、车辆维保/司机休假、日期窗,真有问题会返回明确错误码;前端不需要、也不应该要求车务为「本来就已经派好、且没改动过」的日格重新走一遍选车弹窗。 **核心认识:客户端「候选证据」是对后端已有保证的重复设卡。** 提交时后端在锁内会重新校验资源可用性、档期冲突、车辆维保/司机休假、日期窗,真有问题会返回明确错误码;前端不需要、也不应该要求车务为「本来就已经派好、且没改动过」的日格重新走一遍选车弹窗。
按优先级: ### 最小改动:`utils/daily-vehicle-plan.js` 两行(**必须同改**
1. **(必做)从服务端快照重建日格时,用服务端数据补齐 `_candidateEvidence`**——即把「后端已存在这条派车行」本身当作证据(`serviceDate` / `vehicleId` / `driverId` / `confirmCrossResident` 直接取该行的值)。这样未被用户改动的既有日格天然通过,改动过的仍需走弹窗。 | # | 位置 | 现在 | 改为 | 解决 |
2. **(按 wx 口径)放宽门禁本身**:日格判定与「下一步」启用条件收敛为 **有车 + 有司机 + 服务日期在当前行程窗内**;车型不匹配、座位不足、槽位数与需求数量不符一律降级为提示(见下方分类表)。 |---|---|---|---|---|
3. 保留「用户手动改过车/司机后需重新校验」的行为是合理的,但**即使证据缺失也不应该硬置灰**——改为提示 + 允许提交,由后端做最终裁决。 | 1 | 约 :484 | `requireCandidateEvidence = true` | `false` | 按钮置灰 + 橙标 |
| 2 | 约 :465 | `return preservedFinalized \|\| dailyVehiclePlanCandidateEvidenceMatches(normalized)` | `return true` | 「1/3」与「0/1」计数 |
只改 1 不改 2 → 按钮能点但计数仍显未完成;只改 2 不改 1 → 计数对了但按钮仍被 `dailyPlanValidationError` 卡住。
### 可选增强(与 #5810 / #5824「只提示不门禁」同构)
新增一个独立 computed 调 `validateDailyVehiclePlan(..., { requireCandidateEvidence: true })`,把结果渲染成**灰色软提示**,**不接入 `selectionReady`**。这样"这一天你还没重新验过"的信息仍然可见,但不再卡住流程。
### 另一个更彻底的方向(可与上面二选一)
从服务端快照重建日格时,用服务端数据补齐 `_candidateEvidence``serviceDate` / `vehicleId` / `driverId` / `confirmCrossResident` 直接取该行的值)——把「后端已存在这条派车行」本身当作证据。这样未被用户改动的既有日格天然通过,改动过的仍需走弹窗。
### 回归
需同步调整 `__tests__/assign-modal-title.spec.js`(约 1726 / 1744 行等)与 `utils/__tests__/daily-vehicle-plan.spec.js` 的期望值。
--- ---
@ -204,14 +250,43 @@ export function dailyVehiclePlanCandidateEvidenceMatches(cell = {}) {
--- ---
## 六、后端侧的后续(不在本条,另发) ## 六、后端侧:本条 **0 改动**,放开前端后不会再被拒
- 前端放开门禁后,后端提交链路上是否还有会拒的守卫,正在核;需要后端改的部分由 **#5849** 处理,涉及契约变化会另发接口类 changelog。 已逐条核实(供前端放心放开):
- 若确认需要后端为前端补一个「本槽位是否可推进 + 不可推进原因」的字段,会在那份 changelog 里给出字段名与口径,届时前端可从「自己推导」改为「读后端结论」,避免两边口径再次劈叉。
| 守卫 | 结论 |
|---|---|
| Step2 → Step3 | **无任何后端调用** |
| `batchCreate` 契约 | 明文「可少派、多派…**车型/数量/人数与需求不符不拦截**」;`strictSeats` 三处硬编码 `false` |
| 605062 日期越窗 | 判定式只看日期集合归属,**不含** vehicleType / count / seats / headcount |
| `hasInvalidDispatchPlanGeneration` | 本单三行 `generation=NULL` / `finalized=0` → 返回 false,不拦 |
| `isRequirementFullyAssignedRows` | 只 return false 从不抛,用于快照发布与订单车控 DONE 判定,**非提交门禁** |
| `assertAtomicGroupReady` | Step4 确认执行的门禁,与 Step2 无关 |
| 「每个保留槽位必须覆盖全部服务日」(100001) | 要求每槽×每日提交一条 item,`used=false` 空格合法;前端本就按满矩阵提交,天然满足 |
--- ---
## 七、关联 ## 七、⚠️ 顺带发现的另一个前端缺陷(独立问题,本次修完仍需处理)
测试服 `hl-fleet-service.log` 17:40:39~17:41:20 抓到 **8 条**同款 WARN
```
候选查询排除派单与请求槽位上下文不一致,已忽略排除继续查询:
reqFleetItemIndex=2 excludedFleetItemIndex=0
excludedAssignmentId=2086270138708844545 excludedStatus=holding
```
**新增槽位index=2时,前端仍把 slot0 的 `assignmentId``excludeAssignmentId` 传**。后端判定上下文不一致后**静默放弃排除**(不报错,如实展示占用),于是本单自己的占用被算成阻断冲突 → 该车/该司机在新槽位下 `selectable=false`。实测印证:`fleetItemIndex=1``2` 时三天全部 `selectable=false`,冲突体正是本单自己。
**影响**:新增的槽位**永远拿不到候选证据**,是本次问题的同源二次伤害。
**前端改法**:新增槽位时不要沿用其它槽位的 `assignmentId`,该槽无既有派车行就**不传** `excludeAssignmentId`
> 后端侧会考虑加固(候选入参支持按槽位排除,而不是靠反查单行推断槽位身份),届时另发接口类 changelog。
---
## 八、关联
- 后端工单 #5849 - 后端工单 #5849
- `selectable` 字段口径:`11_5850_候选接口显式返回selectable-修改接口-管理后台.md` - `selectable` 字段口径:`11_5850_候选接口显式返回selectable-修改接口-管理后台.md`