feat: Implement multi-tenancy support in middleware and security layers

- Enhanced TenantMiddleware to validate tenant information from JWT tokens.
- Added LicenseValidationMiddleware to check tenant licenses before processing requests.
- Updated security utilities to extract tenant information from tokens and validate company access.
- Introduced CompanyStore to manage active company state and handle company switching in the frontend.
- Modified API routes to include company_id in requests for better resource management.
- Improved logging and error handling throughout the middleware and API layers.
- Updated frontend components to reflect changes in company management and selection.
- Added new API route for fetching user's companies with proper authentication handling.
This commit is contained in:
2025-11-11 14:00:56 -06:00
parent e1eb6bbd01
commit 52b8fcd434
242 changed files with 7067 additions and 3274 deletions

View File

@@ -2,6 +2,7 @@
DTOs (Data Transfer Objects) para módulo de clases SCAII y SCAF
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
@@ -9,17 +10,38 @@ from datetime import datetime
class ClassCreateDTO(BaseModel):
"""DTO para crear una clase"""
client_id: int = Field(..., description="Client key")
class_code: str = Field(..., max_length=8, description="Class code")
description_spanish: Optional[str] = Field(None, max_length=500, description="Description in Spanish")
description_english: Optional[str] = Field(None, max_length=500, description="Description in English")
material_key: Optional[str] = Field(None, max_length=10, description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)")
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)")
fraction: Optional[str] = Field(None, max_length=10, description="Mexican tariff fraction")
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
sub_key: Optional[str] = Field(None, max_length=5, description="Sub classification key")
physical_review: Optional[int] = Field(None, description="Physical review indicator")
iva_exempt_fraction: Optional[str] = Field(None, max_length=4, description="IVA exempt fraction")
description_spanish: Optional[str] = Field(
None, max_length=500, description="Description in Spanish"
)
description_english: Optional[str] = Field(
None, max_length=500, description="Description in English"
)
material_key: Optional[str] = Field(
None,
max_length=10,
description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)",
)
unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)"
)
fraction: Optional[str] = Field(
None, max_length=10, description="Mexican tariff fraction"
)
us_fraction: Optional[str] = Field(
None, max_length=16, description="US tariff fraction"
)
sub_key: Optional[str] = Field(
None, max_length=5, description="Sub classification key"
)
physical_review: Optional[int] = Field(
None, description="Physical review indicator"
)
iva_exempt_fraction: Optional[str] = Field(
None, max_length=4, description="IVA exempt fraction"
)
class Config:
from_attributes = True
@@ -27,15 +49,36 @@ class ClassCreateDTO(BaseModel):
class ClassUpdateDTO(BaseModel):
"""DTO para actualizar una clase"""
description_spanish: Optional[str] = Field(None, max_length=500, description="Description in Spanish")
description_english: Optional[str] = Field(None, max_length=500, description="Description in English")
material_key: Optional[str] = Field(None, max_length=10, description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)")
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)")
fraction: Optional[str] = Field(None, max_length=10, description="Mexican tariff fraction")
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
sub_key: Optional[str] = Field(None, max_length=5, description="Sub classification key")
physical_review: Optional[int] = Field(None, description="Physical review indicator")
iva_exempt_fraction: Optional[str] = Field(None, max_length=4, description="IVA exempt fraction")
description_spanish: Optional[str] = Field(
None, max_length=500, description="Description in Spanish"
)
description_english: Optional[str] = Field(
None, max_length=500, description="Description in English"
)
material_key: Optional[str] = Field(
None,
max_length=10,
description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)",
)
unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)"
)
fraction: Optional[str] = Field(
None, max_length=10, description="Mexican tariff fraction"
)
us_fraction: Optional[str] = Field(
None, max_length=16, description="US tariff fraction"
)
sub_key: Optional[str] = Field(
None, max_length=5, description="Sub classification key"
)
physical_review: Optional[int] = Field(
None, description="Physical review indicator"
)
iva_exempt_fraction: Optional[str] = Field(
None, max_length=4, description="IVA exempt fraction"
)
class Config:
from_attributes = True
@@ -43,6 +86,7 @@ class ClassUpdateDTO(BaseModel):
class ClassResponseDTO(BaseModel):
"""DTO para respuesta de clase"""
client_id: int
class_code: str
description_spanish: Optional[str] = None
@@ -61,6 +105,7 @@ class ClassResponseDTO(BaseModel):
class ClassBasicDTO(BaseModel):
"""DTO para información básica de clase"""
client_id: int
class_code: str
description_spanish: Optional[str] = None
@@ -74,6 +119,7 @@ class ClassBasicDTO(BaseModel):
class ClassListDTO(BaseModel):
"""DTO para lista de clases"""
classes: list[ClassBasicDTO]
total: int
page: int
@@ -85,13 +131,15 @@ class ClassListDTO(BaseModel):
class ClassSearchDTO(BaseModel):
"""DTO para búsqueda de clases"""
client_id: Optional[int] = Field(None, description="Filter by client key")
class_code: Optional[str] = Field(None, description="Search by class code")
description: Optional[str] = Field(None, description="Search in descriptions")
material_key: Optional[str] = Field(None, description="Filter by material key")
fraction: Optional[str] = Field(None, description="Filter by tariff fraction")
physical_review: Optional[int] = Field(None, description="Filter by physical review indicator")
physical_review: Optional[int] = Field(
None, description="Filter by physical review indicator"
)
class Config:
from_attributes = True