Spaces:
Sleeping
Sleeping
| """ | |
| Visit Model β ORM + Pydantic Schemas | |
| ORM: Visit (SQLAlchemy table: visits) | |
| Schemas: VisitCreate, VisitUpdate, VisitResponse, VisitListResponse | |
| Enhanced for Ghost Shopper program: | |
| - outlet_id: Links to the coffee-shop outlet being evaluated | |
| - ghost_shopper: Name / initials of the Ghost Shopper | |
| - visit_type: dine_in, take_away, drive_thru, delivery | |
| - comments: Ghost Shopper comments (Komentar) | |
| - suggestions: Ghost Shopper suggestions (Saran) | |
| """ | |
| import uuid | |
| from datetime import datetime | |
| from typing import Optional | |
| from sqlalchemy import String, Text, DateTime, ForeignKey, func | |
| from sqlalchemy.dialects.postgresql import UUID | |
| from sqlalchemy.orm import Mapped, mapped_column, relationship | |
| from pydantic import BaseModel, Field, ConfigDict | |
| from models.base import Base | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ORM Model | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Visit(Base): | |
| """SQLAlchemy ORM model for the 'visits' table.""" | |
| __tablename__ = "visits" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| title: Mapped[str] = mapped_column(String(255), nullable=False) | |
| description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) | |
| location: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) | |
| visit_date: Mapped[datetime] = mapped_column(DateTime, nullable=False) | |
| status: Mapped[str] = mapped_column( | |
| String(50), nullable=False, default="pending" | |
| ) | |
| # ββ Ghost Shopper fields (nullable for backward compatibility) ββ | |
| outlet_id: Mapped[Optional[uuid.UUID]] = mapped_column( | |
| UUID(as_uuid=True), | |
| ForeignKey("outlets.id", ondelete="SET NULL"), | |
| nullable=True, | |
| ) | |
| ghost_shopper: Mapped[Optional[str]] = mapped_column( | |
| String(255), nullable=True, | |
| comment="Ghost Shopper name or initials" | |
| ) | |
| visit_type: Mapped[Optional[str]] = mapped_column( | |
| String(50), nullable=True, | |
| comment="dine_in, take_away, drive_thru, delivery" | |
| ) | |
| comments: Mapped[Optional[str]] = mapped_column( | |
| Text, nullable=True, | |
| comment="Ghost Shopper Komentar" | |
| ) | |
| suggestions: Mapped[Optional[str]] = mapped_column( | |
| Text, nullable=True, | |
| comment="Ghost Shopper Saran" | |
| ) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now() | |
| ) | |
| updated_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now(), onupdate=func.now() | |
| ) | |
| # Relationships | |
| outlet = relationship("Outlet", back_populates="visits") | |
| checklists = relationship( | |
| "Checklist", back_populates="visit", cascade="all, delete-orphan" | |
| ) | |
| photos = relationship( | |
| "Photo", back_populates="visit", cascade="all, delete-orphan" | |
| ) | |
| assessments = relationship( | |
| "Assessment", back_populates="visit", cascade="all, delete-orphan" | |
| ) | |
| def __repr__(self) -> str: | |
| return f"<Visit(id={self.id}, title='{self.title}', status='{self.status}')>" | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Pydantic Schemas (Request / Response) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class VisitCreate(BaseModel): | |
| """Schema for creating a new visit.""" | |
| title: str = Field(..., min_length=1, max_length=255, description="Visit title") | |
| description: Optional[str] = Field(None, description="Visit description") | |
| location: Optional[str] = Field(None, max_length=255, description="Visit location") | |
| visit_date: datetime = Field(..., description="Scheduled visit date & time") | |
| status: Optional[str] = Field("pending", description="Visit status") | |
| outlet_id: Optional[uuid.UUID] = Field(None, description="Outlet UUID") | |
| ghost_shopper: Optional[str] = Field(None, max_length=255, description="Ghost Shopper name") | |
| visit_type: Optional[str] = Field(None, max_length=50, description="dine_in, take_away, etc.") | |
| comments: Optional[str] = Field(None, description="Komentar") | |
| suggestions: Optional[str] = Field(None, description="Saran") | |
| class VisitUpdate(BaseModel): | |
| """Schema for updating an existing visit (all fields optional).""" | |
| title: Optional[str] = Field(None, min_length=1, max_length=255) | |
| description: Optional[str] = None | |
| location: Optional[str] = Field(None, max_length=255) | |
| visit_date: Optional[datetime] = None | |
| status: Optional[str] = Field(None, max_length=50) | |
| outlet_id: Optional[uuid.UUID] = None | |
| ghost_shopper: Optional[str] = Field(None, max_length=255) | |
| visit_type: Optional[str] = Field(None, max_length=50) | |
| comments: Optional[str] = None | |
| suggestions: Optional[str] = None | |
| class VisitResponse(BaseModel): | |
| """Schema for a single visit in API responses.""" | |
| model_config = ConfigDict(from_attributes=True) | |
| id: uuid.UUID | |
| title: str | |
| description: Optional[str] | |
| location: Optional[str] | |
| visit_date: datetime | |
| status: str | |
| outlet_id: Optional[uuid.UUID] | |
| ghost_shopper: Optional[str] | |
| visit_type: Optional[str] | |
| comments: Optional[str] | |
| suggestions: Optional[str] | |
| created_at: datetime | |
| updated_at: datetime | |
| class VisitListResponse(BaseModel): | |
| """Schema for paginated visit list.""" | |
| status: str = "success" | |
| data: list[VisitResponse] | |
| meta: dict | |