From 84665e0c5dd64dcd2629a92a69defbe9df0800fa Mon Sep 17 00:00:00 2001 From: XingQue Date: Wed, 24 Jun 2026 21:29:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20undo=5Frepair=5Fwechat=5Fincome=20?= =?UTF-8?q?=E5=8F=8D=E5=90=91=E6=92=A4=E9=94=80=E9=87=8D=E5=A4=8D=E8=AE=B0?= =?UTF-8?q?=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/fix_income_month.py | 114 +++++++ .../commands/undo_repair_wechat_income.py | 133 ++++++++ jituan/services/szjilu_accounting.py | 303 +++++++++++++++++- orders/utils.py | 23 ++ orders/views.py | 4 +- 5 files changed, 558 insertions(+), 19 deletions(-) create mode 100644 config/management/commands/fix_income_month.py create mode 100644 config/management/commands/undo_repair_wechat_income.py diff --git a/config/management/commands/fix_income_month.py b/config/management/commands/fix_income_month.py new file mode 100644 index 0000000..a8f16e3 --- /dev/null +++ b/config/management/commands/fix_income_month.py @@ -0,0 +1,114 @@ +""" +修复 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('请刷新后台财务页核对。') diff --git a/config/management/commands/undo_repair_wechat_income.py b/config/management/commands/undo_repair_wechat_income.py new file mode 100644 index 0000000..89f7c84 --- /dev/null +++ b/config/management/commands/undo_repair_wechat_income.py @@ -0,0 +1,133 @@ +""" +【反向撤销】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('已全部清零。请刷新后台。')) diff --git a/jituan/services/szjilu_accounting.py b/jituan/services/szjilu_accounting.py index a39ab2a..830c2cd 100644 --- a/jituan/services/szjilu_accounting.py +++ b/jituan/services/szjilu_accounting.py @@ -58,6 +58,150 @@ def apply_szjilu_income(amount, club_id=None): szjilu.save() +def subtract_szjilu_income(amount, club_id=None, subtract_daily_flow=True): + """扣减 szjilu 收入(repair 反向操作用)。""" + jine = Decimal(str(amount)) + with transaction.atomic(): + szjilu = _get_szjilu_for_update(club_id) + szjilu.TotalIncome = max(_ZERO, (szjilu.TotalIncome or _ZERO) - jine) + szjilu.TotalFlow = max(_ZERO, (szjilu.TotalFlow or _ZERO) - jine) + if subtract_daily_flow: + szjilu.DailyFlow = max(_ZERO, (szjilu.DailyFlow or _ZERO) - jine) + szjilu.save() + + +# repair_wechat_income 原始扫描逻辑(用于反向撤销,一字不改) +_REPAIR_ORDER_STATUSES = [1, 2, 3, 4, 5, 6, 7, 8] + + +def collect_repair_wechat_income_scan(stat_date): + """ + 与已禁用的 repair_wechat_income 命令完全相同的扫描列表。 + 返回 [(biz_ref, club_id, amount, kind), ...] + """ + from datetime import datetime, timedelta + + from django.db.models import Q + + from orders.models import Order + from products.models import Czjilu + + day_start = datetime.combine(stat_date, datetime.min.time()) + day_end = day_start + timedelta(days=1) + items = [] + + orders = Order.query.filter( + CreateTime__gte=day_start, + CreateTime__lt=day_end, + Status__in=_REPAIR_ORDER_STATUSES, + ) + for od in orders: + cid = normalize_szjilu_club_id(getattr(od, 'ClubID', None)) + items.append((f'order:{od.OrderID}', cid, Decimal(str(od.Amount or 0)), 'order')) + + cz_orders = Czjilu.query.filter( + CreateTime__gte=day_start, + CreateTime__lt=day_end, + zhuangtai=3, + ).filter( + Q(leixing__in=[1, 2, 3, 4]) | Q(leixing__isnull=False), + ) + for cz in cz_orders: + cid = normalize_szjilu_club_id(getattr(cz, 'club_id', None)) + items.append((f'cz:{cz.dingdan_id}', cid, Decimal(str(cz.jine or 0)), f'cz_{cz.leixing}')) + + return items + + +def undo_repair_wechat_income_one(amount, club_id, biz_ref, inflation_date): + """ + 反向执行 repair 的 _repair_one: + - 扣减当日 daily_income_stat(repair 当时 update_daily_income 用的是执行日=inflation_date) + - 扣减 szjilu + - 删除 platform_income_log(repair 若写过) + 若 repair 曾走「删日志再记」分支,会多记一次;用 had_log 判断扣两次。 + """ + from orders.utils import subtract_daily_income + + ref = (biz_ref or '').strip() + jine = Decimal(str(amount)) + cid = normalize_szjilu_club_id(club_id) + + had_log = income_log_exists(ref) + subtract_daily_income(jine, cid, inflation_date) + subtract_szjilu_income(jine, cid, subtract_daily_flow=(inflation_date == date.today())) + + if had_log: + subtract_daily_income(jine, cid, inflation_date) + subtract_szjilu_income(jine, cid, subtract_daily_flow=(inflation_date == date.today())) + + try: + PlatformIncomeLog.objects.filter(biz_ref=ref).delete() + except _PLATFORM_LOG_ERRORS: + pass + + +def undo_repair_wechat_income_period(start_date, end_date, inflation_date, dry_run=True): + """ + 撤销 repair_wechat_income 在 [start_date, end_date] 扫描范围内造成的入账。 + inflation_date = 你执行 repair 那天(repair 把所有金额都记到执行日的 daily 里)。 + """ + from datetime import timedelta + + from django.db.models import Sum + + all_items = [] + d = start_date + while d <= end_date: + all_items.extend(collect_repair_wechat_income_scan(d)) + d += timedelta(days=1) + + total_amt = sum((x[2] for x in all_items), _ZERO) + before_daily = DailyIncomeStat.objects.filter(date=inflation_date).aggregate( + amt=Sum('total_amount'), cnt=Sum('total_count'), + ) + + result = { + 'start': str(start_date), + 'end': str(end_date), + 'inflation_date': str(inflation_date), + 'undo_count': len(all_items), + 'undo_amount': float(total_amt), + 'before_daily_on_inflation_date': float(before_daily['amt'] or 0), + 'items': all_items, + 'dry_run': dry_run, + } + + if dry_run: + return result + + with transaction.atomic(): + for ref, cid, jine, _kind in all_items: + undo_repair_wechat_income_one(jine, cid, ref, inflation_date) + + after_daily = DailyIncomeStat.objects.filter(date=inflation_date).aggregate( + amt=Sum('total_amount'), cnt=Sum('total_count'), + ) + result['after_daily_on_inflation_date'] = float(after_daily['amt'] or 0) + return result + + +def zero_all_income_statistics(): + """核弹:收支统计全部清零(日收入表、幂等日志、szjilu 收入/流水)。""" + with transaction.atomic(): + try: + PlatformIncomeLog.objects.all().delete() + except _PLATFORM_LOG_ERRORS: + pass + DailyIncomeStat.objects.all().delete() + for szjilu in Szjilu.objects.select_for_update().all(): + szjilu.TotalIncome = _ZERO + szjilu.TotalFlow = _ZERO + szjilu.DailyFlow = _ZERO + szjilu.save(update_fields=['TotalIncome', 'TotalFlow', 'DailyFlow', 'UpdateTime']) + return True + + def _apply_income_stats(amount, club_id): """写入 daily_income_stat + szjilu(仅在新 platform_income_log 落库后调用)。""" from orders.utils import update_daily_income @@ -206,15 +350,11 @@ _CZ_WECHAT_LEIXING = [1, 2, 3, 4, 5] def collect_canonical_wechat_income_for_date(stat_date, existing_refs=None): """ - 收集当日真实微信收入(每笔 biz_ref 全局唯一,不重复计已入账订单)。 - - 订单:当日创建且已付款,或当日完成微信支付(有微信交易号) - - 充值:当日创建且 zhuangtai=3 - - 不含退款单(Status=5) - """ + 收集当日真实微信收入(严格:仅统计当日下单且已支付成功的单据,每笔唯一)。 + 不含退款;不按 UpdateTime 计老单(repair 虚高的主要原因之一)。 + """ from datetime import datetime, timedelta - from django.db.models import Q - from orders.models import Order from products.models import Czjilu @@ -235,17 +375,11 @@ def collect_canonical_wechat_income_for_date(stat_date, existing_refs=None): seen[ref] = True items.append((ref, cid, jine, kind)) - order_qs = Order.query.filter( + for od in Order.query.filter( + CreateTime__gte=day_start, + CreateTime__lt=day_end, Status__in=_ORDER_INCOME_STATUSES, - ).filter( - Q(CreateTime__gte=day_start, CreateTime__lt=day_end) - | Q( - UpdateTime__gte=day_start, - UpdateTime__lt=day_end, - WechatTransactionID__isnull=False, - ), - ) - for od in order_qs: + ): txn = (getattr(od, 'WechatTransactionID', None) or '').strip() if not txn: continue @@ -262,6 +396,141 @@ def collect_canonical_wechat_income_for_date(stat_date, existing_refs=None): return items +def dedupe_platform_income_logs(): + """删除 biz_ref 重复的幂等日志(保留最早一条)。""" + try: + seen = {} + dup_ids = [] + for log in PlatformIncomeLog.objects.order_by('id'): + key = (log.biz_ref or '').strip() + if not key: + dup_ids.append(log.id) + continue + if key in seen: + dup_ids.append(log.id) + else: + seen[key] = log.id + if dup_ids: + PlatformIncomeLog.objects.filter(id__in=dup_ids).delete() + return len(dup_ids) + except _PLATFORM_LOG_ERRORS: + return 0 + + +def fix_income_period(start_date, end_date, dry_run=True): + """ + 核弹级修复:删掉区间内错误日志与日统计,按严格规则重建,再对齐 szjilu。 + daily_income_stat 只信 platform_income_log,不再累加脏数据。 + """ + from datetime import datetime, time as dt_time, timedelta + + from django.db.models import Sum + from django.utils import timezone + + before_daily = DailyIncomeStat.objects.filter( + date__gte=start_date, date__lte=end_date, + ).aggregate(amt=Sum('total_amount'), cnt=Sum('total_count')) + + before_log = _ZERO + before_log_cnt = 0 + try: + before_log_cnt = PlatformIncomeLog.objects.filter( + CreateTime__date__gte=start_date, + CreateTime__date__lte=end_date, + ).count() + agg = PlatformIncomeLog.objects.filter( + CreateTime__date__gte=start_date, + CreateTime__date__lte=end_date, + ).aggregate(t=Sum('amount')) + before_log = agg['t'] or _ZERO + except _PLATFORM_LOG_ERRORS: + pass + + all_items = _collect_items_for_period(start_date, end_date) + rebuild_amt = sum((x[3] for x in all_items), _ZERO) + result = { + 'start': str(start_date), + 'end': str(end_date), + 'before_daily_amount': float(before_daily['amt'] or 0), + 'before_daily_count': int(before_daily['cnt'] or 0), + 'before_log_amount': float(before_log), + 'before_log_count': before_log_cnt, + 'after_amount': float(rebuild_amt), + 'after_count': len(all_items), + 'items': all_items, + 'dry_run': dry_run, + } + + if dry_run: + return result + + execute_fix_income_period(start_date, end_date) + return result + + +def _collect_items_for_period(start_date, end_date): + from datetime import timedelta + + items = [] + d = start_date + while d <= end_date: + for ref, cid, jine, kind in collect_canonical_wechat_income_for_date(d, existing_refs=set()): + items.append((d, ref, cid, jine, kind)) + d += timedelta(days=1) + return items + + +def execute_fix_income_period(start_date, end_date): + from datetime import datetime, time as dt_time, timedelta + + from django.utils import timezone + + dedupe_platform_income_logs() + items = _collect_items_for_period(start_date, end_date) + + with transaction.atomic(): + PlatformIncomeLog.objects.filter( + CreateTime__date__gte=start_date, + CreateTime__date__lte=end_date, + ).delete() + DailyIncomeStat.objects.filter( + date__gte=start_date, date__lte=end_date, + ).delete() + + by_day_club = {} + for stat_date, ref, cid, jine, _kind in items: + pay_ts = timezone.make_aware(datetime.combine(stat_date, dt_time(12, 0, 0))) + log = PlatformIncomeLog.objects.create(biz_ref=ref, club_id=cid, amount=jine) + PlatformIncomeLog.objects.filter(pk=log.pk).update(CreateTime=pay_ts) + key = (stat_date, cid) + if key not in by_day_club: + by_day_club[key] = {'amt': _ZERO, 'cnt': 0} + by_day_club[key]['amt'] += jine + by_day_club[key]['cnt'] += 1 + + for (stat_date, cid), agg in by_day_club.items(): + DailyIncomeStat.objects.create( + date=stat_date, + club_id=cid, + year=stat_date.year, + month=stat_date.month, + day=stat_date.day, + total_amount=agg['amt'], + total_count=agg['cnt'], + ) + + sync_szjilu_income_from_logs() + return items + + +def align_daily_income_from_logs_only(): + """不重扫订单:仅按现有 platform_income_log 重写 daily_income_stat + szjilu(日志对、日统计错时用)。""" + dedupe_platform_income_logs() + n = rebuild_all_daily_income_from_logs() + sync_szjilu_income_from_logs() + return n + + def _existing_refs_outside_date(stat_date): """该日之外已入账的 biz_ref(删当日错误日志后仍保留的)。""" try: diff --git a/orders/utils.py b/orders/utils.py index 222709e..f966e7e 100644 --- a/orders/utils.py +++ b/orders/utils.py @@ -65,6 +65,29 @@ def update_daily_income(amount, club_id=None): ) +def subtract_daily_income(amount, club_id=None, stat_date=None): + """扣减指定日期的收入统计(repair 反向操作用)。""" + from jituan.constants import CLUB_ID_DEFAULT + + cid = club_id or CLUB_ID_DEFAULT + target = stat_date or date.today() + + with transaction.atomic(): + stat = DailyIncomeStat.objects.select_for_update().filter( + date=target, club_id=cid, + ).first() + if not stat: + return + jine = Decimal(str(amount)) + zero = Decimal('0.00') + new_amt = max(zero, Decimal(str(stat.total_amount or 0)) - jine) + new_cnt = max(0, int(stat.total_count or 0) - 1) + stat.total_amount = new_amt + stat.total_count = new_cnt + stat.save(update_fields=['total_amount', 'total_count']) + logger.info(f"每日收入扣减[{cid}]: {target}, -{amount}元") + + def update_daily_payout(amount, club_id=None): """ 原子更新当日出款统计(金额累加,笔数加1)。 diff --git a/orders/views.py b/orders/views.py index 633604b..87bf48d 100644 --- a/orders/views.py +++ b/orders/views.py @@ -211,7 +211,7 @@ class WechatPayNotifyView(APIView): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( - dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}', + dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{dingdan.OrderID}', ) except Exception as e: logger.error(f'订单补记收支失败 {out_trade_no}: {e}', exc_info=True) @@ -318,7 +318,7 @@ class WechatPayNotifyView(APIView): try: from jituan.services.szjilu_accounting import repair_wechat_income_if_missing repair_wechat_income_if_missing( - dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}', + dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{dingdan.OrderID}', ) except Exception as e: logger.error(f'订单收支统计失败 {out_trade_no}: {e}', exc_info=True)