"""财务看板:只读 daily_income_stat / daily_payout_stat,支持按俱乐部 Header 过滤。""" from datetime import date, timedelta from decimal import Decimal from django.db.models import Sum from config.models import DailyIncomeStat, DailyPayoutStat from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL from jituan.services.club_context import filter_club_char_field, resolve_club_scope from jituan.services.szjilu_accounting import ( collect_today_live_wechat_payments, normalize_szjilu_club_id, ) def _income_qs(request): if request is None: return DailyIncomeStat.objects.all() return filter_club_char_field(DailyIncomeStat.objects.all(), request, field='club_id') def _payout_qs(request): if request is None: return DailyPayoutStat.objects.all() return filter_club_char_field(DailyPayoutStat.objects.all(), request, field='club_id') def _payment_matches_request(club_id, request): if request is None or resolve_club_scope(request) == DATA_SCOPE_ALL: return True from jituan.services.club_context import resolve_club_id_from_request cid = resolve_club_id_from_request(request) ic = normalize_szjilu_club_id(club_id) if cid == CLUB_ID_DEFAULT: return ic == CLUB_ID_DEFAULT return ic == cid def sum_income_stat_for_date(stat_date, request=None): amount = Decimal('0') count = 0 for row in _income_qs(request).filter(date=stat_date).iterator(): amount += Decimal(str(row.total_amount or 0)) count += int(row.total_count or 0) return float(amount), count def sum_payout_stat_for_date(stat_date, request=None): amount = Decimal('0') count = 0 for row in _payout_qs(request).filter(date=stat_date).iterator(): amount += Decimal(str(row.total_amount or 0)) count += int(row.total_count or 0) return float(amount), count def count_live_income_payments(stat_date, request=None): """当日真实微信支付笔数(去重),可按俱乐部过滤。""" items = collect_today_live_wechat_payments(stat_date) if request is None: return len(items) return sum( 1 for _ref, cid, _jine, _kind in items if _payment_matches_request(cid, request) ) def today_income_for_dashboard(stat_date=None, request=None): """ 今日收入:金额与笔数均来自 daily_income_stat(按俱乐部过滤)。 笔数若被 repair 虚增,用当日真实支付笔数校正。 """ stat_date = stat_date or date.today() amount, table_count = sum_income_stat_for_date(stat_date, request) live_count = count_live_income_payments(stat_date, request) if live_count > 0 and table_count > live_count: display_count = live_count else: display_count = table_count return amount, display_count def cumulative_income_payout(request=None): """累计收入/支出/利润(按当前俱乐部视图范围)。""" total_income = float( _income_qs(request).aggregate(t=Sum('total_amount'))['t'] or 0 ) total_payout = float( _payout_qs(request).aggregate(t=Sum('total_amount'))['t'] or 0 ) return total_income, total_payout, round(total_income - total_payout, 2) def daily_stats_for_request(request=None, days=30): """近 N 天每日收支(按俱乐部视图)。""" since = date.today() - timedelta(days=days) income_by_date = { str(row['date']): { 'income': float(row['total_amount'] or 0), 'income_count': int(row['total_count'] or 0), } for row in _income_qs(request).filter(date__gte=since).values('date').annotate( total_amount=Sum('total_amount'), total_count=Sum('total_count'), ) } payout_by_date = { str(row['date']): float(row['total_amount'] or 0) for row in _payout_qs(request).filter(date__gte=since).values('date').annotate( total_amount=Sum('total_amount'), ) } daily_stats = [] all_dates = sorted( set(income_by_date.keys()) | set(payout_by_date.keys()), reverse=True, )[:days] for d in all_dates: inc = income_by_date.get(d, {'income': 0.0, 'income_count': 0}) payout_amt = payout_by_date.get(d, 0.00) daily_stats.append({ 'date': d, 'income': inc['income'], 'income_count': inc['income_count'], 'payout': payout_amt, 'profit': round(inc['income'] - payout_amt, 2), }) return daily_stats def income_count_for_summary_date(stat_date, request=None): if stat_date == date.today(): _, count = today_income_for_dashboard(stat_date, request) return count _, count = sum_income_stat_for_date(stat_date, request) return count