This commit is contained in:
2026-08-11 18:17:02 +08:00
parent 767ffe8544
commit 60d2f8e097
8 changed files with 407 additions and 89 deletions
@@ -92,7 +92,7 @@
| D11 | 客户状态可见性 | **后端完成。** 客户账单只见 `ISSUED/PARTIALLY_PAID/PAID`;客户收款只见 `CONFIRMED`,使用 `ClientReceipt` 专用 DTO 并去除凭证、操作人和内部字段;脱敏测试通过 | 后端门禁关闭;保留跨客户与序列化回归 |
| D12 | Finance 费用来源写入 | **后端完成。** 内部事件入口、令牌鉴权、Inbox 幂等、Firstleg 快照回查、来源写入/替代/作废均已实现;非作废事件必须 `PUBLISHED`,未发布返回 `FIN-EVT-015`;金额必须 `>0`,零/负返回 `FIN-EVT-010`;专项 10/10、Finance 全量 35/35 通过 | 后端门禁关闭;保留真实跨服务 HTTP 冒烟 |
| D13 | 柜子费用来源 | **后端完成并验证。** Firstleg 已有柜子费用模型、审核/参与结算/作废、事件、Relay 和内部快照;Firstleg 相关 17/17、Finance 柜子来源消费测试通过 | 后端门禁关闭;测试环境补真实跨服务事件冒烟 |
| D14 | 一期 5 页 | **后端查询契约完成,页面开发中。** 五页固定 DTO 路径、Client 脱敏和错误码已经验证;11 个目标写/明细 operation 仍真实标记 `NOT_IMPLEMENTED`,Admin 正在接入 | 完成 Admin 五页矩阵和剩余 11 个 operation;删除费用分析、利润表、充值及假入口并完成端到端验收 |
| D14 | 一期 5 页 | **FAIL / 一期 P0 未关闭。** 部分五页固定 DTO 路径、Client 脱敏和错误码已经验证;11 个目标 operation 仍真实标记 `NOT_IMPLEMENTED`,且全部属于一期 P0,不得列为后续 | 按《财务一期P0操作契约与验收用例》逐项完成 Controller/DTO、状态机、权限、幂等、错误码、客户隔离和直接行为测试,再完成 Admin 五页端到端验收 |
## 4. 非阻断但需锁定的契约说明
@@ -107,7 +107,7 @@
进入前后端联调前至少满足:
1. D1-D8、D10-D13 已具备后端直接证据;剩余以 D9 和 D14 的 11 个 operation/Admin 验收为准。
1. D1-D8、D10-D13 已具备后端直接证据;D14 当前 FAIL,11 个 operation 全部为一期 P0,任一未完成均不得通过一期完整联调验收。
2. D2 已固定为首期非分页数组;后续若改服务端分页须另行升级契约。
3. 幂等门禁已关闭;后续写 operation 继续复用同参重放、异参冲突和恢复语义。
4. Finance 集成测试覆盖:默认 30 天、CNY 拒绝、出账冻结、300 分 3 单、超额/跨客户/草稿拒绝、部分收款到收清、调整修订、已收不可作废。
@@ -118,8 +118,8 @@
## 6. 放行结论
- **当前阶段:专项修复中。** Finance、SQL、Admin 和 Client 均已有实现,不再按“未开发”管理。
- **可继续开发:是。** 路径、权限、固定 DTO、凭证、分摊、幂等和客户可见性已经冻结,不再需要新增产品决策。
- **可继续开发:是。** 11 个 P0 的路径、权限、DTO、状态、凭证、幂等、错误码和客户隔离已经冻结,不再需要新增产品决策。
- **可进行接口冒烟:有条件。** 可验证现有路径、鉴权和基础创建/查询,但结果不代表业务验收通过。
- **可进入完整联调验收:否。** Finance 来源写入及 `PUBLISHED + amount>0`、五页 DTO、完整幂等、File fail-closed、客户脱敏、稳定错误码和柜子来源已经通过后端测试,不再是阻断;剩余阻断是 11 个目标 operation、Admin 五页接入和真实跨服务冒烟。
- **可进入完整联调验收:否。** Finance 来源写入及 `PUBLISHED + amount>0`、现有查询 DTO、完整幂等、File fail-closed、客户脱敏、稳定错误码和柜子来源已经通过后端测试;11 个 P0 operation、Admin 五页接入和真实跨服务冒烟未完成。11 个 P0 不得降级为后续事项。
本次审查只修订文档与 OpenAPI,未修改 Java、前端或 SQL。
+4 -1
View File
@@ -1,7 +1,7 @@
# 财务管理闭环盘点与范围建议
> 盘点日期:2026-08-10
> 当前状态:F0/F1 已有 Finance 服务、数据库、权限、Admin/Client 页面和基础接口;费用来源主链、固定 DTO、脱敏、凭证 fail-closed 和稳定错误码已通过后端验证,剩余为五页页面与未实现 operation 收口
> 当前状态:F0/F1 已有 Finance 服务、数据库、权限、Admin/Client 页面和基础接口;费用来源主链、固定 DTO、脱敏、凭证 fail-closed 和稳定错误码已通过后端验证;尚未实现的 11 个 operation 全部属于一期 P0 阻断,不得排到后续
> 盘点范围:二期入库计费现状、平台后台财务原型、Admin/Client/Firstleg 当前实现
> 结论口径:本文区分“代码已实现”“原型展示”和“建议范围”;原型功能不自动成为开发范围。
@@ -15,6 +15,8 @@
一期领导验收唯一范围入口:[财务一期五页产品与接口验收契约](财务一期五页产品与接口验收契约.md)。
一期 11 个 P0 操作、状态机与验收用例:[财务一期 P0 操作契约与验收用例](财务一期P0操作契约与验收用例.md)。
文档优先级:五页验收契约和本轮产品确认 > 五页目标 OpenAPI > 既有人工 F1 契约/门禁 > 领域模型、历史全量规划和迁移文档。旧文档用于追溯,不得据此把钱包、充值、发票、费用分析或利润表重新纳入一期。
旧文档降级规则:
@@ -35,6 +37,7 @@
- [F1 人工财务契约审查与门禁](F1人工财务契约审查与门禁.md)
- [财务一期五页产品与接口验收契约](财务一期五页产品与接口验收契约.md)
- [财务一期五页 OpenAPI](openapi/finance-phase1-five-pages-v1.yaml)
- [财务一期 P0 操作契约与验收用例](财务一期P0操作契约与验收用例.md)
- [财务历史迁移与上线验收方案](财务历史迁移与上线验收方案.md)
- [财务F0实施任务拆分](财务F0实施任务拆分.md)
@@ -6,7 +6,8 @@ info:
一期领导验收 5 页目标契约:客户账户、客户应收应付、客户对账单、结算单应收、
结算单应付。只支持 CNY。费用分析、利润表、钱包、预收和充值不属于本契约。
每个 operation 通过 x-implementation-status 标记当前 Java 实现事实;NOT_IMPLEMENTED
仅代表目标契约尚未落地,不得生成按钮或模拟成功入口。
代表一期 P0 尚未落地,不得排到后续、生成按钮或模拟成功入口。只有 Controller/DTO
与直接行为测试均提供证据后,才允许逐项改为 IMPLEMENTED。
servers:
- url: /api
x-product-scope:
@@ -31,6 +32,25 @@ x-current-verification:
checkedAt: '2026-08-11'
source: 当前 baiyue-firstleg 工作树中的 Controller、DTO、Service、Client 和现有 Surefire 报告。
testStatus: Finance 35/35、Firstleg 549/549(2 skip)、File 114/114、Gateway 26/26、IAM 163/163、Phase3/PDA SQL 12/12、Order SQL 3/3,均 BUILD SUCCESS;来源消费专项 10/10。
x-phase1-p0-gate:
status: FAIL
notImplementedOperations: 11
rule: 11 个 operation 均为一期 P0;必须有一致的 Controller/固定 DTO/权限/状态机/客户隔离/幂等及直接行为测试证据,方可逐项改为 IMPLEMENTED。
contract: ../财务一期P0操作契约与验收用例.md
x-idempotency-contract:
scope: method + normalizedPath + resolvedCustomerId + completeQueryAndBody
sameKeySameRequest: replay original result
sameKeyDifferentRequest: FIN_409
processing: FIN_409;超时后按现有 Finance 抢占恢复规则执行
failed: 按现有 Finance FAILED 恢复规则执行
x-error-contract:
FIN_400: 参数、日期、CNY 或正金额校验失败
FIN_404: 对象不存在或跨客户对象隐藏
FIN_409: 非法状态、乐观锁、重复占用或幂等冲突
FIN_503: File 或内部依赖不可用且 fail-closed
FIN_001: 未分类服务端错误
authentication: HTTP 401
permission: HTTP 403
paths:
/internal/finance/v1/fee-source-events:
post:
@@ -84,6 +104,14 @@ paths:
summary: 查询客户财务账户派生汇总
description: 只读聚合,不返回钱包余额,不提供充值或余额调整动作。
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: AccountListQuery
x-response-dto: AccountPageEnvelope
x-idempotency: READ_ONLY_NOT_APPLICABLE
x-customer-isolation: 平台权限下按真实客户主体聚合;不得返回 tenantId,不得产生可写余额。
x-state-machine: READ_ONLY_DERIVED_VIEW
x-error-codes: [FIN_400, FIN_001]
x-acceptance-cases: [AC-01, AC-02]
x-permission: platform:finance:account:view
parameters:
- $ref: '#/components/parameters/Page'
@@ -96,6 +124,8 @@ paths:
schema: {type: string, enum: [ACTIVE, DISABLED]}
responses:
'200': {$ref: '#/components/responses/AccountPageResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/customer-accounts/{customerId}:
get:
operationId: getDerivedCustomerAccount
@@ -113,6 +143,14 @@ paths:
operationId: listDerivedCustomerAccountLedger
summary: 查询客户财务派生流水
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: AccountLedgerQuery
x-response-dto: LedgerPageEnvelope
x-idempotency: READ_ONLY_NOT_APPLICABLE
x-customer-isolation: customerId 必须解析为真实客户,流水只聚合该客户数据;跨客户对象不得出现。
x-state-machine: READ_ONLY_DERIVED_VIEW
x-error-codes: [FIN_400, FIN_404, FIN_001]
x-acceptance-cases: [AC-03, AC-04]
x-permission: platform:finance:account:view
parameters:
- $ref: '#/components/parameters/CustomerId'
@@ -129,6 +167,9 @@ paths:
schema: {type: string, format: date}
responses:
'200': {$ref: '#/components/responses/LedgerPageResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/fee-pool:
get:
operationId: listFinanceFeeSources
@@ -151,17 +192,34 @@ paths:
operationId: getFinanceFeeSource
summary: 查询费用来源详情、业务关联和操作记录
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: FeeSourceIdPath
x-response-dto: FeeSourceDetailEnvelope
x-idempotency: READ_ONLY_NOT_APPLICABLE
x-customer-isolation: 按费用来源真实 customerId 返回;跨客户关联按 FIN_404 隐藏。
x-state-machine: READ_ONLY_ALL_SOURCE_STATES
x-error-codes: [FIN_404, FIN_001]
x-acceptance-cases: [AC-05]
x-permission: platform:finance:fee-source:view
parameters:
- $ref: '#/components/parameters/FeeSourceId'
responses:
'200': {$ref: '#/components/responses/FeeSourceDetailResponse'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/fee-sources/{feeSourceId}/review:
post:
operationId: reviewFinanceFeeSource
summary: 审核费用来源
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: ReviewRequest
x-response-dto: FeeSourceDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: 只按 feeSourceId 解析其真实客户;请求不得覆盖 customerId 或金额。
x-state-machine: PENDING -> APPROVED | REJECTED
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_001]
x-acceptance-cases: [AC-06, AC-07, AC-29]
x-permission: platform:finance:fee-source:review
parameters:
- $ref: '#/components/parameters/FeeSourceId'
@@ -173,12 +231,23 @@ paths:
schema: {$ref: '#/components/schemas/ReviewRequest'}
responses:
'200': {$ref: '#/components/responses/FeeSourceDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/fee-sources/{feeSourceId}/settlement-inclusion:
put:
operationId: updateFeeSourceSettlementInclusion
summary: 设置参与结算或禁止结算
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: SettlementInclusionRequest
x-response-dto: FeeSourceDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: 只按 feeSourceId 解析其真实客户;跨客户业务引用按 FIN_404 隐藏。
x-state-machine: APPROVED + PUBLISHED + AVAILABLE + CNY 可切换;LOCKED/BILLED/VOIDED/SUPERSEDED 禁止修改
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_001]
x-acceptance-cases: [AC-08, AC-09, AC-29]
x-permission: platform:finance:fee-source:settlement
parameters:
- $ref: '#/components/parameters/FeeSourceId'
@@ -190,6 +259,9 @@ paths:
schema: {$ref: '#/components/schemas/SettlementInclusionRequest'}
responses:
'200': {$ref: '#/components/responses/FeeSourceDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/statements:
get:
@@ -209,6 +281,14 @@ paths:
operationId: createReconciliationStatement
summary: 按截止时间或勾选来源生成客户对账单草稿
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: CreateReconciliationRequest
x-response-dto: ReconciliationDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: 所有来源必须属于 request.customerId;跨客户来源按 FIN_404,禁止部分落库。
x-state-machine: 合格且未占用来源 -> DRAFT;创建时锁定所选来源
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_001]
x-acceptance-cases: [AC-10, AC-11, AC-12, AC-29]
x-permission: platform:finance:reconciliation:create
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
@@ -219,6 +299,9 @@ paths:
schema: {$ref: '#/components/schemas/CreateReconciliationRequest'}
responses:
'200': {$ref: '#/components/responses/ReconciliationDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/bills/{billId}:
get:
@@ -235,9 +318,24 @@ paths:
/platform/finance/v1/reconciliation-statements/{statementId}/actions:
post:
operationId: actionReconciliationStatement
summary: 提交审核、通过、拒绝或作废客户对账单
summary: 提交、审核、出账、调整、作废或重开客户对账单
x-implementation-status: NOT_IMPLEMENTED
x-permission: platform:finance:reconciliation:review
x-delivery-priority: PHASE1_P0
x-request-dto: ReconciliationActionRequest
x-response-dto: ReconciliationDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: statementId 决定客户;调整行必须全部属于该对账单,跨客户按 FIN_404。
x-state-machine: DRAFT->PENDING_REVIEW->APPROVED->ISSUED;REJECTED/VOIDED 可 REOPEN 新 revision;ISSUED 仅 ADJUST 新 revision
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_001]
x-acceptance-cases: [AC-13, AC-14, AC-15, AC-16, AC-17, AC-29]
x-permission-by-action:
SUBMIT: platform:finance:reconciliation:create
APPROVE: platform:finance:reconciliation:review
REJECT: platform:finance:reconciliation:review
ISSUE: platform:finance:reconciliation:issue
ADJUST: platform:finance:reconciliation:adjust
VOID: platform:finance:reconciliation:void
REOPEN: platform:finance:reconciliation:void
parameters:
- $ref: '#/components/parameters/StatementId'
- $ref: '#/components/parameters/IdempotencyKey'
@@ -245,9 +343,12 @@ paths:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/DocumentActionRequest'}
schema: {$ref: '#/components/schemas/ReconciliationActionRequest'}
responses:
'200': {$ref: '#/components/responses/ReconciliationDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/receivable-settlements:
get:
@@ -267,6 +368,14 @@ paths:
operationId: createReceivableSettlement
summary: 从审核通过的客户对账单生成应收结算单
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: CreateReceivableSettlementRequest
x-response-dto: SettlementDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: 客户从对账单派生,不接受前端覆盖;跨客户关联按 FIN_404。
x-state-machine: ISSUED reconciliation -> ISSUED RECEIVABLE settlement;一张对账单只允许一个有效 AR
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_001]
x-acceptance-cases: [AC-18, AC-19, AC-29]
x-permission: platform:finance:settlement-ar:create
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
@@ -277,6 +386,9 @@ paths:
schema: {$ref: '#/components/schemas/CreateReceivableSettlementRequest'}
responses:
'200': {$ref: '#/components/responses/SettlementDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/payable-settlements:
get:
@@ -296,6 +408,14 @@ paths:
operationId: createPayableSettlement
summary: 从审核通过且参与结算的应付来源生成应付结算单
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: CreatePayableSettlementRequest
x-response-dto: SettlementDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: 所有 PAYABLE 来源必须同 customerId 且同 supplierId;跨客户来源按 FIN_404。
x-state-machine: 合格未占用 PAYABLE 来源 -> ISSUED PAYABLE settlement
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_001]
x-acceptance-cases: [AC-20, AC-21, AC-29]
x-permission: platform:finance:settlement-ap:create
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
@@ -306,6 +426,9 @@ paths:
schema: {$ref: '#/components/schemas/CreatePayableSettlementRequest'}
responses:
'200': {$ref: '#/components/responses/SettlementDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/receipts:
get:
@@ -356,6 +479,14 @@ paths:
operationId: getSettlement
summary: 查询应收或应付结算单详情
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: SettlementIdPath
x-response-dto: SettlementDetailEnvelope
x-idempotency: READ_ONLY_NOT_APPLICABLE
x-customer-isolation: 按结算单真实客户返回;不得返回其他客户数据,客户侧仍使用独立脱敏接口。
x-state-machine: READ_ONLY_ALL_SETTLEMENT_STATES
x-error-codes: [FIN_404, FIN_001]
x-acceptance-cases: [AC-22, AC-23]
x-permission-any-of:
- platform:finance:settlement-ar:view
- platform:finance:settlement-ap:view
@@ -363,12 +494,21 @@ paths:
- $ref: '#/components/parameters/SettlementId'
responses:
'200': {$ref: '#/components/responses/SettlementDetailResponse'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
/platform/finance/v1/settlements/{settlementId}/transactions:
post:
operationId: createSettlementPayment
summary: 人工登记应付结算单付款并上传凭证
x-implementation-status: NOT_IMPLEMENTED
x-delivery-priority: PHASE1_P0
x-request-dto: CreateSettlementPaymentRequest
x-response-dto: SettlementDetailEnvelope
x-idempotency: FULL_REQUEST_HASH_SAME_REPLAY_DIFFERENT_CONFLICT
x-customer-isolation: settlementId 决定客户和供应商;凭证必须属于同一客户的 FINANCE_VOUCHER。
x-state-machine: PAYABLE ISSUED -> PARTIALLY_PAID -> PAID;金额不得超过 outstandingAmount
x-error-codes: [FIN_400, FIN_404, FIN_409, FIN_503, FIN_001]
x-acceptance-cases: [AC-24, AC-25, AC-26, AC-29]
x-permission: platform:finance:payment:create
parameters:
- $ref: '#/components/parameters/SettlementId'
@@ -380,7 +520,11 @@ paths:
schema: {$ref: '#/components/schemas/CreateSettlementPaymentRequest'}
responses:
'200': {$ref: '#/components/responses/SettlementDetailResponse'}
'400': {$ref: '#/components/responses/BusinessError'}
'403': {$ref: '#/components/responses/BusinessError'}
'404': {$ref: '#/components/responses/BusinessError'}
'409': {$ref: '#/components/responses/BusinessError'}
'503': {$ref: '#/components/responses/BusinessError'}
components:
parameters:
InternalServiceToken:
@@ -472,6 +616,9 @@ components:
Money:
type: string
pattern: '^(0|[1-9][0-9]*)(\.[0-9]{1,6})?$'
PositiveMoney:
type: string
pattern: '^(0\.(?=[0-9]{0,5}[1-9])[0-9]{1,6}|[1-9][0-9]*(\.[0-9]{1,6})?)$'
FeeDirection:
type: string
enum: [RECEIVABLE, PAYABLE]
@@ -483,7 +630,7 @@ components:
enum: [PENDING, APPROVED, REJECTED]
ReconciliationStatus:
type: string
enum: [DRAFT, PENDING_REVIEW, APPROVED, REJECTED, VOIDED]
enum: [DRAFT, PENDING_REVIEW, APPROVED, ISSUED, REJECTED, VOIDED]
SettlementStatus:
type: string
enum: [DRAFT, PENDING_REVIEW, ISSUED, PARTIALLY_PAID, PAID, REJECTED, VOIDED]
@@ -672,15 +819,44 @@ components:
allOf:
- $ref: '#/components/schemas/ReconciliationSummary'
- type: object
required: [lines, paymentHistory]
required: [lines, paymentHistory, revisions]
properties:
remark: {type: string}
lines:
type: array
items: {$ref: '#/components/schemas/FeeSource'}
items: {$ref: '#/components/schemas/ReconciliationLine'}
paymentHistory:
type: array
items: {$ref: '#/components/schemas/SettlementTransaction'}
revisions:
type: array
items: {$ref: '#/components/schemas/ReconciliationRevision'}
ReconciliationLine:
type: object
required: [statementLineId, feeSourceId, sourceVersion, feeItemName, sourceAmount, statementAmount, currency]
properties:
statementLineId: {$ref: '#/components/schemas/Id'}
feeSourceId: {$ref: '#/components/schemas/Id'}
sourceVersion: {type: integer, minimum: 1}
sourceHash: {type: string}
inboundId: {$ref: '#/components/schemas/Id'}
containerId: {$ref: '#/components/schemas/Id'}
feeItemName: {type: string}
sourceAmount: {$ref: '#/components/schemas/Money'}
statementAmount: {$ref: '#/components/schemas/Money'}
currency: {type: string, enum: [CNY]}
adjustmentReason: {type: string}
ReconciliationRevision:
type: object
required: [revisionNo, action, beforeAmount, afterAmount, operatorId, occurredAt]
properties:
revisionNo: {type: integer, minimum: 1}
action: {type: string, enum: [CREATE, SUBMIT, APPROVE, REJECT, ISSUE, ADJUST, VOID, REOPEN]}
beforeAmount: {$ref: '#/components/schemas/Money'}
afterAmount: {$ref: '#/components/schemas/Money'}
reason: {type: string}
operatorId: {$ref: '#/components/schemas/Id'}
occurredAt: {type: string, format: date-time}
StatementRead:
type: object
required: [id, customerId, billNo, billDate, dueDate, totalAmount, receivedAmount, outstandingAmount, currency, status, version]
@@ -756,24 +932,57 @@ components:
version: {type: integer, minimum: 0}
CreateReconciliationRequest:
type: object
required: [customerId, generationMode, periodStart, periodEnd]
required: [customerId, currency, generationMode, periodStart, periodEnd]
description: SELECTED 模式 feeSourceIds 至少一项;BY_CUTOFF 模式由服务端按 periodEnd 选取合格来源。来源金额只能取服务端快照。
properties:
customerId: {$ref: '#/components/schemas/Id'}
currency: {type: string, enum: [CNY]}
generationMode: {type: string, enum: [BY_CUTOFF, SELECTED]}
feeSourceIds:
type: array
minItems: 1
uniqueItems: true
items: {$ref: '#/components/schemas/Id'}
periodStart: {type: string, format: date}
periodEnd: {type: string, format: date}
remark: {type: string, maxLength: 500}
DocumentActionRequest:
oneOf:
- properties:
generationMode: {type: string, enum: [BY_CUTOFF]}
not: {required: [feeSourceIds]}
- required: [feeSourceIds]
properties:
generationMode: {type: string, enum: [SELECTED]}
ReconciliationActionRequest:
type: object
required: [action, reason, expectedVersion]
required: [action, expectedVersion]
description: REJECT/ADJUST/VOID/REOPEN 必须填写 reason;ADJUST 必须提交至少一条 adjustmentLines,且调整后总额不得低于已收金额。
properties:
action: {type: string, enum: [SUBMIT, APPROVE, REJECT, VOID]}
reason: {type: string, minLength: 1, maxLength: 500}
action: {type: string, enum: [SUBMIT, APPROVE, REJECT, ISSUE, ADJUST, VOID, REOPEN]}
reason: {type: string, maxLength: 500}
expectedVersion: {type: integer, minimum: 0}
adjustmentLines:
type: array
minItems: 1
items: {$ref: '#/components/schemas/StatementAdjustmentLineRequest'}
oneOf:
- properties:
action: {type: string, enum: [SUBMIT, APPROVE, ISSUE]}
- required: [reason]
properties:
action: {type: string, enum: [REJECT, VOID, REOPEN]}
reason: {type: string, minLength: 1, maxLength: 500}
- required: [reason, adjustmentLines]
properties:
action: {type: string, enum: [ADJUST]}
reason: {type: string, minLength: 1, maxLength: 500}
StatementAdjustmentLineRequest:
type: object
required: [statementLineId, adjustedAmount, reason]
properties:
statementLineId: {$ref: '#/components/schemas/Id'}
adjustedAmount: {$ref: '#/components/schemas/Money'}
reason: {type: string, minLength: 1, maxLength: 500}
SettlementSummary:
type: object
required: [settlementId, settlementNo, direction, currency, totalAmount, paidAmount, outstandingAmount, generatedDate, dueDate, paymentStatus, status, version]
@@ -800,7 +1009,7 @@ components:
allOf:
- $ref: '#/components/schemas/SettlementSummary'
- type: object
required: [lines, transactions]
required: [lines, transactions, revisions]
properties:
lines:
type: array
@@ -808,15 +1017,32 @@ components:
transactions:
type: array
items: {$ref: '#/components/schemas/SettlementTransaction'}
revisions:
type: array
items: {$ref: '#/components/schemas/SettlementRevision'}
SettlementRevision:
type: object
required: [revisionNo, action, beforeAmount, afterAmount, operatorId, occurredAt]
properties:
revisionNo: {type: integer, minimum: 1}
action: {type: string, enum: [CREATE, ISSUE, RECEIPT, PAYMENT, ADJUST, VOID, REOPEN]}
beforeAmount: {$ref: '#/components/schemas/Money'}
afterAmount: {$ref: '#/components/schemas/Money'}
reason: {type: string}
operatorId: {$ref: '#/components/schemas/Id'}
occurredAt: {type: string, format: date-time}
CreateReceivableSettlementRequest:
type: object
required: [reconciliationStatementId]
required: [reconciliationStatementId, currency]
description: 客户、金额、期间和默认 30 天到期日均从已出账对账单与客户账期派生,不接受前端覆盖。
properties:
reconciliationStatementId: {$ref: '#/components/schemas/Id'}
currency: {type: string, enum: [CNY]}
CreatePayableSettlementRequest:
type: object
required: [supplierId, feeSourceIds, dueDate]
required: [customerId, supplierId, feeSourceIds, dueDate, currency]
properties:
customerId: {$ref: '#/components/schemas/Id'}
supplierId: {$ref: '#/components/schemas/Id'}
feeSourceIds:
type: array
@@ -824,6 +1050,7 @@ components:
uniqueItems: true
items: {$ref: '#/components/schemas/Id'}
dueDate: {type: string, format: date}
currency: {type: string, enum: [CNY]}
SettlementTransaction:
type: object
required: [transactionId, transactionType, transactionDate, amount, method, createdBy, createdAt]
@@ -831,9 +1058,12 @@ components:
transactionId: {$ref: '#/components/schemas/Id'}
transactionType: {type: string, enum: [RECEIPT, PAYMENT]}
transactionDate: {type: string, format: date}
amount: {$ref: '#/components/schemas/Money'}
amount: {$ref: '#/components/schemas/PositiveMoney'}
method: {type: string, enum: [BANK_TRANSFER, CASH, OTHER]}
referenceNo: {type: string}
voucherFileIds:
type: array
items: {$ref: '#/components/schemas/Id'}
createdBy: {$ref: '#/components/schemas/Id'}
createdAt: {type: string, format: date-time}
remark: {type: string}
@@ -842,14 +1072,14 @@ components:
required: [settlementId, amount]
properties:
settlementId: {$ref: '#/components/schemas/Id'}
amount: {$ref: '#/components/schemas/Money'}
amount: {$ref: '#/components/schemas/PositiveMoney'}
CreateReceiptRequest:
type: object
required: [customerId, receivedDate, amount, paymentMethod, voucherFileIds, allocations]
properties:
customerId: {$ref: '#/components/schemas/Id'}
receivedDate: {type: string, format: date}
amount: {$ref: '#/components/schemas/Money'}
amount: {$ref: '#/components/schemas/PositiveMoney'}
currency: {type: string, enum: [CNY], default: CNY}
paymentMethod: {type: string, enum: [BANK_TRANSFER, CASH, OTHER]}
voucherFileIds:
@@ -914,10 +1144,12 @@ components:
version: {type: integer, minimum: 0}
CreateSettlementPaymentRequest:
type: object
required: [transactionDate, amount, method, voucherFileIds]
required: [transactionDate, amount, currency, method, voucherFileIds, expectedVersion]
description: 仅用于 PAYABLE 结算单人工付款;amount 必须大于 0 且不得超过未付余额,File 校验必须 fail-closed。
properties:
transactionDate: {type: string, format: date}
amount: {$ref: '#/components/schemas/Money'}
amount: {$ref: '#/components/schemas/PositiveMoney'}
currency: {type: string, enum: [CNY]}
method: {type: string, enum: [BANK_TRANSFER, CASH, OTHER]}
referenceNo: {type: string}
voucherFileIds:
@@ -925,6 +1157,7 @@ components:
minItems: 1
uniqueItems: true
items: {$ref: '#/components/schemas/Id'}
expectedVersion: {type: integer, minimum: 0}
remark: {type: string, maxLength: 500}
ErrorEnvelope:
allOf:
@@ -0,0 +1,114 @@
# 财务一期 P0 操作契约与验收用例
> 日期:2026-08-11
> 适用范围:一期 5 页闭环中尚未实现的 11 个 operation
> 状态口径:以下 11 项全部是一期 P0;只有 Controller/DTO 已落地且直接行为测试通过后,才能逐项把 OpenAPI 的 `NOT_IMPLEMENTED` 改为 `IMPLEMENTED`
> 机器契约:`openapi/finance-phase1-five-pages-v1.yaml`
## 1. 统一冻结规则
- 金额只允许 CNY;请求出现其他币种或金额 `<= 0` 返回 `FIN_400`。
- 财务操作全部由平台财务人员人工提交表单。客户只能读取自己的已出账结果,不能审核、生成、调整、作废或登记收付款。
- 客户账户是账单、费用来源和收付款派生视图,不建设余额表,不提供钱包、预收、充值或余额调整接口。
- 平台接口使用权限码控制。实体必须按其真实客户归属校验;同一写请求中的来源、对账单、结算单和收付款对象必须属于同一客户。跨客户对象按不存在处理,返回 `FIN_404`,不得泄露对象是否存在。
- 所有写操作必须带 `Idempotency-Key`。请求哈希至少覆盖 method、规范化 path、目标客户、全部 query/body 字段;同键同参重放原结果,同键异参或仍在 `PROCESSING` 返回 `FIN_409`,FAILED/超时 PROCESSING 按现有 Finance 恢复规则执行。
- 带 `expectedVersion` 的操作使用乐观锁;版本不一致返回 `FIN_409`。出账后不原地覆盖,调整、作废和重开必须新增 revision 并保留操作人、原因、时间和前后值。
- 稳定错误码固定为:`FIN_400` 参数/CNY/金额错误,`FIN_404` 不存在或跨客户隐藏,`FIN_409` 状态/版本/重复占用/幂等冲突,`FIN_503` File 或内部依赖不可用,`FIN_001` 未分类服务端错误。认证和权限拒绝使用 HTTP `401/403`。
- 所有成功和业务错误响应固定包含 `requestId`、`traceId`、`timestamp`;成功 body 使用 `code="200"`,不得返回 Entity、Map 或临时字段。
## 2. 11 个 P0 operation 冻结矩阵
| P0 | operationId | 请求 DTO | 响应 DTO | 权限 | 状态/隔离与核心验收 |
|---|---|---|---|---|---|
| P0-01 | `listDerivedCustomerAccounts` | `AccountListQuery`(page/pageSize/keyword/status) | `AccountPageEnvelope<AccountSummary>` | `platform:finance:account:view` | 聚合未收、待出账、本期已收、应付;停用客户仍保留历史金额;无钱包字段。验收 AC-01、AC-02 |
| P0-02 | `listDerivedCustomerAccountLedger` | `AccountLedgerQuery`(customerId path、业务类型、日期、分页) | `LedgerPageEnvelope<LedgerEntry>` | `platform:finance:account:view` | 只返回指定客户来源/单据/收付款派生流水;日期闭区间;跨客户对象不可混入。验收 AC-03、AC-04 |
| P0-03 | `getFinanceFeeSource` | `FeeSourceIdPath` | `FeeSourceDetailEnvelope<FeeSourceDetail>` | `platform:finance:fee-source:view` | 返回业务关联、版本、审核、参与结算、占用和操作日志;不接受前端金额覆盖。验收 AC-05 |
| P0-04 | `reviewFinanceFeeSource` | `ReviewRequest` | `FeeSourceDetailEnvelope<FeeSourceDetail>` | `platform:finance:fee-source:review` | `PENDING -> APPROVED/REJECTED`;非 PENDING、旧版本、已占用均拒绝。验收 AC-06、AC-07 |
| P0-05 | `updateFeeSourceSettlementInclusion` | `SettlementInclusionRequest` | `FeeSourceDetailEnvelope<FeeSourceDetail>` | `platform:finance:fee-source:settlement` | 仅 `APPROVED + PUBLISHED + AVAILABLE + CNY` 可纳入;LOCKED/BILLED/VOIDED/SUPERSEDED 不可改。验收 AC-08、AC-09 |
| P0-06 | `createReconciliationStatement` | `CreateReconciliationRequest` | `ReconciliationDetailEnvelope<ReconciliationDetail>` | `platform:finance:reconciliation:create` | 按截止日或勾选来源生成 DRAFT;来源须同客户、合格且未被有效单据占用,金额由服务端快照读取。验收 AC-10 至 AC-12 |
| P0-07 | `actionReconciliationStatement` | `ReconciliationActionRequest` | `ReconciliationDetailEnvelope<ReconciliationDetail>` | 按 action 使用 review/issue/adjust/void 权限 | 提交、审核、出账、调整、作废、重开均走 revision;ISSUED 冻结来源,调整后总额不得低于已收。验收 AC-13 至 AC-17 |
| P0-08 | `createReceivableSettlement` | `CreateReceivableSettlementRequest` | `SettlementDetailEnvelope<SettlementDetail>` | `platform:finance:settlement-ar:create` | 只从同客户 ISSUED 对账单生成,一张有效对账单只能有一个有效应收结算单;直接进入 ISSUED。验收 AC-18、AC-19 |
| P0-09 | `createPayableSettlement` | `CreatePayableSettlementRequest` | `SettlementDetailEnvelope<SettlementDetail>` | `platform:finance:settlement-ap:create` | 同客户、同供应商的 APPROVED/INCLUDED/AVAILABLE/CNY PAYABLE 来源生成;直接进入 ISSUED。验收 AC-20、AC-21 |
| P0-10 | `getSettlement` | `SettlementIdPath` | `SettlementDetailEnvelope<SettlementDetail>` | AR/AP view 二选一并与单据方向匹配 | 返回结算单、来源快照、revision 和收付款记录;没有对应方向权限时 HTTP 403。验收 AC-22、AC-23 |
| P0-11 | `createSettlementPayment` | `CreateSettlementPaymentRequest` | `SettlementDetailEnvelope<SettlementDetail>` | `platform:finance:payment:create` | 只允许 PAYABLE 的 ISSUED/PARTIALLY_PAID;金额不得超过余额,凭证 File fail-closed;更新为 PARTIALLY_PAID/PAID。验收 AC-24 至 AC-26 |
`createReceiptWithAllocations` 已实现,不计入上述 11 项,但仍属于一期 P0 回归门禁:一笔 300 元收款可人工分配到同一客户 3 张各 100 元应收结算单并共用凭证;跨客户、草稿、超额、非 CNY、无有效凭证必须拒绝。
## 3. 状态机
### 3.1 费用来源
```text
PENDING --APPROVE--> APPROVED
PENDING --REJECT---> REJECTED
APPROVED + PUBLISHED + AVAILABLE --INCLUDE/EXCLUDE--> settlementIncluded true/false
AVAILABLE --对账单占用--> LOCKED --对账单出账--> BILLED
任意未出账有效版本 --上游作废--> VOIDED
AVAILABLE --上游调整新版本--> SUPERSEDED
```
### 3.2 客户对账单
```text
DRAFT --SUBMIT--> PENDING_REVIEW
PENDING_REVIEW --APPROVE--> APPROVED
PENDING_REVIEW --REJECT--> REJECTED
REJECTED/VOIDED --REOPEN--> DRAFT(新 revision)
APPROVED --ISSUE--> ISSUED(冻结来源)
ISSUED --ADJUST--> ISSUED(新 revision,不覆盖旧版)
DRAFT/PENDING_REVIEW/APPROVED/ISSUED --VOID--> VOIDED(已收金额=0 且无有效结算单)
```
`ADJUST` 必须提交至少一条调整行和原因;新总额不得小于已收金额。`VOID/REOPEN` 必须填写原因。ISSUED 后普通编辑接口不存在。
### 3.3 应收/应付结算单和收付款
```text
生成成功 -> ISSUED
ISSUED --部分收/付款--> PARTIALLY_PAID
ISSUED/PARTIALLY_PAID --收/付清--> PAID
ISSUED --VOID--> VOIDED(已收付金额=0)
VOIDED --REOPEN--> ISSUED(新 revision)
```
一期不处理预收、多收、退款、手续费、自动银行对账或汇率折算。人工收付款只能分配到同客户、同方向、CNY 且已出账的结算单。
## 4. 请求字段条件
- `CreateReconciliationRequest`:`customerId/currency/generationMode/periodStart/periodEnd` 必填;`currency=CNY`。`SELECTED` 时 `feeSourceIds` 至少 1 个,`BY_CUTOFF` 时服务端按 `periodEnd` 选择合格来源。
- `ReconciliationActionRequest`:`action/expectedVersion` 必填;`REJECT/ADJUST/VOID/REOPEN` 时 `reason` 必填;`ADJUST` 时 `adjustmentLines` 至少 1 个,每行包含 `statementLineId/adjustedAmount/reason`。
- `CreateReceivableSettlementRequest`:只提交 `reconciliationStatementId/currency`;客户、期间、金额和默认 30 天到期日由对账单与客户账期派生,不信任前端重复提交。
- `CreatePayableSettlementRequest`:`customerId/supplierId/feeSourceIds/dueDate/currency` 必填;来源必须同客户、同供应商。
- `CreateSettlementPaymentRequest`:`transactionDate/amount/currency/method/voucherFileIds/expectedVersion` 必填;`currency=CNY`、金额 `>0`、至少一张有效付款凭证。
## 5. 行为验收用例
| 用例 | 期望 |
|---|---|
| AC-01/02 | 账户列表金额可由来源、已出账结算单和收付款重算;响应不存在 balance/recharge/prepayment 字段 |
| AC-03/04 | 客户 A 流水不出现客户 B 单据;起止日期非法返回 `FIN_400` |
| AC-05 | 详情返回来源版本、业务对象和操作日志;不存在返回 `FIN_404` |
| AC-06/07 | PENDING 审核成功;重复审核、旧 expectedVersion 返回 `FIN_409` |
| AC-08/09 | 合格来源可切换参与结算;已锁定/出账/作废/替代版本返回 `FIN_409` |
| AC-10/11/12 | 两种生成模式成功;跨客户、非 CNY、重复占用或不合格来源分别返回 `FIN_404/FIN_400/FIN_409`,且不产生半成品 |
| AC-13/14 | 合法提交、审核和出账按状态迁移;非法 action/state 或并发版本返回 `FIN_409` |
| AC-15/16/17 | ISSUED 调整产生 revision;低于已收拒绝;有收款或有效结算单时作废拒绝;重开保留历史 |
| AC-18/19 | ISSUED 对账单生成唯一 AR;重复生成同参重放,异参或已有有效 AR 返回 `FIN_409` |
| AC-20/21 | 同客户同供应商 PAYABLE 来源生成 AP;混客户/供应商、已占用或非合格来源拒绝 |
| AC-22/23 | AR/AP 详情 DTO 完整;缺方向权限返回 403,不存在返回 `FIN_404` |
| AC-24/25/26 | 部分付款、付清状态正确;超额/重复/非 CNY/无凭证拒绝;File 不可用返回 `FIN_503` 且不落付款或更新余额 |
| AC-27 | 已实现收款回归:300 元分配 3 张 100 元同客户应收单成功并共用凭证 |
| AC-28 | 客户接口只能看到本人 `ISSUED/PARTIALLY_PAID/PAID` 单据和 `CONFIRMED` 收款,不返回凭证、内部备注、供应商成本或其他客户数据 |
| AC-29 | 每个写 operation 覆盖同键同参、同键异参、PROCESSING、FAILED/超时恢复;请求哈希改变任一业务字段即视为异参 |
## 6. 实现状态更新规则
每个 operation 必须同时满足以下证据,才允许把 `x-implementation-status` 从 `NOT_IMPLEMENTED` 改为 `IMPLEMENTED`:
1. Controller 存在与 OpenAPI 一致的 method/path、权限和固定 DTO。
2. Service/Repository 实现上述状态、客户归属、CNY、幂等、版本和审计规则。
3. 正向、非法状态、跨客户、无权限、同参重放、异参冲突和并发版本行为测试通过。
4. 涉及凭证的操作补 File fail-closed 测试;涉及客户可见数据的操作补脱敏与状态过滤测试。
5. 文档记录具体测试类、用例数和最近通过结果。只有代码存在、只有编译通过或只有间接测试均不得提前标完成。
@@ -1,11 +1,12 @@
# 财务一期五页产品与接口验收契约
> 日期:2026-08-11
> 状态:产品范围已确认;Finance 来源链与五页查询契约已实现并验证,剩余 11 个目标 operation、Admin 五页接入及跨服务 HTTP 冒烟待完成
> 状态:产品范围已确认;Finance 来源链与部分五页查询契约已实现并验证;当前 11 个 `NOT_IMPLEMENTED` operation 全部为一期 P0 阻断,禁止排到后续
> 领导验收范围:客户账户、客户应收应付、客户对账单、结算单应收、结算单应付
> 原型依据:`docs/悦/货代SAAS平台后台/货代SaaS平台原型.html`
> 目标 OpenAPI:`openapi/finance-phase1-five-pages-v1.yaml`;当前人工接口继续参考 `openapi/finance-f1-manual-v1.yaml`。
> 11 个 P0 操作的 DTO、状态机、权限、幂等、错误码、隔离和验收用例以 `财务一期P0操作契约与验收用例.md` 为准。
## 1. 一期范围结论
@@ -47,8 +48,8 @@
| 客户账户 | 汇总卡、客户列表、账户详情、流水详情;客户名称/编号、账户状态、余额条件、流水类型和日期 | 客户基本信息、客户编号、联系人、电话、未收余额、待出账金额、本期已收金额、应付余额、账户状态、最后变动时间;流水号、时间、业务类型、关联单据、应收/应付变动、变动后未收余额、操作人、备注 | 详情、查询、重置、导出;**无充值、扣款或余额调整** | 客户主体正常/停用只影响访问和新业务;所有金额可由来源、单据和收付款逐笔追溯,禁止直接写余额 |
| 客户应收应付 | 工具栏、组合查询、选中汇总、费用列表、费用详情、审核进度、关联业务、操作记录 | 客户、费用单号、是否已生成对账单、参与结算、审核状态、应收/应付、入仓码/柜次号、业务状态、费用类型、金额、业务发生日、来源类型、来源版本 | 详情、审核通过、审核拒绝、编辑未审核人工项、参与结算/禁止结算、按截止时间或勾选生成;已发布来源不可删除,只能作废/调整 | `PENDING -> APPROVED/REJECTED`;只有 `APPROVED + INCLUDED + AVAILABLE + CNY` 可生成;同一来源版本不能重复进入有效单据 |
| 客户对账单 | 查询区、主表、账单明细、支付历史;审核状态、账单编号、客户、柜次、结算起止日、是否结清、创建日期、最后收款时间 | 对账单号、客户、期间、账期、CNY、总应收、已收、未收、最后收款时间、是否结清、备注、创建/更新人时间;明细保留入仓单/柜次、业务日、计费量、费用项、金额、来源版本 | 生成草稿、详情、导出、审核通过、审核拒绝、作废;不提供发票和汇率操作 | `DRAFT -> PENDING_REVIEW -> APPROVED/REJECTED/VOIDED`;审核通过后来源冻结,调整必须留 revision;支付历史只读展示有效收款 |
| 结算单应收 | 汇总卡、筛选、列表、详情、费用明细、收款记录;收款状态、客户、结算单号 | 结算单号、客户、CNY、应收、已收、余额、关联对账单、业务期间、生成日、到期日、收款状态、制单人、状态 | 从审核通过对账单生成、详情、部分收款、导出、作废/重开;不保留高级查询假入口 | `DRAFT -> ISSUED -> PARTIALLY_PAID -> PAID`,未收款可作废;出账后冻结,调整保留 revision;收款人工表单和图片沿用 F1 契约 |
| 结算单应付 | 汇总卡、供应商筛选、列表、详情、费用明细、付款记录 | 结算单号、供应商、费用类型、CNY、应付、已付、余额、来源期间、生成日、到期日、付款状态、制单人、审核人、状态 | 从审核通过且参与结算的应付来源生成、详情、审核、部分付款、导出、作废/重开;删除利润分析和利润审批按钮 | `DRAFT -> PENDING_REVIEW -> ISSUED -> PARTIALLY_PAID -> PAID`,拒绝/作废保留记录;付款为人工登记和凭证图片 |
| 结算单应收 | 汇总卡、筛选、列表、详情、费用明细、收款记录;收款状态、客户、结算单号 | 结算单号、客户、CNY、应收、已收、余额、关联对账单、业务期间、生成日、到期日、收款状态、制单人、状态 | 从已出账对账单生成、详情、部分收款、导出、作废/重开;不保留高级查询假入口 | 生成后直接 `ISSUED -> PARTIALLY_PAID -> PAID`;来源已在对账单阶段审核,结算单不重复增加假审批;未收款可作废,调整保留 revision |
| 结算单应付 | 汇总卡、供应商筛选、列表、详情、费用明细、付款记录 | 结算单号、供应商、费用类型、CNY、应付、已付、余额、来源期间、生成日、到期日、付款状态、制单人、状态 | 从审核通过且参与结算的应付来源生成、详情、部分付款、导出、作废/重开;删除利润分析和利润审批按钮 | 生成后直接 `ISSUED -> PARTIALLY_PAID -> PAID`;不重复增加结算单审批;作废/重开保留 revision,付款为人工登记和凭证图片 |
表格“操作”列必须位于第一列。原型中未形成真实闭环的“新增、生成账单、高级查询、快捷导出”等按钮,只有在对应接口和权限完成后才显示。
@@ -57,12 +58,12 @@
| 页面 | 当前仓库事实 | 一期目标 | 差异等级 |
|---|---|---|---|
| 客户账户 | 后端已新增 `/customer-accounts/{customerId}` 和固定读 DTO;无列表接口 | 派生财务往来汇总、详情和流水,无任何充值/余额写入口 | IMPLEMENTED(单客户查询);列表/筛选仍缺,Finance DTO 测试通过 |
| 客户应收应付 | `/fee-pool` 固定 DTO、Firstleg 事件/快照和 `fin_fee_source` 写入均已实现并验证 | 真实入库/柜子费用来源池,支持审核、参与结算、详情和生成对账单 | 查询与来源入池已实现;详情/页面操作仍是 P0/P1 |
| 客户应收应付 | `/fee-pool` 固定 DTO、Firstleg 事件/快照和 `fin_fee_source` 写入均已实现并验证 | 真实入库/柜子费用来源池,支持审核、参与结算、详情和生成对账单 | 查询与来源入池已实现;详情、审核、参与结算均为一期 P0 |
| 客户对账单 | 后端已新增 `/statements` 和 `/bills/{billId}` 固定读 DTO 路径;独立生成/审核模型仍缺失 | 按截止时间或勾选合格来源生成,支持审核、明细、支付历史和 revision | IN_PROGRESS/P0 |
| 结算单应收 | 后端已新增 `/receivable-settlements` 固定读 DTO 路径;`fin_bill + fin_receipt` 已有人工状态能力 | 关联审核通过的客户对账单,按原型结构形成应收结算页 | IN_PROGRESS/P0 适配 |
| 结算单应付 | 后端已新增 `/payable-settlements` 固定读 DTO 路径并读取人工成本;付款单和完整供应商来源池仍缺失 | 从真实 PAYABLE 来源生成,审核后人工登记付款与凭证 | IN_PROGRESS/P0 |
Finance 来源写入不再是阻断:内部事件入口、Inbox 幂等、Firstleg 入库/柜子快照、`fin_fee_source` 新版本写入/替代/作废、`PUBLISHED + amount>0` 入池校验、五页固定 DTO、完整幂等、File fail-closed、Client 脱敏和稳定错误码均已实现并通过测试。剩余主要阻断转为 11 个目标 operation、Admin 五页接入和测试环境跨服务冒烟。
Finance 来源写入不再是阻断:内部事件入口、Inbox 幂等、Firstleg 入库/柜子快照、`fin_fee_source` 新版本写入/替代/作废、`PUBLISHED + amount>0` 入池校验、部分五页固定 DTO、完整幂等、File fail-closed、Client 脱敏和稳定错误码均已实现并通过测试。当前 11 个目标 operation 是一期 P0,不是可延期的“剩余优化”;此外还需完成 Admin 五页接入和测试环境跨服务冒烟。
## 4. 费用来源到结算全链路
@@ -88,16 +89,11 @@ flowchart LR
现有 `fl_inbound_fee_detail` 已具备客户、入库单、方向、业务日、费用项快照、计费量、单价、金额、审核、发布和 `settlement_included` 字段;已有列表、新增、编辑、删除、审核通过和驳回接口。
仍缺:
- 通用费用的“参与结算/禁止结算”接口和原因、操作人、版本控制。
- 非 FBA 头程运费的统一发布动作;当前发布主要由 FBA 费用发送流程触发。
- 费用调整/作废事件及旧版本替代规则的完整实现。
- Finance 消费者和来源快照写入。
当前 Firstleg 来源发布、调整/作废事件、Relay、Finance 消费者和来源快照写入已经实现并通过测试。Finance 五页内的费用来源详情、二次审核和参与结算操作仍按一期 P0 补齐;这些操作不得绕过已发布不可变来源版本或覆盖上游业务事实。
### 4.2 柜子费用项
当前工作树已新增柜子费用来源表/实体、Controller、审核/参与结算/作废服务、发布事件和内部快照代码;这些文件尚未形成通过测试的稳定基线。既有 Firstleg 柜次体积快照仍只服务 Finance 人工整柜成本分摊,不能替代新来源模型的审核和不可变发布链路。
当前柜子费用来源表/实体、Controller、审核/参与结算/作废服务、发布事件、Relay 和内部快照已经实现,相关定向测试 17/17 通过。Firstleg 的真实柜次体积快照同时服务 Finance 整柜成本分摊,但仍不能由 Admin 手工提交的订单归属或体积替代。
一期至少需要业务侧模型:
@@ -146,7 +142,7 @@ Finance 必须通过 Inbox 幂等消费来源版本,或通过受保护的快
| 模型 | 必要字段 | 当前缺口 |
|---|---|---|
| `fin_fee_source` | 来源系统/类型/ID/版本/hash、方向、客户、供应商、订单/入库/柜次、业务日、费用项、数量/单价/金额、审核/参与结算快照、状态、占用单据 | 已有写入、类型、方向、柜次、费用编码、hash、完整 JSON 快照和版本状态;供应商顶层字段及对账单占用关系仍待后续模型落地 |
| `fin_fee_source` | 来源系统/类型/ID/版本/hash、方向、客户、供应商、订单/入库/柜次、业务日、费用项、数量/单价/金额、审核/参与结算快照、状态、占用单据 | 已有写入、类型、方向、柜次、费用编码、hash、完整 JSON 快照和版本状态;供应商顶层字段及对账单占用关系属于本期 P0 数据模型,不得延后 |
| `fin_reconciliation_statement` | 对账单号、客户、期间、账期、CNY 总额、状态、审核、版本、备注 | 缺失 |
| `fin_reconciliation_line` | 对账单、feeSourceId/version/hash、业务与计费快照、金额 | 缺失 |
| `fin_settlement` | AR/AP、客户/供应商、来源对账单、期间、到期日、总额、已收付、余额、状态、版本 | 当前 `fin_bill` 仅覆盖部分 AR |
@@ -156,10 +152,11 @@ Finance 必须通过 Inbox 幂等消费来源版本,或通过受保护的快
## 7. 专项优先级
### P0:不开工或不联调就无法闭环
### P0:一期验收硬阻断
1. 入库费用独立的参与结算/禁止结算和通用发布动作;柜子费用来源模型已完成,不再列为阻断。
2. 客户对账单、应收结算单、应付结算单的数据模型、状态机和剩余 11 个 operation。
1. 以下 11 个 operation 全部实现并通过直接行为测试:账户列表/流水;费用来源详情/审核/参与结算;对账单生成及出账/调整/作废;应收/应付结算单生成;结算单详情;人工付款登记。人工收款已实现但必须继续通过 300 元分配 3 张 100 元应收单等 P0 回归。
2. 对账单、应收/应付结算单、付款和 revision 所需数据模型与唯一约束同步落地,不能只做空 Controller。
3. 详细冻结契约见 `财务一期P0操作契约与验收用例.md`;不得把其中任何一项标为 P1、后续优化或假入口。
### P1:领导验收前必须完成
@@ -48,6 +48,8 @@ G0 允许:完善 API/OpenAPI、数据库迁移草案、契约测试样例、
当前按以下冻结口径修复和补测:
- `财务一期P0操作契约与验收用例.md` 中的 11 个 operation 全部为一期 P0;任一项仍为 `NOT_IMPLEMENTED` 时,G2 不得关闭,也不得写入后续排期;
- 账单聚合维度(首期固定 RMB/CNY)和账期计算规则冻结;
- 平台出账、关闭、重开和线下异议录入状态冻结;客户首期只读;
- 应收生成节点、人工收款表单字段、凭证关联和到期日冻结;首期不实现复杂多对多核销模型;
@@ -83,6 +85,7 @@ G0 允许:完善 API/OpenAPI、数据库迁移草案、契约测试样例、
- 设计 Finance Admin/Client 页面和空态、错误态、线下异议录入态;
- 准备脱敏历史费用样本与迁移校验脚本设计;
- 按 OpenAPI 1.2.0 完成剩余服务端 DTO、File fail-closed、幂等和客户可见 DTO,并让 Admin 移除人工体积、接入账期与 revision。
- 按五页目标 OpenAPI 和 P0 操作契约逐项实现 11 个 operation;仅在 Controller/DTO 与直接行为测试均有证据后更新实现状态。
### 本轮不再等待产品确认