141 lines
5.6 KiB
Python
141 lines
5.6 KiB
Python
from flask import Blueprint, request, jsonify, g
|
|
from datetime import datetime, timedelta, date
|
|
from pydantic import ValidationError
|
|
|
|
from app.services.reservation_service import ReservationService
|
|
from app.schemas.reservation_dto import ReservationCreateDTO, ReservationUpdateDTO
|
|
from app.utils.jwt_decorators import jwt_required, jwt_role_required
|
|
|
|
api_reservations_bp = Blueprint('api_reservations', __name__, url_prefix='/api/v1/reservations')
|
|
reservation_service = ReservationService()
|
|
|
|
@api_reservations_bp.route('', methods=['GET'])
|
|
@jwt_required
|
|
def list_reservations():
|
|
"""Consulta de reservas por rango de fechas y/o aula."""
|
|
start_str = request.args.get('start_date')
|
|
end_str = request.args.get('end_date')
|
|
classroom_id = request.args.get('classroom_id', type=int)
|
|
|
|
now = datetime.utcnow()
|
|
try:
|
|
start_dt = datetime.strptime(start_str, '%Y-%m-%d') if start_str else now.replace(hour=0, minute=0, second=0)
|
|
except ValueError:
|
|
return jsonify({'error': 'BadRequest', 'message': 'Formato inválido de start_date (debe ser YYYY-MM-DD).'}), 400
|
|
|
|
try:
|
|
end_dt = datetime.strptime(end_str, '%Y-%m-%d').replace(hour=23, minute=59, second=59) if end_str else start_dt + timedelta(days=7)
|
|
except ValueError:
|
|
return jsonify({'error': 'BadRequest', 'message': 'Formato inválido de end_date (debe ser YYYY-MM-DD).'}), 400
|
|
|
|
reservations = reservation_service.list_reservations(start_dt, end_dt, classroom_id=classroom_id)
|
|
result = []
|
|
for r in reservations:
|
|
result.append({
|
|
'id': r.id,
|
|
'classroom_id': r.classroom_id,
|
|
'classroom_name': r.classroom.code if r.classroom else None,
|
|
'commission_id': r.commission_id,
|
|
'teacher_name': r.user.name if r.user else None,
|
|
'start_time': r.start_time.isoformat(),
|
|
'end_time': r.end_time.isoformat(),
|
|
'purpose': r.purpose,
|
|
'status': str(r.status),
|
|
'shift': r.shift,
|
|
'virtual_link': r.virtual_link,
|
|
'expected_attendees': r.expected_attendees
|
|
})
|
|
|
|
return jsonify({
|
|
'total': len(result),
|
|
'start_date': start_dt.strftime('%Y-%m-%d'),
|
|
'end_date': end_dt.strftime('%Y-%m-%d'),
|
|
'reservations': result
|
|
}), 200
|
|
|
|
@api_reservations_bp.route('/<int:reservation_id>', methods=['GET'])
|
|
@jwt_required
|
|
def get_reservation(reservation_id: int):
|
|
"""Obtiene el detalle de una reserva específica."""
|
|
r = reservation_service.get_reservation(reservation_id)
|
|
if not r:
|
|
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
|
|
|
|
return jsonify({
|
|
'id': r.id,
|
|
'classroom_id': r.classroom_id,
|
|
'classroom_name': r.classroom.code if r.classroom else None,
|
|
'location_display': r.classroom.location_display if r.classroom else None,
|
|
'commission_id': r.commission_id,
|
|
'teacher_id': r.user_id,
|
|
'teacher_name': r.user.name if r.user else None,
|
|
'start_time': r.start_time.isoformat(),
|
|
'end_time': r.end_time.isoformat(),
|
|
'purpose': r.purpose,
|
|
'status': str(r.status),
|
|
'shift': r.shift,
|
|
'virtual_link': r.virtual_link,
|
|
'expected_attendees': r.expected_attendees,
|
|
'notes': r.notes
|
|
}), 200
|
|
|
|
@api_reservations_bp.route('', methods=['POST'])
|
|
@jwt_required
|
|
def create_reservation():
|
|
"""Crea una reserva aplicando validación DTO y detección de colisiones."""
|
|
data = request.get_json(silent=True) or {}
|
|
|
|
# Inyectar usuario autenticado si no se especifica
|
|
if 'user_id' not in data:
|
|
data['user_id'] = g.jwt_user.id
|
|
|
|
try:
|
|
dto = ReservationCreateDTO(**data)
|
|
except ValidationError as e:
|
|
return jsonify({'error': 'ValidationError', 'details': e.errors()}), 400
|
|
|
|
try:
|
|
reservation = reservation_service.create_reservation(dto)
|
|
return jsonify({
|
|
'message': 'Reserva creada exitosamente.',
|
|
'id': reservation.id,
|
|
'classroom_id': reservation.classroom_id,
|
|
'start_time': reservation.start_time.isoformat(),
|
|
'end_time': reservation.end_time.isoformat(),
|
|
'status': str(reservation.status)
|
|
}), 201
|
|
except ValueError as e:
|
|
return jsonify({'error': 'ConflictOrValidation', 'message': str(e)}), 409
|
|
|
|
@api_reservations_bp.route('/<int:reservation_id>', methods=['PUT'])
|
|
@jwt_required
|
|
def update_reservation(reservation_id: int):
|
|
"""Modifica una reserva existente."""
|
|
data = request.get_json(silent=True) or {}
|
|
try:
|
|
dto = ReservationUpdateDTO(**data)
|
|
except ValidationError as e:
|
|
return jsonify({'error': 'ValidationError', 'details': e.errors()}), 400
|
|
|
|
try:
|
|
reservation = reservation_service.update_reservation(reservation_id, dto)
|
|
return jsonify({
|
|
'message': 'Reserva actualizada exitosamente.',
|
|
'id': reservation.id,
|
|
'start_time': reservation.start_time.isoformat(),
|
|
'end_time': reservation.end_time.isoformat(),
|
|
'status': str(reservation.status)
|
|
}), 200
|
|
except ValueError as e:
|
|
return jsonify({'error': 'ConflictOrValidation', 'message': str(e)}), 409
|
|
|
|
@api_reservations_bp.route('/<int:reservation_id>', methods=['DELETE'])
|
|
@jwt_required
|
|
def cancel_reservation(reservation_id: int):
|
|
"""Cancela una reserva."""
|
|
cancelled = reservation_service.cancel_reservation(reservation_id)
|
|
if not cancelled:
|
|
return jsonify({'error': 'NotFound', 'message': f'Reserva {reservation_id} no encontrada.'}), 404
|
|
|
|
return jsonify({'message': f'Reserva {reservation_id} cancelada correctamente.'}), 200
|