device.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """
  2. Device model - WiFi/BLE receivers/scanners.
  3. """
  4. from datetime import datetime
  5. from sqlalchemy import DateTime, ForeignKey, Integer, String, text
  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(
  19. Integer,
  20. unique=True,
  21. nullable=False,
  22. server_default=text("nextval('device_simple_id_seq')"),
  23. )
  24. # Hardware identifiers
  25. mac_address: Mapped[str] = mapped_column(String(17), unique=True, nullable=False)
  26. serial_number: Mapped[str | None] = mapped_column(String(100))
  27. # Device info
  28. device_type: Mapped[str] = mapped_column(
  29. String(50), default="combo", nullable=False
  30. ) # wifi_scanner, ble_receiver, combo
  31. model: Mapped[str | None] = mapped_column(String(50))
  32. firmware_version: Mapped[str | None] = mapped_column(String(50))
  33. # Organization binding (NULL = unassigned)
  34. organization_id: Mapped[int | None] = mapped_column(
  35. ForeignKey("organizations.id", ondelete="SET NULL")
  36. )
  37. # Status: offline, online, error
  38. status: Mapped[str] = mapped_column(
  39. String(20), default="offline", nullable=False
  40. )
  41. last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
  42. last_ip: Mapped[str | None] = mapped_column(INET)
  43. # Config (flexible JSON for different device types)
  44. config: Mapped[dict] = mapped_column(JSONB, default={}, nullable=False)
  45. # Device credentials (for API access)
  46. device_token: Mapped[str | None] = mapped_column(String(512), unique=True)
  47. device_password: Mapped[str | None] = mapped_column(String(32))
  48. # Notes
  49. notes: Mapped[str | None] = mapped_column(String)
  50. # Relationships
  51. organization: Mapped["Organization | None"] = relationship(
  52. "Organization", back_populates="devices"
  53. )
  54. @property
  55. def display_name(self) -> str:
  56. """Display name: Receiver #5"""
  57. return f"Receiver #{self.simple_id}"
  58. def __repr__(self) -> str:
  59. return f"<Device {self.id}: {self.display_name} ({self.mac_address})>"