fix: 总榜按邀请总人数排行,组长去重统计;总会员子类型解析增强
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -80,6 +80,17 @@ def get_bundle_included_ids(club_id, bundle_huiyuan_id):
|
|||||||
)
|
)
|
||||||
if ids:
|
if ids:
|
||||||
return ids
|
return ids
|
||||||
|
stored_bids = ClubHuiyuanBundleInclude.objects.filter(
|
||||||
|
club_id=club_id,
|
||||||
|
).values_list('bundle_huiyuan_id', flat=True).distinct()
|
||||||
|
for stored_bid in stored_bids:
|
||||||
|
if huiyuan_ids_match(stored_bid, bundle_huiyuan_id):
|
||||||
|
return list(
|
||||||
|
ClubHuiyuanBundleInclude.query.filter(
|
||||||
|
club_id=club_id,
|
||||||
|
bundle_huiyuan_id=stored_bid,
|
||||||
|
).values_list('included_huiyuan_id', flat=True)
|
||||||
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@@ -112,16 +123,22 @@ def _membership_club_candidates(yonghu_id, club_id=None, record_club_id=None):
|
|||||||
|
|
||||||
|
|
||||||
def resolve_bundle_included_ids(bundle_huiyuan_id, club_id=None, yonghu_id=None, record_club_id=None):
|
def resolve_bundle_included_ids(bundle_huiyuan_id, club_id=None, yonghu_id=None, record_club_id=None):
|
||||||
"""按候选俱乐部解析总会员包含的子会员(只读,首个非空配置即返回)。"""
|
"""按候选俱乐部合并总会员包含的子会员(去重,只读)。"""
|
||||||
if not bundle_huiyuan_id:
|
if not bundle_huiyuan_id:
|
||||||
return []
|
return []
|
||||||
|
merged = []
|
||||||
|
seen = set()
|
||||||
for cid in _membership_club_candidates(
|
for cid in _membership_club_candidates(
|
||||||
yonghu_id, club_id=club_id, record_club_id=record_club_id,
|
yonghu_id, club_id=club_id, record_club_id=record_club_id,
|
||||||
):
|
):
|
||||||
ids = get_bundle_included_ids(cid, bundle_huiyuan_id)
|
for hid in get_bundle_included_ids(cid, bundle_huiyuan_id):
|
||||||
if ids:
|
norm = normalize_huiyuan_id(hid)
|
||||||
return list(ids)
|
core = norm.lstrip('0') or '0' if norm else ''
|
||||||
return []
|
dedupe_key = core or norm
|
||||||
|
if dedupe_key and dedupe_key not in seen:
|
||||||
|
seen.add(dedupe_key)
|
||||||
|
merged.append(hid)
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
def _included_huiyuan_id_set(included_ids):
|
def _included_huiyuan_id_set(included_ids):
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ DEFAULT_SORT_FIELD = {
|
|||||||
'shangjia': 'jiesuan_jine',
|
'shangjia': 'jiesuan_jine',
|
||||||
}
|
}
|
||||||
|
|
||||||
# 总榜:管事/组长按无效人数排序展示(其它周期不变)
|
# 总榜:管事=邀请打手总人数;组长=管事+打手身份邀请去重总人数(其它周期不变)
|
||||||
WUXIAO_TOTAL_RANK_SORT_FIELDS = {
|
TOTAL_RANK_INVITE_SORT_FIELDS = {
|
||||||
'guanshi': 'wuxiao_yaoqing_dashou_shu',
|
'guanshi': 'yaoqing_dashou_shu',
|
||||||
'zuzhang': 'wuxiao_yaoqing_guanshi_shu',
|
'zuzhang': 'yaoqing_zongren_shu',
|
||||||
}
|
}
|
||||||
WUXIAO_TOTAL_RANK_SORT_LABEL = '无效人数'
|
TOTAL_RANK_INVITE_SORT_LABEL = '邀请总人数'
|
||||||
|
|
||||||
# shenfen -> sort_field_key -> (model, id_field, db_field, is_int)
|
# shenfen -> sort_field_key -> (model, id_field, db_field, is_int)
|
||||||
SORT_FIELD_REGISTRY = {
|
SORT_FIELD_REGISTRY = {
|
||||||
@@ -94,6 +94,7 @@ SORT_FIELD_LABELS = {
|
|||||||
'shouru_zonge': '收益总额',
|
'shouru_zonge': '收益总额',
|
||||||
'yaoqing_dashou_shu': '邀请打手数',
|
'yaoqing_dashou_shu': '邀请打手数',
|
||||||
'yaoqing_guanshi_shu': '邀请管事数',
|
'yaoqing_guanshi_shu': '邀请管事数',
|
||||||
|
'yaoqing_zongren_shu': '邀请总人数',
|
||||||
'fenyong_jine': '分佣金额',
|
'fenyong_jine': '分佣金额',
|
||||||
'jiesuan_dingdan_shu': '结算单量',
|
'jiesuan_dingdan_shu': '结算单量',
|
||||||
'jiesuan_jine': '结算金额',
|
'jiesuan_jine': '结算金额',
|
||||||
@@ -179,9 +180,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]:
|
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:
|
if riqi == '总榜' and shenfen in TOTAL_RANK_INVITE_SORT_FIELDS:
|
||||||
sf = WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen]
|
sf = TOTAL_RANK_INVITE_SORT_FIELDS[shenfen]
|
||||||
return sf, WUXIAO_TOTAL_RANK_SORT_LABEL
|
return sf, TOTAL_RANK_INVITE_SORT_LABEL
|
||||||
period_type = RIQI_TO_PERIOD.get(riqi) or OPEN_RIQI_PERIOD_MAP.get(riqi)
|
period_type = RIQI_TO_PERIOD.get(riqi) or OPEN_RIQI_PERIOD_MAP.get(riqi)
|
||||||
sort_field = None
|
sort_field = None
|
||||||
if period_type:
|
if period_type:
|
||||||
@@ -257,20 +258,59 @@ def _resolve_date_range_for_display(riqi: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _is_wuxiao_total_rank(riqi: str, shenfen: str, sort_field: str) -> bool:
|
def _is_total_rank_invite_sort(riqi: str, shenfen: str, sort_field: str) -> bool:
|
||||||
return (
|
return (
|
||||||
riqi == '总榜'
|
riqi == '总榜'
|
||||||
and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS
|
and shenfen in TOTAL_RANK_INVITE_SORT_FIELDS
|
||||||
and sort_field == WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen]
|
and sort_field == TOTAL_RANK_INVITE_SORT_FIELDS[shenfen]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def query_wuxiao_total_rank_rows(
|
def _query_zuzhang_total_unique_invite_rows(club_id: str = None, limit: int = MAX_RANK) -> list:
|
||||||
|
"""
|
||||||
|
组长总榜:组长身份邀请的管事 + 管事身份邀请的打手,按 UserUID 去重计数。
|
||||||
|
"""
|
||||||
|
from users.models import UserDashou, UserGuanshi, UserZuzhang
|
||||||
|
|
||||||
|
zuzhang_uids = set(
|
||||||
|
UserZuzhang.objects.filter(zhuangtai=1).values_list('user__UserUID', flat=True)
|
||||||
|
)
|
||||||
|
if club_id:
|
||||||
|
zuzhang_uids &= set(club_member_uids(club_id))
|
||||||
|
if not zuzhang_uids:
|
||||||
|
return []
|
||||||
|
|
||||||
|
invite_sets = {}
|
||||||
|
zuzhang_list = list(zuzhang_uids)
|
||||||
|
|
||||||
|
def _add(inviter, invited_uid):
|
||||||
|
if inviter not in zuzhang_uids or not invited_uid:
|
||||||
|
return
|
||||||
|
invite_sets.setdefault(inviter, set()).add(invited_uid)
|
||||||
|
|
||||||
|
for inviter, invited_uid in UserGuanshi.objects.filter(
|
||||||
|
yaoqingren__in=zuzhang_list,
|
||||||
|
).values_list('yaoqingren', 'user__UserUID'):
|
||||||
|
_add(inviter, invited_uid)
|
||||||
|
|
||||||
|
for inviter, invited_uid in UserDashou.objects.filter(
|
||||||
|
yaoqingren__in=zuzhang_list,
|
||||||
|
).values_list('yaoqingren', 'user__UserUID'):
|
||||||
|
_add(inviter, invited_uid)
|
||||||
|
|
||||||
|
out = [
|
||||||
|
{'yonghuid': uid, 'metric': len(invited), 'is_int': True}
|
||||||
|
for uid, invited in invite_sets.items()
|
||||||
|
if invited
|
||||||
|
]
|
||||||
|
out.sort(key=lambda x: x['metric'], reverse=True)
|
||||||
|
return out[:limit]
|
||||||
|
|
||||||
|
|
||||||
|
def query_total_rank_invite_rows(
|
||||||
shenfen: str, club_id: str = None, limit: int = MAX_RANK,
|
shenfen: str, club_id: str = None, limit: int = MAX_RANK,
|
||||||
) -> list:
|
) -> list:
|
||||||
"""总榜专用:管事/组长按无效人数排行(仅展示,不影响其它周期)。"""
|
"""总榜专用:管事/组长按邀请总人数排行。"""
|
||||||
from rank.display_utils import apply_zuzhang_wuxiao_fields
|
|
||||||
|
|
||||||
member_uids = club_member_uids(club_id) if club_id else None
|
member_uids = club_member_uids(club_id) if club_id else None
|
||||||
if club_id and not member_uids:
|
if club_id and not member_uids:
|
||||||
return []
|
return []
|
||||||
@@ -286,11 +326,7 @@ def query_wuxiao_total_rank_rows(
|
|||||||
)
|
)
|
||||||
rows = (
|
rows = (
|
||||||
base_qs.values(id_field)
|
base_qs.values(id_field)
|
||||||
.annotate(
|
.annotate(metric=Coalesce(Sum('InvitedPlayerCount'), Value(0)))
|
||||||
invited=Coalesce(Sum('InvitedPlayerCount'), Value(0)),
|
|
||||||
valid=Coalesce(Sum('RechargedPlayerCount'), Value(0)),
|
|
||||||
)
|
|
||||||
.annotate(metric=F('invited') - F('valid'))
|
|
||||||
.order_by('-metric')[:fetch_limit]
|
.order_by('-metric')[:fetch_limit]
|
||||||
)
|
)
|
||||||
out = []
|
out = []
|
||||||
@@ -306,39 +342,15 @@ def query_wuxiao_total_rank_rows(
|
|||||||
return out[:limit]
|
return out[:limit]
|
||||||
|
|
||||||
if shenfen == 'zuzhang':
|
if shenfen == 'zuzhang':
|
||||||
model = LeaderDailyStats
|
return _query_zuzhang_total_unique_invite_rows(club_id=club_id, limit=limit)
|
||||||
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 []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def query_club_rank_rows(club_id: str, shenfen: str, sort_field: str, riqi: str, limit: int = MAX_RANK):
|
def query_club_rank_rows(club_id: str, shenfen: str, sort_field: str, riqi: str, limit: int = MAX_RANK):
|
||||||
"""按 club 归属用户 + 日统计 club_id 双过滤(分奖与展示唯一依据)。"""
|
"""按 club 归属用户 + 日统计 club_id 双过滤(分奖与展示唯一依据)。"""
|
||||||
if _is_wuxiao_total_rank(riqi, shenfen, sort_field):
|
if _is_total_rank_invite_sort(riqi, shenfen, sort_field):
|
||||||
return query_wuxiao_total_rank_rows(shenfen, club_id=club_id, limit=limit)
|
return query_total_rank_invite_rows(shenfen, club_id=club_id, limit=limit)
|
||||||
reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field)
|
reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field)
|
||||||
if not reg:
|
if not reg:
|
||||||
return []
|
return []
|
||||||
@@ -411,8 +423,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):
|
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):
|
if _is_total_rank_invite_sort(riqi, shenfen, sort_field):
|
||||||
return query_wuxiao_total_rank_rows(shenfen, club_id=None, limit=limit)
|
return query_total_rank_invite_rows(shenfen, club_id=None, limit=limit)
|
||||||
reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field)
|
reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field)
|
||||||
if not reg:
|
if not reg:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -167,9 +167,9 @@ class PhbJlPhbView(APIView):
|
|||||||
if riqi not in RIQI_OPTIONS:
|
if riqi not in RIQI_OPTIONS:
|
||||||
return Response({'code': 400, 'msg': '周期参数错误', 'data': {'list': []}})
|
return Response({'code': 400, 'msg': '周期参数错误', 'data': {'list': []}})
|
||||||
|
|
||||||
from rank.reward_services import SORT_FIELD_REGISTRY, WUXIAO_TOTAL_RANK_SORT_FIELDS
|
from rank.reward_services import SORT_FIELD_REGISTRY, TOTAL_RANK_INVITE_SORT_FIELDS
|
||||||
|
|
||||||
if riqi == '总榜' and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS:
|
if riqi == '总榜' and shenfen in TOTAL_RANK_INVITE_SORT_FIELDS:
|
||||||
sort_field, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
|
sort_field, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
|
||||||
else:
|
else:
|
||||||
scheme_sort, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
|
scheme_sort, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi)
|
||||||
|
|||||||
@@ -183,20 +183,21 @@ class XzjPhbHqsjView(APIView):
|
|||||||
try:
|
try:
|
||||||
if riqi == '总榜' and shenfen in ('guanshi', 'zuzhang'):
|
if riqi == '总榜' and shenfen in ('guanshi', 'zuzhang'):
|
||||||
from rank.reward_services import (
|
from rank.reward_services import (
|
||||||
WUXIAO_TOTAL_RANK_SORT_FIELDS,
|
TOTAL_RANK_INVITE_SORT_FIELDS,
|
||||||
|
TOTAL_RANK_INVITE_SORT_LABEL,
|
||||||
enrich_rank_stats,
|
enrich_rank_stats,
|
||||||
query_wuxiao_total_rank_rows,
|
query_total_rank_invite_rows,
|
||||||
)
|
)
|
||||||
wuxiao_rows = query_wuxiao_total_rank_rows(shenfen, club_id=None, limit=MAX_RANK)
|
rank_rows = query_total_rank_invite_rows(shenfen, club_id=None, limit=MAX_RANK)
|
||||||
cfg = XZJ_ROLE_CONFIG[shenfen]
|
cfg = XZJ_ROLE_CONFIG[shenfen]
|
||||||
id_field = cfg['id_field']
|
id_field = cfg['id_field']
|
||||||
wuxiao_key = WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen]
|
sort_key = TOTAL_RANK_INVITE_SORT_FIELDS[shenfen]
|
||||||
pseudo_rows = [{id_field: r['yonghuid']} for r in wuxiao_rows]
|
pseudo_rows = [{id_field: r['yonghuid']} for r in rank_rows]
|
||||||
nick_map, avatar_map = self._load_user_display(pseudo_rows, shenfen)
|
nick_map, avatar_map = self._load_user_display(pseudo_rows, shenfen)
|
||||||
yonghuids = [r['yonghuid'] for r in wuxiao_rows]
|
yonghuids = [r['yonghuid'] for r in rank_rows]
|
||||||
stats_map = enrich_rank_stats(shenfen, yonghuids, riqi, '')
|
stats_map = enrich_rank_stats(shenfen, yonghuids, riqi, '')
|
||||||
result_list = []
|
result_list = []
|
||||||
for idx, row in enumerate(wuxiao_rows):
|
for idx, row in enumerate(rank_rows):
|
||||||
yonghuid = row['yonghuid']
|
yonghuid = row['yonghuid']
|
||||||
metric = int(row.get('metric') or 0)
|
metric = int(row.get('metric') or 0)
|
||||||
item = {
|
item = {
|
||||||
@@ -204,10 +205,10 @@ class XzjPhbHqsjView(APIView):
|
|||||||
'yonghuid': yonghuid,
|
'yonghuid': yonghuid,
|
||||||
'nicheng': nick_map.get(yonghuid, '用户'),
|
'nicheng': nick_map.get(yonghuid, '用户'),
|
||||||
'touxiang': avatar_map.get(yonghuid, '') or '',
|
'touxiang': avatar_map.get(yonghuid, '') or '',
|
||||||
wuxiao_key: metric,
|
sort_key: metric,
|
||||||
'sort_field': wuxiao_key,
|
'sort_field': sort_key,
|
||||||
'metric_value': metric,
|
'metric_value': metric,
|
||||||
'metric_label': '无效人数',
|
'metric_label': TOTAL_RANK_INVITE_SORT_LABEL,
|
||||||
}
|
}
|
||||||
extra = stats_map.get(yonghuid) or {}
|
extra = stats_map.get(yonghuid) or {}
|
||||||
for k, v in extra.items():
|
for k, v in extra.items():
|
||||||
@@ -218,8 +219,8 @@ class XzjPhbHqsjView(APIView):
|
|||||||
'msg': '获取成功',
|
'msg': '获取成功',
|
||||||
'data': {
|
'data': {
|
||||||
'list': result_list,
|
'list': result_list,
|
||||||
'sort_field': wuxiao_key,
|
'sort_field': sort_key,
|
||||||
'sort_label': '无效人数',
|
'sort_label': TOTAL_RANK_INVITE_SORT_LABEL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user