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:
Pine
2026-09-03 09:43:34 +08:00
parent 5b229f3de8
commit cb1492f2d6
4 changed files with 100 additions and 9 deletions
+4 -3
View File
@@ -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"):
+2 -2
View File
@@ -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:
+33 -2
View File
@@ -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", [])
+61 -2
View File
@@ -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: