add license

This commit is contained in:
Alejandro Vazquez
2026-03-26 10:14:40 -03:00
commit 48bace5167
29 changed files with 2802 additions and 0 deletions
+277
View File
@@ -0,0 +1,277 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from flask_login import login_required, current_user
from app import db
from app.models.reservation import Reservation, ReservationStatus
from app.models.classroom import Classroom
from app.models.subject import Subject, Commission
from app.forms.reservation import ReservationForm
from datetime import datetime, timedelta
from sqlalchemy import func, or_
schedule_bp = Blueprint('schedule', __name__, url_prefix='/schedule')
@schedule_bp.route('/')
@login_required
def calendar_view():
"""Calendar view of reservations"""
return render_template('schedule/calendar.html')
@schedule_bp.route('/list')
@login_required
def list_reservations():
"""List all reservations"""
page = request.args.get('page', 1, type=int)
status_filter = request.args.get('status', '')
classroom_filter = request.args.get('classroom', 0, type=int)
date_filter = request.args.get('date', '')
query = Reservation.query
# Apply filters
if status_filter:
query = query.filter(Reservation.status == status_filter)
if classroom_filter:
query = query.filter(Reservation.classroom_id == classroom_filter)
if date_filter:
try:
filter_date = datetime.fromisoformat(date_filter).date()
query = query.filter(func.date(Reservation.start_time) == filter_date)
except ValueError:
pass
reservations = query.order_by(Reservation.start_time.desc()).paginate(
page=page,
per_page=20,
error_out=False
)
# Get filter options
classrooms = Classroom.query.filter_by(is_active=True).all()
return render_template('schedule/list.html',
reservations=reservations,
classrooms=classrooms,
status_filter=status_filter,
classroom_filter=classroom_filter,
date_filter=date_filter)
@schedule_bp.route('/add', methods=['GET', 'POST'])
@login_required
def add_reservation():
"""Add new reservation"""
form = ReservationForm()
# Populate classroom choices
classrooms = Classroom.query.filter_by(is_active=True).all()
form.classroom_id.choices = [(c.id, c'{c.code} - {c.name} ({c.capacity} seats)') for c in classrooms]
# Populate commission choices
commissions = Commission.query.filter_by(is_active=True).all()
form.commission_id.choices = [(c.id, c'{c.get_full_code()} - {c.teacher_name}') for c in commissions]
if form.validate_on_submit():
# Check for conflicts
conflicts = Reservation.find_conflicts(
form.classroom_id.data,
form.start_time.data,
form.end_time.data
)
if conflicts:
flash('Classroom is already booked for this time slot.', 'error')
return render_template('schedule/add.html', form=form)
# Check if end time is after start time
if form.end_time.data <= form.start_time.data:
flash('End time must be after start time.', 'error')
return render_template('schedule/add.html', form=form)
# Check capacity
classroom = Classroom.query.get(form.classroom_id.data)
if form.expected_attendees.data > classroom.capacity:
flash('Number of attendees exceeds classroom capacity.', 'error')
return render_template('schedule/add.html', form=form)
reservation = Reservation(
classroom_id=form.classroom_id.data,
commission_id=form.commission_id.data,
user_id=current_user.id,
start_time=form.start_time.data,
end_time=form.end_time.data,
purpose=form.purpose.data,
expected_attendees=form.expected_attendees.data,
notes=form.notes.data,
status=ReservationStatus.PENDING
)
try:
db.session.add(reservation)
db.session.commit()
flash('Reservation created successfully!', 'success')
return redirect(url_for('schedule.view_reservation', Id=reservation.id))
except Exception as e:
db.session.rollback()
flash('Error creating reservation.', 'error')
return render_template('schedule/add.html', form=form)
@schedule_bp.route('/view/<int:Id>')
@login_required
def view_reservation(Id):
"""View reservation details"""
reservation = Reservation.query.get_or_404(Id)
return render_template('schedule/view.html', reservation=reservation)
@schedule_bp.route('/edit/<int:Id>', methods=['GET', 'POST'])
@login_required
def edit_reservation(Id):
"""Edit reservation"""
reservation = Reservation.query.get_or_404(Id)
# Only allow editing pending reservations
if reservation.status != ReservationStatus.PENDING:
flash('Only pending reservations can be edited.', 'warning')
return redirect(url_for('schedule.view_reservation', Id=Id))
form = ReservationForm(obj=reservation)
# Populate choices
classrooms = Classroom.query.filter_by(is_active=True).all()
form.classroom_id.choices = [(c.id, c'{c.code} - {c.name} ({c.capacity} seats)') for c in classrooms]
commissions = Commission.query.filter_by(is_active=True).all()
form.commission_id.choices = [(c.id, c'{c.get_full_code()} - {c.teacher_name}') for c in commissions]
if form.validate_on_submit():
# Check for conflicts (excluding this reservation)
conflicts = Reservation.find_conflicts(
form.classroom_id.data,
form.start_time.data,
form.end_time.data,
exclude_reservation_id=Id
)
if conflicts:
flash('Classroom is already booked for this time slot.', 'error')
return render_template('schedule/edit.html', form=form, reservation=reservation)
# Update reservation
reservation.classroom_id = form.classroom_id.data
reservation.commission_id = form.commission_id.data
reservation.start_time = form.start_time.data
reservation.end_time = form.end_time.data
reservation.purpose = form.purpose.data
reservation.expected_attendees = form.expected_attendees.data
reservation.notes = form.notes.data
reservation.updated_at = datetime.utcnow()
try:
db.session.commit()
flash('Reservation updated successfully!', 'success')
return redirect(url_for('schedule.view_reservation', Id=Id))
except Exception as e:
db.session.rollback()
flash('Error updating reservation.', 'error')
return render_template('schedule/edit.html', form=form, reservation=reservation)
@schedule_bp.route('/confirm/<int:Id>')
@login_required
def confirm_reservation(Id):
"""Confirm a reservation"""
reservation = Reservation.query.get_or_404(Id)
if reservation.status != ReservationStatus.PENDING:
flash('Only pending reservations can be confirmed.', 'warning')
return redirect(url_for('schedule.view_reservation', Id=Id))
try:
reservation.status = ReservationStatus.CONFIRMED
reservation.updated_at = datetime.utcnow()
db.session.commit()
flash('Reservation confirmed successfully!', 'success')
except Exception as e:
db.session.rollback()
flash('Error confirming reservation.', 'error')
return redirect(url_for('schedule.view_reservation', Id=Id))
@schedule_bp.route('/cancel/<int:Id>')
@login_required
def cancel_reservation(Id):
"""Cancel a reservation"""
reservation = Reservation.query.get_or_404(Id)
if reservation.status not in [ReservationStatus.PENDING, ReservationStatus.CONFIRMED]:
flash('Cannot cancel this reservation.', 'warning')
return redirect(url_for('schedule.view_reservation', Id=Id))
try:
reservation.status = ReservationStatus.CANCELLED
reservation.updated_at = datetime.utcnow()
db.session.commit()
flash('Reservation cancelled successfully!', 'success')
except Exception as e:
db.session.rollback()
flash('Error cancelling reservation.', 'error')
return redirect(url_for('schedule.view_reservation', Id=Id))
@schedule_bp.route('/calendar-data')
@login_required
def calendar_data():
"""API endpoint for calendar data"""
start = request.args.get('start')
end = request.args.get('end')
if not start or not end:
return jsonify([])
try:
start_date = datetime.fromisoformat(start)
end_date = datetime.fromisoformat(end)
except ValueError:
return jsonify([])
reservations = Reservation.query.filter(
Reservation.start_time >= start_date,
Reservation.start_time <= end_date,
Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING])
).all()
events = []
for reservation in reservations:
color = {
ReservationStatus.CONFIRMED: '#28a745',
ReservationStatus.PENDING: '#ffc107'
}.get(reservation.status, '#6c757d')
events.append({
'id': reservation.id,
'title': f'{reservation.classroom.code} - {reservation.commission.get_full_code()}',
'start': reservation.start_time.isoformat(),
'end': reservation.end_time.isoformat(),
'url': url_for('schedule.view_reservation', Id=reservation.id),
'backgroundColor': color,
'borderColor': color
})
return jsonify(events)
@schedule_bp.route('/today')
@login_required
def today_schedule():
"""Show today's schedule"""
today = datetime.utcnow().date()
today_start = datetime.combine(today, datetime.min.time())
today_end = datetime.combine(today, datetime.max.time())
reservations = Reservation.query.filter(
Reservation.start_time >= today_start,
Reservation.start_time <= today_end,
Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING])
).order_by(Reservation.start_time).all()
return render_template('schedule/today.html', reservations=reservations)