fix: 财务接口+恢复今日收入

This commit is contained in:
XingQue
2026-06-24 21:35:55 +08:00
parent 84665e0c5d
commit b1fbc3cd8f
5 changed files with 425 additions and 26 deletions

View File

@@ -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)

View File

@@ -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