Add .gitignore, remove __pycache__ from tracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-12 12:16:04 +00:00
parent 8d688b459d
commit c8fad1cf36
181 changed files with 8 additions and 29855 deletions

View file

@ -1,690 +0,0 @@
"""
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)}")