# -*- coding: utf-8 -*- """转园申请两级审核状态机(纯规则,无 DB)。 流程: pending_from(待转出园区审) → 转出园区 approved → pending_to(待转入园区审) → 转入园区 approved → approved(执行迁移) 任一级 rejected → rejected(终态) operator 任意时刻可 force-approve / reject(兜底终审)。 园区未设管理员时该级自动通过(apply 时直接跳级)。 发起人:operator / from-park carrier / to-park carrier / 企业管理员 / 用户本人。 """ from __future__ import annotations STAGES = ("pending_from", "pending_to", "approved", "rejected", "cancelled") INITIATOR_ROLES = ("operator", "carrier", "enterprise", "user") TARGET_KINDS = ("user", "company") class TransferError(Exception): """转园流程违规(路由层转 400/403)。""" def has_park_admin(tenant: dict | None) -> bool: """园区是否已绑定管理员(operator_user_id 或旧 admin_username)。""" if not tenant: return False return bool(tenant.get("operator_user_id") or tenant.get("admin_username")) def initial_stage(from_tenant: dict | None) -> str: """发起时的初始阶段:转出园区无管理员 → 第一级自动通过,直接进第二级。""" return "pending_from" if has_park_admin(from_tenant) else "pending_to" def review(stage: str, *, side: str, decision: str) -> dict: """推进一步状态机。side ∈ from|to|operator;decision ∈ approved|rejected。 返回 {"stage": 新阶段, "from_status":…, "to_status":…};不合法流转 raise TransferError。 """ if decision not in ("approved", "rejected"): raise TransferError("审核决定仅支持 approved/rejected") if stage in ("approved", "rejected", "cancelled"): raise TransferError("该申请已终态,无法再审核") if side == "operator": # 运营端兜底:任意未终态阶段可直接终审 return {"stage": decision, "from_status": decision, "to_status": decision if stage == "pending_to" or decision == "rejected" else "pending"} if side == "from": if stage != "pending_from": raise TransferError("当前不在转出园区审核阶段") if decision == "rejected": return {"stage": "rejected", "from_status": "rejected", "to_status": "pending"} return {"stage": "pending_to", "from_status": "approved", "to_status": "pending"} if side == "to": if stage == "pending_from": raise TransferError("转出园区尚未审核") if stage != "pending_to": raise TransferError("该申请已终态,无法再审核") if decision == "rejected": return {"stage": "rejected", "from_status": "approved", "to_status": "rejected"} return {"stage": "approved", "from_status": "approved", "to_status": "approved"} raise TransferError("非法审核方") def can_initiate(actor_role: str, *, from_park_id: str, to_park_id: str, my_park_id: str = "", is_company_admin: bool = False, target_park_id: str = "") -> bool: """发起资格:operator 任意;carrier 限 from/to 本园区;enterprise 限本企业 admin;user 限本人。""" if actor_role == "operator": return True if actor_role == "carrier": return bool(my_park_id) and my_park_id in (from_park_id, to_park_id) if actor_role == "enterprise": return is_company_admin if actor_role == "user": return bool(target_park_id) and target_park_id == from_park_id return False def review_side(transfer: dict, my_park_id: str) -> str | None: """园区端审核时判定自己审哪一级:from 园区审第一级,to 园区审第二级。""" stage = transfer.get("stage") if stage == "pending_from" and transfer.get("from_park_id") == my_park_id: return "from" if stage == "pending_to" and transfer.get("to_park_id") == my_park_id: return "to" return None