| """ |
| Phase 2 Driver-facing API endpoints. |
| Handles driver operations: assignments, stats, deliveries, feedback, swaps, issues. |
| """ |
|
|
| from datetime import date as date_type |
|
|
| from fastapi import APIRouter, Depends, HTTPException, Query, status |
| from sqlalchemy.ext.asyncio import AsyncSession |
| from uuid import UUID |
|
|
| from app.database import get_db |
| from app.schemas.driver_api import ( |
| TodayAssignmentResponse, |
| DriverStatsWindowResponse, |
| DeliveryLogRequest, |
| DeliveryLogResponse, |
| RouteSwapRequestCreate, |
| RouteSwapRequestResponse, |
| StopIssueRequest, |
| StopIssueResponse, |
| ) |
| from app.services.driver_service import ( |
| get_today_assignment, |
| get_driver_stats, |
| log_delivery, |
| create_route_swap_request, |
| create_stop_issue, |
| ) |
|
|
| router = APIRouter(tags=["Driver"]) |
|
|
|
|
| @router.get( |
| "/assignments/today", |
| response_model=TodayAssignmentResponse, |
| summary="Get today's assignment", |
| description="Fetch the driver's assignment for the given date with full stop details.", |
| ) |
| async def get_assignment_today( |
| driver_id: UUID = Query(..., description="Driver UUID"), |
| target_date: date_type = Query(default=None, description="Date (defaults to today)"), |
| db: AsyncSession = Depends(get_db), |
| ) -> TodayAssignmentResponse: |
| """Get driver's assignment for today or specified date.""" |
| actual_date = target_date or date_type.today() |
| |
| result = await get_today_assignment(db, driver_id, actual_date) |
| |
| if not result: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="No assignment found for driver on given date", |
| ) |
| |
| return result |
|
|
|
|
| @router.get( |
| "/drivers/{driver_id}/stats", |
| response_model=DriverStatsWindowResponse, |
| summary="Get driver stats", |
| description="Get driver statistics over a time window.", |
| ) |
| async def get_driver_stats_endpoint( |
| driver_id: UUID, |
| window_days: int = Query(default=7, ge=1, le=90, description="Days to look back"), |
| db: AsyncSession = Depends(get_db), |
| ) -> DriverStatsWindowResponse: |
| """Get driver stats over time window.""" |
| result = await get_driver_stats(db, driver_id, window_days) |
| |
| if not result: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Driver not found", |
| ) |
| |
| return result |
|
|
|
|
| @router.post( |
| "/deliveries/log", |
| response_model=DeliveryLogResponse, |
| status_code=status.HTTP_201_CREATED, |
| summary="Log delivery", |
| description="Log a delivery attempt at a stop.", |
| ) |
| async def log_delivery_endpoint( |
| request: DeliveryLogRequest, |
| db: AsyncSession = Depends(get_db), |
| ) -> DeliveryLogResponse: |
| """Log a delivery at a stop.""" |
| try: |
| result = await log_delivery( |
| db=db, |
| assignment_id=request.assignment_id, |
| route_id=request.route_id, |
| driver_id=request.driver_id, |
| stop_order=request.stop_order, |
| status=request.status, |
| issue_type=request.issue_type, |
| package_id=request.package_id, |
| photo_url=request.photo_url, |
| signature_data=request.signature_data, |
| notes=request.notes, |
| ) |
| await db.commit() |
| return result |
| except ValueError as e: |
| raise HTTPException( |
| status_code=status.HTTP_400_BAD_REQUEST, |
| detail=str(e), |
| ) |
|
|
|
|
| @router.post( |
| "/route_swap_requests", |
| response_model=RouteSwapRequestResponse, |
| status_code=status.HTTP_201_CREATED, |
| summary="Request route swap", |
| description="Submit a route swap request.", |
| ) |
| async def create_route_swap_endpoint( |
| request: RouteSwapRequestCreate, |
| db: AsyncSession = Depends(get_db), |
| ) -> RouteSwapRequestResponse: |
| """Create a route swap request.""" |
| try: |
| result = await create_route_swap_request( |
| db=db, |
| from_driver_id=request.from_driver_id, |
| assignment_id=request.assignment_id, |
| reason=request.reason, |
| to_driver_id=request.to_driver_id, |
| preferred_date=request.preferred_date, |
| ) |
| await db.commit() |
| return result |
| except ValueError as e: |
| raise HTTPException( |
| status_code=status.HTTP_400_BAD_REQUEST, |
| detail=str(e), |
| ) |
|
|
|
|
| @router.post( |
| "/stop_issues", |
| response_model=StopIssueResponse, |
| status_code=status.HTTP_201_CREATED, |
| summary="Report stop issue", |
| description="Report an issue at a specific stop.", |
| ) |
| async def create_stop_issue_endpoint( |
| request: StopIssueRequest, |
| db: AsyncSession = Depends(get_db), |
| ) -> StopIssueResponse: |
| """Create a stop issue report.""" |
| try: |
| result = await create_stop_issue( |
| db=db, |
| assignment_id=request.assignment_id, |
| route_id=request.route_id, |
| driver_id=request.driver_id, |
| stop_order=request.stop_order, |
| issue_type=request.issue_type, |
| notes=request.notes, |
| ) |
| await db.commit() |
| return result |
| except ValueError as e: |
| raise HTTPException( |
| status_code=status.HTTP_400_BAD_REQUEST, |
| detail=str(e), |
| ) |
|
|