Files
Django/backend/views/penalties.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

736 lines
30 KiB
Python
Raw Permalink 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.
import hmac
import threading
import traceback
import uuid
import hashlib
import xmltodict
import time
import logging
import requests
import os
import secrets
import random
import string
from calendar import monthrange
from decimal import Decimal
from datetime import date, datetime, timedelta
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.db import models, transaction, IntegrityError
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
from gvsdsdk.fluent import db, func, FQ
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
from django.contrib.auth.hashers import make_password
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
# 工具类导入
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from backend.utils import (
update_dashou_daily_by_action,
update_shangjia_daily
)
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
from jituan.services.catalog_scope import block_non_group_catalog_scope
from jituan.services.club_penalty import (
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
)
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
from shop.utils import update_dianpu_daily_stat
from products.utils import update_shangpin_daily_stat
from orders.notice_tasks import dingdan_guangbo
from ..utils import (
verify_kefu_permission,
PERMISSION_TO_SHENFEN,
SHENFEN_PROFILE_MAP,
has_fadan_view_permission,
has_merchant_order_permission,
check_fadan_permission,
pick_order_id,
pick_penalty_create_params,
write_xiugai_log,
XIUGAI_LEIXING_DASHOU,
XIUGAI_LEIXING_GUANSHI,
XIUGAI_LEIXING_SHANGJIA,
XIUGAI_LEIXING_ZUZHANG,
XIUGAI_LEIXING_LABEL,
)
# models 集中导入
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
## backend
from backend.models import WithdrawalDailyStats
## users
from users.models import (
UserGuanshi, UserBoss, UserZuzhang,
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
)
from users.business_models import User
## orders
from orders.models import (
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
OrderPlayerHistory, Order, RefundRecord,
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
)
## shop
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
## products
from products.models import (
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
)
## config
from config.models import (
WithdrawConfig, ShangjiaLianjie, AccountPermission,
TixianQuotaDefault,
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
)
## rank
from rank.models import (
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
)
# 序列化器
from ..serializers import PopupPageSerializer
# 全局常量、日志对象
logger = logging.getLogger('houtai')
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
class FaKuanTongJiView(APIView):
"""
罚款统计接口
POST /houtai/htglyhqcfsltj
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '参数不完整'})
try:
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if not has_fadan_view_permission(permissions, username):
return Response({'code': 403, 'msg': '无权限查看罚款统计'})
from utils.penalty_status import penalty_platform_audit_pending_q
pending_q = penalty_platform_audit_pending_q()
stats = filter_penalty_qs(Penalty.query.all(), request).aggregate(
total=Count('id'),
daijiaona=Count('id', filter=Q(Status=1)),
shensuzhong=Count('id', filter=Q(Status=3)),
yijiaona=Count('id', filter=Q(Status=2)),
yibohui=Count('id', filter=Q(Status=4)),
pingtai_shenhe=Count('id', filter=pending_q),
)
data = {k: v or 0 for k, v in stats.items()}
from jituan.services.club_user_access import list_response_meta
return Response({'code': 0, 'msg': '成功', 'data': {**data, **list_response_meta(request)}})
except Exception as e:
logger.error(f"罚款统计异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})
class FaKuanLieBiaoView(APIView):
"""
罚款列表接口
POST /houtai/hthqfklb
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '参数不完整'})
try:
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if not has_fadan_view_permission(permissions, username):
return Response({'code': 403, 'msg': '无权限查看罚款列表'})
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 20))
if page_size > 100:
page_size = 100
qs = filter_penalty_qs(Penalty.query.all(), request)
# 状态筛选5=平台审核中,仅 zhuangtai=5
from utils.penalty_status import penalty_platform_audit_pending_q
zhuangtai_raw = request.data.get('zhuangtai')
if zhuangtai_raw is not None and str(zhuangtai_raw).strip() != '':
try:
zt = int(zhuangtai_raw)
if zt == 5:
qs = qs.filter(penalty_platform_audit_pending_q())
elif zt in (1, 2, 3, 4):
qs = qs.filter(Status=zt)
except (ValueError, TypeError):
pass
# 被处罚人ID
PenalizedUserID = request.data.get('PenalizedUserID', '').strip()
if PenalizedUserID:
qs = qs.filter(PenalizedUserID=PenalizedUserID)
# 被处罚人身份
beichufa_shenfen = request.data.get('beichufa_shenfen')
if beichufa_shenfen is not None and int(beichufa_shenfen) in [1, 2, 3, 4]:
qs = qs.filter(Identity=int(beichufa_shenfen))
# 模糊搜索订单ID、被罚人ID、申请人ID
sousuo = request.data.get('sousuo', '').strip()
if sousuo:
qs = qs.filter(
Q(RelatedOrderID__icontains=sousuo) |
Q(PenalizedUserID__icontains=sousuo) |
Q(ApplicantID__icontains=sousuo)
)
qs = qs.order_by('-CreateTime')
paginator = Paginator(qs, page_size)
try:
page_obj = paginator.page(page)
except Exception:
return Response({
'code': 0, 'msg': '成功',
'data': {'list': [], 'total': 0, 'page': page, 'page_size': page_size}
})
records = list(page_obj)
penalty_ids = [r.id for r in records]
# 批量获取图片并分组
zhengju_map = {}
shensu_map = {}
if penalty_ids:
tupians = PenaltyAppealImage.query.filter(
Penalty_id__in=penalty_ids
).values('Penalty_id', 'ImageURL', 'Purpose')
for t in tupians:
fid = t['Penalty_id']
url = t['ImageURL']
if t['Purpose'] == 1:
zhengju_map.setdefault(fid, []).append(url)
else:
shensu_map.setdefault(fid, []).append(url)
from utils.penalty_status import penalty_display_status, is_penalty_platform_audit_pending
# 组装列表数据
results = []
for r in records:
results.append({
'id': r.id,
'beichufa_id': r.PenalizedUserID,
'shenfen': r.Identity,
'shenfen_text': dict(Penalty._meta.get_field('Identity').choices).get(r.Identity, ''),
'shenqing_chufa': r.ApplicantID or '',
'shenqingren_shenfen': r.ApplicantIdentity,
'chufaliyou': r.Reason or '',
'fakuanjine': str(r.FineAmount),
'guanliandingdan_id': r.RelatedOrderID or '',
'zhuangtai': r.Status,
'zhuangtai_text': penalty_display_status(r),
'platform_audit_pending': is_penalty_platform_audit_pending(r),
'yingxiang_qiangdan': r.AffectsGrabbing,
'shensuliyou': r.AppealReason or '',
'bohuiliyou': r.RejectReason or '',
'chulizhe': r.ProcessorID or '',
'chulizhe_shenfen': r.ProcessorIdentity,
'zhengju_tupian': zhengju_map.get(r.id, []),
'shensu_tupian': shensu_map.get(r.id, []),
'creat_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
'create_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
'UpdateTime': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '',
})
from jituan.services.club_user_access import list_response_meta
return Response({
'code': 0, 'msg': '成功',
'data': {
'list': results,
'total': paginator.count,
'page': page,
'page_size': page_size,
**list_response_meta(request),
}
})
except Exception as e:
logger.error(f"罚款列表异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})
class FaKuanChuLiView(APIView):
"""
处理罚款申诉(同意/拒绝)
POST /houtai/glyclfk
"""
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
def post(self, request):
try:
username = request.data.get('username', '').strip()
penalty_id = request.data.get('penalty_id') or request.data.get('fadan_id')
action = request.data.get('action', '').strip()
chuli_liyou = request.data.get('chuli_liyou', '').strip()
if not all([username, penalty_id, action]):
return Response({'code': 400, 'msg': '参数不完整'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
with transaction.atomic():
try:
penalty = Penalty.objects.select_for_update().get(id=penalty_id)
except Penalty.DoesNotExist:
return Response({'code': 404, 'msg': '罚单不存在'})
deny = forbid_penalty_out_of_scope(request, penalty)
if deny:
return deny
if penalty.Status != 3:
return Response({'code': 400, 'msg': '当前状态不可处理'})
if not check_fadan_permission(permissions, penalty.Identity):
return Response({'code': 403, 'msg': '无权限处理该类罚单'})
# 处理上传的图片
uploaded_urls = []
files = request.FILES.getlist('tupian') or []
if not files:
single = request.FILES.get('tupian')
if single:
files = [single]
for file in files:
valid, msg = validate_image(file)
if not valid:
return Response({'code': 400, 'msg': msg})
ext = file.name.split('.')[-1] if '.' in file.name else 'jpg'
file_name = f"chufa_{penalty_id}_{int(time.time() * 1000)}.{ext}"
file_path = f"chufatupian/kechufatupian/{file_name}"
url = upload_to_oss(file, file_path)
if not url:
return Response({'code': 500, 'msg': '图片上传失败'})
uploaded_urls.append(file_path)
# 更新状态
if action == 'agree': # 同意申诉 → 驳回处罚
penalty.Status = 4
penalty.RejectReason = chuli_liyou
elif action == 'reject': # 拒绝申诉 → 回到待缴纳
penalty.Status = 1
penalty.RejectReason = chuli_liyou
else:
return Response({'code': 400, 'msg': '无效的操作类型'})
# 记录处理者(使用 request.user 的 phone
penalty.ProcessorID = request.user.Phone
penalty.ProcessorIdentity = 2 # 这里假设处理者是售后身份,可根据实际业务获取
penalty.save()
# 保存图片证据图片用途1
for relative_url in uploaded_urls:
PenaltyAppealImage.query.create(
Penalty_id=penalty_id,
PenalizedUserID=penalty.PenalizedUserID,
ImageURL=relative_url,
Purpose=1
)
logger.info(f"罚款处理成功: {penalty_id}, 操作: {action}")
return Response({'code': 0, 'msg': '处理成功'})
except Exception as e:
logger.error(f"罚款处理异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})
class FaKuanPingTaiShenHeView(APIView):
"""
平台审核商家提交的罚款(同意/驳回)
POST /houtai/glyptshfk
action: platform_approve | platform_reject
"""
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
def post(self, request):
try:
from utils.penalty_status import (
PENALTY_PLATFORM_AUDIT, PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_REJECTED,
)
username = request.data.get('username', '').strip()
penalty_id = request.data.get('penalty_id') or request.data.get('fadan_id')
action = request.data.get('action', '').strip()
chuli_liyou = request.data.get('chuli_liyou', '').strip()
if not all([username, penalty_id, action]):
return Response({'code': 400, 'msg': '参数不完整'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
with transaction.atomic():
try:
penalty = Penalty.objects.select_for_update().get(id=penalty_id)
except Penalty.DoesNotExist:
return Response({'code': 404, 'msg': '罚单不存在'})
deny = forbid_penalty_out_of_scope(request, penalty)
if deny:
return deny
if penalty.Status != PENALTY_PLATFORM_AUDIT:
return Response({'code': 400, 'msg': '当前状态不可平台审核'})
if not check_fadan_permission(permissions, penalty.Identity):
return Response({'code': 403, 'msg': '无权限处理该类罚单'})
if action == 'platform_approve':
if penalty.AppealReason:
penalty.Status = PENALTY_APPEALING
else:
penalty.Status = PENALTY_PENDING_PAY
penalty.ApproveReason = chuli_liyou or penalty.ApproveReason or ''
elif action == 'platform_reject':
penalty.Status = PENALTY_REJECTED
penalty.RejectReason = chuli_liyou or '平台审核驳回'
else:
return Response({'code': 400, 'msg': '无效的操作类型'})
penalty.ProcessorID = request.user.Phone
penalty.ProcessorIdentity = 3
penalty.save()
logger.info(f"罚款平台审核成功: {penalty_id}, 操作: {action}")
return Response({'code': 0, 'msg': '平台审核完成'})
except Exception as e:
logger.error(f"罚款平台审核异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})
class FaKuanChuangJianView(APIView):
"""
管理员创建罚款
POST /houtai/htfksc
"""
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
def post(self, request):
try:
username = request.data.get('username', '').strip()
PenalizedUserID, Reason, FineAmount, AffectsGrabbing = pick_penalty_create_params(request.data)
shenfen = request.data.get('shenfen')
if not all([username, PenalizedUserID, shenfen, Reason, FineAmount is not None]):
return Response({'code': 400, 'msg': '参数不完整'})
try:
shenfen = int(shenfen)
FineAmount = float(FineAmount)
except (ValueError, TypeError):
return Response({'code': 400, 'msg': '参数格式错误'})
if shenfen not in [1, 2, 3, 4]:
return Response({'code': 400, 'msg': '无效的被罚人身份'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if not check_fadan_permission(permissions, shenfen):
return Response({'code': 403, 'msg': '无权限创建该类罚单'})
# 验证用户是否存在对应的扩展表
try:
user = User.query.get(UserUID=PenalizedUserID)
profile_attr = SHENFEN_PROFILE_MAP.get(shenfen)
if not profile_attr or not hasattr(user, profile_attr):
return Response({'code': 400, 'msg': '该用户不是所选身份'})
except User.DoesNotExist:
return Response({'code': 404, 'msg': '用户不存在'})
# 处理图片上传
uploaded_urls = []
files = request.FILES.getlist('tupian') or []
if not files:
single = request.FILES.get('tupian')
if single:
files = [single]
for file in files:
valid, msg = validate_image(file)
if not valid:
return Response({'code': 400, 'msg': msg})
ext = file.name.split('.')[-1] if '.' in file.name else 'jpg'
file_name = f"chufa_{int(time.time() * 1000)}.{ext}"
file_path = f"chufatupian/kechufatupian/{file_name}"
url = upload_to_oss(file, file_path)
if not url:
return Response({'code': 500, 'msg': '图片上传失败'})
uploaded_urls.append(file_path)
with transaction.atomic():
penalty = Penalty.query.create(
PenalizedUserID=PenalizedUserID,
Identity=shenfen,
ApplicantID=request.user.Phone, # 申请人(客服)手机号
ApplicantIdentity=2, # 售后身份
Reason=Reason,
FineAmount=FineAmount,
AffectsGrabbing=AffectsGrabbing,
Status=1, # 待缴纳
ClubID=resolve_penalty_club_id(
penalized_user_id=PenalizedUserID,
request=request,
user=request.user,
),
)
PenaltyAppealImage.objects.bulk_create([
PenaltyAppealImage(
Penalty=penalty,
PenalizedUserID=PenalizedUserID,
ImageURL=relative_url,
Purpose=1,
)
for relative_url in uploaded_urls
])
from users.fadan_fenhong_utils import lock_penalty_bonus
lock_penalty_bonus(penalty)
logger.info(f"罚款创建成功: {penalty.id}")
return Response({'code': 0, 'msg': '罚款已创建', 'data': {'id': penalty.id}})
except Exception as e:
logger.error(f"创建罚款异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})
class FineApplyView(APIView):
"""
客服罚款申请接口(基于订单对打手发起罚款)
POST /houtai/kffkdssq
权限: JWT认证 + 客服权限验证
请求参数:
username (必填) : 客服用户名(手机号)
OrderID (必填) : 订单ID
Reason (必填) : 罚款原因
FineAmount (必填) : 罚款金额(元)
AffectsGrabbing (可选) : 是否影响抢单1=是0=否默认1
"""
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
def post(self, request):
try:
username = request.data.get('username', '').strip()
OrderID = pick_order_id(request.data)
_, Reason, FineAmount, AffectsGrabbing = pick_penalty_create_params(request.data)
if not all([username, OrderID, Reason, FineAmount is not None]):
return Response({'code': 400, 'msg': '参数不完整'})
try:
FineAmount = float(FineAmount)
except (ValueError, TypeError):
return Response({'code': 400, 'msg': '罚款金额格式错误'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
# 验证订单存在
try:
order = Order.query.get(OrderID=OrderID)
except Order.DoesNotExist:
return Response({'code': 404, 'msg': '订单不存在'})
# 获取打手ID
dashou_id = order.PlayerID
if not dashou_id:
return Response({'code': 400, 'msg': '该订单无接单打手,无法罚款'})
# 重复罚款检查
if Penalty.query.filter(PenalizedUserID=dashou_id, RelatedOrderID=OrderID).exists():
return Response({'code': 400, 'msg': '该打手已被罚款过'})
# 检查权限(打手身份 shenfen=1
if not check_fadan_permission(permissions, 1):
return Response({'code': 403, 'msg': '无权限处罚打手'})
# 处理图片上传
uploaded_urls = []
files = request.FILES.getlist('tupian') or []
if not files:
single = request.FILES.get('tupian')
if single:
files = [single]
for file in files:
valid, msg = validate_image(file)
if not valid:
return Response({'code': 400, 'msg': msg})
ext = file.name.split('.')[-1] if '.' in file.name else 'jpg'
file_name = f"chufa_{int(time.time() * 1000)}.{ext}"
file_path = f"chufatupian/kechufatupian/{file_name}"
url = upload_to_oss(file, file_path)
if not url:
return Response({'code': 500, 'msg': '图片上传失败'})
uploaded_urls.append(file_path)
# 创建罚单
with transaction.atomic():
penalty = Penalty.query.create(
PenalizedUserID=dashou_id,
ApplicantID=request.user.Phone,
Identity=1, # 被处罚人身份1 打手
Reason=Reason,
FineAmount=FineAmount,
RelatedOrderID=OrderID,
Status=1, # 待缴纳
AffectsGrabbing=AffectsGrabbing,
ApplicantIdentity=1, # 申请人身份1 客服
ClubID=resolve_penalty_club_id(
order=order,
request=request,
user=request.user,
applicant_id=getattr(request.user, 'UserUID', None),
),
)
for relative_url in uploaded_urls:
PenaltyAppealImage.query.create(
Penalty=penalty,
PenalizedUserID=dashou_id,
ImageURL=relative_url,
Purpose=1
)
from users.fadan_fenhong_utils import lock_penalty_bonus
lock_penalty_bonus(penalty)
logger.info(f"客服罚款申请成功: 订单 {OrderID}, 打手 {dashou_id}, 金额 {FineAmount}")
return Response({'code': 0, 'msg': '罚款已生成', 'data': {'id': penalty.id}})
except Exception as e:
logger.error(f"客服罚款申请异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})
class PunishDashouView(APIView):
"""
客服处罚打手接口(扣除积分)
POST /houtai/kpcf
权限: JWT认证 + 客服权限验证
请求参数:
username (必填) : 客服用户名(手机号)
OrderID (必填) : 订单ID
reason (可选) : 处罚原因
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
try:
username = request.data.get('username', '').strip()
OrderID = request.data.get('OrderID', '').strip()
reason = request.data.get('reason', '').strip()
if not all([username, OrderID]):
return Response({'code': 400, 'msg': '参数不完整'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
# 检查权限(打手身份 shenfen=1
if not check_fadan_permission(permissions, 1):
return Response({'code': 403, 'msg': '无权限处罚打手'})
# 查询订单
try:
order = Order.query.get(OrderID=OrderID)
except Order.DoesNotExist:
return Response({'code': 404, 'msg': '订单不存在'})
# 获取打手ID
dashou_id = order.PlayerID
if not dashou_id:
return Response({'code': 400, 'msg': '订单未接单,无法处罚打手'})
# 查询打手
try:
dashou_user = User.query.get(UserUID=dashou_id)
dashou = dashou_user.DashouProfile
except User.DoesNotExist:
return Response({'code': 404, 'msg': '打手用户不存在'})
except ObjectDoesNotExist:
return Response({'code': 400, 'msg': '该用户不是打手'})
# 创建处罚记录扣除积分5分
jifen = 5
with transaction.atomic():
penalty_record = PenaltyRecord.query.create(
PlayerID=dashou_id,
ApplicantID=username,
PenaltyReason=reason or '',
ApplyStatus=0,
DeductedPoints=jifen,
OrderID=OrderID,
ClubID=resolve_penalty_record_club_id(
order_id=OrderID,
player_id=dashou_id,
request=request,
user=request.user,
applicant_id=getattr(request.user, 'UserUID', None) or username,
),
)
# 扣除打手积分
dashou.jifen = F('jifen') - jifen
dashou.save(update_fields=['jifen'])
logger.info(f"客服处罚打手成功: 打手 {dashou_id}, 订单 {OrderID}, 扣除积分 {jifen}")
return Response({'code': 0, 'msg': '处罚成功', 'data': {'penalty_record_id': penalty_record.id}})
except Exception as e:
logger.error(f"客服处罚打手异常: {traceback.format_exc()}")
return Response({'code': 500, 'msg': '系统繁忙'})