fix: 排行榜按方案档位展示各名次预计奖金
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user