- 划额度扣商家余额,客服派单/链接只扣个人额度 - 新增 wallet/revoke 收回未使用额度,权限 wallet_revoke - 客服额度单撤销/退款只释放额度,不再误退商家余额 - 链接待填单(13)可撤销;总管/财务可划收额度
183 lines
6.4 KiB
Python
183 lines
6.4 KiB
Python
"""子客服日统计 + 商家日统计双写。"""
|
|
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,
|
|
)
|