--- schema: "hl-changelog/v2" ticket: "8301" title: "车务看板带日期筛选时,单条 requirement_id 为空的派车行不再清空整个日期窗" 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: "前端 2026-09-24 核验 not_required:接口契约零变化(路径/参数/字段/错误码全不变),纯后端行为修正——带日期筛选时 requirement_id 空脏行由整窗失败关闭返空改为跳过+WARN,同窗其余订单照常返回。grep 实证:看板列表前端只渲染 records,无对「整窗空」的特殊分支/依赖;fleet/board 前端 requirementId 唯一消费点在 useNoVehicleDeclaration(读详情 /orders/{orderId} 查无车声明),与看板列表日期分支无关。后端修正让数据更完整,前端天然受益零改动。" updated_at: "2026-09-24" base: "dev-v3" --- # fleet: 看板日期筛选分支的脏行失败关闭收窄为跳过单行 > **存放目录**: `changelogs-v2/2026-09/` > > **服务**: hl-fleet-service > **PR**: #8310 > **Issue**: #8301 > **日期**: 2026-09-24 > **影响范围**: 管理后台「车务看板」订单列表/网格视图(带日期筛选时) --- ## ⚠️ 关键变化 **带日期筛选时,单条脏数据不再清空整个日期窗。** 此前只要日期窗里存在任意一条 `requirement_id` 为空的派车行,`GET /admin/fleet/board/orders`(带 `startDate/endDate` 或 `startDayFrom/startDayTo`)就整页返回 `records=[]`、`total=0`、`code=200`,**不报错**——页面上看到的是「这几天没有订单」而不是任何异常。现在这条行被**跳过并记 WARN**(WARN 里带 `orderId`/`assignmentId`/`assignmentGroupId`),同一日期窗内其余订单**照常返回**,与不带日期筛选时的既有口径一致。 **接口契约零变化**:路径、参数、字段、类型、必填性、枚举、错误码全部不变,只修正日期分支的行为。 --- ## 一、背景 车务看板带日期筛选时走一条独立的「当前主单日期候选」扫描分支。该分支原本把四类候选行异常合并成一个失败关闭条件,其中一类是「派车行的 `requirement_id` 为空」——这是历史数据迁移、手工修数或未来写路径缺陷可能留下的占位行。这类行本身匹配不上任何用车需求,在不带日期的路径里只是被跳过并记一条汇总 WARN;但在日期分支里,它会让**同页全部订单**的候选一起作废,表现为整窗静默返回空。 本次把该条件按成因拆开:真正影响槽位聚合正确性的三类结构性畸形(行对象缺失、主单 ID 缺失、派车组 ID 与派车 ID 同时缺失)以及重复槽位键**仍然失败关闭**,只是各自补一条带定位信息的 WARN;`requirement_id` 为空的行改为逐行剔除并记 WARN。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 看板订单列表 | GET | `/admin/fleet/board/orders` | 行为变更(契约不变) | 带日期筛选时,`requirement_id` 为空的派车行由「整窗失败关闭」改为「跳过该行 + WARN」 | --- ## 三、接口详情 ### 1. 看板订单列表 `GET /admin/fleet/board/orders` **VO**: `BoardOrderPageReqVO → BoardOrderPageRespVO`(`records: BoardOrderRecordVO[]`) #### 使用场景 管理后台「车务看板」订单列表/网格视图(`variant=list|grid`)。车务人员按状态、日期、团号、车型等条件筛出待派/已派订单卡片,逐卡执行改派、派车、查看行程等操作。本次改动只影响**带日期筛选**时该列表返回的记录集合。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | startDate | Query | LocalDate | ❌ | `yyyy-MM-dd` | 行程日期窗起(`startDayFrom` 的兼容别名) | | endDate | Query | LocalDate | ❌ | `yyyy-MM-dd` | 行程日期窗止(`startDayTo` 的兼容别名) | | startDayFrom | Query | LocalDate | ❌ | `yyyy-MM-dd` | 行程日期窗起;为空时回落到 `startDate` | | startDayTo | Query | LocalDate | ❌ | `yyyy-MM-dd` | 行程日期窗止;为空时回落到 `endDate` | | statuses / status | Query | String[] / String | ❌ | 枚举:unassigned/unassigned_urgent/holding/holding_urgent/assigned/canceled/completed | 状态筛选,可多选;`status` 为单值兼容别名 | | vehicleTypeKeys / typeKeys | Query | String[] | ❌ | 车型大类字典值 | 车型多选筛选 | | keyword / driverName / contactName / teamNo / plannerName | Query | String | ❌ | - | 关键字、司机、联系人、团号、定制师等模糊搜索 | | groupBatchId | Query | Long | ❌ | 等值匹配 | 运营团期 ID | | consultantId | Query | Long | ❌ | - | 定制师 ID 精确筛选 | | variant | Query | String | ❌ | `list`/`grid` | 视图口径 | | page(或 pageNo) | Query | Integer | ❌ | ≥1,缺省 1 | 页码 | | pageSize | Query | Integer | ❌ | 1-100 | 每页条数 | > 本单不新增、不删除、不改任何参数的类型与必填性;上表只为把「受影响的日期参数」放进完整上下文。 #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | records | BoardOrderRecordVO[] | 本页看板卡片;本次改动只影响带日期筛选时该数组的内容 | | total | Long | 命中总数;带日期筛选时不再被单条脏行清零 | | page | Integer | 当前页码 | | pageSize | Integer | 每页条数 | | records[].id | Long | 派车行/虚拟条目主键 | | records[].orderId / orderNo / teamNo | Long / String / String | 子订单 ID、订单编号、团号 | | records[].assignmentId / assignmentGroupId / requirementId | Long | 派车行 ID、派车分组 ID、当前用车需求 ID | | records[].assignmentStatus | String | 派单状态码 | | records[].virtualPending | Boolean | 虚拟待派卡标记(真实记录恒为 `false`) | | records[].groupVehicleCovered | Boolean | 团车整段接管标记(详见 #8235 交接件) | | records[].urgentBadge / canAssign / dispatchReadOnly | String / Boolean / Boolean | 加急标签、可派车、只读 | | records[].startDate / endDate | LocalDate | 行程起止日期 | | records[].currentVehiclePlate / currentDriverName | String | 当前车辆车牌 / 司机姓名 | #### 请求示例 ```http GET /admin/fleet/board/orders?startDate=2026-11-01&endDate=2026-11-10&variant=list&pageNo=1&pageSize=20 ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "records": [ { "id": 7330843067599549, "orderId": 2101014943313670001, "orderNo": "HL20261106000001", "teamNo": null, "assignmentId": 7330843067599549, "assignmentGroupId": 7330843067599549, "requirementId": 2101014943313670100, "assignmentStatus": "holding", "virtualPending": false, "groupVehicleCovered": null, "urgentBadge": null, "canAssign": true, "dispatchReadOnly": false, "startDate": "2026-11-06", "endDate": "2026-11-07", "currentVehiclePlate": null, "currentDriverName": null } ], "total": 1, "page": 1, "pageSize": 20 }, "success": true } ``` #### 空数据 / 降级响应 无匹配记录时返回空数组与 `total=0`,不报错: ```json { "code": 200, "message": "成功", "data": { "records": [], "total": 0, "page": 1, "pageSize": 20 }, "success": true } ``` 带日期筛选时的空列表**仍不等同于**「该日期窗确实无单」:日期候选扫描取数失败、结果集缺失、候选行结构畸形(行对象缺失 / 主单 ID 缺失 / 派车组 ID 与派车 ID 同时为空)或槽位键重复时,本页仍按既有 fail-closed 口径整体返回空,但每一种成因现在都会在 `hl-fleet-service` 日志里留下一条可区分的 WARN。不带日期筛选的查询不走这条分支。 #### 错误响应 ```json { "code": 401, "message": "未登录或登录已过期", "success": false, "data": null } ``` #### 业务边界 - 只影响**带日期筛选**的分支。不带日期的查询、车辆矩阵接口、看板汇总接口的口径与本次改动前完全一致。 - `requirement_id` 为空的派车行会被剔除:该行原本在不带日期的路径里也匹配不上任何用车需求、不可能出现在看板上,因此剔除它不会让**本该展示**的卡片消失。 - 若某订单在日期窗内**只有**这一条脏行,该订单在带日期的视图里看不到——这与它不带日期时的既有表现一致;让它可见需要的是数据修复,不是看板放行。 - 日期候选结果集本身取到空(无订单上下文)时不查派车行,正常返回空页。 - 鉴权:管理后台登录态,未登录 401(网关拦截)。 --- ## 四、契约约束与正确调用方式 ### ✅ 正确 / ❌ 错误用法对照 | 场景 | 说明 | |------|------| | ✅ 直接使用 `total` 与 `records` | 结构与字段均未变,前端无需做任何解析适配 | | ✅ 把带日期筛选返回的空列表理解为「结构性异常或确实无单」 | 本次改动后,单条 `requirement_id` 为空的脏行不再制造这种空结果;剩余会制造空结果的是结构性畸形(行缺失、主单 ID 缺失、槽位键缺失或重复),均会在服务端留 WARN | | ❌ 为「带日期筛选可能返回空」保留前端兜底或本地缓存回填 | 本次改动后不再是必需;保留也不会出错,但会把真实的结构性畸形空结果一并掩盖 | | ❌ 依赖后端 WARN 的具体文案做前端逻辑 | WARN 只面向排障,文案不是契约 | ### 切换状态时的必要动作 无。本接口是只读 GET,跳过脏行的判定完全由后端在读取时完成,不接受前端传参覆盖,也不需要前端在请求前做任何字段准备。 --- ## 六、边界行为 - 未登录 → 401(网关拦截) - 无匹配记录 → 200 + 空数组、`total=0`,不报错 - 日期候选行含 `requirement_id` 为空的行 → 200,该行被跳过并记 WARN,其余订单照常返回(本次改动) - 日期候选行结构畸形(行对象缺失 / 主单 ID 缺失 / 派车组 ID 与派车 ID 同时缺失)或槽位键重复 → 200 + 整页空(既有 fail-closed 口径保留),并新增带定位信息的 WARN - order-v3 日期候选页取数失败、超过单次一致性快照上限或上下文畸形 → 200 + 整页空(既有 fail-closed 口径保留) - 虚拟待派候选层不可用(开关关闭或远端候选不可用)→ 只丢虚拟条目,真实派车行照常返回 --- ## 六.6、修改前后对比 ### 字段级对比 | 字段 | 改前 | 改后 | 变化说明 | |------|------|------|----------| | 请求 / 响应字段 | 无变化 | 无变化 | 本次为纯行为修正,不新增、不删除、不改类型 | ### 行为级对比 | 行为 | 改前 | 改后 | 影响 | |------|------|------|------| | 日期窗内某订单存在一条 `requirement_id` 为空的派车行 | **整窗**返回 `records=[]`、`total=0`、`code=200`,服务端无日志 | 只**跳过该行**并记 WARN,同窗其余订单正常返回 | 带日期筛选的列表不再被单条脏数据清空 | | 同一数据**不带日期**查询 | 该行被跳过 + 一条汇总 WARN | 不变 | 无 | | 日期候选出现重复槽位键 | 整页失败关闭,无日志 | 整页失败关闭(不变),新增带重复键样本的 WARN | 排障可定位 | | 日期候选行结构畸形(行缺失 / 主单 ID 缺失 / 槽位键全缺) | 整页失败关闭,无日志 | 整页失败关闭(不变),新增带成因与定位字段的 WARN | 排障可定位 | --- ## 六.7、影响评估 - **是否破坏向后兼容**: 否。请求参数、响应结构、字段类型与错误码全部不变,仅带日期筛选时的记录集合与 `total` 不再被单条脏数据清零。 - **前端是否必须同步上线**: 否。前端无需修改即可受益。 - **前端 workaround 清理点**: 若前端此前针对「带日期筛选偶发返回空」做过兜底或本地回填,可以在确认后移除;保留不影响功能。 --- ## 七、不影响范围 - **仅影响**:管理后台「车务看板」订单列表/网格视图(`/admin/fleet/board/orders`)在**带日期筛选**时的记录集合与 `total`。 - **零影响**: - 不带日期筛选的看板订单列表口径 - 车辆矩阵未派清单、网格与月度统计(`/admin/fleet/matrix/**`,不走日期候选扫描分支) - 看板汇总与统计接口 - 派车、改派、确认等写接口 - 用车需求与派车行的数据结构、状态机与任何表结构 --- ## 八、测试环境已验证 测试服部署(`deploy:test` 单写租约):`hl-fleet-service` 2026-09-24 10:49:54 滚动部署到 `dev-v3 @ cc70c9ba3`(该提交是本次 squash 合并 `48c755fd1` 的后代),实例 8187 / 8087 均 UP 且健康检查通过。 网关实测(`https://api.test.1814.love`,管理后台登录态切到车务角色): | # | 请求 | 修复前 `total` | 修复后 `total` | |---|------|----------------|----------------| | 1 | `GET /admin/fleet/board/orders`(不带日期) | 243 | 243(对照组,未变)✓ | | 2 | `...&startDate=2026-11-01&endDate=2026-11-10`(窗内含脏行) | **0** | **8** ✓ | | 3 | `...&startDayFrom=2026-11-01&startDayTo=2026-11-10`(同窗别名参数) | **0** | **8** ✓ | | 4 | `...&startDate=2026-09-24&endDate=2026-11-04`(窗内不含脏行) | 71 | 71(对照组,未变)✓ | | 5 | `...&startDate=2026-11-06&endDate=2026-11-06`(脏行当天) | **0** | **6** ✓ | | 6 | `...&startDate=2026-11-05&endDate=2026-11-07` | **0** | **6** ✓ | 服务端逐行 WARN(两个实例各两条,与上表实测同刻,原始行): ```text 2026-09-24 10:50:56.063 [http-nio-8087-exec-1] WARN c.h.f.b.service.BoardCandidateSource - 车务看板列表日期候选剔除无用车需求ID的派单行(本行不参与候选): orderId=2101014943313670146 assignmentId=7330843067599548 assignmentGroupId=7330843067599548 2026-09-24 10:50:56.556 [http-nio-8187-exec-2] WARN c.h.f.b.service.BoardCandidateSource - 车务看板列表日期候选剔除无用车需求ID的派单行(本行不参与候选): orderId=2101014943313670146 assignmentId=7330843067599548 assignmentGroupId=7330843067599548 ``` 那条脏行(`assignment_id=7330843067599548`,`order_id=2101014943313670146`,行程日期 2026-11-06)本身不再出现在任何日期窗结果里——它匹配不上任何用车需求,本就无法在看板渲染;关键变化是它不再让同窗其余订单一起消失。 本地验证:定向 42/42 绿、看板与矩阵影响面回归 379/379 绿、`spotless:check` 与 `mvn -o -pl hl-fleet-service -am verify` 均 BUILD SUCCESS(该模块 4766 个用例 0 失败 0 错误 6 跳过)。 --- ## 十、相关文档 - 关联 Issue: [wx/HL#8301](https://git.1814.love/wx/HL/issues/8301) - 关联 PR: [wx/HL#8310](https://git.1814.love/wx/HL/pulls/8310) - 同一接口的上一份交接件(#8235,看板订单记录新增 `groupVehicleCovered`),其中「带日期筛选的既有 fail-closed 口径」一条由本文件取代 ## 关联 / 联系人 ### 链接 - **Issue**: [#8301](https://git.1814.love/wx/HL/issues/8301) - **PR**: [#8310](https://git.1814.love/wx/HL/pulls/8310) - **Merge commit**: [48c755fd1](https://git.1814.love/wx/HL/commit/48c755fd1feff715f9ec946b55f77207f23dc942) ### 联系人 - **后端负责人**: @wx - **前端负责人**: @mmg