Files
Django/orders/management/commands/batch_approve_xq_merchant_refunds.py
XingQue 6689b7af00 fix: 批量退款处理人ID默认138377,限制chuliid最长11位
batch_20260710 超长导致762单全部失败;与客服手机号字段一致。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 23:21:23 +08:00

182 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
星阙(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.ProcessorIDchuliid最长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('存在失败单,请查日志后单独处理。'))