diff --git a/inventory/models/inventory.py b/inventory/models/inventory.py index b307842..e431f44 100644 --- a/inventory/models/inventory.py +++ b/inventory/models/inventory.py @@ -2,9 +2,9 @@ from typing import Any, List, Optional, TYPE_CHECKING from .image import Image if TYPE_CHECKING: - from .brands import Brand + from .brands import Brand from .items import Item - from .work_log import WorkLog + from .work_log import WorkLog from .rooms import Room from .image import Image @@ -20,21 +20,21 @@ class Inventory(db.Model, ImageAttachable): __table_args__ = ( Index('Inventory$Barcode', 'barcode'), ) - + id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) timestamp: Mapped[datetime.datetime] = mapped_column(DateTime) condition: Mapped[str] = mapped_column(Unicode(255)) - type_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("item.id"), nullable=True) + type_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("item.id"), nullable=True, index=True) name: Mapped[Optional[str]] = mapped_column(Unicode(255)) serial: Mapped[Optional[str]] = mapped_column(Unicode(255)) model: Mapped[Optional[str]] = mapped_column(Unicode(255)) notes: Mapped[Optional[str]] = mapped_column(Unicode(255)) - owner_id = mapped_column(Integer, ForeignKey('users.id')) - brand_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("brand.id")) - location_id: Mapped[Optional[str]] = mapped_column(ForeignKey("rooms.id")) + owner_id = mapped_column(Integer, ForeignKey('users.id'), nullable=True, index=True) + brand_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("brand.id"), nullable=True, index=True) + location_id: Mapped[Optional[str]] = mapped_column(ForeignKey("rooms.id"), nullable=True, index=True) barcode: Mapped[Optional[str]] = mapped_column(Unicode(255)) shared: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) - image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id', ondelete='SET NULL'), nullable=True) + image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id', ondelete='SET NULL'), nullable=True, index=True) location: Mapped[Optional['Room']] = relationship('Room', back_populates='inventory') owner = relationship('User', back_populates='inventory') @@ -91,7 +91,7 @@ class Inventory(db.Model, ImageAttachable): return f"Serial: {self.serial}" else: return f"ID: {self.id}" - + def serialize(self) -> dict[str, Any]: return { 'id': self.id, @@ -108,11 +108,11 @@ class Inventory(db.Model, ImageAttachable): 'barcode': self.barcode, 'shared': self.shared } - + @classmethod def from_dict(cls, data: dict[str, Any]) -> "Inventory": timestamp_str = data.get("timestamp") - + return cls( timestamp = datetime.datetime.fromisoformat(str(timestamp_str)) if timestamp_str else datetime.datetime.now(), condition=data.get("condition", "Unverified"), diff --git a/inventory/models/rooms.py b/inventory/models/rooms.py index bf1cb31..eb06b18 100644 --- a/inventory/models/rooms.py +++ b/inventory/models/rooms.py @@ -1,6 +1,6 @@ from typing import Optional, TYPE_CHECKING, List if TYPE_CHECKING: - from .areas import Area + from .areas import Area from .room_functions import RoomFunction from .inventory import Inventory from .users import User @@ -17,9 +17,9 @@ class Room(ValidatableMixin, db.Model): VALIDATION_LABEL = "Room" id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) - name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) - area_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("area.id")) - function_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("room_function.id")) + name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) + area_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("area.id"), nullable=True, index=True) + function_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("room_function.id"), nullable=True, index=True) area: Mapped[Optional['Area']] = relationship('Area', back_populates='rooms') room_function: Mapped[Optional['RoomFunction']] = relationship('RoomFunction', back_populates='rooms') @@ -33,13 +33,13 @@ class Room(ValidatableMixin, db.Model): 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, @@ -146,18 +146,18 @@ class Room(ValidatableMixin, db.Model): @classmethod def validate_state(cls, submitted_items: list[dict]) -> list[str]: errors = [] - + for index, item in enumerate(submitted_items): label = f"Room #{index + 1}" - + if not isinstance(item, dict): errors.append(f"{label} is not a valid object.") continue - + name = item.get("name") if not name or not str(name).strip(): errors.append(f"{label} is missing a name.") - + raw_id = item.get("id") if raw_id is not None: try: @@ -165,19 +165,19 @@ class Room(ValidatableMixin, db.Model): except (ValueError, TypeError): if not str(raw_id).startswith("room-"): errors.append(f"{label} has an invalid ID: {raw_id}") - + # These fields are FK IDs, so we're just checking for valid formats here. for fk_field, fk_label in [("section_id", "Section"), ("function_id", "Function")]: fk_val = item.get(fk_field) if fk_val is None: continue # Let the DB enforce nullability - + try: _ = int(fk_val) except (ValueError, TypeError): fk_val_str = str(fk_val) if not fk_val_str.startswith("temp-"): errors.append(f"{label} has invalid {fk_label} ID: {fk_val}") - + return errors diff --git a/inventory/models/users.py b/inventory/models/users.py index 1e8c5f4..7962b66 100644 --- a/inventory/models/users.py +++ b/inventory/models/users.py @@ -1,6 +1,6 @@ from typing import Any, List, Optional, TYPE_CHECKING if TYPE_CHECKING: - from .inventory import Inventory + from .inventory import Inventory from .rooms import Room from .work_log import WorkLog from .image import Image @@ -19,9 +19,9 @@ class User(db.Model, ImageAttachable): active: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) last_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) first_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) - location_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rooms.id"), nullable=True) - supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id")) - image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id', ondelete='SET NULL'), nullable=True) + location_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rooms.id"), nullable=True, index=True) + supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True) + image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id', ondelete='SET NULL'), nullable=True, index=True) supervisor: Mapped[Optional['User']] = relationship('User', remote_side='User.id', back_populates='subordinates') subordinates: Mapped[List['User']] = relationship('User', back_populates='supervisor') @@ -33,7 +33,7 @@ class User(db.Model, ImageAttachable): @property def full_name(self) -> str: return f"{self.first_name or ''} {self.last_name or ''}".strip() - + def __init__(self, first_name: Optional[str] = None, last_name: Optional[str] = None, location_id: Optional[int] = None, supervisor_id: Optional[int] = None, staff: Optional[bool] = False, active: Optional[bool] = False): @@ -47,7 +47,7 @@ class User(db.Model, ImageAttachable): def __repr__(self): return f"" - + def serialize(self): return { 'id': self.id, diff --git a/inventory/models/work_log.py b/inventory/models/work_log.py index 46ef685..796cbd9 100644 --- a/inventory/models/work_log.py +++ b/inventory/models/work_log.py @@ -1,6 +1,6 @@ from typing import Optional, Any, List, TYPE_CHECKING if TYPE_CHECKING: - from .inventory import Inventory + from .inventory import Inventory from .image import Image from .users import User from .work_note import WorkNote @@ -23,15 +23,15 @@ class WorkLog(db.Model, ImageAttachable): notes: Mapped[Optional[str]] = mapped_column(Unicode()) complete: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) followup: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) - contact_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id")) + contact_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True) analysis: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) - work_item_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("inventory.id")) + work_item_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("inventory.id"), nullable=True, index=True) work_item: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='work_logs') contact: Mapped[Optional['User']] = relationship('User', back_populates='work_logs') updates: Mapped[List['WorkNote']] = relationship( - 'WorkNote', - back_populates='work_log', + 'WorkNote', + back_populates='work_log', cascade='all, delete-orphan', order_by='WorkNote.timestamp' ) @@ -58,7 +58,7 @@ class WorkLog(db.Model, ImageAttachable): self.analysis = analysis self.work_item_id = work_item_id self.updates = updates or [] - + def __repr__(self): return f" "WorkLog": start_time_str = data.get("start_time") end_time_str = data.get("end_time") - + updates_raw = data.get("updates", []) updates: list[WorkNote] = [] - + for u in updates_raw: if isinstance(u, dict): content = u.get("content", "").strip() else: content = str(u).strip() - + if content: updates.append(WorkNote(content=content)) - + return cls( start_time=datetime.datetime.fromisoformat(str(start_time_str)) if start_time_str else datetime.datetime.now(), end_time=datetime.datetime.fromisoformat(str(end_time_str)) if end_time_str else None, diff --git a/inventory/models/work_note.py b/inventory/models/work_note.py index b52c45d..605dfbc 100644 --- a/inventory/models/work_note.py +++ b/inventory/models/work_note.py @@ -12,7 +12,7 @@ class WorkNote(db.Model): ) id: Mapped[int] = mapped_column(primary_key=True) - work_log_id: Mapped[int] = mapped_column(ForeignKey('work_log.id', ondelete='CASCADE'), nullable=False) + work_log_id: Mapped[int] = mapped_column(ForeignKey('work_log.id', ondelete='CASCADE'), nullable=False, index=True) timestamp: Mapped[datetime.datetime] = mapped_column(DateTime, default=func.now(), server_default=func.now()) content: Mapped[str] = mapped_column(UnicodeText, nullable=False) @@ -25,7 +25,7 @@ class WorkNote(db.Model): def __repr__(self) -> str: preview = self.content[:30].replace("\n", " ") + "..." if len(self.content) > 30 else self.content return f"" - + def serialize(self) -> dict: return { 'id': self.id,