first commit
This commit is contained in:
224
app/core/baseRepository.py
Normal file
224
app/core/baseRepository.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# core/base_repository.py
|
||||
from typing import Generic, TypeVar, Type, Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session, Query
|
||||
from sqlalchemy import and_, desc
|
||||
|
||||
from database import Base
|
||||
|
||||
ModelType = TypeVar("ModelType", bound= Base)
|
||||
|
||||
|
||||
class BaseRepository(Generic[ModelType]):
|
||||
"""
|
||||
Repositorio base con:
|
||||
- CRUD completo
|
||||
- Soft delete integrado
|
||||
- Trazabilidad (created_by, updated_by, deleted_by)
|
||||
- Métodos para root, admin, admin_licencias
|
||||
"""
|
||||
|
||||
def __init__(self, db: Session, model: Type[ModelType]):
|
||||
self.db = db
|
||||
self.model = model
|
||||
|
||||
# ============================================
|
||||
# QUERY BASE
|
||||
# ============================================
|
||||
|
||||
def _base_query(self, include_deleted: bool = False) -> Query:
|
||||
"""
|
||||
Query base con filtro de soft delete
|
||||
include_deleted=True: incluye registros eliminados
|
||||
"""
|
||||
query = self.db.query(self.model)
|
||||
if not include_deleted:
|
||||
query = query.filter(
|
||||
self.model.deleted_at.is_(None),
|
||||
self.model.is_active == True
|
||||
)
|
||||
return query
|
||||
|
||||
# ============================================
|
||||
# CRUD BÁSICO
|
||||
# ============================================
|
||||
|
||||
def get_by_id(self, id: int, include_deleted: bool = False) -> Optional[ModelType]:
|
||||
"""Obtiene por ID"""
|
||||
return self._base_query(include_deleted).filter(self.model.id == id).first()
|
||||
|
||||
def get_all(
|
||||
self,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Dict[str, Any] = None,
|
||||
order_by: str = None,
|
||||
descending: bool = False,
|
||||
include_deleted: bool = False
|
||||
) -> List[ModelType]:
|
||||
"""Lista con paginación y filtros"""
|
||||
query = self._base_query(include_deleted)
|
||||
|
||||
# Aplicar filtros
|
||||
if filters:
|
||||
for key, value in filters.items():
|
||||
if hasattr(self.model, key) and value is not None:
|
||||
query = query.filter(getattr(self.model, key) == value)
|
||||
|
||||
# Ordenamiento
|
||||
if order_by and hasattr(self.model, order_by):
|
||||
order_col = getattr(self.model, order_by)
|
||||
query = query.order_by(desc(order_col) if descending else order_col)
|
||||
|
||||
return query.offset(skip).limit(limit).all()
|
||||
|
||||
def count(self, filters: Dict[str, Any] = None, include_deleted: bool = False) -> int:
|
||||
"""Cuenta registros"""
|
||||
query = self._base_query(include_deleted)
|
||||
if filters:
|
||||
for key, value in filters.items():
|
||||
if hasattr(self.model, key) and value is not None:
|
||||
query = query.filter(getattr(self.model, key) == value)
|
||||
return query.count()
|
||||
|
||||
def create(self, data: Dict[str, Any], created_by: int = None) -> ModelType:
|
||||
"""Crea un nuevo registro"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
# Eliminar campos que deben ser automáticos
|
||||
data.pop("created_at", None)
|
||||
data.pop("updated_at", None)
|
||||
data.pop("deleted_at", None)
|
||||
data.pop("last_login", None)
|
||||
data.pop("id", None)
|
||||
|
||||
if created_by:
|
||||
data["created_by"] = created_by
|
||||
|
||||
logger.info(f"CREANDO ENTIDAD {self.model.__name__} CON DATOS: {data}")
|
||||
|
||||
entity = self.model(**data)
|
||||
self.db.add(entity)
|
||||
self.db.commit()
|
||||
self.db.refresh(entity)
|
||||
return entity
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"ERROR EN CREATE: {str(e)}")
|
||||
self.db.rollback()
|
||||
raise e
|
||||
|
||||
def update(
|
||||
self,
|
||||
id: int,
|
||||
data: Dict[str, Any],
|
||||
updated_by: int = None,
|
||||
include_deleted: bool = False
|
||||
) -> Optional[ModelType]:
|
||||
"""Actualiza un registro"""
|
||||
entity = self.get_by_id(id, include_deleted)
|
||||
if not entity:
|
||||
return None
|
||||
|
||||
# No permitir actualizar si está eliminado
|
||||
if entity.deleted_at is not None and not include_deleted:
|
||||
return None
|
||||
|
||||
# Actualizar campos
|
||||
for key, value in data.items():
|
||||
if hasattr(entity, key) and value is not None:
|
||||
setattr(entity, key, value)
|
||||
|
||||
if updated_by:
|
||||
entity.updated_by = updated_by
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(entity)
|
||||
return entity
|
||||
|
||||
def soft_delete(self, id: int, deleted_by: int = None) -> bool:
|
||||
"""Soft delete: marca is_active=False y deleted_at"""
|
||||
entity = self.get_by_id(id)
|
||||
if not entity:
|
||||
return False
|
||||
|
||||
if entity.deleted_at is not None:
|
||||
return False
|
||||
|
||||
entity.is_active = False
|
||||
entity.deleted_at = datetime.utcnow()
|
||||
if deleted_by:
|
||||
entity.deleted_by = deleted_by
|
||||
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
def hard_delete(self, id: int) -> bool:
|
||||
"""Eliminación física"""
|
||||
entity = self.get_by_id(id, include_deleted=True)
|
||||
if not entity:
|
||||
return False
|
||||
|
||||
self.db.delete(entity)
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
def restore(self, id: int, updated_by: int = None) -> Optional[ModelType]:
|
||||
"""Restaura un registro soft-deleted"""
|
||||
entity = self.get_by_id(id, include_deleted=True)
|
||||
if not entity:
|
||||
return None
|
||||
|
||||
if entity.deleted_at is None:
|
||||
return entity
|
||||
|
||||
entity.is_active = True
|
||||
entity.deleted_at = None
|
||||
if updated_by:
|
||||
entity.updated_by = updated_by
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(entity)
|
||||
return entity
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS PARA DIFERENCIACIÓN DE USUARIOS
|
||||
# ============================================
|
||||
# Estos métodos serán sobrescritos en repositorios específicos
|
||||
# si el modelo tiene los campos correspondientes
|
||||
|
||||
def get_by_user_type(self, user_type: str) -> List[ModelType]:
|
||||
"""Obtiene por tipo de usuario (root, admin, admin_licencias, user)"""
|
||||
if hasattr(self.model, "tipo_usuario"):
|
||||
return self._base_query().filter(self.model.tipo_usuario == user_type).all()
|
||||
return []
|
||||
|
||||
def get_root(self) -> List[ModelType]:
|
||||
"""Obtiene usuarios tipo root"""
|
||||
return self.get_by_user_type("root")
|
||||
|
||||
def get_admin(self) -> List[ModelType]:
|
||||
"""Obtiene usuarios tipo admin"""
|
||||
return self.get_by_user_type("admin")
|
||||
|
||||
def get_admin_licencias(self) -> List[ModelType]:
|
||||
"""Obtiene usuarios tipo admin_licencias"""
|
||||
return self.get_by_user_type("admin_licencias")
|
||||
|
||||
#===============================================
|
||||
# el tipo de usuario "PRIVILEGED" un esta por definir que clase de permisos tendras
|
||||
#=================================
|
||||
|
||||
def get_by_role(self, role: str) -> List[ModelType]:
|
||||
"""Obtiene por rol operativo"""
|
||||
if hasattr(self.model, "rol_operativo"):
|
||||
return self._base_query().filter(self.model.rol_operativo == role).all()
|
||||
return []
|
||||
|
||||
def get_by_enterprise(self, enterprise_id: int) -> List[ModelType]:
|
||||
"""Obtiene por empresa"""
|
||||
if hasattr(self.model, "enterprise_id"):
|
||||
return self._base_query().filter(self.model.enterprise_id == enterprise_id).all()
|
||||
return []
|
||||
Reference in New Issue
Block a user