"""Booking validation service.""" from datetime import datetime from sqlalchemy import and_ from sqlalchemy.orm import Session from app.models.booking import Booking from app.models.settings import Settings from app.models.space import Space from app.utils.timezone import convert_from_utc, convert_to_utc def validate_booking_rules( db: Session, space_id: int, user_id: int, start_datetime: datetime, end_datetime: datetime, exclude_booking_id: int | None = None, user_timezone: str = "UTC", ) -> list[str]: """ Validate booking against global and per-space settings rules. Args: db: Database session space_id: ID of the space to book user_id: ID of the user making the booking start_datetime: Booking start time (UTC) end_datetime: Booking end time (UTC) exclude_booking_id: Optional booking ID to exclude from overlap check (used when re-validating an existing booking) user_timezone: User's IANA timezone (e.g., "Europe/Bucharest") Returns: List of validation error messages (empty list = validation OK) """ errors = [] # Fetch global settings (create default if not exists) settings = db.query(Settings).filter(Settings.id == 1).first() if not settings: settings = Settings( id=1, min_duration_minutes=30, max_duration_minutes=480, working_hours_start=8, working_hours_end=20, max_bookings_per_day_per_user=3, min_hours_before_cancel=2, ) db.add(settings) db.commit() db.refresh(settings) # Fetch space and get per-space settings (with fallback to global) space = db.query(Space).filter(Space.id == space_id).first() wh_start = ( space.working_hours_start if space and space.working_hours_start is not None else settings.working_hours_start ) wh_end = ( space.working_hours_end if space and space.working_hours_end is not None else settings.working_hours_end ) min_dur = ( space.min_duration_minutes if space and space.min_duration_minutes is not None else settings.min_duration_minutes ) max_dur = ( space.max_duration_minutes if space and space.max_duration_minutes is not None else settings.max_duration_minutes ) # Convert UTC times to user timezone for validation local_start = convert_from_utc(start_datetime, user_timezone) local_end = convert_from_utc(end_datetime, user_timezone) # a) Validate duration in range duration_minutes = (end_datetime - start_datetime).total_seconds() / 60 if duration_minutes < min_dur or duration_minutes > max_dur: errors.append( f"Durata rezervării trebuie să fie între {min_dur} și {max_dur} minute" ) # b) Validate working hours (in user's local time) if local_start.hour < wh_start or local_end.hour > wh_end: errors.append( f"Rezervările sunt permise doar între {wh_start}:00 și {wh_end}:00" ) # c) Check for overlapping bookings query = db.query(Booking).filter( Booking.space_id == space_id, Booking.status.in_(["approved", "pending"]), and_( Booking.start_datetime < end_datetime, Booking.end_datetime > start_datetime, ), ) # Exclude current booking if re-validating if exclude_booking_id is not None: query = query.filter(Booking.id != exclude_booking_id) overlapping_bookings = query.first() if overlapping_bookings: errors.append("Spațiul este deja rezervat în acest interval") # d) Check max bookings per day per user (using local date) booking_date_local = local_start.date() local_start_of_day = datetime.combine(booking_date_local, datetime.min.time()) local_end_of_day = datetime.combine(booking_date_local, datetime.max.time()) # Convert local day boundaries to UTC start_of_day_utc = convert_to_utc(local_start_of_day, user_timezone) end_of_day_utc = convert_to_utc(local_end_of_day, user_timezone) user_bookings_count = ( db.query(Booking) .filter( Booking.user_id == user_id, Booking.status.in_(["approved", "pending"]), Booking.start_datetime >= start_of_day_utc, Booking.start_datetime <= end_of_day_utc, ) .count() ) if user_bookings_count >= settings.max_bookings_per_day_per_user: errors.append( f"Ai atins limita de {settings.max_bookings_per_day_per_user} rezervări pe zi" ) return errors