""" Expected Deliveries API for Procurement Service Public endpoint for expected delivery tracking """ from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from sqlalchemy.orm import selectinload import structlog import uuid from datetime import datetime, timezone, timedelta from typing import Optional, List from decimal import Decimal from app.core.database import get_db from app.models.purchase_order import PurchaseOrder, PurchaseOrderItem, PurchaseOrderStatus from shared.auth.decorators import get_current_user_dep from shared.routing import RouteBuilder logger = structlog.get_logger() route_builder = RouteBuilder('procurement') router = APIRouter(tags=["expected-deliveries"]) @router.get( route_builder.build_base_route("expected-deliveries") ) async def get_expected_deliveries( tenant_id: str, days_ahead: int = Query(1, description="Number of days to look ahead", ge=0, le=30), include_overdue: bool = Query(True, description="Include overdue deliveries"), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """ Get expected deliveries for delivery tracking system. Args: tenant_id: Tenant UUID to query days_ahead: Number of days to look ahead (default 1 = today + tomorrow) include_overdue: Include deliveries past expected date (default True) Returns: { "deliveries": [ { "po_id": "uuid", "po_number": "PO-2025-123", "supplier_id": "uuid", "supplier_name": "Molinos San José", "supplier_phone": "+34 915 234 567", "expected_delivery_date": "2025-12-02T10:00:00Z", "delivery_window_hours": 4, "status": "sent_to_supplier", "line_items": [...], "total_amount": 540.00, "currency": "EUR" } ], "total_count": 8 } """ try: # Parse tenant_id tenant_uuid = uuid.UUID(tenant_id) # Calculate date range now = datetime.now(timezone.utc) end_date = now + timedelta(days=days_ahead) logger.info( "Fetching expected deliveries", tenant_id=tenant_id, days_ahead=days_ahead, include_overdue=include_overdue ) # Build query for purchase orders with expected delivery dates query = select(PurchaseOrder).options( selectinload(PurchaseOrder.items) ).where( PurchaseOrder.tenant_id == tenant_uuid, PurchaseOrder.expected_delivery_date.isnot(None), PurchaseOrder.status.in_([ PurchaseOrderStatus.approved, PurchaseOrderStatus.sent_to_supplier, PurchaseOrderStatus.confirmed ]) ) # Add date filters if include_overdue: # Include any delivery from past until end_date query = query.where( PurchaseOrder.expected_delivery_date <= end_date ) else: # Only future deliveries within range query = query.where( PurchaseOrder.expected_delivery_date >= now, PurchaseOrder.expected_delivery_date <= end_date ) # Order by delivery date query = query.order_by(PurchaseOrder.expected_delivery_date.asc()) # Execute query result = await db.execute(query) purchase_orders = result.scalars().all() # Format deliveries for response deliveries = [] for po in purchase_orders: # Get supplier info from supplier service (for now, use supplier_id) # In production, you'd fetch from supplier service or join if same DB supplier_name = f"Supplier-{str(po.supplier_id)[:8]}" supplier_phone = None # Try to get supplier details from notes or metadata # This is a simplified approach - in production you'd query supplier service if po.notes: if "Molinos San José" in po.notes: supplier_name = "Molinos San José S.L." supplier_phone = "+34 915 234 567" elif "Lácteos del Valle" in po.notes: supplier_name = "Lácteos del Valle S.A." supplier_phone = "+34 913 456 789" elif "Chocolates Valor" in po.notes: supplier_name = "Chocolates Valor" supplier_phone = "+34 965 510 062" elif "Suministros Hostelería" in po.notes: supplier_name = "Suministros Hostelería" supplier_phone = "+34 911 234 567" elif "Miel Artesana" in po.notes: supplier_name = "Miel Artesana" supplier_phone = "+34 918 765 432" # Format line items (limit to first 5) line_items = [] for item in po.items[:5]: line_items.append({ "product_name": item.product_name, "quantity": float(item.ordered_quantity) if item.ordered_quantity else 0, "unit": item.unit_of_measure or "unit" }) # Default delivery window is 4 hours delivery_window_hours = 4 delivery_dict = { "po_id": str(po.id), "po_number": po.po_number, "supplier_id": str(po.supplier_id), "supplier_name": supplier_name, "supplier_phone": supplier_phone, "expected_delivery_date": po.expected_delivery_date.isoformat(), "delivery_window_hours": delivery_window_hours, "status": po.status.value, "line_items": line_items, "total_amount": float(po.total_amount) if po.total_amount else 0.0, "currency": po.currency } deliveries.append(delivery_dict) logger.info( "Expected deliveries retrieved", tenant_id=tenant_id, count=len(deliveries) ) return { "deliveries": deliveries, "total_count": len(deliveries) } except ValueError as e: logger.error("Invalid UUID format", error=str(e), tenant_id=tenant_id) raise HTTPException(status_code=400, detail=f"Invalid UUID: {tenant_id}") except Exception as e: logger.error( "Error fetching expected deliveries", error=str(e), tenant_id=tenant_id, exc_info=True ) raise HTTPException(status_code=500, detail="Internal server error")