From 58b2bdf2154cacb86311f65137a8e56b6dbfb577 Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 16 Jun 2026 01:56:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BB=A3=E7=A0=81=E7=AE=A1?= =?UTF-8?q?=E4=BA=8B=E5=88=86=E7=BA=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dingdan/utils.py | 99 ++++++++++++++++++++++++++++++++++++++---------- dingdan/views.py | 26 ++++++++----- houtai/view.py | 4 ++ yonghu/views.py | 13 +++++-- 4 files changed, 110 insertions(+), 32 deletions(-) diff --git a/dingdan/utils.py b/dingdan/utils.py index 7016aa8..4484fec 100644 --- a/dingdan/utils.py +++ b/dingdan/utils.py @@ -336,9 +336,23 @@ def update_daily_payout(amount): from decimal import Decimal +from django.db import IntegrityError + from dingdan.models import Lilubiao +def _normalize_lilu_rate(raw): + """Lilubiao 存小数比例;若后台误填 15 表示 15%,自动除以 100""" + if raw is None: + return Decimal('0') + rate = Decimal(str(raw)) + if rate <= 0: + return Decimal('0') + if rate > 1: + rate = (rate / Decimal('100')).quantize(Decimal('0.0001')) + return rate + + def calc_shangjia_order_fencheng(jine): """ 商家发单时计算打手/管事分成。 @@ -350,22 +364,20 @@ def calc_shangjia_order_fencheng(jine): jine = Decimal(str(jine)) try: lilu_obj = Lilubiao.objects.get(fadanpingtai='3') - rate_dashou = Decimal(str(lilu_obj.lilu)) if lilu_obj.lilu and lilu_obj.lilu > 0 else Decimal('1') + rate_dashou = _normalize_lilu_rate(lilu_obj.lilu) if lilu_obj.lilu else Decimal('1') + if rate_dashou <= 0: + rate_dashou = Decimal('1') except Lilubiao.DoesNotExist: rate_dashou = Decimal('1') lilu_guanshi_dashou_obj = Lilubiao.objects.filter(fadanpingtai='13').first() - rate_guanshi_dashou = ( - Decimal(str(lilu_guanshi_dashou_obj.lilu)) - if lilu_guanshi_dashou_obj and lilu_guanshi_dashou_obj.lilu is not None - else Decimal('0') + rate_guanshi_dashou = _normalize_lilu_rate( + lilu_guanshi_dashou_obj.lilu if lilu_guanshi_dashou_obj else None ) lilu_guanshi_shangjia_obj = Lilubiao.objects.filter(fadanpingtai='15').first() - rate_guanshi_shangjia = ( - Decimal(str(lilu_guanshi_shangjia_obj.lilu)) - if lilu_guanshi_shangjia_obj and lilu_guanshi_shangjia_obj.lilu is not None - else Decimal('0') + rate_guanshi_shangjia = _normalize_lilu_rate( + lilu_guanshi_shangjia_obj.lilu if lilu_guanshi_shangjia_obj else None ) dashou_fencheng = min( @@ -401,6 +413,17 @@ def _pay_guanshi_order_fenhong(order, guanshi_id, amount, related_user_id, niche logger.info(f"管事分红已处理: 订单{order.dingdan_id}, 类型{fenhong_leixing}") return False + # 旧表结构 dingdan_id 全局唯一时,第二笔分红会插入失败 + legacy_conflict = Gsfenhong.objects.filter(dingdan_id=order.dingdan_id).exclude( + fenhong_leixing=fenhong_leixing + ).exists() + if legacy_conflict: + logger.error( + f"管事分红写入失败: 订单{order.dingdan_id} 已有其他类型分红记录," + f"请执行 python manage.py migrate shangpin 0010 更新 gsfenhong 表唯一约束" + ) + return False + try: with transaction.atomic(): guanshi = UserGuanshi.objects.select_for_update().get(user__yonghuid=guanshi_id) @@ -425,6 +448,14 @@ def _pay_guanshi_order_fenhong(order, guanshi_id, amount, related_user_id, niche except UserGuanshi.DoesNotExist: logger.warning(f"管事分红跳过: 管事{guanshi_id}不存在") return False + except IntegrityError as e: + logger.error( + f"管事分红写入失败(数据库约束): 订单{order.dingdan_id}, 类型{fenhong_leixing}, " + f"关联用户{related_user_id}, 错误={e}. " + f"请确认已执行 migrate shangpin 0010 与 migrate dingdan 0017", + exc_info=True, + ) + return False except Exception as e: logger.error(f"管事分红失败: {e}", exc_info=True) return False @@ -441,6 +472,11 @@ def _pay_guanshi_order_fenhong(order, guanshi_id, amount, related_user_id, niche return True +def _resolve_dashou_inviter_guanshi(dashou_profile): + guanshi_id = (dashou_profile.yaoqingren or '').strip() + return guanshi_id or None + + def settle_shangjia_order_guanshi_fenhong(order, dashou_id): """ 商家订单结单时结算管事分红(幂等,失败不抛异常阻断主流程)。 @@ -448,14 +484,34 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): - 管事商家派单分红:派单商家打手身份的邀请管事(guanshi_shangjia_fencheng) 同一管事可同时获得两笔分红。 """ - from dingdan.models import DingdanShangjia + from dingdan.models import Dingdan, DingdanShangjia from yonghu.models import UserDashou if getattr(order, 'fadan_pingtai', None) != 2: return - # 1. 接单打手邀请管事分红 + # 重新读订单,确保拿到发单时写入的分红金额 + try: + order = Dingdan.objects.get(dingdan_id=order.dingdan_id) + except Dingdan.DoesNotExist: + logger.warning(f"管事分红跳过: 订单{order.dingdan_id}不存在") + return + guanshi_fencheng = order.guanshi_fencheng or Decimal('0') + guanshi_shangjia_fencheng = order.guanshi_shangjia_fencheng or Decimal('0') + logger.info( + f"商家订单管事分红结算: 订单{order.dingdan_id}, 打手{dashou_id}, " + f"打手接单分红={guanshi_fencheng}, 商家派单分红={guanshi_shangjia_fencheng}" + ) + + if guanshi_fencheng <= 0 and guanshi_shangjia_fencheng <= 0: + logger.info( + f"管事分红跳过: 订单{order.dingdan_id} 两项分红均为0 " + f"(请检查 Lilubiao 13/15 费率及打手分成是否占满订单金额)" + ) + return + + # 1. 接单打手邀请管事分红 if guanshi_fencheng > 0 and dashou_id: try: dashou = UserDashou.objects.select_related('user').get(user__yonghuid=dashou_id) @@ -463,11 +519,12 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): logger.info(f"管事打手分红跳过: 打手{dashou_id}不存在") dashou = None - if dashou and dashou.yaoqingren: + guanshi_id = _resolve_dashou_inviter_guanshi(dashou) if dashou else None + if dashou and guanshi_id: user_main = dashou.user _pay_guanshi_order_fenhong( order=order, - guanshi_id=dashou.yaoqingren, + guanshi_id=guanshi_id, amount=guanshi_fencheng, related_user_id=dashou_id, nicheng=dashou.nicheng, @@ -476,13 +533,14 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): shuoming='商家订单打手分红', ) else: - logger.info(f"管事打手分红跳过: 打手{dashou_id}无邀请管事") + logger.info(f"管事打手分红跳过: 打手{dashou_id}无邀请管事(yaoqingren为空)") # 2. 派单商家邀请管事分红 - guanshi_shangjia_fencheng = order.guanshi_shangjia_fencheng or Decimal('0') if guanshi_shangjia_fencheng > 0: try: - shangjia_ext = DingdanShangjia.objects.select_related('dingdan').get(dingdan__dingdan_id=order.dingdan_id) + shangjia_ext = DingdanShangjia.objects.select_related('dingdan').get( + dingdan__dingdan_id=order.dingdan_id + ) shangjia_id = shangjia_ext.shangjia_id except DingdanShangjia.DoesNotExist: logger.info(f"管事商家分红跳过: 订单{order.dingdan_id}无商家扩展") @@ -495,11 +553,12 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): logger.info(f"管事商家分红跳过: 商家{shangjia_id}无打手扩展") merchant_dashou = None - if merchant_dashou and merchant_dashou.yaoqingren: + guanshi_id = _resolve_dashou_inviter_guanshi(merchant_dashou) if merchant_dashou else None + if merchant_dashou and guanshi_id: user_main = merchant_dashou.user _pay_guanshi_order_fenhong( order=order, - guanshi_id=merchant_dashou.yaoqingren, + guanshi_id=guanshi_id, amount=guanshi_shangjia_fencheng, related_user_id=shangjia_id, nicheng=shangjia_ext.sjnicheng or merchant_dashou.nicheng, @@ -508,4 +567,6 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): shuoming='商家派单管事分红', ) else: - logger.info(f"管事商家分红跳过: 商家{shangjia_id}打手身份无邀请管事") \ No newline at end of file + logger.info( + f"管事商家分红跳过: 商家{shangjia_id}打手身份无邀请管事(yaoqingren为空)" + ) \ No newline at end of file diff --git a/dingdan/views.py b/dingdan/views.py index 2fee917..7e63427 100644 --- a/dingdan/views.py +++ b/dingdan/views.py @@ -3159,6 +3159,8 @@ class ShangjiaJiesuanView(APIView): 'data': None }, status=status.HTTP_400_BAD_REQUEST) + settle_ctx = None + success_response = None try: with transaction.atomic(): # 4. 查询订单并加锁(修复并发问题) @@ -3311,13 +3313,8 @@ class ShangjiaJiesuanView(APIView): action=2 # 2 = 结算 ) - # 商家订单管事分红(仅 fadan_pingtai=2) - if order.fadan_pingtai == 2: - from dingdan.utils import settle_shangjia_order_guanshi_fenhong - settle_shangjia_order_guanshi_fenhong(order, jiedan_dashou_id) - - # 返回成功 - return Response({ + settle_ctx = (dingdan_id, jiedan_dashou_id) + success_response = Response({ 'code': 0, 'msg': '结单成功', 'data': { @@ -3331,6 +3328,14 @@ class ShangjiaJiesuanView(APIView): } }, status=status.HTTP_200_OK) + if settle_ctx: + from dingdan.utils import settle_shangjia_order_guanshi_fenhong + settle_order = Dingdan.objects.get(dingdan_id=settle_ctx[0]) + settle_shangjia_order_guanshi_fenhong(settle_order, settle_ctx[1]) + + if success_response is not None: + return success_response + except Dingdan.DoesNotExist: return Response({ 'code': 5, @@ -6359,9 +6364,10 @@ class AdQiangZhiJieDan(APIView): # 商家扩展表不存在,跳过商家更新 logger.warning(f"强制结单:商家{shangjia_id}扩展信息不存在,跳过商家更新") - if fadan_pingtai == 2 and jiedan_dashou_id: - from dingdan.utils import settle_shangjia_order_guanshi_fenhong - settle_shangjia_order_guanshi_fenhong(dingdan_obj, jiedan_dashou_id) + if fadan_pingtai == 2 and jiedan_dashou_id: + from dingdan.utils import settle_shangjia_order_guanshi_fenhong + settle_order = Dingdan.objects.get(dingdan_id=dingdan_id) + settle_shangjia_order_guanshi_fenhong(settle_order, jiedan_dashou_id) # 11. 返回成功响应 return Response({ diff --git a/houtai/view.py b/houtai/view.py index e226974..49e67c8 100644 --- a/houtai/view.py +++ b/houtai/view.py @@ -359,6 +359,8 @@ class GetCrossOrderDetailView(APIView): 'partner_club_id': dingdan_obj.partner_club_id or '', 'jine': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00, 'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00, + 'guanshi_fencheng': float(dingdan_obj.guanshi_fencheng) if dingdan_obj.guanshi_fencheng else 0.00, + 'guanshi_shangjia_fencheng': float(dingdan_obj.guanshi_shangjia_fencheng) if dingdan_obj.guanshi_shangjia_fencheng else 0.00, 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '', 'dashou_liuyan': dingdan_obj.dashou_liuyan or '', 'zhiding_id': dingdan_obj.zhiding_id or '', @@ -6833,6 +6835,8 @@ class UpdateWithdrawSettingsView(APIView): val = order_rates.get(str(role), order_rates.get(role)) if val is not None: rate = Decimal(str(val)) + if rate > 1: + rate = (rate / Decimal('100')).quantize(Decimal('0.0001')) code = order_rate_code_map[role] obj, created = Lilubiao.objects.select_for_update().get_or_create( fadanpingtai=code, diff --git a/yonghu/views.py b/yonghu/views.py index d2591b2..8cf1480 100644 --- a/yonghu/views.py +++ b/yonghu/views.py @@ -8722,6 +8722,7 @@ class KefuGetOrderDetailView(APIView): 'shangjia_kuozhan' ).only( 'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'jine', 'dashou_fencheng', + 'guanshi_fencheng', 'guanshi_shangjia_fencheng', 'jiedan_dashou_id', 'dashou_liuyan', 'zhiding_id', 'shangpin_id', 'leixing_id', 'tupian', 'jieshao', 'beizhu', 'tkly', 'nicheng', 'clkf', 'create_time', 'update_time' @@ -8741,6 +8742,8 @@ class KefuGetOrderDetailView(APIView): 'fadanpingtai': dingdan_obj.fadan_pingtai or 0, 'jiage': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00, 'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00, + 'guanshi_fencheng': float(dingdan_obj.guanshi_fencheng) if dingdan_obj.guanshi_fencheng else 0.00, + 'guanshi_shangjia_fencheng': float(dingdan_obj.guanshi_shangjia_fencheng) if dingdan_obj.guanshi_shangjia_fencheng else 0.00, 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '', 'dashou_liuyan': dingdan_obj.dashou_liuyan or '', 'zhiding_id': dingdan_obj.zhiding_id or '', @@ -10236,6 +10239,7 @@ class KefuForceCompleteView(APIView): return Response({'code': 401, 'msg': '客服状态异常'}) # 2. 订单处理(事务内) + guanshi_settle_ctx = None try: with transaction.atomic(): order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) @@ -10351,10 +10355,8 @@ class KefuForceCompleteView(APIView): logger.error(f"打手每日统计更新失败: {e}") # 不影响主流程 - # 商家订单管事分红 if order.fadan_pingtai == 2: - from dingdan.utils import settle_shangjia_order_guanshi_fenhong - settle_shangjia_order_guanshi_fenhong(order, dashou_id) + guanshi_settle_ctx = (dingdan_id, dashou_id) # 8. 更新客服统计 kefu.jinrichuli = F('jinrichuli') + 1 @@ -10362,6 +10364,11 @@ class KefuForceCompleteView(APIView): kefu.zongchuli = F('zongchuli') + 1 kefu.save(update_fields=['jinrichuli', 'jinyuechuli', 'zongchuli']) + if guanshi_settle_ctx: + from dingdan.utils import settle_shangjia_order_guanshi_fenhong + settle_order = Dingdan.objects.get(dingdan_id=guanshi_settle_ctx[0]) + settle_shangjia_order_guanshi_fenhong(settle_order, guanshi_settle_ctx[1]) + return Response({'code': 0, 'msg': '强制结单成功'}) except Dingdan.DoesNotExist: