diff --git a/backend/api/kds_settings.py b/backend/api/kds_settings.py new file mode 100644 index 0000000..5c8e244 --- /dev/null +++ b/backend/api/kds_settings.py @@ -0,0 +1,137 @@ +""" +KDS settings endpoints served by the kitchen backend. + +The kitchen Settings page configures KDS (timers, GraphQL URL, course order, etc.) +via /kitchen/api/kds/settings. All KDS config lives in kitchen_settings — so these +endpoints belong here, not in the KDS backend. +""" +from datetime import datetime +from typing import Optional + +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from auth import get_current_user, require_cap +from database import get_db +from models.settings import KitchenSettings +from models.user import User + +router = APIRouter() + + +class KDSSettingsResponse(BaseModel): + kds_enabled: bool = False + kds_graphql_url: Optional[str] = None + kds_graphql_username: Optional[str] = None + kds_graphql_password_set: bool = False + kds_graphql_client_id: Optional[str] = None + kds_poll_interval_seconds: int = 6000 + kds_timer_green_seconds: int = 300 + kds_timer_amber_seconds: int = 600 + kds_timer_red_seconds: int = 900 + kds_away_timer_green_seconds: int = 600 + kds_away_timer_amber_seconds: int = 900 + kds_away_timer_red_seconds: int = 1200 + kds_course_order: list = ["Starters", "Mains", "Desserts"] + kds_show_completed_for_seconds: int = 30 + kds_bookings_refresh_seconds: int = 60 + + class Config: + from_attributes = True + + +class KDSSettingsUpdate(BaseModel): + kds_enabled: Optional[bool] = None + kds_graphql_url: Optional[str] = None + kds_graphql_username: Optional[str] = None + kds_graphql_password: Optional[str] = None + kds_graphql_client_id: Optional[str] = None + kds_poll_interval_seconds: Optional[int] = None + kds_timer_green_seconds: Optional[int] = None + kds_timer_amber_seconds: Optional[int] = None + kds_timer_red_seconds: Optional[int] = None + kds_away_timer_green_seconds: Optional[int] = None + kds_away_timer_amber_seconds: Optional[int] = None + kds_away_timer_red_seconds: Optional[int] = None + kds_course_order: Optional[list] = None + kds_show_completed_for_seconds: Optional[int] = None + kds_bookings_refresh_seconds: Optional[int] = None + + +def _normalize_course_config(course_order: list, s: KitchenSettings) -> list: + result = [] + for entry in course_order: + if isinstance(entry, str): + result.append({ + "name": entry, + "prep_green": s.kds_timer_green_seconds or 300, + "prep_amber": s.kds_timer_amber_seconds or 600, + "prep_red": s.kds_timer_red_seconds or 900, + "away_green": s.kds_away_timer_green_seconds or 600, + "away_amber": s.kds_away_timer_amber_seconds or 900, + "away_red": s.kds_away_timer_red_seconds or 1200, + }) + elif isinstance(entry, dict) and "name" in entry: + result.append(entry) + return result + + +async def _get_settings(db: AsyncSession, kitchen_id: int): + result = await db.execute( + select(KitchenSettings).where(KitchenSettings.kitchen_id == kitchen_id) + ) + return result.scalar_one_or_none() + + +def _to_response(s: KitchenSettings) -> KDSSettingsResponse: + raw = s.kds_course_order or ["Starters", "Mains", "Desserts"] + return KDSSettingsResponse( + kds_enabled=s.kds_enabled or False, + kds_graphql_url=s.kds_graphql_url, + kds_graphql_username=s.kds_graphql_username, + kds_graphql_password_set=bool(s.kds_graphql_password), + kds_graphql_client_id=s.kds_graphql_client_id, + kds_poll_interval_seconds=s.kds_poll_interval_seconds or 6000, + kds_timer_green_seconds=s.kds_timer_green_seconds or 300, + kds_timer_amber_seconds=s.kds_timer_amber_seconds or 600, + kds_timer_red_seconds=s.kds_timer_red_seconds or 900, + kds_away_timer_green_seconds=s.kds_away_timer_green_seconds or 600, + kds_away_timer_amber_seconds=s.kds_away_timer_amber_seconds or 900, + kds_away_timer_red_seconds=s.kds_away_timer_red_seconds or 1200, + kds_course_order=_normalize_course_config(raw, s), + kds_show_completed_for_seconds=s.kds_show_completed_for_seconds or 30, + kds_bookings_refresh_seconds=s.kds_bookings_refresh_seconds or 60, + ) + + +@router.get("/settings", response_model=KDSSettingsResponse) +async def get_kds_settings( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + s = await _get_settings(db, current_user.kitchen_id) + if not s: + return KDSSettingsResponse() + return _to_response(s) + + +@router.patch("/settings", response_model=KDSSettingsResponse) +async def update_kds_settings( + update: KDSSettingsUpdate, + current_user: User = Depends(require_cap("settings")), + db: AsyncSession = Depends(get_db), +): + s = await _get_settings(db, current_user.kitchen_id) + if not s: + raise HTTPException(status_code=404, detail="Kitchen settings not found") + + for field, value in update.model_dump(exclude_unset=True).items(): + if hasattr(s, field): + setattr(s, field, value) + + s.updated_at = datetime.utcnow() + await db.commit() + await db.refresh(s) + return _to_response(s) diff --git a/backend/main.py b/backend/main.py index f682081..c071c8e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -16,7 +16,7 @@ from api import ( 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, + event_orders, external, menus, reconciliation, kds_settings, ) from api.internal import router as internal_router @@ -191,6 +191,7 @@ app.include_router(food_flags.router, prefix="/api/food-flags", tags=["Food Flag 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(kds_settings.router, prefix="/api/kds", tags=["KDS Settings"]) app.include_router(internal_router) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index fa39631..9e2e0cf 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -23,9 +23,9 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Cookie $http_cookie; - proxy_read_timeout 300s; + proxy_read_timeout 600s; proxy_connect_timeout 30s; - client_max_body_size 20m; + client_max_body_size 800m; } # Health