49 KiB
Lastleg 业务详细索引
本文件是同服务 AGENTS.md 的按需补充;命中业务后读取相应行/段,不默认全文加载。路径保持以 lastleg-service/ 为根;Java 简写包路径从 src/main/java/com/baiyue/lastleg/ 起算,不相对于本文件的 docs/ 目录。
快速索引
退货预报、签收、库存、合箱与退货出库:
- 客户预报
returnreceipt/controller/MerchantReturnOrderController.java→returnreceipt/service/ReturnReceiptService.java,路径/api/merchant/v1/last-leg/return-orders;客户范围取认证身份,预报按 SKU 保存期望数量。状态PENDING_RECEIPT → PARTIALLY_RECEIVED → COMPLETED表示签收进度,不等于换标或出库完成。 - PDA 后端签收
returnreceipt/controller/PdaReturnReceiptController.java,前缀/api/pda/v1/last-leg/return-receipts,依次start/sku-candidates/confirm;按冻结仓和退货单校验实收,不允许未预报 SKU 或超量。确认必须传提交前全单累计实收量expectedReceivedQuantity,在订单锁内校验;响应丢失后换新键重复提交旧快照返回冲突,刷新后重新操作,同键幂等请求不得重复入账。确认同时生成签收箱与 SKU 库存;设备页面是否接通另查 PDA 工作台,不能以 Controller 存在代替前端接入。 - 库存/箱号
returnreceipt/controller/PlatformReturnInventoryController.java、MerchantReturnInventoryController.java→returnreceipt/service/ReturnInventoryService.java,前缀分别为/api/platform/v1/last-leg/return-inventory、/api/merchant/v1/last-leg/return-inventory;PDA 合箱入口PdaReturnInventoryController.java的/packing/orders、/cartons/merge、/cartons/{cartonMark}/label。同仓同客户选取退货单及可用 SKU,合箱扣减来源箱数量并生成目标箱,空来源箱标为MERGED,不得跨客户或超出可用量。实物合箱/cartons/merge-boxes必须提交扫码时全部来源箱 SKU 数量expectedSourceQuantities,来源行锁内核对完整快照;旧快照换新键也拒绝扣减,重新扫描后才能提交当前实物。内部退货换标调用从已锁定任务及来源库存生成快照。 - 退货出库继续进入
delivery/service/DeliveryTransferService.java的RETURN_OUTBOUND,按退货库存可用箱唛创建与拣货;不能与exceptionorder/的派送退回恢复库存混用。契约见src/main/resources/openapi/overseas-business-v1.yaml,表入口src/main/resources/db/lastleg.sql的ll_return_*。 - 客户箱号管理使用
GET /api/merchant/v1/last-leg/return-inventory/cartons按实物箱分页,库存箱内 SKU 明细仍使用根查询;两者均按登录客户精确隔离。合箱sourceBatchNo/sourceReturnNo保存实际消耗来源的去重业务编号(多个以逗号分隔),不得写状态或包含未取货订单。ReturnCartonBarcode按箱号生成 Code 128 SVG,打印内容不得使用内部客户/仓库 ID。
人工计划自动生成托盘与资源重试:inboundplan/controller/InboundPlanController.java → inboundplan/service/ContainerInboundPlanConversionCoordinator.java 的 convertManualAndAutoMaterialize;保存单票后重读全柜预览,全员为 PLANNED/RESOURCE_PENDING、预览就绪且存在待绑定资源时自动调用柜级 confirm。一票柜立即触发,多票柜在最后一票齐全后触发;资源异常沿 resourcePendingPlanCount 和原柜级预览/确认入口重试,不新增独立正常人工步骤。回归入口为 src/test/java/com/baiyue/lastleg/inboundplan/service/ContainerInboundPlanConversionCoordinatorTest.java。
柜级预览同时返回 sourcePalletCount/supplementPalletCount/targetPalletCount:头程冻结托盘码原样保留,海外仓最终托数较多时按同柜同 FBA 编码序列补足差额,不因头尾装托密度不同阻断;页面必须分别展示头程已有、海外补充和最终托数,不能把最终总数误称为“本次新增”。
头程来源托盘快照:service/LoadingCompletedConsumer.java、mapper/LastlegSourcePalletMapper.java;来源标签随装柜完成事件落入 ll_order_source_pallet,后续由 inboundplan/service/InboundPlanService.java 与 ContainerInboundPlanConversionCoordinator.java 传递给海外资源批量预留。排查标签跨区丢失时沿此链路核对,不能在尾程页面重新生成头程托盘码。
| 功能 | 入口 |
|---|---|
| 内部模块化基线与依赖分析 | docs/lastleg-internal-modularization-baseline.md、scripts/analyze-lastleg-dependencies.mjs、scripts/analyze-lastleg-dependencies.test.mjs、src/test/resources/architecture/lastleg-slice-dependencies.json;只约束本服务内部包依赖,不改变部署单元或数据库边界 |
| 事件接收、平台订单列表/详情、柜级进度、目的仓调整 | src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java;/api/platform/v1/last-leg/orders/container-summaries 按 sourceContainerBookingId 服务端聚合全柜订单、入仓码、有效计划及托盘资源异常并分页返回全部稳定来源柜,已完成计划转换的柜仍保留用于接柜仓查询与修改;单票手工计划保存后,整柜最后一票齐全时自动生成并绑定全柜托盘码,一票柜立即生成,resourcePendingPlanCount 只作为失败重试事实;/{sourceContainerBookingId} 返回柜内全部订单/入仓码/计划状态且不受订单分页截断;plannedArrivalAt 只取装柜完成事件 estimatedWarehouseArrivalAt 冻结到 ll_order.estimated_warehouse_at 的正式事实,按其升序、null 最后、稳定柜 ID 排序;目的仓调整仅接受国内 Warehouse Service 平台公共 ACTIVE + FBA 主档,写入前由 service/FbaWarehouseValidator.java 经内部只读目录校验 ID/编码/名称一致 |
| 尾程订单完整计划、柜级到仓时间、暂扣与人工节点 | controller/LastlegOrderController.java、service/LastlegOrderCommandService.java、inboundplan/service/InboundPlanService.java、dto/LastlegOrderCommandDtos.java;Admin 清关提柜列表通过 /orders/container-arrival-times/query 批量读取整柜预计/实际到仓,预计时间在任何入库计划生成前按柜级版本与幂等键人工维护并原子同步全柜 ll_order.estimated_warehouse_at,实际时间只取海外 PDA 会话首次 receiving_at。单票计划写入校验订单版本、客户范围、订单冻结入仓码、稳定接柜仓 ID、当前仓库主档与幂等键;预计到仓日期不接受计划请求输入,必须锁定来源柜,并由 ContainerWarehouseArrivalService 统一解析:人工维护日期或已有计划冻结日期优先,未维护未建计划时读取 Firstleg 权威到港当地日期加两个自然日;缺失或同柜不一致时阻断,清关提柜、日历和人工/自动计划统一使用 plannedDate,默认日期不补零点,首个计划创建后按已有计划日期冻结;一件代发人工计划必须逐托沿用不可变头程封托的 DP 托盘码和箱数,托数及各托箱数均不得重新分配,禁止保存无法绑定真实资源的半成品计划;接柜仓编码/名称以该 ID 当前目录快照为准,不要求订单残留旧展示快照。暂扣/解除写不可变历史并发布 InboundOrderDetainedOutboxService;新增暂扣只允许完成入库且尚未进入出库流程的订单,解除暂扣不受该状态限制;暂扣只阻断排车和出库,不阻断入库计划或入库作业。人工节点必须一次提交固定八行完整快照且至少一个时间,首次保存后永久使用 MANUAL 来源,不得回退柜级节点或反向修改头程船节点 |
| 客户尾程订单列表与详情 | controller/MerchantLastlegOrderController.java;客户范围只取认证身份,稳定类型/状态见 model/LastlegOrderType.java、LastlegOrderStatus.java |
| 装柜完成事件消费 | service/LoadingCompletedConsumer.java;当前生产信封为 FIRST_LEG_LOADING_PLAN_COMPLETED V3,消费端除冻结 V2 货物事实外,按 sourceContainerBookingId 幂等投影物理柜五类国内装柜照片;FBA 可按箱数且不生成逐箱作业账,OVERSEAS 必须逐箱校验并生成箱级事实;V1/V2 仅用于历史事件读取 |
| 尾程单号生成 | service/LastlegOrderNoGenerator.java、service/LastlegDocumentNumberGenerator.java;头程来源使用14位 WC + yyyyMMdd + 四位流水,建单经 service/LoadingCompletedConsumer.java 在来源柜锁内复用已有号,正式格式见工作区 docs/业务单据编号契约.md |
| 订单查询与更新 | service/LastlegOrderService.java |
| 业务 Inbox | entity/LastlegEventInbox.java、mapper/LastlegEventInboxMapper.java |
| 尾程订单、明细、状态历史 | entity/LastlegOrder.java、LastlegOrderDetail.java、LastlegOrderStatusHistory.java 及对应 Mapper |
| 独立尾程入仓单快照 | entity/LastlegInboundOrder.java、mapper/LastlegInboundOrderMapper.java;按客户与来源入仓标识冻结 inboundCode、来源/根订单及目的仓,不以尾程订单行代替入仓单聚合 |
| 内部令牌保护 | security/InternalServiceTokenGuard.java |
| 统一认证与撤销 | 复用 common-security 的Redis会话及Auth/IAM权威指纹校验,故障拒绝访问;Lastleg不保存独立授权投影 |
| 入仓计划分页、柜级预览、转换与详情 | 平台 inboundplan/controller/InboundPlanController.java,客户只读 inboundplan/controller/MerchantInboundPlanController.java;共用 inboundplan/dto/InboundPlanRequests.java、InboundPlanListResponse.java、ContainerInboundPlanPreviewResponse.java、inboundplan/mapper/InboundPlanQueryMapper.java、inboundplan/service/InboundPlanService.java;客户入口固定 /api/merchant/v1/last-leg/inbound-plans,范围只取 Token。/container-preview 按全柜返回 FBA/一件代发/私人地址分组、托数、就绪、阻断项及 snapshotVersion/snapshotHash;ContainerInboundPlanConversionCoordinator.java 用唯一柜级 Saga 锁柜重算,先经 WarehouseResourceGateway 批量预留真实托盘,再本地原子生成剩余独立计划并确认资源,本地失败只补偿未确认资源,远端确认失败的重放只重试确认,同柜不同幂等键也不得产生第二批资源 |
| 托盘分配算法 | inboundplan/service/PalletAllocator.java |
| FBA 预约创建、详情与编辑 | InboundPlanController.java 的 /fba-appointments/**、inboundplan/service/InboundFbaAppointmentService.java、BatchFbaAppointmentRequest.java、UpdateFbaAppointmentRequest.java |
| PDA 入仓、实收、上架与完成 | inboundplan/pda/controller/ReceivingContainerController.java、inboundplan/pda/service/ReceivingContainerService.java、内部编排 InboundPdaWorkflowService.java;公网入口固定为 /api/pda/v1/last-leg/receiving-containers/**,/warehouse-profile 只按 Token 绑定仓解析共享仓库中文名称与编码,旧 planNo/allocationId Controller 已移除。多计划同柜返回计划级 assigned/mergeAllowed/urgent/mergeHint;普通 FBA/OVERSEAS 分别使用 bulk/carton 收货,一件代发只允许 /current/receipts/pallet 按国内冻结来源托盘整托收货。合托只作现场可选提示;全部计划完整绑定真实 pallet/location/lock 前不得查看、准备或签收该柜,首扫只确认已预留绑定,不得重新分配计划资源 |
| FBA收货短少确认 | inboundplan/controller/InboundPlanController.java 的 /{planNo}/confirm-shortage → InboundPdaWorkflowService.confirmShortage;仅平台入库编辑权限指定客户操作,要求待差异核对、原版本及应收/实收快照、原因和幂等键。仅同柜次同目的仓收货组单票且冻结申报单SKU可按总数确认,多SKU及跨票自动分摊拒绝;逐SKU实收不得超原申报,确认后保留应收与现场结束时间,按实收生成库存和原计租批次,计划/PDA任务进入 PARTIAL_INBOUND,尾程订单完成入库;同事务交接原预排,明确零实收的托盘同步移除预排明细、释放本车占用及预约分配并重算整车货量;缺少零实收证据或已有拣货拒绝。不重复发布卸柜事件。无实收、超收或逐SKU事实不一致拒绝;空预留托盘不能再次作为计划货源排车。 |
| PDA 卸柜照片上传、归属校验与预览 | ReceivingContainerController.java 的 /current/unload-photos/files、/current/unload-photos/files/{fileId}/download,网关 inboundplan/pda/gateway/UnloadPhotoFileGateway.java、HttpUnloadPhotoFileGateway.java;File 类型固定 OVERSEAS_UNLOAD_PHOTO,下载只允许当前会话已签收的 fileId 并返回 no-store |
| PDA 国内装柜照片对照 | ReceivingContainerController.java 的 /current/domestic-loading-photos/{evidenceType}/content、ContainerLoadingEvidenceMapper.java、DomesticLoadingEvidenceGateway.java;元数据按物理柜投影,不保存客户,下载必须经当前 PDA 会话鉴权并复用 FILE_SERVICE_URL 只读访问 File 服务,单图不可用不改变拆柜状态 |
| 卸柜照片与海外卸柜完成 Outbox | ReceivingContainerController.java、ReceivingContainerService.java、inboundplan/pda/service/InboundUnloadCompletedOutboxService.java、entity/LastlegEventOutbox.java、mapper/LastlegEventOutboxMapper.java;多计划同柜只能在全柜完成后调用 publishContainerCompleted,按来源柜预订 ID 幂等发布一次,完成时刻与拍照时刻分离;事件模型见 ../common-event-contract/AGENTS.md |
| 拆柜打托成本与客户应收分摊 | inboundplan/service/DestuffingPalletCostJob.java、DestuffingPalletCostService.java;仅全柜所有 PDA 入仓任务完成后执行,按实物托盘单价汇总并按客户实收 CBM 分摊,尾差落最后一位客户;每份分摊发布 OVERSEAS_CONTAINER_FEE/DESTUFF_PALLET 应收费用来源,不直接改国内钱包 |
| 尾程暂扣状态回传 | service/InboundOrderDetainedOutboxService.java;暂扣与解除暂扣在订单更新事务内按 sourceInboundId 写入海外到国内 Outbox,国内按来源入仓单绑定更新 |
| 派送完成回传 | delivery/service/DeliveryCompletedOutboxService.java;签收资料保存并自动完成时在业务事务内按关联 sourceInboundId 分别写 OVERSEAS_DELIVERY_COMPLETED Outbox,同车不同 FBA 派送单互不代完成;不保留人工完成入口 |
| 入仓后 SKU 库存余额、库位和流水 | inventory/;平台只读查询与非出库库存命令分别从 inventory/query/controller/InventoryQueryController.java、inventory/command/controller/InventoryCommandController.java 进入 |
| 海外PDA盘点、同仓整托移库、托盘组拆换绑与换标现场作业 | pda/stocktake/、pda/move/、palletops/、relabel/pda/;版本化契约 src/main/resources/openapi/overseas-pda-operations-v1.yaml;全部范围取自PDA Token,排车和出库使用独立 PDA Controller |
| 贴标申请、箱唛/SKU/组合换标与 PDA 完工 | relabel/、relabel/pda/;一条 ll_relabel_item 对应一个客户填写的我方箱唛任务,CARTON_LABEL、SKU_LABEL、CARTON_AND_SKU 分别保存箱唛、SKU 或组合需求,SKU 子项 JSON 数量按件;涉及箱唛换标时整张申请只能有一个 FBA 目的仓,服务端拒绝明细目的仓不一致的请求。普通尾程入库没有箱唛到实物托盘的精确快照,历史入仓库存仓位只能作为冻结记账锚点和现场大概位置,禁止把该仓位解释为箱唛已绑定具体托盘。库存只按箱任务冻结一次。涉及箱唛换标时申请必须选择 TRUCK/EXPRESS/PICKUP/AMAZON_PICKUP/CUSTOMER_PICKUP,仅快递方式可要求打印并上传面单;PDA 扫我方箱唛完成逐箱事实和照片,仓库复核后等待客户查看照片确认。对外换标状态只投影为 PENDING_PROCESSING/COMPLETED(待处理/换标完成),PROCESSING、CUSTOMER_CONFIRM_PENDING、READY_FOR_OUTBOUND 只属于内部作业节点且不得直接出现在业务列表;客户确认后仅平台卡车 TRUCK 进入 WAIT_PRE_DISPATCH;快递及普通自提、亚马逊自提、客户自提均直接创建换标出库单,普通库存仍复验实际托盘绑定,已分配仓储转运保留原转运出库链。客户确认前不得进入出库 |
| PDA 仓内通用扫码与操作历史 | pda/warehouse/controller/PdaWarehouseController.java、pda/warehouse/service/PdaWarehouseService.java;解析库存、库位和托盘码并查询操作历史,固定要求 lastleg:pda:operate 与 ROLE_WAREHOUSE_PICKER,仓库范围只能取自 PDA Token |
| 船节点接收与海外 PDA 可收货门禁 | shipping/service/ContainerShippingNodeConsumer.java、shipping/entity/ContainerShippingGate.java、shipping/mapper/ContainerShippingGateMapper.java;按 containerBookingId 柜级接收,信封使用 tenantId=0 且门禁不存客户;只单调接受实际标准节点,PICKUP_READY 及以后才允许对应柜进入 PDA 收货候选 |
| 平台排车列表/详情与智能预排 | dispatch/controller/DispatchQueryController.java、SmartDispatchController.java、dispatch/mapper/DispatchCandidateMapper.java、dispatch/service/DispatchService.java;草稿柜 entity/DispatchDraftContainer.java、mapper/DispatchDraftContainerMapper.java,托盘锁 DispatchPalletLock.java;入口 /api/platform/v1/last-leg/dispatches,路线由当前已排托盘的 FBA 仓编码去重后按稳定顺序以英文逗号连接,预排与普通编辑请求均不接收路线;查询、预排与执行边界见“海外仓全流程开发路由” |
| 排车编辑、PDA 出库、派送与交接 | dispatch/controller/DispatchController.java、dispatch/pda/controller/DispatchPdaController.java、delivery/controller/DeliveryTransferController.java、MerchantDeliveryController.java、OutboundPdaController.java |
| 仓储资源调用 | inboundplan/pda/gateway/WarehouseResourceGateway.java、HttpWarehouseResourceGateway.java |
| 仓储转运、历史订单/入仓绑定、可选换标联动 | transfer/controller/WarehouseTransferController.java、transfer/service/WarehouseTransferService.java |
| 一件代发接收、SKU/箱唛库存 | 内部接收入口 dropship/controller/DropshipOrderController.java 的 /internal/v1/lastleg/dropship/receive,必须经 InternalServiceTokenGuard;版本化契约 src/main/resources/openapi/lastleg-internal-dropship-v1.yaml;处理 dropship/service/DropshipOrderService.java、dropship/entity/、dropship/mapper/ |
| 货物废弃、待审锁箱、审批扣减 | discard/controller/CargoDiscardController.java、discard/service/CargoDiscardService.java |
| 客户拜访、派送状态与签收附件 | customervisit/controller/CustomerVisitController.java、customervisit/service/CustomerVisitService.java、delivery/model/DeliveryStatusPolicy.java;派送单返回服务端 allowedActions/version,状态只允许单调迁移,同状态幂等;人工动作不再提供完成派送,签收资料保存后自动完成当前目的FBA仓派送单 |
| 派送三类资料、摘要与历史证据 | delivery/controller/DeliveryDocumentController.java、delivery/service/DeliveryDocumentService.java、delivery/gateway/DeliveryDocumentFileGateway.java、src/main/resources/openapi/delivery-documents-v1.yaml;固定装车照片 LOADING_PHOTO、BOL文件(含签名)BOL、签收文件(POD签收/快递签收)RECEIPT,按已保存类别计数 x/3。每类0至20文件,文件精确绑定客户、按目的FBA仓拆分的派送单和类别;全量保存共用派送版本并记录不可变历史,替换删除仅解绑,未保存上传不计数、不授权下载。RECEIPT 成功保存进权威快照时事务内自动完成当前派送单,单纯上传不触发完成 |
| 异常订单与派送退回入仓 | exceptionorder/controller/ExceptionOrderController.java、exceptionorder/service/ExceptionOrderService.java、exceptionorder/gateway/ExceptionEvidenceFileGateway.java、inventory/command/service/InventoryCommandService.java;证据固定按客户、异常单 ID 和 OVERSEAS_EXCEPTION_EVIDENCE 校验。退回候选按原出库拣货事实冻结,不接受自由 SKU 或数量;异常按 PENDING→PROCESSING→COMPLETED(待处理可取消)、版本与幂等键流转。退回海外仓须把实收按原出库来源恢复到服务端校验的真实托盘/库位并写 RETURN_STOCK_IN 流水,库存恢复、异常完成与派送 RETURNED 在同一事务完成 |
| 海外采购、客户下单、采购审核、客户/采购仓候选与操作流水 | procurement/controller/、procurement/service/OverseasProcurementService.java;客户入口按 merchant:cross-border:procurement:list/create 拆分且只取 Token 客户范围;OpenAPI src/main/resources/openapi/overseas-business-v1.yaml |
| 海外业务资金指令与结果 | wallet/WalletCommandOutboxService.java、WalletCommandCompletedConsumer.java、WalletResultHandler.java;采购、换标、人工海外费用和仓租通过 WALLET_COMMAND_REQUESTED 请求 Finance 冻结、结算或释放,并以 WALLET_COMMAND_COMPLETED 幂等回写业务资金状态;不得同步调用 Finance 钱包或在 Lastleg 自建余额账本 |
| 海外业务费用来源 | financefee/controller/PlatformOverseasFeeSourceController.java、financefee/service/OverseasManualFeeService.java、OverseasFeeSourceService.java、OverseasFeeBusinessGuard.java、OverseasFeeOutboxService.java;费用必须匹配当前客户的真实服务单,公共费用目录通过 Firstleg 内部接口读取。头程附加费审核后进入账单;海外人工应收创建先冻结、审核后扣款,驳回或取消释放,扣款成功后仅登记本地已收来源,不再推送财务账单;采购、换标原业务收费仍按其独立资金身份处理。头程账单费用及供应商应付经直接 Outbox 投递 Finance,余额应收仅通过资金指令取得扣款回执,OpenAPI src/main/resources/openapi/overseas-finance-fees-v1.yaml。新菜单列表权限与财务编辑、审核按钮权限分离;本地检查不能代替真实资金、账单联调 |
| 自动仓租及出库资金门禁 | storagerent/;实际 PDA 收货建立原计租批次,头程自有海外仓目的地与仓储转运用中转阶梯,退货用原托阶梯;每24小时进一天,部分出库只承担原批次份额,余货不重置库龄。实际出库命令前单独提交资金预校验,冻结成功后才允许扣库存,按真实出库部分生成待审核仓租,订单费用与海外费用管理展示实际入库、出库、原计算额及实扣额;审核前可调价且说明必填,批准后沿原冻结按实额结算,驳回释放冻结;全部仓租使用余额及授信。客户仓租配置优先,无配置按报价表默认,内部查询失败不得回退。预占保存配置版本、完整阶梯、USD/CNY 原币及结算汇率快照,实际扣款不读最新价格。发布及真实验收清单见 docs/overseas-fees-storage-rent-release.md |
| 美国/本土卡车订单 | trucking/controller/PlatformUsTruckOrderController.java、MerchantUsTruckOrderController.java 及同子域 Service/DTO;客户入口按 merchant:cross-border:domestic-trucking:list/create/edit 拆分 |
| 本土散板订单 | looseboard/controller/PlatformLocalLooseboardController.java、MerchantLocalLooseboardController.java 及同子域 Service/DTO/Entity/Mapper;客户入口按 merchant:cross-border:pallet-shipping:list/create/edit 拆分;OpenAPI src/main/resources/openapi/overseas-business-v1.yaml |
| 海外仓报价、月度汇率、车辆主数据 | 报价 masterdata/quote/、汇率 masterdata/exchange/;车辆入口 masterdata/vehicle/adapter/in/web/PlatformVehicleController.java,应用服务 application/VehicleApplicationService.java,持久化端口与适配器 application/port/out/VehicleRepository.java、adapter/out/persistence/;OpenAPI src/main/resources/openapi/overseas-master-data-v1.yaml,车辆档案不含派送履约历史 |
| 跨服务 HTTP 客户端装配 | config/LastlegRestClientFactory.java、config/LastlegRestClientProperties.java;目标地址与内部服务令牌只来自配置,不在业务子域手工创建客户端 |
| 数据库基线、表索引与中文注释契约 | src/main/resources/db/lastleg.sql、src/test/java/com/baiyue/lastleg/schema/SchemaChineseCommentContractTest.java |
| 测试 | src/test/java/com/baiyue/lastleg/ |
| 真实事件消费验收 | src/test/java/com/baiyue/lastleg/service/TaillegRealConsumerAcceptanceTest.java;覆盖真实 V2 建单、独立入仓单快照及下游转运/换标可消费性 |
数据库表索引
| 业务域 | 表名 | 中文含义 |
|---|---|---|
| 车次外接 | ll_fleet_external_booking |
外接询价、约定报价与承运容量预订;历史进入车次历史快照 |
| 核心订单与事件 | ll_loading_*、ll_event_*、ll_order*、ll_inbound_order* |
事件收件/发件、尾程订单、明细及状态历史 |
| 入仓计划与作业 | ll_inbound_plan*、ll_inbound_pda*、ll_inbound_pallet_merge、ll_inbound_fba_appointment* |
入仓计划、托盘分配、PDA 作业、合托及 FBA 预约 |
| 库存 | ll_inventory_*、ll_inbound_stock_receipt、ll_return_stock_receipt |
库存余额、仓位、流水、幂等及原入仓/退回实收来源;同托多票数量逐明细核平,重派按实际退回凭据扣减额度 |
| 派车与出库 | ll_dispatch_*、ll_outbound_*、ll_delivery_order |
派车、PDA 拣货、出库及派送 |
| 转仓与换标 | ll_warehouse_transfer_*、ll_relabel_* |
转仓单、库存转移、目标收货及换标 |
| 一件代发与废弃 | ll_dropship_*、ll_cargo_discard_* |
托盘/箱唛库存、流水和废弃审批 |
| 采购、卡车与本土散板 | ll_overseas_procurement_*、ll_us_truck_*、ll_local_looseboard_* |
海外采购、美国卡车运输及本土散板订单 |
| 通用资料 | ll_overseas_quote*、ll_monthly_exchange_rate*、ll_vehicle |
海外仓报价、月度汇率及车辆档案;不保存车辆派送履约历史 |
海外仓全流程开发路由
-
车次成本:
fleet/trip/FleetTripCostService.java、fleet-trip-costs-v1.yaml;按DEPART实测及外接实收分摊,不产生客户账单/付款。 -
报价/统计:
fleet/quote/、fleet/reporting/,合同fleet-quotes-v1.yaml、fleet-operations-v1.yaml;实测凭证按车次归属,仅当前/历史版本引用可下载。 -
自有业务运营:
fleet/ownedbusiness/、fleet-owned-business-v1.yaml,共同规则见../../../docs/海外仓业务/自有业务首版开发契约.md;原排车装车后三阶段按无实际拼车货物展示,车辆归属独立,仅补金额、逐目的地到达、里程、油耗与照片。原详情只经dispatch.port.DispatchDetailReader读取,原装车事务经OwnedBusinessOwnershipCapture捕获归属,不另建履约流程。 -
外配业务运营:
fleet/outsourcedbusiness/、fleet-outsourced-business-v1.yaml,共同规则见../../../docs/海外仓业务/外配业务首版开发契约.md;原排车装车后三阶段按实际接入需求池拼车货物展示,车辆归属独立,包含MASTER/EXTERNAL两种资源模式。只登记整车承运金额、逐目的仓到达和五类装车照片(每类最多5张),无里程/油耗或成本拆分。内部可信作用域复用四表,入口权限与自有严格分开,不引入第二履约模型。 -
外接选价合同:
fleet-quote-candidates-v1.yaml、fleet-trips-v1.yaml;来源快照与成交价分开,确认承运复验。 -
在途交接:
fleet/exception/、fleet-handovers-v1.yaml;后台确认交接,旧资源分别结束任务,DEPART不重写。 -
车辆履约投影:
fleet/trip/PlatformVehicleFulfillmentController.java→VehicleFulfillmentService/VehicleFulfillmentMapper,合同fleet-vehicle-fulfillment-v1.yaml。派送同时要求vehicle:list与delivery:list,更换历史要求vehicle:list与trip:list;客户与排车双键关联,不跨库。按不可变DEPART/ASSIGN关联车辆,首次分配不算更换,不按当前车牌猜历史。只读重复读事务分页;不返回内部ID或整份客户快照。 -
海外仓全流程连续覆盖尾程订单、入仓计划/PDA 入仓、换标、产品与箱唛快照、库存、转运、废弃、采购、本土卡车、本土散板、排车查询/智能预排、排车编辑/取消/发车、PDA 拣货与出库、一件代发、转派送、客户派送单、异常退回和客户回访。
-
服务端正式入口包括
dispatch/controller/DispatchController.java、dispatch/pda/controller/DispatchPdaController.java、delivery/controller/DeliveryTransferController.java、delivery/controller/OutboundPdaController.java、delivery/controller/MerchantDeliveryController.java、exceptionorder/与customervisit/;设备页面是否可用以海外 PDA 正式路由和能力开关为准,未由 PDA 承载的操作由 Admin/Client 完成。 -
平台一件代发出库候选由
/outbound-orders/one-click-dropship/candidates统一返回已完成入库且仍有可用箱唛、SKU 汇总和库存仓位绑定的托盘明细;查询不强制先传customerId,未传时跨客户展示供页面分页筛选,创建时必须从所选候选解析为唯一客户和唯一海外仓,禁止一张出库单跨客户或跨仓。创建后出库单按WAIT_PICK → PICKING → PICKED → OUTBOUNDED推进;海外 PDA 扫描托盘码时通过整托命令原子拣取该托盘在当前任务内全部剩余箱件和 SKU,任一库存明细失败须整托回滚,逐箱拣货入口继续保留。 -
排车主流程以工作区
docs/海外仓业务/排车列表统一作业改造契约.md为正式入口:一张排车单代表一趟车;Admin/PDA确认loading-complete时,同一事务自动按客户与目的仓生成派送单并发车,成功直接进入DELIVERING。公开手工安排派送及/depart入口已删除;内部AFTER_LOADING仅是同事务中间态。DispatchPdaWorkflowService按公开status、stage、recordVersion和actions返回作业事实与资格,Admin/PDA 共用服务端规则;BOL 与签收资料归拆分后的派送单,费用按其来源去重,不以旧转派送阶段或前端推算代替资格校验。 -
排车主单允许同一起运仓的跨客户货物同车,每条来源保留客户、库存与费用归属;不再要求用户先建独立车次再关联多张客户排车。Fleet 的资源、司机证照、容量、历史与附加能力继续沿
fleet/trip/定位;计划或资源变化对审核、实载确认的影响按正式契约及服务端结果核对,已拣货事实不能因改派或取消被直接抹除,实际发车后不得伪造资源释放。 -
实物装载按仓库托盘去重计量,客户货量与物理占位分别保存;重量/体积由已有实测自动带出,未知保持空值且不阻塞装车,不要求逐托补填,不用计费材积重回填。装车前真实拣货须核平,后台不能补录拣货数量绕过;装车发运编排不重复执行已完成的库存扣减;费用未明确时保持未计费,不要求理货员补费用,也不填零,真实已明确外接报价仍按原来源防重发布。来源、审核、实载与资源版本变化按正式契约重新校验,保留操作历史;双端使用同一版本与原请求键核对结果,任何失败均不得伪造完成事实。
-
车次结案只消费派送事实:实签须signedAt/signedPackages,实退须原来源入账流水/RETURN_COMPLETED;实签加本趟实退等于原实发且余量为零。拒收、待退回、取消不算完成,时间取最后实际终态。结案不改客户状态/费用、不释放占用;车/司机分别记录实际结束时间地点,车辆状态与释放同事务。过期证照不阻止旧任务结束、不恢复新任务资格;原键重放不影响后续车次。
-
已签收后另发起退货保留原运输的签收事实,后续退货不重开原车次、不被车次结案改写;未签收货物仍按实际退回完成核对。
DeliveryDocumentService.save首次保存签收资料触发完成时要求关联出库已OUTBOUNDED,未出库可以准备BOL/装车资料但不能提前完成派送。 -
排车执行与
delivery/Controller 一同注册;平台排车查询和智能预排入口分别沿lastleg.dispatch-query-api.enabled、lastleg.smart-dispatch-api.enabled配置定位,启用现状以配置和实际路由核对。平台查询按全局排车号及来源归属加载跨客户同车明细,客户接口保持本客户隔离;预排创建逐项校验同一起运仓及各来源客户,不能强制整张排车只含一个客户。草稿托盘锁仍按操作人与草稿标识持有,释放、失效及转正式锁由服务端原子处理。 -
仓库 ID、类型、状态、地址和能力唯一权威归国内
warehouse-service;海外库位、实物托盘和资源锁归warehouse-overseas-service/且只引用同一国内warehouseId。Lastleg PDA 通过 Warehouse Overseas 内部资源入口校验该国内共享收货仓,不得要求海外数据库重复建仓;浏览器 API 统一经gateway/。 -
外接预订以父车次行锁串行确认容量;DRAFT不占位,CONFIRMED占位,CANCELLED释放,创建/确认/取消保留请求键与内容摘要。报价与记录快照进入既有车次历史。重新核对自有货计划不得占用已确认外接容量,换小车须能容纳两者;同车只换司机不额外要求先重新核对容量。CONFIRMED和RECEIVED尚未绑定实际发运时阻止整车发车;通过真实来货实测、运费确认及来源核对后转为BOUND,解除未开工排车关联回RECEIVED。报价或登记来货不冒充库存/已出库;取消未执行预订仅释放承运容量,来货及已入库货物保留。
-
外接约定运费由
fleet/trip/FleetBookingFreight按排车来源、绑定车次、目的地和完整实收量复核;DeliveryTransferService在原转派送事务内发布到对应目的地派送单的OVERSEAS_DELIVERY_FEE,通过既有费用Outbox进入Finance。预览只读且使用dispatch:list;确认原货物/报价摘要后写入,全部外接货禁止再填派送应收,额外装车/买约仍独立。明确零约定留操作事实不发布虚构零费用。首版确认承运币种限现行费用支持的CNY/USD,原头程费用覆盖按下条处理。 -
原运费覆盖由
delivery/service/DeliveryFeeCoverageService与DispatchFeeCargoMapper按实际库存组成及本次已拣数量还原,核平每条排车箱数;不可用MIN展示入仓码替代整托全部来源。FinanceFeeCoverageClient批量只读Finance,平台转派送按费用ID/版本/hash和当前货物/目的地摘要复核,只有全部相关货物已覆盖的服务才免新增应收,供应商应付独立。客户接口禁止自行声明覆盖。确认快照保存到ll_outbound_order.fee_coverage_json,原费用不复制、不抵扣金额、不生成伪零费用;详情按本派送货物过滤只读快照,之后财务更正不自动改写旧发运。普通无引用请求保持原新增收费要求。 -
派送出库取消按dispatch→outbound加锁,核对排车未拣货/发车并先解除车次关联;取消出库不代替取消排车占用。来源订单按本次转派送历史的原状态恢复,显式清空出库/派送关联并增加版本,不统一写RECEIVED。只对本派送当前已审核有效的DELIVERY_FEE应收申请作废,其他费用保留核对,完整取消原因分段保存在派送操作记录。
-
financefee/state/OverseasFeeCancellation从不可变费用Outbox批量读取voidStatus;请求/拒绝/未知结果保留ACTIVE,只有Finance匹配APPLIED/DUPLICATE后,在投递审计同事务中落VOIDED。撤销候选必须等待原发布成功,未满足前置的PENDING撤销在LIMIT前排除,避免堵住后续投递;过期PROCESSING仍按租约恢复。费用调整使用READ_COMMITTED并在来源行锁后检查未决撤销,禁止旧快照漏检。前端与API区分待财务确认、待财务处理和确认作废,不能以物流取消或HTTP请求发出认定账务已撤销。
事件建单规则
- 只接受冻结契约支持的事件类型和版本;校验事件 ID、租户、区域、Payload 哈希及必要字段。
- 同一冻结来源柜
sourceContainerBookingId共用一个14位WC + yyyyMMdd + 四位流水尾程单号;首个交接事件在来源柜行锁内取号,后续客户或跨日交接复用该柜已保存的编号,事件重放不取新号。不同来源柜复用真实集装箱号时仍分别编号;payload.orders每项按sourceInboundId独立落一条雪花 IDll_order,并保存对应明细和初始状态历史。真实containerNo继续保留作实箱快照,cabinetNo仅为柜次展示,不替代稳定来源柜身份。 ONE_CLICK_DROPSHIP每个来源入仓单必须携带一个或多个不可变来源托盘快照;每托按 SKU 携带箱数、数量和箱唛。校验订单汇总一致、托盘号/SKU/箱唛唯一后,按托建立档案及 SKU 库存,并建立箱唛和流水。仓库实物托盘由 Warehouse API 分配,不得覆盖来源托盘快照。- 其他订单类型建立入仓单维度的尾程订单与冻结明细,并保留头程推荐托数;尾程入仓计划至少按推荐托数生成计划托盘,PDA 再把箱级事实绑定到海外仓实物托盘。
- 整个事件消费在一个事务内完成;任意订单失败时不得留下部分订单。
- 相同事件重复投递返回既有结果,不重新生成单号或插入明细。来源入仓单唯一约束是最终防线。
- 装柜完成事件只把尾程订单建立为
PENDING_ASSIGNMENT。FIRST_LEG在途库存使用不可变来源入仓码作为计划生成前的inbound_order_no展示快照;平台人工转换成功后,同一事务将订单推进为INBOUND_PLANNED,并把对应库存余额的该快照收口为新planNo。 - 内部接口先执行常量时间令牌校验;缺失或错误令牌不得进入消费事务。
- 通用
/internal/lastleg/events只白名单接收装柜完成、实际船节点与资金结果事件;未知或IAM安全事件必须拒绝。 - PDA 入仓任务完成前必须先通过
/unload-photos保存非空且不重复的卸柜照片文件 ID;照片上传时间由服务端记录,预计报空时间固定为照片上传时间加两天。完成入仓时,库存入账、计划/任务状态推进与OVERSEAS_CONTAINER_UNLOADEDV1 Outbox 必须处于同一事务;事件绑定来源头程货柜身份,不得从前端请求拼装或回查头程数据库。 - 海外 PDA 柜号签收只允许平台
OVERSEAS_WAREHOUSE_PDA_OPERATOR使用lastleg:pda:operate;国内头程 PDA 角色不得进入。PDA 不接收customerId,候选按来源柜跨客户聚合,同柜允许多个客户;选柜会话、三类照片及整柜完成事实只绑定sourceContainerBookingId + containerNo,文件与柜级跨区事件使用tenantId=0平台技术范围。签收后按每个入仓计划自身客户生成任务,扫码、SKU 实收、托盘和库存始终使用入仓码所属票的真实客户。未完成会话按平台操作账号和柜号分别全局唯一;平台管理员和客户账号不得进入该作业入口。
查询与租户约束
- 平台查询范围由可信身份/平台权限决定;商户查询只能使用认证身份中的客户 ID。
- 尾程平台只读查询接口的
customerId为可选筛选条件:平台身份省略时查询全部客户数据,传入时按指定客户过滤;商户身份忽略客户端传入值并始终按 Token 中的自身客户隔离。创建、编辑、审核等需要明确目标客户的写接口仍必须传入customerId。 - 常规入仓库存余额以客户、海外仓、目的仓和入仓单为唯一业务维度并汇总箱数;SKU 继续保存在箱唛、实收和流水快照中,余额 SKU 仅作单 SKU 展示或以
MIXED标识多 SKU,不参与当前唯一维度。 - 托盘是 PDA 出库的作业单位,但不是库存归属单位:
ll_inventory_position通过余额和托盘保存“托盘包含哪些入仓单、各多少箱”的组成明细。合托后的同一实物托盘允许对应多个入仓单,换标冻结和后续出库核算必须按这些明细分别记账;整托出库可扫描托盘一次完成,部分出库先拆托或按明细核平,不得用 FIFO 猜测来源入仓单。普通尾程入库只确认某入仓单在托盘上的箱数,没有逐箱唛到托盘的快照;贴标下单由客户填写我方箱唛,库存仓位只作为冻结记账锚点和现场大概位置,不能据此声称箱唛位于某个具体托盘。贴标任务以我方箱唛为主键,同箱多 SKU 只存为子明细且不重复冻结。 - 换标箱唛经现场确认后执行
pallet-operations/relabel-disassemble:允许仅移动该换标单对应的hold_packages,不得解冻为普通可用库存;事务内同步更新ll_relabel_item.source_inventory_position_id。若客户确认后已生成换标出库单,还必须在拣货开始前同步拆分或改写ll_outbound_pallet_item的托盘、箱唛、箱数与来源仓位快照,使 PDA 后续按新旧托盘分别扫描且每个指定箱唛只出库一次。 FIRST_LEG在途、计划转换与 PDA 入账统一使用尾程订单冻结的实际接柜海外仓 ID/编码/名称,禁止使用国内来源仓或 FBA 目的仓冒充;接柜仓变更必须在同一事务内修订尚未形成实物仓位的在途余额并写零差额审计流水。余额分页、汇总和 CSV 默认排除所有数量桶均为零的维度,跨仓入账清空的无仓位来源余额必须原子删除。- 列表、详情、导出和更新必须使用同一租户边界,不能仅在列表过滤而让详情或更新越权。
- 目的仓变更必须写状态/操作历史或审计证据,并使用版本控制防止并发覆盖。
- 一件代发废弃:申请只锁箱
AVAILABLE→DISCARD_PENDING;平台批准(lastleg:cargo-discard:approve)只授权。绑定仓的海外 PDA(lastleg:pda:operate)实际确认时,同事务调用 discard adjustment,扣 SKU 件数及库存余额/位置,实际箱转DISCARDED并写唯一逐箱流水,数量须守恒;未废弃预留箱释放,失败整体回滚。按实际数量汇总费用,平台结案不再扣减。终态同完整载荷重放不新增流水/历史,异数量拒绝;驳回只释放锁箱。完整流程见../../docs/头程到尾程分阶段流程/阶段五-海外接柜库存与库内服务.md第9.5节。 - 海外采购样品履约按
../../docs/海外仓业务/采购订单样品履约流程变更需求.md执行:通用编辑仅限内部备注,实收、装箱、面单、两段交寄和资金走独立节点。采购使用 Admin 履约台账,不接入 PDA、不写 WMS 可用库存。 - 派车状态只允许 PDA 拣货、完成和出库动作推进,普通派车编辑接口不得修改状态。PDA 写操作要求
lastleg:pda:operate;后台库存表单操作要求lastleg:inventory:manage,盘点调整另要求lastleg:inventory:adjust,转仓库存操作要求lastleg:warehouse-transfer:manage。 ll_order.current_status只保存尾程主流程状态;换标、转仓、出库和派送等子单精确状态保存到各自单据并汇总到sub_order_status,不得再用子单状态覆盖主流程状态。PDA 入仓及库存落账完成后,主流程状态固定推进为INBOUND_COMPLETED。
修改与验证
-
RelabelBindingGuard校验已保存组托历史与当前库存托盘一致,箱唛/混合换标正常完工、客户确认与排车提交均复验;纯 SKU 不要求移托,异常手动完工仍须在客户确认前补齐绑定。创建申请按库存余额串行检查箱唛既有申请,拒绝重复申请;历史扫码全局唯一约束尚未改为多轮复用。 -
已确认箱唛改绑复用
PdaPalletOperationService.relabelDisassemble不带actualSource的精确来源分支,按现有绑定移动 HOLD 库存,不重新分配冻结锚点;成功同样记录PALLET_BIND以展示最新目标。requireUnallocatedRelabel在入口与事务锁单后限制排车/出库占用,已创建出库单也不可直接改绑。回归PdaPalletOperationServiceTest。 -
PDA旧SKU单扫:
PdaRelabelService.scanSku和/relabel-tasks/{relabelNo}/skus/scan,按箱itemId+skuIndex核对旧SKU,服务端取新SKU/数量,逐条记录现有SKU_SCAN历史,版本、幂等、认领和仓库校验不变;同箱全部SKU完成再推进SKU阶段,混合单需两阶段才生成一条箱事实。无新增字段;前端按历史当前轮次隐藏已完成子项,重复旧SKU须由操作员选实际箱。 -
数量拆托成功在现有
ll_relabel_pda_history写PALLET_BIND,保存本轮、箱唛和目标托盘;同轮已组托箱从候选排除并在事务内防重复,返工使用新的 reviewedAt 轮次。PDA 通过详情 history 展示组托记录;不需要新增库存表字段。 -
换标按数量拆合托:
palletops/PdaPalletOperationService.relabelSources返回原入仓库存池的托盘组成,不能视为箱唛定位;relabel-disassemble的actualSource携带仓位版本及现场箱数,cartonMarks为本批目标绑定确认。实际托盘与冻结锚点不同时,在同余额/批次内仅重新分配本单冻结桶后合托,库存余额及原入仓单归属不变,保留逐仓位流水、物理资源补偿及幂等。每次一条实际来源仓位,可分批多托;已生成出库单禁止此重分配。认领人处理中即可新增托盘,不需客户确认;此能力不等于已具备真机打印或整单强制组托门禁。 -
换标申请生成的
RELABEL_APPLICATION尾程主单使用WAIT_RELABEL(待换标),不进入普通入库计划池;子状态仍区分处理中/待客户确认。客户确认后统一为WAIT_OUTBOUND(待出库),卡车分支按换标申请的WAIT_PRE_DISPATCH资格参与排车,不重复入库。存量修正脚本../scripts/db/relabel-wait-status-incremental.sql仅调整尚未生成入库单的遗留待分配换标主单。 -
混合换标
CARTON_AND_SKU使用现有 PDA 历史保存MIXED_CARTON/MIXED_SKU分项事实(itemId + 退回轮次);同一项防重复,两项全部完成才写一条箱事实并增加整箱计数。详情返回cartonProcessedPackages/skuProcessedPackages;不得将单项完成当作整箱完成,返工后旧轮次不计入当前分项进度。 -
换标复核闭环:
PdaRelabelService.complete直接提交待客户确认,RelabelApplicationService.confirmCompletion原子完成 PDA 复核;平台RelabelApplicationController的/review-task、/rework按状态权限提供带版本的退回。退回备注必填,上一轮事实及照片引用写入REWORK_ARCHIVE历史,原事实标记REWORK_ARCHIVED并释放扫描唯一键,当前计数归零;客户已确认不可退回。PDA 不再自行批准复核。 -
移动包、拆分端口/适配器或引入新的跨子域依赖时,先核对内部模块化基线并运行
node --test scripts/analyze-lastleg-dependencies.test.mjs;确需更新依赖基线时必须说明稳定边界变化,不能把临时循环依赖登记为长期豁免。 -
表字段、注释和索引同步维护基线 SQL 与统一增量 SQL;生产/存量库只执行增量脚本。
-
事件契约变化同时检查
common-event-contract、直接生产者/消费者、Outbox/Inbox 和 DeliveryAck 的 eventId/payloadHash;按../../docs/统一认证与服务通信契约.md的逐事件表验证,不引入旧 Payload 兼容。 -
定向验证:从后端根执行
mvn -pl lastleg-service -am test。 -
事件链路变更至少覆盖重复事件、相同来源入仓单、事务回滚、并发单号、内部令牌和租户越权测试。
-
2026-10-26 装车资料:五类依次为司机驾驶证、车牌照片、装车完成、封车照片、BOL司机签字;既有技术槽位与文件归属保持。每类1至5张,未传
replaceFileId追加,指定当前同类文件ID则精确替换;油耗仍单张替换。五类至少各一张且装车确认后才自动派送,同类多张不能充当其他类别,附加行版本/幂等/历史及当前文件下载授权共同保留。无需新增数据库字段,PDA、Admin、Lastleg需配套发布。
海外人工收费首版切换
- 除仓租外,换标报价、外接约定运费、拆柜打托成本仅保留业务依据,不再自动生成客户应收。旧派送应收参数须改由海外费用登记审核,供应商应付不变。订单详情新增费用统一委托人工费用服务。
- 新换标资金状态固定 NOT_REQUIRED,完成不会发布旧自动应收;旧已冻结申请保留原资金收尾,仅在确认实扣后发布已收来源。已有旧来源只补扣款引用,不重复生成来源;旧财务账单需上线前核对撤销或核销。采购保留独立收费流程,禁止进入人工通用收费。
- 同一服务业务的旧自动项目已锁款或已有有效应收时,拦截相同项目的人工重复登记;人工链允许按实际服务合法分次登记。