修复了商家客服接口添加客服操作专属接口,其次商家看日志接口
This commit is contained in:
@@ -86,3 +86,40 @@ SYSTEM_ROLE_TEMPLATES = [
|
||||
]
|
||||
|
||||
OWNER_ALL_PERMISSIONS = [code for code, _ in PERMISSION_DEFINITIONS]
|
||||
|
||||
# 操作审计:仅展示「重要行为」,不含查看类
|
||||
IMPORTANT_AUDIT_ACTIONS = frozenset([
|
||||
'DISPATCH', 'LINK_GENERATE', 'SETTLE', 'CANCEL', 'REFUND_APPLY',
|
||||
'CHANGE_PLAYER', 'PENALTY_APPLY', 'PENALTY_MANAGE',
|
||||
'TEMPLATE_CREATE', 'TEMPLATE_UPDATE', 'TEMPLATE_DELETE',
|
||||
'WALLET_ALLOCATE', 'INVITE_CREATE', 'INVITE_REVOKE',
|
||||
'MEMBER_UPDATE_ROLE', 'MEMBER_REMOVE', 'MEMBER_DISABLE', 'MEMBER_ENABLE',
|
||||
'ROLE_CREATE', 'ROLE_SET_PERMS', 'MEMBER_SET_PERMS',
|
||||
'STAFF_BIND',
|
||||
])
|
||||
|
||||
AUDIT_ACTION_LABELS = {
|
||||
'DISPATCH': '派单发单',
|
||||
'LINK_GENERATE': '生成链接',
|
||||
'SETTLE': '结单',
|
||||
'CANCEL': '撤销订单',
|
||||
'REFUND_APPLY': '申请退款',
|
||||
'CHANGE_PLAYER': '更换打手',
|
||||
'PENALTY_APPLY': '申请罚单',
|
||||
'PENALTY_MANAGE': '改撤罚单',
|
||||
'TEMPLATE_CREATE': '新增模板',
|
||||
'TEMPLATE_UPDATE': '修改模板',
|
||||
'TEMPLATE_DELETE': '删除模板',
|
||||
'WALLET_ALLOCATE': '划额度',
|
||||
'INVITE_CREATE': '生成邀请码',
|
||||
'INVITE_REVOKE': '作废邀请码',
|
||||
'MEMBER_UPDATE_ROLE': '更换客服角色',
|
||||
'MEMBER_REMOVE': '移除客服',
|
||||
'MEMBER_DISABLE': '禁用客服',
|
||||
'MEMBER_ENABLE': '启用客服',
|
||||
'ROLE_CREATE': '创建角色',
|
||||
'ROLE_SET_PERMS': '设置角色权限',
|
||||
'MEMBER_SET_PERMS': '调整成员权限',
|
||||
'STAFF_BIND': '客服绑定入驻',
|
||||
'ORDER_DETAIL': '查看订单详情',
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""操作审计。"""
|
||||
from decimal import Decimal
|
||||
|
||||
from merchant_ops.constants import ACTOR_MERCHANT, ACTOR_STAFF
|
||||
from merchant_ops.models import MerchantStaffAuditLog
|
||||
from merchant_ops.constants import ACTOR_MERCHANT, ACTOR_STAFF, AUDIT_ACTION_LABELS
|
||||
from merchant_ops.models import MerchantStaffAuditLog, MerchantStaffMember
|
||||
|
||||
|
||||
def write_audit(
|
||||
@@ -64,3 +64,38 @@ def _client_ip(request):
|
||||
if xff:
|
||||
return xff.split(',')[0].strip()
|
||||
return request.META.get('REMOTE_ADDR', '') or ''
|
||||
|
||||
|
||||
def audit_action_label(action):
|
||||
return AUDIT_ACTION_LABELS.get(action, action)
|
||||
|
||||
|
||||
def serialize_audit_row(row, member_map=None):
|
||||
"""格式化审计记录供前端展示。"""
|
||||
member_map = member_map or {}
|
||||
label = audit_action_label(row.action)
|
||||
operator_name = '商家老板'
|
||||
role_name = ''
|
||||
if row.operator_type == ACTOR_STAFF and row.staff_member_id:
|
||||
m = member_map.get(row.staff_member_id)
|
||||
if m:
|
||||
operator_name = m.display_name or m.staff_user_id
|
||||
role_name = m.role.role_name if m.role else ''
|
||||
else:
|
||||
operator_name = row.operator_user_id
|
||||
elif row.operator_type == ACTOR_MERCHANT:
|
||||
operator_name = '商家老板'
|
||||
return {
|
||||
'id': row.id,
|
||||
'operator_user_id': row.operator_user_id,
|
||||
'operator_type': row.operator_type,
|
||||
'operator_name': operator_name,
|
||||
'role_name': role_name,
|
||||
'action': row.action,
|
||||
'action_label': label,
|
||||
'resource_type': row.resource_type,
|
||||
'resource_id': row.resource_id,
|
||||
'amount': str(row.amount) if row.amount is not None else '',
|
||||
'remark': row.remark,
|
||||
'time': row.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if row.CreateTime else '',
|
||||
}
|
||||
|
||||
330
merchant_ops/services/link.py
Normal file
330
merchant_ops/services/link.py
Normal file
@@ -0,0 +1,330 @@
|
||||
"""子客服专用:极速派单链接生成 / 列表(独立实现,不修改商家原接口)。"""
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
import time
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
|
||||
from backend.utils import update_shangjia_daily
|
||||
from config.models import ShangjiaLianjie, ShangjiaMoban
|
||||
from merchant_ops.constants import ACTOR_STAFF, STAT_ACTION_DISPATCH
|
||||
from merchant_ops.models import MerchantOrderDispatch, MerchantStaffMember
|
||||
from merchant_ops.services.stats import update_staff_daily
|
||||
from merchant_ops.services.wallet import check_wallet_available, confirm_dispatch_deduct
|
||||
from orders.models import MerchantOrderExt, Order
|
||||
from products.models import ShangpinLeixing
|
||||
from rank.models import Chenghao, DingdanBiaoqian
|
||||
from users.models import CommissionRate, UserShangjia
|
||||
from users.business_models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _generate_dingdan_id():
|
||||
ts = int(time.time() * 1000)
|
||||
pid = os.getpid() % 1000
|
||||
rnd = random.randint(1000, 9999)
|
||||
return f"SJ{ts}{pid:03d}{rnd}"
|
||||
|
||||
|
||||
def _generate_secure_token(dd_id, uid):
|
||||
timestamp = int(time.time() * 1_000_000)
|
||||
salt = secrets.token_hex(8)
|
||||
raw = f"{timestamp}:{dd_id}:{uid}:{salt}"
|
||||
token = hashlib.sha256(raw.encode()).hexdigest()[:24]
|
||||
if ShangjiaLianjie.query.filter(LinkToken=token).exists():
|
||||
return _generate_secure_token(dd_id, uid)
|
||||
return token
|
||||
|
||||
|
||||
def _generate_link_url(token):
|
||||
base = getattr(settings, 'H5_DOMAIN', 'https://h5.yourdomain.com').rstrip('/')
|
||||
return f"{base}/order/{token}"
|
||||
|
||||
|
||||
def _staff_operator_payload(member):
|
||||
role = member.role
|
||||
return {
|
||||
'operatorType': ACTOR_STAFF,
|
||||
'staffMemberId': member.id,
|
||||
'staffUserId': member.staff_user_id,
|
||||
'roleCode': role.role_code if role else '',
|
||||
'roleName': role.role_name if role else '',
|
||||
'displayName': member.display_name or '',
|
||||
}
|
||||
|
||||
|
||||
def staff_generate_link(member, staff_user, data):
|
||||
"""
|
||||
子客服生成发单链接。
|
||||
扣商家余额 + 扣客服额度,并写入 merchant_order_dispatch 标记操作人。
|
||||
"""
|
||||
shangpin_type_id = data.get('shangpinTypeId')
|
||||
moban_id = data.get('mobanId')
|
||||
if not all([shangpin_type_id, moban_id]):
|
||||
return False, {'code': 400, 'msg': '缺少参数', 'data': {}}
|
||||
|
||||
try:
|
||||
shangpin_type_id = int(shangpin_type_id)
|
||||
moban_id = int(moban_id)
|
||||
except (TypeError, ValueError):
|
||||
return False, {'code': 400, 'msg': '参数格式错误', 'data': {}}
|
||||
|
||||
merchant_id = member.merchant_id
|
||||
try:
|
||||
shangjia = UserShangjia.query.get(user__UserUID=merchant_id)
|
||||
except UserShangjia.DoesNotExist:
|
||||
return False, {'code': 403, 'msg': '所属商家不存在', 'data': {}}
|
||||
if shangjia.zhuangtai != 1:
|
||||
return False, {'code': 403, 'msg': '商家状态异常', 'data': {}}
|
||||
|
||||
try:
|
||||
shangpin_leixing = ShangpinLeixing.query.get(id=shangpin_type_id, shenhezhuangtai=1)
|
||||
except ShangpinLeixing.DoesNotExist:
|
||||
return False, {'code': 400, 'msg': '商品类型不存在或已禁用', 'data': {}}
|
||||
|
||||
try:
|
||||
moban = ShangjiaMoban.query.get(
|
||||
id=moban_id, UserID=merchant_id, ProductTypeID=shangpin_type_id,
|
||||
)
|
||||
except ShangjiaMoban.DoesNotExist:
|
||||
return False, {'code': 400, 'msg': '模板不存在', 'data': {}}
|
||||
|
||||
jiage = moban.Price
|
||||
if jiage > shangjia.yue:
|
||||
return False, {'code': 400, 'msg': f'商家余额不足,当前{shangjia.yue}元', 'data': {}}
|
||||
|
||||
try:
|
||||
lilu = float(CommissionRate.query.get(Platform='3').Rate) or 1.0
|
||||
except CommissionRate.DoesNotExist:
|
||||
lilu = 1.0
|
||||
dashou_fencheng = Decimal(str(round(float(jiage) * lilu, 2)))
|
||||
|
||||
chenghao_id = moban.TitleID
|
||||
if moban.CommissionGrabEnabled and moban.CommissionPrice is not None:
|
||||
final_yaoqiuleixing = 2
|
||||
final_yongjin = moban.CommissionPrice
|
||||
final_huiyuan_id = ''
|
||||
else:
|
||||
final_yaoqiuleixing = shangpin_leixing.yaoqiuleixing or 1
|
||||
final_yongjin = None
|
||||
final_huiyuan_id = shangpin_leixing.huiyuan_id or ''
|
||||
|
||||
label_name = ''
|
||||
if chenghao_id:
|
||||
ch = Chenghao.query.filter(id=chenghao_id).first()
|
||||
if ch:
|
||||
label_name = ch.mingcheng
|
||||
|
||||
merchant_user = User.query.filter(UserUID=merchant_id).first()
|
||||
operator = _staff_operator_payload(member)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
check_wallet_available(member, jiage)
|
||||
|
||||
dingdan_id = _generate_dingdan_id()
|
||||
secure_token = _generate_secure_token(dingdan_id, merchant_id)
|
||||
|
||||
dingdan = Order.query.create(
|
||||
OrderID=dingdan_id,
|
||||
Status=13,
|
||||
Platform=2,
|
||||
Amount=jiage,
|
||||
PlayerCommission=dashou_fencheng,
|
||||
ProductTypeID=shangpin_type_id,
|
||||
GrabRequirement=final_yaoqiuleixing,
|
||||
MembershipID=final_huiyuan_id,
|
||||
CommissionReq=final_yongjin,
|
||||
Description=moban.TemplateDesc[:2000],
|
||||
User1ID=f"Sj{merchant_id}",
|
||||
ImageURL=merchant_user.Avatar if merchant_user else '',
|
||||
)
|
||||
|
||||
MerchantOrderExt.query.create(
|
||||
Order=dingdan,
|
||||
MerchantID=merchant_id,
|
||||
MerchantNickname=shangjia.nicheng or f"商家{merchant_id}",
|
||||
)
|
||||
|
||||
if chenghao_id:
|
||||
ch = Chenghao.query.filter(id=chenghao_id).first()
|
||||
if ch:
|
||||
DingdanBiaoqian.query.create(dingdan=dingdan, chenghao=ch)
|
||||
|
||||
UserShangjia.query.filter(id=shangjia.id).update(
|
||||
fabu=F('fabu') + 1,
|
||||
yue=F('yue') - jiage,
|
||||
jinridingdan=F('jinridingdan') + 1,
|
||||
jinriliushui=F('jinriliushui') + jiage,
|
||||
jinyuedingdan=F('jinyuedingdan') + 1,
|
||||
jinyueliushui=F('jinyueliushui') + jiage,
|
||||
)
|
||||
|
||||
link_url = _generate_link_url(secure_token)
|
||||
lianjie_obj = ShangjiaLianjie.query.create(
|
||||
UserID=merchant_id,
|
||||
ProductTypeID=shangpin_type_id,
|
||||
TemplateID=moban_id,
|
||||
LinkURL=link_url,
|
||||
LinkToken=secure_token,
|
||||
OrderID=dingdan_id,
|
||||
is_used=False,
|
||||
ExpireTime=timezone.now() + timezone.timedelta(days=1),
|
||||
)
|
||||
|
||||
ShangjiaMoban.query.filter(id=moban_id).update(
|
||||
PublishedCount=F('PublishedCount') + 1,
|
||||
)
|
||||
|
||||
MerchantOrderDispatch.objects.create(
|
||||
order_id=dingdan_id,
|
||||
merchant_id=merchant_id,
|
||||
dispatch_actor_type=ACTOR_STAFF,
|
||||
dispatch_actor_id=member.staff_user_id,
|
||||
staff_member_id=member.id,
|
||||
)
|
||||
|
||||
confirm_dispatch_deduct(member, jiage, dingdan_id, member.staff_user_id)
|
||||
update_shangjia_daily(merchant_id, Decimal(str(jiage)), STAT_ACTION_DISPATCH)
|
||||
update_staff_daily(
|
||||
merchant_id, member.staff_user_id, member.id, jiage, STAT_ACTION_DISPATCH,
|
||||
)
|
||||
MerchantStaffMember.query.filter(id=member.id).update(
|
||||
today_dispatch_count=F('today_dispatch_count') + 1,
|
||||
today_dispatch_amount=F('today_dispatch_amount') + Decimal(str(jiage)),
|
||||
)
|
||||
|
||||
shangjia.refresh_from_db()
|
||||
from merchant_ops.services.wallet import get_or_create_wallet
|
||||
wallet = get_or_create_wallet(member)
|
||||
quota_available = str(wallet.quota_available)
|
||||
|
||||
except ValueError as e:
|
||||
return False, {'code': 400, 'msg': str(e), 'data': {}}
|
||||
except Exception as e:
|
||||
logger.error(f'子客服生成链接失败: {e}', exc_info=True)
|
||||
return False, {'code': 500, 'msg': '系统错误,生成链接失败', 'data': {}}
|
||||
|
||||
logger.info(
|
||||
f"子客服链接生成 - 订单:{dingdan_id} 客服:{member.staff_user_id} "
|
||||
f"角色:{operator.get('roleName')}"
|
||||
)
|
||||
|
||||
return True, {
|
||||
'code': 200,
|
||||
'msg': '链接生成成功',
|
||||
'data': {
|
||||
'linkUrl': link_url,
|
||||
'newBalance': quota_available,
|
||||
'quotaAvailable': quota_available,
|
||||
'dingdanId': dingdan_id,
|
||||
'dingdanJieshao': moban.TemplateDesc,
|
||||
'jiage': str(jiage),
|
||||
'labelId': chenghao_id,
|
||||
'labelName': label_name,
|
||||
'youxiaoShijian': lianjie_obj.ExpireTime.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
**operator,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def staff_list_links(member, data):
|
||||
"""子客服链接列表;总管看全部,派单员只看自己生成的。"""
|
||||
from merchant_ops.services.authz import member_has_perm
|
||||
|
||||
moban_id = data.get('mobanId')
|
||||
used = int(data.get('used', 0))
|
||||
page = max(1, int(data.get('page', 1)))
|
||||
page_size = min(50, max(1, int(data.get('pageSize', 5))))
|
||||
|
||||
if not moban_id:
|
||||
return False, {'code': 400, 'msg': '模板ID不能为空', 'data': {}}
|
||||
|
||||
merchant_id = member.merchant_id
|
||||
try:
|
||||
ShangjiaMoban.query.get(id=moban_id, UserID=merchant_id)
|
||||
except ShangjiaMoban.DoesNotExist:
|
||||
return False, {'code': 404, 'msg': '模板不存在或无权查看', 'data': {}}
|
||||
|
||||
links = ShangjiaLianjie.query.filter(
|
||||
UserID=merchant_id,
|
||||
TemplateID=moban_id,
|
||||
is_used=(used == 1),
|
||||
).order_by('-CreateTime')
|
||||
|
||||
if not member_has_perm(member, 'order_view_all'):
|
||||
own_order_ids = MerchantOrderDispatch.query.filter(
|
||||
merchant_id=merchant_id,
|
||||
staff_member_id=member.id,
|
||||
).values_list('order_id', flat=True)
|
||||
links = links.filter(OrderID__in=own_order_ids)
|
||||
|
||||
paginator = Paginator(links, page_size)
|
||||
if page > paginator.num_pages and paginator.count > 0:
|
||||
return False, {'code': 400, 'msg': '页码超出范围', 'data': {}}
|
||||
|
||||
current_page = paginator.page(page) if paginator.count else paginator.page(1)
|
||||
order_ids = [lk.OrderID for lk in current_page.object_list if lk.OrderID]
|
||||
dispatch_map = {}
|
||||
if order_ids:
|
||||
for d in MerchantOrderDispatch.query.filter(order_id__in=order_ids):
|
||||
dispatch_map[d.order_id] = d
|
||||
|
||||
member_ids = {d.staff_member_id for d in dispatch_map.values() if d.staff_member_id}
|
||||
members = {}
|
||||
if member_ids:
|
||||
for m in MerchantStaffMember.query.filter(id__in=member_ids).select_related('role'):
|
||||
members[m.id] = m
|
||||
|
||||
data_list = []
|
||||
for link in current_page.object_list:
|
||||
item = {
|
||||
'id': link.id,
|
||||
'lianjie': link.LinkURL,
|
||||
'LinkToken': link.LinkToken,
|
||||
'is_used': link.is_used,
|
||||
'dingdan_id': link.OrderID,
|
||||
'ExpireTime': link.ExpireTime.strftime('%Y-%m-%d %H:%M:%S') if link.ExpireTime else '',
|
||||
'CreateTime': link.CreateTime.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'operatorType': 'MERCHANT',
|
||||
'staffMemberId': None,
|
||||
'staffUserId': '',
|
||||
'roleCode': '',
|
||||
'roleName': '',
|
||||
'displayName': '商家老板',
|
||||
}
|
||||
disp = dispatch_map.get(link.OrderID)
|
||||
if disp and disp.dispatch_actor_type == ACTOR_STAFF and disp.staff_member_id:
|
||||
sm = members.get(disp.staff_member_id)
|
||||
if sm:
|
||||
item.update({
|
||||
'operatorType': ACTOR_STAFF,
|
||||
'staffMemberId': sm.id,
|
||||
'staffUserId': sm.staff_user_id,
|
||||
'roleCode': sm.role.role_code if sm.role else '',
|
||||
'roleName': sm.role.role_name if sm.role else '',
|
||||
'displayName': sm.display_name or sm.role.role_name if sm.role else '',
|
||||
})
|
||||
data_list.append(item)
|
||||
|
||||
return True, {
|
||||
'code': 200,
|
||||
'msg': '获取成功',
|
||||
'data': {
|
||||
'list': data_list,
|
||||
'total': paginator.count,
|
||||
'hasMore': current_page.has_next(),
|
||||
'currentPage': page,
|
||||
'pageSize': page_size,
|
||||
'viewScope': 'all' if member_has_perm(member, 'order_view_all') else 'self',
|
||||
},
|
||||
}
|
||||
@@ -369,10 +369,15 @@ class StaffWalletLedgerView(APIView):
|
||||
|
||||
|
||||
class StaffAuditListView(APIView):
|
||||
"""POST /yonghu/merchant-staff/audit/list — 商家老板或有 audit_view 权限的客服可查看重要操作日志。"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
from merchant_ops.constants import IMPORTANT_AUDIT_ACTIONS
|
||||
from merchant_ops.services.audit import serialize_audit_row
|
||||
|
||||
mid = request.user.UserUID
|
||||
member = None
|
||||
if is_merchant_owner(request.user):
|
||||
@@ -384,23 +389,21 @@ class StaffAuditListView(APIView):
|
||||
mid = member.merchant_id
|
||||
page = max(1, int(request.data.get('page', 1)))
|
||||
page_size = min(50, int(request.data.get('page_size', 20)))
|
||||
qs = MerchantStaffAuditLog.query.filter(merchant_id=mid)
|
||||
qs = MerchantStaffAuditLog.query.filter(
|
||||
merchant_id=mid,
|
||||
action__in=IMPORTANT_AUDIT_ACTIONS,
|
||||
)
|
||||
staff_filter = request.data.get('staff_user_id')
|
||||
if staff_filter:
|
||||
qs = qs.filter(operator_user_id=staff_filter)
|
||||
total = qs.count()
|
||||
rows = qs.order_by('-CreateTime')[(page - 1) * page_size: page * page_size]
|
||||
items = [{
|
||||
'id': r.id,
|
||||
'operator_user_id': r.operator_user_id,
|
||||
'operator_type': r.operator_type,
|
||||
'action': r.action,
|
||||
'resource_type': r.resource_type,
|
||||
'resource_id': r.resource_id,
|
||||
'amount': str(r.amount) if r.amount is not None else '',
|
||||
'remark': r.remark,
|
||||
'time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
|
||||
} for r in rows]
|
||||
rows = list(qs.order_by('-CreateTime')[(page - 1) * page_size: page * page_size])
|
||||
member_ids = {r.staff_member_id for r in rows if r.staff_member_id}
|
||||
member_map = {}
|
||||
if member_ids:
|
||||
for m in MerchantStaffMember.query.filter(id__in=member_ids).select_related('role'):
|
||||
member_map[m.id] = m
|
||||
items = [serialize_audit_row(r, member_map) for r in rows]
|
||||
return Response({'code': 0, 'msg': '成功', 'data': {
|
||||
'list': items, 'total': total, 'has_more': page * page_size < total,
|
||||
}})
|
||||
|
||||
@@ -105,8 +105,6 @@ class StaffOrderDetailView(APIView):
|
||||
resp = proxy_merchant_order_view(
|
||||
request, member, ShangjiaDingdanXiangqingView, 'order_detail',
|
||||
)
|
||||
if resp.status_code == 200 and resp.data.get('code') in (0, 200):
|
||||
audit_staff(member, 'ORDER_DETAIL', request, resource_id=pick_order_id(request.data))
|
||||
return resp
|
||||
except Exception as e:
|
||||
return _err(e)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""子客服专用:派单模板 / 链接(独立路径;读模板用 merchant_id,写模板需 template_manage)。"""
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
@@ -7,12 +8,12 @@ from config.views import (
|
||||
ShangjiaTianjiaMobanView,
|
||||
ShangjiaGengxinMobanView,
|
||||
ShangjiaShanchuMobanView,
|
||||
ShangjiaGenerateLinkView,
|
||||
ShangjiaLianjieListView,
|
||||
)
|
||||
from merchant_ops.helpers import proxy_as_merchant_user
|
||||
from merchant_ops.services.authz import StaffAuthError, get_active_staff_member, member_has_perm
|
||||
from merchant_ops.services.template_query import list_merchant_templates
|
||||
from merchant_ops.services.link import staff_generate_link, staff_list_links
|
||||
from merchant_ops.services.audit import audit_staff
|
||||
from merchant_ops.views_manage import _err
|
||||
|
||||
|
||||
@@ -52,7 +53,11 @@ class StaffTemplateCreateView(APIView):
|
||||
member = _require_staff(request)
|
||||
if not member_has_perm(member, 'template_manage'):
|
||||
raise StaffAuthError(403, '无权限添加模板')
|
||||
return proxy_as_merchant_user(request, member, ShangjiaTianjiaMobanView, 'template_manage')
|
||||
resp = proxy_as_merchant_user(request, member, ShangjiaTianjiaMobanView, 'template_manage')
|
||||
if resp.status_code == 200 and resp.data.get('code') in (0, 200):
|
||||
audit_staff(member, 'TEMPLATE_CREATE', request,
|
||||
resource_id=(resp.data.get('data') or {}).get('id', ''))
|
||||
return resp
|
||||
except Exception as e:
|
||||
return _err(e)
|
||||
|
||||
@@ -65,7 +70,11 @@ class StaffTemplateUpdateView(APIView):
|
||||
member = _require_staff(request)
|
||||
if not member_has_perm(member, 'template_manage'):
|
||||
raise StaffAuthError(403, '无权限修改模板')
|
||||
return proxy_as_merchant_user(request, member, ShangjiaGengxinMobanView, 'template_manage')
|
||||
moban_id = request.data.get('mobanId') or request.data.get('id') or ''
|
||||
resp = proxy_as_merchant_user(request, member, ShangjiaGengxinMobanView, 'template_manage')
|
||||
if resp.status_code == 200 and resp.data.get('code') in (0, 200):
|
||||
audit_staff(member, 'TEMPLATE_UPDATE', request, resource_id=str(moban_id))
|
||||
return resp
|
||||
except Exception as e:
|
||||
return _err(e)
|
||||
|
||||
@@ -78,28 +87,51 @@ class StaffTemplateDeleteView(APIView):
|
||||
member = _require_staff(request)
|
||||
if not member_has_perm(member, 'template_manage'):
|
||||
raise StaffAuthError(403, '无权限删除模板')
|
||||
return proxy_as_merchant_user(request, member, ShangjiaShanchuMobanView, 'template_manage')
|
||||
moban_id = request.data.get('mobanId') or request.data.get('id') or ''
|
||||
resp = proxy_as_merchant_user(request, member, ShangjiaShanchuMobanView, 'template_manage')
|
||||
if resp.status_code == 200 and resp.data.get('code') in (0, 200):
|
||||
audit_staff(member, 'TEMPLATE_DELETE', request, resource_id=str(moban_id))
|
||||
return resp
|
||||
except Exception as e:
|
||||
return _err(e)
|
||||
|
||||
|
||||
class StaffLinkGenerateView(APIView):
|
||||
"""POST /yonghu/merchant-staff/link/generate — 子客服独立实现,标记操作人身份。"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
member = _require_staff(request)
|
||||
return proxy_as_merchant_user(request, member, ShangjiaGenerateLinkView, 'order_dispatch')
|
||||
if not member_has_perm(member, 'order_dispatch'):
|
||||
raise StaffAuthError(403, '无派单/生成链接权限')
|
||||
ok, payload = staff_generate_link(member, request.user, request.data)
|
||||
if ok:
|
||||
audit_staff(
|
||||
member, 'LINK_GENERATE', request,
|
||||
resource_id=payload.get('data', {}).get('dingdanId'),
|
||||
amount=payload.get('data', {}).get('jiage'),
|
||||
remark=f"{member.role.role_name}:{member.display_name or member.staff_user_id}",
|
||||
)
|
||||
code = payload.get('code', 500)
|
||||
return Response(payload, status=status.HTTP_200_OK if code in (0, 200) else status.HTTP_400_BAD_REQUEST)
|
||||
except Exception as e:
|
||||
return _err(e)
|
||||
|
||||
|
||||
class StaffLinkListView(APIView):
|
||||
"""POST /yonghu/merchant-staff/link/list — 子客服链接列表,含操作人身份。"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
member = _require_staff(request)
|
||||
return proxy_as_merchant_user(request, member, ShangjiaLianjieListView, 'order_dispatch')
|
||||
if not member_has_perm(member, 'order_dispatch'):
|
||||
raise StaffAuthError(403, '无权限')
|
||||
ok, payload = staff_list_links(member, request.data)
|
||||
code = payload.get('code', 500)
|
||||
return Response(payload, status=status.HTTP_200_OK if code in (0, 200) else status.HTTP_400_BAD_REQUEST)
|
||||
except Exception as e:
|
||||
return _err(e)
|
||||
|
||||
Reference in New Issue
Block a user