Merge pull request 'Refactor database models to include company_id and update unique constraints' (#12) from feature/catalogos-generales into development
Reviewed-on: ADUANASOFT/anexo76#12
This commit is contained in:
32
backend/api/v1/modules/a24/q/q_classes/models.py
Normal file
32
backend/api/v1/modules/a24/q/q_classes/models.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Boolean, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
|
||||
from core.database import Base
|
||||
|
||||
class QClasses(Base):
|
||||
__tablename__ = 'q_classes' #QClases
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='qclases_pk'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_qclasses_tenants'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_qclasses_company'),
|
||||
ForeignKeyConstraint(['class_id'], ['classes.id'], name='fk_qclasses_classes'),
|
||||
{'schema': 'a24'}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
class_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=True, index=True)
|
||||
|
||||
import_tariff_code: Mapped[str] = mapped_column(String(10)) #FRACCIONIMPO
|
||||
import_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACIMPO
|
||||
export_tariff_code: Mapped[str] = mapped_column(String(10)) #FRACCIONEXPO
|
||||
export_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACEXPO
|
||||
depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) #TASADEPRECIA
|
||||
fda_code: Mapped[str] = mapped_column(String(20)) #FDA
|
||||
eccn_code: Mapped[str] = mapped_column(String(20)) #ECCN
|
||||
class_enabled: Mapped[bool] = mapped_column(Boolean) #HABILITADESHABILITACLASE
|
||||
|
||||
|
||||
21
backend/api/v1/modules/a24/s/s_classes/models.py
Normal file
21
backend/api/v1/modules/a24/s/s_classes/models.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.orm.base import Mapped
|
||||
from core.database import Base
|
||||
|
||||
class SClasses(Base):
|
||||
__tablename__ = 's_classes' #SClases
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_sclasses_tenants'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_sclasses_company'),
|
||||
ForeignKeyConstraint(['class_id'], ['a76.clases.class_id'], name='fk_sclasses_classes'),
|
||||
PrimaryKeyConstraint('id', name='sclases_pk'),
|
||||
{'schema': 'a24'}
|
||||
)
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
class_id: Mapped[int] = mapped_column(Integer, nullable=False) #Id de clase
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, nullable=True, index=True)
|
||||
|
||||
stock_um: Mapped[str] = mapped_column(String(5)) #Unidad de medida para existencia
|
||||
us_tariff_code: Mapped[str] = mapped_column(String(19)) #Fracción americana (USA)
|
||||
@@ -9,7 +9,7 @@ from datetime import datetime
|
||||
|
||||
class ClassCreateDTO(BaseModel):
|
||||
"""DTO para crear una clase"""
|
||||
client_key: int = Field(..., description="Client key")
|
||||
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")
|
||||
@@ -43,7 +43,7 @@ class ClassUpdateDTO(BaseModel):
|
||||
|
||||
class ClassResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de clase"""
|
||||
client_key: int
|
||||
client_id: int
|
||||
class_code: str
|
||||
description_spanish: Optional[str] = None
|
||||
description_english: Optional[str] = None
|
||||
@@ -61,7 +61,7 @@ class ClassResponseDTO(BaseModel):
|
||||
|
||||
class ClassBasicDTO(BaseModel):
|
||||
"""DTO para información básica de clase"""
|
||||
client_key: int
|
||||
client_id: int
|
||||
class_code: str
|
||||
description_spanish: Optional[str] = None
|
||||
description_english: Optional[str] = None
|
||||
@@ -85,7 +85,7 @@ class ClassListDTO(BaseModel):
|
||||
|
||||
class ClassSearchDTO(BaseModel):
|
||||
"""DTO para búsqueda de clases"""
|
||||
client_key: Optional[int] = Field(None, description="Filter by client key")
|
||||
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")
|
||||
|
||||
@@ -18,30 +18,32 @@ class Class(Base):
|
||||
__tablename__ = "classes"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='classes_pkey'),
|
||||
UniqueConstraint('client_key', 'class_code', name='uq_classes_client_key_class_code'),
|
||||
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)
|
||||
|
||||
# Unique constraint compuesta
|
||||
client_key: Mapped[int] = mapped_column()
|
||||
class_code: Mapped[str] = mapped_column(String(8))
|
||||
|
||||
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
|
||||
|
||||
# Basic information
|
||||
description_es: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
description_en: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
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
|
||||
|
||||
# Tariff fractions
|
||||
fraction: Mapped[Optional[str]] = mapped_column(String(10)) # Mexican tariff fraction
|
||||
fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCION
|
||||
us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAME - US tariff fraction
|
||||
|
||||
# Additional classification
|
||||
@@ -54,13 +56,13 @@ class Class(Base):
|
||||
|
||||
# Inverse relationship with GParts that have this class
|
||||
parts: Mapped[list["Part"]] = relationship(
|
||||
primaryjoin="and_(Class.client_key == Part.client_key, Class.class_code == Part.part_class)",
|
||||
foreign_keys="[Part.client_key, Part.part_class]",
|
||||
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"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Class(client_key={self.client_key}, class_code='{self.class_code}', description='{self.description_es}')>"
|
||||
return f"<Class(client_id={self.client_id}, class_code='{self.class_code}', description='{self.description_es}')>"
|
||||
|
||||
|
||||
|
||||
@@ -19,25 +19,11 @@ from .dto import (
|
||||
|
||||
router = APIRouter(prefix="/classes", tags=["Classes"])
|
||||
|
||||
|
||||
@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)
|
||||
):
|
||||
"""
|
||||
Create a new class in the system
|
||||
"""
|
||||
service = ClassService(db)
|
||||
return service.create_class(class_data)
|
||||
|
||||
|
||||
@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"),
|
||||
client_key: Optional[int] = Query(None, description="Filter by client key"),
|
||||
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"),
|
||||
@@ -51,7 +37,7 @@ async def list_classes(
|
||||
"""
|
||||
service = ClassService(db)
|
||||
search_params = ClassSearchDTO(
|
||||
client_key=client_key,
|
||||
client_id=client_id,
|
||||
class_code=class_code,
|
||||
description=description,
|
||||
material_key=material_key,
|
||||
@@ -61,9 +47,9 @@ async def list_classes(
|
||||
return service.list_classes(skip, limit, search_params)
|
||||
|
||||
|
||||
@router.get("/client/{client_key}", response_model=List[ClassBasicDTO])
|
||||
@router.get("/client/{client_id}", response_model=List[ClassBasicDTO])
|
||||
async def get_classes_by_client(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
@@ -73,7 +59,7 @@ async def get_classes_by_client(
|
||||
Get all classes for a specific client
|
||||
"""
|
||||
service = ClassService(db)
|
||||
return service.search_by_client(client_key, skip, limit)
|
||||
return service.search_by_client(client_id, skip, limit)
|
||||
|
||||
|
||||
@router.get("/search/fraction/{fraction}", response_model=List[ClassBasicDTO])
|
||||
@@ -140,29 +126,40 @@ async def get_classes_statistics(
|
||||
return service.get_classes_statistics()
|
||||
|
||||
|
||||
@router.get("/{client_key}/{class_code}", response_model=ClassResponseDTO)
|
||||
@router.get("/{client_id}/{class_code}", response_model=ClassResponseDTO)
|
||||
async def get_class(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get class by composite key (client_key + class_code)
|
||||
Get class by composite key (client_id + class_code)
|
||||
"""
|
||||
service = ClassService(db)
|
||||
class_obj = service.get_class(client_key, class_code)
|
||||
class_obj = service.get_class(client_id, class_code)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
|
||||
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)
|
||||
):
|
||||
"""
|
||||
Create a new class in the system
|
||||
"""
|
||||
service = ClassService(db)
|
||||
return service.create_class(class_data)
|
||||
|
||||
@router.put("/{client_key}/{class_code}", response_model=ClassResponseDTO)
|
||||
@router.put("/{client_id}/{class_code}", response_model=ClassResponseDTO)
|
||||
async def update_class(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
class_data: ClassUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
@@ -172,18 +169,18 @@ async def update_class(
|
||||
Update class information
|
||||
"""
|
||||
service = ClassService(db)
|
||||
class_obj = service.update_class(client_key, class_code, class_data)
|
||||
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_key '{client_key}' and class_code '{class_code}' not found"
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
)
|
||||
return class_obj
|
||||
|
||||
|
||||
@router.delete("/{client_key}/{class_code}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{client_id}/{class_code}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_class(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -194,17 +191,17 @@ async def delete_class(
|
||||
Note: This will completely remove the class from the system.
|
||||
"""
|
||||
service = ClassService(db)
|
||||
if not service.delete_class(client_key, class_code):
|
||||
if not service.delete_class(client_id, class_code):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
)
|
||||
|
||||
|
||||
# Endpoints específicos para información detallada
|
||||
@router.get("/{client_key}/{class_code}/basic", response_model=ClassBasicDTO)
|
||||
@router.get("/{client_id}/{class_code}/basic", response_model=ClassBasicDTO)
|
||||
async def get_class_basic_info(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -213,15 +210,15 @@ async def get_class_basic_info(
|
||||
Get basic information for a class
|
||||
"""
|
||||
service = ClassService(db)
|
||||
class_obj = service.get_class(client_key, class_code)
|
||||
class_obj = service.get_class(client_id, class_code)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
)
|
||||
|
||||
return ClassBasicDTO(
|
||||
client_key=class_obj.client_key,
|
||||
client_id=class_obj.client_id,
|
||||
class_code=class_obj.class_code,
|
||||
description_spanish=class_obj.description_spanish,
|
||||
description_english=class_obj.description_english,
|
||||
@@ -230,9 +227,9 @@ async def get_class_basic_info(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{client_key}/{class_code}/tariff", response_model=dict)
|
||||
@router.get("/{client_id}/{class_code}/tariff", response_model=dict)
|
||||
async def get_class_tariff_info(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
class_code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -241,15 +238,15 @@ async def get_class_tariff_info(
|
||||
Get tariff information for a class (fractions, IVA exempt, etc.)
|
||||
"""
|
||||
service = ClassService(db)
|
||||
class_obj = service.get_class(client_key, class_code)
|
||||
class_obj = service.get_class(client_id, class_code)
|
||||
if not class_obj:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
|
||||
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
|
||||
)
|
||||
|
||||
return {
|
||||
"client_key": class_obj.client_key,
|
||||
"client_id": class_obj.client_id,
|
||||
"class_code": class_obj.class_code,
|
||||
"fraction": class_obj.fraction,
|
||||
"us_fraction": class_obj.us_fraction,
|
||||
|
||||
@@ -44,7 +44,7 @@ class ClassService:
|
||||
# Verificar que no exista la clase
|
||||
existing = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_key == class_data.client_key,
|
||||
Class.client_id == class_data.client_id,
|
||||
Class.class_code == class_data.class_code
|
||||
)
|
||||
).first()
|
||||
@@ -52,12 +52,12 @@ class ClassService:
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Class with client_key '{class_data.client_key}' and class_code '{class_data.class_code}' already exists"
|
||||
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_key=class_data.client_key,
|
||||
client_id=class_data.client_id,
|
||||
class_code=class_data.class_code,
|
||||
description_spanish=class_data.description_spanish,
|
||||
description_english=class_data.description_english,
|
||||
@@ -74,14 +74,14 @@ class ClassService:
|
||||
self.db.commit()
|
||||
self.db.refresh(db_class)
|
||||
|
||||
logger.info(f"Class created: {db_class.client_key}-{db_class.class_code}")
|
||||
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_key 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:
|
||||
@@ -89,12 +89,12 @@ class ClassService:
|
||||
logger.error(f"Error creating class: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error creating class")
|
||||
|
||||
def get_class(self, client_key: int, class_code: str) -> Optional[ClassResponseDTO]:
|
||||
def get_class(self, client_id: int, class_code: str) -> Optional[ClassResponseDTO]:
|
||||
"""
|
||||
Obtiene una clase por clave compuesta
|
||||
|
||||
Args:
|
||||
client_key: Clave del cliente
|
||||
client_id: Clave del cliente
|
||||
class_code: Código de clase
|
||||
|
||||
Returns:
|
||||
@@ -102,7 +102,7 @@ class ClassService:
|
||||
"""
|
||||
class_obj = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_key == client_key,
|
||||
Class.client_id == client_id,
|
||||
Class.class_code == class_code
|
||||
)
|
||||
).first()
|
||||
@@ -132,8 +132,8 @@ class ClassService:
|
||||
|
||||
# Aplicar filtros si se proporcionan
|
||||
if search_params:
|
||||
if search_params.client_key:
|
||||
query = query.filter(Class.client_key == search_params.client_key)
|
||||
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}%"))
|
||||
@@ -172,12 +172,12 @@ class ClassService:
|
||||
size=len(class_dtos)
|
||||
)
|
||||
|
||||
def update_class(self, client_key: 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_key: Clave del cliente
|
||||
client_id: Clave del cliente
|
||||
class_code: Código de clase
|
||||
class_data: Datos a actualizar
|
||||
|
||||
@@ -186,7 +186,7 @@ class ClassService:
|
||||
"""
|
||||
class_obj = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_key == client_key,
|
||||
Class.client_id == client_id,
|
||||
Class.class_code == class_code
|
||||
)
|
||||
).first()
|
||||
@@ -202,21 +202,21 @@ class ClassService:
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(class_obj)
|
||||
logger.info(f"Class updated: {client_key}-{class_code}")
|
||||
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_key}-{class_code}: {str(e)}")
|
||||
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_key: int, class_code: str) -> bool:
|
||||
def delete_class(self, client_id: int, class_code: str) -> bool:
|
||||
"""
|
||||
Elimina una clase
|
||||
|
||||
Args:
|
||||
client_key: Clave del cliente
|
||||
client_id: Clave del cliente
|
||||
class_code: Código de clase
|
||||
|
||||
Returns:
|
||||
@@ -224,7 +224,7 @@ class ClassService:
|
||||
"""
|
||||
class_obj = self.db.query(Class).filter(
|
||||
and_(
|
||||
Class.client_key == client_key,
|
||||
Class.client_id == client_id,
|
||||
Class.class_code == class_code
|
||||
)
|
||||
).first()
|
||||
@@ -235,11 +235,11 @@ class ClassService:
|
||||
try:
|
||||
self.db.delete(class_obj)
|
||||
self.db.commit()
|
||||
logger.info(f"Class deleted: {client_key}-{class_code}")
|
||||
logger.info(f"Class deleted: {client_id}-{class_code}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
logger.error(f"Error deleting class {client_key}-{class_code}: {str(e)}")
|
||||
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]:
|
||||
@@ -247,9 +247,9 @@ class ClassService:
|
||||
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_key: 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_key == client_key).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]:
|
||||
@@ -267,7 +267,7 @@ class ClassService:
|
||||
total_classes = self.db.query(Class).count()
|
||||
|
||||
# Contar por clientes
|
||||
clients_count = self.db.query(Class.client_key).distinct().count()
|
||||
clients_count = self.db.query(Class.client_id).distinct().count()
|
||||
|
||||
# Contar por revisión física
|
||||
physical_review_stats = {}
|
||||
|
||||
@@ -16,11 +16,14 @@ class ClientProvider(Base):
|
||||
__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"}
|
||||
)
|
||||
|
||||
# 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
|
||||
@@ -37,8 +40,7 @@ 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)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
|
||||
|
||||
# Relationships
|
||||
address: Mapped[Optional["ClientProviderAddress"]] = relationship(back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
|
||||
|
||||
@@ -3,7 +3,7 @@ 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
|
||||
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
|
||||
@@ -16,12 +16,13 @@ class Company(Base):
|
||||
__tablename__ = "company"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='company_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_company_tenant'),
|
||||
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))
|
||||
@@ -68,8 +69,6 @@ class Company(Base):
|
||||
# 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)
|
||||
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
|
||||
@@ -6,21 +6,25 @@ from core.database import Base
|
||||
class CountryRuleOct(Base):
|
||||
__tablename__ = "country_rule_oct"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='country_rule_oct_pkey'),
|
||||
UniqueConstraint('permission', 'line', 'fraction', 'country_code', name='uq_country_rule_oct_permission_line_fraction_country'),
|
||||
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'),
|
||||
ForeignKeyConstraint(
|
||||
['permission', 'line', 'fraction'],
|
||||
['a76.fraction_rule_octave.permission', 'a76.fraction_rule_octave.line', 'a76.fraction_rule_octave.fraction'],
|
||||
['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'
|
||||
),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_country_rule_oct_tenant'),
|
||||
),
|
||||
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))
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
@@ -10,13 +10,16 @@ class ExchangeRate(Base):
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='exchange_rate_pkey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_exchange_rate_tenant'),
|
||||
UniqueConstraint('date', 'tenant_id', name='uq_exchange_rate_date_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))
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
foreign_currency: Mapped[Optional[str]] = mapped_column(String(7))
|
||||
@@ -7,13 +7,17 @@ class FractionRuleOctave(Base):
|
||||
__tablename__ = "fraction_rule_octave"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='fraction_rule_octave_pkey'),
|
||||
UniqueConstraint('permission', 'line', 'fraction', name='uq_fraction_rule_octave_permission_line_fraction'),
|
||||
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))
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
@@ -11,13 +11,16 @@ class Package(Base):
|
||||
__tablename__ = "packages" # GBultos
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='packages_pkey'),
|
||||
UniqueConstraint('key', name='packages_key_ukey'),
|
||||
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))
|
||||
|
||||
@@ -10,7 +10,7 @@ from decimal import Decimal
|
||||
|
||||
class PartCreateDTO(BaseModel):
|
||||
"""DTO para crear una parte"""
|
||||
client_key: int = Field(..., description="Client key")
|
||||
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")
|
||||
@@ -95,7 +95,7 @@ class PartUpdateDTO(BaseModel):
|
||||
|
||||
class PartResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de parte"""
|
||||
client_key: int
|
||||
client_id: int
|
||||
part_number: str
|
||||
fraction: Optional[str] = None
|
||||
description_spanish: Optional[str] = None
|
||||
@@ -143,7 +143,7 @@ class PartResponseDTO(BaseModel):
|
||||
|
||||
class PartBasicDTO(BaseModel):
|
||||
"""DTO para información básica de parte"""
|
||||
client_key: int
|
||||
client_id: int
|
||||
part_number: str
|
||||
description_spanish: Optional[str] = None
|
||||
description_english: Optional[str] = None
|
||||
@@ -169,7 +169,7 @@ class PartListDTO(BaseModel):
|
||||
|
||||
class PartSearchDTO(BaseModel):
|
||||
"""DTO para búsqueda de partes"""
|
||||
client_key: Optional[int] = Field(None, description="Filter by client key")
|
||||
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")
|
||||
fraction: Optional[str] = Field(None, description="Filter by tariff fraction")
|
||||
|
||||
@@ -22,20 +22,20 @@ class Part(Base):
|
||||
__tablename__ = "parts"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='parts_pkey'),
|
||||
UniqueConstraint('client_key', 'part_number', name='client_part_ukey'),
|
||||
ForeignKeyConstraint(['country_of_origin'], ['public.countries.m3_key'], name='fk_parts_country'),
|
||||
ForeignKeyConstraint(['currency_key'], ['public.currency_types.code'], name='fk_parts_currency'),
|
||||
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
|
||||
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_key: Mapped[int] = mapped_column(Integer)
|
||||
client_id: Mapped[int] = mapped_column(Integer)
|
||||
part_number: Mapped[str] = mapped_column(String(49))
|
||||
|
||||
# Basic information
|
||||
@@ -84,15 +84,15 @@ class Part(Base):
|
||||
currency: Mapped[Optional["CurrencyType"]] = relationship(foreign_keys=[currency_key])
|
||||
|
||||
# Relationship with Class through composite foreign key
|
||||
# Note: This requires both client_key and part_class to match client_key and class_code in Class
|
||||
# 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_key == Class.client_key, Part.part_class == Class.class_code)",
|
||||
foreign_keys="[Part.client_key, Part.part_class]",
|
||||
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"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Part(client_key={self.client_key}, part_number='{self.part_number}', description='{self.description_spanish}')>"
|
||||
return f"<Part(client_id={self.client_id}, part_number='{self.part_number}', description='{self.description_spanish}')>"
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ async def create_part(
|
||||
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"),
|
||||
client_key: Optional[int] = Query(None, description="Filter by client key"),
|
||||
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"),
|
||||
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"),
|
||||
@@ -51,7 +51,7 @@ async def list_parts(
|
||||
"""
|
||||
service = PartService(db)
|
||||
search_params = PartSearchDTO(
|
||||
client_key=client_key,
|
||||
client_id=client_id,
|
||||
part_number=part_number,
|
||||
description=description,
|
||||
fraction=fraction,
|
||||
@@ -61,9 +61,9 @@ async def list_parts(
|
||||
return service.list_parts(skip, limit, search_params)
|
||||
|
||||
|
||||
@router.get("/client/{client_key}", response_model=List[PartBasicDTO])
|
||||
@router.get("/client/{client_id}", response_model=List[PartBasicDTO])
|
||||
async def get_parts_by_client(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_core_db),
|
||||
@@ -73,7 +73,7 @@ async def get_parts_by_client(
|
||||
Get all parts for a specific client
|
||||
"""
|
||||
service = PartService(db)
|
||||
return service.search_by_client(client_key, skip, limit)
|
||||
return service.search_by_client(client_id, skip, limit)
|
||||
|
||||
|
||||
@router.get("/search/fraction/{fraction}", response_model=List[PartBasicDTO])
|
||||
@@ -127,29 +127,29 @@ async def get_parts_statistics(
|
||||
return service.get_parts_statistics()
|
||||
|
||||
|
||||
@router.get("/{client_key}/{part_number}", response_model=PartResponseDTO)
|
||||
@router.get("/{client_id}/{part_number}", response_model=PartResponseDTO)
|
||||
async def get_part(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get part by composite key (client_key + part_number)
|
||||
Get part by composite key (client_id + part_number)
|
||||
"""
|
||||
service = PartService(db)
|
||||
part = service.get_part(client_key, part_number)
|
||||
part = service.get_part(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
)
|
||||
return part
|
||||
|
||||
|
||||
@router.put("/{client_key}/{part_number}", response_model=PartResponseDTO)
|
||||
@router.put("/{client_id}/{part_number}", response_model=PartResponseDTO)
|
||||
async def update_part(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
part_data: PartUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
@@ -159,18 +159,18 @@ async def update_part(
|
||||
Update part information
|
||||
"""
|
||||
service = PartService(db)
|
||||
part = service.update_part(client_key, part_number, part_data)
|
||||
part = service.update_part(client_id, part_number, part_data)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
)
|
||||
return part
|
||||
|
||||
|
||||
@router.delete("/{client_key}/{part_number}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{client_id}/{part_number}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_part(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -181,16 +181,16 @@ async def delete_part(
|
||||
Note: This will completely remove the part from the system.
|
||||
"""
|
||||
service = PartService(db)
|
||||
if not service.delete_part(client_key, part_number):
|
||||
if not service.delete_part(client_id, part_number):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{client_key}/{part_number}/toggle-status", response_model=PartResponseDTO)
|
||||
@router.patch("/{client_id}/{part_number}/toggle-status", response_model=PartResponseDTO)
|
||||
async def toggle_part_status(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -199,19 +199,19 @@ async def toggle_part_status(
|
||||
Toggle part enabled/disabled status
|
||||
"""
|
||||
service = PartService(db)
|
||||
part = service.toggle_status(client_key, part_number)
|
||||
part = service.toggle_status(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
)
|
||||
return part
|
||||
|
||||
|
||||
# Endpoints específicos para información detallada
|
||||
@router.get("/{client_key}/{part_number}/basic", response_model=PartBasicDTO)
|
||||
@router.get("/{client_id}/{part_number}/basic", response_model=PartBasicDTO)
|
||||
async def get_part_basic_info(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -220,15 +220,15 @@ async def get_part_basic_info(
|
||||
Get basic information for a part
|
||||
"""
|
||||
service = PartService(db)
|
||||
part = service.get_part(client_key, part_number)
|
||||
part = service.get_part(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
)
|
||||
|
||||
return PartBasicDTO(
|
||||
client_key=part.client_key,
|
||||
client_id=part.client_id,
|
||||
part_number=part.part_number,
|
||||
description_spanish=part.description_spanish,
|
||||
description_english=part.description_english,
|
||||
@@ -239,9 +239,9 @@ async def get_part_basic_info(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{client_key}/{part_number}/regulatory", response_model=dict)
|
||||
@router.get("/{client_id}/{part_number}/regulatory", response_model=dict)
|
||||
async def get_part_regulatory_info(
|
||||
client_key: int,
|
||||
client_id: int,
|
||||
part_number: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -250,15 +250,15 @@ async def get_part_regulatory_info(
|
||||
Get regulatory information for a part (FDA, FCC, ECCN, etc.)
|
||||
"""
|
||||
service = PartService(db)
|
||||
part = service.get_part(client_key, part_number)
|
||||
part = service.get_part(client_id, part_number)
|
||||
if not part:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
|
||||
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
|
||||
)
|
||||
|
||||
return {
|
||||
"client_key": part.client_key,
|
||||
"client_id": part.client_id,
|
||||
"part_number": part.part_number,
|
||||
"fraction": part.fraction,
|
||||
"us_fraction": part.us_fraction,
|
||||
|
||||
@@ -34,21 +34,21 @@ 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_key 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}")
|
||||
raise HTTPException(status_code=500, detail="Error creating part")
|
||||
|
||||
@staticmethod
|
||||
def get_part(db: Session, client_key: int, part_number: str) -> Optional[Part]:
|
||||
def get_part(db: Session, client_id: int, part_number: str) -> Optional[Part]:
|
||||
"""
|
||||
Obtener una parte por clave de cliente y número de parte
|
||||
"""
|
||||
try:
|
||||
return db.query(Part).filter(
|
||||
and_(
|
||||
Part.client_key == client_key,
|
||||
Part.client_id == client_id,
|
||||
Part.part_number == part_number
|
||||
)
|
||||
).first()
|
||||
@@ -62,7 +62,7 @@ class PartService:
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search: Optional[str] = None,
|
||||
client_key: Optional[int] = None,
|
||||
client_id: Optional[int] = None,
|
||||
fraction: Optional[str] = None,
|
||||
country_of_origin: Optional[str] = None
|
||||
) -> tuple[List[Part], int]:
|
||||
@@ -80,8 +80,8 @@ class PartService:
|
||||
Part.part_number.ilike(f"%{search}%")
|
||||
))
|
||||
|
||||
if client_key is not None:
|
||||
query = query.filter(Part.client_key == client_key)
|
||||
if client_id is not None:
|
||||
query = query.filter(Part.client_id == client_id)
|
||||
|
||||
if fraction:
|
||||
query = query.filter(Part.fraction == fraction)
|
||||
@@ -101,12 +101,12 @@ class PartService:
|
||||
raise HTTPException(status_code=500, detail="Error retrieving parts")
|
||||
|
||||
@staticmethod
|
||||
def get_parts_by_client(db: Session, client_key: int) -> List[Part]:
|
||||
def get_parts_by_client(db: Session, client_id: int) -> List[Part]:
|
||||
"""
|
||||
Obtener todas las partes de un cliente específico
|
||||
"""
|
||||
try:
|
||||
return db.query(Part).filter(Part.client_key == client_key).all()
|
||||
return db.query(Part).filter(Part.client_id == client_id).all()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting parts by client: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error retrieving client parts")
|
||||
@@ -150,12 +150,12 @@ class PartService:
|
||||
raise HTTPException(status_code=500, detail="Error searching parts by country")
|
||||
|
||||
@staticmethod
|
||||
def update_part(db: Session, client_key: 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
|
||||
"""
|
||||
try:
|
||||
db_part = PartService.get_part(db, client_key, part_number)
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return None
|
||||
|
||||
@@ -172,12 +172,12 @@ class PartService:
|
||||
raise HTTPException(status_code=500, detail="Error updating part")
|
||||
|
||||
@staticmethod
|
||||
def delete_part(db: Session, client_key: int, part_number: str) -> bool:
|
||||
def delete_part(db: Session, client_id: int, part_number: str) -> bool:
|
||||
"""
|
||||
Eliminar una parte
|
||||
"""
|
||||
try:
|
||||
db_part = PartService.get_part(db, client_key, part_number)
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return False
|
||||
|
||||
@@ -190,12 +190,12 @@ class PartService:
|
||||
raise HTTPException(status_code=500, detail="Error deleting part")
|
||||
|
||||
@staticmethod
|
||||
def toggle_part_status(db: Session, client_key: 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
|
||||
"""
|
||||
try:
|
||||
db_part = PartService.get_part(db, client_key, part_number)
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return None
|
||||
|
||||
@@ -220,9 +220,9 @@ class PartService:
|
||||
|
||||
# Partes por cliente
|
||||
parts_by_client = db.query(
|
||||
Part.client_key,
|
||||
Part.client_id,
|
||||
func.count(Part.part_number).label('count')
|
||||
).group_by(Part.client_key).all()
|
||||
).group_by(Part.client_id).all()
|
||||
|
||||
# Partes por país de origen
|
||||
parts_by_country = db.query(
|
||||
@@ -239,7 +239,7 @@ class PartService:
|
||||
"total_parts": total_parts,
|
||||
"enabled_parts": enabled_parts,
|
||||
"disabled_parts": disabled_parts,
|
||||
"parts_by_client": [{"client_key": item[0], "count": item[1]} for item in parts_by_client],
|
||||
"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:
|
||||
@@ -247,17 +247,17 @@ class PartService:
|
||||
raise HTTPException(status_code=500, detail="Error retrieving parts statistics")
|
||||
|
||||
@staticmethod
|
||||
def get_part_regulatory_info(db: Session, client_key: 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
|
||||
"""
|
||||
try:
|
||||
db_part = PartService.get_part(db, client_key, part_number)
|
||||
db_part = PartService.get_part(db, client_id, part_number)
|
||||
if not db_part:
|
||||
return None
|
||||
|
||||
return {
|
||||
"client_key": db_part.client_key,
|
||||
"client_id": db_part.client_id,
|
||||
"part_number": db_part.part_number,
|
||||
"fraction": db_part.fraction,
|
||||
"us_fraction": db_part.us_fraction,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
from core.database import Base
|
||||
|
||||
@@ -12,22 +11,25 @@ if TYPE_CHECKING:
|
||||
class PedimentoConfigAdditional(Base):
|
||||
__tablename__ = 'pedimento_config_additional'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_additional'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_config_additional_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
add_po_identifier = mapped_column(SmallInteger)
|
||||
do_not_exempt_norms_complement_x = mapped_column(SmallInteger)
|
||||
manual_pedimento_year = mapped_column(String(2))
|
||||
enable_import_invoice_recipient = mapped_column(SmallInteger)
|
||||
send_502_validation_file_for_consolidated = mapped_column(SmallInteger)
|
||||
add_remove_norms = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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.orm.base import Mapped
|
||||
from datetime import datetime
|
||||
from core.database import Base
|
||||
|
||||
@@ -11,26 +10,29 @@ if TYPE_CHECKING:
|
||||
class PedimentoConfigCalculations(Base):
|
||||
__tablename__ = 'pedimento_config_calculations'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_calculations'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_config_calculations_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
dta_type = mapped_column(String(1))
|
||||
dta_operation = mapped_column(SmallInteger)
|
||||
dta_vehicle_count = mapped_column(SmallInteger)
|
||||
dta_mixed_rate_8permil = mapped_column(SmallInteger)
|
||||
pays_vat = mapped_column(SmallInteger)
|
||||
pays_prevalidation = mapped_column(SmallInteger)
|
||||
include_sagar_certificate_fee = mapped_column(SmallInteger)
|
||||
fixed_vehicle_dta_fee = mapped_column(SmallInteger)
|
||||
additional_fixed_fee = mapped_column(SmallInteger)
|
||||
additional_fixed_fee_payment_method = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
dta_type: Mapped[str] = mapped_column(String(1))
|
||||
dta_operation: Mapped[int] = mapped_column(SmallInteger)
|
||||
dta_vehicle_count: Mapped[int] = mapped_column(SmallInteger)
|
||||
dta_mixed_rate_8permil: Mapped[int] = mapped_column(SmallInteger)
|
||||
pays_vat: Mapped[int] = mapped_column(SmallInteger)
|
||||
pays_prevalidation: Mapped[int] = mapped_column(SmallInteger)
|
||||
include_sagar_certificate_fee: Mapped[int] = mapped_column(SmallInteger)
|
||||
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())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
@@ -12,27 +13,30 @@ if TYPE_CHECKING:
|
||||
class PedimentoConfigParameters(Base):
|
||||
__tablename__ = 'pedimento_config_parameters'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_parameters'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_config_parameters_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
is_embassy = mapped_column(SmallInteger)
|
||||
embassy_dta = mapped_column(Numeric(11, 2))
|
||||
rule_3121_section_ii = mapped_column(SmallInteger)
|
||||
use_previous_tariff = mapped_column(SmallInteger)
|
||||
use_payment_date_fi = mapped_column(SmallInteger)
|
||||
add_state_supplier_record_505 = mapped_column(SmallInteger)
|
||||
customs_value_calculation = mapped_column(SmallInteger)
|
||||
two_decimals_unit_value = mapped_column(SmallInteger)
|
||||
customs_value_per_item = mapped_column(SmallInteger)
|
||||
is_national_supplier = mapped_column(SmallInteger)
|
||||
is_consolidated = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
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)
|
||||
use_previous_tariff: Mapped[int] = mapped_column(SmallInteger)
|
||||
use_payment_date_fi: Mapped[int] = mapped_column(SmallInteger)
|
||||
add_state_supplier_record_505: Mapped[int] = mapped_column(SmallInteger)
|
||||
customs_value_calculation: Mapped[int] = mapped_column(SmallInteger)
|
||||
two_decimals_unit_value: Mapped[int] = mapped_column(SmallInteger)
|
||||
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())
|
||||
|
||||
@@ -11,22 +11,25 @@ if TYPE_CHECKING:
|
||||
class PedimentoConfigSurcharges(Base):
|
||||
__tablename__ = 'pedimento_config_surcharges'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_surcharges'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
surcharge_igi = mapped_column(SmallInteger)
|
||||
surcharge_dta = mapped_column(SmallInteger)
|
||||
surcharge_vat = mapped_column(SmallInteger)
|
||||
surcharge_isan = mapped_column(SmallInteger)
|
||||
surcharge_ieps = mapped_column(SmallInteger)
|
||||
surcharge_cc = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
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())
|
||||
|
||||
@@ -11,21 +11,24 @@ if TYPE_CHECKING:
|
||||
class PedimentoConfigUpdateRectification(Base):
|
||||
__tablename__ = 'pedimento_config_update_rectification'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_update_rectification'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
update_vat = mapped_column(SmallInteger)
|
||||
update_advalorem = mapped_column(SmallInteger)
|
||||
update_cc = mapped_column(SmallInteger)
|
||||
update_ieps = mapped_column(SmallInteger)
|
||||
calculate_surcharge = mapped_column(SmallInteger)
|
||||
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())
|
||||
|
||||
@@ -11,20 +11,23 @@ if TYPE_CHECKING:
|
||||
class PedimentoConfigUpdates(Base):
|
||||
__tablename__ = 'pedimento_config_updates'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_updates'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_config_updates_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
update_vat = mapped_column(SmallInteger)
|
||||
update_advalorem = mapped_column(SmallInteger)
|
||||
update_cc = mapped_column(SmallInteger)
|
||||
update_ieps = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
|
||||
@@ -11,18 +11,21 @@ if TYPE_CHECKING:
|
||||
class PedimentoCustomsOffices(Base):
|
||||
__tablename__ = 'pedimento_customs_offices'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_customs_offices'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_customs_offices_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
dispatch_customs = mapped_column(String(3))
|
||||
entry_exit_customs = mapped_column(String(3))
|
||||
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)
|
||||
|
||||
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())
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
|
||||
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
|
||||
from datetime import datetime, time as datetime_time
|
||||
from core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -11,29 +11,32 @@ if TYPE_CHECKING:
|
||||
class PedimentoDates(Base):
|
||||
__tablename__ = 'pedimento_dates'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_dates'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_dates_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_dates_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
entry_date = mapped_column(DateTime)
|
||||
pedimento_date = mapped_column(DateTime)
|
||||
payment_date = mapped_column(DateTime)
|
||||
rectification_payment_date = mapped_column(DateTime)
|
||||
extraction_date = mapped_column(DateTime)
|
||||
submission_date = mapped_column(DateTime)
|
||||
eucan_date = mapped_column(DateTime)
|
||||
original_date = mapped_column(DateTime)
|
||||
start_date = mapped_column(DateTime)
|
||||
end_date = mapped_column(DateTime)
|
||||
capture_date = mapped_column(DateTime)
|
||||
capture_time = mapped_column(Time)
|
||||
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)
|
||||
|
||||
entry_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
pedimento_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
rectification_payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
extraction_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
submission_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
eucan_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
original_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
start_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
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())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
@@ -11,25 +12,28 @@ if TYPE_CHECKING:
|
||||
class PedimentoDecrementables(Base):
|
||||
__tablename__ = 'pedimento_decrementables'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_decrementables'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_decrementables_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
freight = mapped_column(Numeric(13, 2))
|
||||
insurance = mapped_column(Numeric(13, 2))
|
||||
loading = mapped_column(Numeric(13, 2))
|
||||
unloading = mapped_column(Numeric(13, 2))
|
||||
others = mapped_column(Numeric(13, 2))
|
||||
currency = mapped_column(String(3))
|
||||
currency_factor = mapped_column(Numeric(15, 8))
|
||||
not_affect_usd_value = mapped_column(SmallInteger)
|
||||
not_affect_customs_value = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
freight: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
loading: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
unloading: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
others: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
currency: Mapped[str] = mapped_column(String(3))
|
||||
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())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
@@ -11,26 +12,29 @@ if TYPE_CHECKING:
|
||||
class PedimentoIncrementables(Base):
|
||||
__tablename__ = 'pedimento_incrementables'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_incrementables'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_incrementables_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
insured_value = mapped_column(Numeric(13, 2))
|
||||
freight = mapped_column(Numeric(13, 2))
|
||||
insurance = mapped_column(Numeric(13, 2))
|
||||
packaging = mapped_column(Numeric(13, 2))
|
||||
others = mapped_column(Numeric(13, 3))
|
||||
deductibles = mapped_column(Numeric(13, 3))
|
||||
currency = mapped_column(String(3))
|
||||
currency_factor = mapped_column(Numeric(15, 8))
|
||||
not_affect_usd_value = mapped_column(SmallInteger)
|
||||
not_affect_customs_value = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
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))
|
||||
packaging: Mapped[Decimal] = mapped_column(Numeric(13, 2))
|
||||
others: Mapped[Decimal] = mapped_column(Numeric(13, 3))
|
||||
deductibles: Mapped[Decimal] = mapped_column(Numeric(13, 3))
|
||||
currency: Mapped[str] = mapped_column(String(3))
|
||||
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())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
@@ -11,19 +12,22 @@ if TYPE_CHECKING:
|
||||
class PedimentoIndexes(Base):
|
||||
__tablename__ = 'pedimento_indexes'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_indexes'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_indexes_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
update_factor_type = mapped_column(SmallInteger)
|
||||
update_factor = mapped_column(Numeric(7, 4))
|
||||
manual_update_factor = mapped_column(SmallInteger)
|
||||
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_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())
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
|
||||
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
|
||||
from datetime import datetime, time as Time2, date as Date2
|
||||
from core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -11,29 +11,32 @@ if TYPE_CHECKING:
|
||||
class PedimentoPayments(Base):
|
||||
__tablename__ = 'pedimento_payments'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_payments'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_payments_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_payments_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
acknowledgment = mapped_column(String(20))
|
||||
operation_number = mapped_column(String(14))
|
||||
bank_code = mapped_column(Integer)
|
||||
cashier = mapped_column(String(2))
|
||||
date = mapped_column(Date)
|
||||
time = mapped_column(Time)
|
||||
shift = mapped_column(String(1))
|
||||
total_cash_paid = mapped_column(Integer)
|
||||
total_contributions = mapped_column(Integer)
|
||||
counter_payment = mapped_column(SmallInteger)
|
||||
pece_code = mapped_column(String(5))
|
||||
payment_id = mapped_column(Integer)
|
||||
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)
|
||||
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)
|
||||
cashier: Mapped[str] = mapped_column(String(2))
|
||||
date: Mapped[Date2] = mapped_column(Date)
|
||||
time: Mapped[Time2] = mapped_column(Time)
|
||||
shift: Mapped[str] = mapped_column(String(1))
|
||||
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))
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint
|
||||
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
|
||||
@@ -10,19 +11,27 @@ if TYPE_CHECKING:
|
||||
class PedimentoRectificationDestination(Base):
|
||||
__tablename__ = 'pedimento_rectification_destination'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_destination'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
destination_pedimento_year = mapped_column(String(2))
|
||||
destination_customs_office = mapped_column(String(3))
|
||||
destination_license = mapped_column(String(4))
|
||||
destination_pedimento_number = mapped_column(String(7))
|
||||
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_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())
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_destination')
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint
|
||||
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
|
||||
@@ -10,28 +11,36 @@ if TYPE_CHECKING:
|
||||
class PedimentoRectificationOrigin(Base):
|
||||
__tablename__ = 'pedimento_rectification_origin'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_origin'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
original_pedimento_year = mapped_column(String(2))
|
||||
original_customs_office = mapped_column(String(3))
|
||||
original_license = mapped_column(String(4))
|
||||
original_pedimento_number = mapped_column(String(7))
|
||||
original_pedimento_code = mapped_column(String(2))
|
||||
original_payment_date = mapped_column(DateTime)
|
||||
total_cash = mapped_column(Integer)
|
||||
total_others = mapped_column(Integer)
|
||||
reason = mapped_column(String(255))
|
||||
charge_to_client = mapped_column(SmallInteger)
|
||||
use_original_payment_date_for_interest_calc = mapped_column(SmallInteger)
|
||||
manual_calculation = mapped_column(SmallInteger)
|
||||
original_pedimento_norms = mapped_column(SmallInteger)
|
||||
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)
|
||||
|
||||
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))
|
||||
original_pedimento_number: Mapped[str] = mapped_column(String(7))
|
||||
original_pedimento_code: Mapped[str] = mapped_column(String(2))
|
||||
original_payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
total_cash: Mapped[int] = mapped_column(Integer)
|
||||
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)
|
||||
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())
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_origin')
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
@@ -10,20 +11,23 @@ if TYPE_CHECKING:
|
||||
class PedimentoTransportMeans(Base):
|
||||
__tablename__ = 'pedimento_transport_means'
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_transport_means'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_transport_means_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
destination = mapped_column(SmallInteger)
|
||||
entry_exit = mapped_column(String(2))
|
||||
arrival = mapped_column(String(2))
|
||||
departure = mapped_column(String(2))
|
||||
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
|
||||
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'))
|
||||
|
||||
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_transport_means')
|
||||
@@ -11,24 +11,28 @@ if TYPE_CHECKING:
|
||||
class PedimentoValidation(Base):
|
||||
__tablename__ = 'pedimento_validation' #PedimentoValidacion
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
|
||||
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_validation'),
|
||||
PrimaryKeyConstraint('id', name='pedimento_validation_pkey'),
|
||||
UniqueConstraint('pedimento_id', name='pedimento_validation_pedimento_id_key'),
|
||||
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_column(Integer)
|
||||
pedimento_id = mapped_column(Integer, nullable=False)
|
||||
tenant_id = mapped_column(Integer, nullable=False, index=True)
|
||||
validator = mapped_column(String(3)) #validador
|
||||
validation_ack = mapped_column(String(8)) #acuse_validacion
|
||||
pre_ack = mapped_column(String(8)) #acuse_previo
|
||||
line_signature = mapped_column(String(50)) #firma_linea_captura
|
||||
electronic_signature = mapped_column(String(999)) #firma_electronica
|
||||
certificate_number = mapped_column(String(99)) #numero_certificado
|
||||
validator_id = mapped_column(Integer)
|
||||
responsible_id = mapped_column(Integer)
|
||||
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
|
||||
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())
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, Numeric, PrimaryKeyConstraint, String, 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
|
||||
@@ -28,11 +28,13 @@ if TYPE_CHECKING:
|
||||
class Pedimentos(Base):
|
||||
__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'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimentos_tenant'),
|
||||
PrimaryKeyConstraint('id', name='pedimentos_pkey'),
|
||||
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'),
|
||||
@@ -41,6 +43,8 @@ class Pedimentos(Base):
|
||||
|
||||
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))
|
||||
|
||||
@@ -8,14 +8,18 @@ class PermissionRuleOct(Base):
|
||||
__tablename__ = "permission_rule_oct"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='permission_rule_oct_pkey'),
|
||||
UniqueConstraint('permission', 'tenant_id', name='permission_rule_oct_permission_tenant_ukey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_permission_rule_oct_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_permission_rule_oct_company'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'permission', name='permission_rule_oct_permission_tenant_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)
|
||||
|
||||
permission: Mapped[str] = mapped_column(String(20))
|
||||
start_date: Mapped[Optional[int]] = mapped_column()
|
||||
end_date: Mapped[Optional[int]] = mapped_column()
|
||||
sector: Mapped[Optional[str]] = mapped_column(String(8))
|
||||
system: Mapped[Optional[str]] = mapped_column(String(5))
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
@@ -7,11 +7,15 @@ class Seal(Base):
|
||||
__tablename__ = "seal"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint('id', name='seal_pkey'),
|
||||
UniqueConstraint('seal', name='seal_ukey'),
|
||||
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_seal_tenant'),
|
||||
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_seal_company'),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'seal', name='seal_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)
|
||||
|
||||
seal: Mapped[str] = mapped_column(String(15))
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from sqlalchemy import String, Integer, ForeignKey, ForeignKeyConstraint, PrimaryKeyConstraint
|
||||
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||
from core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..pedimento_codes.models import PedimentoCode
|
||||
from ..pedimento_regimens.models import RegimenPedimento
|
||||
|
||||
class CodePedimentoRegimen(Base):
|
||||
__tablename__ = "code_pedimento_regimens" #GClavePedRegimen
|
||||
__table_args__ = (
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from typing import List
|
||||
from typing import TYPE_CHECKING, List
|
||||
from sqlalchemy import String, PrimaryKeyConstraint
|
||||
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||
from core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..code_pedimento_regimens.models import CodePedimentoRegimen
|
||||
|
||||
|
||||
class PedimentoCode(Base):
|
||||
__tablename__ = "pedimento_codes" # GClavePed
|
||||
__table_args__ = (
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from typing import List
|
||||
from typing import TYPE_CHECKING, List
|
||||
from sqlalchemy import String, PrimaryKeyConstraint, ForeignKey
|
||||
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||
from core.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..code_pedimento_regimens.models import CodePedimentoRegimen
|
||||
|
||||
class RegimenPedimento(Base):
|
||||
__tablename__ = "pedimento_regimens" #GRegimenPed
|
||||
__table_args__ = (
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
## 🔗 Relaciones de Base de Datos
|
||||
|
||||
### Relaciones Principales
|
||||
- **Part ↔ Class**: Relación de clave compuesta (client_key, part_class ↔ class_code)
|
||||
- **Part ↔ Class**: Relación de clave compuesta (client_id, part_class ↔ class_code)
|
||||
- **Part → Country**: Clave foránea a public.countries (country_of_origin)
|
||||
- **Part → CurrencyType**: Clave foránea a public.currency_types (currency_key)
|
||||
- **Class → MaterialType**: Clave foránea a public.material_types (material_key)
|
||||
@@ -75,34 +75,34 @@ Part (Partes)
|
||||
|--------|----------|-------------|
|
||||
| POST | `/` | Crear parte |
|
||||
| GET | `/` | Listar todas con paginación y filtros |
|
||||
| GET | `/client/{client_key}` | Obtener partes por cliente |
|
||||
| GET | `/client/{client_id}` | Obtener partes por cliente |
|
||||
| GET | `/search/fraction/{fraction}` | Buscar por fracción arancelaria |
|
||||
| GET | `/search/supplier/{supplier}` | Buscar por proveedor |
|
||||
| GET | `/search/country/{country_code}` | Buscar por país |
|
||||
| GET | `/statistics` | Obtener estadísticas de partes |
|
||||
| GET | `/{client_key}/{part_number}` | Obtener parte específica |
|
||||
| PUT | `/{client_key}/{part_number}` | Actualizar parte |
|
||||
| DELETE | `/{client_key}/{part_number}` | Eliminar parte |
|
||||
| PATCH | `/{client_key}/{part_number}/toggle-status` | Cambiar estatus |
|
||||
| GET | `/{client_key}/{part_number}/basic` | Obtener información básica |
|
||||
| GET | `/{client_key}/{part_number}/regulatory` | Obtener información regulatoria |
|
||||
| GET | `/{client_id}/{part_number}` | Obtener parte específica |
|
||||
| PUT | `/{client_id}/{part_number}` | Actualizar parte |
|
||||
| DELETE | `/{client_id}/{part_number}` | Eliminar parte |
|
||||
| PATCH | `/{client_id}/{part_number}/toggle-status` | Cambiar estatus |
|
||||
| GET | `/{client_id}/{part_number}/basic` | Obtener información básica |
|
||||
| GET | `/{client_id}/{part_number}/regulatory` | Obtener información regulatoria |
|
||||
|
||||
### Módulo Clases (`/classes`)
|
||||
| Método | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/` | Crear clase |
|
||||
| GET | `/` | Listar todas con paginación y filtros |
|
||||
| GET | `/client/{client_key}` | Obtener clases por cliente |
|
||||
| GET | `/client/{client_id}` | Obtener clases por cliente |
|
||||
| GET | `/search/fraction/{fraction}` | Buscar por fracción arancelaria |
|
||||
| GET | `/search/material/{material_key}` | Buscar por material |
|
||||
| GET | `/search/unit-measure/{unit_of_measure}` | Buscar por unidad de medida |
|
||||
| GET | `/search/physical-review/{physical_review}` | Buscar por revisión física |
|
||||
| GET | `/statistics` | Obtener estadísticas de clases |
|
||||
| GET | `/{client_key}/{class_code}` | Obtener clase específica |
|
||||
| PUT | `/{client_key}/{class_code}` | Actualizar clase |
|
||||
| DELETE | `/{client_key}/{class_code}` | Eliminar clase |
|
||||
| GET | `/{client_key}/{class_code}/basic` | Obtener información básica |
|
||||
| GET | `/{client_key}/{class_code}/tariff` | Obtener información arancelaria |
|
||||
| GET | `/{client_id}/{class_code}` | Obtener clase específica |
|
||||
| PUT | `/{client_id}/{class_code}` | Actualizar clase |
|
||||
| DELETE | `/{client_id}/{class_code}` | Eliminar clase |
|
||||
| GET | `/{client_id}/{class_code}/basic` | Obtener información básica |
|
||||
| GET | `/{client_id}/{class_code}/tariff` | Obtener información arancelaria |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ El modelo `Part` representa las partes/componentes en los sistemas SCAII, SCAF y
|
||||
- Propósito: Tipo de moneda para el costo unitario
|
||||
|
||||
3. **Con Class (classes)**
|
||||
- Campos: `(client_key, part_class)` → `(client_key, class_code)`
|
||||
- Campos: `(client_id, part_class)` → `(client_id, class_code)`
|
||||
- Relación: Many-to-One (usando primaryjoin complejo)
|
||||
- Propósito: Clasificación de la parte
|
||||
- Atributo: `part_class_info`
|
||||
@@ -34,7 +34,7 @@ El modelo `Class` representa las clases de clasificación en sistemas SCAII y SC
|
||||
- Propósito: Tipo de material de la clase
|
||||
|
||||
2. **Con Part (parts)**
|
||||
- Campos: `(client_key, class_code)` → `(client_key, part_class)`
|
||||
- Campos: `(client_id, class_code)` → `(client_id, part_class)`
|
||||
- Relación: One-to-Many (inversa de la relación en Part)
|
||||
- Propósito: Partes que pertenecen a esta clase
|
||||
- Atributo: `parts`
|
||||
@@ -63,7 +63,7 @@ part = session.query(Part).options(
|
||||
joinedload(Part.currency),
|
||||
joinedload(Part.part_class_info).joinedload(Class.material_type)
|
||||
).filter(
|
||||
Part.client_key == 1,
|
||||
Part.client_id == 1,
|
||||
Part.part_number == "PART001"
|
||||
).first()
|
||||
|
||||
@@ -78,7 +78,7 @@ print(f"Material: {part.part_class_info.material_type.description}")
|
||||
Los DTOs pueden incluir información relacionada:
|
||||
```python
|
||||
class PartDetailResponseDTO(BaseModel):
|
||||
client_key: int
|
||||
client_id: int
|
||||
part_number: str
|
||||
description_spanish: Optional[str]
|
||||
country_name: Optional[str] = None
|
||||
|
||||
613
scripts/init_first_time.sh
Executable file
613
scripts/init_first_time.sh
Executable file
@@ -0,0 +1,613 @@
|
||||
#!/bin/bash
|
||||
|
||||
###############################################################################
|
||||
# Script de inicialización completa para Anexo76
|
||||
#
|
||||
# Este script configura automáticamente:
|
||||
# 1. User Profile Attributes en Keycloak (tenant_id)
|
||||
# 2. Clientes de Keycloak (Backend y Frontend)
|
||||
# 3. Mappers de atributos (tenant_id)
|
||||
# 4. Usuario demo en Keycloak (usuario=demo, password=demo123)
|
||||
# 5. Tenant y Company en PostgreSQL
|
||||
# 6. Relación usuario-tenant en tabla user_tenants
|
||||
# 7. Actualización del tenant_id del usuario con el valor real
|
||||
#
|
||||
# Requisitos:
|
||||
# - Keycloak corriendo en http://localhost:8080
|
||||
# - PostgreSQL corriendo en localhost:5432
|
||||
# - Base de datos anexo76_core creada
|
||||
# - jq instalado (para procesamiento JSON)
|
||||
#
|
||||
# Nota: El atributo tenant_id se crea con valor inicial "1" y luego
|
||||
# se actualiza con el ID real del tenant creado en PostgreSQL.
|
||||
###############################################################################
|
||||
|
||||
set -e # Salir si hay algún error
|
||||
|
||||
# Colores para output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Variables de configuración
|
||||
KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:8080}"
|
||||
KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN:-admin}"
|
||||
KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD:-admin}"
|
||||
KEYCLOAK_REALM="${KEYCLOAK_REALM:-master}"
|
||||
KEYCLOACK_ADMIN_URL="${KEYCLOACK_ADMIN_URL:-http://localhost:9000}"
|
||||
|
||||
POSTGRES_HOST="${POSTGRES_HOST:-localhost}"
|
||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
||||
POSTGRES_DB="${POSTGRES_DB:-anexo76_core}"
|
||||
POSTGRES_USER="${POSTGRES_USER:-postgres}"
|
||||
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-postgres}"
|
||||
|
||||
DEMO_USERNAME="demo"
|
||||
DEMO_PASSWORD="demo123"
|
||||
DEMO_EMAIL="demo@aduanasoft.com"
|
||||
DEMO_FIRSTNAME="Demo"
|
||||
DEMO_LASTNAME="User"
|
||||
|
||||
TENANT_NAME="Aduanasoft"
|
||||
TENANT_SLUG="aduanasoft"
|
||||
COMPANY_NAME="Aduanasoft S.A. de C.V."
|
||||
COMPANY_RFC="ADS010101AAA"
|
||||
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} Inicialización del Sistema Anexo76${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
###############################################################################
|
||||
# 1. Esperar a que Keycloak esté listo
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[1/8] Esperando a que Keycloak esté disponible...${NC}"
|
||||
MAX_RETRIES=30
|
||||
RETRY_COUNT=0
|
||||
|
||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
||||
if curl -s -f "${KEYCLOACK_ADMIN_URL}/health/ready" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ Keycloak está listo${NC}"
|
||||
break
|
||||
fi
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
echo "Intento $RETRY_COUNT/$MAX_RETRIES..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
|
||||
echo -e "${RED}✗ Error: Keycloak no está disponible después de $MAX_RETRIES intentos${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# 2. Obtener token de administrador de Keycloak
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[2/8] Obteniendo token de administrador de Keycloak...${NC}"
|
||||
|
||||
TOKEN_RESPONSE=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=${KEYCLOAK_ADMIN}" \
|
||||
-d "password=${KEYCLOAK_ADMIN_PASSWORD}" \
|
||||
-d "grant_type=password" \
|
||||
-d "client_id=admin-cli")
|
||||
|
||||
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*' | sed 's/"access_token":"//')
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
echo -e "${RED}✗ Error: No se pudo obtener el token de acceso${NC}"
|
||||
echo "Respuesta: $TOKEN_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Token obtenido exitosamente${NC}"
|
||||
|
||||
###############################################################################
|
||||
# 3. Configurar User Profile Attributes con valores por defecto
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[3/8] Configurando User Profile Attributes...${NC}"
|
||||
|
||||
# Obtener configuración actual del User Profile
|
||||
USER_PROFILE=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/profile" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
# Verificar si tenant_id existe
|
||||
TENANT_ID_EXISTS=$(echo "$USER_PROFILE" | grep -q "\"name\":\"tenant_id\"" && echo "true" || echo "false")
|
||||
|
||||
if [ "$TENANT_ID_EXISTS" = "false" ]; then
|
||||
echo "Agregando atributo tenant_id al User Profile..."
|
||||
|
||||
# Crear el atributo tenant_id con valor por defecto
|
||||
UPDATED_PROFILE=$(echo "$USER_PROFILE" | jq '.attributes += [{
|
||||
"name": "tenant_id",
|
||||
"displayName": "Tenant ID",
|
||||
"validations": {},
|
||||
"annotations": {
|
||||
"inputType": "text"
|
||||
},
|
||||
"required": {
|
||||
"roles": [],
|
||||
"scopes": []
|
||||
},
|
||||
"permissions": {
|
||||
"view": ["admin", "user"],
|
||||
"edit": ["admin"]
|
||||
},
|
||||
"multivalued": false,
|
||||
"group": null
|
||||
}]')
|
||||
|
||||
# Actualizar el User Profile
|
||||
curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/profile" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$UPDATED_PROFILE"
|
||||
|
||||
echo -e "${GREEN}✓ Atributo tenant_id agregado al User Profile${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Atributo tenant_id ya existe en User Profile${NC}"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# 4. Configurar clientes de Keycloak (Backend y Frontend)
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[4/8] Configurando clientes de Keycloak...${NC}"
|
||||
|
||||
# 3.1 Crear cliente Backend
|
||||
echo "Configurando cliente Backend..."
|
||||
BACKEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-backend" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
if echo "$BACKEND_CLIENT_EXISTS" | grep -q "\"clientId\":\"anexo76-backend\""; then
|
||||
echo -e "${YELLOW}⚠ Cliente Backend ya existe${NC}"
|
||||
BACKEND_CLIENT_ID=$(echo "$BACKEND_CLIENT_EXISTS" | grep -o '"id":"[^"]*' | head -1 | sed 's/"id":"//')
|
||||
else
|
||||
CREATE_BACKEND=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"clientId": "anexo76-backend",
|
||||
"name": "Anexo76 Backend API",
|
||||
"description": "Backend API para el sistema Anexo76",
|
||||
"enabled": true,
|
||||
"protocol": "openid-connect",
|
||||
"publicClient": false,
|
||||
"serviceAccountsEnabled": true,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"standardFlowEnabled": true,
|
||||
"implicitFlowEnabled": false,
|
||||
"rootUrl": "http://localhost:8000/api",
|
||||
"baseUrl": "http://localhost:8000/api",
|
||||
"redirectUris": [
|
||||
"http://localhost:8000/*",
|
||||
"http://localhost:5180/*"
|
||||
],
|
||||
"webOrigins": ["http://localhost:8000/api"],
|
||||
"attributes": {
|
||||
"access.token.lifespan": "3600",
|
||||
"client.secret.creation.time": "0"
|
||||
}
|
||||
}')
|
||||
|
||||
HTTP_CODE=$(echo "$CREATE_BACKEND" | tail -n1)
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo -e "${GREEN}✓ Cliente Backend creado${NC}"
|
||||
# Obtener ID del cliente recién creado
|
||||
BACKEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-backend" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
BACKEND_CLIENT_ID=$(echo "$BACKEND_CLIENT_EXISTS" | grep -o '"id":"[^"]*' | head -1 | sed 's/"id":"//')
|
||||
else
|
||||
echo -e "${RED}✗ Error al crear cliente Backend (HTTP ${HTTP_CODE})${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Obtener y mostrar el secret del cliente Backend
|
||||
if [ -n "$BACKEND_CLIENT_ID" ]; then
|
||||
BACKEND_SECRET=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/client-secret" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o '"value":"[^"]*' | sed 's/"value":"//')
|
||||
|
||||
echo -e "${GREEN}✓ Backend Client ID: anexo76-backend${NC}"
|
||||
echo -e "${GREEN}✓ Backend Client Secret: ${BACKEND_SECRET}${NC}"
|
||||
|
||||
# Actualizar archivo .env con el client secret
|
||||
if [ -n "$BACKEND_SECRET" ]; then
|
||||
ENV_FILE=""
|
||||
# Buscar archivo .env en el directorio actual o en el directorio padre
|
||||
if [ -f ".env" ]; then
|
||||
ENV_FILE=".env"
|
||||
elif [ -f "../.env" ]; then
|
||||
ENV_FILE="../.env"
|
||||
fi
|
||||
|
||||
if [ -n "$ENV_FILE" ]; then
|
||||
# Verificar si la variable ya existe en el archivo
|
||||
if grep -q "^KEYCLOAK_CLIENT_SECRET=" "$ENV_FILE"; then
|
||||
# Actualizar el valor existente
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
sed -i '' "s|^KEYCLOAK_CLIENT_SECRET=.*|KEYCLOAK_CLIENT_SECRET=${BACKEND_SECRET}|" "$ENV_FILE"
|
||||
else
|
||||
# Linux
|
||||
sed -i "s|^KEYCLOAK_CLIENT_SECRET=.*|KEYCLOAK_CLIENT_SECRET=${BACKEND_SECRET}|" "$ENV_FILE"
|
||||
fi
|
||||
echo -e "${GREEN}✓ KEYCLOAK_CLIENT_SECRET actualizado en ${ENV_FILE}${NC}"
|
||||
else
|
||||
# Agregar la variable si no existe
|
||||
echo "KEYCLOAK_CLIENT_SECRET=${BACKEND_SECRET}" >> "$ENV_FILE"
|
||||
echo -e "${GREEN}✓ KEYCLOAK_CLIENT_SECRET agregado a ${ENV_FILE}${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ No se encontró archivo .env, el secret debe agregarse manualmente${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3.2 Crear cliente Frontend
|
||||
echo "Configurando cliente Frontend..."
|
||||
FRONTEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-frontend" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
if echo "$FRONTEND_CLIENT_EXISTS" | grep -q "\"clientId\":\"anexo76-frontend\""; then
|
||||
echo -e "${YELLOW}⚠ Cliente Frontend ya existe${NC}"
|
||||
FRONTEND_CLIENT_ID=$(echo "$FRONTEND_CLIENT_EXISTS" | grep -o '"id":"[^"]*' | head -1 | sed 's/"id":"//')
|
||||
else
|
||||
CREATE_FRONTEND=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"clientId": "anexo76-frontend",
|
||||
"name": "Anexo76 Frontend",
|
||||
"description": "Aplicación web frontend para el sistema Anexo76",
|
||||
"enabled": true,
|
||||
"protocol": "openid-connect",
|
||||
"publicClient": true,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"standardFlowEnabled": true,
|
||||
"implicitFlowEnabled": false,
|
||||
"rootUrl": "http://localhost:5173",
|
||||
"baseUrl": "http://localhost:5173",
|
||||
"redirectUris": [
|
||||
"http://localhost:5173/*",
|
||||
"http://localhost:3000/*",
|
||||
"http://localhost:5180/*"
|
||||
],
|
||||
"postLogoutRedirectUris": [
|
||||
"http://localhost:5173/*",
|
||||
"http://localhost:3000/*",
|
||||
"http://localhost:5180/*"
|
||||
],
|
||||
"webOrigins": [
|
||||
"http://localhost:5173",
|
||||
"http://localhost:3000",
|
||||
"http://localhost:5180"
|
||||
],
|
||||
"attributes": {
|
||||
"pkce.code.challenge.method": "S256"
|
||||
}
|
||||
}')
|
||||
|
||||
HTTP_CODE=$(echo "$CREATE_FRONTEND" | tail -n1)
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo -e "${GREEN}✓ Cliente Frontend creado${NC}"
|
||||
# Obtener ID del cliente recién creado
|
||||
FRONTEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-frontend" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
FRONTEND_CLIENT_ID=$(echo "$FRONTEND_CLIENT_EXISTS" | grep -o '"id":"[^"]*' | head -1 | sed 's/"id":"//')
|
||||
else
|
||||
echo -e "${RED}✗ Error al crear cliente Frontend (HTTP ${HTTP_CODE})${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# 5. Configurar Mappers para tenant_id
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[5/8] Configurando mappers para tenant_id...${NC}"
|
||||
|
||||
# 4.1 Configurar mapper para Backend
|
||||
if [ -n "$BACKEND_CLIENT_ID" ]; then
|
||||
echo "Configurando mapper para Backend..."
|
||||
|
||||
# Obtener el dedicated scope del cliente backend
|
||||
BACKEND_SCOPES=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/optional-client-scopes" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
# Buscar el scope dedicado
|
||||
BACKEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-backend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
|
||||
|
||||
if [ -n "$BACKEND_DEDICATED_SCOPE_ID" ]; then
|
||||
# Verificar si el mapper tenant_id ya existe
|
||||
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
|
||||
|
||||
if [ -z "$MAPPER_TENANT_EXISTS" ]; then
|
||||
# Crear mapper para tenant_id
|
||||
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}'
|
||||
echo -e "${GREEN}✓ Mapper tenant_id creado para Backend${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Backend${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4.2 Configurar mapper para Frontend
|
||||
if [ -n "$FRONTEND_CLIENT_ID" ]; then
|
||||
echo "Configurando mapper para Frontend..."
|
||||
|
||||
# Buscar el scope dedicado del frontend
|
||||
FRONTEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-frontend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
|
||||
|
||||
if [ -n "$FRONTEND_DEDICATED_SCOPE_ID" ]; then
|
||||
# Verificar si el mapper tenant_id ya existe
|
||||
MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
|
||||
|
||||
if [ -z "$MAPPER_TENANT_EXISTS" ]; then
|
||||
# Crear mapper para tenant_id
|
||||
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}'
|
||||
echo -e "${GREEN}✓ Mapper tenant_id creado para Frontend${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Frontend${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# 6. Crear usuario demo en Keycloak
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[6/8] Creando usuario demo en Keycloak...${NC}"
|
||||
|
||||
# Verificar si el usuario ya existe
|
||||
USER_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${DEMO_USERNAME}" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
if echo "$USER_EXISTS" | grep -q "\"username\":\"${DEMO_USERNAME}\""; then
|
||||
echo -e "${YELLOW}⚠ Usuario demo ya existe, actualizando...${NC}"
|
||||
USER_ID=$(echo "$USER_EXISTS" | grep -o '"id":"[^"]*' | head -1 | sed 's/"id":"//')
|
||||
|
||||
# Actualizar usuario existente
|
||||
curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${USER_ID}" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"enabled\": true,
|
||||
\"firstName\": \"${DEMO_FIRSTNAME}\",
|
||||
\"lastName\": \"${DEMO_LASTNAME}\",
|
||||
\"email\": \"${DEMO_EMAIL}\",
|
||||
\"emailVerified\": true
|
||||
}"
|
||||
|
||||
# Resetear contraseña
|
||||
curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${USER_ID}/reset-password" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"type\": \"password\",
|
||||
\"value\": \"${DEMO_PASSWORD}\",
|
||||
\"temporary\": false
|
||||
}"
|
||||
|
||||
echo -e "${GREEN}✓ Usuario demo actualizado${NC}"
|
||||
else
|
||||
# Crear nuevo usuario con tenant_id por defecto
|
||||
CREATE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"username\": \"${DEMO_USERNAME}\",
|
||||
\"enabled\": true,
|
||||
\"firstName\": \"${DEMO_FIRSTNAME}\",
|
||||
\"lastName\": \"${DEMO_LASTNAME}\",
|
||||
\"email\": \"${DEMO_EMAIL}\",
|
||||
\"emailVerified\": true,
|
||||
\"attributes\": {
|
||||
\"tenant_id\": [\"1\"]
|
||||
},
|
||||
\"credentials\": [{
|
||||
\"type\": \"password\",
|
||||
\"value\": \"${DEMO_PASSWORD}\",
|
||||
\"temporary\": false
|
||||
}]
|
||||
}")
|
||||
|
||||
HTTP_CODE=$(echo "$CREATE_RESPONSE" | tail -n1)
|
||||
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo -e "${GREEN}✓ Usuario demo creado exitosamente${NC}"
|
||||
|
||||
# Obtener el ID del usuario recién creado
|
||||
USER_RESPONSE=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${DEMO_USERNAME}" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json")
|
||||
USER_ID=$(echo "$USER_RESPONSE" | grep -o '"id":"[^"]*' | head -1 | sed 's/"id":"//')
|
||||
else
|
||||
echo -e "${RED}✗ Error al crear usuario (HTTP ${HTTP_CODE})${NC}"
|
||||
echo "Respuesta: $CREATE_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Usuario: ${DEMO_USERNAME}${NC}"
|
||||
echo -e "${GREEN}✓ Password: ${DEMO_PASSWORD}${NC}"
|
||||
echo -e "${GREEN}✓ Keycloak User ID: ${USER_ID}${NC}"
|
||||
|
||||
###############################################################################
|
||||
# 7. Agregar atributo tenant_id al usuario
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[7/8] Configurando atributo tenant_id para usuario demo...${NC}"
|
||||
|
||||
# Nota: El tenant_id se agregará después de crear el tenant en PostgreSQL
|
||||
|
||||
###############################################################################
|
||||
# 8. Crear tenant y company en PostgreSQL
|
||||
###############################################################################
|
||||
echo -e "\n${YELLOW}[8/8] Creando tenant y company en PostgreSQL...${NC}"
|
||||
|
||||
# Esperar a que PostgreSQL esté listo
|
||||
echo "Esperando a que PostgreSQL esté disponible..."
|
||||
MAX_PG_RETRIES=30
|
||||
PG_RETRY_COUNT=0
|
||||
|
||||
while [ $PG_RETRY_COUNT -lt $MAX_PG_RETRIES ]; do
|
||||
if PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ PostgreSQL está listo${NC}"
|
||||
break
|
||||
fi
|
||||
PG_RETRY_COUNT=$((PG_RETRY_COUNT + 1))
|
||||
echo "Intento $PG_RETRY_COUNT/$MAX_PG_RETRIES..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $PG_RETRY_COUNT -eq $MAX_PG_RETRIES ]; then
|
||||
echo -e "${RED}✗ Error: PostgreSQL no está disponible${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Insertar o actualizar tenant
|
||||
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" <<EOF
|
||||
INSERT INTO a76.tenants (name, slug, type, keycloak_realm, contact_email, is_active, created_at, updated_at)
|
||||
VALUES ('${TENANT_NAME}', '${TENANT_SLUG}', 'SHARED', '${KEYCLOAK_REALM}', '${DEMO_EMAIL}', true, now(), now())
|
||||
ON CONFLICT (slug)
|
||||
DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
contact_email = EXCLUDED.contact_email,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
EOF
|
||||
|
||||
# Obtener el ID del tenant
|
||||
TENANT_ID=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -t -c "SELECT id FROM a76.tenants WHERE slug = '${TENANT_SLUG}';")
|
||||
TENANT_ID=$(echo "$TENANT_ID" | xargs) # Trim whitespace
|
||||
|
||||
if [ -z "$TENANT_ID" ]; then
|
||||
echo -e "${RED}✗ Error: No se pudo obtener el ID del tenant${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Tenant ID: ${TENANT_ID}${NC}"
|
||||
|
||||
# Insertar company si no existe
|
||||
COMPANY_EXISTS=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -t -c "SELECT COUNT(*) FROM a76.company WHERE tenant_id = ${TENANT_ID};")
|
||||
COMPANY_EXISTS=$(echo "$COMPANY_EXISTS" | xargs)
|
||||
|
||||
if [ "$COMPANY_EXISTS" = "0" ]; then
|
||||
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" <<EOF
|
||||
INSERT INTO a76.company (tenant_id, name, rfc, is_service_company)
|
||||
VALUES (${TENANT_ID}, '${COMPANY_NAME}', '${COMPANY_RFC}', false);
|
||||
EOF
|
||||
echo -e "${GREEN}✓ Company creada${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Company ya existe para este tenant${NC}"
|
||||
fi
|
||||
|
||||
# Obtener información de la company
|
||||
COMPANY_INFO=$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -t -c "SELECT id, name FROM a76.company WHERE tenant_id = ${TENANT_ID} LIMIT 1;")
|
||||
|
||||
echo -e "${GREEN}✓ Company: ${COMPANY_INFO}${NC}"
|
||||
|
||||
# Agregar tenant_id al usuario demo en Keycloak
|
||||
echo -e "\n${YELLOW}Asignando tenant_id al usuario demo...${NC}"
|
||||
|
||||
curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${USER_ID}" \
|
||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"attributes\": {
|
||||
\"tenant_id\": [\"${TENANT_ID}\"]
|
||||
}
|
||||
}"
|
||||
|
||||
echo -e "${GREEN}✓ Atributo tenant_id asignado al usuario${NC}"
|
||||
|
||||
# Agregar relación usuario-tenant en la base de datos
|
||||
echo -e "\n${YELLOW}Creando relación usuario-tenant en la base de datos...${NC}"
|
||||
|
||||
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" <<EOF
|
||||
INSERT INTO a76.user_tenants (keycloak_user_id, tenant_id, role, is_active, created_at, updated_at)
|
||||
VALUES ('${USER_ID}', ${TENANT_ID}, 'admin', true, now(), now())
|
||||
ON CONFLICT (keycloak_user_id, tenant_id)
|
||||
DO UPDATE SET
|
||||
is_active = true,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Relación usuario-tenant creada en la base de datos${NC}"
|
||||
|
||||
###############################################################################
|
||||
# Resumen final
|
||||
###############################################################################
|
||||
echo -e "\n${GREEN}════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} ✓ Inicialización completada exitosamente${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "\n${YELLOW}Credenciales de acceso:${NC}"
|
||||
echo -e " Usuario: ${GREEN}${DEMO_USERNAME}${NC}"
|
||||
echo -e " Password: ${GREEN}${DEMO_PASSWORD}${NC}"
|
||||
echo -e " Email: ${GREEN}${DEMO_EMAIL}${NC}"
|
||||
echo -e "\n${YELLOW}Información del sistema:${NC}"
|
||||
echo -e " Tenant: ${GREEN}${TENANT_NAME} (ID: ${TENANT_ID})${NC}"
|
||||
echo -e " Realm: ${GREEN}${KEYCLOAK_REALM}${NC}"
|
||||
echo -e " Keycloak URL: ${GREEN}${KEYCLOAK_URL}${NC}"
|
||||
echo -e "\n${YELLOW}Clientes Keycloak configurados:${NC}"
|
||||
echo -e " Backend Client ID: ${GREEN}anexo76-backend${NC}"
|
||||
if [ -n "$BACKEND_SECRET" ]; then
|
||||
echo -e " Backend Secret: ${GREEN}${BACKEND_SECRET}${NC}"
|
||||
if [ -n "$ENV_FILE" ]; then
|
||||
echo -e " ${GREEN}✓${NC} KEYCLOAK_CLIENT_SECRET actualizado en ${ENV_FILE}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} Agregar manualmente al archivo .env como KEYCLOAK_CLIENT_SECRET"
|
||||
fi
|
||||
fi
|
||||
echo -e " Frontend Client ID: ${GREEN}anexo76-frontend${NC}"
|
||||
echo -e "\n${YELLOW}User Profile Attributes configurados:${NC}"
|
||||
echo -e " ${GREEN}✓${NC} tenant_id (opcional, valor inicial: 1)"
|
||||
echo -e "\n${YELLOW}Mappers configurados:${NC}"
|
||||
echo -e " ${GREEN}✓${NC} tenant_id mapper para Backend"
|
||||
echo -e " ${GREEN}✓${NC} tenant_id mapper para Frontend"
|
||||
echo -e "\n${YELLOW}Configuración del usuario demo:${NC}"
|
||||
echo -e " ${GREEN}✓${NC} tenant_id actualizado al ID real: ${TENANT_ID}"
|
||||
echo -e " ${GREEN}✓${NC} Relación usuario-tenant creada (rol: admin)"
|
||||
echo -e "\n${YELLOW}Puedes acceder al sistema en:${NC}"
|
||||
echo -e " ${GREEN}http://localhost:5180${NC}"
|
||||
echo -e "\n${GREEN}════════════════════════════════════════════════════════${NC}\n"
|
||||
Reference in New Issue
Block a user