M2: implement vehicle return vertical slice

Transactional return command with idempotency, row-lock concurrency control, odometer-regression handling, vehicle status derivation, outbox event, audit trail. Result-summary UI on booking detail. 26 backend tests passing, ruff clean. Verified end-to-end via browser against S1 demo scenario; fixed two real defects found only through browser testing (UI state loss on status transition, unflushed UUID default).
This commit is contained in:
NuklearRabbit
2026-08-01 21:49:46 +02:00
parent 03c5b60235
commit 0091c57c7f
14 changed files with 763 additions and 10 deletions
+17 -2
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Response
from sqlalchemy import select
from sqlalchemy.orm import Session
@@ -8,7 +8,8 @@ from app.api.deps import get_current_user, get_db
from app.models.booking import Booking
from app.models.customer import Customer
from app.models.vehicle import Vehicle
from app.schemas import BookingOut, CurrentUser
from app.schemas import BookingOut, CurrentUser, RegisterReturnRequest
from app.services.returns import register_vehicle_return
router = APIRouter(prefix="/api/v1/bookings", tags=["bookings"])
@@ -61,3 +62,17 @@ def get_booking(
customer = db.get(Customer, booking.customer_id)
vehicle = db.get(Vehicle, booking.vehicle_id)
return _to_out(booking, customer, vehicle)
@router.post("/{public_ref}/return")
def register_return(
public_ref: str,
body: RegisterReturnRequest,
response: Response,
idempotency_key: str = Header(..., alias="Idempotency-Key", min_length=8, max_length=128),
db: Session = Depends(get_db),
user: CurrentUser = Depends(get_current_user),
) -> dict:
status_code, result = register_vehicle_return(db, public_ref, body, idempotency_key, user)
response.status_code = status_code
return result