Initial kitchen scaffold — Phase 1 kitchen port (build-verified 2026-07-11)

FastAPI backend (Python 3.11, MSSQL ODBC for SambaPOS, Azure DI OCR),
kitchen_db on central PG. React/TS/Vite frontend with navy sidebar layout.

Backend: auth.py (APP_SLUG=kitchen, SimpleNamespace — archive routes use
.kitchen_id/.is_admin without modification), main.py (51 migrations, scheduler,
internal router for KDS bookings feed), api/internal.py, full archive API
(31 routers: invoices, recipes, menus, sambapos, resos, newbook, disputes,
purchase_orders, etc.), models, migrations, OCR pipeline.
kitchen_id pinned to 1 (B1 — single hotel).

Frontend: AuthGate (app=kitchen, token shim for archive compat — B5b pending),
Layout (navy sidebar, 6 sections, Lucide icons, teal --app-primary),
App.tsx (Outlet pattern, UploadApp outside Layout), index.css (full :root block).
strict: false — archive components have type issues; build clean.

Note: 45 archive components call fetch('/api/...') without /kitchen/ prefix
(B5b). Runtime 404s; deferred until after initial testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-12 12:15:39 +00:00
commit 8d688b459d
10003 changed files with 1928395 additions and 0 deletions

View file

@ -0,0 +1,81 @@
"""
Purchase Order models for pre-allocating budget spend.
"""
from datetime import datetime, date
from decimal import Decimal
from typing import Optional
from sqlalchemy import String, DateTime, Date, ForeignKey, Numeric, Text, Integer, Index
from sqlalchemy.orm import Mapped, mapped_column, relationship
from database import Base
class PurchaseOrder(Base):
__tablename__ = "purchase_orders"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
kitchen_id: Mapped[int] = mapped_column(ForeignKey("kitchens.id"), nullable=False)
supplier_id: Mapped[int] = mapped_column(ForeignKey("suppliers.id"), nullable=False)
order_date: Mapped[date] = mapped_column(Date, nullable=False)
order_type: Mapped[str] = mapped_column(String(20), nullable=False) # 'itemised' or 'single_value'
status: Mapped[str] = mapped_column(String(20), nullable=False, default="DRAFT")
total_amount: Mapped[Optional[Decimal]] = mapped_column(Numeric(12, 2), nullable=True)
order_reference: Mapped[Optional[str]] = mapped_column(String(200), nullable=True)
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
attachment_path: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
attachment_original_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
linked_invoice_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("invoices.id", ondelete="SET NULL"), nullable=True
)
created_by: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_by: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
kitchen: Mapped["Kitchen"] = relationship("Kitchen")
supplier: Mapped["Supplier"] = relationship("Supplier", back_populates="purchase_orders")
linked_invoice: Mapped[Optional["Invoice"]] = relationship("Invoice", foreign_keys=[linked_invoice_id])
created_by_user: Mapped["User"] = relationship("User", foreign_keys=[created_by])
updated_by_user: Mapped[Optional["User"]] = relationship("User", foreign_keys=[updated_by])
line_items: Mapped[list["PurchaseOrderLineItem"]] = relationship(
"PurchaseOrderLineItem",
back_populates="purchase_order",
cascade="all, delete-orphan",
order_by="PurchaseOrderLineItem.line_number",
lazy="selectin",
)
__table_args__ = (
Index("idx_po_kitchen_date", "kitchen_id", "order_date"),
Index("idx_po_kitchen_supplier", "kitchen_id", "supplier_id"),
Index("idx_po_kitchen_status", "kitchen_id", "status"),
)
class PurchaseOrderLineItem(Base):
__tablename__ = "purchase_order_line_items"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
purchase_order_id: Mapped[int] = mapped_column(ForeignKey("purchase_orders.id", ondelete="CASCADE"), nullable=False)
kitchen_id: Mapped[int] = mapped_column(ForeignKey("kitchens.id"), nullable=False)
product_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
product_code: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
description: Mapped[str] = mapped_column(String(500), nullable=False)
unit: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
unit_price: Mapped[Decimal] = mapped_column(Numeric(12, 4), nullable=False)
quantity: Mapped[Decimal] = mapped_column(Numeric(10, 3), nullable=False)
total: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False)
line_number: Mapped[int] = mapped_column(Integer, default=0)
source: Mapped[str] = mapped_column(String(20), default="manual") # 'search' or 'manual'
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationships
purchase_order: Mapped["PurchaseOrder"] = relationship("PurchaseOrder", back_populates="line_items")
kitchen: Mapped["Kitchen"] = relationship("Kitchen")
# Forward references
from .user import Kitchen, User
from .supplier import Supplier
from .invoice import Invoice