fix: 排行榜按方案档位展示各名次预计奖金

This commit is contained in:
XingQue
2026-07-01 19:17:43 +08:00
parent 2504997c53
commit 3394fef79b
2 changed files with 134 additions and 52 deletions

View File

@@ -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,

View File

@@ -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,