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>
This commit is contained in:
131
backend/app/api/settings.py
Normal file
131
backend/app/api/settings.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""Settings management endpoints."""
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.deps import get_current_admin, get_db
|
||||
from app.models.settings import Settings
|
||||
from app.models.user import User
|
||||
from app.schemas.settings import SettingsResponse, SettingsUpdate
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
router = APIRouter(prefix="/admin/settings", tags=["admin"])
|
||||
|
||||
|
||||
@router.get("", response_model=SettingsResponse)
|
||||
def get_settings(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
_: Annotated[User, Depends(get_current_admin)],
|
||||
) -> Settings:
|
||||
"""
|
||||
Get global settings (admin only).
|
||||
|
||||
Returns the current global booking rules.
|
||||
"""
|
||||
settings = db.query(Settings).filter(Settings.id == 1).first()
|
||||
if not settings:
|
||||
# Create default settings if not exist
|
||||
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)
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
@router.put("", response_model=SettingsResponse)
|
||||
def update_settings(
|
||||
settings_data: SettingsUpdate,
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
current_admin: Annotated[User, Depends(get_current_admin)],
|
||||
) -> Settings:
|
||||
"""
|
||||
Update global settings (admin only).
|
||||
|
||||
All booking rules are validated on the client side and applied
|
||||
to all new booking requests.
|
||||
"""
|
||||
settings = db.query(Settings).filter(Settings.id == 1).first()
|
||||
if not settings:
|
||||
# Create if not exist
|
||||
settings = Settings(id=1)
|
||||
db.add(settings)
|
||||
|
||||
# Validate: min_duration <= max_duration
|
||||
if settings_data.min_duration_minutes > settings_data.max_duration_minutes:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Minimum duration cannot be greater than maximum duration",
|
||||
)
|
||||
|
||||
# Validate: working_hours_start < working_hours_end
|
||||
if settings_data.working_hours_start >= settings_data.working_hours_end:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Working hours start must be before working hours end",
|
||||
)
|
||||
|
||||
# Track which fields changed
|
||||
changed_fields = {}
|
||||
if settings.min_duration_minutes != settings_data.min_duration_minutes:
|
||||
changed_fields["min_duration_minutes"] = {
|
||||
"old": settings.min_duration_minutes,
|
||||
"new": settings_data.min_duration_minutes
|
||||
}
|
||||
if settings.max_duration_minutes != settings_data.max_duration_minutes:
|
||||
changed_fields["max_duration_minutes"] = {
|
||||
"old": settings.max_duration_minutes,
|
||||
"new": settings_data.max_duration_minutes
|
||||
}
|
||||
if settings.working_hours_start != settings_data.working_hours_start:
|
||||
changed_fields["working_hours_start"] = {
|
||||
"old": settings.working_hours_start,
|
||||
"new": settings_data.working_hours_start
|
||||
}
|
||||
if settings.working_hours_end != settings_data.working_hours_end:
|
||||
changed_fields["working_hours_end"] = {
|
||||
"old": settings.working_hours_end,
|
||||
"new": settings_data.working_hours_end
|
||||
}
|
||||
if settings.max_bookings_per_day_per_user != settings_data.max_bookings_per_day_per_user:
|
||||
changed_fields["max_bookings_per_day_per_user"] = {
|
||||
"old": settings.max_bookings_per_day_per_user,
|
||||
"new": settings_data.max_bookings_per_day_per_user
|
||||
}
|
||||
if settings.min_hours_before_cancel != settings_data.min_hours_before_cancel:
|
||||
changed_fields["min_hours_before_cancel"] = {
|
||||
"old": settings.min_hours_before_cancel,
|
||||
"new": settings_data.min_hours_before_cancel
|
||||
}
|
||||
|
||||
# Update all fields
|
||||
setattr(settings, "min_duration_minutes", settings_data.min_duration_minutes)
|
||||
setattr(settings, "max_duration_minutes", settings_data.max_duration_minutes)
|
||||
setattr(settings, "working_hours_start", settings_data.working_hours_start)
|
||||
setattr(settings, "working_hours_end", settings_data.working_hours_end)
|
||||
setattr(settings, "max_bookings_per_day_per_user", settings_data.max_bookings_per_day_per_user)
|
||||
setattr(settings, "min_hours_before_cancel", settings_data.min_hours_before_cancel)
|
||||
|
||||
db.commit()
|
||||
db.refresh(settings)
|
||||
|
||||
# Log the action
|
||||
log_action(
|
||||
db=db,
|
||||
action="settings_updated",
|
||||
user_id=current_admin.id,
|
||||
target_type="settings",
|
||||
target_id=1, # Settings ID is always 1 (singleton)
|
||||
details={"changed_fields": changed_fields}
|
||||
)
|
||||
|
||||
return settings
|
||||
Reference in New Issue
Block a user