""" 恢复被 repair_wechat_income 搞乱的收入统计。 repair 会对已入账订单重复加钱(200 变 900)。 本命令:删当日错误日志 → 按真实微信支付重建 → 扣回 szjilu 多记部分。 用法: 1. 先预览:python manage.py recover_wechat_income_stats 2. 看「重建后」金额是否对(应接近真实收入,如 200 左右) 3. 确认后:python manage.py recover_wechat_income_stats --yes 4. 若多日错乱:python manage.py recover_wechat_income_stats --days 7 --yes 5. 最后全量对齐日统计表:python manage.py recover_wechat_income_stats --yes --rebuild-all-daily """ from datetime import date, timedelta from django.core.management.base import BaseCommand from django.db.models import Sum from config.models import DailyIncomeStat from jituan.services.szjilu_accounting import ( rebuild_all_daily_income_from_logs, recover_income_stats_for_date, ) class Command(BaseCommand): help = '恢复 repair_wechat_income 重复记账(先预览,--yes 执行)' def add_arguments(self, parser): parser.add_argument('--date', type=str, default='', help='日期 YYYY-MM-DD,默认今天') parser.add_argument('--days', type=int, default=1, help='向前恢复几天') parser.add_argument('--yes', action='store_true', help='确认执行') parser.add_argument( '--rebuild-all-daily', action='store_true', help='恢复后按 platform_income_log 全量重写 daily_income_stat(纠正累计总收入)', ) def handle(self, *args, **options): if options['date']: end_day = date.fromisoformat(options['date']) else: end_day = date.today() days = max(1, int(options['days'] or 1)) start_day = end_day - timedelta(days=days - 1) dry_run = not options['yes'] if dry_run: self.stdout.write(self.style.WARNING( '【预览】未改数据库。下面「重建后」应是正确金额。\n' )) else: self.stdout.write(self.style.WARNING('【执行】正在恢复...\n')) for i in range(days): d = start_day + timedelta(days=i) r = recover_income_stats_for_date(d, dry_run=dry_run) self.stdout.write(f'=== {r["date"]} ===') self.stdout.write( f' 错乱前日统计: {r["before_daily_amount"]:.2f} 元 / {r["before_daily_count"]} 笔' ) self.stdout.write( f' 当日幂等日志: {r["before_log_amount"]:.2f} 元 / {r["before_log_count"]} 笔' ) self.stdout.write(self.style.SUCCESS( f' 重建后(正确值): {r["after_amount"]:.2f} 元 / {r["after_count"]} 笔' )) if r['items']: self.stdout.write(' 明细:') for ref, cid, amt, kind in r['items']: self.stdout.write(f' {ref} {amt}元 club={cid} ({kind})') if not dry_run and options['rebuild_all_daily']: n = rebuild_all_daily_income_from_logs() self.stdout.write(self.style.SUCCESS(f'已按日志全量重写 daily_income_stat: {n} 行')) if not dry_run: agg = DailyIncomeStat.objects.filter(date=end_day).aggregate( amt=Sum('total_amount'), cnt=Sum('total_count'), ) self.stdout.write(self.style.SUCCESS( f'\n数据库今日日统计现为: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔' )) self.stdout.write(self.style.SUCCESS('恢复完成,请刷新后台财务页。')) else: self.stdout.write(self.style.WARNING( '\n预览完成。重建后金额对的话执行:\n' ' python manage.py recover_wechat_income_stats --yes\n' '累计总收入仍不对再加:\n' ' python manage.py recover_wechat_income_stats --yes --rebuild-all-daily' ))