2025-11-30 09:12:40 +01:00
|
|
|
"""
|
|
|
|
|
Shipment API endpoints for distribution service
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
|
|
|
from typing import List, Optional
|
|
|
|
|
from datetime import date, timedelta
|
|
|
|
|
|
|
|
|
|
from app.api.dependencies import get_distribution_service
|
2025-12-05 20:07:01 +01:00
|
|
|
from shared.auth.tenant_access import verify_tenant_access_dep
|
|
|
|
|
from shared.routing.route_builder import RouteBuilder
|
2025-11-30 09:12:40 +01:00
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
2025-12-05 20:07:01 +01:00
|
|
|
# Initialize route builder for distribution service
|
|
|
|
|
route_builder = RouteBuilder('distribution')
|
2025-11-30 09:12:40 +01:00
|
|
|
|
2025-12-05 20:07:01 +01:00
|
|
|
|
|
|
|
|
@router.get(route_builder.build_base_route("shipments"))
|
2025-11-30 09:12:40 +01:00
|
|
|
async def get_shipments(
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
date_from: Optional[date] = Query(None, description="Start date for shipment filtering"),
|
|
|
|
|
date_to: Optional[date] = Query(None, description="End date for shipment filtering"),
|
|
|
|
|
status: Optional[str] = Query(None, description="Filter by shipment status"),
|
|
|
|
|
distribution_service: object = Depends(get_distribution_service),
|
2025-12-05 20:07:01 +01:00
|
|
|
verified_tenant: str = Depends(verify_tenant_access_dep)
|
2025-11-30 09:12:40 +01:00
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
List shipments with optional filtering
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# If no date range specified, default to today
|
|
|
|
|
if not date_from and not date_to:
|
|
|
|
|
date_from = date.today()
|
|
|
|
|
date_to = date.today()
|
|
|
|
|
elif not date_to:
|
|
|
|
|
date_to = date_from
|
|
|
|
|
|
|
|
|
|
shipments = []
|
|
|
|
|
current_date = date_from
|
|
|
|
|
while current_date <= date_to:
|
|
|
|
|
daily_shipments = await distribution_service.get_shipments_for_date(tenant_id, current_date)
|
|
|
|
|
shipments.extend(daily_shipments)
|
|
|
|
|
current_date = current_date + timedelta(days=1)
|
|
|
|
|
|
|
|
|
|
if status:
|
|
|
|
|
shipments = [s for s in shipments if s.get('status') == status]
|
|
|
|
|
|
|
|
|
|
return {"shipments": shipments}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Failed to get shipments: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
2025-12-05 20:07:01 +01:00
|
|
|
@router.put(route_builder.build_base_route("shipments/{shipment_id}/status"))
|
2025-11-30 09:12:40 +01:00
|
|
|
async def update_shipment_status(
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
shipment_id: str,
|
|
|
|
|
status_update: dict, # Should be a proper Pydantic model
|
|
|
|
|
distribution_service: object = Depends(get_distribution_service),
|
2025-12-05 20:07:01 +01:00
|
|
|
verified_tenant: str = Depends(verify_tenant_access_dep)
|
2025-11-30 09:12:40 +01:00
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Update shipment status
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
new_status = status_update.get('status')
|
|
|
|
|
if not new_status:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Status is required")
|
|
|
|
|
|
|
|
|
|
user_id = "temp_user_id" # Would come from auth context
|
|
|
|
|
result = await distribution_service.update_shipment_status(
|
|
|
|
|
shipment_id=shipment_id,
|
|
|
|
|
new_status=new_status,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
metadata=status_update.get('metadata')
|
|
|
|
|
)
|
|
|
|
|
return result
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Failed to update shipment status: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
2025-12-05 20:07:01 +01:00
|
|
|
@router.post(route_builder.build_base_route("shipments/{shipment_id}/delivery-proof"))
|
2025-11-30 09:12:40 +01:00
|
|
|
async def upload_delivery_proof(
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
shipment_id: str,
|
|
|
|
|
delivery_proof: dict, # Should be a proper Pydantic model
|
|
|
|
|
distribution_service: object = Depends(get_distribution_service),
|
2025-12-05 20:07:01 +01:00
|
|
|
verified_tenant: str = Depends(verify_tenant_access_dep)
|
2025-11-30 09:12:40 +01:00
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Upload delivery proof (signature, photo, etc.)
|
2025-12-05 20:07:01 +01:00
|
|
|
|
|
|
|
|
Expected delivery_proof fields:
|
|
|
|
|
- signature: Base64 encoded signature image or signature data
|
|
|
|
|
- photo_url: URL to uploaded delivery photo
|
|
|
|
|
- received_by_name: Name of person who received delivery
|
|
|
|
|
- delivery_notes: Optional notes about delivery
|
2025-11-30 09:12:40 +01:00
|
|
|
"""
|
|
|
|
|
try:
|
2025-12-05 20:07:01 +01:00
|
|
|
user_id = "temp_user_id" # Would come from auth context
|
|
|
|
|
|
|
|
|
|
# Prepare metadata for shipment update
|
|
|
|
|
metadata = {}
|
|
|
|
|
if 'signature' in delivery_proof:
|
|
|
|
|
metadata['signature'] = delivery_proof['signature']
|
|
|
|
|
if 'photo_url' in delivery_proof:
|
|
|
|
|
metadata['photo_url'] = delivery_proof['photo_url']
|
|
|
|
|
if 'received_by_name' in delivery_proof:
|
|
|
|
|
metadata['received_by_name'] = delivery_proof['received_by_name']
|
|
|
|
|
if 'delivery_notes' in delivery_proof:
|
|
|
|
|
metadata['delivery_notes'] = delivery_proof['delivery_notes']
|
|
|
|
|
|
|
|
|
|
# Update shipment with delivery proof
|
|
|
|
|
result = await distribution_service.update_shipment_status(
|
|
|
|
|
shipment_id=shipment_id,
|
|
|
|
|
new_status='delivered', # Automatically mark as delivered when proof uploaded
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
metadata=metadata
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not result:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"message": "Delivery proof uploaded successfully",
|
|
|
|
|
"shipment_id": shipment_id,
|
|
|
|
|
"status": "delivered"
|
|
|
|
|
}
|
|
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
2025-11-30 09:12:40 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Failed to upload delivery proof: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
2025-12-05 20:07:01 +01:00
|
|
|
@router.get(route_builder.build_base_route("shipments/{shipment_id}"))
|
2025-11-30 09:12:40 +01:00
|
|
|
async def get_shipment_detail(
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
shipment_id: str,
|
|
|
|
|
distribution_service: object = Depends(get_distribution_service),
|
2025-12-05 20:07:01 +01:00
|
|
|
verified_tenant: str = Depends(verify_tenant_access_dep)
|
2025-11-30 09:12:40 +01:00
|
|
|
):
|
|
|
|
|
"""
|
2025-12-05 20:07:01 +01:00
|
|
|
Get detailed information about a specific shipment including:
|
|
|
|
|
- Basic shipment info (number, date, status)
|
|
|
|
|
- Parent and child tenant details
|
|
|
|
|
- Delivery route assignment
|
|
|
|
|
- Purchase order reference
|
|
|
|
|
- Delivery proof (signature, photo, received by)
|
|
|
|
|
- Location tracking data
|
2025-11-30 09:12:40 +01:00
|
|
|
"""
|
|
|
|
|
try:
|
2025-12-05 20:07:01 +01:00
|
|
|
# Access the shipment repository from the distribution service
|
|
|
|
|
shipment = await distribution_service.shipment_repository.get_shipment_by_id(shipment_id)
|
|
|
|
|
|
|
|
|
|
if not shipment:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
|
|
|
|
|
|
# Verify tenant access
|
|
|
|
|
if str(shipment.get('tenant_id')) != tenant_id:
|
|
|
|
|
raise HTTPException(status_code=403, detail="Access denied to this shipment")
|
|
|
|
|
|
|
|
|
|
return shipment
|
|
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
2025-11-30 09:12:40 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Failed to get shipment details: {str(e)}")
|