Files
routesapi/app/routes/riders.py
2026-06-22 17:40:08 +05:30

150 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Riders Admin API
================
Endpoints for the operations team to manage rider substitutions.
POST /api/v1/riders/substitution register one or many substitutions
GET /api/v1/riders/substitution list upcoming/active subs
DELETE /api/v1/riders/substitution/{sub_date}/{absent_rider_id} cancel one
"""
import logging
from datetime import date
from typing import List
from fastapi import APIRouter, Body, HTTPException, Path
from pydantic import BaseModel, model_validator
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/api/v1/riders",
tags=["Riders & Substitutions"],
responses={500: {"description": "Internal server error"}},
)
class SubstitutionEntry(BaseModel):
sub_date: str
absent_rider_id: int
sub_rider_id: int
@model_validator(mode="after")
def validate_entry(self):
try:
date.fromisoformat(self.sub_date)
except ValueError:
raise ValueError(f"Invalid date format '{self.sub_date}'. Use YYYY-MM-DD.")
if self.absent_rider_id == self.sub_rider_id:
raise ValueError("absent_rider_id and sub_rider_id must be different.")
return self
@router.post("/substitution", summary="Register one or multiple rider substitutions")
def register_substitution(entries: List[SubstitutionEntry] = Body(...)):
"""
Register rider substitutions for the operations team. Accepts a list so
you can submit all absent riders for a day in one call.
Example — single:
```json
[{"sub_date": "2026-06-20", "absent_rider_id": 101, "sub_rider_id": 205}]
```
Example — batch (3 riders absent same day):
```json
[
{"sub_date": "2026-06-20", "absent_rider_id": 101, "sub_rider_id": 205},
{"sub_date": "2026-06-20", "absent_rider_id": 102, "sub_rider_id": 206},
{"sub_date": "2026-06-21", "absent_rider_id": 103, "sub_rider_id": 207}
]
```
Each sub rider **must** appear in that day's getriderlogs response.
The assignment engine copies the absent rider's kitchen ownership, soft
preferences, and home location onto the sub rider automatically — reverts
the next day with no action needed.
Posting the same (sub_date, absent_rider_id) again updates sub_rider_id.
"""
if not entries:
raise HTTPException(status_code=400, detail="Request body must be a non-empty list.")
try:
from app.services.rider.substitution_service import get_substitution_service
svc = get_substitution_service()
results = [svc.register(e.sub_date, e.absent_rider_id, e.sub_rider_id) for e in entries]
return {
"status": "ok",
"registered": len(results),
"substitutions": results,
}
except Exception as e:
logger.error(f"[Riders API] register_substitution: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/substitution", summary="List upcoming and active rider substitutions")
def list_substitutions(from_date: str = None):
"""
Returns all substitution records on or after `from_date` (default: today).
Use `?from_date=2026-06-01` to look back further.
"""
if from_date is not None:
try:
date.fromisoformat(from_date)
except ValueError:
raise HTTPException(
status_code=400,
detail=f"Invalid date format '{from_date}'. Use YYYY-MM-DD.",
)
try:
from app.services.rider.substitution_service import get_substitution_service
records = get_substitution_service().list_all(from_date)
return {
"status": "ok",
"count": len(records),
"substitutions": records,
}
except Exception as e:
logger.error(f"[Riders API] list_substitutions: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.delete(
"/substitution/{sub_date}/{absent_rider_id}",
summary="Cancel a rider substitution",
)
def cancel_substitution(
sub_date: str = Path(..., example="2026-06-20"),
absent_rider_id: int = Path(..., example=101),
):
"""
Remove a substitution record. The sub rider will no longer inherit the
absent rider's profile on that date.
"""
try:
date.fromisoformat(sub_date)
except ValueError:
raise HTTPException(
status_code=400,
detail=f"Invalid date format '{sub_date}'. Use YYYY-MM-DD.",
)
try:
from app.services.rider.substitution_service import get_substitution_service
removed = get_substitution_service().cancel(sub_date, absent_rider_id)
if not removed:
raise HTTPException(
status_code=404,
detail=f"No substitution found for absent_rider_id={absent_rider_id} on {sub_date}.",
)
return {
"status": "ok",
"message": f"Substitution cancelled: rider {absent_rider_id} on {sub_date}.",
}
except HTTPException:
raise
except Exception as e:
logger.error(f"[Riders API] cancel_substitution: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))