forked from erp-dev/erp
feat: added frontend_pages models(basic_info) and api(v2), support employees query pages that they can visit by themself token
This commit is contained in:
130
api_v2/tests.py
130
api_v2/tests.py
@@ -1386,3 +1386,133 @@ class PlateOrderBatchUpdateV2APITest(TestCase):
|
||||
self.assertEqual(resp2.status_code, 200)
|
||||
self.po1.refresh_from_db()
|
||||
self.assertTrue(self.po1.is_invalid)
|
||||
|
||||
|
||||
class MyVisiblePagesAPITest(TestCase):
|
||||
"""测试获取当前用户可见页面 API"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户',
|
||||
type=basic_models.MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
self.user = get_user_model().objects.create_user(username='testuser', password='pass12345')
|
||||
|
||||
# 创建前端页面
|
||||
self.main_menu = basic_models.FrontendPage.objects.create(
|
||||
key='production',
|
||||
label='生产管理',
|
||||
page_type=basic_models.FrontendPageTypeEnum.MAIN,
|
||||
sort_order=100
|
||||
)
|
||||
self.sub_menu1 = basic_models.FrontendPage.objects.create(
|
||||
key='production-kanban',
|
||||
label='生产看板',
|
||||
page_type=basic_models.FrontendPageTypeEnum.SUB,
|
||||
parent=self.main_menu,
|
||||
sort_order=101
|
||||
)
|
||||
self.sub_menu2 = basic_models.FrontendPage.objects.create(
|
||||
key='printing-order-list',
|
||||
label='生产订单',
|
||||
page_type=basic_models.FrontendPageTypeEnum.SUB,
|
||||
parent=self.main_menu,
|
||||
sort_order=102
|
||||
)
|
||||
self.other_menu = basic_models.FrontendPage.objects.create(
|
||||
key='inventory',
|
||||
label='库存管理',
|
||||
page_type=basic_models.FrontendPageTypeEnum.MAIN,
|
||||
sort_order=200
|
||||
)
|
||||
|
||||
# 创建岗位并分配页面
|
||||
self.emp_type = basic_models.EmployeeType.objects.create(
|
||||
merchant=self.merchant,
|
||||
title='生产主管'
|
||||
)
|
||||
self.emp_type.visible_pages.add(self.main_menu)
|
||||
|
||||
# 创建员工并关联用户
|
||||
self.employee = basic_models.Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='张三',
|
||||
position=self.emp_type,
|
||||
sys_user=self.user # 关联到用户
|
||||
)
|
||||
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def test_get_visible_pages_success(self):
|
||||
"""测试成功获取当前用户可见页面"""
|
||||
response = self.client.get('/api/v2/me/visible-pages/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.data
|
||||
self.assertEqual(data['employee_id'], self.employee.id)
|
||||
self.assertEqual(data['employee_name'], '张三')
|
||||
self.assertEqual(data['position_id'], self.emp_type.id)
|
||||
self.assertEqual(data['position_title'], '生产主管')
|
||||
|
||||
# 验证可见页面(主菜单 + 子菜单)
|
||||
visible_keys = data['visible_keys']
|
||||
self.assertIn('production', visible_keys)
|
||||
self.assertIn('production-kanban', visible_keys)
|
||||
self.assertIn('printing-order-list', visible_keys)
|
||||
self.assertNotIn('inventory', visible_keys)
|
||||
|
||||
# 验证页面详情
|
||||
self.assertEqual(len(data['visible_pages']), 3)
|
||||
|
||||
def test_get_visible_pages_with_sub_menu_only(self):
|
||||
"""测试仅授予子菜单时只返回该子菜单"""
|
||||
# 重新配置岗位:只授予子菜单
|
||||
self.emp_type.visible_pages.clear()
|
||||
self.emp_type.visible_pages.add(self.sub_menu1)
|
||||
|
||||
response = self.client.get('/api/v2/me/visible-pages/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
visible_keys = response.data['visible_keys']
|
||||
self.assertIn('production-kanban', visible_keys)
|
||||
self.assertNotIn('production', visible_keys)
|
||||
self.assertNotIn('printing-order-list', visible_keys)
|
||||
self.assertEqual(len(visible_keys), 1)
|
||||
|
||||
def test_get_visible_pages_employee_no_position(self):
|
||||
"""测试没有岗位的员工返回空列表"""
|
||||
# 移除员工岗位
|
||||
self.employee.position = None
|
||||
self.employee.save()
|
||||
|
||||
response = self.client.get('/api/v2/me/visible-pages/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.data
|
||||
self.assertEqual(data['employee_id'], self.employee.id)
|
||||
self.assertEqual(data['position_id'], None)
|
||||
self.assertEqual(data['visible_pages'], [])
|
||||
self.assertEqual(data['visible_keys'], [])
|
||||
|
||||
def test_get_visible_pages_user_no_employee(self):
|
||||
"""测试用户未关联员工时返回空列表"""
|
||||
# 创建一个没有关联员工的用户
|
||||
user_no_employee = get_user_model().objects.create_user(username='noemployee', password='pass12345')
|
||||
self.client.force_authenticate(user=user_no_employee)
|
||||
|
||||
response = self.client.get('/api/v2/me/visible-pages/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.data
|
||||
self.assertEqual(data['employee_id'], None)
|
||||
self.assertEqual(data['employee_name'], None)
|
||||
self.assertEqual(data['position_id'], None)
|
||||
self.assertEqual(data['visible_pages'], [])
|
||||
self.assertEqual(data['visible_keys'], [])
|
||||
|
||||
def test_get_visible_pages_unauthenticated(self):
|
||||
"""测试未登录时返回 401"""
|
||||
self.client.logout()
|
||||
response = self.client.get('/api/v2/me/visible-pages/')
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
@@ -13,13 +13,14 @@ from api_v2.views import (
|
||||
PlateOrderBatchUpdateView,
|
||||
BusinessObjectCloneView,
|
||||
)
|
||||
from api_v2.views.basic_info import CustomerEmployeeBindingView
|
||||
from api_v2.views.basic_info import CustomerEmployeeBindingView, MyVisiblePagesView
|
||||
|
||||
urlpatterns = [
|
||||
path('health/', HealthCheckView.as_view(), name='api_v2_health_check'),
|
||||
path('roles/', RoleListView.as_view(), name='api_v2_role_list'),
|
||||
path('users/quick-create/', QuickCreateEmployeeUserView.as_view(), name='api_v2_user_quick_create'),
|
||||
path('customers/bind-employee/', CustomerEmployeeBindingView.as_view(), name='api_v2_customer_bind_employee'),
|
||||
path('me/visible-pages/', MyVisiblePagesView.as_view(), name='api_v2_my_visible_pages'),
|
||||
path('printing-jobs/by-customer/', PrintingJobByCustomerView.as_view(), name='api_v2_printing_job_by_customer'),
|
||||
path('printing-jobs/batch-advance/preview/', PrintingJobBatchAdvancePreviewView.as_view(), name='api_v2_printing_job_batch_advance_preview'),
|
||||
path('printing-jobs/batch-advance/', PrintingJobBatchAdvanceSubmitView.as_view(), name='api_v2_printing_job_batch_advance_submit'),
|
||||
|
||||
@@ -4,6 +4,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from basic_info.models import FrontendPage, FrontendPageTypeEnum
|
||||
|
||||
|
||||
class CustomerEmployeeBindingSerializer(serializers.Serializer):
|
||||
@@ -84,3 +85,97 @@ class CustomerEmployeeBindingView(APIView):
|
||||
},
|
||||
status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
|
||||
class FrontendPageSerializer(serializers.ModelSerializer):
|
||||
"""前端页面序列化器"""
|
||||
parent_key = serializers.CharField(source='parent.key', read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = FrontendPage
|
||||
fields = ['key', 'label', 'page_type', 'parent_key', 'sort_order']
|
||||
|
||||
|
||||
class MyVisiblePagesView(APIView):
|
||||
"""
|
||||
获取当前登录用户的可见页面 API
|
||||
|
||||
根据当前登录用户关联的员工岗位,返回其可见的前端页面列表。
|
||||
授予主菜单会自动包含其所有子菜单。
|
||||
|
||||
GET /api/v2/me/visible-pages/
|
||||
|
||||
响应示例:
|
||||
{
|
||||
"employee_id": 1,
|
||||
"employee_name": "张三",
|
||||
"position_id": 2,
|
||||
"position_title": "生产主管",
|
||||
"visible_pages": [
|
||||
{"key": "production", "label": "生产管理", "page_type": "main", "parent_key": null, "sort_order": 100},
|
||||
{"key": "production-kanban", "label": "生产看板", "page_type": "sub", "parent_key": "production", "sort_order": 101}
|
||||
],
|
||||
"visible_keys": ["production", "production-kanban", "printing-order-list"]
|
||||
}
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
# 从 request.user 获取关联的员工
|
||||
user = request.user
|
||||
|
||||
# 检查用户是否关联员工
|
||||
if not hasattr(user, 'employee'):
|
||||
return Response({
|
||||
'employee_id': None,
|
||||
'employee_name': None,
|
||||
'position_id': None,
|
||||
'position_title': None,
|
||||
'visible_pages': [],
|
||||
'visible_keys': [],
|
||||
})
|
||||
|
||||
employee = user.employee
|
||||
|
||||
# 检查员工是否有岗位
|
||||
if not employee.position:
|
||||
return Response({
|
||||
'employee_id': employee.id,
|
||||
'employee_name': employee.name,
|
||||
'position_id': None,
|
||||
'position_title': None,
|
||||
'visible_pages': [],
|
||||
'visible_keys': [],
|
||||
})
|
||||
|
||||
# 获取岗位直接授予的页面
|
||||
granted_pages = employee.position.visible_pages.filter(is_active=True).select_related('parent')
|
||||
|
||||
# 计算所有可见页面(含子菜单继承)
|
||||
visible_pages = []
|
||||
visible_keys = set()
|
||||
|
||||
for page in granted_pages:
|
||||
visible_pages.append(page)
|
||||
visible_keys.add(page.key)
|
||||
|
||||
# 如果是主菜单,自动包含所有子菜单
|
||||
if page.page_type == FrontendPageTypeEnum.MAIN:
|
||||
children = page.children.filter(is_active=True).select_related('parent')
|
||||
for child in children:
|
||||
if child.key not in visible_keys:
|
||||
visible_pages.append(child)
|
||||
visible_keys.add(child.key)
|
||||
|
||||
# 按 sort_order 排序
|
||||
visible_pages.sort(key=lambda p: (p.sort_order, p.id))
|
||||
|
||||
return Response({
|
||||
'employee_id': employee.id,
|
||||
'employee_name': employee.name,
|
||||
'position_id': employee.position.id,
|
||||
'position_title': employee.position.title,
|
||||
'visible_pages': FrontendPageSerializer(visible_pages, many=True).data,
|
||||
'visible_keys': sorted(visible_keys),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user