Files
docs/财务管理/财务领域模型与跨服务契约.md
2026-09-29 17:55:58 +08:00

16 KiB
Raw Permalink Blame History

财务领域模型与跨服务契约(历史合并稿)

历史文档,非订单与客户实施依据:本合并稿已被《财务领域模型与数据字典》《Firstleg-Finance 跨服务契约》和《财务核心五页产品与接口验收契约》替代。钱包、充值、发票、费用分析和利润模型不属于订单与客户。

文档状态:已被拆分替代,仅保留历史设计过程,不作为后续实施依据
替代文档:财务领域模型与数据字典、Firstleg-Finance 跨服务契约
原版本:F0-V1.0(历史稿)
日期:2026-08-10
适用:finance-service F0,以及 Firstleg → Finance 的费用来源接入

本文只定义财务 F0 的领域对象、来源快照和服务间契约,不创建服务、数据库或业务代码。范围和服务边界以财务服务边界设计为准;财务全量产品项仍以财务管理产品确认表为准。

1. 设计原则与现状基线

  • Finance 是独立 finance-service,使用独立 baiyue_finance 数据库;不得跨库 Join、共享 Entity 或让 Firstleg 直接写 Finance。
  • Firstleg 是费用事实的所有者:负责测量、计费快照、版本、发布、调整和作废;Finance 只保存来源引用、不可变快照及财务状态。
  • 当前 Firstleg 已有 fl_inbound_fee_detail 和 fl_inbound_outbox。费用发布会产生 sourceType=FBA_PRICING、sourceFeeId/sourceFeeVersion、direction=RECEIVABLE、币种和金额等字段;自动发布的 settlementIncluded=true。
  • 当前 Outbox 已写入 FIRSTLEG_FEE_SENT、FIRSTLEG_FEE_ADJUSTED,负载仅含 inboundId/feeId(/feeVersion),仓库中尚未形成 Finance 专用发布器或内部来源查询接口。它们是迁移基础,不是本契约的完整 v1 事件。
  • Firstleg 现有业务错误遵循 HTTP 200 + body code(成功为字符串 "200");认证、网关和基础设施拒绝可使用真实 401/403/503。内部接口沿用该业务响应封装,服务令牌失败使用 401/403。

2. F0 核心对象

Finance 只在自己的库保存以下对象;所有长整型 ID 在 JSON 中以字符串传输,金额以定点十进制字符串传输,币种使用 ISO 4217 三位大写码。

对象 必要字段/约束 生命周期与归属
FinancialEntity 财务主体 entityId、主体名称快照、主体类型、默认币种、状态、版本 Finance 维护;主体、账套、客户隔离键分开建模。主体是否至少平台收款/付款两个方向为产品门禁 F0-02
CounterpartyRef 往来单位引用 counterpartyType=CUSTOMER/SUPPLIER、外部 ID、名称快照、来源服务、快照时间 不复制 IAM/Warehouse 实体;引用变更不改历史快照。供应商主数据来源及字段待 F0-03/F2-01 确认
CurrencyRef 币种 ISO 码、精度、启用状态 F0 固定三位码和精度;汇率来源、生效时间、版本只预留,自动折算待 F0-05/F4-04
AccountingPeriod 财务期间 periodId、起止日期、状态 OPEN/CLOSING/CLOSED、关闭人/时间、版本 Finance 唯一维护;CLOSED 禁止直接改账,只能补差/冲销
DocumentSequence 单据号 业务类型(AR/AP/账单/收付款/核销/发票/钱包)、期间、序号、版本 服务端发号;同一幂等重试返回原单号,不由客户端生成
FeeSource 财务费用来源 sourceType、sourceId=feeDetailId、sourceFeeId、sourceVersion、来源单号、客户、方向、业务日、币种、金额、字段快照、sourceHash、入池状态 Finance 的不可变接入记录;RECEIVABLE 已发布费用可进入来源池,PAYABLE 需供应商/主体/凭证等产品条件确认
FeeSourceLine 来源明细 费用编码/名称快照、数量、单位、单价、金额、业务维度和来源明细 ID 与 FeeSource 同版本冻结;不能用当前 Firstleg 表实时重算历史
SourceInbox 事件接收记录 eventId、事件类型、契约版本、幂等键、payloadHash、状态 RECEIVED/PROCESSED/RETRY/CONFLICT/DLQ、重试信息、错误码 Finance 本地 Inbox;唯一键至少 (eventId) 和 (sourceType,sourceId,sourceVersion)
FinanceAuditLog 财务审计 事件/操作编码、主体/客户范围、操作者、请求/链路 ID、前后快照、结果、错误码、时间 追加写、不可物理删除;敏感字段脱敏

