Fix order saving, isMyOrder, blocked waiters, options pricing; add preferences, table groups, product images
Backend:
- OrderItemInput accepts option objects {id,name,price_delta} instead of int IDs
- extra_cost from selected options added to unit_price snapshot
- GET /api/products/?all=true for manager (includes unavailable)
- PUT /api/products/{id} now replaces options, ingredients, preference_sets
- POST /api/products/{id}/image — persistent image upload to /app/data/product_images
- New models: ProductPreferenceSet, ProductPreferenceChoice, TableGroup
- tables: group_id FK, hard delete (?hard=true), batch create POST /api/tables/batch
- GET /api/tables/groups + POST/PUT/DELETE groups endpoints
- POST /api/auth/me endpoint for token rehydration
- Auto-migration on startup for new columns
PWA:
- AuthRehydrator: fetches /auth/me on load so isMyOrder works after page reload
- 401 response force-logs out (covers blocked waiters)
- ItemOptionsModal: uses extra_cost correctly, shows preferences as radio buttons
Manager:
- ProductsPage: shows unavailable products greyed out, category color picker + reorder,
full option/ingredient/preference editing, image upload
- TablesPage: table groups, auto-increment, deactivate vs hard delete, batch add
This commit is contained in:
@@ -3,24 +3,72 @@ from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from database import get_db
|
||||
from models.table import Table
|
||||
from models.table import Table, TableGroup
|
||||
from models.order import Order
|
||||
from models.user import User
|
||||
from schemas.table import TableCreate, TableUpdate, TableFloorplanUpdate, TableOut
|
||||
from schemas.table import (
|
||||
TableCreate, TableUpdate, TableFloorplanUpdate, TableOut,
|
||||
TableGroupCreate, TableGroupUpdate, TableGroupOut,
|
||||
TableBatchCreate,
|
||||
)
|
||||
from routers.deps import get_current_user, require_manager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ── Table Groups ──────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/groups", response_model=List[TableGroupOut])
|
||||
def list_groups(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
return db.query(TableGroup).order_by(TableGroup.sort_order).all()
|
||||
|
||||
|
||||
@router.post("/groups", response_model=TableGroupOut, status_code=status.HTTP_201_CREATED)
|
||||
def create_group(body: TableGroupCreate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
if db.query(TableGroup).filter(TableGroup.name == body.name).first():
|
||||
raise HTTPException(status_code=400, detail="Group name already exists")
|
||||
sort_order = db.query(TableGroup).count()
|
||||
group = TableGroup(name=body.name, sort_order=sort_order)
|
||||
db.add(group)
|
||||
db.commit()
|
||||
db.refresh(group)
|
||||
return group
|
||||
|
||||
|
||||
@router.put("/groups/{group_id}", response_model=TableGroupOut)
|
||||
def update_group(group_id: int, body: TableGroupUpdate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
group = db.query(TableGroup).filter(TableGroup.id == group_id).first()
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="Group not found")
|
||||
for field, value in body.model_dump(exclude_none=True).items():
|
||||
setattr(group, field, value)
|
||||
db.commit()
|
||||
db.refresh(group)
|
||||
return group
|
||||
|
||||
|
||||
@router.delete("/groups/{group_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_group(group_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
group = db.query(TableGroup).filter(TableGroup.id == group_id).first()
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="Group not found")
|
||||
db.query(Table).filter(Table.group_id == group_id).update({"group_id": None})
|
||||
db.delete(group)
|
||||
db.commit()
|
||||
|
||||
|
||||
# ── Tables ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/", response_model=List[TableOut])
|
||||
def list_tables(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
return db.query(Table).filter(Table.is_active == True).all()
|
||||
def list_tables(include_inactive: bool = False, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
q = db.query(Table)
|
||||
if not include_inactive:
|
||||
q = q.filter(Table.is_active == True)
|
||||
return q.order_by(Table.group_id, Table.number).all()
|
||||
|
||||
|
||||
@router.post("/", response_model=TableOut, status_code=status.HTTP_201_CREATED)
|
||||
def create_table(body: TableCreate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
if db.query(Table).filter(Table.number == body.number).first():
|
||||
raise HTTPException(status_code=400, detail="Table number already exists")
|
||||
table = Table(**body.model_dump())
|
||||
db.add(table)
|
||||
db.commit()
|
||||
@@ -28,6 +76,28 @@ def create_table(body: TableCreate, db: Session = Depends(get_db), user: User =
|
||||
return table
|
||||
|
||||
|
||||
@router.post("/batch", response_model=List[TableOut], status_code=status.HTTP_201_CREATED)
|
||||
def batch_create_tables(body: TableBatchCreate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
if body.count < 1 or body.count > 200:
|
||||
raise HTTPException(status_code=400, detail="Count must be between 1 and 200")
|
||||
created = []
|
||||
for i in range(body.count):
|
||||
n = body.start_number + i
|
||||
table = Table(
|
||||
number=n,
|
||||
label=f"{body.name_prefix}{n}",
|
||||
group_id=body.group_id,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(table)
|
||||
db.flush()
|
||||
created.append(table)
|
||||
db.commit()
|
||||
for t in created:
|
||||
db.refresh(t)
|
||||
return created
|
||||
|
||||
|
||||
@router.put("/{table_id}", response_model=TableOut)
|
||||
def update_table(table_id: int, body: TableUpdate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
table = db.query(Table).filter(Table.id == table_id).first()
|
||||
@@ -41,11 +111,20 @@ def update_table(table_id: int, body: TableUpdate, db: Session = Depends(get_db)
|
||||
|
||||
|
||||
@router.delete("/{table_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def deactivate_table(table_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
def delete_table(table_id: int, hard: bool = False, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
table = db.query(Table).filter(Table.id == table_id).first()
|
||||
if not table:
|
||||
raise HTTPException(status_code=404, detail="Table not found")
|
||||
table.is_active = False
|
||||
if hard:
|
||||
active_order = db.query(Order).filter(
|
||||
Order.table_id == table_id,
|
||||
Order.status.in_(["open", "partially_paid"])
|
||||
).first()
|
||||
if active_order:
|
||||
raise HTTPException(status_code=400, detail="Cannot delete table with active order")
|
||||
db.delete(table)
|
||||
else:
|
||||
table.is_active = False
|
||||
db.commit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user