Files
Django/jituan/services/szxx_stats.py

187 lines
6.7 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.
"""每日收支详细统计(供 /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,
)
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])
income_summary = income_base.filter(date=summary_d).aggregate(
total_income=Sum('total_amount'), total_count=Sum('total_count'),
)
payout_summary = payout_base.filter(date=summary_d).aggregate(
total_payout=Sum('total_amount'), total_count=Sum('total_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