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 (excluye aulas virtuales con 9999) total_classrooms = Classroom.query.filter_by(is_active=True).count() physical_classrooms = [c for c in Classroom.query.filter_by(is_active=True).all() if not getattr(c, 'is_virtual', False) and (c.capacity or 0) < 999 and 'VIRTUAL' not in (c.code or '').upper()] total_physical_capacity = sum(c.capacity for c in physical_classrooms) total_capacity = total_physical_capacity return render_template( 'buildings/list.html', buildings=buildings, total_classrooms=total_classrooms, total_capacity=total_capacity, total_physical_capacity=total_physical_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('/') @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('//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('//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('//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'))