6.0 KiB
海外仓费用统一接入阶段 0 技术契约
版本:V0.5-0A | 状态:开发前技术基线 | 来源:海外仓费用统一接入财务评审方案 V0.5
1. 目标与边界
阶段 0 只完成“费用事实可追踪、可幂等、可被 Finance 接受或拒绝”的底座,不新增业务收费规则,不部署测试环境,不启动 Java/Nacos/数据库。所有费用仍由业务服务产生事实,Finance 独立落库并负责应收/应付、钱包和结算。
实施顺序固定为:
- 0A:既有链路和收费身份;
- 0B:统一来源命令、结果和 Outbox/Inbox;
- 0C:冻结/结算按收费身份和计划版本;
- 0D:退货、仓租、按票及库存日事实;
- 0E:商业调整、拒绝和财务待办。
2. 当前代码事实与必须修正点
| 范围 | 当前实现 | V0.5 要求 |
|---|---|---|
| 来源事件 | BusinessFeeSourceChangedV1 单向完整快照 |
增加命令/结果闭环,Finance 明确 ACCEPTED/REJECTED |
| 费用版本 | Lastleg 调整、作废先改变当前状态再发事件 | Finance 接受新版本后,旧版本才可 SUPERSEDED/VOIDED |
| Wallet | 以 businessType + businessNo + amount 匹配冻结 |
绑定 chargeIdentity + feeSourceVersion + walletPlanId |
| Relabel | 部分完成路径发布费用,SKU/客户确认路径漏发 | 所有完成路径统一产出一条收费身份 |
| Destuff | 直接 insert 已审批客户应收,绕过服务和命令 | 进入统一来源命令;供应商应付按阶段配置 |
| Delivery | 转运时直接发布已审批费用 | 票级 AR/AP 分离,人工金额先待审 |
| 来源模型 | 无 scenarioCode、收费身份、Finance 同步状态 |
三者均为显式字段,不从业务单号推导 |
3. 收费身份与状态
3.1 收费身份
chargeIdentity 是稳定的业务收费对象键,不等同于数据库 ID。格式由场景定义,至少包含 scenarioCode、业务主单号、客户和收费对象序号;同一身份可产生多个不可变 sourceVersion。Finance 唯一键为:
sourceSystem + sourceType + sourceFeeId + sourceVersion
费用明细必须同时保存 chargeIdentity,用于 AR/AP、钱包、账单和追溯关联。
3.2 状态轴
- 来源状态:
DRAFT、PENDING_REVIEW、APPROVED、REJECTED、SUPERSEDED、VOIDED。 - 发布状态:
NOT_PUBLISHED、PUBLISHED、ACCEPTED、REJECTED。 - Finance 同步状态:
PENDING、ACCEPTED、REJECTED、RETRYING。 - 结算状态:
UNSETTLED、FROZEN、SETTLED、RELEASED。
状态轴不可互相覆盖。费用调整必须生成新版本;同一版本不同 hash 必须拒绝。已进入有效账单、结算或钱包结算的来源不得替换或作废。
4. 跨服务命令与结果
4.1 BusinessFeeSourceCommandV2
事件头:eventId、eventType、eventVersion=2、eventKey、occurredAt、sourceSystem、traceId。
业务体:action(UPSERT/VOID)、sourceType、sourceFeeId、sourceVersion、chargeIdentity、scenarioCode、客户/业务/仓库/供应商引用、方向、币种金额、数量单价、期间、付款证据、来源状态、sourceHash。
发送方必须发送完整快照,不允许 Finance 回查海外库。eventKey 固定为 sourceSystem:sourceFeeId:sourceVersion:action。
4.2 BusinessFeeSourceResultV2
Finance 处理后回传:commandEventId、sourceFeeId、sourceVersion、result(ACCEPTED/REJECTED)、reasonCode、reasonMessage、financeFeeSourceId、processedAt、resultHash。
业务方以 commandEventId 幂等接收结果;REJECTED 必须形成可重试或人工待办,不得静默丢弃。结果事件不能改变原始费用快照。
5. 钱包与账单关联
钱包命令新增 chargeIdentity、sourceFeeId、sourceVersion、walletPlanId、walletPlanVersion、allocationId。冻结、结算、释放均以 allocationId 幂等;禁止仅按业务类型、业务单号和金额猜测关联。
柜级费用先按真实客户/订单形成分摊明细,再分别产生收费身份和钱包分配;退货按票、仓租按期间/仓库、配送按票分别生成身份,不复用订单主单号充当费用键。
6. 场景接入约束
- Relabel:所有完成入口调用同一应用服务;一次业务收费身份只允许一个当前版本。
- Destuff:按实际客户 CBM 分摊;客户应收与供应商应付分开建身份,禁止直接写 APPROVED/PUBLISHED 绕过命令。
- Delivery:每票每收费项独立身份;收款方与付款方分离,手工金额进入待审。
- Return:按票计费,退货收货完成才结束计费;迟到实收只能生成新版本/调整记录。
- Warehouse rent:按仓库、库位/货主、计费期间和计费规则生成期间身份,不能伪造入仓码。
7. 阶段 0 开发门禁
开始改代码前必须完成并评审:
- V2 事件 DTO、事件类型、Gateway 路由、OpenAPI、权限码和跨区 Outbox/Inbox 位置;
- Lastleg 来源表与 Finance 来源表的字段/索引/状态迁移设计;
- 钱包计划及分摊表的唯一键与幂等键;
- Relabel、Destuff、Delivery 的生产入口清单和漏发测试用例;
- 拒绝、重试、迟到实收、旧版本已结算等异常验收用例。
实现后必须定向运行 DTO/事件/服务测试、node scripts/gateway-route-contract.test.mjs,若有 Mapper 变更再运行 node scripts/mapper-scan-contract.mjs。本阶段不执行部署和真实 HTTP 验收。
8. 代码入口
- 事件模型:
baiyue-firstleg/common-event-contract/ - 海外来源服务:
baiyue-firstleg/lastleg-service/.../financefee/ - Finance 消费:
baiyue-firstleg/finance/.../BusinessFeeSourceEventService.java - Relabel:
baiyue-firstleg/lastleg-service/.../relabel/ - Delivery:
baiyue-firstleg/lastleg-service/.../delivery/ - Destuff:
baiyue-firstleg/lastleg-service/.../inboundplan/
本文是 V0.5 的技术落地门禁;与 V0.5 方案冲突时,以当前对话确认和 V0.5 方案为准。