"""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.property_settings import PropertySettings 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 # Resolution chain: Space → PropertySettings → Global Settings space = db.query(Space).filter(Space.id == space_id).first() # Fetch property settings if space has a property prop_settings = None if space and space.property_id: prop_settings = db.query(PropertySettings).filter( PropertySettings.property_id == space.property_id ).first() def resolve(space_val, prop_val, global_val): if space_val is not None: return space_val if prop_val is not None: return prop_val return global_val wh_start = resolve( space.working_hours_start if space else None, prop_settings.working_hours_start if prop_settings else None, settings.working_hours_start, ) wh_end = resolve( space.working_hours_end if space else None, prop_settings.working_hours_end if prop_settings else None, settings.working_hours_end, ) min_dur = resolve( space.min_duration_minutes if space else None, prop_settings.min_duration_minutes if prop_settings else None, settings.min_duration_minutes, ) max_dur = resolve( space.max_duration_minutes if space else None, prop_settings.max_duration_minutes if prop_settings else None, 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 (only approved block new bookings; # admin re-validates at approval time to catch conflicts) query = db.query(Booking).filter( Booking.space_id == space_id, Booking.status == "approved", 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