"""一次性回填:给历史账户补开户赠送的 genesis 流水(只补凭证,不改任何余额)。 对每个 account,若"首条流水之前的隐含余额">0 且尚无 trial_grant 流水,补一条 RECHARGE, created_at 设为开户时间(早于所有流水),balance_after=该隐含开户额度。幂等可重跑。 跑法:cd backend && DJANGO_SETTINGS_MODULE=airshelf.settings.development .venv/bin/python ../qa/backfill_genesis_ledger.py """ import os, sys from decimal import Decimal sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend")) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development") import django # noqa: E402 django.setup() from django.db import transaction # noqa: E402 from apps.billing.models import CreditAccount, CreditLedger # noqa: E402 Z = Decimal("0") fixed = 0 for acct in CreditAccount.objects.select_related("team").all(): led = CreditLedger.objects.filter(team=acct.team) if led.filter(metadata__kind="trial_grant").exists(): continue # 已有 genesis first = led.order_by("created_at", "id").first() if first is None: # 纯开户无任何流水:开户额度即 balance opening = acct.balance genesis_time = acct.created_at else: delta = first.amount if first.ledger_type in ("recharge", "refund", "adjustment") else (-first.amount if first.ledger_type == "charge" else Z) opening = first.balance_after - delta genesis_time = acct.created_at if acct.created_at < first.created_at else first.created_at if opening <= Z: continue with transaction.atomic(): g = CreditLedger.objects.create( team=acct.team, user=acct.team.owner, ledger_type=CreditLedger.Type.RECHARGE, amount=opening, balance_after=opening, reason="新用户试用额度赠送(历史回填)", metadata={"kind": "trial_grant", "backfilled": True}, ) # created 字段由 auto_now_add 控制,需手动回拨到开户时刻,保证它排在所有流水之前 CreditLedger.objects.filter(id=g.id).update(created_at=genesis_time) fixed += 1 print(f" + {acct.team} 补 genesis ¥{opening} @ {genesis_time:%Y-%m-%d %H:%M}") print(f"\n回填完成:{fixed} 个账户补齐 genesis 流水(余额未变动)")