Files
plantillas-proyectos/backend/api/v1/modules/a76/licenses/service.py
acazares b68c4316ff Refactor backend and frontend code for improved structure and functionality
- Rearranged imports in multiple files for consistency and clarity.
- Updated logging middleware to exclude specific paths from logging.
- Enhanced security module by cleaning up token handling and improving tenant validation.
- Added tenant and company scoped mixins for better database model management.
- Implemented generic CRUD routes for tenant-scoped resources.
- Improved error handling and response management in API routes.
- Cleaned up login and logout processes to ensure proper session management.
- Introduced mechanisms to clear local storage and cookies on tenant change.
- Enhanced company store to detect tenant changes and clear data accordingly.
- Added new DTO mixins for currency and value affect flags.
2025-11-11 17:20:47 -06:00

262 lines
8.4 KiB
Python

"""
Servicio de lógica de negocio para licencias
"""
import logging
from datetime import datetime, timezone
from typing import Optional
from fastapi import HTTPException
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from .dto import (
LicenseCreateDTO,
LicenseResponseDTO,
LicenseUpdateDTO,
LicenseUsageResponseDTO,
)
from .models import License, LicensePlan, LicenseStatus, LicenseUsage
logger = logging.getLogger(__name__)
class LicenseService:
"""Servicio para gestión de licencias"""
def __init__(self, db: Session):
self.db = db
def create_license(self, license_data: LicenseCreateDTO) -> LicenseResponseDTO:
"""
Crea una nueva licencia para un tenant
Args:
license_data: Datos de la licencia
Returns:
LicenseResponseDTO
Raises:
HTTPException: Si el tenant ya tiene licencia o hay error
"""
try:
# Verificar que el tenant no tenga ya una licencia
existing = (
self.db.query(License)
.filter(License.tenant_id == license_data.tenant_id)
.first()
)
if existing:
raise HTTPException(
status_code=400,
detail=f"Tenant {license_data.tenant_id} already has a license",
)
# Crear licencia
db_license = License(
tenant_id=license_data.tenant_id,
plan=LicensePlan(license_data.plan.value),
status=LicenseStatus.ACTIVE,
max_users=license_data.max_users,
max_storage_gb=license_data.max_storage_gb,
max_monthly_operations=license_data.max_monthly_operations,
feature_api_access=license_data.feature_api_access,
feature_advanced_reports=license_data.feature_advanced_reports,
feature_integrations=license_data.feature_integrations,
feature_dedicated_support=license_data.feature_dedicated_support,
starts_at=license_data.starts_at,
expires_at=license_data.expires_at,
)
self.db.add(db_license)
self.db.commit()
self.db.refresh(db_license)
return LicenseResponseDTO.model_validate(db_license)
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating license: {str(e)}")
raise HTTPException(status_code=400, detail="Database integrity error")
except HTTPException:
raise
except Exception as e:
self.db.rollback()
logger.error(f"Error creating license: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating license")
def get_license_by_tenant(self, tenant_id: int) -> Optional[LicenseResponseDTO]:
"""
Obtiene la licencia de un tenant
Args:
tenant_id: ID del tenant
Returns:
LicenseResponseDTO o None si no existe
"""
license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license:
return None
return LicenseResponseDTO.model_validate(license)
def update_license(
self, tenant_id: int, license_data: LicenseUpdateDTO
) -> Optional[LicenseResponseDTO]:
"""
Actualiza una licencia
Args:
tenant_id: ID del tenant
license_data: Datos a actualizar
Returns:
LicenseResponseDTO actualizado o None si no existe
"""
license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license:
return None
# Actualizar campos proporcionados
update_data = license_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
if field in ["plan", "status"]:
# Convertir enums
value = LicensePlan(value) if field == "plan" else LicenseStatus(value)
setattr(license, field, value)
try:
self.db.commit()
self.db.refresh(license)
logger.info(f"License updated for tenant {tenant_id}")
return LicenseResponseDTO.model_validate(license)
except Exception as e:
self.db.rollback()
logger.error(f"Error updating license for tenant {tenant_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating license")
def validate_license(self, tenant_id: int) -> dict:
"""
Valida si la licencia de un tenant está activa y vigente
Args:
tenant_id: ID del tenant
Returns:
Dict con información de validación
"""
license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license:
return {
"is_valid": False,
"status": "not_found",
"plan": None,
"expires_at": None,
"reason": "License not found",
}
now = datetime.now(timezone.utc)
# Verificar estado
if license.status != LicenseStatus.ACTIVE:
return {
"is_valid": False,
"status": license.status.value,
"plan": license.plan.value,
"expires_at": license.expires_at,
"reason": f"License status is {license.status.value}",
}
# Verificar vigencia
if license.expires_at < now:
# Auto-actualizar a expirada
license.status = LicenseStatus.EXPIRED
self.db.commit()
return {
"is_valid": False,
"status": "expired",
"plan": license.plan.value,
"expires_at": license.expires_at,
"reason": "License has expired",
}
# Licencia válida
return {
"is_valid": True,
"status": license.status.value,
"plan": license.plan.value,
"expires_at": license.expires_at,
"reason": None,
}
def get_usage(self, tenant_id: int) -> Optional[LicenseUsageResponseDTO]:
"""
Obtiene el uso actual de la licencia de un tenant
Args:
tenant_id: ID del tenant
Returns:
LicenseUsageResponseDTO o None
"""
license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license:
return None
# Obtener último registro de uso
usage = (
self.db.query(LicenseUsage)
.filter(LicenseUsage.tenant_id == tenant_id)
.order_by(LicenseUsage.created_at.desc())
.first()
)
if not usage:
# Crear registro inicial si no existe
usage = LicenseUsage(
tenant_id=tenant_id,
period_start=datetime.now(timezone.utc),
period_end=datetime.now(timezone.utc),
active_users=0,
storage_used_gb=0,
operations_count=0,
api_calls_count=0,
)
# Calcular porcentajes
users_usage = (
(usage.active_users / license.max_users * 100)
if license.max_users > 0
else 0
)
storage_usage = (
(usage.storage_used_gb / license.max_storage_gb * 100)
if license.max_storage_gb > 0
else 0
)
operations_usage = (
(usage.operations_count / license.max_monthly_operations * 100)
if license.max_monthly_operations > 0
else 0
)
return LicenseUsageResponseDTO(
tenant_id=tenant_id,
period_start=usage.period_start,
period_end=usage.period_end,
active_users=usage.active_users,
storage_used_gb=usage.storage_used_gb,
operations_count=usage.operations_count,
api_calls_count=usage.api_calls_count,
max_users=license.max_users,
max_storage_gb=license.max_storage_gb,
max_monthly_operations=license.max_monthly_operations,
users_usage_percent=round(users_usage, 2),
storage_usage_percent=round(storage_usage, 2),
operations_usage_percent=round(operations_usage, 2),
)