Files
Django/jituan/services/szxx_stats.py
T

193 lines
7.0 KiB
Python

"""每日收支详细统计(供 /jituan/houtai/szxx,旧 /houtai/szxx 不变)。"""
from datetime import date
from django.db.models import Sum
from django.db.models.functions import ExtractDay, ExtractMonth, ExtractYear
from config.models import DailyIncomeStat, DailyPayoutStat
from jituan.services.club_context import (
filter_club_char_field,
resolve_club_id_from_request,
resolve_club_scope,
)
from jituan.services.daily_finance_display import (
sum_income_stat_for_date,
sum_payout_stat_for_date,
today_income_for_dashboard,
)
def _month_bounds(y, m):
start = date(int(y), int(m), 1)
if int(m) == 12:
end = date(int(y) + 1, 1, 1)
else:
end = date(int(y), int(m) + 1, 1)
return start, end
def _year_bounds(y):
y = int(y)
return date(y, 1, 1), date(y + 1, 1, 1)
def build_szxx_payload(request, granularity, year, month, summary_date):
"""
与 SzxxView 返回结构一致。
统一用 date 字段查询(与 /houtai/caiwu 四个板子一致),不用 year/month/day 冗余列。
"""
now = date.today()
income_base = filter_club_char_field(DailyIncomeStat.objects.all(), request, field='club_id')
payout_base = filter_club_char_field(DailyPayoutStat.objects.all(), request, field='club_id')
if granularity == 'day':
if not year or not month:
return None, '按日统计需要年份和月份'
year, month = int(year), int(month)
chart_start, chart_end = _month_bounds(year, month)
income_rows = (
income_base.filter(date__gte=chart_start, date__lt=chart_end)
.annotate(day_key=ExtractDay('date'))
.values('day_key')
.annotate(total_income=Sum('total_amount'), total_count=Sum('total_count'))
.order_by('day_key')
)
payout_rows = (
payout_base.filter(date__gte=chart_start, date__lt=chart_end)
.annotate(day_key=ExtractDay('date'))
.values('day_key')
.annotate(total_payout=Sum('total_amount'), total_count=Sum('total_count'))
.order_by('day_key')
)
if not summary_date:
summary_date = now.strftime('%Y-%m-%d')
summary_d = date.fromisoformat(str(summary_date)[:10])
if summary_d == now:
inc_amount, inc_count = today_income_for_dashboard(summary_d, request)
else:
inc_amount, inc_count = sum_income_stat_for_date(summary_d, request)
pay_amount, pay_count = sum_payout_stat_for_date(summary_d, request)
income_summary = {'total_income': inc_amount, 'total_count': inc_count}
payout_summary = {'total_payout': pay_amount, 'total_count': pay_count}
def _fmt_day(key):
return f'{year}-{month:02d}-{int(key):02d}'
elif granularity == 'month':
if not year:
return None, '按月统计需要年份'
year = int(year)
chart_start, chart_end = _year_bounds(year)
income_rows = (
income_base.filter(date__gte=chart_start, date__lt=chart_end)
.annotate(month_key=ExtractMonth('date'))
.values('month_key')
.annotate(total_income=Sum('total_amount'), total_count=Sum('total_count'))
.order_by('month_key')
)
payout_rows = (
payout_base.filter(date__gte=chart_start, date__lt=chart_end)
.annotate(month_key=ExtractMonth('date'))
.values('month_key')
.annotate(total_payout=Sum('total_amount'), total_count=Sum('total_count'))
.order_by('month_key')
)
if not summary_date:
summary_date = now.strftime('%Y-%m')
parts = str(summary_date).split('-')
s_start, s_end = _month_bounds(parts[0], parts[1])
income_summary = income_base.filter(date__gte=s_start, date__lt=s_end).aggregate(
total_income=Sum('total_amount'), total_count=Sum('total_count'),
)
payout_summary = payout_base.filter(date__gte=s_start, date__lt=s_end).aggregate(
total_payout=Sum('total_amount'), total_count=Sum('total_count'),
)
def _fmt_day(key):
return f'{year}-{int(key):02d}'
elif granularity == 'year':
income_rows = (
income_base.annotate(year_key=ExtractYear('date'))
.values('year_key')
.annotate(total_income=Sum('total_amount'), total_count=Sum('total_count'))
.order_by('year_key')
)
payout_rows = (
payout_base.annotate(year_key=ExtractYear('date'))
.values('year_key')
.annotate(total_payout=Sum('total_amount'), total_count=Sum('total_count'))
.order_by('year_key')
)
if not summary_date:
summary_date = str(now.year)
s_start, s_end = _year_bounds(int(summary_date))
income_summary = income_base.filter(date__gte=s_start, date__lt=s_end).aggregate(
total_income=Sum('total_amount'), total_count=Sum('total_count'),
)
payout_summary = payout_base.filter(date__gte=s_start, date__lt=s_end).aggregate(
total_payout=Sum('total_amount'), total_count=Sum('total_count'),
)
def _fmt_day(key):
return str(int(key))
else:
return None, '无效的颗粒度'
data_map = {}
if granularity == 'day':
key_field = 'day_key'
elif granularity == 'month':
key_field = 'month_key'
else:
key_field = 'year_key'
for item in income_rows:
key = item[key_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_rows:
key = item[key_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():
profit = round(vals['income'] - vals['payout'], 2)
time_series.append({
'date': _fmt_day(key),
'income': vals['income'],
'payout': vals['payout'],
'profit': profit,
})
time_series.sort(key=lambda x: x['date'])
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