33 KiB
33 KiB
Firstleg Service 模块指引
职责与边界
- Mapper 统一显式使用
@Mapper,仅由BaiyueFirstlegApplication.java对com.baiyue.firstleg根包按annotationClass = Mapper.class扫描;禁止恢复业务子域 Mapper 包白名单。静态门禁见根scripts/mapper-scan-contract.mjs,模块内编译门禁见MapperRegistrationArchitectureTest.java、BaiyueFirstlegApplicationContractTest.java。 - 负责履约任务/分配、入库单、测量/OCR、审核后初始计价、FBA 计费、客户在途柜量统计、国内出仓履约链路,以及海外仓业务国内出仓后的船节点查询任务和节点事实。
- 订单主数据归
order/,托盘/库位归warehouse/,文件对象归file/,OCR 供应商归integration-service/;通过客户端 DTO 和内部令牌协作。 - 入库、分柜、装柜、PDA 和出仓能力均在本模块按对应服务端契约协作;
WAIT_SPLIT + APPROVED + CONFIRMED是入库链路状态节点,不是全量验收的范围终点。财务状态仍归finance,本模块只生产不可变费用来源和柜次成本快照。
模块入口
散货托盘码:service/LoadingPalletLabelService.java、mapper/LoadingPalletLabelMapper.java、controller/PlatformLoadingListController.java,契约 src/main/resources/openapi/loading-lists-v1.yaml 的 /pallet-labels/prepare 与 /pallet-labels/print。仅在计划 LOADING/COMPLETED 且真实柜号有效时,为非一件代发的 FBA 明细按柜号、FBA 地址内部编号、三位序号生成冻结标签;内部编号从 Warehouse 启用主数据读取,缺失或同柜重复时拒绝生成。重复生成须核对已有标签与明细一致,冲突直接拒绝。支持全部或按仓指定张数输出 100×100 mm PDF;装柜完成通过 LoadingCompletedOutboxService.java 传递来源托盘。
| 目标 | 入口 |
|---|---|
| 内部模块化基线与依赖分析 | docs/firstleg-internal-modularization-baseline.md、scripts/analyze-firstleg-dependencies.mjs、scripts/analyze-firstleg-dependencies.test.mjs、src/test/resources/architecture/firstleg-slice-dependencies.json;只约束本服务内部包依赖,不改变部署单元或数据库边界 |
| 履约任务/分配 | controller/PlatformFulfillmentAssignmentController.java、service/FulfillmentTaskService.java |
| 入库单查询/聚合 | controller/PlatformInboundOrderController.java、service/InboundOrderQueryService.java |
| 入库单扣货、履约门禁与凭证 | service/InboundHoldService.java、InboundHoldGuard.java、InboundHoldEvidenceService.java、client/InboundHoldFileClient.java;凭证固定按客户、入库单和 INBOUND_HOLD_EVIDENCE 精确归属,只有进入不可变扣货历史的文件可下载 |
| Order 申报同步与渠道变更履约门禁 | controller/InternalInboundOrderController.java 的 /order-channel-changes/validate、/order-declaration-updated,service/OrderDeclarationSyncService.java、mapper/OrderDeclarationSyncMapper.java、dto/OrderDeclarationSync.java;契约 src/main/resources/openapi/firstleg-v1.yaml,只接受内部服务令牌 |
| 平台测量/OCR 与箱规计费快照 | controller/PlatformInboundMeasurementController.java、service/InboundMeasurementService.java、service/CartonMeasurementService.java、service/CustomerDimensionWeightingService.java;手工单箱测量保存实际尺寸、可选外箱修正、chargeRuleId 与 coefficientMode,计算结果以服务端返回值为准 |
| 箱唛箱码生成、打印与测量绑定 | 审核建单生成钩子 controller/InternalInboundOrderController.java;生成/解析 service/InboundCartonLabelService.java、repository/InboundCartonLabelRepository.java;平台与客户打印 controller/PlatformInboundOrderController.java、MerchantInboundCartonLabelController.java、service/InboundCartonLabelPrintService.java。fl_inbound_carton_label.sku_code 是箱唛的 SKU 冻结快照,生成时经 client/OrderCargoClient.java 获取;既有记录按 inbound_detail_id 回填。ONE_CLICK_DROPSHIP 缺 SKU 时禁止生成箱唛,打印面显示 SKU;box_code 与二维码内容仍只携带稳定箱码。基线/升级 SQL 为 src/main/resources/db/phase2-inbound-carton-label.sql、phase2-carton-label-versioning-migration.sql、../deploy/sql/头程数据库统一增量-MySQL8.sql |
| 一件代发测量后 PDA 合托 | controller/PlatformDropshipPalletizationController.java、service/DropshipPalletizationService.java、mapper/DropshipPalletizationMapper.java;测量通过进入 WAIT_PALLETIZE 并下发独立任务,不创建国内库存,封托生成 DP 托盘号,全部箱数完成后进入 WAIT_SPLIT。契约 src/main/resources/openapi/dropship-palletization-v1.yaml |
| 墨家设备测量上传 | controller/OpenApiInboundMeasurementController.java、dto/MojiaMeasurementDtos.java、service/MojiaMeasurementOpenApiService.java、security/MeasurementRequestAuthenticator.java;接口契约见 src/main/resources/openapi/firstleg-v1.yaml 与 ../../docs/墨家设备测量上传接口说明-V3.md |
| 称重确认与整票库位块 | service/MeasurementLocationOccupationService.java 的 ensureForConfirmedWeight/ensureForSubmission、InboundMeasurementService.java、MeasurementSubmissionService.java、client/WarehousePalletLocationClient.java;Warehouse 块服务见 ../warehouse/src/main/java/com/baiyue/warehouse/service/LocationBlockService.java,接口与上架引导见 src/main/resources/openapi/firstleg-v1.yaml |
| 审核与初始计价 | controller/PlatformInboundMeasurementReviewController.java、service/MeasurementReviewService.java、service/InitialInboundPricingService.java、dto/MeasurementApprovalResponse.java |
| 客户四档箱规与默认算法 | controller/PlatformCustomerDimensionConfigController.java、service/CustomerDimensionConfigService.java、repository/CustomerDimensionConfigRepository.java;固定 SMALL/MEDIUM/LARGE/EXTRA_LARGE,默认算法只允许 ADD/MULTIPLY |
| FBA 主数据/计费与客户规则 | controller/PlatformFbaMasterDataController.java、PlatformFbaPricingController.java、service/FbaPricingService.java、repository/FbaPricingRepository.java;客户规则可保存多个版本,但每个客户仅一条默认规则,计算请求可提交已确认的重量/体积选择值 |
| 客户专属价格簿、柜级阶梯运费与调价边界 | controller/PlatformPriceBookController.java、service/PlatformPriceBookService.java、pricing/ActivePriceBookEstimator.java、service/FbaPricingService.java、pricing/ContainerTierAmountAllocator.java;公共价格簿固定 customerId=0,客户专属版本按客户独立发布与替换,计价优先专属 ACTIVE、缺失才回退公共 ACTIVE。REGION_WEIGHT_TIER 对同柜同客户有效成员聚合实际计费量命中一个阶梯,再按成员计费量守恒分摊;装柜成员变化调用 recalculateUnlockedContainerCustomerFees,已确认/已发送或客户已确认的版本不得改写。人工调整以系统基准量为中心限制上下 10%(含边界),保存版本、原因和二次确认;一件代发 WAIT_PALLETIZE 可在预装确认前试算,markInboundCalculated 只更新费用并保留该托盘化状态 |
| 入库费用明细 | controller/PlatformInboundFeeDetailController.java、service/InboundFeeDetailService.java、dto/InboundFeeDetailDtos.java;PRIVATE_LAST_MILE_DELIVERY 是独立私人仓尾程派送费,只允许 destinationType=PRIVATE,按稳定费用代码校验而不按中文名判断;Admin 消费端见 ../../apps/admin-web/src/modules/unified-orders/api/inboundFeeDetails.ts |
| 入库/柜次直接已付快照与凭证校验 | service/FeePaymentSnapshotValidator.java、client/FeePaymentVoucherFileClient.java;写入入口分别为 InboundFeeDetailService.java、ContainerFeeDetailService.java,File 业务类型固定 FINANCE_VOUCHER |
| 客户运营统计/在途柜量 | customerstatistics/controller/PlatformCustomerTransitStatisticsController.java、customerstatistics/service/CustomerTransitStatisticsService.java;契约见 src/main/resources/openapi/customer-statistics-v1.yaml |
| 费用项目与价格簿 | controller/PlatformFeeItemController.java、PlatformPriceBookController.java、MerchantPriceBookController.java |
| 客户入库结果与头程费用确认 | controller/MerchantInboundBillingController.java、service/MerchantInboundBillingService.java、FbaPricingService.java;查询与确认接口为 /api/merchant/v1/first-leg/orders/{orderId}/inbound-billing、/confirm,契约 src/main/resources/openapi/firstleg-v1.yaml |
| 价格簿查询内部切片 | pricebookquery/application/port/in/PriceBookQueryUseCase.java、application/PriceBookQueryService.java、application/port/out/PriceBookQueryReader.java、adapter/out/persistence/PriceBookQueryMapper.java;查询行模型位于 application/model/PriceBookQueryRows.java |
| 财务费用来源快照 | controller/InternalFinanceFeeSourceController.java;入库费用与柜次费用分别按来源 ID、版本向 Finance 提供不可变快照 |
| 柜次费用明细与跨客户分摊组 | controller/PlatformContainerFeeDetailController.java、service/ContainerFeeDetailService.java、dto/ContainerFeeDetailDtos.java;跨客户公共柜费使用 allocation-groups 按柜内真实客户和订单原子分摊,入仓 ID/入仓码同时填写或同时留空;各行金额合计必须等于原始柜费,审核、驳回、调整、作废均整组处理,单行入口拒绝分摊组;契约 src/main/resources/openapi/container-fees-v1.yaml;Admin 消费端见 ../../apps/admin-web/src/modules/loading-lists/api/containerFees.ts |
| 财务整柜成本分摊快照 | loading/controller/InternalFinanceCostSnapshotController.java、loading/service/FinanceCostSnapshotService.java |
| 分柜、预装、真实柜号后补与正式装柜清单生命周期 | split/controller/PlatformSplitPreloadController.java、controller/PlatformLoadingListController.java、service/LoadingListService.java;契约 src/main/resources/openapi/loading-lists-v1.yaml,详情阶段先查 /loading-plans/{planId}/lifecycle。创建柜次时原子生成唯一预装计划,加入入仓码明细时同步创建入库预装聚合与关联,不设推送步骤;同一 planId 在 EDITING/LOADING 返回 PRELOAD,COMPLETED 返回 LOADING_LIST。订舱关联、预装确认和 PDA task 创建不依赖真实集装箱号;可在 EDITING/LOADING 首次补录,最终 PDA 完成前必须存在;已有值纠正仅限 EDITING 且任务未领取、无物理作业事实 |
| 同柜不可拆组 | loading/coload/controller/PlatformColoadGroupController.java、service/ColoadGroupService.java、persistence/ColoadGroupMapper.java;契约 src/main/resources/openapi/coload-groups-v1.yaml。请求按订单 ID 建组,服务端冻结每票全部 WAIT_SPLIT 可用入仓片段与满箱数;仅要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均保持整组原子 |
| 货柜与全局智能排柜 | controller/PlatformContainerController.java、PlatformSmartLoadingController.java、service/SmartLoadingService.java、repository/SmartLoadingRepository.java、client/PendingReviewOrderClient.java、loading/;全局候选合并本服务履约投影与 Order /internal/order/v1/pending-review-orders 权威待审核页,工作台柜次从 /inbound-candidates、/containers 进入;普通箱数、待审核占位、同柜组和全部未拣移除分别走 /inbound-assignments、/pending-review-assignments、/group-assignments、/unpicked-items,明细规格拆分留在服务端 |
| 货柜资料、PDA 证据与业务总览 | controller/PlatformContainerController.java 的 /{id}/overview、/{id}/documents/{documentType},聚合 mapper/ContainerOverviewMapper.java;契约 src/main/resources/openapi/phase3-container-overview-v1.yaml,四类资料固定为目的港清关、国内出口报关、DO、PU 的当前唯一版本 |
| PDA 拣货与装柜、证据幂等上传 | controller/PlatformLoadingPdaController.java、loading/controller/PlatformLoadingDeliveryController.java、loading/service/CabinetLoadingEvidenceService.java;证据写入和 multipart 上传均先以任务内幂等键匹配请求哈希,上传哈希覆盖仓库、版本、证据类型、锁码、备注、文件名、媒体类型、大小与文件内容;完全重放返回当前视图且不得再次上传 File,相同键不同内容返回冲突 |
| 装柜完成跨区事件 | service/LoadingCompletedOutboxService.java、LoadingCompletedOutboxRelay.java、repository/LoadingCompletedOutboxRepository.java、mapper/LoadingCartonManifestMapper.java、client/EventBridgeClient.java;从 fl_inbound_order.destination_type 冻结 V2 orders[].destinationType(FBA 或 OVERSEAS),OVERSEAS 按最终实装与稳定箱码冻结,FBA 可仅冻结箱数;真实生产验收 src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java;事件模型见 ../common-event-contract/AGENTS.md |
| 海外卸柜完成事件回写 | 内部入口 controller/InternalOverseasContainerEventController.java、消费者 service/OverseasContainerEventConsumer.java、Inbox entity/ContainerEventInboxEntity.java 与 mapper/ContainerEventInboxMapper.java;事件模型见 ../common-event-contract/AGENTS.md |
| 海外暂扣状态回写 | 同一内部事件入口、消费者 service/OverseasInboundOrderDetainedConsumer.java;按来源入仓单 ID 更新 fl_inbound_order.hold_flag/current_hold_reason,复用跨区 Inbox 并写 fl_inbound_hold_record |
| 国内货柜履约海运订舱、资料/打印、关联候选与出仓前船期资料 | oceanbooking/controller/PlatformOceanBookingController.java、oceanbooking/service/OceanBookingService.java、OceanBookingDocumentService.java、OceanBookingPrintService.java、oceanbooking/mapper/OceanBookingMapper.java、portschedule/controller/PlatformPortScheduleController.java;文件经 client/OceanBookingFileClient.java 进入 File,关联候选 /api/platform/v1/ocean-bookings/link-candidates,契约 src/main/resources/openapi/ocean-bookings-v1.yaml,表结构见 src/main/resources/db/phase3-ocean-booking-schema.sql |
| 海外仓业务国内出仓后船节点定时查询 | outbound/dispatch/service/OutboundDispatchService.java 在确认出仓事务内幂等创建现有 CarrierScheduleQuery;编排与租约 portschedule/service/CarrierScheduleQueryScheduler.java,任务/十节点事实/异常事实/不可变 attempt CarrierScheduleQueryService.java,平台查询与人工补录入口 PlatformCarrierScheduleQueryController.java 的 /{queryId}/manual-nodes;停止只由实际 EMPTY_RETURNED 节点触发,不提供无节点停止入口;外部船司/轨迹调用经 integration-service/;标准见 ../../docs/海外仓业务/船节点标准化契约.md |
| 目的港码头拖车与还空预警 | terminaldrayage/controller/PlatformTerminalDrayageController.java、service/TerminalDrayageService.java、mapper/TerminalDrayageMapper.java;货柜实际到港后引用 Warehouse ACTIVE/TRUCKING 供应商,依次选择车队、预约提柜、确认实际提柜,海外卸柜收货完成后确认还柜;服务端按卸柜完成时间加 72 小时返回报空预警,实际提柜和还柜通过现有 CarrierScheduleQueryService 推进 PICKED_UP/EMPTY_RETURNED,不复制船节点或车队主档。契约 src/main/resources/openapi/terminal-drayage-v1.yaml,基线 src/main/resources/db/phase3-terminal-drayage-schema.sql 邮件由 terminaldrayage/service/TerminalDrayageMailService.java 入队,TerminalDrayageMailDispatcher.java 顺序投递,模板与 SMTP 配置见 docs/terminal-drayage-mail.md;首次选队不发信,换队事务内生成取消与请求两封邮件。 |
| 船节点跨区交接事件 | portschedule/service/CarrierScheduleNodeOutboxService.java;实际节点落库后发布 FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED V1,复用 LoadingCompletedOutboxRepository/Relay 投 Event Bridge;海外消费见 ../lastleg-service/src/main/java/com/baiyue/lastleg/shipping/service/ContainerShippingNodeConsumer.java |
| Firstleg 业务单据编号 | service/FirstlegDocumentNumberGenerator.java;当前支持 OB、TA、CB、CAB、LP 前缀,按北京时间每日独立编号;具体业务归属以各调用服务为准 |
| 订舱级拖车与整柜出仓调度 | controller/PlatformTruckAppointmentController.java 的 /candidates、service/TruckAppointmentService.java、repository/TruckAppointmentRepository.java、outbound/dispatch/controller/PlatformOutboundDispatchController.java;拖车契约 src/main/resources/openapi/truck-appointments-v1.yaml。预约只绑定 RELEASED 订舱,候选排除缺提单/有效订舱文件或已有非终态预约的订舱;API 不含客户、柜次、装柜计划或真实柜号,每订舱最多一张非终态预约,PDA 完成不推进拖车状态 |
| 整柜出口报关 | customs/container/controller/PlatformContainerCustomsController.java、customs/container/controller/MerchantContainerCustomsController.java、customs/container/service/ContainerCustomsService.java、customs/container/mapper/ContainerCustomsMapper.java;装柜完成后按柜收集退税资料,多对多覆盖入仓码,开始报关冻结,完成文件逐资料一对一回传,全买单柜由平台确认并通过导出中心取明细;契约 src/main/resources/openapi/customs-v1.yaml,基线 src/main/resources/db/phase3-container-customs-schema.sql;消费者为 Admin ../../apps/admin-web/src/modules/container-loading/components/ContainerCustomsPanel.vue 和 Client ../../apps/client-web/src/modules/first-leg/components/CustomsDocumentsDialog.vue |
| 买单订单明细导出 Provider | customs/container/controller/LoadingListBuyDeclarationExportController.java、service/LoadingListBuyDeclarationExportService.java;只为 File 导出中心提供 LOADING_LIST_BUY_DECLARATION_ITEMS 的已完成装柜实际买单行,不在 Firstleg 生成文件 |
| 跨模型桥接 | loading/bridge/controller/PlatformLoadingModelBridgeController.java |
| 跨服务客户端 | client/OrderCargoClient.java、WarehousePalletLocationClient.java、MeasurementImageClient.java、LoadingEvidenceFileClient.java、EventBridgeClient.java |
| 仓库公共供应商快照 | client/WarehouseSupplierClient.java、service/WarehouseSupplierSnapshotService.java;报关、订舱和拖车分别消费 customs-v1.yaml、ocean-bookings-v1.yaml、truck-appointments-v1.yaml 中的供应商 ID,并由 Warehouse 返回服务端名称快照 |
| SQL/OpenAPI | src/main/resources/db/phase2-*.sql、src/main/resources/db/phase3-*.sql、src/main/resources/openapi/;部署统一 ../deploy/sql/头程数据库统一增量-MySQL8.sql |
| 测试/验证 | src/test/java/com/baiyue/firstleg/;mvn -pl firstleg -am test |
修改约束
- 移动包、拆分端口/适配器或引入新的跨切片依赖时,先核对内部模块化基线并运行
node --test scripts/analyze-firstleg-dependencies.test.mjs;确需更新依赖基线时必须说明稳定边界变化,不能把临时循环依赖登记为长期豁免。 - 平台接口必须校验目标客户、权限、乐观锁、幂等键、历史和审计;商户查询只能使用登录态客户。
- 价格簿查询消费者依赖
PriceBookQueryUseCase或稳定查询端口,不得反向依赖持久化 Mapper;移动旧查询类时同步更新生产调用、测试、Mock 与 import,避免同一职责在根repository/mapper和pricebookquery/双写。 - 第三方回传必须签名、时间窗、防重放、请求哈希,并绑定订单货物和 File 证据;不得信任外部客户字段。
- 稳定箱码映射以入库快照和 Order 货物明细为事实;订单审核创建入库单时必须幂等生成,打印接口只复用或补齐同一映射,用户是否下载箱唛不得决定箱码有效性。手工测量、墨家设备和 PDA 扫码统一通过
InboundCartonLabelService校验客户、入库单、货物明细、总箱数/下标和有效批次,不得在测量时临时伪造箱码或接受跨客户、跨入库箱码。 - 一件代发以
fl_inbound_carton_label.sku_code作为不可变 SKU 快照;PDA 仍只扫描稳定box_code,不得将 SKU 塞入二维码或以 SKU 替代箱码。实际打托后的查询必须能按“现场托盘 -> 客户 -> SKU -> 箱数”聚合;历史箱唛仅能在同客户、同入库单范围内按inbound_detail_id回填,禁止按货名、箱规或箱号猜测 SKU。修改本链路时同步检查OrderCargoClient、Order 内部货物接口及 PDA 打托实现。 - 测量审核通过后直接进入
WAIT_SPLIT,费用仅作为独立初算和人工维护结果,不得阻塞或回退订单主流程;计费量、单价和附加费调整必须版本化并由服务端重算金额。状态或 DTO 变更时逐个检查 Order/Warehouse/File/Integration 消费者。 - 客户箱规配置必须恰好保持四档且默认算法在四行一致;
ADD以实测尺寸为基准对三个维度分别增加命中箱规的尺寸值,MULTIPLY使用当前客户唯一默认计费规则,并由服务端按规则中的规格档位选择系数。测量行必须保存实际尺寸、选用规则和算法快照,页面不得自行重算后覆盖服务端结果;计价使用用户确认的重量/体积选择值并纳入幂等请求哈希。 - 报关、订舱和拖车引用的供应商必须通过
WarehouseSupplierSnapshotService按 ID 向 Warehouse 校验:报关使用CUSTOMS,订舱使用BOOKING(不区分国内/国外 scope),拖车使用TRUCKING;供应商必须为ACTIVE。请求中的名称只能作为一致性提示,持久化名称必须来自 Warehouse 快照,禁止用自由文本绕过 ID、类别或启用状态校验。修改任一供应商引用 DTO、OpenAPI 或内部客户端时,必须同步检查三条业务链、Warehousesupplier-v1.yaml、数据库快照字段和对应契约测试。 - Order 渠道变更门禁必须检查装柜或后续状态、PDA task、预装、分柜及同柜组兼容性;任何不确定结果均失败关闭。
ORDER_DECLARATION_UPDATED_V1消费必须校验订单号/入仓码归属,永久保留eventId + payload hash幂等历史并按版本单调更新申报投影;不得覆盖仓库实测、箱码、测量审核或已确认费用。 - 订舱状态固定为
DRAFT → RELEASED:草稿允许资料不完整并可替换或删除,放舱时校验完整持久化聚合,放舱后永久只读。创建草稿和放舱都只维护订舱聚合、三类参与方及柜型需求,不创建真实containerNo、货柜行或分配关系;真实货柜只在后续提柜关联时占用已放舱柜型需求。 - 订舱柜型需求至少一项、最多 20 项,数量必须为正整数,容量和限重必须匹配平台柜型规格。起运仓必须是启用的国内中转仓;物流渠道必须来自该仓当前 ACTIVE 价格版本,服务端保存渠道名称快照,渠道失效时失败关闭。
- 订舱号统一使用
OB-yyMMdd-####,按北京时间创建日每日独立递增且单日最多 9999;不得以实体 ID 拼接生成。放舱不要求真实柜号,也不得为满足放舱校验伪造柜号。 - 订舱列表
containerCount固定为该订舱全部柜型需求quantity之和,与真实柜关联/解除无关;关联候选只返回RELEASED、剩余数量大于 0 且warehouseId + channelCode + containerType精确匹配的需求,由服务端集合查询 ACTIVE 分配量。前端不得扇出详情计算或用已关联柜数覆盖该字段,最终关联命令仍须重验目标货柜可修改性。 - 拖车预约是平台级订舱聚合,不是客户或柜次资源:请求、实体、查询、响应和附件不得恢复
customerId/customerName,公开 DTO 也不得出现containerBookingId/loadingPlanId/cabinetNo/containerNo。创建候选只返回有提单号、ACTIVEBOOKING_CONFIRMATION或BILL_OF_LADING且没有非终态拖车单的RELEASED订舱;创建或编辑只冻结订舱仓库/提单与 TRUCKING 供应商快照。每订舱通过查询锁与唯一约束最多一张非终态预约,重复创建返回现有派车单号;PDA 装柜完成不得改变拖车状态。附件使用tenantId=0 + TRUCK_APPOINTMENT_ATTACHMENT + appointmentId,提单文件继续归订舱。 - 海外仓业务船节点任务从确认柜子离开国内仓库后启用,必须绑定真实订舱和柜次;十个标准节点、预计/实际时间、异常事实、供应商缺失节点和还柜停止规则统一以
../../docs/海外仓业务/船节点标准化契约.md为准。CarrierScheduleQuery的正常业务查询槽位固定为Asia/Shanghai每日 09:00、12:30、16:00;每分钟任务只扫描已到期next_allowed_at,不是每分钟请求船司。成功、无变化、需人工或永久失败进入下一固定槽位;仅技术性RETRYABLE_FAILURE在当前槽内按15/30分钟退避,含首次最多3次且不得跨下一槽,实际EMPTY_RETURNED后停止。错过的正常槽位直接顺延,不在服务恢复时补跑;单轮按受控批次持续领取并受最大任务数保护。租约、调度尝试和内部停止入口不得暴露为平台页面 API,外部失败不得推进业务节点,不得在国内货柜履约另建查询任务。 - 国内出口报关在装柜完成后启用,不再阻塞
LOADED。整柜fl_customs_container.status=COMPLETED才通过离仓报关门禁;完成后以系统身份、确定性幂等键立即尝试确认国内离仓,其他门禁未齐时由受控定时补偿重试,禁止部分入仓成功或重复创建离仓/船司查询事实。 - 国内出仓确认以真实
containerBookingId聚合全部入仓绑定并锁定校验;装柜证据按物理货柜只计算一次,PDAtaskId仅用于来源审计和任务授权,File 所有权仍固定tenantId=0 + containerBookingId;拖车状态和独立船期只作参考。全部门禁在同一事务通过后推进所有技术行、记录一条可见历史并幂等创建CarrierScheduleQuery/DEPARTED_WAREHOUSE,不允许单入仓局部成功。 - 手工船节点的预计时间只追加事实,不推进
latestStandardNode;携带actualAt时标准节点序号必须严格大于最新实际节点,重复、相同或回退节点返回冲突。已审核实际时间修正只能更新同一事实的时间/证据,不得制造第二条同节点实际事实或绕过单调推进。 - 海外卸柜完成事件只接受
OVERSEAS -> CN的OVERSEAS_CONTAINER_UNLOADEDV1,并通过内部服务令牌进入。消费者必须校验信封、Payload 哈希、来源货柜和时间顺序,以 event ID + Payload 哈希幂等,使用事件聚合版本阻止乱序覆盖;预计报空时间固定按卸柜照片上传时间加两天计算,实际报空时间可为空。 - Firstleg 只拥有业务费用及履约事实;Finance 拥有费用入池、账期、账单、收款、成本分摊结果和财务状态。修改费用来源版本、发布条件、柜次归属或体积快照时同步检查
../finance/src/main/java/com/baiyue/finance/client/FirstlegFeeSourceClient.java、同目录FirstlegCostSnapshotClient.java及双方契约测试,不在 Firstleg 复制财务状态机。 DIRECT_PAID仅适用于PAYABLE,必须同时保存paidAt、合法paymentMethod,并至少提供paymentReferenceNo或一个当前客户范围内的FINANCE_VOUCHER;RECEIVABLE与ON_ACCOUNT不得残留付款字段。创建、调整和审核前均通过FeePaymentSnapshotValidator失败关闭复核。
国内货柜履约装柜与 PDA 资源结算
- 智能装柜全局工作台由服务端把业务渠道分组映射为实际渠道,并分别分页查询候选与
WAIT_LOADING/EDITING/LOADING柜次;候选支持按创建日期和预计到货日期闭区间筛选,状态筛选使用最多四项的orderStatuses,未传表示PENDING_REVIEW/WAIT_MEASURE/WAIT_SPLIT/LOADING全部未完成状态。状态只用于搜索和展示,能否加入以响应canAssign为准,由有效柜次占用、剩余箱数、容量、组锁和版本等事实决定;人工加入允许订单国内仓及渠道与柜次不同,智能推荐允许跨国内仓但仍只选择柜次渠道。容积和重量均允许达到柜型额定值的 150%,超过即拒绝。待审核事实必须经PendingReviewOrderClient重新核验 Order 权威SUBMITTED订单,待审核或待测量可先占位,但确认预装和启动 PDA 前仍必须进入WAIT_SPLIT。计划明细通过totalPackageCount/remainingPackageCount返回可调总量和余量:测量前取 Order 原始申报箱数,进入WAIT_SPLIT/LOADING后取最新已审核测量箱数;只读补充失败时保守回退当前已分配数,调整写入仍重新查询并校验权威数量。同柜不可拆组只有全部成员命中状态集合时才返回,且必须成员完整、满箱并作为单个分页与分配单元,不能跨页或拆分。一次性移除未拣明细时,普通未拣项直接移除;同柜组仅全组未拣时整组移除,任一成员已拣则整组保留,单事务只递增一次计划版本。推荐顺序按入库时间先进先出。装柜计划状态为EDITING -> LOADING -> COMPLETED,PDA 全部明细处理完成后自动完成。 - 客户入库费用只在登录客户范围、测量审核通过、
feeStatus=SENT且存在已发布明细时可见;确认必须具备merchant:order:edit,携带当前expectedFeeVersion与幂等键。成功后记录确认成员和时间并将该费用版本永久锁定,平台不得再调整;旧版本、未发送或重复异参确认均返回冲突,不做兼容覆盖。 - PDA 拣货以
fl_inbound_carton_label.box_code对应的稳定箱唛箱码为扫描身份;审核建单、重复打印、手工测量和墨家测量复用同一映射,版本字段只用于并发控制,不进入扫码文本。一个有效且未重复的箱码扫描表示拣出一箱,不得以 SKU、商品件数或页面展示字段替代箱码。任务下发与提交契约见src/main/resources/openapi/phase3-pda-convergence-v1.yaml。 - PDA 部分拣货生成二级子入库单时,数量必须守恒:
已拣数量 + 子单数量 = 拣货前主单可用数量。子单只允许一层,不能继续拆分。 - 首张称重图片确认时按
max(已确认称重图片数, ceil(有效体积 / 默认托盘容量))尽力预留并确认整票库位块;有效体积先取入库单已落地的正数实测体积,否则对最新测量批次的正数totalVolumeCbm/originalVolumeCbm取较大值,再无有效值才查询 Order 最新客户申报体积,全部无效则拒绝。后续确认绑定最小空槽,所需槽数增长时通过 Warehouse/blocks/expand原子追加,既有blockId/slotNo/reservationId/locationId/sharedOverflow不得变化。库位指引失败只记录警告,不阻断称重确认;测量提交后以最新已确认图片调用ensureForSubmission再次补齐和校验,失败同样不回滚已提交的测量主流程。Firstleg 必须显式保存weightResultId -> blockId/slotNo/reservationId/locationId/locationCode,按confirmedAt + weightResultId稳定顺序绑定,禁止按列表下标推断;普通连续位不足的新增槽由 Warehouse 放入共享 A0,不阻断确认。 - 删除称重图片只解除图片和托盘快照绑定,将槽位恢复为
RESERVED_EMPTY;槽位和整票块继续归当前入仓码。解绑失败时不得调用文件删除,文件删除失败时本地解绑和重量结果删除必须回滚。 - 头程快照必须保存 Warehouse
blockId + slotNo + reservationId;A0 可被多个整票块共享locationId,业务资源身份始终使用reservationId,不得把locationId当作唯一标识。 - PDA 部分拣货不得释放块内单槽;存在余量子单时通过
/internal/warehouse/v1/pallet-locations/blocks/transfer将整段转移给子单,源单与余量子单可各自保留同一块槽及称重快照,因此fl_inbound_pallet_allocation的槽位与称重唯一键必须包含inbound_id。整票全部拣完时只通过/internal/warehouse/v1/pallet-locations/blocks/release一次性释放全部普通位和 A0 预约,禁止恢复逐槽settlements/release-selected协议。 - 数据库字段变更直接维护当前模块基线与
../deploy/sql/头程数据库统一增量-MySQL8.sql;已有phase3-pda-release-allocation-migration.sql只作为当前结构来源,不为历史开发数据再新增兼容迁移。
全量验收导航
本模块承载订单履约、入库测量计费、订单编排、柜次、排柜、订舱、拖车、PDA 和 LOADED 门禁等全量链路;按本次验收范围读取对应业务域文档、Controller、DTO、OpenAPI、当前 SQL 定义和调用方,不以历史规划终点阻断联调。PDA 按 uni-app 在线更新安排开发与验收,不另设设备型号门禁。