fix: 完整审查修复 - 5个问题
1. _resolve_identity: permissions_for 未归一化 role → 使用 account_type+permission_role 2. rbac_org.py: 用 user.role 判断 operator → 改用 capabilities 3. membership_service: sync_user_affiliation 不同步 organization_members → 新增 _sync_org_members 4. repositories.to_profile: capabilities 只查旧表 → 增加 organization_members 查询 5. Header.RoleBadge: account_type_label 被 t() 重复翻译 → 优先直接显示中文
This commit is contained in:
@@ -154,9 +154,10 @@ async def _resolve_identity(user: dict, db: Database, payload: dict | None = Non
|
||||
capabilities 优先从 JWT payload 读取(登录时已计算并写入),避免每次请求查库;
|
||||
若 JWT 中无 capabilities(旧 token),则回退到实时计算。
|
||||
"""
|
||||
user["permissions"] = await db.roles.permissions_for(
|
||||
user.get("role", "opc_member"), None,
|
||||
)
|
||||
from ..domain.account_types import account_type, permission_role
|
||||
atype = account_type(user.get("role"))
|
||||
user["role"] = atype # 归一化为三种账号类型之一
|
||||
user["permissions"] = await db.roles.permissions_for(permission_role(atype), None)
|
||||
user["scope_region_ids"] = await db.regions.visible_region_ids(user.get("region_id"))
|
||||
user["scope_level"] = await db.regions.level(user.get("region_id"))
|
||||
if payload and payload.get("capabilities"):
|
||||
|
||||
@@ -53,7 +53,7 @@ async def org_members(
|
||||
user: dict = Depends(require_roles("carrier", "operator")),
|
||||
):
|
||||
# 机构管理员本人 或 平台运营 可查看
|
||||
if user.get("role") not in ("operator",) and not await db.org_members.is_admin(org_id, user["id"]):
|
||||
if "operator" not in (user.get("capabilities") or []) and not await db.org_members.is_admin(org_id, user["id"]):
|
||||
raise HTTPException(status_code=403, detail="Forbidden: not org admin")
|
||||
return {"items": await db.org_members.list_for_org(org_id)}
|
||||
|
||||
@@ -65,7 +65,7 @@ async def org_add_member(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_roles("carrier", "operator")),
|
||||
):
|
||||
if user.get("role") not in ("operator",) and not await db.org_members.is_admin(org_id, user["id"]):
|
||||
if "operator" not in (user.get("capabilities") or []) and not await db.org_members.is_admin(org_id, user["id"]):
|
||||
raise HTTPException(status_code=403, detail="Forbidden: not org admin")
|
||||
target_id = req.user_id
|
||||
if not target_id and req.username:
|
||||
|
||||
@@ -680,12 +680,43 @@ class UserRepository:
|
||||
pass
|
||||
profile["company_admin_ids"] = company_admin_ids
|
||||
caps = ["opc_member"]
|
||||
if _acct_type(user.get("role")) == "operator":
|
||||
atype = _acct_type(user.get("role"))
|
||||
if atype == "operator":
|
||||
caps.append("operator")
|
||||
if user.get("role") in ("carrier", "park", "park_staff", "carrier_staff") or is_park_admin:
|
||||
# carrier 能力:role 为 carrier 或 统一表 organization_members 管理员 或 旧表管理员
|
||||
is_carrier = atype == "carrier"
|
||||
if not is_carrier:
|
||||
from .models import Organization, OrganizationMember
|
||||
carrier_orgs = (await self.session.scalars(
|
||||
select(Organization.id).join(
|
||||
OrganizationMember, OrganizationMember.org_id == Organization.id
|
||||
).where(
|
||||
OrganizationMember.user_id == user.get("id", ""),
|
||||
OrganizationMember.role == "admin",
|
||||
OrganizationMember.status == "active",
|
||||
Organization.type == "carrier",
|
||||
)
|
||||
)).all()
|
||||
is_carrier = bool(carrier_orgs) or is_park_admin
|
||||
if is_carrier:
|
||||
caps.append("carrier")
|
||||
# enterprise 能力:统一表 organization_members 管理员 或 旧表管理员
|
||||
if company_admin_ids:
|
||||
caps.append("enterprise")
|
||||
else:
|
||||
from .models import Organization, OrganizationMember
|
||||
ent_orgs = (await self.session.scalars(
|
||||
select(Organization.id).join(
|
||||
OrganizationMember, OrganizationMember.org_id == Organization.id
|
||||
).where(
|
||||
OrganizationMember.user_id == user.get("id", ""),
|
||||
OrganizationMember.role == "admin",
|
||||
OrganizationMember.status == "active",
|
||||
Organization.type == "enterprise",
|
||||
)
|
||||
)).all()
|
||||
if ent_orgs:
|
||||
caps.append("enterprise")
|
||||
profile["capabilities"] = caps
|
||||
profile["permissions"] = user.get("permissions", [])
|
||||
profile["scope_region_ids"] = user.get("scope_region_ids", [])
|
||||
|
||||
@@ -2,18 +2,77 @@
|
||||
"""成员关系服务:派生归属同步 + 转园迁移执行(users 单值归属的唯一合法写路径)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from ..infrastructure.models import ParkCompany, ParkTenant
|
||||
from ..infrastructure.models import Organization, ParkCompany, ParkTenant
|
||||
from ..infrastructure.repositories import Database, utcnow_iso
|
||||
|
||||
|
||||
async def _ensure_org(db: Database, org_id: str, org_type: str, name: str = "") -> Organization:
|
||||
"""确保 organization 记录存在,不存在则创建。"""
|
||||
from sqlalchemy import select
|
||||
|
||||
org = await db.session.get(Organization, org_id)
|
||||
if org is None:
|
||||
org = Organization(
|
||||
id=org_id, name=name or org_id, type=org_type,
|
||||
region_id=None, parent_id=None, created_at=utcnow_iso(),
|
||||
)
|
||||
db.session.add(org)
|
||||
await db.session.flush()
|
||||
return org
|
||||
|
||||
|
||||
async def _sync_org_members(db: Database, user_id: str) -> None:
|
||||
"""把 park_members.admin / company_members.is_admin 同步到 organization_members。
|
||||
|
||||
统一成员关系表是权限计算的首选来源,必须与旧表保持一致。
|
||||
"""
|
||||
from sqlalchemy import select
|
||||
from ..infrastructure.models import OrganizationMember
|
||||
|
||||
now = utcnow_iso()
|
||||
|
||||
# 同步园区管理员 → organization_members (type=carrier)
|
||||
parks = await db.park_members.list_by_user(user_id)
|
||||
for pm in parks:
|
||||
if pm.get("member_type") != "admin" or pm.get("status") != "active":
|
||||
continue
|
||||
tenant = await db.session.scalar(select(ParkTenant).where(ParkTenant.id == pm["park_id"]))
|
||||
await _ensure_org(db, pm["park_id"], "carrier", tenant.name if tenant else "")
|
||||
existing = await db.session.get(OrganizationMember, (pm["park_id"], user_id))
|
||||
if existing is None:
|
||||
db.session.add(OrganizationMember(
|
||||
org_id=pm["park_id"], user_id=user_id,
|
||||
role="admin", is_admin=True, status="active", joined_at=now,
|
||||
))
|
||||
|
||||
# 同步企业管理员 → organization_members (type=enterprise)
|
||||
comps = await db.company_members.list_by_user(user_id)
|
||||
for cm in comps:
|
||||
if not cm.get("is_admin") or cm.get("status") != "active":
|
||||
continue
|
||||
comp = await db.session.scalar(select(ParkCompany).where(ParkCompany.id == cm["company_id"]))
|
||||
await _ensure_org(db, cm["company_id"], "enterprise", comp.name if comp else "")
|
||||
existing = await db.session.get(OrganizationMember, (cm["company_id"], user_id))
|
||||
if existing is None:
|
||||
db.session.add(OrganizationMember(
|
||||
org_id=cm["company_id"], user_id=user_id,
|
||||
role="admin", is_admin=True, status="active", joined_at=now,
|
||||
))
|
||||
|
||||
await db.session.flush()
|
||||
|
||||
|
||||
async def sync_user_affiliation(db: Database, user_id: str) -> None:
|
||||
"""重算 users.affiliation / park_id / park_name(派生展示缓存)。
|
||||
"""重算 users.affiliation / park_id / park_name(派生展示缓存),并同步 organization_members。
|
||||
|
||||
主归属规则:member_type=admin 优先,其次最早创建的 active 园区成员;
|
||||
无任何园区成员 → independent。企业展示字段取 is_admin 企业名(否则最早一条)。
|
||||
"""
|
||||
from sqlalchemy import select
|
||||
|
||||
# 先同步统一成员关系表
|
||||
await _sync_org_members(db, user_id)
|
||||
|
||||
parks = await db.park_members.list_by_user(user_id)
|
||||
fields: dict = {}
|
||||
if parks:
|
||||
|
||||
Reference in New Issue
Block a user