- P0/P1:影子总账追加调整、关账/退款/资金/积分幂等、成本版本追溯、对账订单项维度 - 迁移收敛:原 103–114 合并为 103_finance_foundation、104_finance_controls、105_work_order_consignment - 数据:L1 钱包 BIGINT/CHECK/奖励幂等;重复奖励审计口径修正与人工核对清单 - 报表:销售毛利缺失成本收入提示,旧收支页明确现金流口径 - 文档:同步迁移基线、合并评估、代码复核与上线说明
10 KiB
对《财务系统深度审计》的代码级复核(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 |
远大于业务规模 |
证据:
- 001_init.sql:108
orders.total_amount INTEGER - 004_worker_order_platform.sql:31-53 钱包/流水均为
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 的实际动作是:
- 把
order_refunds.status置为voided; - 重算
orders.pay_status(本轮 P0 修复); - 重算销售事实;
- 总账按事实差异追加
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_amountfrozen_deposit_amountpending_unfreeze_amountfrozen_withdraw_amounttotal_credited_amounttotal_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、有未来幂等、有历史审计视图”。