Files
Django/shop/views/shop_refund_views.py
TermiNexus f1c8633345 perf: P1 性能优化批量修复 + kefu 视图拆分
P1 性能优化(避免循环内 N+1 / N 次 DB 查询):
- shop_order_views._batch_images: 移除 ThreadPoolExecutor 掩盖的 N 次查询,改单次批量 + Python 侧分组
- product_query.DashouHuiyuanList: 5N 查询 -> 3 次批量预取 + 本地闭包判断
- roles.GetRolePermissionView / 用户列表: 循环内 RolePermission/Permission/UserRole/Role -> 批量 __in 预取
- guanli / zuzhang 杜次分红 4.2/4.3: 循环内 update_or_create + 单条 delete -> bulk_create + bulk_update + 批量 delete
- admin_config QQ 群配置: 循环内 exists/first/save/create -> 批量预取 + bulk_create + bulk_update
- jituan 服务层多处合并统计查询、批量预取 map
- rank 多处循环 N+1 改批量预取
- backend 多处循环内 count/create 改批量
- config/orders/merchant_ops 多处循环 N+1 改批量预取

其他改动:
- users/views/kefu.py 拆分为 kefu_base/kefu_dashou/kefu_orders/kefu_punishment/kefu_withdraw 5 个文件
- 删除遗留脚本 check_prod_uid.py / create_rbac_tables.sql
2026-07-05 23:17:38 +08:00

