diff --git a/config/migrations/0007_platform_income_log.py b/config/migrations/0007_platform_income_log.py new file mode 100644 index 0000000..bd4536e --- /dev/null +++ b/config/migrations/0007_platform_income_log.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('config', '0006_display_club_id'), + ] + + operations = [ + migrations.CreateModel( + name='PlatformIncomeLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('biz_ref', models.CharField(db_index=True, max_length=64, unique=True, verbose_name='业务单号')), + ('club_id', models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='入账金额')), + ('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ], + options={ + 'verbose_name': '平台入账幂等日志', + 'verbose_name_plural': '平台入账幂等日志', + 'db_table': 'platform_income_log', + }, + ), + ] diff --git a/config/models.py b/config/models.py index 40244ca..d53936f 100644 --- a/config/models.py +++ b/config/models.py @@ -362,6 +362,17 @@ class DailyPayoutStat(QModel): return f"{self.date} - 出款{self.total_amount}元 - {self.total_count}笔" +class PlatformIncomeLog(QModel): + """微信入账幂等日志:同一 biz_ref 只记一次日收入与 szjilu。""" + biz_ref = models.CharField(max_length=64, unique=True, db_index=True, verbose_name='业务单号') + club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID') + amount = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='入账金额') + CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'platform_income_log' + verbose_name = '平台入账幂等日志' + verbose_name_plural = verbose_name class PopupPage(QModel): diff --git a/jituan/services/member_recharge.py b/jituan/services/member_recharge.py index 0f5ae4b..cf9e0a5 100644 --- a/jituan/services/member_recharge.py +++ b/jituan/services/member_recharge.py @@ -33,7 +33,6 @@ from jituan.services.club_config import get_huiyuan_fenchong from jituan.services.club_penalty import resolve_gsfenhong_club_id from jituan.services.club_user import get_user_club_id from jituan.services.wechat_pay import club_id_from_czjilu, get_wechat_v2_config -from orders.utils import update_daily_income from products.models import Czjilu, DuociFenhong, Gsfenhong, Huiyuan, Huiyuangoumai from users.business_models import User from users.models import UserDashou, UserGuanshi, UserZuzhang @@ -331,9 +330,8 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback' jine_decimal = Decimal(str(order.jine)) try: - update_daily_income(jine_decimal, club_id) - from jituan.services.szjilu_accounting import apply_szjilu_income - apply_szjilu_income(jine_decimal, club_id) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once(jine_decimal, club_id, biz_ref=f'cz:{dingdan_id}') except Exception as exc: logger.error('会员收支统计更新失败: %s', exc, exc_info=True) diff --git a/jituan/services/szjilu_accounting.py b/jituan/services/szjilu_accounting.py index 253e6d0..79a799e 100644 --- a/jituan/services/szjilu_accounting.py +++ b/jituan/services/szjilu_accounting.py @@ -1,11 +1,14 @@ """各俱乐部全局收支流水(szjilu)统一记账。""" +import logging from decimal import Decimal -from django.db import transaction +from django.db import IntegrityError, transaction -from config.models import Szjilu +from config.models import PlatformIncomeLog, Szjilu from jituan.constants import CLUB_ID_DEFAULT +logger = logging.getLogger(__name__) + _ZERO = Decimal('0.00') _SZJILU_DEFAULTS = { 'TotalIncome': _ZERO, @@ -40,6 +43,39 @@ def apply_szjilu_income(amount, club_id=None): szjilu.save() +def record_wechat_income_once(amount, club_id=None, biz_ref=None): + """ + 微信入账:同一 biz_ref 只记一次 daily_income_stat + szjilu。 + 返回 True=本次新入账;False=已记过(重复回调/重复代码路径)。 + """ + ref = (biz_ref or '').strip() + if not ref: + raise ValueError('biz_ref 不能为空') + cid = normalize_szjilu_club_id(club_id) + jine = Decimal(str(amount)) + from orders.utils import update_daily_income + + try: + with transaction.atomic(): + PlatformIncomeLog.objects.create( + biz_ref=ref, + club_id=cid, + amount=jine, + ) + except IntegrityError: + logger.info('平台入账已存在,跳过重复记账 biz_ref=%s', ref) + return False + + try: + update_daily_income(jine, cid) + apply_szjilu_income(jine, cid) + except Exception: + PlatformIncomeLog.objects.filter(biz_ref=ref).delete() + raise + logger.info('平台入账记账成功 biz_ref=%s club=%s amount=%s', ref, cid, jine) + return True + + def apply_szjilu_expense(amount, club_id=None): """平台出款/支出:总收益减少,总支出、今日支出增加。""" jine = Decimal(str(amount)) diff --git a/orders/views.py b/orders/views.py index 02a2558..d3a61b1 100644 --- a/orders/views.py +++ b/orders/views.py @@ -43,7 +43,7 @@ from utils.fadan_utils import check_fadan_qiangdan_eligible from orders.utils import ( calc_shangjia_order_fencheng, - update_daily_income, update_daily_payout, + update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat @@ -307,10 +307,10 @@ class WechatPayNotifyView(APIView): # 更新收支记录(总累计) - self._update_szjilu(dingdan.Amount, getattr(dingdan, 'ClubID', None)) - - # 更新每日收入统计 - update_daily_income(dingdan.Amount, getattr(dingdan, 'ClubID', None)) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + dingdan.Amount, getattr(dingdan, 'ClubID', None), biz_ref=f'order:{out_trade_no}', + ) # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用) order_info = { @@ -568,10 +568,12 @@ class PaymentVerifyView(APIView): # 更新收支记录 - self._update_szjilu(dingdan.Amount, getattr(dingdan, 'ClubID', None)) - - # 更新每日收入统计 - update_daily_income(dingdan.Amount, getattr(dingdan, 'ClubID', None)) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + dingdan.Amount, + getattr(dingdan, 'ClubID', None), + biz_ref=f'order:{dingdan.OrderID}', + ) def _query_wechat_payment(self, out_trade_no): """ diff --git a/products/views.py b/products/views.py index d74cff1..0f740bb 100644 --- a/products/views.py +++ b/products/views.py @@ -32,7 +32,6 @@ from rest_framework.throttling import AnonRateThrottle # ==================== 项目工具函数 ==================== from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss -from orders.utils import update_daily_income from backend.utils import ( update_guanshi_daily_by_action, update_guanshi_xufei_daily, @@ -705,10 +704,12 @@ class YajinHuitiao(View): # 获取订单金额(确保是Decimal类型) jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine - # 查询或创建ID=1的收支记录 - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - from jituan.services.szjilu_accounting import apply_szjilu_income - apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None)) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + jine_decimal, + getattr(order, 'club_id', None), + biz_ref=f'cz:{order.dingdan_id}', + ) logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}元") @@ -1208,13 +1209,13 @@ class JifenHuitiao(View): # 更新收支记录表 try: - # 获取订单金额(确保是Decimal类型) jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - from jituan.services.szjilu_accounting import apply_szjilu_income - apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None)) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + jine_decimal, + getattr(order, 'club_id', None), + biz_ref=f'cz:{order.dingdan_id}', + ) logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}元") @@ -2343,14 +2344,13 @@ class ShangjiaHuitiao(View): # ===== 在这里添加收支记录更新代码 ===== # 更新收支记录表 try: - # 获取订单金额(确保是Decimal类型) jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine - - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - from jituan.services.szjilu_accounting import apply_szjilu_income - apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None)) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + jine_decimal, + getattr(order, 'club_id', None), + biz_ref=f'cz:{order.dingdan_id}', + ) logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}元") diff --git a/users/views.py b/users/views.py index d875bbb..1205fcf 100644 --- a/users/views.py +++ b/users/views.py @@ -48,7 +48,7 @@ from users.fadan_fenhong_utils import process_fadan_fenhong from orders.utils import ( update_daily_payout, - update_daily_income, settle_shangjia_order_guanshi_fenhong + settle_shangjia_order_guanshi_fenhong ) from backend.utils import ( update_dashou_daily_by_action, update_guanshi_daily_by_action, @@ -11967,13 +11967,13 @@ class FaKuanHuitiaoView(View): # 9. 更新收支记录表 try: - - # 获取订单金额(确保是Decimal类型) jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine - - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - from jituan.services.szjilu_accounting import apply_szjilu_income - apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None)) + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + jine_decimal, + getattr(order, 'club_id', None), + biz_ref=f'cz:{order.dingdan_id}', + ) logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}元") @@ -12530,11 +12530,13 @@ class KaohePayCallbackView(View): # 更新收支记录 try: jine_decimal = Decimal(str(order.jine)) - update_daily_income(jine_decimal, getattr(order, 'club_id', None)) - logger.info(f"更新每日收入成功: {jine_decimal}元") - from jituan.services.szjilu_accounting import apply_szjilu_income - apply_szjilu_income(jine_decimal, getattr(order, 'club_id', None)) - logger.info(f"更新收支记录成功") + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once( + jine_decimal, + getattr(order, 'club_id', None), + biz_ref=f'cz:{order.dingdan_id}', + ) + logger.info(f"更新收支记录成功: {jine_decimal}元") except Exception as e: logger.error(f"更新收支记录失败: {str(e)}", exc_info=True)