"""users.views.kefu_dashou - auto-generated by split script.""" """users.views.kefu - auto-generated by split script.""" import os import sys import json import re import time import uuid import random import string import requests import hashlib import traceback import decimal import jwt import ipaddress import xmltodict from datetime import datetime, timedelta from decimal import Decimal import defusedxml.ElementTree as ET from django.conf import settings from django.db import models, transaction, IntegrityError, DatabaseError, connection from django.db.models import Q, F, Sum, Count, Case, When, IntegerField, Prefetch from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import get_object_or_404 from django.utils import timezone from django.http import HttpResponse from django.views import View from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.hashers import make_password 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 MultiPartParser, FormParser, JSONParser from rest_framework.throttling import AnonRateThrottle from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.authentication import JWTAuthentication from gvsdsdk.fluent import db, func, FQ from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss from utils.money import yuan_to_fen from utils.fadan_utils import check_fadan_qiangdan_eligible from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode from users.fadan_fenhong_utils import process_fadan_fenhong from orders.utils import ( update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from backend.utils import ( update_dashou_daily_by_action, update_guanshi_daily_by_action, update_shangjia_daily, update_zuzhang_daily_by_action, verify_kefu_permission ) from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan from ..models import ( UserBoss, UserDashou, UserShangjia, UserGuanshi, UserZuzhang, UserKefu, AdminProfile, OfficialAccountUser, TixianAutoRecord, TixianShenheJilu, Tixianjilu, Xiugaijilu, RankingRecord ) from users.business_models import User from ..tixian_shenhe_services import ( load_audit_meta_map, refund_balance, sync_audit_and_jilu_status, mark_transfer_success, release_collect_quota_for_record, close_audit_wechat_failed, query_wechat_transfer_bill, check_shijidaozhang_within_limit, check_dashou_trial_blocks_commission_withdraw, WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING, ) from ..tixian_shenhe_views import process_audit_collect from orders.models import ( Order, PlatformOrderExt, MerchantOrderExt, CommissionRate, PenaltyRecord, PenaltyEvidenceImage, RefundRecord, PlayerDeliveryImage, Penalty, PenaltyAppealImage ) from products.models import ( ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu ) from config.models import Qunpeizhi from backend.models import MerchantDailyStats from rank.models import ( KaohePayTemp, Chenghao, ShenheJilu, KaoheCishuFeiyong, YonghuChenghao ) from users.models import UserShenheguan from products.utils import update_shangpin_daily_stat from shop.utils import update_dianpu_daily_stat from rank.utils import create_shenhe_jilu_from_temp import logging logger = logging.getLogger(__name__) class KefuGetDashouListView(APIView): """ 客服获取打手列表接口(仅打手类型) 请求:POST /yonghu/kefuhqdslb 参数:{ "phone": "客服手机号", "keyword": "搜索关键词(可选,搜索用户ID或昵称)", "page": "页码(从1开始)", "page_size": "每页数量" } 认证:JWT 返回:{ "code": 0, "data": { "list": [打手对象], "has_more": true/false } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('zhanghao', '').strip() keyword = request.data.get('keyword', '').strip() try: page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 20)) except ValueError: return Response({'code': 400, 'msg': '分页参数错误'}, status=status.HTTP_400_BAD_REQUEST) if not phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'Phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.UserType not in ('kefu', 'admin'): logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser kefu = None if not is_admin: try: kefu = current_user.KefuProfile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 构建查询条件(只查询打手) # 通过 UserDashou 扩展表关联 User queryset = UserDashou.query.select_related('user').all() if keyword: # 搜索用户ID或打手昵称 queryset = queryset.filter( Q(user__UserUID__icontains=keyword) | Q(nicheng__icontains=keyword) ) # 4. 计算总数并分页 total = queryset.count() offset = (page - 1) * page_size dashou_list = queryset.order_by('-CreateTime')[offset:offset + page_size] # 5. 构建返回数据(只取需要的字段) result = [] for dashou in dashou_list: user = dashou.user result.append({ 'yonghuid': user.UserUID, 'avatar': user.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'nicheng': dashou.nicheng or '', 'zhuangtai': dashou.zhuangtai, }) # 6. 判断是否有更多数据 has_more = (offset + len(dashou_list)) < total return Response({ 'code': 0, 'data': { 'list': result, 'has_more': has_more } }) class KefuGetDashouDetailView(APIView): """ 客服获取打手详情接口 请求:POST /yonghu/kefuhqdsxq 参数:{ "phone": "客服手机号", "uid": "打手ID (yonghuid)" } 认证:JWT 返回:code=0 + 打手信息 + 会员列表 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() uid = request.data.get('uid', '').strip() if not phone or not uid: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'Phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.UserType not in ('kefu', 'admin'): logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser kefu = None if not is_admin: try: kefu = current_user.KefuProfile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询打手主表和扩展表 try: # 使用 select_related 预加载 dashou_profile,减少数据库查询 user_main = User.query.select_related('DashouProfile').get( UserUID=uid ) except User.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}, status=status.HTTP_404_NOT_FOUND) # 验证用户类型是否为打手(可选,但建议验证) # 如果该用户没有打手扩展表,则返回空信息(但前端会显示未注册) try: dashou = user_main.DashouProfile except ObjectDoesNotExist: # 用户存在但未注册打手身份,返回基本信息 user_info = { 'yonghuid': user_main.UserUID, 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': 0, 'zhanghaozhuangtai': 0, 'zhuangtai': 0, 'nicheng': '', 'chenghao': '', 'dianhua': '', 'wechat': '', 'phone': user_main.Phone or '', 'CreateTime': user_main.UserCreateTime, 'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '', 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, 'tuikuanliang': 0, 'jinrijiedan': 0, 'yue': 0.00, 'zonge': 0.00, 'jifen': 0, 'yajin': 0.00, 'jinrishouyi': 0.00, 'jinyueshouyi': 0.00, 'yaoqingren': '', 'jieshao': '', } member_list = [] return Response({ 'code': 0, 'data': { 'user_info': user_info, 'member_list': member_list } }) # 4. 构建打手信息 user_info = { 'yonghuid': user_main.UserUID, 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'zhuangtai': dashou.zhuangtai, 'nicheng': dashou.nicheng or '', 'chenghao': dashou.chenghao or '', 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', 'phone': user_main.Phone or '', 'CreateTime': user_main.UserCreateTime, 'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '', 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, 'jinrijiedan': dashou.jinrijiedan, 'yue': float(dashou.yue) if dashou.yue else 0.00, 'zonge': float(dashou.zonge) if dashou.zonge else 0.00, 'jifen': dashou.jifen, 'yajin': float(dashou.yajin) if dashou.yajin else 0.00, 'jinrishouyi': float(dashou.jinrishouyi) if dashou.jinrishouyi else 0.00, 'jinyueshouyi': float(dashou.jinyueshouyi) if dashou.jinyueshouyi else 0.00, 'yaoqingren': dashou.yaoqingren or '', 'jieshao': dashou.jieshao or '', } # 5. 查询打手会员信息(仅未过期) member_records = list(Huiyuangoumai.query.filter( yonghu_id=uid ).order_by('-CreateTime')) huiyuan_ids = {r.huiyuan_id for r in member_records if r.huiyuan_id} huiyuan_map = { h.huiyuan_id: h.jieshao for h in Huiyuan.query.filter(huiyuan_id__in=huiyuan_ids).only('huiyuan_id', 'jieshao') } member_list = [] for record in member_records: # 调用模型方法检查是否过期 if hasattr(record, 'jiance_shifou_daoqi'): is_expired = record.jiance_shifou_daoqi() else: # 如果方法不存在,简单判断 is_expired = record.daoqi_time < timezone.now() if record.daoqi_time else True if not is_expired: # 获取会员详情 huiyuan_name = huiyuan_map.get(record.huiyuan_id, '未知会员') member_list.append({ 'id': record.id, 'huiyuan_name': huiyuan_name, 'daoqi_time': record.daoqi_time, 'is_active': record.huiyuan_zhuangtai == 1, }) return Response({ 'code': 0, 'data': { 'user_info': user_info, 'member_list': member_list } }) class KefuUpdateDashouView(APIView): permission_classes = [IsAuthenticated] """ 客服修改打手信息接口 支持操作: - jian_yue / jia_yue : 减/加打手余额 (权限001aa/001bb) - jian_yajin / jia_yajin : 减/加打手押金 (权限001aa/001bb) - jian_jifen / jia_jifen : 减/加打手积分 (权限001cc/001dd) - gai_zhanghaozhuangtai : 修改打手账号状态(封禁/解封) (权限001ee) - gai_zaixianzhuangtai : 修改打手在线状态 (权限001ee) - gai_zhuangtai : 修改打手接单状态(空闲/忙碌) (权限001ee) - jia_huiyuan / jian_huiyuan : 加/减会员 (权限001ff/001gg) """ def post(self, request): # 1. 获取前端参数 username = request.data.get('username', '').strip() dashou_id = request.data.get('dashou_id', '').strip() caozuo = request.data.get('caozuo', '').strip() value = request.data.get('value') # 金额、积分、天数等 huiyuan_id = request.data.get('huiyuan_id', '').strip() # 会员ID(加/减会员时使用) if not username or not dashou_id or not caozuo: return Response({'code': 400, 'msg': '参数不完整'}) # 2. 权限校验(统一方法) kefu, permissions = verify_kefu_permission(request, username) if kefu is None: return permissions # 直接返回错误响应 # 3. 查询打手是否存在 try: dashou_user = User.query.select_related('DashouProfile').get(UserUID=dashou_id) dashou_profile = dashou_user.DashouProfile except User.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}) except UserDashou.DoesNotExist: return Response({'code': 404, 'msg': '打手扩展信息缺失'}) # 4. 根据操作类型执行(事务内) with transaction.atomic(): try: # 记录修改前的值(用于日志) before = { 'yue': dashou_profile.yue, 'yajin': dashou_profile.yajin, 'jifen': dashou_profile.jifen, 'zhanghaozhuangtai': dashou_profile.zhanghaozhuangtai, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'zhuangtai': dashou_profile.zhuangtai, } # ---------- 余额操作 ---------- if caozuo == 'jian_yue': if '001aa' not in permissions: return Response({'code': 403, 'msg': '无权限减打手余额'}) amount = Decimal(str(value)) if dashou_profile.yue < amount: return Response({'code': 400, 'msg': '余额不足'}) dashou_profile.yue -= amount dashou_profile.save() after_yue = dashou_profile.yue # 记录日志 Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, # 2=打手 xiugaitijiao=after_yue, xiugaitijiaoq=before['yue'], ) return Response({'code': 0, 'msg': '减余额成功', 'data': {'new_yue': str(after_yue)}}) elif caozuo == 'jia_yue': if '001bb' not in permissions: return Response({'code': 403, 'msg': '无权限加打手余额'}) from jituan.services.group_finance_gate import deny_group_finance_exec deny = deny_group_finance_exec(request.user, request) if deny: return deny amount = Decimal(str(value)) dashou_profile.yue += amount dashou_profile.save() after_yue = dashou_profile.yue Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, xiugaitijiao=after_yue, xiugaitijiaoq=before['yue'], ) return Response({'code': 0, 'msg': '加余额成功', 'data': {'new_yue': str(after_yue)}}) # ---------- 押金操作 ---------- elif caozuo == 'jian_yajin': if '001aa' not in permissions: return Response({'code': 403, 'msg': '无权限减打手押金'}) amount = Decimal(str(value)) if dashou_profile.yajin < amount: return Response({'code': 400, 'msg': '押金不足'}) dashou_profile.yajin -= amount dashou_profile.save() after_yajin = dashou_profile.yajin Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, yajin=after_yajin, yyajin=before['yajin'], ) return Response({'code': 0, 'msg': '减押金成功', 'data': {'new_yajin': str(after_yajin)}}) elif caozuo == 'jia_yajin': if '001bb' not in permissions: return Response({'code': 403, 'msg': '无权限加打手押金'}) from jituan.services.group_finance_gate import deny_group_finance_exec deny = deny_group_finance_exec(request.user, request) if deny: return deny amount = Decimal(str(value)) dashou_profile.yajin += amount dashou_profile.save() after_yajin = dashou_profile.yajin Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, yajin=after_yajin, yyajin=before['yajin'], ) return Response({'code': 0, 'msg': '加押金成功', 'data': {'new_yajin': str(after_yajin)}}) # ---------- 积分操作 ---------- elif caozuo == 'jian_jifen': if '001cc' not in permissions: return Response({'code': 403, 'msg': '无权限减打手积分'}) jifen = int(value) if dashou_profile.jifen < jifen: return Response({'code': 400, 'msg': '积分不足'}) dashou_profile.jifen -= jifen dashou_profile.save() after_jifen = dashou_profile.jifen Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, jifen=after_jifen, yjifen=before['jifen'], ) return Response({'code': 0, 'msg': '减积分成功', 'data': {'new_jifen': after_jifen}}) elif caozuo == 'jia_jifen': if '001dd' not in permissions: return Response({'code': 403, 'msg': '无权限加打手积分'}) jifen = int(value) dashou_profile.jifen += jifen dashou_profile.save() after_jifen = dashou_profile.jifen Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, jifen=after_jifen, yjifen=before['jifen'], ) return Response({'code': 0, 'msg': '加积分成功', 'data': {'new_jifen': after_jifen}}) # ---------- 状态修改 ---------- elif caozuo == 'gai_zhanghaozhuangtai': if '001ee' not in permissions: return Response({'code': 403, 'msg': '无权限修改打手账号状态'}) new_status = int(value) if new_status not in [0, 1]: return Response({'code': 400, 'msg': '状态值无效(0=封禁,1=正常)'}) dashou_profile.zhanghaozhuangtai = new_status dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改账号状态为{new_status}', ) return Response({'code': 0, 'msg': '修改账号状态成功', 'data': {'new_zhanghaozhuangtai': new_status}}) elif caozuo == 'gai_zaixianzhuangtai': if '001ee' not in permissions: return Response({'code': 403, 'msg': '无权限修改打手在线状态'}) new_status = int(value) if new_status not in [0, 1]: return Response({'code': 400, 'msg': '状态值无效(0=离线,1=在线)'}) dashou_profile.zaixianzhuangtai = new_status dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改在线状态为{new_status}', ) return Response({'code': 0, 'msg': '修改在线状态成功', 'data': {'new_zaixianzhuangtai': new_status}}) elif caozuo == 'gai_zhuangtai': if '001ee' not in permissions: return Response({'code': 403, 'msg': '无权限修改打手接单状态'}) new_status = int(value) if new_status not in [0, 1]: return Response({'code': 400, 'msg': '状态值无效(0=忙碌,1=空闲)'}) dashou_profile.zhuangtai = new_status dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改接单状态为{new_status}', ) return Response({'code': 0, 'msg': '修改接单状态成功', 'data': {'new_zhuangtai': new_status}}) # ---------- 会员操作 ---------- elif caozuo == 'jia_huiyuan': if '001ff' not in permissions: return Response({'code': 403, 'msg': '无权限给打手加会员'}) if not huiyuan_id: return Response({'code': 400, 'msg': '缺少会员ID'}) try: huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id) except Huiyuan.DoesNotExist: return Response({'code': 404, 'msg': '会员类型不存在'}) from jituan.services.club_user import get_user_club_id from jituan.services.member_recharge import extend_member_entitlement_admin, get_club_huiyuan_row player_club = get_user_club_id(dashou_user) row = get_club_huiyuan_row(player_club, huiyuan_id, require_enabled=False) days = int(value) if value else int(row.formal_days or 30) if row else 30 record, err = extend_member_entitlement_admin( dashou_id, huiyuan_id, days, player_club, ) if err: return Response({'code': 400, 'msg': err}) Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, huiyuants=days, huiyuan_id=huiyuan_id, qitashuoming=f'加正式会员 {huiyuan_id},增加{days}天(无支付单、无分红)', ) return Response({'code': 0, 'msg': '加会员成功', 'data': {'new_daoqi': record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S')}}) elif caozuo == 'jian_huiyuan': if '001gg' not in permissions: return Response({'code': 403, 'msg': '无权限给打手减会员'}) if not huiyuan_id: return Response({'code': 400, 'msg': '缺少会员ID'}) from jituan.services.member_recharge import revoke_member_entitlement_admin removed, err = revoke_member_entitlement_admin(dashou_id, huiyuan_id) if err: return Response({'code': 404, 'msg': err}) Xiugaijilu.query.create( yonghuid=dashou_id, xiugaiid=kefu.user.Phone, leixing=2, huiyuan_id=huiyuan_id, qitashuoming=f'移除会员 {huiyuan_id}', ) return Response({'code': 0, 'msg': '减会员成功'}) else: return Response({'code': 400, 'msg': '未知操作类型'}) except Exception as e: logger.error(f"修改打手信息异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'})