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

195
backend/main.py Normal file
View file

@ -0,0 +1,195 @@
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from database import engine, Base
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
from api import (
invoices, suppliers, reports, settings, field_mappings, newbook, sambapos,
backup, search, resos, calendar_events, residents_table_chart, disputes,
credit_notes, public, logbook, imap, support, budget, cover_overrides,
purchase_orders, cost_distributions, ingredients, recipes, food_flags,
event_orders, external, menus, reconciliation,
)
from api.internal import router as internal_router
from migrations.add_invoice_features import run_migration
from migrations.add_newbook_tables import run_migration as run_newbook_migration
from migrations.add_line_item_search import run_migration as run_search_migration
from migrations.add_sambapos_settings import run_migration as run_sambapos_migration
from migrations.add_sambapos_excluded_items import run_migration as run_sambapos_excluded_migration
from migrations.add_admin_restricted_pages import run_migration as run_admin_pages_migration
from migrations.add_nextcloud_backup import run_migration as run_nextcloud_backup_migration
from migrations.add_price_settings import run_migration as run_price_settings_migration
from migrations.add_resos_integration import run_migration as run_resos_migration
from migrations.add_calendar_events import run_migration as run_calendar_events_migration
from migrations.add_resos_upcoming_sync import run_migration as run_resos_upcoming_sync_migration
from migrations.add_newbook_upcoming_sync import run_migration as run_newbook_upcoming_sync_migration
from migrations.add_residents_table_chart import run_migration as run_residents_table_chart_migration
from migrations.add_rooms_breakdown import run_migration as run_rooms_breakdown_migration
from migrations.add_invoice_disputes import run_migration as run_disputes_migration
from migrations.add_awaiting_replacement_status import run_migration as run_awaiting_replacement_migration
from migrations.add_new_status import run_migration as run_new_status_migration
from migrations.add_dispute_attachment_public_hash import run_migration as run_dispute_attachment_hash_migration
from migrations.add_logbook import run_migration as run_logbook_migration
from migrations.add_imap_integration import run_migration as run_imap_migration
from migrations.add_support_request import run_migration as run_support_migration
from migrations.add_pdf_annotation_settings import run_migration as run_pdf_annotation_settings_migration
from migrations.add_linked_dispute import run_migration as run_linked_dispute_migration
from migrations.add_ocr_post_processing import run_migration as run_ocr_post_processing_migration
from migrations.add_ocr_weight_setting import run_migration as run_ocr_weight_setting_migration
from migrations.add_budget_settings import migrate as run_budget_settings_migration
from migrations.add_cover_overrides import migrate as run_cover_overrides_migration
from migrations.add_purchase_orders import migrate as run_purchase_orders_migration
from migrations.add_supplier_po_fields import migrate as run_supplier_po_fields_migration
from migrations.add_kitchen_details import migrate as run_kitchen_details_migration
from migrations.add_cost_distributions import migrate as run_cost_distributions_migration
from migrations.add_recipe_system import migrate as run_recipe_system_migration
from migrations.add_flag_assessment import migrate as run_flag_assessment_migration
from migrations.add_dish_separation import migrate as run_dish_separation_migration
from migrations.add_step_title import migrate as run_step_title_migration
from migrations.add_gross_sell_price import migrate as run_gross_sell_price_migration
from migrations.add_recipe_ingredient_yield import migrate as run_recipe_ingredient_yield_migration
from migrations.add_recipe_batch_type import migrate as run_recipe_batch_type_migration
from migrations.add_sub_recipe_unit import migrate as run_sub_recipe_unit_migration
from migrations.add_recipe_ingredient_unit import migrate as run_recipe_ingredient_unit_migration
from migrations.add_allergen_keywords import migrate as run_allergen_keywords_migration
from migrations.add_brakes_cache import migrate as run_brakes_cache_migration
from migrations.add_description_aliases import migrate as run_description_aliases_migration
from migrations.add_brakes_dietary_info import migrate as run_brakes_dietary_info_migration
from migrations.add_ingredient_flag_dismissals import migrate as run_ingredient_flag_dismissals_migration
from migrations.add_menus import migrate as run_menus_migration
from migrations.add_ingredient_is_free import migrate as run_ingredient_is_free_migration
from migrations.add_recipe_text_flag_dismissals import migrate as run_recipe_text_flag_dismissals_migration
from migrations.add_llm_infrastructure import migrate as run_llm_infrastructure_migration
from migrations.add_changelog_invoice_link import migrate as run_changelog_invoice_link_migration
from migrations.add_sambapos_portion_name import migrate as run_sambapos_portion_name_migration
from scheduler import start_scheduler, stop_scheduler
logger = logging.getLogger(__name__)
async def _run(name, coro):
try:
await coro()
logger.info(f"{name} migration completed")
except Exception as e:
logger.warning(f"{name} migration warning (may be expected): {e}")
@asynccontextmanager
async def lifespan(app: FastAPI):
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
await _run("Invoice features", run_migration)
await _run("Newbook tables", run_newbook_migration)
await _run("Line item search", run_search_migration)
await _run("SambaPOS settings", run_sambapos_migration)
await _run("SambaPOS excluded items", run_sambapos_excluded_migration)
await _run("Admin restricted pages", run_admin_pages_migration)
await _run("Nextcloud backup", run_nextcloud_backup_migration)
await _run("Price settings", run_price_settings_migration)
await _run("Resos integration", run_resos_migration)
await _run("Calendar events", run_calendar_events_migration)
await _run("Resos upcoming sync", run_resos_upcoming_sync_migration)
await _run("Newbook upcoming sync", run_newbook_upcoming_sync_migration)
await _run("Residents table chart", run_residents_table_chart_migration)
await _run("Rooms breakdown", run_rooms_breakdown_migration)
await _run("Invoice disputes", run_disputes_migration)
await _run("Awaiting replacement status", run_awaiting_replacement_migration)
await _run("NEW status", run_new_status_migration)
await _run("Dispute attachment public hash", run_dispute_attachment_hash_migration)
await _run("Logbook", run_logbook_migration)
await _run("IMAP integration", run_imap_migration)
await _run("Support request", run_support_migration)
await _run("PDF annotation settings", run_pdf_annotation_settings_migration)
await _run("Linked dispute", run_linked_dispute_migration)
await _run("OCR post-processing", run_ocr_post_processing_migration)
await _run("OCR weight setting", run_ocr_weight_setting_migration)
await _run("Budget settings", run_budget_settings_migration)
await _run("Cover overrides", run_cover_overrides_migration)
await _run("Purchase orders", run_purchase_orders_migration)
await _run("Supplier PO fields", run_supplier_po_fields_migration)
await _run("Kitchen details", run_kitchen_details_migration)
await _run("Cost distributions", run_cost_distributions_migration)
await _run("Recipe system", run_recipe_system_migration)
await _run("Flag assessment", run_flag_assessment_migration)
await _run("Dish separation", run_dish_separation_migration)
await _run("Step title", run_step_title_migration)
await _run("Gross sell price", run_gross_sell_price_migration)
await _run("Recipe ingredient yield", run_recipe_ingredient_yield_migration)
await _run("Recipe batch type", run_recipe_batch_type_migration)
await _run("Sub-recipe unit", run_sub_recipe_unit_migration)
await _run("Recipe ingredient unit", run_recipe_ingredient_unit_migration)
await _run("Allergen keywords", run_allergen_keywords_migration)
await _run("Brakes product cache", run_brakes_cache_migration)
await _run("Description aliases", run_description_aliases_migration)
await _run("Brakes dietary info", run_brakes_dietary_info_migration)
await _run("Ingredient flag dismissals", run_ingredient_flag_dismissals_migration)
await _run("Menus", run_menus_migration)
await _run("Ingredient is_free", run_ingredient_is_free_migration)
await _run("Recipe text flag dismissals", run_recipe_text_flag_dismissals_migration)
await _run("LLM infrastructure", run_llm_infrastructure_migration)
await _run("Changelog invoice link", run_changelog_invoice_link_migration)
await _run("SambaPOS portion name", run_sambapos_portion_name_migration)
start_scheduler()
yield
stop_scheduler()
await engine.dispose()
app = FastAPI(
title="Kitchen App",
description="Invoice/GP, recipes, menus — Hotel Number Four kitchen management",
version="1.0.0",
lifespan=lifespan,
)
# No CORS middleware — same-origin via nginx (A1)
app.include_router(invoices.router, prefix="/api/invoices", tags=["Invoices"])
app.include_router(disputes.router, prefix="/api/disputes", tags=["Disputes"])
app.include_router(credit_notes.router, prefix="/api/credit-notes", tags=["Credit Notes"])
app.include_router(suppliers.router, prefix="/api/suppliers", tags=["Suppliers"])
app.include_router(reports.router, prefix="/api/reports", tags=["Reports"])
app.include_router(reconciliation.router, prefix="/api/reports", tags=["Reconciliation"])
app.include_router(settings.router, prefix="/api/settings", tags=["Settings"])
app.include_router(field_mappings.router, prefix="/api/field-mappings", tags=["Field Mappings"])
app.include_router(newbook.router, prefix="/api/newbook", tags=["Newbook"])
app.include_router(sambapos.router, prefix="/api/sambapos", tags=["SambaPOS"])
app.include_router(backup.router, prefix="/api/backup", tags=["Backup"])
app.include_router(search.router, tags=["Search"])
app.include_router(resos.router, prefix="/api/resos", tags=["Resos"])
app.include_router(calendar_events.router, prefix="/api/calendar-events", tags=["Calendar Events"])
app.include_router(residents_table_chart.router, prefix="/api", tags=["Residents Table Chart"])
app.include_router(public.router, prefix="/api/public", tags=["Public"])
app.include_router(logbook.router, prefix="/api", tags=["Logbook"])
app.include_router(imap.router, prefix="/api", tags=["IMAP"])
app.include_router(support.router, prefix="/api", tags=["Support"])
app.include_router(budget.router, prefix="/api/budget", tags=["Budget"])
app.include_router(cover_overrides.router, prefix="/api/cover-overrides", tags=["Cover Overrides"])
app.include_router(purchase_orders.router, prefix="/api/purchase-orders", tags=["Purchase Orders"])
app.include_router(cost_distributions.router, prefix="/api/cost-distributions", tags=["Cost Distributions"])
app.include_router(ingredients.router, prefix="/api/ingredients", tags=["Ingredients"])
app.include_router(recipes.router, prefix="/api/recipes", tags=["Recipes"])
app.include_router(food_flags.router, prefix="/api/food-flags", tags=["Food Flags"])
app.include_router(event_orders.router, prefix="/api/event-orders", tags=["Event Orders"])
app.include_router(menus.router, prefix="/api/menus", tags=["Menus"])
app.include_router(external.router, prefix="/api/external", tags=["External API"])
app.include_router(internal_router)
@app.get("/health")
async def health_check():
return {"status": "healthy"}