Files
bakery-ia/services/procurement/app/api/internal_delivery.py
2026-01-12 14:24:14 +01:00

189 lines
6.9 KiB
Python

"""
Internal Delivery Tracking API for Procurement Service
Service-to-service endpoint for expected delivery tracking by orchestrator
"""
from fastapi import APIRouter, Depends, HTTPException, Header, 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 app.core.config import settings
logger = structlog.get_logger()
router = APIRouter(prefix="/internal", tags=["internal"])
@router.get("/expected-deliveries")
async def get_expected_deliveries(
tenant_id: str = Query(..., description="Tenant UUID"),
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"),
db: AsyncSession = Depends(get_db)
):
"""
Get expected deliveries for delivery tracking system.
Called by orchestrator's DeliveryTrackingService to monitor upcoming deliveries
and generate delivery alerts (arriving_soon, overdue, receipt_incomplete).
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() if po.expected_delivery_date else None,
"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")