diff --git a/dingdan/tasks.py b/dingdan/tasks.py index 564c36c..a55b91e 100644 --- a/dingdan/tasks.py +++ b/dingdan/tasks.py @@ -38,6 +38,7 @@ def process_expired_order(self, dingdan_id): return f'自动结算已禁用,跳过订单{dingdan_id}' try: + guanshi_settle_ctx = None # 使用select_for_update锁定记录,防止并发修改 with transaction.atomic(): @@ -125,7 +126,19 @@ def process_expired_order(self, dingdan_id): result_msg += ",商家信息已更新" log_task_execution(f"自动结算订单 {dingdan_id}", True, result_msg) - return result_msg + + if fadan_pingtai == 2 and dashou_id: + guanshi_settle_ctx = (dingdan_id, dashou_id) + + if guanshi_settle_ctx: + try: + 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]) + except Exception as e: + logger.error(f"自动结算管事分红失败 订单{dingdan_id}: {e}", exc_info=True) + + return result_msg except Dingdan.DoesNotExist: error_msg = f"订单{dingdan_id}不存在" @@ -143,6 +156,27 @@ def process_expired_order(self, dingdan_id): log_task_execution(f"自动结算订单 {dingdan_id}", False, error_msg) return error_msg + +@shared_task(bind=True, max_retries=3, default_retry_delay=15) +def retry_establish_order_chat_task(self, dingdan_id): + """抢单后建聊失败时的延迟补偿任务""" + try: + from utils.chat_utils import establish_order_chat_with_retry + ok = establish_order_chat_with_retry(dingdan_id, max_rounds=5) + if ok: + logger.info(f"建聊补偿成功: 订单{dingdan_id}") + return f'chat ok {dingdan_id}' + logger.warning(f"建聊补偿仍失败: 订单{dingdan_id}") + if self.request.retries < self.max_retries: + raise self.retry() + return f'chat failed {dingdan_id}' + except Exception as e: + logger.error(f"建聊补偿任务异常 订单{dingdan_id}: {e}", exc_info=True) + if self.request.retries < self.max_retries: + raise self.retry(exc=e) + return str(e) + + @shared_task @rollback_on_failure("批量检查超时订单") def check_order_expire_task(): diff --git a/dingdan/utils.py b/dingdan/utils.py index 4484fec..d87ebf2 100644 --- a/dingdan/utils.py +++ b/dingdan/utils.py @@ -356,10 +356,10 @@ def _normalize_lilu_rate(raw): def calc_shangjia_order_fencheng(jine): """ 商家发单时计算打手/管事分成。 - - 打手分成(Lilubiao=3)最高不超过订单金额 + - 打手分成(Lilubiao=3) - 管事打手接单分红(Lilubiao=13) - 管事商家派单分红(Lilubiao=15) - 打手优先;三项合计超过订单金额时,优先清零管事商家分红,再清零管事打手分红。 + 三项合计超过订单金额时,优先削减打手分成,再削减商家派单管事分红,最后削减打手接单管事分红。 """ jine = Decimal(str(jine)) try: @@ -387,14 +387,19 @@ def calc_shangjia_order_fencheng(jine): guanshi_fencheng = (jine * rate_guanshi_dashou).quantize(Decimal('0.01')) guanshi_shangjia_fencheng = (jine * rate_guanshi_shangjia).quantize(Decimal('0.01')) - if dashou_fencheng + guanshi_fencheng > jine: - guanshi_fencheng = Decimal('0.00') - - if dashou_fencheng + guanshi_fencheng + guanshi_shangjia_fencheng > jine: - guanshi_shangjia_fencheng = Decimal('0.00') - - if dashou_fencheng + guanshi_fencheng + guanshi_shangjia_fencheng > jine: - guanshi_fencheng = Decimal('0.00') + total = dashou_fencheng + guanshi_fencheng + guanshi_shangjia_fencheng + if total > jine: + overflow = total - jine + cut = min(overflow, dashou_fencheng) + dashou_fencheng -= cut + overflow -= cut + if overflow > 0: + cut = min(overflow, guanshi_shangjia_fencheng) + guanshi_shangjia_fencheng -= cut + overflow -= cut + if overflow > 0: + cut = min(overflow, guanshi_fencheng) + guanshi_fencheng -= cut return dashou_fencheng, guanshi_fencheng, guanshi_shangjia_fencheng @@ -413,17 +418,6 @@ 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) @@ -569,4 +563,76 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): else: logger.info( f"管事商家分红跳过: 商家{shangjia_id}打手身份无邀请管事(yaoqingren为空)" - ) \ No newline at end of file + ) + + +def pay_deposit_guanshi_fenhong(dashou, order, recharge_amount): + """ + 打手充值押金后向邀请管事发放分红(幂等:dingdan_id + fenhong_leixing=2)。 + 网络重试/微信重复回调时不会重复入账。 + """ + from shangpin.models import Gsfenhong + from yonghu.models import UserGuanshi + + guanshi_id = (dashou.yaoqingren or '').strip() + if not guanshi_id: + logger.info(f"押金分红跳过: 打手{dashou.user.yonghuid}无邀请管事") + return False + + if Gsfenhong.objects.filter(dingdan_id=order.dingdan_id, fenhong_leixing=2).exists(): + logger.info(f"押金分红已处理: 订单{order.dingdan_id}") + return True + + lilu_obj = Lilubiao.objects.filter(fadanpingtai='12').first() + rate = lilu_obj.lilu if lilu_obj and lilu_obj.lilu is not None else Decimal('0') + rate = _normalize_lilu_rate(rate) if rate else Decimal('0') + if rate <= 0: + logger.info("押金分红跳过: 费率未配置或为0") + return False + + recharge_amount = Decimal(str(recharge_amount)) + fenhong_jine = (recharge_amount * rate).quantize(Decimal('0.01')) + if fenhong_jine <= 0: + return False + + try: + with transaction.atomic(): + guanshi = UserGuanshi.objects.select_for_update().get(user__yonghuid=guanshi_id) + UserGuanshi.objects.filter(pk=guanshi.pk).update( + yue=F('yue') + fenhong_jine, + chongzhifenrun=F('chongzhifenrun') + fenhong_jine, + ) + user_main = dashou.user + Gsfenhong.objects.create( + dingdan_id=order.dingdan_id, + guanshi=guanshi_id, + dashouid=dashou.user.yonghuid, + shuoming='押金分红', + fenhong=fenhong_jine, + avatar=user_main.avatar if user_main else None, + nicheng=dashou.nicheng or '未知打手', + fenhong_leixing=2, + ) + logger.info( + f"押金分红成功: 订单{order.dingdan_id}, 管事{guanshi_id}, " + f"打手{dashou.user.yonghuid}, 金额{fenhong_jine}元" + ) + try: + from houtai.utils import update_guanshi_daily_by_action + update_guanshi_daily_by_action( + yonghuid=guanshi_id, + action=4, + amount=fenhong_jine, + ) + except Exception as e: + logger.error(f"押金分红管事日统计更新失败: {e}") + return True + except UserGuanshi.DoesNotExist: + logger.warning(f"押金分红跳过: 管事{guanshi_id}不存在") + return False + except IntegrityError: + logger.info(f"押金分红已处理(并发): 订单{order.dingdan_id}") + return True + except Exception as e: + logger.error(f"押金分红处理失败: {e}", exc_info=True) + return False \ No newline at end of file diff --git a/dingdan/views.py b/dingdan/views.py index 1fa17df..4e3688f 100644 --- a/dingdan/views.py +++ b/dingdan/views.py @@ -4235,7 +4235,15 @@ class QiangdanView(APIView): self._execute_qiangdan(order, dashou_profile, request.user.yonghuid) # 事务提交后再建群/建聊,避免长耗时 HTTP 拖住行锁,并支持重试 - chat_success = establish_order_chat_with_retry(dingdan_id) + chat_success = establish_order_chat_with_retry(dingdan_id, max_rounds=5) + + if not chat_success: + try: + from dingdan.tasks import retry_establish_order_chat_task + retry_establish_order_chat_task.apply_async(args=[dingdan_id], countdown=8) + retry_establish_order_chat_task.apply_async(args=[dingdan_id], countdown=30) + except Exception as chat_task_err: + logger.error(f"提交建聊补偿任务失败: {chat_task_err}") try: update_dashou_daily_by_action( diff --git a/shangpin/views.py b/shangpin/views.py index 331505e..62137cc 100644 --- a/shangpin/views.py +++ b/shangpin/views.py @@ -680,7 +680,14 @@ class YajinHuitiao(View): # 5. 检查订单状态是否为未支付(9) if order.zhuangtai != 9: logger.warning(f"订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}") - # 已处理过的订单直接返回成功,避免微信重复回调 + # 已支付订单:补发可能因网络中断未成功的押金分红(幂等) + if order.zhuangtai == 3 and getattr(order, 'leixing', None) == 5: + try: + dashou = UserDashou.objects.get(user__yonghuid=order.yonghuid) + from dingdan.utils import pay_deposit_guanshi_fenhong + pay_deposit_guanshi_fenhong(dashou, order, order.jine) + except Exception as e: + logger.error(f"押金分红补发失败: {order.dingdan_id}, {e}", exc_info=True) return self.wechat_response('SUCCESS', 'OK') # 6. 验证金额(微信返回的是分,需要转换) @@ -709,62 +716,10 @@ class YajinHuitiao(View): logger.info(f"打手押金更新成功: 用户{order.yonghuid}, 增加{order_amount_yuan}元, 新押金{dashou.yajin}") - # 8b. 押金分红(仅微信确认到账后;费率 Lilubiao fadanpingtai=12) + # 8b. 押金分红(微信确认到账后;幂等、可重试) try: - from decimal import Decimal - from django.db import transaction - from dingdan.models import Lilubiao - from shangpin.models import Gsfenhong - from yonghu.models import UserGuanshi - - guanshi_id = dashou.yaoqingren - if not guanshi_id: - logger.info(f"押金分红跳过: 打手{order.yonghuid}无邀请管事") - elif Gsfenhong.objects.filter(dingdan_id=order.dingdan_id).exists(): - logger.info(f"押金分红已处理: 订单{order.dingdan_id}") - else: - lilu_obj = Lilubiao.objects.filter(fadanpingtai='12').first() - rate = lilu_obj.lilu if lilu_obj and lilu_obj.lilu is not None else Decimal('0') - if rate <= 0: - logger.info(f"押金分红跳过: 费率未配置或为0") - else: - recharge_amount = Decimal(str(order_amount_yuan)) - fenhong_jine = (recharge_amount * rate).quantize(Decimal('0.01')) - if fenhong_jine > 0: - with transaction.atomic(): - guanshi = UserGuanshi.objects.select_for_update().get( - user__yonghuid=guanshi_id - ) - guanshi.yue += fenhong_jine - guanshi.chongzhifenrun += fenhong_jine - guanshi.save(update_fields=['yue', 'chongzhifenrun']) - - user_main = dashou.user - Gsfenhong.objects.create( - dingdan_id=order.dingdan_id, - guanshi=guanshi_id, - dashouid=order.yonghuid, - shuoming='押金分红', - fenhong=fenhong_jine, - avatar=user_main.avatar, - nicheng=dashou.nicheng or '未知打手', - fenhong_leixing=2, - ) - logger.info( - f"押金分红成功: 订单{order.dingdan_id}, 管事{guanshi_id}, " - f"打手{order.yonghuid}, 金额{fenhong_jine}元" - ) - try: - from houtai.utils import update_guanshi_daily_by_action - update_guanshi_daily_by_action( - yonghuid=guanshi_id, - action=4, - amount=fenhong_jine, - ) - except Exception as e: - logger.error(f"押金分红管事日统计更新失败: {e}") - except UserGuanshi.DoesNotExist: - logger.warning(f"押金分红跳过: 管事{guanshi_id}不存在") + from dingdan.utils import pay_deposit_guanshi_fenhong + pay_deposit_guanshi_fenhong(dashou, order, order_amount_yuan) except Exception as e: logger.error(f"押金分红处理失败: {str(e)}", exc_info=True)