# 对《财务系统深度审计》的代码级复核(2026-10-27) > 复核对象:用户给出的财务系统审计结论。 > 复核方式:逐条对照当前 `dev` 代码、迁移 `001–105`、本机数据库与生产 dump 模拟升级。 > 总评:**架构判断(双轨期、旧报表口径混编、历史人工成本缺失)方向正确;但有 4 处结论需要纠正,2 处需要降级为“风险假设”,1 处已经是旧代码状态。** --- ## 一、需要纠正的结论 ### 1. “打手报酬 + 提现会被重复扣减 100%” —— 不成立 旧报表的净现金流函数实际是: ```ts // apps/backend/src/services/admin/admin-finance-service.ts:323-338 return ( row.paidAmount + row.rechargeAmount - row.refundAmount - row.withdrawAmount - row.afterSalesRefundAmount + row.workerRecoveryAmount ) ``` **`workerRewardAmount` 没有进入 `netCashFlow`。** 代码没有把“打手报酬”和“提现”同时减掉,因此不存在“100% 双重扣减”。 真正的问题是**口径混编**: - “打手报酬”是权责发生制的成本列; - “提现”是收付实现制的现金流出; - 页面把它们并排展示,但净现金流只减提现,用户如果把两列相加会误读; - 旧页面既不是完整利润表,也不是完整现金流量表。 **修正结论:不是算术 bug,是报表语义/口径设计问题。** ### 2. “总账同步 DELETE 重建,违背不可变性” —— 当前代码已修复 用户引用的 `finance-ledger-service.ts#L251-L265` 是改造前实现。当前代码已经: - 不再 `DELETE FROM finance_journal_entries`; - 按日计算收入/退款/成本的 delta,追加 `sales_*_adjustment` 凭证; - 迁移 `104` 增加 `trg_finance_journal_immutable`,有分录的已过账凭证禁止删除; - 迁移 `104` 增加 `trg_finance_journal_balanced`,事务提交时校验整张凭证借贷平衡。 **修正结论:该隐患在 2026-10-27 的 P0 改造中已关闭;剩余问题是“按日汇总凭证”仍然粒度较粗,不是“物理删除”。** ### 3. “所有金额都是 BIGINT,底层约束极其严密” —— 不成立,L1 钱包反而很弱 当前金额类型并不统一: | 表/列 | 类型 | 上限 | | --- | --- | --- | | `orders.total_amount` | `INTEGER` | ¥21,474,836.47 | | `worker_wallets.*` 六个余额列 | `INTEGER` | 同上 | | `worker_wallet_ledgers.amount/balance_after` | `INTEGER` | 同上 | | `worker_finance_requests.amount/frozen_amount` | `INTEGER` | 同上 | | `fund_*`、`cost_*`、`finance_*` | `BIGINT` | 远大于业务规模 | 证据: - [001_init.sql:108](../apps/backend/src/db/migrations/001_init.sql#L108) `orders.total_amount INTEGER` - [004_worker_order_platform.sql:31-53](../apps/backend/src/db/migrations/004_worker_order_platform.sql#L31-L53) 钱包/流水均为 `INTEGER` 而且 `worker_wallets` / `worker_wallet_ledgers` / `worker_finance_requests` **没有任何 CHECK 约束**: - 没有 `available_amount >= 0`、`frozen_deposit_amount >= 0` 等余额非负约束; - 没有“余额 = 流水汇总”的触发器; - `worker_wallet_ledgers` 没有 `(related_work_order_id, ledger_type, shareId)` 业务唯一键,结算重试/重复调用没有数据库级幂等。 **修正结论:新财务表约束较强;真正掌管钱的 L1 钱包约束很弱,且金额是 int4,不是 BIGINT。** ### 4. “退款撤销生成红字冲正” —— 只有净额调整,没有凭证级 reversal 当前 `voidOrderRefund` 的实际动作是: 1. 把 `order_refunds.status` 置为 `voided`; 2. 重算 `orders.pay_status`(本轮 P0 修复); 3. 重算销售事实; 4. 总账按事实差异追加 `sales_refund_adjustment`。 它**没有**: - `reversal_of_id` 指向原退款凭证; - 一张与原凭证一一对应的红字凭证; - 对原退款凭证的状态标记。 **修正结论:效果上是冲减,审计上不是“红字冲正”;应描述为“软删除 + 事实重算 + 差额调整凭证”。** ### 5. “打手结算/提现/售后必然死锁” —— 是风险假设,不是已证实结论 当前主要锁顺序基本一致: - 工单结算:`work_orders` → `work_order_shares` → `worker_wallets` - 售后:`work_orders` → `work_order_shares` → `worker_after_sales_cases/debts` → `worker_wallets` - 提现:`worker_finance_requests` → `worker_wallets` 共同热点是 `worker_wallets`,但代码里没有看到“先锁钱包、再回头锁工单/份额”的反向路径;因此不能直接断言“必然死锁”。 **修正结论:这是高并发热点与潜在死锁风险,需要压测 + 死锁重试/监控,不是已证实的生产故障。** ### 6. “6 状态可变模型” —— 实际是 6 个余额桶,不是数据库状态机 `worker_wallets` 是 6 个金额字段,不是 6 个状态: - `available_amount` - `frozen_deposit_amount` - `pending_unfreeze_amount` - `frozen_withdraw_amount` - `total_credited_amount` - `total_settled_amount` 状态迁移完全由应用层代码维护,数据库没有状态枚举/非负约束/守恒触发器。 **修正结论:应称为“6 个可变余额桶”,不是严格状态机。** --- ## 二、判断正确、但需要补充的部分 ### 1. 历史人工成本缺失导致“假毛利” —— 正确 - 人工订单项:**115,194**; - 人工收入:**¥14,470,149.94**(1,447.01 万); - 历史人工成本未补录,`cost_snapshots` 没有对应 `manual_case`,销售事实里 `cost_missing_count > 0`、成本按 0 计; - 前端 `AdminSalesProfitPage` 虽然有“有 N 个订单项没有成本快照,当前毛利偏高”的 warning,但报表仍可导出/展示; - 关账门槛只覆盖“首次人工配置之后”的人工单,历史区间不会被自动拦截。 **建议:全生命周期报表必须默认按 `cost_stage` 过滤或显著标注;历史区间单独出“成本未采集”版本,不能与上线后区间混在一起。** ### 2. 影子总账是“日汇总粗粒度账” —— 正确 当前总账仍按 `sales_daily_facts` 的“日期 × 来源 × 店铺 × 商品 × 履约方式”聚合成每日总收入/总退款/总成本,再生成凭证。它没有: - 单笔订单/工单级凭证; - provider/shop/product 维度; - 钱包、押金、提现、寄卖、平台费凭证。 **建议:先把总账定位为“销售/成本影子账”,不要对外称为完整复式总账;转正前补齐事件级过账。** ### 3. 人工履约事务/幂等做得较好 —— 正确 - `SELECT ... FOR UPDATE OF oi` 锁订单项; - `uq_cost_snapshots_manual_order_item` 唯一索引; - 备用金/积分扣减与快照同事务; - 失败整体回滚。 这部分用户判断准确。 --- ## 三、用户审计漏掉的关键问题 ### 1. L1 钱包没有数据库级余额约束 `worker_wallets` 没有 `CHECK`,也没有“余额 = Σ流水”的约束。应用层一旦有 bug/并发/手工 SQL,余额可以直接变负或与流水脱节。新财务表有 `fund_accounts` 的余额触发器,但**没有覆盖 `worker_wallets`**。 ### 2. 打手结算流水没有业务幂等键 `worker_wallet_ledgers` 只有 `(worker_id, created_at)` 索引;没有 `(related_work_order_id, ledger_type, shareId)` 唯一键。工单状态机通常能挡住重复结算,但数据库层没有兜底。 ### 3. `orders.total_amount` 是 int4 单笔订单上限 ¥21,474,836.47。当前业务量远低于上限,但和“全部 BIGINT”的表述不符,未来大额订单/多币种会踩边界。 ### 4. 旧报表不是“利润表”,也不是“现金流量表” 它把订单收款、退款、打手报酬、提现、充值、售后追缴拼在一起;`netCashFlow` 只减现金流出,不减打手报酬(这是对的),但页面没有把“成本/现金流/资产负债”拆开。建议保留为“运营收支流水”,另建利润表/现金流量表,而不是继续给它加字段。 ### 5. 迁移收敛后仍有“生产数据初始化”断层 生产 dump 升级到 `105` 成功,但: - `product_catalog=0`; - `cost_snapshots=0`; - `sales_daily_facts=0`; - `fund_accounts=0`; - 375,447 个订单项需要 revenue/fulfillment 回填。 **代码合并 ≠ 财务可用。** 发布必须按上线手册初始化。 --- ## 四、对用户路线图的校准 | 用户步骤 | 复核结论 | | --- | --- | | 第一步:废弃旧报表 | **方向对,但不要直接删**;先把“现金流/运营流水”和“损益”拆成两个页面,旧页保留为现金流辅助 | | 第二步:事件级实时过账 | **正确**;本轮已完成“停止删除重建 + 追加调整凭证 + 数据库触发器”,但尚未做到每笔业务同事务过账 | | 第三步:平台账单 API | **正确**;当前仍是手工 JSON 导入,快手退款 API 仅部分接入 | | 第四步:钱包转总账投影 | **正确但最远**;先补 L1 钱包约束/幂等,再谈投影 | --- ## 五、一句话结论 **用户的“双轨期”判断是对的,旧报表口径混编、历史人工成本缺失、影子账粒度粗也都是真问题;但“重复扣减 100%”“全部 BIGINT/约束严密”“DELETE 重建”“必然死锁”这几条与当前代码不符。真正更危险的是:掌管钱的 `worker_wallets` 仍然是 int4、无 CHECK、无流水幂等,而新财务表的约束并没有覆盖它。** --- ## 六、后续补全记录(2026-10-27) 针对本复核指出的 L1 钱包问题,已补迁移 `104_finance_controls.sql`(含 L1 钱包硬化;生产 dump `102 → 105` 实测见合并评估): - `orders.total_amount`、`worker_wallets.*`、`worker_wallet_ledgers.amount/balance_after/frozen_after`、`worker_finance_requests.amount/frozen_amount`、工单/份额/售后/寄卖金额全部 **int4 → BIGINT**; - `worker_wallets` 六个余额、流水后余额、提现申请、订单/工单/份额/售后/寄卖金额加 **非负 CHECK**; - `worker_wallet_ledgers.event_key` + 触发器 + 唯一索引:工单 + 份额 + 流水类型,防未来奖励重复结算; - 新增只读视图 `v_worker_wallet_duplicate_rewards` 与审计脚本 `scripts/audit-worker-wallet.sql`; - 生产模拟数据发现 **8 组疑似重复 `reward_settlement`,合计 ¥392.40、重复多计约 ¥196.20**;另有 1 组(84886)是寄卖货款与工单奖励,属误报。未自动扣回,需财务确认。 因此,本复核中“L1 钱包是当前最大短板”的结论仍然成立,但已经从“无约束”变成“有 BIGINT、有 CHECK、有未来幂等、有历史审计视图”。