Initial commit
This commit is contained in:
135
backend/app/models/user.py
Normal file
135
backend/app/models/user.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
User Model - ServiceManagerWeb
|
||||
|
||||
Modelo para usuarios del sistema (internos y clientes)
|
||||
"""
|
||||
|
||||
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Text, ARRAY
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, ENUM
|
||||
from typing import Optional, List
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
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(
|
||||
UUID(as_uuid=True),
|
||||
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(ENUM(UserRole), 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(ARRAY(String))
|
||||
|
||||
# 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"
|
||||
)
|
||||
|
||||
# Unique constraint por tenant
|
||||
__table_args__ = (
|
||||
{"postgresql_tablespace": "users"},
|
||||
)
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user