Files
Django/jituan/services/finance_detail_stats.py

494 lines
19 KiB
Python

"""财务子模块统计(订单/会员/其他充值),供 /jituan/houtai/*;旧 /houtai/* 不变。"""
from datetime import date, datetime
from django.db.models import Count, Q, Sum
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
from jituan.constants import DATA_SCOPE_ALL
from jituan.models import ClubHuiyuanPrice
from jituan.services.club_context import (
filter_queryset_by_club,
resolve_club_id_from_request,
resolve_club_scope,
)
from jituan.services.club_penalty import filter_gsfenhong_qs, filter_penalty_qs
from orders.models import Order, Penalty
from products.models import Czjilu, Gsfenhong, Huiyuan, ShangpinLeixing
from rank.models import Bankuai
def _meta(request):
return {
'club_id': resolve_club_id_from_request(request),
'scope': resolve_club_scope(request),
}
def _parse_time_range(granularity, year, month, summary_date):
now = date.today()
if granularity == 'day':
if not year or not month:
return None, '按日统计需要年份和月份'
start_date = date(int(year), int(month), 1)
end_date = date(int(year), int(month) + 1, 1) if int(month) < 12 else date(int(year) + 1, 1, 1)
trunc_func = TruncDate('CreateTime')
if not summary_date:
summary_date = now.strftime('%Y-%m-%d')
summary_q = datetime.strptime(summary_date, '%Y-%m-%d').date()
elif granularity == 'month':
if not year:
return None, '按月统计需要年份'
y = int(year)
start_date = date(y, 1, 1)
end_date = date(y + 1, 1, 1)
trunc_func = TruncMonth('CreateTime')
if not summary_date:
summary_date = now.strftime('%Y-%m')
summary_q = datetime.strptime(summary_date + '-01', '%Y-%m-%d').date()
elif granularity == 'year':
y = int(year or now.year)
start_date = date(y, 1, 1)
end_date = date(y + 1, 1, 1)
trunc_func = TruncYear('CreateTime')
if not summary_date:
summary_date = str(y)
summary_q = date(y, 1, 1)
else:
return None, '无效的颗粒度'
start_dt = datetime.combine(start_date, datetime.min.time())
end_dt = datetime.combine(end_date, datetime.min.time())
return {
'start_dt': start_dt,
'end_dt': end_dt,
'trunc_func': trunc_func,
'summary_q': summary_q,
'granularity': granularity,
'year': int(year) if year is not None else None,
'month': int(month) if month is not None else None,
}, None
def _summary_date_filter(granularity, summary_q, field_prefix='CreateTime'):
if granularity == 'day':
return {f'{field_prefix}__date': summary_q}
if granularity == 'month':
return {f'{field_prefix}__year': summary_q.year, f'{field_prefix}__month': summary_q.month}
return {f'{field_prefix}__year': summary_q.year}
def build_order_types_payload():
types = ShangpinLeixing.query.filter(shenhezhuangtai=1).values('id', 'jieshao')
return list(types)
def build_order_finance_payload(
request,
granularity,
year,
month,
type_id=0,
order_source=0,
summary_date=None,
):
parsed, err = _parse_time_range(granularity, year, month, summary_date)
if err:
return None, err
start_dt = parsed['start_dt']
end_dt = parsed['end_dt']
trunc_func = parsed['trunc_func']
summary_q = parsed['summary_q']
granularity = parsed['granularity']
filters = {
'CreateTime__gte': start_dt,
'CreateTime__lt': end_dt,
'Status__in': [1, 2, 3, 4, 5, 6, 7, 8],
}
if int(type_id or 0) != 0:
filters['ProductTypeID'] = type_id
if int(order_source or 0) in [1, 2]:
filters['Platform'] = int(order_source)
order_base = filter_queryset_by_club(
Order.query.filter(**filters), request, club_field='ClubID',
)
qs = order_base.annotate(period=trunc_func).values('period').annotate(
order_count=Count('id'),
order_amount=Sum('Amount'),
success_count=Count('id', filter=Q(Status=3)),
success_amount=Sum('Amount', filter=Q(Status=3)),
refund_count=Count('id', filter=Q(Status=5)),
PlayerCommission_sum=Sum('PlayerCommission', filter=Q(Status=3)),
dianpu_fenhong_sum=Sum('pingtai_kuozhan__ShopIncome', filter=Q(Status=3, Platform=1)),
platform_success_amount=Sum('Amount', filter=Q(Status=3, Platform=1)),
merchant_success_amount=Sum('Amount', filter=Q(Status=3, Platform=2)),
platform_dashou=Sum('PlayerCommission', filter=Q(Status=3, Platform=1)),
merchant_dashou=Sum('PlayerCommission', filter=Q(Status=3, Platform=2)),
).order_by('period')
time_series = []
for item in qs:
period_str = (
item['period'].strftime('%Y-%m-%d') if granularity == 'day'
else item['period'].strftime('%Y-%m') if granularity == 'month'
else item['period'].strftime('%Y')
)
platform_profit = (item['platform_success_amount'] or 0) - (item['platform_dashou'] or 0) - (item['dianpu_fenhong_sum'] or 0)
merchant_profit = (item['merchant_success_amount'] or 0) - (item['merchant_dashou'] or 0)
time_series.append({
'date': period_str,
'order_count': item['order_count'],
'order_amount': float(item['order_amount'] or 0),
'success_count': item['success_count'],
'success_amount': float(item['success_amount'] or 0),
'refund_count': item['refund_count'],
'dashou_fencheng': float(item['PlayerCommission_sum'] or 0),
'dianpu_fenhong': float(item['dianpu_fenhong_sum'] or 0),
'profit': round(float(platform_profit + merchant_profit), 2),
})
summary_qs = filter_queryset_by_club(Order.query.filter(**filters), request, club_field='ClubID')
total_order_count = summary_qs.count()
total_order_amount = summary_qs.aggregate(s=Sum('Amount'))['s'] or 0
total_success_count = summary_qs.filter(Status=3).count()
total_success_amount = summary_qs.filter(Status=3).aggregate(s=Sum('Amount'))['s'] or 0
total_refund_count = summary_qs.filter(Status=5).count()
total_dashou = summary_qs.filter(Status=3).aggregate(s=Sum('PlayerCommission'))['s'] or 0
total_dianpu_fenhong = summary_qs.filter(Status=3, Platform=1).aggregate(
s=Sum('pingtai_kuozhan__ShopIncome'),
)['s'] or 0
platform_amount = summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('Amount'))['s'] or 0
merchant_amount = summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('Amount'))['s'] or 0
platform_dashou_sum = summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('PlayerCommission'))['s'] or 0
merchant_dashou_sum = summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('PlayerCommission'))['s'] or 0
profit_platform = platform_amount - platform_dashou_sum - total_dianpu_fenhong
profit_merchant = merchant_amount - merchant_dashou_sum
total_profit = profit_platform + profit_merchant
data = {
**_meta(request),
'time_series': time_series,
'summary': {
'total_order_count': total_order_count,
'total_order_amount': round(float(total_order_amount), 2),
'total_success_count': total_success_count,
'total_success_amount': round(float(total_success_amount), 2),
'total_refund_count': total_refund_count,
'total_PlayerCommission': round(float(total_dashou), 2),
'total_dianpu_fenhong': round(float(total_dianpu_fenhong), 2),
'total_profit': round(float(total_profit), 2),
},
}
return data, None
def build_chongzhi_finance_payload(request, granularity, year, month, types=None, summary_date=None):
if types is None:
types = [2, 3, 4, 5, 6]
if isinstance(types, list) and len(types) == 0:
types = [2, 3, 4, 5, 6]
parsed, err = _parse_time_range(granularity, year, month, summary_date)
if err:
return None, err
start_dt = parsed['start_dt']
end_dt = parsed['end_dt']
trunc_func = parsed['trunc_func']
summary_q = parsed['summary_q']
granularity = parsed['granularity']
time_series_map = {}
def ensure_date(ds):
if ds not in time_series_map:
time_series_map[ds] = {}
return time_series_map[ds]
cz_types = [t for t in types if t in [2, 3, 4, 5]]
if cz_types:
cz_qs = filter_queryset_by_club(
Czjilu.query.filter(
CreateTime__gte=start_dt,
CreateTime__lt=end_dt,
leixing__in=cz_types,
zhuangtai=3,
),
request,
).annotate(period=trunc_func).values('period', 'leixing').annotate(
total_count=Count('id'),
total_amount=Sum('jine'),
).order_by('period', 'leixing')
for item in cz_qs:
period_str = (
item['period'].strftime('%Y-%m-%d') if granularity == 'day'
else item['period'].strftime('%Y-%m') if granularity == 'month'
else item['period'].strftime('%Y')
)
lx = item['leixing']
amount = float(item['total_amount'] or 0)
count = item['total_count']
profit = amount if lx == 3 else 0.0
day_data = ensure_date(period_str)
day_data[str(lx)] = {
'count': count,
'amount': amount,
'profit': profit,
'fenhong_count': 0,
'fenhong_amount': 0.0,
}
if 6 in types:
penalty_qs = filter_penalty_qs(
Penalty.query.filter(CreateTime__gte=start_dt, CreateTime__lt=end_dt, Status=2),
request,
).annotate(period=trunc_func).values('period').annotate(
penalty_count=Count('id'),
penalty_amount=Sum('FineAmount'),
fenhong_amount=Sum('ApplicantBonusAmount'),
fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0)),
).order_by('period')
for item in penalty_qs:
period_str = (
item['period'].strftime('%Y-%m-%d') if granularity == 'day'
else item['period'].strftime('%Y-%m') if granularity == 'month'
else item['period'].strftime('%Y')
)
day_data = ensure_date(period_str)
penalty_amt = float(item['penalty_amount'] or 0)
fenhong_amt = float(item['fenhong_amount'] or 0)
day_data['6'] = {
'count': item['penalty_count'],
'amount': penalty_amt,
'profit': round(penalty_amt - fenhong_amt, 2),
'fenhong_count': item['fenhong_count'],
'fenhong_amount': fenhong_amt,
}
time_series = []
for period_str in sorted(time_series_map.keys()):
entry = {'date': period_str, 'types': {}}
for type_code in sorted(time_series_map[period_str].keys()):
entry['types'][type_code] = time_series_map[period_str][type_code]
time_series.append(entry)
summary_result = {'by_type': {}, 'total_count': 0, 'total_amount': 0.0, 'total_profit': 0.0}
for lx in cz_types:
filter_kwargs = {'leixing': lx, 'zhuangtai': 3}
filter_kwargs.update(_summary_date_filter(granularity, summary_q))
agg = filter_queryset_by_club(Czjilu.query.filter(**filter_kwargs), request).aggregate(
count=Count('id'), amount=Sum('jine'),
)
count = agg['count'] or 0
amount = float(agg['amount'] or 0)
profit = amount if lx == 3 else 0.0
summary_result['by_type'][str(lx)] = {
'count': count,
'amount': round(amount, 2),
'profit': round(profit, 2),
'fenhong_count': 0,
'fenhong_amount': 0.0,
}
summary_result['total_count'] += count
summary_result['total_amount'] += amount
summary_result['total_profit'] += profit
if 6 in types:
penalty_filter = {'Status': 2}
penalty_filter.update(_summary_date_filter(granularity, summary_q))
agg = filter_penalty_qs(Penalty.query.filter(**penalty_filter), request).aggregate(
count=Count('id'),
amount=Sum('FineAmount'),
fenhong_amount=Sum('ApplicantBonusAmount'),
fenhong_count=Count('id', filter=Q(ApplicantBonusAmount__gt=0)),
)
penalty_count = agg['count'] or 0
penalty_amount = float(agg['amount'] or 0)
fh_amount = float(agg['fenhong_amount'] or 0)
fh_count = agg['fenhong_count'] or 0
profit = round(penalty_amount - fh_amount, 2)
summary_result['by_type']['6'] = {
'count': penalty_count,
'amount': round(penalty_amount, 2),
'profit': profit,
'fenhong_count': fh_count,
'fenhong_amount': round(fh_amount, 2),
}
summary_result['total_count'] += penalty_count
summary_result['total_amount'] += penalty_amount
summary_result['total_profit'] += profit
summary_result['total_amount'] = round(summary_result['total_amount'], 2)
summary_result['total_profit'] = round(summary_result['total_profit'], 2)
return {
**_meta(request),
'time_series': time_series,
'summary': summary_result,
}, None
def build_huiyuan_bankuai_payload(request):
club_id = resolve_club_id_from_request(request)
price_map = {}
if resolve_club_scope(request) != DATA_SCOPE_ALL:
for row in ClubHuiyuanPrice.query.filter(club_id=club_id, is_enabled=True):
price_map[row.huiyuan_id] = {
'jiage': float(row.jiage or 0),
'guanshifc': float(row.guanshifc or 0),
'zuzhangfc': float(row.zuzhangfc or 0),
}
bankuai_list = []
for bk in Bankuai.query.all():
members = []
for m in Huiyuan.query.filter(bankuai=bk).values('huiyuan_id', 'jieshao', 'bankuai_id'):
item = dict(m)
if m['huiyuan_id'] in price_map:
item['club_price'] = price_map[m['huiyuan_id']]
members.append(item)
bankuai_list.append({
'bankuai_id': bk.bankuai_id,
'mingcheng': bk.mingcheng,
'members': members,
})
return {**_meta(request), 'bankuai_list': bankuai_list}
def build_huiyuan_stats_payload(
request,
huiyuan_id,
granularity,
year,
month,
include_guanshi=False,
include_zuzhang=False,
summary_date=None,
):
if not huiyuan_id:
return None, '缺少会员ID'
parsed, err = _parse_time_range(granularity, year, month, summary_date)
if err:
return None, err
start_dt = parsed['start_dt']
end_dt = parsed['end_dt']
trunc_func = parsed['trunc_func']
summary_q = parsed['summary_q']
granularity = parsed['granularity']
chongzhi_filter = {
'huiyuan_id': huiyuan_id,
'leixing': 1,
'zhuangtai': 3,
'CreateTime__gte': start_dt,
'CreateTime__lt': end_dt,
}
fenhong_filter = {
'huiyuan_id': huiyuan_id,
'CreateTime__gte': start_dt,
'CreateTime__lt': end_dt,
}
chongzhi_qs = filter_queryset_by_club(
Czjilu.query.filter(**chongzhi_filter), request,
).annotate(period=trunc_func).values('period').annotate(
chongzhi_count=Count('id'),
chongzhi_amount=Sum('jine'),
).order_by('period')
fenhong_qs = None
if include_guanshi or include_zuzhang:
fenhong_agg = {}
if include_guanshi:
fenhong_agg['guanshi_amount'] = Sum('fenhong')
if include_zuzhang:
fenhong_agg['zuzhang_amount'] = Sum('zuzhang_fenhong')
fenhong_qs = filter_gsfenhong_qs(
Gsfenhong.query.filter(**fenhong_filter), request,
).annotate(period=trunc_func).values('period').annotate(**fenhong_agg).order_by('period')
data_map = {}
for item in chongzhi_qs:
period_str = (
item['period'].strftime('%Y-%m-%d') if granularity == 'day'
else item['period'].strftime('%Y-%m') if granularity == 'month'
else item['period'].strftime('%Y')
)
data_map[period_str] = {
'chongzhi_count': item['chongzhi_count'],
'chongzhi_amount': float(item['chongzhi_amount'] or 0),
}
if fenhong_qs:
for item in fenhong_qs:
period_str = (
item['period'].strftime('%Y-%m-%d') if granularity == 'day'
else item['period'].strftime('%Y-%m') if granularity == 'month'
else item['period'].strftime('%Y')
)
if period_str not in data_map:
data_map[period_str] = {'chongzhi_count': 0, 'chongzhi_amount': 0.0}
if include_guanshi:
data_map[period_str]['guanshi_amount'] = float(item.get('guanshi_amount') or 0)
if include_zuzhang:
data_map[period_str]['zuzhang_amount'] = float(item.get('zuzhang_amount') or 0)
time_series = []
for period_str, vals in data_map.items():
guanshi = vals.get('guanshi_amount', 0) if include_guanshi else 0
zuzhang = vals.get('zuzhang_amount', 0) if include_zuzhang else 0
vals['shouyi'] = round(vals['chongzhi_amount'] - guanshi - zuzhang, 2)
vals['date'] = period_str
time_series.append(vals)
time_series.sort(key=lambda x: x['date'])
summary_chongzhi_filter = dict(chongzhi_filter)
summary_chongzhi_filter.pop('CreateTime__gte', None)
summary_chongzhi_filter.pop('CreateTime__lt', None)
summary_chongzhi_filter.update(_summary_date_filter(granularity, summary_q))
summary_chongzhi = filter_queryset_by_club(
Czjilu.query.filter(**summary_chongzhi_filter), request,
).aggregate(total_count=Count('id'), total_amount=Sum('jine'))
total_chongzhi_count = summary_chongzhi['total_count'] or 0
total_chongzhi_amount = float(summary_chongzhi['total_amount'] or 0)
total_guanshi = 0.0
total_zuzhang = 0.0
if include_guanshi or include_zuzhang:
summary_fenhong_filter = dict(fenhong_filter)
summary_fenhong_filter.pop('CreateTime__gte', None)
summary_fenhong_filter.pop('CreateTime__lt', None)
summary_fenhong_filter.update(_summary_date_filter(granularity, summary_q))
fenhong_agg = {}
if include_guanshi:
fenhong_agg['guanshi_sum'] = Sum('fenhong')
if include_zuzhang:
fenhong_agg['zuzhang_sum'] = Sum('zuzhang_fenhong')
summary_fenhong = filter_gsfenhong_qs(
Gsfenhong.query.filter(**summary_fenhong_filter), request,
).aggregate(**fenhong_agg)
total_guanshi = float(summary_fenhong.get('guanshi_sum') or 0)
total_zuzhang = float(summary_fenhong.get('zuzhang_sum') or 0)
total_shouyi = round(total_chongzhi_amount - total_guanshi - total_zuzhang, 2)
return {
**_meta(request),
'time_series': time_series,
'summary': {
'total_chongzhi_count': total_chongzhi_count,
'total_chongzhi_amount': total_chongzhi_amount,
'total_guanshi_fenhong': total_guanshi,
'total_zuzhang_fenhong': total_zuzhang,
'total_shouyi': total_shouyi,
},
}, None