fix time table
This commit is contained in:
+129
-212
@@ -1,227 +1,83 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
|
||||
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 Commission
|
||||
from app.models.subject import Subject, Commission
|
||||
from app.forms.reservation import ReservationForm
|
||||
from app import db
|
||||
from datetime import datetime, timedelta, date
|
||||
from wtforms import SelectField, StringField, TextAreaField, IntegerField, SubmitField, DateTimeLocalField
|
||||
from wtforms.validators import DataRequired, Length, NumberRange, Optional
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
schedule_bp = Blueprint('schedule', __name__)
|
||||
|
||||
@schedule_bp.route('/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_reservation():
|
||||
"""Add a new reservation"""
|
||||
form = ReservationForm()
|
||||
|
||||
# Populate form choices
|
||||
form.classroom_id.choices = [(c.id, f"{c.code_display} (Capacity: {c.capacity})")
|
||||
for c in Classroom.query.filter_by(is_active=True).order_by(Classroom.building, Classroom.code).all()]
|
||||
form.commission_id.choices = [(c.id, c.get_full_code())
|
||||
for c in Commission.query.filter(Commission.active == True).order_by(Commission.code).all()]
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
# Check for time conflicts
|
||||
conflicting_reservations = Reservation.find_conflicts(
|
||||
form.classroom_id.data,
|
||||
form.start_time.data,
|
||||
form.end_time.data
|
||||
)
|
||||
|
||||
if conflicting_reservations:
|
||||
flash('This classroom is already reserved for the selected time slot.', 'error')
|
||||
# Get classrooms for the template
|
||||
classroom_list = Classroom.query.filter_by(is_active=True).order_by(Classroom.building, Classroom.code).all()
|
||||
return render_template('schedule/add.html', form=form, classroom_list=classroom_list, title='New Reservation')
|
||||
|
||||
# Check capacity
|
||||
classroom = Classroom.query.get(form.classroom_id.data)
|
||||
if form.expected_attendees.data > classroom.capacity:
|
||||
flash(f'Expected attendees ({form.expected_attendees.data}) exceeds classroom capacity ({classroom.capacity}).', 'error')
|
||||
# Get classrooms for the template
|
||||
classroom_list = Classroom.query.filter_by(is_active=True).order_by(Classroom.building, Classroom.code).all()
|
||||
return render_template('schedule/add.html', form=form, classroom_list=classroom_list, title='New Reservation')
|
||||
|
||||
# Create reservation
|
||||
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
|
||||
)
|
||||
|
||||
db.session.add(reservation)
|
||||
db.session.commit()
|
||||
|
||||
flash('Reservation created successfully! It will be confirmed after review.', 'success')
|
||||
return redirect(url_for('schedule.list_reservations'))
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error creating reservation: {str(e)}', 'error')
|
||||
|
||||
# Get classrooms for the template
|
||||
classroom_list = Classroom.query.filter_by(is_active=True).order_by(Classroom.building, Classroom.code).all()
|
||||
|
||||
return render_template('schedule/add.html', form=form, classroom_list=classroom_list, title='New Reservation')
|
||||
|
||||
@schedule_bp.route('/list')
|
||||
@login_required
|
||||
def list_reservations():
|
||||
"""List all reservations"""
|
||||
try:
|
||||
page = request.args.get('page', 1, type=int)
|
||||
status_filter = request.args.get('status', 'all')
|
||||
date_from = request.args.get('date_from')
|
||||
date_to = request.args.get('date_to')
|
||||
|
||||
# Build query
|
||||
query = Reservation.query
|
||||
|
||||
# Apply filters
|
||||
if status_filter != 'all':
|
||||
try:
|
||||
query = query.filter_by(status=ReservationStatus[status_filter.upper()])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if date_from:
|
||||
try:
|
||||
from_date = datetime.strptime(date_from, '%Y-%m-%d')
|
||||
query = query.filter(Reservation.start_time >= from_date)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if date_to:
|
||||
try:
|
||||
to_date = datetime.strptime(date_to, '%Y-%m-%d') + timedelta(days=1)
|
||||
query = query.filter(Reservation.start_time < to_date)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Order by start time descending
|
||||
reservations = query.order_by(Reservation.start_time.desc()).paginate(
|
||||
page=page, per_page=20, error_out=False
|
||||
)
|
||||
|
||||
return render_template('schedule/list.html',
|
||||
reservations=reservations,
|
||||
status_filter=status_filter,
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
title='Reservations')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error loading reservations: {str(e)}', 'error')
|
||||
return render_template('schedule/list.html', reservations=None, title='Reservations')
|
||||
schedule_bp = Blueprint('schedule', __name__, url_prefix='/schedule')
|
||||
|
||||
@schedule_bp.route('/today')
|
||||
@login_required
|
||||
def today_schedule():
|
||||
"""Show today's schedule"""
|
||||
try:
|
||||
today_start = datetime.combine(date.today(), datetime.min.time())
|
||||
today_end = datetime.combine(date.today(), datetime.max.time())
|
||||
|
||||
# Get today's reservations
|
||||
reservations = Reservation.query.filter(
|
||||
Reservation.start_time >= today_start,
|
||||
Reservation.start_time <= today_end
|
||||
).order_by(Reservation.start_time).all()
|
||||
|
||||
# Group by time blocks for better display
|
||||
time_blocks = {}
|
||||
|
||||
for reservation in reservations:
|
||||
hour_key = reservation.start_time.hour
|
||||
|
||||
if hour_key not in time_blocks:
|
||||
time_blocks[hour_key] = []
|
||||
|
||||
time_blocks[hour_key].append({
|
||||
'reservation': reservation,
|
||||
'classroom': reservation.classroom,
|
||||
'commission': reservation.commission,
|
||||
'user': reservation.user
|
||||
})
|
||||
|
||||
# Get quick stats
|
||||
stats = {
|
||||
'total_reservations': len(reservations),
|
||||
'confirmed': len([r for r in reservations if r.status == ReservationStatus.CONFIRMED]),
|
||||
'pending': len([r for r in reservations if r.status == ReservationStatus.PENDING]),
|
||||
'cancelled': len([r for r in reservations if r.status == ReservationStatus.CANCELLED]),
|
||||
'total_classrooms': Classroom.query.filter_by(is_active=True).count(),
|
||||
'occupied_classrooms': len(set(r.classroom_id for r in reservations
|
||||
if r.status == ReservationStatus.CONFIRMED))
|
||||
}
|
||||
|
||||
return render_template('schedule/today.html',
|
||||
time_blocks=time_blocks,
|
||||
reservations=reservations,
|
||||
stats=stats,
|
||||
today=date.today(),
|
||||
title='Today\'s Schedule')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error loading today\'s schedule: {str(e)}', 'error')
|
||||
return render_template('schedule/today.html',
|
||||
time_blocks={},
|
||||
reservations=[],
|
||||
stats={},
|
||||
today=date.today(),
|
||||
title='Today\'s Schedule')
|
||||
|
||||
@schedule_bp.route('/<int:id>/confirm', methods=['POST'])
|
||||
@login_required
|
||||
def confirm_reservation(id):
|
||||
"""Confirm a reservation"""
|
||||
reservation = Reservation.query.get_or_404(id)
|
||||
today = datetime.utcnow().date()
|
||||
today_start = datetime.combine(today, datetime.min.time())
|
||||
today_end = datetime.combine(today, datetime.max.time())
|
||||
|
||||
try:
|
||||
# Check if reservation can be confirmed
|
||||
if reservation.status != ReservationStatus.PENDING:
|
||||
flash('Only pending reservations can be confirmed.', 'error')
|
||||
return redirect(request.referrer)
|
||||
|
||||
# Check for conflicts again before confirming
|
||||
conflicts = Reservation.find_conflicts(
|
||||
reservation.classroom_id,
|
||||
reservation.start_time,
|
||||
reservation.end_time
|
||||
)
|
||||
|
||||
if conflicts:
|
||||
flash('Cannot confirm: Time slot is already occupied.', 'error')
|
||||
return redirect(request.referrer)
|
||||
|
||||
reservation.status = ReservationStatus.CONFIRMED
|
||||
db.session.commit()
|
||||
|
||||
flash('Reservation confirmed successfully!', 'success')
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error confirming reservation: {str(e)}', 'error')
|
||||
# Get reservations with eager loading
|
||||
reservations = Reservation.query.options(
|
||||
joinedload(Reservation.classroom),
|
||||
joinedload(Reservation.commission),
|
||||
joinedload(Reservation.user)
|
||||
).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 redirect(request.referrer)
|
||||
# Create time blocks for timeline view
|
||||
time_blocks = {}
|
||||
for reservation in reservations:
|
||||
start_hour = reservation.start_time.hour
|
||||
end_hour = reservation.end_time.hour
|
||||
|
||||
for hour in range(max(7, start_hour), min(22, end_hour + 1)):
|
||||
if hour not in time_blocks:
|
||||
time_blocks[hour] = []
|
||||
if reservation not in time_blocks[hour]:
|
||||
time_blocks[hour].append({
|
||||
'reservation': reservation,
|
||||
'classroom': reservation.classroom,
|
||||
'commission': reservation.commission,
|
||||
'user': reservation.user
|
||||
})
|
||||
|
||||
# Calculate stats
|
||||
total_reservations = len(reservations)
|
||||
confirmed = len([r for r in reservations if r.status == ReservationStatus.CONFIRMED])
|
||||
pending = len([r for r in reservations if r.status == ReservationStatus.PENDING])
|
||||
|
||||
# Get total classrooms and calculate occupied ones
|
||||
from app.models.classroom import Classroom
|
||||
total_classrooms = Classroom.query.count()
|
||||
occupied_classrooms = len(set(r.classroom_id for r in reservations if r.classroom_id))
|
||||
|
||||
stats = {
|
||||
'total_reservations': total_reservations,
|
||||
'confirmed': confirmed,
|
||||
'pending': pending,
|
||||
'occupied_classrooms': occupied_classrooms,
|
||||
'total_classrooms': total_classrooms
|
||||
}
|
||||
|
||||
return render_template('schedule/today.html',
|
||||
reservations=reservations,
|
||||
time_blocks=time_blocks,
|
||||
stats=stats,
|
||||
today=today)
|
||||
|
||||
@schedule_bp.route('/<int:id>/cancel', methods=['POST'])
|
||||
@schedule_bp.route('/cancel/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
def cancel_reservation(id):
|
||||
"""Cancel a reservation"""
|
||||
reservation = Reservation.query.get_or_404(id)
|
||||
|
||||
try:
|
||||
# Check if reservation can be cancelled
|
||||
reservation = Reservation.query.get_or_404(id)
|
||||
|
||||
if not reservation.can_be_cancelled():
|
||||
flash('Reservations can only be cancelled at least 24 hours before start time.', 'error')
|
||||
return redirect(request.referrer)
|
||||
@@ -237,12 +93,66 @@ def cancel_reservation(id):
|
||||
|
||||
return redirect(request.referrer)
|
||||
|
||||
@schedule_bp.route('/reservation/<int:id>/view')
|
||||
@login_required
|
||||
def view_reservation(id):
|
||||
"""View reservation details"""
|
||||
try:
|
||||
reservation = Reservation.query.options(
|
||||
joinedload(Reservation.classroom),
|
||||
joinedload(Reservation.commission),
|
||||
joinedload(Reservation.user)
|
||||
).get_or_404(id)
|
||||
|
||||
return render_template('schedule/view.html',
|
||||
reservation=reservation,
|
||||
title='Reservation Details')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error loading reservation details: {str(e)}', 'error')
|
||||
return redirect(url_for('schedule.today_schedule'))
|
||||
|
||||
@schedule_bp.route('/calendar')
|
||||
@login_required
|
||||
def calendar_view():
|
||||
"""Show calendar view"""
|
||||
return render_template('schedule/calendar.html', title='Calendar View')
|
||||
|
||||
@schedule_bp.route('/add')
|
||||
@login_required
|
||||
def add_reservation():
|
||||
"""Add new reservation"""
|
||||
form = ReservationForm()
|
||||
return render_template('schedule/create.html', form=form, title='New Reservation')
|
||||
|
||||
@schedule_bp.route('/list')
|
||||
@login_required
|
||||
def list_reservations():
|
||||
"""List all reservations"""
|
||||
return redirect(url_for('schedule.today_schedule'))
|
||||
|
||||
@schedule_bp.route('/reservation/<int:id>/confirm', methods=['POST'])
|
||||
@login_required
|
||||
def confirm_reservation(id):
|
||||
"""Confirm a reservation"""
|
||||
try:
|
||||
reservation = Reservation.query.get_or_404(id)
|
||||
|
||||
if reservation.status != ReservationStatus.PENDING:
|
||||
flash('Only pending reservations can be confirmed.', 'error')
|
||||
return redirect(request.referrer)
|
||||
|
||||
reservation.status = ReservationStatus.CONFIRMED
|
||||
db.session.commit()
|
||||
|
||||
flash('Reservation confirmed successfully!', 'success')
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error confirming reservation: {str(e)}', 'error')
|
||||
|
||||
return redirect(request.referrer)
|
||||
|
||||
# API endpoints
|
||||
@schedule_bp.route('/calendar-data')
|
||||
@login_required
|
||||
@@ -265,7 +175,10 @@ def calendar_data():
|
||||
end_date = datetime.combine(next_month.replace(day=1), datetime.max.time())
|
||||
|
||||
# Get reservations in the date range
|
||||
reservations = Reservation.query.filter(
|
||||
reservations = Reservation.query.options(
|
||||
joinedload(Reservation.classroom),
|
||||
joinedload(Reservation.commission)
|
||||
).filter(
|
||||
Reservation.start_time >= start_date,
|
||||
Reservation.start_time <= end_date,
|
||||
Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING])
|
||||
@@ -278,7 +191,7 @@ def calendar_data():
|
||||
'title': f"{reservation.commission.get_full_code() if reservation.commission else 'No Commission'} - {reservation.classroom.code_display}",
|
||||
'start': reservation.start_time.isoformat(),
|
||||
'end': reservation.end_time.isoformat(),
|
||||
'url': f"/schedule/{reservation.id}", # For event clicking
|
||||
'url': url_for('schedule.view_reservation', id=reservation.id) if reservation.id else None,
|
||||
'className': f'fc-event-{reservation.status.value.lower()}',
|
||||
'extendedProps': {
|
||||
'classroom': reservation.classroom.code_display if reservation.classroom else 'Unknown',
|
||||
@@ -303,7 +216,10 @@ def api_today_events():
|
||||
today_start = datetime.combine(date.today(), datetime.min.time())
|
||||
today_end = datetime.combine(date.today(), datetime.max.time())
|
||||
|
||||
reservations = Reservation.query.filter(
|
||||
reservations = Reservation.query.options(
|
||||
joinedload(Reservation.classroom),
|
||||
joinedload(Reservation.commission)
|
||||
).filter(
|
||||
Reservation.start_time >= today_start,
|
||||
Reservation.start_time <= today_end,
|
||||
Reservation.status == ReservationStatus.CONFIRMED
|
||||
@@ -313,12 +229,13 @@ def api_today_events():
|
||||
for reservation in reservations:
|
||||
events.append({
|
||||
'id': reservation.id,
|
||||
'title': f"{reservation.commission.get_full_code()} - {reservation.classroom.code_display}",
|
||||
'title': f"{reservation.commission.get_full_code() if reservation.commission else 'No Commission'} - {reservation.classroom.code_display}",
|
||||
'start': reservation.start_time.isoformat(),
|
||||
'end': reservation.end_time.isoformat(),
|
||||
'url': url_for('schedule.view_reservation', id=reservation.id) if reservation.id else None,
|
||||
'extendedProps': {
|
||||
'classroom': reservation.classroom.code_display,
|
||||
'purpose': reservation.purpose,
|
||||
'classroom': reservation.classroom.code_display if reservation.classroom else 'Unknown',
|
||||
'purpose': reservation.purpose or 'No purpose',
|
||||
'attendees': reservation.expected_attendees,
|
||||
'status': reservation.status.value
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user