""" 【反向撤销】repair_wechat_income 造成的重复记账。 你当时执行的指令是: python manage.py repair_wechat_income 或: python manage.py repair_wechat_income --date 2026-06-24 --days 7 repair 逻辑:按「当日创建的已支付订单/充值单」逐条 +金额、+笔数、写 platform_income_log、加 szjilu。 本命令用完全相同扫描列表,逐条反向 -金额、-笔数、删日志、减 szjilu。 用法: 1. 预览(不改库): python manage.py undo_repair_wechat_income 2. 确认后撤销(加多少减多少): python manage.py undo_repair_wechat_income --yes 3. 若当时带了参数,必须一致: python manage.py undo_repair_wechat_income --date 2026-06-01 --days 30 --run-date 2026-06-24 --yes (--run-date = 你执行 repair 那天,默认今天) 4. 全部清零(恢复到 0): python manage.py undo_repair_wechat_income --zero-all --yes """ 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 ( undo_repair_wechat_income_period, zero_all_income_statistics, ) class Command(BaseCommand): help = '反向撤销 repair_wechat_income 造成的收支重复记账' def add_arguments(self, parser): parser.add_argument( '--date', type=str, default='', help='与 repair 相同的 --date(扫描区间结束日),默认今天', ) parser.add_argument( '--days', type=int, default=1, help='与 repair 相同的 --days,默认 1', ) parser.add_argument( '--run-date', type=str, default='', help='你执行 repair 的日期(金额被记到哪天的 daily),默认今天', ) parser.add_argument('--yes', action='store_true', help='确认执行') parser.add_argument( '--zero-all', action='store_true', help='收支统计全部清零(日收入、日志、szjilu 收入流水归零)', ) def handle(self, *args, **options): if options['zero_all']: self._zero_all(not options['yes']) return 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) if options['run_date']: inflation_date = date.fromisoformat(options['run_date']) else: inflation_date = date.today() dry_run = not options['yes'] self.stdout.write(self.style.WARNING( '========== 反向撤销 repair_wechat_income ==========\n' f'原 repair 扫描区间: {start_day} ~ {end_day} (--days {days})\n' f'金额被记到日统计日期: {inflation_date} (--run-date)\n' )) if dry_run: self.stdout.write('【预览】未改数据库\n') else: self.stdout.write('【执行】正在反向扣减...\n') r = undo_repair_wechat_income_period( start_day, end_day, inflation_date, dry_run=dry_run, ) self.stdout.write(f' 将撤销条数: {r["undo_count"]} 条') self.stdout.write(f' 将扣减金额: {r["undo_amount"]:.2f} 元') self.stdout.write( f' 执行前 {inflation_date} 日统计: {r["before_daily_on_inflation_date"]:.2f} 元' ) 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: self.stdout.write(self.style.SUCCESS( f'\n执行后 {inflation_date} 日统计: {r.get("after_daily_on_inflation_date", 0):.2f} 元' )) self.stdout.write(self.style.SUCCESS('反向撤销完成,请刷新后台财务页。')) else: self.stdout.write(self.style.WARNING( '\n预览完成。确认后执行(参数与 repair 保持一致):\n' f' python manage.py undo_repair_wechat_income --date {end_day} --days {days} ' f'--run-date {inflation_date} --yes\n' '全部清零:\n' ' python manage.py undo_repair_wechat_income --zero-all --yes' )) def _zero_all(self, dry_run): agg = DailyIncomeStat.objects.aggregate(amt=Sum('total_amount'), cnt=Sum('total_count')) self.stdout.write(self.style.ERROR( '========== 收支统计全部清零 ==========\n' f'当前 daily_income_stat 合计: {float(agg["amt"] or 0):.2f} 元 / {int(agg["cnt"] or 0)} 笔\n' '将删除: platform_income_log 全部、daily_income_stat 全部\n' '将归零: szjilu.TotalIncome / TotalFlow / DailyFlow\n' )) if dry_run: self.stdout.write(self.style.WARNING( '预览。确认清零:\n' ' python manage.py undo_repair_wechat_income --zero-all --yes' )) return zero_all_income_statistics() self.stdout.write(self.style.SUCCESS('已全部清零。请刷新后台。'))