217 lines
8.5 KiB
Python
217 lines
8.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from typing import List
|
|
|
|
from database import get_db
|
|
from models.table import Table, TableGroup
|
|
from models.order import Order
|
|
from models.user import User, WaiterZone
|
|
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, prefix=body.prefix, color=body.color, 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 ────────────────────────────────────────────────────────────────────
|
|
|
|
def _next_global_number(db: Session) -> int:
|
|
last = db.query(Table).order_by(Table.number.desc()).first()
|
|
return (last.number + 1) if last else 1
|
|
|
|
|
|
@router.get("/", response_model=List[TableOut])
|
|
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)
|
|
|
|
# Zone-based filtering for waiters
|
|
if user.role not in ("manager", "sysadmin"):
|
|
zones = db.query(WaiterZone).filter(WaiterZone.waiter_id == user.id).all()
|
|
# No zone rows → sees nothing
|
|
if not zones:
|
|
return []
|
|
# Any row with group_id=None → sees all tables (all-zones sentinel)
|
|
has_all_zones = any(z.group_id is None for z in zones)
|
|
if not has_all_zones:
|
|
allowed_group_ids = [z.group_id for z in zones]
|
|
q = q.filter(Table.group_id.in_(allowed_group_ids))
|
|
|
|
tables = q.order_by(Table.group_id, Table.number).all()
|
|
|
|
active_table_ids = {
|
|
row[0] for row in db.query(Order.table_id).filter(
|
|
Order.status.in_(["open", "partially_paid", "paid"])
|
|
).all()
|
|
}
|
|
|
|
result = []
|
|
for t in tables:
|
|
out = TableOut.model_validate(t)
|
|
out.has_active_order = t.id in active_table_ids
|
|
result.append(out)
|
|
return result
|
|
|
|
|
|
@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)):
|
|
number = _next_global_number(db)
|
|
table = Table(number=number, label=body.label, group_id=body.group_id, is_active=True)
|
|
db.add(table)
|
|
db.commit()
|
|
db.refresh(table)
|
|
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")
|
|
|
|
# Group-local label numbering: find highest suffix already used in this group
|
|
existing_in_group = (
|
|
db.query(Table)
|
|
.filter(Table.group_id == body.group_id)
|
|
.all()
|
|
) if body.group_id else []
|
|
|
|
# Extract trailing integers from existing labels that start with this prefix
|
|
used = []
|
|
for t in existing_in_group:
|
|
if t.label and t.label.startswith(body.name_prefix):
|
|
suffix = t.label[len(body.name_prefix):]
|
|
if suffix.isdigit():
|
|
used.append(int(suffix))
|
|
start_label_n = (max(used) + 1) if used else 1
|
|
|
|
created = []
|
|
for i in range(body.count):
|
|
label_n = start_label_n + i
|
|
global_number = _next_global_number(db)
|
|
table = Table(
|
|
number=global_number,
|
|
label=f"{body.name_prefix}{label_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()
|
|
if not table:
|
|
raise HTTPException(status_code=404, detail="Table not found")
|
|
for field, value in body.model_dump(exclude_none=True).items():
|
|
setattr(table, field, value)
|
|
db.commit()
|
|
db.refresh(table)
|
|
return table
|
|
|
|
|
|
@router.delete("/{table_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
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")
|
|
active_order = db.query(Order).filter(
|
|
Order.table_id == table_id,
|
|
Order.status.in_(["open", "partially_paid", "paid"])
|
|
).first()
|
|
if active_order:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Cannot delete or deactivate a table with an active order"
|
|
)
|
|
if hard:
|
|
# Delete all past (non-active) orders for this table so FK constraint doesn't block deletion.
|
|
# Active orders are already blocked above. Items/waiters/print_logs cascade via ORM.
|
|
past_orders = db.query(Order).filter(Order.table_id == table_id).all()
|
|
for order in past_orders:
|
|
db.delete(order)
|
|
db.flush()
|
|
db.delete(table)
|
|
else:
|
|
table.is_active = False
|
|
db.commit()
|
|
|
|
|
|
@router.get("/{table_id}/status")
|
|
def table_status(table_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
|
table = db.query(Table).filter(Table.id == table_id).first()
|
|
if not table:
|
|
raise HTTPException(status_code=404, detail="Table not found")
|
|
active_order = (
|
|
db.query(Order)
|
|
.filter(Order.table_id == table_id, Order.status.in_(["open", "partially_paid", "paid"]))
|
|
.first()
|
|
)
|
|
return {
|
|
"table": TableOut.model_validate(table),
|
|
"active_order_id": active_order.id if active_order else None,
|
|
"order_status": active_order.status if active_order else None,
|
|
}
|
|
|
|
|
|
@router.put("/{table_id}/floorplan", response_model=TableOut)
|
|
def update_floorplan(table_id: int, body: TableFloorplanUpdate, 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.floor_x = body.floor_x
|
|
table.floor_y = body.floor_y
|
|
db.commit()
|
|
db.refresh(table)
|
|
return table
|