fix: 总榜按无效人数排行;总会员ID匹配与子类型展开修复

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-11 01:56:39 +08:00
parent fda4ffb628
commit 5556bbdb61
4 changed files with 172 additions and 31 deletions

View File

@@ -71,12 +71,16 @@ def get_bundle_included_ids(club_id, bundle_huiyuan_id):
if not club_id or not bundle_huiyuan_id:
return []
return list(
ClubHuiyuanBundleInclude.query.filter(
club_id=club_id,
bundle_huiyuan_id=bundle_huiyuan_id,
).values_list('included_huiyuan_id', flat=True)
)
for bid in expand_huiyuan_id_variants(bundle_huiyuan_id):
ids = list(
ClubHuiyuanBundleInclude.query.filter(
club_id=club_id,
bundle_huiyuan_id=bid,
).values_list('included_huiyuan_id', flat=True)
)
if ids:
return ids
return []
def _membership_club_candidates(yonghu_id, club_id=None, record_club_id=None):
@@ -123,10 +127,11 @@ def resolve_bundle_included_ids(bundle_huiyuan_id, club_id=None, yonghu_id=None,
def _included_huiyuan_id_set(included_ids):
included_set = set()
for x in included_ids or []:
sx = normalize_huiyuan_id(x)
if sx:
included_set.add(sx)
included_set.add(sx.lstrip('0') or '0')
for variant in expand_huiyuan_id_variants(x):
sx = normalize_huiyuan_id(variant)
if sx:
included_set.add(sx)
included_set.add(sx.lstrip('0') or '0')
return included_set
@@ -159,10 +164,13 @@ def _record_club_id(record, club_id=None):
def is_bundle_huiyuan(huiyuan_id):
try:
return bool(Huiyuan.query.get(huiyuan_id=huiyuan_id).is_bundle)
except Huiyuan.DoesNotExist:
hid = normalize_huiyuan_id(huiyuan_id)
if not hid:
return False
for bid in Huiyuan.query.filter(is_bundle=True).values_list('huiyuan_id', flat=True):
if huiyuan_ids_match(bid, hid):
return True
return False
def _membership_record_active(record):
@@ -188,18 +196,13 @@ def user_has_huiyuan_access(yonghu_id, required_huiyuan_id, club_id=None):
def _record_valid(rec):
return _membership_record_active(rec)
req_set = {req, req.lstrip('0') or '0'}
for rec in Huiyuangoumai.query.filter(yonghu_id=yonghu_id):
if _record_valid(rec) and huiyuan_ids_match(rec.huiyuan_id, req):
if not _record_valid(rec):
continue
if huiyuan_ids_match(rec.huiyuan_id, req):
return True
bundle_ids = set(
Huiyuan.query.filter(is_bundle=True).values_list('huiyuan_id', flat=True)
)
if bundle_ids:
req_set = {req, req.lstrip('0') or '0'}
for rec in Huiyuangoumai.query.filter(yonghu_id=yonghu_id, huiyuan_id__in=bundle_ids):
if not _record_valid(rec):
continue
if is_bundle_huiyuan(rec.huiyuan_id):
included = resolve_bundle_included_ids(
rec.huiyuan_id,
club_id=club_id,

View File

@@ -7,7 +7,8 @@ from decimal import Decimal
from typing import Optional
from django.db import IntegrityError, transaction
from django.db.models import F, Sum
from django.db.models import F, Sum, Value
from django.db.models.functions import Coalesce
from django.utils import timezone
from backend.models import (
@@ -51,6 +52,13 @@ DEFAULT_SORT_FIELD = {
'shangjia': 'jiesuan_jine',
}
# 总榜:管事/组长按无效人数排序展示(其它周期不变)
WUXIAO_TOTAL_RANK_SORT_FIELDS = {
'guanshi': 'wuxiao_yaoqing_dashou_shu',
'zuzhang': 'wuxiao_yaoqing_guanshi_shu',
}
WUXIAO_TOTAL_RANK_SORT_LABEL = '无效人数'
# shenfen -> sort_field_key -> (model, id_field, db_field, is_int)
SORT_FIELD_REGISTRY = {
'dashou': {
@@ -171,6 +179,9 @@ def sort_field_options_for_shenfen(shenfen: str) -> list:
def resolve_sort_field_for_display(club_id: str, shenfen: str, riqi: str) -> tuple[str, str]:
"""按后台奖励方案决定展示/排序字段(方案存在即生效,与是否启用奖励无关)。"""
if riqi == '总榜' and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS:
sf = WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen]
return sf, WUXIAO_TOTAL_RANK_SORT_LABEL
period_type = RIQI_TO_PERIOD.get(riqi) or OPEN_RIQI_PERIOD_MAP.get(riqi)
sort_field = None
if period_type:
@@ -246,8 +257,88 @@ def _resolve_date_range_for_display(riqi: str):
return None
def _is_wuxiao_total_rank(riqi: str, shenfen: str, sort_field: str) -> bool:
return (
riqi == '总榜'
and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS
and sort_field == WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen]
)
def query_wuxiao_total_rank_rows(
shenfen: str, club_id: str = None, limit: int = MAX_RANK,
) -> list:
"""总榜专用:管事/组长按无效人数排行(仅展示,不影响其它周期)。"""
from rank.display_utils import apply_zuzhang_wuxiao_fields
member_uids = club_member_uids(club_id) if club_id else None
if club_id and not member_uids:
return []
fetch_limit = min(max(limit * 5, 100), 250)
if shenfen == 'guanshi':
model = ManagerDailyStats
id_field = 'ManagerID'
base_qs = model.objects.all()
if club_id:
base_qs = base_qs.filter(
club_id=club_id, **{f'{id_field}__in': member_uids},
)
rows = (
base_qs.values(id_field)
.annotate(
invited=Coalesce(Sum('InvitedPlayerCount'), Value(0)),
valid=Coalesce(Sum('RechargedPlayerCount'), Value(0)),
)
.annotate(metric=F('invited') - F('valid'))
.order_by('-metric')[:fetch_limit]
)
out = []
for r in rows:
uid = r[id_field]
metric = int(r.get('metric') or 0)
if metric <= 0:
continue
out.append({'yonghuid': uid, 'metric': metric, 'is_int': True})
if club_id:
out = filter_rank_rows_by_user_club(out, club_id)
out.sort(key=lambda x: x['metric'], reverse=True)
return out[:limit]
if shenfen == 'zuzhang':
model = LeaderDailyStats
id_field = 'LeaderID'
base_qs = model.objects.all()
if club_id:
base_qs = base_qs.filter(
club_id=club_id, **{f'{id_field}__in': member_uids},
)
candidates = (
base_qs.values(id_field)
.annotate(invited=Coalesce(Sum('InvitedManagerCount'), Value(0)))
.order_by('-invited')[:fetch_limit]
)
out = []
for r in candidates:
uid = r[id_field]
stats = {'yaoqing_guanshi_shu': int(r.get('invited') or 0)}
apply_zuzhang_wuxiao_fields(stats, uid, None, None)
metric = int(stats.get('wuxiao_yaoqing_guanshi_shu') or 0)
if metric <= 0:
continue
out.append({'yonghuid': uid, 'metric': metric, 'is_int': True})
if club_id:
out = filter_rank_rows_by_user_club(out, club_id)
out.sort(key=lambda x: x['metric'], reverse=True)
return out[:limit]
return []
def query_club_rank_rows(club_id: str, shenfen: str, sort_field: str, riqi: str, limit: int = MAX_RANK):
"""按 club 归属用户 + 日统计 club_id 双过滤(分奖与展示唯一依据)。"""
if _is_wuxiao_total_rank(riqi, shenfen, sort_field):
return query_wuxiao_total_rank_rows(shenfen, club_id=club_id, limit=limit)
reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field)
if not reg:
return []
@@ -320,6 +411,8 @@ def query_group_rank_rows(shenfen: str, sort_field: str, riqi: str, limit: int =
def _query_group_rank_rows(shenfen: str, sort_field: str, riqi: str, limit: int = MAX_RANK):
if _is_wuxiao_total_rank(riqi, shenfen, sort_field):
return query_wuxiao_total_rank_rows(shenfen, club_id=None, limit=limit)
reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field)
if not reg:
return []

View File

@@ -167,15 +167,18 @@ class PhbJlPhbView(APIView):
if riqi not in RIQI_OPTIONS:
return Response({'code': 400, 'msg': '周期参数错误', 'data': {'list': []}})
from rank.reward_services import SORT_FIELD_REGISTRY
from rank.reward_services import SORT_FIELD_REGISTRY, WUXIAO_TOTAL_RANK_SORT_FIELDS
scheme_sort, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
if not sort_field:
sort_field = scheme_sort
elif sort_field not in (SORT_FIELD_REGISTRY.get(shenfen) or {}):
return Response({'code': 400, 'msg': '排序指标无效', 'data': {'list': []}})
if riqi == '总榜' and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS:
sort_field, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
else:
sort_label = SORT_FIELD_LABELS.get(sort_field, sort_field)
scheme_sort, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
if not sort_field:
sort_field = scheme_sort
elif sort_field not in (SORT_FIELD_REGISTRY.get(shenfen) or {}):
return Response({'code': 400, 'msg': '排序指标无效', 'data': {'list': []}})
else:
sort_label = SORT_FIELD_LABELS.get(sort_field, sort_field)
if fanwei == 'club':
rows = query_club_rank_rows(club_id, shenfen, sort_field, riqi)

View File

@@ -181,6 +181,48 @@ class XzjPhbHqsjView(APIView):
})
try:
if riqi == '总榜' and shenfen in ('guanshi', 'zuzhang'):
from rank.reward_services import (
WUXIAO_TOTAL_RANK_SORT_FIELDS,
enrich_rank_stats,
query_wuxiao_total_rank_rows,
)
wuxiao_rows = query_wuxiao_total_rank_rows(shenfen, club_id=None, limit=MAX_RANK)
cfg = XZJ_ROLE_CONFIG[shenfen]
id_field = cfg['id_field']
wuxiao_key = WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen]
pseudo_rows = [{id_field: r['yonghuid']} for r in wuxiao_rows]
nick_map, avatar_map = self._load_user_display(pseudo_rows, shenfen)
yonghuids = [r['yonghuid'] for r in wuxiao_rows]
stats_map = enrich_rank_stats(shenfen, yonghuids, riqi, '')
result_list = []
for idx, row in enumerate(wuxiao_rows):
yonghuid = row['yonghuid']
metric = int(row.get('metric') or 0)
item = {
'mingci': idx + 1,
'yonghuid': yonghuid,
'nicheng': nick_map.get(yonghuid, '用户'),
'touxiang': avatar_map.get(yonghuid, '') or '',
wuxiao_key: metric,
'sort_field': wuxiao_key,
'metric_value': metric,
'metric_label': '无效人数',
}
extra = stats_map.get(yonghuid) or {}
for k, v in extra.items():
item[k] = v
result_list.append(item)
return Response({
'code': 200,
'msg': '获取成功',
'data': {
'list': result_list,
'sort_field': wuxiao_key,
'sort_label': '无效人数',
},
})
if riqi == '总榜':
rows = self._query_rows(shenfen, all_time=True)
start_date, end_date = None, None