"""商家发单退款审核通过(与 KefuMerchantRefundView / 后台同意退款业务一致)。""" import logging from decimal import Decimal from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from backend.utils import update_dashou_daily_by_action, update_shangjia_daily from orders.models import Order, RefundRecord from users.business_models import User logger = logging.getLogger(__name__) # tuikuanjilu.chuliid / RefundRecord.ProcessorID 数据库列最长 11 PROCESSOR_ID_MAX_LEN = 11 def normalize_processor_id(processor_id, *, default='138377'): """处理人 ID 须 <=11 位(与客服手机号字段一致),超长则截断。""" val = (processor_id or default or '').strip() if not val: val = default if len(val) > PROCESSOR_ID_MAX_LEN: logger.warning( 'processor_id 超长已截断: %s -> %s', val, val[:PROCESSOR_ID_MAX_LEN], ) val = val[:PROCESSOR_ID_MAX_LEN] return val class MerchantRefundApproveError(Exception): """单笔商家退款同意失败。""" def approve_merchant_refund_order( order, *, processor_id='138377', tuikuan_liyou='', require_status_4=True, ): """ 同意商家发单退款(Platform=2)。 与 POST /yonghu/kefusjtk 核心事务一致: - 订单 Status → 5(已退款) - 打手 tuikuanliang+1、zhuangtai=1 - 商家 tuikuan+1、余额+订单金额(子客服额度单除外) - RefundRecord ApplyStatus → 1 - update_shangjia_daily(action=3)、update_dashou_daily_by_action(action=3) 返回 (True, 'ok');失败抛 MerchantRefundApproveError。 """ dingdan_id = order.OrderID processor_id = normalize_processor_id(processor_id) if order.Platform != 2: raise MerchantRefundApproveError(f'订单 {dingdan_id} 不是商家发单(Platform=2)') if require_status_4 and order.Status != 4: raise MerchantRefundApproveError( f'订单 {dingdan_id} 状态={order.Status},非退款审核中(4)', ) if not require_status_4 and order.Status not in (1, 7, 2, 8, 4): raise MerchantRefundApproveError( f'订单 {dingdan_id} 状态={order.Status},不允许退款', ) jine = order.Amount if jine is None: raise MerchantRefundApproveError(f'订单 {dingdan_id} 缺少订单金额') try: shangjia_id = order.shangjia_kuozhan.MerchantID except ObjectDoesNotExist as exc: raise MerchantRefundApproveError(f'订单 {dingdan_id} 缺少商家扩展信息') from exc jiedan_dashou_id = order.PlayerID with transaction.atomic(): locked = Order.objects.select_for_update().select_related('shangjia_kuozhan').get( pk=order.pk, ) if require_status_4 and locked.Status != 4: raise MerchantRefundApproveError( f'订单 {dingdan_id} 并发冲突:当前状态={locked.Status}', ) if locked.Platform != 2: raise MerchantRefundApproveError(f'订单 {dingdan_id} 不是商家发单') locked.Status = 5 if tuikuan_liyou: locked.RefundReason = tuikuan_liyou if processor_id: locked.AssignedCS = processor_id locked.save(update_fields=['Status', 'RefundReason', 'AssignedCS', 'UpdateTime']) if jiedan_dashou_id: try: dashou_user = User.objects.select_for_update().get(UserUID=jiedan_dashou_id) dashou_profile = dashou_user.DashouProfile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 dashou_profile.save(update_fields=['tuikuanliang', 'zhuangtai']) except (User.DoesNotExist, ObjectDoesNotExist): logger.warning('商家退款:打手 %s 不存在,跳过', jiedan_dashou_id) from merchant_ops.services.wallet import is_staff_quota_order try: shangjia_user = User.objects.select_for_update().get(UserUID=shangjia_id) shangjia_profile = shangjia_user.ShopProfile shangjia_profile.tuikuan += 1 if not is_staff_quota_order(dingdan_id): shangjia_profile.yue += jine shangjia_profile.save(update_fields=['tuikuan', 'yue']) except (User.DoesNotExist, ObjectDoesNotExist): logger.warning('商家退款:商家 %s 不存在,跳过', shangjia_id) try: refund_record = RefundRecord.objects.select_for_update().get(OrderID=dingdan_id) refund_record.ApplyStatus = 1 refund_record.ProcessorID = processor_id refund_record.save(update_fields=['ApplyStatus', 'ProcessorID', 'UpdateTime']) except RefundRecord.DoesNotExist: logger.warning('商家退款:订单 %s 无 RefundRecord,跳过更新', dingdan_id) update_shangjia_daily( yonghuid=shangjia_id, amount=Decimal(str(jine)), action=3, order_id=dingdan_id, ) if jiedan_dashou_id and order.PlayerCommission is not None: try: update_dashou_daily_by_action( yonghuid=jiedan_dashou_id, amount=order.PlayerCommission, action=3, ) except Exception as exc: logger.error('打手每日统计更新失败 order=%s err=%s', dingdan_id, exc) logger.info('同意商家退款成功 order=%s processor=%s', dingdan_id, processor_id) return True, 'ok'