F1-F4 的账单、应收、付款、钱包、发票和利润对象引用 FeeSource,但不改变 F0 来源快照。一个有效来源版本只能进入一张未作废账单明细;出账后调整产生新来源或补差/冲销记录。

3. 来源入池与快照规则

3.1 入池门禁

Finance 仅接受满足以下技术条件的来源:

  1. 事件声明来源版本处于 Firstleg 的有效发布版本,来源明细 reviewStatus=APPROVED、publicationStatus=PUBLISHED;
  2. settlementIncluded=true,方向为 RECEIVABLE;来源未作废、未被有效账单使用;
  3. 客户、入库单、费用明细、来源版本、币种、金额和业务日期齐全,金额非负;
  4. sourceHash 可由事件内容与查询返回的规范化快照校验;校验失败不得入账;
  5. Finance 当前期间允许入池。期间关闭不阻止接收事件,但应转为待处理并返回期间错误。

是否允许客户应付、供应商成本、预收、税额、汇率折算及历史费用迁移,不由本技术契约推断,分别受 F0/F2/F3/F4 门禁约束。

3.2 快照内容

Firstleg 内部查询至少返回:来源 ID/版本/单号、入库单 ID/编号、客户 ID/名称/发票抬头、方向、业务日、费用项目编码/名称/分类快照、渠道和目的地、包裹数、实重/体积/计费量/计费单位、单价、币种、金额、审核/发布时间、settlementIncluded、作废/替代关系及 sourceHash。可选的计费规则、价格表和税模式字段作为追溯扩展。

Finance 持久化查询返回的完整快照及接收时的事件元数据。历史账单只引用此快照;Firstleg 后续 SUPERSEDED 不会覆盖已出账明细。

4. Firstleg → Finance 事件契约 v1

4.1 Envelope

事件使用可靠本地 Outbox 发布,v1 消息主题固定为 finance.firstleg.fee.v1,事件体为 JSON:

{
  "eventId": "190000000000000001",
  "eventType": "FIRSTLEG_FEE_PUBLISHED",
  "schemaVersion": 1,
  "occurredAt": "2026-08-10T08:00:00Z",
  "producer": "firstleg-service",
  "tenantId": "10001",
  "customerId": "10001",
  "sourceType": "INBOUND_FEE_DETAIL",
  "sourceId": "200000000000000001",
  "sourceFeeId": "300000000000000001",
  "sourceVersion": 2,
  "sourceFeeNo": "FBAF-778-2",
  "inboundId": "778",
  "feeDetailId": "200000000000000001",
  "direction": "RECEIVABLE",
  "businessDate": "2026-08-09",
  "currency": "CNY",
  "amount": "1280.00",
  "sourceHash": "sha256:...",
  "traceId": "trace-...",
  "requestId": "req-..."
}

必填字段是上例中除 traceId/requestId 外的全部字段;金额必须是字符串,ID 必须是十进制字符串。事件类型固定为:

  • FIRSTLEG_FEE_PUBLISHED:产生/重新发布一个有效来源版本;
  • FIRSTLEG_FEE_ADJUSTED:替代旧发布版本,必须带 supersedesSourceId/supersedesSourceVersion;
  • FIRSTLEG_FEE_VOIDED:作废来源,必须带作废原因和操作者信息。

事件只声明来源索引,不承载可被截断的完整账单字段;Finance 必须随后调用内部查询获取快照。Outbox 记录的 id 作为 eventId,事件入库与 Firstleg 费用状态变更必须在同一事务内完成。

4.2 当前事件兼容与迁移

