"""后台会员充值记录明细(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, MEMBER_FAILED_STATUS, MEMBER_CANCELLED_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: '待支付', MEMBER_FAILED_STATUS: '履约失败', MEMBER_CANCELLED_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': '待支付'}, {'value': MEMBER_FAILED_STATUS, 'label': '履约失败'}, {'value': MEMBER_CANCELLED_STATUS, 'label': '已取消'}, ], }, 'scope': resolve_club_scope(request), 'club_id': resolve_club_id_from_request(request), **list_response_meta(request), }