add license
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user