diff --git a/app/incubator/service.py b/app/incubator/service.py index 5804011..3380f38 100644 --- a/app/incubator/service.py +++ b/app/incubator/service.py @@ -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"