661 lines
29 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.
"""shop.views.shop_refund_views - 退款/强制结算/转移大厅/罚款申请视图."""
# ==================== 标准库 ====================
import io
import json
import time
import random
import string
import hmac
import hashlib
import requests
import logging
import traceback
import calendar
import threading
import xmltodict
from decimal import Decimal
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from django.conf import settings
from django.db import models, transaction, IntegrityError
from django.db.models import F, Sum, Count, Q, Prefetch
from gvsdsdk.fluent import db, func, FQ
from django.core.cache import cache
from django.core.paginator import Paginator, EmptyPage
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from django.http import HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken
from utils.oss_utils import upload_to_oss, delete_from_oss, get_oss_client
from utils.money import yuan_to_fen
from ..utils import verify_shop_permission
from shop.utils import verify_shop_permission, update_dianpu_daily_stat
from products.utils import update_shangpin_daily_stat
from orders.utils import update_daily_payout
from backend.utils import update_dashou_daily_by_action
from ..models import YonghuPingzheng, Dianpu, DianpuShouzhiMeiriTongji
from users.models import UserDashou, UserShangjia, UserGuanshi, UserZuzhang
from users.business_models import User
from shop.models import YonghuDianpuBangding, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
from products.models import ShangpinLeixing, ShangpinZhuanqu, Shangpin, Huiyuan
from orders.models import (
Order, PlatformOrderExt, PlayerDeliveryImage, RefundRecord,
OrderPlayerHistory, Penalty
)
logger = logging.getLogger(__name__)
class ShopRefundView(APIView):
"""
店铺订单退款接口
URL: POST /shangdian/tk
权限: JWT认证 + 店铺身份验证(只处理平台订单,我方派单)
请求参数:
userId (必填) : 店铺所属用户的ID
dingdan_id (必填): 订单ID
reason (可选) : 退款理由
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 获取参数
user_id = request.data.get('userId', '').strip()
dingdan_id = request.data.get('dingdan_id', '').strip()
reason = request.data.get('reason', '').strip() # 退款理由
if not user_id or not dingdan_id:
return Response({'code': 400, 'msg': '参数不完整'})
# 2. 店铺身份验证
dianpu, error = verify_shop_permission(request, user_id)
if error:
return error
shop_id = dianpu.id
# 3. 查询订单,必须属于本店铺(通过扩展表关联)
try:
# 通过扩展表确认归属,并 select_related 获取订单主表
pingtai_ext = PlatformOrderExt.query.select_related('Order').get(
ShopID=shop_id,
Order__OrderID=dingdan_id
)
order = pingtai_ext.Order
except PlatformOrderExt.DoesNotExist:
logger.warning(f"订单 {dingdan_id} 不属于店铺 {shop_id} 或不存在")
return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})
# 4. 订单状态校验(只有这些状态可退款)
allowed_statuses = [1, 7, 2, 8, 4]
if order.Status not in allowed_statuses:
logger.warning(f"订单状态不允许退款: {order.Status}")
return Response({'code': 400, 'msg': '订单状态不允许退款'})
# 5. 全部视为平台订单(我方派单),调用微信退款
refund_result = self._call_wechat_refund(order)
if refund_result['code'] != 0:
return Response({'code': 400, 'msg': refund_result['msg']})
# 6. 事务内更新订单、打手、记录、统计
dashou_id = order.PlayerID
try:
with transaction.atomic():
# 更新订单状态为已退款
order.Status = 5
order.AssignedCS = str(shop_id) # 处理人记录为店铺ID
if reason:
order.RefundReason = reason
order.save()
# 更新打手统计(退款量+1状态置为空闲
if dashou_id:
try:
dashou_user = User.query.select_related('DashouProfile').get(UserUID=dashou_id)
dashou_profile = dashou_user.DashouProfile
dashou_profile.tuikuanliang += 1
dashou_profile.zhuangtai = 1
dashou_profile.save()
except (User.DoesNotExist, ObjectDoesNotExist):
logger.warning(f"退款:打手 {dashou_id} 不存在,跳过状态更新")
# 更新退款记录表
self._update_refund_record(order, dashou_id, str(shop_id), reason)
# 打手每日统计(退款动作)
try:
update_dashou_daily_by_action(
yonghuid=dashou_id,
amount=order.PlayerCommission or Decimal('0.00'),
action=3 # 3 表示退款
)
except Exception as e:
logger.error(f"打手每日统计更新失败: {e}")
# 更新平台支出记录
try:
update_daily_payout(order.Amount, getattr(order, 'ClubID', None))
except Exception as e:
logger.error(f"更新每日支出失败: {e}")
# ========== 新增:商品和店铺每日统计 ==========
try:
# 商品每日统计只要有商品ID就执行
if order.ProductID:
# 从扩展表获取已计算好的收益值(下单时已存储)
pingtai_shouyi = Decimal('0.00')
dianpu_shouyi = Decimal('0.00')
if hasattr(order, 'pingtai_kuozhan'):
pingtai_shouyi = order.pingtai_kuozhan.PlatformIncome or Decimal('0.00')
dianpu_shouyi = order.pingtai_kuozhan.ShopIncome or Decimal('0.00')
update_shangpin_daily_stat(
shangpin_id=order.ProductID,
caozuo_leixing=3, # 3 = 退款
dingdan_jiage=order.Amount,
pingtai_shouyi=pingtai_shouyi,
dianpu_zongshouyi=dianpu_shouyi
)
logger.info(f"商品统计更新完成商品ID: {order.shangpin_id}")
# 店铺每日统计仅当存在店铺ID时执行
dianpu_id = None
if hasattr(order, 'pingtai_kuozhan'):
dianpu_id = order.pingtai_kuozhan.dianpu_id
if dianpu_id:
dianpu_shouyi = order.pingtai_kuozhan.ShopIncome or Decimal('0.00')
update_dianpu_daily_stat(
dianpu_id=dianpu_id,
caozuo_leixing=3, # 3 = 退款
dingdan_jiage=order.Amount,
dianpu_shouyi=dianpu_shouyi
)
logger.info(f"店铺统计更新完成店铺ID: {dianpu_id}")
except Exception as e:
logger.error(f"商品/店铺统计更新失败: {e}")
logger.error(traceback.format_exc())
# 统计失败不影响主流程
# =============================================
except Exception as e:
logger.exception("退款事务执行失败")
return Response({'code': 500, 'msg': '退款处理失败,请稍后重试'})
return Response({'code': 200, 'msg': '退款成功'})
# ========== 微信退款 ==========
def _call_wechat_refund(self, order):
"""调用微信支付退款接口"""
appid = getattr(settings, 'WEIXIN_APPID', '')
mch_id = getattr(settings, 'WEIXIN_MCHID', '')
key = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '')
cert_path = getattr(settings, 'WEIXIN_CERT_PATH', '')
key_path = getattr(settings, 'WEIXIN_KEY_PATH', '')
if not all([appid, mch_id, key, cert_path, key_path]):
return {'code': 500, 'msg': '微信支付配置不完整'}
out_refund_no = self._generate_refund_no(order.OrderID)
total_fee = yuan_to_fen(order.Amount or 0)
refund_fee = total_fee
params = {
'appid': appid,
'mch_id': mch_id,
'nonce_str': self._generate_nonce_str(),
'out_refund_no': out_refund_no,
'total_fee': total_fee,
'refund_fee': refund_fee,
'refund_desc': '店铺退款',
}
if order.WechatTransactionID:
params['transaction_id'] = order.WechatTransactionID
else:
params['out_trade_no'] = order.OrderID
# 签名
sorted_keys = sorted(params.keys())
stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys])
stringSignTemp = f"{stringA}&key={key}"
sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
params['sign'] = sign
xml_data = self._dict_to_xml(params)
url = 'https://api.mch.weixin.qq.com/secapi/pay/refund'
try:
response = requests.post(url, data=xml_data, cert=(cert_path, key_path), timeout=10)
result = xmltodict.parse(response.content)['xml']
logger.info(f"微信退款响应: {result}")
except Exception as e:
logger.error(f"微信退款请求异常: {e}", exc_info=True)
return {'code': 500, 'msg': '微信退款请求异常'}
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
return {'code': 0, 'msg': '退款成功'}
else:
err_msg = result.get('err_code_des', result.get('return_msg', '未知错误'))
return {'code': 400, 'msg': err_msg}
# ========== 退款记录 ==========
def _update_refund_record(self, order, dashou_id, chuli_id, reason):
"""更新退款记录表"""
try:
refund_record, created = RefundRecord.query.get_or_create(
OrderID=order.OrderID,
defaults={
'PlayerID': dashou_id or '',
'ApplicantID': '',
'ProcessorID': chuli_id,
'Reason': reason or '店铺退款',
'ApplyStatus': 1,
'Amount': order.Amount or 0,
'PlayerCommission': order.PlayerCommission or 0,
'Description': order.Description or '',
'Remark': '店铺退款',
'Nickname': order.Nickname or '',
}
)
if not created:
refund_record.ApplyStatus = 1
refund_record.ProcessorID = chuli_id
if reason:
refund_record.Reason = reason
refund_record.save()
except Exception as e:
logger.error(f"更新退款记录异常: {e}")
# ========== 工具方法 ==========
def _generate_nonce_str(self):
return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
def _generate_refund_no(self, dingdan_id):
timestamp = str(int(time.time()))
rand = ''.join(random.choices('0123456789', k=4))
return f"{dingdan_id}REF{timestamp}{rand}"
def _dict_to_xml(self, data):
xml = ['<xml>']
for k, v in data.items():
xml.append(f'<{k}>{v}</{k}>')
xml.append('</xml>')
return ''.join(xml)
class ShopForceCompleteView(APIView):
"""
店铺强制结单接口
URL: POST /shangdian/qzjd
权限: JWT认证 + 店铺身份验证
请求参数:
userId (必填) : 店铺所属用户的ID
dingdan_id(必填) : 订单ID
说明:
- 只处理平台订单fadan_pingtai == 1
- 订单必须属于当前店铺
- 订单状态必须为 8结算中才能强制结单
- 默认视为我方派单根据是否有对方订单ID判断是否跨平台
- 本地订单:给打手结算、更新打手余额和统计、更新店铺收益统计
- 跨平台订单:仅通知对方平台,不处理打手
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# ========== 1. 获取参数 ==========
user_id = request.data.get('userId', '').strip()
dingdan_id = request.data.get('dingdan_id', '').strip()
if not user_id or not dingdan_id:
return Response({'code': 400, 'msg': '参数不完整'})
# ========== 2. 店铺身份验证 ==========
dianpu, error = verify_shop_permission(request, user_id)
if error:
return error
shop_id = dianpu.id
# ========== 3. 查询订单并验证归属 ==========
try:
# 通过平台扩展表确认订单属于本店铺
pingtai_ext = PlatformOrderExt.query.select_related('Order').get(
ShopID=shop_id,
Order__OrderID=dingdan_id
)
order = pingtai_ext.Order
except PlatformOrderExt.DoesNotExist:
logger.warning(f"订单 {dingdan_id} 不属于店铺 {shop_id} 或不存在")
return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})
# ========== 4. 校验订单状态必须为 8结算中 ==========
if order.Status != 8:
logger.warning(f"订单 {dingdan_id} 当前状态为 {order.Status},不是结算中")
return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.Status}'})
dashou_id = order.PlayerID
dashou_fencheng = order.PlayerCommission or Decimal('0.00')
jine = order.Amount or Decimal('0.00')
try:
with transaction.atomic():
# 锁定订单行,防止并发
order = Order.objects.select_for_update().get(OrderID=dingdan_id)
if not dashou_id:
return Response({'code': 400, 'msg': '订单无接单打手,无法结算'})
if dashou_fencheng < 0:
return Response({'code': 400, 'msg': '打手分成金额无效'})
# ---------- 更新打手余额和统计 ----------
try:
dashou_user = User.query.select_related('DashouProfile').get(UserUID=dashou_id)
dashou_profile = dashou_user.DashouProfile
dashou_profile.chengjiaozongliang += 1
dashou_profile.yue += dashou_fencheng
dashou_profile.zonge += dashou_fencheng
dashou_profile.jinrishouyi += dashou_fencheng
dashou_profile.jinyueshouyi += dashou_fencheng
dashou_profile.zhuangtai = 1 # 空闲
dashou_profile.save()
except (User.DoesNotExist, ObjectDoesNotExist):
logger.warning(f"本地订单结算:打手 {dashou_id} 不存在,跳过打手更新")
# ---------- 更新订单状态 ----------
order.Status = 3
order.AssignedCS = str(shop_id)
order.save()
# ---------- 打手每日统计(成交) ----------
try:
update_dashou_daily_by_action(
yonghuid=dashou_id,
amount=dashou_fencheng,
action=2 # 2 表示成交(结算)
)
except Exception as e:
logger.error(f"打手每日统计更新失败: {e}")
# ---------- 店铺收益统计(结算) ----------
if pingtai_ext.ShopIncome:
try:
# 商品每日统计只要有商品ID就执行
if order.ProductID:
# 从扩展表获取已计算好的收益值(下单时已存储)
pingtai_shouyi = Decimal('0.00')
dianpu_shouyi = Decimal('0.00')
if hasattr(order, 'pingtai_kuozhan'):
pingtai_shouyi = order.pingtai_kuozhan.PlatformIncome or Decimal('0.00')
dianpu_shouyi = order.pingtai_kuozhan.ShopIncome or Decimal('0.00')
update_shangpin_daily_stat(
shangpin_id=order.ProductID,
caozuo_leixing=2, # 3 = 退款
dingdan_jiage=order.Amount,
pingtai_shouyi=pingtai_shouyi,
dianpu_zongshouyi=dianpu_shouyi
)
logger.info(f"商品统计更新完成商品ID: {order.shangpin_id}")
update_dianpu_daily_stat(
dianpu_id=shop_id,
caozuo_leixing=2, # 结算操作
dingdan_jiage=jine,
dianpu_shouyi=pingtai_ext.ShopIncome,
)
except Exception as e:
logger.error(f"店铺每日统计更新失败: {e}")
# ========== 5. 记录操作日志 ==========
logger.info(f"强制结单成功,订单 {dingdan_id},店铺 {shop_id}")
return Response({'code': 200, 'msg': '强制结单成功'})
except Order.DoesNotExist:
logger.warning(f"订单不存在: {dingdan_id}")
return Response({'code': 404, 'msg': '订单不存在'})
except Exception as e:
logger.error(f"强制结单接口异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'})
# ================================================================
# shangdian/views/transfer_hall_view.py
# 店铺转移大厅接口 —— 严格二改,不添加任何原接口没有的逻辑
# ================================================================
# ================================================================
# shangdian/views/transfer_hall_view.py
# 店铺转移大厅接口 —— 添加本地打手退款统计
# ================================================================
class ShopTransferHallView(APIView):
"""
店铺转移大厅接口
URL: POST /shangdian/zydt
权限: JWT认证 + 店铺身份验证
请求参数:
userId (必填) : 店铺所属用户的ID
dingdan_id(必填) : 订单ID
说明:
- 只处理平台订单,我方派单
- 允许转移的状态2,8,4
- 决定是否处理本地打手有对方订单ID或俱乐部ID跨平台则不处理本地打手只通知对方
没有对方信息则处理本地打手(释放+退款统计)
- 转移后清空对方平台信息,订单重回可接单状态
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# ---------- 1. 获取参数 ----------
user_id = request.data.get('userId', '').strip()
dingdan_id = request.data.get('dingdan_id', '').strip()
if not user_id or not dingdan_id:
return Response({'code': 400, 'msg': '参数不完整'})
# ---------- 2. 店铺身份验证 ----------
dianpu, error = verify_shop_permission(request, user_id)
if error:
return error
shop_id = dianpu.id
# ---------- 3. 验证订单归属 ----------
try:
pingtai_ext = PlatformOrderExt.query.select_related('Order').get(
ShopID=shop_id,
Order__OrderID=dingdan_id
)
order = pingtai_ext.Order
except PlatformOrderExt.DoesNotExist:
logger.warning(f"订单 {dingdan_id} 不属于店铺 {shop_id} 或不存在")
return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})
# ---------- 4. 状态校验 ----------
if order.Status not in [2, 8, 4]:
return Response({'code': 400, 'msg': f'订单状态不允许转移大厅,当前状态: {order.Status}'})
# ---------- 5. 提取关键字段 ----------
current_dashou_id = order.PlayerID
zhiding_id = order.zhiding_id
dashou_fencheng = order.PlayerCommission or 0 # 用于退款统计
try:
with transaction.atomic():
# 锁定订单行
order = Order.objects.select_for_update().get(OrderID=dingdan_id)
# ---------- 6. 记录打手历史(原接口保留逻辑) ----------
if current_dashou_id:
last_history = OrderPlayerHistory.query.filter(
dingdan_id=dingdan_id
).order_by('-times').first()
next_times = (last_history.times + 1) if last_history else 1
OrderPlayerHistory.query.create(
dingdan_id=dingdan_id,
dashou_id=current_dashou_id,
times=next_times,
operator=str(shop_id)
)
# ---------- 7. 释放本地打手 + 退款统计 ----------
if current_dashou_id:
try:
dashou_profile = UserDashou.query.get(user__UserUID=current_dashou_id)
dashou_profile.zhuangtai = 1 # 设为空闲
dashou_profile.save()
except ObjectDoesNotExist:
logger.warning(f"打手 {current_dashou_id} 不存在,跳过状态更新")
else:
# 打手释放成功后,记录每日退款统计(转移视为退款)
try:
update_dashou_daily_by_action(
yonghuid=current_dashou_id,
amount=dashou_fencheng,
action=3 # 3 表示退款
)
logger.info(f"打手 {current_dashou_id} 转移大厅退款统计成功")
except Exception as e:
logger.error(f"打手每日统计失败: {e}")
# ---------- 8. 重置订单 ----------
order.jiedan_dashou_id = None
order.clkf = str(shop_id)
# 根据是否有指定打手决定状态
order.zhuangtai = 7 if zhiding_id else 1
order.save()
logger.info(f"转移大厅成功,订单 {dingdan_id}")
return Response({'code': 200, 'msg': '转移大厅成功'})
except Order.DoesNotExist:
return Response({'code': 404, 'msg': '订单不存在'})
except Exception as e:
logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'})
# ================================================================
# shangdian/views/fine_apply_view.py
# 店铺罚款申请接口 —— 二改自 FineApplyView客服罚款
# ================================================================
class ShopFineApplyView(APIView):
"""
店铺罚款申请接口
URL: POST /shangdian/fksq
权限: JWT认证 + 店铺身份验证
请求参数:
userId (必填) : 店铺所属用户的ID用于身份验证
dingdan_id (必填) : 订单ID
reason (必填) : 罚款原因
amount (必填) : 罚款金额(元)
yingxiang_qiangdan (可选) : 是否影响抢单1=是0=否默认1
说明:
- 仅处理平台订单,需属于当前店铺
- 本地/对方派单处理逻辑与客服接口一致:
无对方订单ID 或 对方派单 → 本地生成罚单
有对方订单ID且我方派单 → 跨平台通知对方本地也记录标记对方俱乐部ID
- 申请人店铺ID身份码固定为4店铺管理员
- 被处罚人接单打手身份码固定为1
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# ---------- 1. 提取参数 ----------
user_id = request.data.get('userId', '').strip() # 店铺用户ID
dingdan_id = request.data.get('dingdan_id', '').strip()
chufaliyou = request.data.get('reason', '').strip() # 罚款原因对应前端的reason
fakuanjine = request.data.get('amount', 0) # 罚款金额对应前端的amount
yingxiang_qiangdan = request.data.get('yingxiang_qiangdan', 1) # 影响抢单
if not all([user_id, dingdan_id, chufaliyou, fakuanjine]):
return Response({'code': 400, 'msg': '参数不完整'})
try:
# ---------- 2. 店铺身份验证 ----------
dianpu, error = verify_shop_permission(request, user_id)
if error:
return error
shop_id = dianpu.id # 店铺ID作为罚款申请人
# ---------- 3. 验证订单归属 ----------
try:
pingtai_ext = PlatformOrderExt.query.select_related('Order').get(
ShopID=shop_id,
Order__OrderID=dingdan_id
)
order = pingtai_ext.Order
except PlatformOrderExt.DoesNotExist:
logger.warning(f"罚款申请:订单 {dingdan_id} 不属于店铺 {shop_id}")
return Response({'code': 404, 'msg': '订单不存在或不属于本店铺'})
# ---------- 4. 获取打手ID ----------
dashou_id = order.PlayerID
if not dashou_id:
return Response({'code': 400, 'msg': '该订单无接单打手,无法罚款'})
# ---------- 5. 重复罚款检查 ----------
if Penalty.query.filter(PenalizedUserID=dashou_id, RelatedOrderID=dingdan_id).exists():
return Response({'code': 400, 'msg': '该打手已被罚款过'})
# ---------- 6. 创建罚单 ----------
from jituan.services.club_penalty import resolve_penalty_club_id
with transaction.atomic():
penalty = Penalty.query.create(
PenalizedUserID=dashou_id,
ApplicantID=str(shop_id), # 申请人记为店铺ID
Identity=1, # 被处罚人身份1 打手
Reason=chufaliyou,
FineAmount=fakuanjine,
RelatedOrderID=dingdan_id,
Status=1, # 待缴纳
AffectsGrabbing=yingxiang_qiangdan,
ApplicantIdentity=4, # 申请人身份4 店铺管理员
ClubID=resolve_penalty_club_id(
order=order,
request=request,
user=request.user,
applicant_id=str(shop_id),
),
)
from users.fadan_fenhong_utils import lock_penalty_bonus
lock_penalty_bonus(penalty)
logger.info(f"本地罚款成功: 订单 {dingdan_id}, 打手 {dashou_id}, 金额 {fakuanjine}")
return Response({'code': 200, 'msg': '罚款已生成'})
except Exception as e:
logger.error(f"店铺罚款申请异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '服务器内部错误'})