from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, JSON, String, Text, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.database import Base class Repair(Base): __tablename__ = "repairs" id: Mapped[int] = mapped_column(primary_key=True) repair_number: Mapped[str] = mapped_column(String(20), unique=True, index=True) customer_id: Mapped[int | None] = mapped_column(ForeignKey("customers.id", ondelete="SET NULL"), nullable=True, index=True) contact_id: Mapped[int | None] = mapped_column(ForeignKey("customer_contacts.id", ondelete="SET NULL"), nullable=True, index=True) status: Mapped[str] = mapped_column(String(50), index=True) priority: Mapped[str] = mapped_column(String(30), index=True) source: Mapped[str] = mapped_column(String(50), index=True) source_reference: Mapped[str | None] = mapped_column(String(120), nullable=True, index=True) customer_name: Mapped[str] = mapped_column(String(255), index=True) customer_email: Mapped[str] = mapped_column(String(255), default="", server_default="") customer_phone: Mapped[str] = mapped_column(String(80), default="", server_default="") device_manufacturer: Mapped[str] = mapped_column(String(160), index=True) device_model: Mapped[str] = mapped_column(String(160), index=True) device_serial_number: Mapped[str | None] = mapped_column(String(160), nullable=True) device_type: Mapped[str] = mapped_column(String(120), default="", server_default="") accessories: Mapped[str] = mapped_column(Text, default="", server_default="") fault_description: Mapped[str] = mapped_column(Text) previous_work: Mapped[str] = mapped_column(Text, default="", server_default="") device_opened: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false") intake_notes: Mapped[str] = mapped_column(Text, default="", server_default="") diagnosis_notes: Mapped[str] = mapped_column(Text, default="", server_default="") repair_notes: Mapped[str] = mapped_column(Text, default="", server_default="") estimate_notes: Mapped[str] = mapped_column(Text, default="", server_default="") estimated_cost_cents: Mapped[int | None] = mapped_column(Integer, nullable=True) approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) history: Mapped[list["RepairStatusHistory"]] = relationship(back_populates="repair", cascade="all, delete-orphan", lazy="selectin") public_access_tokens: Mapped[list["RepairPublicAccessToken"]] = relationship(back_populates="repair", cascade="all, delete-orphan") notification_events: Mapped[list["RepairNotificationEvent"]] = relationship(back_populates="repair", cascade="all, delete-orphan") class RepairStatusHistory(Base): __tablename__ = "repair_status_history" id: Mapped[int] = mapped_column(primary_key=True) repair_id: Mapped[int] = mapped_column(ForeignKey("repairs.id", ondelete="CASCADE"), index=True) old_status: Mapped[str | None] = mapped_column(String(50), nullable=True) new_status: Mapped[str] = mapped_column(String(50), index=True) note: Mapped[str | None] = mapped_column(Text, nullable=True) actor_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) repair: Mapped[Repair] = relationship(back_populates="history") actor = relationship("User", lazy="joined") @property def actor_username(self) -> str: return self.actor.username if self.actor is not None else "" @property def actor_display_name(self) -> str: if self.actor is None: return "" display_name = f"{self.actor.first_name} {self.actor.last_name}".strip() return display_name or self.actor.username class RepairIntakeEvent(Base): __tablename__ = "repair_intake_events" id: Mapped[int] = mapped_column(primary_key=True) repair_id: Mapped[int | None] = mapped_column(ForeignKey("repairs.id", ondelete="SET NULL"), nullable=True, index=True) external_source: Mapped[str] = mapped_column(String(80), index=True) external_reference: Mapped[str | None] = mapped_column(String(160), nullable=True, index=True) payload: Mapped[dict] = mapped_column(JSON) status: Mapped[str] = mapped_column(String(50), index=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class RepairDocument(Base): __tablename__ = "repair_documents" id: Mapped[int] = mapped_column(primary_key=True) repair_id: Mapped[int] = mapped_column(ForeignKey("repairs.id", ondelete="CASCADE"), index=True) file_id: Mapped[int | None] = mapped_column(Integer, nullable=True) storage_path: Mapped[str | None] = mapped_column(String(500), nullable=True) title: Mapped[str] = mapped_column(String(255)) document_type: Mapped[str] = mapped_column(String(80), index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class RepairPublicAccessToken(Base): __tablename__ = "repair_public_access_tokens" id: Mapped[int] = mapped_column(primary_key=True) repair_id: Mapped[int] = mapped_column(ForeignKey("repairs.id", ondelete="CASCADE"), index=True) token_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True) token_hint: Mapped[str | None] = mapped_column(String(16), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true", index=True) expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) repair: Mapped[Repair] = relationship(back_populates="public_access_tokens") class RepairNotificationEvent(Base): __tablename__ = "repair_notification_events" id: Mapped[int] = mapped_column(primary_key=True) repair_id: Mapped[int] = mapped_column(ForeignKey("repairs.id", ondelete="CASCADE"), index=True) event_type: Mapped[str] = mapped_column(String(80), index=True) channel: Mapped[str] = mapped_column(String(40), index=True) recipient: Mapped[str] = mapped_column(String(255), default="", server_default="") subject: Mapped[str] = mapped_column(String(255)) status: Mapped[str] = mapped_column(String(40), index=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) repair: Mapped[Repair] = relationship(back_populates="notification_events")