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

@@ -67,16 +67,21 @@ def _audit(db: Session, order_id: int, event_type: str, waiter_id: int = None,
))
ACTIVE_STATUSES = ["open", "partially_paid", "paid"]
@router.get("/", response_model=List[OrderOut])
def list_orders(
order_status: Optional[str] = None,
waiter_id: Optional[int] = None,
all: bool = False,
db: Session = Depends(get_db),
user: User = Depends(require_manager),
):
q = db.query(Order)
if order_status:
q = q.filter(Order.status == order_status)
elif not all:
q = q.filter(Order.status.in_(ACTIVE_STATUSES))
if waiter_id:
q = q.join(OrderWaiter).filter(OrderWaiter.waiter_id == waiter_id)
return q.all()
@@ -183,9 +188,13 @@ def get_order(order_id: int, db: Session = Depends(get_db), user: User = Depends
"is_duplicate": l.is_duplicate,
}
table = db.query(Table).filter(Table.id == order.table_id).first() if order.table_id else None
table_name = (table.label or f"T{table.number}") if table else None
return {
"id": order.id,
"table_id": order.table_id,
"table_name": table_name,
"opened_by": order.opened_by,
"opened_at": order.opened_at,
"status": order.status,
@@ -347,12 +356,13 @@ def pay_items(order_id: int, body: PayItemsRequest, db: Session = Depends(get_db
WaiterShift.waiter_id == user.id,
WaiterShift.ended_at == None,
).first()
effective_method = body.payment_method or "cash"
total_paid = 0.0
for item in items:
item.status = "paid"
item.paid_by = user.id
item.paid_at = now
item.payment_method = body.payment_method
item.payment_method = effective_method
item.paid_in_shift_id = active_shift.id if active_shift else None
total_paid += item.unit_price * item.quantity
@@ -452,6 +462,7 @@ def pay_items_offline(
WaiterShift.ended_at == None,
).first()
effective_method = body.payment_method or "cash"
total_paid = 0.0
paid_ids = []
if not is_duplicate:
@@ -459,7 +470,7 @@ def pay_items_offline(
item.status = "paid"
item.paid_by = user.id
item.paid_at = paid_at
item.payment_method = body.payment_method
item.payment_method = effective_method
item.paid_in_shift_id = active_shift.id if active_shift else None
total_paid += item.unit_price * item.quantity
paid_ids.append(item.id)
@@ -605,7 +616,7 @@ def print_order(
"notes": order.notes,
}
background_tasks.add_task(print_order_receipt, printer.ip_address, printer.port, receipt)
background_tasks.add_task(print_order_receipt, printer.ip_address, printer.port, receipt, printer.line_width)
return {"status": "printing"}
@@ -900,5 +911,5 @@ def print_synopsis(
"remaining": total - paid_total,
}
background_tasks.add_task(print_order_synopsis, printer.ip_address, printer.port, synopsis)
background_tasks.add_task(print_order_synopsis, printer.ip_address, printer.port, synopsis, printer.line_width)
return {"status": "printing"}