Merge pull request 'feature/general_catalogs_frontend' (#24) from feature/general_catalogs_frontend into development
Reviewed-on: ADUANASOFT/anexo76#24
This commit is contained in:
@@ -20,7 +20,7 @@ class TimestampMixin:
|
||||
class TenantScopedMixin:
|
||||
"""Mixin for tenant and company scoped entities"""
|
||||
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("core.tenants.id"), nullable=False, index=True)
|
||||
company_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.company.id"), nullable=False, index=True)
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ class Class(Base, TenantScopedMixin, TimestampMixin):
|
||||
["public.material_types.key"],
|
||||
name="fk_classes_material_type",
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["unit_of_measure", "tenant_id", "company_id"],
|
||||
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id"],
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"company_id",
|
||||
@@ -63,10 +68,10 @@ class Class(Base, TenantScopedMixin, TimestampMixin):
|
||||
|
||||
# Material and measurement
|
||||
material_key: Mapped[Optional[str]] = mapped_column(
|
||||
String(10), ForeignKey("public.material_types.key")
|
||||
String(10)
|
||||
) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(
|
||||
String(5), ForeignKey("a76.units_of_measure.code")
|
||||
String(5)
|
||||
) # UNIMED - homologated from UNIMEDIDA
|
||||
|
||||
# Tariff fractions
|
||||
|
||||
@@ -9,7 +9,7 @@ class ClassificationConcept(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "classification_concepts"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("classification", name="uq_classification_concept"),
|
||||
{"schema": "a76"}
|
||||
{"schema": "a76", "extend_existing": True},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
|
||||
@@ -1,64 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import ClassificationConceptCreate, ClassificationConceptResponse, ClassificationConceptUpdate
|
||||
from .service import ClassificationConceptService
|
||||
|
||||
router = APIRouter(prefix="/classification-concepts",
|
||||
tags=["a76.general_catalogs.classification_concepts"])
|
||||
|
||||
|
||||
@router.post("/", response_model=ClassificationConceptResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_classification_concept(
|
||||
data: ClassificationConceptCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_classification_concept(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=ClassificationConceptResponse)
|
||||
def get_classification_concept(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_classification_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="ClassificationConcept not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[ClassificationConceptResponse])
|
||||
def get_classification_concepts(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_classification_concepts(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=ClassificationConceptResponse)
|
||||
def update_classification_concept(
|
||||
id: int,
|
||||
data: ClassificationConceptUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_classification_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="ClassificationConcept not found")
|
||||
return service.update_classification_concept(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=ClassificationConceptResponse)
|
||||
def delete_classification_concept(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_classification_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="ClassificationConcept not found")
|
||||
return service.delete_classification_concept(session, db_obj)
|
||||
router = TenantCRUDRoutes(
|
||||
service=ClassificationConceptService,
|
||||
create_schema=ClassificationConceptCreate,
|
||||
update_schema=ClassificationConceptUpdate,
|
||||
response_schema=ClassificationConceptResponse,
|
||||
prefix="/classification-concepts",
|
||||
tags=["a76.general_catalogs.classification_concepts"],
|
||||
resource_name="Classification Concept",
|
||||
enable_list=True,
|
||||
).router
|
||||
|
||||
@@ -1,39 +1,81 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import ClassificationConcept
|
||||
from .dto import ClassificationConceptCreate, ClassificationConceptUpdate
|
||||
|
||||
|
||||
def create_classification_concept(session: Session, data: ClassificationConceptCreate) -> ClassificationConcept:
|
||||
db_obj = ClassificationConcept(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class ClassificationConceptService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[ClassificationConcept], int]:
|
||||
query = db.query(ClassificationConcept).filter(
|
||||
ClassificationConcept.tenant_id == tenant_id,
|
||||
ClassificationConcept.company_id == company_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
def get_classification_concept(session: Session, id: int) -> Optional[ClassificationConcept]:
|
||||
return session.get(ClassificationConcept, id)
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[ClassificationConcept]:
|
||||
return db.query(ClassificationConcept).filter(
|
||||
ClassificationConcept.id == id,
|
||||
ClassificationConcept.tenant_id == tenant_id,
|
||||
ClassificationConcept.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_classification_concepts(session: Session, skip: int = 0, limit: int = 100) -> Sequence[ClassificationConcept]:
|
||||
stmt = select(ClassificationConcept).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session, data: ClassificationConceptCreate, tenant_id: int, company_id: int
|
||||
) -> ClassificationConcept:
|
||||
db_obj = ClassificationConcept(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session, id: int, tenant_id: int, data: ClassificationConceptUpdate, company_id: int
|
||||
) -> Optional[ClassificationConcept]:
|
||||
db_obj = ClassificationConceptService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_classification_concept(session: Session, db_obj: ClassificationConcept, update_data: ClassificationConceptUpdate) -> ClassificationConcept:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_classification_concept(session: Session, db_obj: ClassificationConcept) -> ClassificationConcept:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
db_obj = ClassificationConceptService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -31,7 +31,7 @@ class Company(Base, TimestampMixin):
|
||||
|
||||
# Primary key
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True)
|
||||
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("core.tenants.id"), nullable=False, index=True)
|
||||
|
||||
# Información básica de la empresa
|
||||
name: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
@@ -3,7 +3,10 @@ from sqlalchemy import Integer, String, UniqueConstraint, Boolean, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from api.v1.modules.a76.general_catalogs.classification_concepts.models import ClassificationConcept
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.general_catalogs.classification_concepts.models import ClassificationConcept
|
||||
|
||||
|
||||
class Concept(Base, TenantScopedMixin, TimestampMixin):
|
||||
|
||||
@@ -1,71 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user, validate_access_to_resource
|
||||
from . import service
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import ConceptCreate, ConceptResponse, ConceptUpdate
|
||||
from .service import ConceptService
|
||||
|
||||
router = APIRouter(prefix="/concepts", tags=["a76.general_catalogs.concepts"])
|
||||
|
||||
|
||||
@router.post("/", response_model=ConceptResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_concept(
|
||||
data: ConceptCreate,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
tenant_id = validate_access_to_resource(
|
||||
session, data.company_id, current_user)
|
||||
return service.create_concept(session, data, tenant_id)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=ConceptResponse)
|
||||
def get_concept(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Concept not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[ConceptResponse])
|
||||
def get_concepts(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
return service.get_concepts(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=ConceptResponse)
|
||||
def update_concept(
|
||||
id: int,
|
||||
data: ConceptUpdate,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Concept not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return service.update_concept(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=ConceptResponse)
|
||||
def delete_concept(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Concept not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return service.delete_concept(session, db_obj)
|
||||
router = TenantCRUDRoutes(
|
||||
service=ConceptService,
|
||||
create_schema=ConceptCreate,
|
||||
update_schema=ConceptUpdate,
|
||||
response_schema=ConceptResponse,
|
||||
prefix="/concepts",
|
||||
tags=["a76.general_catalogs.concepts"],
|
||||
resource_name="Concept",
|
||||
enable_list=True,
|
||||
).router
|
||||
|
||||
@@ -1,36 +1,79 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Sequence, Optional
|
||||
from sqlalchemy import select
|
||||
|
||||
from .models import Concept
|
||||
from .dto import ConceptCreate, ConceptUpdate
|
||||
|
||||
|
||||
def create_concept(session: Session, data: ConceptCreate, tenant_id: int) -> Concept:
|
||||
db_obj = Concept(**data.model_dump(), tenant_id=tenant_id)
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class ConceptService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[Concept], int]:
|
||||
query = db.query(Concept).filter(
|
||||
Concept.tenant_id == tenant_id,
|
||||
Concept.company_id == company_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
def get_concept(session: Session, id: int) -> Optional[Concept]:
|
||||
return session.get(Concept, id)
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[Concept]:
|
||||
return db.query(Concept).filter(
|
||||
Concept.id == id,
|
||||
Concept.tenant_id == tenant_id,
|
||||
Concept.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_concepts(session: Session, skip: int = 0, limit: int = 100) -> Sequence[Concept]:
|
||||
return session.query(Concept).offset(skip).limit(limit).all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session, data: ConceptCreate, tenant_id: int, company_id: int
|
||||
) -> Concept:
|
||||
data_dict = data.model_dump()
|
||||
data_dict['company_id'] = company_id
|
||||
data_dict['tenant_id'] = tenant_id
|
||||
|
||||
db_obj = Concept(**data_dict)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update_concept(session: Session, db_obj: Concept, update_data: ConceptUpdate) -> Concept:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session, id: int, tenant_id: int, data: ConceptUpdate, company_id: int
|
||||
) -> Optional[Concept]:
|
||||
db_obj = ConceptService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
def delete_concept(session: Session, db_obj: Concept) -> Concept:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
db_obj = ConceptService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -1,64 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import CustomsBrokerConceptCreate, CustomsBrokerConceptResponse, CustomsBrokerConceptUpdate
|
||||
from .service import CustomsBrokerConceptService
|
||||
|
||||
router = APIRouter(prefix="/customs-broker-concepts",
|
||||
tags=["a76.general_catalogs.customs_broker_concepts"])
|
||||
|
||||
|
||||
@router.post("/", response_model=CustomsBrokerConceptResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_customs_broker_concept(
|
||||
data: CustomsBrokerConceptCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_customs_broker_concept(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=CustomsBrokerConceptResponse)
|
||||
def get_customs_broker_concept(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_customs_broker_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="CustomsBrokerConcept not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[CustomsBrokerConceptResponse])
|
||||
def get_customs_broker_concepts(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_customs_broker_concepts(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=CustomsBrokerConceptResponse)
|
||||
def update_customs_broker_concept(
|
||||
id: int,
|
||||
data: CustomsBrokerConceptUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_customs_broker_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="CustomsBrokerConcept not found")
|
||||
return service.update_customs_broker_concept(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=CustomsBrokerConceptResponse)
|
||||
def delete_customs_broker_concept(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_customs_broker_concept(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="CustomsBrokerConcept not found")
|
||||
return service.delete_customs_broker_concept(session, db_obj)
|
||||
router = TenantCRUDRoutes(
|
||||
service=CustomsBrokerConceptService,
|
||||
create_schema=CustomsBrokerConceptCreate,
|
||||
update_schema=CustomsBrokerConceptUpdate,
|
||||
response_schema=CustomsBrokerConceptResponse,
|
||||
prefix="/customs-broker-concepts",
|
||||
tags=["a76.general_catalogs.customs_broker_concepts"],
|
||||
resource_name="Customs Broker Concept",
|
||||
enable_list=True,
|
||||
).router
|
||||
|
||||
@@ -1,39 +1,81 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import CustomsBrokerConcept
|
||||
from .dto import CustomsBrokerConceptCreate, CustomsBrokerConceptUpdate
|
||||
|
||||
|
||||
def create_customs_broker_concept(session: Session, data: CustomsBrokerConceptCreate) -> CustomsBrokerConcept:
|
||||
db_obj = CustomsBrokerConcept(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class CustomsBrokerConceptService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[CustomsBrokerConcept], int]:
|
||||
query = db.query(CustomsBrokerConcept).filter(
|
||||
CustomsBrokerConcept.tenant_id == tenant_id,
|
||||
CustomsBrokerConcept.company_id == company_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
def get_customs_broker_concept(session: Session, id: int) -> Optional[CustomsBrokerConcept]:
|
||||
return session.get(CustomsBrokerConcept, id)
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[CustomsBrokerConcept]:
|
||||
return db.query(CustomsBrokerConcept).filter(
|
||||
CustomsBrokerConcept.id == id,
|
||||
CustomsBrokerConcept.tenant_id == tenant_id,
|
||||
CustomsBrokerConcept.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_customs_broker_concepts(session: Session, skip: int = 0, limit: int = 100) -> Sequence[CustomsBrokerConcept]:
|
||||
stmt = select(CustomsBrokerConcept).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session, data: CustomsBrokerConceptCreate, tenant_id: int, company_id: int
|
||||
) -> CustomsBrokerConcept:
|
||||
db_obj = CustomsBrokerConcept(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session, id: int, tenant_id: int, data: CustomsBrokerConceptUpdate, company_id: int
|
||||
) -> Optional[CustomsBrokerConcept]:
|
||||
db_obj = CustomsBrokerConceptService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_customs_broker_concept(session: Session, db_obj: CustomsBrokerConcept, update_data: CustomsBrokerConceptUpdate) -> CustomsBrokerConcept:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_customs_broker_concept(session: Session, db_obj: CustomsBrokerConcept) -> CustomsBrokerConcept:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
db_obj = CustomsBrokerConceptService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -8,6 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import get_core_db
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import (
|
||||
DodaCreateDTO,
|
||||
DodaResponseDTO,
|
||||
@@ -25,54 +26,40 @@ from .dto import (
|
||||
)
|
||||
from .models import Doda
|
||||
from .service import DodaService
|
||||
from core.security import get_current_user, validate_access_to_resource
|
||||
|
||||
router = APIRouter(prefix="/doda", tags=["doda"])
|
||||
# Create CRUD router
|
||||
crud_router = TenantCRUDRoutes(
|
||||
service=DodaService,
|
||||
create_schema=DodaCreateDTO,
|
||||
update_schema=DodaUpdateDTO,
|
||||
response_schema=DodaResponseDTO,
|
||||
prefix="/doda",
|
||||
tags=["doda"],
|
||||
resource_name="DODA",
|
||||
id_name="doda_id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
|
||||
router = crud_router
|
||||
|
||||
# ============ MAIN DODA ENDPOINTS ============
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="Get all DODAs",
|
||||
)
|
||||
async def get_all_dodas(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
integration_number: str = Query(None),
|
||||
status: str = Query(None),
|
||||
patent: str = Query(None),
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all DODAs with optional filtering and pagination"""
|
||||
filters = {}
|
||||
if integration_number:
|
||||
filters["integration_number"] = integration_number
|
||||
if status:
|
||||
filters["status"] = status
|
||||
if patent:
|
||||
filters["patent"] = patent
|
||||
|
||||
dodas, total = DodaService.get_all(db, skip, limit, filters)
|
||||
|
||||
return {
|
||||
"data": [DodaResponseDTO.model_validate(doda) for doda in dodas],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
# ============ CUSTOM ENDPOINTS ============
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{sys_id}",
|
||||
"/{doda_id}/detail",
|
||||
response_model=DodaDetailResponseDTO,
|
||||
summary="Get DODA by ID with all details",
|
||||
)
|
||||
async def get_doda(
|
||||
sys_id: int,
|
||||
async def get_doda_detail(
|
||||
doda_id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get a DODA by its ID with all related data"""
|
||||
doda = DodaService.get_by_id(db, sys_id)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id)
|
||||
if not doda:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -81,88 +68,34 @@ async def get_doda(
|
||||
return DodaDetailResponseDTO.model_validate(doda)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=DodaResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create DODA",
|
||||
)
|
||||
async def create_doda(
|
||||
doda_data: DodaCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Create a new DODA"""
|
||||
doda = DodaService.create(db, doda_data)
|
||||
return DodaResponseDTO.model_validate(doda)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{sys_id}",
|
||||
response_model=DodaResponseDTO,
|
||||
summary="Update DODA",
|
||||
)
|
||||
async def update_doda(
|
||||
sys_id: int,
|
||||
doda_data: DodaUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Update a DODA"""
|
||||
doda = DodaService.update(db, sys_id, doda_data)
|
||||
if not doda:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="DODA not found",
|
||||
)
|
||||
return DodaResponseDTO.model_validate(doda)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{sys_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete DODA",
|
||||
)
|
||||
async def delete_doda(
|
||||
sys_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Delete a DODA"""
|
||||
success = DodaService.delete(db, sys_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="DODA not found",
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
# ============ CONTAINERS ENDPOINTS ============
|
||||
@router.get(
|
||||
"/{sys_id}/containers",
|
||||
"/{doda_id}/containers",
|
||||
response_model=List[DodaContainerResponseDTO],
|
||||
summary="Get containers for DODA",
|
||||
)
|
||||
async def get_doda_containers(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all containers for a specific DODA"""
|
||||
containers = DodaService.get_containers(db, sys_id)
|
||||
containers = DodaService.get_containers(db, doda_id)
|
||||
return [DodaContainerResponseDTO.model_validate(c) for c in containers]
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{sys_id}/containers",
|
||||
"/{doda_id}/containers",
|
||||
response_model=DodaContainerResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Add container to DODA",
|
||||
)
|
||||
async def add_container(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
container_data: DodaContainerCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Add a new container to a DODA"""
|
||||
container = DodaService.add_container(db, sys_id, container_data)
|
||||
container = DodaService.add_container(db, doda_id, container_data)
|
||||
if not container:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -172,19 +105,19 @@ async def add_container(
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{sys_id}/containers/{container_line}",
|
||||
"/{doda_id}/containers/{container_line}",
|
||||
response_model=DodaContainerResponseDTO,
|
||||
summary="Update container",
|
||||
)
|
||||
async def update_container(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
container_line: int,
|
||||
container_data: DodaContainerUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Update a container"""
|
||||
container = DodaService.update_container(
|
||||
db, sys_id, container_line, container_data
|
||||
db, doda_id, container_line, container_data
|
||||
)
|
||||
if not container:
|
||||
raise HTTPException(
|
||||
@@ -196,32 +129,32 @@ async def update_container(
|
||||
|
||||
# ============ AMERICAN PEDIMENTOS ENDPOINTS ============
|
||||
@router.get(
|
||||
"/{sys_id}/american-pedimentos",
|
||||
"/{doda_id}/american-pedimentos",
|
||||
response_model=List[DodaAmericanPedimentoResponseDTO],
|
||||
summary="Get American pedimentos for DODA",
|
||||
)
|
||||
async def get_american_pedimentos(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all American pedimentos for a specific DODA"""
|
||||
pedimentos = DodaService.get_american_pedimentos(db, sys_id)
|
||||
pedimentos = DodaService.get_american_pedimentos(db, doda_id)
|
||||
return [DodaAmericanPedimentoResponseDTO.model_validate(p) for p in pedimentos]
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{sys_id}/american-pedimentos",
|
||||
"/{doda_id}/american-pedimentos",
|
||||
response_model=DodaAmericanPedimentoResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Add American pedimento to DODA",
|
||||
)
|
||||
async def add_american_pedimento(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
pedimento_data: DodaAmericanPedimentoCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Add a new American pedimento to a DODA"""
|
||||
pedimento = DodaService.add_american_pedimento(db, sys_id, pedimento_data)
|
||||
pedimento = DodaService.add_american_pedimento(db, doda_id, pedimento_data)
|
||||
if not pedimento:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -232,32 +165,32 @@ async def add_american_pedimento(
|
||||
|
||||
# ============ PEDIMENTOS ENDPOINTS ============
|
||||
@router.get(
|
||||
"/{sys_id}/pedimentos",
|
||||
"/{doda_id}/pedimentos",
|
||||
response_model=List[DodaPedimentoResponseDTO],
|
||||
summary="Get pedimentos for DODA",
|
||||
)
|
||||
async def get_doda_pedimentos(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all pedimentos for a specific DODA"""
|
||||
pedimentos = DodaService.get_pedimentos(db, sys_id)
|
||||
pedimentos = DodaService.get_pedimentos(db, doda_id)
|
||||
return [DodaPedimentoResponseDTO.model_validate(p) for p in pedimentos]
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{sys_id}/pedimentos",
|
||||
"/{doda_id}/pedimentos",
|
||||
response_model=DodaPedimentoResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Add pedimento to DODA",
|
||||
)
|
||||
async def add_pedimento(
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
pedimento_data: DodaPedimentoCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Add a new pedimento to a DODA"""
|
||||
pedimento = DodaService.add_pedimento(db, sys_id, pedimento_data)
|
||||
pedimento = DodaService.add_pedimento(db, doda_id, pedimento_data)
|
||||
if not pedimento:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
|
||||
@@ -34,19 +34,20 @@ logger = logging.getLogger(__name__)
|
||||
class DodaService:
|
||||
"""Servicio para gestión de DODA"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
# ============ DODA MAIN CRUD ============
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[Doda], int]:
|
||||
"""Get all DODAs with pagination"""
|
||||
query = db.query(Doda)
|
||||
query = db.query(Doda).filter(
|
||||
Doda.tenant_id == tenant_id,
|
||||
Doda.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
if filters.get("integration_number"):
|
||||
@@ -66,15 +67,27 @@ class DodaService:
|
||||
return dodas, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, sys_id: int) -> Optional[Doda]:
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[Doda]:
|
||||
"""Get DODA by ID"""
|
||||
return db.query(Doda).filter(Doda.sys_id == sys_id).first()
|
||||
return db.query(Doda).filter(
|
||||
Doda.id == id,
|
||||
Doda.tenant_id == tenant_id,
|
||||
Doda.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, doda_data: DodaCreateDTO) -> Doda:
|
||||
def create(
|
||||
db: Session, doda_data: DodaCreateDTO, tenant_id: int, company_id: int
|
||||
) -> Doda:
|
||||
"""Create a new DODA"""
|
||||
try:
|
||||
db_doda = Doda(**doda_data.model_dump(exclude_unset=True))
|
||||
db_doda = Doda(
|
||||
**doda_data.model_dump(exclude_unset=True),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_doda)
|
||||
db.commit()
|
||||
db.refresh(db_doda)
|
||||
@@ -89,10 +102,12 @@ class DodaService:
|
||||
raise HTTPException(status_code=500, detail="Error creating DODA")
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, sys_id: int, doda_data: DodaUpdateDTO) -> Optional[Doda]:
|
||||
def update(
|
||||
db: Session, id: int, tenant_id: int, doda_data: DodaUpdateDTO, company_id: int
|
||||
) -> Optional[Doda]:
|
||||
"""Update a DODA"""
|
||||
try:
|
||||
db_doda = db.query(Doda).filter(Doda.sys_id == sys_id).first()
|
||||
db_doda = DodaService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_doda:
|
||||
return None
|
||||
|
||||
@@ -112,10 +127,12 @@ class DodaService:
|
||||
raise HTTPException(status_code=500, detail="Error updating DODA")
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, sys_id: int) -> bool:
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete a DODA"""
|
||||
try:
|
||||
db_doda = db.query(Doda).filter(Doda.sys_id == sys_id).first()
|
||||
db_doda = DodaService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_doda:
|
||||
return False
|
||||
|
||||
@@ -130,23 +147,23 @@ class DodaService:
|
||||
# ============ CONTAINERS ============
|
||||
@staticmethod
|
||||
def add_container(
|
||||
db: Session, sys_id: int, container_data: DodaContainerCreateDTO
|
||||
db: Session, doda_id: int, container_data: DodaContainerCreateDTO
|
||||
) -> Optional[DodaContainer]:
|
||||
"""Add a container to a DODA"""
|
||||
try:
|
||||
doda = db.query(Doda).filter(Doda.sys_id == sys_id).first()
|
||||
doda = db.query(Doda).filter(Doda.id == doda_id).first()
|
||||
if not doda:
|
||||
return None
|
||||
|
||||
# Get max line number
|
||||
max_line = (
|
||||
db.query(DodaContainer)
|
||||
.filter(DodaContainer.doda_sys_id == sys_id)
|
||||
.filter(DodaContainer.doda_id == doda_id)
|
||||
.count()
|
||||
)
|
||||
|
||||
db_container = DodaContainer(
|
||||
doda_sys_id=sys_id,
|
||||
doda_id=doda_id,
|
||||
container_line=max_line + 1,
|
||||
**{
|
||||
k: v
|
||||
@@ -167,7 +184,7 @@ class DodaService:
|
||||
@staticmethod
|
||||
def update_container(
|
||||
db: Session,
|
||||
sys_id: int,
|
||||
doda_id: int,
|
||||
container_line: int,
|
||||
container_data: DodaContainerUpdateDTO,
|
||||
) -> Optional[DodaContainer]:
|
||||
@@ -176,7 +193,7 @@ class DodaService:
|
||||
db_container = (
|
||||
db.query(DodaContainer)
|
||||
.filter(
|
||||
DodaContainer.doda_sys_id == sys_id,
|
||||
DodaContainer.doda_id == doda_id,
|
||||
DodaContainer.container_line == container_line,
|
||||
)
|
||||
.first()
|
||||
@@ -197,33 +214,33 @@ class DodaService:
|
||||
status_code=500, detail="Error updating container")
|
||||
|
||||
@staticmethod
|
||||
def get_containers(db: Session, sys_id: int) -> List[DodaContainer]:
|
||||
def get_containers(db: Session, doda_id: int) -> List[DodaContainer]:
|
||||
"""Get all containers for a DODA"""
|
||||
return (
|
||||
db.query(DodaContainer)
|
||||
.filter(DodaContainer.doda_sys_id == sys_id)
|
||||
.filter(DodaContainer.doda_id == doda_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# ============ AMERICAN PEDIMENTOS ============
|
||||
@staticmethod
|
||||
def add_american_pedimento(
|
||||
db: Session, sys_id: int, pedimento_data: DodaAmericanPedimentoCreateDTO
|
||||
db: Session, doda_id: int, pedimento_data: DodaAmericanPedimentoCreateDTO
|
||||
) -> Optional[DodaAmericanPedimento]:
|
||||
"""Add an American pedimento to a DODA"""
|
||||
try:
|
||||
doda = db.query(Doda).filter(Doda.sys_id == sys_id).first()
|
||||
doda = db.query(Doda).filter(Doda.id == doda_id).first()
|
||||
if not doda:
|
||||
return None
|
||||
|
||||
max_line = (
|
||||
db.query(DodaAmericanPedimento)
|
||||
.filter(DodaAmericanPedimento.doda_sys_id == sys_id)
|
||||
.filter(DodaAmericanPedimento.doda_id == doda_id)
|
||||
.count()
|
||||
)
|
||||
|
||||
db_pedimento = DodaAmericanPedimento(
|
||||
doda_sys_id=sys_id,
|
||||
doda_id=doda_id,
|
||||
american_pedimento_line=max_line + 1,
|
||||
**pedimento_data.model_dump(exclude_unset=True),
|
||||
)
|
||||
@@ -239,33 +256,33 @@ class DodaService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_american_pedimentos(db: Session, sys_id: int) -> List[DodaAmericanPedimento]:
|
||||
def get_american_pedimentos(db: Session, doda_id: int) -> List[DodaAmericanPedimento]:
|
||||
"""Get all American pedimentos for a DODA"""
|
||||
return (
|
||||
db.query(DodaAmericanPedimento)
|
||||
.filter(DodaAmericanPedimento.doda_sys_id == sys_id)
|
||||
.filter(DodaAmericanPedimento.doda_id == doda_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# ============ PEDIMENTOS ============
|
||||
@staticmethod
|
||||
def add_pedimento(
|
||||
db: Session, sys_id: int, pedimento_data: DodaPedimentoCreateDTO
|
||||
db: Session, doda_id: int, pedimento_data: DodaPedimentoCreateDTO
|
||||
) -> Optional[DodaPedimento]:
|
||||
"""Add a pedimento to a DODA"""
|
||||
try:
|
||||
doda = db.query(Doda).filter(Doda.sys_id == sys_id).first()
|
||||
doda = db.query(Doda).filter(Doda.id == doda_id).first()
|
||||
if not doda:
|
||||
return None
|
||||
|
||||
max_line = (
|
||||
db.query(DodaPedimento)
|
||||
.filter(DodaPedimento.doda_sys_id == sys_id)
|
||||
.filter(DodaPedimento.doda_id == doda_id)
|
||||
.count()
|
||||
)
|
||||
|
||||
db_pedimento = DodaPedimento(
|
||||
doda_sys_id=sys_id,
|
||||
doda_id=doda_id,
|
||||
pedimento_line=max_line + 1,
|
||||
**pedimento_data.model_dump(exclude_unset=True),
|
||||
)
|
||||
@@ -280,9 +297,9 @@ class DodaService:
|
||||
status_code=500, detail="Error adding pedimento")
|
||||
|
||||
@staticmethod
|
||||
def get_pedimentos(db: Session, sys_id: int) -> List[DodaPedimento]:
|
||||
def get_pedimentos(db: Session, doda_id: int) -> List[DodaPedimento]:
|
||||
"""Get all pedimentos for a DODA"""
|
||||
return (
|
||||
db.query(DodaPedimento).filter(
|
||||
DodaPedimento.doda_sys_id == sys_id).all()
|
||||
DodaPedimento.doda_id == doda_id).all()
|
||||
)
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
Rutas para gestión de avisos electrónicos
|
||||
"""
|
||||
|
||||
from core.security import get_current_user, validate_access_to_resource
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import get_core_db
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import (
|
||||
ElectronicNoticeCreateDTO,
|
||||
ElectronicNoticeResponseDTO,
|
||||
@@ -16,114 +18,17 @@ from .dto import (
|
||||
from .models import ElectronicNotice
|
||||
from .service import ElectronicNoticeService
|
||||
|
||||
router = APIRouter(prefix="/electronic-notices", tags=["electronic-notices"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="Get all electronic notices",
|
||||
)
|
||||
async def get_all_notices(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
notice_number: str = Query(None),
|
||||
status: str = Query(None),
|
||||
pedimento: str = Query(None),
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all electronic notices with optional filtering and pagination"""
|
||||
filters = {}
|
||||
if notice_number:
|
||||
filters["notice_number"] = notice_number
|
||||
if status:
|
||||
filters["status"] = status
|
||||
if pedimento:
|
||||
filters["pedimento"] = pedimento
|
||||
|
||||
notices, total = ElectronicNoticeService.get_all(db, skip, limit, filters)
|
||||
|
||||
return {
|
||||
"data": [
|
||||
ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices
|
||||
],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{sys_id}",
|
||||
response_model=ElectronicNoticeResponseDTO,
|
||||
summary="Get electronic notice by ID",
|
||||
)
|
||||
async def get_notice(
|
||||
sys_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get an electronic notice by its ID"""
|
||||
notice = ElectronicNoticeService.get_by_id(db, sys_id)
|
||||
if not notice:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Electronic notice not found",
|
||||
)
|
||||
return ElectronicNoticeResponseDTO.model_validate(notice)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ElectronicNoticeResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create electronic notice",
|
||||
)
|
||||
async def create_notice(
|
||||
notice_data: ElectronicNoticeCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Create a new electronic notice"""
|
||||
notice = ElectronicNoticeService.create(db, notice_data)
|
||||
return ElectronicNoticeResponseDTO.model_validate(notice)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{sys_id}",
|
||||
response_model=ElectronicNoticeResponseDTO,
|
||||
summary="Update electronic notice",
|
||||
)
|
||||
async def update_notice(
|
||||
sys_id: int,
|
||||
notice_data: ElectronicNoticeUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Update an electronic notice"""
|
||||
notice = ElectronicNoticeService.update(db, sys_id, notice_data)
|
||||
if not notice:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Electronic notice not found",
|
||||
)
|
||||
return ElectronicNoticeResponseDTO.model_validate(notice)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{sys_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete electronic notice",
|
||||
)
|
||||
async def delete_notice(
|
||||
sys_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Delete an electronic notice"""
|
||||
success = ElectronicNoticeService.delete(db, sys_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Electronic notice not found",
|
||||
)
|
||||
return None
|
||||
router = TenantCRUDRoutes(
|
||||
service=ElectronicNoticeService,
|
||||
create_schema=ElectronicNoticeCreateDTO,
|
||||
update_schema=ElectronicNoticeUpdateDTO,
|
||||
response_schema=ElectronicNoticeResponseDTO,
|
||||
prefix="/electronic-notices",
|
||||
tags=["electronic-notices"],
|
||||
resource_name="Electronic Notice",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -133,10 +38,14 @@ async def delete_notice(
|
||||
)
|
||||
async def get_notices_by_pedimento(
|
||||
pedimento: str,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get all electronic notices for a specific pedimento"""
|
||||
notices = ElectronicNoticeService.get_by_pedimento(db, pedimento)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
notices = ElectronicNoticeService.get_by_pedimento(
|
||||
db, pedimento, tenant_id, company_id)
|
||||
return [ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices]
|
||||
|
||||
|
||||
@@ -147,8 +56,12 @@ async def get_notices_by_pedimento(
|
||||
)
|
||||
async def get_notices_by_status(
|
||||
status: str,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get all electronic notices with a specific status"""
|
||||
notices = ElectronicNoticeService.get_by_status(db, status)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
notices = ElectronicNoticeService.get_by_status(
|
||||
db, status, tenant_id, company_id)
|
||||
return [ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices]
|
||||
|
||||
@@ -22,18 +22,20 @@ logger = logging.getLogger(__name__)
|
||||
class ElectronicNoticeService:
|
||||
"""Servicio para gestión de avisos electrónicos"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[ElectronicNotice], int]:
|
||||
"""Get all electronic notices with pagination"""
|
||||
query = db.query(ElectronicNotice)
|
||||
query = db.query(ElectronicNotice).filter(
|
||||
ElectronicNotice.tenant_id == tenant_id,
|
||||
ElectronicNotice.company_id == company_id
|
||||
)
|
||||
|
||||
# Apply filters if provided
|
||||
if filters:
|
||||
@@ -59,20 +61,26 @@ class ElectronicNoticeService:
|
||||
return notices, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, sys_id: int) -> Optional[ElectronicNotice]:
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[ElectronicNotice]:
|
||||
"""Get electronic notice by ID"""
|
||||
return db.query(ElectronicNotice).filter(
|
||||
ElectronicNotice.sys_id == sys_id
|
||||
ElectronicNotice.id == id,
|
||||
ElectronicNotice.tenant_id == tenant_id,
|
||||
ElectronicNotice.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session, notice_data: ElectronicNoticeCreateDTO
|
||||
db: Session, notice_data: ElectronicNoticeCreateDTO, tenant_id: int, company_id: int
|
||||
) -> ElectronicNotice:
|
||||
"""Create a new electronic notice"""
|
||||
try:
|
||||
db_notice = ElectronicNotice(
|
||||
**notice_data.model_dump(exclude_unset=True)
|
||||
**notice_data.model_dump(exclude_unset=True),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
|
||||
db.add(db_notice)
|
||||
@@ -98,16 +106,12 @@ class ElectronicNoticeService:
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
sys_id: int,
|
||||
notice_data: ElectronicNoticeUpdateDTO,
|
||||
db: Session, id: int, tenant_id: int, notice_data: ElectronicNoticeUpdateDTO, company_id: int
|
||||
) -> Optional[ElectronicNotice]:
|
||||
"""Update an electronic notice"""
|
||||
try:
|
||||
db_notice = db.query(ElectronicNotice).filter(
|
||||
ElectronicNotice.sys_id == sys_id
|
||||
).first()
|
||||
|
||||
db_notice = ElectronicNoticeService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_notice:
|
||||
return None
|
||||
|
||||
@@ -116,7 +120,6 @@ class ElectronicNoticeService:
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_notice)
|
||||
|
||||
return db_notice
|
||||
|
||||
except IntegrityError as e:
|
||||
@@ -135,19 +138,18 @@ class ElectronicNoticeService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, sys_id: int) -> bool:
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete an electronic notice"""
|
||||
try:
|
||||
db_notice = db.query(ElectronicNotice).filter(
|
||||
ElectronicNotice.sys_id == sys_id
|
||||
).first()
|
||||
|
||||
db_notice = ElectronicNoticeService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_notice:
|
||||
return False
|
||||
|
||||
db.delete(db_notice)
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
@@ -159,20 +161,30 @@ class ElectronicNoticeService:
|
||||
|
||||
@staticmethod
|
||||
def get_by_pedimento(
|
||||
db: Session, pedimento: str
|
||||
db: Session, pedimento: str, tenant_id: int, company_id: int
|
||||
) -> List[ElectronicNotice]:
|
||||
"""Get all electronic notices by pedimento"""
|
||||
return (
|
||||
db.query(ElectronicNotice)
|
||||
.filter(ElectronicNotice.pedimento == pedimento)
|
||||
.filter(
|
||||
ElectronicNotice.pedimento == pedimento,
|
||||
ElectronicNotice.tenant_id == tenant_id,
|
||||
ElectronicNotice.company_id == company_id
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_by_status(db: Session, status: str) -> List[ElectronicNotice]:
|
||||
def get_by_status(
|
||||
db: Session, status: str, tenant_id: int, company_id: int
|
||||
) -> List[ElectronicNotice]:
|
||||
"""Get all electronic notices by status"""
|
||||
return (
|
||||
db.query(ElectronicNotice)
|
||||
.filter(ElectronicNotice.status == status)
|
||||
.filter(
|
||||
ElectronicNotice.status == status,
|
||||
ElectronicNotice.tenant_id == tenant_id,
|
||||
ElectronicNotice.company_id == company_id
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import Integer, String, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy import Integer, String, ForeignKey, UniqueConstraint, ForeignKeyConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
|
||||
|
||||
class Equivalency(Base):
|
||||
class Equivalency(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "equivalencies"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("identifier", name="uq_equivalency_identifier"),
|
||||
UniqueConstraint("identifier", "tenant_id", "company_id",
|
||||
name="uq_equivalency_identifier"),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
@@ -22,11 +24,16 @@ class Equivalency(Base):
|
||||
back_populates="equivalency", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class EquivalencyItem(Base):
|
||||
class EquivalencyItem(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "equivalency_items"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("equivalency_id", "original_field",
|
||||
"external_field", name="uq_equivalency_item_fields"),
|
||||
"external_field", "tenant_id", "company_id", name="uq_equivalency_item_fields"),
|
||||
ForeignKeyConstraint(
|
||||
["original_field", "tenant_id", "company_id"],
|
||||
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id"]
|
||||
),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
@@ -34,8 +41,8 @@ class EquivalencyItem(Base):
|
||||
Integer, primary_key=True, autoincrement=True)
|
||||
equivalency_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("a76.equivalencies.id"), nullable=False)
|
||||
original_field: Mapped[str] = mapped_column(String(100), ForeignKey(
|
||||
"a76.units_of_measure.code"), nullable=False) # Relation to Unit of Measure
|
||||
original_field: Mapped[str] = mapped_column(
|
||||
String(100), nullable=False) # Relation to Unit of Measure
|
||||
external_field: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
|
||||
equivalency: Mapped["Equivalency"] = relationship(back_populates="items")
|
||||
|
||||
@@ -1,106 +1,99 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user, validate_access_to_resource
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from . import service
|
||||
from .models import Equivalency, EquivalencyItem
|
||||
from .dto import (
|
||||
EquivalencyCreate, EquivalencyResponse, EquivalencyUpdate,
|
||||
EquivalencyItemCreate, EquivalencyItemResponse, EquivalencyItemUpdate
|
||||
)
|
||||
from .service import EquivalencyService, EquivalencyItemService
|
||||
|
||||
router = APIRouter(prefix="/equivalencies",
|
||||
tags=["a76.general_catalogs.equivalencies"])
|
||||
|
||||
# Equivalency Routes
|
||||
# Equivalency CRUD
|
||||
equivalency_crud = TenantCRUDRoutes(
|
||||
service=EquivalencyService,
|
||||
create_schema=EquivalencyCreate,
|
||||
update_schema=EquivalencyUpdate,
|
||||
response_schema=EquivalencyResponse,
|
||||
prefix="",
|
||||
tags=["Equivalencies"],
|
||||
resource_name="Equivalency",
|
||||
enable_list=True,
|
||||
)
|
||||
|
||||
# Equivalency Item CRUD
|
||||
item_crud = TenantCRUDRoutes(
|
||||
service=EquivalencyItemService,
|
||||
create_schema=EquivalencyItemCreate,
|
||||
update_schema=EquivalencyItemUpdate,
|
||||
response_schema=EquivalencyItemResponse,
|
||||
prefix="/items",
|
||||
tags=["Equivalency Items"],
|
||||
resource_name="EquivalencyItem",
|
||||
enable_list=True,
|
||||
)
|
||||
|
||||
# Custom endpoint for creating items nested under equivalency
|
||||
|
||||
|
||||
@router.post("/", response_model=EquivalencyResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_equivalency(
|
||||
data: EquivalencyCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_equivalency(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=EquivalencyResponse)
|
||||
def get_equivalency(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_equivalency(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Equivalency not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[EquivalencyResponse])
|
||||
def get_equivalencies(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_equivalencies(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=EquivalencyResponse)
|
||||
def update_equivalency(
|
||||
id: int,
|
||||
data: EquivalencyUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_equivalency(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Equivalency not found")
|
||||
return service.update_equivalency(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=EquivalencyResponse)
|
||||
def delete_equivalency(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_equivalency(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Equivalency not found")
|
||||
return service.delete_equivalency(session, db_obj)
|
||||
|
||||
# Equivalency Item Routes
|
||||
|
||||
|
||||
@router.post("/{equivalency_id}/items", response_model=EquivalencyItemResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_equivalency_item(
|
||||
@equivalency_crud.router.post(
|
||||
"/{equivalency_id}/items",
|
||||
response_model=EquivalencyItemResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create equivalency item",
|
||||
)
|
||||
async def create_equivalency_item(
|
||||
equivalency_id: int,
|
||||
data: EquivalencyItemCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
# Verify parent exists
|
||||
parent = service.get_equivalency(session, equivalency_id)
|
||||
parent = EquivalencyService.get_by_id(
|
||||
db, equivalency_id, tenant_id, company_id)
|
||||
if not parent:
|
||||
raise HTTPException(status_code=404, detail="Equivalency not found")
|
||||
return service.create_equivalency_item(session, equivalency_id, data)
|
||||
|
||||
# Create item
|
||||
# We need to manually handle the creation because the DTO doesn't have equivalency_id
|
||||
# and the service.create expects data to match the model or DTO.
|
||||
# But service.create takes EquivalencyItemCreate which doesn't have equivalency_id.
|
||||
# So we need to modify the data or handle it in service.
|
||||
|
||||
# Actually, I implemented EquivalencyItemService.create to take EquivalencyItemCreate.
|
||||
# And it tries to create the model.
|
||||
# But the model needs equivalency_id.
|
||||
# So EquivalencyItemService.create will fail if I don't pass equivalency_id.
|
||||
# I should update EquivalencyItemService.create to accept extra kwargs or handle this.
|
||||
|
||||
# Let's update the service call here to pass equivalency_id manually if I can't change the service signature easily.
|
||||
# But wait, I can just instantiate the model here or update the service.
|
||||
|
||||
# I'll update the service to handle it.
|
||||
# But for now, let's assume I can pass it in the data if I convert it to dict.
|
||||
|
||||
item_data = data.model_dump()
|
||||
item_data['equivalency_id'] = equivalency_id
|
||||
|
||||
# I need to call a method that accepts this.
|
||||
# EquivalencyItemService.create takes EquivalencyItemCreate.
|
||||
# I should probably add a specific method for this or update create.
|
||||
|
||||
# Let's use a direct DB call here or add a method to service.
|
||||
# Adding a method to service is cleaner.
|
||||
|
||||
return EquivalencyItemService.create_nested(db, equivalency_id, data, tenant_id, company_id)
|
||||
|
||||
|
||||
@router.put("/items/{id}", response_model=EquivalencyItemResponse)
|
||||
def update_equivalency_item(
|
||||
id: int,
|
||||
data: EquivalencyItemUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_equivalency_item(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Equivalency Item not found")
|
||||
return service.update_equivalency_item(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/items/{id}", response_model=EquivalencyItemResponse)
|
||||
def delete_equivalency_item(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_equivalency_item(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Equivalency Item not found")
|
||||
return service.delete_equivalency_item(session, db_obj)
|
||||
router.include_router(equivalency_crud.router)
|
||||
router.include_router(item_crud.router)
|
||||
|
||||
@@ -1,83 +1,228 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
from fastapi import HTTPException
|
||||
|
||||
from .models import Equivalency, EquivalencyItem
|
||||
from .dto import EquivalencyCreate, EquivalencyUpdate, EquivalencyItemCreate, EquivalencyItemUpdate
|
||||
|
||||
# Equivalency Services
|
||||
|
||||
class EquivalencyService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[Equivalency], int]:
|
||||
query = db.query(Equivalency).filter(
|
||||
Equivalency.tenant_id == tenant_id,
|
||||
Equivalency.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Equivalency]:
|
||||
return db.query(Equivalency).filter(
|
||||
Equivalency.id == id,
|
||||
Equivalency.tenant_id == tenant_id,
|
||||
Equivalency.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: EquivalencyCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Equivalency:
|
||||
db_obj = Equivalency(
|
||||
identifier=data.identifier,
|
||||
description=data.description,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
|
||||
if data.items:
|
||||
for item_data in data.items:
|
||||
item = EquivalencyItem(
|
||||
**item_data.model_dump(),
|
||||
equivalency_id=db_obj.id,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: EquivalencyUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Equivalency]:
|
||||
db_obj = EquivalencyService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
if key != 'items': # Handle items separately if needed, or ignore for now as per original code
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = EquivalencyService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def create_equivalency(session: Session, data: EquivalencyCreate) -> Equivalency:
|
||||
db_obj = Equivalency(identifier=data.identifier,
|
||||
description=data.description)
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
class EquivalencyItemService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[EquivalencyItem], int]:
|
||||
query = db.query(EquivalencyItem).filter(
|
||||
EquivalencyItem.tenant_id == tenant_id,
|
||||
EquivalencyItem.company_id == company_id
|
||||
)
|
||||
|
||||
if data.items:
|
||||
for item_data in data.items:
|
||||
item = EquivalencyItem(
|
||||
**item_data.model_dump(), equivalency_id=db_obj.id)
|
||||
session.add(item)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
if filters and "equivalency_id" in filters:
|
||||
query = query.filter(
|
||||
EquivalencyItem.equivalency_id == filters["equivalency_id"])
|
||||
|
||||
return db_obj
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[EquivalencyItem]:
|
||||
return db.query(EquivalencyItem).filter(
|
||||
EquivalencyItem.id == id,
|
||||
EquivalencyItem.tenant_id == tenant_id,
|
||||
EquivalencyItem.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_equivalency(session: Session, id: int) -> Optional[Equivalency]:
|
||||
stmt = select(Equivalency).where(Equivalency.id == id)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().first()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: EquivalencyItemCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> EquivalencyItem:
|
||||
# Note: equivalency_id should be in data or handled by the caller if it's a nested route
|
||||
# But TenantCRUDRoutes for child resources might pass it in filters or we need to handle it.
|
||||
# For now, assuming it's in data or we don't use child resource feature yet.
|
||||
|
||||
# If using child resource, the parent_id is usually passed in the path.
|
||||
# But TenantCRUDRoutes passes the body.
|
||||
|
||||
def get_equivalencies(session: Session, skip: int = 0, limit: int = 100) -> Sequence[Equivalency]:
|
||||
stmt = select(Equivalency).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
db_obj = EquivalencyItem(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def create_nested(
|
||||
db: Session,
|
||||
equivalency_id: int,
|
||||
data: EquivalencyItemCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> EquivalencyItem:
|
||||
db_obj = EquivalencyItem(
|
||||
**data.model_dump(),
|
||||
equivalency_id=equivalency_id,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update_equivalency(session: Session, db_obj: Equivalency, update_data: EquivalencyUpdate) -> Equivalency:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: EquivalencyItemUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[EquivalencyItem]:
|
||||
db_obj = EquivalencyItemService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
def delete_equivalency(session: Session, db_obj: Equivalency) -> Equivalency:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
# Equivalency Item Services
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = EquivalencyItemService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
|
||||
def create_equivalency_item(session: Session, equivalency_id: int, data: EquivalencyItemCreate) -> EquivalencyItem:
|
||||
db_obj = EquivalencyItem(
|
||||
**data.model_dump(), equivalency_id=equivalency_id)
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
def get_equivalency_item(session: Session, id: int) -> Optional[EquivalencyItem]:
|
||||
return session.get(EquivalencyItem, id)
|
||||
|
||||
|
||||
def update_equivalency_item(session: Session, db_obj: EquivalencyItem, update_data: EquivalencyItemUpdate) -> EquivalencyItem:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
def delete_equivalency_item(session: Session, db_obj: EquivalencyItem) -> EquivalencyItem:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -4,10 +4,12 @@ Rutas para gestión de catálogos de errores
|
||||
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import (
|
||||
ErrorClassificationCreateDTO,
|
||||
ErrorClassificationResponseDTO,
|
||||
@@ -23,73 +25,39 @@ from .service import ErrorClassificationService, ErrorCatalogService
|
||||
|
||||
router = APIRouter(prefix="/error-catalogs", tags=["error-catalogs"])
|
||||
|
||||
|
||||
# ============ ERROR CLASSIFICATIONS ENDPOINTS ============
|
||||
@router.get(
|
||||
"/classifications",
|
||||
response_model=dict,
|
||||
summary="Get all error classifications",
|
||||
|
||||
classification_crud = TenantCRUDRoutes(
|
||||
service=ErrorClassificationService,
|
||||
create_schema=ErrorClassificationCreateDTO,
|
||||
update_schema=ErrorClassificationUpdateDTO,
|
||||
response_schema=ErrorClassificationResponseDTO,
|
||||
prefix="/classifications",
|
||||
tags=["error-classifications"],
|
||||
resource_name="ErrorClassification",
|
||||
enable_list=True,
|
||||
)
|
||||
async def get_all_classifications(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
code: str = Query(None),
|
||||
level: str = Query(None),
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all error classifications with optional filtering and pagination"""
|
||||
filters = {}
|
||||
if code:
|
||||
filters["code"] = code
|
||||
if level:
|
||||
filters["level"] = level
|
||||
|
||||
classifications, total = ErrorClassificationService.get_all(
|
||||
db, skip, limit, filters
|
||||
)
|
||||
|
||||
return {
|
||||
"data": [
|
||||
ErrorClassificationResponseDTO.model_validate(classification)
|
||||
for classification in classifications
|
||||
],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
# Add custom endpoints for classifications
|
||||
|
||||
|
||||
@router.get(
|
||||
"/classifications/{classification_id}",
|
||||
response_model=ErrorClassificationDetailResponseDTO,
|
||||
summary="Get error classification by ID with errors",
|
||||
)
|
||||
async def get_classification(
|
||||
classification_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get an error classification by its ID with all related errors"""
|
||||
classification = ErrorClassificationService.get_by_id(
|
||||
db, classification_id)
|
||||
if not classification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Classification not found",
|
||||
)
|
||||
return ErrorClassificationDetailResponseDTO.model_validate(classification)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/classifications/code/{code}",
|
||||
@classification_crud.router.get(
|
||||
"/code/{code}",
|
||||
response_model=ErrorClassificationDetailResponseDTO,
|
||||
summary="Get error classification by code with errors",
|
||||
)
|
||||
async def get_classification_by_code(
|
||||
code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get an error classification by its code with all related errors"""
|
||||
classification = ErrorClassificationService.get_by_code(db, code)
|
||||
classification = ErrorClassificationService.get_by_code(
|
||||
db,
|
||||
code,
|
||||
tenant_id=current_user["tenant_id"],
|
||||
company_id=current_user["company_id"]
|
||||
)
|
||||
if not classification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -97,118 +65,53 @@ async def get_classification_by_code(
|
||||
)
|
||||
return ErrorClassificationDetailResponseDTO.model_validate(classification)
|
||||
|
||||
# Override get_by_id to return detail DTO
|
||||
|
||||
@router.post(
|
||||
"/classifications",
|
||||
response_model=ErrorClassificationResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create error classification",
|
||||
|
||||
@classification_crud.router.get(
|
||||
"/{id}",
|
||||
response_model=ErrorClassificationDetailResponseDTO,
|
||||
summary="Get error classification by ID with errors",
|
||||
)
|
||||
async def create_classification(
|
||||
classification_data: ErrorClassificationCreateDTO,
|
||||
async def get_classification(
|
||||
id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new error classification"""
|
||||
classification = ErrorClassificationService.create(db, classification_data)
|
||||
return ErrorClassificationResponseDTO.model_validate(classification)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/classifications/{classification_id}",
|
||||
response_model=ErrorClassificationResponseDTO,
|
||||
summary="Update error classification",
|
||||
)
|
||||
async def update_classification(
|
||||
classification_id: int,
|
||||
classification_data: ErrorClassificationUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Update an error classification"""
|
||||
classification = ErrorClassificationService.update(
|
||||
db, classification_id, classification_data
|
||||
"""Get an error classification by its ID with all related errors"""
|
||||
classification = ErrorClassificationService.get_by_id(
|
||||
db,
|
||||
id,
|
||||
tenant_id=current_user["tenant_id"],
|
||||
company_id=current_user["company_id"]
|
||||
)
|
||||
if not classification:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Classification not found",
|
||||
)
|
||||
return ErrorClassificationResponseDTO.model_validate(classification)
|
||||
return ErrorClassificationDetailResponseDTO.model_validate(classification)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/classifications/{classification_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete error classification",
|
||||
)
|
||||
async def delete_classification(
|
||||
classification_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Delete an error classification"""
|
||||
success = ErrorClassificationService.delete(db, classification_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Classification not found",
|
||||
)
|
||||
return None
|
||||
router.include_router(classification_crud.router)
|
||||
|
||||
|
||||
# ============ ERROR CATALOG ENDPOINTS ============
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="Get all errors in catalog",
|
||||
|
||||
catalog_crud = TenantCRUDRoutes(
|
||||
service=ErrorCatalogService,
|
||||
create_schema=ErrorCatalogCreateDTO,
|
||||
update_schema=ErrorCatalogUpdateDTO,
|
||||
response_schema=ErrorCatalogResponseDTO,
|
||||
prefix="",
|
||||
tags=["error-catalogs"],
|
||||
resource_name="ErrorCatalog",
|
||||
enable_list=True,
|
||||
)
|
||||
async def get_all_errors(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
code: str = Query(None),
|
||||
description: str = Query(None),
|
||||
classification_code: str = Query(None),
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all error catalogs with optional filtering and pagination"""
|
||||
filters = {}
|
||||
if code:
|
||||
filters["code"] = code
|
||||
if description:
|
||||
filters["description"] = description
|
||||
if classification_code:
|
||||
filters["classification_code"] = classification_code
|
||||
|
||||
catalogs, total = ErrorCatalogService.get_all(db, skip, limit, filters)
|
||||
|
||||
return {
|
||||
"data": [
|
||||
ErrorCatalogResponseDTO.model_validate(catalog) for catalog in catalogs
|
||||
],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
# Add custom endpoints for catalogs
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{error_id}",
|
||||
response_model=ErrorCatalogDetailResponseDTO,
|
||||
summary="Get error by ID",
|
||||
)
|
||||
async def get_error(
|
||||
error_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get an error by its ID with classification details"""
|
||||
error = ErrorCatalogService.get_by_id(db, error_id)
|
||||
if not error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Error not found",
|
||||
)
|
||||
return ErrorCatalogDetailResponseDTO.model_validate(error)
|
||||
|
||||
|
||||
@router.get(
|
||||
@catalog_crud.router.get(
|
||||
"/code/{code}",
|
||||
response_model=ErrorCatalogDetailResponseDTO,
|
||||
summary="Get error by code",
|
||||
@@ -216,9 +119,15 @@ async def get_error(
|
||||
async def get_error_by_code(
|
||||
code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get an error by its code with classification details"""
|
||||
error = ErrorCatalogService.get_by_code(db, code)
|
||||
error = ErrorCatalogService.get_by_code(
|
||||
db,
|
||||
code,
|
||||
tenant_id=current_user["tenant_id"],
|
||||
company_id=current_user["company_id"]
|
||||
)
|
||||
if not error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -227,61 +136,7 @@ async def get_error_by_code(
|
||||
return ErrorCatalogDetailResponseDTO.model_validate(error)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ErrorCatalogResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create error in catalog",
|
||||
)
|
||||
async def create_error(
|
||||
error_data: ErrorCatalogCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Create a new error in the catalog"""
|
||||
error = ErrorCatalogService.create(db, error_data)
|
||||
return ErrorCatalogResponseDTO.model_validate(error)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{error_id}",
|
||||
response_model=ErrorCatalogResponseDTO,
|
||||
summary="Update error in catalog",
|
||||
)
|
||||
async def update_error(
|
||||
error_id: int,
|
||||
error_data: ErrorCatalogUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Update an error in the catalog"""
|
||||
error = ErrorCatalogService.update(db, error_id, error_data)
|
||||
if not error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Error not found",
|
||||
)
|
||||
return ErrorCatalogResponseDTO.model_validate(error)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{error_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete error from catalog",
|
||||
)
|
||||
async def delete_error(
|
||||
error_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Delete an error from the catalog"""
|
||||
success = ErrorCatalogService.delete(db, error_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Error not found",
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@router.get(
|
||||
@catalog_crud.router.get(
|
||||
"/classification/{classification_id}",
|
||||
response_model=List[ErrorCatalogResponseDTO],
|
||||
summary="Get errors by classification",
|
||||
@@ -289,7 +144,42 @@ async def delete_error(
|
||||
async def get_errors_by_classification(
|
||||
classification_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get all errors for a specific classification"""
|
||||
errors = ErrorCatalogService.get_by_classification(db, classification_id)
|
||||
errors = ErrorCatalogService.get_by_classification(
|
||||
db,
|
||||
classification_id,
|
||||
tenant_id=current_user["tenant_id"],
|
||||
company_id=current_user["company_id"]
|
||||
)
|
||||
return [ErrorCatalogResponseDTO.model_validate(error) for error in errors]
|
||||
|
||||
# Override get_by_id to return detail DTO
|
||||
|
||||
|
||||
@catalog_crud.router.get(
|
||||
"/{id}",
|
||||
response_model=ErrorCatalogDetailResponseDTO,
|
||||
summary="Get error by ID",
|
||||
)
|
||||
async def get_error(
|
||||
id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get an error by its ID with classification details"""
|
||||
error = ErrorCatalogService.get_by_id(
|
||||
db,
|
||||
id,
|
||||
tenant_id=current_user["tenant_id"],
|
||||
company_id=current_user["company_id"]
|
||||
)
|
||||
if not error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Error not found",
|
||||
)
|
||||
return ErrorCatalogDetailResponseDTO.model_validate(error)
|
||||
|
||||
router.include_router(catalog_crud.router)
|
||||
|
||||
@@ -25,18 +25,20 @@ logger = logging.getLogger(__name__)
|
||||
class ErrorClassificationService:
|
||||
"""Servicio para gestión de clasificaciones de errores"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[ErrorClassification], int]:
|
||||
"""Get all error classifications with pagination"""
|
||||
query = db.query(ErrorClassification)
|
||||
query = db.query(ErrorClassification).filter(
|
||||
ErrorClassification.tenant_id == tenant_id,
|
||||
ErrorClassification.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
if filters.get("code"):
|
||||
@@ -54,31 +56,45 @@ class ErrorClassificationService:
|
||||
return classifications, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_code(db: Session, code: str) -> Optional[ErrorClassification]:
|
||||
def get_by_code(
|
||||
db: Session, code: str, tenant_id: int, company_id: int
|
||||
) -> Optional[ErrorClassification]:
|
||||
"""Get error classification by code"""
|
||||
return (
|
||||
db.query(ErrorClassification)
|
||||
.filter(ErrorClassification.code == code)
|
||||
.filter(
|
||||
ErrorClassification.code == code,
|
||||
ErrorClassification.tenant_id == tenant_id,
|
||||
ErrorClassification.company_id == company_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, classification_id: int) -> Optional[ErrorClassification]:
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[ErrorClassification]:
|
||||
"""Get error classification by ID"""
|
||||
return (
|
||||
db.query(ErrorClassification)
|
||||
.filter(ErrorClassification.id == classification_id)
|
||||
.filter(
|
||||
ErrorClassification.id == id,
|
||||
ErrorClassification.tenant_id == tenant_id,
|
||||
ErrorClassification.company_id == company_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session, classification_data: ErrorClassificationCreateDTO
|
||||
db: Session, classification_data: ErrorClassificationCreateDTO, tenant_id: int, company_id: int
|
||||
) -> ErrorClassification:
|
||||
"""Create a new error classification"""
|
||||
try:
|
||||
db_classification = ErrorClassification(
|
||||
**classification_data.model_dump(exclude_unset=True)
|
||||
**classification_data.model_dump(exclude_unset=True),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
|
||||
db.add(db_classification)
|
||||
@@ -104,18 +120,12 @@ class ErrorClassificationService:
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
classification_id: int,
|
||||
classification_data: ErrorClassificationUpdateDTO,
|
||||
db: Session, id: int, tenant_id: int, classification_data: ErrorClassificationUpdateDTO, company_id: int
|
||||
) -> Optional[ErrorClassification]:
|
||||
"""Update an error classification"""
|
||||
try:
|
||||
db_classification = (
|
||||
db.query(ErrorClassification)
|
||||
.filter(ErrorClassification.id == classification_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
db_classification = ErrorClassificationService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_classification:
|
||||
return None
|
||||
|
||||
@@ -124,7 +134,6 @@ class ErrorClassificationService:
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_classification)
|
||||
|
||||
return db_classification
|
||||
|
||||
except IntegrityError as e:
|
||||
@@ -143,21 +152,18 @@ class ErrorClassificationService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, classification_id: int) -> bool:
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete an error classification"""
|
||||
try:
|
||||
db_classification = (
|
||||
db.query(ErrorClassification)
|
||||
.filter(ErrorClassification.id == classification_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
db_classification = ErrorClassificationService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_classification:
|
||||
return False
|
||||
|
||||
db.delete(db_classification)
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
@@ -171,18 +177,20 @@ class ErrorClassificationService:
|
||||
class ErrorCatalogService:
|
||||
"""Servicio para gestión de catálogos de errores"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[ErrorCatalog], int]:
|
||||
"""Get all error catalogs with pagination"""
|
||||
query = db.query(ErrorCatalog)
|
||||
query = db.query(ErrorCatalog).filter(
|
||||
ErrorCatalog.tenant_id == tenant_id,
|
||||
ErrorCatalog.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
if filters.get("code"):
|
||||
@@ -204,35 +212,57 @@ class ErrorCatalogService:
|
||||
return catalogs, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_code(db: Session, code: str) -> Optional[ErrorCatalog]:
|
||||
def get_by_code(
|
||||
db: Session, code: str, tenant_id: int, company_id: int
|
||||
) -> Optional[ErrorCatalog]:
|
||||
"""Get error catalog by code"""
|
||||
return db.query(ErrorCatalog).filter(ErrorCatalog.code == code).first()
|
||||
return db.query(ErrorCatalog).filter(
|
||||
ErrorCatalog.code == code,
|
||||
ErrorCatalog.tenant_id == tenant_id,
|
||||
ErrorCatalog.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, error_id: int) -> Optional[ErrorCatalog]:
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[ErrorCatalog]:
|
||||
"""Get error catalog by ID"""
|
||||
return db.query(ErrorCatalog).filter(ErrorCatalog.id == error_id).first()
|
||||
return db.query(ErrorCatalog).filter(
|
||||
ErrorCatalog.id == id,
|
||||
ErrorCatalog.tenant_id == tenant_id,
|
||||
ErrorCatalog.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_classification(
|
||||
db: Session, classification_id: int
|
||||
db: Session, classification_id: int, tenant_id: int, company_id: int
|
||||
) -> List[ErrorCatalog]:
|
||||
"""Get all errors by classification"""
|
||||
return (
|
||||
db.query(ErrorCatalog)
|
||||
.filter(ErrorCatalog.classification_id == classification_id)
|
||||
.filter(
|
||||
ErrorCatalog.classification_id == classification_id,
|
||||
ErrorCatalog.tenant_id == tenant_id,
|
||||
ErrorCatalog.company_id == company_id
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, error_data: ErrorCatalogCreateDTO) -> ErrorCatalog:
|
||||
def create(
|
||||
db: Session, error_data: ErrorCatalogCreateDTO, tenant_id: int, company_id: int
|
||||
) -> ErrorCatalog:
|
||||
"""Create a new error catalog"""
|
||||
try:
|
||||
# Validate classification exists if provided
|
||||
if error_data.classification_id:
|
||||
classification = (
|
||||
db.query(ErrorClassification)
|
||||
.filter(ErrorClassification.id == error_data.classification_id)
|
||||
.filter(
|
||||
ErrorClassification.id == error_data.classification_id,
|
||||
ErrorClassification.tenant_id == tenant_id,
|
||||
ErrorClassification.company_id == company_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not classification:
|
||||
@@ -242,7 +272,10 @@ class ErrorCatalogService:
|
||||
)
|
||||
|
||||
db_error = ErrorCatalog(
|
||||
**error_data.model_dump(exclude_unset=True))
|
||||
**error_data.model_dump(exclude_unset=True),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
|
||||
db.add(db_error)
|
||||
db.commit()
|
||||
@@ -263,30 +296,17 @@ class ErrorCatalogService:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating error catalog: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error creating error catalog")
|
||||
status_code=500, detail="Error creating error catalog"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session, error_id: int, error_data: ErrorCatalogUpdateDTO
|
||||
db: Session, id: int, tenant_id: int, error_data: ErrorCatalogUpdateDTO, company_id: int
|
||||
) -> Optional[ErrorCatalog]:
|
||||
"""Update an error catalog"""
|
||||
try:
|
||||
# Validate classification exists if provided
|
||||
if error_data.classification_id:
|
||||
classification = (
|
||||
db.query(ErrorClassification)
|
||||
.filter(ErrorClassification.id == error_data.classification_id)
|
||||
.first()
|
||||
)
|
||||
if not classification:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Classification not found",
|
||||
)
|
||||
|
||||
db_error = db.query(ErrorCatalog).filter(
|
||||
ErrorCatalog.id == error_id).first()
|
||||
|
||||
db_error = ErrorCatalogService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_error:
|
||||
return None
|
||||
|
||||
@@ -295,11 +315,8 @@ class ErrorCatalogService:
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_error)
|
||||
|
||||
return db_error
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError updating error catalog: {str(e)}")
|
||||
@@ -315,22 +332,23 @@ class ErrorCatalogService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, error_id: int) -> bool:
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete an error catalog"""
|
||||
try:
|
||||
db_error = db.query(ErrorCatalog).filter(
|
||||
ErrorCatalog.id == error_id).first()
|
||||
|
||||
db_error = ErrorCatalogService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_error:
|
||||
return False
|
||||
|
||||
db.delete(db_error)
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting error catalog: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error deleting error catalog")
|
||||
status_code=500, detail="Error deleting error catalog"
|
||||
)
|
||||
|
||||
@@ -26,7 +26,8 @@ class ExchangeRateService:
|
||||
# Apply filters if provided
|
||||
if filters:
|
||||
if filters.get("date"):
|
||||
query = query.filter(models.ExchangeRate.date == filters["date"])
|
||||
query = query.filter(
|
||||
models.ExchangeRate.date == filters["date"])
|
||||
if filters.get("local_currency"):
|
||||
query = query.filter(
|
||||
models.ExchangeRate.local_currency == filters["local_currency"]
|
||||
@@ -37,7 +38,8 @@ class ExchangeRateService:
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
exchange_rates = query.order_by(models.ExchangeRate.date.desc()).offset(skip).limit(limit).all()
|
||||
exchange_rates = query.order_by(
|
||||
models.ExchangeRate.date.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
return exchange_rates, total
|
||||
|
||||
@@ -76,9 +78,9 @@ class ExchangeRateService:
|
||||
def update(
|
||||
db: Session,
|
||||
exchange_rate_id: int,
|
||||
exchange_rate_data: dto.ExchangeRateUpdateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
exchange_rate_data: dto.ExchangeRateUpdateDTO,
|
||||
) -> Optional[models.ExchangeRate]:
|
||||
"""Update an exchange rate"""
|
||||
exchange_rate = ExchangeRateService.get_by_id(
|
||||
|
||||
@@ -1,142 +1,36 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user, validate_access_to_resource
|
||||
from . import service
|
||||
from fastapi import APIRouter
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import (
|
||||
IdentifierCreate, IdentifierResponse, IdentifierUpdate,
|
||||
IdentifierDetailCreate, IdentifierDetailResponse, IdentifierDetailUpdate
|
||||
)
|
||||
from .service import IdentifierService, IdentifierDetailService
|
||||
|
||||
router = APIRouter(prefix="/identifiers",
|
||||
tags=["a76.general_catalogs.identifiers"])
|
||||
|
||||
# Identifier Routes
|
||||
# Identifier CRUD
|
||||
identifier_crud = TenantCRUDRoutes(
|
||||
create_schema=IdentifierCreate,
|
||||
update_schema=IdentifierUpdate,
|
||||
response_schema=IdentifierResponse,
|
||||
service=IdentifierService,
|
||||
prefix="",
|
||||
tags=["Identifiers"],
|
||||
resource_name="Identifier",
|
||||
enable_list=True
|
||||
)
|
||||
|
||||
# Identifier Detail CRUD
|
||||
detail_crud = TenantCRUDRoutes(
|
||||
create_schema=IdentifierDetailCreate,
|
||||
update_schema=IdentifierDetailUpdate,
|
||||
response_schema=IdentifierDetailResponse,
|
||||
service=IdentifierDetailService,
|
||||
prefix="/details",
|
||||
tags=["Identifier Details"],
|
||||
resource_name="Identifier Detail"
|
||||
)
|
||||
|
||||
@router.post("/", response_model=IdentifierResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_identifier(
|
||||
data: IdentifierCreate,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
tenant_id = validate_access_to_resource(
|
||||
session, data.company_id, current_user)
|
||||
return service.create_identifier(session, data, tenant_id)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=IdentifierResponse)
|
||||
def get_identifier(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_identifier(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Identifier not found")
|
||||
# Validate access to the resource's company
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[IdentifierResponse])
|
||||
def get_identifiers(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
# Note: Listing usually requires filtering by company/tenant, but for simplicity here we just list.
|
||||
# In a real scenario, we should filter by tenant_id from token or company_id query param.
|
||||
# For now, we just return all, assuming the service might filter later or this is admin only.
|
||||
# But since we need to validate access, we should probably ask for company_id in query.
|
||||
# However, to keep it simple and consistent with previous modules (which didn't have this check),
|
||||
# I will just return the list. But the user asked for validation.
|
||||
# If I don't have a company_id to validate against, I can't validate.
|
||||
# I'll leave it as is for list, but individual access is validated.
|
||||
return service.get_identifiers(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=IdentifierResponse)
|
||||
def update_identifier(
|
||||
id: int,
|
||||
data: IdentifierUpdate,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_identifier(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Identifier not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return service.update_identifier(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=IdentifierResponse)
|
||||
def delete_identifier(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_identifier(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Identifier not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return service.delete_identifier(session, db_obj)
|
||||
|
||||
# Identifier Detail Routes
|
||||
|
||||
|
||||
@router.post("/details", response_model=IdentifierDetailResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_identifier_detail(
|
||||
data: IdentifierDetailCreate,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
tenant_id = validate_access_to_resource(
|
||||
session, data.company_id, current_user)
|
||||
return service.create_identifier_detail(session, data, tenant_id)
|
||||
|
||||
|
||||
@router.get("/details/{id}", response_model=IdentifierDetailResponse)
|
||||
def get_identifier_detail(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_identifier_detail(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Identifier Detail not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.put("/details/{id}", response_model=IdentifierDetailResponse)
|
||||
def update_identifier_detail(
|
||||
id: int,
|
||||
data: IdentifierDetailUpdate,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_identifier_detail(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Identifier Detail not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return service.update_identifier_detail(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/details/{id}", response_model=IdentifierDetailResponse)
|
||||
def delete_identifier_detail(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db),
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
db_obj = service.get_identifier_detail(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Identifier Detail not found")
|
||||
validate_access_to_resource(session, db_obj.company_id, current_user)
|
||||
return service.delete_identifier_detail(session, db_obj)
|
||||
router.include_router(identifier_crud.router)
|
||||
router.include_router(detail_crud.router)
|
||||
|
||||
@@ -1,71 +1,196 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Sequence, Optional
|
||||
from sqlalchemy import select
|
||||
|
||||
from .models import Identifier, IdentifierDetail
|
||||
from .dto import IdentifierCreate, IdentifierUpdate, IdentifierDetailCreate, IdentifierDetailUpdate
|
||||
|
||||
# Identifier Services
|
||||
|
||||
class IdentifierService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[Identifier], int]:
|
||||
query = db.query(Identifier).filter(
|
||||
Identifier.tenant_id == tenant_id,
|
||||
Identifier.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Identifier]:
|
||||
return db.query(Identifier).filter(
|
||||
Identifier.id == id,
|
||||
Identifier.tenant_id == tenant_id,
|
||||
Identifier.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: IdentifierCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Identifier:
|
||||
# Exclude company_id from data as it is passed separately
|
||||
data_dict = data.model_dump()
|
||||
if 'company_id' in data_dict:
|
||||
del data_dict['company_id']
|
||||
|
||||
db_obj = Identifier(
|
||||
**data_dict,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: IdentifierUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Identifier]:
|
||||
db_obj = IdentifierService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = IdentifierService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def create_identifier(session: Session, data: IdentifierCreate, tenant_id: int) -> Identifier:
|
||||
db_obj = Identifier(**data.model_dump(), tenant_id=tenant_id)
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class IdentifierDetailService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[IdentifierDetail], int]:
|
||||
query = db.query(IdentifierDetail).filter(
|
||||
IdentifierDetail.tenant_id == tenant_id,
|
||||
IdentifierDetail.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
def get_identifier(session: Session, id: int) -> Optional[Identifier]:
|
||||
return session.get(Identifier, id)
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[IdentifierDetail]:
|
||||
return db.query(IdentifierDetail).filter(
|
||||
IdentifierDetail.id == id,
|
||||
IdentifierDetail.tenant_id == tenant_id,
|
||||
IdentifierDetail.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_identifiers(session: Session, skip: int = 0, limit: int = 100) -> Sequence[Identifier]:
|
||||
return session.query(Identifier).offset(skip).limit(limit).all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: IdentifierDetailCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> IdentifierDetail:
|
||||
# Exclude company_id from data as it is passed separately
|
||||
data_dict = data.model_dump()
|
||||
if 'company_id' in data_dict:
|
||||
del data_dict['company_id']
|
||||
|
||||
db_obj = IdentifierDetail(
|
||||
**data_dict,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update_identifier(session: Session, db_obj: Identifier, update_data: IdentifierUpdate) -> Identifier:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: IdentifierDetailUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[IdentifierDetail]:
|
||||
db_obj = IdentifierDetailService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
def delete_identifier(session: Session, db_obj: Identifier) -> Identifier:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
# Identifier Detail Services
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = IdentifierDetailService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
|
||||
def create_identifier_detail(session: Session, data: IdentifierDetailCreate, tenant_id: int) -> IdentifierDetail:
|
||||
db_obj = IdentifierDetail(**data.model_dump(), tenant_id=tenant_id)
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
def get_identifier_detail(session: Session, id: int) -> Optional[IdentifierDetail]:
|
||||
return session.get(IdentifierDetail, id)
|
||||
|
||||
|
||||
def get_identifier_details(session: Session, skip: int = 0, limit: int = 100) -> Sequence[IdentifierDetail]:
|
||||
return session.query(IdentifierDetail).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def update_identifier_detail(session: Session, db_obj: IdentifierDetail, update_data: IdentifierDetailUpdate) -> IdentifierDetail:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
def delete_identifier_detail(session: Session, db_obj: IdentifierDetail) -> IdentifierDetail:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -9,7 +9,8 @@ from core.database import Base
|
||||
class INPC(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "inpc"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("year", "month", name="uq_inpc_year_month"),
|
||||
UniqueConstraint("year", "month", "tenant_id",
|
||||
"company_id", name="uq_inpc_year_month"),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,60 +1,20 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from fastapi import APIRouter
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .models import INPC
|
||||
from .dto import INPCCreate, INPCResponse, INPCUpdate
|
||||
from .service import INPCService
|
||||
|
||||
router = APIRouter(prefix="/inpc", tags=["a76.general_catalogs.inpc"])
|
||||
|
||||
inpc_crud = TenantCRUDRoutes(
|
||||
service=INPCService,
|
||||
create_schema=INPCCreate,
|
||||
update_schema=INPCUpdate,
|
||||
response_schema=INPCResponse,
|
||||
prefix="",
|
||||
tags=["INPC"],
|
||||
resource_name="INPC",
|
||||
enable_list=True,
|
||||
)
|
||||
|
||||
@router.post("/", response_model=INPCResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_inpc(
|
||||
data: INPCCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_inpc(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=INPCResponse)
|
||||
def get_inpc(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_inpc(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="INPC not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[INPCResponse])
|
||||
def get_inpcs(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_inpcs(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=INPCResponse)
|
||||
def update_inpc(
|
||||
id: int,
|
||||
data: INPCUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_inpc(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="INPC not found")
|
||||
return service.update_inpc(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=INPCResponse)
|
||||
def delete_inpc(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_inpc(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="INPC not found")
|
||||
return service.delete_inpc(session, db_obj)
|
||||
router.include_router(inpc_crud.router)
|
||||
|
||||
@@ -1,39 +1,95 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import INPC
|
||||
from .dto import INPCCreate, INPCUpdate
|
||||
|
||||
|
||||
def create_inpc(session: Session, data: INPCCreate) -> INPC:
|
||||
db_obj = INPC(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class INPCService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[INPC], int]:
|
||||
query = db.query(INPC).filter(
|
||||
INPC.tenant_id == tenant_id,
|
||||
INPC.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
def get_inpc(session: Session, id: int) -> Optional[INPC]:
|
||||
return session.get(INPC, id)
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[INPC]:
|
||||
return db.query(INPC).filter(
|
||||
INPC.id == id,
|
||||
INPC.tenant_id == tenant_id,
|
||||
INPC.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_inpcs(session: Session, skip: int = 0, limit: int = 100) -> Sequence[INPC]:
|
||||
stmt = select(INPC).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: INPCCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> INPC:
|
||||
db_obj = INPC(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: INPCUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[INPC]:
|
||||
db_obj = INPCService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_inpc(session: Session, db_obj: INPC, update_data: INPCUpdate) -> INPC:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_inpc(session: Session, db_obj: INPC) -> INPC:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = INPCService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -8,7 +8,8 @@ from core.database import Base
|
||||
class Legend(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "legends"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_legend_code"),
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="uq_legend_code"),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,60 +1,20 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from fastapi import APIRouter
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .models import Legend
|
||||
from .dto import LegendCreate, LegendResponse, LegendUpdate
|
||||
from .service import LegendService
|
||||
|
||||
router = APIRouter(prefix="/legends", tags=["a76.general_catalogs.legends"])
|
||||
|
||||
legend_crud = TenantCRUDRoutes(
|
||||
service=LegendService,
|
||||
create_schema=LegendCreate,
|
||||
update_schema=LegendUpdate,
|
||||
response_schema=LegendResponse,
|
||||
prefix="",
|
||||
tags=["Legends"],
|
||||
resource_name="Legend",
|
||||
enable_list=True,
|
||||
)
|
||||
|
||||
@router.post("/", response_model=LegendResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_legend(
|
||||
data: LegendCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_legend(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=LegendResponse)
|
||||
def get_legend(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_legend(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Legend not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[LegendResponse])
|
||||
def get_legends(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_legends(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=LegendResponse)
|
||||
def update_legend(
|
||||
id: int,
|
||||
data: LegendUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_legend(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Legend not found")
|
||||
return service.update_legend(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=LegendResponse)
|
||||
def delete_legend(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_legend(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Legend not found")
|
||||
return service.delete_legend(session, db_obj)
|
||||
router.include_router(legend_crud.router)
|
||||
|
||||
@@ -1,39 +1,95 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import Legend
|
||||
from .dto import LegendCreate, LegendUpdate
|
||||
|
||||
|
||||
def create_legend(session: Session, data: LegendCreate) -> Legend:
|
||||
db_obj = Legend(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class LegendService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[Legend], int]:
|
||||
query = db.query(Legend).filter(
|
||||
Legend.tenant_id == tenant_id,
|
||||
Legend.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
def get_legend(session: Session, id: int) -> Optional[Legend]:
|
||||
return session.get(Legend, id)
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Legend]:
|
||||
return db.query(Legend).filter(
|
||||
Legend.id == id,
|
||||
Legend.tenant_id == tenant_id,
|
||||
Legend.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_legends(session: Session, skip: int = 0, limit: int = 100) -> Sequence[Legend]:
|
||||
stmt = select(Legend).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: LegendCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Legend:
|
||||
db_obj = Legend(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: LegendUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Legend]:
|
||||
db_obj = LegendService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_legend(session: Session, db_obj: Legend, update_data: LegendUpdate) -> Legend:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_legend(session: Session, db_obj: Legend) -> Legend:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = LegendService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -11,7 +11,7 @@ from api.v1.modules.public.reference_data.countries.models import Country
|
||||
class MultiCurrencyType(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "multi_currency_types"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("currency_type_code", "publication_date",
|
||||
UniqueConstraint("currency_type_code", "publication_date", "tenant_id", "company_id",
|
||||
name="uq_multi_currency_type_code_date"),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
@@ -1,64 +1,21 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from fastapi import APIRouter
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .models import MultiCurrencyType
|
||||
from .dto import MultiCurrencyTypeCreate, MultiCurrencyTypeResponse, MultiCurrencyTypeUpdate
|
||||
from .service import MultiCurrencyTypeService
|
||||
|
||||
router = APIRouter(prefix="/multi-currency-types",
|
||||
tags=["a76.general_catalogs.multi_currency_types"])
|
||||
|
||||
multi_currency_type_crud = TenantCRUDRoutes(
|
||||
service=MultiCurrencyTypeService,
|
||||
create_schema=MultiCurrencyTypeCreate,
|
||||
update_schema=MultiCurrencyTypeUpdate,
|
||||
response_schema=MultiCurrencyTypeResponse,
|
||||
prefix="",
|
||||
tags=["Multi Currency Types"],
|
||||
resource_name="MultiCurrencyType",
|
||||
enable_list=True,
|
||||
)
|
||||
|
||||
@router.post("/", response_model=MultiCurrencyTypeResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_multi_currency_type(
|
||||
data: MultiCurrencyTypeCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_multi_currency_type(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=MultiCurrencyTypeResponse)
|
||||
def get_multi_currency_type(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_multi_currency_type(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="MultiCurrencyType not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[MultiCurrencyTypeResponse])
|
||||
def get_multi_currency_types(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_multi_currency_types(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=MultiCurrencyTypeResponse)
|
||||
def update_multi_currency_type(
|
||||
id: int,
|
||||
data: MultiCurrencyTypeUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_multi_currency_type(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="MultiCurrencyType not found")
|
||||
return service.update_multi_currency_type(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=MultiCurrencyTypeResponse)
|
||||
def delete_multi_currency_type(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_multi_currency_type(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="MultiCurrencyType not found")
|
||||
return service.delete_multi_currency_type(session, db_obj)
|
||||
router.include_router(multi_currency_type_crud.router)
|
||||
|
||||
@@ -1,39 +1,97 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import MultiCurrencyType
|
||||
from .dto import MultiCurrencyTypeCreate, MultiCurrencyTypeUpdate
|
||||
|
||||
|
||||
def create_multi_currency_type(session: Session, data: MultiCurrencyTypeCreate) -> MultiCurrencyType:
|
||||
db_obj = MultiCurrencyType(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class MultiCurrencyTypeService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[MultiCurrencyType], int]:
|
||||
query = db.query(MultiCurrencyType).filter(
|
||||
MultiCurrencyType.tenant_id == tenant_id,
|
||||
MultiCurrencyType.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
def get_multi_currency_type(session: Session, id: int) -> Optional[MultiCurrencyType]:
|
||||
return session.get(MultiCurrencyType, id)
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[MultiCurrencyType]:
|
||||
return db.query(MultiCurrencyType).filter(
|
||||
MultiCurrencyType.id == id,
|
||||
MultiCurrencyType.tenant_id == tenant_id,
|
||||
MultiCurrencyType.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_multi_currency_types(session: Session, skip: int = 0, limit: int = 100) -> Sequence[MultiCurrencyType]:
|
||||
stmt = select(MultiCurrencyType).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: MultiCurrencyTypeCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> MultiCurrencyType:
|
||||
db_obj = MultiCurrencyType(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: MultiCurrencyTypeUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[MultiCurrencyType]:
|
||||
db_obj = MultiCurrencyTypeService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_multi_currency_type(session: Session, db_obj: MultiCurrencyType, update_data: MultiCurrencyTypeUpdate) -> MultiCurrencyType:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_multi_currency_type(session: Session, db_obj: MultiCurrencyType) -> MultiCurrencyType:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = MultiCurrencyTypeService.get_by_id(
|
||||
db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -35,7 +35,8 @@ class PackageService:
|
||||
)
|
||||
if filters.get("description_es"):
|
||||
query = query.filter(
|
||||
models.Package.description_es.ilike(f"%{filters['description_es']}%")
|
||||
models.Package.description_es.ilike(
|
||||
f"%{filters['description_es']}%")
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
@@ -79,16 +80,18 @@ class PackageService:
|
||||
db: Session,
|
||||
package_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
package_data: dto.PackageUpdateDTO,
|
||||
company_id: int,
|
||||
) -> Optional[models.Package]:
|
||||
"""Update a package"""
|
||||
package = PackageService.get_by_id(db, package_id, tenant_id, company_id)
|
||||
package = PackageService.get_by_id(
|
||||
db, package_id, tenant_id, company_id)
|
||||
if not package:
|
||||
return None
|
||||
|
||||
# Update fields (excluding key if it's meant to be immutable)
|
||||
update_data = package_data.model_dump(exclude_unset=True, exclude={"key"})
|
||||
update_data = package_data.model_dump(
|
||||
exclude_unset=True, exclude={"key"})
|
||||
for field, value in update_data.items():
|
||||
setattr(package, field, value)
|
||||
|
||||
@@ -101,7 +104,8 @@ class PackageService:
|
||||
db: Session, package_id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete a package"""
|
||||
package = PackageService.get_by_id(db, package_id, tenant_id, company_id)
|
||||
package = PackageService.get_by_id(
|
||||
db, package_id, tenant_id, company_id)
|
||||
if not package:
|
||||
return False
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class PortType(str, enum.Enum):
|
||||
class Port(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "ports"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("port_code", "location_code",
|
||||
UniqueConstraint("port_code", "location_code", "tenant_id", "company_id",
|
||||
name="uq_port_location"),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
@@ -1,60 +1,16 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from fastapi import APIRouter
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .models import Port
|
||||
from .dto import PortCreate, PortResponse, PortUpdate
|
||||
from .service import PortService
|
||||
|
||||
router = APIRouter(prefix="/ports", tags=["a76.general_catalogs.ports"])
|
||||
|
||||
|
||||
@router.post("/", response_model=PortResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_port(
|
||||
data: PortCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_port(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=PortResponse)
|
||||
def get_port(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_port(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Port not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[PortResponse])
|
||||
def get_ports(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_ports(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=PortResponse)
|
||||
def update_port(
|
||||
id: int,
|
||||
data: PortUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_port(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Port not found")
|
||||
return service.update_port(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=PortResponse)
|
||||
def delete_port(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_port(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Port not found")
|
||||
return service.delete_port(session, db_obj)
|
||||
router = TenantCRUDRoutes(
|
||||
service=PortService,
|
||||
create_schema=PortCreate,
|
||||
update_schema=PortUpdate,
|
||||
response_schema=PortResponse,
|
||||
prefix="/ports",
|
||||
tags=["a76.general_catalogs.ports"],
|
||||
resource_name="Port",
|
||||
enable_list=True,
|
||||
).router
|
||||
|
||||
@@ -1,38 +1,95 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import Port
|
||||
from .dto import PortCreate, PortUpdate
|
||||
|
||||
|
||||
def create_port(session: Session, data: PortCreate) -> Port:
|
||||
db_obj = Port(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class PortService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None
|
||||
) -> Tuple[List[Port], int]:
|
||||
query = db.query(Port).filter(
|
||||
Port.tenant_id == tenant_id,
|
||||
Port.company_id == company_id
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters here if needed
|
||||
pass
|
||||
|
||||
def get_port(session: Session, id: int) -> Optional[Port]:
|
||||
return session.get(Port, id)
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Port]:
|
||||
return db.query(Port).filter(
|
||||
Port.id == id,
|
||||
Port.tenant_id == tenant_id,
|
||||
Port.company_id == company_id
|
||||
).first()
|
||||
|
||||
def get_ports(session: Session, skip: int = 0, limit: int = 100) -> Sequence[Port]:
|
||||
stmt = select(Port).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: PortCreate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Port:
|
||||
db_obj = Port(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: PortUpdate,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Port]:
|
||||
db_obj = PortService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_port(session: Session, db_obj: Port, update_data: PortUpdate) -> Port:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
def delete_port(session: Session, db_obj: Port) -> Port:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> bool:
|
||||
db_obj = PortService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -18,7 +18,8 @@ class Prevalidator(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "prevalidators" # GPrevalidadores
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="prevalidators_pkey"),
|
||||
UniqueConstraint("code", name="prevalidators_code_unique"),
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="prevalidators_code_unique"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
@@ -26,7 +27,7 @@ class Prevalidator(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
|
||||
# Prevalidator code (unique)
|
||||
code: Mapped[str] = mapped_column(String(20), nullable=False, unique=True)
|
||||
code: Mapped[str] = mapped_column(String(20), nullable=False)
|
||||
|
||||
# Prevalidator information
|
||||
customs_prevalidator: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
|
||||
@@ -4,10 +4,12 @@ Rutas para gestión de prevalidadores
|
||||
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import (
|
||||
PrevalidatorCreateDTO,
|
||||
PrevalidatorResponseDTO,
|
||||
@@ -18,63 +20,21 @@ from .service import PrevalidatorService
|
||||
|
||||
router = APIRouter(prefix="/prevalidators", tags=["prevalidators"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="Get all prevalidators",
|
||||
prevalidator_crud = TenantCRUDRoutes(
|
||||
service=PrevalidatorService,
|
||||
create_schema=PrevalidatorCreateDTO,
|
||||
update_schema=PrevalidatorUpdateDTO,
|
||||
response_schema=PrevalidatorResponseDTO,
|
||||
prefix="",
|
||||
tags=["Prevalidators"],
|
||||
resource_name="Prevalidator",
|
||||
enable_list=True,
|
||||
)
|
||||
async def get_all_prevalidators(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
code: str = Query(None),
|
||||
description: str = Query(None),
|
||||
customs_prevalidator: str = Query(None),
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all prevalidators with optional filtering and pagination"""
|
||||
filters = {}
|
||||
if code:
|
||||
filters["code"] = code
|
||||
if description:
|
||||
filters["description"] = description
|
||||
if customs_prevalidator:
|
||||
filters["customs_prevalidator"] = customs_prevalidator
|
||||
|
||||
prevalidators, total = PrevalidatorService.get_all(
|
||||
db, skip, limit, filters)
|
||||
|
||||
return {
|
||||
"data": [
|
||||
PrevalidatorResponseDTO.model_validate(prevalidator)
|
||||
for prevalidator in prevalidators
|
||||
],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
# Add custom endpoints
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{prevalidator_id}",
|
||||
response_model=PrevalidatorResponseDTO,
|
||||
summary="Get prevalidator by ID",
|
||||
)
|
||||
async def get_prevalidator(
|
||||
prevalidator_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get a prevalidator by its ID"""
|
||||
prevalidator = PrevalidatorService.get_by_id(db, prevalidator_id)
|
||||
if not prevalidator:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Prevalidator not found",
|
||||
)
|
||||
return PrevalidatorResponseDTO.model_validate(prevalidator)
|
||||
|
||||
|
||||
@router.get(
|
||||
@prevalidator_crud.router.get(
|
||||
"/code/{code}",
|
||||
response_model=PrevalidatorResponseDTO,
|
||||
summary="Get prevalidator by code",
|
||||
@@ -82,9 +42,15 @@ async def get_prevalidator(
|
||||
async def get_prevalidator_by_code(
|
||||
code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get a prevalidator by its code"""
|
||||
prevalidator = PrevalidatorService.get_by_code(db, code)
|
||||
prevalidator = PrevalidatorService.get_by_code(
|
||||
db,
|
||||
code,
|
||||
tenant_id=current_user["tenant_id"],
|
||||
company_id=current_user["company_id"]
|
||||
)
|
||||
if not prevalidator:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -92,13 +58,9 @@ async def get_prevalidator_by_code(
|
||||
)
|
||||
return PrevalidatorResponseDTO.model_validate(prevalidator)
|
||||
|
||||
router.include_router(prevalidator_crud.router)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=PrevalidatorResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create prevalidator",
|
||||
)
|
||||
async def create_prevalidator(
|
||||
prevalidator_data: PrevalidatorCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
|
||||
@@ -22,18 +22,20 @@ logger = logging.getLogger(__name__)
|
||||
class PrevalidatorService:
|
||||
"""Servicio para gestión de prevalidadores"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[Prevalidator], int]:
|
||||
"""Get all prevalidators with pagination"""
|
||||
query = db.query(Prevalidator)
|
||||
query = db.query(Prevalidator).filter(
|
||||
Prevalidator.tenant_id == tenant_id,
|
||||
Prevalidator.company_id == company_id
|
||||
)
|
||||
|
||||
# Apply filters if provided
|
||||
if filters:
|
||||
@@ -59,21 +61,40 @@ class PrevalidatorService:
|
||||
return prevalidators, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_code(db: Session, code: str) -> Optional[Prevalidator]:
|
||||
def get_by_code(
|
||||
db: Session, code: str, tenant_id: int, company_id: int
|
||||
) -> Optional[Prevalidator]:
|
||||
"""Get prevalidator by code"""
|
||||
return db.query(Prevalidator).filter(Prevalidator.code == code).first()
|
||||
return db.query(Prevalidator).filter(
|
||||
Prevalidator.code == code,
|
||||
Prevalidator.tenant_id == tenant_id,
|
||||
Prevalidator.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, prevalidator_id: int) -> Optional[Prevalidator]:
|
||||
def get_by_id(
|
||||
db: Session, prevalidator_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[Prevalidator]:
|
||||
"""Get prevalidator by ID"""
|
||||
return db.query(Prevalidator).filter(Prevalidator.id == prevalidator_id).first()
|
||||
return db.query(Prevalidator).filter(
|
||||
Prevalidator.id == prevalidator_id,
|
||||
Prevalidator.tenant_id == tenant_id,
|
||||
Prevalidator.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, prevalidator_data: PrevalidatorCreateDTO) -> Prevalidator:
|
||||
def create(
|
||||
db: Session,
|
||||
prevalidator_data: PrevalidatorCreateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Prevalidator:
|
||||
"""Create a new prevalidator"""
|
||||
try:
|
||||
db_prevalidator = Prevalidator(
|
||||
**prevalidator_data.model_dump(exclude_unset=True)
|
||||
**prevalidator_data.model_dump(exclude_unset=True),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
|
||||
db.add(db_prevalidator)
|
||||
@@ -100,13 +121,14 @@ class PrevalidatorService:
|
||||
db: Session,
|
||||
prevalidator_id: int,
|
||||
prevalidator_data: PrevalidatorUpdateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> Optional[Prevalidator]:
|
||||
"""Update a prevalidator"""
|
||||
try:
|
||||
db_prevalidator = db.query(Prevalidator).filter(
|
||||
Prevalidator.id == prevalidator_id
|
||||
).first()
|
||||
|
||||
db_prevalidator = PrevalidatorService.get_by_id(
|
||||
db, prevalidator_id, tenant_id, company_id
|
||||
)
|
||||
if not db_prevalidator:
|
||||
return None
|
||||
|
||||
@@ -133,13 +155,14 @@ class PrevalidatorService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, prevalidator_id: int) -> bool:
|
||||
def delete(
|
||||
db: Session, prevalidator_id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete a prevalidator"""
|
||||
try:
|
||||
db_prevalidator = db.query(Prevalidator).filter(
|
||||
Prevalidator.id == prevalidator_id
|
||||
).first()
|
||||
|
||||
db_prevalidator = PrevalidatorService.get_by_id(
|
||||
db, prevalidator_id, tenant_id, company_id
|
||||
)
|
||||
if not db_prevalidator:
|
||||
return False
|
||||
|
||||
@@ -155,10 +178,16 @@ class PrevalidatorService:
|
||||
status_code=500, detail="Error deleting prevalidator")
|
||||
|
||||
@staticmethod
|
||||
def get_by_customs(db: Session, customs: str) -> List[Prevalidator]:
|
||||
def get_by_customs(
|
||||
db: Session, customs: str, tenant_id: int, company_id: int
|
||||
) -> List[Prevalidator]:
|
||||
"""Get all prevalidators by customs"""
|
||||
return (
|
||||
db.query(Prevalidator)
|
||||
.filter(Prevalidator.customs_prevalidator == customs)
|
||||
.filter(
|
||||
Prevalidator.customs_prevalidator == customs,
|
||||
Prevalidator.tenant_id == tenant_id,
|
||||
Prevalidator.company_id == company_id
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from sqlalchemy import (
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
@@ -13,7 +12,7 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
class Seal(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "seal"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="seal_pkey"),
|
||||
PrimaryKeyConstraint("id", name="seal_pkey"),
|
||||
UniqueConstraint("tenant_id", "company_id", "seal", name="seal_ukey"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
@@ -18,7 +18,8 @@ class Signature(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "signatures" # GFirmas
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="signatures_pkey"),
|
||||
UniqueConstraint("code", name="signatures_code_unique"),
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="signatures_code_unique"),
|
||||
{"schema": "a76"},
|
||||
)
|
||||
|
||||
@@ -27,7 +28,7 @@ class Signature(Base, TenantScopedMixin, TimestampMixin):
|
||||
Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
# Signature code (unique)
|
||||
code: Mapped[str] = mapped_column(String(10), nullable=False, unique=True)
|
||||
code: Mapped[str] = mapped_column(String(10), nullable=False)
|
||||
|
||||
# Signature information
|
||||
signature: Mapped[Optional[str]] = mapped_column(String(1000))
|
||||
|
||||
@@ -2,60 +2,34 @@
|
||||
Rutas para gestión de firmas
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from typing import Any, Dict
|
||||
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource
|
||||
from fastapi import Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import get_core_db
|
||||
|
||||
from .dto import SignatureCreateDTO, SignatureResponseDTO, SignatureUpdateDTO
|
||||
from .models import Signature
|
||||
from .service import SignatureService
|
||||
|
||||
router = APIRouter(prefix="/signatures", tags=["signatures"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=dict,
|
||||
summary="Get all signatures",
|
||||
# Create router using TenantCRUDRoutes factory
|
||||
signature_crud = TenantCRUDRoutes(
|
||||
service=SignatureService,
|
||||
create_schema=SignatureCreateDTO,
|
||||
update_schema=SignatureUpdateDTO,
|
||||
response_schema=SignatureResponseDTO,
|
||||
prefix="/signatures",
|
||||
tags=["signatures"],
|
||||
resource_name="Signature",
|
||||
id_name="id", # Using numeric ID
|
||||
enable_list=True, # Enable GET /signatures with pagination
|
||||
enable_filters=True, # Enable filtering by code
|
||||
default_page_size=50,
|
||||
max_page_size=100,
|
||||
)
|
||||
async def get_all_signatures(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
code: str = Query(None),
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get all signatures with optional filtering and pagination"""
|
||||
filters = {}
|
||||
if code:
|
||||
filters["code"] = code
|
||||
|
||||
signatures, total = SignatureService.get_all(db, skip, limit, filters)
|
||||
|
||||
return {
|
||||
"data": [SignatureResponseDTO.model_validate(sig) for sig in signatures],
|
||||
"total": total,
|
||||
"skip": skip,
|
||||
"limit": limit,
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{signature_id}",
|
||||
response_model=SignatureResponseDTO,
|
||||
summary="Get signature by ID",
|
||||
)
|
||||
async def get_signature(
|
||||
signature_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Get a signature by its ID"""
|
||||
signature = SignatureService.get_by_id(db, signature_id)
|
||||
if not signature:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Signature not found",
|
||||
)
|
||||
return SignatureResponseDTO.model_validate(signature)
|
||||
router = signature_crud.router
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -65,48 +39,16 @@ async def get_signature(
|
||||
)
|
||||
async def get_signature_by_code(
|
||||
code: str,
|
||||
db: Session = Depends(get_core_db),
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(signature_crud.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency),
|
||||
):
|
||||
"""Get a signature by its code"""
|
||||
signature = SignatureService.get_by_code(db, code)
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
signature = SignatureService.get_by_code(db, code, tenant_id, company_id)
|
||||
if not signature:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Signature not found",
|
||||
)
|
||||
return SignatureResponseDTO.model_validate(signature)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=SignatureResponseDTO,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create signature",
|
||||
)
|
||||
async def create_signature(
|
||||
signature_data: SignatureCreateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Create a new signature"""
|
||||
signature = SignatureService.create(db, signature_data)
|
||||
return SignatureResponseDTO.model_validate(signature)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{signature_id}",
|
||||
response_model=SignatureResponseDTO,
|
||||
summary="Update signature",
|
||||
)
|
||||
async def update_signature(
|
||||
signature_id: int,
|
||||
signature_data: SignatureUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
):
|
||||
"""Update a signature"""
|
||||
signature = SignatureService.update(db, signature_id, signature_data)
|
||||
if not signature:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
status_code=404,
|
||||
detail="Signature not found",
|
||||
)
|
||||
return SignatureResponseDTO.model_validate(signature)
|
||||
|
||||
@@ -5,12 +5,9 @@ Capa de servicio para lógica de negocio de firmas
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .dto import SignatureCreateDTO, SignatureResponseDTO, SignatureUpdateDTO
|
||||
from .models import Signature
|
||||
from . import dto, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -18,23 +15,25 @@ logger = logging.getLogger(__name__)
|
||||
class SignatureService:
|
||||
"""Servicio para gestión de firmas"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[Signature], int]:
|
||||
) -> Tuple[List[models.Signature], int]:
|
||||
"""Get all signatures with pagination"""
|
||||
query = db.query(Signature)
|
||||
query = db.query(models.Signature).filter(
|
||||
models.Signature.tenant_id == tenant_id,
|
||||
models.Signature.company_id == company_id,
|
||||
)
|
||||
|
||||
if filters:
|
||||
if filters.get("code"):
|
||||
query = query.filter(
|
||||
Signature.code.ilike(f"%{filters['code']}%"))
|
||||
models.Signature.code.ilike(f"%{filters['code']}%"))
|
||||
|
||||
total = query.count()
|
||||
signatures = query.offset(skip).limit(limit).all()
|
||||
@@ -42,93 +41,82 @@ class SignatureService:
|
||||
return signatures, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, signature_id: int) -> Optional[Signature]:
|
||||
def get_by_id(
|
||||
db: Session, signature_id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[models.Signature]:
|
||||
"""Get signature by ID"""
|
||||
return db.query(Signature).filter(Signature.id == signature_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_code(db: Session, code: str) -> Optional[Signature]:
|
||||
"""Get signature by code"""
|
||||
return db.query(Signature).filter(Signature.code == code).first()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, signature_data: SignatureCreateDTO) -> Signature:
|
||||
"""Create a new signature"""
|
||||
try:
|
||||
db_signature = Signature(
|
||||
**signature_data.model_dump(exclude_unset=True))
|
||||
|
||||
db.add(db_signature)
|
||||
db.commit()
|
||||
db.refresh(db_signature)
|
||||
|
||||
return db_signature
|
||||
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError creating signature: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Signature code already exists",
|
||||
return (
|
||||
db.query(models.Signature)
|
||||
.filter(
|
||||
models.Signature.id == signature_id,
|
||||
models.Signature.tenant_id == tenant_id,
|
||||
models.Signature.company_id == company_id,
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating signature: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error creating signature")
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_by_code(
|
||||
db: Session, code: str, tenant_id: int, company_id: int
|
||||
) -> Optional[models.Signature]:
|
||||
"""Get signature by code"""
|
||||
return (
|
||||
db.query(models.Signature)
|
||||
.filter(
|
||||
models.Signature.code == code,
|
||||
models.Signature.tenant_id == tenant_id,
|
||||
models.Signature.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
signature_data: dto.SignatureCreateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> models.Signature:
|
||||
"""Create a new signature"""
|
||||
new_signature = models.Signature(
|
||||
**signature_data.model_dump(), tenant_id=tenant_id, company_id=company_id
|
||||
)
|
||||
db.add(new_signature)
|
||||
db.commit()
|
||||
db.refresh(new_signature)
|
||||
return new_signature
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session, signature_id: int, signature_data: SignatureUpdateDTO
|
||||
) -> Optional[Signature]:
|
||||
db: Session,
|
||||
signature_id: int,
|
||||
tenant_id: int,
|
||||
signature_data: dto.SignatureUpdateDTO,
|
||||
company_id: int,
|
||||
) -> Optional[models.Signature]:
|
||||
"""Update a signature"""
|
||||
try:
|
||||
db_signature = db.query(Signature).filter(
|
||||
Signature.id == signature_id
|
||||
).first()
|
||||
signature = SignatureService.get_by_id(
|
||||
db, signature_id, tenant_id, company_id)
|
||||
if not signature:
|
||||
return None
|
||||
|
||||
if not db_signature:
|
||||
return None
|
||||
for key, value in signature_data.model_dump(exclude_unset=True).items():
|
||||
setattr(signature, key, value)
|
||||
|
||||
for key, value in signature_data.model_dump(exclude_unset=True).items():
|
||||
setattr(db_signature, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_signature)
|
||||
|
||||
return db_signature
|
||||
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError updating signature: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Error updating signature",
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error updating signature: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error updating signature")
|
||||
db.commit()
|
||||
db.refresh(signature)
|
||||
return signature
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, signature_id: int) -> bool:
|
||||
def delete(
|
||||
db: Session, signature_id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete a signature"""
|
||||
try:
|
||||
db_signature = db.query(Signature).filter(
|
||||
Signature.id == signature_id
|
||||
).first()
|
||||
signature = SignatureService.get_by_id(
|
||||
db, signature_id, tenant_id, company_id)
|
||||
if not signature:
|
||||
return False
|
||||
|
||||
if not db_signature:
|
||||
return False
|
||||
|
||||
db.delete(db_signature)
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting signature: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error deleting signature")
|
||||
db.delete(signature)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Integer, String, ForeignKey, UniqueConstraint, Numeric
|
||||
from sqlalchemy import Integer, String, ForeignKeyConstraint, UniqueConstraint, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
@@ -10,21 +10,27 @@ from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMe
|
||||
class UnitConversion(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "unit_conversions"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("from_unit_code", "to_unit_code",
|
||||
UniqueConstraint("from_unit_code", "to_unit_code", "tenant_id", "company_id",
|
||||
name="uq_unit_conversion_pair"),
|
||||
ForeignKeyConstraint(
|
||||
["from_unit_code", "tenant_id", "company_id"],
|
||||
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id"],
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["to_unit_code", "tenant_id", "company_id"],
|
||||
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id"],
|
||||
),
|
||||
{"schema": "a76"}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
Integer, primary_key=True, autoincrement=True)
|
||||
from_unit_code: Mapped[str] = mapped_column(
|
||||
String(5), ForeignKey("a76.units_of_measure.code"), nullable=False)
|
||||
to_unit_code: Mapped[str] = mapped_column(
|
||||
String(5), ForeignKey("a76.units_of_measure.code"), nullable=False)
|
||||
from_unit_code: Mapped[str] = mapped_column(String(5), nullable=False)
|
||||
to_unit_code: Mapped[str] = mapped_column(String(5), nullable=False)
|
||||
conversion_factor: Mapped[Optional[Decimal]] = mapped_column(
|
||||
Numeric(13, 6), nullable=True)
|
||||
|
||||
from_unit: Mapped["UnitOfMeasure"] = relationship(
|
||||
foreign_keys=[from_unit_code])
|
||||
to_unit: Mapped["UnitOfMeasure"] = relationship(
|
||||
foreign_keys=[to_unit_code])
|
||||
# Relationships
|
||||
# Note: Complex composite foreign keys might require explicit primaryjoin if used
|
||||
|
||||
@@ -1,61 +1,16 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from .dto import UnitConversionCreate, UnitConversionResponse, UnitConversionUpdate
|
||||
from .service import UnitConversionService
|
||||
|
||||
router = APIRouter(prefix="/unit-conversions",
|
||||
tags=["a76.general_catalogs.unit_conversions"])
|
||||
|
||||
|
||||
@router.post("/", response_model=UnitConversionResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_unit_conversion(
|
||||
data: UnitConversionCreate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.create_unit_conversion(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=UnitConversionResponse)
|
||||
def get_unit_conversion(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_unit_conversion(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="UnitConversion not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[UnitConversionResponse])
|
||||
def get_unit_conversions(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
return service.get_unit_conversions(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=UnitConversionResponse)
|
||||
def update_unit_conversion(
|
||||
id: int,
|
||||
data: UnitConversionUpdate,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_unit_conversion(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="UnitConversion not found")
|
||||
return service.update_unit_conversion(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=UnitConversionResponse)
|
||||
def delete_unit_conversion(
|
||||
id: int,
|
||||
session: Session = Depends(get_core_db)
|
||||
):
|
||||
db_obj = service.get_unit_conversion(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="UnitConversion not found")
|
||||
return service.delete_unit_conversion(session, db_obj)
|
||||
router = TenantCRUDRoutes(
|
||||
service=UnitConversionService,
|
||||
create_schema=UnitConversionCreate,
|
||||
update_schema=UnitConversionUpdate,
|
||||
response_schema=UnitConversionResponse,
|
||||
prefix="/unit-conversions",
|
||||
tags=["a76.general_catalogs.unit_conversions"],
|
||||
resource_name="UnitConversion",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
|
||||
@@ -1,39 +1,89 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from .models import UnitConversion
|
||||
from .dto import UnitConversionCreate, UnitConversionUpdate
|
||||
|
||||
|
||||
def create_unit_conversion(session: Session, data: UnitConversionCreate) -> UnitConversion:
|
||||
db_obj = UnitConversion(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
class UnitConversionService:
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[UnitConversion], int]:
|
||||
query = db.query(UnitConversion).filter(
|
||||
UnitConversion.tenant_id == tenant_id,
|
||||
UnitConversion.company_id == company_id,
|
||||
)
|
||||
|
||||
if filters:
|
||||
# Add filters if needed
|
||||
pass
|
||||
|
||||
def get_unit_conversion(session: Session, id: int) -> Optional[UnitConversion]:
|
||||
return session.get(UnitConversion, id)
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[UnitConversion]:
|
||||
return db.query(UnitConversion).filter(
|
||||
UnitConversion.id == id,
|
||||
UnitConversion.tenant_id == tenant_id,
|
||||
UnitConversion.company_id == company_id,
|
||||
).first()
|
||||
|
||||
def get_unit_conversions(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitConversion]:
|
||||
stmt = select(UnitConversion).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
data: UnitConversionCreate,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> UnitConversion:
|
||||
db_obj = UnitConversion(
|
||||
**data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
data: UnitConversionUpdate,
|
||||
company_id: int,
|
||||
) -> Optional[UnitConversion]:
|
||||
db_obj = UnitConversionService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
def update_unit_conversion(session: Session, db_obj: UnitConversion, update_data: UnitConversionUpdate) -> UnitConversion:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete_unit_conversion(session: Session, db_obj: UnitConversion) -> UnitConversion:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
db_obj = UnitConversionService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Integer, String, ForeignKey, UniqueConstraint, Numeric
|
||||
from sqlalchemy import ForeignKey, Integer, String, ForeignKeyConstraint, UniqueConstraint, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
|
||||
# 1. GUniMedACE
|
||||
|
||||
|
||||
class UnitOfMeasureACE(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "unit_of_measure_ace"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_uom_ace_code"),
|
||||
{"schema": "a76"}
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="uq_uom_ace_code"),
|
||||
{"schema": "a76", "extend_existing": True}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
@@ -20,11 +23,14 @@ class UnitOfMeasureACE(Base, TenantScopedMixin, TimestampMixin):
|
||||
String(49), nullable=True)
|
||||
|
||||
# 2. GUMOMA
|
||||
|
||||
|
||||
class UnitOfMeasureOMA(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "unit_of_measure_oma"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_uom_oma_code"),
|
||||
{"schema": "a76"}
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="uq_uom_oma_code"),
|
||||
{"schema": "a76", "extend_existing": True}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
@@ -34,11 +40,14 @@ class UnitOfMeasureOMA(Base, TenantScopedMixin, TimestampMixin):
|
||||
String(200), nullable=True)
|
||||
|
||||
# 3. GUMAme
|
||||
|
||||
|
||||
class UnitOfMeasureAmerican(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "unit_of_measure_american"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_uom_american_code"),
|
||||
{"schema": "a76"}
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="uq_uom_american_code"),
|
||||
{"schema": "a76", "extend_existing": True}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
@@ -48,11 +57,14 @@ class UnitOfMeasureAmerican(Base, TenantScopedMixin, TimestampMixin):
|
||||
String(40), nullable=True)
|
||||
|
||||
# 4. GUMAduana
|
||||
|
||||
|
||||
class UnitOfMeasureCustoms(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "unit_of_measure_customs"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_uom_customs_code"),
|
||||
{"schema": "a76"}
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="uq_uom_customs_code"),
|
||||
{"schema": "a76", "extend_existing": True}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
@@ -64,11 +76,42 @@ class UnitOfMeasureCustoms(Base, TenantScopedMixin, TimestampMixin):
|
||||
String(5), nullable=True) # UNIDADSCAII
|
||||
|
||||
# 5. GUniMedida (Main)
|
||||
|
||||
|
||||
class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "units_of_measure"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_uom_code"),
|
||||
{"schema": "a76"}
|
||||
UniqueConstraint("code", "tenant_id",
|
||||
"company_id", name="uq_uom_code"),
|
||||
ForeignKeyConstraint(
|
||||
["customs_code", "tenant_id", "company_id"],
|
||||
["a76.unit_of_measure_customs.code", "a76.unit_of_measure_customs.tenant_id",
|
||||
"a76.unit_of_measure_customs.company_id"],
|
||||
use_alter=True,
|
||||
name="fk_uom_customs"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["american_code", "tenant_id", "company_id"],
|
||||
["a76.unit_of_measure_american.code", "a76.unit_of_measure_american.tenant_id",
|
||||
"a76.unit_of_measure_american.company_id"],
|
||||
use_alter=True,
|
||||
name="fk_uom_american"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["ace_code", "tenant_id", "company_id"],
|
||||
["a76.unit_of_measure_ace.code", "a76.unit_of_measure_ace.tenant_id",
|
||||
"a76.unit_of_measure_ace.company_id"],
|
||||
use_alter=True,
|
||||
name="fk_uom_ace"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["oma_code", "tenant_id", "company_id"],
|
||||
["a76.unit_of_measure_oma.code", "a76.unit_of_measure_oma.tenant_id",
|
||||
"a76.unit_of_measure_oma.company_id"],
|
||||
use_alter=True,
|
||||
name="fk_uom_oma"
|
||||
),
|
||||
{"schema": "a76", "extend_existing": True}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
@@ -79,26 +122,44 @@ class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin):
|
||||
description_en: Mapped[Optional[str]] = mapped_column(
|
||||
String(100), nullable=True)
|
||||
|
||||
customs_code: Mapped[Optional[str]] = mapped_column(String(2), ForeignKey(
|
||||
"a76.unit_of_measure_customs.code"), nullable=True) # CLAVE_AMEX
|
||||
american_code: Mapped[Optional[str]] = mapped_column(String(3), ForeignKey(
|
||||
"a76.unit_of_measure_american.code"), nullable=True) # CLAVE_AAMER
|
||||
ace_code: Mapped[Optional[str]] = mapped_column(String(4), ForeignKey(
|
||||
"a76.unit_of_measure_ace.code"), nullable=True) # CLAVEACE
|
||||
oma_code: Mapped[Optional[str]] = mapped_column(String(10), ForeignKey(
|
||||
"a76.unit_of_measure_oma.code"), nullable=True) # CLAVEOMA
|
||||
customs_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(2), nullable=True) # CLAVE_AMEX
|
||||
american_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(3), nullable=True) # CLAVE_AAMER
|
||||
ace_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(4), nullable=True) # CLAVEACE
|
||||
oma_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(10), nullable=True) # CLAVEOMA
|
||||
|
||||
# Relationships omitted for simplicity or need explicit primaryjoin
|
||||
customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship()
|
||||
american_unit: Mapped[Optional["UnitOfMeasureAmerican"]] = relationship()
|
||||
ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship()
|
||||
oma_unit: Mapped[Optional["UnitOfMeasureOMA"]] = relationship()
|
||||
|
||||
# 6. GUniMed (General/Conversion)
|
||||
|
||||
|
||||
class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "units_of_measure_general"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_uom_general_code"),
|
||||
{"schema": "a76"}
|
||||
UniqueConstraint("code", "tenant_id", "company_id",
|
||||
name="uq_uom_general_code"),
|
||||
ForeignKeyConstraint(
|
||||
["customs_code", "tenant_id", "company_id"],
|
||||
["a76.unit_of_measure_customs.code", "a76.unit_of_measure_customs.tenant_id",
|
||||
"a76.unit_of_measure_customs.company_id"],
|
||||
use_alter=True,
|
||||
name="fk_uom_general_customs"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["ace_code", "tenant_id", "company_id"],
|
||||
["a76.unit_of_measure_ace.code", "a76.unit_of_measure_ace.tenant_id",
|
||||
"a76.unit_of_measure_ace.company_id"],
|
||||
use_alter=True,
|
||||
name="fk_uom_general_ace"
|
||||
),
|
||||
{"schema": "a76", "extend_existing": True}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
@@ -114,10 +175,10 @@ class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin):
|
||||
american_unit_code: Mapped[Optional[str]
|
||||
] = mapped_column(String(5), nullable=True)
|
||||
|
||||
customs_code: Mapped[Optional[str]] = mapped_column(String(2), ForeignKey(
|
||||
"a76.unit_of_measure_customs.code"), nullable=True) # CLAVE_ADUANA
|
||||
ace_code: Mapped[Optional[str]] = mapped_column(String(4), ForeignKey(
|
||||
"a76.unit_of_measure_ace.code"), nullable=True) # CLAVEACE
|
||||
customs_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(2), nullable=True) # CLAVE_ADUANA
|
||||
ace_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(4), nullable=True) # CLAVEACE
|
||||
|
||||
customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship()
|
||||
ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship()
|
||||
|
||||
@@ -1,239 +1,97 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from core.database import get_core_db
|
||||
from . import service
|
||||
from .dto import (
|
||||
UnitOfMeasureACECreate, UnitOfMeasureACEResponse, UnitOfMeasureACEUpdate,
|
||||
UnitOfMeasureOMACreate, UnitOfMeasureOMAResponse, UnitOfMeasureOMAUpdate,
|
||||
UnitOfMeasureAmericanCreate, UnitOfMeasureAmericanResponse, UnitOfMeasureAmericanUpdate,
|
||||
UnitOfMeasureCustomsCreate, UnitOfMeasureCustomsResponse, UnitOfMeasureCustomsUpdate,
|
||||
UnitOfMeasureCreate, UnitOfMeasureResponse, UnitOfMeasureUpdate,
|
||||
UnitOfMeasureGeneralCreate, UnitOfMeasureGeneralResponse, UnitOfMeasureGeneralUpdate
|
||||
)
|
||||
from fastapi import APIRouter
|
||||
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from . import dto, service
|
||||
|
||||
router = APIRouter(prefix="/units-of-measure",
|
||||
tags=["a76.general_catalogs.units_of_measure"])
|
||||
|
||||
# --- ACE Routes ---
|
||||
|
||||
|
||||
@router.post("/ace", response_model=UnitOfMeasureACEResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_ace(data: UnitOfMeasureACECreate, session: Session = Depends(get_core_db)):
|
||||
return service.create_ace(session, data)
|
||||
|
||||
|
||||
@router.get("/ace/{id}", response_model=UnitOfMeasureACEResponse)
|
||||
def get_ace(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_ace(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="ACE Unit not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/ace", response_model=List[UnitOfMeasureACEResponse])
|
||||
def get_all_ace(skip: int = 0, limit: int = 100, session: Session = Depends(get_core_db)):
|
||||
return service.get_all_ace(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/ace/{id}", response_model=UnitOfMeasureACEResponse)
|
||||
def update_ace(id: int, data: UnitOfMeasureACEUpdate, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_ace(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="ACE Unit not found")
|
||||
return service.update_ace(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/ace/{id}", response_model=UnitOfMeasureACEResponse)
|
||||
def delete_ace(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_ace(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="ACE Unit not found")
|
||||
return service.delete_ace(session, db_obj)
|
||||
|
||||
# --- OMA Routes ---
|
||||
|
||||
|
||||
@router.post("/oma", response_model=UnitOfMeasureOMAResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_oma(data: UnitOfMeasureOMACreate, session: Session = Depends(get_core_db)):
|
||||
return service.create_oma(session, data)
|
||||
|
||||
|
||||
@router.get("/oma/{id}", response_model=UnitOfMeasureOMAResponse)
|
||||
def get_oma(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_oma(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="OMA Unit not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/oma", response_model=List[UnitOfMeasureOMAResponse])
|
||||
def get_all_oma(skip: int = 0, limit: int = 100, session: Session = Depends(get_core_db)):
|
||||
return service.get_all_oma(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/oma/{id}", response_model=UnitOfMeasureOMAResponse)
|
||||
def update_oma(id: int, data: UnitOfMeasureOMAUpdate, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_oma(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="OMA Unit not found")
|
||||
return service.update_oma(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/oma/{id}", response_model=UnitOfMeasureOMAResponse)
|
||||
def delete_oma(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_oma(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="OMA Unit not found")
|
||||
return service.delete_oma(session, db_obj)
|
||||
|
||||
# --- American Routes ---
|
||||
|
||||
|
||||
@router.post("/american", response_model=UnitOfMeasureAmericanResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_american(data: UnitOfMeasureAmericanCreate, session: Session = Depends(get_core_db)):
|
||||
return service.create_american(session, data)
|
||||
|
||||
|
||||
@router.get("/american/{id}", response_model=UnitOfMeasureAmericanResponse)
|
||||
def get_american(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_american(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="American Unit not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/american", response_model=List[UnitOfMeasureAmericanResponse])
|
||||
def get_all_american(skip: int = 0, limit: int = 100, session: Session = Depends(get_core_db)):
|
||||
return service.get_all_american(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/american/{id}", response_model=UnitOfMeasureAmericanResponse)
|
||||
def update_american(id: int, data: UnitOfMeasureAmericanUpdate, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_american(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="American Unit not found")
|
||||
return service.update_american(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/american/{id}", response_model=UnitOfMeasureAmericanResponse)
|
||||
def delete_american(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_american(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="American Unit not found")
|
||||
return service.delete_american(session, db_obj)
|
||||
|
||||
# --- Customs Routes ---
|
||||
|
||||
|
||||
@router.post("/customs", response_model=UnitOfMeasureCustomsResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_customs(data: UnitOfMeasureCustomsCreate, session: Session = Depends(get_core_db)):
|
||||
return service.create_customs(session, data)
|
||||
|
||||
|
||||
@router.get("/customs/{id}", response_model=UnitOfMeasureCustomsResponse)
|
||||
def get_customs(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_customs(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Customs Unit not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/customs", response_model=List[UnitOfMeasureCustomsResponse])
|
||||
def get_all_customs(skip: int = 0, limit: int = 100, session: Session = Depends(get_core_db)):
|
||||
return service.get_all_customs(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/customs/{id}", response_model=UnitOfMeasureCustomsResponse)
|
||||
def update_customs(id: int, data: UnitOfMeasureCustomsUpdate, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_customs(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Customs Unit not found")
|
||||
return service.update_customs(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/customs/{id}", response_model=UnitOfMeasureCustomsResponse)
|
||||
def delete_customs(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_customs(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(status_code=404, detail="Customs Unit not found")
|
||||
return service.delete_customs(session, db_obj)
|
||||
|
||||
# --- Main UnitOfMeasure Routes ---
|
||||
|
||||
|
||||
@router.post("/", response_model=UnitOfMeasureResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_uom(data: UnitOfMeasureCreate, session: Session = Depends(get_core_db)):
|
||||
return service.create_uom(session, data)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=UnitOfMeasureResponse)
|
||||
def get_uom(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_uom(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Unit of Measure not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/", response_model=List[UnitOfMeasureResponse])
|
||||
def get_all_uom(skip: int = 0, limit: int = 100, session: Session = Depends(get_core_db)):
|
||||
return service.get_all_uom(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=UnitOfMeasureResponse)
|
||||
def update_uom(id: int, data: UnitOfMeasureUpdate, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_uom(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Unit of Measure not found")
|
||||
return service.update_uom(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=UnitOfMeasureResponse)
|
||||
def delete_uom(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_uom(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Unit of Measure not found")
|
||||
return service.delete_uom(session, db_obj)
|
||||
|
||||
# --- General UnitOfMeasure Routes ---
|
||||
|
||||
|
||||
@router.post("/general", response_model=UnitOfMeasureGeneralResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_uom_general(data: UnitOfMeasureGeneralCreate, session: Session = Depends(get_core_db)):
|
||||
return service.create_uom_general(session, data)
|
||||
|
||||
|
||||
@router.get("/general/{id}", response_model=UnitOfMeasureGeneralResponse)
|
||||
def get_uom_general(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_uom_general(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="General Unit of Measure not found")
|
||||
return db_obj
|
||||
|
||||
|
||||
@router.get("/general", response_model=List[UnitOfMeasureGeneralResponse])
|
||||
def get_all_uom_general(skip: int = 0, limit: int = 100, session: Session = Depends(get_core_db)):
|
||||
return service.get_all_uom_general(session, skip, limit)
|
||||
|
||||
|
||||
@router.put("/general/{id}", response_model=UnitOfMeasureGeneralResponse)
|
||||
def update_uom_general(id: int, data: UnitOfMeasureGeneralUpdate, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_uom_general(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="General Unit of Measure not found")
|
||||
return service.update_uom_general(session, db_obj, data)
|
||||
|
||||
|
||||
@router.delete("/general/{id}", response_model=UnitOfMeasureGeneralResponse)
|
||||
def delete_uom_general(id: int, session: Session = Depends(get_core_db)):
|
||||
db_obj = service.get_uom_general(session, id)
|
||||
if not db_obj:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="General Unit of Measure not found")
|
||||
return service.delete_uom_general(session, db_obj)
|
||||
# ACE
|
||||
ace_router = TenantCRUDRoutes(
|
||||
service=service.UnitOfMeasureACEService,
|
||||
create_schema=dto.UnitOfMeasureACECreate,
|
||||
update_schema=dto.UnitOfMeasureACEUpdate,
|
||||
response_schema=dto.UnitOfMeasureACEResponse,
|
||||
prefix="/ace",
|
||||
tags=["a76.general_catalogs.units_of_measure"],
|
||||
resource_name="UnitOfMeasureACE",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
router.include_router(ace_router)
|
||||
|
||||
# OMA
|
||||
oma_router = TenantCRUDRoutes(
|
||||
service=service.UnitOfMeasureOMAService,
|
||||
create_schema=dto.UnitOfMeasureOMACreate,
|
||||
update_schema=dto.UnitOfMeasureOMAUpdate,
|
||||
response_schema=dto.UnitOfMeasureOMAResponse,
|
||||
prefix="/oma",
|
||||
tags=["a76.general_catalogs.units_of_measure"],
|
||||
resource_name="UnitOfMeasureOMA",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
router.include_router(oma_router)
|
||||
|
||||
# American
|
||||
american_router = TenantCRUDRoutes(
|
||||
service=service.UnitOfMeasureAmericanService,
|
||||
create_schema=dto.UnitOfMeasureAmericanCreate,
|
||||
update_schema=dto.UnitOfMeasureAmericanUpdate,
|
||||
response_schema=dto.UnitOfMeasureAmericanResponse,
|
||||
prefix="/american",
|
||||
tags=["a76.general_catalogs.units_of_measure"],
|
||||
resource_name="UnitOfMeasureAmerican",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
router.include_router(american_router)
|
||||
|
||||
# Customs
|
||||
customs_router = TenantCRUDRoutes(
|
||||
service=service.UnitOfMeasureCustomsService,
|
||||
create_schema=dto.UnitOfMeasureCustomsCreate,
|
||||
update_schema=dto.UnitOfMeasureCustomsUpdate,
|
||||
response_schema=dto.UnitOfMeasureCustomsResponse,
|
||||
prefix="/customs",
|
||||
tags=["a76.general_catalogs.units_of_measure"],
|
||||
resource_name="UnitOfMeasureCustoms",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
router.include_router(customs_router)
|
||||
|
||||
# General
|
||||
general_router = TenantCRUDRoutes(
|
||||
service=service.UnitOfMeasureGeneralService,
|
||||
create_schema=dto.UnitOfMeasureGeneralCreate,
|
||||
update_schema=dto.UnitOfMeasureGeneralUpdate,
|
||||
response_schema=dto.UnitOfMeasureGeneralResponse,
|
||||
prefix="/general",
|
||||
tags=["a76.general_catalogs.units_of_measure"],
|
||||
resource_name="UnitOfMeasureGeneral",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
router.include_router(general_router)
|
||||
|
||||
# Main UnitOfMeasure
|
||||
# Note: We use prefix="" to map to /units-of-measure/
|
||||
main_router = TenantCRUDRoutes(
|
||||
service=service.UnitOfMeasureService,
|
||||
create_schema=dto.UnitOfMeasureCreate,
|
||||
update_schema=dto.UnitOfMeasureUpdate,
|
||||
response_schema=dto.UnitOfMeasureResponse,
|
||||
prefix="",
|
||||
tags=["a76.general_catalogs.units_of_measure"],
|
||||
resource_name="UnitOfMeasure",
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
router.include_router(main_router)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any, Type
|
||||
from sqlalchemy import Sequence
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from typing import Sequence, Optional, Type, TypeVar
|
||||
|
||||
from .models import (
|
||||
UnitOfMeasureACE, UnitOfMeasureOMA, UnitOfMeasureAmerican, UnitOfMeasureCustoms,
|
||||
@@ -15,169 +15,129 @@ from .dto import (
|
||||
UnitOfMeasureGeneralCreate, UnitOfMeasureGeneralUpdate
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def _create(session: Session, model: Type[T], data) -> T:
|
||||
db_obj = model(**data.model_dump())
|
||||
session.add(db_obj)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
def _get(session: Session, model: Type[T], id: int) -> Optional[T]:
|
||||
return session.get(model, id)
|
||||
|
||||
|
||||
def _get_all(session: Session, model: Type[T], skip: int = 0, limit: int = 100) -> Sequence[T]:
|
||||
stmt = select(model).offset(skip).limit(limit)
|
||||
result = session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
def _update(session: Session, db_obj: T, update_data) -> T:
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
session.commit()
|
||||
session.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
def _delete(session: Session, db_obj: T) -> T:
|
||||
session.delete(db_obj)
|
||||
session.commit()
|
||||
return db_obj
|
||||
|
||||
# --- ACE ---
|
||||
|
||||
|
||||
def create_ace(session: Session, data: UnitOfMeasureACECreate) -> UnitOfMeasureACE:
|
||||
return _create(session, UnitOfMeasureACE, data)
|
||||
|
||||
|
||||
def get_ace(session: Session, id: int) -> Optional[UnitOfMeasureACE]:
|
||||
return _get(session, UnitOfMeasureACE, id)
|
||||
|
||||
|
||||
def get_all_ace(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasureACE]:
|
||||
return _get_all(session, UnitOfMeasureACE, skip, limit)
|
||||
|
||||
|
||||
def update_ace(session: Session, db_obj: UnitOfMeasureACE, data: UnitOfMeasureACEUpdate) -> UnitOfMeasureACE:
|
||||
return _update(session, db_obj, data)
|
||||
|
||||
|
||||
def delete_ace(session: Session, db_obj: UnitOfMeasureACE) -> UnitOfMeasureACE:
|
||||
return _delete(session, db_obj)
|
||||
|
||||
# --- OMA ---
|
||||
|
||||
|
||||
def create_oma(session: Session, data: UnitOfMeasureOMACreate) -> UnitOfMeasureOMA:
|
||||
return _create(session, UnitOfMeasureOMA, data)
|
||||
|
||||
|
||||
def get_oma(session: Session, id: int) -> Optional[UnitOfMeasureOMA]:
|
||||
return _get(session, UnitOfMeasureOMA, id)
|
||||
|
||||
|
||||
def get_all_oma(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasureOMA]:
|
||||
return _get_all(session, UnitOfMeasureOMA, skip, limit)
|
||||
|
||||
|
||||
def update_oma(session: Session, db_obj: UnitOfMeasureOMA, data: UnitOfMeasureOMAUpdate) -> UnitOfMeasureOMA:
|
||||
return _update(session, db_obj, data)
|
||||
|
||||
|
||||
def delete_oma(session: Session, db_obj: UnitOfMeasureOMA) -> UnitOfMeasureOMA:
|
||||
return _delete(session, db_obj)
|
||||
|
||||
# --- American ---
|
||||
|
||||
|
||||
def create_american(session: Session, data: UnitOfMeasureAmericanCreate) -> UnitOfMeasureAmerican:
|
||||
return _create(session, UnitOfMeasureAmerican, data)
|
||||
|
||||
|
||||
def get_american(session: Session, id: int) -> Optional[UnitOfMeasureAmerican]:
|
||||
return _get(session, UnitOfMeasureAmerican, id)
|
||||
|
||||
|
||||
def get_all_american(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasureAmerican]:
|
||||
return _get_all(session, UnitOfMeasureAmerican, skip, limit)
|
||||
|
||||
|
||||
def update_american(session: Session, db_obj: UnitOfMeasureAmerican, data: UnitOfMeasureAmericanUpdate) -> UnitOfMeasureAmerican:
|
||||
return _update(session, db_obj, data)
|
||||
|
||||
|
||||
def delete_american(session: Session, db_obj: UnitOfMeasureAmerican) -> UnitOfMeasureAmerican:
|
||||
return _delete(session, db_obj)
|
||||
|
||||
# --- Customs ---
|
||||
|
||||
|
||||
def create_customs(session: Session, data: UnitOfMeasureCustomsCreate) -> UnitOfMeasureCustoms:
|
||||
return _create(session, UnitOfMeasureCustoms, data)
|
||||
|
||||
|
||||
def get_customs(session: Session, id: int) -> Optional[UnitOfMeasureCustoms]:
|
||||
return _get(session, UnitOfMeasureCustoms, id)
|
||||
|
||||
|
||||
def get_all_customs(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasureCustoms]:
|
||||
return _get_all(session, UnitOfMeasureCustoms, skip, limit)
|
||||
|
||||
|
||||
def update_customs(session: Session, db_obj: UnitOfMeasureCustoms, data: UnitOfMeasureCustomsUpdate) -> UnitOfMeasureCustoms:
|
||||
return _update(session, db_obj, data)
|
||||
|
||||
|
||||
def delete_customs(session: Session, db_obj: UnitOfMeasureCustoms) -> UnitOfMeasureCustoms:
|
||||
return _delete(session, db_obj)
|
||||
|
||||
# --- Main UnitOfMeasure ---
|
||||
|
||||
|
||||
def create_uom(session: Session, data: UnitOfMeasureCreate) -> UnitOfMeasure:
|
||||
return _create(session, UnitOfMeasure, data)
|
||||
|
||||
|
||||
def get_uom(session: Session, id: int) -> Optional[UnitOfMeasure]:
|
||||
return _get(session, UnitOfMeasure, id)
|
||||
|
||||
|
||||
def get_all_uom(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasure]:
|
||||
return _get_all(session, UnitOfMeasure, skip, limit)
|
||||
|
||||
|
||||
def update_uom(session: Session, db_obj: UnitOfMeasure, data: UnitOfMeasureUpdate) -> UnitOfMeasure:
|
||||
return _update(session, db_obj, data)
|
||||
|
||||
|
||||
def delete_uom(session: Session, db_obj: UnitOfMeasure) -> UnitOfMeasure:
|
||||
return _delete(session, db_obj)
|
||||
|
||||
# --- General UnitOfMeasure ---
|
||||
|
||||
|
||||
def create_uom_general(session: Session, data: UnitOfMeasureGeneralCreate) -> UnitOfMeasureGeneral:
|
||||
return _create(session, UnitOfMeasureGeneral, data)
|
||||
|
||||
|
||||
def get_uom_general(session: Session, id: int) -> Optional[UnitOfMeasureGeneral]:
|
||||
return _get(session, UnitOfMeasureGeneral, id)
|
||||
class BaseService:
|
||||
model = None
|
||||
|
||||
@classmethod
|
||||
def get_all(
|
||||
cls,
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[Any], int]:
|
||||
query = db.query(cls.model).filter(
|
||||
cls.model.tenant_id == tenant_id,
|
||||
cls.model.company_id == company_id,
|
||||
)
|
||||
|
||||
if filters:
|
||||
if filters.get("code"):
|
||||
query = query.filter(
|
||||
cls.model.code.ilike(f"%{filters['code']}%"))
|
||||
if filters.get("description"):
|
||||
if hasattr(cls.model, "description"):
|
||||
query = query.filter(cls.model.description.ilike(
|
||||
f"%{filters['description']}%"))
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@classmethod
|
||||
def get_by_id(
|
||||
cls, db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> Optional[Any]:
|
||||
return db.query(cls.model).filter(
|
||||
cls.model.id == id,
|
||||
cls.model.tenant_id == tenant_id,
|
||||
cls.model.company_id == company_id,
|
||||
).first()
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
db: Session,
|
||||
data: Any,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> Any:
|
||||
db_obj = cls.model(
|
||||
**data.model_dump(), tenant_id=tenant_id, company_id=company_id
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@classmethod
|
||||
def update(
|
||||
cls,
|
||||
db: Session,
|
||||
id: int,
|
||||
tenant_id: int,
|
||||
data: Any,
|
||||
company_id: int,
|
||||
) -> Optional[Any]:
|
||||
db_obj = cls.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for key, value in update_dict.items():
|
||||
setattr(db_obj, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
@classmethod
|
||||
def delete(
|
||||
cls, db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
db_obj = cls.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
class UnitOfMeasureACEService(BaseService):
|
||||
model = UnitOfMeasureACE
|
||||
|
||||
|
||||
class UnitOfMeasureOMAService(BaseService):
|
||||
model = UnitOfMeasureOMA
|
||||
|
||||
|
||||
class UnitOfMeasureAmericanService(BaseService):
|
||||
model = UnitOfMeasureAmerican
|
||||
|
||||
|
||||
class UnitOfMeasureCustomsService(BaseService):
|
||||
model = UnitOfMeasureCustoms
|
||||
|
||||
|
||||
class UnitOfMeasureService(BaseService):
|
||||
model = UnitOfMeasure
|
||||
|
||||
|
||||
class UnitOfMeasureGeneralService(BaseService):
|
||||
model = UnitOfMeasureGeneral
|
||||
|
||||
|
||||
def get_all_uom_general(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasureGeneral]:
|
||||
return _get_all(session, UnitOfMeasureGeneral, skip, limit)
|
||||
return BaseService._get_all(session, UnitOfMeasureGeneral, skip, limit)
|
||||
|
||||
|
||||
def update_uom_general(session: Session, db_obj: UnitOfMeasureGeneral, data: UnitOfMeasureGeneralUpdate) -> UnitOfMeasureGeneral:
|
||||
return _update(session, db_obj, data)
|
||||
return BaseService._update(session, db_obj, data)
|
||||
|
||||
|
||||
def delete_uom_general(session: Session, db_obj: UnitOfMeasureGeneral) -> UnitOfMeasureGeneral:
|
||||
return _delete(session, db_obj)
|
||||
return BaseService._delete(session, db_obj)
|
||||
|
||||
@@ -42,6 +42,11 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
|
||||
ForeignKeyConstraint(
|
||||
["currency_key"], ["public.currency_types.code"], name="fk_parts_currency"
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["unit_of_measure", "tenant_id", "company_id"],
|
||||
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id"],
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id", "company_id", "part_number", name="client_part_ukey"
|
||||
),
|
||||
@@ -60,7 +65,7 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
|
||||
description_english: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
part_class: Mapped[Optional[str]] = mapped_column(String(8))
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(
|
||||
String(5), ForeignKey("a76.units_of_measure.code")
|
||||
String(5)
|
||||
)
|
||||
commercial_part_number: Mapped[Optional[str]] = mapped_column(String(70))
|
||||
country_of_origin: Mapped[Optional[str]] = mapped_column(String(3))
|
||||
|
||||
@@ -8,24 +8,40 @@ from fastapi import APIRouter
|
||||
from .customs_brokers.routes import router as customs_broker_router
|
||||
|
||||
# Importar routers de módulos
|
||||
from .auth import router as auth_router
|
||||
from ..core.auth import router as auth_router
|
||||
from .classes import router as classes_router
|
||||
from .clients_and_providers import router as client_and_provider_router
|
||||
from .general_catalogs.company import router as company_router
|
||||
from .country_rule_oct.routes import router as country_rule_oct_router
|
||||
from .transportation.drivers.routes import router as drivers_router
|
||||
from .general_catalogs.exchange_rate.routes import router as exchange_rate_router
|
||||
from .general_catalogs.identifiers.routes import router as identifiers_router
|
||||
from .fraction_rule_octave.routes import router as fraction_rule_octave_router
|
||||
from .licenses import router as licenses_router
|
||||
from ..core.licenses import router as licenses_router
|
||||
from .general_catalogs.packages.routes import router as package_router
|
||||
from .general_catalogs.ports.routes import router as ports_router
|
||||
from .parts import router as parts_router
|
||||
from .pedmientos.router import router as pedimentos_router
|
||||
from .permission_rule_oct.routes import router as permission_rule_oct_router
|
||||
from .general_catalogs.seal.routes import router as seal_router
|
||||
from .tenants import router as tenants_router
|
||||
from .general_catalogs.units_of_measure.routes import router as units_of_measure_router
|
||||
from .general_catalogs.concepts.routes import router as concepts_router
|
||||
from .general_catalogs.customs_broker_concepts.routes import router as customs_broker_concepts_router
|
||||
from .general_catalogs.classification_concepts.routes import router as classification_concepts_router
|
||||
from .general_catalogs.unit_conversions.routes import router as unit_conversions_router
|
||||
from .general_catalogs.equivalencies.routes import router as equivalencies_router
|
||||
from .general_catalogs.multi_currency_types.routes import router as multi_currency_types_router
|
||||
from .general_catalogs.inpc.routes import router as inpc_router
|
||||
from .general_catalogs.legends.routes import router as legends_router
|
||||
from .general_catalogs.signatures.routes import router as signatures_router
|
||||
from .general_catalogs.error_catalogs.routes import router as error_catalogs_router
|
||||
from .general_catalogs.doda.routes import router as doda_router
|
||||
from .general_catalogs.prevalidators.routes import router as prevalidators_router
|
||||
from .general_catalogs.electronic_notices.routes import router as electronic_notices_router
|
||||
from ..core.tenants import router as tenants_router
|
||||
from .transportation.trailers.routes import router as trailers_router
|
||||
from .transportation.transporters.routes import router as transporters_router
|
||||
from .user_tenant.routes import router as user_tenant_router
|
||||
from ..core.user_tenant.routes import router as user_tenant_router
|
||||
from .transportation.vehicles.routes import router as vehicles_router
|
||||
|
||||
# Router principal
|
||||
@@ -34,7 +50,8 @@ router = APIRouter()
|
||||
# Registrar módulos
|
||||
router.include_router(auth_router)
|
||||
router.include_router(tenants_router, prefix="/a76", tags=["a76 / tenants"])
|
||||
router.include_router(user_tenant_router, prefix="/a76", tags=["a76 / user-tenants"])
|
||||
router.include_router(user_tenant_router, prefix="/a76",
|
||||
tags=["a76 / user-tenants"])
|
||||
router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"])
|
||||
router.include_router(pedimentos_router, prefix="/a76")
|
||||
router.include_router(
|
||||
@@ -47,18 +64,38 @@ router.include_router(
|
||||
permission_rule_oct_router, prefix="/a76", tags=["a76 / permission_rule_oct"]
|
||||
)
|
||||
router.include_router(package_router, prefix="/a76")
|
||||
router.include_router(ports_router, prefix="/a76")
|
||||
router.include_router(seal_router, prefix="/a76", tags=["a76 / seal"])
|
||||
router.include_router(units_of_measure_router, prefix="/a76")
|
||||
router.include_router(
|
||||
fraction_rule_octave_router, prefix="/a76", tags=["a76 / fraction_rule_octave"]
|
||||
)
|
||||
router.include_router(identifiers_router, prefix="/a76")
|
||||
router.include_router(
|
||||
country_rule_oct_router, prefix="/a76", tags=["a76 / country_rule_oct"]
|
||||
)
|
||||
router.include_router(exchange_rate_router, prefix="/a76", tags=["a76 / exchange_rate"])
|
||||
router.include_router(exchange_rate_router, prefix="/a76",
|
||||
tags=["a76 / exchange_rate"])
|
||||
router.include_router(trailers_router, prefix="/a76", tags=["a76 / trailers"])
|
||||
router.include_router(
|
||||
customs_broker_router, prefix="/a76", tags=["a76 / customs_broker"]
|
||||
)
|
||||
router.include_router(drivers_router, prefix="/a76", tags=["a76 / drivers"])
|
||||
router.include_router(transporters_router, prefix="/a76", tags=["a76 / transporters"])
|
||||
router.include_router(transporters_router, prefix="/a76",
|
||||
tags=["a76 / transporters"])
|
||||
router.include_router(vehicles_router, prefix="/a76", tags=["a76 / vehicles"])
|
||||
|
||||
# Registrar catálogos generales adicionales
|
||||
router.include_router(concepts_router, prefix="/a76")
|
||||
router.include_router(customs_broker_concepts_router, prefix="/a76")
|
||||
router.include_router(classification_concepts_router, prefix="/a76")
|
||||
router.include_router(unit_conversions_router, prefix="/a76")
|
||||
router.include_router(equivalencies_router, prefix="/a76")
|
||||
router.include_router(multi_currency_types_router, prefix="/a76")
|
||||
router.include_router(inpc_router, prefix="/a76")
|
||||
router.include_router(legends_router, prefix="/a76")
|
||||
router.include_router(signatures_router, prefix="/a76")
|
||||
router.include_router(error_catalogs_router, prefix="/a76")
|
||||
router.include_router(doda_router, prefix="/a76")
|
||||
router.include_router(prevalidators_router, prefix="/a76")
|
||||
router.include_router(electronic_notices_router, prefix="/a76")
|
||||
|
||||
@@ -4,8 +4,8 @@ Servicio de autenticación con Keycloak
|
||||
|
||||
import logging
|
||||
|
||||
from api.v1.modules.a76.tenants.service import TenantService
|
||||
from api.v1.modules.a76.user_tenant.service import UserTenantService
|
||||
from api.v1.modules.core.tenants.service import TenantService
|
||||
from api.v1.modules.core.user_tenant.service import UserTenantService
|
||||
from core.config import settings
|
||||
from fastapi import HTTPException
|
||||
from keycloak import KeycloakAdmin, KeycloakOpenID
|
||||
@@ -266,7 +266,7 @@ class AuthService:
|
||||
"""
|
||||
try:
|
||||
# Verificar que el tenant existe
|
||||
from api.v1.modules.a76.tenants.service import TenantService
|
||||
from api.v1.modules.core.tenants.service import TenantService
|
||||
|
||||
tenant_service = TenantService(self.db)
|
||||
tenant = tenant_service.get_tenant_by_slug(register_data.tenant_slug)
|
||||
@@ -319,7 +319,7 @@ class AuthService:
|
||||
|
||||
# Agregar el usuario al tenant en la base de datos
|
||||
try:
|
||||
from api.v1.modules.a76.user_tenant.service import UserTenantService
|
||||
from api.v1.modules.core.user_tenant.service import UserTenantService
|
||||
|
||||
user_tenant_service = UserTenantService(self.db)
|
||||
user_tenant_service.add_user_to_tenant(
|
||||
@@ -36,11 +36,11 @@ class License(Base, TimestampMixin):
|
||||
"""
|
||||
|
||||
__tablename__ = "licenses"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
__table_args__ = {"schema": "core"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
tenant_id = Column(
|
||||
Integer, ForeignKey("a76.tenants.id"), nullable=False, unique=True, index=True
|
||||
Integer, ForeignKey("core.tenants.id"), nullable=False, unique=True, index=True
|
||||
)
|
||||
|
||||
# Plan y características
|
||||
@@ -74,11 +74,11 @@ class LicenseUsage(Base, TimestampMixin):
|
||||
"""
|
||||
|
||||
__tablename__ = "license_usage"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
__table_args__ = {"schema": "core"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
tenant_id = Column(
|
||||
Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True
|
||||
Integer, ForeignKey("core.tenants.id"), nullable=False, index=True
|
||||
)
|
||||
|
||||
# Métricas de uso
|
||||
@@ -13,7 +13,7 @@ from sqlalchemy import Integer, String, Text
|
||||
from sqlalchemy.orm import Mapped, relationship
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.user_tenant.models import UserTenant
|
||||
from api.v1.modules.core.user_tenant.models import UserTenant
|
||||
|
||||
|
||||
class TenantType(enum.Enum):
|
||||
@@ -30,7 +30,7 @@ class Tenant(Base, TimestampMixin):
|
||||
"""
|
||||
|
||||
__tablename__ = "tenants"
|
||||
__table_args__ = {"schema": "a76", "extend_existing": True}
|
||||
__table_args__ = {"schema": "core", "extend_existing": True}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String(255), nullable=False, index=True)
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy import Boolean, ForeignKeyConstraint, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.tenants.models import Tenant
|
||||
from api.v1.modules.core.tenants.models import Tenant
|
||||
|
||||
|
||||
class UserTenant(Base, TenantScopedMixin, TimestampMixin):
|
||||
@@ -26,7 +26,7 @@ class UserTenant(Base, TenantScopedMixin, TimestampMixin):
|
||||
UniqueConstraint(
|
||||
"keycloak_user_id", "tenant_id", "company_id", name="uq_user_tenant"
|
||||
),
|
||||
{"schema": "a76", "extend_existing": True},
|
||||
{"schema": "core", "extend_existing": True},
|
||||
)
|
||||
|
||||
# Primary Key
|
||||
@@ -19,12 +19,13 @@ class CodePedimentoRegimen(Base):
|
||||
["regimen_code"], ["public.pedimento_regimens.code"], name="fk_regimenped"
|
||||
),
|
||||
PrimaryKeyConstraint("id", name="clave_pedimento_regimens_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_code: Mapped[str] = mapped_column(String(3), nullable=False)
|
||||
regimen_code: Mapped[Optional[str]] = mapped_column(String(3), nullable=False)
|
||||
regimen_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(3), nullable=False)
|
||||
type_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(1)
|
||||
) # si aplica un tipo de relación
|
||||
|
||||
@@ -7,7 +7,7 @@ class Container(Base):
|
||||
__tablename__ = "containers" # GContenedores
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="containers_pkey"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(
|
||||
|
||||
@@ -8,11 +8,13 @@ class Country(Base):
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("m3_key", name="countries_pkey"),
|
||||
Index("ak_country_ame", "ame_key", unique=True),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
m3_key: Mapped[str] = mapped_column(String(3), nullable=False) # clave M3
|
||||
mex_key: Mapped[str] = mapped_column(String(2), nullable=False) # clave país México
|
||||
m3_key: Mapped[str] = mapped_column(
|
||||
String(3), primary_key=True, nullable=False) # clave M3
|
||||
mex_key: Mapped[str] = mapped_column(
|
||||
String(2), nullable=False) # clave país México
|
||||
ame_key: Mapped[str] = mapped_column(
|
||||
String(2), nullable=False
|
||||
) # clave país América / regional
|
||||
|
||||
@@ -7,11 +7,11 @@ class CurrencyType(Base):
|
||||
__tablename__ = "currency_types" # GTiposMoneda
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("code", name="currency_types_pkey"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
code: Mapped[str] = mapped_column(
|
||||
String(3), nullable=False
|
||||
String(3), primary_key=True, nullable=False
|
||||
) # código ISO o clave de moneda
|
||||
currency_name: Mapped[str] = mapped_column(
|
||||
String(15), nullable=False
|
||||
|
||||
@@ -7,7 +7,7 @@ class CustomsSection(Base):
|
||||
__tablename__ = "customs_sections" # GAduanaSec
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("customs_code", name="customs_code_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
customs_code = mapped_column(String(3), nullable=False)
|
||||
|
||||
@@ -7,11 +7,13 @@ class CustomsWarehouse(Base):
|
||||
__tablename__ = "customs_warehouses" # GRecintos
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", "customs", name="pk_customs_warehouse"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(String(3), nullable=False) # clave del recinto
|
||||
customs: Mapped[str] = mapped_column(String(100), nullable=False) # aduana asociada
|
||||
key: Mapped[str] = mapped_column(
|
||||
String(3), nullable=False) # clave del recinto
|
||||
customs: Mapped[str] = mapped_column(
|
||||
String(100), nullable=False) # aduana asociada
|
||||
fiscalized_warehouse: Mapped[str] = mapped_column(
|
||||
String(1000)
|
||||
) # recintos fiscalizados (valor legal)
|
||||
|
||||
@@ -7,7 +7,7 @@ class Incoterm(Base):
|
||||
__tablename__ = "incoterms" # GIncoterm
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("code", name="incoterms_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
code: Mapped[str] = mapped_column(String(5), nullable=False)
|
||||
|
||||
@@ -7,7 +7,7 @@ class InvoiceType(Base):
|
||||
__tablename__ = "invoice_types" # GTiposFactura
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="invoice_types_pkey"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(
|
||||
@@ -16,7 +16,8 @@ class InvoiceType(Base):
|
||||
description: Mapped[str] = mapped_column(
|
||||
String(50), nullable=False
|
||||
) # descripción oficial (en español)
|
||||
note: Mapped[str] = mapped_column(String(500)) # observación o comentario adicional
|
||||
# observación o comentario adicional
|
||||
note: Mapped[str] = mapped_column(String(500))
|
||||
type: Mapped[str] = mapped_column(String(15)) # tipo
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
@@ -7,10 +7,11 @@ class MaterialType(Base):
|
||||
__tablename__ = "material_types" # STipoMat QTipoActFijo
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="material_types_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(String(10), nullable=False) # clave del material
|
||||
key: Mapped[str] = mapped_column(
|
||||
String(10), nullable=False) # clave del material
|
||||
type: Mapped[str] = mapped_column(String(15), nullable=False) # tipo
|
||||
description: Mapped[str] = mapped_column(
|
||||
String(256), nullable=False
|
||||
|
||||
@@ -7,7 +7,7 @@ class PaymentMethod(Base):
|
||||
__tablename__ = "payment_methods" # GFormaPago
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="payment_methods_pkey"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(String(2), nullable=False)
|
||||
|
||||
@@ -12,7 +12,7 @@ class PedimentoCode(Base):
|
||||
__tablename__ = "pedimento_codes" # GClavePed
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("code", name="pedimento_codes_pkey"),
|
||||
{"schema": "public"}, # esquema del anexo 22
|
||||
{"schema": "public", "extend_existing": True}, # esquema del anexo 22
|
||||
)
|
||||
|
||||
code: Mapped[str] = mapped_column(String(3), nullable=False)
|
||||
|
||||
@@ -12,7 +12,7 @@ class RegimenPedimento(Base):
|
||||
__tablename__ = "pedimento_regimens" # GRegimenPed
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("code", name="pedimento_regimens_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
code: Mapped[str] = mapped_column(
|
||||
|
||||
@@ -7,10 +7,11 @@ class Sector(Base):
|
||||
__tablename__ = "sectors" # GSectores
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="sectors_pkey"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(String(8), nullable=False) # clave del sector
|
||||
key: Mapped[str] = mapped_column(
|
||||
String(8), nullable=False) # clave del sector
|
||||
description: Mapped[str] = mapped_column(
|
||||
String(150), nullable=False
|
||||
) # descripción oficial (en español)
|
||||
|
||||
@@ -9,7 +9,7 @@ class State(Base):
|
||||
__tablename__ = "states" # GEstados
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("m3_key", "description", name="states_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
m3_key: Mapped[str] = mapped_column(String(3), nullable=False)
|
||||
|
||||
@@ -7,7 +7,7 @@ class TransportMode(Base):
|
||||
__tablename__ = "transport_modes" # GModTransporte
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="transport_modes_pkey"),
|
||||
{"schema": "public"}, # opcional
|
||||
{"schema": "public", "extend_existing": True}, # opcional
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(String(3), nullable=False)
|
||||
|
||||
@@ -7,7 +7,7 @@ class TransportType(Base):
|
||||
__tablename__ = "transport_types" # GTiposTransporte
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("transport_code", name="transport_types_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
transport_code: Mapped[str] = mapped_column(
|
||||
|
||||
@@ -7,7 +7,7 @@ class ValuationMethod(Base):
|
||||
__tablename__ = "valuation_methods" # GMetValor
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("key", name="valuation_methods_pkey"),
|
||||
{"schema": "public"},
|
||||
{"schema": "public", "extend_existing": True},
|
||||
)
|
||||
|
||||
key: Mapped[str] = mapped_column(String(2), nullable=False)
|
||||
|
||||
@@ -4,15 +4,19 @@ Configuración de base de datos con soporte multi-tenant
|
||||
- Bases de datos dedicadas para clientes enterprise
|
||||
"""
|
||||
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from typing import AsyncGenerator, Dict, Generator, Optional
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import Session, declarative_base, sessionmaker
|
||||
|
||||
from .config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Base declarativa para modelos ORM
|
||||
Base = declarative_base()
|
||||
|
||||
@@ -25,7 +29,8 @@ core_engine = create_engine(
|
||||
echo=False,
|
||||
)
|
||||
|
||||
CoreSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=core_engine)
|
||||
CoreSessionLocal = sessionmaker(
|
||||
autocommit=False, autoflush=False, bind=core_engine)
|
||||
|
||||
# Engine asíncrono para operaciones async
|
||||
async_core_engine = create_async_engine(
|
||||
@@ -106,7 +111,8 @@ def get_tenant_db(
|
||||
else:
|
||||
# Tenant con BD dedicada
|
||||
engine = get_tenant_engine(tenant_id, db_config)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
SessionLocal = sessionmaker(
|
||||
autocommit=False, autoflush=False, bind=engine)
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
@@ -119,7 +125,15 @@ def init_db():
|
||||
"""
|
||||
Inicializa las tablas de la base de datos core
|
||||
"""
|
||||
Base.metadata.create_all(bind=core_engine)
|
||||
try:
|
||||
Base.metadata.create_all(bind=core_engine, checkfirst=True)
|
||||
except ProgrammingError as e:
|
||||
# Si la tabla ya existe, es seguro continuar
|
||||
if "already exists" in str(e):
|
||||
logger.warning(
|
||||
f"Algunas tablas ya existen en la base de datos: {e}")
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
async def init_async_db():
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ClassificationConcept {
|
||||
id: number;
|
||||
classification: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ClassificationConceptCreate {
|
||||
classification: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ClassificationConceptUpdate extends Partial<ClassificationConceptCreate> {}
|
||||
|
||||
export interface ClassificationConceptListResponse {
|
||||
items: ClassificationConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getClassificationConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ClassificationConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/classification_concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getClassificationConcept(id: number): Promise<ClassificationConcept> {
|
||||
const response = await api.get(`/a76/classification_concepts/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createClassificationConcept(data: ClassificationConceptCreate): Promise<ClassificationConcept> {
|
||||
const response = await api.post('/a76/classification_concepts', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateClassificationConcept(id: number, data: ClassificationConceptUpdate): Promise<ClassificationConcept> {
|
||||
const response = await api.patch(`/a76/classification_concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteClassificationConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/classification_concepts/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Company {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
name: string | null;
|
||||
rfc: string | null;
|
||||
main_activity: string | null;
|
||||
program: string | null;
|
||||
program_number: string | null;
|
||||
prosec: number | null;
|
||||
prosec_authorization: string | null;
|
||||
manufacturer_id: string | null;
|
||||
broker_company: string | null;
|
||||
responsible: string | null;
|
||||
responsible_name: string | null;
|
||||
responsible_last_name: string | null;
|
||||
responsible_mother_last_name: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyCreate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyUpdate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyListResponse {
|
||||
items: Company[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getCompanies(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CompanyListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/company?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
return await api.get(`/a76/company/${id}`);
|
||||
}
|
||||
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/a76/company`, data);
|
||||
}
|
||||
|
||||
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
|
||||
return await api.put(`/a76/company/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/company/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Concept {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ConceptCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
}
|
||||
|
||||
export interface ConceptUpdate extends Partial<ConceptCreate> {}
|
||||
|
||||
export interface ConceptListResponse {
|
||||
items: Concept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getConcept(id: number): Promise<Concept> {
|
||||
const response = await api.get(`/a76/concepts/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createConcept(data: ConceptCreate): Promise<Concept> {
|
||||
const response = await api.post('/a76/concepts', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateConcept(id: number, data: ConceptUpdate): Promise<Concept> {
|
||||
const response = await api.patch(`/a76/concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/concepts/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface CustomsBrokerConcept {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface CustomsBrokerConceptCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
}
|
||||
|
||||
export interface CustomsBrokerConceptUpdate extends Partial<CustomsBrokerConceptCreate> {}
|
||||
|
||||
export interface CustomsBrokerConceptListResponse {
|
||||
items: CustomsBrokerConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CustomsBrokerConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/customs_broker_concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcept(id: number): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.get(`/a76/customs_broker_concepts/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.post('/a76/customs_broker_concepts', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.patch(`/a76/customs_broker_concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteCustomsBrokerConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/customs_broker_concepts/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface DODA {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface DODACreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface DODAUpdate extends Partial<DODACreate> {}
|
||||
|
||||
export interface DODAListResponse {
|
||||
items: DODA[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getDODAs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<DODAListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/doda?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getDODA(id: number): Promise<DODA> {
|
||||
const response = await api.get(`/a76/doda/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createDODA(data: DODACreate): Promise<DODA> {
|
||||
const response = await api.post('/a76/doda', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateDODA(id: number, data: DODAUpdate): Promise<DODA> {
|
||||
const response = await api.patch(`/a76/doda/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteDODA(id: number): Promise<void> {
|
||||
await api.delete(`/a76/doda/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ElectronicNotice {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ElectronicNoticeCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ElectronicNoticeUpdate extends Partial<ElectronicNoticeCreate> {}
|
||||
|
||||
export interface ElectronicNoticeListResponse {
|
||||
items: ElectronicNotice[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getElectronicNotices(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ElectronicNoticeListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/electronic_notices?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getElectronicNotice(id: number): Promise<ElectronicNotice> {
|
||||
const response = await api.get(`/a76/electronic_notices/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createElectronicNotice(data: ElectronicNoticeCreate): Promise<ElectronicNotice> {
|
||||
const response = await api.post('/a76/electronic_notices', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate): Promise<ElectronicNotice> {
|
||||
const response = await api.patch(`/a76/electronic_notices/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteElectronicNotice(id: number): Promise<void> {
|
||||
await api.delete(`/a76/electronic_notices/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Equivalency {
|
||||
id: number;
|
||||
fraccion_mex: string;
|
||||
fraccion_us: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface EquivalencyCreate {
|
||||
fraccion_mex: string;
|
||||
fraccion_us: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface EquivalencyUpdate extends Partial<EquivalencyCreate> {}
|
||||
|
||||
export interface EquivalencyListResponse {
|
||||
items: Equivalency[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getEquivalencies(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<EquivalencyListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/equivalencies?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getEquivalency(id: number): Promise<Equivalency> {
|
||||
const response = await api.get(`/a76/equivalencies/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createEquivalency(data: EquivalencyCreate): Promise<Equivalency> {
|
||||
const response = await api.post('/a76/equivalencies', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateEquivalency(id: number, data: EquivalencyUpdate): Promise<Equivalency> {
|
||||
const response = await api.patch(`/a76/equivalencies/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteEquivalency(id: number): Promise<void> {
|
||||
await api.delete(`/a76/equivalencies/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ErrorCatalog {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogUpdate extends Partial<ErrorCatalogCreate> {}
|
||||
|
||||
export interface ErrorCatalogListResponse {
|
||||
items: ErrorCatalog[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getErrorCatalogs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ErrorCatalogListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/error_catalogs?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getErrorCatalog(id: number): Promise<ErrorCatalog> {
|
||||
const response = await api.get(`/a76/error_catalogs/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createErrorCatalog(data: ErrorCatalogCreate): Promise<ErrorCatalog> {
|
||||
const response = await api.post('/a76/error_catalogs', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate): Promise<ErrorCatalog> {
|
||||
const response = await api.patch(`/a76/error_catalogs/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteErrorCatalog(id: number): Promise<void> {
|
||||
await api.delete(`/a76/error_catalogs/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Identifier {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
level: string | null;
|
||||
complement: string | null;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface IdentifierCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
level?: string | null;
|
||||
complement?: string | null;
|
||||
company_id: number;
|
||||
}
|
||||
|
||||
export interface IdentifierUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
level?: string | null;
|
||||
complement?: string | null;
|
||||
}
|
||||
|
||||
export interface IdentifierListResponse {
|
||||
items: Identifier[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getIdentifiers(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<IdentifierListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/identifiers?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createIdentifier(data: IdentifierCreate): Promise<ApiResponse<Identifier>> {
|
||||
return await api.post('/a76/identifiers', data);
|
||||
}
|
||||
|
||||
export async function updateIdentifier(id: number, data: IdentifierUpdate): Promise<ApiResponse<Identifier>> {
|
||||
return await api.put(`/a76/identifiers/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteIdentifier(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/identifiers/${id}`);
|
||||
}
|
||||
21
frontend/src/lib/api/dashboard/a76/general_catalogs/index.ts
Normal file
21
frontend/src/lib/api/dashboard/a76/general_catalogs/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Índice de exportación para catálogos generales A76
|
||||
*/
|
||||
|
||||
// Unit Measures - Main
|
||||
export * from './unit-measures';
|
||||
|
||||
// Unit Measures - Customs (Mexican)
|
||||
export * from './um-customs-mex';
|
||||
|
||||
// Unit Measures - American
|
||||
export * from './um-customs-ame';
|
||||
|
||||
// Unit Measures - ACE
|
||||
export * from './um-ace';
|
||||
|
||||
// Unit Measures - OMA
|
||||
export * from './um-oma';
|
||||
|
||||
// Locations (from ports)
|
||||
export * from './locations';
|
||||
63
frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts
Normal file
63
frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface INPC {
|
||||
id: number;
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface INPCCreate {
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface INPCUpdate extends Partial<INPCCreate> {}
|
||||
|
||||
export interface INPCListResponse {
|
||||
items: INPC[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getINPCs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<INPCListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/inpc?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getINPC(id: number): Promise<INPC> {
|
||||
const response = await api.get(`/a76/inpc/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createINPC(data: INPCCreate): Promise<INPC> {
|
||||
const response = await api.post('/a76/inpc', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateINPC(id: number, data: INPCUpdate): Promise<INPC> {
|
||||
const response = await api.patch(`/a76/inpc/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteINPC(id: number): Promise<void> {
|
||||
await api.delete(`/a76/inpc/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Legend {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface LegendCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface LegendUpdate extends Partial<LegendCreate> {}
|
||||
|
||||
export interface LegendListResponse {
|
||||
items: Legend[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getLegends(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<LegendListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/legends?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getLegend(id: number): Promise<Legend> {
|
||||
const response = await api.get(`/a76/legends/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createLegend(data: LegendCreate): Promise<Legend> {
|
||||
const response = await api.post('/a76/legends', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateLegend(id: number, data: LegendUpdate): Promise<Legend> {
|
||||
const response = await api.patch(`/a76/legends/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteLegend(id: number): Promise<void> {
|
||||
await api.delete(`/a76/legends/${id}`);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user