"""子客服日统计 + 商家日统计双写。""" import logging from datetime import date from decimal import Decimal from django.db import transaction from django.db.models import F from backend.utils import update_shangjia_daily from merchant_ops.constants import STAT_ACTION_DISPATCH, STAT_ACTION_REFUND, STAT_ACTION_SETTLE from merchant_ops.models import MerchantOrderDispatch, MerchantStaffDailyStats, MerchantStaffMember from merchant_ops.services.wallet import release_dispatch_quota logger = logging.getLogger(__name__) def _action_fields(action, amount): amount = Decimal(str(amount)) if action == STAT_ACTION_DISPATCH: return {'dispatch_count': 1, 'dispatch_amount': amount} if action == STAT_ACTION_SETTLE: return {'settle_count': 1, 'settle_amount': amount} if action == STAT_ACTION_REFUND: return {'refund_count': 1, 'refund_amount': amount} return {} def update_staff_daily(merchant_id, staff_user_id, member_id, amount, action): """子客服日统计;老板操作不写此表。""" amount = Decimal(str(amount)) if amount else Decimal('0.00') fields = _action_fields(action, amount) if not fields: return today = date.today() with transaction.atomic(): stat, _ = MerchantStaffDailyStats.objects.select_for_update().get_or_create( merchant_id=merchant_id, staff_user_id=staff_user_id, stat_date=today, defaults={ 'club_id': 'xq', 'member_id': member_id, 'year': today.year, 'month': today.month, 'day': today.day, }, ) update_kwargs = {} for k, v in fields.items(): if k.endswith('_count'): update_kwargs[k] = F(k) + int(v) else: update_kwargs[k] = F(k) + v MerchantStaffDailyStats.query.filter(id=stat.id).update(**update_kwargs) def sync_staff_stats_by_order_id(order_id, amount, action): """按订单派单归属同步子客服日统计(商家侧由 update_shangjia_daily 负责)。""" if not order_id: return amount = Decimal(str(amount)) if amount else Decimal('0.00') if amount <= 0: return disp = MerchantOrderDispatch.objects.filter(order_id=order_id).first() if not disp or not disp.staff_member_id: return member = MerchantStaffMember.objects.filter(id=disp.staff_member_id).first() if not member: return update_staff_daily( disp.merchant_id, member.staff_user_id, member.id, amount, action, ) if action == STAT_ACTION_DISPATCH: MerchantStaffMember.objects.filter(id=member.id).update( today_dispatch_count=F('today_dispatch_count') + 1, today_dispatch_amount=F('today_dispatch_amount') + amount, ) elif action == STAT_ACTION_REFUND: try: release_dispatch_quota(order_id, amount, member.staff_user_id) except Exception: logger.warning('释放客服派单额度失败 order_id=%s', order_id, exc_info=True) def update_merchant_and_staff_stats(merchant_id, staff_member, amount, action, order_id=None): """商家日统计必写;有 order_id 时按派单归属写客服统计,否则写操作人。""" update_shangjia_daily(merchant_id, amount, action, order_id=order_id) if order_id: return if staff_member: update_staff_daily( merchant_id, staff_member.staff_user_id, staff_member.id, amount, action, ) def get_staff_stats_payload(member): """子客服个人统计(今日 + 累计),供 /me 与首页展示。""" from django.db.models import Sum today = date.today() today_stat = MerchantStaffDailyStats.objects.filter( member_id=member.id, stat_date=today, ).first() total = MerchantStaffDailyStats.objects.filter(member_id=member.id).aggregate( dispatch_count=Sum('dispatch_count'), dispatch_amount=Sum('dispatch_amount'), settle_count=Sum('settle_count'), settle_amount=Sum('settle_amount'), refund_count=Sum('refund_count'), refund_amount=Sum('refund_amount'), ) return { 'today_dispatch_count': ( today_stat.dispatch_count if today_stat else int(member.today_dispatch_count or 0) ), 'today_dispatch_amount': str( today_stat.dispatch_amount if today_stat else (member.today_dispatch_amount or '0.00') ), 'today_refund_count': today_stat.refund_count if today_stat else 0, 'today_settle_count': today_stat.settle_count if today_stat else 0, 'dispatch_count': total['dispatch_count'] or 0, 'dispatch_amount': str(total['dispatch_amount'] or '0.00'), 'settle_count': total['settle_count'] or 0, 'settle_amount': str(total['settle_amount'] or '0.00'), 'refund_count': total['refund_count'] or 0, 'refund_amount': str(total['refund_amount'] or '0.00'), } def bump_cancel_count(merchant_id, staff_member): if not staff_member: return today = date.today() with transaction.atomic(): stat, _ = MerchantStaffDailyStats.objects.select_for_update().get_or_create( merchant_id=merchant_id, staff_user_id=staff_member.staff_user_id, stat_date=today, defaults={ 'club_id': 'xq', 'member_id': staff_member.id, 'year': today.year, 'month': today.month, 'day': today.day, }, ) MerchantStaffDailyStats.query.filter(id=stat.id).update( cancel_count=F('cancel_count') + 1, ) def bump_penalty_count(merchant_id, staff_member): if not staff_member: return today = date.today() with transaction.atomic(): stat, _ = MerchantStaffDailyStats.objects.select_for_update().get_or_create( merchant_id=merchant_id, staff_user_id=staff_member.staff_user_id, stat_date=today, defaults={ 'club_id': 'xq', 'member_id': staff_member.id, 'year': today.year, 'month': today.month, 'day': today.day, }, ) MerchantStaffDailyStats.query.filter(id=stat.id).update( penalty_count=F('penalty_count') + 1, )