现有 FIRSTLEG_FEE_SENT 仅允许作为迁移期别名:消费者可解析其 feeId,调用内部查询后映射为 FIRSTLEG_FEE_PUBLISHED,但不得把最小 payload 直接入财务来源。Firstleg 发布正式 v1 后停止新增 FEE_SENT;旧事件重放必须保留原 eventId,不得重新生成导致重复来源。FIRSTLEG_FEE_ADJUSTED 旧 payload 缺少完整替代关系时同样必须查询补齐,否则进入 CONFLICT/DLQ。

5. Finance → Firstleg 内部查询契约

调用方式:Finance 使用服务间令牌,禁止前端访问。v1 接口固定为:

GET /internal/firstleg/v1/finance/fee-sources/{sourceType}/{sourceId}/versions/{sourceVersion}
Authorization: Bearer <service-token>
X-Request-Id: <id>

返回统一业务封装 {code:"200", message, data:{...}};data 为 DTO,不返回 Entity。查询必须按 tenantId 服务端上下文隔离,且校验版本的客户、发布状态、作废/替代关系和 sourceHash。业务成功/失败使用 HTTP 200,由 body code 区分成功、来源不存在和版本/状态冲突;服务身份失败使用 HTTP 401/403,网关或基础设施暂时不可用可使用 HTTP 503。

Finance 不以当前查询结果覆盖已保存快照。查询超时、5xx 或网络错误可重试;明确的 404/状态冲突不可无限重试,应记录错误并进入人工处理。

6. 幂等、乱序、重试与死信

  1. Inbox 先以 eventId 去重,再以 (sourceType,sourceId,sourceVersion) 防止不同事件重复入池。相同键且 payloadHash 相同,返回首次 PROCESSED 结果;相同键内容不同,标记 CONFLICT,错误码 FIN_SOURCE_IDEMPOTENCY_CONFLICT,不得覆盖。
  2. ADJUSTED 可能先于 PUBLISHED 到达。Finance 先保存事件为 RETRY,查询完整快照成功后再按来源版本建立替代关系;不能按到达顺序直接入账。旧版本已出账时只生成待处理补差/冲销来源。
  3. v1 默认采用 1m、5m、30m、2h、12h 的指数退避,最多 5 次且允许配置覆盖;每次保存 retryCount/nextRetryAt/lastErrorCode。超过上限进入 DLQ,支持受权限保护的重放;重放沿用原事件 ID 和幂等键。
  4. 只有来源快照落库、校验通过、Inbox 标记 PROCESSED 后,才允许进入费用池;消费失败不得部分写入财务单据。并发消费使用行锁/版本号,避免双写。

7. 错误码与响应语义

资金指令消费可靠性(2026-09-29)

WalletCommandRequested 先独立提交 eventId/hash 身份记录,再在处理事务使用 Inbox 行锁重新校验 hash;相同 ID 不同 hash 即使并发到达也不能执行钱包。资金变更、成功 Inbox 结果和结果 Outbox 同事务提交,任何结果写入失败均回滚资金变更,原事件保留身份并允许重试。

仅 FIN-WALLET-INSUFFICIENT、FIN-WALLET-HOLD-409、FIN-IDEMPOTENCY-CONFLICT、FIN-REQ-400 作为明确业务拒绝,在原事务回滚后重新锁定事件、确认无既有最终结果,再保存 FAILED 与结果 Outbox。临时数据库异常、处理中、乐观锁冲突、冻结依赖未到达及其他未分类异常继续抛出,不确认消费成功,不固化为最终业务失败。传输确认仍不等于资金业务成功,Lastleg 继续消费资金结果事件。

本条是当前消费者实现规则;单机去桥目标及统一 ACK 见统一认证与服务通信契约,后者尚未整体落地。

通用响应

错误码是跨服务稳定字符串,message 可本地化且不能作为程序判断依据:

错误码 语义 是否重试
FIN_SOURCE_NOT_FOUND 来源或指定版本不存在 否
FIN_SOURCE_NOT_PUBLISHED 来源未达到有效发布门禁 否
FIN_SOURCE_VOIDED 来源已作废 否
FIN_SOURCE_HASH_MISMATCH 事件与快照哈希不一致 否,人工处理
FIN_SOURCE_IDEMPOTENCY_CONFLICT 幂等键对应不同内容 否,人工处理
FIN_SOURCE_VERSION_CONFLICT 乱序/替代关系冲突 否或待人工
FIN_PERIOD_CLOSED 期间已关闭,禁止直接入账/改账 否
FIN_CURRENCY_MISMATCH 跨币种合并或核销 否
FIN_TENANT_SCOPE_DENIED 客户范围不匹配 否
FIN_SERVICE_UNAVAILABLE Firstleg/消息基础设施暂不可用 是
FIN_CONSUME_RETRY_EXHAUSTED 重试耗尽,已进入 DLQ 仅授权重放

