import time from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import List from database import get_db from models.printer import Printer from schemas.printer import PrinterCreate, PrinterUpdate, PrinterOut from routers.deps import get_current_user, require_manager, require_sysadmin from models.user import User from models.product import Category, Product from models.table import Table, TableGroup from services import printer_service from services.cloud_sync import _sync_once from middleware.license_check import license_state from config import settings router = APIRouter() _start_time = time.time() @router.get("/health") def health(): return {"status": "ok", "version": settings.VERSION} @router.get("/status") def system_status(db: Session = Depends(get_db), user: User = Depends(get_current_user)): from datetime import datetime, timezone printers = db.query(Printer).filter(Printer.is_active == True).all() printer_statuses = [] for p in printers: reachable = printer_service.check_printer(p.ip_address, p.port) printer_statuses.append({"id": p.id, "name": p.name, "reachable": reachable}) licensed = license_state.get("licensed", True) locked = license_state.get("locked", False) lock_pending = license_state.get("lock_pending", False) expires_at = license_state.get("expires_at") days_until_expiry = license_state.get("days_until_expiry") grace_expires_at = license_state.get("grace_expires_at") # Determine lock_reason for the frontend banner logic # "admin" — locked by sysadmin (immediately or deferred) # "expired" — license grace period over, site is blocked # None — all good lock_reason = None if locked or lock_pending: lock_reason = "admin" elif not licensed: lock_reason = "expired" # Grace days remaining (only meaningful while in expiry grace period) grace_days_remaining = None if grace_expires_at: try: grace_dt = datetime.fromisoformat(grace_expires_at) if grace_dt.tzinfo is None: grace_dt = grace_dt.replace(tzinfo=timezone.utc) grace_days_remaining = max(0, (grace_dt - datetime.now(timezone.utc)).days) except ValueError: pass return { "uptime_seconds": int(time.time() - _start_time), "version": settings.VERSION, "latest_version": license_state.get("latest_version"), "licensed": licensed, "locked": locked, "lock_pending": lock_pending, "lock_reason": lock_reason, "expires_at": expires_at, "days_until_expiry": days_until_expiry, "grace_expires_at": grace_expires_at, "grace_days_remaining": grace_days_remaining, "sync_failed": license_state.get("sync_failed", False), "last_sync": license_state.get("last_sync"), "waiter_domain": license_state.get("waiter_domain"), "printers": printer_statuses, } @router.post("/sync-license") async def sync_license_now(user: User = Depends(require_manager)): """Trigger an immediate cloud heartbeat and return the fresh license state.""" await _sync_once() return { "licensed": license_state.get("licensed", True), "locked": license_state.get("locked", False), "lock_pending": license_state.get("lock_pending", False), "lock_reason": ( "admin" if (license_state.get("locked") or license_state.get("lock_pending")) else "expired" if not license_state.get("licensed", True) else None ), "expires_at": license_state.get("expires_at"), "days_until_expiry": license_state.get("days_until_expiry"), "sync_failed": license_state.get("sync_failed", False), "last_sync": license_state.get("last_sync"), } @router.get("/printers", response_model=List[PrinterOut]) def list_printers(db: Session = Depends(get_db), user: User = Depends(require_manager)): return db.query(Printer).all() @router.post("/printers", response_model=PrinterOut) def create_printer(body: PrinterCreate, db: Session = Depends(get_db), user: User = Depends(require_manager)): printer = Printer(**body.model_dump()) db.add(printer) db.commit() db.refresh(printer) return printer @router.post("/printers/test") def test_printer(printer_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)): printer = db.query(Printer).filter(Printer.id == printer_id).first() if not printer: raise HTTPException(status_code=404, detail="Printer not found") success, error = printer_service.send_test_print(printer.ip_address, printer.port, printer.name) return {"success": success, "error": error} @router.post("/printers/test-order") def test_order_print(printer_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)): printer = db.query(Printer).filter(Printer.id == printer_id).first() if not printer: raise HTTPException(status_code=404, detail="Printer not found") success, error = printer_service.send_test_order_print(printer.ip_address, printer.port, db) return {"success": success, "error": error} @router.put("/printers/{printer_id}", response_model=PrinterOut) def update_printer(printer_id: int, body: PrinterUpdate, db: Session = Depends(get_db), user: User = Depends(require_manager)): printer = db.query(Printer).filter(Printer.id == printer_id).first() if not printer: raise HTTPException(status_code=404, detail="Printer not found") for field, value in body.model_dump(exclude_none=True).items(): setattr(printer, field, value) db.commit() db.refresh(printer) return printer @router.delete("/printers/{printer_id}") def delete_printer(printer_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)): printer = db.query(Printer).filter(Printer.id == printer_id).first() if not printer: raise HTTPException(status_code=404, detail="Printer not found") db.delete(printer) db.commit() return {"ok": True} @router.get("/stats") def system_stats(db: Session = Depends(get_db), user: User = Depends(get_current_user)): return { "categories": db.query(Category).count(), "products": db.query(Product).filter(Product.lifecycle_status == "active").count(), "tables": db.query(Table).filter(Table.is_active == True).count(), "table_groups": db.query(TableGroup).count(), "managers": db.query(User).filter(User.role == "manager", User.is_active == True).count(), "waiters": db.query(User).filter(User.role == "waiter", User.is_active == True).count(), } @router.post("/lock") def lock_system(token: str, user: User = Depends(require_sysadmin)): license_state["locked"] = True return {"status": "locked"} @router.post("/unlock") def unlock_system(token: str, user: User = Depends(require_sysadmin)): license_state["locked"] = False return {"status": "unlocked"}