fix: 支付入账降级 + 财务全平台汇总

This commit is contained in:
XingQue
2026-06-24 20:58:28 +08:00
parent a693e35177
commit 0827da594c
4 changed files with 163 additions and 56 deletions

View File

@@ -6939,15 +6939,20 @@ class CaiwuView(APIView):
today_start = datetime.combine(today, datetime.min.time()) today_start = datetime.combine(today, datetime.min.time())
today_end = today_start + timedelta(days=1) today_end = today_start + timedelta(days=1)
# 今日收入 # 今日收入 / 支出(全平台各俱乐部合计)
today_income = DailyIncomeStat.query.filter(date=today).first() today_inc = DailyIncomeStat.query.filter(date=today).aggregate(
today_income_amount = float(today_income.total_amount) if today_income else 0.00 total_amount=Sum('total_amount'),
today_income_count = today_income.total_count if today_income else 0 total_count=Sum('total_count'),
)
today_income_amount = float(today_inc['total_amount'] or 0.00)
today_income_count = int(today_inc['total_count'] or 0)
# 今日支出 today_pay = DailyPayoutStat.query.filter(date=today).aggregate(
today_payout = DailyPayoutStat.query.filter(date=today).first() total_amount=Sum('total_amount'),
today_payout_amount = float(today_payout.total_amount) if today_payout else 0.00 total_count=Sum('total_count'),
today_payout_count = today_payout.total_count if today_payout else 0 )
today_payout_amount = float(today_pay['total_amount'] or 0.00)
today_payout_count = int(today_pay['total_count'] or 0)
# 今日订单(已付款及之后的状态) # 今日订单(已付款及之后的状态)
today_orders = Order.query.filter( today_orders = Order.query.filter(
@@ -7016,21 +7021,38 @@ class CaiwuView(APIView):
total_payout = float(DailyPayoutStat.query.aggregate(total=Sum('total_amount'))['total'] or 0.00) total_payout = float(DailyPayoutStat.query.aggregate(total=Sum('total_amount'))['total'] or 0.00)
platform_profit = round(total_income - total_payout, 2) platform_profit = round(total_income - total_payout, 2)
# 近30天每日收支明细 # 近30天每日收支明细(按日期合并各俱乐部)
daily_stats = [] daily_stats = []
income_qs = DailyIncomeStat.query.order_by('-date')[:30] since = today - timedelta(days=30)
payout_dict = { income_by_date = {
str(p.date): float(p.total_amount) str(row['date']): {
for p in DailyPayoutStat.query.filter(date__gte=today - timedelta(days=30)) 'income': float(row['total_amount'] or 0),
'income_count': int(row['total_count'] or 0),
}
for row in DailyIncomeStat.query.filter(date__gte=since).values('date').annotate(
total_amount=Sum('total_amount'),
total_count=Sum('total_count'),
)
} }
for inc in income_qs: payout_by_date = {
d = str(inc.date) str(row['date']): float(row['total_amount'] or 0)
for row in DailyPayoutStat.query.filter(date__gte=since).values('date').annotate(
total_amount=Sum('total_amount'),
)
}
all_dates = sorted(
set(income_by_date.keys()) | set(payout_by_date.keys()),
reverse=True,
)[:30]
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({ daily_stats.append({
'date': d, 'date': d,
'income': float(inc.total_amount), 'income': inc['income'],
'income_count': inc.total_count, 'income_count': inc['income_count'],
'payout': payout_dict.get(d, 0.00), 'payout': payout_amt,
'profit': round(float(inc.total_amount) - payout_dict.get(d, 0.00), 2) 'profit': round(inc['income'] - payout_amt, 2),
}) })
return Response({ return Response({

View File

@@ -87,13 +87,29 @@ def build_caiwu_payload(request):
income_qs = _daily_income_qs(request) income_qs = _daily_income_qs(request)
payout_qs = _daily_payout_qs(request) payout_qs = _daily_payout_qs(request)
today_income = income_qs.filter(date=today).first() scope = resolve_club_scope(request)
today_income_amount = float(today_income.total_amount) if today_income else 0.00 today_income_qs = income_qs.filter(date=today)
today_income_count = today_income.total_count if today_income else 0 today_payout_qs = payout_qs.filter(date=today)
if scope == DATA_SCOPE_ALL:
today_payout = payout_qs.filter(date=today).first() inc_agg = today_income_qs.aggregate(
today_payout_amount = float(today_payout.total_amount) if today_payout else 0.00 total_amount=Sum('total_amount'),
today_payout_count = today_payout.total_count if today_payout else 0 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
today_orders = order_qs.filter( today_orders = order_qs.filter(
CreateTime__gte=today_start, CreateTime__gte=today_start,
@@ -150,20 +166,54 @@ def build_caiwu_payload(request):
platform_profit = round(total_income - total_payout, 2) platform_profit = round(total_income - total_payout, 2)
daily_stats = [] daily_stats = []
income_list = income_qs.order_by('-date')[:30] since = today - timedelta(days=30)
payout_dict = { if scope == DATA_SCOPE_ALL:
str(p.date): float(p.total_amount) income_by_date = {
for p in payout_qs.filter(date__gte=today - timedelta(days=30)) str(row['date']): {
} 'income': float(row['total_amount'] or 0),
for inc in income_list: 'income_count': int(row['total_count'] or 0),
d = str(inc.date) }
daily_stats.append({ for row in income_qs.filter(date__gte=since).values('date').annotate(
'date': d, total_amount=Sum('total_amount'),
'income': float(inc.total_amount), total_count=Sum('total_count'),
'income_count': inc.total_count, )
'payout': payout_dict.get(d, 0.00), }
'profit': round(float(inc.total_amount) - payout_dict.get(d, 0.00), 2), payout_by_date = {
}) str(row['date']): float(row['total_amount'] or 0)
for row in payout_qs.filter(date__gte=since).values('date').annotate(
total_amount=Sum('total_amount'),
)
}
all_dates = sorted(
set(income_by_date.keys()) | set(payout_by_date.keys()),
reverse=True,
)[:30]
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),
})
else:
income_list = income_qs.filter(date__gte=since).order_by('-date')[:30]
payout_dict = {
str(p.date): float(p.total_amount)
for p in payout_qs.filter(date__gte=since)
}
for inc in income_list:
d = str(inc.date)
payout_amt = payout_dict.get(d, 0.00)
daily_stats.append({
'date': d,
'income': float(inc.total_amount),
'income_count': inc.total_count,
'payout': payout_amt,
'profit': round(float(inc.total_amount) - payout_amt, 2),
})
return { return {
'club_id': club_id, 'club_id': club_id,

View File

@@ -3,6 +3,7 @@ import logging
from decimal import Decimal from decimal import Decimal
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.db.utils import OperationalError, ProgrammingError
from config.models import PlatformIncomeLog, Szjilu from config.models import PlatformIncomeLog, Szjilu
from jituan.constants import CLUB_ID_DEFAULT from jituan.constants import CLUB_ID_DEFAULT
@@ -18,6 +19,8 @@ _SZJILU_DEFAULTS = {
'DailyFlow': _ZERO, 'DailyFlow': _ZERO,
} }
_PLATFORM_LOG_ERRORS = (ProgrammingError, OperationalError)
def normalize_szjilu_club_id(club_id): def normalize_szjilu_club_id(club_id):
return (club_id or '').strip() or CLUB_ID_DEFAULT return (club_id or '').strip() or CLUB_ID_DEFAULT
@@ -43,17 +46,27 @@ def apply_szjilu_income(amount, club_id=None):
szjilu.save() szjilu.save()
def _apply_income_stats(amount, club_id):
"""写入 daily_income_stat + szjilu核心统计必须成功"""
from orders.utils import update_daily_income
cid = normalize_szjilu_club_id(club_id)
jine = Decimal(str(amount))
update_daily_income(jine, cid)
apply_szjilu_income(jine, cid)
def record_wechat_income_once(amount, club_id=None, biz_ref=None): def record_wechat_income_once(amount, club_id=None, biz_ref=None):
""" """
微信入账:同一 biz_ref 只记一次 daily_income_stat + szjilu。 微信入账:同一 biz_ref 只记一次 daily_income_stat + szjilu。
返回 True=本次新入账False=已记过(重复回调/重复代码路径)。 返回 True=本次新入账False=已记过(重复回调)。
若 platform_income_log 表不可用,仍记入收入(与改造前一致,不能因幂等表故障丢统计)。
""" """
ref = (biz_ref or '').strip() ref = (biz_ref or '').strip()
if not ref: if not ref:
raise ValueError('biz_ref 不能为空') raise ValueError('biz_ref 不能为空')
cid = normalize_szjilu_club_id(club_id) cid = normalize_szjilu_club_id(club_id)
jine = Decimal(str(amount)) jine = Decimal(str(amount))
from orders.utils import update_daily_income
try: try:
with transaction.atomic(): with transaction.atomic():
@@ -65,12 +78,28 @@ def record_wechat_income_once(amount, club_id=None, biz_ref=None):
except IntegrityError: except IntegrityError:
logger.info('平台入账已存在,跳过重复记账 biz_ref=%s', ref) logger.info('平台入账已存在,跳过重复记账 biz_ref=%s', ref)
return False return False
except _PLATFORM_LOG_ERRORS as exc:
logger.warning(
'platform_income_log 不可用(%s),跳过幂等直接记入收入 biz_ref=%s',
exc, ref,
)
_apply_income_stats(jine, cid)
return True
except Exception as exc:
logger.error(
'platform_income_log 写入异常,仍记入收入 biz_ref=%s err=%s',
ref, exc, exc_info=True,
)
_apply_income_stats(jine, cid)
return True
try: try:
update_daily_income(jine, cid) _apply_income_stats(jine, cid)
apply_szjilu_income(jine, cid)
except Exception: except Exception:
PlatformIncomeLog.objects.filter(biz_ref=ref).delete() try:
PlatformIncomeLog.objects.filter(biz_ref=ref).delete()
except Exception:
pass
raise raise
logger.info('平台入账记账成功 biz_ref=%s club=%s amount=%s', ref, cid, jine) logger.info('平台入账记账成功 biz_ref=%s club=%s amount=%s', ref, cid, jine)
return True return True

View File

@@ -307,11 +307,14 @@ class WechatPayNotifyView(APIView):
# ============================================= # =============================================
# 更新收支记录(总累计) # 更新收支记录(总累计);统计失败不得回滚订单支付
from jituan.services.szjilu_accounting import record_wechat_income_once try:
record_wechat_income_once( from jituan.services.szjilu_accounting import record_wechat_income_once
dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}', record_wechat_income_once(
) dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}',
)
except Exception as e:
logger.error(f'订单收支统计失败 {out_trade_no}: {e}', exc_info=True)
# 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用) # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
order_info = { order_info = {
@@ -569,12 +572,15 @@ class PaymentVerifyView(APIView):
# 更新收支记录 # 更新收支记录
from jituan.services.szjilu_accounting import record_wechat_income_once try:
record_wechat_income_once( from jituan.services.szjilu_accounting import record_wechat_income_once
dingdan.Amount, record_wechat_income_once(
getattr(dingdan, 'ClubID', None), dingdan.Amount,
biz_ref=f'order:{dingdan.OrderID}', getattr(dingdan, 'ClubID', None),
) biz_ref=f'order:{dingdan.OrderID}',
)
except Exception as e:
logger.error(f'订单收支统计失败 {dingdan.OrderID}: {e}', exc_info=True)
def _query_wechat_payment(self, out_trade_no): def _query_wechat_payment(self, out_trade_no):
""" """