Files
docs/海外仓业务/排车列表派送费用接口设计.md
T
2026-10-20 10:35:59 +08:00

621 lines
100 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 排车列表派送费用接口设计
> 2026-10-19 已按正式契约第八至十节解锁首批实施。本文保留设计依据,实际骨架与本轮实现交接见第10、11节;与旧准备措辞冲突时以正式契约及实际共享骨架为准。Java编译/单测由主后端统一执行,尚无本轮部署或真实验收结论。
业务唯一基线为[排车列表统一作业改造契约](排车列表统一作业改造契约.md)。本任务为 `01a1524f-56a9-7320-a5fd-8ff76fa4356b`,总控为 `01a15230-a9d6-7503-a8a9-d70691df49e5`;主流程后端为 `01a1524f-4f11-70c2-8a46-8e06e73a2d86`。本文件只提出 `delivery/`、必要 `financefee/` 和 `FleetBookingFreight.java` 的接口、数据及验证方案;共同 OpenAPI、权限、SQL 由总控集成,主流程类由主流程任务实现。
## 1 改造前核对与本批差异
下表路径相对 `baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/`,记录实施前的源码定位,不是当前实现或部署完成证明;旧单客户转换类/DTO已按第10节清除。
| 当前入口 | 当前行为与必须调整之处 |
| --- | --- |
| `delivery/service/DeliveryTransferService.platformTransfer/transferResolved` | 先锁全局排车,以 `DispatchOrder.customerId` 作为所有明细、出库和费用的客户;一个排车生成一个 `WAIT_OUTBOUND` 出库头,再按目的地生成派送单。当前未要求先确认装车完成,与新阶段不一致。 |
| 同类 `validateTransferFees`、费用发布循环 | 全请求按 `direction:feeType` 去重;普通费用只挂第一张或第一个相关目的地派送单,不能表达同车多客户多目的地分别收费。 |
| `delivery/mapper/OutboundOrderMapper` | `findByDispatch/lockByDispatch` 带客户且 `LIMIT 1`,可作为“每客户一张出库单”查询,不能充当新整车唯一出库查询。 |
| `delivery/service/DispatchCargoSourceQuery`、`DispatchFeeCargoMapper` | 普通整托按库存组成及本次已拣恢复来源,核平每条排车箱数;换标、退货使用专用事实,不能改回展示入仓码或计划托单一来源。 |
| `fleet/trip/FleetBookingFreight.preview` | 单客户参数;按真实入仓码找外接预订,检查 BOUND、车次/排车、目的地、实收托盘及数量、确认版本、约定币种和价格,返回报价及摘要。明确零约定只记商业事实,不发布零费用。 |
| `delivery/service/DeliveryFeeCoverageService` | 单客户查询 Finance;核对费用 ID/版本/hash/状态和货物 hash。引用服务仅为派送、装车、买约;全相关货物已覆盖才免新增应收,供应商应付独立。 |
| `delivery/service/DeliveryExecutionGuard` | 按一张客户出库核对派送覆盖、客户、数量和未执行状态;新整车应遍历完整客户出库集合并核对来源集合,不能只比较总箱数。 |
| `delivery/service/DeliveryDocumentService.save` | 显式 READ_COMMITTED,先锁本派送;首次签收资料保存要求真实 OUTBOUNDED,校验文件归属及版本/请求键,写 signedAt/signedPackages、资料版本、完成 Outbox、回访。存在真实退回时可签剩余部分。 |
| `delivery/service/SourceDeliveryCompletionQuery` | 调用方先锁自己的派送,再一次锁齐同客户全部相关来源订单(数字 ID 升序);历史派送/排车/异常/库存只读,不反向加锁。用于跨多次发运证明头程来源全签,不能用一趟车全签替代。 |
| `dispatch/fleet/DispatchFleetAdapter.completion`、`fleet/trip/FleetTripCompletionService` | 旧结案允许实签加真实退库等于实发;不是本批五阶段“全部正常签收”的判据。保留附加结案及资源释放能力,但不拿该结果自动映射本批派送完成。 |
| `delivery/service/DeliveryTransferService.cancelOutbound` | 按 dispatch→outbound→delivery 锁序;已拣/已发禁止取消,车次须先解除;恢复来源原状态、清空关联、增加版本;只申请作废未履行的有效派送费应收,其他费用保留。 |
| `delivery/controller/MerchantDeliveryController` | 客户列表/详情/签收资料严格取身份客户;当前另有客户 POST 创建接口,直接调用单客户转派送。该写入口与本批“后台安排整车派送”需要总控裁定收敛,不能悄悄开放整车操作。 |
已观察到 `DeliveryTransferService`、`DeliveryFeeCoverageService` 有 Service 直调 Mapper,`OverseasFeeBusinessGuard` 有 JDBC。实施触及的方法须按后端指引收敛为 Service→Repository→Mapper,并补中文方法/关键规则注释;本轮未整改,不扩展无关财务业务。
## 2 推荐单据结构与归属
### 2.1 整车、客户出库、目的地派送
推荐保留客户出库头模型:**一张整车排车 → 每客户一张出库单 → 每客户每目的地一张派送单**。不把出库头或费用的 customerId 写成平台 0,也不选第一个客户代替其他客户。
示例仅说明数量关系:同车 A/ONT8、A/LAX9、B/ONT8 三组货物,产生 A、B 两张出库单和三张派送单。同一目的仓的不同客户永不合单;每张费用的客户必须与其业务派送单一致。
- `ll_outbound_order` 保留真实 customer_id、dispatch_order_id/dispatch_no、仓及 `DISPATCH_DELIVERY` 类型。冻结提案为 `UNIQUE(dispatch_order_id,customer_id)`,约束语义与现有反序唯一键相同,不同时新增冗余反序索引;整车出库集合的 customerId 必须精确等于货物客户集合,每客户恰好一张,仓及排车引用一致。所有整车读取改为集合查询,不使用第一张出库单代表全车。
- `ll_delivery_order` 保留真实 customer_id、所属客户出库、整车排车、目的地、地址及联系信息。保留 `UNIQUE(customer_id,outbound_order_id,delivery_group_key)`;每个“客户+目的地”恰好一张,客户与所属出库相同,整车派送组集合与货物组集合精确相等。取消历史不删除、不靠清空关联绕唯一键;本批没有取消后新建第二次安排的协议。
- 明细必须保存原排车明细标识及不可变实际来源关联;出库及派送都能由该关联还原每个客户、来源订单、来源入仓、实际箱数。不得仅从 `ll_order.outbound_order_no/delivery_order_no` 当前指针还原历史。
- `deliveryGroupKey` 延续既有目的地键:FBA `CODE:<大写仓码>`,私人地址 `PRIVATE:<trim地址>:<trim邮编>`;仅有内部目的仓 ID 的情况需由主流程提供真实目的地,缺失阻断,页面不能展示 ID 回退。避免同时存在旧 CODE 和新 FBA 两套键;外接 `destinationKey` 与派送键只做显式、可测试的领域映射。
- 对外预览的 `groupKey` 是客户与上述目的地的稳定摘要,包含 customerId;请求不能靠换 customerId 或 destinationKey 把一组货物移给另一客户。服务端完整重算分组,组集合必须与实际货物精确相等。
- 物理托盘数按真实仓托盘 ID 去重;逐客户、逐目的地货物行不可当整车物理托数直接相加。实际重量/体积沿主流程确认值,不拿计费材积重补实测值。
- 安排时只映射已完成的真实 PDA 拣货事实,不生成第二套 PDA 出库任务。普通 `DispatchPdaWorkflowService.pick` 已在扫码事务调用 `inventory.allocate/pick/ship`;退货换标由专用库存入口完成实际扣减。安排、装车完成和实际发车均不得再次调用这些已完成数量的库存 `ship` 或其他扣减命令。
- 本稿的 **picked→shipped 仅指 `ll_order` 业务履约计数及单据快照转换**:`DispatchPickOrderAttributionService.depart` 锁来源订单,再调用 `LastlegOrderMapper.transferPickedToShipped` 将订单 picked_package_count 减少、shipped_package_count 增加、设置出库关联;它不是库存余额/仓位变更。发车仅对真实归属拣货执行一次该业务更新、更新客户出库/派送和整车阶段,全部客户同事务,任一失败全车回滚;扫码已经提交的库存事实保持,不因失败重做或伪造退库。
- 出库明细已拣计数只来自对应客户、明细及来源的真实拣货,不能直接把计划箱数填成已拣,也不可表单补录。发车更新此类快照不增加任何第二笔库存流水。
### 2.2 整车安排记录(提交总控的结构提案)
已落地delivery域 `ll_dispatch_delivery_arrangement` 的Entity/Mapper/Repository:唯一 `dispatch_order_id`,保存整车依据、business_hash、首次请求键及操作者、完整结果与来源JSON;具体列见第11节SQL提案。严格commandHash及每次请求结果使用主流程全局唯一键共同账本,不在安排表复制第二套命令协议。
结果引用各客户出库和各组派送;出库头不新增冗余arrangement_id。出库明细保存dispatch_item_id及真实source_attribution_id、source_order_id、source_inventory_position_id、source_return_receipt_id、source_return_carton_id,避免混托/退回消费者仅凭展示入仓码猜来源。完整结果和cargo_json用于整车精确集合核对。不得仅以“有任意出库单”表示安排完成。
必须有同一排车唯一安排、全局请求键共同账本和子单唯一约束三层防重复。正式契约已选定全局唯一idempotencyKey,不按客户/操作者分片;安排表original_command_key也唯一。键对应原主体、动作和排车不可更改,复用别人的键拒绝;新键同业务经当前权限及版本核验只引用原事实。实际调用DispatchExecutionPort.replay/recordArrangement,不在delivery自建主体校验账本。
## 3 待冻结 API 与 DTO
下列均为拟议契约。统一平台前缀 `/api/platform/v1/last-leg`,复用 Gateway 已有 Lastleg 路由;本轮未改 Controller/OpenAPI/权限。
| 方法与路径 | 请求/返回及权限建议 |
| --- | --- |
| GET `/dispatches/{dispatchNo}/delivery-arrangement` | 新增组合预览及业务事实恢复入口,`dispatch:list`。返回阶段、recordVersion、装车完成事实、canArrange/blockReason、cargoHash、groups、各组外接报价/原费候选/应填服务、已存在的安排结果。一次 GET 内共享同一 Lastleg 只读快照,前端只调用这一次,不再并发两个辅助 GET 拼不同版本;恢复不依赖客户端重组结果。严格命令原请求/原结果另受命令主体授权,不因 list 权限开放别人的原请求。 |
| POST `/dispatches/{dispatchNo}/transfer-delivery` | 复用路径,替换旧单客户 DTO 为整车安排请求,独立 `dispatch:transfer-delivery` 权限。只允许平台且装车后、未发车;不接收顶层 customerId、货物数量或车辆司机覆盖值。 |
| GET `/dispatches/{dispatchNo}/external-freight` | 现有只读辅助入口按客户和目的地分组返回,报价增加真实 customerId/groupKey。与组合预览共用同一查询,不维护第二种算法。 |
| GET `/dispatches/{dispatchNo}/fee-coverage` | 现有只读辅助入口按客户和目的地返回货物/原费用候选;保持平台权限,不能将整车 Finance 费用候选暴露给客户。 |
| GET `/delivery-orders`、`/delivery-orders/{deliveryNo}`及资料接口 | 平台沿现有权限与客户定位,客户沿身份客户过滤;新结构不改变单张派送资料的归属。客户只读响应不带其他客户出库、派送或原费用快照。 |
拟议记录(名字和 JSON 字段供总控冻结,整数 ID 对外按既有 Long 字符串约定):
```text
ArrangeDeliveryRequest {
idempotencyKey, recordVersion, cargoHash, loadingCompletionHash,
groups: [ {
groupKey, groupCargoHash,
plannedDeliveryAt, deliveryMethod, waybillNo, carrierName, pickupMethod,
loadingType, vehicleType, vehicleSize, appointmentAccount, sellerName, remark,
externalFreightHash, feeCoverageCargoHash,
feeCoverage: [{ financeFeeId, sourceVersion, sourceHash, services }],
fees: [ FeeItem ]
} ]
}
ArrangementResult {
dispatchNo, recordVersion, arrangementKey, cargoHash, arrangedAt,
outbounds: [{ customerId, customerName, outboundNo, status, packageCount, palletCount }],
deliveries: [{ groupKey, customerId, customerName, outboundNo, deliveryNo,
destinationWarehouseCode, destinationWarehouseName,
destinationType, deliveryAddress, status, packageCount, palletCount }],
}
CommandResult { dispatchNo, action, idempotencyKey, recordVersion, status, stage,
occurredAt, effect, counts, result: ArrangementResult, replayed, alreadyArranged }
```
- `FeeItem` 复用现有费用字段:类型、名称、方向、币种、金额、数量/单位/单价、期间、供应商、结算方式、直接已付信息及凭证;按组确认,不再把整车总金额默认挂第一客户或第一目的地。每组 `(direction,feeType)` 唯一,整车不同组可重复同类型。
- 对外安排请求只用 `recordVersion`,0 是合法首版;无 `expectedVersion` 别名。内部锁/CAS 参数如叫 expectedVersion 仅表示预期含义,不进入公开 JSON。派送资料等本批实际触及 DTO 的版本命名也由总控统一收敛,不把当前源码字段当作新契约别名。
- 车辆司机只从主流程已确认快照复制(包括真实临时外配信息和空主档关联),安排请求无权改派。`carrierName` 等派送字段不得反向修改整车资源。
- 返回所有出库和派送单,不保留单个 `outboundNo` 作为多客户响应别名;Admin、恢复逻辑及脚本同批更新。`replayed` 仅表示原严格命令重放;`alreadyArranged` 表示查到既有整车安排而未再次建单,两种情形按第5节区分。不可变动作结果中的 recordVersion 是原动作版本;组合 GET 的顶层 recordVersion/stage 才是当前事实,前端不能用旧结果覆盖当前详情。缺失业务编号显示“—”,内部 arrangement ID 仅用于技术关联。
- 既有费用更正继续走正式来源更正,不通过换请求键重新安排、重发外接报价或重建单据。
- 用户已确认本类整车派送仅平台安排。本轮已删除Merchant创建POST及merchantTransfer服务入口,保留客户列表、详情、签收资料及下载。Client直接消费者由前端任务删除,共同权限/OpenAPI与脚本由总控集成,不保留另一套客户部分安排协议。
组合 GET 拟在一层 `REPEATABLE_READ, readOnly=true` 事务里一次读取整车上下文、全部客户货物、子单、费用本地快照,再按该上下文生成分组与本地摘要;内部查询必须加入同一事务,不各开快照。Finance 远程候选无法参与 Lastleg 数据库快照,按客户带费用版本/hash及读取时间返回,POST重新确认,不声称跨服务同刻快照。组内缺来源/报价变化/候选不可用分别返回结构化 `blockReasons`、`sourceComplete`、`feeCoverageStatus`,不把失败转成已覆盖;需要该证据的确认必须阻断。前端不能在同一弹框重复辅助 GET 另拼资格。
## 4 来源、报价与费用校验
### 4.1 实际货物摘要
主流程提供不可变、已核平来源行:`customerId, dispatchItemId, sourceStage, orderId, sourceRegion, sourceInboundId, inboundCode, warehouseId, warehousePalletId, palletNo, destinationKey, packages, pickId/attributionId, inventoryPositionId, receiptId/returnReceiptId`,以及退货/换标所需真实箱和数量引用。不能因为某类没有头程来源 ID 就伪造一个。
排车明细箱数、真实拣货箱数、来源归属箱数按“客户+排车明细”逐条相等,明细集合也相等;同托多来源全部保留。排除展示 `MIN(inboundCode)`、订单声明 SKU 箱数、FIFO 猜来源。装车后费用范围以本趟冻结拣货为准,不能把该托后来新进的可用库存算入本次派送。
`cargoHash` 应按稳定字段、明确 null 语义及固定排序计算;排序至少按 customerId/dispatchItemId/attributionId。分组 hash 包含客户、目的地、实际来源及数量;装车 hash/事实版本单独保留。规范化仅对契约声明无序的组、费用及选择集合排序、金额固定精度序列化;不能把有业务意义的顺序或原载荷值丢掉。
两类摘要职责严格分开:
- **commandHash**:动作 ARRANGE_DELIVERY、排车号、可信原请求主体(userId/appCode/客户范围/PDA绑定仓如适用)、原始 recordVersion、完整规范化请求载荷(含所有业务与证据字段)。请求键作为独立唯一键;除该键外不删除 recordVersion、操作者或请求字段。角色/权限每次读取实时验证,不把旧角色 hash 当当前授权。原键要求主体、动作、单号、版本、载荷全部相同。
- **arrangementBusinessHash**:排车号+货物/装车依据+全部客户目的地派送信息+费用内容和引用证据,移除请求键、操作者及纯并发 recordVersion。仅用于同车已有安排与另一请求键的业务等价判定及防重复;不能用它验证原命令重放、越权主体或旧版本。
请求 DTO 不接受客户端自报上述两个可信摘要;由服务端计算。前端保留原载荷、原 recordVersion 和键,未知结果不自动改版本再重试。
### 4.2 外接实收报价
- `FleetBookingFreight` 以真实客户分片接收来源和绑定上下文;Quote 增加 customerId/groupKey,按 `(customerId,inboundCode)` 识别,不能用全车 inboundCode 集合跨客户免收费。
- 保留 BOUND、排车及车次身份、预订 recordVersion、实收记录、完整托盘数量、目的地、PALLET/整单计价和 CNY/USD 校验。预览 hash 包含客户及主流程货物 hash;确认前在整车父锁协议内重验,报价/实收变化一律冲突,不补造库存。
- 正数约定只发到同客户同目的地派送,费用键基于真实 bookingNo+本趟排车+组+FREIGHT,稳定且与操作者无关;现有费用命令即使包含 operatorId,整车安排唯一事实也必须阻止换人重复发布。
- 明确零约定保存确认操作和来源版本,不发布零费用。全部为外接已约定来源的组禁止另填派送应收;混托普通来源仍按该组未覆盖的真实普通货物收费,不能整托免单。
- `priorInboundCodes` 按客户查询原有效派送应收;未确认作废前保持阻断。申请撤销、超时、拒绝均不能当作已作废再次收费。
### 4.3 原费用覆盖与新增收费
- Finance 仍按既有内部批量只读契约,每客户查询真实头程 sourceInboundId;不跨库、不扩大 Finance 账务模型。只有请求确实选择原费覆盖时才强制 Finance 可用,不能在失败后静默认定已覆盖;未选择覆盖的预览也应明确候选加载失败。
- 确认复核 financeFeeId、customerId、sourceInboundId、sourceVersion、sourceHash、原费类型/状态/币种及实际货物摘要;跨客户费用或过期证据直接拒绝。该协议是确认时有效快照,之后财务更正不追溯改写发运,不声称拥有跨服务数据库原子锁。
- 覆盖选择在客户内按 `(sourceInboundId,service)` 去重;同一真实原费覆盖的来源分到多个目的地时允许分别引用证据,不能据此复制原费用。每组计算未覆盖来源,不能用“全车 coveredServices”免除另一客户或目的地应收。
- 原费只证明明确服务覆盖,不复制费用、不按原金额抵扣、不生成伪零费用。新增派送应收仅针对未覆盖普通来源;散装/混装装车费、买约费沿现有规则逐组核对,买约仍要求卖家。
- 供应商应付独立,不能因客户原费覆盖而免掉。金额必须正数,费用币种 CNY/USD,已付凭证继续按真实客户/来源校验,不伪造支付。
- 每笔费用 `businessType=DELIVERY_ORDER`、businessId/businessNo 为本组派送;customerId 与该派送相同。只有来源唯一时附 orderId/inboundCode/集装箱引用;多来源不借第一票展示码冒认归属,由派送及冻结来源明细证明范围。
- 费用来源、自动审核、不可变 Outbox 与安排单据处于同一 Lastleg 事务;Finance 入池经现有投递、幂等和 ACK。安排成功只代表本地费用与 Outbox 已保存,不代表 Finance 已到账、已收款或已结算。
## 5 事务、幂等与锁顺序
### 5.1 安排与发车
采用显式READ_COMMITTED写事务;正式契约第九节已选定 **trip→车辆→司机/资源→dispatch**,取代准备稿的相反序列。先非锁定定位父身份,锁后重验一对一关系;delivery只调用实际DispatchExecutionPort.lockForArrangement,不自行取得或重排父/资源锁。安排与发车先收齐全部客户出库/派送锁,再来源/费用;尚未完成联合并发验证,不能宣称全域无死锁。
严格命令与业务重复的处理:
| 场景 | 授权与结果 |
| --- | --- |
| 同键、同主体、同原版本/完整载荷 | 每次先核验当前平台写权限及合法范围;commandHash一致才返回该命令原不可变结果,`replayed=true`。命令恢复在首次写入版本/阶段检查前,允许原命令后阶段已变化;不再创建单据、递增版本或发布费用。 |
| 同键、不同动作/单号/原版本/载荷 | 409,不能拿相同 businessHash 豁免严格冲突。 |
| 同键、不同操作者/主体 | 403或不泄露原内容的冲突,禁止读取或借用原请求。其他操作者即使有安排权限,也不能重放别人的命令;正式审计读取如有既有独立权限只读原证据,不获得执行身份。 |
| 新键、同车同业务、同或不同操作者 | 当前主体须具有安排写权限,recordVersion须为当前版本;锁内比 arrangementBusinessHash。既有安排完整有效则只引用原事实,`alreadyArranged=true,replayed=false`,在共同命令账本保存本主体此次“引用既有安排”不可变结果,不增加主版本、不产生新费用。以后重放此新键仍按其自身 commandHash。 |
| 新键、同车不同业务或旧版本 | 409,费用调整另走正式流程,不能第二次建单。新键使用旧版本即使业务相同也先冲突,不自动刷新版本;用户读取当前事实后才形成明确的新请求。 |
| 原安排取消/失效/缺子单 | 严格原键仍可取原不可变动作证据,当前状态通过GET另查;新键不得把它报作有效已安排,也不补建残缺集合。GET返回实际状态/缺项并阻断新写,等待正式业务处理。 |
安排首次写入顺序:当前身份授权→父锁→严格命令恢复/全局键归属→recordVersion与装车后资格→核对全车来源快照/分组→锁或创建**全部**客户出库及派送→锁齐本次需要更新的来源订单/换标/转运→来源关联→逐组费用/Outbox→主流程 `finishArrangement`→完整命令结果。不能每组“锁来源、写费用后再去锁下一组派送”;全部子行先于任何来源写锁。新建子单此时未提交不可能合法签收,已有安排在恢复分支返回,不对同一事实重复执行此链。
发车顺序:父锁→命令恢复/当前版本→资源/审核/PDA核平及预约批次复验→锁齐完整客户出库/派送并核对来源集合→`DispatchDeliveryPort.markDeparted` 写已锁子单→主流程 `DispatchPickOrderAttributionService.depart` 按真实来源逐单转换**订单履约计数**及换标/转运/Fleet履约事实→整车阶段/唯一历史/命令结果。后段不得再获取一张此前未锁定的派送行;安排、装车、发车均不执行普通扫码已经完成的库存 ship。
所有客户、所有目的地、全部来源绑定、费用来源/Outbox和主单CAS均在一个 Lastleg 事务内;任意一笔失败全体回滚,不能逐客户 REQUIRES_NEW 提交。数据库唯一键竞争用当前读确认 commandHash/业务hash及完整数组结果,不能依赖旧快照或吞掉部分写失败。未知结果保留原键,业务组合 GET 可查既有单据,原命令查询/POST重放才证明该次操作归属;404或查到相似单据不能当原命令成功。
列表和组合预览批量查询,不逐行请求详情/附件;资格只是展示,写入必须重验。不得在锁内投递 Finance HTTP 写命令。Finance 原费只读确认如放在锁内必须有现有超时并失败回滚;可先预检,再在锁内做最终只读复验,不把锁前结果冒充确认结果。
### 5.2 本轮沿代码核对的实际锁点与拟收敛执行链
| 入口及当前实际锁点 | 本批精确收敛要求/唯一写者 |
| --- | --- |
| `FleetTripExecutionService.depart` 当前先trip,后车辆及资源,再 `DispatchFleetAdapter.manifest` 的 dispatch→outbound→task→delivery;随后子发车 | 正式共同根锁保持trip→车辆→司机/资源→dispatch,预约批次按batchKey固定顺序,完整客户子单先于来源锁。主流程所有入口须复用同序列,不能回退准备稿trip→dispatch→资源。 |
| `DispatchPdaWorkflowService.pick` 当前 dispatch→task→`requirePickingApproved`内binding;`preparePicking`再锁来源订单数字ID升序及退回实收记录,库存内部 `locked()` 为position→balance;退货换标先 `prepareNativeReturn` 来源订单再锁箱 | 主流程共同根锁为trip→车辆→司机/资源→dispatch;随后task/binding→来源订单/实收→position→balance(或退货专用箱锁)。扫码不申请出库/派送行锁,结束前不进入派送回调。保留库存内部顺序和真实扣减;不能改成balance→position。 |
| 旧 `DeliveryTransferService.platformTransfer` 为dispatch→已有outbound→binding,按目的地循环创建再绑定来源;新Controller已改接DispatchDeliveryService | 新服务只通过主流程父端口取得trip→车辆→司机/资源→dispatch,再完成全部子出库/派送创建或锁定,最后才统一锁来源/绑定/费用。普通UPDATE也产生行锁,不能仅统计显式FOR UPDATE。旧内部实现与跨域旧测试的最终清理由后续联合收口列明。 |
| `DispatchPdaWorkflowService.depart` 当前 dispatch→binding→outbound→`DeliveryExecutionGuard.requireDeparture`锁派送→task→`DispatchPickOrderAttributionService.depart`锁来源订单 | 主流程与本任务协作:父级→task/只读核平→全部客户outbound→全部delivery→来源。`markDeparted`先于来源更新,来源更新只触及已锁的单据集合;不重新请求其他派送锁。 |
| `DeliveryTransferService.cancelOutbound` 当前先dispatch/相关relabel,后outbound→delivery,之后费用来源、来源恢复 | 有整车关联的合法未执行取消,先非锁定定位,再父级/必要批次→全部目标outbound/delivery→来源/相关relabel→费用及历史;已拣直接拒绝。其他独立出库类型维持其已确认协议,不伪造trip。父级改动归主流程,取消实现归本任务,跨relabel/转运的真实锁点实施时定向复核。 |
| `CustomerVisitService.updateDeliveryStatus` 当前 outbound→delivery,不申请dispatch;`DeliveryDocumentService.save` 当前delivery→`SourceDeliveryCompletionQuery.proveAll`的来源订单 | 普通状态变更若与发车竞争需先取得共同父级再outbound→delivery,由总控分配该类写权。签收资料保持delivery→来源,不拿outbound写锁、不反向父锁;仅提交后持久提醒。 |
| `FleetPoolOrderBridge.signed/departed` 在来源处理后 `links.lockDispatch`,再由 `move` 锁本土卡车/散板商业单;不申请父trip | 这两条既有直接副作用保留来源→pool link→商业单顺序,不在其后回取派送/父锁;全部相关来源应在进入商业事实更新前收齐锁。主流程/Fleet商业域写权不足的触点交总控,不能以“只写历史”忽略商业单真实行锁。 |
约束是分命令的有向次序,不是把所有领域拼成一条虚构的全局序列:**扫码为父级→task/binding→来源→库存,且不再取派送锁;安排/发车/相关取消为父级→完整子出库/派送→来源→费用;签收为本派送→来源→本地提醒,始终不取父锁。** 来源订单对多客户使用统一数字ID升序(ID全局唯一),同一客户下也保持现有数字顺序;子单按customerId数字值、业务ID升序,明细/来源集合一次收齐,不边持后序锁边发现新前序行。
预约批次/资源提供者、换标和转运的直接调用是否会回头取父/派送锁仍属实施审查项,触点未分配不得改;若发现反向锁必须由总控调整唯一入口或分配写权后再实施,不能以“整车已串行”忽略跨历史来源并发。现阶段只是完成上述代码锁点核对,未运行并发测试或证明全域无死锁。
### 5.3 取消、修改和财务竞争
- 本批安排发生在真实拣货并装车完成后,既有“已拣禁止直接取消出库”仍成立;不能为新流程放开取消或伪造退库。装车后取消/退装的新增业务流程尚未授权,不提供快捷撤销接口。
- 既有其他尚未执行出库类型的取消保留其业务限制;有整车父关联时按第5.2节父级→出库/派送→来源取得锁,禁止子单自行解除全车。物流取消不释放另一客户占用或自动恢复实发库存。
- 来源原状态只从真实本次转换历史恢复,清空关联和版本规则保留;混托来源各自核对。费用只申请作废本派送当前有效 DELIVERY_FEE 应收,装车费、买约费、供应商应付及其他来源不自动作废。
- 费用调整/撤销保留现有 READ_COMMITTED、来源行锁和未决撤销检查;Finance APPLIED/DUPLICATE 且 hash 匹配才确认 VOIDED。若调整与整车证据变化会影响发车,主流程须按安排事实失效规则阻断,不能静默重写原安排费用。
- 改货物、目的地、关键资源后如何使审核/装车/安排失效由主流程提供单一规则;delivery 不解锁修改、不隐式删除派送。冻结时需说明是否在存在安排事实后直接拒绝这类修改,或如何经过明确重审动作;不得保留“修改了车、货但派送仍旧可发”的窗口。
## 6 正常签收汇总与异常边界
`NormalDeliveryFact` 建议包含:`customerId, dispatchNo, arrangementKey, outboundNo, deliveryNo, groupKey, deliveryVersion, originalPackages, signedPackages, signedAt, outboundCompleted, returnedPackages, activeReturnCount, status`。来源为真实出库、派送签收字段、资料修订及退回事实;不以附件数、状态文字或最后更新时间单独判定。
整车正常完成必须同时满足:已真实发车;安排组集合和实际派送集合精确相等;全部客户出库已 OUTBOUNDED;每张原实发大于零;每张具有真实 signedAt 且 signedPackages 等于本张原实发;本趟没有以退回替代正常签收的数量及未处理异常;签收时间不早于实际发车且不晚于当前时间。完成时间取本趟最后实际签收时间,空集合/部分签收绝不完成。
- `DeliveryDocumentService.save` 保留 READ_COMMITTED、本派送锁、版本、请求 hash、文件客户+业务+类别归属;首次签收仍先验证实际出库和活动异常。BOL/装车资料可提前准备,签收资料不能提前伪造完成。
- 一张单发生拒收、RETURN_PENDING、真实退回,或退回后仅签剩余部分,本趟仍不能汇总“派送完成”。既有异常单及真实退库能力保留,本批不新建异常闭环,也不把线下处理意见当作全量正常签收。
- 已经全量签收后再另发起退货,保留原运输不可变签收事实,不重开已正常完成的本趟;核对退回事实是否发生在本次全签之前,不能只看当前状态或任何历史退货存在就否认原运输完成。
- 整车正常完成不代替 `SourceDeliveryCompletionQuery` 跨全部历史发运的头程来源证明;跨次拆发、实退再派必须沿真实来源及流水证明,防止提前发布头程整来源完成事件。
- 现有 `FleetPoolOrderBridge`、完成 Outbox 和回访只消费各客户真实签收;不因整车汇总二次发布费用或重复扣库存。车辆/司机释放仍是独立事实,不随五阶段完成自动释放。
### 汇总调用建议:提交后重读,避免反向锁
当前签收锁序为本派送→全部相关来源订单。**不要在该事务持有派送或来源锁时回调申请父车次/排车锁。** 采纳主流程稿第5/6节持久 `DeliveryFactsChanged` 方向,具体持久化边界提案如下,等待总控冻结表名/调度入口,不留“靠内存通知”分支。
| 环节 | 具体提案及唯一写者 |
| --- | --- |
| 记录结构 | delivery域新增 `ll_delivery_facts_changed`,字段 `id,customer_id,dispatch_id,dispatch_no,delivery_id,delivery_no,delivery_version,facts_hash,occurred_at,state,attempt_count,next_attempt_at,last_error_code,processed_at`;状态仅PENDING/PROCESSED,唯一 `(customer_id,delivery_id,delivery_version)`。只作同Lastleg本地待核对事实,不新增跨服务事件类型;DDL交总控集成。 |
| 事实写入 | 本任务在 `DeliveryDocumentService.save` 正常签收事实落库、来源证明/完成Outbox/回访成功的**同一事务**插入PENDING;回滚则提醒也不存在。原资料命令重放在写入前返回,无第二条提醒。未来其他合法入口改变正常完成依据,也须同事务写提醒,由总控分配其文件写者,不让本任务越权修改异常/回访。 |
| 数据访问 | 本任务独占 delivery内Entity、Repository、Mapper及直接持久化测试;通过共同端口提供 `pendingCandidates(limit,now)` 普通读、`ackProcessed(ids)`、`recordFailure(id,code,nextAt)`,不从签收线程调用主流程Service。消费端不直调delivery Mapper。 |
| 消费与汇总 | 主流程唯一维护 dispatch内有界任务/调度入口(拟 `DispatchDeliveryCompletionJob`)及 `DispatchCompletionPort.reconcile`。先普通读已提交PENDING候选;每趟另起READ_COMMITTED事务,trip→dispatch锁,调用 `DispatchDeliveryPort.normalDeliveryFacts` 批量普通读全部已提交事实,再CAS五阶段及唯一完成依据,最后经delivery端口确认本次已核对的候选。整个事务不锁历史来源、不执行库存/费用/回访完成副作用。 |
| 领取和确认顺序 | 不先锁/持有提醒行再申请父锁。候选扫描不使用FOR UPDATE、也不在父事务外预持PROCESSING锁;两个执行器可看见同一候选,由父锁/CAS与幂等汇总串行。提醒确认置PROCESSED与主单汇总处于同一事务,顺序为父→普通读事实→主单写→提醒行;不能先标处理完再更新阶段。未全签但已核对完的候选也可确认,后续客户签收会产生自己的新提醒。 |
| 失败和崩溃恢复 | 主流程负责重试、退避/批量上限及有界重扫已发车未正常完成整车;本任务提供提醒持久化操作。汇总或确认失败整体回滚、PENDING保留;错误次数/下次时间只能在回滚后独立短事务记录,不持提醒锁回头申请父/来源锁。进程提交前退出仍为PENDING,签收提交后通知丢失靠持久扫描恢复,重复消费没有二次阶段/历史副作用。无需新增长期调度平台或依赖内存afterCommit;如用afterCommit只作唤醒优化。 |
| 测试边界 | 本任务写“签收与提醒原子、签收重放无重复、提醒Repository及状态操作”测试;主流程写“两个最后签收、重复消费、消费中断/重试、确认回滚、未完成整车有界重扫、父锁汇总不反向子锁”测试并统一运行Maven。跨两包集成fixture由总控指定唯一写者。 |
`facts_hash` 仅证明触发时的派送事实版本;消费必须重新读取全部客户,不将消息中的allSigned结论直接落主单。只确认已扫描且确实核对过的事件ID,不能批量吞掉扫描后新产生的提醒。组合GET可返回已提交当前阶段,签收提交到汇总提交之间允许短暂保持派送中,前端不直接写成完成;故障可观测字段若需公开由总控另冻最小schema。
主流程汇总只普通读客户出库/派送,不调用会锁来源订单的 `SourceDeliveryCompletionQuery`。来源全签证明继续在签收自己的事务完成,整车汇总不重复发布原来源事件。以上是有明确写者的提案,尚未写记录、调度器或运行恢复测试。
## 7 实际主流程端口与共同实现核对
本次差异修订选用主流程稿第5节的一套 `dispatch/port/` 契约名作为冻结提案;该包仅放DTO/接口,不依赖任一实现/Entity,delivery实现派送端口、消费主流程执行端口,不再并建初稿的ContextPort/ArrangementQuery/Departure/NormalFacts四套同义接口。主流程类不得注入派送具体实现反向调用其公开Controller;依赖分析仍待实施验证,包名最终由总控冻结。以下不是已存在代码。
| 方向 | 拟议端口 | 必须保证 |
| --- | --- | --- |
| delivery→主流程 | 实际 `DispatchExecutionPort.preview(actor,dispatchNo)`,来源在Context.cargo | 加入组合GET同一只读事务;返回全车/逐客户已核平来源、仓、recordVersion、阶段、装车事实及车辆司机快照,真实临时外配无需伪造档案。cargo使用唯一权威来源,不能两域各推一份数量。 |
| delivery→主流程 | 实际 `DispatchExecutionPort.lockForArrangement(actor,dispatchNo)` | MANDATORY,trip→车辆→司机/资源→dispatch;先只锁及复验身份/关联供严格命令恢复,新命令才验证recordVersion和阶段,不能在恢复前拒绝旧版本。这里只取得父/资格事实,不先锁来源后让delivery反向锁子单。 |
| delivery→主流程 | 实际 `DispatchExecutionPort.replay(context,actor,identity)`、`requireFreshArrangement(context,recordVersion)`、`recordArrangement(context,actor,identity,fact,alreadyArranged)` | 严格命令恢复先于新版本阶段校验;首次全部客户成功才CAS/历史及安排引用,阶段仍装车后;不写派送表。新键同业务只复用事实,不增版本或重复收费。 |
| 主流程→delivery | `DispatchDeliveryPort.departureFacts(locked,lock)` | 全部客户出库/派送/费用确认事实及精确来源集合;lock=true仅在父锁写事务中,按第5.2节一次锁齐子单,之后才能请求来源写锁。 |
| 主流程→delivery | 实际 `DispatchDeliveryPort.markDeparted(context,actor,departedAt,commandKey)` | MANDATORY,更新已锁全部客户出库/派送执行快照,返回逐客户实发业务事实;不调用库存ship、不转换ll_order计数、不更新整车阶段。主流程随后唯一调用订单归属发车更新,任一失败所有客户同事务回滚。 |
| delivery→持久记录→主流程 | `DeliveryFactsChanged(dispatchNo,deliveryNo,deliveryVersion)`、提醒数据访问端口 | delivery保存提醒及Repository实现;主流程扫描/消费/重试,经端口确认;明细及ack边界见第6节,无签收线程同步reconcile。 |
| 主流程→delivery | 实际 `DispatchDeliveryPort.normalDeliveryFacts(context)` | 批量普通读所有本趟客户出库/派送、正常签收/异常/缺项,返回expectedDeliveryNos、数量、allNormallySigned、factsHash及最后正常签收时间;空集合为false,不锁来源,不分页漏项。 |
| 主流程消费入口 | 实际 `DispatchCompletionPort.reconcile(signal)` | 主流程新事务父锁后读取已提交正常签收事实,幂等CAS阶段和完成时间/依据;最后经DispatchSignalPort确认已核对提醒,不能回原签收事务执行。 |
| 主流程→FleetBookingFreight | 权威整车绑定/客户来源上下文 | 逐客户真实派送来源+统一父身份、预订/实收版本;`FleetBookingFreight` 不在子锁后反向申请父锁。其他 fleet 文件改动归主流程。 |
共同实现核对项:
1. 一对一fleet父行及trip→车辆→司机/资源→dispatch已由正式契约确定;公开dispatchNo、recordVersion及五阶段以实际DispatchContracts/DispatchTypes为准,主流程来源由共享Context.cargo提供。
2. 逐客户 `dispatchItemId`、实际来源/拣货/退货/换标事实字段、目的地规范与组键;尤其父排车头平台范围时,`DeliverySourceEvidenceMapper` 当前 `ds.customer_id=d.customer_id` 的联查必须同步调整为权威整车关联+明细客户隔离,不能直接删客户校验。
3. 装车完成事实/hash、严格commandHash与arrangementBusinessHash、全主体安排键注册与共同命令账本的唯一约束;原键恢复先于新版本/阶段判断,新键复用须当前recordVersion和当前写权限。旧严格命令跨主体不可重放。
4. 具体采用本稿每客户一出库及每客户目的地一派送、全数组响应与组合GET单一快照的提案;缺项/加载失败schema及HTTP 409/403/404;单客户DTO/单出库消费者同批替换。
5. delivery只更新子出库/派送;主流程唯一更新订单履约计数和整车阶段;扫码已完成的普通库存ship不再调用。全部客户发车原子回滚,预约批次/工作事项及外接BOUND守卫保留。
6. 第6节持久提醒表/端口及主流程任务类名称、重试/重扫上限和唯一写者;第5.2节所有父/子/来源锁序,预约/换标/转运附加锁点还需对应写者审查;旧fleet结案只作为附加事实保留。
7. 客户旧创建入口已获批准移除;Client由前端任务、脚本/权限/OpenAPI由总控集成。临时外配证据、实载弹框和零扫码退回重审已按第八节确认,不再待用户重复裁定。日历新增预约按第十节排除,不新增其设计、API或矩阵。
已读取主流程实际dispatch/port骨架并接入DispatchExecutionPort、DispatchDeliveryPort、DispatchSignalPort;它们覆盖早期提案的签名。主根锁、全局键及客户创建去向已由正式契约冻结;剩余为真实实现及联合验证,不能沿用准备稿相反序列或另造DTO。
## 8 直接生产者、消费者和文件写权
| 文件/范围 | 本批关系及归属 |
| --- | --- |
| Lastleg `delivery/controller/DeliveryTransferController.java`、`PlatformDeliveryFeeCoverageController.java`、`MerchantDeliveryController.java`、DTO/Entity/Repository/Mapper/Service及本域测试 | 本轮已按解锁范围实施,实际差异见第10节;其他独立出库业务保留。 |
| `fleet/trip/FleetBookingFreight.java`、`FleetBookingFreightTest` | 本任务独占;其引用的 FleetBookingMapper/Reservations/DispatchGateway 变化由主流程提供端口,不能顺手修改其他 fleet 文件。 |
| `financefee/service/OverseasFeeSourceService.java`、`OverseasFeeBusinessGuard.java`、相关 Repository/Mapper/直接测试 | 仅发生真实归属/幂等或必须分层整改时修改;既有 Outbox、撤销 ACK、币种/供应商校验保留,不扩大 Finance。 |
| `dispatch/pda/service/DispatchPdaWorkflowService.java`、`dispatch/fleet/DispatchFleetAdapter.java`、`fleet/trip/FleetTripExecutionService.java`、`FleetTripCompletionService.java` | 主流程唯一写者:当前依赖单客户、单出库、旧完成规则的主消费者。 |
| `delivery/mapper/DeliverySourceEvidenceMapper.java`、`SourceDeliveryCompletionQuery`、完成 Outbox | 本任务同步新父关联/分组和历史实际来源证明;跨次来源锁协议保留。 |
| `customervisit/service/CustomerVisitService.java`、`exceptionorder/`、File 提供者 | 直接相关但本任务未获生产写权;单据归属/签收事实若要求触及,列精确方法给总控分配。不能删除异常、回访或附件权限。 |
| Admin `overseas-warehouse/api/dispatches.ts`、`pages/DispatchesView.vue`及测试 | 前端任务处理组合预览、逐客户目的地费用、完整数组响应与恢复;详情仅消费已冻结契约。 |
| Client `order-management/views/DeliveryOrderCreateView.vue`、`api/deliveryOrders.ts`、`deliveryOrders.test.mjs` | 已由正式契约批准移除,前端任务负责直接消费者;客户列表/详情/签收下载继续真实客户隔离。 |
| PDA `fulfillment.js`、dispatch 工作流 | 前端及主流程任务同步全车结果/版本,保持真实拣货、装车与发车动作分离;不消费平台整车费用候选。 |
| `src/main/resources/openapi/overseas-business-v1.yaml`、安全权限、基线及统一 SQL、Gateway | 总控统一集成。派送资料/客户契约的 schema 引用一并核对,不由本任务并写。 |
| 后端 `scripts/overseas-relabel-fulfillment-acceptance.mjs`、`prod-release-overseas-stage.mjs`、`admin-test-env-modules/phase5-root-fulfillment.mjs`、`phase5-overseas-fulfillment.mjs`、`fleet-external-dispatch-acceptance.mjs`、`fleet-fee-coverage-acceptance.mjs`、`fleet-test-env-acceptance.mjs`及其合同测试 | 已命中旧转派送路径或单出库形态,需总控分配脚本同步;本轮不执行、不假定未命中的脚本都无需修改。 |
既有工作区还有其他模块未提交修改,包含 `ExceptionOrderDispatchReturnSourceTest.java`;本任务未写这些文件,后续跨域测试冲突交总控协调。
## 9 用例与后续验证命令
以下为待实施的行为测试清单,不是本轮通过结果。Maven 全部交主流程任务串行执行。
| 组 | 必覆盖行为与证据 |
| --- | --- |
| 单据与客户 | A/ONT8、A/LAX9、B/ONT8 得两出库三派送;同目的仓不串客户;所有子单来源集合完整;客户 B 查询/附件/原费引用不能访问 A;伪造 groupKey/customer/destination 被拒。 |
| 阶段与空单 | 空单、拣货未齐、未确认装车、装车 hash 过期、已发车禁止首次安排;装车后安排成功才能发车;不能通过普通状态编辑绕过。 |
| 真实来源 | 普通多入仓混托、混普通/外接、换标、退货换标及真实退回重派;扫码库存只扣一次,安排/装车/发车没有新增ship流水;发车只变订单picked/shipped计数及出库事实,同托后到货不改变已冻派送;实际SKU/箱来源不从声明推定。 |
| 原费 | A 引用 B 原费、过期版本/hash、Finance 不可用、同源同服务重复选择拒绝;部分覆盖仅免对应来源;全覆盖不造新/零费用;不同目的地引用同源证据不复制原费;供应商应付独立。 |
| 外接 | 两客户相同或相似入仓码不串价;预订/实收/目的地/数量/绑定改变拒绝;明确零只记事实;普通混货仍收未覆盖部分;前笔撤销未 ACK 不重收。 |
| 事务与竞争 | 同键同主体严格原载荷恢复;同键改recordVersion/动作/单号/操作者拒绝;新键同业务仍核当前权限/版本,只引用既有事实不重收;已撤权/换仓恢复拒绝;第N组失败所有客户及费用/Outbox/主单CAS全回滚;安排与Admin/PDA发车、改车/货竞争,发车一处失败全回滚;验证真实Mapper与唯一约束,不能仅靠Mock。 |
| 组合GET与数组 | 一次GET共享本地只读快照,无单出库别名;过程中本地版本变化不拼接新旧组,Finance候选保留版本并POST复核;来源不全/候选失败明确表达;数组覆盖全客户目的地,业务GET不泄露原命令请求或凭它冒认当前操作者成功。 |
| 签收 | 未出库不准首次签收;部分签收、异常/退回和签剩余不汇总完成;全部原实发正常签收自动汇总且完成时间取实际最后签收;签后另退不改原运输;重复保存和本地提醒不重复 Outbox/回访。 |
| 并发汇总 | 两客户同时最后签收、签收与提醒原子提交、提交后通知丢失、消费前/汇总中进程中断、重复扫描/消费、汇总CAS或提醒确认失败;父与ack同事务回滚,未扫描新提醒不被误确认,有界重扫最终恢复;无签收子锁回父,跨多趟来源证明保持RC+来源锁协议。 |
| 取消与撤销 | 保留已拣禁止取消、未执行合法取消原状态/关联恢复、仅派送应收申请作废、其他费用保留;与发车竞争只允许一个合法结果;请求撤销不当作确认作废。 |
复用/扩充现有测试:`DeliveryTransferServiceTest`、`DispatchDeliveryFeesTest`、`DispatchFeeCargoMapperTest`、`FleetBookingFreightTest`、`DeliveryExecutionGuardTest`、`DeliveryDocumentServiceTest`、`PlatformDispatchOutboundContractTest`、`MerchantDeliveryControllerTest`、`DeliverySourceEvidenceMapperTest`、`DeliverySourcePublicationPersistenceTest`、`SourceDeliveryCompletionConcurrencyTest`、`OverseasFeeCancellationPersistenceTest`。主流程的 `DispatchRepairWorkflowTest`、PDA/FleetTrip 直接测试由其唯一写者同步;新增整车安排持久化/竞争测试放本任务 `delivery/` 测试包,不与主流程测试并写。
主流程可采用以下后端根命令,具体 Test 类以最终实际新增为准:
```powershell
mvn -pl lastleg-service -am test-compile
mvn -pl lastleg-service -am test '-Dtest=DeliveryTransferServiceTest,DispatchDeliveryFeesTest,DispatchFeeCargoMapperTest,FleetBookingFreightTest,DeliveryExecutionGuardTest,DeliveryDocumentServiceTest,PlatformDispatchOutboundContractTest,MerchantDeliveryControllerTest,DeliverySourceEvidenceMapperTest,DeliverySourcePublicationPersistenceTest,SourceDeliveryCompletionConcurrencyTest,OverseasFeeCancellationPersistenceTest' -Dsurefire.failIfNoSpecifiedTests=false
mvn -pl lastleg-service -am test
node --test scripts/gateway-route-contract.test.mjs
node scripts/mapper-scan-contract.mjs
```
新增端口/依赖边界后按服务指引执行 `node --test lastleg-service/scripts/analyze-lastleg-dependencies.test.mjs`,核对依赖基线,不登记临时循环豁免。总控同步 SQL 后运行 Schema/中文注释合同;Client、Admin、PDA 的直接调用和构建由其获权执行者完成。真实 HTTP、部署、SQL、人工页面/真机验收仍待各自单独授权及证据。
## 10 本轮交付与停止点
首批实现已写入delivery域:DispatchDeliveryService实现实际DispatchDeliveryPort,DispatchDeliveryFees处理逐组报价/覆盖/发布,DeliveryCargoGroups与NormalDeliveryCompletionPolicy核对来源及正常签收。已复核总控14:36就绪的共享骨架,ArrangeDelivery按实际Long版本构造,缺失/负数拒绝、0合法。新安排事实/信号均有独立Mapper→Repository;DeliveryDocumentService触及的数据访问收进DeliveryDocumentRepository,签收经实际DispatchSignalPort入队、不回调父流程。
Controller的整车POST已使用共享ArrangeDelivery并返回CommandResult,新增组合GET返回ArrangementPreview;原辅助GET共用组合服务和PreviewGroup。Merchant创建POST和merchantTransfer服务入口已删除,客户读取及签收资料下载保留。未改SQL/OpenAPI/权限/主流程/预约在途文件;未提交、部署、执行环境SQL/HTTP或跨对话发信,未执行Maven。
已执行:Mapper扫描合同通过(10持久化模块、384 Mapper);Gateway路由合同5/5通过;git diff --check无空白错误。Lastleg依赖合同12例中9过3失败,均指出工作区fleet/pickup三类对WarehouseTransferOrder的新依赖,超出本任务写权,未放宽基线。该结果绑定当次工作区快照,不替代后续集成复验。
Java新增直接用例:DeliveryCargoGroupsTest、NormalDeliveryCompletionPolicyTest、DispatchDeliveryFeesTest、DispatchDeliveryServiceTest、DispatchDeliveryArrangementPersistenceTest、DeliveryFactsChangedPersistenceTest;另更新Merchant、资料、平台合同及来源持久化直接测试。全部尚未由Maven执行,不能声称编译或行为测试已通过。
旧单客户平台转换内部实现、DeliveryFeeCoverageService及旧Transfer/FeeItem/TransferResult DTO已清除;原行为用例由新整车领域用例承接,其他出库及取消用例保留。取消已接共同父锁端口,触及的数据访问收进DeliveryCancellationRepository;全部子单锁后先锁来源、再费用,保留费用撤销ACK及原状态恢复,不放开已拣取消。
联合收口事项:主流程DispatchExecutionPort实现、共同命令账本/阶段CAS/消费器;Context逐行实测重量/体积须为实际归属值、不能重复整托总量;主流程独占DispatchRepairWorkflowTest、FleetTripPersistenceTest手工fixture需注入新DispatchExecutionPort和DeliveryCancellationRepository,并补出库明细新来源字段。异常退回消费者须按新增不可变来源字段核对真实行;ExceptionOrderService超出本任务写权,交总控分配。真实转仓/换标及预约提供者的交集锁审查同样由总控分配唯一写者。共同SQL/OpenAPI/权限及真实跨客户竞争由总控集成、主后端串行验证。
本稳定点提供本域代码及测试供主后端统一编译/运行;接口实现/结构及外部消费者尚未联合完成,不标整体验收通过。日历新增预约始终在本任务范围外。
## 11 给总控的精确结构、API与验证提案
### 11.1 SQL(仅提案,未写共享SQL、未执行)
以下表/列与本轮Mapper完全一致。保留现有出库客户唯一键和派送客户目的地唯一键,不并加反序冗余索引;共同全局命令表由主流程提供。
```sql
CREATE TABLE IF NOT EXISTS ll_dispatch_delivery_arrangement (
id BIGINT NOT NULL COMMENT '安排事实内部主键',
dispatch_order_id BIGINT NOT NULL COMMENT '唯一整车排车主单ID',
dispatch_no VARCHAR(128) NOT NULL COMMENT '真实排车业务号',
arrangement_key VARCHAR(128) NOT NULL COMMENT '本次唯一安排业务事实标识',
business_hash CHAR(64) NOT NULL COMMENT '排除操作者及纯并发版本的安排业务摘要',
cargo_hash CHAR(64) NOT NULL COMMENT '本车实际拣货来源摘要',
loading_completion_hash CHAR(64) NOT NULL COMMENT '装车完成事实摘要',
original_command_key VARCHAR(255) NOT NULL COMMENT '首次安排的全局请求键',
original_operator_id VARCHAR(128) NOT NULL COMMENT '首次安排操作主体',
result_json JSON NOT NULL COMMENT '全部客户出库及客户目的地派送不可变结果',
cargo_json JSON NOT NULL COMMENT '逐客户逐拣货归属的不可变真实来源',
created_at DATETIME(3) NOT NULL COMMENT '本次安排实际创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_dispatch_arrangement_dispatch (dispatch_order_id),
UNIQUE KEY uk_dispatch_arrangement_key (arrangement_key),
UNIQUE KEY uk_dispatch_arrangement_command (original_command_key)
) COMMENT='整车排车唯一派送安排事实';
CREATE TABLE IF NOT EXISTS ll_delivery_facts_changed (
id BIGINT NOT NULL COMMENT '持久信号内部主键',
customer_id VARCHAR(128) NOT NULL COMMENT '真实签收货物客户ID',
dispatch_id BIGINT NOT NULL COMMENT '整车排车主单ID',
dispatch_no VARCHAR(128) NOT NULL COMMENT '真实排车业务号',
delivery_id BIGINT NOT NULL COMMENT '本客户派送单ID',
delivery_no VARCHAR(128) NOT NULL COMMENT '真实派送业务号',
delivery_version BIGINT NOT NULL COMMENT '触发信号的真实派送版本',
facts_hash CHAR(64) NOT NULL COMMENT '该版本真实签收事实摘要',
occurred_at DATETIME(3) NOT NULL COMMENT '签收事实实际发生时间',
state VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '待核对PENDING或已核对PROCESSED',
attempt_count INT NOT NULL DEFAULT 0 COMMENT '已记录的汇总失败次数',
next_attempt_at DATETIME(3) NOT NULL COMMENT '下次可重试时间',
last_error_code VARCHAR(64) NULL COMMENT '最近失败诊断码不保存敏感异常正文',
processed_at DATETIME(3) NULL COMMENT '与主阶段汇总同事务确认时间',
PRIMARY KEY (id),
UNIQUE KEY uk_delivery_fact_version (customer_id,delivery_id,delivery_version),
KEY idx_delivery_fact_due (state,next_attempt_at,id),
KEY idx_delivery_fact_dispatch (dispatch_id,state,id)
) COMMENT='正常签收事实变化的本地持久待汇总信号';
ALTER TABLE ll_outbound_pallet_item
ADD COLUMN dispatch_item_id BIGINT NULL COMMENT '本次排车作业明细ID',
ADD COLUMN source_attribution_id BIGINT NULL COMMENT '不可变PDA拣货归属ID',
ADD COLUMN source_order_id BIGINT NULL COMMENT '本行真实来源尾程订单ID',
ADD COLUMN source_inventory_position_id BIGINT NULL COMMENT '本行实际拣货库存仓位ID',
ADD COLUMN source_return_receipt_id BIGINT NULL COMMENT '本行实际退回再派实收记录ID',
ADD COLUMN source_return_carton_id BIGINT NULL COMMENT '本行实际退货换标来源箱ID',
ADD UNIQUE KEY uk_outbound_source_attribution (customer_id,source_attribution_id);
```
本轮不为开发存量设计迁移/回填或自动删重;总控在最新基线及统一结构脚本按列存在性集成,以上ALTER仅字段提案,不能直接当成可重复执行脚本。新来源字段在其他正式独立出库类型可空,排车来源按共享CargoLine逐行填真实值;同一真实归属不能在两次出库重复使用。主流程消费者必须先父锁,再普通读取事实,再阶段CAS及ack同事务;失败后调用DispatchSignalPort.failed独立短事务退避。due上限当前200,调用者使用1—200有界批量。
### 11.2 OpenAPI与权限
- 新增GET `/api/platform/v1/last-leg/dispatches/{dispatchNo}/delivery-arrangement`:dispatch:list,ArrangementPreview及其共享嵌套记录;顶层recordVersion/stage为当前快照,arrangement为不可变原结果。
- POST同前缀 `/{dispatchNo}/transfer-delivery`:dispatch:transfer-delivery,完整共享ArrangeDelivery请求;响应为DispatchContracts.CommandResult,result是ArrangementResult,replayed/alreadyArranged在外层,不保留单个outboundNo。
- 两个既有辅助GET external-freight、fee-coverage当前返回 `List<PreviewGroup>`,复用组合查询;前端弹框仅用一次组合GET,不再拼两个快照。
- 删除Merchant集合POST及其创建schema/权限资源;保留Merchant集合GET、单单GET及签收资料/下载。精确Client消费者见第8节,脚本由总控分配,不在本任务改动。
- 统一Gateway已有Lastleg通配覆盖,上述源码路由合同已通过;运行时部署及共享OpenAPI仍待总控集成,不推定已可访问。
- 服务AGENTS当前仍指向已删除的DeliveryFeeCoverageService;由总控在共享指引同步时改为DispatchDeliveryService/DispatchDeliveryFees及全局安排模型,并按整批指引门禁统一检查,本任务未并写AGENTS。
### 11.3 主后端串行运行包
```powershell
mvn -pl lastleg-service -am test-compile
mvn -pl lastleg-service -am test '-Dtest=DeliveryCargoGroupsTest,NormalDeliveryCompletionPolicyTest,DispatchDeliveryFeesTest,DispatchDeliveryServiceTest,DispatchDeliveryArrangementPersistenceTest,DeliveryFactsChangedPersistenceTest,DeliveryTransferServiceTest,DeliveryDocumentServiceTest,DeliverySourcePublicationPersistenceTest,MerchantDeliveryControllerTest,PlatformDispatchOutboundContractTest,DispatchFeeCargoMapperTest,FleetBookingFreightTest,SourceDeliveryCompletionConcurrencyTest,OverseasFeeCancellationPersistenceTest' -Dsurefire.failIfNoSpecifiedTests=false
```
主流程旧直接fixture签名与共享OpenAPI完成后再运行本包及Lastleg全量;尤其Merchant OpenAPI测试当前明确要求旧POST/create权限已删除,不能用旧共享文件使测试放宽。旧来源证明并发用例、外接报价、费用撤销ACK与取消竞争沿第9节纳入最终回归。
### 11.4 本域稳定文件快照
本轮固定delivery主源码和测试39个变更路径(37个现存文件、2个删除路径),不含本设计文档。统一编译反馈后,总控先解除`delivery/controller/MerchantDeliveryControllerTest.java`测试文件冻结,随后授权修正`delivery/PlatformDispatchOutboundContractTest.java`的当前Controller/DTO直接消费者;生产文件不变,测试类/路径集合不变。当前候选摘要采用**JavaScript `paths.sort((a,b) => a.localeCompare(b))`,不传locale/options参数**:
`90b54852f18e5f1244aeee81313f99bee50cb5abf405e372718c322ad60427c2`
此次测试文件SHA-256为`c45c68d01b54a67dab7f1fc7e3c55b4511530eb0aad716561ae84d6cda94dcb6`。派送列表/详情以及签收资料/下载均按精确路径块提取,到下一同级路径或根章节停止;保留禁止客户创建、Token客户隔离和签收资料只读的全部断言。新增一个相邻业务POST不误入、派送自身POST仍可检出的回归方法。未删除共享OpenAPI中的合法仓储转运和弃货POST。File三文件哈希与上一交付一致。
`PlatformDispatchOutboundContractTest.java`当前SHA-256为`e59a2bb29d3122aeb93a5dc0618bad8831ca83b5621a5a07d8316dfb5475b619`。取消反射及直接调用使用`DispatchAggregateController.cancel(user,dispatchNo,DispatchCommands.ReasonCommand)`,校验`POST /{dispatchNo}/cancel`和请求体;更新反射及字段集合使用`DispatchCommands.Update`。保留各动作独立权限、无customerId参数、客户在业务查找前拒绝,客户取消按公共身份工厂的`BizException(FORBIDDEN)`核对且验证整车Service零交互。草稿切片中按总控反馈进一步将旧vehicleSize整份YAML字符串断言改为当前DispatchArrangeDelivery的公共版本、完整groups及分组身份/费用证据,并核对结果outbounds/deliveries两数组、禁止旧DeliveryTransfer schema;未改共同OpenAPI。取消修正时的中间候选90778a9b…由本候选取代。
首版候选摘要`ab2a553af145d7a2c708f1b44d88eb2c349970d81295bbbe4504af5f55ed2f81`以及仅修正Merchant测试的中间候选`ef2d6bd5063f2aa9b030d68faef950582ac956e09a5ca349a615efc2f45ac130`均由当前候选取代。首版曾与Ordinal排序摘要`932c52c825613ae020ea60538a7cb8efa4941c905975b67644c7c926fae4bda1`有排序差异:小写controller子目录与大写Delivery文件的先后不同;该历史对照不代表当前测试输入。
精确重算算法(在后端根由Node执行,只读文件;这里保留为候选输入定义,不是新增测试入口):路径统一使用Git返回的正斜线相对路径;Set精确去重后按上述localeCompare排序;每行 `path + U+0020 + 小写文件SHA256`,删除路径后缀为DELETED;LF连接、末尾无换行;无BOM的UTF-8字节再做SHA-256。
```javascript
const { execFileSync } = require('node:child_process');
const { existsSync, readFileSync } = require('node:fs');
const { createHash } = require('node:crypto');
const scope = [
'lastleg-service/src/main/java/com/baiyue/lastleg/delivery',
'lastleg-service/src/test/java/com/baiyue/lastleg/delivery',
];
const changed = execFileSync('git', ['diff', '--name-only', '--', ...scope], { encoding: 'utf8' });
const untracked = execFileSync('git', ['ls-files', '--others', '--exclude-standard', '--', ...scope], { encoding: 'utf8' });
const paths = [...new Set((changed + '\n' + untracked).split(/\r?\n/).filter(Boolean))]
.sort((a, b) => a.localeCompare(b));
const rows = paths.map(path => path + ' ' + (existsSync(path)
? createHash('sha256').update(readFileSync(path)).digest('hex') : 'DELETED'));
console.log(createHash('sha256').update(rows.join('\n'), 'utf8').digest('hex'));
```
当前测试类集合仍为第11.3节列出的15个测试类,编译反馈后只更新其中Merchant与Platform两份合同测试。精确路径离线提取检查和`git diff --check`通过;JUnit/Maven未运行,由主后端串行重跑`mvn -pl lastleg-service -am '-Dtest=MerchantDeliveryControllerTest,PlatformDispatchOutboundContractTest' '-Dsurefire.failIfNoSpecifiedTests=false' test`。本次未修改生产代码、SQL、OpenAPI或File文件。
该快照只用于总控确定本次待统一编译/测试的候选,不是提交、构建通过或部署证据。主后端实际编译发现接口或用例问题时再修改并更新快照;当前本域文件继续固定,等待统一验证反馈。
### 12 有界草稿实现输入(2026-10-19)
本任务新接管SmartDispatchController、DispatchDraft/Container及其Mapper,新增专用Draft Service/Repository、占用Mapper和持久命令记录及直接测试;共享port、AggregateService、DispatchSourceMapper、旧DispatchService、PDA/Fleet仍由原写者维护。公开请求/返回严格使用DispatchPlanningModels/Port及Admin现有dispatches.ts:GET/POST drafts、POST drafts/containers及snapshot、draft-locks lock/unlock/release、DELETE空container(ReasonCommand请求体)、POST drafts/pre-arrange;旧standalone pre-arrange不再注册。
共享SQL由总控唯一同步,精确模型输入:`ll_dispatch_draft`增加`warehouse_id BIGINT NOT NULL`、`record_version BIGINT NOT NULL DEFAULT 0`、`sources_json LONGTEXT NOT NULL`(服务器解析的Candidate数组),保留operator_id、token唯一、plan_ids_json、status ACTIVE/RELEASED/CONVERTED及真实created/updated/expires/released时间;草稿头无客户语义。`ll_dispatch_draft_container`增加`source_keys_json LONGTEXT NOT NULL`,保留draft_id/token、sequence_no、capacity(15/30)、时间,唯一`(draft_id,sequence_no)`。新增`ll_dispatch_draft_command(id BIGINT PK,idempotency_key VARCHAR(128) NOT NULL UNIQUE,operator_id VARCHAR(64) NOT NULL,action VARCHAR(32) NOT NULL,target_token VARCHAR(128) NOT NULL,request_hash CHAR(64) NOT NULL,result_json LONGTEXT NULL,created_at DATETIME(3) NOT NULL)`;命令预占与结果同事务,绝不提交未完成命令,原键全载荷/主体/动作/原版本一致才恢复原结果。草稿占用复用`ll_dispatch_pallet_lock`,dispatch_order_id对草稿允许NULL,draft_token、draft_container_id、operator_id、expires_at必须填写,source/实托两个活动唯一键沿主流程现有规则;释放仅匹配本token/owner且dispatch_order_id IS NULL,不触碰已转换正式占用。
写事务顺序为命令唯一行→本owner草稿根→容器普通读;选择时真实来源订单升序→来源托盘/收据→库存→活动占用。布局JSON只允许移动当前已锁来源,不新增来源资格;全布局必须精确覆盖原容器/来源集合,序号唯一,单仓,按物理实托去重计算capacity。移除只允许空容器。释放/取消选择在草稿根之后仅释放本占用,不反取资源父锁。
转换先校验所有非空容器恰好一组、每组cargo精确等于该容器服务器选择及原sourceVersion、每组warehouse同草稿;再锁齐全批资源→全批来源→本草稿占用,释放草稿原键,逐组调用真实Aggregate.create(每组一张整车、不按客户/目的地拆),全部结果和草稿CONVERTED及命令结果同事务提交。任一失败回滚全部来源占用、主单/Fleet/历史/命令,不先提交部分组。
需要主后端唯一写者接线的最小方法:①`DispatchService.lockDraftSources(String warehouseId,List<DispatchCommands.CargoSelection> selections): List<DispatchService.WholeSource>`,MANDATORY,沿现有lockWholeSources的真实身份/仓/订单/库存/版本核验,草稿选择允许换标申请逐托暂存,正式create继续要求完整申请;②`DispatchAggregateService.lockDraftCreationResources(Actor actor,List<DispatchCommands.Create> creates): void`,MANDATORY,一次按正式资源全局顺序锁齐所有新建车辆/司机及关联资源,不能在锁完一组来源后才去取下一组资源。新增草稿服务不会自行改这两个提供者。旧DispatchService草稿方法及旧DTO的最终清理由其唯一写者负责;本轮Controller不再调用旧客户拆单链。
当前桥接已由主后端落地并接入。草稿头`customerId`已从本包实体移除,不恢复旧字段以维持编译;旧Service存量草稿方法已由主后端移除。`plan_ids_json`已由总控在两份共享SQL中改为LONGTEXT:公开筛选允许最多500个正数Long,JSON可能超过原VARCHAR(4000)。候选参数唯一为`destinationWarehouseCode`。按总控最终确认的正式权限口径,候选及草稿两个GET均为`platform:overseas-warehouse:dispatch:list`,八个草稿写入口均为`platform:overseas-warehouse:dispatch:create`;不保留旧入仓权限,不添加OR,不修改共享角色/IAM。
容器序号交换在同一草稿根事务内先将原正序号变为负数,再写全部新正序号,避免`(draft_id,sequence_no)`的临时碰撞;失败与草稿命令一起回滚。直接测试使用真实MyBatis/H2/Spring事务代理验证命令恢复、拥有者/旧版本拒绝、真实共享唯一占用竞争、同版本并发、序号互换、布局不能新增来源、单仓/容量、退回/换标选择字段互斥,以及第二辆车失败使全批与原占用释放回滚。来源和整车提供者在边界隔离,生产端到端创建/来源资格仍由主后端集成测试验证,不用该夹具冒充完整联调通过。Maven仅由主后端执行。
### 12.1 草稿切片稳定输入
草稿包精确13路径(10个生产文件、3个直接测试),当前修复候选聚合指纹`b0db1bb5191fa3578dacfa0f07c7c2a6a30346f0198047e34fc1fe3a99b0e554`,取代首轮`db84f7527d8ee88a5b893ed291081041c5a1461d7dbdafc66716014c575d3ac0`。按第11.4节同一localeCompare排序、path加空格加文件SHA256、LF连接无尾换行算法生成;不含设计文档、共享端口/桥接/SQL/OpenAPI,也不把原39路径并入草稿包。原39路径指纹保持第11.4节值;本批另仅修改原File包的DispatchEvidenceFileTest两处受检异常声明,File类别和独占契约未变。
```text
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/controller/SmartDispatchController.java 63cf9f9644a8fb1b08cbbff66ef646943bba23950c5662232ae540bb470195fc
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchDraft.java 4f1509204cfff1a6993f75cfc7dd2ab62b8a56c3217c58e2e00326958db76a12
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchDraftCommand.java 38366ac363927ada62a25e1ea1cd3cb39bd76df10242e3fa5ff8dd4d3745c14f
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchDraftContainer.java deded482497de649ae66600f372c4ed2d9450f4cfe08da249feaacaa3008d619
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchDraftClaimMapper.java f23aa194955b7058da25992e43ee786ce3cde0c3ba9025a9ae13440c36aafc66
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchDraftCommandMapper.java e63f642901fbb0a0f1b3895e871c3c93539cea9b7928a86899073bee05a46920
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchDraftContainerMapper.java 4a752ec506f3dbd7f86ad41b5f9fc1751b166e0b25f5d394b8d9c588e1b2c142
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchDraftMapper.java 141df6769ed0c9399a59a550204998c8d0b0c23e935e108f96602d1729c319dd
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/repository/DispatchDraftRepository.java 6ee39efc395352f43ce36003545956c917990e2f2913dd2be5ca7c19dea9675d
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDraftService.java 4a97ba8a592278ab635317a54f525268c51a7ac7f0ed04699085712e534af0c3
lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/controller/SmartDispatchControllerTest.java d577703bafa8cd51fd877c613b38c4bfcf16455a2cd4e8cb539b26f1bb418ef4
lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchDraftPersistenceTest.java d9ff50b04abddd54fba5c8c7528cb10c08dc7df8e3eecf43bc5172f7b20aeeec
lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/SmartDispatchOpenApiContractTest.java cab62da8a36e47f8f2c923f43429fa2565f242ac7e568f43640aff2f7b9404ea
```
主后端报告首轮输入test-compile通过(1056生产/450测试),直接178项176通过、2失败,失败为本包路由权限反射和转换结果重启恢复;本任务未自行执行Maven。独立只读审查另确认等待来源锁跨到期的占用漏洞,纳入同一修复。当前三份草稿测试共17个用例(DispatchDraftPersistenceTest 10、SmartDispatchControllerTest 6、SmartDispatchOpenApiContractTest 1),修复后尚未重跑。Mapper扫描393项及原静态检查是首轮证据;本轮已完成差异检查、构造器直接消费者核对、两个GET/八个写入口精确权限静态核对。共同OpenAPI的草稿业务必填列表已由总控补齐,不再作为待处理项。
请主后端在本输入上统一执行`mvn -pl lastleg-service -am -DskipTests test-compile`及`mvn -pl lastleg-service -am '-Dtest=DispatchDraftPersistenceTest,SmartDispatchControllerTest,SmartDispatchOpenApiContractTest,PlatformDispatchOutboundContractTest' '-Dsurefire.failIfNoSpecifiedTests=false' test`。本任务不启动Maven,不执行环境操作,不将静态核对写成编译、事务或并发通过。
### 12.2 首轮反馈修复范围
本批仅解除13包中Controller、Service、Repository、ClaimMapper及两份直接测试的冻结,原派送生产/其他测试保持不变。权限测试先按真实HTTP映射筛选并核对仍为10个路由,再逐项校验精确权限字符串,不能跳过正式路由鉴权。转换恢复注入现有DispatchCommandCodec,对每个嵌套车辆结果按原action解码;仅外层批次标记replayed,子车的原版本/时间/Saved载荷不变,不执行create、不读取当前快照、不注册第三套多态协议。
到期修复在阻塞来源/占用锁返回后重新核对原deadline,变更/转换最后写入后再次核对;续期后在锁内逐项核对选择集合、owner/token/容器/客户/实托和统一expiresAt,不能只延长草稿根。保留renew原拥有者、ACTIVE、尚未到期及未转换谓词,不复活过期行;更新行数与真实有效行集合联合核对,允许驱动对未改变值返回0,但不允许缺失/过期占用冒充成功。新增firstSelectionWaitingAcrossExpiryRollsBackSelectionClaimAndCommand:真实H2来源行锁、共享原子UTC测试时钟,从原到期前1ms跨至后1ms,要求失败无新选择/占用/命令且原版本不变,之后其他拥有者能唯一取得有效占用。该回归尚未运行。
File七类验证此前被DispatchEvidenceFileTest受检IOException编译阻塞;本批仅为uploadsKeepPlatformDispatchScopeAndImmutableObjects与metadataAndDownloadEnforceEveryScopeKeyBeforeReading补throws Exception,未改变断言。该文件新SHA-256为`bba87946457fb1290960218127087552de95b59f443eb6d5dacf354084d04a19`;BusinessFileCategory仍`b274532a0e4a263c56558eae68eb5717182791dec8e399e21bd459f6dffd32ff`,dispatch-evidence-v1.yaml仍`b3bc23d9f25513b258f0282886743e4323cb65b89c0203d4d2921ac89da08d14`。File七类测试此前尚未执行,本轮仍交主后端唯一Maven窗口。
### 13 既有预约整车接线只读提案
本节仅为总控决定唯一写者的提案。没有修改生产/测试/共享SQL/OpenAPI,没有运行Maven/HTTP,没有接管日历新增预约;草稿13路径与File包继续冻结。以下代码位置绑定本节末SHA快照,后续原写者继续改动的文件不是稳定实现承诺。
#### 13.1 优先解除的真实编译与运行交集
- FbaAppointmentExecutionAdapter.eligibility约87行仍调用TerminalEvidence.customerId(),当前FleetDispatchGateway.TerminalEvidence已只有dispatchNo/warehouseId/shippedPackages/deliveries,客户在TerminalDelivery与Outcome明细中。不能补回头客户或取首客户;按当前member.customerId过滤全部明细,再按destinationKey核齐本客户原实发/真实签收退回。read仍按旧DEPARTED判断是否读取证据,eligibility仍判断PENDING_DEPARTURE;应读取TransportRow的正交status/stage/主单recordVersion,保持现行执行资格,不给ACTIVE一个“待发车”别名。
- FbaAppointmentExecutionAdapterTest.proof约60行仍用5参数TerminalEvidence,改为实际4参数并增加同车多客户明细。FbaAppointmentTransportAdapterTest约46行仍用7参数DispatchBinding(含已移除customerId),实际为6参数;Detail当前6参数并未过期,不应盲目改它。
- FbaAppointmentTransportAdapter.apply约95/101/102行分别调用已移除的FleetTripService.unbindDispatch、FleetDispatchGateway.removeItem、FleetTripService.bindDispatch,直接测试的同名mock也已失效。不能恢复这些旧入口;原预约运输调整必须委托整车命令,不能自行解绑/重绑共享车次或更新数量表。原测试以A/B/C三张客户排车共用一车且头部存customer_id,须改为一张整车、真实明细客户、一个整车PDA任务;逐客户出库只在其真实阶段存在。
- AppointmentWorkDispatchMapper.references/transportRows仍联接d.customer_id;InboundFbaAppointmentQueryMapper.countActiveDispatches约47行也一样。后者由InboundFbaAppointmentService.batchContext直接调用,故不只是分配引用,既有批次候选读取在无头客户的新基线上也会失败。
- DispatchAggregateService.create/addCargo的requireBatchProvider以及addResolved的activeAppointments均是明确保护门禁。不能只删第一个门禁就宣称接通:旧bind(customer,...)在50/79行按dispatch_id删整车所有分配,循环A/B调用会互相覆盖;空分配加货也会因整车原有别的客户FBA分配被误拒绝。
#### 13.2 最小分配Provider合同
建议由原唯一写者替换旧按头客户bind,不加旧签名重载或兼容分支。复用原physical/pickedPhysical、AppointmentWorkPhysicalSources.canonical、真实batchContext与收货权威:
```java
record PhysicalItem(Long itemId, String customerId, Long planPalletId,
String sourceStage, Integer packages) {}
// MANDATORY;所有预期批次按既有统一字符串顺序一次锁齐、逐批核对当前版本。
void lockBatches(Map<String, Long> expectedBatchVersions);
// MANDATORY;仅替换touchedItems,Provider在DB核对它们属于该整车及真实明细客户。
void bindItems(long dispatchId, List<PhysicalItem> touchedItems,
List<Allocation> allocations);
// 这两个整车读取/校验签名可保留。
Map<Long, List<BatchReference>> references(List<Long> dispatchIds);
void requireDeparture(String dispatchNo);
```
PhysicalItem.customerId必须从已保存DispatchItem读取,不能从头部、请求customerId或Allocation反向推定。Allocation中的客户只作为必须匹配的断言。排车选择键PLAN:/RETURN:/RELABEL:也不能直接当预约逐票sourceKey;后者仍由真实订单规范化为BOOKING:/ORDER:,连同destinationKey核对。
创建时touched为本车全部新明细;加货时仅为新明细。Provider先读整车原分配,形成effective=原未触及行+本次新增/替换行,再按(batch,customer,source,destination)校验“其他整车已分配+effective本车量”不超真实批次。不能沿用allocated(... exclude dispatch)+仅本次增量的公式遗漏本车原量。删除限定dispatch_id与touched item IDs;A原分配应保持原样,给B加无批次/客户仓货物不能清空或要求重填A。客户仓自动DISPATCH:引用继续原规则,不伪造FBA预约版本。
版本是当前明确合同缺口:AppointmentWorkDispatchCandidates.Result已有version,但现Allocation、DispatchContracts.BatchAllocation和Admin DispatchBatchAllocation均不携带;当前sourceVersion摘要也不含预约版本,不能声称校验了用户选中时的原版本。若要满足该保证,最小补充是给两端Allocation增加嵌套recordVersion(明确指batchKey的权威版本,Long,0合法),并给候选BatchReference返回该版本;同批次多行版本必须一致,锁后比较真实FBA版本。该公共合同补充由总控/主后端与原前端写者统一决定和同步,不能本任务暗加字段。若不补,只能称“锁内当前权威核验”,原预览版本门禁不能宣告完成。
Aggregate候选当前appointmentBatches为空;应由既有来源逐票关联批次的只读Provider补真实引用/版本,不让前端猜批次或拿显示约号造绑定。可在既有DispatchCandidates读能力上增加有界批量“真实客户+planPalletId→相关批次”内部查询,不新增预约创建接口。
SQL最小修正:a→d按dispatch_id,a→i按dispatch_item_id+dispatch_order_id+真实customer_id;outbound按dispatch_id+a.customer_id,PDA按dispatch_id;transportRows增加d.stage与d.record_version。references/countActiveDispatches同样由明细证明客户。候选占用查询还应计入expires_at及全局实托占用,不能把已过期ACTIVE草稿永久判为占用,也不能遗漏RETURN/RELABEL别名对同一实托的占用。GET只筛选有效状态,不做清理写入。
#### 13.3 原运输调整消费者的最小桥接,不恢复旧绑定流程
ExecutionAdapter按TerminalDelivery.customerId与Outcome.customerId投影;completed必须来自本客户全部实发目的地的真实终态,不能用全车shippedPackages替代本客户数量,也不能用其他客户完成代替。批次范围版本摘要仍覆盖同批所有真实事实,不能因只选择客户B而漏掉A变化。
TransportAdapter.inspect按真实整车stage/计数和批次客户投影,Vehicle行至少按(dispatchNo,batchKey,customerId)分组,不能取首行客户再合并全车量。apply保留完整实物选择/原数量/同事务scope一致性守卫,但把旧unbind/bind/removeItem换成原主流程拥有的窄内部整车命令。建议接口形状如下,仅供唯一写者实现/审定,不是新增公开API:
```java
CommandResult applyExistingAppointmentChange(
Actor actor, String dispatchNo, ReasonCommand identity,
List<PhysicalItem> touchedItems,
List<Allocation> expectedAllocations,
List<Allocation> nextAllocations,
boolean removePhysicalItems);
```
其输入只能来自原批次与本事务lockAndInspect已核对的scope;主流程再核当前主版本、BEFORE_LOADING、零真实扫码、无下游执行、明细客户和原分配精确集合。移出只按已核齐完整item处理,不接受部分箱数猜实物;原绑定/来源/审核/版本/历史/资源投影与原预约改量同一事务。不得先删关联来绕过现有removeCargo保护。整车主单/资源关联保留当前统一规则,不能把“客户B退出”误作整车解绑/取消;所有明细退出也由当前空单/取消命令规则处理,不在Provider自动结案/释放资源。把父预约命令键透传为可验证的子命令身份,不能编造新随机键重做未知结果。为此FbaAppointmentTransportPort.apply及FbaReceivingService的直接调用需透传原idempotencyKey;沿用现有原命令账本,不新增第三套业务幂等协议。
此桥接之前,原预约改量消费者应显式保持未接通/不可执行,不恢复旧Fleet方法追求编译或放宽守卫;同样不能把只读/绑定修复当作该写链已完成。
#### 13.4 统一锁序与已确认反序
建议统一为:全范围trip父记录→全部车辆/司机/资源(同域稳定顺序)→全部dispatch→全部客户outbound→全部delivery(需要时)→全部既有预约批次(现有batchKey字符串排序)→来源自身既有申请/订单/实托/库存/占用稳定顺序→收货组等业务行→工作协调guard;命令键唯一记录沿各域原事务协议。锁后复读真实集合与版本,范围新增则拒绝并重试完整范围。
已有发车DispatchDepartureService约44行先requireDispatchDeparture取批次锁,46行才锁来源订单;原FBA分批/收货改量同样先batch后resolveForWrite的ll_order锁。若在Aggregate.addResolved取来源后直接补旧bind,将形成“源→批次”对“批次→同一源”的确定反序。可复现场景为一事务加同来源另一托,另一事务持原批次分批/发车后等待该来源。修法是Aggregate.create/add在来源之前预锁“原未变分配+本次请求”的全部批次;草稿转换已在调用create前锁来源,因此主后端lockDraftCreationResources必须在返回草稿域前就预锁全批groups的预约,不能只在后面的create补锁。不改冻结草稿服务来循环取另一批次锁。
FbaTransport.lockAndInspect目前只先锁trip、再dispatch,没有完整车辆/司机资源预锁;需复用主流程有界“全范围根/资源锁”入口,不循环单车方法形成trip1→resource1→trip2。当前DispatchFleetAdapter.completion已是dispatch→全部outbound→全部delivery,不能继续引用旧实现推断其仍反向取父锁。
协调锁方面,changes.requirementsChanged会UPDATE单行guard;FbaReceiving.resolve约115行seedFollowup经store.lock先拿guard,121行才transport.apply,后续可能继续取业务锁。应先收齐原运输/批次/来源/收货行与主流程变更,再做继承/失效协调写;不能把guard搬到整条业务入口后再取得父级/来源。当前guard-first同步/事项读取的直接查询未发现相反FOR UPDATE链,但这不是全域无死锁证据;新增接线必须做两会话锁竞争验证。
发车仍复用requireDeparture整车签名:批次锁内对每个实际(batch,customer)重读pending、ACTIVE agreement、SUCCESS及真实预约时间、唯一成员、source+destination覆盖/数量和替代批次状态,再核真实pickedPhysical。按(customer,item,pallet)核物理量,不能因某客户成功就放行另一个客户。发车核对当前权威,不机械冻结为初次绑定版本;后续真实收货确认可以产生新版本,结构/数量变化则按真实核对结果阻断。
#### 13.5 精确写入候选与直接回归(均待授权/未执行)
原预约消费者必需文件:AppointmentWorkDispatchPort、inboundplan/work下DispatchBinding/DispatchMapper/DispatchCandidates、InboundFbaAppointmentQueryMapper、FbaAppointmentExecutionAdapter/TransportAdapter及它们两份直接测试。主流程接线由其唯一写者处理DispatchAggregateService、必要的DispatchReadMapper/共享BatchAllocation与BatchReference,以及上述内部整车变更端口/实现;发车现有FleetTripExecutionService.requireDispatchDeparture入口可复用,不另建发车接口。原改量桥接才触及FbaAppointmentTransportPort、FbaReceivingService(父命令身份/协调顺序)。公共字段同步涉及原Admin dispatchContracts.ts、主后端共享port及总控OpenAPI;不能由本只读任务抢写。
车辆预约:当前只定位到旧DispatchOrder.vehicleAppointmentStatus/旧请求手填状态、新AppointmentReference(referenceNo,recordVersion)、DispatchInfo空投影及Aggregate.update显式拒绝;Lastleg实际业务入口/直接调用中未定位到可验证的独立车辆预约权威记录。不能用FBA批次、外接FleetBooking或旧手填状态代替。接线所需最小契约为“verifyExisting(AppointmentReference requested, Context dispatch)→原权威referenceNo/recordVersion/status/appointmentAt及可核归属”;如为远程仅能声明版本快照,不能声称跨服务原子锁。原提供者未明确前保持该门禁;持久关联位置、详情版本与审核/发车复验也需随真实提供者明确,不能只删reject然后丢弃请求。本批排除任何新的车辆预约权威表、创建API、日历新建/表单和新的预约业务流程。既有FBA收货确认/改量及其已存在后送批次属于原预约消费者,不等于本轮新增预约。
建议直接回归:①无头customer的新基线能读batchContext/references/transportRows,fixture一车多客户一PDA;②加B保留A原分配逐行不变,含A已有FBA而B无FBA/客户仓;③effective含本车原量,跨车/同车超分配均拒绝;④批次原版本0合法、缺失/混版本/替代/旧预览拒绝;⑤A已确认B未确认或pending必须挡车,B补齐后核全部真实拣货/数量,不能用头部SUCCESS;⑥同车A完成不伪造B完成,终态读使用四字段TerminalEvidence明细,未发车保持真实阻断;⑦B退出保留同车A/C与主单编号,不调用旧bind/unbind,部分110不能匹配实物时全部回滚;⑧加货/发车/原预约分批/收货确认两会话按批次→来源锁序竞争,资源与guard不能反向;⑨父命令/主版本/后续写失败时主单、原分配、来源占用、审核/历史与原批次改量全回滚,未知结果只恢复原账本。以上仅清单,没有执行。
本次读取SHA-256(源码随原写者继续变动时须重核):
```text
lastleg-service/src/main/java/com/baiyue/lastleg/appointmentwork/AppointmentWorkDispatchPort.java 45d1b7bdcb675cc6ef2f0020eac1651dd778538296913cf8d517380157ca954a
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchBinding.java 26db8a78d747d4cf25204255bbc5c2fdcde9efc0adebaea35dadec872d684e64
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchMapper.java 856a0fa618ecb143b6591cfd2ccde982c161b36181c4dc5bdf177973dd0f3351
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchCandidates.java bbd2bbdcaab8b418ef0f4732e44b765da7f9bc919c6492bc770e6ba7fc4c8c9a
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentExecutionAdapter.java fc7be6a167dd279d02b0055f8425703343eb763f2beabfd43de21ced6fa0b245
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentTransportAdapter.java e07c8049206927fe47b24734ac071f80ca3000174891053dcd86967f5376484b
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundFbaAppointmentQueryMapper.java ca61cb28603aa8a922bec4b3f8caab39fb29d6eb818fdf151e34856a378b5980
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java 4dae88abe91fd872c0d0441d0ffa9f10574623bb4effdfaa9c3031cfa5d5d93b
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDepartureService.java 3123fe0e2beb47caa30c8ecc262efc084b42fd96c1f04eb7e08ca7f54a0c0878
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetDispatchGateway.java c71831f0af4330622a30a7f304b8ceeda91e58f982f27a5542f4d23bf64677e9
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetTripModels.java d3419d0cb473b9cd688d8eab2bced47fcc4b073dfb23c30ac930948c09dbdaeb
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetTripService.java 703f2a844486b484396db68f694b1c441901fec54ad66a7b9a66bef41e9ab91b
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentTransportAdapterTest.java 86a0e46ef135db7b9a89b9f33d33d62c61b28b77547f058a477bfb2ea504dda6
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentExecutionAdapterTest.java 736901b404d085447f676a5cd049a225d998519d5358ee30c506d9bc42a0d14e
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentService.java e917cced0f40b7475bb3310754a4b3dc37bc26117f6c99f697d6db3b5941ddb0
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/receiving/FbaReceivingService.java 34f4ae02c448d0abcdef0b37c163ea7214b35c92819367c995436eda7d35635d
```
#### 13.6 总控可审批的两层范围(只读,不代表获权)
编译最小包精确四文件:inboundplan/work/FbaAppointmentExecutionAdapter.java、FbaAppointmentTransportAdapter.java,以及test/java对应FbaAppointmentExecutionAdapterTest.java、FbaAppointmentTransportAdapterTest.java。前者用当前四字段TerminalEvidence的明细客户,不恢复customerId getter;后者删除三处不可编译旧调用及对应mock。若整车变更桥接尚未实施,apply应明确保持未接通并拒绝写入,保留scope/版本/执行守卫,不让测试模拟旧写成功;不能补回旧bind/unbind/removeItem追绿。测试按当前TerminalEvidence四参数、DispatchBinding六参数,并淘汰头客户/旧主状态夹具。该包只能证明编译和失败关闭,不能宣告原预约改量业务恢复。
完整接线包在上述四文件外,核心必须圈定AppointmentWorkDispatchPort、AppointmentWorkDispatchBinding、AppointmentWorkDispatchMapper、AppointmentWorkDispatchCandidates、InboundFbaAppointmentQueryMapper、DispatchAggregateService;如实施既有改量写链,再圈定FbaAppointmentTransportPort、FbaReceivingService及主后端内部整车变更端口/实现。建议新增的有界父锁方法为void lockExistingAppointmentDispatches(Actor actor,List<String> dispatchNos),同事务一次锁全范围trip/车辆司机资源/dispatch,不能逐车交错获取。其后适配器读原Mapper复核完整scope,才取批次和来源。applyExistingAppointmentChange中的removePhysicalItems只能由已核齐原scope的服务端决定,不能新增为用户可随意选择的公开字段;expected/nextAllocations必须来自原权威与本次真实核心改量结果,不以删除关联绕过守卫。
严格原批次版本若采用13.2的Allocation.recordVersion补充,须另外精确圈定dispatch/port/DispatchContracts.java、DispatchReadMapper、FleetTripService等已知直接BatchReference构造者、原Admin dispatchContracts.ts及总控OpenAPI;不因此授予目录级扩写。候选/绑定/发车只消费原预约,不新增预约权威表、日历表单或创建API。车辆预约权威当前未定位,列为外部契约依赖,保持门禁;停止扩大搜索,不为填空自建Provider/表。
总控另转交ExceptionOrderService.java约548行dispatchPdaTasks.findByDispatch(customerId,dispatchId)属于独立旧PDA消费者,待总控协调原唯一写者;未纳入本只读包写权、未读取或修改该文件。
## 14. 第13节既有预约接线实施包(2026-10-19)
本轮授权后实际实现包为下列19个后端路径;总控随后只对`FbaAppointmentPersistenceTest`与`FbaReceivingPersistenceTest`补齐13处父命令键参数(部分移动直接断言原键`partial-confirm`),其余17文件保持交付输入,本节已更新两文件及聚合摘要;主流程公共合同、Aggregate桥接和锁实现由主后端独占,根接走的旧查询3+1不在本包。File冻结;草稿仅解除DispatchDraftService候选/视图及其直接测试,生命周期、命令重放、到期修正保持。此处仅是实施交接,编译和回归仍由唯一Maven执行者确认,不将新增测试或静态检查记为通过结果。
- 原Provider:`Allocation(planPalletId,batchKey,Long recordVersion,customerId,sourceKey,destinationKey,packageCount)`;`PhysicalItem(itemId,customerId,planPalletId,sourceStage,packages)`;`BatchReference(batchKey,batchNo,Long recordVersion,customerId,sources)`。版本显式必填,零合法;移除旧bind/requireReplay。批次预锁固定String键排序,一次锁齐,事务后续只能复用已经声明的集合。
- 候选实际入口:`Map<CandidatePhysical,CandidateBatches> candidateBatches(List<CandidatePhysical>)`;`CandidatePhysical(String customerId,Long planPalletId)`;`CandidateBatches(List<BatchReference> appointmentBatches,List<String> blockReasons,boolean occupied)`。最多1000个输入,整页两次本地SQL,来源去重后遵守原100票边界分块规范化;不逐候选循环batchContext。真实客户/计划托盘、BOOKING/ORDER、批次版本、目的地、跨车原分配余量与全来源有效实托占用共同决定结果。sources.packageCount由本托逐票实物和该批余量共同限定,零余量不伪装成可选项;缺失/陈旧归属返回阻断。occupied含本草稿自身,调用方仍按真实草稿身份核占用,不能直接阻断自己的已选来源。
- 增量绑定:逐项重查真实DispatchItem客户、托盘、阶段和数量;保留未触及实物,将保留行与新行共同计入本车批次余量。只按明确item删除,不再整车清空。发车按真实客户/item/pallet分组,重新检查当前收货确认、替代批次和实际逐票拣货。
- 原运输:消费实际DispatchAppointmentAmendmentPort。锁定方法增加真实operator;apply增加parentCommandKey。expectedAllocations只含touched原分配,next为空、removePhysicalItems=true表示完整实物移出本车;主流程派生稳定子键。没有恢复旧Fleet bind/unbind/remove别名,不凭某客户退出解绑整车。
- 执行判断:当前ACTIVE/BEFORE_LOADING等真实阶段,TerminalEvidence使用四参数结构;客户身份取TerminalDelivery/Outcome,整车发运总数不能替代客户完成量。
- guard:跟进seed移至批次、来源、运输和收货组全部写完后;Provider失效标记在同一原事务beforeCommit统一写入,避免逐item移货时先拿guard、再释放后续来源。失效失败仍使原事务回滚,无异步补偿或第三套账本。
- 草稿:整页批读当前预约投影,显式映射公共DTO末尾recordVersion;不推进草稿版本,不改来源摘要、占用和转换逻辑;同键重试仍恢复原命令结果,当前GET允许反映最新预约版本。
新增/调整直接回归覆盖同车280箱(101=80、202=100+60、303=40)、B退出剩120、完整60移出剩220、无法完整匹配110拒绝、后续失败整体回滚、同车其他客户/同客户其他实物保留、跨车并发批次余量、伪客户/版本缺失或过期、真实BOOKING来源、有界候选批读、过期草稿及RETURN/RELABEL同实托占用、草稿投影刷新与原键结果不变。原批次/来源或主流程边界被测试替身隔离的用例不等于这些生产者自身集成验收。
本轮仅执行静态直接引用核对、无BOM/尾随空白核对和git diff --check,均通过;未运行Maven、javac、HTTP、SQL环境或部署。建议唯一执行者直接运行:
```powershell
mvn -pl lastleg-service -am '-Dtest=AppointmentWorkDispatchBindingTest,AppointmentWorkDispatchPersistenceTest,FbaAppointmentExecutionAdapterTest,FbaAppointmentTransportAdapterTest,AppointmentWorkPreArrangementPersistenceTest,FbaAppointmentPersistenceTest,FbaReceivingPersistenceTest,DispatchDraftPersistenceTest' '-Dsurefire.failIfNoSpecifiedTests=false' test
```
车辆预约未定位权威提供者,现有拒绝门禁保持;未新增预约权威表、预约创建API或日历表单/面板。
19路径聚合SHA256:`70526153276ae4d8f5be3e211b5067ca3a8ae79a24eefe7b490df4b1486ad658`。按JS默认localeCompare排序,使用`path + 空格 + 小写SHA256`以LF连接,末尾无换行、UTF-8无BOM:
```text
lastleg-service/src/main/java/com/baiyue/lastleg/appointmentwork/AppointmentWorkDispatchPort.java 33f2ec5f8a19eaa814a9f618dc7e91908dfb609174912a98224fb89be47cd653
lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchDraftService.java 0330fe2f78484a0a744b4d5fbdb5471dd1ad1fbe5360af190040377419114d46
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundFbaAppointmentQueryMapper.java 018193279a361234401dee825d255e2c72e557b08a0fd4edb5a1c58cdf38e280
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/receiving/FbaAppointmentExecutionPort.java 64b0dab0e5a8ad74dc2b1412cd2d628b9f7f5850da72c121bb265e654b288efd
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/receiving/FbaAppointmentTransportPort.java c9cba7c2688fc0621d127b83ba0dca1693201bdff711f962e2eddc34a30b8083
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/receiving/FbaReceivingService.java 17cba0f20cb3ff2c13c8c6ad9cd38843e05aab8954f885a1e08685f36b541310
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchBinding.java a9b6c9004960f9ad0b763b156a68dc4ca59f9cbdc2646fe612a6d7a9f78be3a1
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchCandidates.java b43ced699717dfd8eafb429e67b95109d0b5189281ec37902cc70c318f1e84bc
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchMapper.java 1d0dc2a6d27eead02730fbe90263dc78435dfc258f1760e65cd5e6263375d015
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentExecutionAdapter.java eb7b3069c8557043d573cfaa96eec068047bda93a1701f9e4f0886ab7372fd43
lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentTransportAdapter.java 4124e454ea63d6803cf1d82f5376a009806088ce8213f2d736759edf369f2c9c
lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchDraftPersistenceTest.java 846aa50bb8af260a16f806cb92686f155bf5c81d2dc607f94216965e66220e96
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/AppointmentWorkPreArrangementPersistenceTest.java 8300008cda492a6be58542e1ec5e22409ebb2b22caec315b797a2c5dd83643a2
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/FbaAppointmentPersistenceTest.java 222cc71e3b24f7bbe25cc743fc964891f6d2c385e276744af59b45d5abe72d7c
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/FbaReceivingPersistenceTest.java 504c79ab02cac49cbb5c4698959ace76d2c3970532bf9e3c372e64930f449407
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchBindingTest.java df2b0e6a7f86f2678ae8c857a20016e1ce9a7ed2adf6870571e0577974212e61
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/AppointmentWorkDispatchPersistenceTest.java 06b3fb5da55e373f874e6f700a699f60f5edd1c8d1a33498be2da3e154853f27
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentExecutionAdapterTest.java 076198846409ee929853807d3e822c9cbf106bf26fb09a69def7bbe2d715774f
lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/work/FbaAppointmentTransportAdapterTest.java f84644e00665b1579d960ae9a6378490b1f325f862c6beccbbef9291f5b7d533
```