Files
bakery-ia/services/suppliers/app/api/deliveries.py
2025-08-13 17:39:35 +02:00

404 lines
16 KiB
Python

# services/suppliers/app/api/deliveries.py
"""
Delivery API endpoints
"""
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from typing import List, Optional
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, DeliveryStatusUpdate, DeliveryReceiptConfirmation,
DeliveryPerformanceStats, DeliverySummaryStats
)
from app.models.suppliers import DeliveryStatus
from shared.auth.dependencies import get_current_user, require_permissions
from shared.auth.models import UserInfo
router = APIRouter(prefix="/deliveries", tags=["deliveries"])
logger = structlog.get_logger()
@router.post("/", response_model=DeliveryResponse)
async def create_delivery(
delivery_data: DeliveryCreate,
current_user: UserInfo = Depends(get_current_user),
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("/", 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: UserInfo = Depends(get_current_user),
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("/today", response_model=List[DeliverySummary])
async def get_todays_deliveries(
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get deliveries scheduled for today"""
require_permissions(current_user, ["deliveries:read"])
try:
service = DeliveryService(db)
deliveries = await service.get_todays_deliveries(current_user.tenant_id)
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
except Exception as e:
logger.error("Error getting today's deliveries", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve today's deliveries")
@router.get("/overdue", response_model=List[DeliverySummary])
async def get_overdue_deliveries(
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get overdue deliveries"""
require_permissions(current_user, ["deliveries:read"])
try:
service = DeliveryService(db)
deliveries = await service.get_overdue_deliveries(current_user.tenant_id)
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
except Exception as e:
logger.error("Error getting overdue deliveries", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve overdue deliveries")
@router.get("/scheduled", response_model=List[DeliverySummary])
async def get_scheduled_deliveries(
date_from: Optional[str] = Query(None, description="From date (YYYY-MM-DD)"),
date_to: Optional[str] = Query(None, description="To date (YYYY-MM-DD)"),
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get scheduled deliveries for a date range"""
require_permissions(current_user, ["deliveries:read"])
try:
from datetime import datetime
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")
service = DeliveryService(db)
deliveries = await service.get_scheduled_deliveries(
tenant_id=current_user.tenant_id,
date_from=date_from_parsed,
date_to=date_to_parsed
)
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
except HTTPException:
raise
except Exception as e:
logger.error("Error getting scheduled deliveries", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve scheduled deliveries")
@router.get("/performance-stats", response_model=DeliveryPerformanceStats)
async def get_delivery_performance_stats(
days_back: int = Query(30, ge=1, le=365, description="Number of days to analyze"),
supplier_id: Optional[UUID] = Query(None, description="Filter by supplier ID"),
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get delivery performance statistics"""
require_permissions(current_user, ["deliveries:read"])
try:
service = DeliveryService(db)
stats = await service.get_delivery_performance_stats(
tenant_id=current_user.tenant_id,
days_back=days_back,
supplier_id=supplier_id
)
return DeliveryPerformanceStats(**stats)
except Exception as e:
logger.error("Error getting delivery performance stats", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve delivery performance statistics")
@router.get("/summary-stats", response_model=DeliverySummaryStats)
async def get_delivery_summary_stats(
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get delivery summary statistics for dashboard"""
require_permissions(current_user, ["deliveries:read"])
try:
service = DeliveryService(db)
stats = await service.get_upcoming_deliveries_summary(current_user.tenant_id)
return DeliverySummaryStats(**stats)
except Exception as e:
logger.error("Error getting delivery summary stats", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve delivery summary statistics")
@router.get("/{delivery_id}", response_model=DeliveryResponse)
async def get_delivery(
delivery_id: UUID = Path(..., description="Delivery ID"),
current_user: UserInfo = Depends(get_current_user),
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("/{delivery_id}", response_model=DeliveryResponse)
async def update_delivery(
delivery_data: DeliveryUpdate,
delivery_id: UUID = Path(..., description="Delivery ID"),
current_user: UserInfo = Depends(get_current_user),
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")
@router.patch("/{delivery_id}/status", response_model=DeliveryResponse)
async def update_delivery_status(
status_data: DeliveryStatusUpdate,
delivery_id: UUID = Path(..., description="Delivery ID"),
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Update delivery status"""
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_status(
delivery_id=delivery_id,
status=status_data.status,
updated_by=current_user.user_id,
notes=status_data.notes,
update_timestamps=status_data.update_timestamps
)
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 status", delivery_id=str(delivery_id), error=str(e))
raise HTTPException(status_code=500, detail="Failed to update delivery status")
@router.post("/{delivery_id}/receive", response_model=DeliveryResponse)
async def receive_delivery(
receipt_data: DeliveryReceiptConfirmation,
delivery_id: UUID = Path(..., description="Delivery ID"),
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Mark delivery as received with inspection details"""
require_permissions(current_user, ["deliveries:receive"])
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.mark_as_received(
delivery_id=delivery_id,
received_by=current_user.user_id,
inspection_passed=receipt_data.inspection_passed,
inspection_notes=receipt_data.inspection_notes,
quality_issues=receipt_data.quality_issues,
notes=receipt_data.notes
)
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 receiving delivery", delivery_id=str(delivery_id), error=str(e))
raise HTTPException(status_code=500, detail="Failed to receive delivery")
@router.get("/purchase-order/{po_id}", response_model=List[DeliverySummary])
async def get_deliveries_by_purchase_order(
po_id: UUID = Path(..., description="Purchase order ID"),
current_user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get all deliveries for a purchase order"""
require_permissions(current_user, ["deliveries:read"])
try:
service = DeliveryService(db)
deliveries = await service.get_deliveries_by_purchase_order(po_id)
# Check tenant access for first delivery (all should belong to same tenant)
if deliveries and deliveries[0].tenant_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="Access denied")
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
except HTTPException:
raise
except Exception as e:
logger.error("Error getting deliveries by purchase order", po_id=str(po_id), error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve deliveries for purchase order")