gmao/app_new/core/models/college.py

289 lines
13 KiB
Python
Raw Normal View History

2026-08-14 18:02:25 +02:00
"""
Modèle College et Building - GMAO Collège
Gestion des collèges et bâtiments
Compatible avec la base de données existante
"""
from datetime import datetime, timezone
from ...extensions import db
class College(db.Model):
"""Collège."""
__tablename__ = "colleges"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
address = db.Column(db.Text, nullable=True)
city = db.Column(db.String(100), nullable=True)
zip_code = db.Column(db.String(10), nullable=True)
phone = db.Column(db.String(20), nullable=True)
email = db.Column(db.String(100), nullable=True)
website = db.Column(db.String(255), nullable=True)
logo = db.Column(db.String(255), nullable=True)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
# Relations - buildings n'a pas de college_id, pas de relation directe
def __repr__(self):
return f"<College {self.name}>"
class Site(db.Model):
"""Site physique : collège, logements de fonction, annexe, etc."""
__tablename__ = "sites"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False, unique=True)
site_type = db.Column(db.String(40), nullable=False, default="college")
address = db.Column(db.Text, nullable=True)
is_active = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
buildings = db.relationship("Building", back_populates="site", lazy="dynamic")
2026-08-14 18:02:25 +02:00
class Building(db.Model):
"""Bâtiment du collège."""
__tablename__ = "buildings"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=True)
site_id = db.Column(db.Integer, db.ForeignKey("sites.id"), nullable=True, index=True)
2026-08-14 18:02:25 +02:00
# Relations - utiliser overlaps pour éviter warning SQLAlchemy
rooms = db.relationship("Room", overlaps="building", lazy="dynamic")
zones = db.relationship("Zone", back_populates="building", lazy="dynamic")
site = db.relationship("Site", back_populates="buildings")
2026-08-14 18:02:25 +02:00
@property
def room_count(self):
"""Nombre de salles dans le bâtiment."""
return self.rooms.count()
def __repr__(self):
return f"<Building {self.name}>"
class HousingUnit(db.Model):
"""Logement de fonction et historique courant de son occupation."""
__tablename__ = "housing_units"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
code = db.Column(db.String(40), nullable=True, unique=True)
building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False, index=True)
housing_type = db.Column(db.String(40), nullable=False, default="fonction")
occupancy_status = db.Column(db.String(30), nullable=False, default="vacant")
occupant_name = db.Column(db.String(150), nullable=True)
occupant_contact = db.Column(db.String(150), nullable=True)
occupancy_start = db.Column(db.Date, nullable=True)
occupancy_end = db.Column(db.Date, nullable=True)
notes = db.Column(db.Text, nullable=True)
is_active = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
building = db.relationship("Building", backref=db.backref("housing_units", lazy="dynamic"))
rooms = db.relationship("Room", back_populates="housing_unit", lazy="dynamic")
occupancies = db.relationship("HousingOccupancy", back_populates="housing_unit",
order_by="HousingOccupancy.start_date.desc()",
cascade="all, delete-orphan")
class HousingOccupancy(db.Model):
"""Historique daté des occupants, clés et états des lieux."""
__tablename__ = "housing_occupancies"
id = db.Column(db.Integer, primary_key=True)
housing_unit_id = db.Column(db.Integer, db.ForeignKey("housing_units.id", ondelete="CASCADE"), nullable=False, index=True)
occupant_name = db.Column(db.String(150), nullable=False)
occupant_contact = db.Column(db.String(150), nullable=True)
start_date = db.Column(db.Date, nullable=False)
end_date = db.Column(db.Date, nullable=True)
key_count = db.Column(db.Integer, nullable=False, default=0)
entry_condition = db.Column(db.Text, nullable=True)
exit_condition = db.Column(db.Text, nullable=True)
notes = db.Column(db.Text, nullable=True)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
housing_unit = db.relationship("HousingUnit", back_populates="occupancies")
2026-08-14 18:02:25 +02:00
class Zone(db.Model):
"""Zone dans un bâtiment."""
__tablename__ = "zones"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False)
# Durées propres aux espaces verts de cette zone (minutes). Elles restent
# nulles pour les zones qui ne sont pas concernées.
mowing_minutes = db.Column(db.Integer, nullable=True)
edging_minutes = db.Column(db.Integer, nullable=True)
hedge_minutes = db.Column(db.Integer, nullable=True)
branch_removal_minutes = db.Column(db.Integer, nullable=True)
2026-08-14 18:02:25 +02:00
# Relations
building = db.relationship("Building", back_populates="zones")
rooms = db.relationship("Room", back_populates="zone")
@property
def full_name(self):
"""Nom complet avec bâtiment."""
if self.building:
return f"{self.building.name} - {self.name}"
return self.name
def __repr__(self):
return f"<Zone {self.name}>"
class Room(db.Model):
"""Salle du collège."""
__tablename__ = "rooms"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
code = db.Column(db.String(20), nullable=True)
zone_id = db.Column(db.Integer, db.ForeignKey("zones.id"), nullable=True)
floor = db.Column(db.Integer, default=0)
building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False)
room_type_id = db.Column(db.Integer, db.ForeignKey("room_types.id"), nullable=True)
room_profile_id = db.Column(db.Integer, db.ForeignKey("room_profiles.id"), nullable=True, index=True)
housing_unit_id = db.Column(db.Integer, db.ForeignKey("housing_units.id"), nullable=True, index=True)
2026-08-14 18:02:25 +02:00
# Libelle historique conserve pour les imports anterieurs a zone_id.
legacy_zone_name = db.Column("zone", db.String(100), nullable=True)
# Relations - utiliser overlaps pour éviter warning SQLAlchemy
building = db.relationship("Building", overlaps="rooms")
zone = db.relationship("Zone", back_populates="rooms")
room_type = db.relationship("RoomType", back_populates="rooms")
room_profile = db.relationship("RoomProfile", backref=db.backref("rooms", lazy="dynamic"))
2026-08-14 18:02:25 +02:00
equipments = db.relationship("Equipment", back_populates="room")
housing_unit = db.relationship("HousingUnit", back_populates="rooms")
2026-08-14 18:02:25 +02:00
@property
def full_name(self):
"""Nom complet avec bâtiment."""
if self.building:
return f"{self.building.name} - {self.name}"
return self.name
def __repr__(self):
return f"<Room {self.name}>"
class RoomType(db.Model):
"""Type de salle."""
__tablename__ = "room_types"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=True)
is_teaching = db.Column(db.Boolean, default=False)
color = db.Column(db.String(20), nullable=True)
icon = db.Column(db.String(50), nullable=True)
# Relation vers les salles
rooms = db.relationship("Room", back_populates="room_type")
def __repr__(self):
return f"<RoomType {self.name}>"
class RoomProfile(db.Model):
"""Modèle de proposition pour préconfigurer un local.
``RoomType`` reste un classement structurel (notamment utilisé par les
emplois du temps). Un profil est une proposition éditable qui ne crée
aucun équipement tant que l'utilisateur n'a pas validé l'application.
"""
__tablename__ = "room_profiles"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False, unique=True)
description = db.Column(db.Text, nullable=True)
room_type_id = db.Column(db.Integer, db.ForeignKey("room_types.id"), nullable=True, index=True)
is_active = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
room_type = db.relationship("RoomType", backref=db.backref("profiles", lazy="dynamic"))
items = db.relationship("RoomProfileItem", back_populates="profile", cascade="all, delete-orphan",
order_by="RoomProfileItem.sort_order, RoomProfileItem.id")
surfaces = db.relationship("RoomSurface", back_populates="profile", cascade="all, delete-orphan")
def __repr__(self):
return f"<RoomProfile {self.name}>"
class RoomProfileItem(db.Model):
"""Une proposition d'équipement dans un profil de local."""
__tablename__ = "room_profile_items"
id = db.Column(db.Integer, primary_key=True)
profile_id = db.Column(db.Integer, db.ForeignKey("room_profiles.id", ondelete="CASCADE"), nullable=False, index=True)
label = db.Column(db.String(200), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey("equipment_categories.id"), nullable=True, index=True)
lot_id = db.Column(db.Integer, db.ForeignKey("lots.id"), nullable=True, index=True)
quantity = db.Column(db.Integer, nullable=False, default=1)
is_group = db.Column(db.Boolean, nullable=False, default=False)
enabled_by_default = db.Column(db.Boolean, nullable=False, default=True)
sort_order = db.Column(db.Integer, nullable=False, default=0)
profile = db.relationship("RoomProfile", back_populates="items")
category = db.relationship("EquipmentCategory")
lot = db.relationship("Lot")
class RoomSurface(db.Model):
"""Composition facultative d'une surface réelle ou proposée."""
__tablename__ = "room_surfaces"
id = db.Column(db.Integer, primary_key=True)
room_id = db.Column(db.Integer, db.ForeignKey("rooms.id", ondelete="CASCADE"), nullable=True, index=True)
profile_id = db.Column(db.Integer, db.ForeignKey("room_profiles.id", ondelete="CASCADE"), nullable=True, index=True)
surface_type = db.Column(db.String(30), nullable=False) # mur, sol, plafond
material = db.Column(db.String(80), nullable=False)
finish = db.Column(db.String(80), nullable=True)
label = db.Column(db.String(120), nullable=True)
sort_order = db.Column(db.Integer, nullable=False, default=0)
room = db.relationship("Room", backref=db.backref("surfaces", cascade="all, delete-orphan"))
profile = db.relationship("RoomProfile", back_populates="surfaces")
2026-08-14 18:02:25 +02:00
class RoomSchedule(db.Model):
"""Emploi du temps d'une salle."""
__tablename__ = "room_schedules"
id = db.Column(db.Integer, primary_key=True)
room_id = db.Column(db.Integer, db.ForeignKey("rooms.id"), nullable=False)
week_start = db.Column(db.Date, nullable=False)
day_of_week = db.Column(db.Integer, nullable=False)
start_time = db.Column(db.Time, nullable=False)
end_time = db.Column(db.Time, nullable=False)
subject = db.Column(db.String(100), nullable=True)
teacher = db.Column(db.String(100), nullable=True)
class_name = db.Column(db.String(50), nullable=True)
course_name = db.Column(db.String(255), nullable=True)
source = db.Column(db.String(20), nullable=False, default="manual", index=True)
external_id = db.Column(db.String(191), nullable=True, index=True)
last_synced_at = db.Column(db.DateTime, nullable=True)
source_updated_at = db.Column(db.DateTime, nullable=True)
protected_from_sync = db.Column(db.Boolean, nullable=False, default=False, index=True)
resolution_status = db.Column(db.String(20), nullable=False, default="active", index=True)
valid_from = db.Column(db.Date, nullable=True)
valid_to = db.Column(db.Date, nullable=True)
event_type = db.Column(db.String(30), nullable=False, default="cours")
conflict_note = db.Column(db.Text, nullable=True)
2026-08-14 18:02:25 +02:00
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
# Relations
room = db.relationship("Room", backref="schedules")
def __repr__(self):
return f"<RoomSchedule {self.room.name} - {self.day_of_week}>"