1529 lines
64 KiB
Python
1529 lines
64 KiB
Python
"""orders.views.dashou - 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 DashouDingdanHuoquView(APIView):
|
||
"""
|
||
获取可抢订单列表(支持标签筛选)
|
||
POST /dingdan/ddhq
|
||
需要JWT Token认证
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
from django.conf import settings
|
||
from orders.services.fake_order_pool import (
|
||
dashou_should_use_fake_pool,
|
||
build_fake_order_pool_response,
|
||
)
|
||
if getattr(settings, 'FAKE_GRAB_ORDER_POOL_ENABLED', False):
|
||
if dashou_should_use_fake_pool(request.user):
|
||
return build_fake_order_pool_response(request)
|
||
|
||
# 1. 获取请求参数
|
||
data = request.data
|
||
leixing_id = pick_leixing_id(data)
|
||
page = int(data.get('page', 1))
|
||
page_size = int(data.get('page_size', 10))
|
||
biaoqian_id = data.get('biaoqian_id')
|
||
|
||
from jituan.services.club_context import resolve_effective_club_id
|
||
from jituan.services.huiyuan_bundle import (
|
||
refresh_grab_pool_membership_state,
|
||
resolve_grab_pool_member_display,
|
||
)
|
||
club_id = resolve_effective_club_id(request, getattr(request, 'user', None))
|
||
yonghuid = request.user.UserUID
|
||
_has_member, user_clumber = refresh_grab_pool_membership_state(
|
||
yonghuid, club_id=club_id,
|
||
)
|
||
|
||
# 2. 构建基础查询条件
|
||
base_query = Q(Status__in=[1, 7])
|
||
if leixing_id is not None:
|
||
base_query &= Q(ProductTypeID=leixing_id)
|
||
|
||
# 3. 标签筛选:先查出符合条件的订单ID,避免跨表 JOIN 排序冲突
|
||
if biaoqian_id and int(biaoqian_id) > 0:
|
||
biaoqian_order_ids = DingdanBiaoqian.query.filter(
|
||
chenghao_id=int(biaoqian_id)
|
||
).values_list('dingdan_id', flat=True)
|
||
base_query &= Q(OrderID__in=list(biaoqian_order_ids))
|
||
|
||
# 4. 构建基础查询集
|
||
base_qs = Order.query.filter(base_query).order_by('-CreateTime').distinct()
|
||
|
||
# 5. 获取总记录数
|
||
total = base_qs.count()
|
||
|
||
# 6. 计算分页偏移量
|
||
offset = (page - 1) * page_size
|
||
limit = page_size
|
||
|
||
# 7. 获取当前页数据
|
||
page_qs = base_qs[offset:offset + limit]
|
||
|
||
# 8. 使用子查询获取商家昵称(Subquery 须用 Django 原生 QuerySet,不能用 .query.values())
|
||
shangjia_sub = MerchantOrderExt.objects.filter(
|
||
Order__OrderID=OuterRef('OrderID')
|
||
).values('MerchantNickname')[:1]
|
||
|
||
# 9. 获取当前页所有需要的字段
|
||
dingdan_values = page_qs.annotate(
|
||
MerchantNickname=Subquery(shangjia_sub)
|
||
).values(
|
||
'OrderID', 'Status', 'Platform', 'PlayerCommission',
|
||
'AssignedID', 'AssignedResponse', 'AssignedResponseAt', 'AssignedLaterNote',
|
||
'ProductTypeID', 'ImageURL', 'GrabRequirement',
|
||
'MembershipID', 'CommissionReq', 'Description', 'Remark', 'CreateTime',
|
||
'MerchantNickname'
|
||
)
|
||
|
||
# 10. 转换为列表
|
||
dingdan_list = list(dingdan_values)
|
||
|
||
# 11. 批量获取指定打手的头像和昵称
|
||
zhiding_ids = {item['AssignedID'] for item in dingdan_list if item.get('AssignedID')}
|
||
zhiding_info = {}
|
||
if zhiding_ids:
|
||
dashou_query = User.query.filter(
|
||
UserUID__in=zhiding_ids
|
||
).select_related('DashouProfile').annotate(
|
||
Nickname=F('DashouProfile__nicheng')
|
||
).values('UserUID', 'Avatar', 'Nickname')
|
||
zhiding_info = {
|
||
item['UserUID']: {
|
||
'avatar': item['Avatar'] or '',
|
||
'nicheng': item['Nickname'] or ''
|
||
}
|
||
for item in dashou_query
|
||
}
|
||
|
||
# ============ 批量获取三层标签 ============
|
||
order_ids = [item['OrderID'] for item in dingdan_list]
|
||
|
||
# --- 订单需求标签 ---
|
||
xuqiu_tags_map = {}
|
||
if order_ids:
|
||
try:
|
||
dbq_qs = DingdanBiaoqian.query.filter(
|
||
dingdan_id__in=order_ids
|
||
).select_related('chenghao')
|
||
for dbq in dbq_qs:
|
||
oid = dbq.dingdan_id
|
||
if oid not in xuqiu_tags_map:
|
||
xuqiu_tags_map[oid] = []
|
||
ch = dbq.chenghao
|
||
xuqiu_tags_map[oid].append({
|
||
'id': ch.id,
|
||
'mingcheng': ch.mingcheng,
|
||
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"查询订单需求标签失败: {e}", exc_info=True)
|
||
|
||
# --- 所有需要查询标签的用户ID集合(指定打手 + 商家) ---
|
||
yonghuid_set = set(zhiding_ids)
|
||
shangjia_order_ids = [item['OrderID'] for item in dingdan_list if item['Platform'] == 2]
|
||
shangjia_id_map = {}
|
||
if shangjia_order_ids:
|
||
# 使用跨表查询 Order__OrderID__in 匹配字符串订单ID
|
||
sj_exts = MerchantOrderExt.query.filter(
|
||
Order__OrderID__in=shangjia_order_ids
|
||
).values('Order__OrderID', 'MerchantID')
|
||
for sj in sj_exts:
|
||
shangjia_id_map[sj['Order__OrderID']] = sj['MerchantID']
|
||
if sj['MerchantID']:
|
||
yonghuid_set.add(sj['MerchantID'])
|
||
|
||
# 将 yonghuid 字符串转换为 User 的主键 UserUUID
|
||
user_id_map = {}
|
||
user_avatar_map = {}
|
||
if yonghuid_set:
|
||
users = User.query.filter(UserUID__in=yonghuid_set).values('UserUUID', 'UserUID', 'Avatar')
|
||
for u in users:
|
||
user_id_map[u['UserUID']] = u['UserUUID']
|
||
user_avatar_map[u['UserUID']] = (u['Avatar'] or '').strip()
|
||
|
||
user_main_ids = list(user_id_map.values())
|
||
user_tags_map = {}
|
||
if user_main_ids:
|
||
uc_qs = YonghuChenghao.query.filter(
|
||
yonghu_id__in=user_main_ids
|
||
).select_related('chenghao')
|
||
for uc in uc_qs:
|
||
uid = uc.yonghu_id
|
||
if uid not in user_tags_map:
|
||
user_tags_map[uid] = []
|
||
user_tags_map[uid].append(uc.chenghao)
|
||
|
||
dashou_tags_map = {}
|
||
shangjia_tags_map = {}
|
||
for item in dingdan_list:
|
||
oid = item['OrderID']
|
||
# 指定打手标签
|
||
zhid = item['AssignedID']
|
||
dashou_tags = []
|
||
if zhid and zhid in user_id_map:
|
||
uid = user_id_map[zhid]
|
||
if uid in user_tags_map:
|
||
for ch in user_tags_map[uid]:
|
||
if ch.leixing == 'dashou':
|
||
dashou_tags.append({
|
||
'id': ch.id,
|
||
'mingcheng': ch.mingcheng,
|
||
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
|
||
})
|
||
dashou_tags_map[oid] = dashou_tags
|
||
|
||
# 商家标签
|
||
sj_tags = []
|
||
if item['Platform'] == 2 and oid in shangjia_id_map:
|
||
sj_yonghuid = shangjia_id_map[oid]
|
||
if sj_yonghuid and sj_yonghuid in user_id_map:
|
||
uid = user_id_map[sj_yonghuid]
|
||
if uid in user_tags_map:
|
||
for ch in user_tags_map[uid]:
|
||
if ch.leixing == 'shangjia':
|
||
sj_tags.append({
|
||
'id': ch.id,
|
||
'mingcheng': ch.mingcheng,
|
||
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
|
||
})
|
||
shangjia_tags_map[oid] = sj_tags
|
||
|
||
# --- 身份装饰标签 + 优质商家 ---
|
||
from jituan.services.identity_tag import (
|
||
SHENFEN_DASHOU,
|
||
SHENFEN_SHANGJIA,
|
||
batch_identity_tags,
|
||
)
|
||
from users.models import UserShangjia
|
||
|
||
identity_pairs = []
|
||
youzhi_map = {}
|
||
merchant_uids = set(shangjia_id_map.values())
|
||
if merchant_uids:
|
||
for sj in UserShangjia.query.filter(
|
||
user__UserUID__in=merchant_uids
|
||
).select_related('user').values('user__UserUID', 'is_youzhi_shangjia'):
|
||
youzhi_map[sj['user__UserUID']] = bool(sj['is_youzhi_shangjia'])
|
||
|
||
for item in dingdan_list:
|
||
oid = item['OrderID']
|
||
zhid = item.get('AssignedID')
|
||
if zhid:
|
||
identity_pairs.append((zhid, SHENFEN_DASHOU))
|
||
if item['Platform'] == 2 and oid in shangjia_id_map:
|
||
sj_uid = shangjia_id_map[oid]
|
||
if sj_uid:
|
||
identity_pairs.append((sj_uid, SHENFEN_SHANGJIA))
|
||
|
||
identity_tag_map = batch_identity_tags(club_id, identity_pairs) if identity_pairs else {}
|
||
|
||
# 12. 格式化返回数据
|
||
formatted_list = []
|
||
for order in dingdan_list:
|
||
oid = order['OrderID']
|
||
zid = order.get('AssignedID')
|
||
info = zhiding_info.get(zid, {})
|
||
sj_uid = shangjia_id_map.get(oid) if order['Platform'] == 2 else None
|
||
display_grab_req, display_huiyuan_id, has_required_member = (
|
||
resolve_grab_pool_member_display(
|
||
order, yonghuid, club_id=club_id, user_clumber=user_clumber,
|
||
)
|
||
)
|
||
formatted_list.append({
|
||
'dingdan_id': order['OrderID'],
|
||
'zhuangtai': order['Status'],
|
||
'pingtai': order['Platform'],
|
||
'dashou_fencheng': float(order['PlayerCommission']) if order['PlayerCommission'] else 0.0,
|
||
'leixing_id': order['ProductTypeID'] or 0,
|
||
'tupian': order['ImageURL'] or '',
|
||
'yaoqiuleixing': display_grab_req,
|
||
'huiyuan_id': display_huiyuan_id,
|
||
'hasRequiredMember': has_required_member,
|
||
'has_required_member': has_required_member,
|
||
'yajin': float(order['CommissionReq']) if order['CommissionReq'] else 0.0,
|
||
'jieshao': order['Description'] or '',
|
||
'beizhu': order['Remark'] or '',
|
||
'creat_time': fmt_datetime(order['CreateTime']),
|
||
'create_time': fmt_datetime(order['CreateTime']),
|
||
'sjnicheng': order['MerchantNickname'] or '',
|
||
'sj_avatar': user_avatar_map.get(sj_uid, '') if sj_uid else '',
|
||
'zhiding_uid': order['AssignedID'] or '',
|
||
'zhiding_avatar': info.get('avatar', ''),
|
||
'zhiding_nicheng': info.get('nicheng', ''),
|
||
'zhiding_hf': order.get('AssignedResponse'),
|
||
'zhiding_hf_text': ZHIDING_HF_TEXT.get(order.get('AssignedResponse'), ''),
|
||
'zhiding_dhj_sm': order.get('AssignedLaterNote') or '',
|
||
'zhiding_hf_sj': fmt_datetime(order['AssignedResponseAt']) if order.get('AssignedResponseAt') else '',
|
||
# 三层标签
|
||
'xuqiu_biaoqian': xuqiu_tags_map.get(oid, []),
|
||
'dashou_biaoqian': dashou_tags_map.get(oid, []),
|
||
'shangjia_biaoqian': shangjia_tags_map.get(oid, []),
|
||
# 身份装饰标签 + 优质商家
|
||
'shangjia_identity_biaoqian': identity_tag_map.get((sj_uid, SHENFEN_SHANGJIA), []) if sj_uid else [],
|
||
'zhiding_identity_biaoqian': identity_tag_map.get((zid, SHENFEN_DASHOU), []) if zid else [],
|
||
'shangjia_youzhi': bool(youzhi_map.get(sj_uid)) if sj_uid else False,
|
||
})
|
||
|
||
has_more = (page * page_size) < total
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '获取成功',
|
||
'data': {
|
||
'list': formatted_list,
|
||
'has_more': has_more,
|
||
'total': total,
|
||
'current_page': page,
|
||
'page_size': page_size,
|
||
'total_pages': (total + page_size - 1) // page_size,
|
||
'clumber': user_clumber,
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"订单列表查询失败: {e}", exc_info=True)
|
||
return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
|
||
|
||
def _parse_texiao_json(self, texiao_miaoshu):
|
||
if not texiao_miaoshu:
|
||
return {}
|
||
try:
|
||
return json.loads(texiao_miaoshu)
|
||
except:
|
||
return {}
|
||
|
||
|
||
|
||
|
||
|
||
class ShangpinLeixingHuoquView(APIView):
|
||
"""
|
||
获取商品类型列表
|
||
POST /dingdan/ddlxhq
|
||
需要JWT Token认证
|
||
"""
|
||
authentication_classes = [JWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 1. 查询商品类型表
|
||
# 假设你的商品类型模型字段为:id, jieshao, tupian_url
|
||
leixing_list = ShangpinLeixing.query.all().values(
|
||
'id', # 商品类型ID
|
||
'jieshao', # 类型介绍
|
||
'tupian_url' # 类型图片URL(相对URL)
|
||
).order_by('id') # 按ID排序,可根据需要调整
|
||
|
||
# 2. 格式化返回数据
|
||
formatted_list = []
|
||
for leixing in leixing_list:
|
||
formatted_leixing = {
|
||
'id': leixing['id'],
|
||
'jieshao': leixing['jieshao'] or '', # 处理空值
|
||
'tupian_url': leixing['tupian_url'] or '' # 处理空值
|
||
}
|
||
formatted_list.append(formatted_leixing)
|
||
|
||
# 3. 返回响应
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '获取成功',
|
||
'data': {
|
||
'list': formatted_list,
|
||
'total': len(formatted_list)
|
||
}
|
||
})
|
||
|
||
|
||
|
||
|
||
# ----------------- 基础配置获取 -----------------
|
||
|
||
|
||
class QiangdanView(APIView):
|
||
authentication_classes = [JWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
data = request.data
|
||
dingdan_id = data.get('dingdan_id') or data.get('OrderID')
|
||
if not dingdan_id:
|
||
return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
|
||
|
||
dingdan_id = str(dingdan_id).strip()
|
||
if dingdan_id.upper().startswith('JD'):
|
||
return Response({'code': 400, 'msg': '您没充值会员或保证金不可接单'})
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
# 1. 验证打手身份和状态
|
||
dashou_profile = self._validate_dashou_status(request.user)
|
||
if not dashou_profile:
|
||
return Response({'code': 403, 'msg': '您不是打手或账号状态异常'}, status=403)
|
||
|
||
|
||
# ==================== 【新增】罚单检查 ====================
|
||
fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.UserUID,1)
|
||
if not fadan_ok:
|
||
return Response({'code': 400, 'msg': fadan_msg})
|
||
# =========================================================
|
||
# ========== 标签快速校验(提前拒绝,避免锁资源) ==========
|
||
biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.UserUID)
|
||
if not biaoqian_ok:
|
||
return Response({'code': 400, 'msg': biaoqian_msg})
|
||
# ===========================================================
|
||
|
||
# 2. 锁定并验证订单(行锁)
|
||
order_result = self._validate_and_lock_order(dingdan_id, request.user.UserUID)
|
||
if isinstance(order_result, str):
|
||
return Response({'code': 400, 'msg': order_result})
|
||
order = order_result
|
||
|
||
validation_result = self._validate_order_requirements(order, dashou_profile, request.user.UserUID)
|
||
if not validation_result['success']:
|
||
return Response({'code': 400, 'msg': validation_result['message']})
|
||
self._execute_qiangdan(order, dashou_profile, request.user.UserUID)
|
||
|
||
# ========== 【唯一改动】聊天建立方式 ==========
|
||
chat_success = establish_order_chat(dingdan_id)
|
||
|
||
try:
|
||
update_dashou_daily_by_action(
|
||
yonghuid=order.PlayerID,
|
||
amount=order.PlayerCommission,
|
||
action=1
|
||
)
|
||
logger.info(f"打手 {order.PlayerID} 每日统计更新成功")
|
||
except Exception as e:
|
||
logger.error(f"打手每日统计更新失败: {e}")
|
||
|
||
|
||
# 在 QiangdanView.post 里,_execute_qiangdan 之后添加:
|
||
# 记录抢单表现(不影响主流程)
|
||
# 如果订单表里有 bankuai 信息,可以用 order.bankuai 代替 None
|
||
record_dashou_biaoxian(
|
||
dingdan_id=dingdan_id,
|
||
dashou_id=order.PlayerID,
|
||
xingwei=1,
|
||
jiedan_time=timezone.now(), # 直接取当前时间
|
||
bankuai=None # 暂不填,后续提交时补上
|
||
)
|
||
|
||
return Response({
|
||
'code': 200,
|
||
'msg': '抢单成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'chat_established': chat_success
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"抢单异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '系统异常,请稍后重试'}, status=500)
|
||
|
||
# ---------- 以下所有方法原封不动照抄旧版 ----------
|
||
def _validate_dashou_status(self, user):
|
||
try:
|
||
dashou_profile = user.DashouProfile
|
||
if (dashou_profile.zhuangtai != 1 or
|
||
dashou_profile.zhanghaozhuangtai != 1 or
|
||
dashou_profile.jifen < 5):
|
||
return None
|
||
return dashou_profile
|
||
except UserDashou.DoesNotExist:
|
||
return None
|
||
|
||
def _validate_and_lock_order(self, dingdan_id, dashou_yonghuid):
|
||
try:
|
||
order = Order.query.select_for_update().get(OrderID=dingdan_id)
|
||
if order.Status not in [1, 7]:
|
||
return "订单状态不可抢"
|
||
if order.Status == 7:
|
||
if not order.AssignedID or order.AssignedID != dashou_yonghuid:
|
||
return "此订单为指定单,您不是指定打手"
|
||
if order.PlayerID:
|
||
return "订单已被其他打手抢走"
|
||
return order
|
||
except Order.DoesNotExist:
|
||
return "订单不存在"
|
||
|
||
def _validate_order_requirements(self, order, dashou_profile, dashou_yonghuid):
|
||
if order.GrabRequirement == 1:
|
||
return self._validate_huiyuan_requirement(order, dashou_yonghuid)
|
||
elif order.GrabRequirement == 2:
|
||
return self._validate_yajin_requirement(order, dashou_profile)
|
||
return {'success': True, 'message': '验证通过'}
|
||
|
||
def _validate_huiyuan_requirement(self, order, dashou_yonghuid):
|
||
from jituan.services.huiyuan_bundle import (
|
||
user_has_huiyuan_access_any,
|
||
resolve_order_membership_ids,
|
||
)
|
||
from jituan.services.club_context import resolve_effective_club_id
|
||
|
||
club_id = None
|
||
try:
|
||
club_id = resolve_effective_club_id(self.request, self.request.user)
|
||
except Exception:
|
||
pass
|
||
order_dict = {
|
||
'MembershipID': order.MembershipID,
|
||
'GrabRequirement': order.GrabRequirement,
|
||
'ProductTypeID': order.ProductTypeID,
|
||
}
|
||
required_ids = resolve_order_membership_ids(order_dict)
|
||
if user_has_huiyuan_access_any(dashou_yonghuid, required_ids, club_id=club_id):
|
||
return {'success': True, 'message': '会员验证通过'}
|
||
return {'success': False, 'message': '未开通对应会员或会员已过期,无法抢此订单'}
|
||
|
||
def _validate_yajin_requirement(self, order, dashou_profile):
|
||
yajin_required = order.CommissionReq or 0
|
||
dashou_yajin = dashou_profile.yajin or 0
|
||
if dashou_yajin < yajin_required:
|
||
return {'success': False, 'message': f'押金不足,需要{yajin_required}元,当前有{dashou_yajin}元'}
|
||
return {'success': True, 'message': '押金验证通过'}
|
||
|
||
def _execute_qiangdan(self, order, dashou_profile, dashou_yonghuid):
|
||
order.Status = 2
|
||
order.PlayerID = dashou_yonghuid
|
||
order.User2ID = f"Ds{dashou_yonghuid}"
|
||
order.save()
|
||
|
||
dashou_profile.zhuangtai = 0
|
||
dashou_profile.jiedanzongliang = F('jiedanzongliang') + 1
|
||
dashou_profile.jinrijiedan = F('jinrijiedan') + 1
|
||
dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1
|
||
dashou_profile.save()
|
||
|
||
|
||
class DashouDingdanHuoquView1(APIView):
|
||
"""
|
||
打手订单获取接口
|
||
支持筛选条件:
|
||
- zhuangtai_list: 订单状态列表(可选,空数组或不传表示全部状态)
|
||
- leixing_id: 商品类型ID(可选)
|
||
- keyword: 搜索关键词,模糊匹配订单ID和订单介绍(可选)
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 1. 获取当前登录用户的 yonghuid
|
||
try:
|
||
current_user = request.user
|
||
yonghuid = current_user.UserUID
|
||
except AttributeError:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '用户认证失败',
|
||
'data': None
|
||
}, status=status.HTTP_401_UNAUTHORIZED)
|
||
|
||
# 2. 获取并校验前端参数
|
||
try:
|
||
data = request.data
|
||
zhuangtai_list = data.get('zhuangtai_list', []) # 可选,默认空列表(全部)
|
||
page = max(1, int(data.get('page', 1)))
|
||
page_size = min(100, max(1, int(data.get('page_size', 10))))
|
||
leixing_id = pick_leixing_id(data) # 可选,商品类型ID
|
||
keyword = data.get('keyword', '').strip() # 可选,搜索关键词
|
||
except (ValueError, TypeError):
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '参数格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 参数默认值与类型修正
|
||
if not isinstance(zhuangtai_list, list):
|
||
zhuangtai_list = [] # 非列表则视为全部
|
||
|
||
# 4. 构建查询条件
|
||
try:
|
||
# 基础条件:当前打手接单
|
||
query = Q(PlayerID=yonghuid)
|
||
|
||
# 状态筛选:如果 zhuangtai_list 不为空列表,则加入筛选;否则查询全部状态
|
||
if zhuangtai_list:
|
||
query &= Q(Status__in=zhuangtai_list)
|
||
|
||
# 商品类型筛选
|
||
if leixing_id is not None:
|
||
query &= Q(ProductTypeID=leixing_id)
|
||
|
||
# 关键词搜索:模糊匹配订单ID和订单介绍
|
||
if keyword:
|
||
keyword_query = Q(OrderID__icontains=keyword) | Q(Description__icontains=keyword)
|
||
query &= keyword_query
|
||
|
||
# 5. 执行查询,按创建时间倒序
|
||
dingdan_query = Order.query.filter(query).order_by('-CreateTime')
|
||
|
||
# 只选择需要的字段,提高性能
|
||
dingdan_query = dingdan_query.only(
|
||
'OrderID',
|
||
'Status',
|
||
'PlayerCommission', # 打手分成金额
|
||
'Description',
|
||
'ImageURL',
|
||
'Nickname',
|
||
'CreateTime'
|
||
)
|
||
|
||
total_count = dingdan_query.count()
|
||
|
||
# 6. 分页处理
|
||
start_index = (page - 1) * page_size
|
||
end_index = start_index + page_size
|
||
current_page_orders = dingdan_query[start_index:end_index]
|
||
current_page_count = len(current_page_orders)
|
||
|
||
# 7. 构建返回数据列表
|
||
order_list = []
|
||
for order in current_page_orders:
|
||
order_data = {
|
||
'dingdan_id': order.OrderID,
|
||
'zhuangtai': order.Status,
|
||
'jine': float(order.PlayerCommission) if order.PlayerCommission else 0.0,
|
||
'jieshao': order.Description or '',
|
||
'tupian': order.ImageURL or '',
|
||
'nicheng': order.Nickname or '',
|
||
**datetime_aliases(order.CreateTime),
|
||
}
|
||
order_list.append(order_data)
|
||
|
||
# 8. 判断是否还有更多数据
|
||
has_more = (current_page_count == page_size) and (end_index < total_count)
|
||
if total_count == 0:
|
||
has_more = False
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': {
|
||
'list': order_list,
|
||
'has_more': has_more,
|
||
'total': total_count
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"打手订单获取失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
class DashouCOSZhengshuView(APIView):
|
||
"""打手获取COS上传凭证接口(调试版本)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 接收参数(兼容 OrderID / dingdan_id)
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
yongtu = request.data.get('yongtu', 'dingdan')
|
||
|
||
# 2. 条件判断修正:除了 fakuan 之外,其他用途均需提供 dingdan_id
|
||
if yongtu != 'fakuan' and not dingdan_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '订单ID不能为空', 'error': 'dingdan_id is required when yongtu is not fakuan'},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 3. 业务验证逻辑(根据用途分别验证)
|
||
if yongtu == 'chufa':
|
||
try:
|
||
chufa_record = PenaltyRecord.query.filter(
|
||
OrderID=dingdan_id,
|
||
PlayerID=request.user.UserUID,
|
||
ApplyStatus__in=[0, 3]
|
||
).first()
|
||
if not chufa_record:
|
||
return Response(
|
||
{'code': 403, 'msg': '处罚记录无权限或状态不对',
|
||
'error': f'No punishment record found for user {request.user.UserUID} on order {dingdan_id}'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"查处罚记录异常: {str(e)}")
|
||
return Response(
|
||
{'code': 500, 'msg': '服务器内部错误', 'error': str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
elif yongtu == 'fakuan':
|
||
# 罚款申诉不需要订单验证
|
||
pass
|
||
elif yongtu in ('fakuan_evidence', 'fakuan_evidence_draft'):
|
||
# 商家或子客服(总管等)上传/补传罚单证据图
|
||
from merchant_ops.helpers import (
|
||
PenaltyEvidenceAuthError,
|
||
resolve_penalty_evidence_upload_auth,
|
||
)
|
||
if not dingdan_id:
|
||
return Response({'code': 400, 'msg': '缺少订单ID'}, status=400)
|
||
fadan_id = request.data.get('fadan_id') if yongtu == 'fakuan_evidence' else None
|
||
if yongtu == 'fakuan_evidence' and not fadan_id:
|
||
return Response(
|
||
{'code': 400, 'msg': '缺少订单ID或罚单ID'},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
try:
|
||
resolve_penalty_evidence_upload_auth(request, dingdan_id, fadan_id=fadan_id)
|
||
except PenaltyEvidenceAuthError as e:
|
||
return Response({'code': e.code, 'msg': e.msg}, status=403)
|
||
else:
|
||
# 默认为订单相关,需要验证订单且打手必须接单
|
||
try:
|
||
dingdan = Order.query.get(
|
||
OrderID=dingdan_id,
|
||
PlayerID=request.user.UserUID,
|
||
Status__in=[2, 8]
|
||
)
|
||
except Order.DoesNotExist:
|
||
return Response(
|
||
{'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.UserUID}'},
|
||
status=status.HTTP_403_FORBIDDEN
|
||
)
|
||
|
||
# 4. 获取腾讯云STS临时密钥
|
||
try:
|
||
# 初始化固定凭证
|
||
cred = credential.Credential(
|
||
settings.COS_SECRET_ID,
|
||
settings.COS_SECRET_KEY
|
||
)
|
||
|
||
# 创建STS客户端
|
||
http_profile = HttpProfile()
|
||
http_profile.endpoint = "sts.tencentcloudapi.com"
|
||
client_profile = ClientProfile()
|
||
client_profile.httpProfile = http_profile
|
||
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
|
||
|
||
# 构建策略资源
|
||
resources = []
|
||
if yongtu == 'chufa':
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/dssstp/*"
|
||
)
|
||
elif yongtu == 'fakuan':
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/dashoufakuan/dashoufakuanshensu/*"
|
||
)
|
||
elif yongtu == 'fakuan_evidence':
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/shangjiafakuan/zhengju/*"
|
||
)
|
||
elif yongtu == 'fakuan_evidence_draft':
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/shangjiafakuan/zhengju/*"
|
||
)
|
||
else:
|
||
resources.append(
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
|
||
)
|
||
|
||
policy = {
|
||
"version": "2.0",
|
||
"statement": [{
|
||
"effect": "allow",
|
||
"action": [
|
||
"cos:PutObject",
|
||
"cos:PostObject",
|
||
"cos:InitiateMultipartUpload",
|
||
"cos:UploadPart",
|
||
"cos:CompleteMultipartUpload"
|
||
],
|
||
"resource": resources
|
||
}]
|
||
}
|
||
|
||
req = models.GetFederationTokenRequest()
|
||
req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
|
||
req.Policy = json.dumps(policy)
|
||
req.DurationSeconds = 7200
|
||
|
||
resp = client.GetFederationToken(req)
|
||
credentials = resp.Credentials
|
||
|
||
except TencentCloudSDKException as e:
|
||
logger.error(f"获取STS临时密钥失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'获取上传凭证失败: {str(e)}',
|
||
'error_detail': str(e)
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 5. 构建响应
|
||
current_time = int(time.time())
|
||
upload_dir = (
|
||
'fakuan/shangjiafakuan/zhengju/' if yongtu == 'fakuan_evidence' else
|
||
'fakuan/dashoufakuan/dashoufakuanshensu/' if yongtu == 'fakuan' else (
|
||
'a_long/chfajltp/dssstp/' if yongtu == 'chufa' else f'order/{dingdan_id}/'
|
||
)
|
||
)
|
||
response_data = {
|
||
'credentials': {
|
||
'tmpSecretId': credentials.TmpSecretId,
|
||
'tmpSecretKey': credentials.TmpSecretKey,
|
||
'sessionToken': credentials.Token,
|
||
},
|
||
'startTime': current_time,
|
||
'expiredTime': current_time + 7200,
|
||
'bucket': settings.COS_BUCKET,
|
||
'region': settings.COS_REGION,
|
||
'upload_dir': upload_dir,
|
||
}
|
||
return Response({'code': 0, 'msg': '成功', 'data': response_data})
|
||
|
||
except Exception as e:
|
||
# 捕捉所有未预期的异常,返回完整 traceback
|
||
logger.error(f'COS凭证接口未知错误: {traceback.format_exc()}')
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'服务器内部错误: {str(e)}',
|
||
'error_detail': traceback.format_exc()
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class ShangjiaCOSZhengshuView(APIView):
|
||
"""商家获取COS上传凭证接口(用于处罚申诉图片上传)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 接收参数
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
yongtu = request.data.get('yongtu', 'chufa') # 用途:chufa=处罚申诉
|
||
|
||
if not dingdan_id:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 🔴【修改】验证商家身份 - 通过反向查询shop_profile判断是否为商家
|
||
try:
|
||
# 检查是否有商家扩展表
|
||
shangjia_profile = request.user.ShopProfile
|
||
|
||
# 验证商家状态是否为1(正常)
|
||
if shangjia_profile.zhuangtai != 1:
|
||
return Response({'code': 3, 'msg': '商家账号状态异常,无法操作', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 🔴【重要】获取商家ID(从商家扩展表中获取)
|
||
shangjia_id = request.user.UserUID # 假设商家扩展表中有yonghuid字段
|
||
|
||
except AttributeError:
|
||
# 没有shop_profile属性,说明不是商家
|
||
return Response({'code': 3, 'msg': '非商家用户无权限', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 🔴【修改】验证订单权限 - 通过商家订单扩展表验证
|
||
try:
|
||
# 获取订单
|
||
dingdan = Order.query.get(OrderID=dingdan_id)
|
||
|
||
# 🔴【重要】通过反向查询商家扩展表验证订单是否属于当前商家
|
||
try:
|
||
shangjia_kuozhan = dingdan.shangjia_kuozhan # 使用反向关系名
|
||
|
||
# 验证商家ID是否匹配
|
||
if str(shangjia_kuozhan.MerchantID) != str(shangjia_id):
|
||
return Response({'code': 2, 'msg': '订单不属于当前商家', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 验证订单状态是否允许申请处罚(根据前端逻辑)
|
||
allowed_zhuangtai = [2, 4, 5, 6, 8]
|
||
if dingdan.Status not in allowed_zhuangtai:
|
||
return Response({'code': 4, 'msg': '当前订单状态不允许申请处罚', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 🔴【新增】检查是否已存在处罚申请(通过Chufajilu模型)
|
||
# 注意:这里假设Chufajilu模型存在,并且有shangjia_id字段
|
||
try:
|
||
existing_chufa = PenaltyRecord.query.filter(
|
||
OrderID=dingdan_id,
|
||
ApplicantID=shangjia_id,
|
||
ApplyStatus__in=[0, 1] # 审核中或已处罚状态
|
||
).exists()
|
||
|
||
if existing_chufa:
|
||
return Response({'code': 5, 'msg': '该订单已有处罚申请,请勿重复上传', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
except ImportError:
|
||
# 如果没有Chufajilu模型,跳过这个检查
|
||
pass
|
||
|
||
except MerchantOrderExt.DoesNotExist:
|
||
# 没有商家扩展记录,说明这不是商家订单
|
||
return Response({'code': 2, 'msg': '非商家订单无法操作', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
except Order.DoesNotExist:
|
||
return Response({'code': 2, 'msg': '订单不存在', 'data': None},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
logger.error(f"订单验证异常: {str(e)}")
|
||
return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 4. 获取腾讯云STS临时密钥
|
||
try:
|
||
# 初始化固定凭证
|
||
cred = credential.Credential(
|
||
settings.COS_SECRET_ID,
|
||
settings.COS_SECRET_KEY
|
||
)
|
||
|
||
# 创建STS客户端
|
||
http_profile = HttpProfile()
|
||
http_profile.endpoint = "sts.tencentcloudapi.com"
|
||
client_profile = ClientProfile()
|
||
client_profile.httpProfile = http_profile
|
||
|
||
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
|
||
|
||
# 🔴【修改】构建策略 - 只允许上传到商家处罚证据图片目录
|
||
resources = [
|
||
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/sjzjtp/*"
|
||
]
|
||
|
||
policy = {
|
||
"version": "2.0",
|
||
"statement": [
|
||
{
|
||
"effect": "allow",
|
||
"action": [
|
||
"cos:PutObject",
|
||
"cos:PostObject",
|
||
"cos:InitiateMultipartUpload",
|
||
"cos:UploadPart",
|
||
"cos:CompleteMultipartUpload"
|
||
],
|
||
"resource": resources
|
||
}
|
||
]
|
||
}
|
||
|
||
# 请求STS临时凭证
|
||
req = models.GetFederationTokenRequest()
|
||
# 🔴【修改】命名包含商家ID
|
||
req.Name = f"shangjia_{shangjia_id}_{dingdan_id}_{int(time.time())}"
|
||
req.Policy = json.dumps(policy)
|
||
req.DurationSeconds = 7200 # 2小时有效期
|
||
|
||
resp = client.GetFederationToken(req)
|
||
|
||
# 提取临时密钥
|
||
credentials = resp.Credentials
|
||
|
||
except TencentCloudSDKException as e:
|
||
logger.error(f"商家获取STS临时密钥失败: {str(e)}")
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'获取上传凭证失败: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
# 5. 构建响应数据
|
||
current_time = int(time.time())
|
||
response_data = {
|
||
'credentials': {
|
||
'tmpSecretId': credentials.TmpSecretId,
|
||
'tmpSecretKey': credentials.TmpSecretKey,
|
||
'sessionToken': credentials.Token,
|
||
},
|
||
'startTime': current_time,
|
||
'expiredTime': current_time + 7200,
|
||
'bucket': settings.COS_BUCKET,
|
||
'region': settings.COS_REGION,
|
||
# 🔴【修改】商家专用上传目录
|
||
'upload_dir': 'a_long/chfajltp/sjzjtp/',
|
||
# 返回商家ID和订单ID,便于前端生成文件名
|
||
'dingdan_id': dingdan_id,
|
||
'shangjia_id': shangjia_id,
|
||
# 返回商家昵称,便于前端显示
|
||
'shangjia_nicheng': shangjia_profile.nicheng if hasattr(shangjia_profile, 'nicheng') else ''
|
||
}
|
||
|
||
# 6. 返回成功
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f'商家COS凭证接口未知错误: {str(e)}', exc_info=True)
|
||
return Response({
|
||
'code': 500,
|
||
'msg': f'服务器内部错误: {str(e)}',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class DashouTijiaoView(APIView):
|
||
"""打手提交订单完成接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取参数
|
||
data = request.data
|
||
dingdan_id = str(data.get('OrderID') or data.get('dingdan_id') or '').strip()
|
||
liuyan = data.get('liuyan', '')
|
||
jiaofu_tupian_urls = data.get('jiaofu_tupian_urls', [])
|
||
|
||
# 2. 参数验证
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not isinstance(jiaofu_tupian_urls, list):
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '图片URL列表格式错误',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 限制图片数量(最多9张,最少1张)
|
||
if len(jiaofu_tupian_urls) == 0:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '至少需要上传一张图片',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if len(jiaofu_tupian_urls) > 9:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '最多只能上传9张图片',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 留言长度限制(50字)
|
||
if liuyan and len(liuyan) > 50:
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '留言最多50个字',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 3. 获取当前用户并验证打手身份
|
||
current_user = request.user
|
||
yonghuid = current_user.UserUID
|
||
|
||
try:
|
||
dashou_profile = current_user.DashouProfile
|
||
except UserDashou.DoesNotExist:
|
||
return Response({
|
||
'code': 6,
|
||
'msg': '用户不是打手身份',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 4. 验证订单状态和权限
|
||
try:
|
||
dingdan = Order.query.get(
|
||
OrderID=dingdan_id,
|
||
PlayerID=yonghuid,
|
||
Status=2 # 必须是进行中的订单
|
||
)
|
||
except Order.DoesNotExist:
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '订单不存在或没有提交权限',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 5. 存储图片到Dashoutupian表
|
||
for tupian_url in jiaofu_tupian_urls:
|
||
# 创建打手提交图片记录
|
||
PlayerDeliveryImage.query.create(
|
||
OrderID=dingdan_id,
|
||
PlayerID=yonghuid,
|
||
ImageURL=tupian_url,
|
||
CreateTime=timezone.now()
|
||
)
|
||
|
||
# 6. 更新订单主表
|
||
dingdan.PlayerRemark = liuyan
|
||
dingdan.Status = 8 # 状态改为结算中
|
||
dingdan.UpdateTime = timezone.now()
|
||
dingdan.save()
|
||
|
||
# 7. 更新打手扩展表状态
|
||
dashou_profile.zhuangtai = 1 # 改为数字1(根据你之前说的)
|
||
dashou_profile.save()
|
||
|
||
# ========== 新增:记录打手提交时间 ==========
|
||
record_dashou_biaoxian(
|
||
dingdan_id=dingdan_id,
|
||
dashou_id=yonghuid,
|
||
xingwei=2, # 提交
|
||
tijiao_time=timezone.now()
|
||
)
|
||
# ===========================================
|
||
|
||
# 8. 返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '订单提交成功',
|
||
'data': {
|
||
'dingdan_id': dingdan_id,
|
||
'new_zhuangtai': 8, # 新的订单状态
|
||
'dashou_zhuangtai': 1 # 打手新的状态
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"打手提交订单失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 8,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
class DashouDingdanXiangqingView(APIView):
|
||
"""打手订单详情获取接口"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
# 1. 获取订单ID
|
||
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
|
||
if not dingdan_id:
|
||
return Response({
|
||
'code': 1,
|
||
'msg': '订单ID不能为空',
|
||
'data': None
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 2. 获取当前用户并验证权限
|
||
current_user = request.user
|
||
yonghuid = current_user.UserUID
|
||
|
||
try:
|
||
dashou_profile = current_user.DashouProfile
|
||
except UserDashou.DoesNotExist:
|
||
return Response({
|
||
'code': 2,
|
||
'msg': '用户不是打手身份',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 3. 查询订单主表
|
||
try:
|
||
dingdan = Order.query.get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
return Response({
|
||
'code': 3,
|
||
'msg': '订单不存在',
|
||
'data': None
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 4. 验证订单是否属于当前打手
|
||
if dingdan.PlayerID != yonghuid:
|
||
return Response({
|
||
'code': 4,
|
||
'msg': '没有权限查看此订单',
|
||
'data': None
|
||
}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 5. 构建基础订单数据
|
||
dingdan_data = {
|
||
'dingdan_id': dingdan.OrderID,
|
||
'zhuangtai': dingdan.Status,
|
||
'jine': float(dingdan.PlayerCommission) if dingdan.PlayerCommission else 0.0,
|
||
'jieshao': dingdan.Description or '',
|
||
'tupian': dingdan.ImageURL or '',
|
||
'nicheng': dingdan.Nickname or '',
|
||
'beizhu': dingdan.Remark or '',
|
||
**datetime_aliases(dingdan.CreateTime),
|
||
'tuikuan_liyou':dingdan.RefundReason or '',
|
||
'dashou_liuyan': dingdan.PlayerRemark or '',
|
||
'fuwudashou_id': dingdan.PlayerID or '',
|
||
'fadanpingtai': dingdan.Platform or ''
|
||
}
|
||
|
||
# 6. 查询打手提交的图片
|
||
dashou_tupian_list = PlayerDeliveryImage.query.filter(
|
||
OrderID=dingdan_id,
|
||
PlayerID=yonghuid
|
||
).order_by('CreateTime')
|
||
|
||
jiaofu_tupian_urls = []
|
||
for tupian_record in dashou_tupian_list:
|
||
if tupian_record.ImageURL:
|
||
jiaofu_tupian_urls.append(tupian_record.ImageURL)
|
||
|
||
dingdan_data['dashou_jiaofu'] = jiaofu_tupian_urls
|
||
|
||
# 7. 查询商家扩展表信息
|
||
shangjia_info = {}
|
||
try:
|
||
shangjia_kuozhan = dingdan.shangjia_kuozhan
|
||
shangjia_info = {
|
||
'shangjia_id': shangjia_kuozhan.MerchantID or '',
|
||
'shangjia_nicheng': shangjia_kuozhan.MerchantNickname or '',
|
||
'shangjia_liuyan': shangjia_kuozhan.MerchantReview or '',
|
||
#'tuikuan_liyou': '',
|
||
'chufa_liyou': shangjia_kuozhan.PenaltyReason or '',
|
||
'chufa_zhuangtai': shangjia_kuozhan.PenaltyApplyStatus or 0,
|
||
#'chufa_jieguo': shangjia_kuozhan.chufa_jieguo or '',
|
||
'bohui_liyou': shangjia_kuozhan.RejectReason or ''
|
||
}
|
||
except MerchantOrderExt.DoesNotExist:
|
||
# 没有商家扩展信息,返回空值
|
||
shangjia_info = {
|
||
'shangjia_id': '',
|
||
'shangjia_nicheng': '',
|
||
'shangjia_liuyan': '',
|
||
'tuikuan_liyou': '',
|
||
'chufa_liyou': '',
|
||
'chufa_zhuangtai': 0,
|
||
'chufa_jieguo': '',
|
||
'bohui_liyou': ''
|
||
}
|
||
|
||
# 8. 查询平台扩展表信息(如果有的话)
|
||
pingtai_info = {}
|
||
try:
|
||
pingtai_kuozhan = dingdan.pingtai_kuozhan
|
||
pingtai_info = {
|
||
'laoban_id': pingtai_kuozhan.BossID or '',
|
||
'laoban_pingjia': pingtai_kuozhan.BossReview or ''
|
||
}
|
||
except PlatformOrderExt.DoesNotExist:
|
||
pingtai_info = {
|
||
'laoban_id': '',
|
||
'laoban_pingjia': ''
|
||
}
|
||
|
||
# 9. 合并所有数据
|
||
response_data = {
|
||
**dingdan_data,
|
||
**shangjia_info,
|
||
**pingtai_info
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '成功',
|
||
'data': response_data
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取打手订单详情失败: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
'code': 5,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class DashouHuoquLeixingView(APIView):
|
||
"""
|
||
打手获取商品类型接口 - 抢单页面使用
|
||
接口地址:POST /dingdan/dsqdhqddlx
|
||
功能:返回所有正常的商品类型,已按排序权重排序
|
||
|
||
返回格式(与前端原数据格式完全一致):
|
||
{
|
||
"code": 200,
|
||
"msg": "成功",
|
||
"data": {
|
||
"list": [
|
||
{
|
||
"id": 1,
|
||
"jieshao": "王者荣耀",
|
||
"tupian_url": "leixing/wzry.jpg",
|
||
"yaoqiuleixing": null,
|
||
"huiyuan_id": null,
|
||
"yongjin": null,
|
||
"paixu": 100,
|
||
"shenhezhuangtai": 1
|
||
},
|
||
// ... 更多类型
|
||
]
|
||
}
|
||
}
|
||
"""
|
||
|
||
# 🔴 使用JWT Token认证
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
"""
|
||
处理POST请求,返回商品类型列表
|
||
|
||
请求参数:无特殊参数,只验证用户身份
|
||
|
||
安全验证:
|
||
1. JWT Token验证用户身份
|
||
2. 只返回 shenhezhuangtai=1(正常状态)的类型
|
||
3. 数据已排序:paixu降序 → id升序
|
||
"""
|
||
try:
|
||
logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.UserUID}")
|
||
|
||
from jituan.services.club_context import resolve_effective_club_id
|
||
from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api
|
||
|
||
club_id = resolve_effective_club_id(request, request.user)
|
||
leixing_list = build_club_leixing_list_for_api(club_id)
|
||
|
||
logger.info(f"返回商品类型数据:{len(leixing_list)}条,俱乐部:{club_id} 用户: {request.user.UserUID}")
|
||
|
||
# 🔴 返回成功响应
|
||
# 注意:保持与前端原数据格式完全一致
|
||
return Response({
|
||
"code": 200,
|
||
"msg": "成功",
|
||
"data": {
|
||
"list": leixing_list # 🔴 保持原字段名 "list"
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
# 🔴 异常处理
|
||
logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.UserUID}, 错误: {str(e)}", exc_info=True)
|
||
|
||
return Response({
|
||
"code": 500,
|
||
"msg": f"服务器内部错误: {str(e)}",
|
||
"data": None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class DashouXiugaiView(APIView):
|
||
"""
|
||
打手修改订单交付内容接口
|
||
请求方式:POST
|
||
请求路径:/dingdan/dsxiugaidd
|
||
权限:需携带有效JWT Token,且用户为打手身份
|
||
请求参数:
|
||
dingdan_id : 订单ID(必填)
|
||
liuyan : 新的留言内容(可选,最长50字)
|
||
deleted_images : 要删除的图片URL列表(数组,每个元素为完整COS URL)
|
||
new_images : 新增的图片URL列表(数组,每个元素为完整COS URL)
|
||
返回:
|
||
成功:{"code":0,"msg":"修改成功","data":null}
|
||
失败:返回相应错误码和提示
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
# 获取请求数据
|
||
data = request.data
|
||
dingdan_id = str(data.get('OrderID') or data.get('dingdan_id') or '').strip()
|
||
liuyan = data.get('liuyan', '')
|
||
deleted_images = data.get('deleted_images', [])
|
||
new_images = data.get('new_images', [])
|
||
|
||
# ---------- 参数校验 ----------
|
||
if not dingdan_id:
|
||
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not isinstance(deleted_images, list):
|
||
return Response({'code': 2, 'msg': 'deleted_images 必须为数组', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not isinstance(new_images, list):
|
||
return Response({'code': 3, 'msg': 'new_images 必须为数组', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if liuyan and len(liuyan) > 50:
|
||
return Response({'code': 4, 'msg': '留言最多50个字', 'data': None},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# ---------- 用户身份验证 ----------
|
||
current_user = request.user
|
||
yonghuid = current_user.UserUID
|
||
|
||
# 检查是否为打手
|
||
try:
|
||
dashou_profile = current_user.DashouProfile # User 模型反向 related_name='DashouProfile'
|
||
except UserDashou.DoesNotExist:
|
||
return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# ---------- 订单查询及权限验证 ----------
|
||
try:
|
||
# 订单必须属于该打手,且状态为4(退款中)或8(结算中)
|
||
dingdan = Order.query.get(
|
||
OrderID=dingdan_id,
|
||
PlayerID=yonghuid,
|
||
Status__in=[4, 8] # 只允许修改退款中或结算中的订单
|
||
)
|
||
except Order.DoesNotExist:
|
||
return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
|
||
status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# ---------- 执行修改(使用事务保证原子性) ----------
|
||
try:
|
||
with transaction.atomic():
|
||
# 1. 处理删除的图片:从 Dashoutupian 表中删除记录
|
||
if deleted_images:
|
||
# 根据订单ID、打手ID和图片URL删除记录
|
||
delete_count, _ = PlayerDeliveryImage.query.filter(
|
||
OrderID=dingdan_id,
|
||
PlayerID=yonghuid,
|
||
ImageURL__in=deleted_images
|
||
).delete()
|
||
# 可选:记录删除数量
|
||
logger.info(f"订单 {dingdan_id} 删除图片 {delete_count} 张")
|
||
|
||
# 2. 处理新增的图片:插入新记录
|
||
if new_images:
|
||
# 批量创建
|
||
new_records = []
|
||
for url in new_images:
|
||
new_records.append(PlayerDeliveryImage(
|
||
OrderID=dingdan_id,
|
||
PlayerID=yonghuid,
|
||
ImageURL=url,
|
||
CreateTime=timezone.now()
|
||
))
|
||
PlayerDeliveryImage.objects.bulk_create(new_records)
|
||
logger.info(f"订单 {dingdan_id} 新增图片 {len(new_records)} 张")
|
||
|
||
# 3. 更新订单留言(如果发生变化)
|
||
# 注意:即使留言为空字符串,也允许更新,表示清空留言
|
||
if 'liuyan' in data: # 判断是否传了liuyan字段(可能为空字符串)
|
||
dingdan.PlayerRemark = liuyan
|
||
dingdan.UpdateTime = timezone.now()
|
||
dingdan.save(update_fields=['PlayerRemark', 'UpdateTime'])
|
||
|
||
# 全部成功,返回成功响应
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '修改成功',
|
||
'data': None
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
logger.error(f"修改订单失败: {str(e)}", exc_info=True)
|
||
return Response({
|
||
'code': 7,
|
||
'msg': '服务器内部错误',
|
||
'data': None
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
|