""" 星阙(xq)俱乐部 · 商家发单 · 退款审核中订单 — 批量同意退款 与客服接口 POST /yonghu/kefusjtk 同一套业务逻辑(见 orders.services.merchant_refund_approve)。 用法(先排查,再执行): # 1. 仅统计,默认要求待审单数 >= 762 python manage.py batch_approve_xq_merchant_refunds # 2. 指定最低单数、只看某商家 python manage.py batch_approve_xq_merchant_refunds --min-count 762 --merchant-nicheng 星阙 # 3. 确认无误后真正执行(建议先备份库) python manage.py batch_approve_xq_merchant_refunds --execute --processor batch_20260710 # 4. 单数不足也强制跑(危险) python manage.py batch_approve_xq_merchant_refunds --execute --force """ from django.core.management.base import BaseCommand, CommandError from jituan.constants import CLUB_ID_DEFAULT from orders.models import Order, RefundRecord from orders.services.merchant_refund_approve import ( MerchantRefundApproveError, approve_merchant_refund_order, normalize_processor_id, ) from users.models import UserShangjia class Command(BaseCommand): help = '批量同意星阙(xq)商家发单退款审核(Status=4 → 5)' def add_arguments(self, parser): parser.add_argument( '--club-id', default=CLUB_ID_DEFAULT, help='俱乐部 ID,默认 xq(星阙电竞)', ) parser.add_argument( '--min-count', type=int, default=762, help='待审单数低于该值则中止(除非 --force),默认 762', ) parser.add_argument( '--merchant-id', default='', help='仅处理指定商家 UserUID(可选)', ) parser.add_argument( '--merchant-nicheng', default='', help='仅处理商家昵称包含该关键字(可选,如 星阙)', ) parser.add_argument( '--processor', default='138377', help='写入 RefundRecord.ProcessorID(chuliid,最长11位)', ) parser.add_argument( '--tuikuan-liyou', default='批量同意退款', help='可选:写入订单退款理由', ) parser.add_argument( '--execute', action='store_true', help='真正执行;默认仅排查统计(dry-run)', ) parser.add_argument( '--force', action='store_true', help='待审单数 < min-count 时也继续(默认中止)', ) parser.add_argument( '--limit', type=int, default=0, help='最多处理 N 单,0 表示不限制(排查模式忽略)', ) def handle(self, *args, **options): club_id = (options['club_id'] or CLUB_ID_DEFAULT).strip() min_count = int(options['min_count'] or 0) merchant_id = (options['merchant_id'] or '').strip() merchant_nicheng = (options['merchant_nicheng'] or '').strip() processor = normalize_processor_id((options['processor'] or '').strip()) tuikuan_liyou = (options['tuikuan_liyou'] or '').strip() do_execute = bool(options['execute']) force = bool(options['force']) limit = int(options['limit'] or 0) pending_refund_ids = RefundRecord.query.filter( ApplyStatus=0, ).values_list('OrderID', flat=True) qs = ( Order.query.filter( ClubID=club_id, Platform=2, Status=4, OrderID__in=pending_refund_ids, ) .select_related('shangjia_kuozhan') .order_by('OrderID') ) if merchant_id: qs = qs.filter(shangjia_kuozhan__MerchantID=merchant_id) if merchant_nicheng: merchant_uids = list( UserShangjia.query.filter( nicheng__icontains=merchant_nicheng, ).values_list('user__UserUID', flat=True) ) if not merchant_uids: raise CommandError(f'未找到昵称包含「{merchant_nicheng}」的商家') qs = qs.filter(shangjia_kuozhan__MerchantID__in=merchant_uids) orders = list(qs) total = len(orders) self.stdout.write('') self.stdout.write('=== 星阙商家退款审核批量排查 ===') self.stdout.write(f'俱乐部 : {club_id}') self.stdout.write(f'筛选条件 : Platform=2(商家发单) Status=4(退款审核中) RefundRecord.ApplyStatus=0') self.stdout.write(f'商家 UID 过滤 : {merchant_id or "(不限)"}') self.stdout.write(f'商家昵称过滤 : {merchant_nicheng or "(不限)"}') self.stdout.write(f'待审订单数 : {total}') self.stdout.write(f'要求最低单数 : {min_count}({"已满足" if total >= min_count else "未满足"})') self.stdout.write(f'模式 : {"执行" if do_execute else "仅排查(dry-run)"}') self.stdout.write('') if total == 0: self.stdout.write(self.style.WARNING('没有符合条件的待审退款单,退出。')) return if total < min_count and not force: raise CommandError( f'待审单数 {total} < {min_count},与预期不符,已中止。' f'若确认仍要处理,请加 --force' ) sample = orders[:5] self.stdout.write('样例订单(前 5 条):') for o in sample: mid = getattr(getattr(o, 'shangjia_kuozhan', None), 'MerchantID', '') self.stdout.write( f' {o.OrderID} 金额={o.Amount} 商家={mid} 打手={o.PlayerID or "-"}', ) if total > 5: self.stdout.write(f' ... 共 {total} 条') self.stdout.write('') if not do_execute: self.stdout.write(self.style.WARNING( '当前为 dry-run,未改任何数据。确认后执行:\n' f' python manage.py batch_approve_xq_merchant_refunds --execute --processor {processor}' )) return if limit > 0: orders = orders[:limit] self.stdout.write(self.style.WARNING(f'--limit={limit},本次只处理前 {len(orders)} 单')) ok_count = 0 fail_count = 0 for order in orders: try: approve_merchant_refund_order( order, processor_id=processor, tuikuan_liyou=tuikuan_liyou, require_status_4=True, ) ok_count += 1 if ok_count <= 10 or ok_count % 50 == 0: self.stdout.write(self.style.SUCCESS(f' OK {order.OrderID}')) except MerchantRefundApproveError as exc: fail_count += 1 self.stdout.write(self.style.ERROR(f' FAIL {order.OrderID}: {exc}')) except Exception as exc: fail_count += 1 self.stdout.write(self.style.ERROR(f' ERR {order.OrderID}: {exc}')) self.stdout.write('') self.stdout.write(self.style.SUCCESS(f'完成:成功 {ok_count},失败 {fail_count},合计 {len(orders)}')) if fail_count: self.stdout.write(self.style.WARNING('存在失败单,请查日志后单独处理。'))