From 80cca803f87f008bfb98e4c4d150d062260552f7 Mon Sep 17 00:00:00 2001 From: XingQue Date: Sat, 11 Jul 2026 02:00:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=80=BB=E6=A6=9C=E6=8C=89=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E6=80=BB=E4=BA=BA=E6=95=B0=E6=8E=92=E8=A1=8C=EF=BC=8C?= =?UTF-8?q?=E7=BB=84=E9=95=BF=E5=8E=BB=E9=87=8D=E7=BB=9F=E8=AE=A1=EF=BC=9B?= =?UTF-8?q?=E6=80=BB=E4=BC=9A=E5=91=98=E5=AD=90=E7=B1=BB=E5=9E=8B=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- jituan/services/huiyuan_bundle.py | 27 ++++++-- rank/reward_services.py | 110 +++++++++++++++++------------- rank/reward_views.py | 4 +- users/xzj_paihang_views.py | 25 +++---- 4 files changed, 98 insertions(+), 68 deletions(-) diff --git a/jituan/services/huiyuan_bundle.py b/jituan/services/huiyuan_bundle.py index ed31f63..018e621 100644 --- a/jituan/services/huiyuan_bundle.py +++ b/jituan/services/huiyuan_bundle.py @@ -80,6 +80,17 @@ def get_bundle_included_ids(club_id, bundle_huiyuan_id): ) if 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 [] @@ -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): - """按候选俱乐部解析总会员包含的子会员(只读,首个非空配置即返回)。""" + """按候选俱乐部合并总会员包含的子会员(去重,只读)。""" if not bundle_huiyuan_id: return [] + merged = [] + seen = set() for cid in _membership_club_candidates( yonghu_id, club_id=club_id, record_club_id=record_club_id, ): - ids = get_bundle_included_ids(cid, bundle_huiyuan_id) - if ids: - return list(ids) - return [] + for hid in get_bundle_included_ids(cid, bundle_huiyuan_id): + norm = normalize_huiyuan_id(hid) + core = norm.lstrip('0') or '0' if norm else '' + 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): diff --git a/rank/reward_services.py b/rank/reward_services.py index d9aae73..7575708 100644 --- a/rank/reward_services.py +++ b/rank/reward_services.py @@ -52,12 +52,12 @@ DEFAULT_SORT_FIELD = { 'shangjia': 'jiesuan_jine', } -# 总榜:管事/组长按无效人数排序展示(其它周期不变) -WUXIAO_TOTAL_RANK_SORT_FIELDS = { - 'guanshi': 'wuxiao_yaoqing_dashou_shu', - 'zuzhang': 'wuxiao_yaoqing_guanshi_shu', +# 总榜:管事=邀请打手总人数;组长=管事+打手身份邀请去重总人数(其它周期不变) +TOTAL_RANK_INVITE_SORT_FIELDS = { + 'guanshi': 'yaoqing_dashou_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) SORT_FIELD_REGISTRY = { @@ -94,6 +94,7 @@ SORT_FIELD_LABELS = { 'shouru_zonge': '收益总额', 'yaoqing_dashou_shu': '邀请打手数', 'yaoqing_guanshi_shu': '邀请管事数', + 'yaoqing_zongren_shu': '邀请总人数', 'fenyong_jine': '分佣金额', 'jiesuan_dingdan_shu': '结算单量', '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]: """按后台奖励方案决定展示/排序字段(方案存在即生效,与是否启用奖励无关)。""" - if riqi == '总榜' and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS: - sf = WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen] - return sf, WUXIAO_TOTAL_RANK_SORT_LABEL + if riqi == '总榜' and shenfen in TOTAL_RANK_INVITE_SORT_FIELDS: + sf = TOTAL_RANK_INVITE_SORT_FIELDS[shenfen] + return sf, TOTAL_RANK_INVITE_SORT_LABEL period_type = RIQI_TO_PERIOD.get(riqi) or OPEN_RIQI_PERIOD_MAP.get(riqi) sort_field = None if period_type: @@ -257,20 +258,59 @@ def _resolve_date_range_for_display(riqi: str): 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 ( riqi == '总榜' - and shenfen in WUXIAO_TOTAL_RANK_SORT_FIELDS - and sort_field == WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen] + and shenfen in TOTAL_RANK_INVITE_SORT_FIELDS + 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, ) -> 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 [] @@ -286,11 +326,7 @@ def query_wuxiao_total_rank_rows( ) 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')) + .annotate(metric=Coalesce(Sum('InvitedPlayerCount'), Value(0))) .order_by('-metric')[:fetch_limit] ) out = [] @@ -306,39 +342,15 @@ def query_wuxiao_total_rank_rows( 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 _query_zuzhang_total_unique_invite_rows(club_id=club_id, limit=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) + if _is_total_rank_invite_sort(riqi, shenfen, sort_field): + return query_total_rank_invite_rows(shenfen, club_id=club_id, limit=limit) reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field) if not reg: 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): - if _is_wuxiao_total_rank(riqi, shenfen, sort_field): - return query_wuxiao_total_rank_rows(shenfen, club_id=None, limit=limit) + if _is_total_rank_invite_sort(riqi, shenfen, sort_field): + return query_total_rank_invite_rows(shenfen, club_id=None, limit=limit) reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field) if not reg: return [] diff --git a/rank/reward_views.py b/rank/reward_views.py index a530521..f3c2298 100644 --- a/rank/reward_views.py +++ b/rank/reward_views.py @@ -167,9 +167,9 @@ class PhbJlPhbView(APIView): if riqi not in RIQI_OPTIONS: 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) else: scheme_sort, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi) diff --git a/users/xzj_paihang_views.py b/users/xzj_paihang_views.py index fe60c95..14e6873 100644 --- a/users/xzj_paihang_views.py +++ b/users/xzj_paihang_views.py @@ -183,20 +183,21 @@ class XzjPhbHqsjView(APIView): try: if riqi == '总榜' and shenfen in ('guanshi', 'zuzhang'): from rank.reward_services import ( - WUXIAO_TOTAL_RANK_SORT_FIELDS, + TOTAL_RANK_INVITE_SORT_FIELDS, + TOTAL_RANK_INVITE_SORT_LABEL, 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] id_field = cfg['id_field'] - wuxiao_key = WUXIAO_TOTAL_RANK_SORT_FIELDS[shenfen] - pseudo_rows = [{id_field: r['yonghuid']} for r in wuxiao_rows] + sort_key = TOTAL_RANK_INVITE_SORT_FIELDS[shenfen] + pseudo_rows = [{id_field: r['yonghuid']} for r in rank_rows] 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, '') result_list = [] - for idx, row in enumerate(wuxiao_rows): + for idx, row in enumerate(rank_rows): yonghuid = row['yonghuid'] metric = int(row.get('metric') or 0) item = { @@ -204,10 +205,10 @@ class XzjPhbHqsjView(APIView): 'yonghuid': yonghuid, 'nicheng': nick_map.get(yonghuid, '用户'), 'touxiang': avatar_map.get(yonghuid, '') or '', - wuxiao_key: metric, - 'sort_field': wuxiao_key, + sort_key: metric, + 'sort_field': sort_key, 'metric_value': metric, - 'metric_label': '无效人数', + 'metric_label': TOTAL_RANK_INVITE_SORT_LABEL, } extra = stats_map.get(yonghuid) or {} for k, v in extra.items(): @@ -218,8 +219,8 @@ class XzjPhbHqsjView(APIView): 'msg': '获取成功', 'data': { 'list': result_list, - 'sort_field': wuxiao_key, - 'sort_label': '无效人数', + 'sort_field': sort_key, + 'sort_label': TOTAL_RANK_INVITE_SORT_LABEL, }, })