fix: 财务接口+恢复今日收入
This commit is contained in:
@@ -90,26 +90,20 @@ def build_caiwu_payload(request):
|
||||
scope = resolve_club_scope(request)
|
||||
today_income_qs = income_qs.filter(date=today)
|
||||
today_payout_qs = payout_qs.filter(date=today)
|
||||
if scope == DATA_SCOPE_ALL:
|
||||
inc_agg = today_income_qs.aggregate(
|
||||
total_amount=Sum('total_amount'),
|
||||
total_count=Sum('total_count'),
|
||||
)
|
||||
today_income_amount = float(inc_agg['total_amount'] or 0.00)
|
||||
today_income_count = int(inc_agg['total_count'] or 0)
|
||||
pay_agg = today_payout_qs.aggregate(
|
||||
total_amount=Sum('total_amount'),
|
||||
total_count=Sum('total_count'),
|
||||
)
|
||||
today_payout_amount = float(pay_agg['total_amount'] or 0.00)
|
||||
today_payout_count = int(pay_agg['total_count'] or 0)
|
||||
else:
|
||||
today_income = today_income_qs.first()
|
||||
today_income_amount = float(today_income.total_amount) if today_income else 0.00
|
||||
today_income_count = today_income.total_count if today_income else 0
|
||||
today_payout = today_payout_qs.first()
|
||||
today_payout_amount = float(today_payout.total_amount) if today_payout else 0.00
|
||||
today_payout_count = today_payout.total_count if today_payout else 0
|
||||
|
||||
inc_agg = today_income_qs.aggregate(
|
||||
total_amount=Sum('total_amount'),
|
||||
total_count=Sum('total_count'),
|
||||
)
|
||||
today_income_amount = float(inc_agg['total_amount'] or 0.00)
|
||||
today_income_count = int(inc_agg['total_count'] or 0)
|
||||
|
||||
pay_agg = today_payout_qs.aggregate(
|
||||
total_amount=Sum('total_amount'),
|
||||
total_count=Sum('total_count'),
|
||||
)
|
||||
today_payout_amount = float(pay_agg['total_amount'] or 0.00)
|
||||
today_payout_count = int(pay_agg['total_count'] or 0)
|
||||
|
||||
today_orders = order_qs.filter(
|
||||
CreateTime__gte=today_start,
|
||||
@@ -160,10 +154,17 @@ def build_caiwu_payload(request):
|
||||
|
||||
szjilu_payload = _build_szjilu_payload(request)
|
||||
|
||||
# 账户总收入/总支出:日收支统计表全历史 SUM(与「每日收支详细」按年汇总一致)
|
||||
total_income = float(income_qs.aggregate(total=Sum('total_amount'))['total'] or 0.00)
|
||||
total_payout = float(payout_qs.aggregate(total=Sum('total_amount'))['total'] or 0.00)
|
||||
platform_profit = round(total_income - total_payout, 2)
|
||||
# 账户总收入/总支出:始终全集团合计(切换俱乐部也不变少)
|
||||
total_income = float(
|
||||
DailyIncomeStat.query.aggregate(total=Sum('total_amount'))['total'] or 0.00
|
||||
)
|
||||
total_payout = float(
|
||||
DailyPayoutStat.query.aggregate(total=Sum('total_amount'))['total'] or 0.00
|
||||
)
|
||||
# 俱乐部利润:当前视图范围内累计收入减累计支出
|
||||
scoped_total_income = float(income_qs.aggregate(total=Sum('total_amount'))['total'] or 0.00)
|
||||
scoped_total_payout = float(payout_qs.aggregate(total=Sum('total_amount'))['total'] or 0.00)
|
||||
platform_profit = round(scoped_total_income - scoped_total_payout, 2)
|
||||
|
||||
daily_stats = []
|
||||
since = today - timedelta(days=30)
|
||||
|
||||
@@ -738,3 +738,283 @@ def reset_all_daily_szjilu():
|
||||
szjilu.save(update_fields=['DailyExpense', 'DailyFlow', 'UpdateTime'])
|
||||
updated += 1
|
||||
return updated
|
||||
|
||||
|
||||
# 合法微信支付收入(不含退款 Status=5)
|
||||
_LEGIT_ORDER_STATUSES = [1, 2, 3, 4, 6, 7, 8]
|
||||
_LEGIT_CZ_LEIXING = [1, 2, 3, 4, 5]
|
||||
|
||||
|
||||
def collect_legitimate_wechat_payments(since_year, until_year):
|
||||
"""
|
||||
从业务单据重建:每笔 biz_ref 全局只出现一次。
|
||||
返回 [(biz_ref, club_id, amount, stat_date, pay_datetime, kind), ...]
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from orders.models import Order
|
||||
from products.models import Czjilu
|
||||
|
||||
start = datetime(since_year, 1, 1)
|
||||
end = datetime(until_year + 1, 1, 1)
|
||||
seen = {}
|
||||
items = []
|
||||
|
||||
def _add(ref, club_id, amount, pay_dt, kind):
|
||||
ref = (ref or '').strip()
|
||||
if not ref or ref in seen:
|
||||
return
|
||||
jine = Decimal(str(amount or 0))
|
||||
if jine <= 0:
|
||||
return
|
||||
cid = normalize_szjilu_club_id(club_id)
|
||||
if not pay_dt:
|
||||
pay_dt = datetime.now()
|
||||
stat_d = pay_dt.date() if hasattr(pay_dt, 'date') else pay_dt
|
||||
seen[ref] = True
|
||||
items.append((ref, cid, jine, stat_d, pay_dt, kind))
|
||||
|
||||
for od in Order.query.filter(
|
||||
CreateTime__gte=start,
|
||||
CreateTime__lt=end,
|
||||
Status__in=_LEGIT_ORDER_STATUSES,
|
||||
):
|
||||
_add(
|
||||
f'order:{od.OrderID}',
|
||||
getattr(od, 'ClubID', None),
|
||||
od.Amount,
|
||||
od.CreateTime,
|
||||
'order',
|
||||
)
|
||||
|
||||
for cz in Czjilu.query.filter(
|
||||
CreateTime__gte=start,
|
||||
CreateTime__lt=end,
|
||||
zhuangtai=3,
|
||||
leixing__in=_LEGIT_CZ_LEIXING,
|
||||
):
|
||||
_add(
|
||||
f'cz:{cz.dingdan_id}',
|
||||
getattr(cz, 'club_id', None),
|
||||
cz.jine,
|
||||
cz.CreateTime,
|
||||
f'cz_{cz.leixing}',
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def rebuild_income_stats_from_scratch(since_year=None, until_year=None, dry_run=True):
|
||||
"""
|
||||
终极恢复:清空全部收支脏数据 → 按真实微信支付逐笔重建(每笔唯一)。
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db.models import Sum
|
||||
|
||||
until_year = until_year or date.today().year
|
||||
since_year = since_year or (until_year - 1)
|
||||
|
||||
before = {
|
||||
'daily': float(DailyIncomeStat.objects.aggregate(t=Sum('total_amount'))['t'] or 0),
|
||||
'logs': 0,
|
||||
'log_cnt': 0,
|
||||
}
|
||||
try:
|
||||
before['logs'] = float(PlatformIncomeLog.objects.aggregate(t=Sum('amount'))['t'] or 0)
|
||||
before['log_cnt'] = PlatformIncomeLog.objects.count()
|
||||
except _PLATFORM_LOG_ERRORS:
|
||||
pass
|
||||
|
||||
items = collect_legitimate_wechat_payments(since_year, until_year)
|
||||
total_amt = sum((x[2] for x in items), _ZERO)
|
||||
today = date.today()
|
||||
today_amt = sum((x[2] for x in items if x[3] == today), _ZERO)
|
||||
today_cnt = sum(1 for x in items if x[3] == today)
|
||||
|
||||
month_amt = sum(
|
||||
(x[2] for x in items if x[3].year == today.year and x[3].month == today.month),
|
||||
_ZERO,
|
||||
)
|
||||
|
||||
result = {
|
||||
'since_year': since_year,
|
||||
'until_year': until_year,
|
||||
'before_daily_total': before['daily'],
|
||||
'before_log_total': before['logs'],
|
||||
'before_log_count': before['log_cnt'],
|
||||
'after_total': float(total_amt),
|
||||
'after_count': len(items),
|
||||
'today_amount': float(today_amt),
|
||||
'today_count': today_cnt,
|
||||
'month_amount': float(month_amt),
|
||||
'items': items,
|
||||
'dry_run': dry_run,
|
||||
}
|
||||
|
||||
if dry_run:
|
||||
return result
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
PlatformIncomeLog.objects.all().delete()
|
||||
except _PLATFORM_LOG_ERRORS as exc:
|
||||
raise RuntimeError(f'platform_income_log 不可用: {exc}') from exc
|
||||
DailyIncomeStat.objects.all().delete()
|
||||
|
||||
by_day_club = defaultdict(lambda: {'amt': _ZERO, 'cnt': 0})
|
||||
for ref, cid, jine, stat_d, pay_dt, _kind in items:
|
||||
log = PlatformIncomeLog.objects.create(biz_ref=ref, club_id=cid, amount=jine)
|
||||
if pay_dt:
|
||||
PlatformIncomeLog.objects.filter(pk=log.pk).update(CreateTime=pay_dt)
|
||||
key = (stat_d, cid)
|
||||
by_day_club[key]['amt'] += jine
|
||||
by_day_club[key]['cnt'] += 1
|
||||
|
||||
for (stat_d, cid), agg in by_day_club.items():
|
||||
DailyIncomeStat.objects.create(
|
||||
date=stat_d,
|
||||
club_id=cid,
|
||||
year=stat_d.year,
|
||||
month=stat_d.month,
|
||||
day=stat_d.day,
|
||||
total_amount=agg['amt'],
|
||||
total_count=agg['cnt'],
|
||||
)
|
||||
|
||||
today_d = date.today()
|
||||
club_ids = set(by_day_club.keys())
|
||||
club_ids |= {(today_d, row.club_id) for row in Szjilu.objects.all()}
|
||||
|
||||
all_clubs = {cid for _, cid in club_ids}
|
||||
for cid in all_clubs:
|
||||
total = PlatformIncomeLog.objects.filter(club_id=cid).aggregate(
|
||||
t=Sum('amount'),
|
||||
)['t'] or _ZERO
|
||||
today_flow = PlatformIncomeLog.objects.filter(
|
||||
club_id=cid, CreateTime__date=today_d,
|
||||
).aggregate(t=Sum('amount'))['t'] or _ZERO
|
||||
szjilu, _ = Szjilu.objects.select_for_update().get_or_create(
|
||||
club_id=cid,
|
||||
defaults=dict(_SZJILU_DEFAULTS),
|
||||
)
|
||||
szjilu.TotalIncome = total
|
||||
szjilu.TotalFlow = total
|
||||
szjilu.DailyFlow = today_flow
|
||||
szjilu.save(update_fields=['TotalIncome', 'TotalFlow', 'DailyFlow', 'UpdateTime'])
|
||||
|
||||
result['after_daily_total'] = float(
|
||||
DailyIncomeStat.objects.aggregate(t=Sum('total_amount'))['t'] or 0
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def collect_today_live_wechat_payments(stat_date=None):
|
||||
"""今日实时微信支付(含当日下单或当日完成支付的订单/充值)。"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from orders.models import Order
|
||||
from products.models import Czjilu
|
||||
|
||||
stat_date = stat_date or date.today()
|
||||
day_start = datetime.combine(stat_date, datetime.min.time())
|
||||
day_end = day_start + timedelta(days=1)
|
||||
seen = {}
|
||||
items = []
|
||||
|
||||
def _add(ref, club_id, amount, kind):
|
||||
ref = (ref or '').strip()
|
||||
if not ref or ref in seen:
|
||||
return
|
||||
jine = Decimal(str(amount or 0))
|
||||
if jine <= 0:
|
||||
return
|
||||
cid = normalize_szjilu_club_id(club_id)
|
||||
seen[ref] = True
|
||||
items.append((ref, cid, jine, kind))
|
||||
|
||||
for od in Order.query.filter(Status__in=_LEGIT_ORDER_STATUSES).filter(
|
||||
Q(CreateTime__gte=day_start, CreateTime__lt=day_end)
|
||||
| Q(UpdateTime__gte=day_start, UpdateTime__lt=day_end),
|
||||
):
|
||||
_add(f'order:{od.OrderID}', getattr(od, 'ClubID', None), od.Amount, 'order')
|
||||
|
||||
for cz in Czjilu.query.filter(zhuangtai=3, leixing__in=_LEGIT_CZ_LEIXING).filter(
|
||||
Q(CreateTime__gte=day_start, CreateTime__lt=day_end)
|
||||
| Q(UpdateTime__gte=day_start, UpdateTime__lt=day_end),
|
||||
):
|
||||
_add(f'cz:{cz.dingdan_id}', getattr(cz, 'club_id', None), cz.jine, f'cz_{cz.leixing}')
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def repopulate_today_income_stats(stat_date=None, dry_run=True):
|
||||
"""只重建指定日期的日收入(不删历史其它天),修复今日显示为 0。"""
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, time as dt_time
|
||||
|
||||
from django.db.models import Sum
|
||||
from django.utils import timezone
|
||||
|
||||
stat_date = stat_date or date.today()
|
||||
before = DailyIncomeStat.objects.filter(date=stat_date).aggregate(
|
||||
amt=Sum('total_amount'), cnt=Sum('total_count'),
|
||||
)
|
||||
items = collect_today_live_wechat_payments(stat_date)
|
||||
total = sum((x[2] for x in items), _ZERO)
|
||||
|
||||
result = {
|
||||
'date': str(stat_date),
|
||||
'before_amount': float(before['amt'] or 0),
|
||||
'before_count': int(before['cnt'] or 0),
|
||||
'after_amount': float(total),
|
||||
'after_count': len(items),
|
||||
'items': items,
|
||||
'dry_run': dry_run,
|
||||
}
|
||||
if dry_run:
|
||||
return result
|
||||
|
||||
pay_ts = timezone.make_aware(datetime.combine(stat_date, dt_time(12, 0, 0)))
|
||||
with transaction.atomic():
|
||||
DailyIncomeStat.objects.filter(date=stat_date).delete()
|
||||
|
||||
by_club = defaultdict(lambda: {'amt': _ZERO, 'cnt': 0})
|
||||
for ref, cid, jine, _kind in items:
|
||||
if not income_log_exists(ref):
|
||||
try:
|
||||
log = PlatformIncomeLog.objects.create(biz_ref=ref, club_id=cid, amount=jine)
|
||||
PlatformIncomeLog.objects.filter(pk=log.pk).update(CreateTime=pay_ts)
|
||||
except IntegrityError:
|
||||
pass
|
||||
by_club[cid]['amt'] += jine
|
||||
by_club[cid]['cnt'] += 1
|
||||
|
||||
for cid, agg in by_club.items():
|
||||
DailyIncomeStat.objects.create(
|
||||
date=stat_date,
|
||||
club_id=cid,
|
||||
year=stat_date.year,
|
||||
month=stat_date.month,
|
||||
day=stat_date.day,
|
||||
total_amount=agg['amt'],
|
||||
total_count=agg['cnt'],
|
||||
)
|
||||
|
||||
today_d = date.today()
|
||||
for cid, agg in by_club.items():
|
||||
szjilu, _ = Szjilu.objects.select_for_update().get_or_create(
|
||||
club_id=cid, defaults=dict(_SZJILU_DEFAULTS),
|
||||
)
|
||||
if stat_date == today_d:
|
||||
szjilu.DailyFlow = agg['amt']
|
||||
total_all = PlatformIncomeLog.objects.filter(club_id=cid).aggregate(
|
||||
t=Sum('amount'),
|
||||
)['t'] or _ZERO
|
||||
szjilu.TotalIncome = total_all
|
||||
szjilu.TotalFlow = total_all
|
||||
szjilu.save(update_fields=['TotalIncome', 'TotalFlow', 'DailyFlow', 'UpdateTime'])
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user