Initial commit - production deployment
This commit is contained in:
188
services/procurement/app/api/internal_delivery.py
Normal file
188
services/procurement/app/api/internal_delivery.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user