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:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Módulo de Authentication
|
||||
"""
|
||||
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -1,58 +1,63 @@
|
||||
"""
|
||||
DTOs para módulo de autenticación
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class LoginRequestDTO(BaseModel):
|
||||
"""DTO para solicitud de login"""
|
||||
|
||||
username: str = Field(..., description="Usuario o email")
|
||||
password: str = Field(..., min_length=6, description="Contraseña")
|
||||
tenant_slug: str = Field(..., description="Slug del tenant")
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"username": "usuario@ejemplo.com",
|
||||
"password": "password123",
|
||||
"tenant_slug": "empresa-abc"
|
||||
"tenant_slug": "empresa-abc",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TokenResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de token"""
|
||||
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
expires_in: int
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600
|
||||
"expires_in": 3600,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RefreshTokenRequestDTO(BaseModel):
|
||||
"""DTO para solicitud de refresh token"""
|
||||
|
||||
refresh_token: str = Field(..., description="Refresh token")
|
||||
|
||||
|
||||
class UserInfoResponseDTO(BaseModel):
|
||||
"""DTO para información de usuario"""
|
||||
|
||||
sub: str
|
||||
email: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
preferred_username: Optional[str] = None
|
||||
tenant_id: Optional[int] = None
|
||||
roles: list[str] = []
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
@@ -61,25 +66,29 @@ class UserInfoResponseDTO(BaseModel):
|
||||
"name": "Juan Pérez",
|
||||
"preferred_username": "jperez",
|
||||
"tenant_id": 1,
|
||||
"roles": ["user", "admin"]
|
||||
"roles": ["user", "admin"],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LogoutRequestDTO(BaseModel):
|
||||
"""DTO para solicitud de logout"""
|
||||
|
||||
refresh_token: str = Field(..., description="Refresh token para invalidar")
|
||||
|
||||
|
||||
class RegisterRequestDTO(BaseModel):
|
||||
"""DTO para solicitud de registro"""
|
||||
username: str = Field(..., min_length=3, max_length=50, description="Nombre de usuario")
|
||||
|
||||
username: str = Field(
|
||||
..., min_length=3, max_length=50, description="Nombre de usuario"
|
||||
)
|
||||
email: EmailStr = Field(..., description="Email del usuario")
|
||||
password: str = Field(..., min_length=8, description="Contraseña")
|
||||
first_name: str = Field(..., min_length=2, max_length=50, description="Nombre")
|
||||
last_name: str = Field(..., min_length=2, max_length=50, description="Apellido")
|
||||
tenant_slug: str = Field(..., description="Slug del tenant")
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
@@ -88,54 +97,57 @@ class RegisterRequestDTO(BaseModel):
|
||||
"password": "MiPassword123!",
|
||||
"first_name": "Juan",
|
||||
"last_name": "Pérez",
|
||||
"tenant_slug": "empresa-abc"
|
||||
"tenant_slug": "empresa-abc",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RegisterResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de registro"""
|
||||
|
||||
user_id: str
|
||||
username: str
|
||||
email: str
|
||||
message: str
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"username": "jperez",
|
||||
"email": "jperez@ejemplo.com",
|
||||
"message": "User registered successfully"
|
||||
"message": "User registered successfully",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ExchangeCodeRequestDTO(BaseModel):
|
||||
"""DTO para intercambiar authorization code por tokens (OAuth2 flow)"""
|
||||
|
||||
code: str = Field(..., description="Authorization code de OAuth2")
|
||||
redirect_uri: str = Field(..., description="Redirect URI usado en la autorización")
|
||||
tenant_slug: Optional[str] = Field(None, description="Slug del tenant (opcional)")
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"code": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Ii...",
|
||||
"redirect_uri": "http://localhost:5173/auth/callback",
|
||||
"tenant_slug": "empresa-abc"
|
||||
"tenant_slug": "empresa-abc",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SetCookieRequestDTO(BaseModel):
|
||||
"""DTO para establecer cookies de autenticación"""
|
||||
|
||||
access_token: str = Field(..., description="Access token JWT")
|
||||
refresh_token: str = Field(..., description="Refresh token JWT")
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Endpoints API para autenticación
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -16,7 +17,7 @@ from .dto import (
|
||||
RegisterRequestDTO,
|
||||
RegisterResponseDTO,
|
||||
ExchangeCodeRequestDTO,
|
||||
SetCookieRequestDTO
|
||||
SetCookieRequestDTO,
|
||||
)
|
||||
from .service import AuthService
|
||||
|
||||
@@ -26,12 +27,11 @@ security = HTTPBearer()
|
||||
|
||||
@router.post("/register", response_model=RegisterResponseDTO, status_code=201)
|
||||
async def register(
|
||||
register_data: RegisterRequestDTO,
|
||||
db: Session = Depends(get_core_db)
|
||||
register_data: RegisterRequestDTO, db: Session = Depends(get_core_db)
|
||||
):
|
||||
"""
|
||||
Registra un nuevo usuario en Keycloak
|
||||
|
||||
|
||||
El usuario debe proporcionar:
|
||||
- username: Nombre de usuario único
|
||||
- email: Email único
|
||||
@@ -39,7 +39,7 @@ async def register(
|
||||
- first_name: Nombre
|
||||
- last_name: Apellido
|
||||
- tenant_slug: Slug del tenant al que pertenece
|
||||
|
||||
|
||||
El usuario se crea automáticamente en Keycloak con:
|
||||
- Cuenta habilitada
|
||||
- Rol 'user' asignado por defecto
|
||||
@@ -50,13 +50,10 @@ async def register(
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponseDTO)
|
||||
async def login(
|
||||
login_data: LoginRequestDTO,
|
||||
db: Session = Depends(get_core_db)
|
||||
):
|
||||
async def login(login_data: LoginRequestDTO, db: Session = Depends(get_core_db)):
|
||||
"""
|
||||
Autentica usuario con Keycloak y retorna tokens JWT
|
||||
|
||||
|
||||
El usuario debe proporcionar:
|
||||
- username: Usuario o email
|
||||
- password: Contraseña
|
||||
@@ -68,8 +65,7 @@ async def login(
|
||||
|
||||
@router.post("/refresh", response_model=TokenResponseDTO)
|
||||
async def refresh_token(
|
||||
refresh_data: RefreshTokenRequestDTO,
|
||||
db: Session = Depends(get_core_db)
|
||||
refresh_data: RefreshTokenRequestDTO, db: Session = Depends(get_core_db)
|
||||
):
|
||||
"""
|
||||
Refresca el access token usando el refresh token
|
||||
@@ -81,7 +77,7 @@ async def refresh_token(
|
||||
@router.get("/me", response_model=UserInfoResponseDTO)
|
||||
async def get_current_user_info(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: Session = Depends(get_core_db)
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""
|
||||
Obtiene información del usuario actual desde el token
|
||||
@@ -94,7 +90,7 @@ async def get_current_user_info(
|
||||
async def logout(
|
||||
logout_data: LogoutRequestDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Cierra sesión invalidando el refresh token
|
||||
@@ -105,15 +101,14 @@ async def logout(
|
||||
|
||||
@router.post("/exchange-code", response_model=TokenResponseDTO)
|
||||
async def exchange_code(
|
||||
exchange_data: ExchangeCodeRequestDTO,
|
||||
db: Session = Depends(get_core_db)
|
||||
exchange_data: ExchangeCodeRequestDTO, db: Session = Depends(get_core_db)
|
||||
):
|
||||
"""
|
||||
Intercambia un authorization code de OAuth2 por tokens
|
||||
|
||||
|
||||
Este endpoint es útil cuando el frontend usa el flujo de autorización
|
||||
con proveedores externos (Microsoft, Google, etc.) a través de Keycloak.
|
||||
|
||||
|
||||
El código se obtiene después de que el usuario se autentica con el proveedor
|
||||
externo y Keycloak lo redirige al frontend con el código en los query params.
|
||||
"""
|
||||
@@ -125,15 +120,15 @@ async def exchange_code(
|
||||
async def set_cookie(
|
||||
cookie_data: SetCookieRequestDTO,
|
||||
response: Response,
|
||||
db: Session = Depends(get_core_db)
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""
|
||||
Establece cookies HttpOnly con los tokens de autenticación
|
||||
|
||||
|
||||
Este endpoint se llama desde el frontend después de una autenticación
|
||||
SSO exitosa para establecer las cookies de sesión necesarias para
|
||||
la validación server-side en los layouts protegidos.
|
||||
|
||||
|
||||
Las cookies se configuran como:
|
||||
- HttpOnly: No accesibles desde JavaScript (mayor seguridad)
|
||||
- Secure: Solo se envían por HTTPS (en producción)
|
||||
@@ -145,7 +140,7 @@ async def set_cookie(
|
||||
try:
|
||||
# Validar el access token
|
||||
user_info = service.get_user_info(cookie_data.access_token)
|
||||
|
||||
|
||||
# Establecer las cookies
|
||||
# Access token cookie
|
||||
response.set_cookie(
|
||||
@@ -155,9 +150,9 @@ async def set_cookie(
|
||||
secure=False, # TODO: Cambiar a True en producción con HTTPS
|
||||
samesite="lax", # Protección CSRF
|
||||
max_age=3600, # 1 hora (ajustar según configuración del token)
|
||||
path="/"
|
||||
path="/",
|
||||
)
|
||||
|
||||
|
||||
# Refresh token cookie
|
||||
response.set_cookie(
|
||||
key="refresh_token",
|
||||
@@ -166,17 +161,14 @@ async def set_cookie(
|
||||
secure=False, # TODO: Cambiar a True en producción con HTTPS
|
||||
samesite="lax",
|
||||
max_age=86400, # 24 horas (ajustar según configuración del token)
|
||||
path="/"
|
||||
path="/",
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Cookies establecidas correctamente",
|
||||
"user": user_info
|
||||
"user": user_info,
|
||||
}
|
||||
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Error validando tokens: {str(e)}"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail=f"Error validando tokens: {str(e)}")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Servicio de autenticación con Keycloak
|
||||
"""
|
||||
|
||||
from keycloak import KeycloakOpenID, KeycloakAdmin
|
||||
from keycloak.exceptions import KeycloakError
|
||||
from fastapi import HTTPException
|
||||
@@ -15,7 +16,7 @@ from .dto import (
|
||||
UserInfoResponseDTO,
|
||||
LogoutRequestDTO,
|
||||
RegisterRequestDTO,
|
||||
RegisterResponseDTO
|
||||
RegisterResponseDTO,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -23,26 +24,26 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class AuthService:
|
||||
"""Servicio de autenticación"""
|
||||
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.keycloak_openid = KeycloakOpenID(
|
||||
server_url=settings.KEYCLOAK_SERVER_URL,
|
||||
client_id=settings.KEYCLOAK_CLIENT_ID,
|
||||
realm_name=settings.KEYCLOAK_REALM,
|
||||
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET
|
||||
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET,
|
||||
)
|
||||
|
||||
|
||||
def login(self, login_data: LoginRequestDTO) -> TokenResponseDTO:
|
||||
"""
|
||||
Autentica usuario y obtiene tokens
|
||||
|
||||
|
||||
Args:
|
||||
login_data: Credenciales de login
|
||||
|
||||
|
||||
Returns:
|
||||
TokenResponseDTO con access_token y refresh_token
|
||||
|
||||
|
||||
Raises:
|
||||
HTTPException: Si las credenciales son inválidas
|
||||
"""
|
||||
@@ -50,47 +51,50 @@ class AuthService:
|
||||
# Verificar que el tenant existe
|
||||
from api.v1.modules.a76.tenants.service import TenantService
|
||||
from api.v1.modules.a76.user_tenant.service import UserTenantService
|
||||
|
||||
|
||||
tenant_service = TenantService(self.db)
|
||||
user_tenant_service = UserTenantService(self.db)
|
||||
tenant = tenant_service.get_tenant_by_slug(login_data.tenant_slug)
|
||||
|
||||
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="Tenant not found")
|
||||
|
||||
|
||||
if not tenant.is_active:
|
||||
raise HTTPException(status_code=403, detail="Tenant is not active")
|
||||
|
||||
|
||||
# Crear nueva instancia de KeycloakOpenID con el realm del tenant
|
||||
keycloak_client = KeycloakOpenID(
|
||||
server_url=settings.KEYCLOAK_SERVER_URL,
|
||||
client_id=settings.KEYCLOAK_CLIENT_ID,
|
||||
realm_name=tenant.keycloak_realm,
|
||||
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET
|
||||
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET,
|
||||
)
|
||||
|
||||
|
||||
# Obtener token de Keycloak
|
||||
token_response = keycloak_client.token(
|
||||
username=login_data.username,
|
||||
password=login_data.password,
|
||||
grant_type=["password"]
|
||||
grant_type=["password"],
|
||||
)
|
||||
|
||||
|
||||
# Obtener información del usuario y verificar acceso al tenant
|
||||
user_info = keycloak_client.userinfo(token_response["access_token"])
|
||||
user_id = user_info.get("sub")
|
||||
|
||||
|
||||
if user_id:
|
||||
# Verificar si el usuario tiene acceso a este tenant
|
||||
has_access = user_tenant_service.user_has_access_to_tenant(user_id, tenant.id)
|
||||
|
||||
has_access = user_tenant_service.user_has_access_to_tenant(
|
||||
user_id, tenant.id
|
||||
)
|
||||
|
||||
if not has_access:
|
||||
logger.warning(f"User {user_id} tried to access tenant {tenant.id} without permission")
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You don't have access to this tenant"
|
||||
logger.warning(
|
||||
f"User {user_id} tried to access tenant {tenant.id} without permission"
|
||||
)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=403, detail="You don't have access to this tenant"
|
||||
)
|
||||
|
||||
# Actualizar el tenant_id del usuario en Keycloak basado en el slug usado
|
||||
try:
|
||||
# Crear instancia de KeycloakAdmin para actualizar atributos
|
||||
@@ -100,19 +104,19 @@ class AuthService:
|
||||
password=settings.KEYCLOAK_ADMIN_PASSWORD,
|
||||
realm_name=tenant.keycloak_realm,
|
||||
user_realm_name="master",
|
||||
verify=True
|
||||
verify=True,
|
||||
)
|
||||
|
||||
|
||||
# Obtener los datos actuales del usuario para no sobrescribirlos
|
||||
current_user = keycloak_admin.get_user(user_id)
|
||||
|
||||
|
||||
# Obtener los atributos actuales o crear un dict vacío
|
||||
current_attributes = current_user.get("attributes", {})
|
||||
|
||||
|
||||
# Actualizar solo los atributos de tenant
|
||||
current_attributes["tenant_id"] = [str(tenant.id)]
|
||||
current_attributes["tenant_slug"] = [tenant.slug]
|
||||
|
||||
|
||||
# Actualizar el usuario enviando TODOS los campos para evitar que se borren
|
||||
update_payload = {
|
||||
"email": current_user.get("email"),
|
||||
@@ -120,23 +124,25 @@ class AuthService:
|
||||
"lastName": current_user.get("lastName"),
|
||||
"enabled": current_user.get("enabled", True),
|
||||
"emailVerified": current_user.get("emailVerified", False),
|
||||
"attributes": current_attributes
|
||||
"attributes": current_attributes,
|
||||
}
|
||||
|
||||
|
||||
keycloak_admin.update_user(user_id=user_id, payload=update_payload)
|
||||
logger.info(f"Updated tenant_id={tenant.id} for user {login_data.username}")
|
||||
|
||||
logger.info(
|
||||
f"Updated tenant_id={tenant.id} for user {login_data.username}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# No queremos que falle el login si no se puede actualizar el atributo
|
||||
logger.warning(f"Could not update tenant_id attribute: {str(e)}")
|
||||
|
||||
|
||||
return TokenResponseDTO(
|
||||
access_token=token_response["access_token"],
|
||||
refresh_token=token_response["refresh_token"],
|
||||
token_type="bearer",
|
||||
expires_in=token_response["expires_in"]
|
||||
expires_in=token_response["expires_in"],
|
||||
)
|
||||
|
||||
|
||||
except KeycloakError as e:
|
||||
logger.warning(f"Keycloak authentication failed: {str(e)}")
|
||||
raise HTTPException(status_code=401, detail="Invalid credentials")
|
||||
@@ -145,14 +151,14 @@ class AuthService:
|
||||
except Exception as e:
|
||||
logger.error(f"Login error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Authentication error")
|
||||
|
||||
|
||||
def refresh_token(self, refresh_data: RefreshTokenRequestDTO) -> TokenResponseDTO:
|
||||
"""
|
||||
Refresca el access token usando refresh token
|
||||
|
||||
|
||||
Args:
|
||||
refresh_data: Refresh token
|
||||
|
||||
|
||||
Returns:
|
||||
TokenResponseDTO con nuevos tokens
|
||||
"""
|
||||
@@ -160,67 +166,69 @@ class AuthService:
|
||||
token_response = self.keycloak_openid.refresh_token(
|
||||
refresh_data.refresh_token
|
||||
)
|
||||
|
||||
|
||||
return TokenResponseDTO(
|
||||
access_token=token_response["access_token"],
|
||||
refresh_token=token_response["refresh_token"],
|
||||
token_type="bearer",
|
||||
expires_in=token_response["expires_in"]
|
||||
expires_in=token_response["expires_in"],
|
||||
)
|
||||
|
||||
|
||||
except KeycloakError as e:
|
||||
logger.warning(f"Token refresh failed: {str(e)}")
|
||||
raise HTTPException(status_code=401, detail="Invalid or expired refresh token")
|
||||
raise HTTPException(
|
||||
status_code=401, detail="Invalid or expired refresh token"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Token refresh error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Token refresh error")
|
||||
|
||||
|
||||
def get_user_info(self, access_token: str) -> UserInfoResponseDTO:
|
||||
"""
|
||||
Obtiene información del usuario desde el token
|
||||
|
||||
|
||||
Args:
|
||||
access_token: Access token JWT
|
||||
|
||||
|
||||
Returns:
|
||||
UserInfoResponseDTO con información del usuario
|
||||
"""
|
||||
try:
|
||||
user_info = self.keycloak_openid.userinfo(access_token)
|
||||
|
||||
|
||||
# Extraer roles
|
||||
roles = []
|
||||
if "realm_access" in user_info:
|
||||
roles = user_info["realm_access"].get("roles", [])
|
||||
|
||||
|
||||
# Extraer tenant_id si está presente
|
||||
tenant_id = user_info.get("tenant_id")
|
||||
if not tenant_id and "attributes" in user_info:
|
||||
tenant_id = user_info["attributes"].get("tenant_id")
|
||||
|
||||
|
||||
return UserInfoResponseDTO(
|
||||
sub=user_info.get("sub"),
|
||||
email=user_info.get("email"),
|
||||
name=user_info.get("name"),
|
||||
preferred_username=user_info.get("preferred_username"),
|
||||
tenant_id=int(tenant_id) if tenant_id else None,
|
||||
roles=roles
|
||||
roles=roles,
|
||||
)
|
||||
|
||||
|
||||
except KeycloakError as e:
|
||||
logger.warning(f"Get user info failed: {str(e)}")
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
except Exception as e:
|
||||
logger.error(f"Get user info error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error retrieving user info")
|
||||
|
||||
|
||||
def logout(self, logout_data: LogoutRequestDTO) -> dict:
|
||||
"""
|
||||
Cierra sesión invalidando el refresh token
|
||||
|
||||
|
||||
Args:
|
||||
logout_data: Refresh token a invalidar
|
||||
|
||||
|
||||
Returns:
|
||||
Dict con mensaje de éxito
|
||||
"""
|
||||
@@ -228,7 +236,7 @@ class AuthService:
|
||||
self.keycloak_openid.logout(logout_data.refresh_token)
|
||||
logger.info("User logged out successfully")
|
||||
return {"message": "Logged out successfully"}
|
||||
|
||||
|
||||
except KeycloakError as e:
|
||||
logger.warning(f"Logout failed: {str(e)}")
|
||||
# No lanzamos error aquí, el logout puede fallar si el token ya expiró
|
||||
@@ -236,32 +244,33 @@ class AuthService:
|
||||
except Exception as e:
|
||||
logger.error(f"Logout error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Logout error")
|
||||
|
||||
|
||||
def register(self, register_data: RegisterRequestDTO) -> RegisterResponseDTO:
|
||||
"""
|
||||
Registra un nuevo usuario en Keycloak
|
||||
|
||||
|
||||
Args:
|
||||
register_data: Datos del usuario a registrar
|
||||
|
||||
|
||||
Returns:
|
||||
RegisterResponseDTO con información del usuario creado
|
||||
|
||||
|
||||
Raises:
|
||||
HTTPException: Si el registro falla
|
||||
"""
|
||||
try:
|
||||
# Verificar que el tenant existe
|
||||
from api.v1.modules.a76.tenants.service import TenantService
|
||||
|
||||
tenant_service = TenantService(self.db)
|
||||
tenant = tenant_service.get_tenant_by_slug(register_data.tenant_slug)
|
||||
|
||||
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="Tenant not found")
|
||||
|
||||
|
||||
if not tenant.is_active:
|
||||
raise HTTPException(status_code=403, detail="Tenant is not active")
|
||||
|
||||
|
||||
# Crear instancia de KeycloakAdmin para gestión de usuarios
|
||||
keycloak_admin = KeycloakAdmin(
|
||||
server_url=settings.KEYCLOAK_SERVER_URL,
|
||||
@@ -269,9 +278,9 @@ class AuthService:
|
||||
password=settings.KEYCLOAK_ADMIN_PASSWORD,
|
||||
realm_name=tenant.keycloak_realm,
|
||||
user_realm_name="master", # El admin suele estar en master realm
|
||||
verify=True
|
||||
verify=True,
|
||||
)
|
||||
|
||||
|
||||
# Preparar datos del usuario para Keycloak
|
||||
user_data = {
|
||||
"username": register_data.username,
|
||||
@@ -280,20 +289,19 @@ class AuthService:
|
||||
"lastName": register_data.last_name,
|
||||
"enabled": True,
|
||||
"emailVerified": False,
|
||||
"credentials": [{
|
||||
"type": "password",
|
||||
"value": register_data.password,
|
||||
"temporary": False
|
||||
}],
|
||||
"attributes": {
|
||||
"tenant_id": str(tenant.id),
|
||||
"tenant_slug": tenant.slug
|
||||
}
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": register_data.password,
|
||||
"temporary": False,
|
||||
}
|
||||
],
|
||||
"attributes": {"tenant_id": str(tenant.id), "tenant_slug": tenant.slug},
|
||||
}
|
||||
|
||||
|
||||
# Crear usuario en Keycloak
|
||||
user_id = keycloak_admin.create_user(user_data)
|
||||
|
||||
|
||||
# Asignar rol por defecto (user) - opcional, solo si existe
|
||||
try:
|
||||
user_role = keycloak_admin.get_realm_role("user")
|
||||
@@ -303,15 +311,16 @@ class AuthService:
|
||||
except KeycloakError as e:
|
||||
# El rol 'user' no existe, no es un error crítico
|
||||
logger.warning(f"Could not assign 'user' role: {str(e)}")
|
||||
|
||||
|
||||
# Agregar el usuario al tenant en la base de datos
|
||||
try:
|
||||
from api.v1.modules.a76.user_tenant.service import UserTenantService
|
||||
|
||||
user_tenant_service = UserTenantService(self.db)
|
||||
user_tenant_service.add_user_to_tenant(
|
||||
keycloak_user_id=user_id,
|
||||
tenant_id=tenant.id,
|
||||
role="user" # Rol por defecto
|
||||
role="user", # Rol por defecto
|
||||
)
|
||||
logger.info(f"Added user {user_id} to tenant {tenant.id} in database")
|
||||
except Exception as e:
|
||||
@@ -323,106 +332,116 @@ class AuthService:
|
||||
except:
|
||||
pass
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Failed to register user in database"
|
||||
status_code=500, detail="Failed to register user in database"
|
||||
)
|
||||
|
||||
logger.info(f"User registered: {register_data.username} (tenant: {tenant.slug}, user_id: {user_id})")
|
||||
|
||||
|
||||
logger.info(
|
||||
f"User registered: {register_data.username} (tenant: {tenant.slug}, user_id: {user_id})"
|
||||
)
|
||||
|
||||
return RegisterResponseDTO(
|
||||
user_id=user_id,
|
||||
username=register_data.username,
|
||||
email=register_data.email,
|
||||
message="User registered successfully"
|
||||
message="User registered successfully",
|
||||
)
|
||||
|
||||
|
||||
except KeycloakError as e:
|
||||
error_message = str(e)
|
||||
logger.warning(f"Keycloak registration failed: {error_message}")
|
||||
|
||||
|
||||
# Mensajes de error más específicos
|
||||
if "User exists" in error_message or "409" in error_message:
|
||||
raise HTTPException(status_code=409, detail="Username or email already exists")
|
||||
raise HTTPException(
|
||||
status_code=409, detail="Username or email already exists"
|
||||
)
|
||||
elif "Invalid" in error_message:
|
||||
raise HTTPException(status_code=400, detail="Invalid user data")
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Registration error")
|
||||
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Registration error: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Registration error")
|
||||
|
||||
|
||||
def exchange_code(self, exchange_data) -> TokenResponseDTO:
|
||||
"""
|
||||
Intercambia un authorization code por tokens
|
||||
|
||||
|
||||
Este método se usa cuando el frontend recibe un código de autorización
|
||||
después de un login con proveedor externo (Microsoft, Google, etc.)
|
||||
a través de Keycloak.
|
||||
|
||||
|
||||
Args:
|
||||
exchange_data: Datos del código y redirect_uri
|
||||
|
||||
|
||||
Returns:
|
||||
TokenResponseDTO con access_token y refresh_token
|
||||
|
||||
|
||||
Raises:
|
||||
HTTPException: Si el código es inválido o expiró
|
||||
"""
|
||||
try:
|
||||
# Importar el DTO aquí para evitar referencias circulares
|
||||
from .dto import ExchangeCodeRequestDTO
|
||||
|
||||
|
||||
# Intercambiar código por tokens usando Keycloak
|
||||
token_response = self.keycloak_openid.token(
|
||||
grant_type='authorization_code',
|
||||
grant_type="authorization_code",
|
||||
code=exchange_data.code,
|
||||
redirect_uri=exchange_data.redirect_uri
|
||||
redirect_uri=exchange_data.redirect_uri,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"Code exchanged successfully")
|
||||
|
||||
|
||||
# Si se proporciona tenant_slug, podríamos validar que el usuario pertenece a ese tenant
|
||||
# Por ahora simplemente retornamos los tokens
|
||||
if exchange_data.tenant_slug:
|
||||
# Decodificar token para obtener tenant_id del usuario
|
||||
user_info = self.keycloak_openid.introspect(token_response['access_token'])
|
||||
user_tenant_id = user_info.get('tenant_id')
|
||||
|
||||
user_info = self.keycloak_openid.introspect(
|
||||
token_response["access_token"]
|
||||
)
|
||||
user_tenant_id = user_info.get("tenant_id")
|
||||
|
||||
# Validar que el tenant existe y está activo
|
||||
from api.v1.modules.a76.tenants.service import TenantService
|
||||
|
||||
tenant_service = TenantService(self.db)
|
||||
tenant = tenant_service.get_tenant_by_slug(exchange_data.tenant_slug)
|
||||
|
||||
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="Tenant not found")
|
||||
|
||||
|
||||
if not tenant.is_active:
|
||||
raise HTTPException(status_code=403, detail="Tenant is not active")
|
||||
|
||||
|
||||
# Opcional: Verificar que el usuario pertenece al tenant
|
||||
# Esto depende de cómo manejes los tenants en tu aplicación
|
||||
|
||||
|
||||
return TokenResponseDTO(
|
||||
access_token=token_response['access_token'],
|
||||
refresh_token=token_response['refresh_token'],
|
||||
token_type=token_response.get('token_type', 'bearer'),
|
||||
expires_in=token_response.get('expires_in', 3600)
|
||||
access_token=token_response["access_token"],
|
||||
refresh_token=token_response["refresh_token"],
|
||||
token_type=token_response.get("token_type", "bearer"),
|
||||
expires_in=token_response.get("expires_in", 3600),
|
||||
)
|
||||
|
||||
|
||||
except KeycloakError as e:
|
||||
error_message = str(e)
|
||||
logger.warning(f"Code exchange failed: {error_message}")
|
||||
|
||||
|
||||
if "invalid_grant" in error_message.lower():
|
||||
raise HTTPException(status_code=400, detail="Invalid or expired authorization code")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="Invalid or expired authorization code"
|
||||
)
|
||||
elif "invalid_client" in error_message.lower():
|
||||
raise HTTPException(status_code=401, detail="Invalid client credentials")
|
||||
raise HTTPException(
|
||||
status_code=401, detail="Invalid client credentials"
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Token exchange error")
|
||||
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Módulo de Class
|
||||
"""
|
||||
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
"""
|
||||
Modelos ORM para gestión de clases SCAII y SCAF
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from sqlalchemy import Integer, String, SmallInteger, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
SmallInteger,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from core.database import Base
|
||||
|
||||
@@ -15,54 +24,78 @@ class Class(Base):
|
||||
"""
|
||||
Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF
|
||||
"""
|
||||
|
||||
__tablename__ = "classes"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='classes_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_classes_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_classes_company'),
|
||||
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], name='fk_classes_client'),
|
||||
ForeignKeyConstraint(['material_key'], ['public.material_types.key'], name='fk_classes_material_type'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'class_code', name='uq_classes_client_id_class_code'),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
PrimaryKeyConstraint("id", name="classes_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_classes_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_classes_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["client_id"], ["a76.client_provider.id"], name="fk_classes_client"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["material_key"],
|
||||
["public.material_types.key"],
|
||||
name="fk_classes_material_type",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"class_code",
|
||||
name="uq_classes_client_id_class_code",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
client_id: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
# Unique constraint compuesta
|
||||
class_code: Mapped[str] = mapped_column(String(8)) #CLASE
|
||||
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
client_id: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
# Unique constraint compuesta
|
||||
class_code: Mapped[str] = mapped_column(String(8)) # CLASE
|
||||
|
||||
# Basic information
|
||||
description_es: Mapped[Optional[str]] = mapped_column(String(500)) #DESCRIPCIONE
|
||||
description_en: Mapped[Optional[str]] = mapped_column(String(500)) #DESCRIPCIONI
|
||||
|
||||
description_es: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONE
|
||||
description_en: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONI
|
||||
|
||||
# Material and measurement
|
||||
material_key: Mapped[Optional[str]] = mapped_column(String(10), ForeignKey('public.material_types.key')) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMED - homologated from UNIMEDIDA
|
||||
|
||||
material_key: Mapped[Optional[str]] = mapped_column(
|
||||
String(10), ForeignKey("public.material_types.key")
|
||||
) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(
|
||||
String(5)
|
||||
) # UNIMED - homologated from UNIMEDIDA
|
||||
|
||||
# Tariff fractions
|
||||
fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCION
|
||||
us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAME - US tariff fraction
|
||||
|
||||
us_fraction: Mapped[Optional[str]] = mapped_column(
|
||||
String(16)
|
||||
) # FRACCIONAME - US tariff fraction
|
||||
|
||||
# Additional classification
|
||||
sub_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVESUB
|
||||
physical_review: Mapped[Optional[int]] = mapped_column(SmallInteger) # REVFISICA
|
||||
iva_exempt_fraction: Mapped[Optional[str]] = mapped_column(String(4)) # FRACCIONEXENTAIVA
|
||||
|
||||
iva_exempt_fraction: Mapped[Optional[str]] = mapped_column(
|
||||
String(4)
|
||||
) # FRACCIONEXENTAIVA
|
||||
|
||||
# Relationships
|
||||
material_type: Mapped[Optional["MaterialType"]] = relationship(foreign_keys=[material_key])
|
||||
|
||||
material_type: Mapped[Optional["MaterialType"]] = relationship(
|
||||
foreign_keys=[material_key]
|
||||
)
|
||||
|
||||
# Inverse relationship with GParts that have this class
|
||||
parts: Mapped[list["Part"]] = relationship(
|
||||
primaryjoin="and_(Class.client_id == Part.client_id, Class.class_code == Part.part_class)",
|
||||
foreign_keys="[Part.client_id, Part.part_class]",
|
||||
viewonly=True,
|
||||
back_populates="part_class_info"
|
||||
back_populates="part_class_info",
|
||||
)
|
||||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Class(client_id={self.client_id}, class_code='{self.class_code}', description='{self.description_es}')>"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Endpoints API para gestión de clases SCAII y SCAF
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
@@ -9,28 +10,33 @@ from core.database import get_core_db
|
||||
from core.security import get_current_user, has_role
|
||||
from .service import ClassService
|
||||
from .dto import (
|
||||
ClassCreateDTO,
|
||||
ClassUpdateDTO,
|
||||
ClassCreateDTO,
|
||||
ClassUpdateDTO,
|
||||
ClassResponseDTO,
|
||||
ClassBasicDTO,
|
||||
ClassListDTO,
|
||||
ClassSearchDTO
|
||||
ClassSearchDTO,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/classes", tags=["Classes"])
|
||||
|
||||
|
||||
@router.get("/", response_model=ClassListDTO)
|
||||
async def list_classes(
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
|
||||
limit: int = Query(
|
||||
100, ge=1, le=1000, description="Maximum number of records to return"
|
||||
),
|
||||
client_id: Optional[int] = Query(None, description="Filter by client key"),
|
||||
class_code: Optional[str] = Query(None, description="Search by class code"),
|
||||
description: Optional[str] = Query(None, description="Search in descriptions"),
|
||||
material_key: Optional[str] = Query(None, description="Filter by material key"),
|
||||
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"),
|
||||
physical_review: Optional[int] = Query(None, description="Filter by physical review indicator"),
|
||||
physical_review: Optional[int] = Query(
|
||||
None, description="Filter by physical review indicator"
|
||||
),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List classes with optional filters and pagination
|
||||
@@ -49,7 +55,7 @@ async def list_classes(
|
||||
description=description,
|
||||
material_key=material_key,
|
||||
fraction=fraction,
|
||||
physical_review=physical_review
|
||||
physical_review=physical_review,
|
||||
)
|
||||
return service.list_classes(skip, limit, search_params)
|
||||
|
||||
@@ -60,7 +66,7 @@ async def get_classes_by_client(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all classes for a specific client
|
||||
@@ -80,7 +86,7 @@ async def get_classes_by_client(
|
||||
async def search_by_fraction(
|
||||
fraction: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Search classes by tariff fraction
|
||||
@@ -93,7 +99,7 @@ async def search_by_fraction(
|
||||
async def search_by_material(
|
||||
material_key: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Search classes by material key
|
||||
@@ -102,11 +108,13 @@ async def search_by_material(
|
||||
return service.search_by_material(material_key)
|
||||
|
||||
|
||||
@router.get("/search/unit-measure/{unit_of_measure}", response_model=List[ClassBasicDTO])
|
||||
@router.get(
|
||||
"/search/unit-measure/{unit_of_measure}", response_model=List[ClassBasicDTO]
|
||||
)
|
||||
async def get_classes_by_unit_measure(
|
||||
unit_of_measure: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get classes by unit of measure
|
||||
@@ -115,11 +123,13 @@ async def get_classes_by_unit_measure(
|
||||
return service.get_classes_by_unit_measure(unit_of_measure)
|
||||
|
||||
|
||||
@router.get("/search/physical-review/{physical_review}", response_model=List[ClassBasicDTO])
|
||||
@router.get(
|
||||
"/search/physical-review/{physical_review}", response_model=List[ClassBasicDTO]
|
||||
)
|
||||
async def get_classes_by_physical_review(
|
||||
physical_review: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get classes by physical review indicator
|
||||
@@ -130,8 +140,7 @@ async def get_classes_by_physical_review(
|
||||
|
||||
@router.get("/statistics", response_model=dict)
|
||||
async def get_classes_statistics(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get basic classes statistics
|
||||
@@ -145,7 +154,7 @@ async def get_class(
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get class by composite key (client_id + class_code)
|
||||
@@ -154,16 +163,17 @@ async def get_class(
|
||||
class_obj = service.get_class(client_id, class_code)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
|
||||
)
|
||||
return class_obj
|
||||
|
||||
|
||||
@router.post("/", response_model=ClassResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||
async def create_class(
|
||||
class_data: ClassCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new class in the system
|
||||
@@ -171,13 +181,14 @@ async def create_class(
|
||||
service = ClassService(db)
|
||||
return service.create_class(class_data)
|
||||
|
||||
|
||||
@router.put("/{client_id}/{class_code}", response_model=ClassResponseDTO)
|
||||
async def update_class(
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
class_data: ClassUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update class information
|
||||
@@ -186,8 +197,8 @@ async def update_class(
|
||||
class_obj = service.update_class(client_id, class_code, class_data)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
|
||||
)
|
||||
return class_obj
|
||||
|
||||
@@ -197,18 +208,18 @@ async def delete_class(
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete class from the system
|
||||
|
||||
|
||||
Note: This will completely remove the class from the system.
|
||||
"""
|
||||
service = ClassService(db)
|
||||
if not service.delete_class(client_id, class_code):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
|
||||
)
|
||||
|
||||
|
||||
@@ -218,7 +229,7 @@ async def get_class_basic_info(
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get basic information for a class
|
||||
@@ -227,17 +238,17 @@ async def get_class_basic_info(
|
||||
class_obj = service.get_class(client_id, class_code)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
|
||||
)
|
||||
|
||||
|
||||
return ClassBasicDTO(
|
||||
client_id=class_obj.client_id,
|
||||
class_code=class_obj.class_code,
|
||||
description_spanish=class_obj.description_spanish,
|
||||
description_english=class_obj.description_english,
|
||||
material_key=class_obj.material_key,
|
||||
fraction=class_obj.fraction
|
||||
fraction=class_obj.fraction,
|
||||
)
|
||||
|
||||
|
||||
@@ -246,7 +257,7 @@ async def get_class_tariff_info(
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get tariff information for a class (fractions, IVA exempt, etc.)
|
||||
@@ -255,10 +266,10 @@ async def get_class_tariff_info(
|
||||
class_obj = service.get_class(client_id, class_code)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
status_code=404,
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"client_id": class_obj.client_id,
|
||||
"class_code": class_obj.class_code,
|
||||
@@ -266,7 +277,5 @@ async def get_class_tariff_info(
|
||||
"us_fraction": class_obj.us_fraction,
|
||||
"iva_exempt_fraction": class_obj.iva_exempt_fraction,
|
||||
"sub_key": class_obj.sub_key,
|
||||
"physical_review": class_obj.physical_review
|
||||
"physical_review": class_obj.physical_review,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Capa de servicio para lógica de negocio de clases SCAII y SCAF
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy import or_, and_, func
|
||||
@@ -10,12 +11,12 @@ import logging
|
||||
|
||||
from .models import Class
|
||||
from .dto import (
|
||||
ClassCreateDTO,
|
||||
ClassUpdateDTO,
|
||||
ClassCreateDTO,
|
||||
ClassUpdateDTO,
|
||||
ClassResponseDTO,
|
||||
ClassBasicDTO,
|
||||
ClassListDTO,
|
||||
ClassSearchDTO
|
||||
ClassSearchDTO,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -23,38 +24,42 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ClassService:
|
||||
"""Servicio para gestión de clases SCAII y SCAF"""
|
||||
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
|
||||
def create_class(self, class_data: ClassCreateDTO) -> ClassResponseDTO:
|
||||
"""
|
||||
Crea una nueva clase en el sistema
|
||||
|
||||
|
||||
Args:
|
||||
class_data: Datos de la clase a crear
|
||||
|
||||
|
||||
Returns:
|
||||
ClassResponseDTO con información de la clase creada
|
||||
|
||||
|
||||
Raises:
|
||||
HTTPException: Si la clase ya existe o error en la creación
|
||||
"""
|
||||
try:
|
||||
# Verificar que no exista la clase
|
||||
existing = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_id == class_data.client_id,
|
||||
Class.class_code == class_data.class_code
|
||||
existing = (
|
||||
self.db.query(Class)
|
||||
.filter(
|
||||
and_(
|
||||
Class.client_id == class_data.client_id,
|
||||
Class.class_code == class_data.class_code,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Class with client_id '{class_data.client_id}' and class_code '{class_data.class_code}' already exists"
|
||||
status_code=400,
|
||||
detail=f"Class with client_id '{class_data.client_id}' and class_code '{class_data.class_code}' already exists",
|
||||
)
|
||||
|
||||
|
||||
# Crear clase
|
||||
db_class = Class(
|
||||
client_id=class_data.client_id,
|
||||
@@ -67,171 +72,181 @@ class ClassService:
|
||||
us_fraction=class_data.us_fraction,
|
||||
sub_key=class_data.sub_key,
|
||||
physical_review=class_data.physical_review,
|
||||
iva_exempt_fraction=class_data.iva_exempt_fraction
|
||||
iva_exempt_fraction=class_data.iva_exempt_fraction,
|
||||
)
|
||||
|
||||
|
||||
self.db.add(db_class)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_class)
|
||||
|
||||
|
||||
logger.info(f"Class created: {db_class.client_id}-{db_class.class_code}")
|
||||
|
||||
|
||||
return ClassResponseDTO.model_validate(db_class)
|
||||
|
||||
|
||||
except IntegrityError as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"IntegrityError creating class: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail="Class with this client_id and class_code already exists")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Class with this client_id and class_code already exists",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error creating class: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error creating class")
|
||||
|
||||
|
||||
def get_class(self, client_id: int, class_code: str) -> Optional[ClassResponseDTO]:
|
||||
"""
|
||||
Obtiene una clase por clave compuesta
|
||||
|
||||
|
||||
Args:
|
||||
client_id: Clave del cliente
|
||||
class_code: Código de clase
|
||||
|
||||
|
||||
Returns:
|
||||
ClassResponseDTO o None si no existe
|
||||
"""
|
||||
class_obj = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_id == client_id,
|
||||
Class.class_code == class_code
|
||||
)
|
||||
).first()
|
||||
|
||||
class_obj = (
|
||||
self.db.query(Class)
|
||||
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
|
||||
.first()
|
||||
)
|
||||
|
||||
if not class_obj:
|
||||
return None
|
||||
return ClassResponseDTO.model_validate(class_obj)
|
||||
|
||||
|
||||
def list_classes(
|
||||
self,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search_params: Optional[ClassSearchDTO] = None
|
||||
self,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search_params: Optional[ClassSearchDTO] = None,
|
||||
) -> ClassListDTO:
|
||||
"""
|
||||
Lista clases con filtros
|
||||
|
||||
|
||||
Args:
|
||||
skip: Número de registros a omitir
|
||||
limit: Número máximo de registros a retornar
|
||||
search_params: Parámetros de búsqueda
|
||||
|
||||
|
||||
Returns:
|
||||
ClassListDTO con la lista paginada
|
||||
"""
|
||||
query = self.db.query(Class)
|
||||
|
||||
|
||||
# Aplicar filtros si se proporcionan
|
||||
if search_params:
|
||||
if search_params.client_id:
|
||||
query = query.filter(Class.client_id == search_params.client_id)
|
||||
|
||||
|
||||
if search_params.class_code:
|
||||
query = query.filter(Class.class_code.ilike(f"%{search_params.class_code}%"))
|
||||
|
||||
query = query.filter(
|
||||
Class.class_code.ilike(f"%{search_params.class_code}%")
|
||||
)
|
||||
|
||||
if search_params.description:
|
||||
description_pattern = f"%{search_params.description}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
Class.description_spanish.ilike(description_pattern),
|
||||
Class.description_english.ilike(description_pattern)
|
||||
Class.description_english.ilike(description_pattern),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if search_params.material_key:
|
||||
query = query.filter(Class.material_key.ilike(f"%{search_params.material_key}%"))
|
||||
|
||||
query = query.filter(
|
||||
Class.material_key.ilike(f"%{search_params.material_key}%")
|
||||
)
|
||||
|
||||
if search_params.fraction:
|
||||
query = query.filter(Class.fraction.ilike(f"%{search_params.fraction}%"))
|
||||
|
||||
query = query.filter(
|
||||
Class.fraction.ilike(f"%{search_params.fraction}%")
|
||||
)
|
||||
|
||||
if search_params.physical_review is not None:
|
||||
query = query.filter(Class.physical_review == search_params.physical_review)
|
||||
|
||||
query = query.filter(
|
||||
Class.physical_review == search_params.physical_review
|
||||
)
|
||||
|
||||
# Contar total
|
||||
total = query.count()
|
||||
|
||||
|
||||
# Aplicar paginación
|
||||
classes = query.offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
# Convertir a DTOs básicos
|
||||
class_dtos = [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||
|
||||
|
||||
return ClassListDTO(
|
||||
classes=class_dtos,
|
||||
total=total,
|
||||
page=(skip // limit) + 1 if limit > 0 else 1,
|
||||
size=len(class_dtos)
|
||||
size=len(class_dtos),
|
||||
)
|
||||
|
||||
def update_class(self, client_id: int, class_code: str, class_data: ClassUpdateDTO) -> Optional[ClassResponseDTO]:
|
||||
|
||||
def update_class(
|
||||
self, client_id: int, class_code: str, class_data: ClassUpdateDTO
|
||||
) -> Optional[ClassResponseDTO]:
|
||||
"""
|
||||
Actualiza una clase
|
||||
|
||||
|
||||
Args:
|
||||
client_id: Clave del cliente
|
||||
class_code: Código de clase
|
||||
class_data: Datos a actualizar
|
||||
|
||||
|
||||
Returns:
|
||||
ClassResponseDTO actualizado o None si no existe
|
||||
"""
|
||||
class_obj = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_id == client_id,
|
||||
Class.class_code == class_code
|
||||
)
|
||||
).first()
|
||||
|
||||
class_obj = (
|
||||
self.db.query(Class)
|
||||
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
|
||||
.first()
|
||||
)
|
||||
|
||||
if not class_obj:
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
# Actualizar solo campos proporcionados
|
||||
update_data = class_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(class_obj, field, value)
|
||||
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(class_obj)
|
||||
logger.info(f"Class updated: {client_id}-{class_code}")
|
||||
|
||||
|
||||
return ClassResponseDTO.model_validate(class_obj)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error updating class {client_id}-{class_code}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error updating class")
|
||||
|
||||
|
||||
def delete_class(self, client_id: int, class_code: str) -> bool:
|
||||
"""
|
||||
Elimina una clase
|
||||
|
||||
|
||||
Args:
|
||||
client_id: Clave del cliente
|
||||
class_code: Código de clase
|
||||
|
||||
|
||||
Returns:
|
||||
True si se eliminó, False si no existe
|
||||
"""
|
||||
class_obj = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_id == client_id,
|
||||
Class.class_code == class_code
|
||||
)
|
||||
).first()
|
||||
|
||||
class_obj = (
|
||||
self.db.query(Class)
|
||||
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
|
||||
.first()
|
||||
)
|
||||
|
||||
if not class_obj:
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
self.db.delete(class_obj)
|
||||
self.db.commit()
|
||||
@@ -241,54 +256,75 @@ class ClassService:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error deleting class {client_id}-{class_code}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error deleting class")
|
||||
|
||||
|
||||
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
|
||||
"""Busca clases por fracción arancelaria"""
|
||||
classes = self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all()
|
||||
classes = (
|
||||
self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all()
|
||||
)
|
||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||
|
||||
def search_by_client(self, client_id: int, skip: int = 0, limit: int = 100) -> List[ClassBasicDTO]:
|
||||
|
||||
def search_by_client(
|
||||
self, client_id: int, skip: int = 0, limit: int = 100
|
||||
) -> List[ClassBasicDTO]:
|
||||
"""Obtiene todas las clases de un cliente específico"""
|
||||
classes = self.db.query(Class).filter(Class.client_id == client_id).offset(skip).limit(limit).all()
|
||||
classes = (
|
||||
self.db.query(Class)
|
||||
.filter(Class.client_id == client_id)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||
|
||||
|
||||
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
|
||||
"""Busca clases por clave de material"""
|
||||
classes = self.db.query(Class).filter(Class.material_key.ilike(f"%{material_key}%")).all()
|
||||
classes = (
|
||||
self.db.query(Class)
|
||||
.filter(Class.material_key.ilike(f"%{material_key}%"))
|
||||
.all()
|
||||
)
|
||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||
|
||||
def get_classes_by_physical_review(self, physical_review: int) -> List[ClassBasicDTO]:
|
||||
|
||||
def get_classes_by_physical_review(
|
||||
self, physical_review: int
|
||||
) -> List[ClassBasicDTO]:
|
||||
"""Obtiene clases por indicador de revisión física"""
|
||||
classes = self.db.query(Class).filter(Class.physical_review == physical_review).all()
|
||||
classes = (
|
||||
self.db.query(Class).filter(Class.physical_review == physical_review).all()
|
||||
)
|
||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||
|
||||
|
||||
def get_classes_statistics(self) -> dict:
|
||||
"""Obtiene estadísticas básicas de clases"""
|
||||
total_classes = self.db.query(Class).count()
|
||||
|
||||
|
||||
# Contar por clientes
|
||||
clients_count = self.db.query(Class.client_id).distinct().count()
|
||||
|
||||
|
||||
# Contar por revisión física
|
||||
physical_review_stats = {}
|
||||
for i in range(3): # Asumiendo valores 0, 1, 2
|
||||
count = self.db.query(Class).filter(Class.physical_review == i).count()
|
||||
physical_review_stats[f"physical_review_{i}"] = count
|
||||
|
||||
|
||||
# Contar clases con fracciones
|
||||
with_fraction = self.db.query(Class).filter(Class.fraction.isnot(None)).count()
|
||||
with_us_fraction = self.db.query(Class).filter(Class.us_fraction.isnot(None)).count()
|
||||
|
||||
with_us_fraction = (
|
||||
self.db.query(Class).filter(Class.us_fraction.isnot(None)).count()
|
||||
)
|
||||
|
||||
return {
|
||||
"total_classes": total_classes,
|
||||
"clients_with_classes": clients_count,
|
||||
"classes_with_fraction": with_fraction,
|
||||
"classes_with_us_fraction": with_us_fraction,
|
||||
**physical_review_stats
|
||||
**physical_review_stats,
|
||||
}
|
||||
|
||||
|
||||
def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]:
|
||||
"""Obtiene clases por unidad de medida"""
|
||||
classes = self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all()
|
||||
classes = (
|
||||
self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all()
|
||||
)
|
||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Módulo de Client & Provider
|
||||
"""
|
||||
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
DTOs (Data Transfer Objects) para módulo de clientes y proveedores
|
||||
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, EmailStr
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
@@ -11,11 +12,18 @@ from decimal import Decimal
|
||||
# DTOs para dirección
|
||||
class ClientProviderAddressDTO(BaseModel):
|
||||
"""DTO para dirección de cliente/proveedor"""
|
||||
municipality: Optional[str] = Field(None, max_length=150, description="Municipality")
|
||||
|
||||
municipality: Optional[str] = Field(
|
||||
None, max_length=150, description="Municipality"
|
||||
)
|
||||
streets: Optional[str] = Field(None, max_length=100, description="Streets")
|
||||
neighborhood: Optional[str] = Field(None, max_length=40, description="Neighborhood")
|
||||
interior_number: Optional[str] = Field(None, max_length=20, description="Interior number")
|
||||
exterior_number: Optional[str] = Field(None, max_length=20, description="Exterior number")
|
||||
interior_number: Optional[str] = Field(
|
||||
None, max_length=20, description="Interior number"
|
||||
)
|
||||
exterior_number: Optional[str] = Field(
|
||||
None, max_length=20, description="Exterior number"
|
||||
)
|
||||
postal_code: Optional[str] = Field(None, max_length=15, description="Postal code")
|
||||
city: Optional[str] = Field(None, max_length=30, description="City")
|
||||
state: Optional[str] = Field(None, max_length=30, description="State")
|
||||
@@ -33,26 +41,49 @@ class ClientProviderAddressDTO(BaseModel):
|
||||
# DTOs para programas
|
||||
class ClientProviderProgramsDTO(BaseModel):
|
||||
"""DTO para programas de cliente/proveedor"""
|
||||
|
||||
program: Optional[str] = Field(None, max_length=7, description="Program")
|
||||
program_number: Optional[str] = Field(None, max_length=40, description="Program number")
|
||||
program_number: Optional[str] = Field(
|
||||
None, max_length=40, description="Program number"
|
||||
)
|
||||
prosec: Optional[int] = Field(None, description="PROSEC")
|
||||
prosec_authorization: Optional[str] = Field(None, max_length=20, description="PROSEC authorization")
|
||||
prosec_authorization: Optional[str] = Field(
|
||||
None, max_length=20, description="PROSEC authorization"
|
||||
)
|
||||
secon_auth_date: Optional[int] = Field(None, description="SECON authorization date")
|
||||
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID")
|
||||
manufacturer_id: Optional[str] = Field(
|
||||
None, max_length=25, description="Manufacturer ID"
|
||||
)
|
||||
tax_id: Optional[str] = Field(None, max_length=30, description="Tax ID")
|
||||
broker: Optional[str] = Field(None, max_length=6, description="Broker")
|
||||
import_broker: Optional[str] = Field(None, max_length=6, description="Import broker")
|
||||
import_broker: Optional[str] = Field(
|
||||
None, max_length=6, description="Import broker"
|
||||
)
|
||||
transfer_key: Optional[str] = Field(None, max_length=8, description="Transfer key")
|
||||
secon_authorization: Optional[str] = Field(None, max_length=20, description="SECON authorization")
|
||||
applied_proportion: Optional[Decimal] = Field(None, description="Applied proportion")
|
||||
is_certified_company: Optional[str] = Field(None, max_length=1, description="Is certified company")
|
||||
certified_company_registry: Optional[str] = Field(None, max_length=40, description="Certified company registry")
|
||||
donation_auth_number: Optional[str] = Field(None, max_length=50, description="Donation authorization number")
|
||||
secon_authorization: Optional[str] = Field(
|
||||
None, max_length=20, description="SECON authorization"
|
||||
)
|
||||
applied_proportion: Optional[Decimal] = Field(
|
||||
None, description="Applied proportion"
|
||||
)
|
||||
is_certified_company: Optional[str] = Field(
|
||||
None, max_length=1, description="Is certified company"
|
||||
)
|
||||
certified_company_registry: Optional[str] = Field(
|
||||
None, max_length=40, description="Certified company registry"
|
||||
)
|
||||
donation_auth_number: Optional[str] = Field(
|
||||
None, max_length=50, description="Donation authorization number"
|
||||
)
|
||||
ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI")
|
||||
tax_registry_number: Optional[str] = Field(None, max_length=40, description="Tax registry number")
|
||||
tax_registry_number: Optional[str] = Field(
|
||||
None, max_length=40, description="Tax registry number"
|
||||
)
|
||||
subassembly_service: Optional[int] = Field(None, description="Subassembly service")
|
||||
autse_dates: Optional[int] = Field(None, description="AUTSE dates")
|
||||
autse_number: Optional[str] = Field(None, max_length=300, description="AUTSE number")
|
||||
autse_number: Optional[str] = Field(
|
||||
None, max_length=300, description="AUTSE number"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -61,26 +92,43 @@ class ClientProviderProgramsDTO(BaseModel):
|
||||
# DTOs principales
|
||||
class ClientProviderCreateDTO(BaseModel):
|
||||
"""DTO para crear cliente/proveedor"""
|
||||
|
||||
client_id: str = Field(..., max_length=8, description="Client ID")
|
||||
type_nat_foreign: Optional[str] = Field(None, max_length=1, description="Type national/foreign")
|
||||
type_nat_foreign: Optional[str] = Field(
|
||||
None, max_length=1, description="Type national/foreign"
|
||||
)
|
||||
name: Optional[str] = Field(None, max_length=256, description="Name")
|
||||
short_name: Optional[str] = Field(None, max_length=10, description="Short name")
|
||||
rfc: Optional[str] = Field(None, max_length=30, description="RFC")
|
||||
curp: Optional[str] = Field(None, max_length=19, description="CURP")
|
||||
client_or_provider: Optional[str] = Field(None, max_length=1, description="Client or provider")
|
||||
client_or_provider: Optional[str] = Field(
|
||||
None, max_length=1, description="Client or provider"
|
||||
)
|
||||
linking: Optional[str] = Field(None, max_length=1, description="Linking")
|
||||
transform_subassembly: Optional[str] = Field(None, max_length=1, description="Transform subassembly")
|
||||
extra_information: Optional[str] = Field(None, max_length=399, description="Extra information")
|
||||
transform_subassembly: Optional[str] = Field(
|
||||
None, max_length=1, description="Transform subassembly"
|
||||
)
|
||||
extra_information: Optional[str] = Field(
|
||||
None, max_length=399, description="Extra information"
|
||||
)
|
||||
web_key: Optional[str] = Field(None, max_length=40, description="Web key")
|
||||
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
|
||||
responsible: Optional[str] = Field(
|
||||
None, max_length=80, description="Responsible person"
|
||||
)
|
||||
position: Optional[str] = Field(None, max_length=30, description="Position")
|
||||
incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm")
|
||||
is_national_provider: Optional[str] = Field(None, max_length=2, description="Is national provider")
|
||||
is_national_provider: Optional[str] = Field(
|
||||
None, max_length=2, description="Is national provider"
|
||||
)
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
|
||||
|
||||
# Nested DTOs
|
||||
address: Optional[ClientProviderAddressDTO] = Field(None, description="Address information")
|
||||
programs: Optional[ClientProviderProgramsDTO] = Field(None, description="Programs information")
|
||||
address: Optional[ClientProviderAddressDTO] = Field(
|
||||
None, description="Address information"
|
||||
)
|
||||
programs: Optional[ClientProviderProgramsDTO] = Field(
|
||||
None, description="Programs information"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -88,25 +136,42 @@ class ClientProviderCreateDTO(BaseModel):
|
||||
|
||||
class ClientProviderUpdateDTO(BaseModel):
|
||||
"""DTO para actualizar cliente/proveedor"""
|
||||
type_nat_foreign: Optional[str] = Field(None, max_length=1, description="Type national/foreign")
|
||||
|
||||
type_nat_foreign: Optional[str] = Field(
|
||||
None, max_length=1, description="Type national/foreign"
|
||||
)
|
||||
name: Optional[str] = Field(None, max_length=256, description="Name")
|
||||
short_name: Optional[str] = Field(None, max_length=10, description="Short name")
|
||||
rfc: Optional[str] = Field(None, max_length=30, description="RFC")
|
||||
curp: Optional[str] = Field(None, max_length=19, description="CURP")
|
||||
client_or_provider: Optional[str] = Field(None, max_length=1, description="Client or provider")
|
||||
client_or_provider: Optional[str] = Field(
|
||||
None, max_length=1, description="Client or provider"
|
||||
)
|
||||
linking: Optional[str] = Field(None, max_length=1, description="Linking")
|
||||
transform_subassembly: Optional[str] = Field(None, max_length=1, description="Transform subassembly")
|
||||
extra_information: Optional[str] = Field(None, max_length=399, description="Extra information")
|
||||
transform_subassembly: Optional[str] = Field(
|
||||
None, max_length=1, description="Transform subassembly"
|
||||
)
|
||||
extra_information: Optional[str] = Field(
|
||||
None, max_length=399, description="Extra information"
|
||||
)
|
||||
web_key: Optional[str] = Field(None, max_length=40, description="Web key")
|
||||
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
|
||||
responsible: Optional[str] = Field(
|
||||
None, max_length=80, description="Responsible person"
|
||||
)
|
||||
position: Optional[str] = Field(None, max_length=30, description="Position")
|
||||
incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm")
|
||||
is_national_provider: Optional[str] = Field(None, max_length=2, description="Is national provider")
|
||||
is_national_provider: Optional[str] = Field(
|
||||
None, max_length=2, description="Is national provider"
|
||||
)
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
|
||||
|
||||
# Nested DTOs
|
||||
address: Optional[ClientProviderAddressDTO] = Field(None, description="Address information")
|
||||
programs: Optional[ClientProviderProgramsDTO] = Field(None, description="Programs information")
|
||||
address: Optional[ClientProviderAddressDTO] = Field(
|
||||
None, description="Address information"
|
||||
)
|
||||
programs: Optional[ClientProviderProgramsDTO] = Field(
|
||||
None, description="Programs information"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -114,6 +179,7 @@ class ClientProviderUpdateDTO(BaseModel):
|
||||
|
||||
class ClientProviderResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de cliente/proveedor"""
|
||||
|
||||
client_id: str
|
||||
type_nat_foreign: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
@@ -130,7 +196,7 @@ class ClientProviderResponseDTO(BaseModel):
|
||||
incoterm: Optional[str] = None
|
||||
is_national_provider: Optional[str] = None
|
||||
enabled_disabled: Optional[int] = None
|
||||
|
||||
|
||||
# Nested DTOs
|
||||
address: Optional[ClientProviderAddressDTO] = None
|
||||
programs: Optional[ClientProviderProgramsDTO] = None
|
||||
@@ -142,6 +208,7 @@ class ClientProviderResponseDTO(BaseModel):
|
||||
# DTOs para respuestas específicas
|
||||
class ClientProviderBasicDTO(BaseModel):
|
||||
"""DTO para información básica de cliente/proveedor"""
|
||||
|
||||
client_id: str
|
||||
name: Optional[str] = None
|
||||
short_name: Optional[str] = None
|
||||
@@ -155,6 +222,7 @@ class ClientProviderBasicDTO(BaseModel):
|
||||
|
||||
class ClientProviderListDTO(BaseModel):
|
||||
"""DTO para lista de clientes/proveedores"""
|
||||
|
||||
clients: list[ClientProviderBasicDTO]
|
||||
total: int
|
||||
page: int
|
||||
@@ -162,4 +230,3 @@ class ClientProviderListDTO(BaseModel):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
"""
|
||||
Modelos ORM para gestión de clientes y proveedores
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Integer, String, SmallInteger, Numeric, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
SmallInteger,
|
||||
Numeric,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from core.database import Base
|
||||
|
||||
@@ -12,21 +21,28 @@ class ClientProvider(Base):
|
||||
"""
|
||||
Modelo para la tabla GClientesPro - Información de clientes y proveedores
|
||||
"""
|
||||
|
||||
__tablename__ = "client_provider"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='client_provider_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_client_provider_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_client_provider_company'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="client_provider_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_client_provider_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_client_provider_company"
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
# Primary key
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
# Basic information
|
||||
type_nat_foreign: Mapped[Optional[str]] = mapped_column(String(1)) # TIPO NACIONAL/EXTRANJERO
|
||||
type_nat_foreign: Mapped[Optional[str]] = mapped_column(
|
||||
String(1)
|
||||
) # TIPO NACIONAL/EXTRANJERO
|
||||
name: Mapped[Optional[str]] = mapped_column(String(256))
|
||||
short_name: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
rfc: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
@@ -40,31 +56,45 @@ class ClientProvider(Base):
|
||||
position: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
incoterm: Mapped[Optional[str]] = mapped_column(String(19))
|
||||
is_national_provider: Mapped[Optional[str]] = mapped_column(String(2))
|
||||
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
|
||||
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
|
||||
# Relationships
|
||||
address: Mapped[Optional["ClientProviderAddress"]] = relationship(back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
programs: Mapped[Optional["ClientProviderPrograms"]] = relationship(back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
address: Mapped[Optional["ClientProviderAddress"]] = relationship(
|
||||
back_populates="client_provider", uselist=False, cascade="all, delete-orphan"
|
||||
)
|
||||
programs: Mapped[Optional["ClientProviderPrograms"]] = relationship(
|
||||
back_populates="client_provider", uselist=False, cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
|
||||
class ClientProviderAddress(Base):
|
||||
"""
|
||||
Modelo para la tabla GClientesPro_Direccion - Dirección de clientes y proveedores
|
||||
"""
|
||||
|
||||
__tablename__ = "client_provider_address"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='client_provider_address_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_client_provider_address_tenant'),
|
||||
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], ondelete='CASCADE', name='fk_client_provider_address_client'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="client_provider_address_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_client_provider_address_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["client_id"],
|
||||
["a76.client_provider.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_client_provider_address_client",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
# Primary key (foreign key)
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
client_id: Mapped[int] = mapped_column(Integer, ForeignKey('a76.client_provider.id', ondelete='CASCADE'))
|
||||
|
||||
client_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("a76.client_provider.id", ondelete="CASCADE")
|
||||
)
|
||||
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
# Address information
|
||||
municipality: Mapped[Optional[str]] = mapped_column(String(150))
|
||||
streets: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
@@ -80,7 +110,7 @@ class ClientProviderAddress(Base):
|
||||
email: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
contact: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
reference: Mapped[Optional[str]] = mapped_column(String(250))
|
||||
|
||||
|
||||
# Relationship
|
||||
client_provider: Mapped["ClientProvider"] = relationship(back_populates="address")
|
||||
|
||||
@@ -89,20 +119,30 @@ class ClientProviderPrograms(Base):
|
||||
"""
|
||||
Modelo para la tabla GClientesPro_Programas - Programas de clientes y proveedores
|
||||
"""
|
||||
|
||||
__tablename__ = "client_provider_programs"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='client_provider_programs_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_client_provider_programs_tenant'),
|
||||
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], ondelete='CASCADE', name='fk_client_provider_programs_client'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="client_provider_programs_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_client_provider_programs_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["client_id"],
|
||||
["a76.client_provider.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_client_provider_programs_client",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
# Primary key (foreign key)
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
client_id: Mapped[int] = mapped_column(Integer, ForeignKey('a76.client_provider.id', ondelete='CASCADE'))
|
||||
|
||||
client_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("a76.client_provider.id", ondelete="CASCADE")
|
||||
)
|
||||
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
# Program information
|
||||
program: Mapped[Optional[str]] = mapped_column(String(7))
|
||||
program_number: Mapped[Optional[str]] = mapped_column(String(40))
|
||||
@@ -124,8 +164,6 @@ class ClientProviderPrograms(Base):
|
||||
subassembly_service: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
autse_dates: Mapped[Optional[int]] = mapped_column()
|
||||
autse_number: Mapped[Optional[str]] = mapped_column(String(300))
|
||||
|
||||
|
||||
# Relationship
|
||||
client_provider: Mapped["ClientProvider"] = relationship(back_populates="programs")
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Endpoints API para gestión de clientes y proveedores
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
@@ -9,21 +10,23 @@ from core.database import get_core_db
|
||||
from core.security import get_current_user, has_role
|
||||
from .service import ClientProviderService
|
||||
from .dto import (
|
||||
ClientProviderCreateDTO,
|
||||
ClientProviderUpdateDTO,
|
||||
ClientProviderCreateDTO,
|
||||
ClientProviderUpdateDTO,
|
||||
ClientProviderResponseDTO,
|
||||
ClientProviderBasicDTO,
|
||||
ClientProviderListDTO
|
||||
ClientProviderListDTO,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/clients-providers")
|
||||
|
||||
|
||||
@router.post("/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_client_provider(
|
||||
client_data: ClientProviderCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new client or provider in the system
|
||||
@@ -46,12 +49,16 @@ async def create_client_provider(
|
||||
@router.get("/", response_model=ClientProviderListDTO)
|
||||
async def list_clients_providers(
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
|
||||
limit: int = Query(
|
||||
100, ge=1, le=1000, description="Maximum number of records to return"
|
||||
),
|
||||
search: Optional[str] = Query(None, description="Search text for name, RFC, or ID"),
|
||||
client_or_provider: Optional[str] = Query(None, regex="^[CP]$", description="Filter by type: C=Client, P=Provider"),
|
||||
client_or_provider: Optional[str] = Query(
|
||||
None, regex="^[CP]$", description="Filter by type: C=Client, P=Provider"
|
||||
),
|
||||
enabled_only: bool = Query(False, description="Show only enabled records"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List clients and providers with optional filters and pagination
|
||||
@@ -64,7 +71,9 @@ async def list_clients_providers(
|
||||
raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found")
|
||||
|
||||
service = ClientProviderService(db)
|
||||
return service.list_clients_providers(skip, limit, search, client_or_provider, enabled_only)
|
||||
return service.list_clients_providers(
|
||||
skip, limit, search, client_or_provider, enabled_only
|
||||
)
|
||||
|
||||
|
||||
@router.get("/clients", response_model=List[ClientProviderBasicDTO])
|
||||
@@ -72,7 +81,7 @@ async def get_clients_only(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get only clients (client_or_provider = 'C')
|
||||
@@ -93,7 +102,7 @@ async def get_providers_only(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get only providers (client_or_provider = 'P')
|
||||
@@ -113,7 +122,7 @@ async def get_providers_only(
|
||||
async def search_by_rfc(
|
||||
rfc: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Search clients/providers by RFC
|
||||
@@ -133,7 +142,7 @@ async def search_by_rfc(
|
||||
async def get_client_provider(
|
||||
client_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get client/provider by ID with all related information
|
||||
@@ -148,7 +157,9 @@ async def get_client_provider(
|
||||
service = ClientProviderService(db)
|
||||
client = service.get_client_provider(client_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
@@ -157,7 +168,7 @@ async def update_client_provider(
|
||||
client_id: str,
|
||||
client_data: ClientProviderUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update client/provider information
|
||||
@@ -172,7 +183,9 @@ async def update_client_provider(
|
||||
service = ClientProviderService(db)
|
||||
client = service.update_client_provider(client_id, client_data)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
@@ -180,11 +193,11 @@ async def update_client_provider(
|
||||
async def delete_client_provider(
|
||||
client_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete client/provider from the system
|
||||
|
||||
|
||||
Note: This will completely remove the client/provider and all related data.
|
||||
"""
|
||||
# Validate access to the tenant and company
|
||||
@@ -196,14 +209,16 @@ async def delete_client_provider(
|
||||
|
||||
service = ClientProviderService(db)
|
||||
if not service.delete_client_provider(client_id):
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO)
|
||||
async def toggle_client_provider_status(
|
||||
client_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Toggle client/provider enabled/disabled status
|
||||
@@ -218,7 +233,9 @@ async def toggle_client_provider_status(
|
||||
service = ClientProviderService(db)
|
||||
client = service.toggle_status(client_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
@@ -227,7 +244,7 @@ async def toggle_client_provider_status(
|
||||
async def get_client_provider_address(
|
||||
client_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get only address information for a client/provider
|
||||
@@ -242,19 +259,18 @@ async def get_client_provider_address(
|
||||
service = ClientProviderService(db)
|
||||
client = service.get_client_provider(client_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
|
||||
return {
|
||||
"client_id": client.client_id,
|
||||
"address": client.address
|
||||
}
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
|
||||
return {"client_id": client.client_id, "address": client.address}
|
||||
|
||||
|
||||
@router.get("/{client_id}/programs", response_model=dict)
|
||||
async def get_client_provider_programs(
|
||||
client_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get only programs information for a client/provider
|
||||
@@ -269,19 +285,18 @@ async def get_client_provider_programs(
|
||||
service = ClientProviderService(db)
|
||||
client = service.get_client_provider(client_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
|
||||
return {
|
||||
"client_id": client.client_id,
|
||||
"programs": client.programs
|
||||
}
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
|
||||
return {"client_id": client.client_id, "programs": client.programs}
|
||||
|
||||
|
||||
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO)
|
||||
async def get_client_provider_basic_info(
|
||||
client_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get basic information for a client/provider (without address and programs)
|
||||
@@ -296,14 +311,15 @@ async def get_client_provider_basic_info(
|
||||
service = ClientProviderService(db)
|
||||
client = service.get_client_provider(client_id)
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
|
||||
)
|
||||
|
||||
return ClientProviderBasicDTO(
|
||||
client_id=client.client_id,
|
||||
name=client.name,
|
||||
short_name=client.short_name,
|
||||
rfc=client.rfc,
|
||||
client_or_provider=client.client_or_provider,
|
||||
enabled_disabled=client.enabled_disabled
|
||||
enabled_disabled=client.enabled_disabled,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Capa de servicio para lógica de negocio de clientes y proveedores
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy import or_, and_
|
||||
@@ -10,13 +11,13 @@ import logging
|
||||
|
||||
from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms
|
||||
from .dto import (
|
||||
ClientProviderCreateDTO,
|
||||
ClientProviderUpdateDTO,
|
||||
ClientProviderCreateDTO,
|
||||
ClientProviderUpdateDTO,
|
||||
ClientProviderResponseDTO,
|
||||
ClientProviderBasicDTO,
|
||||
ClientProviderListDTO,
|
||||
ClientProviderAddressDTO,
|
||||
ClientProviderProgramsDTO
|
||||
ClientProviderProgramsDTO,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -24,29 +25,38 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ClientProviderService:
|
||||
"""Servicio para gestión de clientes y proveedores"""
|
||||
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def create_client_provider(self, client_data: ClientProviderCreateDTO) -> ClientProviderResponseDTO:
|
||||
|
||||
def create_client_provider(
|
||||
self, client_data: ClientProviderCreateDTO
|
||||
) -> ClientProviderResponseDTO:
|
||||
"""
|
||||
Crea un nuevo cliente/proveedor en el sistema
|
||||
|
||||
|
||||
Args:
|
||||
client_data: Datos del cliente/proveedor a crear
|
||||
|
||||
|
||||
Returns:
|
||||
ClientProviderResponseDTO con información del cliente/proveedor creado
|
||||
|
||||
|
||||
Raises:
|
||||
HTTPException: Si el cliente ya existe o error en la creación
|
||||
"""
|
||||
try:
|
||||
# Verificar que no exista el cliente
|
||||
existing = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_data.client_id).first()
|
||||
existing = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.client_id == client_data.client_id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"Client with ID '{client_data.client_id}' already exists")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Client with ID '{client_data.client_id}' already exists",
|
||||
)
|
||||
|
||||
# Crear cliente/proveedor principal
|
||||
db_client = ClientProvider(
|
||||
client_id=client_data.client_id,
|
||||
@@ -64,92 +74,106 @@ class ClientProviderService:
|
||||
position=client_data.position,
|
||||
incoterm=client_data.incoterm,
|
||||
is_national_provider=client_data.is_national_provider,
|
||||
enabled_disabled=client_data.enabled_disabled
|
||||
enabled_disabled=client_data.enabled_disabled,
|
||||
)
|
||||
|
||||
|
||||
self.db.add(db_client)
|
||||
self.db.flush() # Para obtener el ID antes del commit
|
||||
|
||||
|
||||
# Crear dirección si se proporciona
|
||||
if client_data.address:
|
||||
db_address = ClientProviderAddress(
|
||||
client_id=client_data.client_id,
|
||||
**client_data.address.model_dump(exclude_unset=True)
|
||||
**client_data.address.model_dump(exclude_unset=True),
|
||||
)
|
||||
self.db.add(db_address)
|
||||
|
||||
|
||||
# Crear programas si se proporciona
|
||||
if client_data.programs:
|
||||
db_programs = ClientProviderPrograms(
|
||||
client_id=client_data.client_id,
|
||||
**client_data.programs.model_dump(exclude_unset=True)
|
||||
**client_data.programs.model_dump(exclude_unset=True),
|
||||
)
|
||||
self.db.add(db_programs)
|
||||
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(db_client)
|
||||
|
||||
logger.info(f"Client/Provider created: {db_client.client_id} - {db_client.name}")
|
||||
|
||||
|
||||
logger.info(
|
||||
f"Client/Provider created: {db_client.client_id} - {db_client.name}"
|
||||
)
|
||||
|
||||
return self._get_client_with_relations(client_data.client_id)
|
||||
|
||||
|
||||
except IntegrityError as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"IntegrityError creating client/provider: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail="Client/Provider with this ID already exists")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="Client/Provider with this ID already exists"
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error creating client/provider: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error creating client/provider")
|
||||
|
||||
def get_client_provider(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error creating client/provider"
|
||||
)
|
||||
|
||||
def get_client_provider(
|
||||
self, client_id: str
|
||||
) -> Optional[ClientProviderResponseDTO]:
|
||||
"""
|
||||
Obtiene un cliente/proveedor por ID
|
||||
|
||||
|
||||
Args:
|
||||
client_id: ID del cliente/proveedor
|
||||
|
||||
|
||||
Returns:
|
||||
ClientProviderResponseDTO o None si no existe
|
||||
"""
|
||||
return self._get_client_with_relations(client_id)
|
||||
|
||||
def _get_client_with_relations(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
||||
|
||||
def _get_client_with_relations(
|
||||
self, client_id: str
|
||||
) -> Optional[ClientProviderResponseDTO]:
|
||||
"""Método privado para obtener cliente con relaciones"""
|
||||
client = self.db.query(ClientProvider).options(
|
||||
joinedload(ClientProvider.address),
|
||||
joinedload(ClientProvider.programs)
|
||||
).filter(ClientProvider.client_id == client_id).first()
|
||||
|
||||
client = (
|
||||
self.db.query(ClientProvider)
|
||||
.options(
|
||||
joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
|
||||
)
|
||||
.filter(ClientProvider.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not client:
|
||||
return None
|
||||
return ClientProviderResponseDTO.model_validate(client)
|
||||
|
||||
|
||||
def list_clients_providers(
|
||||
self,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
self,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search: Optional[str] = None,
|
||||
client_or_provider: Optional[str] = None,
|
||||
enabled_only: bool = False
|
||||
enabled_only: bool = False,
|
||||
) -> ClientProviderListDTO:
|
||||
"""
|
||||
Lista clientes/proveedores con filtros
|
||||
|
||||
|
||||
Args:
|
||||
skip: Número de registros a omitir
|
||||
limit: Número máximo de registros a retornar
|
||||
search: Texto de búsqueda (nombre, RFC, ID)
|
||||
client_or_provider: Filtrar por tipo (C=Cliente, P=Proveedor)
|
||||
enabled_only: Si True, solo retorna activos
|
||||
|
||||
|
||||
Returns:
|
||||
ClientProviderListDTO con la lista paginada
|
||||
"""
|
||||
query = self.db.query(ClientProvider)
|
||||
|
||||
|
||||
# Aplicar filtros
|
||||
if search:
|
||||
search_pattern = f"%{search}%"
|
||||
@@ -158,56 +182,72 @@ class ClientProviderService:
|
||||
ClientProvider.name.ilike(search_pattern),
|
||||
ClientProvider.short_name.ilike(search_pattern),
|
||||
ClientProvider.rfc.ilike(search_pattern),
|
||||
ClientProvider.client_id.ilike(search_pattern)
|
||||
ClientProvider.client_id.ilike(search_pattern),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if client_or_provider:
|
||||
query = query.filter(ClientProvider.client_or_provider == client_or_provider)
|
||||
|
||||
query = query.filter(
|
||||
ClientProvider.client_or_provider == client_or_provider
|
||||
)
|
||||
|
||||
if enabled_only:
|
||||
query = query.filter(ClientProvider.enabled_disabled == 1)
|
||||
|
||||
|
||||
# Contar total
|
||||
total = query.count()
|
||||
|
||||
|
||||
# Aplicar paginación
|
||||
clients = query.offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
# Convertir a DTOs básicos
|
||||
client_dtos = [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||
|
||||
client_dtos = [
|
||||
ClientProviderBasicDTO.model_validate(client) for client in clients
|
||||
]
|
||||
|
||||
return ClientProviderListDTO(
|
||||
clients=client_dtos,
|
||||
total=total,
|
||||
page=(skip // limit) + 1 if limit > 0 else 1,
|
||||
size=len(client_dtos)
|
||||
size=len(client_dtos),
|
||||
)
|
||||
|
||||
def update_client_provider(self, client_id: str, client_data: ClientProviderUpdateDTO) -> Optional[ClientProviderResponseDTO]:
|
||||
|
||||
def update_client_provider(
|
||||
self, client_id: str, client_data: ClientProviderUpdateDTO
|
||||
) -> Optional[ClientProviderResponseDTO]:
|
||||
"""
|
||||
Actualiza un cliente/proveedor
|
||||
|
||||
|
||||
Args:
|
||||
client_id: ID del cliente/proveedor a actualizar
|
||||
client_data: Datos a actualizar
|
||||
|
||||
|
||||
Returns:
|
||||
ClientProviderResponseDTO actualizado o None si no existe
|
||||
"""
|
||||
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
|
||||
client = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
if not client:
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
# Actualizar campos del cliente principal
|
||||
update_data = client_data.model_dump(exclude_unset=True, exclude={'address', 'programs'})
|
||||
update_data = client_data.model_dump(
|
||||
exclude_unset=True, exclude={"address", "programs"}
|
||||
)
|
||||
for field, value in update_data.items():
|
||||
setattr(client, field, value)
|
||||
|
||||
|
||||
# Actualizar dirección
|
||||
if client_data.address:
|
||||
address = self.db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first()
|
||||
address = (
|
||||
self.db.query(ClientProviderAddress)
|
||||
.filter(ClientProviderAddress.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
if address:
|
||||
# Actualizar dirección existente
|
||||
address_data = client_data.address.model_dump(exclude_unset=True)
|
||||
@@ -217,13 +257,17 @@ class ClientProviderService:
|
||||
# Crear nueva dirección
|
||||
address = ClientProviderAddress(
|
||||
client_id=client_id,
|
||||
**client_data.address.model_dump(exclude_unset=True)
|
||||
**client_data.address.model_dump(exclude_unset=True),
|
||||
)
|
||||
self.db.add(address)
|
||||
|
||||
|
||||
# Actualizar programas
|
||||
if client_data.programs:
|
||||
programs = self.db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first()
|
||||
programs = (
|
||||
self.db.query(ClientProviderPrograms)
|
||||
.filter(ClientProviderPrograms.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
if programs:
|
||||
# Actualizar programas existentes
|
||||
programs_data = client_data.programs.model_dump(exclude_unset=True)
|
||||
@@ -233,34 +277,40 @@ class ClientProviderService:
|
||||
# Crear nuevos programas
|
||||
programs = ClientProviderPrograms(
|
||||
client_id=client_id,
|
||||
**client_data.programs.model_dump(exclude_unset=True)
|
||||
**client_data.programs.model_dump(exclude_unset=True),
|
||||
)
|
||||
self.db.add(programs)
|
||||
|
||||
|
||||
self.db.commit()
|
||||
logger.info(f"Client/Provider updated: {client_id}")
|
||||
|
||||
|
||||
return self._get_client_with_relations(client_id)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error updating client/provider {client_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error updating client/provider")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error updating client/provider"
|
||||
)
|
||||
|
||||
def delete_client_provider(self, client_id: str) -> bool:
|
||||
"""
|
||||
Elimina un cliente/proveedor
|
||||
|
||||
|
||||
Args:
|
||||
client_id: ID del cliente/proveedor a eliminar
|
||||
|
||||
|
||||
Returns:
|
||||
True si se eliminó, False si no existe
|
||||
"""
|
||||
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
|
||||
client = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
if not client:
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
self.db.delete(client) # Las relaciones se eliminan en cascada
|
||||
self.db.commit()
|
||||
@@ -269,41 +319,61 @@ class ClientProviderService:
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error deleting client/provider {client_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error deleting client/provider")
|
||||
|
||||
def get_clients_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error deleting client/provider"
|
||||
)
|
||||
|
||||
def get_clients_only(
|
||||
self, skip: int = 0, limit: int = 100
|
||||
) -> List[ClientProviderBasicDTO]:
|
||||
"""Obtiene solo clientes (C)"""
|
||||
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'C')
|
||||
query = self.db.query(ClientProvider).filter(
|
||||
ClientProvider.client_or_provider == "C"
|
||||
)
|
||||
clients = query.offset(skip).limit(limit).all()
|
||||
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||
|
||||
def get_providers_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
|
||||
|
||||
def get_providers_only(
|
||||
self, skip: int = 0, limit: int = 100
|
||||
) -> List[ClientProviderBasicDTO]:
|
||||
"""Obtiene solo proveedores (P)"""
|
||||
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'P')
|
||||
query = self.db.query(ClientProvider).filter(
|
||||
ClientProvider.client_or_provider == "P"
|
||||
)
|
||||
providers = query.offset(skip).limit(limit).all()
|
||||
return [ClientProviderBasicDTO.model_validate(provider) for provider in providers]
|
||||
|
||||
return [
|
||||
ClientProviderBasicDTO.model_validate(provider) for provider in providers
|
||||
]
|
||||
|
||||
def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
|
||||
"""Busca clientes/proveedores por RFC"""
|
||||
clients = self.db.query(ClientProvider).filter(ClientProvider.rfc.ilike(f"%{rfc}%")).all()
|
||||
clients = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.rfc.ilike(f"%{rfc}%"))
|
||||
.all()
|
||||
)
|
||||
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||
|
||||
|
||||
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
||||
"""Cambia el estado habilitado/deshabilitado"""
|
||||
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
|
||||
client = (
|
||||
self.db.query(ClientProvider)
|
||||
.filter(ClientProvider.client_id == client_id)
|
||||
.first()
|
||||
)
|
||||
if not client:
|
||||
return None
|
||||
|
||||
|
||||
# Toggle status (1 = habilitado, 0 = deshabilitado)
|
||||
client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
|
||||
|
||||
|
||||
try:
|
||||
self.db.commit()
|
||||
logger.info(f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}")
|
||||
logger.info(
|
||||
f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}"
|
||||
)
|
||||
return self._get_client_with_relations(client_id)
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error toggling status for {client_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error updating status")
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Módulo de Company
|
||||
"""
|
||||
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
DTOs (Data Transfer Objects) para módulo de empresa
|
||||
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
@@ -9,47 +10,80 @@ from datetime import datetime
|
||||
|
||||
class CompanyCreateDTO(BaseModel):
|
||||
"""DTO para crear una empresa"""
|
||||
id: str = Field(default='EMP', max_length=3, description="Company ID")
|
||||
|
||||
id: str = Field(default="EMP", max_length=3, description="Company ID")
|
||||
consecutive: bool = Field(default=True, description="Unique record control")
|
||||
name: Optional[str] = Field(None, max_length=255, description="Company name")
|
||||
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC")
|
||||
main_activity: Optional[str] = Field(None, max_length=255, description="Main activity")
|
||||
|
||||
main_activity: Optional[str] = Field(
|
||||
None, max_length=255, description="Main activity"
|
||||
)
|
||||
|
||||
# Program information
|
||||
program: Optional[str] = Field(None, max_length=10, description="Program")
|
||||
program_number: Optional[str] = Field(None, max_length=40, description="Program number")
|
||||
program_number: Optional[str] = Field(
|
||||
None, max_length=40, description="Program number"
|
||||
)
|
||||
prosec: Optional[int] = Field(None, description="PROSEC")
|
||||
prosec_authorization: Optional[str] = Field(None, max_length=20, description="PROSEC authorization")
|
||||
|
||||
prosec_authorization: Optional[str] = Field(
|
||||
None, max_length=20, description="PROSEC authorization"
|
||||
)
|
||||
|
||||
# Identifiers
|
||||
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID")
|
||||
broker_company: Optional[str] = Field(None, max_length=10, description="Broker company")
|
||||
|
||||
manufacturer_id: Optional[str] = Field(
|
||||
None, max_length=25, description="Manufacturer ID"
|
||||
)
|
||||
broker_company: Optional[str] = Field(
|
||||
None, max_length=10, description="Broker company"
|
||||
)
|
||||
|
||||
# Responsible person
|
||||
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
|
||||
responsible_name: Optional[str] = Field(None, max_length=20, description="Responsible first name")
|
||||
responsible_last_name: Optional[str] = Field(None, max_length=20, description="Responsible last name")
|
||||
responsible_mother_last_name: Optional[str] = Field(None, max_length=20, description="Responsible mother's last name")
|
||||
responsible_rfc: Optional[str] = Field(None, max_length=30, description="Responsible RFC")
|
||||
position: Optional[str] = Field(None, max_length=30, description="Responsible position")
|
||||
|
||||
responsible: Optional[str] = Field(
|
||||
None, max_length=80, description="Responsible person"
|
||||
)
|
||||
responsible_name: Optional[str] = Field(
|
||||
None, max_length=20, description="Responsible first name"
|
||||
)
|
||||
responsible_last_name: Optional[str] = Field(
|
||||
None, max_length=20, description="Responsible last name"
|
||||
)
|
||||
responsible_mother_last_name: Optional[str] = Field(
|
||||
None, max_length=20, description="Responsible mother's last name"
|
||||
)
|
||||
responsible_rfc: Optional[str] = Field(
|
||||
None, max_length=30, description="Responsible RFC"
|
||||
)
|
||||
position: Optional[str] = Field(
|
||||
None, max_length=30, description="Responsible position"
|
||||
)
|
||||
|
||||
# Configuration
|
||||
logo: Optional[str] = Field(None, max_length=255, description="Company logo")
|
||||
has_express_line: Optional[bool] = Field(None, description="Has express line")
|
||||
order_format_type: Optional[str] = Field(None, max_length=19, description="Order format type")
|
||||
order_format_type: Optional[str] = Field(
|
||||
None, max_length=19, description="Order format type"
|
||||
)
|
||||
previous_code: Optional[int] = Field(None, description="Previous code")
|
||||
is_service_company: Optional[bool] = Field(None, description="Is service company")
|
||||
|
||||
|
||||
# Client and subassembly
|
||||
client_name: Optional[str] = Field(None, max_length=300, description="Client name")
|
||||
subassembly_mode: Optional[str] = Field(None, max_length=7, description="Subassembly mode")
|
||||
|
||||
subassembly_mode: Optional[str] = Field(
|
||||
None, max_length=7, description="Subassembly mode"
|
||||
)
|
||||
|
||||
# Additional information
|
||||
curp: Optional[str] = Field(None, max_length=19, description="CURP")
|
||||
inter_db_name: Optional[str] = Field(None, max_length=100, description="Inter DB name")
|
||||
inter_db_name: Optional[str] = Field(
|
||||
None, max_length=100, description="Inter DB name"
|
||||
)
|
||||
ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI")
|
||||
trusted_exporter_number: Optional[str] = Field(None, max_length=50, description="Trusted exporter number")
|
||||
prevalidator_key: Optional[str] = Field(None, max_length=20, description="Prevalidator key")
|
||||
trusted_exporter_number: Optional[str] = Field(
|
||||
None, max_length=50, description="Trusted exporter number"
|
||||
)
|
||||
prevalidator_key: Optional[str] = Field(
|
||||
None, max_length=20, description="Prevalidator key"
|
||||
)
|
||||
seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment")
|
||||
|
||||
class Config:
|
||||
@@ -58,45 +92,78 @@ class CompanyCreateDTO(BaseModel):
|
||||
|
||||
class CompanyUpdateDTO(BaseModel):
|
||||
"""DTO para actualizar una empresa"""
|
||||
|
||||
name: Optional[str] = Field(None, max_length=255, description="Company name")
|
||||
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC")
|
||||
main_activity: Optional[str] = Field(None, max_length=255, description="Main activity")
|
||||
|
||||
main_activity: Optional[str] = Field(
|
||||
None, max_length=255, description="Main activity"
|
||||
)
|
||||
|
||||
# Program information
|
||||
program: Optional[str] = Field(None, max_length=10, description="Program")
|
||||
program_number: Optional[str] = Field(None, max_length=40, description="Program number")
|
||||
program_number: Optional[str] = Field(
|
||||
None, max_length=40, description="Program number"
|
||||
)
|
||||
prosec: Optional[int] = Field(None, description="PROSEC")
|
||||
prosec_authorization: Optional[str] = Field(None, max_length=20, description="PROSEC authorization")
|
||||
|
||||
prosec_authorization: Optional[str] = Field(
|
||||
None, max_length=20, description="PROSEC authorization"
|
||||
)
|
||||
|
||||
# Identifiers
|
||||
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID")
|
||||
broker_company: Optional[str] = Field(None, max_length=10, description="Broker company")
|
||||
|
||||
manufacturer_id: Optional[str] = Field(
|
||||
None, max_length=25, description="Manufacturer ID"
|
||||
)
|
||||
broker_company: Optional[str] = Field(
|
||||
None, max_length=10, description="Broker company"
|
||||
)
|
||||
|
||||
# Responsible person
|
||||
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
|
||||
responsible_name: Optional[str] = Field(None, max_length=20, description="Responsible first name")
|
||||
responsible_last_name: Optional[str] = Field(None, max_length=20, description="Responsible last name")
|
||||
responsible_mother_last_name: Optional[str] = Field(None, max_length=20, description="Responsible mother's last name")
|
||||
responsible_rfc: Optional[str] = Field(None, max_length=30, description="Responsible RFC")
|
||||
position: Optional[str] = Field(None, max_length=30, description="Responsible position")
|
||||
|
||||
responsible: Optional[str] = Field(
|
||||
None, max_length=80, description="Responsible person"
|
||||
)
|
||||
responsible_name: Optional[str] = Field(
|
||||
None, max_length=20, description="Responsible first name"
|
||||
)
|
||||
responsible_last_name: Optional[str] = Field(
|
||||
None, max_length=20, description="Responsible last name"
|
||||
)
|
||||
responsible_mother_last_name: Optional[str] = Field(
|
||||
None, max_length=20, description="Responsible mother's last name"
|
||||
)
|
||||
responsible_rfc: Optional[str] = Field(
|
||||
None, max_length=30, description="Responsible RFC"
|
||||
)
|
||||
position: Optional[str] = Field(
|
||||
None, max_length=30, description="Responsible position"
|
||||
)
|
||||
|
||||
# Configuration
|
||||
logo: Optional[str] = Field(None, max_length=255, description="Company logo")
|
||||
has_express_line: Optional[bool] = Field(None, description="Has express line")
|
||||
order_format_type: Optional[str] = Field(None, max_length=19, description="Order format type")
|
||||
order_format_type: Optional[str] = Field(
|
||||
None, max_length=19, description="Order format type"
|
||||
)
|
||||
previous_code: Optional[int] = Field(None, description="Previous code")
|
||||
is_service_company: Optional[bool] = Field(None, description="Is service company")
|
||||
|
||||
|
||||
# Client and subassembly
|
||||
client_name: Optional[str] = Field(None, max_length=300, description="Client name")
|
||||
subassembly_mode: Optional[str] = Field(None, max_length=7, description="Subassembly mode")
|
||||
|
||||
subassembly_mode: Optional[str] = Field(
|
||||
None, max_length=7, description="Subassembly mode"
|
||||
)
|
||||
|
||||
# Additional information
|
||||
curp: Optional[str] = Field(None, max_length=19, description="CURP")
|
||||
inter_db_name: Optional[str] = Field(None, max_length=100, description="Inter DB name")
|
||||
inter_db_name: Optional[str] = Field(
|
||||
None, max_length=100, description="Inter DB name"
|
||||
)
|
||||
ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI")
|
||||
trusted_exporter_number: Optional[str] = Field(None, max_length=50, description="Trusted exporter number")
|
||||
prevalidator_key: Optional[str] = Field(None, max_length=20, description="Prevalidator key")
|
||||
trusted_exporter_number: Optional[str] = Field(
|
||||
None, max_length=50, description="Trusted exporter number"
|
||||
)
|
||||
prevalidator_key: Optional[str] = Field(
|
||||
None, max_length=20, description="Prevalidator key"
|
||||
)
|
||||
seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment")
|
||||
|
||||
class Config:
|
||||
@@ -105,22 +172,23 @@ class CompanyUpdateDTO(BaseModel):
|
||||
|
||||
class CompanyResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de empresa"""
|
||||
id: str
|
||||
consecutive: bool
|
||||
|
||||
id: int
|
||||
tenant_id: int
|
||||
name: Optional[str] = None
|
||||
rfc: Optional[str] = None
|
||||
main_activity: Optional[str] = None
|
||||
|
||||
|
||||
# Program information
|
||||
program: Optional[str] = None
|
||||
program_number: Optional[str] = None
|
||||
prosec: Optional[int] = None
|
||||
prosec_authorization: Optional[str] = None
|
||||
|
||||
|
||||
# Identifiers
|
||||
manufacturer_id: Optional[str] = None
|
||||
broker_company: Optional[str] = None
|
||||
|
||||
|
||||
# Responsible person
|
||||
responsible: Optional[str] = None
|
||||
responsible_name: Optional[str] = None
|
||||
@@ -128,18 +196,18 @@ class CompanyResponseDTO(BaseModel):
|
||||
responsible_mother_last_name: Optional[str] = None
|
||||
responsible_rfc: Optional[str] = None
|
||||
position: Optional[str] = None
|
||||
|
||||
|
||||
# Configuration
|
||||
logo: Optional[str] = None
|
||||
has_express_line: Optional[bool] = None
|
||||
order_format_type: Optional[str] = None
|
||||
previous_code: Optional[int] = None
|
||||
is_service_company: Optional[bool] = None
|
||||
|
||||
|
||||
# Client and subassembly
|
||||
client_name: Optional[str] = None
|
||||
subassembly_mode: Optional[str] = None
|
||||
|
||||
|
||||
# Additional information
|
||||
curp: Optional[str] = None
|
||||
inter_db_name: Optional[str] = None
|
||||
@@ -147,11 +215,10 @@ class CompanyResponseDTO(BaseModel):
|
||||
trusted_exporter_number: Optional[str] = None
|
||||
prevalidator_key: Optional[str] = None
|
||||
seventh_amendment: Optional[bool] = None
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
"""
|
||||
Modelos ORM para gestión de empresa
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from sqlalchemy import DateTime, Integer, String, Boolean, SmallInteger, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
Integer,
|
||||
String,
|
||||
Boolean,
|
||||
SmallInteger,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
@@ -13,32 +24,35 @@ class Company(Base):
|
||||
"""
|
||||
Modelo para la tabla Company - Información de la empresa
|
||||
"""
|
||||
|
||||
__tablename__ = "company"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='company_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_company_tenant'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="company_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_company_tenant"
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
# Primary key
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
# Información básica de la empresa
|
||||
name: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
rfc: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
main_activity: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
|
||||
# Información del programa
|
||||
program: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
program_number: Mapped[Optional[str]] = mapped_column(String(40))
|
||||
prosec: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
|
||||
|
||||
# Identificadores
|
||||
manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25))
|
||||
broker_company: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
|
||||
|
||||
# Responsable
|
||||
responsible: Mapped[Optional[str]] = mapped_column(String(80))
|
||||
responsible_name: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
@@ -46,29 +60,33 @@ class Company(Base):
|
||||
responsible_mother_last_name: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
responsible_rfc: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
position: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
|
||||
|
||||
# Configuración
|
||||
logo: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
has_express_line: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
order_format_type: Mapped[Optional[str]] = mapped_column(String(19))
|
||||
previous_code: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
is_service_company: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
|
||||
|
||||
# Cliente y submaquila
|
||||
client_name: Mapped[Optional[str]] = mapped_column(String(300))
|
||||
subassembly_mode: Mapped[Optional[str]] = mapped_column(String(7))
|
||||
|
||||
|
||||
# Información adicional
|
||||
curp: Mapped[Optional[str]] = mapped_column(String(19))
|
||||
inter_db_name: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
trusted_exporter_number: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
prevalidator_key: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
seventh_amendment: Mapped[Optional[bool]] = mapped_column(Boolean) # FINALCONTADORAELECTRONICO renombrado
|
||||
|
||||
seventh_amendment: Mapped[Optional[bool]] = mapped_column(
|
||||
Boolean
|
||||
) # FINALCONTADORAELECTRONICO renombrado
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
"""
|
||||
Endpoints API para gestión de empresa
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user, has_role
|
||||
from core.security import get_current_user, has_role, get_tenant_from_token
|
||||
from .service import CompanyService
|
||||
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
|
||||
|
||||
router = APIRouter(prefix="/company")
|
||||
|
||||
|
||||
@router.post("/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_company(
|
||||
company_data: CompanyCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new company in the system
|
||||
|
||||
|
||||
Only one company can exist per system due to the unique consecutive field.
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
@@ -30,12 +33,11 @@ async def create_company(
|
||||
|
||||
@router.get("/", response_model=Optional[CompanyResponseDTO])
|
||||
async def get_company(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get the registered company information
|
||||
|
||||
|
||||
Returns the unique company in the system or None if it doesn't exist.
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
@@ -45,73 +47,48 @@ async def get_company(
|
||||
return company
|
||||
|
||||
|
||||
@router.get("/{company_id}", response_model=CompanyResponseDTO)
|
||||
async def get_company_by_id(
|
||||
company_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
@router.get("/my-companies", response_model=list[CompanyResponseDTO])
|
||||
async def get_my_companies(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get company by specific ID
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
company = service.get_company_by_id(company_id)
|
||||
if not company:
|
||||
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
|
||||
return company
|
||||
|
||||
|
||||
@router.put("/{company_id}", response_model=CompanyResponseDTO)
|
||||
async def update_company(
|
||||
company_id: str,
|
||||
company_data: CompanyUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Update company information
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
company = service.update_company(company_id, company_data)
|
||||
if not company:
|
||||
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
|
||||
return company
|
||||
|
||||
|
||||
@router.delete("/{company_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_company(
|
||||
company_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Delete company from the system
|
||||
Get all companies that belong to the user's tenant
|
||||
|
||||
Note: This will completely remove the company from the system.
|
||||
Returns a list of companies associated with the tenant_id from the user's token
|
||||
"""
|
||||
tenant_id = get_tenant_from_token(current_user)
|
||||
if not tenant_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Tenant ID not found in token"
|
||||
)
|
||||
|
||||
service = CompanyService(db)
|
||||
if not service.delete_company(company_id):
|
||||
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
|
||||
companies = service.get_companies_by_tenant(tenant_id)
|
||||
|
||||
return companies
|
||||
|
||||
|
||||
@router.get("/status/exists", response_model=dict)
|
||||
async def check_company_exists(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Check if a company is registered in the system
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
exists = service.exists_company()
|
||||
return {"exists": exists, "message": "Company found" if exists else "No company registered"}
|
||||
return {
|
||||
"exists": exists,
|
||||
"message": "Company found" if exists else "No company registered",
|
||||
}
|
||||
|
||||
|
||||
# Specific endpoints for important fields
|
||||
@router.get("/info/basic", response_model=dict)
|
||||
async def get_company_basic_info(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get basic company information (name, RFC, main activity)
|
||||
@@ -120,19 +97,18 @@ async def get_company_basic_info(
|
||||
company = service.get_company()
|
||||
if not company:
|
||||
raise HTTPException(status_code=404, detail="No company found")
|
||||
|
||||
|
||||
return {
|
||||
"name": company.name,
|
||||
"rfc": company.rfc,
|
||||
"main_activity": company.main_activity,
|
||||
"logo": company.logo
|
||||
"logo": company.logo,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/info/responsible", response_model=dict)
|
||||
async def get_company_responsible_info(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get company responsible person information
|
||||
@@ -141,21 +117,20 @@ async def get_company_responsible_info(
|
||||
company = service.get_company()
|
||||
if not company:
|
||||
raise HTTPException(status_code=404, detail="No company found")
|
||||
|
||||
|
||||
return {
|
||||
"responsible": company.responsible,
|
||||
"responsible_name": company.responsible_name,
|
||||
"responsible_last_name": company.responsible_last_name,
|
||||
"responsible_mother_last_name": company.responsible_mother_last_name,
|
||||
"responsible_rfc": company.responsible_rfc,
|
||||
"position": company.position
|
||||
"position": company.position,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/info/program", response_model=dict)
|
||||
async def get_company_program_info(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get company program information
|
||||
@@ -164,13 +139,67 @@ async def get_company_program_info(
|
||||
company = service.get_company()
|
||||
if not company:
|
||||
raise HTTPException(status_code=404, detail="No company found")
|
||||
|
||||
|
||||
return {
|
||||
"program": company.program,
|
||||
"program_number": company.program_number,
|
||||
"prosec": company.prosec,
|
||||
"prosec_authorization": company.prosec_authorization,
|
||||
"manufacturer_id": company.manufacturer_id
|
||||
"manufacturer_id": company.manufacturer_id,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{company_id}", response_model=CompanyResponseDTO)
|
||||
async def get_company_by_id(
|
||||
company_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get company by specific ID
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
company = service.get_company_by_id(company_id)
|
||||
if not company:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Company with ID '{company_id}' not found"
|
||||
)
|
||||
return company
|
||||
|
||||
|
||||
@router.put("/{company_id}", response_model=CompanyResponseDTO)
|
||||
async def update_company(
|
||||
company_id: str,
|
||||
company_data: CompanyUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update company information
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
company = service.update_company(company_id, company_data)
|
||||
if not company:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Company with ID '{company_id}' not found"
|
||||
)
|
||||
return company
|
||||
|
||||
|
||||
@router.delete("/{company_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_company(
|
||||
company_id: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete company from the system
|
||||
|
||||
Note: This will completely remove the company from the system.
|
||||
"""
|
||||
service = CompanyService(db)
|
||||
if not service.delete_company(company_id):
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Company with ID '{company_id}' not found"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Capa de servicio para lógica de negocio de empresa
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
@@ -15,29 +16,34 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class CompanyService:
|
||||
"""Servicio para gestión de empresa"""
|
||||
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
|
||||
def create_company(self, company_data: CompanyCreateDTO) -> CompanyResponseDTO:
|
||||
"""
|
||||
Crea una nueva empresa en el sistema
|
||||
|
||||
|
||||
Args:
|
||||
company_data: Datos de la empresa a crear
|
||||
|
||||
|
||||
Returns:
|
||||
CompanyResponseDTO con información de la empresa creada
|
||||
|
||||
|
||||
Raises:
|
||||
HTTPException: Si ya existe una empresa o error en la creación
|
||||
"""
|
||||
try:
|
||||
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único)
|
||||
existing = self.db.query(Company).filter(Company.consecutive == True).first()
|
||||
existing = (
|
||||
self.db.query(Company).filter(Company.consecutive == True).first()
|
||||
)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="A company is already registered in the system")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="A company is already registered in the system",
|
||||
)
|
||||
|
||||
# Crear empresa
|
||||
db_company = Company(
|
||||
id=company_data.id,
|
||||
@@ -69,32 +75,35 @@ class CompanyService:
|
||||
ctpat_svi=company_data.ctpat_svi,
|
||||
trusted_exporter_number=company_data.trusted_exporter_number,
|
||||
prevalidator_key=company_data.prevalidator_key,
|
||||
seventh_amendment=company_data.seventh_amendment
|
||||
seventh_amendment=company_data.seventh_amendment,
|
||||
)
|
||||
|
||||
|
||||
self.db.add(db_company)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_company)
|
||||
|
||||
|
||||
logger.info(f"Company created: {db_company.id} - {db_company.name}")
|
||||
|
||||
|
||||
return CompanyResponseDTO.model_validate(db_company)
|
||||
|
||||
|
||||
except IntegrityError as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"IntegrityError creating company: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail="Integrity error: A company already exists in the system")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Integrity error: A company already exists in the system",
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error creating company: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error creating company")
|
||||
|
||||
|
||||
def get_company(self) -> Optional[CompanyResponseDTO]:
|
||||
"""
|
||||
Obtiene la empresa (solo puede haber una)
|
||||
|
||||
|
||||
Returns:
|
||||
CompanyResponseDTO o None si no existe
|
||||
"""
|
||||
@@ -102,14 +111,14 @@ class CompanyService:
|
||||
if not company:
|
||||
return None
|
||||
return CompanyResponseDTO.model_validate(company)
|
||||
|
||||
|
||||
def get_company_by_id(self, company_id: str) -> Optional[CompanyResponseDTO]:
|
||||
"""
|
||||
Obtiene una empresa por ID
|
||||
|
||||
|
||||
Args:
|
||||
company_id: ID de la empresa
|
||||
|
||||
|
||||
Returns:
|
||||
CompanyResponseDTO o None si no existe
|
||||
"""
|
||||
@@ -117,27 +126,29 @@ class CompanyService:
|
||||
if not company:
|
||||
return None
|
||||
return CompanyResponseDTO.model_validate(company)
|
||||
|
||||
def update_company(self, company_id: str, company_data: CompanyUpdateDTO) -> Optional[CompanyResponseDTO]:
|
||||
|
||||
def update_company(
|
||||
self, company_id: str, company_data: CompanyUpdateDTO
|
||||
) -> Optional[CompanyResponseDTO]:
|
||||
"""
|
||||
Actualiza una empresa
|
||||
|
||||
|
||||
Args:
|
||||
company_id: ID de la empresa a actualizar
|
||||
company_data: Datos a actualizar
|
||||
|
||||
|
||||
Returns:
|
||||
CompanyResponseDTO actualizada o None si no existe
|
||||
"""
|
||||
company = self.db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
return None
|
||||
|
||||
|
||||
# Actualizar solo campos proporcionados
|
||||
update_data = company_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(company, field, value)
|
||||
|
||||
|
||||
try:
|
||||
self.db.commit()
|
||||
self.db.refresh(company)
|
||||
@@ -147,21 +158,21 @@ class CompanyService:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error updating company {company_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error updating company")
|
||||
|
||||
|
||||
def delete_company(self, company_id: str) -> bool:
|
||||
"""
|
||||
Elimina una empresa
|
||||
|
||||
|
||||
Args:
|
||||
company_id: ID de la empresa a eliminar
|
||||
|
||||
|
||||
Returns:
|
||||
True si se eliminó, False si no existe
|
||||
"""
|
||||
company = self.db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
self.db.delete(company)
|
||||
self.db.commit()
|
||||
@@ -171,14 +182,34 @@ class CompanyService:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error deleting company {company_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error deleting company")
|
||||
|
||||
|
||||
def exists_company(self) -> bool:
|
||||
"""
|
||||
Verifica si existe una empresa registrada
|
||||
|
||||
|
||||
Returns:
|
||||
True si existe una empresa, False en caso contrario
|
||||
"""
|
||||
return self.db.query(Company).filter(Company.consecutive == True).first() is not None
|
||||
return (
|
||||
self.db.query(Company).filter(Company.consecutive == True).first()
|
||||
is not None
|
||||
)
|
||||
|
||||
def get_companies_by_tenant(self, tenant_id: int) -> List[CompanyResponseDTO]:
|
||||
"""
|
||||
Obtiene todas las compañías que pertenecen a un tenant específico
|
||||
|
||||
Args:
|
||||
tenant_id: ID del tenant
|
||||
|
||||
Returns:
|
||||
Lista de CompanyResponseDTO
|
||||
"""
|
||||
companies = (
|
||||
self.db.query(Company)
|
||||
.filter(Company.tenant_id == tenant_id)
|
||||
.order_by(Company.name)
|
||||
.all()
|
||||
)
|
||||
|
||||
return [CompanyResponseDTO.model_validate(company) for company in companies]
|
||||
|
||||
@@ -4,15 +4,18 @@ DTOs for CountryRuleOct.
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CountryRuleOctBaseDTO(BaseModel):
|
||||
permission: str
|
||||
line: int
|
||||
fraction: str
|
||||
country_code: str
|
||||
|
||||
|
||||
class CountryRuleOctCreateDTO(CountryRuleOctBaseDTO):
|
||||
pass
|
||||
|
||||
|
||||
class CountryRuleOctResponseDTO(CountryRuleOctBaseDTO):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
from_attributes = True
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
from sqlalchemy import Integer, String, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
|
||||
@@ -6,25 +13,42 @@ from core.database import Base
|
||||
class CountryRuleOct(Base):
|
||||
__tablename__ = "country_rule_oct"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='country_rule_oct_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_country_rule_oct_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_country_rule_oct_company'),
|
||||
PrimaryKeyConstraint("id", name="country_rule_oct_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
['tenant_id', 'company_id', 'permission', 'line', 'fraction'],
|
||||
['a76.fraction_rule_octave.tenant_id', 'a76.fraction_rule_octave.company_id', 'a76.fraction_rule_octave.permission', 'a76.fraction_rule_octave.line', 'a76.fraction_rule_octave.fraction'],
|
||||
ondelete="CASCADE",
|
||||
name='fk_country_rule_oct_frac_octava'
|
||||
),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'permission', 'line', 'fraction', 'country_code', name='uq_country_rule_oct_permission_line_fraction_country'),
|
||||
{"schema": "a76"}
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_country_rule_oct_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_country_rule_oct_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id", "company_id", "permission", "line", "fraction"],
|
||||
[
|
||||
"a76.fraction_rule_octave.tenant_id",
|
||||
"a76.fraction_rule_octave.company_id",
|
||||
"a76.fraction_rule_octave.permission",
|
||||
"a76.fraction_rule_octave.line",
|
||||
"a76.fraction_rule_octave.fraction",
|
||||
],
|
||||
ondelete="CASCADE",
|
||||
name="fk_country_rule_oct_frac_octava",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"permission",
|
||||
"line",
|
||||
"fraction",
|
||||
"country_code",
|
||||
name="uq_country_rule_oct_permission_line_fraction_country",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
permission: Mapped[str] = mapped_column(String(20))
|
||||
line: Mapped[int] = mapped_column()
|
||||
fraction: Mapped[str] = mapped_column(String(10))
|
||||
country_code: Mapped[str] = mapped_column(String(3))
|
||||
|
||||
@@ -12,8 +12,7 @@ router = APIRouter(prefix="/country-rule-oct", tags=["CountryRuleOct"])
|
||||
|
||||
@router.get("/", response_model=List[CountryRuleOctResponseDTO])
|
||||
async def list_countries(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
List all CountryRuleOct entries.
|
||||
@@ -28,14 +27,17 @@ async def list_countries(
|
||||
return db.query(CountryRuleOctService).all()
|
||||
|
||||
|
||||
@router.get("/{permission}/{line}/{fraction}/{country_code}", response_model=CountryRuleOctResponseDTO)
|
||||
@router.get(
|
||||
"/{permission}/{line}/{fraction}/{country_code}",
|
||||
response_model=CountryRuleOctResponseDTO,
|
||||
)
|
||||
async def read_country_rule(
|
||||
permission: str,
|
||||
line: int,
|
||||
fraction: str,
|
||||
country_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific CountryRuleOct by its composite key.
|
||||
@@ -53,11 +55,13 @@ async def read_country_rule(
|
||||
return country
|
||||
|
||||
|
||||
@router.post("/", response_model=CountryRuleOctResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/", response_model=CountryRuleOctResponseDTO, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_country_rule(
|
||||
country_data: CountryRuleOctCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new CountryRuleOct entry.
|
||||
@@ -65,18 +69,23 @@ async def create_country_rule(
|
||||
return CountryRuleOctService.create_country_rule(db, country_data)
|
||||
|
||||
|
||||
@router.delete("/{permission}/{line}/{fraction}/{country_code}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{permission}/{line}/{fraction}/{country_code}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_country_rule(
|
||||
permission: str,
|
||||
line: int,
|
||||
fraction: str,
|
||||
country_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a CountryRuleOct by its composite key.
|
||||
"""
|
||||
country = CountryRuleOctService.delete_country_rule(db, permission, line, fraction, country_code)
|
||||
country = CountryRuleOctService.delete_country_rule(
|
||||
db, permission, line, fraction, country_code
|
||||
)
|
||||
if not country:
|
||||
raise HTTPException(status_code=404, detail="CountryRuleOct not found")
|
||||
raise HTTPException(status_code=404, detail="CountryRuleOct not found")
|
||||
|
||||
@@ -5,15 +5,22 @@ Service layer for CountryRuleOct.
|
||||
from sqlalchemy.orm import Session
|
||||
from . import models, dto
|
||||
|
||||
|
||||
class CountryRuleOctService:
|
||||
@staticmethod
|
||||
def get_country_by_keys(db: Session, permission: str, line: int, fraction: str, country_code: str):
|
||||
return db.query(models.CountryRuleOct).filter(
|
||||
models.CountryRuleOct.permission == permission,
|
||||
models.CountryRuleOct.line == line,
|
||||
models.CountryRuleOct.fraction == fraction,
|
||||
models.CountryRuleOct.country_code == country_code
|
||||
).first()
|
||||
def get_country_by_keys(
|
||||
db: Session, permission: str, line: int, fraction: str, country_code: str
|
||||
):
|
||||
return (
|
||||
db.query(models.CountryRuleOct)
|
||||
.filter(
|
||||
models.CountryRuleOct.permission == permission,
|
||||
models.CountryRuleOct.line == line,
|
||||
models.CountryRuleOct.fraction == fraction,
|
||||
models.CountryRuleOct.country_code == country_code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_country_rule(db: Session, country_data: dto.CountryRuleOctCreateDTO):
|
||||
@@ -24,9 +31,13 @@ class CountryRuleOctService:
|
||||
return new_country
|
||||
|
||||
@staticmethod
|
||||
def delete_country_rule(db: Session, permission: str, line: int, fraction: str, country_code: str):
|
||||
country = CountryRuleOctService.get_country_by_keys(db, permission, line, fraction, country_code)
|
||||
def delete_country_rule(
|
||||
db: Session, permission: str, line: int, fraction: str, country_code: str
|
||||
):
|
||||
country = CountryRuleOctService.get_country_by_keys(
|
||||
db, permission, line, fraction, country_code
|
||||
)
|
||||
if country:
|
||||
db.delete(country)
|
||||
db.commit()
|
||||
return country
|
||||
return country
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ExchangeRateBaseDTO(BaseModel):
|
||||
date: int
|
||||
value: Optional[float]
|
||||
local_currency: Optional[str]
|
||||
foreign_currency: Optional[str]
|
||||
|
||||
|
||||
class ExchangeRateCreateDTO(ExchangeRateBaseDTO):
|
||||
pass
|
||||
|
||||
|
||||
class ExchangeRateResponseDTO(ExchangeRateBaseDTO):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
from_attributes = True
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Integer, String, DECIMAL, PrimaryKeyConstraint, DateTime, ForeignKeyConstraint, UniqueConstraint, ForeignKey
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
DECIMAL,
|
||||
PrimaryKeyConstraint,
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
ForeignKey,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
|
||||
@@ -8,18 +17,24 @@ from core.database import Base
|
||||
class ExchangeRate(Base):
|
||||
__tablename__ = "exchange_rate"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='exchange_rate_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_exchange_rate_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_exchange_rate_company'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'date', name='uq_exchange_rate_date_tenant'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="exchange_rate_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_exchange_rate_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_exchange_rate_company"
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id", "company_id", "date", name="uq_exchange_rate_date_tenant"
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
date: Mapped[int] = mapped_column(DateTime)
|
||||
value: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(13, 6))
|
||||
local_currency: Mapped[Optional[str]] = mapped_column(String(7))
|
||||
foreign_currency: Mapped[Optional[str]] = mapped_column(String(7))
|
||||
foreign_currency: Mapped[Optional[str]] = mapped_column(String(7))
|
||||
|
||||
@@ -12,8 +12,7 @@ router = APIRouter(prefix="/exchange-rate", tags=["ExchangeRate"])
|
||||
|
||||
@router.get("/", response_model=List[ExchangeRateResponseDTO])
|
||||
async def list_exchange_rates(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
List all ExchangeRate entries.
|
||||
@@ -32,7 +31,7 @@ async def list_exchange_rates(
|
||||
async def read_exchange_rate(
|
||||
date: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific ExchangeRate by its date.
|
||||
@@ -50,11 +49,13 @@ async def read_exchange_rate(
|
||||
return exchange_rate
|
||||
|
||||
|
||||
@router.post("/", response_model=ExchangeRateResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/", response_model=ExchangeRateResponseDTO, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_exchange_rate(
|
||||
exchange_rate_data: ExchangeRateCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new ExchangeRate entry.
|
||||
@@ -73,7 +74,7 @@ async def create_exchange_rate(
|
||||
async def delete_exchange_rate(
|
||||
date: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete an ExchangeRate by its date.
|
||||
@@ -87,4 +88,4 @@ async def delete_exchange_rate(
|
||||
|
||||
exchange_rate = ExchangeRateService.delete_exchange_rate(db, date)
|
||||
if not exchange_rate:
|
||||
raise HTTPException(status_code=404, detail="ExchangeRate not found")
|
||||
raise HTTPException(status_code=404, detail="ExchangeRate not found")
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from . import models, dto
|
||||
|
||||
|
||||
class ExchangeRateService:
|
||||
@staticmethod
|
||||
def get_exchange_rate_by_date(db: Session, date: int):
|
||||
return db.query(models.ExchangeRate).filter(models.ExchangeRate.date == date).first()
|
||||
return (
|
||||
db.query(models.ExchangeRate)
|
||||
.filter(models.ExchangeRate.date == date)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_exchange_rate(db: Session, exchange_rate_data: dto.ExchangeRateCreateDTO):
|
||||
def create_exchange_rate(
|
||||
db: Session, exchange_rate_data: dto.ExchangeRateCreateDTO
|
||||
):
|
||||
new_exchange_rate = models.ExchangeRate(**exchange_rate_data.dict())
|
||||
db.add(new_exchange_rate)
|
||||
db.commit()
|
||||
@@ -20,4 +27,4 @@ class ExchangeRateService:
|
||||
if exchange_rate:
|
||||
db.delete(exchange_rate)
|
||||
db.commit()
|
||||
return exchange_rate
|
||||
return exchange_rate
|
||||
|
||||
@@ -5,6 +5,7 @@ DTOs for FractionRuleOctave.
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class FractionRuleOctaveBaseDTO(BaseModel):
|
||||
PERMISSION: str
|
||||
LINE: int
|
||||
@@ -16,9 +17,11 @@ class FractionRuleOctaveBaseDTO(BaseModel):
|
||||
UNIT_COST_ME: Optional[float]
|
||||
UNIT_MEASURE: Optional[str]
|
||||
|
||||
|
||||
class FractionRuleOctaveCreateDTO(FractionRuleOctaveBaseDTO):
|
||||
pass
|
||||
|
||||
|
||||
class FractionRuleOctaveResponseDTO(FractionRuleOctaveBaseDTO):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
from_attributes = True
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
from sqlalchemy import Integer, String, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
|
||||
@@ -6,18 +13,28 @@ from core.database import Base
|
||||
class FractionRuleOctave(Base):
|
||||
__tablename__ = "fraction_rule_octave"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='fraction_rule_octave_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_fraction_rule_octave_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_fraction_rule_octave_company'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'permission', 'line', 'fraction', name='uq_fraction_rule_octave_permission_line_fraction'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="fraction_rule_octave_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_fraction_rule_octave_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_fraction_rule_octave_company"
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"permission",
|
||||
"line",
|
||||
"fraction",
|
||||
name="uq_fraction_rule_octave_permission_line_fraction",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
permission: Mapped[str] = mapped_column(String(20))
|
||||
line: Mapped[int] = mapped_column(Integer)
|
||||
fraction: Mapped[str] = mapped_column(String(10))
|
||||
|
||||
@@ -12,8 +12,7 @@ router = APIRouter(prefix="/fraction_rule_octave", tags=["FractionRuleOctave"])
|
||||
|
||||
@router.get("/", response_model=List[FractionRuleOctaveResponseDTO])
|
||||
async def list_fractions(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
List all FractionRuleOctave entries.
|
||||
@@ -28,13 +27,15 @@ async def list_fractions(
|
||||
return db.query(FractionRuleOctaveService).all()
|
||||
|
||||
|
||||
@router.get("/{permission}/{line}/{fraction}", response_model=FractionRuleOctaveResponseDTO)
|
||||
@router.get(
|
||||
"/{permission}/{line}/{fraction}", response_model=FractionRuleOctaveResponseDTO
|
||||
)
|
||||
async def read_fraction(
|
||||
permission: str,
|
||||
line: int,
|
||||
fraction: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific FractionRuleOctave by its composite key.
|
||||
@@ -52,11 +53,15 @@ async def read_fraction(
|
||||
return frac
|
||||
|
||||
|
||||
@router.post("/", response_model=FractionRuleOctaveResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=FractionRuleOctaveResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_frac(
|
||||
frac_data: FractionRuleOctaveCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new FractionRuleOctave entry.
|
||||
@@ -71,13 +76,15 @@ async def create_frac(
|
||||
return FractionRuleOctaveService.create_frac(db, frac_data)
|
||||
|
||||
|
||||
@router.delete("/{permission}/{line}/{fraction}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{permission}/{line}/{fraction}", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def delete_fraction(
|
||||
permission: str,
|
||||
line: int,
|
||||
fraction: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a FractionRuleOctave by its composite key.
|
||||
@@ -91,4 +98,4 @@ async def delete_fraction(
|
||||
|
||||
frac = FractionRuleOctaveService.delete_fraction(db, permission, line, fraction)
|
||||
if not frac:
|
||||
raise HTTPException(status_code=404, detail="FractionRuleOctave not found")
|
||||
raise HTTPException(status_code=404, detail="FractionRuleOctave not found")
|
||||
|
||||
@@ -5,14 +5,21 @@ from . import models, dto
|
||||
Service layer for FractionRuleOctave.
|
||||
"""
|
||||
|
||||
|
||||
class FractionRuleOctaveService:
|
||||
@staticmethod
|
||||
def get_fraction_by_permission_line(db: Session, permission: str, line: int, fraction: str):
|
||||
return db.query(models.FractionRuleOctave).filter(
|
||||
models.FractionRuleOctave.permission == permission,
|
||||
models.FractionRuleOctave.line == line,
|
||||
models.FractionRuleOctave.fraction == fraction
|
||||
).first()
|
||||
def get_fraction_by_permission_line(
|
||||
db: Session, permission: str, line: int, fraction: str
|
||||
):
|
||||
return (
|
||||
db.query(models.FractionRuleOctave)
|
||||
.filter(
|
||||
models.FractionRuleOctave.permission == permission,
|
||||
models.FractionRuleOctave.line == line,
|
||||
models.FractionRuleOctave.fraction == fraction,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_frac(db: Session, frac_data: dto.FractionRuleOctaveCreateDTO):
|
||||
@@ -24,8 +31,10 @@ class FractionRuleOctaveService:
|
||||
|
||||
@staticmethod
|
||||
def delete_fraction(db: Session, permission: str, line: int, fraction: str):
|
||||
frac = FractionRuleOctaveService.get_fraction_by_permission_line(db, permission, line, fraction)
|
||||
frac = FractionRuleOctaveService.get_fraction_by_permission_line(
|
||||
db, permission, line, fraction
|
||||
)
|
||||
if frac:
|
||||
db.delete(frac)
|
||||
db.commit()
|
||||
return frac
|
||||
return frac
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Módulo de Licenses
|
||||
"""
|
||||
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
DTOs para módulo de licencias
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
@@ -9,6 +10,7 @@ from enum import Enum
|
||||
|
||||
class LicensePlanDTO(str, Enum):
|
||||
"""Planes de licencia"""
|
||||
|
||||
FREE = "free"
|
||||
BASIC = "basic"
|
||||
PROFESSIONAL = "professional"
|
||||
@@ -17,6 +19,7 @@ class LicensePlanDTO(str, Enum):
|
||||
|
||||
class LicenseStatusDTO(str, Enum):
|
||||
"""Estados de licencia"""
|
||||
|
||||
ACTIVE = "active"
|
||||
EXPIRED = "expired"
|
||||
SUSPENDED = "suspended"
|
||||
@@ -26,20 +29,25 @@ class LicenseStatusDTO(str, Enum):
|
||||
|
||||
class LicenseCreateDTO(BaseModel):
|
||||
"""DTO para crear una nueva licencia"""
|
||||
|
||||
tenant_id: int = Field(..., description="ID del tenant")
|
||||
plan: LicensePlanDTO = Field(..., description="Plan de licencia")
|
||||
max_users: int = Field(default=5, ge=1, description="Número máximo de usuarios")
|
||||
max_storage_gb: int = Field(default=10, ge=1, description="Almacenamiento máximo en GB")
|
||||
max_monthly_operations: int = Field(default=1000, ge=1, description="Operaciones mensuales máximas")
|
||||
|
||||
max_storage_gb: int = Field(
|
||||
default=10, ge=1, description="Almacenamiento máximo en GB"
|
||||
)
|
||||
max_monthly_operations: int = Field(
|
||||
default=1000, ge=1, description="Operaciones mensuales máximas"
|
||||
)
|
||||
|
||||
feature_api_access: bool = Field(default=True)
|
||||
feature_advanced_reports: bool = Field(default=False)
|
||||
feature_integrations: bool = Field(default=False)
|
||||
feature_dedicated_support: bool = Field(default=False)
|
||||
|
||||
|
||||
starts_at: datetime = Field(..., description="Fecha de inicio de vigencia")
|
||||
expires_at: datetime = Field(..., description="Fecha de expiración")
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
@@ -53,60 +61,63 @@ class LicenseCreateDTO(BaseModel):
|
||||
"feature_integrations": True,
|
||||
"feature_dedicated_support": False,
|
||||
"starts_at": "2025-01-01T00:00:00Z",
|
||||
"expires_at": "2025-12-31T23:59:59Z"
|
||||
"expires_at": "2025-12-31T23:59:59Z",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LicenseUpdateDTO(BaseModel):
|
||||
"""DTO para actualizar una licencia"""
|
||||
|
||||
plan: Optional[LicensePlanDTO] = None
|
||||
status: Optional[LicenseStatusDTO] = None
|
||||
max_users: Optional[int] = Field(None, ge=1)
|
||||
max_storage_gb: Optional[int] = Field(None, ge=1)
|
||||
max_monthly_operations: Optional[int] = Field(None, ge=1)
|
||||
|
||||
|
||||
feature_api_access: Optional[bool] = None
|
||||
feature_advanced_reports: Optional[bool] = None
|
||||
feature_integrations: Optional[bool] = None
|
||||
feature_dedicated_support: Optional[bool] = None
|
||||
|
||||
|
||||
expires_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class LicenseResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de licencia"""
|
||||
|
||||
id: int
|
||||
tenant_id: int
|
||||
plan: LicensePlanDTO
|
||||
status: LicenseStatusDTO
|
||||
|
||||
|
||||
max_users: int
|
||||
max_storage_gb: int
|
||||
max_monthly_operations: int
|
||||
|
||||
|
||||
feature_api_access: bool
|
||||
feature_advanced_reports: bool
|
||||
feature_integrations: bool
|
||||
feature_dedicated_support: bool
|
||||
|
||||
|
||||
starts_at: datetime
|
||||
expires_at: datetime
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class LicenseValidationResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de validación de licencia"""
|
||||
|
||||
is_valid: bool
|
||||
status: LicenseStatusDTO
|
||||
plan: LicensePlanDTO
|
||||
expires_at: datetime
|
||||
reason: Optional[str] = None
|
||||
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
@@ -114,13 +125,14 @@ class LicenseValidationResponseDTO(BaseModel):
|
||||
"status": "active",
|
||||
"plan": "professional",
|
||||
"expires_at": "2025-12-31T23:59:59Z",
|
||||
"reason": None
|
||||
"reason": None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LicenseUsageResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de uso de licencia"""
|
||||
|
||||
tenant_id: int
|
||||
period_start: datetime
|
||||
period_end: datetime
|
||||
@@ -128,16 +140,16 @@ class LicenseUsageResponseDTO(BaseModel):
|
||||
storage_used_gb: int
|
||||
operations_count: int
|
||||
api_calls_count: int
|
||||
|
||||
|
||||
# Límites actuales
|
||||
max_users: int
|
||||
max_storage_gb: int
|
||||
max_monthly_operations: int
|
||||
|
||||
|
||||
# Porcentajes de uso
|
||||
users_usage_percent: float
|
||||
storage_usage_percent: float
|
||||
operations_usage_percent: float
|
||||
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
"""
|
||||
Modelos ORM para gestión de licencias
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey, Enum as SQLEnum
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
DateTime,
|
||||
Boolean,
|
||||
ForeignKey,
|
||||
Enum as SQLEnum,
|
||||
)
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
@@ -12,6 +21,7 @@ import enum
|
||||
|
||||
class LicensePlan(enum.Enum):
|
||||
"""Planes de licencia disponibles"""
|
||||
|
||||
FREE = "free"
|
||||
BASIC = "basic"
|
||||
PROFESSIONAL = "professional"
|
||||
@@ -20,6 +30,7 @@ class LicensePlan(enum.Enum):
|
||||
|
||||
class LicenseStatus(enum.Enum):
|
||||
"""Estados de licencia"""
|
||||
|
||||
ACTIVE = "active"
|
||||
EXPIRED = "expired"
|
||||
SUSPENDED = "suspended"
|
||||
@@ -31,36 +42,45 @@ class License(Base):
|
||||
"""
|
||||
Modelo de Licencia - Control de planes y límites por tenant
|
||||
"""
|
||||
|
||||
__tablename__ = "licenses"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
tenant_id = Column(Integer, ForeignKey("a76.tenants.id"), nullable=False, unique=True, index=True)
|
||||
|
||||
tenant_id = Column(
|
||||
Integer, ForeignKey("a76.tenants.id"), nullable=False, unique=True, index=True
|
||||
)
|
||||
|
||||
# Plan y características
|
||||
plan = Column(SQLEnum(LicensePlan), default=LicensePlan.FREE, nullable=False)
|
||||
status = Column(SQLEnum(LicenseStatus), default=LicenseStatus.PENDING, nullable=False)
|
||||
|
||||
status = Column(
|
||||
SQLEnum(LicenseStatus), default=LicenseStatus.PENDING, nullable=False
|
||||
)
|
||||
|
||||
# Límites del plan
|
||||
max_users = Column(Integer, default=5, nullable=False)
|
||||
max_storage_gb = Column(Integer, default=10, nullable=False)
|
||||
max_monthly_operations = Column(Integer, default=1000, nullable=False)
|
||||
|
||||
|
||||
# Features habilitadas (booleans)
|
||||
feature_api_access = Column(Boolean, default=True)
|
||||
feature_advanced_reports = Column(Boolean, default=False)
|
||||
feature_integrations = Column(Boolean, default=False)
|
||||
feature_dedicated_support = Column(Boolean, default=False)
|
||||
|
||||
|
||||
# Vigencia
|
||||
starts_at = Column(DateTime(timezone=True), nullable=False)
|
||||
expires_at = Column(DateTime(timezone=True), nullable=False)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"<License(tenant_id={self.tenant_id}, plan={self.plan.value}, status={self.status.value})>"
|
||||
|
||||
@@ -69,25 +89,32 @@ class LicenseUsage(Base):
|
||||
"""
|
||||
Modelo para tracking de uso de licencia
|
||||
"""
|
||||
|
||||
__tablename__ = "license_usage"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
tenant_id = Column(Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True)
|
||||
|
||||
tenant_id = Column(
|
||||
Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True
|
||||
)
|
||||
|
||||
# Métricas de uso
|
||||
period_start = Column(DateTime(timezone=True), nullable=False)
|
||||
period_end = Column(DateTime(timezone=True), nullable=False)
|
||||
|
||||
|
||||
active_users = Column(Integer, default=0)
|
||||
storage_used_gb = Column(Integer, default=0)
|
||||
operations_count = Column(Integer, default=0)
|
||||
api_calls_count = Column(Integer, default=0)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"<LicenseUsage(tenant_id={self.tenant_id}, operations={self.operations_count})>"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Endpoints API para gestión de licencias
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -11,7 +12,7 @@ from .dto import (
|
||||
LicenseUpdateDTO,
|
||||
LicenseResponseDTO,
|
||||
LicenseValidationResponseDTO,
|
||||
LicenseUsageResponseDTO
|
||||
LicenseUsageResponseDTO,
|
||||
)
|
||||
from .service import LicenseService
|
||||
|
||||
@@ -22,11 +23,11 @@ router = APIRouter(prefix="/licenses")
|
||||
async def create_license(
|
||||
license_data: LicenseCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(has_role("admin"))
|
||||
current_user: dict = Depends(has_role("admin")),
|
||||
):
|
||||
"""
|
||||
Crea una nueva licencia para un tenant
|
||||
|
||||
|
||||
Requiere rol: admin
|
||||
"""
|
||||
service = LicenseService(db)
|
||||
@@ -37,7 +38,7 @@ async def create_license(
|
||||
async def get_license_by_tenant(
|
||||
tenant_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Obtiene la licencia de un tenant específico
|
||||
@@ -54,11 +55,11 @@ async def update_license(
|
||||
tenant_id: int,
|
||||
license_data: LicenseUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(has_role("admin"))
|
||||
current_user: dict = Depends(has_role("admin")),
|
||||
):
|
||||
"""
|
||||
Actualiza la licencia de un tenant
|
||||
|
||||
|
||||
Requiere rol: admin
|
||||
"""
|
||||
service = LicenseService(db)
|
||||
@@ -72,7 +73,7 @@ async def update_license(
|
||||
async def validate_license(
|
||||
tenant_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Valida si la licencia de un tenant está activa y vigente
|
||||
@@ -86,7 +87,7 @@ async def validate_license(
|
||||
async def get_license_usage(
|
||||
tenant_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Obtiene el uso actual de la licencia de un tenant
|
||||
@@ -102,7 +103,7 @@ async def get_license_usage(
|
||||
async def get_my_license(
|
||||
request: Request,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Obtiene la licencia del tenant del usuario actual
|
||||
@@ -110,7 +111,7 @@ async def get_my_license(
|
||||
tenant_id = getattr(request.state, "tenant_id", None)
|
||||
if not tenant_id:
|
||||
raise HTTPException(status_code=400, detail="Tenant ID not found in request")
|
||||
|
||||
|
||||
service = LicenseService(db)
|
||||
license = service.get_license_by_tenant(tenant_id)
|
||||
if not license:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Servicio de lógica de negocio para licencias
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
@@ -14,7 +15,7 @@ from .dto import (
|
||||
LicenseUpdateDTO,
|
||||
LicenseResponseDTO,
|
||||
LicenseValidationResponseDTO,
|
||||
LicenseUsageResponseDTO
|
||||
LicenseUsageResponseDTO,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -22,35 +23,37 @@ 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()
|
||||
|
||||
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"
|
||||
detail=f"Tenant {license_data.tenant_id} already has a license",
|
||||
)
|
||||
|
||||
|
||||
# Crear licencia
|
||||
db_license = License(
|
||||
tenant_id=license_data.tenant_id,
|
||||
@@ -64,17 +67,17 @@ class LicenseService:
|
||||
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
|
||||
expires_at=license_data.expires_at,
|
||||
)
|
||||
|
||||
|
||||
self.db.add(db_license)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_license)
|
||||
|
||||
|
||||
logger.info(f"License created for tenant {license_data.tenant_id}")
|
||||
|
||||
|
||||
return LicenseResponseDTO.model_validate(db_license)
|
||||
|
||||
|
||||
except IntegrityError as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"IntegrityError creating license: {str(e)}")
|
||||
@@ -85,14 +88,14 @@ class LicenseService:
|
||||
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
|
||||
"""
|
||||
@@ -100,22 +103,24 @@ class LicenseService:
|
||||
if not license:
|
||||
return None
|
||||
return LicenseResponseDTO.model_validate(license)
|
||||
|
||||
def update_license(self, tenant_id: int, license_data: LicenseUpdateDTO) -> Optional[LicenseResponseDTO]:
|
||||
|
||||
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():
|
||||
@@ -123,7 +128,7 @@ class LicenseService:
|
||||
# Convertir enums
|
||||
value = LicensePlan(value) if field == "plan" else LicenseStatus(value)
|
||||
setattr(license, field, value)
|
||||
|
||||
|
||||
try:
|
||||
self.db.commit()
|
||||
self.db.refresh(license)
|
||||
@@ -133,30 +138,30 @@ class LicenseService:
|
||||
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"
|
||||
"reason": "License not found",
|
||||
}
|
||||
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
|
||||
# Verificar estado
|
||||
if license.status != LicenseStatus.ACTIVE:
|
||||
return {
|
||||
@@ -164,51 +169,54 @@ class LicenseService:
|
||||
"status": license.status.value,
|
||||
"plan": license.plan.value,
|
||||
"expires_at": license.expires_at,
|
||||
"reason": f"License status is {license.status.value}"
|
||||
"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"
|
||||
"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
|
||||
"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()
|
||||
|
||||
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(
|
||||
@@ -218,14 +226,26 @@ class LicenseService:
|
||||
active_users=0,
|
||||
storage_used_gb=0,
|
||||
operations_count=0,
|
||||
api_calls_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
|
||||
|
||||
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,
|
||||
@@ -239,5 +259,5 @@ class LicenseService:
|
||||
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)
|
||||
operations_usage_percent=round(operations_usage, 2),
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ DTOs for GBultos.
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class GBultoBaseDTO(BaseModel):
|
||||
CODE: str
|
||||
DESCRIPTION: Optional[str]
|
||||
@@ -15,9 +16,11 @@ class GBultoBaseDTO(BaseModel):
|
||||
CODE_ACE: Optional[str]
|
||||
CODE_AAMEX: Optional[str]
|
||||
|
||||
|
||||
class GBultoCreateDTO(GBultoBaseDTO):
|
||||
pass
|
||||
|
||||
|
||||
class GBultoUpdateDTO(BaseModel):
|
||||
DESCRIPTION: Optional[str]
|
||||
DESCRIPTIONI: Optional[str]
|
||||
@@ -27,9 +30,10 @@ class GBultoUpdateDTO(BaseModel):
|
||||
CODE_ACE: Optional[str]
|
||||
CODE_AAMEX: Optional[str]
|
||||
|
||||
|
||||
class GBultoResponseDTO(GBultoBaseDTO):
|
||||
CREATED_AT: Optional[str]
|
||||
UPDATED_AT: Optional[str]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
from_attributes = True
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import DateTime, Integer, String, DECIMAL, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
Integer,
|
||||
String,
|
||||
DECIMAL,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
@@ -10,17 +19,21 @@ from core.database import Base
|
||||
class Package(Base):
|
||||
__tablename__ = "packages" # GBultos
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='packages_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_packages_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_packages_company'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'key', name='packages_key_ukey'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="packages_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_packages_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_packages_company"
|
||||
),
|
||||
UniqueConstraint("tenant_id", "company_id", "key", name="packages_key_ukey"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
key: Mapped[str] = mapped_column(String(5))
|
||||
description_es: Mapped[Optional[str]] = mapped_column(String(40))
|
||||
description_en: Mapped[Optional[str]] = mapped_column(String(40))
|
||||
@@ -29,10 +42,12 @@ class Package(Base):
|
||||
plural_in: Mapped[Optional[str]] = mapped_column(String(4))
|
||||
code_ace: Mapped[Optional[str]] = mapped_column(String(4))
|
||||
code_aamex: Mapped[Optional[str]] = mapped_column(String(9))
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
@@ -16,7 +16,7 @@ async def list_bultos(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List all GBultos with pagination.
|
||||
@@ -35,7 +35,7 @@ async def list_bultos(
|
||||
async def read_bulto(
|
||||
code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get a specific Package by its CODE.
|
||||
@@ -57,7 +57,7 @@ async def read_bulto(
|
||||
async def create_gbulto(
|
||||
bulto_data: GBultoCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new Package.
|
||||
@@ -77,7 +77,7 @@ async def update_bulto(
|
||||
code: str,
|
||||
bulto_data: GBultoUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update an existing Package.
|
||||
@@ -99,7 +99,7 @@ async def update_bulto(
|
||||
async def delete_bulto(
|
||||
code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete a Package by its CODE.
|
||||
@@ -113,4 +113,4 @@ async def delete_bulto(
|
||||
|
||||
bulto = GBultoService.delete_bulto(db, code)
|
||||
if not bulto:
|
||||
raise HTTPException(status_code=404, detail="Package not found")
|
||||
raise HTTPException(status_code=404, detail="Package not found")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from . import models, dto
|
||||
|
||||
|
||||
class GBultoService:
|
||||
"""
|
||||
Service layer for GBultos.
|
||||
@@ -34,4 +35,4 @@ class GBultoService:
|
||||
if bulto:
|
||||
db.delete(bulto)
|
||||
db.commit()
|
||||
return bulto
|
||||
return bulto
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Módulo de GParts
|
||||
"""
|
||||
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
DTOs (Data Transfer Objects) para módulo de partes/componentes
|
||||
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
@@ -10,43 +11,66 @@ from decimal import Decimal
|
||||
|
||||
class PartCreateDTO(BaseModel):
|
||||
"""DTO para crear una parte"""
|
||||
|
||||
client_id: int = Field(..., description="Client key")
|
||||
part_number: str = Field(..., max_length=49, description="Part number")
|
||||
fraction: Optional[str] = Field(None, max_length=10, description="Tariff 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")
|
||||
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"
|
||||
)
|
||||
part_class: Optional[str] = Field(None, max_length=8, description="Part class")
|
||||
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure")
|
||||
commercial_part_number: Optional[str] = Field(None, max_length=70, description="Commercial part number")
|
||||
country_of_origin: Optional[str] = Field(None, max_length=3, description="Country of origin code")
|
||||
|
||||
unit_of_measure: Optional[str] = Field(
|
||||
None, max_length=5, description="Unit of measure"
|
||||
)
|
||||
commercial_part_number: Optional[str] = Field(
|
||||
None, max_length=70, description="Commercial part number"
|
||||
)
|
||||
country_of_origin: Optional[str] = Field(
|
||||
None, max_length=3, description="Country of origin code"
|
||||
)
|
||||
|
||||
# Pricing and currency
|
||||
unit_cost: Optional[Decimal] = Field(None, description="Unit cost")
|
||||
currency_type: Optional[str] = Field(None, max_length=2, description="Currency type")
|
||||
currency_type: Optional[str] = Field(
|
||||
None, max_length=2, description="Currency type"
|
||||
)
|
||||
currency_key: Optional[str] = Field(None, max_length=3, description="Currency key")
|
||||
|
||||
|
||||
# Weight information
|
||||
unit_weight: Optional[Decimal] = Field(None, description="Unit weight")
|
||||
weight_type: Optional[str] = Field(None, max_length=6, description="Weight type")
|
||||
|
||||
|
||||
# Classification and regulatory
|
||||
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
|
||||
us_fraction: Optional[str] = Field(
|
||||
None, max_length=16, description="US tariff fraction"
|
||||
)
|
||||
fda_key: Optional[str] = Field(None, max_length=20, description="FDA key")
|
||||
fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key")
|
||||
license_code: Optional[str] = Field(None, max_length=3, description="License code")
|
||||
eccn: Optional[str] = Field(None, max_length=20, description="Export Control Classification Number")
|
||||
eccn: Optional[str] = Field(
|
||||
None, max_length=20, description="Export Control Classification Number"
|
||||
)
|
||||
export_code: Optional[str] = Field(None, max_length=2, description="Export code")
|
||||
exclusion_symbol: Optional[str] = Field(None, max_length=19, description="Exclusion symbol")
|
||||
|
||||
exclusion_symbol: Optional[str] = Field(
|
||||
None, max_length=19, description="Exclusion symbol"
|
||||
)
|
||||
|
||||
# Additional information
|
||||
supplier: Optional[str] = Field(None, max_length=14, description="Supplier")
|
||||
alternate_unit_measure: Optional[str] = Field(None, max_length=14, description="Alternate unit of measure")
|
||||
alternate_unit_measure: Optional[str] = Field(
|
||||
None, max_length=14, description="Alternate unit of measure"
|
||||
)
|
||||
added_value: Optional[Decimal] = Field(None, description="Added value")
|
||||
|
||||
|
||||
# Status and media
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
creation_date: Optional[int] = Field(None, description="Creation date")
|
||||
part_photo: Optional[str] = Field(None, max_length=255, description="Part photo URL")
|
||||
part_photo: Optional[str] = Field(
|
||||
None, max_length=255, description="Part photo URL"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -54,40 +78,63 @@ class PartCreateDTO(BaseModel):
|
||||
|
||||
class PartUpdateDTO(BaseModel):
|
||||
"""DTO para actualizar una parte"""
|
||||
|
||||
fraction: Optional[str] = Field(None, max_length=10, description="Tariff 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")
|
||||
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"
|
||||
)
|
||||
part_class: Optional[str] = Field(None, max_length=8, description="Part class")
|
||||
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure")
|
||||
commercial_part_number: Optional[str] = Field(None, max_length=70, description="Commercial part number")
|
||||
country_of_origin: Optional[str] = Field(None, max_length=3, description="Country of origin code")
|
||||
|
||||
unit_of_measure: Optional[str] = Field(
|
||||
None, max_length=5, description="Unit of measure"
|
||||
)
|
||||
commercial_part_number: Optional[str] = Field(
|
||||
None, max_length=70, description="Commercial part number"
|
||||
)
|
||||
country_of_origin: Optional[str] = Field(
|
||||
None, max_length=3, description="Country of origin code"
|
||||
)
|
||||
|
||||
# Pricing and currency
|
||||
unit_cost: Optional[Decimal] = Field(None, description="Unit cost")
|
||||
currency_type: Optional[str] = Field(None, max_length=2, description="Currency type")
|
||||
currency_type: Optional[str] = Field(
|
||||
None, max_length=2, description="Currency type"
|
||||
)
|
||||
currency_key: Optional[str] = Field(None, max_length=3, description="Currency key")
|
||||
|
||||
|
||||
# Weight information
|
||||
unit_weight: Optional[Decimal] = Field(None, description="Unit weight")
|
||||
weight_type: Optional[str] = Field(None, max_length=6, description="Weight type")
|
||||
|
||||
|
||||
# Classification and regulatory
|
||||
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
|
||||
us_fraction: Optional[str] = Field(
|
||||
None, max_length=16, description="US tariff fraction"
|
||||
)
|
||||
fda_key: Optional[str] = Field(None, max_length=20, description="FDA key")
|
||||
fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key")
|
||||
license_code: Optional[str] = Field(None, max_length=3, description="License code")
|
||||
eccn: Optional[str] = Field(None, max_length=20, description="Export Control Classification Number")
|
||||
eccn: Optional[str] = Field(
|
||||
None, max_length=20, description="Export Control Classification Number"
|
||||
)
|
||||
export_code: Optional[str] = Field(None, max_length=2, description="Export code")
|
||||
exclusion_symbol: Optional[str] = Field(None, max_length=19, description="Exclusion symbol")
|
||||
|
||||
exclusion_symbol: Optional[str] = Field(
|
||||
None, max_length=19, description="Exclusion symbol"
|
||||
)
|
||||
|
||||
# Additional information
|
||||
supplier: Optional[str] = Field(None, max_length=14, description="Supplier")
|
||||
alternate_unit_measure: Optional[str] = Field(None, max_length=14, description="Alternate unit of measure")
|
||||
alternate_unit_measure: Optional[str] = Field(
|
||||
None, max_length=14, description="Alternate unit of measure"
|
||||
)
|
||||
added_value: Optional[Decimal] = Field(None, description="Added value")
|
||||
|
||||
|
||||
# Status and media
|
||||
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
|
||||
part_photo: Optional[str] = Field(None, max_length=255, description="Part photo URL")
|
||||
part_photo: Optional[str] = Field(
|
||||
None, max_length=255, description="Part photo URL"
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -95,6 +142,7 @@ class PartUpdateDTO(BaseModel):
|
||||
|
||||
class PartResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de parte"""
|
||||
|
||||
client_id: int
|
||||
part_number: str
|
||||
fraction: Optional[str] = None
|
||||
@@ -104,16 +152,16 @@ class PartResponseDTO(BaseModel):
|
||||
unit_of_measure: Optional[str] = None
|
||||
commercial_part_number: Optional[str] = None
|
||||
country_of_origin: Optional[str] = None
|
||||
|
||||
|
||||
# Pricing and currency
|
||||
unit_cost: Optional[Decimal] = None
|
||||
currency_type: Optional[str] = None
|
||||
currency_key: Optional[str] = None
|
||||
|
||||
|
||||
# Weight information
|
||||
unit_weight: Optional[Decimal] = None
|
||||
weight_type: Optional[str] = None
|
||||
|
||||
|
||||
# Classification and regulatory
|
||||
us_fraction: Optional[str] = None
|
||||
fda_key: Optional[str] = None
|
||||
@@ -122,18 +170,18 @@ class PartResponseDTO(BaseModel):
|
||||
eccn: Optional[str] = None
|
||||
export_code: Optional[str] = None
|
||||
exclusion_symbol: Optional[str] = None
|
||||
|
||||
|
||||
# Additional information
|
||||
supplier: Optional[str] = None
|
||||
alternate_unit_measure: Optional[str] = None
|
||||
added_value: Optional[Decimal] = None
|
||||
|
||||
|
||||
# Status and dates
|
||||
enabled_disabled: Optional[int] = None
|
||||
creation_date: Optional[int] = None
|
||||
modification_date: Optional[int] = None
|
||||
modification_date_iso: Optional[datetime] = None
|
||||
|
||||
|
||||
# Media
|
||||
part_photo: Optional[str] = None
|
||||
|
||||
@@ -143,6 +191,7 @@ class PartResponseDTO(BaseModel):
|
||||
|
||||
class PartBasicDTO(BaseModel):
|
||||
"""DTO para información básica de parte"""
|
||||
|
||||
client_id: int
|
||||
part_number: str
|
||||
description_spanish: Optional[str] = None
|
||||
@@ -158,6 +207,7 @@ class PartBasicDTO(BaseModel):
|
||||
|
||||
class PartListDTO(BaseModel):
|
||||
"""DTO para lista de partes"""
|
||||
|
||||
parts: list[PartBasicDTO]
|
||||
total: int
|
||||
page: int
|
||||
@@ -169,6 +219,7 @@ class PartListDTO(BaseModel):
|
||||
|
||||
class PartSearchDTO(BaseModel):
|
||||
"""DTO para búsqueda de partes"""
|
||||
|
||||
client_id: Optional[int] = Field(None, description="Filter by client key")
|
||||
part_number: Optional[str] = Field(None, description="Search by part number")
|
||||
description: Optional[str] = Field(None, description="Search in descriptions")
|
||||
@@ -178,5 +229,3 @@ class PartSearchDTO(BaseModel):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
"""
|
||||
Modelos ORM para gestión de partes/componentes
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Integer, String, Numeric, SmallInteger, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Integer,
|
||||
String,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
ForeignKey,
|
||||
PrimaryKeyConstraint,
|
||||
ForeignKeyConstraint,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from core.database import Base
|
||||
@@ -19,25 +29,34 @@ class Part(Base):
|
||||
"""
|
||||
Modelo para la tabla GPartes - Información de partes en los sistemas SCAII (N), SCAF (S) Y WINSAAI (W)
|
||||
"""
|
||||
|
||||
__tablename__ = "parts"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='parts_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_parts_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_parts_company'),
|
||||
ForeignKeyConstraint(['country_of_origin'], ['public.countries.m3_key'], name='fk_parts_country'),
|
||||
ForeignKeyConstraint(['currency_key'], ['public.currency_types.code'], name='fk_parts_currency'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'part_number', name='client_part_ukey'),
|
||||
{"schema": "a76"}
|
||||
PrimaryKeyConstraint("id", name="parts_pkey"),
|
||||
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"], name="fk_parts_tenant"),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_parts_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["country_of_origin"], ["public.countries.m3_key"], name="fk_parts_country"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["currency_key"], ["public.currency_types.code"], name="fk_parts_currency"
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id", "company_id", "part_number", name="client_part_ukey"
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
# Unique constraint compuesta
|
||||
client_id: Mapped[int] = mapped_column(Integer)
|
||||
part_number: Mapped[str] = mapped_column(String(49))
|
||||
|
||||
|
||||
# Basic information
|
||||
fraction: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
description_spanish: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
@@ -46,53 +65,59 @@ class Part(Base):
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5))
|
||||
commercial_part_number: Mapped[Optional[str]] = mapped_column(String(70))
|
||||
country_of_origin: Mapped[Optional[str]] = mapped_column(String(3))
|
||||
|
||||
|
||||
# Pricing and currency
|
||||
unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
|
||||
currency_type: Mapped[Optional[str]] = mapped_column(String(2))
|
||||
currency_key: Mapped[Optional[str]] = mapped_column(String(3))
|
||||
|
||||
|
||||
# Weight information
|
||||
unit_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8))
|
||||
weight_type: Mapped[Optional[str]] = mapped_column(String(6))
|
||||
|
||||
|
||||
# Classification and regulatory
|
||||
us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAME
|
||||
fda_key: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
fcc_key: Mapped[Optional[str]] = mapped_column(String(30))
|
||||
license_code: Mapped[Optional[str]] = mapped_column(String(3))
|
||||
eccn: Mapped[Optional[str]] = mapped_column(String(20)) # Export Control Classification Number
|
||||
eccn: Mapped[Optional[str]] = mapped_column(
|
||||
String(20)
|
||||
) # Export Control Classification Number
|
||||
export_code: Mapped[Optional[str]] = mapped_column(String(2))
|
||||
exclusion_symbol: Mapped[Optional[str]] = mapped_column(String(19)) # SIMBOLOEXCLIC
|
||||
|
||||
|
||||
# Additional information
|
||||
supplier: Mapped[Optional[str]] = mapped_column(String(14))
|
||||
alternate_unit_measure: Mapped[Optional[str]] = mapped_column(String(14))
|
||||
added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
|
||||
|
||||
|
||||
# Status and dates
|
||||
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE
|
||||
modification_date: Mapped[Optional[int]] = mapped_column() # FECHAMODIFICA
|
||||
modification_date_iso: Mapped[Optional[datetime]] = mapped_column() # FECHAMODIFICA_ISO
|
||||
|
||||
modification_date_iso: Mapped[Optional[datetime]] = (
|
||||
mapped_column()
|
||||
) # FECHAMODIFICA_ISO
|
||||
|
||||
# Media
|
||||
part_photo: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
part_photo: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
# Relationships
|
||||
country: Mapped[Optional["Country"]] = relationship(foreign_keys=[country_of_origin])
|
||||
currency: Mapped[Optional["CurrencyType"]] = relationship(foreign_keys=[currency_key])
|
||||
|
||||
country: Mapped[Optional["Country"]] = relationship(
|
||||
foreign_keys=[country_of_origin]
|
||||
)
|
||||
currency: Mapped[Optional["CurrencyType"]] = relationship(
|
||||
foreign_keys=[currency_key]
|
||||
)
|
||||
|
||||
# Relationship with Class through composite foreign key
|
||||
# Note: This requires both client_id and part_class to match client_id and class_code in Class
|
||||
part_class_info: Mapped[Optional["Class"]] = relationship(
|
||||
primaryjoin="and_(Part.client_id == Class.client_id, Part.part_class == Class.class_code)",
|
||||
foreign_keys="[Part.client_id, Part.part_class]",
|
||||
viewonly=True,
|
||||
back_populates="parts"
|
||||
back_populates="parts",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Part(client_id={self.client_id}, part_number='{self.part_number}', description='{self.description_spanish}')>"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Endpoints API para gestión de partes/componentes
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
@@ -9,12 +10,12 @@ from core.database import get_core_db
|
||||
from core.security import get_current_user, has_role
|
||||
from .service import PartService
|
||||
from .dto import (
|
||||
PartCreateDTO,
|
||||
PartUpdateDTO,
|
||||
PartCreateDTO,
|
||||
PartUpdateDTO,
|
||||
PartResponseDTO,
|
||||
PartBasicDTO,
|
||||
PartListDTO,
|
||||
PartSearchDTO
|
||||
PartSearchDTO,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/parts")
|
||||
@@ -24,7 +25,7 @@ router = APIRouter(prefix="/parts")
|
||||
async def create_part(
|
||||
part_data: PartCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Create a new part in the system
|
||||
@@ -43,7 +44,9 @@ async def create_part(
|
||||
@router.get("/", response_model=PartListDTO)
|
||||
async def list_parts(
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
|
||||
limit: int = Query(
|
||||
100, ge=1, le=1000, description="Maximum number of records to return"
|
||||
),
|
||||
client_id: Optional[int] = Query(None, description="Filter by client key"),
|
||||
part_number: Optional[str] = Query(None, description="Search by part number"),
|
||||
description: Optional[str] = Query(None, description="Search in descriptions"),
|
||||
@@ -51,7 +54,7 @@ async def list_parts(
|
||||
supplier: Optional[str] = Query(None, description="Filter by supplier"),
|
||||
enabled_only: bool = Query(False, description="Show only enabled parts"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
List parts with optional filters and pagination
|
||||
@@ -70,7 +73,7 @@ async def list_parts(
|
||||
description=description,
|
||||
fraction=fraction,
|
||||
supplier=supplier,
|
||||
enabled_only=enabled_only
|
||||
enabled_only=enabled_only,
|
||||
)
|
||||
return service.list_parts(skip, limit, search_params)
|
||||
|
||||
@@ -81,7 +84,7 @@ async def get_parts_by_client(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get all parts for a specific client
|
||||
@@ -101,7 +104,7 @@ async def get_parts_by_client(
|
||||
async def search_by_fraction(
|
||||
fraction: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Search parts by tariff fraction
|
||||
@@ -121,7 +124,7 @@ async def search_by_fraction(
|
||||
async def search_by_supplier(
|
||||
supplier: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Search parts by supplier
|
||||
@@ -141,7 +144,7 @@ async def search_by_supplier(
|
||||
async def get_parts_by_country(
|
||||
country_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get parts by country of origin
|
||||
@@ -159,8 +162,7 @@ async def get_parts_by_country(
|
||||
|
||||
@router.get("/statistics", response_model=dict)
|
||||
async def get_parts_statistics(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get basic parts statistics
|
||||
@@ -181,7 +183,7 @@ async def get_part(
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get part by composite key (client_id + part_number)
|
||||
@@ -197,8 +199,8 @@ async def get_part(
|
||||
part = service.get_part(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
|
||||
)
|
||||
return part
|
||||
|
||||
@@ -209,7 +211,7 @@ async def update_part(
|
||||
part_number: str,
|
||||
part_data: PartUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Update part information
|
||||
@@ -225,8 +227,8 @@ async def update_part(
|
||||
part = service.update_part(client_id, part_number, part_data)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
|
||||
)
|
||||
return part
|
||||
|
||||
@@ -236,11 +238,11 @@ async def delete_part(
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Delete part from the system
|
||||
|
||||
|
||||
Note: This will completely remove the part from the system.
|
||||
"""
|
||||
# Validate access to the tenant and company
|
||||
@@ -253,17 +255,19 @@ async def delete_part(
|
||||
service = PartService(db)
|
||||
if not service.delete_part(client_id, part_number):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{client_id}/{part_number}/toggle-status", response_model=PartResponseDTO)
|
||||
@router.patch(
|
||||
"/{client_id}/{part_number}/toggle-status", response_model=PartResponseDTO
|
||||
)
|
||||
async def toggle_part_status(
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Toggle part enabled/disabled status
|
||||
@@ -279,8 +283,8 @@ async def toggle_part_status(
|
||||
part = service.toggle_status(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
|
||||
)
|
||||
return part
|
||||
|
||||
@@ -291,7 +295,7 @@ async def get_part_basic_info(
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get basic information for a part
|
||||
@@ -307,10 +311,10 @@ async def get_part_basic_info(
|
||||
part = service.get_part(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
|
||||
)
|
||||
|
||||
|
||||
return PartBasicDTO(
|
||||
client_id=part.client_id,
|
||||
part_number=part.part_number,
|
||||
@@ -319,7 +323,7 @@ async def get_part_basic_info(
|
||||
part_class=part.part_class,
|
||||
unit_cost=part.unit_cost,
|
||||
currency_key=part.currency_key,
|
||||
enabled_disabled=part.enabled_disabled
|
||||
enabled_disabled=part.enabled_disabled,
|
||||
)
|
||||
|
||||
|
||||
@@ -328,7 +332,7 @@ async def get_part_regulatory_info(
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get regulatory information for a part (FDA, FCC, ECCN, etc.)
|
||||
@@ -344,10 +348,10 @@ async def get_part_regulatory_info(
|
||||
part = service.get_part(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
status_code=404,
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"client_id": part.client_id,
|
||||
"part_number": part.part_number,
|
||||
@@ -358,7 +362,5 @@ async def get_part_regulatory_info(
|
||||
"license_code": part.license_code,
|
||||
"eccn": part.eccn,
|
||||
"export_code": part.export_code,
|
||||
"exclusion_symbol": part.exclusion_symbol
|
||||
"exclusion_symbol": part.exclusion_symbol,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Capa de servicio para lógica de negocio de partes/componentes
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy import or_, and_, func
|
||||
@@ -34,7 +35,10 @@ class PartService:
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating part: {e}")
|
||||
raise HTTPException(status_code=400, detail="Part with this client_id and part_number already exists")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Part with this client_id and part_number already exists",
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Unexpected error creating part: {e}")
|
||||
@@ -46,55 +50,58 @@ class PartService:
|
||||
Obtener una parte por clave de cliente y número de parte
|
||||
"""
|
||||
try:
|
||||
return db.query(Part).filter(
|
||||
and_(
|
||||
Part.client_id == client_id,
|
||||
Part.part_number == part_number
|
||||
return (
|
||||
db.query(Part)
|
||||
.filter(
|
||||
and_(Part.client_id == client_id, Part.part_number == part_number)
|
||||
)
|
||||
).first()
|
||||
.first()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting part: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error retrieving part")
|
||||
|
||||
@staticmethod
|
||||
def get_parts_paginated(
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search: Optional[str] = None,
|
||||
client_id: Optional[int] = None,
|
||||
fraction: Optional[str] = None,
|
||||
country_of_origin: Optional[str] = None
|
||||
country_of_origin: Optional[str] = None,
|
||||
) -> tuple[List[Part], int]:
|
||||
"""
|
||||
Obtener partes con paginación y filtros
|
||||
"""
|
||||
try:
|
||||
query = db.query(Part)
|
||||
|
||||
|
||||
# Aplicar filtros
|
||||
if search:
|
||||
query = query.filter(or_(
|
||||
Part.description_spanish.ilike(f"%{search}%"),
|
||||
Part.description_english.ilike(f"%{search}%"),
|
||||
Part.part_number.ilike(f"%{search}%")
|
||||
))
|
||||
|
||||
query = query.filter(
|
||||
or_(
|
||||
Part.description_spanish.ilike(f"%{search}%"),
|
||||
Part.description_english.ilike(f"%{search}%"),
|
||||
Part.part_number.ilike(f"%{search}%"),
|
||||
)
|
||||
)
|
||||
|
||||
if client_id is not None:
|
||||
query = query.filter(Part.client_id == client_id)
|
||||
|
||||
|
||||
if fraction:
|
||||
query = query.filter(Part.fraction == fraction)
|
||||
|
||||
|
||||
if country_of_origin:
|
||||
query = query.filter(Part.country_of_origin == country_of_origin)
|
||||
|
||||
|
||||
# Contar total
|
||||
total = query.count()
|
||||
|
||||
|
||||
# Aplicar paginación
|
||||
parts = query.offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
return parts, total
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting paginated parts: {e}")
|
||||
@@ -117,15 +124,21 @@ class PartService:
|
||||
Buscar partes por fracción arancelaria
|
||||
"""
|
||||
try:
|
||||
return db.query(Part).filter(
|
||||
or_(
|
||||
Part.fraction.ilike(f"%{fraction}%"),
|
||||
Part.us_fraction.ilike(f"%{fraction}%")
|
||||
return (
|
||||
db.query(Part)
|
||||
.filter(
|
||||
or_(
|
||||
Part.fraction.ilike(f"%{fraction}%"),
|
||||
Part.us_fraction.ilike(f"%{fraction}%"),
|
||||
)
|
||||
)
|
||||
).all()
|
||||
.all()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching parts by fraction: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error searching parts by fraction")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error searching parts by fraction"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def search_parts_by_supplier(db: Session, supplier: str) -> List[Part]:
|
||||
@@ -136,7 +149,9 @@ class PartService:
|
||||
return db.query(Part).filter(Part.supplier.ilike(f"%{supplier}%")).all()
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching parts by supplier: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error searching parts by supplier")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error searching parts by supplier"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def search_parts_by_country(db: Session, country_code: str) -> List[Part]:
|
||||
@@ -147,10 +162,14 @@ class PartService:
|
||||
return db.query(Part).filter(Part.country_of_origin == country_code).all()
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching parts by country: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error searching parts by country")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error searching parts by country"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_part(db: Session, client_id: int, part_number: str, part_data: PartUpdateDTO) -> Optional[Part]:
|
||||
def update_part(
|
||||
db: Session, client_id: int, part_number: str, part_data: PartUpdateDTO
|
||||
) -> Optional[Part]:
|
||||
"""
|
||||
Actualizar una parte existente
|
||||
"""
|
||||
@@ -158,11 +177,11 @@ class PartService:
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return None
|
||||
|
||||
|
||||
# Actualizar campos
|
||||
for field, value in part_data.model_dump(exclude_unset=True).items():
|
||||
setattr(db_part, field, value)
|
||||
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_part)
|
||||
return db_part
|
||||
@@ -180,7 +199,7 @@ class PartService:
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return False
|
||||
|
||||
|
||||
db.delete(db_part)
|
||||
db.commit()
|
||||
return True
|
||||
@@ -190,7 +209,9 @@ class PartService:
|
||||
raise HTTPException(status_code=500, detail="Error deleting part")
|
||||
|
||||
@staticmethod
|
||||
def toggle_part_status(db: Session, client_id: int, part_number: str) -> Optional[Part]:
|
||||
def toggle_part_status(
|
||||
db: Session, client_id: int, part_number: str
|
||||
) -> Optional[Part]:
|
||||
"""
|
||||
Cambiar el estado habilitado/deshabilitado de una parte
|
||||
"""
|
||||
@@ -198,10 +219,10 @@ class PartService:
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return None
|
||||
|
||||
|
||||
# Toggle status (assuming 1 = enabled, 0 = disabled)
|
||||
db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0
|
||||
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_part)
|
||||
return db_part
|
||||
@@ -217,37 +238,49 @@ class PartService:
|
||||
"""
|
||||
try:
|
||||
total_parts = db.query(Part).count()
|
||||
|
||||
|
||||
# Partes por cliente
|
||||
parts_by_client = db.query(
|
||||
Part.client_id,
|
||||
func.count(Part.part_number).label('count')
|
||||
).group_by(Part.client_id).all()
|
||||
|
||||
parts_by_client = (
|
||||
db.query(Part.client_id, func.count(Part.part_number).label("count"))
|
||||
.group_by(Part.client_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Partes por país de origen
|
||||
parts_by_country = db.query(
|
||||
Part.country_of_origin,
|
||||
func.count(Part.part_number).label('count')
|
||||
).filter(Part.country_of_origin.isnot(None))\
|
||||
.group_by(Part.country_of_origin).all()
|
||||
|
||||
parts_by_country = (
|
||||
db.query(
|
||||
Part.country_of_origin, func.count(Part.part_number).label("count")
|
||||
)
|
||||
.filter(Part.country_of_origin.isnot(None))
|
||||
.group_by(Part.country_of_origin)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Partes habilitadas vs deshabilitadas
|
||||
enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
|
||||
disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count()
|
||||
|
||||
|
||||
return {
|
||||
"total_parts": total_parts,
|
||||
"enabled_parts": enabled_parts,
|
||||
"disabled_parts": disabled_parts,
|
||||
"parts_by_client": [{"client_id": item[0], "count": item[1]} for item in parts_by_client],
|
||||
"parts_by_country": [{"country": item[0], "count": item[1]} for item in parts_by_country]
|
||||
"parts_by_client": [
|
||||
{"client_id": item[0], "count": item[1]} for item in parts_by_client
|
||||
],
|
||||
"parts_by_country": [
|
||||
{"country": item[0], "count": item[1]} for item in parts_by_country
|
||||
],
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting parts statistics: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error retrieving parts statistics")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error retrieving parts statistics"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_part_regulatory_info(db: Session, client_id: int, part_number: str) -> Optional[dict]:
|
||||
def get_part_regulatory_info(
|
||||
db: Session, client_id: int, part_number: str
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Obtener información regulatoria específica de una parte
|
||||
"""
|
||||
@@ -255,7 +288,7 @@ class PartService:
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return None
|
||||
|
||||
|
||||
return {
|
||||
"client_id": db_part.client_id,
|
||||
"part_number": db_part.part_number,
|
||||
@@ -267,9 +300,10 @@ class PartService:
|
||||
"eccn": db_part.eccn,
|
||||
"export_code": db_part.export_code,
|
||||
"exclusion_symbol": db_part.exclusion_symbol,
|
||||
"country_of_origin": db_part.country_of_origin
|
||||
"country_of_origin": db_part.country_of_origin,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting part regulatory info: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error retrieving part regulatory information")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error retrieving part regulatory information"
|
||||
)
|
||||
|
||||
@@ -5,23 +5,34 @@ from datetime import datetime
|
||||
|
||||
class PedimentoConfigAdditionalBase(BaseModel):
|
||||
"""Base schema for Pedimento Config Additional"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
add_po_identifier: Optional[int] = Field(None, description="Add PO identifier")
|
||||
do_not_exempt_norms_complement_x: Optional[int] = Field(None, description="Do not exempt norms complement X")
|
||||
manual_pedimento_year: Optional[str] = Field(None, max_length=2, description="Manual pedimento year")
|
||||
enable_import_invoice_recipient: Optional[int] = Field(None, description="Enable import invoice recipient")
|
||||
send_502_validation_file_for_consolidated: Optional[int] = Field(None, description="Send 502 validation file for consolidated")
|
||||
do_not_exempt_norms_complement_x: Optional[int] = Field(
|
||||
None, description="Do not exempt norms complement X"
|
||||
)
|
||||
manual_pedimento_year: Optional[str] = Field(
|
||||
None, max_length=2, description="Manual pedimento year"
|
||||
)
|
||||
enable_import_invoice_recipient: Optional[int] = Field(
|
||||
None, description="Enable import invoice recipient"
|
||||
)
|
||||
send_502_validation_file_for_consolidated: Optional[int] = Field(
|
||||
None, description="Send 502 validation file for consolidated"
|
||||
)
|
||||
add_remove_norms: Optional[int] = Field(None, description="Add/remove norms")
|
||||
|
||||
|
||||
class PedimentoConfigAdditionalCreate(PedimentoConfigAdditionalBase):
|
||||
"""Schema for creating a new Pedimento Config Additional"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoConfigAdditionalUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Config Additional"""
|
||||
|
||||
add_po_identifier: Optional[int] = None
|
||||
do_not_exempt_norms_complement_x: Optional[int] = None
|
||||
manual_pedimento_year: Optional[str] = Field(None, max_length=2)
|
||||
@@ -32,6 +43,7 @@ class PedimentoConfigAdditionalUpdate(BaseModel):
|
||||
|
||||
class PedimentoConfigAdditionalResponse(PedimentoConfigAdditionalBase):
|
||||
"""Schema for Pedimento Config Additional response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,27 +5,40 @@ from datetime import datetime
|
||||
|
||||
class PedimentoConfigCalculationsBase(BaseModel):
|
||||
"""Base schema for Pedimento Config Calculations"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
dta_type: Optional[str] = Field(None, max_length=1, description="DTA type")
|
||||
dta_operation: Optional[int] = Field(None, description="DTA operation")
|
||||
dta_vehicle_count: Optional[int] = Field(None, description="DTA vehicle count")
|
||||
dta_mixed_rate_8permil: Optional[int] = Field(None, description="DTA mixed rate 8 per mil")
|
||||
dta_mixed_rate_8permil: Optional[int] = Field(
|
||||
None, description="DTA mixed rate 8 per mil"
|
||||
)
|
||||
pays_vat: Optional[int] = Field(None, description="Pays VAT")
|
||||
pays_prevalidation: Optional[int] = Field(None, description="Pays prevalidation")
|
||||
include_sagar_certificate_fee: Optional[int] = Field(None, description="Include SAGAR certificate fee")
|
||||
fixed_vehicle_dta_fee: Optional[int] = Field(None, description="Fixed vehicle DTA fee")
|
||||
additional_fixed_fee: Optional[int] = Field(None, description="Additional fixed fee")
|
||||
additional_fixed_fee_payment_method: Optional[int] = Field(None, description="Additional fixed fee payment method")
|
||||
include_sagar_certificate_fee: Optional[int] = Field(
|
||||
None, description="Include SAGAR certificate fee"
|
||||
)
|
||||
fixed_vehicle_dta_fee: Optional[int] = Field(
|
||||
None, description="Fixed vehicle DTA fee"
|
||||
)
|
||||
additional_fixed_fee: Optional[int] = Field(
|
||||
None, description="Additional fixed fee"
|
||||
)
|
||||
additional_fixed_fee_payment_method: Optional[int] = Field(
|
||||
None, description="Additional fixed fee payment method"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoConfigCalculationsCreate(PedimentoConfigCalculationsBase):
|
||||
"""Schema for creating a new Pedimento Config Calculations"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoConfigCalculationsUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Config Calculations"""
|
||||
|
||||
dta_type: Optional[str] = Field(None, max_length=1)
|
||||
dta_operation: Optional[int] = None
|
||||
dta_vehicle_count: Optional[int] = None
|
||||
@@ -40,6 +53,7 @@ class PedimentoConfigCalculationsUpdate(BaseModel):
|
||||
|
||||
class PedimentoConfigCalculationsResponse(PedimentoConfigCalculationsBase):
|
||||
"""Schema for Pedimento Config Calculations response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -6,28 +6,43 @@ from datetime import datetime
|
||||
|
||||
class PedimentoConfigParametersBase(BaseModel):
|
||||
"""Base schema for Pedimento Config Parameters"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
is_embassy: Optional[int] = Field(None, description="Is embassy")
|
||||
embassy_dta: Optional[Decimal] = Field(None, description="Embassy DTA")
|
||||
rule_3121_section_ii: Optional[int] = Field(None, description="Rule 3.1.21 Section II")
|
||||
rule_3121_section_ii: Optional[int] = Field(
|
||||
None, description="Rule 3.1.21 Section II"
|
||||
)
|
||||
use_previous_tariff: Optional[int] = Field(None, description="Use previous tariff")
|
||||
use_payment_date_fi: Optional[int] = Field(None, description="Use payment date FI")
|
||||
add_state_supplier_record_505: Optional[int] = Field(None, description="Add state supplier record 505")
|
||||
customs_value_calculation: Optional[int] = Field(None, description="Customs value calculation")
|
||||
two_decimals_unit_value: Optional[int] = Field(None, description="Two decimals unit value")
|
||||
customs_value_per_item: Optional[int] = Field(None, description="Customs value per item")
|
||||
is_national_supplier: Optional[int] = Field(None, description="Is national supplier")
|
||||
add_state_supplier_record_505: Optional[int] = Field(
|
||||
None, description="Add state supplier record 505"
|
||||
)
|
||||
customs_value_calculation: Optional[int] = Field(
|
||||
None, description="Customs value calculation"
|
||||
)
|
||||
two_decimals_unit_value: Optional[int] = Field(
|
||||
None, description="Two decimals unit value"
|
||||
)
|
||||
customs_value_per_item: Optional[int] = Field(
|
||||
None, description="Customs value per item"
|
||||
)
|
||||
is_national_supplier: Optional[int] = Field(
|
||||
None, description="Is national supplier"
|
||||
)
|
||||
is_consolidated: Optional[int] = Field(None, description="Is consolidated")
|
||||
|
||||
|
||||
class PedimentoConfigParametersCreate(PedimentoConfigParametersBase):
|
||||
"""Schema for creating a new Pedimento Config Parameters"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoConfigParametersUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Config Parameters"""
|
||||
|
||||
is_embassy: Optional[int] = None
|
||||
embassy_dta: Optional[Decimal] = None
|
||||
rule_3121_section_ii: Optional[int] = None
|
||||
@@ -43,6 +58,7 @@ class PedimentoConfigParametersUpdate(BaseModel):
|
||||
|
||||
class PedimentoConfigParametersResponse(PedimentoConfigParametersBase):
|
||||
"""Schema for Pedimento Config Parameters response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
|
||||
class PedimentoConfigSurchargesBase(BaseModel):
|
||||
"""Base schema for Pedimento Config Surcharges"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
surcharge_igi: Optional[int] = Field(None, description="Surcharge IGI")
|
||||
@@ -17,11 +18,13 @@ class PedimentoConfigSurchargesBase(BaseModel):
|
||||
|
||||
class PedimentoConfigSurchargesCreate(PedimentoConfigSurchargesBase):
|
||||
"""Schema for creating a new Pedimento Config Surcharges"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoConfigSurchargesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Config Surcharges"""
|
||||
|
||||
surcharge_igi: Optional[int] = None
|
||||
surcharge_dta: Optional[int] = None
|
||||
surcharge_vat: Optional[int] = None
|
||||
@@ -32,6 +35,7 @@ class PedimentoConfigSurchargesUpdate(BaseModel):
|
||||
|
||||
class PedimentoConfigSurchargesResponse(PedimentoConfigSurchargesBase):
|
||||
"""Schema for Pedimento Config Surcharges response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
|
||||
class PedimentoConfigUpdateRectificationBase(BaseModel):
|
||||
"""Base schema for Pedimento Config Update Rectification"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
update_vat: Optional[int] = Field(None, description="Update VAT")
|
||||
@@ -16,11 +17,13 @@ class PedimentoConfigUpdateRectificationBase(BaseModel):
|
||||
|
||||
class PedimentoConfigUpdateRectificationCreate(PedimentoConfigUpdateRectificationBase):
|
||||
"""Schema for creating a new Pedimento Config Update Rectification"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoConfigUpdateRectificationUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Config Update Rectification"""
|
||||
|
||||
update_vat: Optional[int] = None
|
||||
update_advalorem: Optional[int] = None
|
||||
update_cc: Optional[int] = None
|
||||
@@ -28,8 +31,11 @@ class PedimentoConfigUpdateRectificationUpdate(BaseModel):
|
||||
calculate_surcharge: Optional[int] = None
|
||||
|
||||
|
||||
class PedimentoConfigUpdateRectificationResponse(PedimentoConfigUpdateRectificationBase):
|
||||
class PedimentoConfigUpdateRectificationResponse(
|
||||
PedimentoConfigUpdateRectificationBase
|
||||
):
|
||||
"""Schema for Pedimento Config Update Rectification response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
|
||||
class PedimentoConfigUpdatesBase(BaseModel):
|
||||
"""Base schema for Pedimento Config Updates"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
update_vat: Optional[int] = Field(None, description="Update VAT")
|
||||
@@ -15,11 +16,13 @@ class PedimentoConfigUpdatesBase(BaseModel):
|
||||
|
||||
class PedimentoConfigUpdatesCreate(PedimentoConfigUpdatesBase):
|
||||
"""Schema for creating a new Pedimento Config Updates"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoConfigUpdatesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Config Updates"""
|
||||
|
||||
update_vat: Optional[int] = None
|
||||
update_advalorem: Optional[int] = None
|
||||
update_cc: Optional[int] = None
|
||||
@@ -28,6 +31,7 @@ class PedimentoConfigUpdatesUpdate(BaseModel):
|
||||
|
||||
class PedimentoConfigUpdatesResponse(PedimentoConfigUpdatesBase):
|
||||
"""Schema for Pedimento Config Updates response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,25 +5,33 @@ from datetime import datetime
|
||||
|
||||
class PedimentoCustomsOfficesBase(BaseModel):
|
||||
"""Base schema for Pedimento Customs Offices"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
dispatch_customs: Optional[str] = Field(None, max_length=3, description="Dispatch customs")
|
||||
entry_exit_customs: Optional[str] = Field(None, max_length=3, description="Entry/exit customs")
|
||||
dispatch_customs: Optional[str] = Field(
|
||||
None, max_length=3, description="Dispatch customs"
|
||||
)
|
||||
entry_exit_customs: Optional[str] = Field(
|
||||
None, max_length=3, description="Entry/exit customs"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoCustomsOfficesCreate(PedimentoCustomsOfficesBase):
|
||||
"""Schema for creating a new Pedimento Customs Offices"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoCustomsOfficesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Customs Offices"""
|
||||
|
||||
dispatch_customs: Optional[str] = Field(None, max_length=3)
|
||||
entry_exit_customs: Optional[str] = Field(None, max_length=3)
|
||||
|
||||
|
||||
class PedimentoCustomsOfficesResponse(PedimentoCustomsOfficesBase):
|
||||
"""Schema for Pedimento Customs Offices response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ from datetime import datetime, time
|
||||
|
||||
class PedimentoDatesBase(BaseModel):
|
||||
"""Base schema for Pedimento Dates"""
|
||||
|
||||
entry_date: Optional[datetime] = Field(None, description="Entry date")
|
||||
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
|
||||
payment_date: Optional[datetime] = Field(None, description="Payment date")
|
||||
rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date")
|
||||
rectification_payment_date: Optional[datetime] = Field(
|
||||
None, description="Rectification payment date"
|
||||
)
|
||||
extraction_date: Optional[datetime] = Field(None, description="Extraction date")
|
||||
submission_date: Optional[datetime] = Field(None, description="Submission date")
|
||||
eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
|
||||
@@ -21,11 +24,13 @@ class PedimentoDatesBase(BaseModel):
|
||||
|
||||
class PedimentoDatesCreate(PedimentoDatesBase):
|
||||
"""Schema for creating a new Pedimento Dates"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoDatesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Dates"""
|
||||
|
||||
entry_date: Optional[datetime] = None
|
||||
pedimento_date: Optional[datetime] = None
|
||||
payment_date: Optional[datetime] = None
|
||||
@@ -42,6 +47,7 @@ class PedimentoDatesUpdate(BaseModel):
|
||||
|
||||
class PedimentoDatesResponse(PedimentoDatesBase):
|
||||
"""Schema for Pedimento Dates response"""
|
||||
|
||||
id: int
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime
|
||||
|
||||
class PedimentoDecrementablesBase(BaseModel):
|
||||
"""Base schema for Pedimento Decrementables"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
freight: Optional[Decimal] = Field(None, description="Freight")
|
||||
@@ -15,17 +16,23 @@ class PedimentoDecrementablesBase(BaseModel):
|
||||
others: Optional[Decimal] = Field(None, description="Others")
|
||||
currency: Optional[str] = Field(None, max_length=3, description="Currency")
|
||||
currency_factor: Optional[Decimal] = Field(None, description="Currency factor")
|
||||
not_affect_usd_value: Optional[int] = Field(None, description="Not affect USD value")
|
||||
not_affect_customs_value: Optional[int] = Field(None, description="Not affect customs value")
|
||||
not_affect_usd_value: Optional[int] = Field(
|
||||
None, description="Not affect USD value"
|
||||
)
|
||||
not_affect_customs_value: Optional[int] = Field(
|
||||
None, description="Not affect customs value"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoDecrementablesCreate(PedimentoDecrementablesBase):
|
||||
"""Schema for creating a new Pedimento Decrementables"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoDecrementablesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Decrementables"""
|
||||
|
||||
freight: Optional[Decimal] = None
|
||||
insurance: Optional[Decimal] = None
|
||||
loading: Optional[Decimal] = None
|
||||
@@ -39,6 +46,7 @@ class PedimentoDecrementablesUpdate(BaseModel):
|
||||
|
||||
class PedimentoDecrementablesResponse(PedimentoDecrementablesBase):
|
||||
"""Schema for Pedimento Decrementables response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime
|
||||
|
||||
class PedimentoIncrementablesBase(BaseModel):
|
||||
"""Base schema for Pedimento Incrementables"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
insured_value: Optional[Decimal] = Field(None, description="Insured value")
|
||||
@@ -16,17 +17,23 @@ class PedimentoIncrementablesBase(BaseModel):
|
||||
deductibles: Optional[Decimal] = Field(None, description="Deductibles")
|
||||
currency: Optional[str] = Field(None, max_length=3, description="Currency")
|
||||
currency_factor: Optional[Decimal] = Field(None, description="Currency factor")
|
||||
not_affect_usd_value: Optional[int] = Field(None, description="Not affect USD value")
|
||||
not_affect_customs_value: Optional[int] = Field(None, description="Not affect customs value")
|
||||
not_affect_usd_value: Optional[int] = Field(
|
||||
None, description="Not affect USD value"
|
||||
)
|
||||
not_affect_customs_value: Optional[int] = Field(
|
||||
None, description="Not affect customs value"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoIncrementablesCreate(PedimentoIncrementablesBase):
|
||||
"""Schema for creating a new Pedimento Incrementables"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoIncrementablesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Incrementables"""
|
||||
|
||||
insured_value: Optional[Decimal] = None
|
||||
freight: Optional[Decimal] = None
|
||||
insurance: Optional[Decimal] = None
|
||||
@@ -41,6 +48,7 @@ class PedimentoIncrementablesUpdate(BaseModel):
|
||||
|
||||
class PedimentoIncrementablesResponse(PedimentoIncrementablesBase):
|
||||
"""Schema for Pedimento Incrementables response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -6,20 +6,25 @@ from datetime import datetime
|
||||
|
||||
class PedimentoIndexesBase(BaseModel):
|
||||
"""Base schema for Pedimento Indexes"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
update_factor_type: Optional[int] = Field(None, description="Update factor type")
|
||||
update_factor: Optional[Decimal] = Field(None, description="Update factor")
|
||||
manual_update_factor: Optional[int] = Field(None, description="Manual update factor")
|
||||
manual_update_factor: Optional[int] = Field(
|
||||
None, description="Manual update factor"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoIndexesCreate(PedimentoIndexesBase):
|
||||
"""Schema for creating a new Pedimento Indexes"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoIndexesUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Indexes"""
|
||||
|
||||
update_factor_type: Optional[int] = None
|
||||
update_factor: Optional[Decimal] = None
|
||||
manual_update_factor: Optional[int] = None
|
||||
@@ -27,6 +32,7 @@ class PedimentoIndexesUpdate(BaseModel):
|
||||
|
||||
class PedimentoIndexesResponse(PedimentoIndexesBase):
|
||||
"""Schema for Pedimento Indexes response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,10 +5,15 @@ from datetime import datetime, date as Date, time as Time
|
||||
|
||||
class PedimentoPaymentsBase(BaseModel):
|
||||
"""Base schema for Pedimento Payments"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment")
|
||||
operation_number: Optional[str] = Field(None, max_length=14, description="Operation number")
|
||||
acknowledgment: Optional[str] = Field(
|
||||
None, max_length=20, description="Acknowledgment"
|
||||
)
|
||||
operation_number: Optional[str] = Field(
|
||||
None, max_length=14, description="Operation number"
|
||||
)
|
||||
bank_code: Optional[int] = Field(None, description="Bank code")
|
||||
cashier: Optional[str] = Field(None, max_length=2, description="Cashier")
|
||||
date: Optional[Date] = Field(None, description="Date")
|
||||
@@ -23,11 +28,13 @@ class PedimentoPaymentsBase(BaseModel):
|
||||
|
||||
class PedimentoPaymentsCreate(PedimentoPaymentsBase):
|
||||
"""Schema for creating a new Pedimento Payments"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoPaymentsUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Payments"""
|
||||
|
||||
acknowledgment: Optional[str] = Field(None, max_length=20)
|
||||
operation_number: Optional[str] = Field(None, max_length=14)
|
||||
bank_code: Optional[int] = None
|
||||
@@ -44,6 +51,7 @@ class PedimentoPaymentsUpdate(BaseModel):
|
||||
|
||||
class PedimentoPaymentsResponse(PedimentoPaymentsBase):
|
||||
"""Schema for Pedimento Payments response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -4,21 +4,32 @@ from typing import Optional
|
||||
|
||||
class PedimentoRectificationDestinationBase(BaseModel):
|
||||
"""Base schema for Pedimento Rectification Destination"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
destination_pedimento_year: Optional[str] = Field(None, max_length=2, description="Destination pedimento year")
|
||||
destination_customs_office: Optional[str] = Field(None, max_length=3, description="Destination customs office")
|
||||
destination_license: Optional[str] = Field(None, max_length=4, description="Destination license")
|
||||
destination_pedimento_number: Optional[str] = Field(None, max_length=7, description="Destination pedimento number")
|
||||
destination_pedimento_year: Optional[str] = Field(
|
||||
None, max_length=2, description="Destination pedimento year"
|
||||
)
|
||||
destination_customs_office: Optional[str] = Field(
|
||||
None, max_length=3, description="Destination customs office"
|
||||
)
|
||||
destination_license: Optional[str] = Field(
|
||||
None, max_length=4, description="Destination license"
|
||||
)
|
||||
destination_pedimento_number: Optional[str] = Field(
|
||||
None, max_length=7, description="Destination pedimento number"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoRectificationDestinationCreate(PedimentoRectificationDestinationBase):
|
||||
"""Schema for creating a new Pedimento Rectification Destination"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoRectificationDestinationUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Rectification Destination"""
|
||||
|
||||
destination_pedimento_year: Optional[str] = Field(None, max_length=2)
|
||||
destination_customs_office: Optional[str] = Field(None, max_length=3)
|
||||
destination_license: Optional[str] = Field(None, max_length=4)
|
||||
@@ -27,6 +38,7 @@ class PedimentoRectificationDestinationUpdate(BaseModel):
|
||||
|
||||
class PedimentoRectificationDestinationResponse(PedimentoRectificationDestinationBase):
|
||||
"""Schema for Pedimento Rectification Destination response"""
|
||||
|
||||
id: int
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -5,30 +5,49 @@ from datetime import datetime
|
||||
|
||||
class PedimentoRectificationOriginBase(BaseModel):
|
||||
"""Base schema for Pedimento Rectification Origin"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
original_pedimento_year: Optional[str] = Field(None, max_length=2, description="Original pedimento year")
|
||||
original_customs_office: Optional[str] = Field(None, max_length=3, description="Original customs office")
|
||||
original_license: Optional[str] = Field(None, max_length=4, description="Original license")
|
||||
original_pedimento_number: Optional[str] = Field(None, max_length=7, description="Original pedimento number")
|
||||
original_pedimento_code: Optional[str] = Field(None, max_length=2, description="Original pedimento key")
|
||||
original_payment_date: Optional[datetime] = Field(None, description="Original payment date")
|
||||
original_pedimento_year: Optional[str] = Field(
|
||||
None, max_length=2, description="Original pedimento year"
|
||||
)
|
||||
original_customs_office: Optional[str] = Field(
|
||||
None, max_length=3, description="Original customs office"
|
||||
)
|
||||
original_license: Optional[str] = Field(
|
||||
None, max_length=4, description="Original license"
|
||||
)
|
||||
original_pedimento_number: Optional[str] = Field(
|
||||
None, max_length=7, description="Original pedimento number"
|
||||
)
|
||||
original_pedimento_code: Optional[str] = Field(
|
||||
None, max_length=2, description="Original pedimento key"
|
||||
)
|
||||
original_payment_date: Optional[datetime] = Field(
|
||||
None, description="Original payment date"
|
||||
)
|
||||
total_cash: Optional[int] = Field(None, description="Total cash")
|
||||
total_others: Optional[int] = Field(None, description="Total others")
|
||||
reason: Optional[str] = Field(None, max_length=255, description="Reason")
|
||||
charge_to_client: Optional[int] = Field(None, description="Charge to client")
|
||||
use_original_payment_date_for_interest_calc: Optional[int] = Field(None, description="Use original payment date for interest calculation")
|
||||
use_original_payment_date_for_interest_calc: Optional[int] = Field(
|
||||
None, description="Use original payment date for interest calculation"
|
||||
)
|
||||
manual_calculation: Optional[int] = Field(None, description="Manual calculation")
|
||||
original_pedimento_norms: Optional[int] = Field(None, description="Original pedimento norms")
|
||||
original_pedimento_norms: Optional[int] = Field(
|
||||
None, description="Original pedimento norms"
|
||||
)
|
||||
|
||||
|
||||
class PedimentoRectificationOriginCreate(PedimentoRectificationOriginBase):
|
||||
"""Schema for creating a new Pedimento Rectification Origin"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoRectificationOriginUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Rectification Origin"""
|
||||
|
||||
original_pedimento_year: Optional[str] = Field(None, max_length=2)
|
||||
original_customs_office: Optional[str] = Field(None, max_length=3)
|
||||
original_license: Optional[str] = Field(None, max_length=4)
|
||||
@@ -46,6 +65,7 @@ class PedimentoRectificationOriginUpdate(BaseModel):
|
||||
|
||||
class PedimentoRectificationOriginResponse(PedimentoRectificationOriginBase):
|
||||
"""Schema for Pedimento Rectification Origin response"""
|
||||
|
||||
id: int
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
|
||||
class PedimentoTransportMeansBase(BaseModel):
|
||||
"""Base schema for Pedimento Transport Means"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
destination: Optional[int] = Field(None, description="Destination")
|
||||
@@ -15,11 +16,13 @@ class PedimentoTransportMeansBase(BaseModel):
|
||||
|
||||
class PedimentoTransportMeansCreate(PedimentoTransportMeansBase):
|
||||
"""Schema for creating a new Pedimento Transport Means"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoTransportMeansUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Transport Means"""
|
||||
|
||||
destination: Optional[int] = None
|
||||
entry_exit: Optional[str] = Field(None, max_length=2)
|
||||
arrival: Optional[str] = Field(None, max_length=2)
|
||||
@@ -28,6 +31,7 @@ class PedimentoTransportMeansUpdate(BaseModel):
|
||||
|
||||
class PedimentoTransportMeansResponse(PedimentoTransportMeansBase):
|
||||
"""Schema for Pedimento Transport Means response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -5,25 +5,36 @@ from datetime import datetime
|
||||
|
||||
class PedimentoValidationBase(BaseModel):
|
||||
"""Base schema for Pedimento Validation"""
|
||||
|
||||
pedimento_id: int = Field(..., description="Pedimento ID")
|
||||
tenant_id: int = Field(..., description="Tenant ID")
|
||||
validator: Optional[str] = Field(None, max_length=3, description="Validator")
|
||||
validation_ack: Optional[str] = Field(None, max_length=8, description="Validation acknowledgment")
|
||||
validation_ack: Optional[str] = Field(
|
||||
None, max_length=8, description="Validation acknowledgment"
|
||||
)
|
||||
pre_ack: Optional[str] = Field(None, max_length=8, description="Pre-acknowledgment")
|
||||
line_signature: Optional[str] = Field(None, max_length=50, description="Line signature")
|
||||
electronic_signature: Optional[str] = Field(None, max_length=999, description="Electronic signature")
|
||||
certificate_number: Optional[str] = Field(None, max_length=99, description="Certificate number")
|
||||
line_signature: Optional[str] = Field(
|
||||
None, max_length=50, description="Line signature"
|
||||
)
|
||||
electronic_signature: Optional[str] = Field(
|
||||
None, max_length=999, description="Electronic signature"
|
||||
)
|
||||
certificate_number: Optional[str] = Field(
|
||||
None, max_length=99, description="Certificate number"
|
||||
)
|
||||
validator_id: Optional[int] = Field(None, description="Validator ID")
|
||||
responsible_id: Optional[int] = Field(None, description="Responsible ID")
|
||||
|
||||
|
||||
class PedimentoValidationCreate(PedimentoValidationBase):
|
||||
"""Schema for creating a new Pedimento Validation"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentoValidationUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento Validation"""
|
||||
|
||||
validator: Optional[str] = Field(None, max_length=3)
|
||||
validation_ack: Optional[str] = Field(None, max_length=8)
|
||||
pre_ack: Optional[str] = Field(None, max_length=8)
|
||||
@@ -36,6 +47,7 @@ class PedimentoValidationUpdate(BaseModel):
|
||||
|
||||
class PedimentoValidationResponse(PedimentoValidationBase):
|
||||
"""Schema for Pedimento Validation response"""
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@@ -4,20 +4,29 @@ from typing import Optional
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class OperationType(IntEnum):
|
||||
EXPORTACION = 1
|
||||
IMPORTACION = 2
|
||||
|
||||
|
||||
class PedimentosBase(BaseModel):
|
||||
"""Base schema for Pedimentos"""
|
||||
|
||||
year: Optional[str] = Field(None, max_length=2, description="Year")
|
||||
customs_office: Optional[str] = Field(None, max_length=2, description="Customs office")
|
||||
customs_office: Optional[str] = Field(
|
||||
None, max_length=2, description="Customs office"
|
||||
)
|
||||
license: Optional[str] = Field(None, max_length=4, description="License")
|
||||
pedimento_number: Optional[str] = Field(None, max_length=7, description="Pedimento number")
|
||||
pedimento_number: Optional[str] = Field(
|
||||
None, max_length=7, description="Pedimento number"
|
||||
)
|
||||
client_id: Optional[int] = Field(None, description="Client ID")
|
||||
operation_type: Optional[int] = Field(None, description="Operation type")
|
||||
pedimento_type: Optional[int] = Field(None, description="Pedimento type")
|
||||
pedimento_code: Optional[str] = Field(None, max_length=2, description="Pedimento key")
|
||||
pedimento_code: Optional[str] = Field(
|
||||
None, max_length=2, description="Pedimento key"
|
||||
)
|
||||
regime: Optional[str] = Field(None, max_length=3, description="Regime")
|
||||
status: Optional[str] = Field(None, max_length=30, description="Status")
|
||||
usd_value: Optional[Decimal] = Field(None, description="USD value")
|
||||
@@ -28,11 +37,13 @@ class PedimentosBase(BaseModel):
|
||||
|
||||
class PedimentosCreate(PedimentosBase):
|
||||
"""Schema for creating a new Pedimento"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PedimentosUpdate(BaseModel):
|
||||
"""Schema for updating a Pedimento"""
|
||||
|
||||
year: Optional[str] = Field(..., max_length=2)
|
||||
customs_office: Optional[str] = Field(..., max_length=2)
|
||||
license: Optional[str] = Field(..., max_length=4)
|
||||
@@ -51,6 +62,7 @@ class PedimentosUpdate(BaseModel):
|
||||
|
||||
class PedimentosResponse(PedimentosBase):
|
||||
"""Schema for Pedimento response"""
|
||||
|
||||
id: int
|
||||
tenant_id: int
|
||||
created_at: datetime
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from datetime import datetime
|
||||
from core.database import Base
|
||||
|
||||
@@ -9,31 +19,55 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class PedimentoConfigAdditional(Base):
|
||||
__tablename__ = 'pedimento_config_additional'
|
||||
__tablename__ = "pedimento_config_additional"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_additional_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_additional_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_additional'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_additional_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_config_additional_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_config_additional_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_config_additional_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_config_additional",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_config_additional_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped [int] = mapped_column(Integer)
|
||||
tenant_id: Mapped [int] = mapped_column(Integer, nullable=False, index=True)
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped [int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
add_po_identifier: Mapped [int] = mapped_column(SmallInteger)
|
||||
do_not_exempt_norms_complement_x: Mapped [int] = mapped_column(SmallInteger)
|
||||
manual_pedimento_year: Mapped [str] = mapped_column(String(2))
|
||||
enable_import_invoice_recipient: Mapped [int] = mapped_column(SmallInteger)
|
||||
send_502_validation_file_for_consolidated: Mapped [int] = mapped_column(SmallInteger)
|
||||
add_remove_norms: Mapped [int] = mapped_column(SmallInteger)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
add_po_identifier: Mapped[int] = mapped_column(SmallInteger)
|
||||
do_not_exempt_norms_complement_x: Mapped[int] = mapped_column(SmallInteger)
|
||||
manual_pedimento_year: Mapped[str] = mapped_column(String(2))
|
||||
enable_import_invoice_recipient: Mapped[int] = mapped_column(SmallInteger)
|
||||
send_502_validation_file_for_consolidated: Mapped[int] = mapped_column(SmallInteger)
|
||||
add_remove_norms: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_additional')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_config_additional"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from datetime import datetime
|
||||
from core.database import Base
|
||||
@@ -7,22 +17,33 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoConfigCalculations(Base):
|
||||
__tablename__ = 'pedimento_config_calculations'
|
||||
__tablename__ = "pedimento_config_calculations"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_calculations'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_calculations_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_config_calculations_pkey"),
|
||||
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
|
||||
ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_config_calculations",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_config_calculations_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
dta_type: Mapped[str] = mapped_column(String(1))
|
||||
dta_operation: Mapped[int] = mapped_column(SmallInteger)
|
||||
dta_vehicle_count: Mapped[int] = mapped_column(SmallInteger)
|
||||
@@ -33,10 +54,16 @@ class PedimentoConfigCalculations(Base):
|
||||
fixed_vehicle_dta_fee: Mapped[int] = mapped_column(SmallInteger)
|
||||
additional_fixed_fee: Mapped[int] = mapped_column(SmallInteger)
|
||||
additional_fixed_fee_payment_method: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_calculations')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_config_calculations"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
Numeric,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -11,21 +21,39 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class PedimentoConfigParameters(Base):
|
||||
__tablename__ = 'pedimento_config_parameters'
|
||||
__tablename__ = "pedimento_config_parameters"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_parameters_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_parameters_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_parameters'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_parameters_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_config_parameters_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_config_parameters_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_config_parameters_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_config_parameters",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_config_parameters_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
is_embassy: Mapped[int] = mapped_column(SmallInteger)
|
||||
embassy_dta: Mapped[Decimal] = mapped_column(Numeric(11, 2))
|
||||
rule_3121_section_ii: Mapped[int] = mapped_column(SmallInteger)
|
||||
@@ -37,10 +65,16 @@ class PedimentoConfigParameters(Base):
|
||||
customs_value_per_item: Mapped[int] = mapped_column(SmallInteger)
|
||||
is_national_supplier: Mapped[int] = mapped_column(SmallInteger)
|
||||
is_consolidated: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_parameters')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_config_parameters"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -8,32 +17,57 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoConfigSurcharges(Base):
|
||||
__tablename__ = 'pedimento_config_surcharges'
|
||||
__tablename__ = "pedimento_config_surcharges"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_surcharges_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_surcharges_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_surcharges'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_config_surcharges_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_config_surcharges_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_config_surcharges_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_config_surcharges",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_config_surcharges_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
surcharge_igi: Mapped[int] = mapped_column(SmallInteger)
|
||||
surcharge_dta: Mapped[int] = mapped_column(SmallInteger)
|
||||
surcharge_vat: Mapped[int] = mapped_column(SmallInteger)
|
||||
surcharge_isan: Mapped[int] = mapped_column(SmallInteger)
|
||||
surcharge_ieps: Mapped[int] = mapped_column(SmallInteger)
|
||||
surcharge_cc: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_surcharges')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_config_surcharges"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -8,31 +17,56 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoConfigUpdateRectification(Base):
|
||||
__tablename__ = 'pedimento_config_update_rectification'
|
||||
__tablename__ = "pedimento_config_update_rectification"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_update_rectification_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_update_rectification_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_update_rectification'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_config_update_rectification_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_config_update_rectification_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_config_update_rectification_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_config_update_rectification",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_config_update_rectification_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
|
||||
update_vat: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_advalorem: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_cc: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_ieps: Mapped[int] = mapped_column(SmallInteger)
|
||||
calculate_surcharge: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_update_rectification')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_config_update_rectification"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -8,30 +17,53 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoConfigUpdates(Base):
|
||||
__tablename__ = 'pedimento_config_updates'
|
||||
__tablename__ = "pedimento_config_updates"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_updates_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_updates_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_updates'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_updates_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_config_updates_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_config_updates_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_config_updates_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_config_updates",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_config_updates_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
update_vat: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_advalorem: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_cc: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_ieps: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_updates')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_config_updates"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -8,28 +17,53 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoCustomsOffices(Base):
|
||||
__tablename__ = 'pedimento_customs_offices'
|
||||
__tablename__ = "pedimento_customs_offices"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_customs_offices_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_customs_offices_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_customs_offices'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_customs_offices_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_customs_offices_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_customs_offices_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_customs_offices_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_customs_offices",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_customs_offices_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
dispatch_customs: Mapped[str] = mapped_column(String(3))
|
||||
entry_exit_customs: Mapped[str] = mapped_column(String(3))
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_customs_offices')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_customs_offices"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, PrimaryKeyConstraint, Time, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Index,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
Time,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime, time as datetime_time
|
||||
@@ -8,23 +18,38 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoDates(Base):
|
||||
__tablename__ = 'pedimento_dates'
|
||||
__tablename__ = "pedimento_dates"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_dates_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_dates_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_dates_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_dates'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_dates_pedimento_id_key'),
|
||||
Index('idx_pedimento_dates_pedimento_id', 'pedimento_id'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_dates_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_dates_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_pedimento_dates_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_dates",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_dates_pedimento_id_key",
|
||||
),
|
||||
Index("idx_pedimento_dates_pedimento_id", "pedimento_id"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
entry_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
pedimento_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
@@ -37,10 +62,16 @@ class PedimentoDates(Base):
|
||||
end_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
capture_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
capture_time: Mapped[datetime_time] = mapped_column(Time)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_dates')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_dates"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
Numeric,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -9,22 +20,39 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoDecrementables(Base):
|
||||
__tablename__ = 'pedimento_decrementables'
|
||||
__tablename__ = "pedimento_decrementables"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_decrementables_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_decrementables_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_decrementables'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_decrementables_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_decrementables_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_decrementables_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_decrementables_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_decrementables",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_decrementables_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
freight: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
loading: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
@@ -34,10 +62,16 @@ class PedimentoDecrementables(Base):
|
||||
currency_factor: Mapped[Decimal] = mapped_column(Numeric(15, 8))
|
||||
not_affect_usd_value: Mapped[int] = mapped_column(SmallInteger)
|
||||
not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_decrementables')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_decrementables"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
Numeric,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -9,22 +20,39 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoIncrementables(Base):
|
||||
__tablename__ = 'pedimento_incrementables'
|
||||
__tablename__ = "pedimento_incrementables"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_incrementables_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_incrementables_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_incrementables'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_incrementables_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_incrementables_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_incrementables_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_incrementables_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_incrementables",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_incrementables_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
insured_value: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
freight: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
@@ -35,10 +63,16 @@ class PedimentoIncrementables(Base):
|
||||
currency_factor: Mapped[Decimal] = mapped_column(Numeric(15, 8))
|
||||
not_affect_usd_value: Mapped[int] = mapped_column(SmallInteger)
|
||||
not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_incrementables')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_incrementables"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
Numeric,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -9,29 +19,50 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoIndexes(Base):
|
||||
__tablename__ = 'pedimento_indexes'
|
||||
__tablename__ = "pedimento_indexes"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_indexes_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_indexes_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_indexes'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_indexes_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_indexes_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_indexes_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_pedimento_indexes_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_indexes",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_indexes_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
update_factor_type: Mapped[int] = mapped_column(SmallInteger)
|
||||
update_factor: Mapped[Decimal] = mapped_column(Numeric(7, 4))
|
||||
manual_update_factor: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_indexes')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_indexes"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import Date, DateTime, ForeignKeyConstraint, Index, Integer, PrimaryKeyConstraint, SmallInteger, String, Time, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
Date,
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Index,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
Time,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime, time as Time2, date as Date2
|
||||
@@ -8,24 +21,39 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoPayments(Base):
|
||||
__tablename__ = 'pedimento_payments'
|
||||
__tablename__ = "pedimento_payments"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_payments_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_payments_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_payments_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_payments'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_payments_pedimento_id_key'),
|
||||
Index('idx_pedimento_payments_pedimento_id', 'pedimento_id'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_payments_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_payments_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_pedimento_payments_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_payments",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_payments_pedimento_id_key",
|
||||
),
|
||||
Index("idx_pedimento_payments_pedimento_id", "pedimento_id"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
payment_id: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
|
||||
acknowledgment: Mapped[str] = mapped_column(String(20))
|
||||
operation_number: Mapped[str] = mapped_column(String(14))
|
||||
bank_code: Mapped[int] = mapped_column(Integer)
|
||||
@@ -36,11 +64,17 @@ class PedimentoPayments(Base):
|
||||
total_cash_paid: Mapped[int] = mapped_column(Integer)
|
||||
total_contributions: Mapped[int] = mapped_column(Integer)
|
||||
counter_payment: Mapped[int] = mapped_column(SmallInteger)
|
||||
pece_code: Mapped[str] = mapped_column(String(5))
|
||||
|
||||
pece_code: Mapped[str] = mapped_column(String(5))
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_payments')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_payments"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, func
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from core.database import Base
|
||||
@@ -8,30 +16,55 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoRectificationDestination(Base):
|
||||
__tablename__ = 'pedimento_rectification_destination'
|
||||
__tablename__ = "pedimento_rectification_destination"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_rectification_destination_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_rectification_destination_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_destination'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_rectification_destination_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_rectification_destination_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_rectification_destination_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_rectification_destination",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_rectification_destination_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
destination_pedimento_year: Mapped[str] = mapped_column(String(2))
|
||||
destination_customs_office: Mapped[str] = mapped_column(String(3))
|
||||
destination_license: Mapped[str] = mapped_column(String(4))
|
||||
destination_pedimento_number: Mapped[str] = mapped_column(String(7))
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_destination')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_rectification_destination"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from core.database import Base
|
||||
@@ -8,22 +17,41 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoRectificationOrigin(Base):
|
||||
__tablename__ = 'pedimento_rectification_origin'
|
||||
__tablename__ = "pedimento_rectification_origin"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_rectification_origin_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_rectification_origin_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_origin'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_rectification_origin_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_rectification_origin_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_rectification_origin_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_rectification_origin",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_rectification_origin_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
original_pedimento_year: Mapped[str] = mapped_column(String(2))
|
||||
original_customs_office: Mapped[str] = mapped_column(String(3))
|
||||
original_license: Mapped[str] = mapped_column(String(4))
|
||||
@@ -34,13 +62,21 @@ class PedimentoRectificationOrigin(Base):
|
||||
total_others: Mapped[int] = mapped_column(Integer)
|
||||
reason: Mapped[str] = mapped_column(String(255))
|
||||
charge_to_client: Mapped[int] = mapped_column(SmallInteger)
|
||||
use_original_payment_date_for_interest_calc: Mapped[int] = mapped_column(SmallInteger)
|
||||
use_original_payment_date_for_interest_calc: Mapped[int] = mapped_column(
|
||||
SmallInteger
|
||||
)
|
||||
manual_calculation: Mapped[int] = mapped_column(SmallInteger)
|
||||
original_pedimento_norms: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_origin')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_rectification_origin"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
SmallInteger,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from core.database import Base
|
||||
@@ -8,26 +17,49 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoTransportMeans(Base):
|
||||
__tablename__ = 'pedimento_transport_means'
|
||||
__tablename__ = "pedimento_transport_means"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_transport_means_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_transport_means_company'),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_transport_means'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_transport_means_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_transport_means_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["a76.tenants.id"],
|
||||
name="fk_pedimento_transport_means_tenant",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"],
|
||||
["a76.company.id"],
|
||||
name="fk_pedimento_transport_means_company",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_transport_means",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_transport_means_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
|
||||
destination: Mapped[int] = mapped_column(SmallInteger)
|
||||
entry_exit: Mapped[str] = mapped_column(String(2))
|
||||
arrival: Mapped[str] = mapped_column(String(2))
|
||||
departure: Mapped[str] = mapped_column(String(2))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=text("CURRENT_TIMESTAMP")
|
||||
)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_transport_means')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_transport_means"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
@@ -8,35 +17,50 @@ from core.database import Base
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
|
||||
|
||||
|
||||
class PedimentoValidation(Base):
|
||||
__tablename__ = 'pedimento_validation' #PedimentoValidacion
|
||||
__tablename__ = "pedimento_validation" # PedimentoValidacion
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimento_validation_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_validation'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_validation_pedimento_id_key'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimento_validation_pkey"),
|
||||
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_id"],
|
||||
["a76.pedimentos.id"],
|
||||
ondelete="CASCADE",
|
||||
name="fk_pedimento_validation",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"pedimento_id",
|
||||
name="pedimento_validation_pedimento_id_key",
|
||||
),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
validator: Mapped[str] = mapped_column(String(3)) #validador
|
||||
validation_ack: Mapped[str] = mapped_column(String(8)) #acuse_validacion
|
||||
pre_ack: Mapped[str] = mapped_column(String(8)) #acuse_previo
|
||||
line_signature: Mapped[str] = mapped_column(String(50)) #firma_linea_captura
|
||||
electronic_signature: Mapped[str] = mapped_column(String(999)) #firma_electronica
|
||||
certificate_number: Mapped[str] = mapped_column(String(99)) #numero_certificado
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
validator: Mapped[str] = mapped_column(String(3)) # validador
|
||||
validation_ack: Mapped[str] = mapped_column(String(8)) # acuse_validacion
|
||||
pre_ack: Mapped[str] = mapped_column(String(8)) # acuse_previo
|
||||
line_signature: Mapped[str] = mapped_column(String(50)) # firma_linea_captura
|
||||
electronic_signature: Mapped[str] = mapped_column(String(999)) # firma_electronica
|
||||
certificate_number: Mapped[str] = mapped_column(String(99)) # numero_certificado
|
||||
validator_id: Mapped[int] = mapped_column(Integer)
|
||||
responsible_id: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_validation')
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_validation"
|
||||
)
|
||||
|
||||
@@ -1,50 +1,110 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, Numeric, PrimaryKeyConstraint, String, UniqueConstraint, func, text
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKeyConstraint,
|
||||
Index,
|
||||
Integer,
|
||||
Numeric,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
text,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
from core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import PedimentoConfigAdditional
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import PedimentoConfigCalculations
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_parameters import PedimentoConfigParameters
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_surcharges import PedimentoConfigSurcharges
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_updates import PedimentoConfigUpdates
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_customs_offices import PedimentoCustomsOffices
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import (
|
||||
PedimentoConfigAdditional,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import (
|
||||
PedimentoConfigCalculations,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_parameters import (
|
||||
PedimentoConfigParameters,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_surcharges import (
|
||||
PedimentoConfigSurcharges,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import (
|
||||
PedimentoConfigUpdateRectification,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_config_updates import (
|
||||
PedimentoConfigUpdates,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_customs_offices import (
|
||||
PedimentoCustomsOffices,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import PedimentoDecrementables
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import PedimentoIncrementables
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import (
|
||||
PedimentoDecrementables,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import (
|
||||
PedimentoIncrementables,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_indexes import PedimentoIndexes
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_payments import PedimentoPayments
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import PedimentoRectificationDestination
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_origin import PedimentoRectificationOrigin
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_transport_means import PedimentoTransportMeans
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_validation import PedimentoValidation
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_payments import (
|
||||
PedimentoPayments,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import (
|
||||
PedimentoRectificationDestination,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_origin import (
|
||||
PedimentoRectificationOrigin,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_transport_means import (
|
||||
PedimentoTransportMeans,
|
||||
)
|
||||
from api.v1.modules.a76.pedmientos.models.pedimento_validation import (
|
||||
PedimentoValidation,
|
||||
)
|
||||
|
||||
|
||||
class Pedimentos(Base):
|
||||
__tablename__ = 'pedimentos'
|
||||
__tablename__ = "pedimentos"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='pedimentos_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimentos_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimentos_company'),
|
||||
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], name='fk_pedimentos_client'),
|
||||
ForeignKeyConstraint(['regime'], ['public.pedimento_regimens.code'], name='fk_pedimentos_regime'),
|
||||
ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code'], name='fk_pedimentos_code'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'year', 'customs_office', 'license', 'pedimento_number', name='pedimentos_unique_key'),
|
||||
Index('idx_pedimentos_client_id', 'client_id'),
|
||||
Index('idx_pedimentos_created_at', 'created_at'),
|
||||
Index('idx_pedimentos_status', 'status'),
|
||||
{'schema': 'a76'}
|
||||
PrimaryKeyConstraint("id", name="pedimentos_pkey"),
|
||||
ForeignKeyConstraint(
|
||||
["tenant_id"], ["a76.tenants.id"], name="fk_pedimentos_tenant"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["company_id"], ["a76.company.id"], name="fk_pedimentos_company"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["client_id"], ["a76.client_provider.id"], name="fk_pedimentos_client"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["regime"], ["public.pedimento_regimens.code"], name="fk_pedimentos_regime"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["pedimento_code"],
|
||||
["public.pedimento_codes.code"],
|
||||
name="fk_pedimentos_code",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
"year",
|
||||
"customs_office",
|
||||
"license",
|
||||
"pedimento_number",
|
||||
name="pedimentos_unique_key",
|
||||
),
|
||||
Index("idx_pedimentos_client_id", "client_id"),
|
||||
Index("idx_pedimentos_created_at", "created_at"),
|
||||
Index("idx_pedimentos_status", "status"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
year: Mapped[str] = mapped_column(String(2))
|
||||
customs_office: Mapped[str] = mapped_column(String(2))
|
||||
license: Mapped[str] = mapped_column(String(4))
|
||||
@@ -59,26 +119,69 @@ class Pedimentos(Base):
|
||||
paid_price: Mapped[Optional[Decimal]] = mapped_column(Numeric(17, 6))
|
||||
gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 3))
|
||||
exchange_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(9, 5))
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, default=func.now(), onupdate=func.now()
|
||||
)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento_config_additional: Mapped['PedimentoConfigAdditional'] = relationship('PedimentoConfigAdditional', uselist=False, back_populates='pedimento')
|
||||
pedimento_config_calculations: Mapped['PedimentoConfigCalculations'] = relationship('PedimentoConfigCalculations', uselist=False, back_populates='pedimento')
|
||||
pedimento_config_parameters: Mapped['PedimentoConfigParameters'] = relationship('PedimentoConfigParameters', uselist=False, back_populates='pedimento')
|
||||
pedimento_config_surcharges: Mapped['PedimentoConfigSurcharges'] = relationship('PedimentoConfigSurcharges', uselist=False, back_populates='pedimento')
|
||||
pedimento_config_update_rectification: Mapped['PedimentoConfigUpdateRectification'] = relationship('PedimentoConfigUpdateRectification', uselist=False, back_populates='pedimento')
|
||||
pedimento_config_updates: Mapped['PedimentoConfigUpdates'] = relationship('PedimentoConfigUpdates', uselist=False, back_populates='pedimento')
|
||||
pedimento_customs_offices: Mapped['PedimentoCustomsOffices'] = relationship('PedimentoCustomsOffices', uselist=False, back_populates='pedimento')
|
||||
pedimento_dates: Mapped['PedimentoDates'] = relationship('PedimentoDates', uselist=False, back_populates='pedimento')
|
||||
pedimento_decrementables: Mapped['PedimentoDecrementables'] = relationship('PedimentoDecrementables', uselist=False, back_populates='pedimento')
|
||||
pedimento_incrementables: Mapped['PedimentoIncrementables'] = relationship('PedimentoIncrementables', uselist=False, back_populates='pedimento')
|
||||
pedimento_indexes: Mapped['PedimentoIndexes'] = relationship('PedimentoIndexes', uselist=False, back_populates='pedimento')
|
||||
pedimento_payments: Mapped['PedimentoPayments'] = relationship('PedimentoPayments', uselist=False, back_populates='pedimento')
|
||||
pedimento_rectification_destination: Mapped['PedimentoRectificationDestination'] = relationship('PedimentoRectificationDestination', uselist=False, back_populates='pedimento')
|
||||
pedimento_rectification_origin: Mapped['PedimentoRectificationOrigin'] = relationship('PedimentoRectificationOrigin', uselist=False, back_populates='pedimento')
|
||||
pedimento_transport_means: Mapped['PedimentoTransportMeans'] = relationship('PedimentoTransportMeans', uselist=False, back_populates='pedimento')
|
||||
pedimento_validation: Mapped['PedimentoValidation'] = relationship('PedimentoValidation', uselist=False, back_populates='pedimento')
|
||||
|
||||
pedimento_config_additional: Mapped["PedimentoConfigAdditional"] = relationship(
|
||||
"PedimentoConfigAdditional", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_config_calculations: Mapped["PedimentoConfigCalculations"] = relationship(
|
||||
"PedimentoConfigCalculations", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_config_parameters: Mapped["PedimentoConfigParameters"] = relationship(
|
||||
"PedimentoConfigParameters", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_config_surcharges: Mapped["PedimentoConfigSurcharges"] = relationship(
|
||||
"PedimentoConfigSurcharges", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_config_update_rectification: Mapped[
|
||||
"PedimentoConfigUpdateRectification"
|
||||
] = relationship(
|
||||
"PedimentoConfigUpdateRectification", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_config_updates: Mapped["PedimentoConfigUpdates"] = relationship(
|
||||
"PedimentoConfigUpdates", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_customs_offices: Mapped["PedimentoCustomsOffices"] = relationship(
|
||||
"PedimentoCustomsOffices", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_dates: Mapped["PedimentoDates"] = relationship(
|
||||
"PedimentoDates", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_decrementables: Mapped["PedimentoDecrementables"] = relationship(
|
||||
"PedimentoDecrementables", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_incrementables: Mapped["PedimentoIncrementables"] = relationship(
|
||||
"PedimentoIncrementables", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_indexes: Mapped["PedimentoIndexes"] = relationship(
|
||||
"PedimentoIndexes", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_payments: Mapped["PedimentoPayments"] = relationship(
|
||||
"PedimentoPayments", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_rectification_destination: Mapped["PedimentoRectificationDestination"] = (
|
||||
relationship(
|
||||
"PedimentoRectificationDestination",
|
||||
uselist=False,
|
||||
back_populates="pedimento",
|
||||
)
|
||||
)
|
||||
pedimento_rectification_origin: Mapped["PedimentoRectificationOrigin"] = (
|
||||
relationship(
|
||||
"PedimentoRectificationOrigin", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
)
|
||||
pedimento_transport_means: Mapped["PedimentoTransportMeans"] = relationship(
|
||||
"PedimentoTransportMeans", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
pedimento_validation: Mapped["PedimentoValidation"] = relationship(
|
||||
"PedimentoValidation", uselist=False, back_populates="pedimento"
|
||||
)
|
||||
|
||||
@@ -1,39 +1,119 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .routes.pedimento_config_additional import router as pedimento_config_additional_router
|
||||
from .routes.pedimento_config_calculations import router as pedimento_config_calculations_router
|
||||
from .routes.pedimento_config_parameters import router as pedimento_config_parameters_router
|
||||
from .routes.pedimento_config_surcharges import router as pedimento_config_surcharges_router
|
||||
from .routes.pedimento_config_update_rectification import router as pedimento_config_update_rectification_router
|
||||
from .routes.pedimento_config_additional import (
|
||||
router as pedimento_config_additional_router,
|
||||
)
|
||||
from .routes.pedimento_config_calculations import (
|
||||
router as pedimento_config_calculations_router,
|
||||
)
|
||||
from .routes.pedimento_config_parameters import (
|
||||
router as pedimento_config_parameters_router,
|
||||
)
|
||||
from .routes.pedimento_config_surcharges import (
|
||||
router as pedimento_config_surcharges_router,
|
||||
)
|
||||
from .routes.pedimento_config_update_rectification import (
|
||||
router as pedimento_config_update_rectification_router,
|
||||
)
|
||||
from .routes.pedimento_config_updates import router as pedimento_config_updates_router
|
||||
from .routes.pedimento_customs_offices import router as pedimento_customs_offices_router
|
||||
from .routes.pedimento_dates import router as pedimento_dates_router
|
||||
from .routes.pedimento_decrementables import router as pedimento_decrementables_router
|
||||
from .routes.pedimento_incrementables import router as pedimento_incrementables_router
|
||||
from .routes.pedimento_incrementables import router as pedimento_incrementables_router
|
||||
from .routes.pedimento_indexes import router as pedimento_indexes_router
|
||||
from .routes.pedimento_payments import router as pedimento_payments_router
|
||||
from .routes.pedimento_rectification_destination import router as pedimento_rectification_destination_router
|
||||
from .routes.pedimento_rectification_origin import router as pedimento_rectification_origin_router
|
||||
from .routes.pedimento_rectification_destination import (
|
||||
router as pedimento_rectification_destination_router,
|
||||
)
|
||||
from .routes.pedimento_rectification_origin import (
|
||||
router as pedimento_rectification_origin_router,
|
||||
)
|
||||
from .routes.pedimento_transport_means import router as pedimento_transport_means_router
|
||||
from .routes.pedimento_validation import router as pedimento_validation_router
|
||||
from .routes.pedimentos import router as pedimentos_router
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(pedimento_config_additional_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_additional"])
|
||||
router.include_router(pedimento_config_calculations_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_calculations"])
|
||||
router.include_router(pedimento_config_parameters_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_parameters"])
|
||||
router.include_router(pedimento_config_surcharges_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_surcharges"])
|
||||
router.include_router(pedimento_config_update_rectification_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_update_rectification"])
|
||||
router.include_router(pedimento_config_updates_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_updates"])
|
||||
router.include_router(pedimento_customs_offices_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_customs_offices"])
|
||||
router.include_router(pedimento_dates_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_dates"])
|
||||
router.include_router(pedimento_decrementables_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_decrementables"])
|
||||
router.include_router(pedimento_incrementables_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_incrementables"])
|
||||
router.include_router(pedimento_indexes_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_indexes"])
|
||||
router.include_router(pedimento_payments_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_payments"])
|
||||
router.include_router(pedimento_rectification_destination_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_rectification_destination"])
|
||||
router.include_router(pedimento_rectification_origin_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_rectification_origin"])
|
||||
router.include_router(pedimento_transport_means_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_transport_means"])
|
||||
router.include_router(pedimento_validation_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_validation"])
|
||||
router.include_router(pedimentos_router, prefix="/pedimentos", tags=["a76 / pedimentos"])
|
||||
router.include_router(
|
||||
pedimento_config_additional_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_config_additional"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_config_calculations_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_config_calculations"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_config_parameters_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_config_parameters"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_config_surcharges_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_config_surcharges"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_config_update_rectification_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_config_update_rectification"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_config_updates_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_config_updates"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_customs_offices_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_customs_offices"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_dates_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_dates"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_decrementables_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_decrementables"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_incrementables_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_incrementables"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_indexes_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_indexes"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_payments_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_payments"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_rectification_destination_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_rectification_destination"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_rectification_origin_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_rectification_origin"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_transport_means_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_transport_means"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimento_validation_router,
|
||||
prefix="/pedimentos",
|
||||
tags=["a76 / pedimentos / pedimento_validation"],
|
||||
)
|
||||
router.include_router(
|
||||
pedimentos_router, prefix="/pedimentos", tags=["a76 / pedimentos"]
|
||||
)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoConfigAdditional CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_config_additional import PedimentoConfigAdditionalService
|
||||
from ..dtos.pedimento_config_additional import (
|
||||
PedimentoConfigAdditionalCreate,
|
||||
PedimentoConfigAdditionalUpdate,
|
||||
PedimentoConfigAdditionalResponse
|
||||
PedimentoConfigAdditionalResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_config_additional(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get config additional by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigAdditionalService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config additional not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -39,14 +44,15 @@ async def create_config_additional(
|
||||
data: PedimentoConfigAdditionalCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create config additional"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id and company_id match
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
config = PedimentoConfigAdditionalService.create(db, data, tenant_id, company_id)
|
||||
return config
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_config_additional(
|
||||
data: PedimentoConfigAdditionalUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update config additional"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigAdditionalService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigAdditionalService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config additional not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -73,12 +82,15 @@ async def delete_config_additional(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete config additional"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoConfigAdditionalService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoConfigAdditionalService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Config additional not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoConfigCalculations CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_config_calculations import PedimentoConfigCalculationsService
|
||||
from ..dtos.pedimento_config_calculations import (
|
||||
PedimentoConfigCalculationsCreate,
|
||||
PedimentoConfigCalculationsUpdate,
|
||||
PedimentoConfigCalculationsResponse
|
||||
PedimentoConfigCalculationsResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_config_calculations(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get config calculations by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigCalculationsService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config calculations not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -39,14 +44,15 @@ async def create_config_calculations(
|
||||
data: PedimentoConfigCalculationsCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create config calculations"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
config = PedimentoConfigCalculationsService.create(db, data, tenant_id, company_id)
|
||||
return config
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_config_calculations(
|
||||
data: PedimentoConfigCalculationsUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update config calculations"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigCalculationsService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigCalculationsService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config calculations not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -73,12 +82,13 @@ async def delete_config_calculations(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete config calculations"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoConfigCalculationsService.delete(db, pedimento_id, company_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Config calculations not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoConfigParameters CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_config_parameters import PedimentoConfigParametersService
|
||||
from ..dtos.pedimento_config_parameters import (
|
||||
PedimentoConfigParametersCreate,
|
||||
PedimentoConfigParametersUpdate,
|
||||
PedimentoConfigParametersResponse
|
||||
PedimentoConfigParametersResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_config_parameters(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get config parameters by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigParametersService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config parameters not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -39,14 +44,15 @@ async def create_config_parameters(
|
||||
data: PedimentoConfigParametersCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create config parameters"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
config = PedimentoConfigParametersService.create(db, data, tenant_id, company_id)
|
||||
return config
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_config_parameters(
|
||||
data: PedimentoConfigParametersUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update config parameters"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigParametersService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigParametersService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config parameters not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -73,12 +82,15 @@ async def delete_config_parameters(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete config parameters"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoConfigParametersService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoConfigParametersService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Config parameters not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoConfigSurcharges CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_config_surcharges import PedimentoConfigSurchargesService
|
||||
from ..dtos.pedimento_config_surcharges import (
|
||||
PedimentoConfigSurchargesCreate,
|
||||
PedimentoConfigSurchargesUpdate,
|
||||
PedimentoConfigSurchargesResponse
|
||||
PedimentoConfigSurchargesResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_config_surcharges(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get config surcharges by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigSurchargesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config surcharges not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -39,14 +44,15 @@ async def create_config_surcharges(
|
||||
data: PedimentoConfigSurchargesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create config surcharges"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
config = PedimentoConfigSurchargesService.create(db, data, tenant_id, company_id)
|
||||
return config
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_config_surcharges(
|
||||
data: PedimentoConfigSurchargesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update config surcharges"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigSurchargesService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigSurchargesService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config surcharges not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -73,12 +82,15 @@ async def delete_config_surcharges(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete config surcharges"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoConfigSurchargesService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoConfigSurchargesService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Config surcharges not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
"""
|
||||
Routes for PedimentoConfigUpdateRectification CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService
|
||||
from ..services.pedimento_config_update_rectification import (
|
||||
PedimentoConfigUpdateRectificationService,
|
||||
)
|
||||
from ..dtos.pedimento_config_update_rectification import (
|
||||
PedimentoConfigUpdateRectificationCreate,
|
||||
PedimentoConfigUpdateRectificationUpdate,
|
||||
PedimentoConfigUpdateRectificationResponse
|
||||
PedimentoConfigUpdateRectificationResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,32 +26,42 @@ async def get_config_update_rectification(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get config update rectification by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config update rectification not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Config update rectification not found"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@router.post("/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201)
|
||||
@router.post(
|
||||
"/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201
|
||||
)
|
||||
async def create_config_update_rectification(
|
||||
pedimento_id: int,
|
||||
data: PedimentoConfigUpdateRectificationCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create config update rectification"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
config = PedimentoConfigUpdateRectificationService.create(db, data, tenant_id, company_id)
|
||||
|
||||
config = PedimentoConfigUpdateRectificationService.create(
|
||||
db, data, tenant_id, company_id
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -57,14 +71,19 @@ async def update_config_update_rectification(
|
||||
data: PedimentoConfigUpdateRectificationUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update config update rectification"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigUpdateRectificationService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigUpdateRectificationService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config update rectification not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Config update rectification not found"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -73,12 +92,17 @@ async def delete_config_update_rectification(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete config update rectification"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoConfigUpdateRectificationService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoConfigUpdateRectificationService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Config update rectification not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Config update rectification not found"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoConfigUpdates CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_config_updates import PedimentoConfigUpdatesService
|
||||
from ..dtos.pedimento_config_updates import (
|
||||
PedimentoConfigUpdatesCreate,
|
||||
PedimentoConfigUpdatesUpdate,
|
||||
PedimentoConfigUpdatesResponse
|
||||
PedimentoConfigUpdatesResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_config_updates(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get config updates by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigUpdatesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config updates not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -38,15 +43,16 @@ async def create_config_updates(
|
||||
pedimento_id: int,
|
||||
data: PedimentoConfigUpdatesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db)
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create config updates"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
config = PedimentoConfigUpdatesService.create(db, data, tenant_id, company_id)
|
||||
return config
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_config_updates(
|
||||
data: PedimentoConfigUpdatesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update config updates"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
config = PedimentoConfigUpdatesService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
config = PedimentoConfigUpdatesService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Config updates not found")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -73,12 +82,15 @@ async def delete_config_updates(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete config updates"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoConfigUpdatesService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoConfigUpdatesService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Config updates not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
"""
|
||||
Routes for PedimentoCustomsOffices CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from typing import Dict, Any, List
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
from ..services.pedimento_customs_offices import PedimentoCustomsOfficesService
|
||||
from ..dtos.pedimento_customs_offices import (
|
||||
PedimentoCustomsOfficesCreate,
|
||||
PedimentoCustomsOfficesUpdate,
|
||||
PedimentoCustomsOfficesResponse
|
||||
PedimentoCustomsOfficesResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,11 +23,14 @@ async def list_customs_offices(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get all customs offices for a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
return offices
|
||||
|
||||
|
||||
@@ -36,14 +40,17 @@ async def get_customs_office(
|
||||
office_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get a specific customs office by ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
office = PedimentoCustomsOfficesService.get_by_id(db, office_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
office = PedimentoCustomsOfficesService.get_by_id(
|
||||
db, office_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not office:
|
||||
raise HTTPException(status_code=404, detail="Customs office not found")
|
||||
|
||||
|
||||
return office
|
||||
|
||||
|
||||
@@ -53,14 +60,15 @@ async def create_customs_office(
|
||||
data: PedimentoCustomsOfficesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new customs office"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
office = PedimentoCustomsOfficesService.create(db, data, tenant_id, company_id)
|
||||
return office
|
||||
|
||||
@@ -72,14 +80,17 @@ async def update_customs_office(
|
||||
data: PedimentoCustomsOfficesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update a customs office"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
office = PedimentoCustomsOfficesService.update(db, office_id, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
office = PedimentoCustomsOfficesService.update(
|
||||
db, office_id, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not office:
|
||||
raise HTTPException(status_code=404, detail="Customs office not found")
|
||||
|
||||
|
||||
return office
|
||||
|
||||
|
||||
@@ -89,12 +100,15 @@ async def delete_customs_office(
|
||||
office_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a customs office"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoCustomsOfficesService.delete(db, office_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoCustomsOfficesService.delete(
|
||||
db, office_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Customs office not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,48 +1,55 @@
|
||||
"""
|
||||
Routes for PedimentoDates CRUD operations
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_dates import PedimentoDatesService
|
||||
from ..dtos.pedimento_dates import (
|
||||
PedimentoDatesCreate,
|
||||
PedimentoDatesUpdate,
|
||||
PedimentoDatesResponse
|
||||
PedimentoDatesResponse,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/{pedimento_id}/dates")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("/", response_model=PedimentoDatesResponse)
|
||||
async def get_dates(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get dates by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
dates = PedimentoDatesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not dates:
|
||||
raise HTTPException(status_code=404, detail="Pedimento dates not found")
|
||||
|
||||
|
||||
return dates
|
||||
|
||||
|
||||
@router.post("/", response_model=PedimentoDatesResponse, status_code=201)
|
||||
async def create_dates(
|
||||
async def create_dates(
|
||||
data: PedimentoDatesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create pedimento dates"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
dates = PedimentoDatesService.create(db, data, tenant_id, company_id)
|
||||
return dates
|
||||
|
||||
@@ -53,14 +60,15 @@ async def update_dates(
|
||||
data: PedimentoDatesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update pedimento dates"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
dates = PedimentoDatesService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
if not dates:
|
||||
raise HTTPException(status_code=404, detail="Pedimento dates not found")
|
||||
|
||||
|
||||
return dates
|
||||
|
||||
|
||||
@@ -69,12 +77,13 @@ async def delete_dates(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete pedimento dates"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoDatesService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Pedimento dates not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoDecrementables CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from typing import Dict, Any, List
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_decrementables import PedimentoDecrementablesService
|
||||
from ..dtos.pedimento_decrementables import (
|
||||
PedimentoDecrementablesCreate,
|
||||
PedimentoDecrementablesUpdate,
|
||||
PedimentoDecrementablesResponse
|
||||
PedimentoDecrementablesResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +24,14 @@ async def list_decrementables(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get all decrementables for a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
return decrementables
|
||||
|
||||
|
||||
@@ -37,14 +41,17 @@ async def get_decrementable(
|
||||
decrementable_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get a specific decrementable by ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
decrementable = PedimentoDecrementablesService.get_by_id(db, decrementable_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
decrementable = PedimentoDecrementablesService.get_by_id(
|
||||
db, decrementable_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not decrementable:
|
||||
raise HTTPException(status_code=404, detail="Decrementable not found")
|
||||
|
||||
|
||||
return decrementable
|
||||
|
||||
|
||||
@@ -54,15 +61,18 @@ async def create_decrementable(
|
||||
data: PedimentoDecrementablesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new decrementable"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
decrementable = PedimentoDecrementablesService.create(db, data, tenant_id, company_id)
|
||||
|
||||
decrementable = PedimentoDecrementablesService.create(
|
||||
db, data, tenant_id, company_id
|
||||
)
|
||||
return decrementable
|
||||
|
||||
|
||||
@@ -73,14 +83,17 @@ async def update_decrementable(
|
||||
data: PedimentoDecrementablesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update a decrementable"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
decrementable = PedimentoDecrementablesService.update(db, decrementable_id, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
decrementable = PedimentoDecrementablesService.update(
|
||||
db, decrementable_id, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not decrementable:
|
||||
raise HTTPException(status_code=404, detail="Decrementable not found")
|
||||
|
||||
|
||||
return decrementable
|
||||
|
||||
|
||||
@@ -90,12 +103,15 @@ async def delete_decrementable(
|
||||
decrementable_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a decrementable"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoDecrementablesService.delete(db, decrementable_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoDecrementablesService.delete(
|
||||
db, decrementable_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Decrementable not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoIncrementables CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from typing import Dict, Any, List
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_incrementables import PedimentoIncrementablesService
|
||||
from ..dtos.pedimento_incrementables import (
|
||||
PedimentoIncrementablesCreate,
|
||||
PedimentoIncrementablesUpdate,
|
||||
PedimentoIncrementablesResponse
|
||||
PedimentoIncrementablesResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +24,14 @@ async def list_incrementables(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get all incrementables for a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
return incrementables
|
||||
|
||||
|
||||
@@ -37,14 +41,17 @@ async def get_incrementable(
|
||||
incrementable_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get a specific incrementable by ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
incrementable = PedimentoIncrementablesService.get_by_id(db, incrementable_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
incrementable = PedimentoIncrementablesService.get_by_id(
|
||||
db, incrementable_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not incrementable:
|
||||
raise HTTPException(status_code=404, detail="Incrementable not found")
|
||||
|
||||
|
||||
return incrementable
|
||||
|
||||
|
||||
@@ -54,15 +61,18 @@ async def create_incrementable(
|
||||
data: PedimentoIncrementablesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new incrementable"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
incrementable = PedimentoIncrementablesService.create(db, data, tenant_id, company_id)
|
||||
|
||||
incrementable = PedimentoIncrementablesService.create(
|
||||
db, data, tenant_id, company_id
|
||||
)
|
||||
return incrementable
|
||||
|
||||
|
||||
@@ -73,14 +83,17 @@ async def update_incrementable(
|
||||
data: PedimentoIncrementablesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update an incrementable"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
incrementable = PedimentoIncrementablesService.update(db, incrementable_id, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
incrementable = PedimentoIncrementablesService.update(
|
||||
db, incrementable_id, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not incrementable:
|
||||
raise HTTPException(status_code=404, detail="Incrementable not found")
|
||||
|
||||
|
||||
return incrementable
|
||||
|
||||
|
||||
@@ -90,12 +103,15 @@ async def delete_incrementable(
|
||||
incrementable_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete an incrementable"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoIncrementablesService.delete(db, incrementable_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoIncrementablesService.delete(
|
||||
db, incrementable_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Incrementable not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoIndexes CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_indexes import PedimentoIndexesService
|
||||
from ..dtos.pedimento_indexes import (
|
||||
PedimentoIndexesCreate,
|
||||
PedimentoIndexesUpdate,
|
||||
PedimentoIndexesResponse
|
||||
PedimentoIndexesResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_indexes(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get indexes by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
indexes = PedimentoIndexesService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not indexes:
|
||||
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
|
||||
|
||||
|
||||
return indexes
|
||||
|
||||
|
||||
@@ -39,14 +44,15 @@ async def create_indexes(
|
||||
data: PedimentoIndexesCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create pedimento indexes"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
indexes = PedimentoIndexesService.create(db, data, tenant_id, company_id)
|
||||
return indexes
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_indexes(
|
||||
data: PedimentoIndexesUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update pedimento indexes"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
indexes = PedimentoIndexesService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
indexes = PedimentoIndexesService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not indexes:
|
||||
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
|
||||
|
||||
|
||||
return indexes
|
||||
|
||||
|
||||
@@ -73,12 +82,13 @@ async def delete_indexes(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete pedimento indexes"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoIndexesService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoPayments CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from typing import Dict, Any, List
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_payments import PedimentoPaymentsService
|
||||
from ..dtos.pedimento_payments import (
|
||||
PedimentoPaymentsCreate,
|
||||
PedimentoPaymentsUpdate,
|
||||
PedimentoPaymentsResponse
|
||||
PedimentoPaymentsResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +24,14 @@ async def list_payments(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get all payments for a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
payments = PedimentoPaymentsService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
return payments
|
||||
|
||||
|
||||
@@ -37,14 +41,17 @@ async def get_payment(
|
||||
payment_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get a specific payment by ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
payment = PedimentoPaymentsService.get_by_id(db, payment_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
payment = PedimentoPaymentsService.get_by_id(
|
||||
db, payment_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not payment:
|
||||
raise HTTPException(status_code=404, detail="Payment not found")
|
||||
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
@@ -54,14 +61,15 @@ async def create_payment(
|
||||
data: PedimentoPaymentsCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new payment"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
payment = PedimentoPaymentsService.create(db, data, tenant_id, company_id)
|
||||
return payment
|
||||
|
||||
@@ -73,14 +81,17 @@ async def update_payment(
|
||||
data: PedimentoPaymentsUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update a payment"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
payment = PedimentoPaymentsService.update(db, payment_id, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
payment = PedimentoPaymentsService.update(
|
||||
db, payment_id, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not payment:
|
||||
raise HTTPException(status_code=404, detail="Payment not found")
|
||||
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
@@ -90,12 +101,15 @@ async def delete_payment(
|
||||
payment_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a payment"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoPaymentsService.delete(db, payment_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoPaymentsService.delete(
|
||||
db, payment_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Payment not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
"""
|
||||
Routes for PedimentoRectificationDestination CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_rectification_destination import PedimentoRectificationDestinationService
|
||||
from ..services.pedimento_rectification_destination import (
|
||||
PedimentoRectificationDestinationService,
|
||||
)
|
||||
from ..dtos.pedimento_rectification_destination import (
|
||||
PedimentoRectificationDestinationCreate,
|
||||
PedimentoRectificationDestinationUpdate,
|
||||
PedimentoRectificationDestinationResponse
|
||||
PedimentoRectificationDestinationResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,32 +26,42 @@ async def get_rectification_destination(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get rectification destination by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not rectification:
|
||||
raise HTTPException(status_code=404, detail="Rectification destination not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Rectification destination not found"
|
||||
)
|
||||
|
||||
return rectification
|
||||
|
||||
|
||||
@router.post("/", response_model=PedimentoRectificationDestinationResponse, status_code=201)
|
||||
@router.post(
|
||||
"/", response_model=PedimentoRectificationDestinationResponse, status_code=201
|
||||
)
|
||||
async def create_rectification_destination(
|
||||
pedimento_id: int,
|
||||
data: PedimentoRectificationDestinationCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create rectification destination"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
rectification = PedimentoRectificationDestinationService.create(db, data, tenant_id, company_id)
|
||||
|
||||
rectification = PedimentoRectificationDestinationService.create(
|
||||
db, data, tenant_id, company_id
|
||||
)
|
||||
return rectification
|
||||
|
||||
|
||||
@@ -57,14 +71,19 @@ async def update_rectification_destination(
|
||||
data: PedimentoRectificationDestinationUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update rectification destination"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
rectification = PedimentoRectificationDestinationService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
rectification = PedimentoRectificationDestinationService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not rectification:
|
||||
raise HTTPException(status_code=404, detail="Rectification destination not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Rectification destination not found"
|
||||
)
|
||||
|
||||
return rectification
|
||||
|
||||
|
||||
@@ -73,12 +92,17 @@ async def delete_rectification_destination(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete rectification destination"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoRectificationDestinationService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoRectificationDestinationService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Rectification destination not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Rectification destination not found"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
"""
|
||||
Routes for PedimentoRectificationOrigin CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_rectification_origin import PedimentoRectificationOriginService
|
||||
from ..services.pedimento_rectification_origin import (
|
||||
PedimentoRectificationOriginService,
|
||||
)
|
||||
from ..dtos.pedimento_rectification_origin import (
|
||||
PedimentoRectificationOriginCreate,
|
||||
PedimentoRectificationOriginUpdate,
|
||||
PedimentoRectificationOriginResponse
|
||||
PedimentoRectificationOriginResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +26,17 @@ async def get_rectification_origin(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get rectification origin by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not rectification:
|
||||
raise HTTPException(status_code=404, detail="Rectification origin not found")
|
||||
|
||||
|
||||
return rectification
|
||||
|
||||
|
||||
@@ -39,15 +46,18 @@ async def create_rectification_origin(
|
||||
data: PedimentoRectificationOriginCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create rectification origin"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
rectification = PedimentoRectificationOriginService.create(db, data, tenant_id, company_id)
|
||||
|
||||
rectification = PedimentoRectificationOriginService.create(
|
||||
db, data, tenant_id, company_id
|
||||
)
|
||||
return rectification
|
||||
|
||||
|
||||
@@ -57,14 +67,17 @@ async def update_rectification_origin(
|
||||
data: PedimentoRectificationOriginUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update rectification origin"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
rectification = PedimentoRectificationOriginService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
rectification = PedimentoRectificationOriginService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not rectification:
|
||||
raise HTTPException(status_code=404, detail="Rectification origin not found")
|
||||
|
||||
|
||||
return rectification
|
||||
|
||||
|
||||
@@ -73,12 +86,15 @@ async def delete_rectification_origin(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete rectification origin"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoRectificationOriginService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoRectificationOriginService.delete(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Rectification origin not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoTransportMeans CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from typing import Dict, Any, List
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_transport_means import PedimentoTransportMeansService
|
||||
from ..dtos.pedimento_transport_means import (
|
||||
PedimentoTransportMeansCreate,
|
||||
PedimentoTransportMeansUpdate,
|
||||
PedimentoTransportMeansResponse
|
||||
PedimentoTransportMeansResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +24,14 @@ async def list_transport_means(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get all transport means for a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
transport_means = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
transport_means = PedimentoTransportMeansService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
return transport_means
|
||||
|
||||
|
||||
@@ -37,14 +41,17 @@ async def get_transport_mean(
|
||||
transport_mean_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get a specific transport mean by ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
transport_mean = PedimentoTransportMeansService.get_by_id(db, transport_mean_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
transport_mean = PedimentoTransportMeansService.get_by_id(
|
||||
db, transport_mean_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not transport_mean:
|
||||
raise HTTPException(status_code=404, detail="Transport mean not found")
|
||||
|
||||
|
||||
return transport_mean
|
||||
|
||||
|
||||
@@ -54,15 +61,18 @@ async def create_transport_mean(
|
||||
data: PedimentoTransportMeansCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new transport mean"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
transport_mean = PedimentoTransportMeansService.create(db, data, tenant_id, company_id)
|
||||
|
||||
transport_mean = PedimentoTransportMeansService.create(
|
||||
db, data, tenant_id, company_id
|
||||
)
|
||||
return transport_mean
|
||||
|
||||
|
||||
@@ -73,14 +83,17 @@ async def update_transport_mean(
|
||||
data: PedimentoTransportMeansUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update a transport mean"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
transport_mean = PedimentoTransportMeansService.update(db, transport_mean_id, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
transport_mean = PedimentoTransportMeansService.update(
|
||||
db, transport_mean_id, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not transport_mean:
|
||||
raise HTTPException(status_code=404, detail="Transport mean not found")
|
||||
|
||||
|
||||
return transport_mean
|
||||
|
||||
|
||||
@@ -90,12 +103,15 @@ async def delete_transport_mean(
|
||||
transport_mean_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a transport mean"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
success = PedimentoTransportMeansService.delete(db, transport_mean_id, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoTransportMeansService.delete(
|
||||
db, transport_mean_id, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Transport mean not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
"""
|
||||
Routes for PedimentoValidation CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimento_validation import PedimentoValidationService
|
||||
from ..dtos.pedimento_validation import (
|
||||
PedimentoValidationCreate,
|
||||
PedimentoValidationUpdate,
|
||||
PedimentoValidationResponse
|
||||
PedimentoValidationResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -22,14 +24,17 @@ async def get_validation(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get validation by pedimento ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
validation = PedimentoValidationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
validation = PedimentoValidationService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not validation:
|
||||
raise HTTPException(status_code=404, detail="Pedimento validation not found")
|
||||
|
||||
|
||||
return validation
|
||||
|
||||
|
||||
@@ -39,14 +44,15 @@ async def create_validation(
|
||||
data: PedimentoValidationCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create pedimento validation"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Ensure pedimento_id matches
|
||||
if data.pedimento_id != pedimento_id:
|
||||
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
|
||||
|
||||
|
||||
validation = PedimentoValidationService.create(db, data, tenant_id, company_id)
|
||||
return validation
|
||||
|
||||
@@ -57,14 +63,17 @@ async def update_validation(
|
||||
data: PedimentoValidationUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update pedimento validation"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
validation = PedimentoValidationService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
validation = PedimentoValidationService.update(
|
||||
db, pedimento_id, tenant_id, company_id, data
|
||||
)
|
||||
if not validation:
|
||||
raise HTTPException(status_code=404, detail="Pedimento validation not found")
|
||||
|
||||
|
||||
return validation
|
||||
|
||||
|
||||
@@ -73,12 +82,13 @@ async def delete_validation(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete pedimento validation"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentoValidationService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Pedimento validation not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"""
|
||||
Routes for Pedimentos CRUD operations
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, Optional
|
||||
from core.database import get_core_db
|
||||
from core.security import validate_access_to_resource
|
||||
from core.security import validate_access_to_resource, get_current_user
|
||||
|
||||
from ..services.pedimentos import PedimentosService
|
||||
from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate, PedimentosResponse
|
||||
@@ -23,10 +24,11 @@ async def list_pedimentos(
|
||||
client_id: Optional[int] = Query(None, description="Filter by client ID"),
|
||||
year: Optional[str] = Query(None, description="Filter by year"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get all pedimentos with pagination and filters"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
filters = {}
|
||||
if status:
|
||||
filters["status"] = status
|
||||
@@ -34,15 +36,17 @@ async def list_pedimentos(
|
||||
filters["client_id"] = client_id
|
||||
if year:
|
||||
filters["year"] = year
|
||||
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
items, total = PedimentosService.get_all(db, tenant_id, company_id, skip, page_size, filters)
|
||||
|
||||
items, total = PedimentosService.get_all(
|
||||
db, tenant_id, company_id, skip, page_size, filters
|
||||
)
|
||||
|
||||
return {
|
||||
"items": [PedimentosResponse.model_validate(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
"page_size": page_size,
|
||||
}
|
||||
|
||||
|
||||
@@ -51,14 +55,15 @@ async def get_pedimento(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Get a pedimento by ID"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id, company_id)
|
||||
if not pedimento:
|
||||
raise HTTPException(status_code=404, detail="Pedimento not found")
|
||||
|
||||
|
||||
return pedimento
|
||||
|
||||
|
||||
@@ -67,10 +72,11 @@ async def create_pedimento(
|
||||
data: PedimentosCreate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
pedimento = PedimentosService.create(db, data, tenant_id, company_id)
|
||||
return pedimento
|
||||
|
||||
@@ -81,14 +87,15 @@ async def update_pedimento(
|
||||
data: PedimentosUpdate,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Update a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
pedimento = PedimentosService.update(db, pedimento_id, tenant_id, company_id, data)
|
||||
if not pedimento:
|
||||
raise HTTPException(status_code=404, detail="Pedimento not found")
|
||||
|
||||
|
||||
return pedimento
|
||||
|
||||
|
||||
@@ -97,12 +104,13 @@ async def delete_pedimento(
|
||||
pedimento_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a pedimento"""
|
||||
tenant_id = validate_access_to_resource(company_id)
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
success = PedimentosService.delete(db, pedimento_id, tenant_id, company_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Pedimento not found")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,32 +1,47 @@
|
||||
"""
|
||||
Service layer for PedimentoConfigAdditional CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models.pedimento_config_additional import PedimentoConfigAdditional
|
||||
from ..dtos.pedimento_config_additional import PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalUpdate
|
||||
from ..dtos.pedimento_config_additional import (
|
||||
PedimentoConfigAdditionalCreate,
|
||||
PedimentoConfigAdditionalUpdate,
|
||||
)
|
||||
|
||||
|
||||
class PedimentoConfigAdditionalService:
|
||||
"""Service class for PedimentoConfigAdditional business logic"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> Optional[PedimentoConfigAdditional]:
|
||||
def get_by_pedimento_id(
|
||||
db: Session, pedimento_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[PedimentoConfigAdditional]:
|
||||
"""Get config by pedimento ID"""
|
||||
return db.query(PedimentoConfigAdditional).filter(
|
||||
PedimentoConfigAdditional.pedimento_id == pedimento_id,
|
||||
PedimentoConfigAdditional.tenant_id == tenant_id,
|
||||
PedimentoConfigAdditional.company_id == company_id
|
||||
).first()
|
||||
return (
|
||||
db.query(PedimentoConfigAdditional)
|
||||
.filter(
|
||||
PedimentoConfigAdditional.pedimento_id == pedimento_id,
|
||||
PedimentoConfigAdditional.tenant_id == tenant_id,
|
||||
PedimentoConfigAdditional.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, config_data: PedimentoConfigAdditionalCreate, tenant_id: int, company_id: int) -> PedimentoConfigAdditional:
|
||||
def create(
|
||||
db: Session,
|
||||
config_data: PedimentoConfigAdditionalCreate,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> PedimentoConfigAdditional:
|
||||
"""Create a new config"""
|
||||
config = PedimentoConfigAdditional(**config_data.model_dump())
|
||||
config.tenant_id = tenant_id
|
||||
config.company_id = company_id
|
||||
|
||||
|
||||
db.add(config)
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
@@ -38,17 +53,19 @@ class PedimentoConfigAdditionalService:
|
||||
pedimento_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
config_data: PedimentoConfigAdditionalUpdate
|
||||
config_data: PedimentoConfigAdditionalUpdate,
|
||||
) -> Optional[PedimentoConfigAdditional]:
|
||||
"""Update config"""
|
||||
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
config = PedimentoConfigAdditionalService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
return None
|
||||
|
||||
|
||||
update_data = config_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(config, field, value)
|
||||
|
||||
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
return config
|
||||
@@ -56,10 +73,12 @@ class PedimentoConfigAdditionalService:
|
||||
@staticmethod
|
||||
def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> bool:
|
||||
"""Delete config"""
|
||||
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
config = PedimentoConfigAdditionalService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
return False
|
||||
|
||||
|
||||
db.delete(config)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -1,32 +1,47 @@
|
||||
"""
|
||||
Service layer for PedimentoConfigCalculations CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models.pedimento_config_calculations import PedimentoConfigCalculations
|
||||
from ..dtos.pedimento_config_calculations import PedimentoConfigCalculationsCreate, PedimentoConfigCalculationsUpdate
|
||||
from ..dtos.pedimento_config_calculations import (
|
||||
PedimentoConfigCalculationsCreate,
|
||||
PedimentoConfigCalculationsUpdate,
|
||||
)
|
||||
|
||||
|
||||
class PedimentoConfigCalculationsService:
|
||||
"""Service class for PedimentoConfigCalculations business logic"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> Optional[PedimentoConfigCalculations]:
|
||||
def get_by_pedimento_id(
|
||||
db: Session, pedimento_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[PedimentoConfigCalculations]:
|
||||
"""Get config by pedimento ID"""
|
||||
return db.query(PedimentoConfigCalculations).filter(
|
||||
PedimentoConfigCalculations.pedimento_id == pedimento_id,
|
||||
PedimentoConfigCalculations.tenant_id == tenant_id,
|
||||
PedimentoConfigCalculations.company_id == company_id
|
||||
).first()
|
||||
return (
|
||||
db.query(PedimentoConfigCalculations)
|
||||
.filter(
|
||||
PedimentoConfigCalculations.pedimento_id == pedimento_id,
|
||||
PedimentoConfigCalculations.tenant_id == tenant_id,
|
||||
PedimentoConfigCalculations.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, config_data: PedimentoConfigCalculationsCreate, tenant_id: int, company_id: int) -> PedimentoConfigCalculations:
|
||||
def create(
|
||||
db: Session,
|
||||
config_data: PedimentoConfigCalculationsCreate,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> PedimentoConfigCalculations:
|
||||
"""Create a new config"""
|
||||
config = PedimentoConfigCalculations(**config_data.model_dump())
|
||||
config.tenant_id = tenant_id
|
||||
config.company_id = company_id
|
||||
|
||||
|
||||
db.add(config)
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
@@ -38,17 +53,19 @@ class PedimentoConfigCalculationsService:
|
||||
pedimento_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
config_data: PedimentoConfigCalculationsUpdate
|
||||
config_data: PedimentoConfigCalculationsUpdate,
|
||||
) -> Optional[PedimentoConfigCalculations]:
|
||||
"""Update config"""
|
||||
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
config = PedimentoConfigCalculationsService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
return None
|
||||
|
||||
|
||||
update_data = config_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(config, field, value)
|
||||
|
||||
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
return config
|
||||
@@ -56,10 +73,12 @@ class PedimentoConfigCalculationsService:
|
||||
@staticmethod
|
||||
def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> bool:
|
||||
"""Delete config"""
|
||||
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
|
||||
config = PedimentoConfigCalculationsService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id, company_id
|
||||
)
|
||||
if not config:
|
||||
return False
|
||||
|
||||
|
||||
db.delete(config)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
"""
|
||||
Service layer for PedimentoConfigParameters CRUD operations
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models.pedimento_config_parameters import PedimentoConfigParameters
|
||||
from ..dtos.pedimento_config_parameters import PedimentoConfigParametersCreate, PedimentoConfigParametersUpdate
|
||||
from ..dtos.pedimento_config_parameters import (
|
||||
PedimentoConfigParametersCreate,
|
||||
PedimentoConfigParametersUpdate,
|
||||
)
|
||||
|
||||
|
||||
class PedimentoConfigParametersService:
|
||||
"""Service class for PedimentoConfigParameters business logic"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigParameters]:
|
||||
def get_by_pedimento_id(
|
||||
db: Session, pedimento_id: int, tenant_id: int
|
||||
) -> Optional[PedimentoConfigParameters]:
|
||||
"""Get config by pedimento ID"""
|
||||
return db.query(PedimentoConfigParameters).filter(
|
||||
PedimentoConfigParameters.pedimento_id == pedimento_id,
|
||||
PedimentoConfigParameters.tenant_id == tenant_id
|
||||
).first()
|
||||
return (
|
||||
db.query(PedimentoConfigParameters)
|
||||
.filter(
|
||||
PedimentoConfigParameters.pedimento_id == pedimento_id,
|
||||
PedimentoConfigParameters.tenant_id == tenant_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, config_data: PedimentoConfigParametersCreate) -> PedimentoConfigParameters:
|
||||
def create(
|
||||
db: Session, config_data: PedimentoConfigParametersCreate
|
||||
) -> PedimentoConfigParameters:
|
||||
"""Create a new config"""
|
||||
config = PedimentoConfigParameters(**config_data.model_dump())
|
||||
db.add(config)
|
||||
@@ -33,17 +45,19 @@ class PedimentoConfigParametersService:
|
||||
db: Session,
|
||||
pedimento_id: int,
|
||||
tenant_id: int,
|
||||
config_data: PedimentoConfigParametersUpdate
|
||||
config_data: PedimentoConfigParametersUpdate,
|
||||
) -> Optional[PedimentoConfigParameters]:
|
||||
"""Update config"""
|
||||
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id)
|
||||
config = PedimentoConfigParametersService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id
|
||||
)
|
||||
if not config:
|
||||
return None
|
||||
|
||||
|
||||
update_data = config_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(config, field, value)
|
||||
|
||||
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
return config
|
||||
@@ -51,10 +65,12 @@ class PedimentoConfigParametersService:
|
||||
@staticmethod
|
||||
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
|
||||
"""Delete config"""
|
||||
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id)
|
||||
config = PedimentoConfigParametersService.get_by_pedimento_id(
|
||||
db, pedimento_id, tenant_id
|
||||
)
|
||||
if not config:
|
||||
return False
|
||||
|
||||
|
||||
db.delete(config)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user