fix: 支付入账降级 + 财务全平台汇总
This commit is contained in:
@@ -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({
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user