2026-08-10 18:12:20 +08:00
|
|
|
|
# 财务领域模型与跨服务契约(历史合并稿)
|
2026-08-10 17:48:45 +08:00
|
|
|
|
|
2026-08-11 18:06:30 +08:00
|
|
|
|
> **历史文档,非一期实施依据**:本合并稿已被《财务领域模型与数据字典》《Firstleg-Finance 跨服务契约》和《财务一期五页产品与接口验收契约》替代。钱包、充值、发票、费用分析和利润模型不属于一期。
|
|
|
|
|
|
|
2026-08-10 18:12:20 +08:00
|
|
|
|
> 文档状态:已被拆分替代,仅保留历史设计过程,不作为后续实施依据
|
|
|
|
|
|
> 替代文档:[财务领域模型与数据字典](财务领域模型与数据字典.md)、[Firstleg-Finance 跨服务契约](Firstleg-Finance跨服务契约.md)
|
|
|
|
|
|
> 原版本:F0-V1.0(历史稿)
|
2026-08-10 17:48:45 +08:00
|
|
|
|
> 日期: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. 错误码与响应语义
|
|
|
|
|
|
|
|
|
|
|
|
错误码是跨服务稳定字符串,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 财务单据实现;本轮不修改任何代码或其他文档。
|