Files
space-booking/backend/app/services/booking_service.py
Claude Agent df4031d99c feat: Space Booking System - MVP complet
Sistem web pentru rezervarea de birouri și săli de ședință
cu flux de aprobare administrativă.

Stack: FastAPI + Vue.js 3 + SQLite + TypeScript

Features implementate:
- Autentificare JWT + Self-registration cu email verification
- CRUD Spații, Utilizatori, Settings (Admin)
- Calendar interactiv (FullCalendar) cu drag-and-drop
- Creare rezervări cu validare (durată, program, overlap, max/zi)
- Rezervări recurente (săptămânal)
- Admin: aprobare/respingere/anulare cereri
- Admin: creare directă rezervări (bypass approval)
- Admin: editare orice rezervare
- User: editare/anulare rezervări proprii
- Notificări in-app (bell icon + dropdown)
- Notificări email (async SMTP cu BackgroundTasks)
- Jurnal acțiuni administrative (audit log)
- Rapoarte avansate (utilizare, top users, approval rate)
- Șabloane rezervări (booking templates)
- Atașamente fișiere (upload/download)
- Conflict warnings (verificare disponibilitate real-time)
- Integrare Google Calendar (OAuth2)
- Suport timezone (UTC storage + user preference)
- 225+ teste backend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:51:29 +00:00

113 lines
3.5 KiB
Python

"""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
def validate_booking_rules(
db: Session,
space_id: int,
user_id: int,
start_datetime: datetime,
end_datetime: datetime,
exclude_booking_id: int | None = None,
) -> list[str]:
"""
Validate booking against global 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
end_datetime: Booking end time
exclude_booking_id: Optional booking ID to exclude from overlap check
(used when re-validating an existing booking)
Returns:
List of validation error messages (empty list = validation OK)
"""
errors = []
# Fetch 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)
# a) Validate duration in range
duration_minutes = (end_datetime - start_datetime).total_seconds() / 60
if (
duration_minutes < settings.min_duration_minutes
or duration_minutes > settings.max_duration_minutes
):
errors.append(
f"Durata rezervării trebuie să fie între {settings.min_duration_minutes} "
f"și {settings.max_duration_minutes} minute"
)
# b) Validate working hours
if (
start_datetime.hour < settings.working_hours_start
or end_datetime.hour > settings.working_hours_end
):
errors.append(
f"Rezervările sunt permise doar între {settings.working_hours_start}:00 "
f"și {settings.working_hours_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
booking_date = start_datetime.date()
start_of_day = datetime.combine(booking_date, datetime.min.time())
end_of_day = datetime.combine(booking_date, datetime.max.time())
user_bookings_count = (
db.query(Booking)
.filter(
Booking.user_id == user_id,
Booking.status.in_(["approved", "pending"]),
Booking.start_datetime >= start_of_day,
Booking.start_datetime <= end_of_day,
)
.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