Files
saas_backend/app/models/auth/org_unit_model.py
T
2026-08-31 20:39:41 -04:00

90 lines
3.6 KiB
Python

"""Departments, branches and teams, and who administers which."""
from __future__ import annotations
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Text, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.config.database import Base
class OrgUnit(Base):
__tablename__ = "org_units"
id = Column(UUID(as_uuid=True), primary_key=True,
server_default=func.gen_random_uuid())
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"),
nullable=False)
name = Column(String(160), nullable=False)
code = Column(String(60))
parent_id = Column(UUID(as_uuid=True), ForeignKey("org_units.id", ondelete="RESTRICT"))
path = Column(Text, nullable=False, default="/")
is_active = Column(Boolean, nullable=False, default=True)
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
deleted_at = Column(DateTime(timezone=True))
deleted_by_id = Column(UUID(as_uuid=True))
parent = relationship("OrgUnit", remote_side=[id], lazy="raise")
@property
def depth(self) -> int:
"""How far down the tree. `/` is depth 0."""
return max(0, len([part for part in (self.path or "/").split("/") if part]) - 1)
@property
def is_deleted(self) -> bool:
return self.deleted_at is not None
def descendant_prefix(self) -> str:
"""What a descendant's path starts with.
Includes this unit itself, because "everything I administer" always
means "this unit and everything under it" — a lead who could not manage
their own unit would be a strange kind of lead.
"""
return self.path or "/"
class UserOrgUnit(Base):
__tablename__ = "user_org_units"
id = Column(UUID(as_uuid=True), primary_key=True,
server_default=func.gen_random_uuid())
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"),
nullable=False)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"),
nullable=False)
org_unit_id = Column(UUID(as_uuid=True),
ForeignKey("org_units.id", ondelete="CASCADE"), nullable=False)
is_primary = Column(Boolean, nullable=False, default=False)
is_lead = Column(Boolean, nullable=False, default=False)
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
org_unit = relationship("OrgUnit", lazy="raise")
class UserAdminScope(Base):
"""A unit whose people this person may administer.
Separate from membership and from being a lead. All three exist because they
are genuinely different: somebody can be *in* a unit without administering
it, can *lead* it without the platform granting them anything, and can
administer a unit they do not belong to — a regional HR administrator, for
instance.
"""
__tablename__ = "user_admin_scopes"
id = Column(UUID(as_uuid=True), primary_key=True,
server_default=func.gen_random_uuid())
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"),
nullable=False)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"),
nullable=False)
org_unit_id = Column(UUID(as_uuid=True),
ForeignKey("org_units.id", ondelete="CASCADE"), nullable=False)
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
org_unit = relationship("OrgUnit", lazy="raise")