from app import db from datetime import datetime class Classroom(db.Model): __tablename__ = 'classrooms' id = db.Column(db.Integer, primary_key=True) campus_id = db.Column(db.Integer, nullable=True) # Maps to campus_id in DB building_id = db.Column(db.Integer, db.ForeignKey('buildings.id'), nullable=True) building = db.Column(db.String(100), nullable=False) code = db.Column('room_number', db.String(50), nullable=False) # Maps to room_number column in DB floor = db.Column(db.Integer, nullable=False) capacity = db.Column(db.Integer, nullable=False) description = db.Column(db.Text) is_active = db.Column('active', db.Boolean, default=True, nullable=False) # Maps to active column in DB created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Virtual properties for compatibility @property def name(self): """Generate name from building and code""" return f"{self.building}-{self.code}" @property def campus_location(self): """Placeholder for campus location - can be expanded when campus model is implemented""" return None # Virtual field for compatibility - combines building and code @property def room_number(self): return self.code @room_number.setter def room_number(self, value): self.code = value # Override code property for consistency @property def code_display(self): return f"{self.building}-{self.code}" @property def capacity_display(self): """Retorna '∞' para aulas virtuales o capacidad ilimitada, o el número entero.""" if self.capacity == 0 or (self.code and 'VIRTUAL' in self.code.upper()): return '∞' return str(self.capacity) @property def is_unlimited(self): return self.capacity == 0 or (self.code and 'VIRTUAL' in self.code.upper()) def to_dict(self): """Convert classroom to dictionary""" return { 'id': self.id, 'code': self.code_display, 'building': self.building, 'name': self.name, 'floor': self.floor, 'capacity': self.capacity, 'description': self.description, 'campus_location': self.campus_location, 'is_active': self.is_active, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, 'resources': [resource.to_dict() for resource in self.resources] if hasattr(self, 'resources') else [] } def get_full_location(self): """Get full classroom location""" location = f"{self.building} - Floor {self.floor}" if self.campus_location: location += f" ({self.campus_location})" return location @staticmethod def find_best_classroom(max_students, start_time, end_time, exclude_reservation_id=None): """Find the best suitable classroom for a commission based on capacity and availability""" from app.models.reservation import Reservation, ReservationStatus # Get all active classrooms with sufficient capacity suitable_classrooms = Classroom.query.filter( Classroom.is_active == True, Classroom.capacity >= max_students ).order_by(Classroom.capacity.asc()).all() # Start with smallest suitable # Check availability for each classroom available_classrooms = [] for classroom in suitable_classrooms: # Check for conflicts conflicts = Reservation.find_conflicts( classroom.id, start_time, end_time, exclude_reservation_id ) if not conflicts: # Calculate efficiency score (how well the capacity matches) efficiency = classroom.capacity / max_students if max_students > 0 else float('inf') available_classrooms.append({ 'classroom': classroom, 'efficiency': efficiency, 'wasted_capacity': classroom.capacity - max_students }) # Sort by efficiency (closest capacity match first) available_classrooms.sort(key=lambda x: x['efficiency']) return available_classrooms @staticmethod def get_availability_status(start_time, end_time, exclude_reservation_id=None): """Get all classrooms classified as available or occupied for a time slot""" from app.models.reservation import Reservation, ReservationStatus all_classrooms = Classroom.query.filter_by(is_active=True).all() available = [] occupied = [] for classroom in all_classrooms: conflicts = Reservation.find_conflicts( classroom.id, start_time, end_time, exclude_reservation_id ) classroom_info = { 'classroom': classroom, 'conflicts': conflicts } if conflicts: occupied.append(classroom_info) else: available.append(classroom_info) return { 'available': available, 'occupied': occupied, 'total_available': len(available), 'total_occupied': len(occupied) } def __repr__(self): return f'' class ClassroomResource(db.Model): __tablename__ = 'classroom_resources' id = db.Column(db.Integer, primary_key=True) classroom_id = db.Column(db.Integer, db.ForeignKey('classrooms.id'), nullable=False) resource_type = db.Column(db.String(50), nullable=False) # PROJECTOR, WHITEBOARD, COMPUTER, etc. brand = db.Column(db.String(100)) model = db.Column(db.String(100)) specifications = db.Column(db.Text) functional = db.Column(db.Boolean, default=True) last_maintenance_date = db.Column(db.DateTime) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Relationships classroom = db.relationship('Classroom', backref=db.backref('resources', lazy=True, cascade='all, delete-orphan')) def to_dict(self): """Convert resource to dictionary""" return { 'id': self.id, 'classroom_id': self.classroom_id, 'resource_type': self.resource_type, 'brand': self.brand, 'model': self.model, 'specifications': self.specifications, 'functional': self.functional, 'last_maintenance_date': self.last_maintenance_date.isoformat() if self.last_maintenance_date else None, 'created_at': self.created_at.isoformat() if self.created_at else None } def __repr__(self): return f''