Files
Django/jituan/services/member_recharge_records.py
XingQue 2a350a2ec8 feat: 会员充值明细接口与俱乐部一键创建总会员
新增 hy-recharge-records 查询 czjilu 会员单并关联 gsfenhong 分红;hy-bundle-create 支持俱乐部一步创建总会员;会员数据分析页权限改为 3300a。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 18:27:48 +08:00

309 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""后台会员充值记录明细czjilu leixing=1+ 管事/组长分红关联查询。"""
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from django.db.models import Q, Sum, Count
from django.utils import timezone
from jituan.services.club_context import filter_queryset_by_club, resolve_club_scope, resolve_club_id_from_request
from jituan.services.club_user_access import list_response_meta
from jituan.services.member_recharge import (
MEMBER_LEIXING,
MEMBER_PAID_STATUS,
MEMBER_PENDING_STATUS,
resolve_formal_purchase_cishu,
)
from jituan.constants import DATA_SCOPE_ALL
from products.models import Czjilu, Gsfenhong, Huiyuan
from users.models import UserDashou, UserBoss
MEMBER_STATUS_LABELS = {
MEMBER_PAID_STATUS: '已支付',
MEMBER_PENDING_STATUS: '待支付',
}
def _parse_dt(value):
if not value:
return None
if isinstance(value, datetime):
return value
text = str(value).strip()
if not text:
return None
for fmt in ('%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d'):
try:
dt = datetime.strptime(text[:19] if 'T' not in text else text.replace('Z', '')[:19], fmt)
if timezone.is_naive(dt):
return timezone.make_aware(dt, timezone.get_current_timezone())
return dt
except ValueError:
continue
return None
def _batch_user_display_names(user_ids):
user_ids = [x for x in {str(i).strip() for i in user_ids if i} if x]
if not user_ids:
return {}
result = {uid: uid for uid in user_ids}
dashou_map = {
r.user.UserUID: (r.nicheng or '').strip()
for r in UserDashou.query.filter(user__UserUID__in=user_ids).select_related('user')
if getattr(r.user, 'UserUID', None)
}
boss_map = {
r.user.UserUID: (r.nickname or '').strip()
for r in UserBoss.query.filter(user__UserUID__in=user_ids).select_related('user')
if getattr(r.user, 'UserUID', None)
}
for uid in user_ids:
name = dashou_map.get(uid) or boss_map.get(uid)
if name:
result[uid] = name
return result
def _build_base_queryset(request):
qs = Czjilu.query.filter(leixing=MEMBER_LEIXING)
return filter_queryset_by_club(qs, request, club_field='club_id')
def _apply_filters(qs, filters):
f = filters or {}
dingdan_id = (f.get('dingdan_id') or '').strip()
if dingdan_id:
qs = qs.filter(dingdan_id__icontains=dingdan_id)
yonghuid = (f.get('yonghuid') or '').strip()
if yonghuid:
qs = qs.filter(yonghuid__icontains=yonghuid)
buyer_keyword = (f.get('buyer_keyword') or f.get('dashou_keyword') or '').strip()
if buyer_keyword:
dashou_ids = list(
UserDashou.query.filter(nicheng__icontains=buyer_keyword).values_list('user__UserUID', flat=True)
)
qs = qs.filter(
Q(yonghuid__icontains=buyer_keyword)
| Q(shuoming__icontains=buyer_keyword)
| Q(yonghuid__in=dashou_ids)
)
huiyuan_id = f.get('huiyuan_id')
if huiyuan_id:
if isinstance(huiyuan_id, (list, tuple)):
ids = [str(x).strip() for x in huiyuan_id if str(x).strip()]
if ids:
qs = qs.filter(huiyuan_id__in=ids)
else:
qs = qs.filter(huiyuan_id=str(huiyuan_id).strip())
if f.get('is_trial') is not None and f.get('is_trial') != '':
qs = qs.filter(is_trial=bool(f.get('is_trial')))
zhuangtai = f.get('zhuangtai')
if zhuangtai is not None and zhuangtai != '':
qs = qs.filter(zhuangtai=int(zhuangtai))
date_from = _parse_dt(f.get('date_from') or f.get('start_time'))
date_to = _parse_dt(f.get('date_to') or f.get('end_time'))
if date_from:
qs = qs.filter(CreateTime__gte=date_from)
if date_to:
if len(str(f.get('date_to') or f.get('end_time') or '')) <= 10:
date_to = date_to.replace(hour=23, minute=59, second=59)
qs = qs.filter(CreateTime__lte=date_to)
jine_min = f.get('jine_min')
jine_max = f.get('jine_max')
if jine_min not in (None, ''):
qs = qs.filter(jine__gte=Decimal(str(jine_min)))
if jine_max not in (None, ''):
qs = qs.filter(jine__lte=Decimal(str(jine_max)))
guanshi_id = (f.get('guanshi_id') or '').strip()
if guanshi_id:
order_ids = Gsfenhong.query.filter(
guanshi__icontains=guanshi_id,
fenhong_leixing=1,
).values_list('dingdan_id', flat=True)
qs = qs.filter(dingdan_id__in=list(order_ids))
zuzhang_id = (f.get('zuzhang_id') or '').strip()
if zuzhang_id:
order_ids = Gsfenhong.query.filter(
zuzhang_id__icontains=zuzhang_id,
fenhong_leixing=1,
).values_list('dingdan_id', flat=True)
qs = qs.filter(dingdan_id__in=list(order_ids))
keyword = (f.get('keyword') or '').strip()
if keyword:
qs = qs.filter(
Q(dingdan_id__icontains=keyword)
| Q(yonghuid__icontains=keyword)
| Q(shuoming__icontains=keyword)
| Q(huiyuan_id__icontains=keyword)
)
is_bundle = f.get('is_bundle')
if is_bundle is not None and is_bundle != '':
bundle_ids = list(
Huiyuan.query.filter(is_bundle=bool(is_bundle)).values_list('huiyuan_id', flat=True)
)
if bool(is_bundle):
qs = qs.filter(huiyuan_id__in=bundle_ids)
else:
qs = qs.exclude(huiyuan_id__in=bundle_ids)
has_fenhong = f.get('has_fenhong')
if has_fenhong is not None and has_fenhong != '':
paid_ids = Gsfenhong.query.filter(fenhong_leixing=1).values_list('dingdan_id', flat=True)
paid_set = set(paid_ids)
if bool(has_fenhong):
qs = qs.filter(dingdan_id__in=paid_set)
else:
qs = qs.exclude(dingdan_id__in=paid_set)
club_id = (f.get('club_id') or '').strip()
if club_id:
qs = qs.filter(club_id=club_id)
return qs
def _build_summary(qs):
paid_qs = qs.filter(zhuangtai=MEMBER_PAID_STATUS)
paid_ids = list(paid_qs.values_list('dingdan_id', flat=True))
fh_qs = Gsfenhong.query.filter(dingdan_id__in=paid_ids, fenhong_leixing=1)
agg = paid_qs.aggregate(
paid_count=Count('dingdan_id'),
paid_jine=Sum('jine'),
trial_count=Count('dingdan_id', filter=Q(is_trial=True)),
)
fh_agg = fh_qs.aggregate(
guanshi_total=Sum('fenhong'),
zuzhang_total=Sum('zuzhang_fenhong'),
fenhong_count=Count('dingdan_id'),
)
return {
'total_count': qs.count(),
'paid_count': int(agg.get('paid_count') or 0),
'pending_count': qs.filter(zhuangtai=MEMBER_PENDING_STATUS).count(),
'paid_jine': str(agg.get('paid_jine') or Decimal('0')),
'trial_count': int(agg.get('trial_count') or 0),
'guanshi_fenhong_total': str(fh_agg.get('guanshi_total') or Decimal('0')),
'zuzhang_fenhong_total': str(fh_agg.get('zuzhang_total') or Decimal('0')),
'fenhong_record_count': int(fh_agg.get('fenhong_count') or 0),
}
def _huiyuan_options(request):
qs = Huiyuan.query.all().order_by('-CreateTime')
return [
{
'huiyuan_id': m.huiyuan_id,
'jieshao': m.jieshao,
'is_bundle': bool(getattr(m, 'is_bundle', False)),
}
for m in qs
]
def build_member_recharge_records_payload(request, filters=None, page=1, page_size=20):
page = max(1, int(page or 1))
page_size = min(100, max(1, int(page_size or 20)))
qs = _apply_filters(_build_base_queryset(request), filters)
qs = qs.order_by('-CreateTime')
total = qs.count()
offset = (page - 1) * page_size
rows = list(qs[offset:offset + page_size])
dingdan_ids = [r.dingdan_id for r in rows]
fenhong_map = {
g.dingdan_id: g
for g in Gsfenhong.query.filter(dingdan_id__in=dingdan_ids, fenhong_leixing=1)
}
huiyuan_ids = {r.huiyuan_id for r in rows if r.huiyuan_id}
huiyuan_map = {m.huiyuan_id: m for m in Huiyuan.query.filter(huiyuan_id__in=huiyuan_ids)}
user_ids = {r.yonghuid for r in rows if r.yonghuid}
for g in fenhong_map.values():
if g.guanshi:
user_ids.add(g.guanshi)
if g.zuzhang_id:
user_ids.add(g.zuzhang_id)
if g.dashouid:
user_ids.add(g.dashouid)
name_map = _batch_user_display_names(user_ids)
items = []
for order in rows:
hy = huiyuan_map.get(order.huiyuan_id)
fh = fenhong_map.get(order.dingdan_id)
formal_cishu = None
if order.zhuangtai == MEMBER_PAID_STATUS and not order.is_trial:
formal_cishu = resolve_formal_purchase_cishu(
order.yonghuid, order.huiyuan_id, order.dingdan_id, order.club_id,
)
fenhong_detail = None
if fh:
fenhong_detail = {
'guanshi_id': fh.guanshi or '',
'guanshi_nicheng': name_map.get(fh.guanshi, fh.guanshi or ''),
'guanshi_fenhong': str(fh.fenhong or 0),
'zuzhang_id': fh.zuzhang_id or '',
'zuzhang_nicheng': name_map.get(fh.zuzhang_id, fh.zuzhang_id or ''),
'zuzhang_fenhong': str(fh.zuzhang_fenhong or 0),
'fenhong_leixing': fh.fenhong_leixing,
'is_trial': bool(fh.is_trial),
'fenhong_time': fh.CreateTime.isoformat() if fh.CreateTime else None,
'formal_cishu': formal_cishu,
'shuoming': fh.shuoming or '',
}
items.append({
'dingdan_id': order.dingdan_id,
'yonghuid': order.yonghuid,
'buyer_nicheng': name_map.get(order.yonghuid, order.yonghuid),
'huiyuan_id': order.huiyuan_id,
'huiyuan_name': hy.jieshao if hy else (order.huiyuan_id or ''),
'is_bundle': bool(hy and getattr(hy, 'is_bundle', False)),
'jine': str(order.jine or 0),
'zhuangtai': order.zhuangtai,
'zhuangtai_label': MEMBER_STATUS_LABELS.get(order.zhuangtai, f'状态{order.zhuangtai}'),
'is_trial': bool(order.is_trial),
'purchase_days': order.purchase_days,
'shuoming': order.shuoming or '',
'club_id': order.club_id,
'CreateTime': order.CreateTime.isoformat() if order.CreateTime else None,
'UpdateTime': order.UpdateTime.isoformat() if order.UpdateTime else None,
'formal_cishu': formal_cishu,
'fenhong': fenhong_detail,
})
return {
'list': items,
'total': total,
'page': page,
'page_size': page_size,
'summary': _build_summary(_apply_filters(_build_base_queryset(request), filters)),
'filter_options': {
'huiyuan_list': _huiyuan_options(request),
'zhuangtai_options': [
{'value': MEMBER_PAID_STATUS, 'label': '已支付'},
{'value': MEMBER_PENDING_STATUS, 'label': '待支付'},
],
},
'scope': resolve_club_scope(request),
'club_id': resolve_club_id_from_request(request),
**list_response_meta(request),
}