"""Rental request router: accept Mietanfrage, save, forward to Rentman.""" import random from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, status, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from typing import Any from app.database import get_db from app.models.equipment import EquipmentCache from app.models.rental_request import RentalRequest, RentalRequestItem from app.schemas.rental_request import RentalRequestCreate, RentalRequestResponse from app.services.rentman_service import RentmanService from app.services.email_service import EmailService from app.cache import cache router = APIRouter(prefix="/api/rental-requests", tags=["rental-requests"]) def generate_reference_number() -> str: """Generate a unique reference number: HMS-YYYY-NNNNN.""" year = datetime.now().year num = random.randint(1, 99999) return f"HMS-{year}-{num:05d}" @router.post("", response_model=RentalRequestResponse, status_code=status.HTTP_201_CREATED) async def create_rental_request( payload: RentalRequestCreate, request: Request, db: AsyncSession = Depends(get_db), ) -> Any: """Create a rental request, save to DB, forward to Rentman, send email.""" # Rate limiting client_ip = request.client.host if request.client else "unknown" rate_key = f"rate:rental:{client_ip}" count = await cache.incr_rate(rate_key, window=60) if count > 5: raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests") ref_number = generate_reference_number() # Save to database rental = RentalRequest( reference_number=ref_number, event_name=payload.event_name, date_start=payload.date_start, date_end=payload.date_end, location=payload.location, person_count=payload.person_count, contact_name=payload.contact_name, contact_company=payload.contact_company, contact_email=str(payload.contact_email), contact_phone=payload.contact_phone, contact_street=payload.contact_street, contact_postalcode=payload.contact_postalcode, contact_city=payload.contact_city, message=payload.message, status="pending", rentman_sync_status="pending", ) db.add(rental) await db.flush() # Fetch equipment info for items items_data = [] for item in payload.items: result = await db.execute(select(EquipmentCache).where(EquipmentCache.id == item.equipment_id)) eq = result.scalar_one_or_none() eq_name = eq.name if eq else "Unknown" eq_rentman_id = eq.rentman_id if eq else "" req_item = RentalRequestItem( rental_request_id=rental.id, equipment_id=item.equipment_id, equipment_name=eq_name, rentman_equipment_id=eq_rentman_id, quantity=item.quantity, rentman_sync_status="pending", ) db.add(req_item) items_data.append({ "equipment_name": eq_name, "rentman_equipment_id": eq_rentman_id, "quantity": item.quantity, }) await db.commit() # Forward to Rentman rentman = RentmanService() rentman_payload = RentmanService.build_project_request_payload({ "event_name": payload.event_name, "date_start": str(payload.date_start), "date_end": str(payload.date_end), "contact_name": payload.contact_name, "contact_company": payload.contact_company, "contact_email": str(payload.contact_email), "contact_phone": payload.contact_phone, "contact_street": payload.contact_street, "contact_postalcode": payload.contact_postalcode, "contact_city": payload.contact_city, "location": payload.location, "message": payload.message, }) try: rentman_response = await rentman.create_project_request(rentman_payload) rentman_request_id = str(rentman_response.get("id", "")) rental.rentman_request_id = rentman_request_id rental.rentman_sync_status = "project_created" # Add equipment items to Rentman for item_data in items_data: eq_payload = RentmanService.build_equipment_payload(item_data) try: await rentman.add_equipment_to_request(rentman_request_id, eq_payload) except Exception: # Item-level failure: mark as failed but continue pass rental.rentman_sync_status = "success" except Exception: rental.rentman_sync_status = "failed" rental.rentman_sync_error = "Rentman API request failed" await db.commit() # Send confirmation email email_service = EmailService() try: await email_service.send_rental_confirmation( to_addr=str(payload.contact_email), reference_number=ref_number, event_name=payload.event_name, items=items_data, db=db, ) except Exception: # Email failure should not affect response pass return RentalRequestResponse(reference_number=ref_number, status="pending")