From 2168ace3731a3afd9321a2ee0b327094265230aa Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 22 Sep 2026 14:01:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../海外仓库位与A0溢出位补齐方案评审稿.md | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 海外仓业务/海外仓库位与A0溢出位补齐方案评审稿.md diff --git a/海外仓业务/海外仓库位与A0溢出位补齐方案评审稿.md b/海外仓业务/海外仓库位与A0溢出位补齐方案评审稿.md new file mode 100644 index 0000000..bb31d6f --- /dev/null +++ b/海外仓业务/海外仓库位与A0溢出位补齐方案评审稿.md @@ -0,0 +1,253 @@ +# 海外仓库位与 A0 溢出位补齐方案(评审稿) + +## 1. 目的与范围 + +当前海外仓可以完成接柜、收货、组托和库存入账,但尚未形成与国内仓一致的“库位—托盘—库存”闭环。本方案只讨论海外仓库位、托盘位置、A0 共享溢出位及入库后的上架/移库,不扩展海外仓主档、客户自助仓储服务或新的库存业务模型。 + +## 2. 当前实现与缺口 + +### 已有能力 + +- `warehouse-overseas-service` 已拥有海外库位、托盘、资源锁和托盘物理流转能力。 +- 海外入库计划托盘已保存 `warehousePalletId`、`warehouseLocationId` 和资源锁。 +- PDA 入库库存落账会读取托盘库位,并写入库存 position 的库位 ID/编码。 +- Lastleg 已有 PDA 商品移库能力,且海外 PDA 已有移库页面和路由。 +- 海外资源服务已经有平台库位 CRUD:`/api/platform/v1/warehouse-admin/{warehouseId}/locations`;缺的是 Admin 正式海外库位页面和完整业务接入。 + +### 主要缺口 + +- 真实库位分配通常没有进入入库业务,托盘会落到虚拟收货位。 +- 海外库位没有国内仓已有的 A0 共享溢出语义。 +- `wh_location` 没有 `shared_overflow` 标识,普通位和 A0 的并发占用规则未建立。 +- Admin 现有 `/platform/v1/locations` 是国内仓页面接口,不能作为海外库位接口复用。 +- 海外入库详情不展示当前库位,也没有平台移库入口。 + +### 2.1 评审发现的实现偏差 + +- 当前资源服务、资源锁和托盘流转代码只把 `VIRTUAL` 当作非独占库位;仅增加 `shared_overflow` 字段不会让 A0 生效,`reserve/release/scrap/transition/resolve/reservationBlockReason` 以及 PDA 库位查询都必须改用统一的“非独占库位”判定。 +- 当前柜级转换会调用 `ensurePalletBatch` 并把返回的 `locationId` 写入入库计划托盘。新建托盘通常得到虚拟位,但复用已有托盘时可能已经带有普通位。因此“转换阶段不锁普通位”只对新建资源成立,复用托盘的行为必须在接口契约中明确,不能把所有转换都描述成虚拟位。 +- 当前 PDA 移库把 `PROCESSING` 记录、远程物理变更和本地库存更新放在同一个 Lastleg 事务中。远程成功而本地失败时,`PROCESSING` 记录也会回滚,现有实现不具备方案中所称的可重放 Saga;必须先补持久化命令/Outbox 或独立事务状态机。 +- 当前 `locationType` 仍允许任意字符串,现有测试和数据使用过 `STORAGE`。本方案采用 `NORMAL` 后,需在最新基线中直接收敛并同步 OpenAPI、Fixture 和测试,不保留 `STORAGE` 的双重语义。 + +## 3. 目标规则 + +### 3.1 库位类型 + +- `VIRTUAL`:虚拟收货位,仅用于收货阶段临时承载。 +- `NORMAL`:普通库位,例如货架位、地面位,默认一托一位。 +- `SHARED_OVERFLOW`:系统共享溢出位 A0,每个海外仓默认一个。 +- `UNKNOWN`:仅用于发现历史缺失或不一致时的诊断返回,不是可创建或可移动到的库位类型。 + +### 3.2 A0 规则 + +1. 普通库位有可用位置时优先使用普通库位。 +2. 普通位候选竞争失败且现场确认后,托盘进入同仓 A0。 +3. A0 可同时承载多个托盘,但每个托盘必须有独立 `resourceLockId` 和 `reservation_key`。 +4. A0 是系统保留编码,不可由后台编辑、停用或删除。 +5. A0 中的单个托盘可以换绑到普通库位,不影响 A0 中其他托盘。 +6. A0 是合法的实际落位,不等于未上架;只有虚拟收货位是 `putawayStatus=PENDING`。 + +### 3.3 库位状态矩阵(必须固定) + +| 库位 | `locationType` | `shared_overflow` | `status` | 占用事实 | +|---|---|---:|---|---| +| 虚拟收货位 | `VIRTUAL` | 0 | 永远 `ACTIVE` | 不以库位状态计数,按托盘和资源锁区分 | +| 普通库位 | `NORMAL` | 0 | 空闲 `IDLE`、占用 `OCCUPIED` | 一个活动托盘;由服务端 CAS/占用事实保证唯一 | +| A0 | `SHARED_OVERFLOW` | 1 | 永远 `ACTIVE` | 多个活动托盘;按托盘、资源锁和 reservation key 区分 | + +`locationType` 与 `shared_overflow` 不得出现不一致组合;不一致时查询、预留和流转均失败关闭。A0 不得通过把库位状态改成 `OCCUPIED` 表示“已占用”,否则第二个托盘会被错误拒绝或释放掉其他托盘。普通位才允许使用 `IDLE/OCCUPIED` 的库位状态机。 + +### 3.4 分配与物理落位必须分离 + +服务端可以返回普通位优先、A0 兜底的候选目标,但候选不是实物已落位。只有“扫描托盘 + 扫描目标库位”的物理变更成功,并且托盘、资源锁、库存位置和操作记录同步成功后,才把该托盘视为已上架。普通位没有候选时可以返回 A0 候选;第一期不得在没有现场确认的情况下把库存自动改写为 A0,避免把虚拟收货位误报成实际库位。 + +普通位候选必须由服务端选择,至少按 `warehouse_id`、启用状态、库位类型和确定排序(建议 `zone_code、location_code、id`)返回建议;候选读取本身不占用库位,最终上架/移库命令锁定目标并执行 CAS。竞争失败后重选,重选仍无普通位才返回 A0。前端提交的 `locationId` 只能作为扫描结果,不能绕过候选和同仓校验。若未来需要提前占位,必须另建带命令 ID 和 TTL 的预留表,不能提前把普通位改成 `OCCUPIED`。 + +最终命令仍需在服务端复验“普通位优先”:普通位仍有可用位时,直接提交 A0 必须返回稳定阻断原因;若业务确需越过普通位,只能使用独立的 overflow override 权限并要求填写原因,不能由前端隐藏候选来代替门禁。 + +### 3.5 分配时机 + +入库计划转换阶段对新建托盘继续使用虚拟收货位,不提前锁死未来数日可能需要的普通位;复用已有真实托盘时必须保留其真实当前库位并在返回中标明“已有落位”。收货完成后由正式上架命令返回候选,现场确认后再按“普通位优先、A0 兜底”提交真实位置。 + +平台海外实物资源的虚拟位也统一使用 `customer_id=0`。历史上按客户作用域创建的虚拟位不得继续生成新资源;迁移后若仍被引用,先完成资源锁和托盘归属收口,再允许进入新流程,不能让公共 resolve 因作用域不同找不到位置。 + +## 4. 目标流程 + +```text +柜到仓 / PDA 签收 + ↓ +拆柜收货、扫描箱码、组托 + ↓ +托盘暂在虚拟收货位 + ↓ + 库存入账 + ↓ + 服务端返回普通位候选;无可用普通位时返回 A0 候选 + ↓ + 扫描托盘 + 扫描目标库位完成上架/移库 + ↓ + 更新托盘、资源锁、库存 position 和移库流水 + ↓ + 必要时将 A0 托盘换绑到普通库位 +``` + +复用已有 `NORMAL` 或 A0 实际托盘时,不再经过“虚拟位→候选”分支:库存入账直接沿用当前位置并标记该托盘 `putawayStatus=COMPLETED`;只有当前位为 `VIRTUAL` 且状态为 `PENDING` 的托盘才进入上架候选。 + +## 5. 改造范围 + +### 5.1 `warehouse-overseas-service` + +#### 数据库 + +在 `wh_location` 增加: + +```sql +shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否为系统共享溢出位A0' +``` + +字段本身不构成完整约束。A0 必须使用公共资源作用域 `customer_id=0`、固定库位码 `A0`、固定分区 `OVERFLOW`、`location_type=SHARED_OVERFLOW`、`shared_overflow=1`、`status=ACTIVE`;客户端不能提交或覆盖这些值。每个海外仓只能存在一个 A0,A0 的 ID 和编码永久稳定。`max_load_kg` 固定使用 `999999999.000` 哨兵值并在页面显示“不参与容量判定”,候选逻辑不得读取该值。懒创建必须在国内仓目录确认 `OVERSEAS + ACTIVE + RECEIVING` 后执行,并用固定编码唯一键加并发锁保证幂等;编码冲突、重复 A0、仓库失效或目录不可用均失败关闭。 + +A0 的懒创建只由候选命令(使用写语义)或正式落位命令触发;普通列表 GET 不产生写副作用。初始化失败必须返回明确的 `A0_NOT_READY`/`A0_CONFLICT` 阻断原因,不得静默把托盘留在一个不存在的 A0。 + +同时补齐: + +- 按 `warehouseId` 懒创建且幂等的 A0; +- A0 编码冲突、仓库失效、并发创建的失败规则; +- 普通库位活动占用唯一约束; +- A0 允许多托盘共享但按托盘锁区分; +- 按 `resourceLockId/reservationKey` 单独释放; +- 基线 SQL、增量 SQL、中文字段注释和 schema 合同测试。 + +不能只增加 `shared_overflow` 字段:现有资源服务大量以 `locationType=VIRTUAL` 判断非独占位置,需要统一改为“虚拟位或 A0 均为非独占位置”,覆盖 reserve、release、scrap、transition、resolve、reservationBlockReason 和状态校验。A0 的 `status` 永远保持 `ACTIVE`,任何托盘进入或离开 A0 都不能修改 A0 的状态;普通位才执行 `IDLE/OCCUPIED` CAS。 + +当前 `wh_resource_lock` 的真实字段名是 `reservation_key`,不是 `reservationId`。它可以作为托盘级锁基础,但必须明确 `location_id` 的语义:本方案将其定义为当前物理库位,并要求移库时与 `wh_pallet.location_id` 在资源服务内原子更新;`wh_pallet_batch_item.location_id` 改为 `reserved_location_id` 快照,不再被当作当前库位。普通位唯一占用必须通过占用事实表,或带 `shared_overflow` 快照的生成列/唯一索引明确保证,不能只依赖查询后再写入。A0 的多托盘释放、报废必须按托盘和 reservation key 精确匹配,不能释放整个位。 + +建议复用国内仓二期的模式:保存共享标识快照,普通位用条件唯一约束保证一个活动托盘,A0 不写独占占用状态;海外实现仍以本服务的 `wh_` 表和内部接口为准,不跨库复制仓库主档。 + +库位解析、批量托盘响应、Lastleg Gateway、PDA/Admin DTO 和 OpenAPI 必须贯穿返回 `zoneCode`、`locationType`、`sharedOverflow`、`status`、`version`;批量转换不能只返回 `locationId`,还要让 Lastleg 区分新建虚拟位与复用的真实落位。 + +#### 接口边界 + +已有资源 CRUD 继续使用: + +```text +/api/platform/v1/warehouse-admin/{warehouseId}/locations +``` + +该服务负责库位、托盘资源和内部物理位置变更;不负责更新 Lastleg 库存或入库业务。资源服务的物理变更必须同时更新 `wh_pallet`、活动资源锁、普通位占用事实和 `wh_pallet_operation`;Lastleg 只在远程成功后更新自己的库存和业务快照,不能直接写 `wh_` 表。 + +### 5.2 `lastleg-service` + +- 增加或扩展平台移库命令,Admin 不直接调用 PDA 权限接口。 +- 入库计划/托盘 DTO 增加库位编码、库位类型、`sharedOverflow`、库位状态和上架状态。 +- 增加服务端库位候选查询和同仓校验,前端不能任意提交 `locationId`。 +- 资源 Gateway 的 `locationId` 使用可空类型兼容历史无库位托盘;库位返回必须显式携带 `sharedOverflow`、`locationType` 和 `status`,调用方不得猜测 A0 语义。 +- 复用 `ll_pda_inventory_move` 作为平台/PDA 共用移库命令记录,扩展 `PREPARED/REMOTE_APPLIED/LOCAL_COMMITTED/COMPENSATION_REQUIRED`、租约和远端 operation 快照;平台与 PDA 使用不同的操作人/幂等键命名空间。 +- 移库时同步更新 `ll_inventory_position`;计划托盘的 `warehouse_location_id` 只作为当前值冗余字段并在成功移库后同步,`wh_pallet_batch_item.location_id` 改名为 `reserved_location_id` 历史快照,收货凭证和库存流水中的位置只读不可变。 +- `wh_resource_lock.location_id` 定义为当前物理库位;普通位、A0、虚拟位换绑都必须与 `wh_pallet.location_id` 在资源服务同一事务内更新。任何锁、托盘、库存 position 或计划当前字段不一致时,新的移库、排车和出库均失败关闭并进入对账队列。 +- 合托按实物托盘级移动,同一托盘内多个入仓单的库存明细一起移动;不得只移动某个客户或某条库存 position。若要部分移动,先使用既有拆托/换绑能力形成新实物托盘,再执行整托移库。 +- 移库前锁定同仓该托盘全部库存 position,要求所有 position 的库位一致,保持 `available/hold/allocated/picked/receiving` 数量桶、客户集合和托盘号不变,并写零差额库存流水。正在收货、拣货、出库、换标冻结或存在未完成命令时返回 `canMove=false` 和稳定 `blockReason`;零库存托盘和孤儿 position 先对账处理。 +- 混托场景要同时锁定该物理托盘的全部活动业务锁和业务引用;移动时所有客户的库存 position 一起更新。一方客户库存清零不能释放公共托盘,只有全部库存、活动锁和未完成业务引用均结束后才能释放或报废。 + +跨服务不使用分布式事务,但不得在持有 Lastleg 数据库事务时调用资源服务。移库命令先独立提交 `PREPARED`(请求哈希、租约、托盘版本、源位置快照和目标快照),再调用资源服务 transition;远程按 operation key 重放后进入 `REMOTE_APPLIED`,随后本地锁定全部 position/计划当前字段并提交 `LOCAL_COMMITTED`。本地失败进入 `COMPENSATION_REQUIRED`,补偿必须核对远端 afterVersion/afterLocation 未被后续作业修改;无法补偿进入人工对账和告警。批量移库按托盘逐项返回结果,不声称跨托盘全局原子。验收必须覆盖“远程成功、本地失败、重放、后续作业后拒绝补偿”。 + +同一 move command 重试必须保持原 `targetSnapshot` 和 `requestHash`;目标普通位被其他托盘占用时原命令返回冲突,由新候选命令重选,不能用同一幂等键静默改投 A0。只有确认远端 operation 已成功时,才允许按 operation key 恢复本地提交。 + +优先复用 `pda/move` 的核心逻辑,不新增绕过资源服务的直接写库接口。 + +### 5.3 Admin Web + +新增海外仓库位正式页面、route、navigation/menu、权限和接口类型,接入已有海外 locations API,不能复用国内 `/platform/v1/locations`。 + +页面支持:海外仓、分区、库位编码、类型、状态、A0 标识、占用摘要;A0 禁止编辑/停用/删除。普通库位改码、改类型、改分区、停用或删除前检查托盘、库存、活动锁和未完成移库,有任何当前引用时阻断。 + +海外入库详情的托盘明细增加: + +- 当前库位编码和库位类型; +- A0 标识; +- 上架状态; +- 单托/批量移库; +- 移库历史。 + +Admin 只调用 Lastleg 平台移库命令,携带当前托盘版本、源位置版本、幂等键、操作原因和源位置快照,并消费服务端返回的 `canMove/blockReason` 及每托结果。该命令经 `gateway-overseas` 的平台路由暴露,使用 `lastleg:inventory:manage` 等后台权限,与 PDA 的 `lastleg:pda:operate` 分离;请求不接收 `customerId`,客户集合由服务端从整托库存 position 解析。 + +### 5.4 海外 PDA + +现有 PDA 已有扫描移库入口。第一期不新增完整“上架任务”体系,但需补充: + +- A0 目标库位展示; +- `sharedOverflow` 文案和状态; +- 虚拟位、普通位、A0 的目标校验; +- A0 到普通位的换绑。 + +一件代发当前存在客户作用域的专用虚拟托盘。该特例不能直接套用公共 A0 规则;如后续需要把一件代发托盘转入 A0,必须先完成公共资源归属和锁模型迁移,并单独验收。 + +## 6. 状态与门禁 + +位置类型和流程状态分开: + +```text +locationKind: VIRTUAL / NORMAL / SHARED_OVERFLOW / UNKNOWN +putawayStatus: PENDING / COMPLETED / RECONCILIATION_REQUIRED +``` + +`putawayStatus` 当前没有现成字段,第一期建议由当前物理位置派生,并在 DTO 中统一返回:`VIRTUAL → PENDING`,`NORMAL/A0 → COMPLETED`,位置为空或多份位置不一致 → `RECONCILIATION_REQUIRED`。后者必须阻断排车、拣货、出库和再次移库,进入修复队列,不能当作普通的未上架。若为了查询性能落库,只能在 `ll_inbound_plan_pallet` 增加一个唯一权威字段,由上架命令写入;`wh_pallet.location_id` 和 `ll_inventory_position.warehouse_location_id/code` 仍是当前位置事实,计划字段只是同步冗余,`ll_inbound_stock_receipt` 与库存流水只保留不可变快照。不能让三处各自推进状态。 + +允许 PDA 收货完成后仍有托盘暂留虚拟位,但虚拟位的 `PENDING` 必须阻断智能排车、PDA 拣货、出库和发车候选;只有物理落到普通位或 A0 后才进入这些履约候选。`INBOUND_COMPLETED` 仍表示库存入账完成,不等于已经完成上架。 + +允许的物理流转为 `VIRTUAL→NORMAL/A0`、`NORMAL↔NORMAL`、`A0↔NORMAL`;`NORMAL/A0→VIRTUAL` 一律拒绝(仅人工对账恢复命令例外),同一 A0 的 `A0→A0` 是幂等 no-op,不增加托盘版本、位置占用或库存流水。平台和 PDA 必须共用这套门禁。 + +## 7. 容量边界 + +第一期按“一托一普通库位”判断普通位是否可用,A0 只解决普通位数量不足,不实现重量/体积累计容量。A0 不是无限容量,也不改变托盘自身 `capacity_cbm`;至少维护当前托盘数、最早进入时间和可配置的软阈值告警,超过硬阈值时停止返回 A0 候选并返回稳定阻断原因。 + +若后续需要按承重或体积判断,还需增加当前库位占用重量/体积、托盘实际重量/体积快照、并发容量扣减和超限转 A0 的事务门禁。 + +## 8. 权限、审计与跨客户 + +库位查询、库位维护、平台移库、A0 换绑和 PDA 现场移库分别定义权限。平台资源实体仍使用公共资源作用域 `customer_id=0`,资源锁使用真实业务客户 ID;A0 共享不等于跨客户越权。普通库位和 A0 的查询、候选、库存过滤均必须以 `warehouseId` 为边界,不能让客户端传入客户或跨仓位置。 + +移库写入当前版本、幂等键、请求哈希、操作原因、操作人、源/目标库位快照和跨服务关联 key。优先复用现有 `wh_pallet_operation`、Lastleg 移库记录和库存流水,不新建无关联的第二套历史。增加定时或人工对账,核对托盘当前位置、活动资源锁、库存 position、计划当前冗余字段和普通位占用事实;发现不一致时记录 `COMPENSATION_REQUIRED`/人工处理,不静默覆盖后续作业。 + +## 9. 验收重点 + +1. 新建托盘转换阶段使用公共作用域虚拟位;复用已有真实托盘时保留其当前位,且不提前占用新的普通位。 +2. 普通位候选仅读,最终命令原子竞争;普通位全部竞争失败后由操作员确认进入同仓 A0。 +3. 多托盘并发进入 A0,A0 始终 `ACTIVE`,互不覆盖、互不误释放。 +4. 覆盖 `VIRTUAL→A0`、`A0→NORMAL`、`NORMAL↔NORMAL`;同一 A0 的 `A0→A0` 返回幂等 no-op,`NORMAL/A0→VIRTUAL` 失败关闭。 +5. A0 单托换绑普通位不影响其他 A0 托盘;普通位并发竞争只能一个托盘成功。 +6. 合托实物托盘移动后,所有入仓单库存位置一致,数量桶守恒并写零差额流水。 +7. `wh_resource_lock.location_id`、`wh_pallet.location_id`、计划当前冗余和库存 position 一致;批次 `reserved_location_id` 仍明确为快照。 +8. `PENDING` 虚拟位库存不进入排车、拣货或出库候选;部分收货按已形成的实物托盘逐托落位,失败可幂等重试。 +9. 位置为空或多份位置不一致时返回 `RECONCILIATION_REQUIRED`,阻断履约和再次移库并进入修复队列;远程物理成功、本地更新失败时可重放/补偿,后续作业已修改远端时补偿必须拒绝并进入对账。 +10. 平台与 PDA 并发同托、停用/改码/改类型/删除被引用库位、跨仓库位、错误版本和幂等键复用均失败关闭。 +11. A0 缺失、重复、编码碰撞、仓库失效、非公共作用域和阈值超限均失败关闭;A0 永远不可编辑、停用或删除。 +12. Admin、库存查询、托盘管理和 PDA 展示同一业务库位码及 `sharedOverflow`;`NORMAL/SHARED_OVERFLOW` 枚举、OpenAPI、Gateway、Fixture 和测试已同步。 +13. 验收夹具至少准备 1 个可用 `NORMAL`、系统 A0 和 2 个托盘;另覆盖无普通位/普通位全停用时进入 A0,以及普通位竞争时只有一个托盘成功。 + +## 10. 待评审决策 + +1. 是否采用“一托一普通库位,普通位不足时由现场确认进入 A0”?建议采用。 +2. 是否允许收货完成但未上架的托盘暂留虚拟位?建议允许,但 `PENDING` 必须阻断排车、拣货、出库和发车。 +3. A0 是否视为合法正式落位并允许履约?建议允许;A0 与普通位一样进入履约候选,受 A0 阈值和现场安全门禁约束。 +4. 第一期是否同时开放 Admin 平台移库,并复用现有 PDA 移库补 A0 语义?建议同时开放,但共用同一 Lastleg 平台移库命令和物理状态机。 +5. 是否每个海外仓固定一个 A0,还是按库区配置多个 A0?建议第一期每仓一个,ID/编码永久稳定。 +6. 第一期是否暂不做重量/体积容量校验?建议暂不做累计容量计算,但保留托盘数/时间阈值告警和超限阻断。 + +## 11. 建议结论 + +建议第一期采用:每个海外仓一个使用公共作用域、按仓库 ID 幂等懒创建的 A0;新建托盘计划阶段使用虚拟位,正式上架时普通位候选优先、现场确认后 A0 兜底;A0 作为合法正式落位并允许履约;Admin 和 PDA 共用 Lastleg 平台移库命令;位置、资源锁、库存和计划当前字段按同一事实同步;跨服务使用可持久化的 Saga 状态机、补偿和对账;暂不实现重量/体积累计容量,但保留 A0 数量阈值和告警。 + +## 12. 开发前置清单 + +开发开始前应先把以下合同一次性冻结,避免各端按自己的理解实现: + +1. 冻结库位枚举和迁移规则:`VIRTUAL/NORMAL/SHARED_OVERFLOW`,将现有 `STORAGE` 统一收敛为 `NORMAL`,同步基线 SQL、增量 SQL、OpenAPI、Fixture、Gateway 和测试。 +2. 冻结 A0 常量和作用域:`customer_id=0`、`location_code=A0`、`zone_code=OVERFLOW`、`status=ACTIVE`、`max_load_kg=999999999.000`;冻结懒创建入口、冲突码和阈值阻断码。 +3. 冻结资源服务状态机和占用事实:A0/虚拟位不改库位状态,普通位 CAS 独占;`wh_resource_lock.location_id` 是当前值,按托盘和 `reservation_key` 精确释放;移库同步锁、托盘、占用事实和操作记录。 +4. 冻结 Lastleg 事实字段:`wh_pallet` 与 `ll_inventory_position` 为当前物理位置事实,计划托盘为同步冗余,`wh_pallet_batch_item.reserved_location_id` 为预留快照,收货凭证/库存流水为不可变历史;`putawayStatus` 由位置派生或只落在一个唯一权威字段。 +5. 冻结平台/PDA 共用移库 API、权限、请求哈希、版本快照、幂等键命名空间和 Saga 状态;先持久化 `PREPARED`,禁止在 Lastleg 事务中直接调用资源服务。 +6. 冻结履约门禁:虚拟位 `PENDING` 不得进入排车、拣货、出库和发车候选;A0 与普通位均可履约;整托移动保持所有库存桶、客户集合和活动业务锁一致。 +7. 冻结验收数据夹具:至少一个普通位、一个系统 A0、两个托盘,覆盖混托双客户、部分收货、普通位竞争、A0 并发、A0→A0 no-op、远端成功本地失败、补偿被后续作业拒绝、A0 缺失/冲突和普通位改码阻断。 + +满足以上清单后,开发可以按 `warehouse-overseas-service → lastleg-service → gateway/OpenAPI → Admin/PDA → 端到端验收` 的顺序拆分实现;任何偏离都应先回写本方案,而不是在代码中引入第二套语义。