fix: 订单时间字段别名与子客服鉴权/读权限统一
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -503,6 +503,19 @@ def check_fadan_permission(permissions, shenfen):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_datetime(dt):
|
||||||
|
"""格式化时间为前端通用字符串。"""
|
||||||
|
if not dt:
|
||||||
|
return ''
|
||||||
|
return dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
|
def datetime_aliases(dt):
|
||||||
|
"""同时返回 CreateTime / create_time,避免前后端字段名不一致。"""
|
||||||
|
s = fmt_datetime(dt)
|
||||||
|
return {'CreateTime': s, 'create_time': s}
|
||||||
|
|
||||||
|
|
||||||
def pick_order_id(data):
|
def pick_order_id(data):
|
||||||
"""兼容前端 dingdan_id 与 SDK 字段 OrderID。"""
|
"""兼容前端 dingdan_id 与 SDK 字段 OrderID。"""
|
||||||
return str(data.get('OrderID') or data.get('dingdan_id') or '').strip()
|
return str(data.get('OrderID') or data.get('dingdan_id') or '').strip()
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
from rest_framework.exceptions import PermissionDenied, NotFound
|
from rest_framework.exceptions import PermissionDenied, NotFound
|
||||||
|
|
||||||
from merchant_ops.constants import (
|
from merchant_ops.constants import (
|
||||||
MEMBER_STATUS_ACTIVE, MEMBER_STATUS_REMOVED, OWNER_ALL_PERMISSIONS,
|
MEMBER_STATUS_ACTIVE, MEMBER_STATUS_DISABLED, MEMBER_STATUS_REMOVED,
|
||||||
|
OWNER_ALL_PERMISSIONS,
|
||||||
)
|
)
|
||||||
from merchant_ops.models import MerchantStaffMember, MerchantStaffMemberPermission
|
from merchant_ops.models import MerchantStaffMember, MerchantStaffMemberPermission
|
||||||
from merchant_ops.services.bootstrap import ensure_merchant_roles
|
from merchant_ops.services.bootstrap import ensure_merchant_roles
|
||||||
@@ -31,6 +32,24 @@ def get_active_staff_member(user):
|
|||||||
).select_related('role').first()
|
).select_related('role').first()
|
||||||
|
|
||||||
|
|
||||||
|
def require_active_staff_member(user):
|
||||||
|
"""子客服接口鉴权:区分老板误入、未绑定、禁用、移除等场景。"""
|
||||||
|
if is_merchant_owner(user):
|
||||||
|
raise StaffAuthError(403, '您是商家老板,请使用商家端原接口')
|
||||||
|
member = MerchantStaffMember.query.filter(
|
||||||
|
staff_user_id=user.UserUID,
|
||||||
|
).select_related('role').order_by('-id').first()
|
||||||
|
if not member:
|
||||||
|
raise StaffAuthError(403, '未绑定子客服,请使用商家邀请码绑定')
|
||||||
|
if member.status == MEMBER_STATUS_DISABLED:
|
||||||
|
raise StaffAuthError(403, '子客服账号已被禁用,请联系商家老板')
|
||||||
|
if member.status == MEMBER_STATUS_REMOVED:
|
||||||
|
raise StaffAuthError(403, '子客服已被移除,请联系商家老板重新邀请')
|
||||||
|
if member.status != MEMBER_STATUS_ACTIVE:
|
||||||
|
raise StaffAuthError(403, '非有效子客服')
|
||||||
|
return member
|
||||||
|
|
||||||
|
|
||||||
def get_member_permissions(member):
|
def get_member_permissions(member):
|
||||||
"""角色权限 + 成员覆盖。"""
|
"""角色权限 + 成员覆盖。"""
|
||||||
perms = set(
|
perms = set(
|
||||||
@@ -48,6 +67,14 @@ def member_has_perm(member, perm_code):
|
|||||||
return perm_code in get_member_permissions(member)
|
return perm_code in get_member_permissions(member)
|
||||||
|
|
||||||
|
|
||||||
|
def staff_order_read_perm(member):
|
||||||
|
"""子客服读订单/商品类型/模板时,任一看单或派单权限即可。"""
|
||||||
|
for code in ('order_dispatch', 'order_view_all', 'order_view_self', 'order_detail'):
|
||||||
|
if member_has_perm(member, code):
|
||||||
|
return code
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def assert_staff_perm(member, perm_code):
|
def assert_staff_perm(member, perm_code):
|
||||||
if not member_has_perm(member, perm_code):
|
if not member_has_perm(member, perm_code):
|
||||||
raise StaffAuthError(403, f'无权限: {perm_code}')
|
raise StaffAuthError(403, f'无权限: {perm_code}')
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""子客服订单查询:按权限隔离,不返回商家全量(除非 order_view_all)。"""
|
"""子客服订单查询:按权限隔离,不返回商家全量(除非 order_view_all)。"""
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from backend.utils import pick_leixing_id
|
from backend.utils import fmt_datetime, pick_leixing_id
|
||||||
from merchant_ops.models import MerchantOrderDispatch
|
from merchant_ops.models import MerchantOrderDispatch
|
||||||
from merchant_ops.services.authz import member_has_perm
|
from merchant_ops.services.authz import member_has_perm
|
||||||
from orders.models import MerchantOrderExt
|
from orders.models import MerchantOrderExt
|
||||||
@@ -43,7 +43,7 @@ def staff_apply_order_filters(qs, data):
|
|||||||
|
|
||||||
def staff_order_to_dict(ext):
|
def staff_order_to_dict(ext):
|
||||||
o = ext.Order
|
o = ext.Order
|
||||||
ct = o.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if o.CreateTime else ''
|
ct = fmt_datetime(o.CreateTime)
|
||||||
return {
|
return {
|
||||||
'dingdan_id': o.OrderID,
|
'dingdan_id': o.OrderID,
|
||||||
'zhuangtai': o.Status,
|
'zhuangtai': o.Status,
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from backend.utils import pick_order_id
|
from backend.utils import pick_order_id
|
||||||
from merchant_ops.helpers import proxy_merchant_order_view
|
from merchant_ops.helpers import proxy_merchant_order_view
|
||||||
from merchant_ops.services.authz import StaffAuthError, get_active_staff_member, member_has_perm
|
from merchant_ops.services.authz import (
|
||||||
|
StaffAuthError, require_active_staff_member, member_has_perm, staff_order_read_perm,
|
||||||
|
)
|
||||||
from merchant_ops.services.order_query import (
|
from merchant_ops.services.order_query import (
|
||||||
staff_apply_order_filters,
|
staff_apply_order_filters,
|
||||||
staff_can_view_all_orders,
|
staff_can_view_all_orders,
|
||||||
@@ -29,10 +31,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def _require_staff(request):
|
def _require_staff(request):
|
||||||
member = get_active_staff_member(request.user)
|
return require_active_staff_member(request.user)
|
||||||
if not member:
|
|
||||||
raise StaffAuthError(403, '非有效子客服')
|
|
||||||
return member
|
|
||||||
|
|
||||||
|
|
||||||
def _err(e):
|
def _err(e):
|
||||||
@@ -138,9 +137,10 @@ class StaffProductTypesView(APIView):
|
|||||||
def post(self, request):
|
def post(self, request):
|
||||||
try:
|
try:
|
||||||
member = _require_staff(request)
|
member = _require_staff(request)
|
||||||
if not member_has_perm(member, 'order_dispatch'):
|
perm = staff_order_read_perm(member)
|
||||||
raise StaffAuthError(403, '无权限')
|
if not perm:
|
||||||
return proxy_merchant_order_view(request, member, ShangpinLeixingView, 'order_dispatch')
|
raise StaffAuthError(403, '无权限查看商品类型')
|
||||||
|
return proxy_merchant_order_view(request, member, ShangpinLeixingView, perm)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return _err(e)
|
return _err(e)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ from config.views import (
|
|||||||
ShangjiaShanchuMobanView,
|
ShangjiaShanchuMobanView,
|
||||||
)
|
)
|
||||||
from merchant_ops.helpers import proxy_as_merchant_user
|
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.authz import (
|
||||||
|
StaffAuthError, require_active_staff_member, member_has_perm, staff_order_read_perm,
|
||||||
|
)
|
||||||
from merchant_ops.services.template_query import list_merchant_templates
|
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.link import staff_generate_link, staff_list_links
|
||||||
from merchant_ops.services.audit import audit_staff
|
from merchant_ops.services.audit import audit_staff
|
||||||
@@ -18,15 +20,12 @@ from merchant_ops.views_manage import _err
|
|||||||
|
|
||||||
|
|
||||||
def _require_staff(request):
|
def _require_staff(request):
|
||||||
member = get_active_staff_member(request.user)
|
return require_active_staff_member(request.user)
|
||||||
if not member:
|
|
||||||
raise StaffAuthError(403, '非有效子客服')
|
|
||||||
return member
|
|
||||||
|
|
||||||
|
|
||||||
def _can_read_templates(member):
|
def _can_read_templates(member):
|
||||||
"""派单发常规单需读模板;改模板需 template_manage。"""
|
"""派单/看单/管模板任一权限即可读模板列表。"""
|
||||||
return member_has_perm(member, 'order_dispatch') or member_has_perm(member, 'template_manage')
|
return staff_order_read_perm(member) or member_has_perm(member, 'template_manage')
|
||||||
|
|
||||||
|
|
||||||
class StaffTemplateListView(APIView):
|
class StaffTemplateListView(APIView):
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ from orders.utils import (
|
|||||||
)
|
)
|
||||||
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
|
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
|
||||||
from products.utils import update_shangpin_daily_stat
|
from products.utils import update_shangpin_daily_stat
|
||||||
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id
|
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime
|
||||||
from rank.services import record_dashou_biaoxian
|
from rank.services import record_dashou_biaoxian
|
||||||
from rank.utils import check_dashou_biaoqian_required
|
from rank.utils import check_dashou_biaoqian_required
|
||||||
|
|
||||||
@@ -1097,7 +1097,8 @@ class DingdanHuoquView(APIView):
|
|||||||
'jieshao': order.Description or '',
|
'jieshao': order.Description or '',
|
||||||
'beizhu': order.Remark or '',
|
'beizhu': order.Remark or '',
|
||||||
'nicheng': order.Nickname or '',
|
'nicheng': order.Nickname or '',
|
||||||
'chuangjianshijian': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S')
|
'chuangjianshijian': fmt_datetime(order.CreateTime),
|
||||||
|
'create_time': fmt_datetime(order.CreateTime),
|
||||||
}
|
}
|
||||||
order_list.append(order_data)
|
order_list.append(order_data)
|
||||||
|
|
||||||
@@ -1759,12 +1760,12 @@ class ShangpinLeixingView(APIView):
|
|||||||
if getattr(request, '_staff_member', None) is not None:
|
if getattr(request, '_staff_member', None) is not None:
|
||||||
return True
|
return True
|
||||||
from merchant_ops.services.authz import (
|
from merchant_ops.services.authz import (
|
||||||
get_active_staff_member, is_merchant_owner, member_has_perm,
|
get_active_staff_member, is_merchant_owner, member_has_perm, staff_order_read_perm,
|
||||||
)
|
)
|
||||||
if is_merchant_owner(request.user):
|
if is_merchant_owner(request.user):
|
||||||
return True
|
return True
|
||||||
member = get_active_staff_member(request.user)
|
member = get_active_staff_member(request.user)
|
||||||
return bool(member and member_has_perm(member, 'order_dispatch'))
|
return bool(member and staff_order_read_perm(member))
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
|
||||||
@@ -2140,7 +2141,7 @@ class ShangjiaDingdanHuoquView(APIView):
|
|||||||
'leixing_id': order.ProductTypeID,
|
'leixing_id': order.ProductTypeID,
|
||||||
'tupian': order.ImageURL or '',
|
'tupian': order.ImageURL or '',
|
||||||
'jiedan_dashou_id': order.PlayerID or '',
|
'jiedan_dashou_id': order.PlayerID or '',
|
||||||
'CreateTime': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else ''
|
**datetime_aliases(order.CreateTime),
|
||||||
}
|
}
|
||||||
order_list.append(order_data)
|
order_list.append(order_data)
|
||||||
|
|
||||||
@@ -2249,6 +2250,8 @@ class ShangjiaDingdanXiangqingView(APIView):
|
|||||||
).first()
|
).first()
|
||||||
fadan_dict = None
|
fadan_dict = None
|
||||||
if fadan_obj:
|
if fadan_obj:
|
||||||
|
ct = fmt_datetime(fadan_obj.CreateTime)
|
||||||
|
ut = fmt_datetime(fadan_obj.UpdateTime)
|
||||||
fadan_dict = {
|
fadan_dict = {
|
||||||
'id': fadan_obj.id,
|
'id': fadan_obj.id,
|
||||||
'chufaliyou': fadan_obj.Reason or '',
|
'chufaliyou': fadan_obj.Reason or '',
|
||||||
@@ -2258,8 +2261,10 @@ class ShangjiaDingdanXiangqingView(APIView):
|
|||||||
'shensuliyou': fadan_obj.AppealReason or '',
|
'shensuliyou': fadan_obj.AppealReason or '',
|
||||||
'tongyi_chufaliyou': fadan_obj.ApproveReason or '',
|
'tongyi_chufaliyou': fadan_obj.ApproveReason or '',
|
||||||
'bohuiliyou': fadan_obj.RejectReason or '',
|
'bohuiliyou': fadan_obj.RejectReason or '',
|
||||||
'CreateTime': fadan_obj.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.CreateTime else '',
|
'CreateTime': ct,
|
||||||
'UpdateTime': fadan_obj.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.UpdateTime else '',
|
'create_time': ct,
|
||||||
|
'UpdateTime': ut,
|
||||||
|
'update_time': ut,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 7. 商家自己的分红利率(用于罚款分红计算)
|
# 7. 商家自己的分红利率(用于罚款分红计算)
|
||||||
@@ -2297,7 +2302,7 @@ class ShangjiaDingdanXiangqingView(APIView):
|
|||||||
'jieshao': order.Description or '',
|
'jieshao': order.Description or '',
|
||||||
'beizhu': order.Remark or '',
|
'beizhu': order.Remark or '',
|
||||||
'nicheng': order.Nickname or '',
|
'nicheng': order.Nickname or '',
|
||||||
'CreateTime': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else '',
|
**datetime_aliases(order.CreateTime),
|
||||||
|
|
||||||
# 打手信息
|
# 打手信息
|
||||||
'dashou_yonghuid': dashou_yonghuid,
|
'dashou_yonghuid': dashou_yonghuid,
|
||||||
@@ -3174,7 +3179,8 @@ class DashouDingdanHuoquView(APIView):
|
|||||||
'yajin': float(order['CommissionReq']) if order['CommissionReq'] else 0.0,
|
'yajin': float(order['CommissionReq']) if order['CommissionReq'] else 0.0,
|
||||||
'jieshao': order['Description'] or '',
|
'jieshao': order['Description'] or '',
|
||||||
'beizhu': order['Remark'] or '',
|
'beizhu': order['Remark'] or '',
|
||||||
'creat_time': order['CreateTime'].strftime('%Y-%m-%d %H:%M:%S') if order['CreateTime'] else '',
|
'creat_time': fmt_datetime(order['CreateTime']),
|
||||||
|
'create_time': fmt_datetime(order['CreateTime']),
|
||||||
'sjnicheng': order['MerchantNickname'] or '',
|
'sjnicheng': order['MerchantNickname'] or '',
|
||||||
'zhiding_uid': order['AssignedID'] or '',
|
'zhiding_uid': order['AssignedID'] or '',
|
||||||
'zhiding_avatar': info.get('avatar', ''),
|
'zhiding_avatar': info.get('avatar', ''),
|
||||||
@@ -3792,7 +3798,7 @@ class DashouDingdanHuoquView1(APIView):
|
|||||||
'jieshao': order.Description or '',
|
'jieshao': order.Description or '',
|
||||||
'tupian': order.ImageURL or '',
|
'tupian': order.ImageURL or '',
|
||||||
'nicheng': order.Nickname or '',
|
'nicheng': order.Nickname or '',
|
||||||
'CreateTime': order.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if order.CreateTime else ''
|
**datetime_aliases(order.CreateTime),
|
||||||
}
|
}
|
||||||
order_list.append(order_data)
|
order_list.append(order_data)
|
||||||
|
|
||||||
@@ -4324,7 +4330,7 @@ class DashouDingdanXiangqingView(APIView):
|
|||||||
'tupian': dingdan.ImageURL or '',
|
'tupian': dingdan.ImageURL or '',
|
||||||
'nicheng': dingdan.Nickname or '',
|
'nicheng': dingdan.Nickname or '',
|
||||||
'beizhu': dingdan.Remark or '',
|
'beizhu': dingdan.Remark or '',
|
||||||
'CreateTime': dingdan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if dingdan.CreateTime else '',
|
**datetime_aliases(dingdan.CreateTime),
|
||||||
'tuikuan_liyou':dingdan.RefundReason or '',
|
'tuikuan_liyou':dingdan.RefundReason or '',
|
||||||
'dashou_liuyan': dingdan.PlayerRemark or '',
|
'dashou_liuyan': dingdan.PlayerRemark or '',
|
||||||
'fuwudashou_id': dingdan.PlayerID or '',
|
'fuwudashou_id': dingdan.PlayerID or '',
|
||||||
@@ -4496,10 +4502,10 @@ class AdGetDingdanXiangQing(APIView):
|
|||||||
'dashou_liuyan': dingdan_obj.PlayerRemark or '',
|
'dashou_liuyan': dingdan_obj.PlayerRemark or '',
|
||||||
|
|
||||||
# 时间字段
|
# 时间字段
|
||||||
'chuangjianshijian': dingdan_obj.CreateTime.strftime(
|
'chuangjianshijian': fmt_datetime(dingdan_obj.CreateTime),
|
||||||
'%Y-%m-%d %H:%M:%S') if dingdan_obj.CreateTime else '',
|
'create_time': fmt_datetime(dingdan_obj.CreateTime),
|
||||||
'genggaishijian': dingdan_obj.UpdateTime.strftime(
|
'genggaishijian': fmt_datetime(dingdan_obj.UpdateTime),
|
||||||
'%Y-%m-%d %H:%M:%S') if dingdan_obj.UpdateTime else '',
|
'update_time': fmt_datetime(dingdan_obj.UpdateTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 8. 根据发单平台查询扩展表数据
|
# 8. 根据发单平台查询扩展表数据
|
||||||
|
|||||||
Reference in New Issue
Block a user