diff --git a/独立任务协作登记.md b/独立任务协作登记.md index 3732d0f..067b066 100644 --- a/独立任务协作登记.md +++ b/独立任务协作登记.md @@ -18,11 +18,16 @@ | task_id | display_name | scope | owner | status | worktree/branch | last_update | notes | |---|---|---|---|---|---|---|---| -| 019feb17-b5de-7d63-a0e4-5a8c2181e323 | PDA 后端 S0500 修复 | `baiyue-firstleg/firstleg` PDA 拣货事务、库位结算和相关测试 | 三期总控 | active | 后端 `test` | 2026-08-11 | 当前发布阻断;已部署 3bfc11a 后仍需继续定位第二个真实 S0500 | -| 019feb17-c56c-7e02-8f9c-0b74f960df34 | 三期验收 fixture 审计 | `baiyue-firstleg/scripts/admin-test-env-modules` | 三期总控 | completed | 后端 `test` | 2026-08-10 | 无代码变更;确认 fixture 顺序和真实失败级联正确 | -| 019feb17-d3c2-7480-802a-498db11c7d3c | Admin 装柜清单页面 | `apps/admin-web` 智能装柜、预装清单、装柜清单 | 三期总控 | completed | Admin `release/test` | 2026-08-10 | b593d72 已推送并部署,8/8 定向、typecheck、build 通过 | +| 019feb17-b5de-7d63-a0e4-5a8c2181e323 | PDA 后端作业链收口 | `baiyue-firstleg/firstleg` PDA 拣货事务、原子批量提交和相关测试 | 三期总控 | active | 后端 `test` | 2026-08-11 | S0500 与直接完成状态链已收口;当前在同一 PDA 后端范围内补单次 batch、整批回滚、幂等和版本冲突契约 | +| 019feb17-c56c-7e02-8f9c-0b74f960df34 | 三期验收 fixture 审计 | `baiyue-firstleg/scripts/admin-test-env-modules` | 三期总控 | active | 后端 `test` | 2026-08-11 | `fe0f166` 已完成状态映射;继续在同一职责内收口 customs 对新单余量“来源分片承载”模型的断言 | +| 019feb17-d3c2-7480-802a-498db11c7d3c | Admin 装柜工作流页面 | `apps/admin-web` 统一订单、智能装柜、预装清单、装柜清单、证据与完成入口 | 三期总控 | active | Admin `release/test` | 2026-08-11 | b593d72 已推送并部署;继续在原模块范围内做三期终验与真实 API 浏览器复核,独占 router/navigation | | 019feb2a-419b-7161-8174-5aed7e84a047 | PDA 原子批量提交契约 | PDA 后端 Controller/DTO/OpenAPI/调用方只读契约梳理 | 三期总控 | completed | 后端 `test` | 2026-08-10 | 只读阶段完成;等待 S0500 修复后进入实现 | | 019feb29-ef52-7f83-96d7-42d963d2ae34 | PDA uni-app 前端验收 | `apps/pda-uniapp` 登录、领取、广播扫码、设备草稿、批量提交、证据和真实 API 映射 | 三期总控 | active | PDA 当前工作区 | 2026-08-11 | 只读验收;必须区分代码/Node 测试与 HBuilderX/Android 真机验证 | +| 019febc3-8735-7e53-b797-a8af24bcc0ec | Firstleg 单余量装柜闭环 | `baiyue-firstleg/firstleg` 单余量结算、PDA pick、装柜完成与跨服务幂等/补偿 | 三期总控 | active | 后端 `test` | 2026-08-11 | `7aa2b50` 已部署且拆单定向 244/244;继续处理 loading/complete 首次及幂等重放 S0500,回查停在 LOADING/version 5 | +| 019feeb5-6c6b-7f50-9bcd-eba8c7057b24 | Gateway 与 Order 测试环境发布 | Jenkins `backend-java` 的 `gateway + order` 定向发布与健康检查 | 三期总控 | completed | 后端 `test` | 2026-08-11 | backend-java #100、gateway #36、order #41 成功;部署 166ebfb7,未执行 SQL,未部署 Firstleg/Admin | +| 019feec0-008e-72e2-95d6-8e99d604b5c0 | Admin 服务中心三期页面 | `apps/admin-web` 订舱、拖车、船期、报关与出仓模块页面 | 三期总控 | active | Admin `release/test` | 2026-08-11 | 新建专用任务;不得修改 router/navigation 或装柜工作流模块 | +| 019feed6-edeb-7da3-aa75-8c40aa5fb305 | Firstleg P0 测试环境发布 | Jenkins `backend-java` 的 `firstleg` 定向发布与健康检查 | 三期总控 | completed | 后端 `test` | 2026-08-11 | backend-java #101、firstleg #71 成功;部署 `9f455b6`,包含 `7aa2b50` 与 PDA 原子 `/batch`;无 SQL,已通知全量复跑 | +| 019fdf0a-4933-7903-8a0c-10018c379f6f | 后台真实全量测试 | `baiyue-firstleg/scripts/admin-test-env-*` 与测试环境真实 HTTP 验收 | 三期总控 | active | 后端 `test` | 2026-08-11 | 当前基线复跑与下一批 PDA batch/Admin 终验分两轮执行;禁止 mock、放宽断言或 gap 伪装 PASS | ## 通信与汇总格式 diff --git a/财务管理/F1人工财务契约审查与门禁.md b/财务管理/F1人工财务契约审查与门禁.md new file mode 100644 index 0000000..62ba6c7 --- /dev/null +++ b/财务管理/F1人工财务契约审查与门禁.md @@ -0,0 +1,87 @@ +# F1 人工财务契约审查与门禁 + +> 审查日期:2026-08-11 +> 审查范围:Finance Java、Finance 建库 SQL、统一增量 SQL、Admin/Client Finance API、`openapi/finance-f1-manual-v1.yaml` +> 结论:F0/F1 基础代码已经实现,当前处于专项修复与契约收敛阶段;不是整体未开发。修复项完成前尚未达到完整联调验收/测试放行。 + +## 1. 已确认且已对齐 + +| 范围 | 目标 | 当前结论 | +|---|---|---| +| 产品边界 | 人工账单、人工收款、人工成本,不做汇率、月结和自动认款 | 对齐 | +| 币种 | 只支持 CNY | Controller 校验、OpenAPI 枚举和 SQL CHECK 均为 CNY | +| 客户账期 | 每客户配置,默认 30 个自然日 | Finance 已有 GET/PUT;未配置返回 30;范围 0-3650 天 | +| 到期日 | `billDate + creditDays` | Finance 创建/修改草稿时已计算 | +| 出账冻结 | 只有 DRAFT 可直接修改,出账后走调整 | Finance 更新 SQL 限制 `status='DRAFT'`,已对齐基本门禁 | +| 收款分配 | 一笔 300 元可分配 3 张各 100 元账单 | 已校验分配合计、客户归属、状态和未收余额,并在同一事务更新三张账单 | +| 客户权限 | 客户只读且只能看本客户 | Client Controller 从认证上下文取客户范围,无客户写接口 | +| 成本类型 | 单票、整柜公共成本 | Java/OpenAPI/Admin 均统一为 `SINGLE_ORDER/CONTAINER_SHARED` | +| 权限码 | 平台 12 个按钮权限,客户 2 个按钮权限 | Controller、OpenAPI 和统一 SQL 权限种子已对齐 | +| 表结构 | 账单、修订、收款分配、成本及成本分摊 | Finance SQL 与统一增量 SQL 均存在对应表 | + +当前实现包括 Finance Controller/Service、独立财务表、统一 SQL 权限种子、Admin 人工财务页面和 Client 只读账单页面。本文中的 D1-D10 是对现有实现的修复门禁,不是从零建设清单。 + +## 2. 接口与权限基线 + +| 接口 | 权限 | 当前状态 | +|---|---|---| +| `GET /api/platform/finance/v1/bills?customerId=` | `platform:finance:bill:view` | 已实现;返回非分页数组 | +| `POST /api/platform/finance/v1/bills` | `platform:finance:bill:create` | 已实现 | +| `GET/PUT /api/platform/finance/v1/bills/{billId}` | `bill:view/update` | 已实现;Admin 尚未接详情/修改 | +| `POST .../{billId}/issue|adjust|void` | `bill:issue/adjust/void` | Java 已实现;Admin 仅已接出账 | +| `GET/POST /api/platform/finance/v1/receipts` | `receipt:view/create` | 已实现 | +| `GET/POST /api/platform/finance/v1/cost-entries` | `cost:view/create` | 登记已实现;整柜分摊未实现 | +| `GET/PUT /api/platform/finance/v1/customers/{tenantId}/terms` | `customer-terms:view/update` | Java 已实现;Admin 尚未接入 | +| `GET /api/client/finance/v1/bills` | `merchant:finance:statement:list` | 已实现 | +| `GET /api/client/finance/v1/bills/{billId}` | `merchant:finance:statement:view` | 已实现 | +| `GET /api/client/finance/v1/receipts` | `merchant:finance:statement:view` | 已实现 | + +`tenantId` 只保留为后端兼容路径字段,产品语义统一为客户 ID,不增加用户可见的“租户”层级。 + +## 3. 开发门禁阻断项 + +以下项目未完成前,可以继续编码,但不能宣称 F1 已具备完整业务闭环。 + +| 编号 | 阻断项 | 当前事实 | 完成标准 | +|---|---|---|---| +| D1 | 稳定响应 DTO | Finance 直接返回 JDBC `Map`/`List`,字段为 snake_case | 按 OpenAPI 1.1.0 输出账单、收款、成本、账期固定 camelCase DTO;所有成功/失败响应带 `requestId/traceId/timestamp` | +| D2 | 列表形态统一 | Java 为非分页数组;Admin/Client 已做本地分页适配,但仍发送分页/筛选参数 | 首期明确维持非分页并清理无效参数,或后端真正实现统一分页;两端测试与 OpenAPI 同步 | +| D3 | 调整记录可查询 | Java 调整前写 `fin_bill_revision`,再更新当前账单及替换明细,具备数据库快照;但无修订查询接口,产品无法查看调整前后 | 明确“同账单版本修订”即首期方案,并提供调整记录查询/展示;不得无痕覆盖 | +| D4 | 幂等语义 | 重复幂等键目前直接返回冲突,`request_hash` 只哈希键本身 | 同键同参成功后返回原资源;异参返回 `FIN-IDEMPOTENCY-CONFLICT`;`PROCESSING` 返回 409 + `Retry-After` 并由超时恢复任务核对 | +| D5 | 财务凭证链路 | Admin 已主动阻止空凭证保存;Finance 未校验 File,File 也没有财务专用公开上传/内部关联接口 | 实现 OpenAPI 中临时上传、批量校验、幂等关联;严格校验客户、用途、状态、类型和大小,关联失败由 Finance Outbox 补偿 | +| D6 | 整柜体积分摊 | 有 `fin_cost_allocation` 表,但 `createCost` 只写 `fin_cost`,未计算或落分摊 | 同客户有效票按体积、6 位 `HALF_UP` 分摊,最后一票承接尾差;总体积为零才允许人工分摊且合计严格相等 | +| D7 | 成本归属校验 | Java 仅校验成本类型和金额,未强制单票 `orderId`/整柜 `containerId` | 两种类型分别强制唯一归属,且校验对象存在、属于对应客户 | +| D8 | Admin 成本字段映射 | 后端列为 `fee_item_name/cost_amount`,Admin 读取 `expense_name/amount`,列表费用名和金额会为空/0 | Admin 适配真实线协议并增加回归测试 | +| D9 | Admin 完整操作 | 已有创建、列表和出账;缺账单详情/草稿修改/调整/作废、客户账期维护 | 接齐接口、权限按钮、版本冲突和错误态;调整原因、作废原因必填 | +| D10 | 业务错误码 | 文档定义 `FIN-AR-*`/`FIN-COST-*`;Java 当前主要返回通用 BAD_REQUEST/CONFLICT + 中文消息 | 后端建立稳定业务码并由 Admin/Client 按码处理,不依赖消息文本 | +| D11 | 客户状态可见性 | Client Controller 当前按客户隔离,但账单列表/详情未过滤状态 | 客户只可见 `ISSUED/PARTIALLY_PAID/PAID`;草稿、作废、跨客户和不存在详情统一按不存在处理 | + +## 4. 非阻断但需锁定的契约说明 + +- `UpdateBillRequest` 与当前 Java 一致,包含 `customerId`、`billDate`、`lines`、`expectedVersion`。 +- 平台账单列表的 `customerId` 必填;收款、成本列表可不传客户,供平台总览。 +- 收款分配字段在请求中为 `allocations[].amount`;数据库返回联表字段为 `applied_amount`。对外 DTO 应统一一个命名,建议请求继续用 `amount`,响应用 `allocatedAmount`。 +- 已收账单不得作废;当前 Java 只允许 `DRAFT/ISSUED` 且 `received_amount=0` 作废,符合首期简化原则。 +- 调整后总额不得低于已收金额;这条现有服务端约束应保留。 +- 客户停用才影响客户账单访问;客户子用户停用只影响该用户登录,不改变账单的客户主体归属。 + +## 5. 可测试门禁 + +进入前后端联调前至少满足: + +1. D1、D3、D5、D6、D7、D8、D9、D10、D11 已完成并有直接测试。 +2. D2 选定“首期非分页”或“服务端分页”之一,OpenAPI、Admin、Client 三方一致。 +3. D4 至少完成账单创建、出账和收款登记的同键重放测试。 +4. Finance 集成测试覆盖:默认 30 天、CNY 拒绝、出账冻结、300 分 3 单、超额/跨客户/草稿拒绝、部分收款到收清、调整修订、已收不可作废。 +5. 成本测试覆盖:单票归属、整柜按体积分摊、零体积人工处理、舍入尾差、跨客户对象拒绝。 +6. 权限测试覆盖平台 12 个权限码和客户 2 个权限码,并验证客户无法写入及无法跨客户查询。 +7. 客户查询测试证明列表、详情和收款均不会暴露 `DRAFT/VOIDED`、平台凭证 fileId 或内部备注。 + +## 6. 放行结论 + +- **当前阶段:专项修复中。** Finance、SQL、Admin 和 Client 均已有实现,不再按“未开发”管理。 +- **可继续开发:是。** 路径、权限、固定 DTO、凭证、分摊、幂等和客户可见性已经冻结,不再需要新增产品决策。 +- **可进行接口冒烟:有条件。** 可验证现有路径、鉴权和基础创建/查询,但结果不代表业务验收通过。 +- **可进入完整联调验收:否。** 主要阻断为整柜成本分摊、凭证服务端校验、Admin 完整操作、稳定 DTO/错误码和幂等重放。 + +本次审查只修订文档与 OpenAPI,未修改 Java、前端或 SQL。 diff --git a/财务管理/F1人工账单收款成本接口契约.md b/财务管理/F1人工账单收款成本接口契约.md index 9e7d156..6201003 100644 --- a/财务管理/F1人工账单收款成本接口契约.md +++ b/财务管理/F1人工账单收款成本接口契约.md @@ -25,6 +25,15 @@ - 文件先由 File 服务上传,Finance 接收 `fileId` 引用,不直接接收二进制。 - 平台账单列表必须带 `customerId`;收款和成本列表的 `customerId` 为可选筛选。首期接口返回非分页数组,前端分页只能在适配层完成;不得把数组伪装成服务端分页对象。 - Finance 对外 DTO 应稳定使用 camelCase;数据库 JDBC `snake_case` 仅限适配层,不能作为长期前后端契约。 +- 所有成功和失败响应固定包含 `requestId`、`traceId`、`timestamp`;`timestamp` 使用 UTC ISO-8601。账单、收款、成本和账期分别使用 OpenAPI 中的固定 DTO,不允许返回 JDBC `Map` 或未声明字段。 + +### 2.1 幂等处理 + +- 幂等范围为 `customerId + operatorId + operation + Idempotency-Key`,请求哈希由 HTTP 方法、规范化路径、客户范围和规范化 JSON 请求体计算,不得只哈希幂等键。 +- 同键同参且原请求已成功:返回第一次创建的资源和业务结果,不重复写库;本次响应生成新的 `requestId/traceId/timestamp`。 +- 同键异参:返回 HTTP 409、`FIN-IDEMPOTENCY-CONFLICT`,不得覆盖原记录。 +- 同键同参但原请求为 `PROCESSING`:返回 HTTP 409、`FIN-IDEMPOTENCY-PROCESSING` 和 `Retry-After: 2`;调用方只能用原键重试,不得自动换键重复创建。 +- `PROCESSING` 超过 30 秒由服务端恢复任务核对业务资源和事务结果,恢复为 `SUCCEEDED` 或可重试失败;客户端不负责判断原事务是否落库。 ## 3. 对象与状态 @@ -42,11 +51,30 @@ `allocations[]` 每项为 `billId + allocatedAmount`。首期要求分配金额之和等于收款金额;每项不得超过对应账单未收余额。收款凭证可被同一笔收款的多张账单共享。 +收款只允许分配到同一客户且状态为 `ISSUED/PARTIALLY_PAID` 的账单;已收清、草稿、作废和跨客户账单均拒绝。服务端必须在同一事务内锁定所有账单、写入收款与分配并更新余额,不允许部分成功。 + ### 3.3 成本 字段:`costEntryId`、`costNo`、`costType`(`SINGLE_ORDER`/`CONTAINER_SHARED`)、`orderId`/`containerId`、费用项目、供应商/成本对象快照、金额、币种、凭证文件、备注、操作人。 -单票成本直接归属单票;整柜公共成本按 `票体积 / 柜内有效票总体积` 分摊。体积为零或总量为零时拒绝自动分摊,改为人工指定;舍入尾差归最后一票并记录调整原因。 +单票成本必须提供 `orderId`,订单必须存在并属于表单中的客户。整柜公共成本必须提供 `containerId`,柜次及柜内参与分摊的有效票必须属于同一客户;混入其他客户、已作废票或不属于该柜的票时拒绝。 + +整柜分摊固定规则: + +1. 使用成本创建时冻结的订单有效体积快照,单位 CBM、精度 6 位;有效票按 `orderId` 升序形成确定顺序。 +2. `ratio = orderVolume / totalVolume`;除最后一票外,`allocatedAmount = costAmount × ratio`,按 6 位小数 `HALF_UP`。 +3. 最后一票金额为 `costAmount - 前序分摊金额合计`,承接全部尾差;保存 `roundingAdjustment`,保证分摊金额合计严格等于成本金额。 +4. 单票体积为零时该票自动分摊为 0;柜内有效票总体积为零时返回 `FIN-COST-002`,随后仅允许 `MANUAL_ZERO_VOLUME` 人工指定同柜订单金额,且合计必须等于成本金额。 +5. 已落库的体积、比例、分摊金额和尾差均为不可变快照;来源体积后续变化不追改历史成本。 + +### 3.4 财务凭证上传与关联 + +1. Admin 先调用 File 的 `POST /api/platform/v1/files/finance-vouchers`,提交 `customerId + purpose(RECEIPT/COST) + file`,取得临时 `fileId`。 +2. File 只接受 JPEG、PNG、WebP、PDF,单文件不超过 10 MiB;记录客户、用途、上传人、SHA-256、状态 `TEMPORARY`。临时文件不得直接对客户开放。 +3. Finance 创建收款/成本前,使用内部服务令牌调用 `POST /internal/file/v1/finance-vouchers/validate` 批量校验:文件存在、`ACTIVE/TEMPORARY`、未删除、客户一致、用途一致、类型/大小合规、尚未关联其他资源。任一失败则整个财务表单不落库。 +4. Finance 落单成功后调用 `POST /internal/file/v1/finance-vouchers/associate`,把文件幂等关联到 `FINANCE_RECEIPT/FINANCE_COST + resourceId`;失败由 Finance Outbox 使用原幂等键重试,不重复创建收款或成本。 +5. 相同文件关联同一资源视为同参重放并返回原结果;关联其他资源返回 `FIN-FILE-004`。Finance 保存 fileId 关联,文件内容和元数据由 File 管理。 +6. 客户侧收款 DTO 不返回 `voucherFileIds`、内部备注或下载地址;首期客户不能查看平台收款/成本凭证。 ## 4. 接口清单 @@ -78,6 +106,8 @@ 客户端禁止 POST、PUT、PATCH、DELETE 财务写操作。 +客户列表和详情只能返回当前客户且状态为 `ISSUED/PARTIALLY_PAID/PAID` 的账单。`DRAFT/VOIDED`、跨客户和不存在的账单详情统一返回 `FIN-AR-001`,不得通过错误差异泄露账单存在性;客户收款列表只返回分配到上述可见账单的记录。 + ## 5. 最小错误码 | 错误码 | 场景 | @@ -94,6 +124,14 @@ | `FIN-AR-010` | 幂等键重复但请求内容不同 | | `FIN-COST-001` | 成本归属对象缺失 | | `FIN-COST-002` | 体积为零,无法按体积分摊 | +| `FIN-COST-003` | 订单、柜次或柜内票不属于指定客户 | +| `FIN-COST-004` | 人工分摊金额合计不等于成本金额 | +| `FIN-FILE-001` | 凭证不存在、已删除或状态无效 | +| `FIN-FILE-002` | 凭证客户或用途不匹配 | +| `FIN-FILE-003` | 凭证类型或大小不允许 | +| `FIN-FILE-004` | 凭证已关联其他财务资源 | +| `FIN-IDEMPOTENCY-CONFLICT` | 同一幂等键对应不同请求内容 | +| `FIN-IDEMPOTENCY-PROCESSING` | 同参请求仍在处理中,可按 Retry-After 使用原键重试 | | `FIN-AUTH-001` | 当前角色无平台财务写权限 | ## 6. 验收用例 @@ -107,7 +145,11 @@ 7. 客户只读可见本客户账单和收款记录,不可写入、不跨客户查询。 8. 客户子用户停用后不能访问财务页面,但历史操作人和客户财务归属不变。 9. 登记单票成本成功;整柜公共成本按体积分摊,零体积场景要求人工指定。 -10. 重复 `Idempotency-Key` 返回原结果;同键不同请求返回 `FIN-AR-010`。 +10. 重复 `Idempotency-Key` 且同参返回原结果;同键不同请求返回 `FIN-IDEMPOTENCY-CONFLICT`。 +11. 同键同参处于 `PROCESSING` 时返回 409、`FIN-IDEMPOTENCY-PROCESSING` 和 `Retry-After`,不会生成第二张单。 +12. File 凭证客户、用途、状态、类型或大小任一不匹配时,收款/成本和分配均不落库;关联失败可由 Outbox 幂等补偿。 +13. 整柜自动分摊按 6 位 `HALF_UP` 计算,最后一票承接尾差且合计严格等于成本金额;总体积为零只允许同柜同客户订单人工分摊。 +14. 客户列表、详情和收款记录均看不到 `DRAFT/VOIDED` 账单,也看不到平台凭证 fileId 和内部备注。 ## 7. 权限矩阵 diff --git a/财务管理/README.md b/财务管理/README.md index 5e53bee..a8e77bd 100644 --- a/财务管理/README.md +++ b/财务管理/README.md @@ -1,7 +1,7 @@ # 财务管理闭环盘点与范围建议 > 盘点日期:2026-08-10 -> 当前状态:开发前详细设计,未进入业务开发 +> 当前状态:F0/F1 已有 Finance 服务、数据库、权限、Admin/Client 页面和基础接口实现;专项契约与闭环缺口修复中 > 盘点范围:二期入库计费现状、平台后台财务原型、Admin/Client/Firstleg 当前实现 > 结论口径:本文区分“代码已实现”“原型展示”和“建议范围”;原型功能不自动成为开发范围。 @@ -19,6 +19,7 @@ - [财务领域模型与数据字典](财务领域模型与数据字典.md) - [Firstleg-Finance跨服务契约](Firstleg-Finance跨服务契约.md) - [F1 人工账单、收款、成本接口契约](F1人工账单收款成本接口契约.md) +- [F1 人工财务契约审查与门禁](F1人工财务契约审查与门禁.md) - [财务历史迁移与上线验收方案](财务历史迁移与上线验收方案.md) - [财务F0实施任务拆分](财务F0实施任务拆分.md) @@ -28,7 +29,9 @@ 当前首期已确定采用人工简化方案:平台财务人工填写账单、收款和成本表单并上传凭证,客户只读;首期不做银行自动对账、自动核销任务、复杂审批、客户钱包、发票、供应商在线结算、汇率、月结或总账。完整原型能力仅作为后续扩展参考,不作为本期开工范围。 -当前已经实现了从入库测量数据到对客头程费用发布的**业务计费闭环**: +当前已经实现业务计费闭环,并已落地 Finance 独立服务/数据库、人工账单/收款/成本基础接口、平台与客户权限种子、Admin 财务页面和 Client 只读账单页面。当前不是“整体未开发”,而是已进入专项修复和契约收敛阶段。 + +已具备的基础链路: ```text 测量/称重 @@ -40,18 +43,18 @@ -> 完成入库 ``` -当前尚未实现从费用到资金结清的**财务闭环**: +当前 F1 已具备的实现链路: ```text -已发布费用 - -X-> 对账单 - -X-> 应收单 - -X-> 收款记录 - -X-> 核销 - -X-> 结清/反核销 +平台财务创建草稿账单 + -> 出账冻结 + -> 人工登记收款 + -> 一笔收款分配多张账单 + -> 部分收款/收清状态更新 + -> 客户只读查询 ``` -因此,“已经算出重量、体积和金额”只说明已经有可进入财务流程的费用来源,不代表已经形成应收、账单或收款。现状可以支撑财务一期以“客户应收”为起点继续建设,但不能直接宣称财务管理已经闭环。 +当前仍需修复:固定响应 DTO、财务凭证真实上传及 File 校验/关联、整柜体积分摊落库、完整幂等重放、客户账单状态过滤,以及 Admin 调整/作废/账期等操作。完成《F1人工财务契约审查与门禁》中的测试门禁后,才能宣称 F1 人工财务闭环通过验收。 本轮确认“全部都需要做”后,目标范围升级为覆盖原型财务菜单的完整财务域:客户账户、客户应收应付、客户对账单、结算单应收、结算单应付、收付款与核销、费用分析、发票和利润报表。仍建议按依赖关系分阶段交付,避免在应收、应付、钱包和利润各自造一套金额口径。 @@ -125,22 +128,20 @@ Firstleg 已有 `fl_inbound_fee_detail`,包含: 原型还出现了发票抬头、开票日期、汇率、支付方式、收付款凭证号等字段。它们只说明未来可能需要,不代表当前产品已经确认税务、外币折算、资金或发票边界。 -当前 Admin 中财务导航整体为隐藏状态,路由指向 `src/prototype/` 占位页;Client 的“应付账单”也标记为 `release: 'prototype'`。这些页面不可作为已交付能力。 +当前 Admin 已有正式财务路由、人工账单/收款/成本页面和出账操作,Client 已有正式只读账单页面及线协议适配;但凭证上传仍被界面主动阻止,Admin 尚未接齐草稿修改、调整、作废和客户账期操作,因此只能视为修复中的实现,不能视为已验收交付。 -## 五、当前闭环断点 +## 五、当前修复中缺口 | 序号 | 断点 | 直接影响 | |---|---|---| -| 1 | 没有财务费用池的跨订单查询和筛选 | 财务无法集中选择待出账费用 | -| 2 | 没有费用冻结/锁定规则 | 账单生成后上游调整可能改变应收依据 | -| 3 | 没有对账单及唯一编号、账期、版本 | 无法向客户确认一个确定期间的应收集合 | -| 4 | 没有平台财务调整、作废重开和线下异议记录机制 | 客户反馈金额有误时无法在保留历史的前提下纠错 | -| 5 | 没有应收单及到期规则 | 无法管理待收、部分收款、逾期和结清 | -| 6 | 没有收款记录和凭证 | 无法证明资金已到账 | -| 7 | 没有核销与反核销 | 收款不能对应到具体账单或费用,也无法纠错 | -| 8 | 没有跨单据审计链 | 无法从入库费用追溯到对账、收款和核销 | -| 9 | 没有财务级幂等和并发约束 | 重复生成账单、重复核销和超额核销风险无法控制 | -| 10 | 没有财务报表口径 | 原型统计数字无法由真实台账可靠计算 | +| 1 | Finance 仍直接返回 JDBC `Map/List` | 字段和响应结构未形成稳定 DTO,前端依赖临时适配 | +| 2 | 凭证上传与 File 校验/关联未接通 | 收款和成本表单不能带真实凭证完成保存 | +| 3 | 整柜成本仅登记主记录,尚未生成体积分摊明细 | 柜级清关费、拖车费不能形成可审计的单票成本 | +| 4 | 幂等当前将所有重复键作为冲突 | 同键同参不能返回第一次结果,`PROCESSING` 恢复语义缺失 | +| 5 | 客户查询未在服务端过滤可见状态 | 草稿或作废账单存在泄露风险 | +| 6 | Admin 未接齐账单详情、修改、调整、作废和客户账期 | 平台财务无法在正式页面完成全部人工处理 | +| 7 | 业务错误码仍以通用错误和中文消息为主 | 前端无法稳定区分版本、幂等、凭证和归属错误 | +| 8 | 修订快照已有表和写入逻辑,但缺少查询展示 | 调整历史尚不能由产品和测试人员核对 | ## 六、全量财务规划与交付顺序 @@ -327,6 +328,6 @@ DRAFT -> REVIEWED -> SENT -> CONFIRMED -> CLOSED 1. 由产品/业务填写 F1-F12,冻结首期范围。 2. 形成《客户应收闭环需求确认表》,明确页面、角色、状态和异常处理。 3. 使用《财务管理产品确认表》完成 S1-S8、F0-F4 的产品确认。 -4. 服务边界、领域模型草案和跨服务契约草案已经形成;下一步由产品填写 XLSX,按《财务开工门禁与 F0/F1 任务清单》放行 G1/G2,不再重复讨论是否拆服务。 +4. 服务边界、领域模型、跨服务契约及 F0/F1 基础实现已经形成;当前按《F1人工财务契约审查与门禁》完成专项修复和测试放行,不再等待产品填写 XLSX 或重复讨论是否拆服务。 5. 形成《客户应收接口与权限契约》,包括错误码、幂等、数据范围和审计事件。 6. 形成《历史费用迁移与上线方案》,避免已有线下结算数据被重复出账。 diff --git a/财务管理/openapi/finance-f1-manual-v1.yaml b/财务管理/openapi/finance-f1-manual-v1.yaml index b7479d6..6d4a899 100644 --- a/财务管理/openapi/finance-f1-manual-v1.yaml +++ b/财务管理/openapi/finance-f1-manual-v1.yaml @@ -1,8 +1,10 @@ openapi: 3.0.3 info: title: Finance F1 Manual AR API - version: 1.0.0 - description: 首期人工账单、收款和成本接口;仅 CNY,平台财务可写,客户只读。 + version: 1.1.0 + description: >- + F1 人工账单、收款和成本目标契约。仅 CNY;平台财务可写;客户只读且仅可见 + ISSUED、PARTIALLY_PAID、PAID 账单。所有响应使用固定 DTO 和统一追踪字段。 servers: - url: /api paths: @@ -10,242 +12,1334 @@ paths: get: summary: 查询客户账期 x-permission: platform:finance:customer-terms:view - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/TenantId'}] - responses: {'200': {$ref: '#/components/responses/SuccessObject'}, '403': {$ref: '#/components/responses/BusinessError'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/TenantId' + responses: + '200': + $ref: '#/components/responses/CustomerTermsResponse' + '403': + $ref: '#/components/responses/BusinessError' put: summary: 保存客户账期 x-permission: platform:finance:customer-terms:update - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CustomerTermsRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessObject'}, '403': {$ref: '#/components/responses/BusinessError'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/TenantId' + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CustomerTermsRequest' + responses: + '200': + $ref: '#/components/responses/CustomerTermsResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' /platform/finance/v1/bills: get: summary: 查询平台账单 x-permission: platform:finance:bill:view - security: [{platformFinance: []}] + security: + - platformFinance: [] parameters: - - {$ref: '#/components/parameters/RequiredCustomerId'} - responses: {'200': {$ref: '#/components/responses/SuccessList'}} + - $ref: '#/components/parameters/RequiredCustomerId' + responses: + '200': + $ref: '#/components/responses/BillListResponse' post: summary: 创建账单草稿 x-permission: platform:finance:bill:create - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreateBillRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessBill'}, '403': {$ref: '#/components/responses/BusinessError'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBillRequest' + responses: + '200': + $ref: '#/components/responses/BillDetailResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' /platform/finance/v1/bills/{billId}: get: summary: 查询平台账单详情 x-permission: platform:finance:bill:view - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/BillId'}] - responses: {'200': {$ref: '#/components/responses/SuccessBill'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/BillId' + responses: + '200': + $ref: '#/components/responses/BillDetailResponse' put: summary: 更新账单草稿 x-permission: platform:finance:bill:update - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/BillId'}, {$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/UpdateBillRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessBill'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/BillId' + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateBillRequest' + responses: + '200': + $ref: '#/components/responses/BillDetailResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' /platform/finance/v1/bills/{billId}/issue: post: summary: 出账并冻结账单 x-permission: platform:finance:bill:issue - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/BillId'}, {$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/VersionRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessBill'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/BillId' + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VersionRequest' + responses: + '200': + $ref: '#/components/responses/BillDetailResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' /platform/finance/v1/bills/{billId}/adjust: post: - summary: 出账后调整并生成新版本 + summary: 调整已出账账单并保留修订历史 x-permission: platform:finance:bill:adjust - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/BillId'}, {$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/AdjustBillRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessBill'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/BillId' + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AdjustBillRequest' + responses: + '200': + $ref: '#/components/responses/BillDetailResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' /platform/finance/v1/bills/{billId}/void: post: - summary: 作废账单 + summary: 作废未收款账单 x-permission: platform:finance:bill:void - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/BillId'}, {$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ReasonRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessBill'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/BillId' + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReasonRequest' + responses: + '200': + $ref: '#/components/responses/BillDetailResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' /platform/finance/v1/receipts: - post: - summary: 人工登记收款并分配账单 - x-permission: platform:finance:receipt:create - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreateReceiptRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessReceipt'}} get: summary: 查询收款记录 x-permission: platform:finance:receipt:view - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/OptionalCustomerId'}] - responses: {'200': {$ref: '#/components/responses/SuccessList'}} - /platform/finance/v1/cost-entries: + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/OptionalCustomerId' + responses: + '200': + $ref: '#/components/responses/ReceiptListResponse' post: - summary: 人工登记单票或整柜成本 - x-permission: platform:finance:cost:create - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/IdempotencyKey'}] - requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CreateCostRequest'}}}} - responses: {'200': {$ref: '#/components/responses/SuccessCost'}} + summary: 人工登记收款并分配账单 + description: voucherFileIds 必须先通过 File 财务凭证上传接口取得,并通过客户、用途和状态校验。 + x-permission: platform:finance:receipt:create + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateReceiptRequest' + responses: + '200': + $ref: '#/components/responses/ReceiptResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' + /platform/finance/v1/cost-entries: get: summary: 查询成本记录 x-permission: platform:finance:cost:view - security: [{platformFinance: []}] - parameters: [{$ref: '#/components/parameters/OptionalCustomerId'}] - responses: {'200': {$ref: '#/components/responses/SuccessList'}} + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/OptionalCustomerId' + responses: + '200': + $ref: '#/components/responses/CostListResponse' + post: + summary: 人工登记单票或整柜成本 + description: >- + SINGLE_ORDER 直接归属同客户订单;CONTAINER_SHARED 默认按柜内同客户有效票体积分摊, + 只有总体积为零时才接受 MANUAL_ZERO_VOLUME。 + x-permission: platform:finance:cost:create + security: + - platformFinance: [] + parameters: + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCostRequest' + responses: + '200': + $ref: '#/components/responses/CostResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' + /platform/v1/files/finance-vouchers: + post: + summary: 上传财务凭证临时文件 + description: >- + File 服务目标接口。文件上传后为 TEMPORARY,Finance 成功创建收款或成本后通过内部接口幂等关联。 + 临时文件不得直接向客户开放下载。 + x-permissions: + - platform:finance:receipt:create + - platform:finance:cost:create + security: + - platformFinance: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/FinanceVoucherUploadRequest' + responses: + '200': + $ref: '#/components/responses/VoucherUploadResponse' /client/finance/v1/bills: get: - summary: 客户只读查询账单 + summary: 客户只读查询可见账单 + description: 服务端只返回当前客户且状态为 ISSUED、PARTIALLY_PAID、PAID 的账单。 x-permission: merchant:finance:statement:list - security: [{customerFinanceRead: []}] - responses: {'200': {$ref: '#/components/responses/SuccessList'}, '403': {$ref: '#/components/responses/BusinessError'}} + security: + - customerFinanceRead: [] + responses: + '200': + $ref: '#/components/responses/ClientBillListResponse' + '403': + $ref: '#/components/responses/BusinessError' /client/finance/v1/bills/{billId}: get: - summary: 客户只读查看账单详情 + summary: 客户只读查看可见账单详情 + description: DRAFT、VOIDED、跨客户或不存在的账单统一返回 FIN-AR-001,不泄露资源是否存在。 x-permission: merchant:finance:statement:view - security: [{customerFinanceRead: []}] - parameters: [{$ref: '#/components/parameters/BillId'}] - responses: {'200': {$ref: '#/components/responses/SuccessBill'}, '403': {$ref: '#/components/responses/BusinessError'}} + security: + - customerFinanceRead: [] + parameters: + - $ref: '#/components/parameters/BillId' + responses: + '200': + $ref: '#/components/responses/ClientBillDetailResponse' + '404': + $ref: '#/components/responses/BusinessError' /client/finance/v1/receipts: get: summary: 客户只读查询收款记录 + description: 仅返回分配到当前客户可见账单的收款信息,不返回平台凭证 fileId 和内部备注。 x-permission: merchant:finance:statement:view - security: [{customerFinanceRead: []}] - responses: {'200': {$ref: '#/components/responses/SuccessList'}, '403': {$ref: '#/components/responses/BusinessError'}} + security: + - customerFinanceRead: [] + responses: + '200': + $ref: '#/components/responses/ClientReceiptListResponse' + '403': + $ref: '#/components/responses/BusinessError' + /internal/file/v1/finance-vouchers/validate: + post: + servers: + - url: / + summary: Finance 批量校验财务凭证 + description: Finance 落单前同步调用;任一文件不合规则整批失败,Finance 不落业务数据。 + security: + - internalService: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VoucherValidationRequest' + responses: + '200': + $ref: '#/components/responses/VoucherValidationResponse' + /internal/file/v1/finance-vouchers/associate: + post: + servers: + - url: / + summary: Finance 幂等关联财务凭证 + description: >- + Finance 落单成功后以业务幂等键调用。相同文件、资源类型和资源 ID 重放返回原关联结果; + 已关联到其他资源时拒绝。调用失败进入 Finance Outbox 重试,不重复创建财务单据。 + security: + - internalService: [] + parameters: + - $ref: '#/components/parameters/IdempotencyKey' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VoucherAssociationRequest' + responses: + '200': + $ref: '#/components/responses/VoucherAssociationResponse' + '409': + $ref: '#/components/responses/IdempotencyConflict' components: securitySchemes: - platformFinance: {type: http, scheme: bearer} - customerFinanceRead: {type: http, scheme: bearer} + platformFinance: + type: http + scheme: bearer + customerFinanceRead: + type: http + scheme: bearer + internalService: + type: apiKey + in: header + name: X-Internal-Service-Token parameters: - BillId: {name: billId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - TenantId: {name: tenantId, in: path, required: true, description: 后端兼容字段,对应产品中的客户 ID, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - RequiredCustomerId: {name: customerId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - OptionalCustomerId: {name: customerId, in: query, required: false, schema: {type: string, pattern: '^[1-9][0-9]*$'}} - IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}} + BillId: + name: billId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + TenantId: + name: tenantId + in: path + required: true + description: 后端兼容字段,对应产品中的客户 ID。 + schema: + $ref: '#/components/schemas/Id' + RequiredCustomerId: + name: customerId + in: query + required: true + schema: + $ref: '#/components/schemas/Id' + OptionalCustomerId: + name: customerId + in: query + required: false + schema: + $ref: '#/components/schemas/Id' + IdempotencyKey: + name: Idempotency-Key + in: header + required: true + schema: + type: string + minLength: 1 + maxLength: 128 schemas: + Id: + type: string + pattern: '^[1-9][0-9]*$' + Money: + type: string + pattern: '^(0|[1-9][0-9]*)(\.[0-9]{1,6})?$' + example: '300.000000' + ResponseMeta: + type: object + required: + - requestId + - traceId + - timestamp + properties: + requestId: + type: string + minLength: 1 + traceId: + type: string + minLength: 1 + timestamp: + type: string + format: date-time + description: UTC ISO-8601 时间。 CustomerTermsRequest: type: object - required: [creditDays] + required: + - creditDays properties: - customerName: {type: string, maxLength: 200, description: 客户名称快照;未传时由服务端按客户主数据补齐} - creditDays: {type: integer, minimum: 0, maximum: 3650, default: 30, description: 自然日账期} + customerName: + type: string + maxLength: 200 + creditDays: + type: integer + minimum: 0 + maximum: 3650 + default: 30 CreateBillRequest: type: object - required: [customerId, billDate, lines] + required: + - customerId + - billDate + - lines properties: - customerId: {type: string, pattern: '^[1-9][0-9]*$'} - billDate: {type: string, format: date} - lines: {type: array, minItems: 1, items: {$ref: '#/components/schemas/BillLineInput'}} + customerId: + $ref: '#/components/schemas/Id' + billDate: + type: string + format: date + lines: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/BillLineInput' UpdateBillRequest: type: object - required: [customerId, billDate, expectedVersion, lines] + required: + - customerId + - billDate + - expectedVersion + - lines properties: - customerId: {type: string, pattern: '^[1-9][0-9]*$'} - billDate: {type: string, format: date} - expectedVersion: {type: integer, minimum: 0} - lines: {type: array, minItems: 1, items: {$ref: '#/components/schemas/BillLineInput'}} + customerId: + $ref: '#/components/schemas/Id' + billDate: + type: string + format: date + expectedVersion: + type: integer + minimum: 0 + lines: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/BillLineInput' BillLineInput: type: object - required: [sourceFeeId, sourceFeeVersion, amount] + required: + - sourceFeeId + - sourceFeeVersion + - feeItemName + - amount properties: - sourceFeeId: {type: string, pattern: '^[1-9][0-9]*$'} - sourceFeeVersion: {type: integer, minimum: 1} - orderId: {type: string, pattern: '^[1-9][0-9]*$'} - inboundId: {type: string, pattern: '^[1-9][0-9]*$'} - feeItemName: {type: string} - amount: {type: number, minimum: 0, multipleOf: 0.000001} + sourceFeeId: + $ref: '#/components/schemas/Id' + sourceFeeVersion: + type: integer + minimum: 1 + orderId: + $ref: '#/components/schemas/Id' + inboundId: + $ref: '#/components/schemas/Id' + feeItemName: + type: string + minLength: 1 + maxLength: 100 + amount: + $ref: '#/components/schemas/Money' VersionRequest: type: object - required: [expectedVersion] - properties: {expectedVersion: {type: integer, minimum: 0}} + required: + - expectedVersion + properties: + expectedVersion: + type: integer + minimum: 0 ReasonRequest: type: object - required: [expectedVersion, reason] + required: + - expectedVersion + - reason properties: - expectedVersion: {type: integer, minimum: 0} - reason: {type: string, minLength: 1, maxLength: 500} + expectedVersion: + type: integer + minimum: 0 + reason: + type: string + minLength: 1 + maxLength: 500 AdjustBillRequest: allOf: - - {$ref: '#/components/schemas/ReasonRequest'} + - $ref: '#/components/schemas/ReasonRequest' - type: object - required: [lines] - properties: {lines: {type: array, minItems: 1, items: {$ref: '#/components/schemas/BillLineInput'}}} + required: + - lines + properties: + lines: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/BillLineInput' CreateReceiptRequest: type: object - required: [customerId, receivedDate, amount, paymentMethod, voucherFileIds, allocations] + required: + - customerId + - receivedDate + - amount + - paymentMethod + - voucherFileIds + - allocations properties: - customerId: {type: string, pattern: '^[1-9][0-9]*$'} - receivedDate: {type: string, format: date} - amount: {type: number, minimum: 0, exclusiveMinimum: true, multipleOf: 0.000001} - currency: {type: string, enum: [CNY], default: CNY} - paymentMethod: {type: string, enum: [BANK_TRANSFER, CASH, OTHER]} - voucherFileIds: {type: array, minItems: 1, items: {type: string}} - remark: {type: string, maxLength: 500} - allocations: {type: array, minItems: 1, items: {$ref: '#/components/schemas/ReceiptAllocationInput'}} + customerId: + $ref: '#/components/schemas/Id' + receivedDate: + type: string + format: date + amount: + $ref: '#/components/schemas/Money' + currency: + type: string + enum: + - CNY + default: CNY + paymentMethod: + type: string + enum: + - BANK_TRANSFER + - CASH + - OTHER + voucherFileIds: + type: array + minItems: 1 + uniqueItems: true + items: + $ref: '#/components/schemas/Id' + remark: + type: string + maxLength: 500 + allocations: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/ReceiptAllocationInput' example: customerId: '10001' receivedDate: '2026-08-11' - amount: 300 + amount: '300.000000' currency: CNY paymentMethod: BANK_TRANSFER - voucherFileIds: ['90001'] + voucherFileIds: + - '90001' allocations: - - {billId: '81001', amount: 100} - - {billId: '81002', amount: 100} - - {billId: '81003', amount: 100} + - billId: '81001' + amount: '100.000000' + - billId: '81002' + amount: '100.000000' + - billId: '81003' + amount: '100.000000' ReceiptAllocationInput: type: object - required: [billId, amount] + required: + - billId + - amount properties: - billId: {type: string, pattern: '^[1-9][0-9]*$'} - amount: {type: number, minimum: 0, exclusiveMinimum: true, multipleOf: 0.000001} + billId: + $ref: '#/components/schemas/Id' + amount: + $ref: '#/components/schemas/Money' CreateCostRequest: + oneOf: + - $ref: '#/components/schemas/CreateSingleOrderCostRequest' + - $ref: '#/components/schemas/CreateContainerCostRequest' + discriminator: + propertyName: costType + mapping: + SINGLE_ORDER: '#/components/schemas/CreateSingleOrderCostRequest' + CONTAINER_SHARED: '#/components/schemas/CreateContainerCostRequest' + CostRequestBase: type: object - description: SINGLE_ORDER 必须提供 orderId;CONTAINER_SHARED 必须提供 containerId,并按柜内有效票体积分摊。 - required: [customerId, costType, amount, expenseName, voucherFileIds] + required: + - customerId + - amount + - expenseName + - voucherFileIds properties: - customerId: {type: string, pattern: '^[1-9][0-9]*$'} - costType: {type: string, enum: [SINGLE_ORDER, CONTAINER_SHARED]} - orderId: {type: string, pattern: '^[1-9][0-9]*$'} - containerId: {type: string, pattern: '^[1-9][0-9]*$'} - expenseName: {type: string, minLength: 1, maxLength: 100} - amount: {type: number, minimum: 0, exclusiveMinimum: true, multipleOf: 0.000001} - currency: {type: string, enum: [CNY], default: CNY} - voucherFileIds: {type: array, minItems: 1, items: {type: string}} - remark: {type: string, maxLength: 500} - BillStatus: {type: string, enum: [DRAFT, ISSUED, PARTIALLY_PAID, PAID, VOIDED]} - ApiEnvelope: + customerId: + $ref: '#/components/schemas/Id' + expenseName: + type: string + minLength: 1 + maxLength: 100 + amount: + $ref: '#/components/schemas/Money' + currency: + type: string + enum: + - CNY + default: CNY + voucherFileIds: + type: array + minItems: 1 + uniqueItems: true + items: + $ref: '#/components/schemas/Id' + remark: + type: string + maxLength: 500 + CreateSingleOrderCostRequest: + allOf: + - $ref: '#/components/schemas/CostRequestBase' + - type: object + required: + - costType + - orderId + properties: + costType: + type: string + enum: + - SINGLE_ORDER + orderId: + $ref: '#/components/schemas/Id' + CreateContainerCostRequest: + allOf: + - $ref: '#/components/schemas/CostRequestBase' + - type: object + required: + - costType + - containerId + - allocationMode + properties: + costType: + type: string + enum: + - CONTAINER_SHARED + containerId: + $ref: '#/components/schemas/Id' + allocationMode: + type: string + enum: + - AUTO_VOLUME + - MANUAL_ZERO_VOLUME + default: AUTO_VOLUME + manualAllocations: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/ManualCostAllocationInput' + description: 仅柜内有效票总体积为零且 allocationMode=MANUAL_ZERO_VOLUME 时允许。 + ManualCostAllocationInput: type: object - required: [code, message, data] + required: + - orderId + - amount properties: - code: {type: string, example: '200'} - message: {type: string} - data: {type: object, additionalProperties: true} - requestId: {type: string} - ApiListEnvelope: + orderId: + $ref: '#/components/schemas/Id' + amount: + $ref: '#/components/schemas/Money' + FinanceVoucherUploadRequest: type: object - required: [code, message, data] + required: + - customerId + - purpose + - file properties: - code: {type: string, example: '200'} - message: {type: string} - data: {type: array, items: {type: object, additionalProperties: true}} - requestId: {type: string} + customerId: + $ref: '#/components/schemas/Id' + purpose: + type: string + enum: + - RECEIPT + - COST + file: + type: string + format: binary + VoucherValidationRequest: + type: object + required: + - customerId + - purpose + - fileIds + properties: + customerId: + $ref: '#/components/schemas/Id' + purpose: + type: string + enum: + - RECEIPT + - COST + fileIds: + type: array + minItems: 1 + uniqueItems: true + items: + $ref: '#/components/schemas/Id' + VoucherAssociationRequest: + type: object + required: + - customerId + - purpose + - resourceType + - resourceId + - fileIds + properties: + customerId: + $ref: '#/components/schemas/Id' + purpose: + type: string + enum: + - RECEIPT + - COST + resourceType: + type: string + enum: + - FINANCE_RECEIPT + - FINANCE_COST + resourceId: + $ref: '#/components/schemas/Id' + fileIds: + type: array + minItems: 1 + uniqueItems: true + items: + $ref: '#/components/schemas/Id' + BillStatus: + type: string + enum: + - DRAFT + - ISSUED + - PARTIALLY_PAID + - PAID + - VOIDED + ClientBillStatus: + type: string + enum: + - ISSUED + - PARTIALLY_PAID + - PAID + Bill: + type: object + required: + - billId + - billNo + - customerId + - customerName + - billDate + - dueDate + - currency + - totalAmount + - receivedAmount + - outstandingAmount + - status + - version + properties: + billId: + $ref: '#/components/schemas/Id' + billNo: + type: string + customerId: + $ref: '#/components/schemas/Id' + customerName: + type: string + billDate: + type: string + format: date + dueDate: + type: string + format: date + currency: + type: string + enum: + - CNY + totalAmount: + $ref: '#/components/schemas/Money' + receivedAmount: + $ref: '#/components/schemas/Money' + outstandingAmount: + $ref: '#/components/schemas/Money' + status: + $ref: '#/components/schemas/BillStatus' + version: + type: integer + minimum: 0 + ClientBill: + allOf: + - $ref: '#/components/schemas/Bill' + - type: object + properties: + status: + $ref: '#/components/schemas/ClientBillStatus' + BillLine: + type: object + required: + - lineId + - sourceFeeId + - sourceFeeVersion + - feeItemName + - amount + properties: + lineId: + $ref: '#/components/schemas/Id' + sourceFeeId: + $ref: '#/components/schemas/Id' + sourceFeeVersion: + type: integer + minimum: 1 + orderId: + $ref: '#/components/schemas/Id' + inboundId: + $ref: '#/components/schemas/Id' + feeItemName: + type: string + amount: + $ref: '#/components/schemas/Money' + BillDetail: + allOf: + - $ref: '#/components/schemas/Bill' + - type: object + required: + - lines + properties: + lines: + type: array + items: + $ref: '#/components/schemas/BillLine' + ClientBillDetail: + allOf: + - $ref: '#/components/schemas/ClientBill' + - type: object + required: + - lines + properties: + lines: + type: array + items: + $ref: '#/components/schemas/BillLine' + ReceiptAllocation: + type: object + required: + - billId + - allocatedAmount + properties: + billId: + $ref: '#/components/schemas/Id' + allocatedAmount: + $ref: '#/components/schemas/Money' + Receipt: + type: object + required: + - receiptId + - receiptNo + - customerId + - receivedDate + - amount + - currency + - paymentMethod + - voucherFileIds + - allocations + - createdAt + properties: + receiptId: + $ref: '#/components/schemas/Id' + receiptNo: + type: string + customerId: + $ref: '#/components/schemas/Id' + receivedDate: + type: string + format: date + amount: + $ref: '#/components/schemas/Money' + currency: + type: string + enum: + - CNY + paymentMethod: + type: string + enum: + - BANK_TRANSFER + - CASH + - OTHER + voucherFileIds: + type: array + items: + $ref: '#/components/schemas/Id' + remark: + type: string + allocations: + type: array + items: + $ref: '#/components/schemas/ReceiptAllocation' + createdAt: + type: string + format: date-time + ClientReceipt: + type: object + required: + - receiptId + - receiptNo + - receivedDate + - amount + - currency + - paymentMethod + - allocations + properties: + receiptId: + $ref: '#/components/schemas/Id' + receiptNo: + type: string + receivedDate: + type: string + format: date + amount: + $ref: '#/components/schemas/Money' + currency: + type: string + enum: + - CNY + paymentMethod: + type: string + allocations: + type: array + items: + $ref: '#/components/schemas/ReceiptAllocation' + CostAllocation: + type: object + required: + - orderId + - volumeCbm + - totalVolumeCbm + - ratio + - allocatedAmount + - roundingAdjustment + properties: + orderId: + $ref: '#/components/schemas/Id' + volumeCbm: + $ref: '#/components/schemas/Money' + totalVolumeCbm: + $ref: '#/components/schemas/Money' + ratio: + type: string + pattern: '^(0|1|0\.[0-9]{1,12})$' + allocatedAmount: + $ref: '#/components/schemas/Money' + roundingAdjustment: + $ref: '#/components/schemas/Money' + Cost: + type: object + required: + - costId + - costNo + - customerId + - costType + - expenseName + - amount + - currency + - voucherFileIds + - allocations + - createdAt + properties: + costId: + $ref: '#/components/schemas/Id' + costNo: + type: string + customerId: + $ref: '#/components/schemas/Id' + costType: + type: string + enum: + - SINGLE_ORDER + - CONTAINER_SHARED + orderId: + $ref: '#/components/schemas/Id' + containerId: + $ref: '#/components/schemas/Id' + allocationMode: + type: string + enum: + - DIRECT + - AUTO_VOLUME + - MANUAL_ZERO_VOLUME + expenseName: + type: string + amount: + $ref: '#/components/schemas/Money' + currency: + type: string + enum: + - CNY + voucherFileIds: + type: array + items: + $ref: '#/components/schemas/Id' + allocations: + type: array + items: + $ref: '#/components/schemas/CostAllocation' + createdAt: + type: string + format: date-time + CustomerTerms: + type: object + required: + - customerId + - creditDays + - status + - version + properties: + customerId: + $ref: '#/components/schemas/Id' + customerName: + type: string + creditDays: + type: integer + minimum: 0 + maximum: 3650 + status: + type: string + enum: + - ACTIVE + version: + type: integer + minimum: 0 + VoucherFile: + type: object + required: + - fileId + - customerId + - purpose + - fileName + - contentType + - sizeBytes + - sha256 + - status + properties: + fileId: + $ref: '#/components/schemas/Id' + customerId: + $ref: '#/components/schemas/Id' + purpose: + type: string + enum: + - RECEIPT + - COST + fileName: + type: string + contentType: + type: string + enum: + - image/jpeg + - image/png + - image/webp + - application/pdf + sizeBytes: + type: integer + format: int64 + minimum: 1 + maximum: 10485760 + sha256: + type: string + pattern: '^[a-f0-9]{64}$' + status: + type: string + enum: + - TEMPORARY + - ASSOCIATED + resourceType: + type: string + resourceId: + $ref: '#/components/schemas/Id' + VoucherValidationResult: + type: object + required: + - valid + - files + properties: + valid: + type: boolean + files: + type: array + items: + $ref: '#/components/schemas/VoucherFile' + VoucherAssociationResult: + type: object + required: + - resourceType + - resourceId + - files + properties: + resourceType: + type: string + enum: + - FINANCE_RECEIPT + - FINANCE_COST + resourceId: + $ref: '#/components/schemas/Id' + files: + type: array + items: + $ref: '#/components/schemas/VoucherFile' + ErrorDetail: + type: object + required: + - field + - reason + properties: + field: + type: string + reason: + type: string ErrorEnvelope: type: object - required: [code, message] - properties: {code: {type: string}, message: {type: string}, requestId: {type: string}} + required: + - code + - message + - requestId + - traceId + - timestamp + properties: + code: + type: string + example: FIN-AR-002 + message: + type: string + details: + type: array + items: + $ref: '#/components/schemas/ErrorDetail' + requestId: + type: string + traceId: + type: string + timestamp: + type: string + format: date-time + BillListEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: + type: array + items: {$ref: '#/components/schemas/Bill'} + ClientBillListEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: + type: array + items: {$ref: '#/components/schemas/ClientBill'} + BillDetailEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/BillDetail'} + ClientBillDetailEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/ClientBillDetail'} + ReceiptEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/Receipt'} + ReceiptListEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: + type: array + items: {$ref: '#/components/schemas/Receipt'} + ClientReceiptListEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: + type: array + items: {$ref: '#/components/schemas/ClientReceipt'} + CostEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/Cost'} + CostListEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: + type: array + items: {$ref: '#/components/schemas/Cost'} + CustomerTermsEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/CustomerTerms'} + VoucherUploadEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/VoucherFile'} + VoucherValidationEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/VoucherValidationResult'} + VoucherAssociationEnvelope: + allOf: + - $ref: '#/components/schemas/ResponseMeta' + - type: object + required: [code, message, data] + properties: + code: {type: string, example: '200'} + message: {type: string} + data: {$ref: '#/components/schemas/VoucherAssociationResult'} responses: - SuccessBill: {description: Bill result, content: {application/json: {schema: {$ref: '#/components/schemas/ApiEnvelope'}}}} - SuccessReceipt: {description: Receipt result, content: {application/json: {schema: {$ref: '#/components/schemas/ApiEnvelope'}}}} - SuccessCost: {description: Cost result, content: {application/json: {schema: {$ref: '#/components/schemas/ApiEnvelope'}}}} - SuccessObject: {description: Object result, content: {application/json: {schema: {$ref: '#/components/schemas/ApiEnvelope'}}}} - SuccessList: {description: Non-paged list result, content: {application/json: {schema: {$ref: '#/components/schemas/ApiListEnvelope'}}}} - BusinessError: {description: Business error, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorEnvelope'}}}} + BillListResponse: + description: 平台账单列表。 + content: + application/json: + schema: {$ref: '#/components/schemas/BillListEnvelope'} + ClientBillListResponse: + description: 客户可见账单列表。 + content: + application/json: + schema: {$ref: '#/components/schemas/ClientBillListEnvelope'} + BillDetailResponse: + description: 平台账单详情。 + content: + application/json: + schema: {$ref: '#/components/schemas/BillDetailEnvelope'} + ClientBillDetailResponse: + description: 客户可见账单详情。 + content: + application/json: + schema: {$ref: '#/components/schemas/ClientBillDetailEnvelope'} + ReceiptResponse: + description: 收款结果。 + content: + application/json: + schema: {$ref: '#/components/schemas/ReceiptEnvelope'} + ReceiptListResponse: + description: 平台收款列表。 + content: + application/json: + schema: {$ref: '#/components/schemas/ReceiptListEnvelope'} + ClientReceiptListResponse: + description: 客户可见收款列表。 + content: + application/json: + schema: {$ref: '#/components/schemas/ClientReceiptListEnvelope'} + CostResponse: + description: 成本结果。 + content: + application/json: + schema: {$ref: '#/components/schemas/CostEnvelope'} + CostListResponse: + description: 成本列表。 + content: + application/json: + schema: {$ref: '#/components/schemas/CostListEnvelope'} + CustomerTermsResponse: + description: 客户账期结果。 + content: + application/json: + schema: {$ref: '#/components/schemas/CustomerTermsEnvelope'} + VoucherUploadResponse: + description: 临时财务凭证元数据。 + content: + application/json: + schema: {$ref: '#/components/schemas/VoucherUploadEnvelope'} + VoucherValidationResponse: + description: 财务凭证批量校验结果。 + content: + application/json: + schema: {$ref: '#/components/schemas/VoucherValidationEnvelope'} + VoucherAssociationResponse: + description: 财务凭证关联结果。 + content: + application/json: + schema: {$ref: '#/components/schemas/VoucherAssociationEnvelope'} + BusinessError: + description: 业务错误。 + content: + application/json: + schema: {$ref: '#/components/schemas/ErrorEnvelope'} + IdempotencyConflict: + description: >- + 幂等冲突。同键异参返回 FIN-IDEMPOTENCY-CONFLICT;同键同参且原请求仍为 PROCESSING + 返回 FIN-IDEMPOTENCY-PROCESSING,并携带 Retry-After: 2。 + headers: + Retry-After: + schema: + type: integer + minimum: 1 + description: 仅 PROCESSING 时返回,单位秒。 + content: + application/json: + schema: {$ref: '#/components/schemas/ErrorEnvelope'} diff --git a/财务管理/财务F0实施任务拆分.md b/财务管理/财务F0实施任务拆分.md index 25b08b2..794cd86 100644 --- a/财务管理/财务F0实施任务拆分.md +++ b/财务管理/财务F0实施任务拆分.md @@ -1,6 +1,6 @@ # 财务 F0 实施任务拆分与验收门禁 -> 当前状态:开发任务草案;《财务开工门禁与 F0/F1 任务清单》G1 放行前不得据此启动业务开发。 +> 当前状态:F0 基础实现已落地;本文作为实施追溯与缺口核对清单,不再作为“尚未开工”说明。 > 总控门禁:[财务开工门禁与 F0/F1 任务清单](财务开工门禁与F0-F1任务清单.md)。 > > 本文只拆分技术实施任务,不定义新的业务规则,也不修改现有规划。范围依据《财务服务边界设计》《财务管理产品确认表》和后端仓库现行 Maven、Nacos、Gateway、Docker Compose、Jenkins 约定。 diff --git a/财务管理/财务开工门禁与F0-F1任务清单.md b/财务管理/财务开工门禁与F0-F1任务清单.md index 7bc6d32..c1e99d2 100644 --- a/财务管理/财务开工门禁与F0-F1任务清单.md +++ b/财务管理/财务开工门禁与F0-F1任务清单.md @@ -1,13 +1,13 @@ # 财务开工门禁与 F0/F1 任务清单 -> 版本:V1.0 -> 日期:2026-08-10 -> 当前阶段:开发前详细设计 -> 结论:技术方案可继续;业务开发须按门禁逐项放行 +> 版本:V1.1 +> 日期:2026-08-11 +> 当前阶段:F0/F1 基础实现已落地,专项修复与测试门禁收敛中 +> 结论:本文任务表保留为实施追溯;当前放行依据改为《F1人工财务契约审查与门禁》 ## 一、总控结论 -Finance 的服务拆分、独立数据库、服务内部模块边界和 Firstleg 协作方式已经技术确认,可以继续做详细设计和契约测试准备。 +Finance 的服务拆分、独立数据库、基础表、权限、人工账单/收款/成本接口及 Admin/Client 基础页面已经落地。当前不是等待开工,而是按专项测试退回项修复固定 DTO、凭证/File 链路、体积分摊、幂等和客户可见状态。 F0 的平台主体、首期仅 RMB/CNY、自定义账期、编号、费用冻结、无历史迁移、客户角色和首期简化审批已经确认。当前采用人工简化方案:账单、收款、成本均由平台财务填写表单并上传凭证;客户账户、发票、供应商在线应付、银行接口、复杂审批和月结首期不做。原表状态已归一为 25 项已确认、7 项暂缓。详见[产品确认版差异与剩余待确认](财务管理产品确认版差异与剩余待确认.md)。 @@ -16,14 +16,14 @@ F0 的平台主体、首期仅 RMB/CNY、自定义账期、编号、费用冻结 | 材料 | 状态 | 作用 | |---|---|---| | 财务服务边界设计 | 已完成 | 确认 `finance-service`、`baiyue_finance`、9203 预留和部署改造清单 | -| 财务管理产品确认表/XLSX | 部分已确认 | 会议继续填写剩余业务项;技术结论单独列示 | -| 财务领域模型与数据字典 | 草案完成 | F0/F1 模型、表和约束;未确认口径显式标记 | -| Firstleg-Finance 跨服务契约 | 草案完成 | 事件、快照查询、幂等、重试、权限和契约测试 | +| 财务管理产品确认表/XLSX | 首期已确认 | CNY、默认 30 天账期、人工账单/收款/成本、客户只读 | +| 财务领域模型与数据字典 | 已形成并进入实现校正 | F0/F1 模型、表和约束 | +| Firstleg-Finance 跨服务契约 | 技术边界已确认 | 事件、快照查询、幂等、重试、权限和契约测试 | | 财务历史迁移与上线验收方案 | 首发不启用历史迁移 | 保留未来补迁参考;首发使用其中 F0/F1 灰度和验收部分 | ## 三、分阶段开工门禁 -### G0:详细设计门禁 +### G0:详细设计门禁(已完成) 可立即进行。条件已满足: @@ -34,9 +34,9 @@ F0 的平台主体、首期仅 RMB/CNY、自定义账期、编号、费用冻结 G0 允许:完善 API/OpenAPI、数据库迁移草案、契约测试样例、页面线框和任务拆分;不允许写入生产财务数据。 -### G1:F0 财务底座开发门禁 +### G1:F0 财务底座开发门禁(基础实现已完成) -进入 F0 开发前必须满足: +以下条目保留为实现核对依据,不再表示尚未开工: 1. 产品确认表中的 F0 主体/账套、往来单位、币种精度、汇率预留、期间、编号、来源入池、权限审计和历史迁移口径已填写,或明确书面暂缓方案。 其中 F0-11“一客户企业=一个对客结算对象”已确认;客户子用户仍不成为新的财务主体。 @@ -44,9 +44,9 @@ G0 允许:完善 API/OpenAPI、数据库迁移草案、契约测试样例、 3. 事件信封、来源版本、Inbox/Outbox、幂等冲突和内部快照查询契约冻结。 4. Finance 权限码、客户范围校验、平台员工操作范围和审计事件清单冻结。 -### G2:F1 客户应收开发门禁 +### G2:F1 客户应收开发门禁(基础实现已完成,专项修复中) -进入 F1 前必须满足: +当前按以下冻结口径修复和补测: - 账单聚合维度(首期固定 RMB/CNY)和账期计算规则冻结; - 平台出账、关闭、重开和线下异议录入状态冻结;客户首期只读; @@ -74,24 +74,24 @@ G0 允许:完善 API/OpenAPI、数据库迁移草案、契约测试样例、 | F1-T05 | 客户侧账单 | 客户只读查看、余额和收款记录 | F1-T02/F1-T04 | 客户不可见内部备注、供应商成本和内部附件 | | F1-T06 | F1 回归验收 | 正常、重复、并发、越权、冲销、迁移案例 | 以上任务 | 通过迁移与上线验收方案中的 F0/F1 用例 | -## 五、当前可立即执行与必须等待 +## 五、当前执行重点 -### 现在可以继续 +### 当前继续执行 - 把草案契约转换为 OpenAPI/事件契约测试样例; - 完善 F0/F1 数据字典、状态转移表和权限矩阵; - 设计 Finance Admin/Client 页面和空态、错误态、线下异议录入态; - 准备脱敏历史费用样本与迁移校验脚本设计; -- 按 F0-T01~F0-T05 拆解开发任务,但暂不合入业务代码。 +- 按 OpenAPI 1.1.0 修复服务端 DTO、凭证链路、体积分摊、幂等和客户可见状态,并补齐契约测试。 -### 必须等待产品确认 +### 本轮不再等待产品确认 -- 生产账单、应收、收款、核销的正式业务规则; -- 客户账户、发票、供应商应付和利润的正式口径; -- 历史费用是否迁移、线下已结算排除台账和灰度客户范围。 +- F1 首期只做 CNY 人工账单、人工收款、人工成本和客户只读,产品口径已经冻结。 +- 客户账户、发票、供应商在线应付、汇率、月结和利润不属于本轮,不阻塞 F1 修复。 +- 首发不迁移历史,不阻塞 F1 修复。 ## 六、总控放行结论 -当前状态为:**G0 已通过,G1/G2 待产品确认后放行。** +当前状态为:**G0 已完成,G1/G2 基础实现已落地,F1 正在按专项测试门禁修复。** -这意味着可以继续做开发前详细设计和可逆的契约/测试准备;产品回传 XLSX 并冻结 F0/F1 后,即可进入 `finance-service` 骨架、独立数据库和客户应收闭环开发。 +当前无需再次等待产品确认或重新创建服务骨架。修复完成、OpenAPI 契约测试和《F1人工财务契约审查与门禁》验收用例通过后,方可放行完整联调。 diff --git a/财务管理/财务管理产品确认表.md b/财务管理/财务管理产品确认表.md index 2cd6f86..f99edf9 100644 --- a/财务管理/财务管理产品确认表.md +++ b/财务管理/财务管理产品确认表.md @@ -2,7 +2,7 @@ > 版本:V1.0 > 创建日期:2026-08-10 -> 状态:待产品与业务确认 +> 状态:首期人工简化财务口径已确认;后续 F2-F4 待确认项不阻塞本轮 F1 修复 > 适用范围:客户账户、客户应收应付、客户对账单、结算单应收/应付、收付款核销、发票、费用分析和利润 > 技术方案说明:服务边界、跨服务访问、事件幂等、金额存储、状态留痕和正式模块发布方式已按当前仓库架构直接确认,不再要求产品确认;以 XLSX 的“技术结论”工作表为准。