""" 排行榜接口 — 基于 houtai 日统计表聚合查询 POST /yonghu/phbhqsj """ import logging from datetime import date, timedelta from decimal import Decimal from django.db.models import Sum from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from backend.models import ( PlayerDailyStats, ManagerDailyStats, LeaderDailyStats, MerchantDailyStats, ) from users.models import UserDashou, UserShangjia, UserBoss from gvsdsdk.models import User logger = logging.getLogger(__name__) MAX_RANK = 50 RIQI_OPTIONS = frozenset({'今日', '本周', '本月', '总榜', '昨日', '上周', '上月'}) SHENFEN_OPTIONS = frozenset({'dashou', 'guanshi', 'zuzhang', 'shangjia'}) ROLE_CONFIG = { 'dashou': { 'model': PlayerDailyStats, 'id_field': 'PlayerID', 'sort_field': 'CompletedAmount', 'fields': ('AcceptedOrderTotal', 'CompletedOrderTotal', 'AcceptedAmount', 'CompletedAmount'), 'int_fields': frozenset({'AcceptedOrderTotal', 'CompletedOrderTotal'}), }, 'guanshi': { 'model': ManagerDailyStats, 'id_field': 'ManagerID', 'sort_field': 'TotalIncome', 'fields': ('InvitedPlayerCount', 'RechargedPlayerCount', 'TotalIncome'), 'int_fields': frozenset({'InvitedPlayerCount', 'RechargedPlayerCount'}), }, 'zuzhang': { 'model': LeaderDailyStats, 'id_field': 'LeaderID', 'sort_field': 'TotalIncome', 'fields': ('InvitedManagerCount', 'CommissionAmount', 'TotalIncome'), 'int_fields': frozenset({'InvitedManagerCount'}), }, 'shangjia': { 'model': MerchantDailyStats, 'id_field': 'MerchantID', 'sort_field': 'SettledAmount', 'fields': ('AssignedOrderCount', 'AssignedAmount', 'SettledOrderCount', 'SettledAmount'), 'int_fields': frozenset({'AssignedOrderCount', 'SettledOrderCount'}), }, } def _resolve_date_range(riqi: str): """根据前端 riqi 参数返回 (start_date, end_date) 闭区间。""" today = date.today() if riqi == '今日': return today, today if riqi == '昨日': d = today - timedelta(days=1) return d, d if riqi == '本周': monday = today - timedelta(days=today.weekday()) return monday, today if riqi == '上周': this_monday = today - timedelta(days=today.weekday()) last_sunday = this_monday - timedelta(days=1) last_monday = last_sunday - timedelta(days=6) return last_monday, last_sunday if riqi == '本月': return today.replace(day=1), today if riqi == '上月': first_this_month = today.replace(day=1) last_day_prev = first_this_month - timedelta(days=1) return last_day_prev.replace(day=1), last_day_prev return None def _fmt_value(val, is_int: bool): if val is None: return 0 if is_int else 0.0 if is_int: return int(val) if isinstance(val, Decimal): return float(val.quantize(Decimal('0.01'))) return float(val) class PhbHqsjView(APIView): """ 排行榜数据接口 POST /yonghu/phbhqsj 请求参数: shenfen: dashou | guanshi | zuzhang | shangjia riqi: 今日 | 本周 | 本月 | 总榜 | 昨日 | 上周 | 上月 响应 data.list[](最多50条,已按各身份排序字段降序): mingci, yonghuid, nicheng, touxiang + 各身份统计字段 """ permission_classes = [IsAuthenticated] def post(self, request): shenfen = (request.data.get('shenfen') or '').strip() riqi = (request.data.get('riqi') or '').strip() if shenfen not in SHENFEN_OPTIONS: return Response({ 'code': 400, 'msg': '参数错误:shenfen 必须为 dashou/guanshi/zuzhang/shangjia', 'data': {'list': []}, }) if riqi not in RIQI_OPTIONS: return Response({ 'code': 400, 'msg': '参数错误:riqi 必须为 今日/本周/本月/总榜/昨日/上周/上月', 'data': {'list': []}, }) try: if riqi == '总榜': rows = self._query_rank_rows(shenfen, all_time=True) else: date_range = _resolve_date_range(riqi) if date_range is None: return Response({ 'code': 400, 'msg': '参数错误:无效的日期范围', 'data': {'list': []}, }) rows = self._query_rank_rows(shenfen, date_range[0], date_range[1]) nick_map, avatar_map = self._load_user_display(rows, shenfen) cfg = ROLE_CONFIG[shenfen] int_fields = cfg['int_fields'] id_field = cfg['id_field'] result_list = [] for idx, row in enumerate(rows): yonghuid = row[id_field] item = { 'mingci': idx + 1, 'yonghuid': yonghuid, 'nicheng': nick_map.get(yonghuid, '用户'), 'touxiang': avatar_map.get(yonghuid, '') or '', } for field in cfg['fields']: item[field] = _fmt_value(row.get(field), field in int_fields) result_list.append(item) return Response({ 'code': 200, 'msg': '获取成功', 'data': {'list': result_list}, }) except Exception as e: logger.error('排行榜查询失败 shenfen=%s riqi=%s: %s', shenfen, riqi, e, exc_info=True) return Response({ 'code': 500, 'msg': '服务器内部错误', 'data': {'list': []}, }) def _query_rank_rows(self, shenfen, start_date=None, end_date=None, all_time=False): cfg = ROLE_CONFIG[shenfen] model = cfg['model'] sort_field = cfg['sort_field'] fields = cfg['fields'] id_field = cfg['id_field'] annotate_kwargs = {f: Sum(f) for f in fields} if all_time: rows = ( model.objects .values(id_field) .annotate(**annotate_kwargs) .order_by(f'-{sort_field}')[:MAX_RANK] ) return list(rows) if start_date == end_date: qs = model.objects.filter(Date=start_date).order_by(f'-{sort_field}')[:MAX_RANK] return [ {id_field: getattr(r, id_field), **{f: getattr(r, f) for f in fields}} for r in qs ] rows = ( model.objects .filter(Date__gte=start_date, Date__lte=end_date) .values(id_field) .annotate(**annotate_kwargs) .order_by(f'-{sort_field}')[:MAX_RANK] ) return list(rows) def _load_user_display(self, rows, shenfen): id_field = ROLE_CONFIG[shenfen]['id_field'] yonghuids = [r[id_field] for r in rows if r.get(id_field)] if not yonghuids: return {}, {} users = User.objects.filter(UserUID__in=yonghuids) avatar_map = {u.UserUID: u.Avatar or '' for u in users} nick_map = {} if shenfen == 'dashou': for p in UserDashou.objects.filter(user__UserUID__in=yonghuids).select_related('user'): nick_map[p.user.UserUID] = p.nicheng or '用户' elif shenfen == 'shangjia': for p in UserShangjia.objects.filter(user__UserUID__in=yonghuids).select_related('user'): nick_map[p.user.UserUID] = p.nicheng or '用户' else: for b in UserBoss.objects.filter(user__UserUID__in=yonghuids).select_related('user'): nick_map[b.user.UserUID] = b.nickname or '用户' return nick_map, avatar_map