From 3394fef79b696e6acbab0a8ee223108794a1e9cd Mon Sep 17 00:00:00 2001 From: XingQue Date: Wed, 1 Jul 2026 19:17:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=92=E8=A1=8C=E6=A6=9C=E6=8C=89?= =?UTF-8?q?=E6=96=B9=E6=A1=88=E6=A1=A3=E4=BD=8D=E5=B1=95=E7=A4=BA=E5=90=84?= =?UTF-8?q?=E5=90=8D=E6=AC=A1=E9=A2=84=E8=AE=A1=E5=A5=96=E9=87=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rank/reward_services.py | 135 +++++++++++++++++++++++++++++++++------- rank/reward_views.py | 51 ++++++--------- 2 files changed, 134 insertions(+), 52 deletions(-) diff --git a/rank/reward_services.py b/rank/reward_services.py index a48553f..3095563 100644 --- a/rank/reward_services.py +++ b/rank/reward_services.py @@ -37,6 +37,19 @@ RIQI_TO_PERIOD = { } OPEN_RIQI = frozenset({'今日', '本周', '本月', '总榜'}) +OPEN_RIQI_PERIOD_MAP = { + '今日': RankRewardScheme.PERIOD_DAY, + '本周': RankRewardScheme.PERIOD_WEEK, + '本月': RankRewardScheme.PERIOD_MONTH, +} + +DEFAULT_SORT_FIELD = { + 'dashou': 'chengjiao_zongliang', + 'guanshi': 'chongzhi_dashou_shu', + 'zuzhang': 'yaoqing_guanshi_shu', + 'shangjia': 'jiesuan_jine', +} + # shenfen -> sort_field_key -> (model, id_field, db_field, is_int) SORT_FIELD_REGISTRY = { 'dashou': { @@ -95,6 +108,39 @@ 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]: + """按后台奖励方案决定展示/排序字段(进行中与已结束周期均适用)。""" + period_type = RIQI_TO_PERIOD.get(riqi) or OPEN_RIQI_PERIOD_MAP.get(riqi) + sort_field = None + if period_type: + scheme = RankRewardScheme.query.filter( + club_id=club_id, shenfen=shenfen, period_type=period_type, + ).first() + if scheme and scheme.enabled: + reg = SORT_FIELD_REGISTRY.get(shenfen) or {} + if scheme.sort_field in reg: + sort_field = scheme.sort_field + if not sort_field: + sort_field = DEFAULT_SORT_FIELD.get(shenfen, 'chengjiao_zongliang') + return sort_field, SORT_FIELD_LABELS.get(sort_field, sort_field) + + +def club_member_uids(club_id: str) -> list: + """仅统计归属该俱乐部的用户(未开业/无用户则空榜)。""" + if not club_id: + return [] + return list( + User.objects.filter(ClubID=club_id).values_list('UserUID', flat=True) + ) + + +def filter_rank_rows_by_user_club(rows: list, club_id: str) -> list: + if not rows or not club_id: + return rows or [] + club_map = load_user_club_map([r['yonghuid'] for r in rows]) + return [r for r in rows if club_map.get(r['yonghuid']) == club_id] + + def resolve_period_dates(riqi: str) -> Optional[tuple[date, date, str]]: today = date.today() if riqi == '昨日': @@ -140,23 +186,44 @@ def _resolve_date_range_for_display(riqi: str): def query_club_rank_rows(club_id: str, shenfen: str, sort_field: str, riqi: str, limit: int = MAX_RANK): - """按 club_id 过滤的俱乐部榜(分奖唯一依据)。""" + """按 club 归属用户 + 日统计 club_id 双过滤(分奖与展示唯一依据)。""" reg = SORT_FIELD_REGISTRY.get(shenfen, {}).get(sort_field) if not reg: return [] model, id_field, db_field, is_int = reg + member_uids = club_member_uids(club_id) + if not member_uids: + return [] + + fetch_limit = min(max(limit * 5, 100), 250) + + def _pack(qs_or_rows, is_values=False): + out = [] + if is_values: + for r in qs_or_rows: + uid = r[id_field] + metric = r.get('metric') or 0 + if metric and uid in member_uids: + out.append({'yonghuid': uid, 'metric': metric, 'is_int': is_int}) + else: + for r in qs_or_rows: + uid = getattr(r, id_field) + metric = getattr(r, db_field) or 0 + if metric and uid in member_uids: + out.append({'yonghuid': uid, 'metric': metric, 'is_int': is_int}) + out = filter_rank_rows_by_user_club(out, club_id) + out.sort(key=lambda x: x['metric'], reverse=True) + return out[:limit] + if riqi == '总榜': rows = ( - model.objects.filter(club_id=club_id) + model.objects.filter(club_id=club_id, **{f'{id_field}__in': member_uids}) .values(id_field) .annotate(metric=Sum(db_field)) - .order_by('-metric')[:limit] + .order_by(f'-metric')[:fetch_limit] ) - return [ - {'yonghuid': r[id_field], 'metric': r['metric'] or 0, 'is_int': is_int} - for r in rows if r.get('metric') - ] + return _pack(rows, is_values=True) date_range = _resolve_date_range_for_display(riqi) if not date_range: @@ -164,22 +231,26 @@ def query_club_rank_rows(club_id: str, shenfen: str, sort_field: str, riqi: str, start_date, end_date = date_range if start_date == end_date: - qs = model.objects.filter(club_id=club_id, Date=start_date).order_by(f'-{db_field}')[:limit] - return [ - {'yonghuid': getattr(r, id_field), 'metric': getattr(r, db_field) or 0, 'is_int': is_int} - for r in qs if getattr(r, db_field) - ] + qs = ( + model.objects.filter( + club_id=club_id, Date=start_date, **{f'{id_field}__in': member_uids}, + ) + .order_by(f'-{db_field}')[:fetch_limit] + ) + return _pack(qs, is_values=False) rows = ( - model.objects.filter(club_id=club_id, Date__gte=start_date, Date__lte=end_date) + model.objects.filter( + club_id=club_id, + Date__gte=start_date, + Date__lte=end_date, + **{f'{id_field}__in': member_uids}, + ) .values(id_field) .annotate(metric=Sum(db_field)) - .order_by('-metric')[:limit] + .order_by('-metric')[:fetch_limit] ) - return [ - {'yonghuid': r[id_field], 'metric': r['metric'] or 0, 'is_int': is_int} - for r in rows if r.get('metric') - ] + return _pack(rows, is_values=True) def query_group_rank_rows(shenfen: str, sort_field: str, riqi: str, limit: int = MAX_RANK): @@ -254,6 +325,21 @@ def tier_amount_for_rank(tiers, mingci: int) -> Optional[Decimal]: return None +def build_tier_rewards_preview(scheme) -> dict: + """按方案档位生成各名次预计奖金(进行中周期展示用)。""" + if not scheme or not scheme.enabled: + return {} + tiers = list(RankRewardTier.query.filter(scheme_id=scheme.id).order_by('rank_from')) + if not tiers: + return {} + preview = {} + for mingci in range(1, MAX_RANK + 1): + amt = tier_amount_for_rank(tiers, mingci) + if amt and amt > 0: + preview[mingci] = float(amt) + return preview + + def maybe_settle_club_rewards(club_id: str, shenfen: str, riqi: str) -> Optional[RankRewardSettlement]: """懒结算:仅已结束周期;幂等。""" if riqi in OPEN_RIQI: @@ -352,7 +438,8 @@ def maybe_settle_club_rewards(club_id: str, shenfen: str, riqi: str) -> Optional def build_reward_payload(club_id: str, shenfen: str, riqi: str, yonghuid: str = '') -> dict: """奖励规则 + 我的待领取/已领取状态。""" - period_type = RIQI_TO_PERIOD.get(riqi) + period_type = RIQI_TO_PERIOD.get(riqi) or OPEN_RIQI_PERIOD_MAP.get(riqi) + display_sort, display_label = resolve_sort_field_for_display(club_id, shenfen, riqi) scheme = None if period_type: scheme = RankRewardScheme.query.filter( @@ -386,6 +473,8 @@ def build_reward_payload(club_id: str, shenfen: str, riqi: str, yonghuid: str = for c in RankRewardClaim.query.filter(settlement_id=settlement.id): rank_rewards[c.mingci] = float(c.reward_amount) + tier_rewards = build_tier_rewards_preview(scheme) if scheme else {} + my_claim = None my_claimed = None if yonghuid and settlement: @@ -426,11 +515,15 @@ def build_reward_payload(club_id: str, shenfen: str, riqi: str, yonghuid: str = 'scheme_enabled': bool(scheme and scheme.enabled), 'period_status': 'open' if riqi in OPEN_RIQI else 'closed', 'period_label': riqi, - 'sort_field': scheme.sort_field if scheme else '', - 'sort_label': SORT_FIELD_LABELS.get(scheme.sort_field if scheme else '', ''), + 'sort_field': (scheme.sort_field if scheme and scheme.enabled else display_sort), + 'sort_label': ( + SORT_FIELD_LABELS.get(scheme.sort_field, display_label) + if scheme and scheme.enabled else display_label + ), 'title': scheme.title if scheme else '', 'description': scheme.description if scheme else '', 'tiers': tiers, + 'tier_rewards': tier_rewards, 'rank_rewards': rank_rewards, 'my_claim': my_claim, 'my_claimed': my_claimed, diff --git a/rank/reward_views.py b/rank/reward_views.py index 68d9422..d6fc884 100644 --- a/rank/reward_views.py +++ b/rank/reward_views.py @@ -16,6 +16,7 @@ from rank.reward_services import ( list_my_pending_claims, query_club_rank_rows, query_group_rank_rows, + resolve_sort_field_for_display, sort_field_options_for_shenfen, ) from users.business_models import User @@ -31,10 +32,10 @@ def _resolve_club_id(request, data_club_id=''): from jituan.services.club_context import resolve_club_id_from_request cid = (data_club_id or '').strip() if not cid: - cid = get_user_club_id(request.user) or getattr(request.user, 'ClubID', None) or '' + cid = resolve_club_id_from_request(request) or '' if not cid: - cid = resolve_club_id_from_request(request) or 'xq' - return cid + cid = get_user_club_id(request.user) or getattr(request.user, 'ClubID', None) or '' + return cid or 'xq' def _load_display_maps(yonghuids, shenfen): @@ -80,14 +81,12 @@ def _format_rank_list(rows, shenfen, sort_field, sort_label): 'touxiang': avatar_map.get(uid, '') or '', 'club_id': cid, 'club_name': club_names.get(cid, cid), + 'sort_field': sort_field, 'metric_value': int(metric) if is_int else float(metric), 'metric_label': sort_label, + 'metric_is_int': is_int, } - # 兼容 fighter-rank 现有字段 - if is_int: - item['chengjiao_zongliang'] = int(metric) - else: - item['chengjiao_zonge'] = float(metric) + item[sort_field] = item['metric_value'] result.append(item) return result @@ -169,30 +168,15 @@ class PhbJlPhbView(APIView): if riqi not in RIQI_OPTIONS: return Response({'code': 400, 'msg': '周期参数错误', 'data': {'list': []}}) - from rank.reward_models import RankRewardScheme - from rank.reward_services import RIQI_TO_PERIOD, SORT_FIELD_REGISTRY + from rank.reward_services import SORT_FIELD_REGISTRY + scheme_sort, sort_label = resolve_sort_field_for_display(club_id, shenfen, riqi) if not sort_field: - period_type = RIQI_TO_PERIOD.get(riqi) - if period_type and fanwei == 'club': - scheme = RankRewardScheme.query.filter( - club_id=club_id, shenfen=shenfen, period_type=period_type, enabled=True, - ).first() - if scheme: - sort_field = scheme.sort_field - if not sort_field: - defaults = { - 'dashou': 'chengjiao_zongliang', - 'guanshi': 'chongzhi_dashou_shu', - 'zuzhang': 'yaoqing_guanshi_shu', - 'shangjia': 'jiesuan_jine', - } - sort_field = defaults.get(shenfen, 'chengjiao_zongliang') - - if sort_field not in (SORT_FIELD_REGISTRY.get(shenfen) or {}): + sort_field = scheme_sort + elif sort_field not in (SORT_FIELD_REGISTRY.get(shenfen) or {}): return Response({'code': 400, 'msg': '排序指标无效', 'data': {'list': []}}) - - sort_label = SORT_FIELD_LABELS.get(sort_field, sort_field) + 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) @@ -204,9 +188,14 @@ class PhbJlPhbView(APIView): # 仅俱乐部榜展示各行奖金(集团总榜名次与俱乐部分奖名次不同,不可混用) rank_rewards = reward.get('rank_rewards') or {} - if fanwei == 'club': + tier_rewards = reward.get('tier_rewards') or {} + if fanwei == 'club' and reward.get('scheme_enabled'): for item in result_list: - item['reward_amount'] = rank_rewards.get(item['mingci'], 0) + mc = item['mingci'] + settled = rank_rewards.get(mc) + preview = tier_rewards.get(mc, 0) + item['reward_amount'] = settled if settled else preview + item['reward_is_preview'] = not settled and preview > 0 return Response({ 'code': 200,