154 lines
9.1 KiB
Python
154 lines
9.1 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import BigInteger, 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")
|
|
documents: Mapped[list["RepairDocument"]] = 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)
|
|
title: Mapped[str] = mapped_column(String(255))
|
|
document_type: Mapped[str] = mapped_column(String(80), index=True)
|
|
original_filename: Mapped[str] = mapped_column(String(255), default="", server_default="")
|
|
stored_filename: Mapped[str] = mapped_column(String(255), default="", server_default="")
|
|
storage_path: Mapped[str] = mapped_column(String(500), default="", server_default="")
|
|
mime_type: Mapped[str] = mapped_column(String(120), default="", server_default="")
|
|
size_bytes: Mapped[int] = mapped_column(BigInteger, default=0, server_default="0")
|
|
checksum_sha256: Mapped[str] = mapped_column(String(64), default="", server_default="", index=True)
|
|
visibility: Mapped[str] = mapped_column(String(40), default="internal", server_default="internal", index=True)
|
|
note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
uploaded_by_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())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
repair: Mapped[Repair] = relationship(back_populates="documents")
|
|
uploaded_by = relationship("User", lazy="joined")
|
|
|
|
@property
|
|
def uploaded_by_username(self) -> str:
|
|
return self.uploaded_by.username if self.uploaded_by is not None else ""
|
|
|
|
@property
|
|
def uploaded_by_display_name(self) -> str:
|
|
if self.uploaded_by is None:
|
|
return ""
|
|
display_name = f"{self.uploaded_by.first_name} {self.uploaded_by.last_name}".strip()
|
|
return display_name or self.uploaded_by.username
|
|
|
|
|
|
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))
|
|
template: Mapped[str] = mapped_column(Text, default="", server_default="")
|
|
status: Mapped[str] = mapped_column(String(40), index=True)
|
|
success: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false", 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")
|