114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
"""Endpoint pubblici — no auth. Usati dal widget di prenotazione."""
|
|
from datetime import date, datetime, timedelta
|
|
from zoneinfo import ZoneInfo
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session, joinedload
|
|
from app.database import get_db
|
|
from app.models import Service, Booking, ProviderService
|
|
from app.schemas import ServiceOut, BookingCreate, BookingOut, TimeSlot
|
|
from app.services.availability import get_available_slots
|
|
from app.services.notifications import notify_booking_confirmed, notify_operator
|
|
|
|
router = APIRouter(prefix="/api", tags=["public"])
|
|
TZ = ZoneInfo("Europe/Rome")
|
|
|
|
|
|
@router.get("/services", response_model=list[ServiceOut])
|
|
def list_services(db: Session = Depends(get_db)):
|
|
"""Lista servizi attivi."""
|
|
return (
|
|
db.query(Service)
|
|
.filter(Service.active == True)
|
|
.order_by(Service.sort_order, Service.name)
|
|
.all()
|
|
)
|
|
|
|
|
|
@router.get("/services/{service_id}/slots")
|
|
def get_slots(service_id: int, date: date, db: Session = Depends(get_db)):
|
|
"""Slot disponibili per un servizio in una data."""
|
|
service = db.query(Service).filter(Service.id == service_id).first()
|
|
if not service:
|
|
raise HTTPException(404, "Servizio non trovato")
|
|
|
|
slots = get_available_slots(db, service_id, date, service.duration_min)
|
|
return {"service": service.name, "date": str(date), "duration_min": service.duration_min, "slots": slots}
|
|
|
|
|
|
@router.post("/bookings", response_model=BookingOut)
|
|
async def create_booking(data: BookingCreate, db: Session = Depends(get_db)):
|
|
"""Crea una prenotazione. No login richiesto."""
|
|
# Verifica servizio
|
|
service = db.query(Service).filter(Service.id == data.service_id).first()
|
|
if not service:
|
|
raise HTTPException(404, "Servizio non trovato")
|
|
|
|
# Verifica che lo slot sia ancora libero
|
|
slots = get_available_slots(db, data.service_id, data.start_at.date(), service.duration_min)
|
|
slot_time = data.start_at.astimezone(TZ).strftime("%H:%M")
|
|
available = [s for s in slots if s.start == slot_time and s.provider_id == data.provider_id]
|
|
if not available:
|
|
raise HTTPException(409, "Lo slot non è più disponibile")
|
|
|
|
# Crea prenotazione
|
|
end_at = data.start_at + timedelta(minutes=service.duration_min)
|
|
booking = Booking(
|
|
service_id=data.service_id,
|
|
provider_id=data.provider_id,
|
|
customer_name=data.customer_name,
|
|
customer_phone=data.customer_phone,
|
|
customer_email=data.customer_email,
|
|
start_at=data.start_at,
|
|
end_at=end_at,
|
|
notes=data.notes,
|
|
)
|
|
db.add(booking)
|
|
db.commit()
|
|
db.refresh(booking)
|
|
|
|
# Carica relazioni per la risposta
|
|
booking = db.query(Booking).options(
|
|
joinedload(Booking.service), joinedload(Booking.provider)
|
|
).filter(Booking.id == booking.id).first()
|
|
|
|
# Notifiche async
|
|
await notify_booking_confirmed(booking, service.name, booking.provider.name)
|
|
if booking.provider.email:
|
|
await notify_operator(booking, service.name, booking.provider.email)
|
|
|
|
return booking
|
|
|
|
|
|
@router.get("/bookings/my")
|
|
def my_bookings(phone: str, db: Session = Depends(get_db)):
|
|
"""Le prenotazioni di un numero di telefono (per check 'ho già prenotato?')."""
|
|
clean = phone.replace("+", "").replace(" ", "").replace("-", "")
|
|
bookings = (
|
|
db.query(Booking)
|
|
.options(joinedload(Booking.service), joinedload(Booking.provider))
|
|
.filter(
|
|
Booking.customer_phone.contains(clean),
|
|
Booking.status == "confirmed",
|
|
Booking.start_at >= datetime.now(TZ),
|
|
)
|
|
.order_by(Booking.start_at)
|
|
.all()
|
|
)
|
|
return bookings
|
|
|
|
|
|
@router.delete("/bookings/{booking_id}")
|
|
def cancel_booking(booking_id: int, phone: str, db: Session = Depends(get_db)):
|
|
"""Cancella una prenotazione (verifica phone per sicurezza)."""
|
|
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
|
if not booking:
|
|
raise HTTPException(404, "Prenotazione non trovata")
|
|
|
|
clean = phone.replace("+", "").replace(" ", "").replace("-", "")
|
|
if clean not in booking.customer_phone:
|
|
raise HTTPException(403, "Numero non corrispondente")
|
|
|
|
booking.status = "cancelled"
|
|
db.commit()
|
|
return {"ok": True, "message": "Prenotazione cancellata"}
|