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>
690 lines
23 KiB
Python
690 lines
23 KiB
Python
"""
|
|
SambaPOS API Endpoints
|
|
|
|
Handles SambaPOS MSSQL configuration and category management.
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from pydantic import BaseModel
|
|
|
|
from database import get_db
|
|
from models.user import User
|
|
from models.settings import KitchenSettings
|
|
from auth import get_current_user, require_cap
|
|
from services.sambapos_api import SambaPOSClient
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ============ Pydantic Schemas ============
|
|
|
|
class SambaPOSSettingsResponse(BaseModel):
|
|
sambapos_db_host: str | None
|
|
sambapos_db_port: int | None
|
|
sambapos_db_name: str | None
|
|
sambapos_db_username: str | None
|
|
sambapos_db_password_set: bool
|
|
sambapos_tracked_categories: list[str]
|
|
sambapos_excluded_items: list[str]
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class SambaPOSSettingsUpdate(BaseModel):
|
|
sambapos_db_host: str | None = None
|
|
sambapos_db_port: int | None = None
|
|
sambapos_db_name: str | None = None
|
|
sambapos_db_username: str | None = None
|
|
sambapos_db_password: str | None = None
|
|
|
|
|
|
class CategoryResponse(BaseModel):
|
|
id: int
|
|
name: str
|
|
|
|
|
|
class MenuItemResponse(BaseModel):
|
|
name: str
|
|
category: str
|
|
|
|
|
|
class TrackedCategoriesUpdate(BaseModel):
|
|
categories: list[str]
|
|
|
|
|
|
class ExcludedItemsUpdate(BaseModel):
|
|
items: list[str]
|
|
|
|
|
|
# ============ Settings Endpoints ============
|
|
|
|
@router.get("/settings", response_model=SambaPOSSettingsResponse)
|
|
async def get_sambapos_settings(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Get SambaPOS connection settings"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Parse tracked categories from comma-separated string
|
|
tracked_categories = []
|
|
if settings.sambapos_tracked_categories:
|
|
tracked_categories = [c.strip() for c in settings.sambapos_tracked_categories.split(',') if c.strip()]
|
|
|
|
# Parse excluded items from comma-separated string
|
|
excluded_items = []
|
|
if settings.sambapos_excluded_items:
|
|
excluded_items = [i.strip() for i in settings.sambapos_excluded_items.split('|') if i.strip()]
|
|
|
|
return SambaPOSSettingsResponse(
|
|
sambapos_db_host=settings.sambapos_db_host,
|
|
sambapos_db_port=settings.sambapos_db_port,
|
|
sambapos_db_name=settings.sambapos_db_name,
|
|
sambapos_db_username=settings.sambapos_db_username,
|
|
sambapos_db_password_set=bool(settings.sambapos_db_password),
|
|
sambapos_tracked_categories=tracked_categories,
|
|
sambapos_excluded_items=excluded_items
|
|
)
|
|
|
|
|
|
@router.patch("/settings", response_model=SambaPOSSettingsResponse)
|
|
async def update_sambapos_settings(
|
|
update: SambaPOSSettingsUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Update SambaPOS connection settings"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Update fields
|
|
update_data = update.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
if value is not None:
|
|
setattr(settings, field, value)
|
|
|
|
await db.commit()
|
|
await db.refresh(settings)
|
|
|
|
# Parse tracked categories from comma-separated string
|
|
tracked_categories = []
|
|
if settings.sambapos_tracked_categories:
|
|
tracked_categories = [c.strip() for c in settings.sambapos_tracked_categories.split(',') if c.strip()]
|
|
|
|
# Parse excluded items from pipe-separated string
|
|
excluded_items = []
|
|
if settings.sambapos_excluded_items:
|
|
excluded_items = [i.strip() for i in settings.sambapos_excluded_items.split('|') if i.strip()]
|
|
|
|
return SambaPOSSettingsResponse(
|
|
sambapos_db_host=settings.sambapos_db_host,
|
|
sambapos_db_port=settings.sambapos_db_port,
|
|
sambapos_db_name=settings.sambapos_db_name,
|
|
sambapos_db_username=settings.sambapos_db_username,
|
|
sambapos_db_password_set=bool(settings.sambapos_db_password),
|
|
sambapos_tracked_categories=tracked_categories,
|
|
sambapos_excluded_items=excluded_items
|
|
)
|
|
|
|
|
|
@router.post("/test-connection")
|
|
async def test_sambapos_connection(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Test SambaPOS database connection"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not fully configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
result = await client.test_connection()
|
|
|
|
if result["success"]:
|
|
return {"status": "success", "message": "SambaPOS connection successful"}
|
|
else:
|
|
raise HTTPException(status_code=400, detail=f"Connection failed: {result['message']}")
|
|
|
|
|
|
# ============ Categories Endpoints ============
|
|
|
|
@router.get("/categories", response_model=list[CategoryResponse])
|
|
async def get_sambapos_categories(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Fetch all menu categories from SambaPOS database"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
categories = await client.get_categories()
|
|
return [CategoryResponse(id=cat["id"], name=cat["name"]) for cat in categories]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch categories: {str(e)}")
|
|
|
|
|
|
@router.get("/tracked-categories")
|
|
async def get_tracked_categories(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Get list of category names enabled for top sellers"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Parse tracked categories from comma-separated string
|
|
tracked_categories = []
|
|
if settings.sambapos_tracked_categories:
|
|
tracked_categories = [c.strip() for c in settings.sambapos_tracked_categories.split(',') if c.strip()]
|
|
|
|
return {"categories": tracked_categories}
|
|
|
|
|
|
@router.patch("/tracked-categories")
|
|
async def update_tracked_categories(
|
|
update: TrackedCategoriesUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Update which categories are included in top sellers"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Store as comma-separated string
|
|
settings.sambapos_tracked_categories = ','.join(update.categories)
|
|
|
|
await db.commit()
|
|
|
|
return {"status": "success", "categories": update.categories}
|
|
|
|
|
|
@router.get("/debug/menuitems")
|
|
async def debug_menuitems(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Debug endpoint to explore MenuItems table structure"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings or not settings.sambapos_db_password:
|
|
raise HTTPException(status_code=400, detail="SambaPOS not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
return await client.debug_menu_items()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
# ============ Menu Items Endpoints ============
|
|
|
|
@router.get("/menu-items", response_model=list[MenuItemResponse])
|
|
async def get_menu_items(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Fetch all unique menu item names with their categories for exclusion selection"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
# Get tracked categories to filter menu items
|
|
tracked_categories = []
|
|
if settings.sambapos_tracked_categories:
|
|
tracked_categories = [c.strip() for c in settings.sambapos_tracked_categories.split(',') if c.strip()]
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
items = await client.get_menu_item_names(categories=tracked_categories if tracked_categories else None)
|
|
return [MenuItemResponse(name=item["name"], category=item["category"]) for item in items]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch menu items: {str(e)}")
|
|
|
|
|
|
class MenuItemWithPortionResponse(BaseModel):
|
|
menu_item_name: str
|
|
portion_name: str
|
|
category: str
|
|
on_pos_menu: bool
|
|
|
|
|
|
@router.get("/menu-items-with-portions", response_model=list[MenuItemWithPortionResponse])
|
|
async def get_menu_items_with_portions(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Fetch all menu items with their portion names, grouped by Kitchen Course category."""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
# Get tracked categories to filter
|
|
tracked_categories = []
|
|
if settings.sambapos_tracked_categories:
|
|
tracked_categories = [c.strip() for c in settings.sambapos_tracked_categories.split(',') if c.strip()]
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
items = await client.get_menu_items_with_portions(
|
|
categories=tracked_categories if tracked_categories else None
|
|
)
|
|
return [
|
|
MenuItemWithPortionResponse(
|
|
menu_item_name=item["menu_item_name"],
|
|
portion_name=item["portion_name"],
|
|
category=item["category"],
|
|
on_pos_menu=item.get("on_pos_menu", False)
|
|
)
|
|
for item in items
|
|
]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch menu items with portions: {str(e)}")
|
|
|
|
|
|
@router.get("/excluded-items")
|
|
async def get_excluded_items(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Get list of menu item names excluded from top sellers report"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Parse excluded items from pipe-separated string
|
|
excluded_items = []
|
|
if settings.sambapos_excluded_items:
|
|
excluded_items = [i.strip() for i in settings.sambapos_excluded_items.split('|') if i.strip()]
|
|
|
|
return {"items": excluded_items}
|
|
|
|
|
|
@router.patch("/excluded-items")
|
|
async def update_excluded_items(
|
|
update: ExcludedItemsUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Update which menu item GroupCodes are excluded from top sellers report"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Store as pipe-separated string (to allow commas in names)
|
|
settings.sambapos_excluded_items = '|'.join(update.items)
|
|
|
|
await db.commit()
|
|
|
|
return {"status": "success", "items": update.items}
|
|
|
|
|
|
# ============ Group Codes Endpoints ============
|
|
|
|
class GroupCodeResponse(BaseModel):
|
|
name: str
|
|
|
|
|
|
@router.get("/group-codes", response_model=list[GroupCodeResponse])
|
|
async def get_group_codes(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Fetch all distinct GroupCode values from MenuItems table for exclusion selection"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
group_codes = await client.get_menu_group_codes()
|
|
return [GroupCodeResponse(name=gc["name"]) for gc in group_codes]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch group codes: {str(e)}")
|
|
|
|
|
|
# ============ GL Codes Endpoints (Phase 8) ============
|
|
|
|
class GLCodeResponse(BaseModel):
|
|
code: str
|
|
|
|
|
|
class GLCodesUpdate(BaseModel):
|
|
food_codes: list[str]
|
|
beverage_codes: list[str]
|
|
|
|
|
|
@router.get("/gl-codes", response_model=list[GLCodeResponse])
|
|
async def get_gl_codes(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Fetch all unique GL codes from ProductTag custom tags for food/beverage classification"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
gl_codes = await client.get_gl_codes()
|
|
return [GLCodeResponse(code=gc["code"]) for gc in gl_codes]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch GL codes: {str(e)}")
|
|
|
|
|
|
@router.get("/gl-codes/selected")
|
|
async def get_selected_gl_codes(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Get currently selected food and beverage GL codes"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Parse food GL codes from comma-separated string
|
|
food_codes = []
|
|
if settings.sambapos_food_gl_codes:
|
|
food_codes = [c.strip() for c in settings.sambapos_food_gl_codes.split(',') if c.strip()]
|
|
|
|
# Parse beverage GL codes from comma-separated string
|
|
beverage_codes = []
|
|
if settings.sambapos_beverage_gl_codes:
|
|
beverage_codes = [c.strip() for c in settings.sambapos_beverage_gl_codes.split(',') if c.strip()]
|
|
|
|
return {
|
|
"food_codes": food_codes,
|
|
"beverage_codes": beverage_codes
|
|
}
|
|
|
|
|
|
@router.patch("/gl-codes")
|
|
async def update_gl_codes(
|
|
update: GLCodesUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Update food and beverage GL code selections"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
# Store as comma-separated strings
|
|
settings.sambapos_food_gl_codes = ','.join(update.food_codes)
|
|
settings.sambapos_beverage_gl_codes = ','.join(update.beverage_codes)
|
|
|
|
await db.commit()
|
|
|
|
return {
|
|
"status": "success",
|
|
"food_codes": update.food_codes,
|
|
"beverage_codes": update.beverage_codes
|
|
}
|
|
|
|
|
|
@router.get("/debug/custom-tags")
|
|
async def debug_custom_tags(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Debug endpoint to see sample CustomTags from MenuItems"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
return await client.debug_menu_items()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to debug custom tags: {str(e)}")
|
|
|
|
|
|
@router.get("/debug/zero-price-order-states")
|
|
async def debug_zero_price_order_states(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Debug endpoint: returns sample OrderStates blobs for zero-priced orders.
|
|
Use this to discover the JSON structure and identify where original/package price is stored.
|
|
"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings or not settings.sambapos_db_password:
|
|
raise HTTPException(status_code=400, detail="SambaPOS not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
return await client.debug_zero_price_order_states()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.get("/debug/table-schema")
|
|
async def debug_table_schema(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Debug endpoint to inspect column names in SambaPOS tables"""
|
|
result = await db.execute(
|
|
select(KitchenSettings).where(KitchenSettings.kitchen_id == current_user.kitchen_id)
|
|
)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if not settings:
|
|
raise HTTPException(status_code=404, detail="Settings not found")
|
|
|
|
if not all([
|
|
settings.sambapos_db_host,
|
|
settings.sambapos_db_name,
|
|
settings.sambapos_db_username,
|
|
settings.sambapos_db_password
|
|
]):
|
|
raise HTTPException(status_code=400, detail="SambaPOS database credentials not configured")
|
|
|
|
client = SambaPOSClient(
|
|
host=settings.sambapos_db_host,
|
|
port=settings.sambapos_db_port or 1433,
|
|
database=settings.sambapos_db_name,
|
|
username=settings.sambapos_db_username,
|
|
password=settings.sambapos_db_password
|
|
)
|
|
|
|
try:
|
|
return await client.debug_table_schema()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to debug schema: {str(e)}")
|