""" 修复 repair_wechat_income 搞乱的收入(200 显示成 600+)。 两种模式(先预览,再加 --yes): A. 默认:重扫本月真实微信支付,删掉脏数据后重建 python manage.py fix_income_month python manage.py fix_income_month --yes B. 若日志金额已对、只是日统计表脏了: python manage.py fix_income_month --logs-only --yes """ from calendar import monthrange from datetime import date from django.core.management.base import BaseCommand from django.db.models import Sum from config.models import DailyIncomeStat, PlatformIncomeLog from jituan.services.szjilu_accounting import ( align_daily_income_from_logs_only, dedupe_platform_income_logs, fix_income_period, ) class Command(BaseCommand): help = '修复本月收入统计错乱(repair 重复记账)' def add_arguments(self, parser): parser.add_argument('--year', type=int, default=0, help='年,默认今年') parser.add_argument('--month', type=int, default=0, help='月,默认本月') parser.add_argument('--today-only', action='store_true', help='只修今天') parser.add_argument('--logs-only', action='store_true', help='只按现有日志重写日统计,不重扫订单') parser.add_argument('--yes', action='store_true', help='确认执行') def handle(self, *args, **options): today = date.today() year = options['year'] or today.year month = options['month'] or today.month dry_run = not options['yes'] if options['today_only']: start = end = today label = f'今日 {today}' else: _, last = monthrange(year, month) start = date(year, month, 1) end = date(year, month, last) label = f'{year}-{month:02d} 全月' if options['logs_only']: self._run_logs_only(dry_run, label) return self.stdout.write(self.style.WARNING(f'【{label}】收入修复\n')) if dry_run: self.stdout.write('预览模式(未改库)\n') r = fix_income_period(start, end, dry_run=dry_run) 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 row in r['items']: if len(row) == 5: d, ref, cid, amt, kind = row self.stdout.write(f' {d} {ref} {amt}元 club={cid} ({kind})') else: ref, cid, amt, kind = row self.stdout.write(f' {ref} {amt}元 club={cid} ({kind})') if not dry_run: self._print_today_total(today) else: self.stdout.write(self.style.WARNING( '\n重建后金额对 → python manage.py fix_income_month --yes\n' '只修今天 → python manage.py fix_income_month --today-only --yes\n' '日志已对、日统计脏 → python manage.py fix_income_month --logs-only --yes' )) def _run_logs_only(self, dry_run, label): try: log_sum = PlatformIncomeLog.objects.aggregate(t=Sum('amount'))['t'] or 0 log_cnt = PlatformIncomeLog.objects.count() daily_sum = DailyIncomeStat.objects.aggregate(t=Sum('total_amount'))['t'] or 0 except Exception as exc: self.stderr.write(f'读取失败: {exc}') return self.stdout.write(f'【{label}】仅按日志对齐日统计\n') self.stdout.write(f' platform_income_log 合计: {float(log_sum):.2f} 元 / {log_cnt} 笔') self.stdout.write(f' daily_income_stat 现合计: {float(daily_sum):.2f} 元') if dry_run: self.stdout.write(self.style.WARNING('\n预览。确认后: python manage.py fix_income_month --logs-only --yes')) return n = align_daily_income_from_logs_only() self.stdout.write(self.style.SUCCESS(f'已重写 daily_income_stat {n} 行,并重算 szjilu')) self._print_today_total(date.today()) def _print_today_total(self, today): agg = DailyIncomeStat.objects.filter(date=today).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('请刷新后台财务页核对。')