feat: 星阙商家退款审核批量同意脚本

新增 batch_approve_xq_merchant_refunds 管理命令,复用 kefusjtk 同意退款逻辑;
默认要求待审>=762单且 dry-run,--execute 才真正处理。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-10 23:18:56 +08:00
parent 48eca8e5d0
commit 73f0ce36f2
2 changed files with 309 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
"""
星阙(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,
)
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='batch_script',
help='写入 RefundRecord.ProcessorID / Order.AssignedCS',
)
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 = (options['processor'] or 'batch_script').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('存在失败单,请查日志后单独处理。'))