diff --git a/backend/utils.py b/backend/utils.py index 0b6e31b..44d18bf 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -503,6 +503,19 @@ def check_fadan_permission(permissions, shenfen): 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): """兼容前端 dingdan_id 与 SDK 字段 OrderID。""" return str(data.get('OrderID') or data.get('dingdan_id') or '').strip() diff --git a/merchant_ops/services/authz.py b/merchant_ops/services/authz.py index 69273f0..63f3676 100644 --- a/merchant_ops/services/authz.py +++ b/merchant_ops/services/authz.py @@ -2,7 +2,8 @@ from rest_framework.exceptions import PermissionDenied, NotFound 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.services.bootstrap import ensure_merchant_roles @@ -31,6 +32,24 @@ def get_active_staff_member(user): ).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): """角色权限 + 成员覆盖。""" perms = set( @@ -48,6 +67,14 @@ def member_has_perm(member, perm_code): 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): if not member_has_perm(member, perm_code): raise StaffAuthError(403, f'无权限: {perm_code}') diff --git a/merchant_ops/services/order_query.py b/merchant_ops/services/order_query.py index edd7e50..b996c15 100644 --- a/merchant_ops/services/order_query.py +++ b/merchant_ops/services/order_query.py @@ -1,7 +1,7 @@ """子客服订单查询:按权限隔离,不返回商家全量(除非 order_view_all)。""" 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.services.authz import member_has_perm from orders.models import MerchantOrderExt @@ -43,7 +43,7 @@ def staff_apply_order_filters(qs, data): def staff_order_to_dict(ext): o = ext.Order - ct = o.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if o.CreateTime else '' + ct = fmt_datetime(o.CreateTime) return { 'dingdan_id': o.OrderID, 'zhuangtai': o.Status, diff --git a/merchant_ops/views_orders.py b/merchant_ops/views_orders.py index 1cf3742..b148f2a 100644 --- a/merchant_ops/views_orders.py +++ b/merchant_ops/views_orders.py @@ -8,7 +8,9 @@ from rest_framework.views import APIView from backend.utils import pick_order_id 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 ( staff_apply_order_filters, staff_can_view_all_orders, @@ -29,10 +31,7 @@ logger = logging.getLogger(__name__) def _require_staff(request): - member = get_active_staff_member(request.user) - if not member: - raise StaffAuthError(403, '非有效子客服') - return member + return require_active_staff_member(request.user) def _err(e): @@ -138,9 +137,10 @@ class StaffProductTypesView(APIView): def post(self, request): try: member = _require_staff(request) - if not member_has_perm(member, 'order_dispatch'): - raise StaffAuthError(403, '无权限') - return proxy_merchant_order_view(request, member, ShangpinLeixingView, 'order_dispatch') + perm = staff_order_read_perm(member) + if not perm: + raise StaffAuthError(403, '无权限查看商品类型') + return proxy_merchant_order_view(request, member, ShangpinLeixingView, perm) except Exception as e: return _err(e) diff --git a/merchant_ops/views_templates.py b/merchant_ops/views_templates.py index bb104ed..4759532 100644 --- a/merchant_ops/views_templates.py +++ b/merchant_ops/views_templates.py @@ -10,7 +10,9 @@ from config.views import ( ShangjiaShanchuMobanView, ) 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.link import staff_generate_link, staff_list_links from merchant_ops.services.audit import audit_staff @@ -18,15 +20,12 @@ from merchant_ops.views_manage import _err def _require_staff(request): - member = get_active_staff_member(request.user) - if not member: - raise StaffAuthError(403, '非有效子客服') - return member + return require_active_staff_member(request.user) 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): diff --git a/orders/views.py b/orders/views.py index c4595f7..0f7626b 100644 --- a/orders/views.py +++ b/orders/views.py @@ -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 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.utils import check_dashou_biaoqian_required @@ -1097,7 +1097,8 @@ class DingdanHuoquView(APIView): 'jieshao': order.Description or '', 'beizhu': order.Remark 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) @@ -1759,12 +1760,12 @@ class ShangpinLeixingView(APIView): if getattr(request, '_staff_member', None) is not None: return True 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): return True 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): @@ -2140,7 +2141,7 @@ class ShangjiaDingdanHuoquView(APIView): 'leixing_id': order.ProductTypeID, 'tupian': order.ImageURL 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) @@ -2249,6 +2250,8 @@ class ShangjiaDingdanXiangqingView(APIView): ).first() fadan_dict = None if fadan_obj: + ct = fmt_datetime(fadan_obj.CreateTime) + ut = fmt_datetime(fadan_obj.UpdateTime) fadan_dict = { 'id': fadan_obj.id, 'chufaliyou': fadan_obj.Reason or '', @@ -2258,8 +2261,10 @@ class ShangjiaDingdanXiangqingView(APIView): 'shensuliyou': fadan_obj.AppealReason or '', 'tongyi_chufaliyou': fadan_obj.ApproveReason or '', 'bohuiliyou': fadan_obj.RejectReason or '', - 'CreateTime': fadan_obj.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.CreateTime else '', - 'UpdateTime': fadan_obj.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.UpdateTime else '', + 'CreateTime': ct, + 'create_time': ct, + 'UpdateTime': ut, + 'update_time': ut, } # 7. 商家自己的分红利率(用于罚款分红计算) @@ -2297,7 +2302,7 @@ class ShangjiaDingdanXiangqingView(APIView): 'jieshao': order.Description or '', 'beizhu': order.Remark 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, @@ -3174,7 +3179,8 @@ class DashouDingdanHuoquView(APIView): 'yajin': float(order['CommissionReq']) if order['CommissionReq'] else 0.0, 'jieshao': order['Description'] 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 '', 'zhiding_uid': order['AssignedID'] or '', 'zhiding_avatar': info.get('avatar', ''), @@ -3792,7 +3798,7 @@ class DashouDingdanHuoquView1(APIView): 'jieshao': order.Description or '', 'tupian': order.ImageURL 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) @@ -4324,7 +4330,7 @@ class DashouDingdanXiangqingView(APIView): 'tupian': dingdan.ImageURL or '', 'nicheng': dingdan.Nickname 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 '', 'dashou_liuyan': dingdan.PlayerRemark or '', 'fuwudashou_id': dingdan.PlayerID or '', @@ -4496,10 +4502,10 @@ class AdGetDingdanXiangQing(APIView): 'dashou_liuyan': dingdan_obj.PlayerRemark or '', # 时间字段 - 'chuangjianshijian': dingdan_obj.CreateTime.strftime( - '%Y-%m-%d %H:%M:%S') if dingdan_obj.CreateTime else '', - 'genggaishijian': dingdan_obj.UpdateTime.strftime( - '%Y-%m-%d %H:%M:%S') if dingdan_obj.UpdateTime else '', + 'chuangjianshijian': fmt_datetime(dingdan_obj.CreateTime), + 'create_time': fmt_datetime(dingdan_obj.CreateTime), + 'genggaishijian': fmt_datetime(dingdan_obj.UpdateTime), + 'update_time': fmt_datetime(dingdan_obj.UpdateTime), } # 8. 根据发单平台查询扩展表数据