Files
order_site/docs/财务代码审计复核-2026-10-27.md
T
yml2213 9d5af359fa feat(finance): 完成财务控制、L1 钱包硬化与迁移收敛
- P0/P1:影子总账追加调整、关账/退款/资金/积分幂等、成本版本追溯、对账订单项维度
- 迁移收敛:原 103–114 合并为 103_finance_foundation、104_finance_controls、105_work_order_consignment
- 数据:L1 钱包 BIGINT/CHECK/奖励幂等;重复奖励审计口径修正与人工核对清单
- 报表:销售毛利缺失成本收入提示,旧收支页明确现金流口径
- 文档:同步迁移基线、合并评估、代码复核与上线说明
2026-10-27 16:08:16 +08:00

10 KiB
Raw Blame History

对《财务系统深度审计》的代码级复核(2026-10-27)

复核对象:用户给出的财务系统审计结论。 复核方式:逐条对照当前 dev 代码、迁移 001–105、本机数据库与生产 dump 模拟升级。 总评:架构判断(双轨期、旧报表口径混编、历史人工成本缺失)方向正确;但有 4 处结论需要纠正,2 处需要降级为“风险假设”,1 处已经是旧代码状态。


一、需要纠正的结论

1. “打手报酬 + 提现会被重复扣减 100%” —— 不成立

旧报表的净现金流函数实际是:

// 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 远大于业务规模

证据:

而且 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、有未来幂等、有历史审计视图”。