New enterprise feature
This commit is contained in:
225
services/distribution/app/api/routes.py
Normal file
225
services/distribution/app/api/routes.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
API Routes for Distribution Service
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Header
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import date, timedelta
|
||||
import structlog
|
||||
import os
|
||||
|
||||
from app.api.dependencies import get_distribution_service
|
||||
from shared.auth.tenant_access import verify_tenant_permission_dep
|
||||
from app.core.config import settings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
async def verify_internal_api_key(x_internal_api_key: str = Header(None)):
|
||||
"""Verify internal API key for service-to-service communication"""
|
||||
required_key = settings.INTERNAL_API_KEY
|
||||
if x_internal_api_key != required_key:
|
||||
logger.warning("Unauthorized internal API access attempted")
|
||||
raise HTTPException(status_code=403, detail="Invalid internal API key")
|
||||
return True
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/distribution/plans/generate")
|
||||
async def generate_daily_distribution_plan(
|
||||
tenant_id: str,
|
||||
target_date: date = Query(..., description="Date for which to generate distribution plan"),
|
||||
vehicle_capacity_kg: float = Query(1000.0, description="Vehicle capacity in kg"),
|
||||
distribution_service: object = Depends(get_distribution_service),
|
||||
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
||||
):
|
||||
"""
|
||||
Generate daily distribution plan for internal transfers
|
||||
|
||||
**Enterprise Tier Feature**: Distribution and routing require Enterprise subscription.
|
||||
"""
|
||||
try:
|
||||
# Validate subscription tier for distribution features
|
||||
from shared.subscription.plans import PlanFeatures
|
||||
from shared.clients import get_tenant_client
|
||||
|
||||
tenant_client = get_tenant_client(config=settings, service_name="distribution-service")
|
||||
subscription = await tenant_client.get_tenant_subscription(tenant_id)
|
||||
|
||||
if not subscription:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="No active subscription found. Distribution routing requires Enterprise tier."
|
||||
)
|
||||
|
||||
# Check if tier has distribution feature (enterprise only)
|
||||
tier = subscription.get("plan", "starter")
|
||||
if not PlanFeatures.has_feature(tier, "distribution_management"):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Distribution routing requires Enterprise tier. Current tier: {tier}"
|
||||
)
|
||||
|
||||
result = await distribution_service.generate_daily_distribution_plan(
|
||||
parent_tenant_id=tenant_id,
|
||||
target_date=target_date,
|
||||
vehicle_capacity_kg=vehicle_capacity_kg
|
||||
)
|
||||
return result
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error generating distribution plan", error=str(e), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to generate distribution plan: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/distribution/routes")
|
||||
async def get_delivery_routes(
|
||||
tenant_id: str,
|
||||
date_from: Optional[date] = Query(None, description="Start date for route filtering"),
|
||||
date_to: Optional[date] = Query(None, description="End date for route filtering"),
|
||||
status: Optional[str] = Query(None, description="Filter by route status"),
|
||||
distribution_service: object = Depends(get_distribution_service),
|
||||
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
||||
):
|
||||
"""
|
||||
Get delivery routes 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
|
||||
|
||||
routes = []
|
||||
current_date = date_from
|
||||
while current_date <= date_to:
|
||||
daily_routes = await distribution_service.get_delivery_routes_for_date(tenant_id, current_date)
|
||||
routes.extend(daily_routes)
|
||||
current_date = current_date + timedelta(days=1)
|
||||
|
||||
if status:
|
||||
routes = [r for r in routes if r.get('status') == status]
|
||||
|
||||
return {"routes": routes}
|
||||
except Exception as e:
|
||||
logger.error("Error getting delivery routes", error=str(e), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get delivery routes: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/distribution/shipments")
|
||||
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),
|
||||
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
||||
):
|
||||
"""
|
||||
Get 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:
|
||||
logger.error("Error getting shipments", error=str(e), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get shipments: {str(e)}")
|
||||
|
||||
|
||||
@router.put("/tenants/{tenant_id}/distribution/shipments/{shipment_id}/status")
|
||||
async def update_shipment_status(
|
||||
tenant_id: str,
|
||||
shipment_id: str,
|
||||
status_update: dict, # Should be a Pydantic model in production
|
||||
distribution_service: object = Depends(get_distribution_service),
|
||||
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
||||
):
|
||||
"""
|
||||
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" # 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:
|
||||
logger.error("Error updating shipment status", error=str(e), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update shipment status: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/distribution/shipments/{shipment_id}/delivery-proof")
|
||||
async def upload_delivery_proof(
|
||||
tenant_id: str,
|
||||
shipment_id: str,
|
||||
delivery_proof: dict, # Should be a Pydantic model in production
|
||||
distribution_service: object = Depends(get_distribution_service),
|
||||
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
||||
):
|
||||
"""
|
||||
Upload delivery proof (signature, photo, etc.)
|
||||
"""
|
||||
try:
|
||||
# Implementation would handle signature/photo upload
|
||||
# This is a placeholder until proper models are created
|
||||
raise HTTPException(status_code=501, detail="Delivery proof upload endpoint not yet implemented")
|
||||
except Exception as e:
|
||||
logger.error("Error uploading delivery proof", error=str(e), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to upload delivery proof: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/distribution/routes/{route_id}")
|
||||
async def get_route_detail(
|
||||
tenant_id: str,
|
||||
route_id: str,
|
||||
distribution_service: object = Depends(get_distribution_service),
|
||||
verified_tenant: str = Depends(verify_tenant_permission_dep)
|
||||
):
|
||||
"""
|
||||
Get delivery route details
|
||||
"""
|
||||
try:
|
||||
# Implementation would fetch detailed route information
|
||||
# For now, return a simple response
|
||||
routes = await distribution_service.get_delivery_routes_for_date(tenant_id, date.today())
|
||||
route = next((r for r in routes if r.get('id') == route_id), None)
|
||||
|
||||
if not route:
|
||||
raise HTTPException(status_code=404, detail="Route not found")
|
||||
|
||||
return route
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error getting route detail", error=str(e), exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get route detail: {str(e)}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user