fix(gerp): role_scope负责人/组长判定改读关联表RoleType,支持多部门作用域与公司过滤

- 移除UserRole遗留字段(RoleType/DeptUUID/WorkgroupUUID)相关隐藏与防写入规则
- 同步UserRole/UserRoleDepartment列展示优化
This commit is contained in:
TermiNexus committed 2026-08-28 15:34:28 +08:00
1 parent 741bfb7740
commit 058fd11fb0
3 files changed
+74 -67

No files matched your search

+14 -8
View File
@@ -154,7 +154,7 @@ class FormService(FormServiceBase):
}
# 列表页表格隐藏主键 UUID 列的表(UUID 无业务展示意义,仅前端隐藏,后端仍返回)
HIDE_PK_UUID_IN_TABLE = {'Company', 'Department', 'Workgroup'}
HIDE_PK_UUID_IN_TABLE = {'Company', 'Department', 'Workgroup', 'UserRoleDepartment', 'UserRole'}
def __init__(self, user, current_company_uuid=None, admin_verify=False, current_role_uuid=None, current_tenant_uuid=None, ip_address='', role_scope=None):
super().__init__(user=user)
@@ -726,9 +726,18 @@ class FormService(FormServiceBase):
col['type'] = 'readonly'
col['editable'] = False
col['hideInForm'] = True
# UserRole 遗留字段(DeptUUID/WorkgroupUUID):部门/工作组归属已迁移到
# UserRoleDepartment/UserRoleWorkgroup 关联表,表单+列表均隐藏,禁止再次写入
if table_name == 'UserRole' and pascal_name in ('DeptUUID', 'WorkgroupUUID'):
# UserRole 列表可读性:UserUUID/RoleUUID 值已由 _enrich_entity_labels 回显为
# 用户名/角色名,列标题直接改"用户名"/"角色名"(避免误导为裸 UUID);
# CompanyUUID 为隔离冗余列,角色成员列表均按公司隔离展示,列表隐藏
if table_name == 'UserRole' and pascal_name == 'UserUUID':
col['title'] = '用户名'
if table_name == 'UserRole' and pascal_name == 'RoleUUID':
col['title'] = '角色名'
if table_name == 'UserRole' and pascal_name == 'CompanyUUID':
col['visible'] = False
# UserRoleDepartment 关联表冗余字段(DeptUUID/TenantUUID/CompanyUUID):
# 仅用于租户/公司隔离过滤与详情页上下文注入,列表+表单均隐藏(详情页由 query 注入)
if table_name == 'UserRoleDepartment' and pascal_name in ('DeptUUID', 'TenantUUID', 'CompanyUUID'):
col['editable'] = False
col['hideInForm'] = True
col['visible'] = False
@@ -868,7 +877,7 @@ class FormService(FormServiceBase):
# 系统级表(Menu/Permission)豁免隔离
field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')}
# 租户管理(Tenant 表)仅租户管理员/跨租户管理员可读;普通用户无权限(菜单已隐藏,此处数据兜底)
if table_name == 'Tenant' and not self._is_admin():
if table_name == 'Tenant' and not self._is_admin(self.user):
return {'records': [], 'total': 0, 'page': 1, 'PageSize': 20}
if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES:
# 公司隔离:有 CompanyUUID 字段的表按当前公司过滤
@@ -1464,9 +1473,6 @@ class FormService(FormServiceBase):
_valid_values = [opt['value'] for opt in _opt_map[resolved]]
if val not in _valid_values:
return {'error': f"字段 '{resolved}' 值非法:只允许 {_valid_values}", 'field': resolved}
# 遗留字段防写入:UserRole 的 DeptUUID/WorkgroupUUID 已迁移到关联表,禁止再改
if table_name == 'UserRole' and resolved in ('DeptUUID', 'WorkgroupUUID'):
continue
# 订单受保护字段:状态机/审计字段禁止通用修改(状态流转必须走专用端点)
if table_name == 'Order' and resolved in self.ORDER_PROTECTED_FIELDS:
continue
+54 -55
View File
@@ -3,14 +3,17 @@
作用域类型:
admin —— 管理员(当前角色拥有 admin:super 或 admin:tenant 权限),看全公司
dept_manager —— 部门负责人(UserRoleDepartment 关联 + RoleType=1),看本部门成员
workgroup_lead —— 工作组长(UserRoleWorkgroup 关联 + RoleType=1),看本工作组成员
dept_manager —— 部门负责人(UserRoleDepartment 关联行 RoleType=1),看本部门成员
workgroup_lead —— 工作组长(UserRoleWorkgroup 关联行 RoleType=1),看本工作组成员
member —— 普通组员,只看自己
负责人/组长判定一律以关联表 RoleType=1 为准(UserRole.RoleType / DeptUUID /
WorkgroupUUID 遗留字段已废弃删除,见 gvsdsdk 0002 迁移)。
用法:
scope = GetUserScope(request)
if scope.Type == 'dept_manager':
qs = qs.filter(DeptUUID=scope.DeptUUID)
qs = qs.filter(DeptUUID__in=scope.DeptUUIDs)
"""
import logging
import uuid as _uuid
@@ -21,10 +24,10 @@ logger = logging.getLogger(__name__)
class RoleScope(NamedTuple):
Type: str # admin / dept_manager / workgroup_lead / member
DeptUUID: Optional[bytes] # 部门 UUID(dept_manager 时非空)
WorkgroupUUID: Optional[bytes] # 工作组 UUID(workgroup_lead 时非空)
DeptUUIDs: Optional[tuple] # 部门 UUID 列表(dept_manager 时非空,可多部门)
WorkgroupUUIDs: Optional[tuple] # 工作组 UUID 列表(workgroup_lead 时非空)
UserUUID: Optional[bytes] # 当前用户 UUID(member 时用于过滤)
RoleType: int # 当前角色类型
RoleType: int # 0=成员 1=负责人/组长(以关联表 RoleType 为准)
def _to_bytes(value):
@@ -56,9 +59,12 @@ def GetUserScope(request) -> RoleScope:
优先级:
1. 管理员(IsAdmin=true)→ admin
2. 当前 UserRole 的 RoleType=1 且 DeptUUID 非空 → dept_manager
3. 当前 UserRole 的 RoleType=1 且 WorkgroupUUID 非空 → workgroup_lead
2. 当前 (用户, 角色) 关联在 UserRoleDepartment 有 RoleType=1 的行 → dept_manager
3. 当前 (用户, 角色) 关联在 UserRoleWorkgroup 有 RoleType=1 的行 → workgroup_lead
4. 其他 → member
负责人/组长判定以关联表 RoleType 为准(UserRole.RoleType / DeptUUID /
WorkgroupUUID 遗留字段已废弃删除),并按当前公司过滤(跨公司同名对不串)。
"""
user = getattr(request, 'user', None) if request else None
user_uuid = getattr(user, 'UserUUID', None) if user else None
@@ -68,13 +74,13 @@ def GetUserScope(request) -> RoleScope:
if IsAdmin(request):
return RoleScope(
Type='admin',
DeptUUID=None,
WorkgroupUUID=None,
DeptUUIDs=None,
WorkgroupUUIDs=None,
UserUUID=user_uuid_bytes,
RoleType=1,
)
# 获取当前角色关联的 UserRole 记录(含 DeptUUID/WorkgroupUUID/RoleType)
# 获取当前角色关联的 UserRole 记录(RoleType 等遗留字段已废弃,仅取关联 UUID)
current_role = getattr(request, 'current_role', None)
role_uuid = getattr(current_role, 'RoleUUID', None) if current_role else None
current_company = getattr(request, 'current_company_uuid', None)
@@ -90,8 +96,8 @@ def GetUserScope(request) -> RoleScope:
if not role_uuid or not user_uuid_bytes:
return RoleScope(
Type='member',
DeptUUID=None,
WorkgroupUUID=None,
DeptUUIDs=None,
WorkgroupUUIDs=None,
UserUUID=user_uuid_bytes,
RoleType=0,
)
@@ -105,60 +111,46 @@ def GetUserScope(request) -> RoleScope:
if not ur:
return RoleScope(
Type='member',
DeptUUID=None,
WorkgroupUUID=None,
DeptUUIDs=None,
WorkgroupUUIDs=None,
UserUUID=user_uuid_bytes,
RoleType=0,
)
role_type = ur.RoleType or 0
ur_uuid_bytes = _to_bytes(ur.UserRoleUUID)
# 部门/工作组归属以关联表为准(UserRole.DeptUUID/WorkgroupUUID 已废弃清空):
# 部门负责人/工作组长通过 UserRoleDepartment / UserRoleWorkgroup 关联行判定
dept_uuid = None
workgroup_uuid = None
if role_type == 1 and ur_uuid_bytes:
try:
urd = RoleManager.GetUserRoleDepartment(ur_uuid_bytes)
if urd:
dept_uuid = _to_bytes(urd.DeptUUID)
except Exception:
dept_uuid = None
if not dept_uuid:
try:
urw = RoleManager.GetUserRoleWorkgroup(ur_uuid_bytes)
if urw:
workgroup_uuid = _to_bytes(urw.WorkgroupUUID)
except Exception:
workgroup_uuid = None
# 2. 部门负责人
if role_type == 1 and dept_uuid:
# 2. 部门负责人:关联表 UserRoleDepartment 中 RoleType=1 的行(按当前公司过滤)
manager_depts = RoleManager.GetUserRoleDepartments(
ur_uuid_bytes, CompanyUUID=company_uuid_bytes, RoleType=1,
)
if manager_depts:
return RoleScope(
Type='dept_manager',
DeptUUID=dept_uuid,
WorkgroupUUID=None,
DeptUUIDs=tuple(d for d in (_to_bytes(x.DeptUUID) for x in manager_depts) if d),
WorkgroupUUIDs=None,
UserUUID=user_uuid_bytes,
RoleType=role_type,
RoleType=1,
)
# 3. 工作组长
if role_type == 1 and workgroup_uuid:
# 3. 工作组长:关联表 UserRoleWorkgroup 中 RoleType=1 的行(按当前公司过滤)
manager_wgs = RoleManager.GetUserRoleWorkgroups(
ur_uuid_bytes, CompanyUUID=company_uuid_bytes, RoleType=1,
)
if manager_wgs:
return RoleScope(
Type='workgroup_lead',
DeptUUID=None,
WorkgroupUUID=workgroup_uuid,
DeptUUIDs=None,
WorkgroupUUIDs=tuple(w for w in (_to_bytes(x.WorkgroupUUID) for x in manager_wgs) if w),
UserUUID=user_uuid_bytes,
RoleType=role_type,
RoleType=1,
)
# 4. 普通组员
return RoleScope(
Type='member',
DeptUUID=None,
WorkgroupUUID=None,
DeptUUIDs=None,
WorkgroupUUIDs=None,
UserUUID=user_uuid_bytes,
RoleType=role_type,
RoleType=0,
)
@@ -245,12 +237,19 @@ def ApplyScopeToQueryset(qs, scope: RoleScope, table_name: str):
def _get_scope_members(scope: RoleScope):
"""获取当前作用域下的成员 UUID 列表(委托 gvsdsdk RoleManager.GetScopeMembers)
dept_manager: 查询 UserRoleDepartment 中 DeptUUID = scope.DeptUUID 的用户
workgroup_lead: 查询 UserRoleWorkgroup 中 WorkgroupUUID = scope.WorkgroupUUID 的用户
dept_manager: 查询 UserRoleDepartment 中 DeptUUID ∈ scope.DeptUUIDs 的用户(多部门并集)
workgroup_lead: 查询 UserRoleWorkgroup 中 WorkgroupUUID ∈ scope.WorkgroupUUIDs 的用户
"""
from gvsdsdk.auth.managers import RoleManager
if scope.Type == 'dept_manager' and scope.DeptUUID:
return RoleManager.GetScopeMembers('dept_manager', scope.DeptUUID)
if scope.Type == 'workgroup_lead' and scope.WorkgroupUUID:
return RoleManager.GetScopeMembers('workgroup_lead', scope.WorkgroupUUID)
return None
member_uuids = set()
if scope.Type == 'dept_manager' and scope.DeptUUIDs:
for dept_uuid in scope.DeptUUIDs:
members = RoleManager.GetScopeMembers('dept_manager', dept_uuid)
if members:
member_uuids.update(members)
elif scope.Type == 'workgroup_lead' and scope.WorkgroupUUIDs:
for wg_uuid in scope.WorkgroupUUIDs:
members = RoleManager.GetScopeMembers('workgroup_lead', wg_uuid)
if members:
member_uuids.update(members)
return list(member_uuids) if scope.Type in ('dept_manager', 'workgroup_lead') else None
+6 -4
View File
@@ -418,10 +418,12 @@ class CheckAdminView(ELTAuthenticatedAPIView):
scope = GetUserScope(request)
scope_type = scope.Type
scope_label = self.SCOPE_TYPE_LABEL.get(scope_type, '普通组员')
if scope.DeptUUID:
dept_uuid_str = UUIDObj(scope.DeptUUID).str
if scope.WorkgroupUUID:
workgroup_uuid_str = UUIDObj(scope.WorkgroupUUID).str
# 负责人/组长可挂多个部门/工作组:多个 UUID 用逗号拼接
# (前端当前仅消费 Type/IsDeptManager/IsWorkgroupLead,列表值供扩展)
if scope.DeptUUIDs:
dept_uuid_str = ','.join(UUIDObj(d).str for d in scope.DeptUUIDs if d)
if scope.WorkgroupUUIDs:
workgroup_uuid_str = ','.join(UUIDObj(w).str for w in scope.WorkgroupUUIDs if w)
except Exception:
pass