138 lines
4.7 KiB
Python
138 lines
4.7 KiB
Python
"""财务看板:只读 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
|