Initial KDS scaffold — Phase 2 kitchen port
FastAPI backend (Python 3.11, httpx for SignalR/GraphQL — no MSSQL ODBC), shares kitchen_db directly. React/TS/Vite fullscreen board frontend. Backend: auth.py (APP_SLUG=kds, SimpleNamespace), main.py (4 KDS migrations, SignalR start/stop), kds.py router, models (kds/settings/resos — read from kitchen_db), signalr_listener.py (backoff pre-existing), kds_graphql.py, database.py. Requirements stripped to ~9 packages; image ~400 MB lighter than kitchen (no MSSQL ODBC layer). Frontend: AuthGate (app=kds), single fullscreen route, dark board theme. KDS.tsx URL prefix patched (/api/kds/ → /kds/api/kds/), recipe images cross-app (/kitchen/api/recipes/). nginx: 5 blocks with SSE proxy headers on /kds/api/ block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
b94585084a
35 changed files with 5195 additions and 0 deletions
88
backend/models/kds.py
Normal file
88
backend/models/kds.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"""
|
||||
KDS (Kitchen Display System) Models
|
||||
|
||||
Local state tracking for kitchen orders, course bumping, and display.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, DateTime, ForeignKey, Text, Boolean, Integer, Float
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from database import Base
|
||||
|
||||
|
||||
class KDSTicket(Base):
|
||||
"""
|
||||
Local tracking of SambaPOS tickets for KDS display.
|
||||
|
||||
Stores the current state of each ticket being displayed on KDS,
|
||||
including course progress and timing.
|
||||
"""
|
||||
__tablename__ = "kds_tickets"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
kitchen_id: Mapped[int] = mapped_column(ForeignKey("kitchens.id"), index=True)
|
||||
|
||||
# SambaPOS ticket reference
|
||||
sambapos_ticket_id: Mapped[int] = mapped_column(Integer, index=True)
|
||||
sambapos_ticket_uid: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
ticket_number: Mapped[str] = mapped_column(String(50))
|
||||
|
||||
# Ticket info from SambaPOS
|
||||
table_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
covers: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
total_amount: Mapped[float | None] = mapped_column(Float, nullable=True)
|
||||
|
||||
# Timing
|
||||
received_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
last_sambapos_update: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
# Local state tracking
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
is_bumped: Mapped[bool] = mapped_column(Boolean, default=False) # Fully bumped/completed
|
||||
bumped_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
# Current course state (which courses have been bumped)
|
||||
# Format: {"Starters": {"bumped": true, "bumped_at": "2025-01-25T12:00:00"}, ...}
|
||||
course_states: Mapped[dict | None] = mapped_column(JSONB, nullable=True, default=dict)
|
||||
|
||||
# Cached order data (refreshed on each poll)
|
||||
orders_data: Mapped[list | None] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
# Order IDs captured at ticket creation (for detecting +ADDITION orders later)
|
||||
initial_order_ids: Mapped[list | None] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
kitchen: Mapped["Kitchen"] = relationship("Kitchen")
|
||||
course_bumps: Mapped[list["KDSCourseBump"]] = relationship("KDSCourseBump", back_populates="ticket", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class KDSCourseBump(Base):
|
||||
"""
|
||||
Track individual course bumps for audit trail.
|
||||
|
||||
Records when each course was bumped for a ticket.
|
||||
"""
|
||||
__tablename__ = "kds_course_bumps"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
ticket_id: Mapped[int] = mapped_column(ForeignKey("kds_tickets.id", ondelete="CASCADE"), index=True)
|
||||
|
||||
course_name: Mapped[str] = mapped_column(String(100)) # e.g., "Starters", "Mains", "Desserts"
|
||||
action: Mapped[str] = mapped_column(String(20), default="sent") # "away" or "sent"
|
||||
bumped_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
bumped_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
||||
|
||||
# Time since previous course bump (for analytics)
|
||||
time_since_previous_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
|
||||
# Relationships
|
||||
ticket: Mapped["KDSTicket"] = relationship("KDSTicket", back_populates="course_bumps")
|
||||
bumped_by: Mapped["User"] = relationship("User")
|
||||
|
||||
|
||||
# Forward references
|
||||
from .user import Kitchen, User
|
||||
Loading…
Add table
Add a link
Reference in a new issue