from typing import Optional, TYPE_CHECKING, List if TYPE_CHECKING: from .areas import Area from .room_functions import RoomFunction from .inventory import Inventory from .users import User from sqlalchemy import ForeignKey, Identity, Integer, Unicode from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db class Room(db.Model): __tablename__ = 'Rooms' id: Mapped[int] = mapped_column("ID", Integer, Identity(start=1, increment=1), primary_key=True) name: Mapped[Optional[str]] = mapped_column("Room", Unicode(255), nullable=True) area_id: Mapped[Optional[int]] = mapped_column("Area", Integer, ForeignKey("Areas.ID")) function_id: Mapped[Optional[int]] = mapped_column("Function", Integer, ForeignKey("Room Functions.ID")) area: Mapped[Optional['Area']] = relationship('Area', back_populates='rooms') room_function: Mapped[Optional['RoomFunction']] = relationship('RoomFunction', back_populates='rooms') inventory: Mapped[List['Inventory']] = relationship('Inventory', back_populates='location') users: Mapped[List['User']] = relationship('User', back_populates='location') def __init__(self, name: Optional[str] = None, area_id: Optional[int] = None, function_id: Optional[int] = None): self.name = name self.area_id = area_id self.function_id = function_id def __repr__(self): return f"" @property def full_name(self): name = self.name or "" func = self.room_function.description if self.room_function else "" return f"{name} - {func}".strip(" -") def serialize(self): return { 'id': self.id, 'name': self.name, 'area_id': self.area_id, 'function_id': self.function_id } @classmethod def sync_from_state(cls, submitted_rooms: list[dict], section_map: dict, function_map: dict, section_fallbacks: list, function_fallbacks: list): """ Syncs the Rooms table with the submitted room list. Resolves foreign keys using section_map and function_map. """ def resolve_id(raw_id, fallback_list, id_map, label): try: resolved = int(raw_id) if resolved >= 0: if resolved in id_map.values(): return resolved raise ValueError(f"ID {resolved} not found in {label}.") except Exception: pass index = abs(resolved + 1) try: entry = fallback_list[index] key = entry.get("name") if isinstance(entry, dict) else str(entry).strip() final_id = id_map.get(key) if final_id is None: raise ValueError(f"ID for {key} not found in {label}.") return final_id except Exception as e: raise ValueError(f"Failed to resolve {label} ID: {e}") from e existing_query = db.session.query(cls) existing = {room.name: room for room in existing_query.all()} submitted = { str(room.get("name", "")).strip(): room for room in submitted_rooms if isinstance(room, dict) and str(room.get("name", "")).strip() } existing_names = set(existing.keys()) submitted_names = set(submitted.keys()) print(f"Existing rooms: {existing_names}") print(f"Submitted rooms: {submitted_names}") print(f"Rooms to add: {submitted_names - existing_names}") print(f"Rooms to remove: {existing_names - submitted_names}") for name in submitted_names - existing_names: room_data = submitted[name] area_id = resolve_id(room_data.get("section_id", -1), section_fallbacks, section_map, "section") if room_data.get("section_id") is not None else None function_id = resolve_id(room_data.get("function_id", -1), function_fallbacks, function_map, "function") if room_data.get("function_id") is not None else None new_room = cls(name=name, area_id=area_id, function_id=function_id) db.session.add(new_room) print(f"➕ Adding room: {new_room}") for name in existing_names - submitted_names: room_to_remove = existing[name] db.session.delete(room_to_remove) print(f"🗑️ Removing room: {room_to_remove}")