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

190 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 财务领域模型与跨服务契约(历史合并稿)
> **历史文档,非订单与客户实施依据**:本合并稿已被《财务领域模型与数据字典》《Firstleg-Finance 跨服务契约》和《财务核心五页产品与接口验收契约》替代。钱包、充值、发票、费用分析和利润模型不属于订单与客户。
> 文档状态:已被拆分替代,仅保留历史设计过程,不作为后续实施依据
> 替代文档:[财务领域模型与数据字典](财务领域模型与数据字典.md)、[Firstleg-Finance 跨服务契约](Firstleg-Finance跨服务契约.md)
> 原版本:F0-V1.0(历史稿)
> 日期:2026-08-10
> 适用:`finance-service` F0,以及 Firstleg → Finance 的费用来源接入
本文只定义财务 F0 的领域对象、来源快照和服务间契约,不创建服务、数据库或业务代码。范围和服务边界以[财务服务边界设计](财务服务边界设计.md)为准;财务全量产品项仍以[财务管理产品确认表](财务管理产品确认表.md)为准。
## 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:
```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 接口固定为:
```text
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 见[统一认证与服务通信契约](../统一认证与服务通信契约.md),后者尚未整体落地。
### 通用响应
错误码是跨服务稳定字符串,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. 产品门禁(不得由本技术文档代定)
以下事项在[财务管理产品确认表](财务管理产品确认表.md)仍为“待确认”,本契约只预留扩展,不视为已决定:
- 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 财务单据实现;本轮不修改任何代码或其他文档。