8. 权限、客户隔离与审计

Finance 服务端从登录态/服务令牌取得客户范围,绝不信任客户端传入的客户 ID。平台操作 tenantId=0 仅表示平台作用域,访问具体客户仍需目标客户权限;客户用户只能读写自身客户范围。跨客户、跨主体、跨币种和关闭期间操作必须硬拒绝。

v1 权限资源编码固定为(角色如何分配仍待 F0-10):finance:source:view、finance:source:retry、finance:period:manage、finance:document:create、finance:document:review、finance:payment:create、finance:allocation:create、finance:allocation:reverse、finance:invoice:operate、finance:report:export。反核销、付款、关账、红冲/作废和来源重放属于高风险权限,需独立授权,不得复用 Firstleg 费用编辑权限。

每次接收、重试、冲突、重放及后续财务写操作均追加审计:eventId/requestId/traceId、业务客户/主体、操作者及权限域、资源和版本、前后脱敏快照、结果/错误码、时间和服务名。审计写入失败不得回滚已完成的核心交易,但必须告警并可补偿;Token、凭证和敏感附件不得进入日志。

9. 版本与兼容规则

  • 本契约当前为 schemaVersion=1。新增可选字段向后兼容;不得复用字段含义、改变枚举语义或把可选字段改为必填。
  • 破坏性变更递增主版本并使用新主题/事件类型(例如 ...fee.v2),Finance 至少保留 v1 消费窗口;不得静默重写历史事件。
  • 事件和来源都以 (sourceType,sourceId,sourceVersion) 标识业务版本;sourceHash 是对内部查询完整快照计算的 SHA-256,计算时排除 sourceHash 本身及请求/链路元数据,JSON 字段按字典序、无空白、金额按约定精度、时间统一 UTC,结果前缀为 sha256:。同版本哈希不可变。
  • Firstleg 新版本替代旧版本时,旧来源仍可查询;Finance 通过新事件的替代字段建立有向版本链。已出账、已核销或已开票记录只能新增补差/冲销/红冲,不更新原金额。
  • 合同测试必须覆盖:事件字段完整性、版本链、重复/冲突、乱序、重试耗尽、查询 DTO 脱敏及权限范围。

10. 产品门禁(不得由本技术文档代定)

以下事项在财务管理产品确认表仍为“待确认”,本契约只预留扩展,不视为已决定:

  • F0-02 财务主体/账套的实际数量和收付款方向;
  • F0-03 往来单位字段和供应商主数据责任;
  • F0-05/F4-04 汇率来源、折算日、本位币和汇兑损益;
  • F0-08/F2-01 供应商成本、客户应付、PAYABLE 入池条件;
  • F0-10 角色分配、制单/复核是否必须分离及金额分级审批;
  • F1/F2 账期、账单确认、收付款方式、核销关系和反核销流程;
  • F3 钱包资金性质、冻结/退款、税务主体和外部开票平台;
  • F4 收入/成本确认时点、分摊算法、利润维度和导出范围。

在上述门禁未关闭前,只能实现可逆的契约校验、来源接收和审计骨架;不得以原型静态数据或当前人工 PAYABLE 明细推导完整应付、钱包、发票或利润业务。

11. 验收清单

  • 发布、调整、作废均可追溯到不可变来源版本;Finance 接收后保存完整快照。
  • 重复事件不重复生成来源,内容冲突可发现;乱序可重试,失败可进入 DLQ 并授权重放。
  • 查询使用服务令牌和 DTO;跨客户/越权/跨币种/关闭期间操作被拒绝。
  • ID、金额、币种、哈希和版本符合本契约;所有接收与写操作有审计记录。
  • 事件/查询契约冻结后,才进入 F1/F2 财务单据实现;本轮不修改任何代码或其他文档。