Files
simple-pos-system/local_backend/main.py
bonamin d07c7634e6 Backend: table groups with prefix/color, auto-numbering, has_active_order flag
TableGroup gains prefix and color columns for display in the PWA zone filter.
Table creation now assigns a global auto-increment number; batch creation uses
group-local label numbering (avoids gaps/conflicts when adding to existing groups).
DELETE table now blocks if an active order exists (soft or hard delete).
Hard delete cascades past orders before removing the table row.
list_tables enriches each TableOut with has_active_order computed server-side.
TableOut no longer requires number in the input payload; TableCreate simplified.

Migration runner refactored to give each ALTER TABLE its own connection so a
no-op (column already exists) doesn't leave a dirty transaction blocking later
migrations. New migrations added for all new columns.

Order.print_logs relationship gains cascade="all, delete-orphan" so print logs
are removed when an order is deleted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 09:28:56 +03:00

80 lines
3.1 KiB
Python

import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from database import engine, Base
from middleware.license_check import LicenseCheckMiddleware
from services.cloud_sync import start_cloud_sync
# Import all models so SQLAlchemy can create their tables
import models.user # noqa: F401
import models.table # noqa: F401
import models.printer # noqa: F401
import models.product # noqa: F401
import models.order # noqa: F401
from routers import auth, tables, products, orders, waiters, reports, system
def _run_migrations():
"""Apply additive schema changes that create_all won't handle.
Each migration gets its own connection so a no-op (column already exists)
doesn't leave a dirty transaction that blocks subsequent migrations."""
from sqlalchemy import text
migrations = [
"ALTER TABLE product_ingredients ADD COLUMN extra_cost REAL NOT NULL DEFAULT 0.0",
"ALTER TABLE products ADD COLUMN image_url VARCHAR",
"ALTER TABLE tables ADD COLUMN group_id INTEGER REFERENCES table_groups(id)",
"ALTER TABLE table_groups ADD COLUMN prefix VARCHAR",
"ALTER TABLE table_groups ADD COLUMN color VARCHAR",
"ALTER TABLE products ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0",
"ALTER TABLE product_preference_sets ADD COLUMN default_choice_id INTEGER",
"ALTER TABLE product_preference_choices ADD COLUMN sub_choices TEXT",
"ALTER TABLE product_preference_choices ADD COLUMN disables_subset INTEGER NOT NULL DEFAULT 0",
"ALTER TABLE product_preference_sets ADD COLUMN shared_subset TEXT",
"ALTER TABLE product_options ADD COLUMN sub_choices TEXT",
]
for sql in migrations:
try:
with engine.connect() as conn:
conn.execute(text(sql))
conn.commit()
except Exception:
pass
@asynccontextmanager
async def lifespan(app: FastAPI):
Base.metadata.create_all(bind=engine)
_run_migrations()
sync_task = await start_cloud_sync()
yield
sync_task.cancel()
app = FastAPI(title="POS Local Backend", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(LicenseCheckMiddleware)
# Serve product images as static files
IMAGE_DIR = "/app/data/product_images"
os.makedirs(IMAGE_DIR, exist_ok=True)
app.mount("/static/product_images", StaticFiles(directory=IMAGE_DIR), name="product_images")
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(tables.router, prefix="/api/tables", tags=["tables"])
app.include_router(products.router, prefix="/api/products", tags=["products"])
app.include_router(orders.router, prefix="/api/orders", tags=["orders"])
app.include_router(waiters.router, prefix="/api/waiters", tags=["waiters"])
app.include_router(reports.router, prefix="/api/reports", tags=["reports"])
app.include_router(system.router, prefix="/api/system", tags=["system"])