文件
hl-api-changelog/changelogs-v2/2026-09/20_5935_改期残留清理用现有读写口即可-前端缺陷-管理后台.md
API Changelog Bot和Claude Opus 5.5 69f82a17a2
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): #8371 改期残留行单行取消 + #8372 团期总览 groupCode;20_5935 加订正指针
- 26_8371:DELETE /admin/fleet/assignments/{assignmentId} 命中改期残留行只取消该行、豁免 605027/605047/605028;
  CancelRespVO.evidenceFileIds 改为按组累计(本次未传凭证时为空);取窗失败 605710 失败关闭。
- 26_8372:GET 团期派车总览 days[].vehicles[] 新增 groupCode,可原样回提 reconfigure 的 assignments[].groupId。
- 20_5935:旧件各【需确认】/错误说法处加 #8371 订正指针,指向新件。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 15:13:41 +08:00

34 KiB

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 5935 改期残留清理用现有读写口即可,无需新增字段/接口 admin wx(GIT) 前端缺陷 deployed not_required verified mmg 28d4482c636dbfeba1803c64ef263f09c3656934 hl-ui@28d4482c 2026-09-21 前端 2026-09-20 反馈清单第4项引用了一个不存在的写口 POST .../slots/{slotId}/clear-residue-dates(该端点已随 #7067 去槽位化整体删除,origin/dev-v3 全仓零命中),并要求后端在读侧新增 residueServiceDates 字段。核实:读侧 cells[].rescheduleResidue 已能推导出该日期子集,写侧 DELETE /{assignmentId} 与 POST /batch 两条既有端点已覆盖单日/整批清理,本次零后端代码改动。POST /batch 对已过去的残留日期有硬门禁会拦截,DELETE 是否放行未取证,本文档已如实标注【需确认】。前端 2026-09-21 已交付:Step2 逐日栅格恢复残留格「取消残留」入口(canDelete!==false 且 assignmentId 时渲染,只读行禁用,deleteBlockReason 透传),逐日清理走 DELETE /admin/fleet/assignments/{assignmentId}(cancelReason/driverNotified/requestId 按契约显式传,requestId=createRequestId('residue-clear'));整批 POST /batch 100001「已过去或已完结」识别后提示改走逐日清理。DELETE 对过去日期是否放行(605028)仍未实证,由拦截器统一透后端 message 兜底。2026-09-26 订正(#8371):DELETE 命中改期残留行已改为单行取消,并豁免 605027/605047/605028 三道日期门,见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。 2026-09-26 dev-v3

车务派单:改期残留清理用现有读写口即可(用法说明,零代码改动)

存放目录: 二期(v3) → changelogs-v2/2026-09/ 服务: hl-fleet-service (端口 8082) PR: 无(零代码改动,未产生新 PR) Issue: #5935(原始功能单,2026-08-16 已上线并随 #7067 部分退役;本文档是第三次说明) 日期: 2026-09-20 影响范围: 管理后台车务排车 Step2 弹窗「改期残留清理」交互的前端实现方式;不改后端代码、不改网关路由


给前端的一句话结论

你要的能力今天就已经有:可勾选的残留日期数组不需要后端新增字段,从现有 candidates 响应的 cells[] 里按 rescheduleResidue === true 过滤即可拿到;清理动作也不需要等新接口,逐日清理用 DELETE /admin/fleet/assignments/{assignmentId}、整批清理用 POST /admin/fleet/assignments/batch(不提交残留日期项即视为清理)。但你在清单里引用的那个写口 POST .../slots/{slotId}/clear-residue-dates 不存在——它已经随 #7067 去槽位化被整体删除,请改用下面两条现成的写口。


⚠️ 关键变化

  • 本次没有新增、修改或删除任何接口,后端零代码改动。
  • 前端诉求引用的写口 POST .../slots/{slotId}/clear-residue-dates 在 origin/dev-v3 全仓零命中(clear-residue-dates / clearResidueDates / cancel-residue 均零命中)。AssignmentControllerTest.java:938 留有明确注释:「#7067:cancel-residue 端点已随槽位概念退役删除」——「槽位(slot)」这个概念本身已经退役,基于 slotId 的写口不会再出现。
  • 前端要求新增的读侧字段 residueServiceDates不需要新增:AssignmentCandidateRespVO.CanonicalSnapshotVO.cells[](AssignmentCandidateRespVO.java:108-150)里每个 cell 已经逐日携带 rescheduleResidue(布尔)+ serviceDate + assignmentId,前端自己过滤即可得到这个数组。
  • ⚠️ POST /batch 整批清理对"已过去的残留日期"有硬门禁会拦截(详见「四、契约约束」),前端做整批清理时必须预期这种场景下的 400。逐日清理 DELETE /{assignmentId} 是否放行过去日期,本轮未取证,标记为【需确认】。

2026-09-26 订正(#8371):

  • DELETE /{assignmentId} 逐日清理残留行现已单行取消(不再整组取消),并豁免 605027/605047/605028 三道日期门。
  • 改期残留行判定、单行处理、错误码豁免的细节,见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。

一、背景

前端诉求(原文摘要,仅作背景,不作为下文技术结论的依据)

前端 2026-09-20 反馈清单第4项:「写口 POST .../slots/{slotId}/clear-residue-dates 已就绪,但前端只有 rescheduleResidue 布尔徽标,无可勾日期子集,无法实现『选槽位+勾日期』交互」,要求后端在读侧补一个 residueServiceDates 日期数组。

「前端以为的」vs「实际是」

维度 前端以为的 实际是(文件:行 @origin/dev-v3)
写口 POST .../slots/{slotId}/clear-residue-dates 已就绪可直接调 该端点不存在:clear-residue-dates/clearResidueDates/cancel-residue 全仓零命中;AssignmentControllerTest.java:938「#7067:cancel-residue 端点已随槽位概念退役删除」。「槽位」概念本身已随 #7067 去槽位化退役,请改用 DELETE /admin/fleet/assignments/{assignmentId}(逐日)或 POST /admin/fleet/assignments/batch(整批)
读侧字段 需要后端新增 residueServiceDates: string[] 字段才能拿到可勾选日期子集 不需要新增:cells[] 已逐日携带 rescheduleResidue(AssignmentCandidateRespVO.java:146-147)+ serviceDate,前端 cells.filter(c => c.rescheduleResidue).map(c => c.serviceDate) 即得;editableServiceDates(AssignmentCandidateRespVO.java:90)也已把残留日期并入集合(Step2CanonicalSnapshotService.java:218-227)

该问题的历史脉络(同一张单 #5935)

  1. 2026-08-16:changelogs-v2/2026-08/16_5935_排车按日期清理改期残留-新增接口-管理后台.md 记录了 当时新增 的端点 POST /admin/fleet/assignments/slots/{slotId}/clear-residue-dates(commit 1fe44677a,PR #5985)。
  2. #7067 去槽位化整体重构派单契约,slotId 概念被删除,该端点随之整体删除(该 changelog 的 status_note 已自我标注:"本端点...已被 #7067 去槽位化整体删除(404),无前端落地对象...残留清理语义由 POST /batch 整批 diff 覆盖")。
  3. 2026-09-20:前端仍然引用了这个已退役的端点、并额外要求新增字段——本文档是第三次澄清:不用等新接口,现有两条写口 + 现有读侧字段已完全覆盖这个场景。

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 查询派单候选资源(含残留标记) POST /admin/fleet/assignments/candidates 复用不改 canonicalSnapshot.cells[].rescheduleResidue 已可推导残留日期子集
2 取消派单(逐日清理残留) DELETE /admin/fleet/assignments/{assignmentId} 复用不改 按 cell 的 assignmentId 单日取消,条件释放占用
3 批量创建派单(整批清理残留) POST /admin/fleet/assignments/batch 复用不改 提交时不带残留日期项,后端按 diff 精确取消

2026-09-26 订正(#8371):上表第 2 行「按 cell 的 assignmentId 单日取消」在写作当时并不成立——彼时 DELETE 命中改期残留行仍按整组取消(还会连带取消同组窗内的其它日期行,这正是 #8371 要修的缺陷)。#8371 已实测确认:DELETE 现在对「改期残留行」做真正的单行取消,窗内其它行不受影响;非残留行仍按原整组逻辑处理。细节与实测证据见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。


三、接口详情

1. 查询派单候选资源 POST /admin/fleet/assignments/candidates

VO: AssignmentCandidateReqVO → AssignmentCandidateRespVO

使用场景

车务打开排车/派单 Step2 弹窗时调用本端点获取 canonicalSnapshot;改期后旧日期的逐日派车切片会作为「改期残留」继续出现在 cells[] 里(Step2CanonicalSnapshotService.java:199-227:完整可编辑服务日 = 需求 serviceDates 权威集合 ∪ 在途旧行覆盖的越窗旧日期,注释原文「并入改期残留旧日期:在途行覆盖日期中越出当前需求窗的部分(含全程行展开)」)。前端从返回的 cells[] 中筛出 rescheduleResidue === true 的项,即得到「待清理的改期残留」候选日期集合,供「选槽位+勾日期」交互勾选。

本节仅列出与「识别/清理改期残留」直接相关的字段;候选资源查询的完整契约(车辆/司机分页、筛选、常驻关系、自动代入司机等)不在本文档范围。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Body String(Long) 否(改派排除自身时必填) - 当前订单ID
requirementId Body String(Long) 否(改派排除自身时必填) - 当前用车需求ID;requirementId 缺失或需求上下文不可用时 canonicalSnapshot 为 null(AssignmentCandidateRespVO.java:65-68)
startDate Body LocalDate 是 @NotNull 查询窗口开始日
endDate Body LocalDate 是 @NotNull 查询窗口结束日

出参字段表

字段 类型 说明
data.canonicalSnapshot Object 无 requirementId 或上下文不可用时为 null
data.canonicalSnapshot.editableServiceDates LocalDate[] 完整有序可编辑服务日集合;已并入改期残留旧日期(AssignmentCandidateRespVO.java:90;并入逻辑 Step2CanonicalSnapshotService.java:218-227)
data.canonicalSnapshot.cells[] Array 每个 RetainedGroupSet × editableServiceDates 笛卡尔积位置的唯一 cell
data.canonicalSnapshot.cells[].groupId String(Long) 稳定派车组ID(历史行无 assignmentGroupId 时回退 assignmentId)
data.canonicalSnapshot.cells[].serviceDate LocalDate 服务日期
data.canonicalSnapshot.cells[].assignmentId String(Long) 当天逐日派车行ID;全程行/无行/不用车为 null;「取消残留派车按此ID单日取消」(AssignmentCandidateRespVO.java:124-126原文注释)
data.canonicalSnapshot.cells[].used String USED(实际用车) / UNUSED(显式不用车) / null(无切片行)
data.canonicalSnapshot.cells[].rescheduleResidue Boolean 前端要的「可勾选残留日期」判据就是这个字段:true=该 cell 服务日期越出当前需求日期窗,属改期后待清理的旧实派车,前端据此挂「改期残留/待清理」徽标(AssignmentCandidateRespVO.java:146-147)
data.canonicalSnapshot.cells[].canDelete Boolean 是否可删除/取消:改期残留旧行恒 true,只能改派或取消释放占用;受只读窗口约束时为 false(AssignmentCandidateRespVO.java:149-150原文注释)
data.canonicalSnapshot.cells[].readOnly Boolean 该 cell 是否只读;只读 cell 不可被批量操作改写
data.canonicalSnapshot.cells[].readOnlyReason String 只读原因;可编辑时为 null

请求示例

以下示例按 VO 字段与类型构造,非测试服抓包实测。

{
  "orderId": "2096412454643802114",
  "requirementId": "2096412454643812345",
  "startDate": "2026-09-10",
  "endDate": "2026-09-15"
}

响应示例

按 AssignmentCandidateRespVO/SnapshotCellVO 字段契约与仓库内测试夹具(AssignmentControllerTest.java:143-199)构造,非测试服抓包实测;cells[1] 展示一个 rescheduleResidue=true 的残留日期。

{
  "code": 200,
  "message": "成功",
  "data": {
    "canonicalSnapshot": {
      "planGeneration": "9007199254741001",
      "snapshotVersion": "1",
      "retainedGroupIds": ["9007199254741002"],
      "editableServiceDates": ["2026-09-10", "2026-09-11", "2026-09-08"],
      "cells": [
        {
          "groupId": "9007199254741002",
          "serviceDate": "2026-09-10",
          "assignmentId": "2096412454488612900",
          "used": "USED",
          "rescheduleResidue": false,
          "canDelete": true,
          "readOnly": false,
          "readOnlyReason": null
        },
        {
          "groupId": "9007199254741002",
          "serviceDate": "2026-09-08",
          "assignmentId": "2096412454488612866",
          "used": "USED",
          "rescheduleResidue": true,
          "canDelete": true,
          "readOnly": false,
          "readOnlyReason": null
        }
      ]
    }
  },
  "success": true
}

空数据 / 降级响应

requirementId 缺失或需求上下文不可用时 canonicalSnapshot 整体为 null(不是空对象),前端应据此区分「无残留」和「查不到需求上下文」两种态;没有任何改期残留时 cells[] 内所有项 rescheduleResidue 恒为 false,不会单独返回空数组表示"无残留"。

{ "code": 200, "message": "成功", "data": { "canonicalSnapshot": null }, "success": true }

错误响应

{
  "code": 400,
  "message": "参数非法: 用车开始日期不能为空",
  "data": null,
  "success": false
}

业务边界

  • cells[] 是 RetainedGroupSet × editableServiceDates 的笛卡尔积,并非只含残留日期——必须用 rescheduleResidue === true 过滤,不能假设整个数组都是残留。
  • canDelete 字段的设计意图是「改期残留旧行恒 true」,但受「只读窗口」约束时会翻转为 false;前端在渲染可勾选状态时应优先读 canDelete,而不是只看 rescheduleResidue。
  • 改派场景(排除自身占用)需另传 excludeAssignmentId(本文档聚焦残留清理场景,未展开该分支)。

2. 取消派单(逐日清理残留) DELETE /admin/fleet/assignments/{assignmentId}

VO: CancelReqVO → CancelRespVO

2026-09-26 订正(#8371):本小节标题「逐日清理残留」写作时是前端诉求的期望描述,当时源码并不支持——DELETE 命中改期残留行时仍按整组取消。#8371 已实测确认标题所述行为现已成立:目标行若判定为「改期残留行」,走单行取消分支,操作日志 operationLog[].detailJson 会带 scope="RESIDUE_ROW" 可供核验;非残留行不受影响,仍是整组取消。细节见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。

使用场景

车务在 Step2 栅格里勾选一个或多个 rescheduleResidue=true 的日期后,对每个勾选日期取其 cell 的 assignmentId,逐日调用本端点单独取消,条件释放该行占用的车辆/司机。适合「只清理某几天」「部分勾选」场景。

入参字段表

字段 位置 类型 必填 约束 说明
assignmentId Path Long 是 - 取自 cells[].assignmentId(残留清理场景下即待取消的那一天)
cancelReason Body String 是 @NotBlank 取消原因
driverNotified Body Boolean 是 @NotNull 是否已告知司机(人工存证;true/false 均可取消)
notifyNote Body String 否 - 告知备注
cutoffDate Body LocalDate 否 - 取消生效日;服务端仅接受当天,空值按当天处理,不支持预约未来取消(CancelReqVO.java:35-37原文注释)
evidenceFileIds Body List 否 最多20个 取消凭证文件ID列表
confirmWithoutEvidence Body Boolean 否 - 无凭证时的二次确认标记
requestId Body String 是 @NotBlank,最长64 请求幂等ID

出参字段表

字段 类型 说明
data.assignmentStatus String canceled=已取消
data.assignmentGroupId String(Long) 派车组ID;历史行无该字段时回退 assignmentId
data.assignmentSlotId String(Long) 稳定车辆槽位ID(历史字段,随 #7067 去槽位化不再是新契约主键)
data.effectiveDate LocalDate 实际取消生效日
data.evidenceFileIds String(Long)[] 取消凭证文件ID列表
data.confirmedWithoutEvidence Boolean 无凭证时是否已二次确认
data.sideEffects.vehicleStatusUpdated String 车辆状态更新结果(idle/busy/maint;不涉及车为 null)
data.sideEffects.driverStatusUpdated String 司机状态更新结果(busy/idle;不涉及司机为 null)
data.sideEffects.reconPrepRowsCreated Integer 对账预备单创建条数(M1 降级恒 0)
data.sideEffects.reconPrepMarkedCanceled Integer 对账预备单标记取消条数(M1 降级恒 0)

请求示例

{
  "cancelReason": "改期残留清理",
  "driverNotified": true,
  "notifyNote": "已电话通知师傅",
  "requestId": "residue-clear-2096412454488612866"
}

响应示例

按 CancelRespVO 字段契约构造,非测试服抓包实测。

{
  "code": 200,
  "message": "成功",
  "data": {
    "assignmentStatus": "canceled",
    "assignmentGroupId": "9007199254741002",
    "assignmentSlotId": null,
    "effectiveDate": "2026-09-20",
    "evidenceFileIds": [],
    "confirmedWithoutEvidence": null,
    "sideEffects": {
      "vehicleStatusUpdated": "idle",
      "driverStatusUpdated": "idle",
      "reconPrepRowsCreated": 0,
      "reconPrepMarkedCanceled": 0
    }
  },
  "success": true
}

空数据 / 降级响应

本端点是单条操作,无「空数据」态;命中不存在的 assignmentId 走错误响应(见下)。

{ "code": 605009, "message": "派单不存在", "data": null, "success": false }

错误响应

{
  "code": 605028,
  "message": "取消生效日不在派单服务日期范围内",
  "data": null,
  "success": false
}

其它错误码(AssignmentController.java:469-471):400(cancelReason/driverNotified/requestId 请求校验)/ 605009(派单不存在)/ 605020(当前状态不允许取消)/ 605026(无凭证未二次确认)/ 605027(已出发禁止整组取消)。

2026-09-26 订正(#8371):目标行判定为「改期残留行」时,605027(连同 605047/605028)不再触发,改走单行取消;新增 605710「订单服务不可用:{0}」——残留行判定依赖 order-v3 查询窗口数据,查询失败时整笔 fail-closed,不取消任何行。非残留行的错误码集合不变。见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。

业务边界

  • 【需确认】本端点对"服务日期已过去的改期残留行"是否放行取消,本轮未取证:CancelReqVO.cutoffDate 注释写明「服务端仅接受当天,空值按当天处理,不支持预约未来取消」,而 605028 的语义是「取消生效日不在派单服务日期范围内」——若某残留行 serviceDate 是 3 天前、cutoffDate 按当天处理,"当天"是否落在该行的服务日期范围内、会不会触发 605028,未经实测确认。读侧 canDelete 字段的设计意图是「改期残留旧行恒 true,受只读窗口约束时 false」(AssignmentCandidateRespVO.java:149-150),但这是响应快照上的展示态,不等于运行时 DELETE 调用本身在过去日期上必然放行。若实测发现本端点对已过去日期同样拦截,那才是真正需要后端开一张单的地方(放开残留行对过去日期的取消)。

    2026-09-26 订正(#8371):已实测坐实——修复前,改期残留行(服务日期已过去)确实被拦截,实测复现 605027;#8371 修复后,同一残留行 DELETE 直接取消成功,不再命中 605027/605047/605028。取消后该行状态落 exception(不是 canceled,因取消前处于 assigned/holding,与通用取消 #6152 同口径)。判定逻辑与全链路证据见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。

  • assignmentId 必须取自对应 cell(残留清理场景下即 rescheduleResidue=true 的那一项),传错会命中另一天的派车行。

  • driverNotified 传 false 也允许取消,只是前端需要在未告知司机时给出强提示(后端只做存证,不代为通知)。


3. 批量创建派单(整批清理残留) POST /admin/fleet/assignments/batch

VO: BatchCreateAssignmentReqVO → BatchAssignmentWriteRespVO

使用场景

车务在 Step2 栅格里编辑好完整的新逐日配车方案后一次性提交。若不希望保留某些残留日期,只需在 dailyPlan[] 里不包含那些日期,后端按(服务日期×车辆)diff 原子重写:新方案未覆盖的现行日期行自动取消(含改期残留行)。适合"整批一次性对齐新方案"场景。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Body String(Long) 是 @NotNull 订单ID
requirementId Body String(Long) 是 @NotNull 当前生效用车需求ID
kind Body String 否 TRAVEL/TRANSFER 需求类别,不传按 TRAVEL
startDate Body LocalDate 是 @NotNull 用车开始日期
endDate Body LocalDate 是 @NotNull 用车结束日期
confirmNoVehicleServiceDates Body Boolean 否 - 逐日计划未覆盖全部服务日期时的显式二次确认
sendItinerarySms Body Boolean 否 不传按 false 是否向本批各车师傅发送行程短信
requestId Body String 是 @NotBlank,最长64 批次级幂等请求标识
dailyPlan[] Body Array 是 最多4000项 按行程日的完整配车列表;要清理的残留日期不要出现在这个数组里
dailyPlan[].serviceDate Body LocalDate 是 @NotNull 服务日期
dailyPlan[].vehicleId Body String(Long) 是 @NotNull 车辆ID
dailyPlan[].driverId Body String(Long) 是 @NotNull 司机ID
dailyPlan[].assignmentPrice Body String(BigDecimal) 否 ≥0 本车当天实际价格,不传按车型价格日历兜底
dailyPlan[].priceAdjustmentReason Body String 否 最长256 实际价格与参考价不一致时的调整原因
items / chargeableServiceDates / vehicleFeeWaiverReason / confirmAllServiceDatesFree / holdMode Body - - 禁止携带 #7067 已移除的旧字段,携带即 400(BatchCreateAssignmentReqVO.java:98-124)

出参字段表

字段 类型 说明
data.assignments[] Array 按 fleetItemIndex 升序返回的派单结果(历史字段名,结构见 AssignmentWriteRespVO)
data.finalPlanPublished Boolean 本次是否已发布最终方案;false 表示排车已落库但接送机未配齐
data.pickupDropoffGate Object 接送机门禁状态(要求日与缺口日)
data.failedFleetItemIndex Integer 直接派定基线失败的车辆槽位序号
data.dailyDifferences[] Array 直接派定基线失败的逐日差异

请求示例

{
  "orderId": "2096412454643802114",
  "requirementId": "2096412454643812345",
  "startDate": "2026-09-09",
  "endDate": "2026-09-15",
  "requestId": "batch-residue-clear-20260920-001",
  "dailyPlan": [
    { "serviceDate": "2026-09-10", "vehicleId": "2079857983403024385", "driverId": "2079857983403024387" },
    { "serviceDate": "2026-09-11", "vehicleId": "2079857983403024385", "driverId": "2079857983403024387" }
  ]
}

上例中 2026-09-09(残留日期)未出现在 dailyPlan[] 里,后端 diff 会把该日现行行自动取消。

响应示例

按 BatchAssignmentWriteRespVO 字段契约构造,非测试服抓包实测。

{
  "code": 200,
  "message": "成功",
  "data": {
    "assignments": [],
    "finalPlanPublished": true,
    "pickupDropoffGate": null,
    "failedFleetItemIndex": null,
    "dailyDifferences": null
  },
  "success": true
}

空数据 / 降级响应

未配置接送机声明的订单 pickupDropoffGate 恒为 null;无接送机声明时提交即完成(finalPlanPublished=true)。

错误响应

本场景最关键的错误——残留日期已过去时,diff 判定要取消它会被"日期已过去"门禁拦下(AssignmentService.java:2775-2778 assertDailyPlanDateMutable),整个批次连同一并提交的其它合法项一起被拒绝,非部分成功:

{
  "code": 100001,
  "message": "参数非法: 2026-09-09已过去或已完结,不能修改逐日派车配置",
  "data": null,
  "success": false
}

其它已知错误码:605062(提交项的服务日期越出当前需求日期窗;已有的窗外在途行不受此码约束,改由整批 diff 精确取消,见 AssignmentServiceTest.java:1727 用例)/ 605912(派车快照缺少车型)。

业务边界

  • ⚠️ 改期后延的残留日期多半已经过去,本端点无法清理它们:assertDailyPlanDateMutable(AssignmentService.java:2775-2778)对"新方案未覆盖、因此要被 diff 自动取消"的现行行同样做日期校验,命中过去日期即整批 400。前端做"整批清理"操作前必须预判这种场景,遇到 400 应引导车务改用逐日 DELETE /{assignmentId}。
  • 旧字段(items/fleetItemIndex/used/pickupParticipant 等)携带即 400,不再兼容。
  • 接送机不在本接口配置,走独立的 PUT /pickup-dropoff-config。
  • 整批提交是原子操作:校验失败不会部分落库。

四、契约约束与正确调用方式

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

✅ 正确 / ❌ 错误 用法对照

场景 用法
✅ 获取可勾选残留日期 GET candidates 响应的 cells.filter(c => c.rescheduleResidue).map(c => c.serviceDate)
✅ 逐日清理(含过去日期,未取证是否放行) DELETE /admin/fleet/assignments/{cell.assignmentId}
✅ 整批清理(残留日期均未过去) POST /batch,dailyPlan[] 不包含要清理的日期
❌ 整批清理(残留日期已过去) 同上 payload → 400(code=100001)整批被拒,不会"跳过过去日期、只清未来的"
❌ 调用文档外的写口 POST .../slots/{slotId}/clear-residue-dates → 该路径不存在(#7067 已删除,无 slotId 概念)

切换清理方式的必要动作

前端在实现"整批清理"按钮时,遇到 code=100001 且消息含"已过去或已完结",应识别为"残留日期已过去,本接口无法清理",引导车务改走逐日 DELETE 路径(而不是当成普通校验失败重试原样提交)。

2026-09-26 订正(#8371):上表「✅ 逐日清理(含过去日期,未取证是否放行)」与本段「引导车务改走逐日 DELETE」在写作时均未经实测;实测结果是当时同样会被拦截(改期残留行过去日期 DELETE 命中 605027),引导车务改走逐日 DELETE 在当时并不能真正解决问题。#8371 已修复:残留行现在 DELETE 直接取消成功,完全跳过整组门禁,上表与本段的方向自此才成立。见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。


五、数据库行为

本次零改动;以下是既有行为,供前端理解结果落库口径:

清理方式 命中行为
DELETE /{assignmentId} 该派车行状态置 canceled,条件释放车辆/司机占用(sideEffects 回真实反算结果)
POST /batch(diff 自动取消未覆盖的现行行) assignmentMapper.cancelActiveRowsByIds(...) 批量置 canceled,cancel_source 显式留空(AssignmentService.java:2796-2802原文注释「#5924:方案重写不是取消动作,cancel_source不盖章(留NULL)」),取消原因固定写"逐日派车方案调整"

两条路径都不会物理删除行,只做状态流转;assignmentId 一旦取消不可复用为新方案的匹配键。

2026-09-26 订正(#8371):上表「状态置 canceled」是简化写法。测试服实测:取消前处于 assigned/holding 的行,落库状态是 exception(与通用取消 #6152 同口径),仅 unassigned 行取消才直接落 canceled。改期残留行走单行取消分支时同样遵循这条状态机,不因残留身份而特殊化。见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。


六、边界行为

  • 未登录 → 401(网关拦截)

  • assignmentId 不存在 → DELETE 返 605009

  • requirementId 缺失或需求上下文不可用 → candidates 的 canonicalSnapshot 为 null,不报错

  • 已出发/已完结的派单 → 605027(整组取消禁止)/「该日已完结不可改派」400(AssignmentService.java:2714)

    2026-09-26 订正(#8371):上条仅对非改期残留行成立。目标行判定为改期残留行时,605027/605047/605028 三道日期门全部豁免,按单行取消处理;order-v3 查询窗口失败时新增 605710 fail-closed(不取消任何行)。见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。

  • 老数据兼容:历史行无 assignmentGroupId 时,cells[].groupId 回退返回 assignmentId,对任何真实行恒非空


六.5、枚举 / 数据字典

used(AssignmentCandidateRespVO.SnapshotCellVO.used)

所属字段: data.canonicalSnapshot.cells[].used | 类型: String

值 中文 说明
USED 实际用车 该 cell 当天有实派车辆/司机
UNUSED 显式不用车 车务明确标记该日不配车
null 无切片行(未编辑) 该 group×日尚无任何逐日行

六.6、修改前后对比

本文档不涉及接口修改(三条端点均为"复用不改"),无字段级/行为级对比。

六.7、影响评估

  • 是否破坏向后兼容: 否(无任何接口变更)
  • 前端是否必须同步上线: 否(后端零改动,前端可按自己节奏切换实现方式)
  • 前端 workaround 清理点: 若前端此前已按"等待新写口/新字段"的假设搭了占位代码或临时禁用了"整批清理"按钮,可撤——改用本文档给出的两条现成端点与现成字段即可实现完整交互

七、不影响范围

  • 仅影响: 管理后台车务排车 Step2 弹窗「改期残留清理」交互的前端实现方式(用哪个端点、用哪个字段)
  • 零影响:
    • 后端代码(本次零改动)
    • 数据库 schema
    • 网关路由(三条端点均已在网关既有路由内)
    • POST /candidates、DELETE /{assignmentId}、POST /batch 的既有契约与行为
    • 其它车务/排车流程(本文档只梳理"改期残留清理"这一个场景)

八、测试环境已验证

本文档不改代码,本轮未做新的测试服 HTTP 实测;以下用源码引用替代实测取证,均为 origin/dev-v3(HEAD 10efbaddf,与本地工作树一致)实际存在的代码:

AssignmentCandidateRespVO.java:90        editableServiceDates 字段存在 ✓
AssignmentCandidateRespVO.java:108-150   SnapshotCellVO 含 rescheduleResidue/canDelete/assignmentId ✓
Step2CanonicalSnapshotService.java:218-227  残留日期并入 editableServiceDates 的逻辑 ✓
AssignmentController.java:75              类级路由 /admin/fleet/assignments ✓
AssignmentController.java:201-234         POST /batch 端点存在 ✓
AssignmentController.java:462-477         DELETE /{assignmentId} 端点存在 ✓
AssignmentControllerTest.java:938         cancel-residue 端点已随 #7067 删除的注释 ✓
AssignmentService.java:2743-2744,2775-2778  diff 自动取消对过去日期的硬门禁 ✓
AssignmentServiceTest.java:1727           窗外在途行由 diff 取消而非 605062 拦整批的用例 ✓
grep clear-residue-dates / clearResidueDates / cancel-residue → 全仓零命中 ✓

【需确认】未取证项:DELETE /{assignmentId} 对服务日期已过去的改期残留行是否放行取消(涉及 605028 与只读窗口判定),需要一次真实测试服调用才能确认,本文档不代为下结论。

2026-09-26 订正(#8371):已取证,不再是【需确认】。改前基线:确实拦截,命中 605027。改后(#8371 测试服全链路 6 步实测):改期残留行判定为真时,DELETE 直接放行取消,不命中 605027/605047/605028;判定失败(order-v3 不可达)时新码 605710 fail-closed。见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。


十、相关文档

  • 原始功能单及第一版说明:changelogs-v2/2026-08/16_5935_排车按日期清理改期残留-新增接口-管理后台.md(已在其 status_note 里自我标注端点已随 #7067 退役)
  • 退役背景:#7067 去槽位化重构(06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md)
  • 后续计划:若【需确认】项实测发现 DELETE 对过去日期同样拦截,需另开工单放开残留行的过去日期取消

2026-09-26 订正(#8371):该「另开工单」已完成,即 #8371——已实测坐实过去拦截、已修复并验证放行。见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。

关联 / 联系人

链接

  • Issue: #5935
  • PR: 无(零代码改动,未产生新 PR)

联系人

  • 后端负责人: @wx