145 lines
4.7 KiB
Python
145 lines
4.7 KiB
Python
"""
|
|
User Model - ServiceManagerWeb
|
|
|
|
Modelo para usuarios del sistema (internos y clientes)
|
|
"""
|
|
|
|
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Text, JSON, Enum as SAEnum
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM, ARRAY as PG_ARRAY
|
|
from typing import Optional, List
|
|
import enum
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from app.core.database import Base, GUID
|
|
|
|
|
|
class UserRole(str, enum.Enum):
|
|
"""Roles de usuario en el sistema."""
|
|
# Staff interno
|
|
ADMIN = "ADMIN" # Control total
|
|
SUPPORT_MANAGER = "SUPPORT_MANAGER" # Gestión de equipos y SLAs
|
|
AGENT = "AGENT" # Atención de tickets
|
|
AUDITOR = "AUDITOR" # Solo lectura para auditoría
|
|
|
|
# Clientes
|
|
CLIENT_ADMIN = "CLIENT_ADMIN" # Admin de organización cliente
|
|
CLIENT_USER = "CLIENT_USER" # Usuario final cliente
|
|
|
|
|
|
class User(Base):
|
|
"""Modelo de Usuario."""
|
|
|
|
__tablename__ = "users"
|
|
|
|
# Relación con tenant
|
|
tenant_id: Mapped[uuid.UUID] = mapped_column(
|
|
GUID(),
|
|
ForeignKey("tenants.id", ondelete="CASCADE"),
|
|
nullable=False
|
|
)
|
|
|
|
# Información básica
|
|
email: Mapped[str] = mapped_column(String(320), nullable=False)
|
|
first_name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
last_name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
avatar_url: Mapped[Optional[str]] = mapped_column(String(500))
|
|
|
|
# Autenticación
|
|
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
role: Mapped[UserRole] = mapped_column(
|
|
SAEnum(UserRole, name="user_role_enum", native_enum=False).with_variant(
|
|
PG_ENUM(UserRole, name="user_role_enum", create_type=True),
|
|
"postgresql",
|
|
),
|
|
nullable=False,
|
|
)
|
|
|
|
# 2FA (opcional para staff interno)
|
|
totp_secret: Mapped[Optional[str]] = mapped_column(String(32))
|
|
totp_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
backup_codes: Mapped[Optional[List[str]]] = mapped_column(
|
|
JSON().with_variant(PG_ARRAY(String), "postgresql")
|
|
)
|
|
|
|
# Estado
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
email_verified: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
last_activity: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
|
|
|
# Preferencias
|
|
language: Mapped[str] = mapped_column(String(10), default="es")
|
|
timezone: Mapped[str] = mapped_column(String(50), default="UTC")
|
|
notifications_email: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
|
|
# Relaciones
|
|
tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="users")
|
|
created_tickets: Mapped[List["Ticket"]] = relationship(
|
|
"Ticket",
|
|
back_populates="created_by_user",
|
|
foreign_keys="Ticket.created_by"
|
|
)
|
|
assigned_tickets: Mapped[List["Ticket"]] = relationship(
|
|
"Ticket",
|
|
back_populates="assigned_to_user",
|
|
foreign_keys="Ticket.assigned_to"
|
|
)
|
|
refresh_tokens: Mapped[List["RefreshToken"]] = relationship(
|
|
"RefreshToken",
|
|
back_populates="user",
|
|
foreign_keys="RefreshToken.user_id",
|
|
cascade="all, delete-orphan"
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<User(id={self.id}, email='{self.email}', role='{self.role}')>"
|
|
|
|
@property
|
|
def full_name(self) -> str:
|
|
"""Get user's full name."""
|
|
return f"{self.first_name} {self.last_name}"
|
|
|
|
@property
|
|
def is_staff(self) -> bool:
|
|
"""Check if user is internal staff."""
|
|
return self.role in [
|
|
UserRole.ADMIN,
|
|
UserRole.SUPPORT_MANAGER,
|
|
UserRole.AGENT,
|
|
UserRole.AUDITOR
|
|
]
|
|
|
|
@property
|
|
def is_client(self) -> bool:
|
|
"""Check if user is a client."""
|
|
return self.role in [
|
|
UserRole.CLIENT_ADMIN,
|
|
UserRole.CLIENT_USER
|
|
]
|
|
|
|
@property
|
|
def can_manage_users(self) -> bool:
|
|
"""Check if user can manage other users."""
|
|
return self.role in [
|
|
UserRole.ADMIN,
|
|
UserRole.SUPPORT_MANAGER,
|
|
UserRole.CLIENT_ADMIN
|
|
]
|
|
|
|
@property
|
|
def can_manage_tickets(self) -> bool:
|
|
"""Check if user can manage tickets."""
|
|
return self.role in [
|
|
UserRole.ADMIN,
|
|
UserRole.SUPPORT_MANAGER,
|
|
UserRole.AGENT
|
|
]
|
|
|
|
@property
|
|
def requires_2fa(self) -> bool:
|
|
"""Check if 2FA is required for this user."""
|
|
# 2FA opcional para staff interno, no requerido para clientes
|
|
return self.is_staff
|