# services/suppliers/app/api/deliveries.py """ Delivery CRUD API endpoints (ATOMIC) """ from fastapi import APIRouter, Depends, HTTPException, Query, Path from typing import List, Optional, Dict, Any from uuid import UUID import structlog from sqlalchemy.orm import Session from app.core.database import get_db from app.services.delivery_service import DeliveryService from app.schemas.suppliers import ( DeliveryCreate, DeliveryUpdate, DeliveryResponse, DeliverySummary, DeliverySearchParams ) from app.models.suppliers import DeliveryStatus from shared.auth.decorators import get_current_user_dep from shared.routing import RouteBuilder from shared.auth.access_control import require_user_role # Create route builder for consistent URL structure route_builder = RouteBuilder('suppliers') router = APIRouter(tags=["deliveries"]) logger = structlog.get_logger() @router.post(route_builder.build_base_route("deliveries"), response_model=DeliveryResponse) @require_user_role(['admin', 'owner', 'member']) async def create_delivery( delivery_data: DeliveryCreate, current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """Create a new delivery""" # require_permissions(current_user, ["deliveries:create"]) try: service = DeliveryService(db) delivery = await service.create_delivery( tenant_id=current_user["tenant_id"], delivery_data=delivery_data, created_by=current_user["user_id"] ) return DeliveryResponse.from_orm(delivery) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error("Error creating delivery", error=str(e)) raise HTTPException(status_code=500, detail="Failed to create delivery") @router.get(route_builder.build_base_route("deliveries"), response_model=List[DeliverySummary]) async def list_deliveries( supplier_id: Optional[UUID] = Query(None, description="Filter by supplier ID"), status: Optional[str] = Query(None, description="Filter by status"), date_from: Optional[str] = Query(None, description="Filter from date (YYYY-MM-DD)"), date_to: Optional[str] = Query(None, description="Filter to date (YYYY-MM-DD)"), search_term: Optional[str] = Query(None, description="Search term"), limit: int = Query(50, ge=1, le=1000, description="Number of results to return"), offset: int = Query(0, ge=0, description="Number of results to skip"), current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """List deliveries with optional filters""" # require_permissions(current_user, ["deliveries:read"]) try: from datetime import datetime # Parse date filters date_from_parsed = None date_to_parsed = None if date_from: try: date_from_parsed = datetime.fromisoformat(date_from) except ValueError: raise HTTPException(status_code=400, detail="Invalid date_from format") if date_to: try: date_to_parsed = datetime.fromisoformat(date_to) except ValueError: raise HTTPException(status_code=400, detail="Invalid date_to format") # Validate status status_enum = None if status: try: status_enum = DeliveryStatus(status.upper()) except ValueError: raise HTTPException(status_code=400, detail="Invalid status") service = DeliveryService(db) search_params = DeliverySearchParams( supplier_id=supplier_id, status=status_enum, date_from=date_from_parsed, date_to=date_to_parsed, search_term=search_term, limit=limit, offset=offset ) deliveries = await service.search_deliveries( tenant_id=current_user["tenant_id"], search_params=search_params ) return [DeliverySummary.from_orm(delivery) for delivery in deliveries] except HTTPException: raise except Exception as e: logger.error("Error listing deliveries", error=str(e)) raise HTTPException(status_code=500, detail="Failed to retrieve deliveries") @router.get(route_builder.build_resource_detail_route("deliveries", "delivery_id"), response_model=DeliveryResponse) async def get_delivery( delivery_id: UUID = Path(..., description="Delivery ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """Get delivery by ID with items""" # require_permissions(current_user, ["deliveries:read"]) try: service = DeliveryService(db) delivery = await service.get_delivery(delivery_id) if not delivery: raise HTTPException(status_code=404, detail="Delivery not found") # Check tenant access if delivery.tenant_id != current_user["tenant_id"]: raise HTTPException(status_code=403, detail="Access denied") return DeliveryResponse.from_orm(delivery) except HTTPException: raise except Exception as e: logger.error("Error getting delivery", delivery_id=str(delivery_id), error=str(e)) raise HTTPException(status_code=500, detail="Failed to retrieve delivery") @router.put(route_builder.build_resource_detail_route("deliveries", "delivery_id"), response_model=DeliveryResponse) @require_user_role(['admin', 'owner', 'member']) async def update_delivery( delivery_data: DeliveryUpdate, delivery_id: UUID = Path(..., description="Delivery ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """Update delivery information""" # require_permissions(current_user, ["deliveries:update"]) try: service = DeliveryService(db) # Check delivery exists and belongs to tenant existing_delivery = await service.get_delivery(delivery_id) if not existing_delivery: raise HTTPException(status_code=404, detail="Delivery not found") if existing_delivery.tenant_id != current_user["tenant_id"]: raise HTTPException(status_code=403, detail="Access denied") delivery = await service.update_delivery( delivery_id=delivery_id, delivery_data=delivery_data, updated_by=current_user["user_id"] ) if not delivery: raise HTTPException(status_code=404, detail="Delivery not found") return DeliveryResponse.from_orm(delivery) except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error("Error updating delivery", delivery_id=str(delivery_id), error=str(e)) raise HTTPException(status_code=500, detail="Failed to update delivery")