feat(local_backend): reservations, print analytics, workday summary, zone-PIN management

- New reservations module: model, schema, router (CRUD + status updates + upcoming alerts)
  and background task for auto-expiring stale reservations
- Reports: print_products, print_categories, print_tables analytics endpoints
  plus meta_products and business_day_summary for workday close/view flow
- printer_service: configurable font sizes/weights, donut/bar chart print layout helpers,
  analytics print blocks per printer
- tables/schemas: surfaced color, zone, and other new fields on Table, Product, User, Printer
- demo_seed.py for quick dev DB population; wipe_database.py utility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 20:25:22 +03:00
parent aed71a18d8
commit 79bd3b0f41
25 changed files with 2017 additions and 146 deletions

View File

@@ -1,19 +1,24 @@
from fastapi import APIRouter, Depends, HTTPException, status, Body
from sqlalchemy.orm import Session
from typing import List
from datetime import datetime, timezone, timedelta
from database import get_db
from models.table import Table, TableGroup
from models.order import Order
from models.reservation import Reservation
from models.user import User, WaiterZone
from schemas.table import (
TableCreate, TableUpdate, TableFloorplanUpdate, TableOut,
TableCreate, TableUpdate, TableFloorplanUpdate, TableOut, UpcomingReservation,
TableGroupCreate, TableGroupUpdate, TableGroupOut,
TableBatchCreate, MAX_TABLE_NAME_LENGTH,
)
from routers.deps import get_current_user, require_manager
from services.sse_bus import broadcast_sync
# Tables with a pending reservation due within this many hours get the RESERVED badge
RESERVATION_LOOKAHEAD_HOURS = 4
router = APIRouter()
@@ -98,10 +103,32 @@ def list_tables(include_inactive: bool = False, db: Session = Depends(get_db), u
).all()
}
# Fetch pending reservations due within the lookahead window for the RESERVED badge
now = datetime.now(timezone.utc)
lookahead = now + timedelta(hours=RESERVATION_LOOKAHEAD_HOURS)
upcoming_reservations = (
db.query(Reservation)
.filter(
Reservation.status == "pending",
Reservation.table_id.isnot(None),
Reservation.reserved_for >= now,
Reservation.reserved_for <= lookahead,
)
.all()
)
# Map table_id → soonest upcoming reservation
reservation_by_table: dict[int, Reservation] = {}
for res in sorted(upcoming_reservations, key=lambda r: r.reserved_for):
if res.table_id not in reservation_by_table:
reservation_by_table[res.table_id] = res
result = []
for t in tables:
out = TableOut.model_validate(t)
out.has_active_order = t.id in active_table_ids
res = reservation_by_table.get(t.id)
if res:
out.upcoming_reservation = UpcomingReservation.model_validate(res)
result.append(out)
return result