device.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. """
  2. Device model - WiFi/BLE receivers/scanners.
  3. """
  4. from datetime import datetime
  5. from sqlalchemy import DateTime, ForeignKey, Integer, String
  6. from sqlalchemy.dialects.postgresql import INET, JSONB
  7. from sqlalchemy.orm import Mapped, mapped_column, relationship
  8. from app.models.base import Base
  9. class Device(Base):
  10. """
  11. Device model - WiFi/BLE receivers/scanners.
  12. Uses simple_id for customer support (Receiver #1, #2, #3...)
  13. instead of MAC addresses.
  14. """
  15. __tablename__ = "devices"
  16. id: Mapped[int] = mapped_column(primary_key=True)
  17. # Simple ID for customer support (auto-increment, never reused)
  18. simple_id: Mapped[int] = mapped_column(Integer, unique=True, nullable=False)
  19. # Hardware identifiers
  20. mac_address: Mapped[str] = mapped_column(String(17), unique=True, nullable=False)
  21. serial_number: Mapped[str | None] = mapped_column(String(100))
  22. # Device info
  23. device_type: Mapped[str] = mapped_column(
  24. String(50), default="combo", nullable=False
  25. ) # wifi_scanner, ble_receiver, combo
  26. model: Mapped[str | None] = mapped_column(String(50))
  27. firmware_version: Mapped[str | None] = mapped_column(String(50))
  28. # Organization binding (NULL = unassigned)
  29. organization_id: Mapped[int | None] = mapped_column(
  30. ForeignKey("organizations.id", ondelete="SET NULL")
  31. )
  32. # Status: offline, online, error
  33. status: Mapped[str] = mapped_column(
  34. String(20), default="offline", nullable=False
  35. )
  36. last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
  37. last_ip: Mapped[str | None] = mapped_column(INET)
  38. # Config (flexible JSON for different device types)
  39. config: Mapped[dict] = mapped_column(JSONB, default={}, nullable=False)
  40. # Notes
  41. notes: Mapped[str | None] = mapped_column(String)
  42. # Relationships
  43. organization: Mapped["Organization | None"] = relationship(
  44. "Organization", back_populates="devices"
  45. )
  46. @property
  47. def display_name(self) -> str:
  48. """Display name: Receiver #5"""
  49. return f"Receiver #{self.simple_id}"
  50. def __repr__(self) -> str:
  51. return f"<Device {self.id}: {self.display_name} ({self.mac_address})>"