101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""Calcolo slot disponibili per un servizio in una data.
|
|
|
|
Logica: regole orario provider - prenotazioni esistenti - busy Google Calendar.
|
|
Google Calendar è opzionale (fase 2).
|
|
"""
|
|
from datetime import datetime, date, timedelta, time
|
|
from zoneinfo import ZoneInfo
|
|
from sqlalchemy.orm import Session
|
|
from app.models import ProviderService, Booking
|
|
from app.schemas import TimeSlot
|
|
|
|
TZ = ZoneInfo("Europe/Rome")
|
|
|
|
|
|
def get_available_slots(
|
|
db: Session,
|
|
service_id: int,
|
|
target_date: date,
|
|
duration_min: int,
|
|
) -> list[TimeSlot]:
|
|
"""Restituisce tutti gli slot liberi per un servizio in una data."""
|
|
|
|
# 1. Trova tutti i provider che erogano questo servizio
|
|
ps_list = (
|
|
db.query(ProviderService)
|
|
.filter(ProviderService.service_id == service_id)
|
|
.all()
|
|
)
|
|
if not ps_list:
|
|
return []
|
|
|
|
weekday = target_date.weekday() # 0=lunedì
|
|
all_slots = []
|
|
|
|
for ps in ps_list:
|
|
if not ps.provider.active:
|
|
continue
|
|
|
|
# 2. Filtra le regole di disponibilità per il giorno della settimana
|
|
rules = ps.availability_rules or []
|
|
day_rules = [r for r in rules if r.get("weekday") == weekday]
|
|
|
|
if not day_rules:
|
|
continue
|
|
|
|
# 3. Genera tutti gli slot possibili dalle regole
|
|
raw_slots = []
|
|
for rule in day_rules:
|
|
start_h, start_m = map(int, rule["start"].split(":"))
|
|
end_h, end_m = map(int, rule["end"].split(":"))
|
|
|
|
slot_start = datetime.combine(target_date, time(start_h, start_m), tzinfo=TZ)
|
|
slot_end_limit = datetime.combine(target_date, time(end_h, end_m), tzinfo=TZ)
|
|
|
|
while slot_start + timedelta(minutes=duration_min) <= slot_end_limit:
|
|
raw_slots.append(slot_start)
|
|
slot_start += timedelta(minutes=duration_min)
|
|
|
|
# 4. Filtra via le prenotazioni esistenti (confirmed)
|
|
day_start = datetime.combine(target_date, time(0, 0), tzinfo=TZ)
|
|
day_end = day_start + timedelta(days=1)
|
|
|
|
existing = (
|
|
db.query(Booking)
|
|
.filter(
|
|
Booking.provider_id == ps.provider_id,
|
|
Booking.start_at >= day_start,
|
|
Booking.start_at < day_end,
|
|
Booking.status == "confirmed",
|
|
)
|
|
.all()
|
|
)
|
|
|
|
busy_ranges = [(b.start_at, b.end_at) for b in existing]
|
|
|
|
for slot_start in raw_slots:
|
|
slot_end = slot_start + timedelta(minutes=duration_min)
|
|
conflict = any(
|
|
slot_start < busy_end and slot_end > busy_start
|
|
for busy_start, busy_end in busy_ranges
|
|
)
|
|
if not conflict:
|
|
all_slots.append(
|
|
TimeSlot(
|
|
start=slot_start.strftime("%H:%M"),
|
|
end=slot_end.strftime("%H:%M"),
|
|
provider_id=ps.provider_id,
|
|
provider_name=ps.provider.name,
|
|
)
|
|
)
|
|
|
|
# 5. Ordina per orario
|
|
all_slots.sort(key=lambda s: s.start)
|
|
|
|
# 6. Filtra slot nel passato se target_date è oggi
|
|
now = datetime.now(TZ)
|
|
if target_date == now.date():
|
|
all_slots = [s for s in all_slots if s.start > now.strftime("%H:%M")]
|
|
|
|
return all_slots
|