"""每日收支详细统计(供 /jituan/houtai/szxx,旧 /houtai/szxx 不变)。""" from datetime import date, datetime from django.db.models import Sum from config.models import DailyIncomeStat, DailyPayoutStat from jituan.services.club_context import filter_queryset_by_club, resolve_club_id_from_request, resolve_club_scope def build_szxx_payload(request, granularity, year, month, summary_date): """ 与 SzxxView 返回结构一致,按俱乐部过滤日收入/日出款统计。 返回 (data_dict, error_msg);error_msg 非空时表示参数错误。 """ now = date.today() if granularity == 'day': if not year or not month: return None, '按日统计需要年份和月份' group_field = 'day' income_filter = {'year': year, 'month': month} payout_filter = {'year': year, 'month': month} if not summary_date: summary_date = now.strftime('%Y-%m-%d') summary_parts = summary_date.split('-') summary_filter = { 'year': int(summary_parts[0]), 'month': int(summary_parts[1]), 'day': int(summary_parts[2]), } elif granularity == 'month': if not year: return None, '按月统计需要年份' group_field = 'month' income_filter = {'year': year} payout_filter = {'year': year} if not summary_date: summary_date = now.strftime('%Y-%m') summary_parts = summary_date.split('-') summary_filter = {'year': int(summary_parts[0]), 'month': int(summary_parts[1])} elif granularity == 'year': group_field = 'year' income_filter = {} payout_filter = {} if not summary_date: summary_date = str(now.year) summary_filter = {'year': int(summary_date)} else: return None, '无效的颗粒度' income_qs = filter_queryset_by_club( DailyIncomeStat.query.filter(**income_filter), request, ).values(group_field).annotate( total_income=Sum('total_amount'), total_count=Sum('total_count'), ).order_by(group_field) payout_qs = filter_queryset_by_club( DailyPayoutStat.query.filter(**payout_filter), request, ).values(group_field).annotate( total_payout=Sum('total_amount'), total_count=Sum('total_count'), ).order_by(group_field) data_map = {} for item in income_qs: key = item[group_field] data_map[key] = { 'income': float(item['total_income'] or 0), 'income_count': item['total_count'] or 0, 'payout': 0.0, 'payout_count': 0, } for item in payout_qs: key = item[group_field] if key not in data_map: data_map[key] = {'income': 0.0, 'income_count': 0, 'payout': 0.0, 'payout_count': 0} data_map[key]['payout'] = float(item['total_payout'] or 0) data_map[key]['payout_count'] = item['total_count'] or 0 time_series = [] for key, vals in data_map.items(): if granularity == 'day': date_str = f"{year}-{month:02d}-{key:02d}" elif granularity == 'month': date_str = f"{year}-{key:02d}" else: date_str = str(key) profit = round(vals['income'] - vals['payout'], 2) time_series.append({ 'date': date_str, 'income': vals['income'], 'payout': vals['payout'], 'profit': profit, }) time_series.sort(key=lambda x: x['date']) income_summary = filter_queryset_by_club( DailyIncomeStat.query.filter(**summary_filter), request, ).aggregate(total_income=Sum('total_amount'), total_count=Sum('total_count')) payout_summary = filter_queryset_by_club( DailyPayoutStat.query.filter(**summary_filter), request, ).aggregate(total_payout=Sum('total_amount'), total_count=Sum('total_count')) total_income = float(income_summary['total_income'] or 0) total_payout = float(payout_summary['total_payout'] or 0) return { 'club_id': resolve_club_id_from_request(request), 'scope': resolve_club_scope(request), 'time_series': time_series, 'summary': { 'total_income': total_income, 'total_income_count': income_summary['total_count'] or 0, 'total_payout': total_payout, 'total_payout_count': payout_summary['total_count'] or 0, 'total_profit': round(total_income - total_payout, 2), }, }, None