fix(incubator): 完善昆明市大学生创业园权限矩阵

- resolve_identity: 子管理员(park_members.member_type=admin, active)纳入园区管理方
- 月报(create/update/submit/reopen): 仅企业管理员(is_company_admin)可操作
- list_company_members: 仅企业管理员可查看本企业成员(所有成员权限)
- 请假: list/get 企业管理员看本企业全部; review_leave 企业管理员可审批本企业请假
- 普通企业成员: 仅请假/预约共享区域(维持); 无权限账号一律拒绝
This commit is contained in:
Pine
2026-09-10 00:32:46 +08:00
parent 040f8737b3
commit c94d4b72d9
+40 -10
View File
@@ -29,6 +29,7 @@ from ..infrastructure.models import (
IncubatorSetting,
IncubatorSharedArea,
ParkCompany,
ParkMember,
ParkTenant,
User,
)
@@ -106,6 +107,19 @@ async def resolve_identity(db, user: dict) -> Identity:
return Identity(is_manager=True, tenant_id=tid, company_id="",
company_name="", role="carrier:admin", user_id=uid,
user_name=user_name)
# —— 子管理员:park_members.member_type=admin 且 active(园区绑定子管理员)——
sub_admin = (await db.session.execute(
select(ParkMember).where(
ParkMember.user_id == uid,
ParkMember.park_id == tid,
ParkMember.member_type == "admin",
ParkMember.status == "active",
)
)).scalar_one_or_none()
if sub_admin is not None:
return Identity(is_manager=True, tenant_id=tid, company_id="",
company_name="", role="carrier:sub_admin", user_id=uid,
user_name=user_name)
# 兜底:平台运营方(operator)账号可管理本园区(天然全权限)。
# 说明:本应用按“园区管理员”语义放行平台运营账号,便于运营方直接查看/
# 审核全园区申请与月报;若需收紧,仅保留 yunpeng 绑定即可删去该分支。
@@ -231,10 +245,12 @@ def _check_tenant_access(identity: Identity, tenant_id: str, company_id: str) ->
async def create_report(db, identity: Identity, data: dict) -> dict:
"""新建/保存草稿(提交方)。同一企业+月份已存在则返回既有记录。"""
"""新建/保存草稿(企业管理员)。同一企业+月份已存在则返回既有记录。"""
require_identity(identity)
if identity.is_manager:
raise PermissionError("管理方不填报报告")
if not identity.is_company_admin:
raise PermissionError("仅企业管理员可填报月报")
month = str(data.get("report_month", "")).strip()
if not month:
raise ValueError("report_month 必填(如 2026-08")
@@ -289,8 +305,10 @@ def _apply_report_fields(r: IncubatorMonthlyReport, data: dict) -> None:
async def update_report(db, identity: Identity, report_id: str, data: dict) -> dict:
"""更新报告(draft,或 submitted 未审核可撤回重改)。"""
"""更新报告(draft,或 submitted 未审核可撤回重改;仅企业管理员)。"""
require_identity(identity)
if not identity.is_company_admin:
raise PermissionError("仅企业管理员可编辑本企业月报")
r = await db.session.get(IncubatorMonthlyReport, report_id)
if r is None:
raise LookupError("报告不存在")
@@ -307,8 +325,10 @@ async def update_report(db, identity: Identity, report_id: str, data: dict) -> d
async def submit_report(db, identity: Identity, report_id: str) -> dict:
"""提交审核:draft → submitted。"""
"""提交审核:draft → submitted(仅企业管理员)"""
require_identity(identity)
if not identity.is_company_admin:
raise PermissionError("仅企业管理员可提交本企业月报")
r = await db.session.get(IncubatorMonthlyReport, report_id)
if r is None:
raise LookupError("报告不存在")
@@ -325,8 +345,10 @@ async def submit_report(db, identity: Identity, report_id: str) -> dict:
async def reopen_report(db, identity: Identity, report_id: str) -> dict:
"""撤回:submitted(未审核) → draft,允许重改。"""
"""撤回:submitted(未审核) → draft,允许重改(仅企业管理员)"""
require_identity(identity)
if not identity.is_company_admin:
raise PermissionError("仅企业管理员可撤回本企业月报")
r = await db.session.get(IncubatorMonthlyReport, report_id)
if r is None:
raise LookupError("报告不存在")
@@ -806,8 +828,10 @@ async def get_company_profile(db, identity: Identity) -> dict:
async def list_company_members(db, identity: Identity) -> dict:
"""企业绑定成员列表(联系人自动填入用):姓名/手机/职务/是否管理员"""
"""企业绑定成员列表(联系人自动填入用,仅企业管理员可查看全部成员)"""
require_identity(identity)
if not identity.is_company_admin:
raise PermissionError("仅企业管理员可查看本企业成员")
if not identity.company_id:
return {"items": [], "total": 0}
rows = (await db.session.execute(
@@ -1102,12 +1126,14 @@ LEAVE_STATUSES = ("pending", "approved", "rejected", "cancelled")
async def list_leaves(db, identity: Identity, *, status: str = "",
company_id: str = "") -> dict:
"""请假列表:园区管理员=全园区;企业成员=本人。"""
"""请假列表:园区管理员=全园区;企业管理员=本企业;普通成员=本人。"""
require_identity(identity)
stmt = select(IncubatorLeave).where(IncubatorLeave.tenant_id == identity.tenant_id)
if identity.is_manager:
if company_id:
stmt = stmt.where(IncubatorLeave.company_id == company_id)
elif identity.is_company_admin:
stmt = stmt.where(IncubatorLeave.company_id == identity.company_id)
else:
stmt = stmt.where(IncubatorLeave.user_id == identity.user_id)
if status and status in LEAVE_STATUSES:
@@ -1123,7 +1149,9 @@ async def get_leave(db, identity: Identity, leave_id: str) -> dict:
r = await db.session.get(IncubatorLeave, leave_id)
if r is None or r.tenant_id != identity.tenant_id:
raise LookupError("请假单不存在")
if not identity.is_manager and r.user_id != identity.user_id:
if not identity.is_manager and not (identity.is_company_admin
and r.company_id == identity.company_id) \
and r.user_id != identity.user_id:
raise LookupError("请假单不存在")
return _leave_to_dict(r)
@@ -1165,15 +1193,17 @@ async def create_leave(db, identity: Identity, body: dict) -> dict:
async def review_leave(db, identity: Identity, leave_id: str,
action: str, comment: str = "") -> dict:
"""审批请假:园区管理员(carrier / 园区绑定管理员)。"""
"""审批请假:园区管理员(carrier / 园区绑定管理员)或本企业管理员"""
require_identity(identity)
if not identity.is_manager:
raise PermissionError("仅园区管理员可审批请假")
if not identity.is_manager and not identity.is_company_admin:
raise PermissionError("仅园区管理员或本企业管理员可审批请假")
if action not in ("approve", "reject"):
raise ValueError("action 仅支持 approve / reject")
r = await db.session.get(IncubatorLeave, leave_id)
if r is None or r.tenant_id != identity.tenant_id:
raise LookupError("请假单不存在")
if not identity.is_manager and r.company_id != identity.company_id:
raise PermissionError("仅可审批本企业请假")
if r.status != "pending":
raise ValueError(f"当前状态 {r.status} 不可审批")
r.status = "approved" if action == "approve" else "rejected"