Files
server-core/app/domain/park_transfer.py
T

94 lines
4.0 KiB
Python
Raw Normal View History

# -*- 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|operatordecision ∈ 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 限本企业 adminuser 限本人。"""
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