Refactor and enhance CRUD operations for Seal, Trailer, Transporter, Vehicle, and Customs Broker modules

- Updated SealService to support tenant and company filtering with pagination and enhanced CRUD methods.
- Refactored TrailerService to include tenant and company support, added filtering capabilities, and improved CRUD methods.
- Introduced TenantCRUDRoutes for Trailer and Transporter routes to streamline API endpoint creation and management.
- Enhanced TransporterService with tenant and company filtering, pagination, and improved CRUD operations.
- Added Customs Broker module with DTOs, models, services, and routes for managing customs broker data.
- Implemented CRUD operations for Customs Broker, including personnel and VU management.
- Improved data validation and descriptions in DTOs for better API documentation.
This commit is contained in:
2025-11-11 18:20:39 -06:00
parent b68c4316ff
commit 962f43fe62
39 changed files with 1488 additions and 1366 deletions

View File

@@ -1,26 +1,34 @@
from typing import Optional
from pydantic import BaseModel
from pydantic import BaseModel, Field
class TrailerBaseDTO(BaseModel):
trailer_number: str
ace_trailer_number: Optional[str]
trailer_type_key: Optional[str]
seal: Optional[str]
entity_code: Optional[str]
plate_number: Optional[str]
state: Optional[str]
country: Optional[str]
container_key: Optional[str]
company_id: str
tenant_id: str
trailer_number: str = Field(..., description="Trailer number (primary identifier)")
ace_trailer_number: Optional[str] = None
trailer_type_key: Optional[str] = None
seal: Optional[str] = None
entity_code: Optional[str] = None
plate_number: Optional[str] = None
state: Optional[str] = None
country: Optional[str] = None
container_key: Optional[str] = None
class TrailerCreateDTO(TrailerBaseDTO):
"""Schema for creating a trailer"""
pass
class TrailerUpdateDTO(TrailerBaseDTO):
"""Schema for updating a trailer"""
trailer_number: Optional[str] = Field(None, description="Trailer number (cannot be modified)")
class TrailerResponseDTO(TrailerBaseDTO):
"""Schema for trailer response"""
company_id: int
tenant_id: int
class Config:
from_attributes = True

View File

@@ -1,30 +1,22 @@
from core.database import get_core_db
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from . import dto, services
from .dto import TrailerCreateDTO, TrailerResponseDTO, TrailerUpdateDTO
from .services import TrailerService
router = APIRouter()
@router.get("/trailers/{trailer_number}", response_model=dto.TrailerResponseDTO)
def get_trailer(trailer_number: str, db: Session = Depends(get_core_db)):
trailer = services.TrailerService.get_trailer_by_number(db, trailer_number)
if not trailer:
raise HTTPException(status_code=404, detail="Trailer not found")
return trailer
@router.post("/trailers", response_model=dto.TrailerResponseDTO)
def create_trailer(
trailer_data: dto.TrailerCreateDTO, db: Session = Depends(get_core_db)
):
return services.TrailerService.create_trailer(db, trailer_data)
@router.delete("/trailers/{trailer_number}", response_model=dto.TrailerResponseDTO)
def delete_trailer(trailer_number: str, db: Session = Depends(get_core_db)):
trailer = services.TrailerService.delete_trailer(db, trailer_number)
if not trailer:
raise HTTPException(status_code=404, detail="Trailer not found")
return trailer
# Create router using TenantCRUDRoutes factory
# Note: trailer_number is a string (not int) and is used as the primary key
router = TenantCRUDRoutes(
service=TrailerService,
create_schema=TrailerCreateDTO,
update_schema=TrailerUpdateDTO,
response_schema=TrailerResponseDTO,
prefix="/trailers",
tags=[],
resource_name="Trailer",
id_name="trailer_number", # Using trailer_number instead of numeric ID
id_type=str, # Specify that the ID is a string
enable_list=True, # Enable GET /trailers with pagination
enable_filters=True, # Enable filtering by plate_number and trailer_type_key
default_page_size=50,
max_page_size=100,
).router

View File

@@ -1,29 +1,108 @@
from typing import Optional, Tuple, List, Dict, Any
from sqlalchemy.orm import Session
from . import dto, models
class TrailerService:
"""Service for Trailer CRUD operations with tenant support"""
@staticmethod
def get_trailer_by_number(db: Session, trailer_number: str):
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[models.Trailer], int]:
"""Get all trailers for a tenant/company with pagination"""
query = db.query(models.Trailer).filter(
models.Trailer.tenant_id == tenant_id,
models.Trailer.company_id == company_id,
)
# Apply filters if provided
if filters:
if filters.get("plate_number"):
query = query.filter(
models.Trailer.plate_number.ilike(f"%{filters['plate_number']}%")
)
if filters.get("trailer_type_key"):
query = query.filter(
models.Trailer.trailer_type_key == filters["trailer_type_key"]
)
total = query.count()
trailers = query.offset(skip).limit(limit).all()
return trailers, total
@staticmethod
def get_by_id(
db: Session, trailer_number: str, tenant_id: int, company_id: int
) -> Optional[models.Trailer]:
"""Get trailer by trailer_number"""
return (
db.query(models.Trailer)
.filter(models.Trailer.trailer_number == trailer_number)
.filter(
models.Trailer.trailer_number == trailer_number,
models.Trailer.tenant_id == tenant_id,
models.Trailer.company_id == company_id,
)
.first()
)
@staticmethod
def create_trailer(db: Session, trailer_data: dto.TrailerCreateDTO):
new_trailer = models.Trailer(**trailer_data.dict())
def create(
db: Session,
trailer_data: dto.TrailerCreateDTO,
tenant_id: int,
company_id: int,
) -> models.Trailer:
"""Create a new trailer"""
new_trailer = models.Trailer(
**trailer_data.model_dump(), tenant_id=tenant_id, company_id=company_id
)
db.add(new_trailer)
db.commit()
db.refresh(new_trailer)
return new_trailer
@staticmethod
def delete_trailer(db: Session, trailer_number: str):
trailer = TrailerService.get_trailer_by_number(db, trailer_number)
if trailer:
db.delete(trailer)
db.commit()
def update(
db: Session,
trailer_number: str,
tenant_id: int,
company_id: int,
trailer_data: dto.TrailerUpdateDTO,
) -> Optional[models.Trailer]:
"""Update a trailer"""
trailer = TrailerService.get_by_id(db, trailer_number, tenant_id, company_id)
if not trailer:
return None
# Update fields (excluding trailer_number as it's the primary key)
update_data = trailer_data.model_dump(
exclude_unset=True, exclude={"trailer_number"}
)
for field, value in update_data.items():
setattr(trailer, field, value)
db.commit()
db.refresh(trailer)
return trailer
@staticmethod
def delete(
db: Session, trailer_number: str, tenant_id: int, company_id: int
) -> bool:
"""Delete a trailer"""
trailer = TrailerService.get_by_id(db, trailer_number, tenant_id, company_id)
if not trailer:
return False
db.delete(trailer)
db.commit()
return True