194 lines
7.8 KiB
Python
194 lines
7.8 KiB
Python
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
|
from flask_login import login_required, current_user
|
|
from app import db
|
|
from app.models.building import Building
|
|
from app.models.classroom import Classroom
|
|
from app.forms.building import BuildingForm
|
|
from app.utils.decorators import permission_required
|
|
from datetime import datetime
|
|
|
|
buildings_bp = Blueprint('buildings', __name__, url_prefix='/buildings')
|
|
|
|
@buildings_bp.route('/')
|
|
@login_required
|
|
def list_buildings():
|
|
"""List all buildings with statistics"""
|
|
search = request.args.get('search', '').strip()
|
|
status_filter = request.args.get('status', '')
|
|
|
|
query = Building.query
|
|
if search:
|
|
query = query.filter(
|
|
db.or_(
|
|
Building.name.ilike(f'%{search}%'),
|
|
Building.code.ilike(f'%{search}%'),
|
|
Building.address.ilike(f'%{search}%')
|
|
)
|
|
)
|
|
if status_filter == 'active':
|
|
query = query.filter(Building.is_active == True)
|
|
elif status_filter == 'inactive':
|
|
query = query.filter(Building.is_active == False)
|
|
|
|
buildings = query.order_by(Building.name.asc()).all()
|
|
|
|
# Calculate global building stats
|
|
total_classrooms = Classroom.query.filter_by(is_active=True).count()
|
|
total_capacity = sum(c.capacity for c in Classroom.query.filter_by(is_active=True).all())
|
|
|
|
return render_template(
|
|
'buildings/list.html',
|
|
buildings=buildings,
|
|
total_classrooms=total_classrooms,
|
|
total_capacity=total_capacity,
|
|
search=search,
|
|
status_filter=status_filter,
|
|
title='Building Management'
|
|
)
|
|
|
|
@buildings_bp.route('/add', methods=['GET', 'POST'])
|
|
@login_required
|
|
@permission_required('classrooms', 'read_write')
|
|
def add_building():
|
|
"""Add new building"""
|
|
form = BuildingForm()
|
|
|
|
if form.validate_on_submit():
|
|
try:
|
|
# Check unique name
|
|
existing = Building.query.filter(
|
|
db.func.lower(Building.name) == form.name.data.lower()
|
|
).first()
|
|
if existing:
|
|
flash('Ya existe un edificio con este nombre.', 'danger')
|
|
return render_template('buildings/form.html', form=form, title='Add Building')
|
|
|
|
building = Building(
|
|
name=form.name.data.strip(),
|
|
code=form.code.data.strip().upper() if form.code.data else None,
|
|
address=form.address.data.strip() if form.address.data else None,
|
|
floors=form.floors.data,
|
|
description=form.description.data.strip() if form.description.data else None,
|
|
is_active=form.is_active.data
|
|
)
|
|
db.session.add(building)
|
|
db.session.commit()
|
|
flash(f'Edificio "{building.name}" creado con éxito.', 'success')
|
|
return redirect(url_for('buildings.view_building', id=building.id))
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash(f'Error al crear el edificio: {str(e)}', 'danger')
|
|
|
|
return render_template('buildings/form.html', form=form, title='Add Building')
|
|
|
|
@buildings_bp.route('/<int:id>')
|
|
@login_required
|
|
def view_building(id):
|
|
"""View building details and its classrooms by floor"""
|
|
building = Building.query.get_or_404(id)
|
|
|
|
# Group active classrooms by floor
|
|
classrooms = Classroom.query.filter_by(building_id=building.id).order_by(Classroom.floor.asc(), Classroom.code.asc()).all()
|
|
# Fallback to name matching if building_id was not set
|
|
if not classrooms:
|
|
classrooms = Classroom.query.filter_by(building=building.name).order_by(Classroom.floor.asc(), Classroom.code.asc()).all()
|
|
|
|
classrooms_by_floor = {}
|
|
for c in classrooms:
|
|
floor_key = c.floor
|
|
if floor_key not in classrooms_by_floor:
|
|
classrooms_by_floor[floor_key] = []
|
|
classrooms_by_floor[floor_key].append(c)
|
|
|
|
return render_template(
|
|
'buildings/view.html',
|
|
building=building,
|
|
classrooms=classrooms,
|
|
classrooms_by_floor=classrooms_by_floor,
|
|
title=f'Building - {building.name}'
|
|
)
|
|
|
|
@buildings_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
@permission_required('classrooms', 'read_write')
|
|
def edit_building(id):
|
|
"""Edit building"""
|
|
building = Building.query.get_or_404(id)
|
|
form = BuildingForm(obj=building)
|
|
|
|
if form.validate_on_submit():
|
|
try:
|
|
# Check unique name excluding current
|
|
existing = Building.query.filter(
|
|
db.func.lower(Building.name) == form.name.data.lower(),
|
|
Building.id != id
|
|
).first()
|
|
if existing:
|
|
flash('Ya existe otro edificio con este nombre.', 'danger')
|
|
return render_template('buildings/form.html', form=form, building=building, title='Edit Building')
|
|
|
|
old_name = building.name
|
|
new_name = form.name.data.strip()
|
|
|
|
building.name = new_name
|
|
building.code = form.code.data.strip().upper() if form.code.data else None
|
|
building.address = form.address.data.strip() if form.address.data else None
|
|
building.floors = form.floors.data
|
|
building.description = form.description.data.strip() if form.description.data else None
|
|
building.is_active = form.is_active.data
|
|
building.updated_at = datetime.utcnow()
|
|
|
|
# If name changed, update string building on classrooms
|
|
if old_name != new_name:
|
|
Classroom.query.filter_by(building_id=building.id).update({'building': new_name})
|
|
|
|
db.session.commit()
|
|
flash(f'Edificio "{building.name}" actualizado con éxito.', 'success')
|
|
return redirect(url_for('buildings.view_building', id=building.id))
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash(f'Error al actualizar el edificio: {str(e)}', 'danger')
|
|
|
|
return render_template('buildings/form.html', form=form, building=building, title='Edit Building')
|
|
|
|
@buildings_bp.route('/<int:id>/toggle', methods=['POST'])
|
|
@login_required
|
|
@permission_required('classrooms', 'read_write')
|
|
def toggle_building(id):
|
|
"""Toggle building active status"""
|
|
building = Building.query.get_or_404(id)
|
|
try:
|
|
building.is_active = not building.is_active
|
|
building.updated_at = datetime.utcnow()
|
|
db.session.commit()
|
|
status_text = 'activado' if building.is_active else 'desactivado'
|
|
flash(f'Edificio "{building.name}" {status_text} con éxito.', 'success')
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash(f'Error al cambiar estado del edificio: {str(e)}', 'danger')
|
|
return redirect(url_for('buildings.list_buildings'))
|
|
|
|
@buildings_bp.route('/<int:id>/delete', methods=['POST'])
|
|
@login_required
|
|
@permission_required('classrooms', 'read_write')
|
|
def delete_building(id):
|
|
"""Delete building if it has no classrooms assigned"""
|
|
building = Building.query.get_or_404(id)
|
|
has_classrooms = Classroom.query.filter(
|
|
db.or_(Classroom.building_id == building.id, Classroom.building == building.name)
|
|
).count()
|
|
|
|
if has_classrooms > 0:
|
|
flash(f'No se puede eliminar el edificio "{building.name}" porque tiene {has_classrooms} aulas asociadas. Primero reasigna o elimina las aulas.', 'danger')
|
|
return redirect(url_for('buildings.view_building', id=building.id))
|
|
|
|
try:
|
|
db.session.delete(building)
|
|
db.session.commit()
|
|
flash(f'Edificio "{building.name}" eliminado correctamente.', 'success')
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash(f'Error al eliminar edificio: {str(e)}', 'danger')
|
|
|
|
return redirect(url_for('buildings.list_buildings'))
|