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
1959 lines
85 KiB
Python
1959 lines
85 KiB
Python
"""orders.views.admin - auto-generated by split script."""
|
||
# ==================== 标准库 ====================
|
||
import hmac
|
||
import os
|
||
import json
|
||
import time
|
||
import random
|
||
import string
|
||
import traceback
|
||
import threading
|
||
import requests
|
||
import hashlib
|
||
import xml.etree.ElementTree as ET
|
||
import xmltodict
|
||
import logging
|
||
from decimal import Decimal
|
||
|
||
from django.conf import settings
|
||
from django.db import models, transaction
|
||
from gvsdsdk.fluent import db, func, FQ
|
||
from django.db.models import F, Q, OuterRef, Subquery
|
||
from django.core.cache import cache
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from django.http import HttpResponse
|
||
from django.utils import timezone
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import permissions, status
|
||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||
from rest_framework.parsers import JSONParser
|
||
from rest_framework.throttling import AnonRateThrottle
|
||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||
|
||
from tencentcloud.common import credential
|
||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||
from tencentcloud.sts.v20180813 import sts_client, models
|
||
|
||
from utils.weixin_broadcast import WeixinBroadcastSender
|
||
from utils.money import yuan_to_fen
|
||
from utils.chat_utils import (
|
||
_send_group_message, _subscribe_users_to_group, establish_order_chat,
|
||
prepare_order_group_chat, resolve_pair_group_id,
|
||
parse_pair_from_group_id, resolve_pair_from_order, check_pair_chat_permission,
|
||
query_pair_orders, order_to_chat_dict,
|
||
)
|
||
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
||
|
||
from orders.utils import (
|
||
calc_shangjia_order_fencheng,
|
||
update_daily_payout,
|
||
settle_shangjia_order_guanshi_fenhong
|
||
)
|
||
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
|
||
from products.utils import update_shangpin_daily_stat
|
||
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime
|
||
from rank.services import record_dashou_biaoxian
|
||
from rank.utils import check_dashou_biaoqian_required
|
||
|
||
from orders.notice_tasks import dingdan_guangbo
|
||
|
||
from ..models import (
|
||
Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage,
|
||
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyAppealImage, PenaltyBonus, PenaltyBonusRate,
|
||
CommissionRate, PlayerRating, RefundRecord,
|
||
)
|
||
from jituan.services.club_config import get_commission_rate, get_commission_rate_object
|
||
from jituan.services.club_context import resolve_club_id_from_request
|
||
from jituan.services.club_user import get_payment_openid
|
||
from jituan.services.club_penalty import resolve_penalty_club_id, resolve_penalty_record_club_id
|
||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||
from users.fadan_fenhong_utils import lock_penalty_bonus
|
||
from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai
|
||
from users.models import UserDashou, UserShangjia, UserBoss
|
||
from users.business_models import User
|
||
from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao
|
||
from config.models import (
|
||
ShangjiaLianjie
|
||
)
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
ZHIDING_HF_TEXT = {1: '想接', 2: '不想接', 3: '等会接'}
|
||
|
||
class AdGetDingdanXiangQing(APIView):
|
||
"""
|
||
后台订单详情获取接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddxq
|
||
参数: {"zhanghao": "管理员手机号", "dingdan_id": "订单ID"}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
获取订单详情数据
|
||
返回: 订单主表数据 + 扩展表数据 + 打手图片列表
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证 (IsAuthenticated已处理)
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
|
||
# 查询订单主表 - 使用select_related预加载关联表
|
||
dingdan_obj = Order.query.select_related(
|
||
'pingtai_kuozhan', 'shangjia_kuozhan'
|
||
).get(OrderID=dingdan_id)
|
||
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 构建返回数据 - 主表字段
|
||
response_data = {
|
||
# 基础信息字段
|
||
'dingdan_id': dingdan_obj.OrderID or '',
|
||
'jieshao': dingdan_obj.Description or '',
|
||
'zhuangtai': dingdan_obj.Status or 0,
|
||
'leixing': dingdan_obj.ProductTypeID or 0, # 前端需要 leixing,对应 leixing_id
|
||
'fadanpingtai': dingdan_obj.Platform or 0, # 前端需要 fadanpingtai,对应 fadan_pingtai
|
||
'jiage': float(dingdan_obj.Amount) if dingdan_obj.Amount else 0.00, # 前端需要 jiage,对应 jine
|
||
'tupian': dingdan_obj.ImageURL or '',
|
||
'beizhu': dingdan_obj.Remark or '',
|
||
|
||
# 服务信息字段
|
||
'jiedan_dashou_id': dingdan_obj.PlayerID or '',
|
||
'dashou_fencheng': float(dingdan_obj.PlayerCommission) if dingdan_obj.PlayerCommission else 0.00,
|
||
'zhiding_id': dingdan_obj.AssignedID or '',
|
||
'dashou_liuyan': dingdan_obj.PlayerRemark or '',
|
||
|
||
# 时间字段
|
||
'chuangjianshijian': fmt_datetime(dingdan_obj.CreateTime),
|
||
'create_time': fmt_datetime(dingdan_obj.CreateTime),
|
||
'genggaishijian': fmt_datetime(dingdan_obj.UpdateTime),
|
||
'update_time': fmt_datetime(dingdan_obj.UpdateTime),
|
||
}
|
||
|
||
# 8. 根据发单平台查询扩展表数据
|
||
fadan_pingtai = dingdan_obj.Platform
|
||
|
||
if fadan_pingtai == 1:
|
||
# 平台发单 - 查询平台订单扩展表
|
||
try:
|
||
# 使用反向关联 pingtai_kuozhan
|
||
pingtai_kuozhan = dingdan_obj.pingtai_kuozhan
|
||
|
||
response_data.update({
|
||
'laoban_id': pingtai_kuozhan.BossID or '',
|
||
'nicheng': dingdan_obj.Nickname or '', # 这个字段在订单主表
|
||
'laoban_pingjia': pingtai_kuozhan.BossReview or '',
|
||
})
|
||
except PlatformOrderExt.DoesNotExist:
|
||
# 扩展表不存在,返回空值
|
||
response_data.update({
|
||
'laoban_id': '',
|
||
'nicheng': dingdan_obj.Nickname or '',
|
||
'laoban_pingjia': '',
|
||
})
|
||
|
||
elif fadan_pingtai == 2:
|
||
# 商家发单 - 查询商家订单扩展表
|
||
try:
|
||
# 使用反向关联 shangjia_kuozhan
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
|
||
response_data.update({
|
||
'shangjia_id': shangjia_kuozhan.MerchantID or '',
|
||
'shangjia_nicheng': shangjia_kuozhan.MerchantNickname or '',
|
||
'shangjia_pingjia': shangjia_kuozhan.MerchantReview or '',
|
||
'cfliyou': shangjia_kuozhan.PenaltyReason or '', # 商家处罚理由
|
||
'bhliyou': shangjia_kuozhan.RejectReason or '', # 驳回理由
|
||
})
|
||
except MerchantOrderExt.DoesNotExist:
|
||
# 扩展表不存在,返回空值
|
||
response_data.update({
|
||
'shangjia_id': '',
|
||
'shangjia_nicheng': '',
|
||
'shangjia_pingjia': '',
|
||
'cfliyou': '',
|
||
'bhliyou': '',
|
||
})
|
||
|
||
# 9. 查询打手提交的图片
|
||
try:
|
||
# 查询该订单的所有打手提交图片
|
||
dashou_tupian_list = PlayerDeliveryImage.query.filter(
|
||
OrderID=dingdan_id
|
||
).values_list('ImageURL', flat=True)
|
||
|
||
# 转换为列表,过滤空值
|
||
dashou_images = [img for img in dashou_tupian_list if img]
|
||
response_data['dashou_images'] = dashou_images
|
||
except Exception as e:
|
||
logger.warning(f"查询打手图片失败: {str(e)}")
|
||
response_data['dashou_images'] = []
|
||
|
||
# 🔥 新增:查询退款记录表中的商家退款理由
|
||
try:
|
||
# 导入退款记录模型
|
||
# 查询该订单的退款记录(一个订单可能有多次退款申请,我们取最新的)
|
||
tuikuan_record = RefundRecord.query.filter(
|
||
OrderID=dingdan_id
|
||
).order_by('-CreateTime').first() # 按创建时间倒序,取最新的
|
||
|
||
if tuikuan_record and tuikuan_record.Reason:
|
||
# 如果有退款记录且理由不为空,添加到返回数据
|
||
response_data['tuikuan_liyou'] = tuikuan_record.Reason
|
||
else:
|
||
# 没有找到退款记录或理由为空
|
||
response_data['tuikuan_liyou'] = ''
|
||
except RefundRecord.DoesNotExist:
|
||
# 退款记录表不存在该订单的记录
|
||
response_data['tuikuan_liyou'] = ''
|
||
except Exception as e:
|
||
# 查询过程中出现其他异常
|
||
logger.warning(f"查询退款记录失败: {str(e)}")
|
||
response_data['tuikuan_liyou'] = ''
|
||
|
||
# 10. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '获取成功',
|
||
'data': response_data
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取订单详情异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
# 获取日志器
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AdGengHuanDaShou(APIView):
|
||
"""
|
||
后台更换打手接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddgh
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"new_dashou_id": "新打手ID"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
更换订单的打手
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 验证订单存在且状态为进行中(2)或结算中(8)
|
||
3. 验证新打手ID存在且是打手身份
|
||
4. 验证新打手状态正常(Status=1且zhanghaoStatus=1)
|
||
5. 根据订单的抢单要求类型进行验证:
|
||
- 会员抢单(1):验证打手有对应会员且未过期
|
||
- 押金抢单(2):验证打手押金≥订单押金
|
||
6. 更新新打手状态和统计数据
|
||
7. 更新原打手状态和统计数据
|
||
8. 更新订单信息
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
new_dashou_id = request.data.get('new_dashou_id', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not new_dashou_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
dingdan_obj = Order.query.get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 验证订单状态是否为进行中(2)或结算中(8)
|
||
if dingdan_obj.Status not in [2, 8]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许更换打手', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 8. 验证新打手ID是否存在,并且是打手身份
|
||
try:
|
||
new_dashou_user = User.query.select_related('DashouProfile').get(
|
||
UserUID=new_dashou_id,
|
||
#user_type='PlayerID'
|
||
)
|
||
except User.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '打手不存在或不是打手身份', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 9. 获取新打手扩展表
|
||
try:
|
||
new_dashou_profile = new_dashou_user.DashouProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 404, 'message': '打手信息不完整', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 10. 验证新打手状态:Status=1且zhanghaoStatus=1
|
||
if new_dashou_profile.zhuangtai != 1 or new_dashou_profile.zhanghaozhuangtai != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '打手状态不允许接单', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 11. 根据订单的抢单要求类型进行验证
|
||
yaoqiuleixing = dingdan_obj.GrabRequirement
|
||
|
||
if yaoqiuleixing == 1: # 会员抢单
|
||
# 获取订单的会员ID
|
||
huiyuan_id = dingdan_obj.MembershipID
|
||
if not huiyuan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '订单会员ID为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 查询会员购买记录
|
||
try:
|
||
huiyuan_goumai = Huiyuangoumai.query.get(
|
||
huiyuan_id=huiyuan_id,
|
||
yonghu_id=new_dashou_id # 注意:yonghu_id存储的是用户ID(yonghuid)
|
||
)
|
||
except Huiyuangoumai.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'message': '打手未购买该订单要求的会员', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 检查会员是否过期
|
||
# ✅ 可以调用!因为jiance_shifou_daoqi()是Huiyuangoumai模型的实例方法
|
||
is_expired = huiyuan_goumai.jiance_shifou_daoqi()
|
||
if is_expired or huiyuan_goumai.huiyuan_zhuangtai != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '打手的会员已过期', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
elif yaoqiuleixing == 2: # 押金抢单
|
||
# 获取订单的押金(yongjin字段)
|
||
order_yajin = dingdan_obj.CommissionReq
|
||
if order_yajin is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单押金为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 验证打手押金是否大于等于订单押金
|
||
if new_dashou_profile.yajin < order_yajin:
|
||
return Response(
|
||
{'code': 400, 'message': '打手押金不足', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
else:
|
||
# 未知的抢单要求类型
|
||
return Response(
|
||
{'code': 400, 'message': '订单抢单要求类型错误', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 12. 所有验证通过,开始更换打手(使用事务保证一致性)
|
||
with transaction.atomic():
|
||
# 12.1 更新新打手扩展表
|
||
new_dashou_profile.zhuangtai = 0 # 接单中
|
||
new_dashou_profile.jiedanzongliang += 1
|
||
new_dashou_profile.jinrijiedan += 1
|
||
new_dashou_profile.jinyuejiedan += 1
|
||
new_dashou_profile.save()
|
||
|
||
# 12.2 如果原订单有接单打手,则更新原打手状态
|
||
old_dashou_id = dingdan_obj.PlayerID
|
||
if old_dashou_id:
|
||
try:
|
||
# 查询原打手主表
|
||
old_dashou_user = User.query.select_related('DashouProfile').get(
|
||
UserUID=old_dashou_id,
|
||
#user_type='PlayerID'
|
||
)
|
||
|
||
# 获取原打手扩展表
|
||
old_dashou_profile = old_dashou_user.DashouProfile
|
||
|
||
# 更新原打手状态
|
||
old_dashou_profile.zhuangtai = 1 # 空闲
|
||
old_dashou_profile.tuikuanliang += 1 # 退款订单总量+1
|
||
old_dashou_profile.save()
|
||
|
||
logger.info(f"成功更新原打手{old_dashou_id}状态")
|
||
except User.DoesNotExist:
|
||
# 原打手不存在,继续执行不影响更换
|
||
logger.warning(f"原打手{old_dashou_id}不存在,跳过更新")
|
||
except AttributeError:
|
||
# 原打手扩展表不存在,继续执行不影响更换
|
||
logger.warning(f"原打手{old_dashou_id}扩展信息不存在,跳过更新")
|
||
except Exception as e:
|
||
# 其他异常,记录日志但继续执行
|
||
logger.error(f"更新原打手状态异常: {str(e)}", exc_info=True)
|
||
|
||
# 12.3 更新订单表
|
||
dingdan_obj.PlayerID = new_dashou_id
|
||
dingdan_obj.Status = 2 # 进行中状态
|
||
dingdan_obj.save()
|
||
|
||
# 13. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '更换打手成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"更换打手接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdQiangZhiJieDan(APIView):
|
||
"""
|
||
后台强制结单接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddqzjd
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
强制结单接口
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 验证订单状态为2(进行中)、8(结算中)或4(退款审核中)才能强制结单
|
||
3. 根据发单平台(1=平台发单, 2=商家发单)分别处理
|
||
4. 更新订单状态为3(已完成)
|
||
5. 给打手结算分成金额
|
||
6. 如果是商家发单,还需要更新商家成交订单数量
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
dingdan_obj = Order.query.select_related('shangjia_kuozhan', 'pingtai_kuozhan').get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 验证订单状态是否为2、8或4
|
||
if dingdan_obj.Status not in [2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许强制结单', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 8. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.PlayerID # 接单打手ID
|
||
dashou_fencheng = dingdan_obj.PlayerCommission # 打手分成金额
|
||
fadan_pingtai = dingdan_obj.Platform # 发单平台
|
||
|
||
# 9. 验证打手分成金额是否存在
|
||
if dashou_fencheng is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 使用事务保证数据一致性
|
||
with transaction.atomic():
|
||
# 10.1 更新订单状态为3(已完成)
|
||
dingdan_obj.Status = 3
|
||
dingdan_obj.save()
|
||
|
||
# 10.2 处理打手结算
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = User.query.select_related('DashouProfile').get(
|
||
UserUID=jiedan_dashou_id,
|
||
#user_type='PlayerID'
|
||
)
|
||
|
||
# 获取打手扩展表
|
||
dashou_profile = dashou_user.DashouProfile
|
||
|
||
# 更新打手扩展表数据
|
||
# ✅ 确认字段:chengjiaozongliang 成交单总量
|
||
dashou_profile.chengjiaozongliang += 1
|
||
|
||
# ✅ 确认字段:yue 可提现余额
|
||
dashou_profile.yue += dashou_fencheng
|
||
|
||
# ✅ 确认字段:zonge 打单赚取总额
|
||
dashou_profile.zonge += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinrishouyi 今日收益金额
|
||
dashou_profile.jinrishouyi += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinyueshouyi 今月收益金额
|
||
dashou_profile.jinyueshouyi += dashou_fencheng
|
||
|
||
dashou_profile.save()
|
||
logger.info(f"强制结单成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
|
||
|
||
except User.DoesNotExist:
|
||
# 打手用户不存在,记录日志但不影响强制结单
|
||
logger.warning(f"强制结单:打手{jiedan_dashou_id}不存在,跳过打手结算")
|
||
except AttributeError:
|
||
# 打手扩展表不存在,记录日志但不影响强制结单
|
||
logger.warning(f"强制结单:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
|
||
|
||
# 10.3 如果是商家发单,还需要更新商家扩展表
|
||
if fadan_pingtai == 2:
|
||
try:
|
||
# 获取商家订单扩展表
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
shangjia_id = shangjia_kuozhan.MerchantID
|
||
|
||
if shangjia_id:
|
||
# 查询商家用户主表
|
||
shangjia_user = User.query.select_related('ShopProfile').get(
|
||
UserUID=shangjia_id,
|
||
#user_type='shop'
|
||
)
|
||
|
||
# 获取商家扩展表
|
||
shangjia_profile = shangjia_user.ShopProfile
|
||
|
||
# ✅ 确认字段:chengjiao 商家成交订单总量
|
||
shangjia_profile.chengjiao += 1
|
||
shangjia_profile.save()
|
||
|
||
logger.info(f"强制结单成功,商家{shangjia_id}成交订单+1")
|
||
|
||
except MerchantOrderExt.DoesNotExist:
|
||
# 商家订单扩展表不存在,跳过商家更新
|
||
logger.warning(f"强制结单:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
|
||
except User.DoesNotExist:
|
||
# 商家用户不存在,跳过商家更新
|
||
logger.warning(f"强制结单:商家{shangjia_id}不存在,跳过商家更新")
|
||
except AttributeError:
|
||
# 商家扩展表不存在,跳过商家更新
|
||
logger.warning(f"强制结单:商家{shangjia_id}扩展信息不存在,跳过商家更新")
|
||
|
||
# 11. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '强制结单成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"强制结单接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdJuJueTuiKuan(APIView):
|
||
"""
|
||
后台拒绝退款接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddjj
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"jujue_liyou": "拒绝退款理由(可选)"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
拒绝退款接口
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 拒绝退款后订单状态改为6(退款失败)
|
||
3. 给打手结算分成金额
|
||
4. 如果是商家发单,更新商家成交订单数量
|
||
5. 更新退款记录表,状态改为2(驳回)
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
jujue_liyou = request.data.get('jujue_liyou', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 查询订单主表
|
||
try:
|
||
dingdan_obj = Order.query.select_related('shangjia_kuozhan', 'pingtai_kuozhan').get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 7. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.PlayerID # 接单打手ID
|
||
dashou_fencheng = dingdan_obj.PlayerCommission # 打手分成金额
|
||
fadan_pingtai = dingdan_obj.Platform # 发单平台
|
||
|
||
# 8. 验证打手分成金额是否存在
|
||
if dashou_fencheng is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 9. 使用事务保证数据一致性
|
||
with transaction.atomic():
|
||
# 9.1 更新订单状态为6(退款失败)
|
||
dingdan_obj.Status = 6
|
||
dingdan_obj.save()
|
||
|
||
# 9.2 处理打手结算
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = User.query.select_related('DashouProfile').get(
|
||
UserUID=jiedan_dashou_id,
|
||
#user_type='PlayerID'
|
||
)
|
||
|
||
# 获取打手扩展表
|
||
dashou_profile = dashou_user.DashouProfile
|
||
|
||
# 更新打手扩展表数据
|
||
# ✅ 确认字段:chengjiaozongliang 成交单总量
|
||
dashou_profile.chengjiaozongliang += 1
|
||
|
||
# ✅ 确认字段:yue 可提现余额
|
||
dashou_profile.yue += dashou_fencheng
|
||
|
||
# ✅ 确认字段:zonge 打单赚取总额
|
||
dashou_profile.zonge += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinrishouyi 今日收益金额
|
||
dashou_profile.jinrishouyi += dashou_fencheng
|
||
|
||
# ✅ 确认字段:jinyueshouyi 今月收益金额
|
||
dashou_profile.jinyueshouyi += dashou_fencheng
|
||
|
||
dashou_profile.save()
|
||
logger.info(f"拒绝退款成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
|
||
|
||
except User.DoesNotExist:
|
||
# 打手用户不存在,记录日志但不影响操作
|
||
logger.warning(f"拒绝退款:打手{jiedan_dashou_id}不存在,跳过打手结算")
|
||
except AttributeError:
|
||
# 打手扩展表不存在,记录日志但不影响操作
|
||
logger.warning(f"拒绝退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
|
||
|
||
# 9.3 如果是商家发单,还需要更新商家扩展表
|
||
if fadan_pingtai == 2:
|
||
try:
|
||
# 获取商家订单扩展表
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
shangjia_id = shangjia_kuozhan.MerchantID
|
||
|
||
if shangjia_id:
|
||
# 查询商家用户主表
|
||
shangjia_user = User.query.select_related('ShopProfile').get(
|
||
UserUID=shangjia_id,
|
||
#user_type='shop'
|
||
)
|
||
|
||
# 获取商家扩展表
|
||
shangjia_profile = shangjia_user.ShopProfile
|
||
|
||
# ✅ 确认字段:chengjiao 商家成交订单总量
|
||
shangjia_profile.chengjiao += 1
|
||
shangjia_profile.save()
|
||
|
||
logger.info(f"拒绝退款成功,商家{shangjia_id}成交订单+1")
|
||
|
||
except MerchantOrderExt.DoesNotExist:
|
||
# 商家订单扩展表不存在,跳过商家更新
|
||
logger.warning(f"拒绝退款:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
|
||
except User.DoesNotExist:
|
||
# 商家用户不存在,跳过商家更新
|
||
logger.warning(f"拒绝退款:商家{shangjia_id}不存在,跳过商家更新")
|
||
except AttributeError:
|
||
# 商家扩展表不存在,跳过商家更新
|
||
logger.warning(f"拒绝退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
|
||
|
||
# 9.4 更新退款记录表
|
||
try:
|
||
# 查询退款记录表
|
||
tuikuan_jilu = RefundRecord.query.get(OrderID=dingdan_id)
|
||
|
||
# ✅ 确认字段:sqzhuangtai 申请状态,改为2(驳回)
|
||
tuikuan_jilu.ApplyStatus = 2
|
||
|
||
# ✅ 确认字段:bhliyou 驳回理由
|
||
tuikuan_jilu.RejectReason = jujue_liyou
|
||
|
||
# ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
|
||
tuikuan_jilu.ProcessorID = qianzhanghao
|
||
|
||
tuikuan_jilu.save()
|
||
logger.info(f"拒绝退款成功,更新退款记录表,订单:{dingdan_id}")
|
||
|
||
except RefundRecord.DoesNotExist:
|
||
# 退款记录表不存在,记录日志但不影响主流程
|
||
logger.warning(f"拒绝退款:订单{dingdan_id}退款记录不存在,跳过更新")
|
||
|
||
# 10. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '拒绝退款成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"拒绝退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdTongYiTuiKuanShangJia(APIView):
|
||
"""
|
||
后台同意退款接口(商家发单)
|
||
权限: JWT Token认证 + 管理员身份验证 + 密码验证
|
||
前端调用: POST /dingdan/adddsjtk
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"tuikuan_liyou": "退款理由(可选)",
|
||
"pay_password": "支付密码(openid)"
|
||
}
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
"""
|
||
同意退款接口(商家发单)
|
||
核心逻辑:
|
||
1. 验证管理员身份
|
||
2. 验证支付密码(openid)
|
||
3. 检查订单状态为4(退款审核中)
|
||
4. 更新订单状态为5(已退款)
|
||
5. 更新打手退款订单数量
|
||
6. 更新商家退款订单数量和余额(加上订单金额)
|
||
7. 更新退款记录表,状态改为1(成功)
|
||
"""
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
|
||
pay_password = request.data.get('pay_password', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not pay_password:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 验证支付密码(对比openid)
|
||
if pay_password != dangqianyonghu.OpenID:
|
||
return Response(
|
||
{'code': 401, 'message': '支付密码错误', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 7. 查询订单主表
|
||
try:
|
||
dingdan_obj = Order.query.select_related('shangjia_kuozhan', 'pingtai_kuozhan').get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 8. 验证订单状态为4(退款审核中)
|
||
if dingdan_obj.Status != 4:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不是退款审核中', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 9. 验证订单为商家发单
|
||
if dingdan_obj.Platform != 2:
|
||
return Response(
|
||
{'code': 400, 'message': '此接口只处理商家发单的退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.PlayerID # 接单打手ID
|
||
jine = dingdan_obj.Amount # 订单金额(不是打手分成)
|
||
|
||
# 11. 验证订单金额是否存在
|
||
if jine is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少订单金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 12. 获取商家ID
|
||
try:
|
||
shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
|
||
shangjia_id = shangjia_kuozhan.MerchantID
|
||
except MerchantOrderExt.DoesNotExist:
|
||
return Response(
|
||
{'code': 400, 'message': '订单商家信息不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 13. 使用事务保证数据一致性
|
||
with transaction.atomic():
|
||
# 13.1 更新订单状态为5(已退款)
|
||
dingdan_obj.Status = 5
|
||
dingdan_obj.save()
|
||
|
||
# 13.2 更新打手扩展表(退款订单总量+1)
|
||
if jiedan_dashou_id:
|
||
try:
|
||
# 查询打手用户主表
|
||
dashou_user = User.query.select_related('DashouProfile').get(
|
||
UserUID=jiedan_dashou_id,
|
||
#user_type='PlayerID'
|
||
)
|
||
|
||
# 获取打手扩展表
|
||
dashou_profile = dashou_user.DashouProfile
|
||
|
||
# ✅ 确认字段:tuikuanliang 退款订单总量
|
||
dashou_profile.tuikuanliang += 1
|
||
|
||
dashou_profile.save()
|
||
logger.info(f"同意退款成功,打手{jiedan_dashou_id}退款订单+1")
|
||
|
||
except User.DoesNotExist:
|
||
# 打手用户不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:打手{jiedan_dashou_id}不存在,跳过打手更新")
|
||
except AttributeError:
|
||
# 打手扩展表不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手更新")
|
||
|
||
# 13.3 更新商家扩展表
|
||
if shangjia_id:
|
||
try:
|
||
# 查询商家用户主表
|
||
shangjia_user = User.query.select_related('ShopProfile').get(
|
||
UserUID=shangjia_id,
|
||
#user_type='shop'
|
||
)
|
||
|
||
# 获取商家扩展表
|
||
shangjia_profile = shangjia_user.ShopProfile
|
||
|
||
# ✅ 确认字段:tuikuan 退款订单总量
|
||
shangjia_profile.tuikuan += 1
|
||
|
||
from merchant_ops.services.wallet import is_staff_quota_order
|
||
if not is_staff_quota_order(dingdan_id):
|
||
shangjia_profile.yue += jine
|
||
|
||
shangjia_profile.save()
|
||
logger.info(f"同意退款成功,商家{shangjia_id}退款订单+1,余额增加:{jine}")
|
||
|
||
update_shangjia_daily(
|
||
yonghuid=shangjia_id,
|
||
amount=Decimal(str(jine)),
|
||
action=3,
|
||
order_id=dingdan_id,
|
||
)
|
||
|
||
except User.DoesNotExist:
|
||
# 商家用户不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:商家{shangjia_id}不存在,跳过商家更新")
|
||
except AttributeError:
|
||
# 商家扩展表不存在,记录日志但不影响操作
|
||
logger.warning(f"同意退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
|
||
|
||
# 13.4 更新退款记录表
|
||
try:
|
||
# 查询退款记录表
|
||
tuikuan_jilu = RefundRecord.query.get(OrderID=dingdan_id)
|
||
|
||
# ✅ 确认字段:sqzhuangtai 申请状态,改为1(成功)
|
||
tuikuan_jilu.PenaltyApplyStatus = 1
|
||
|
||
# 可以添加同意理由字段,如:tongyi_liyou
|
||
# tuikuan_jilu.tongyi_liyou = tuikuan_liyou
|
||
|
||
# ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
|
||
tuikuan_jilu.ProcessorID = qianzhanghao
|
||
|
||
tuikuan_jilu.save()
|
||
logger.info(f"同意退款成功,更新退款记录表,订单:{dingdan_id}")
|
||
|
||
except RefundRecord.DoesNotExist:
|
||
# 退款记录表不存在,记录日志但不影响主流程
|
||
logger.warning(f"同意退款:订单{dingdan_id}退款记录不存在,跳过更新")
|
||
|
||
# 14. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'message': '同意退款成功',
|
||
'data': None
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"同意退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdTongYiTuiKuanPingTai(APIView):
|
||
"""
|
||
后台同意退款接口(平台发单)
|
||
权限: JWT Token认证 + 管理员身份验证 + 密码验证
|
||
前端调用: POST /dingdan/adddpttk
|
||
参数: {
|
||
"zhanghao": "管理员账号",
|
||
"dingdan_id": "订单ID",
|
||
"tuikuan_liyou": "退款理由(可选)",
|
||
"pay_password": "支付密码(openid)"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取前端参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
|
||
pay_password = request.data.get('pay_password', '').strip()
|
||
|
||
if not qianzhanghao or not dingdan_id or not pay_password:
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT Token身份验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号是否匹配
|
||
if str(getattr(dangqianyonghu, 'Phone', '')) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型是否为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 验证管理员扩展表是否存在
|
||
try:
|
||
guanliyuan = dangqianyonghu.AdminProfile
|
||
except AttributeError:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 6. 验证支付密码(对比openid)
|
||
if pay_password != dangqianyonghu.OpenID:
|
||
return Response(
|
||
{'code': 401, 'message': '支付密码错误', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 7. 查询订单主表(加锁)
|
||
with transaction.atomic():
|
||
try:
|
||
dingdan_obj = Order.query.select_related('pingtai_kuozhan').select_for_update().get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 8. 验证订单状态为1、7、2、8、4(允许退款的状态)
|
||
if dingdan_obj.Status not in [1, 7, 2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '订单状态不允许退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 9. 验证订单为平台发单
|
||
if dingdan_obj.Platform != 1:
|
||
return Response(
|
||
{'code': 400, 'message': '此接口只处理平台发单的退款', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 10. 获取订单的重要字段
|
||
jiedan_dashou_id = dingdan_obj.PlayerID
|
||
jine = dingdan_obj.Amount
|
||
if jine is None:
|
||
return Response(
|
||
{'code': 400, 'message': '订单缺少订单金额', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 11. 获取老板ID(从平台订单扩展表)
|
||
if not hasattr(dingdan_obj, 'pingtai_kuozhan') or not dingdan_obj.pingtai_kuozhan:
|
||
return Response(
|
||
{'code': 400, 'message': '订单老板信息不存在', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
laoban_id = dingdan_obj.pingtai_kuozhan.BossID
|
||
|
||
# 12. 调用微信退款接口(优先使用 wechat_transaction_id)
|
||
transaction_id = getattr(dingdan_obj, 'WechatTransactionID', None)
|
||
weixin_refund_result = self.call_weixin_refund(
|
||
out_trade_no=dingdan_id,
|
||
Amount=jine,
|
||
transaction_id=transaction_id
|
||
)
|
||
|
||
if not weixin_refund_result.get('success'):
|
||
logger.error(f"微信退款失败,订单:{dingdan_id},错误:{weixin_refund_result.get('msg')}")
|
||
return Response(
|
||
{'code': 400, 'message': weixin_refund_result.get('msg', '退款失败')},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 13. 更新订单状态为5(已退款)
|
||
dingdan_obj.Status = 5
|
||
dingdan_obj.save()
|
||
|
||
# 14. 更新打手扩展表
|
||
if jiedan_dashou_id:
|
||
try:
|
||
dashou_user = User.query.select_related('DashouProfile').get(UserUID=jiedan_dashou_id)
|
||
dashou_profile = dashou_user.DashouProfile
|
||
dashou_profile.zhuangtai = 1 # 打手状态设为空闲
|
||
dashou_profile.tuikuanliang += 1
|
||
dashou_profile.save()
|
||
except (User.DoesNotExist, AttributeError):
|
||
logger.warning(f"平台退款:打手{jiedan_dashou_id}不存在或扩展信息缺失,跳过")
|
||
|
||
# 15. 更新老板扩展表
|
||
if laoban_id:
|
||
try:
|
||
laoban_user = User.query.select_related('BossProfile').get(UserUID=laoban_id)
|
||
laoban_profile = laoban_user.BossProfile
|
||
laoban_profile.alltui += 1
|
||
laoban_profile.zonge -= jine
|
||
if laoban_profile.zonge < 0:
|
||
laoban_profile.zonge = 0
|
||
laoban_profile.save()
|
||
except (User.DoesNotExist, AttributeError):
|
||
logger.warning(f"平台退款:老板{laoban_id}不存在或扩展信息缺失,跳过")
|
||
|
||
# 16. 更新收支记录表(按俱乐部)
|
||
try:
|
||
from jituan.services.szjilu_accounting import apply_szjilu_expense
|
||
update_daily_payout(jine, getattr(dingdan_obj, 'ClubID', None))
|
||
apply_szjilu_expense(jine, getattr(dingdan_obj, 'ClubID', None))
|
||
except Exception as e:
|
||
logger.error(f"更新收支记录表异常: {str(e)}")
|
||
|
||
# 17. 更新退款记录表
|
||
try:
|
||
tuikuan_jilu, created = RefundRecord.query.get_or_create(
|
||
OrderID=dingdan_id,
|
||
defaults={
|
||
'PlayerID': jiedan_dashou_id if jiedan_dashou_id else '',
|
||
'ApplicantID': laoban_id if laoban_id else '',
|
||
'ProcessorID': qianzhanghao,
|
||
'Reason': tuikuan_liyou if tuikuan_liyou else '给老板退款',
|
||
'ApplyStatus': 1,
|
||
'Amount': jine,
|
||
'PlayerCommission': dingdan_obj.PlayerCommission or 0,
|
||
'Description': dingdan_obj.Description or '',
|
||
'Remark': '给老板退款',
|
||
'Nickname': dingdan_obj.Nickname or '',
|
||
}
|
||
)
|
||
if not created:
|
||
tuikuan_jilu.ApplyStatus = 1
|
||
if tuikuan_liyou:
|
||
tuikuan_jilu.Reason = tuikuan_liyou
|
||
tuikuan_jilu.ProcessorID = qianzhanghao
|
||
tuikuan_jilu.save()
|
||
except Exception as e:
|
||
logger.error(f"更新退款记录表异常: {str(e)}")
|
||
|
||
logger.info(f"平台退款成功,订单:{dingdan_id},退款单号:{weixin_refund_result.get('refund_id')}")
|
||
|
||
return Response({'code': 0, 'message': '退款处理成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
def call_weixin_refund(self, out_trade_no, jine, transaction_id=None):
|
||
"""
|
||
调用微信退款接口
|
||
:param out_trade_no: 商户订单号
|
||
:param jine: 退款金额
|
||
:param transaction_id: 微信支付订单号(优先使用)
|
||
:return: {'success': True/False, 'msg': '消息', 'refund_id': '退款单号'}
|
||
"""
|
||
try:
|
||
# 检查配置
|
||
required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH', 'WEIXIN_KEY_PATH']
|
||
for setting in required_settings:
|
||
if not hasattr(settings, setting) or not getattr(settings, setting):
|
||
return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
|
||
|
||
# 生成退款单号
|
||
refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
|
||
total_fee_in_fen = yuan_to_fen(jine)
|
||
|
||
# 构造请求参数
|
||
refund_data = {
|
||
'appid': settings.WEIXIN_APPID,
|
||
'mch_id': settings.WEIXIN_MCHID,
|
||
'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
|
||
'out_refund_no': refund_id,
|
||
'total_fee': total_fee_in_fen,
|
||
'refund_fee': total_fee_in_fen,
|
||
'op_user_id': settings.WEIXIN_MCHID,
|
||
'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS'
|
||
}
|
||
# 优先使用 transaction_id,否则用 out_trade_no
|
||
if transaction_id:
|
||
refund_data['transaction_id'] = transaction_id
|
||
else:
|
||
refund_data['out_trade_no'] = out_trade_no
|
||
|
||
# 生成签名
|
||
sorted_params = sorted(refund_data.items())
|
||
sign_string = '&'.join([f"{k}={v}" for k, v in sorted_params if v])
|
||
sign_string += f"&key={settings.WEIXIN_SHANGHUMIYAO}"
|
||
sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
|
||
refund_data['sign'] = sign
|
||
|
||
xml_data = self.dict_to_xml(refund_data)
|
||
url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
|
||
response = requests.post(
|
||
url,
|
||
data=xml_data.encode('utf-8'),
|
||
headers={'Content-Type': 'application/xml'},
|
||
cert=(settings.WEIXIN_CERT_PATH, settings.WEIXIN_KEY_PATH),
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
result = self.xml_to_dict(response.content)
|
||
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
|
||
logger.info(f"微信退款成功,订单:{out_trade_no},退款单号:{refund_id}")
|
||
return {'success': True, 'msg': '退款申请提交成功', 'refund_id': refund_id}
|
||
else:
|
||
error_msg = result.get('err_code_des', result.get('return_msg', '退款申请失败'))
|
||
logger.error(f"微信退款失败,订单:{out_trade_no},错误:{error_msg}")
|
||
return {'success': False, 'msg': f'微信退款失败: {error_msg}'}
|
||
else:
|
||
logger.error(f"微信退款接口调用失败,状态码:{response.status_code}")
|
||
return {'success': False, 'msg': '微信退款接口调用失败'}
|
||
|
||
except requests.exceptions.Timeout:
|
||
logger.error(f"微信退款接口请求超时,订单:{out_trade_no}")
|
||
return {'success': False, 'msg': '微信退款接口请求超时'}
|
||
except Exception as e:
|
||
logger.error(f"调用微信退款接口异常: {str(e)}")
|
||
return {'success': False, 'msg': f'退款处理异常: {str(e)}'}
|
||
|
||
def dict_to_xml(self, dict_data):
|
||
xml = ["<xml>"]
|
||
for k, v in dict_data.items():
|
||
xml.append(f"<{k}>{v}</{k}>")
|
||
xml.append("</xml>")
|
||
return "".join(xml)
|
||
|
||
def xml_to_dict(self, xml_data):
|
||
try:
|
||
root = ET.fromstring(xml_data)
|
||
return {child.tag: child.text for child in root}
|
||
except ET.ParseError as e:
|
||
logger.error(f"XML解析失败: {str(e)}")
|
||
return {'return_code': 'FAIL', 'return_msg': 'XML解析失败'}
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
|
||
|
||
class AdJuJueJieSuan(APIView):
|
||
"""
|
||
后台拒绝结算接口(平台发单)
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddjjjs
|
||
参数: {
|
||
"zhanghao": "管理员账号(手机号)",
|
||
"dingdan_id": "订单ID",
|
||
"tkly": "拒绝结算理由(必填)"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取参数
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
tkly = request.data.get('tkly', '').strip()
|
||
|
||
if not all([qianzhanghao, dingdan_id, tkly]):
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id, tkly)', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 2. JWT用户验证
|
||
dangqianyonghu = request.user
|
||
|
||
# 3. 验证手机号匹配(管理员账号通常为手机号)
|
||
if str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 4. 验证用户类型为管理员
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
# 5. 查询订单
|
||
try:
|
||
dingdan = Order.query.get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 6. 校验订单条件:平台发单(1)且状态为2或8
|
||
if dingdan.Platform != 1 or dingdan.Status not in [2, 8]:
|
||
return Response(
|
||
{'code': 400, 'message': '仅平台发单且状态为进行中(2)或结算中(8)的订单可拒绝结算', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
jiedan_dashou_id = dingdan.PlayerID
|
||
if not jiedan_dashou_id:
|
||
return Response(
|
||
{'code': 400, 'message': '订单暂无接单打手', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
with transaction.atomic():
|
||
# 7. 更新订单状态为5(已退款),并保存拒绝理由
|
||
dingdan.Status = 5
|
||
dingdan.RefundReason = tkly
|
||
dingdan.save()
|
||
|
||
# 8. 更新打手扩展表
|
||
try:
|
||
dashou_user = User.query.select_related('DashouProfile').get(UserUID=jiedan_dashou_id)
|
||
dashou_profile = dashou_user.DashouProfile
|
||
dashou_profile.zhuangtai = 1 # 打手状态设为正常
|
||
dashou_profile.tuikuanliang += 1 # 退款订单总量+1
|
||
dashou_profile.save()
|
||
except (User.DoesNotExist, AttributeError) as e:
|
||
logger.warning(f"拒绝结算:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
|
||
|
||
return Response({'code': 0, 'message': '拒绝结算成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"拒绝结算接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdZhuanYiDaTing(APIView):
|
||
"""
|
||
后台转移大厅接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddzydt
|
||
参数: {
|
||
"zhanghao": "管理员账号(手机号)",
|
||
"dingdan_id": "订单ID"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
|
||
if not all([qianzhanghao, dingdan_id]):
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
dangqianyonghu = request.user
|
||
|
||
if str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
dingdan = Order.query.get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 校验订单状态:允许2、8、4
|
||
if dingdan.Status not in [2, 8, 4]:
|
||
return Response(
|
||
{'code': 400, 'message': '仅状态为进行中(2)、结算中(8)或退款审核(4)的订单可转移大厅', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
jiedan_dashou_id = dingdan.PlayerID
|
||
|
||
with transaction.atomic():
|
||
# 更新订单:状态改为1(下单中),清空接单打手ID
|
||
dingdan.Status = 1
|
||
dingdan.PlayerID = None
|
||
dingdan.save()
|
||
|
||
# 如果有接单打手,更新其扩展表
|
||
if jiedan_dashou_id:
|
||
try:
|
||
dashou_user = User.query.select_related('DashouProfile').get(UserUID=jiedan_dashou_id)
|
||
dashou_profile = dashou_user.DashouProfile
|
||
dashou_profile.zhuangtai = 1
|
||
dashou_profile.tuikuanliang += 1
|
||
dashou_profile.save()
|
||
except (User.DoesNotExist, AttributeError) as e:
|
||
logger.warning(f"转移大厅:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
|
||
|
||
return Response({'code': 0, 'message': '转移大厅成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
class AdQuXiaoZhiDing(APIView):
|
||
"""
|
||
后台取消指定接口
|
||
权限: JWT Token认证 + 管理员身份验证
|
||
前端调用: POST /dingdan/adddqxzd
|
||
参数: {
|
||
"zhanghao": "管理员账号(手机号)",
|
||
"dingdan_id": "订单ID"
|
||
}
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
try:
|
||
qianzhanghao = request.data.get('zhanghao', '').strip()
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
|
||
if not all([qianzhanghao, dingdan_id]):
|
||
return Response(
|
||
{'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
dangqianyonghu = request.user
|
||
|
||
if str(dangqianyonghu.Phone) != qianzhanghao:
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
if dangqianyonghu.UserType != 'admin':
|
||
return Response(
|
||
{'code': 401, 'message': '身份验证失败', 'data': None},
|
||
status=status.HTTP_401_UNAUTHORIZED
|
||
)
|
||
|
||
try:
|
||
dingdan = Order.query.get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 404, 'message': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 校验订单状态:必须为7(指定中)
|
||
if dingdan.Status != 7:
|
||
return Response(
|
||
{'code': 400, 'message': '仅状态为指定中(7)的订单可取消指定', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
with transaction.atomic():
|
||
# 更新订单:状态改为1(下单中),清空指定ID
|
||
dingdan.Status = 1
|
||
dingdan.AssignedID = None
|
||
dingdan.save()
|
||
|
||
return Response({'code': 0, 'message': '取消指定成功', 'data': None})
|
||
|
||
except Exception as e:
|
||
logger.error(f"取消指定接口异常: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{'code': 500, 'message': '服务器内部错误', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
|
||
class ZhiDingChaxunView(APIView):
|
||
"""
|
||
查询当前打手被指定的待接订单
|
||
POST /dingdan/zdcx
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
dashou_uid = str(request.user.UserUID)
|
||
try:
|
||
dashou_profile = request.user.DashouProfile
|
||
if dashou_profile.zhanghaozhuangtai != 1:
|
||
return Response({'code': 403, 'msg': '账号状态异常'}, status=403)
|
||
except Exception:
|
||
return Response({'code': 403, 'msg': '您不是打手'}, status=403)
|
||
|
||
orders = Order.query.filter(
|
||
Status=7,
|
||
AssignedID=dashou_uid,
|
||
).filter(
|
||
Q(PlayerID__isnull=True) | Q(PlayerID='')
|
||
).order_by('-CreateTime')[:20]
|
||
|
||
result = []
|
||
for o in orders:
|
||
hf = getattr(o, 'AssignedResponse', None)
|
||
result.append({
|
||
'dingdan_id': o.OrderID,
|
||
'zhuangtai': o.Status,
|
||
'pingtai': o.Platform,
|
||
'dashou_fencheng': float(o.PlayerCommission or 0),
|
||
'leixing_id': o.ProductTypeID or 0,
|
||
'tupian': o.ImageURL or '',
|
||
'jieshao': o.Description or '',
|
||
'beizhu': o.Remark or '',
|
||
'creat_time': fmt_datetime(o.CreateTime),
|
||
'create_time': fmt_datetime(o.CreateTime),
|
||
'zhiding_uid': o.AssignedID or '',
|
||
'zhiding_hf': hf,
|
||
'zhiding_hf_text': ZHIDING_HF_TEXT.get(hf, ''),
|
||
'zhiding_dhj_sm': getattr(o, 'AssignedLaterNote', '') or '',
|
||
'zhiding_hf_sj': fmt_datetime(o.AssignedResponseAt) if getattr(o, 'AssignedResponseAt', None) else '',
|
||
'yaoqiuleixing': o.GrabRequirement or 0,
|
||
'huiyuan_id': o.MembershipID or '',
|
||
'yajin': float(o.CommissionReq or 0),
|
||
})
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '获取成功',
|
||
'data': {'list': result, 'count': len(result)},
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"指定订单查询失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|
||
|
||
|
||
class ZhiDingHuiFuView(APIView):
|
||
"""
|
||
指定单打手响应:想接 / 不想接 / 等会接
|
||
POST /dingdan/zdhf
|
||
参数: dingdan_id, huifu (1|2|3), deng_hui_note (等会接说明,可选)
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
data = request.data
|
||
dingdan_id = str(data.get('dingdan_id') or data.get('OrderID') or '').strip()
|
||
huifu = int(data.get('huifu') or 0)
|
||
deng_hui_note = str(data.get('deng_hui_note') or '').strip()[:100]
|
||
|
||
if not dingdan_id:
|
||
return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
|
||
if huifu not in (1, 2, 3):
|
||
return Response({'code': 400, 'msg': '无效的响应类型'}, status=400)
|
||
if huifu == 3 and not deng_hui_note:
|
||
return Response({'code': 400, 'msg': '请填写预计接待时间'}, status=400)
|
||
|
||
dashou_uid = str(request.user.UserUID)
|
||
|
||
with transaction.atomic():
|
||
try:
|
||
order = Order.query.select_for_update().get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '订单不存在'}, status=404)
|
||
|
||
if order.Status != 7:
|
||
return Response({'code': 400, 'msg': '订单已不是指定中状态'}, status=400)
|
||
if str(order.AssignedID or '') != dashou_uid:
|
||
return Response({'code': 403, 'msg': '您不是该订单的指定打手'}, status=403)
|
||
if order.PlayerID:
|
||
return Response({'code': 400, 'msg': '订单已被接单'}, status=400)
|
||
|
||
now = timezone.now()
|
||
order.AssignedResponse = huifu
|
||
order.AssignedResponseAt = now
|
||
|
||
if huifu == 2:
|
||
order.Status = 1
|
||
order.AssignedID = None
|
||
order.AssignedLaterNote = ''
|
||
elif huifu == 3:
|
||
order.AssignedLaterNote = deng_hui_note
|
||
else:
|
||
order.AssignedLaterNote = ''
|
||
|
||
order.save()
|
||
|
||
msg_map = {1: '已标记想接,请点击接待完成接单', 2: '已婉拒,订单已退回大厅', 3: '已记录,请按时接待'}
|
||
return Response({
|
||
'code': 200,
|
||
'msg': msg_map.get(huifu, '操作成功'),
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'huifu': huifu,
|
||
'huifu_text': ZHIDING_HF_TEXT.get(huifu, ''),
|
||
},
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"指定订单响应失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|
||
|
||
|
||
|
||
|
||
class ZxsjghdsView(APIView):
|
||
"""
|
||
商家更换打手接口
|
||
路径:POST /dingdan/zxsjghds
|
||
权限:仅限已认证商家(通过JWT)
|
||
功能:
|
||
1. 将原订单状态改为已退款(5),释放接单打手(若有)
|
||
2. 更新打手统计(退款量、状态、每日统计)
|
||
3. 写入退款记录
|
||
4. 若为跨平台订单,通知对方平台退款(必须成功)
|
||
5. 基于原订单生成新订单(状态直接为1),复制核心数据
|
||
6. 不操作商家余额、退款次数等(替换订单)
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
# ========== 1. 获取当前商家用户 ==========
|
||
current_user = request.user
|
||
try:
|
||
yonghuid = current_user.UserUID
|
||
except AttributeError:
|
||
return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# ========== 2. 提取订单ID ==========
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
if not dingdan_id:
|
||
return Response({'code': 2, 'msg': '订单ID不能为空'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 3. 查询订单并校验权限 ==========
|
||
try:
|
||
# 关联查询商家扩展表,确保当前商家是该订单的发布者
|
||
shangjia_kuozhan = MerchantOrderExt.query.select_related('Order').get(
|
||
Order__OrderID=dingdan_id,
|
||
MerchantID=yonghuid
|
||
)
|
||
except MerchantOrderExt.DoesNotExist:
|
||
return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
old_order = shangjia_kuozhan.Order
|
||
|
||
# ========== 4. 状态校验(仅允许进行中或结算中) ==========
|
||
if old_order.Status not in [2, 8]:
|
||
return Response({'code': 4, 'msg': '当前订单状态不允许更换打手'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ========== 5. 收集订单关键数据(用于新订单复制) ==========
|
||
# 这些字段将在退款处理后用于创建新订单
|
||
jine = old_order.Amount
|
||
dashou_fencheng = old_order.PlayerCommission
|
||
leixing_id = old_order.ProductTypeID
|
||
yaoqiuleixing = old_order.GrabRequirement
|
||
huiyuan_id = old_order.MembershipID
|
||
yongjin = old_order.CommissionReq
|
||
jieshao = old_order.Description
|
||
nicheng = old_order.Nickname
|
||
beizhu = old_order.Remark
|
||
waibu_dingdan_id = old_order.ExternalOrderID
|
||
user1_id = old_order.User1ID
|
||
tupian = old_order.ImageURL
|
||
fadan_pingtai = old_order.Platform
|
||
|
||
dashou_id = old_order.PlayerID # 可能为空
|
||
|
||
# ========== 6. 退款处理(事务块1:退款原订单 + 打手统计 + 跨平台通知) ==========
|
||
try:
|
||
with transaction.atomic():
|
||
# 6.1 订单状态更新为已退款
|
||
old_order.Status = 5
|
||
old_order.save(update_fields=['Status'])
|
||
|
||
# 6.2 释放打手
|
||
if dashou_id:
|
||
try:
|
||
dashou_user = User.query.select_related('DashouProfile').get(UserUID=dashou_id)
|
||
dashou_profile = dashou_user.DashouProfile
|
||
dashou_profile.tuikuanliang = (dashou_profile.tuikuanliang or 0) + 1
|
||
dashou_profile.zhuangtai = 1 # 空闲
|
||
dashou_profile.save(update_fields=['tuikuanliang', 'zhuangtai'])
|
||
except (User.DoesNotExist, ObjectDoesNotExist):
|
||
logger.warning(f"打手 {dashou_id} 不存在或扩展表缺失,跳过更新")
|
||
|
||
# 6.3 更新退款记录
|
||
RefundRecord.query.update_or_create(
|
||
OrderID=dingdan_id,
|
||
defaults={
|
||
'PlayerID': dashou_id or '',
|
||
'ApplicantID': yonghuid, # 申请人为当前商家
|
||
'ProcessorID': yonghuid, # 处理人也是商家自己
|
||
'Reason': '商家更换打手',
|
||
'ApplyStatus': 1, # 状态 1=成功
|
||
'Amount': jine or 0,
|
||
'PlayerCommission': dashou_fencheng or 0,
|
||
'Description': jieshao or '',
|
||
'Remark': '更换打手系统自动退款',
|
||
'Nickname': nicheng or '',
|
||
}
|
||
)
|
||
|
||
# 6.4 打手每日统计(退款行为)
|
||
if dashou_id:
|
||
try:
|
||
update_dashou_daily_by_action(
|
||
yonghuid=dashou_id,
|
||
amount=dashou_fencheng,
|
||
action=3 # 3=退款
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"更新打手每日统计失败: {e}")
|
||
# 统计失败不应阻断主流程,但可记录
|
||
|
||
except Exception as e:
|
||
logger.error(f"退款处理事务失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '退款处理失败'})
|
||
|
||
# ========== 7. 生成新订单(事务块2) ==========
|
||
try:
|
||
with transaction.atomic():
|
||
# 7.1 生成新的订单ID
|
||
def generate_dingdan_id():
|
||
timestamp = int(time.time() * 1000)
|
||
process_id = os.getpid() % 1000
|
||
random_num = random.randint(1000, 9999)
|
||
return f"SJ{timestamp}{process_id:03d}{random_num}"
|
||
|
||
new_dingdan_id = generate_dingdan_id()
|
||
# 确保唯一
|
||
while Order.query.filter(OrderID=new_dingdan_id).exists():
|
||
new_dingdan_id = generate_dingdan_id()
|
||
|
||
# 7.2 创建新订单(复制数据,状态为 1)
|
||
new_order = Order.query.create(
|
||
OrderID=new_dingdan_id,
|
||
Status=1, # 已下单
|
||
Platform=fadan_pingtai,
|
||
Amount=jine,
|
||
PlayerCommission=dashou_fencheng,
|
||
ProductTypeID=leixing_id,
|
||
GrabRequirement=yaoqiuleixing,
|
||
MembershipID=huiyuan_id,
|
||
CommissionReq=yongjin,
|
||
Description=jieshao,
|
||
Nickname=nicheng,
|
||
Remark=beizhu,
|
||
User1ID=user1_id, # 商家标识
|
||
ImageURL=tupian,
|
||
ExternalOrderID=waibu_dingdan_id,
|
||
ClubID=getattr(old_order, 'ClubID', None) or 'xq',
|
||
# 不继承:zhiding_id, jiedan_dashou_id, dashou_liuyan 等
|
||
)
|
||
|
||
# 7.3 创建商家扩展表(关联新订单)
|
||
MerchantOrderExt.query.create(
|
||
Order=new_order,
|
||
MerchantID=yonghuid,
|
||
MerchantNickname=shangjia_kuozhan.MerchantNickname or '',
|
||
)
|
||
|
||
# 7.4 异步通知打手(广播新订单)
|
||
try:
|
||
order_info = {
|
||
'OrderID': new_dingdan_id,
|
||
'game_type': self._get_game_type_name(leixing_id),
|
||
'amount': str(jine),
|
||
'order_desc': (jieshao or '')[:50],
|
||
}
|
||
dingdan_guangbo.delay(order_info)
|
||
except Exception as e:
|
||
logger.error(f"广播通知失败: {e}")
|
||
|
||
# 注意:此处不更新商家余额、发布量等统计,因为是替换订单
|
||
|
||
logger.info(f"商家{yonghuid}更换打手成功,旧订单{dingdan_id}已退款,新订单{new_dingdan_id}")
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '更换成功',
|
||
'data': {
|
||
'xin_dingdan_id': new_dingdan_id,
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"生成新订单事务失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '生成新订单失败'})
|
||
|
||
def _get_game_type_name(self, leixing_id):
|
||
"""根据类型ID获取游戏类型名称"""
|
||
try:
|
||
gt = ShangpinLeixing.query.filter(id=leixing_id).first()
|
||
return gt.jieshao if gt else '游戏订单'
|
||
except Exception:
|
||
return '游戏订单'
|
||
|
||
|