feat: bump client-services (accumulated feature work + deploy fixes)
Snapshot of in-progress work across local_backend, manager_dashboard, and waiter_pwa (pricing, chat, fiscal, prep zones, recovery codes, CRM, inventory, permissions), plus the nginx/docker-compose deploy fixes for the Unraid + NPM reverse-proxy setup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import models.user # noqa: F401 — also registers WaiterZone
|
||||
import models.table # noqa: F401
|
||||
import models.printer # noqa: F401
|
||||
import models.product # noqa: F401
|
||||
import models.order # noqa: F401 — also registers OrderAuditLog, OrderDiscount
|
||||
import models.order # noqa: F401 — also registers OrderAuditLog, OrderDiscount, PrintJob
|
||||
import models.business_day # noqa: F401
|
||||
import models.shift # noqa: F401 — registers WaiterShift, ShiftBreak
|
||||
import models.settings # noqa: F401
|
||||
@@ -26,6 +26,10 @@ import models.customers # noqa: F401 — registers Customer
|
||||
import models.tabs # noqa: F401 — registers Tab, TabEntry, TabPayment
|
||||
import models.waste # noqa: F401 — registers WasteLog
|
||||
import models.schedule # noqa: F401 — registers ScheduledShift
|
||||
import models.chat # noqa: F401 — registers Conversation, ConversationParticipant, ChatMessage
|
||||
import models.prep_zone # noqa: F401 — registers PrepZone, prep_zone_printers, product_prep_zones
|
||||
import models.pricing # noqa: F401 — registers PriceGroup, PriceModifier, Deal, PriceEventLog, WaiterDiscountSettings
|
||||
import models.recovery_code # noqa: F401 — registers RecoveryCode
|
||||
|
||||
from routers import auth, tables, products, orders, waiters, reports, system, setup as setup_router
|
||||
from routers import business_day as business_day_router
|
||||
@@ -34,6 +38,7 @@ from routers import settings as settings_router
|
||||
from routers import flags as flags_router
|
||||
from routers import messages as messages_router
|
||||
from routers import sse as sse_router
|
||||
from routers import ws as ws_router
|
||||
from routers import data_transfer as data_transfer_router
|
||||
from routers import connect_orders as connect_orders_router
|
||||
from routers import reservations as reservations_router
|
||||
@@ -44,6 +49,12 @@ from routers import tabs as tabs_router
|
||||
from routers import waste as waste_router
|
||||
from routers import kds as kds_router
|
||||
from routers import schedule as schedule_router
|
||||
from routers import chat as chat_router
|
||||
from routers import prep_zones as prep_zones_router
|
||||
from routers import pricing as pricing_router
|
||||
from routers import fiscal as fiscal_router
|
||||
from routers import phone as phone_router
|
||||
from routers import recovery as recovery_router
|
||||
|
||||
|
||||
def _run_migrations():
|
||||
@@ -275,6 +286,8 @@ def _run_migrations():
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)""",
|
||||
# Product unit of measure (piece / portion / kg / liter / gram / ml)
|
||||
"ALTER TABLE products ADD COLUMN unit_type VARCHAR NOT NULL DEFAULT 'piece'",
|
||||
# Phase 2A — product cost tracking
|
||||
"ALTER TABLE products ADD COLUMN cost_simple REAL",
|
||||
"ALTER TABLE products ADD COLUMN cost_breakdown TEXT",
|
||||
@@ -411,6 +424,314 @@ def _run_migrations():
|
||||
# Waiter cancellation permissions
|
||||
"ALTER TABLE users ADD COLUMN can_cancel_orders INTEGER NOT NULL DEFAULT 0",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('orders.waiter_cancellations_allowed', 'false', CURRENT_TIMESTAMP)",
|
||||
# KDS — kitchen display system status tracking
|
||||
"ALTER TABLE orders ADD COLUMN kds_status VARCHAR NOT NULL DEFAULT 'pending'",
|
||||
"ALTER TABLE orders ADD COLUMN order_type VARCHAR NOT NULL DEFAULT 'here'",
|
||||
"ALTER TABLE order_items ADD COLUMN kds_status VARCHAR NOT NULL DEFAULT 'pending'",
|
||||
# KDS notifications — message type + zone tagging on staff messages
|
||||
"ALTER TABLE staff_messages ADD COLUMN message_type VARCHAR NOT NULL DEFAULT 'manager'",
|
||||
"ALTER TABLE staff_messages ADD COLUMN kds_zone VARCHAR",
|
||||
# Chat system
|
||||
"""CREATE TABLE IF NOT EXISTS conversations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type VARCHAR NOT NULL DEFAULT 'direct',
|
||||
name VARCHAR,
|
||||
is_system INTEGER NOT NULL DEFAULT 0,
|
||||
created_by INTEGER REFERENCES users(id),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS conversation_participants (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER NOT NULL REFERENCES conversations(id),
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_read_at DATETIME
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS chat_messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER NOT NULL REFERENCES conversations(id),
|
||||
sender_id INTEGER NOT NULL REFERENCES users(id),
|
||||
body TEXT NOT NULL,
|
||||
sent_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME
|
||||
)""",
|
||||
# Prep Zones — replaces per-product printer assignment
|
||||
"""CREATE TABLE IF NOT EXISTS prep_zones (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
description VARCHAR
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS prep_zone_printers (
|
||||
prep_zone_id INTEGER NOT NULL REFERENCES prep_zones(id),
|
||||
printer_id INTEGER NOT NULL REFERENCES printers(id),
|
||||
PRIMARY KEY (prep_zone_id, printer_id)
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS product_prep_zones (
|
||||
product_id INTEGER NOT NULL REFERENCES products(id),
|
||||
prep_zone_id INTEGER NOT NULL REFERENCES prep_zones(id),
|
||||
PRIMARY KEY (product_id, prep_zone_id)
|
||||
)""",
|
||||
# KDS decline tracking on items
|
||||
"ALTER TABLE order_items ADD COLUMN decline_note TEXT",
|
||||
# Prep zone notification name
|
||||
"ALTER TABLE prep_zones ADD COLUMN notification_name VARCHAR",
|
||||
# KDS status change timestamp on orders (for "latest change" sorting in PWA)
|
||||
"ALTER TABLE orders ADD COLUMN kds_status_changed_at DATETIME",
|
||||
# Auto-close table after full payment setting
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('orders.auto_close_on_full_payment', 'false', CURRENT_TIMESTAMP)",
|
||||
# Quick Add: allow direct 1-unit add from product list without opening config modal
|
||||
"ALTER TABLE products ADD COLUMN quick_add_enabled INTEGER NOT NULL DEFAULT 1",
|
||||
# Per-waiter settings + favorites sync (JSON blob)
|
||||
"ALTER TABLE users ADD COLUMN waiter_settings TEXT",
|
||||
# Prep zone print settings — synced with KDS auto-print controls
|
||||
"ALTER TABLE prep_zones ADD COLUMN auto_print VARCHAR NOT NULL DEFAULT 'none'",
|
||||
"ALTER TABLE prep_zones ADD COLUMN print_copies INTEGER NOT NULL DEFAULT 1",
|
||||
# Bypass KDS/serve flow: new orders immediately start as served
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('orders.bypass_kds_serve', 'false', CURRENT_TIMESTAMP)",
|
||||
# Prep zone: rename auto_print value 'primary' → 'master' for existing rows
|
||||
"UPDATE prep_zones SET auto_print = 'master' WHERE auto_print = 'primary'",
|
||||
# Prep zone: master printer (single FK) + per-role copy counts
|
||||
"ALTER TABLE prep_zones ADD COLUMN master_printer_id INTEGER REFERENCES printers(id)",
|
||||
"ALTER TABLE prep_zones ADD COLUMN master_copies INTEGER NOT NULL DEFAULT 1",
|
||||
"ALTER TABLE prep_zones ADD COLUMN secondary_copies INTEGER NOT NULL DEFAULT 1",
|
||||
# Prep zone: item sort / category grouping options
|
||||
"ALTER TABLE prep_zones ADD COLUMN sort_items_by VARCHAR NOT NULL DEFAULT 'order_time'",
|
||||
"ALTER TABLE prep_zones ADD COLUMN group_by_category INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE prep_zones ADD COLUMN category_order TEXT NOT NULL DEFAULT '[]'",
|
||||
# Prep zone: ticket formatting option
|
||||
"ALTER TABLE prep_zones ADD COLUMN print_checkboxes INTEGER NOT NULL DEFAULT 0",
|
||||
# Prep zone: KDS bypass options
|
||||
"ALTER TABLE prep_zones ADD COLUMN bypass_pending INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE prep_zones ADD COLUMN bypass_kds INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE prep_zones ADD COLUMN auto_ready_to_served INTEGER NOT NULL DEFAULT 0",
|
||||
# Prep zone: migrate existing master_printer_id from first printer in zone (best-effort)
|
||||
# (handled in Python at runtime, not SQL — no-op SQL here)
|
||||
# Product tags (JSON array of strings)
|
||||
"ALTER TABLE products ADD COLUMN tags TEXT",
|
||||
# Hide revenue from waiters on shift overview
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('shifts.hide_revenue_from_waiters', 'false', CURRENT_TIMESTAMP)",
|
||||
# WebSocket event log — append-only, pruned to 24h on startup
|
||||
"""CREATE TABLE IF NOT EXISTS sync_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_type VARCHAR NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
target_user_ids TEXT,
|
||||
created_at VARCHAR NOT NULL
|
||||
)""",
|
||||
"CREATE INDEX IF NOT EXISTS ix_sync_events_created_at ON sync_events (created_at)",
|
||||
# Migrate from legacy per-product printer to prep zones: clear old printer_zone_id
|
||||
"UPDATE products SET printer_zone_id = NULL WHERE printer_zone_id IS NOT NULL",
|
||||
# Per-printer ESC/POS code page index (n in ESC t n) — default 29 = CP737 on Jolimark
|
||||
"ALTER TABLE printers ADD COLUMN codepage_n INTEGER NOT NULL DEFAULT 29",
|
||||
# Price adjustment: per-item on-the-fly price delta (positive or negative)
|
||||
"ALTER TABLE order_items ADD COLUMN price_adjustment REAL NOT NULL DEFAULT 0.0",
|
||||
# Customer count: how many people are at the table for this order
|
||||
"ALTER TABLE orders ADD COLUMN customer_count INTEGER",
|
||||
# Service items: flag on products, these are non-inventoried table-service items
|
||||
"ALTER TABLE products ADD COLUMN is_service_item INTEGER NOT NULL DEFAULT 0",
|
||||
# Table seat count — physical seats at each table (foundation for per-seat dish assignment)
|
||||
"ALTER TABLE tables ADD COLUMN seat_count INTEGER",
|
||||
# Courses — optional course assignment for sequenced firing
|
||||
"ALTER TABLE order_items ADD COLUMN course_id INTEGER",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('orders.courses_enabled', 'false', CURRENT_TIMESTAMP)",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('orders.courses', '[]', CURRENT_TIMESTAMP)",
|
||||
# Print job lifecycle tracking — auto-retry system
|
||||
"""CREATE TABLE IF NOT EXISTS print_jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id INTEGER NOT NULL REFERENCES orders(id),
|
||||
printer_id INTEGER NOT NULL REFERENCES printers(id),
|
||||
zone_id INTEGER,
|
||||
item_ids TEXT NOT NULL,
|
||||
copies INTEGER NOT NULL DEFAULT 1,
|
||||
status VARCHAR NOT NULL DEFAULT 'pending',
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
first_attempted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_attempted_at DATETIME,
|
||||
succeeded_at DATETIME,
|
||||
cancelled_at DATETIME,
|
||||
cancel_reason VARCHAR
|
||||
)""",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('orders.waiter_price_adjust_allowed', 'false', CURRENT_TIMESTAMP)",
|
||||
# Pricing system — Phase 2 pricing overhaul
|
||||
"""CREATE TABLE IF NOT EXISTS price_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
color VARCHAR,
|
||||
is_active INTEGER NOT NULL DEFAULT 0,
|
||||
auto_enable_time VARCHAR,
|
||||
auto_disable_time VARCHAR,
|
||||
auto_days TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by INTEGER REFERENCES users(id)
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS price_modifiers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
color VARCHAR,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
is_favorite INTEGER NOT NULL DEFAULT 0,
|
||||
allow_stack INTEGER NOT NULL DEFAULT 0,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
scope VARCHAR NOT NULL DEFAULT 'global',
|
||||
item_id INTEGER REFERENCES products(id),
|
||||
action_type VARCHAR NOT NULL,
|
||||
action_value REAL NOT NULL,
|
||||
round_to VARCHAR,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by INTEGER REFERENCES users(id),
|
||||
updated_at DATETIME,
|
||||
updated_by INTEGER REFERENCES users(id)
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS price_modifier_conditions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
modifier_id INTEGER NOT NULL REFERENCES price_modifiers(id) ON DELETE CASCADE,
|
||||
condition_type VARCHAR NOT NULL,
|
||||
params TEXT NOT NULL DEFAULT '{}'
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS price_modifier_targets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
modifier_id INTEGER NOT NULL REFERENCES price_modifiers(id) ON DELETE CASCADE,
|
||||
target_type VARCHAR NOT NULL,
|
||||
target_id INTEGER,
|
||||
target_tag VARCHAR
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS deals (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
color VARCHAR,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
action_type VARCHAR NOT NULL,
|
||||
action_modifier_id INTEGER REFERENCES price_modifiers(id),
|
||||
action_value REAL,
|
||||
action_free_item_id INTEGER REFERENCES products(id),
|
||||
action_free_target_type VARCHAR,
|
||||
action_free_target_ids TEXT,
|
||||
action_free_quantity INTEGER NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by INTEGER REFERENCES users(id)
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS deal_conditions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
deal_id INTEGER NOT NULL REFERENCES deals(id) ON DELETE CASCADE,
|
||||
condition_type VARCHAR NOT NULL,
|
||||
params TEXT NOT NULL DEFAULT '{}'
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS deal_targets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
deal_id INTEGER NOT NULL REFERENCES deals(id) ON DELETE CASCADE,
|
||||
target_type VARCHAR NOT NULL,
|
||||
target_id INTEGER,
|
||||
target_tag VARCHAR
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS price_event_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id INTEGER NOT NULL REFERENCES orders(id),
|
||||
order_item_id INTEGER REFERENCES order_items(id),
|
||||
event_type VARCHAR NOT NULL,
|
||||
modifier_id INTEGER REFERENCES price_modifiers(id),
|
||||
deal_id INTEGER REFERENCES deals(id),
|
||||
price_before REAL,
|
||||
price_after REAL,
|
||||
delta_amount REAL,
|
||||
applied_by_user_id INTEGER REFERENCES users(id),
|
||||
waiter_note TEXT,
|
||||
conditions_snapshot TEXT,
|
||||
selected_item_ids TEXT,
|
||||
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)""",
|
||||
"CREATE INDEX IF NOT EXISTS ix_price_event_log_order_id ON price_event_log (order_id)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_price_event_log_order_item_id ON price_event_log (order_item_id)",
|
||||
"""CREATE TABLE IF NOT EXISTS waiter_discount_settings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
|
||||
can_apply_discounts INTEGER NOT NULL DEFAULT 0,
|
||||
max_discount_percent REAL,
|
||||
max_discount_amount REAL,
|
||||
max_total_value_shift REAL,
|
||||
max_total_value_workday REAL,
|
||||
max_items_per_shift INTEGER,
|
||||
max_items_per_workday INTEGER,
|
||||
max_items_per_order INTEGER
|
||||
)""",
|
||||
# Extend order_discounts with price snapshot columns for clean audit trail
|
||||
"ALTER TABLE order_discounts ADD COLUMN price_before REAL",
|
||||
"ALTER TABLE order_discounts ADD COLUMN price_after REAL",
|
||||
# Global discount settings in pos_settings
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('discounts.enabled', 'true', CURRENT_TIMESTAMP)",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('discounts.max_total_value_workday', '', CURRENT_TIMESTAMP)",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('discounts.max_total_value_shift', '', CURRENT_TIMESTAMP)",
|
||||
"INSERT OR IGNORE INTO pos_settings (key, value, updated_at) VALUES ('discounts.max_items_per_shift', '', CURRENT_TIMESTAMP)",
|
||||
# Deal item binding — which deal created this item, and which item it's linked to
|
||||
"ALTER TABLE order_items ADD COLUMN deal_id INTEGER REFERENCES deals(id)",
|
||||
"ALTER TABLE order_items ADD COLUMN linked_item_id INTEGER REFERENCES order_items(id)",
|
||||
# Multi-select targets: JSON arrays of IDs/tags per target row
|
||||
"ALTER TABLE price_modifier_targets ADD COLUMN target_ids TEXT",
|
||||
"ALTER TABLE price_modifier_targets ADD COLUMN target_tags TEXT",
|
||||
"ALTER TABLE deal_targets ADD COLUMN target_ids TEXT",
|
||||
"ALTER TABLE deal_targets ADD COLUMN target_tags TEXT",
|
||||
# Role & permissions overhaul — granular per-user permission flags
|
||||
# Access permissions
|
||||
"ALTER TABLE users ADD COLUMN perm_access_dashboard INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_access_waiter_app INTEGER NOT NULL DEFAULT 1",
|
||||
"ALTER TABLE users ADD COLUMN perm_access_kds INTEGER NOT NULL DEFAULT 0",
|
||||
# Order action permissions
|
||||
"ALTER TABLE users ADD COLUMN perm_cancel_orders INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_apply_discounts INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_modify_prices INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_open_orders INTEGER NOT NULL DEFAULT 1",
|
||||
"ALTER TABLE users ADD COLUMN perm_close_orders INTEGER NOT NULL DEFAULT 1",
|
||||
# Management permissions
|
||||
"ALTER TABLE users ADD COLUMN perm_view_reports INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_manage_staff INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_manage_tables INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_manage_menu INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE users ADD COLUMN perm_manage_settings INTEGER NOT NULL DEFAULT 0",
|
||||
# Migrate existing managers/sysadmins to have dashboard access + all management perms
|
||||
"UPDATE users SET perm_access_dashboard=1, perm_access_waiter_app=1, perm_access_kds=1, perm_cancel_orders=1, perm_apply_discounts=1, perm_modify_prices=1, perm_open_orders=1, perm_close_orders=1, perm_view_reports=1, perm_manage_staff=1, perm_manage_tables=1, perm_manage_menu=1, perm_manage_settings=1 WHERE role IN ('manager', 'sysadmin')",
|
||||
# Migrate existing waiters: update can_cancel_orders → perm_cancel_orders
|
||||
"UPDATE users SET perm_cancel_orders = can_cancel_orders WHERE role = 'waiter'",
|
||||
# Rename legacy roles: manager → store_manager, sysadmin → superadmin
|
||||
"UPDATE users SET role = 'superadmin' WHERE role = 'sysadmin'",
|
||||
"UPDATE users SET role = 'store_manager' WHERE role = 'manager'",
|
||||
# Feature: modifier groups (folders), multi-select extras, compact mode
|
||||
"""CREATE TABLE IF NOT EXISTS product_modifier_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
product_id INTEGER NOT NULL REFERENCES products(id),
|
||||
modifier_type VARCHAR NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0
|
||||
)""",
|
||||
"ALTER TABLE product_options ADD COLUMN multi_select INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE product_options ADD COLUMN is_compact INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE product_options ADD COLUMN group_id INTEGER REFERENCES product_modifier_groups(id)",
|
||||
"ALTER TABLE product_ingredients ADD COLUMN is_compact INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE product_ingredients ADD COLUMN group_id INTEGER REFERENCES product_modifier_groups(id)",
|
||||
"ALTER TABLE product_preference_choices ADD COLUMN is_compact INTEGER NOT NULL DEFAULT 0",
|
||||
"ALTER TABLE product_preference_sets ADD COLUMN group_id INTEGER REFERENCES product_modifier_groups(id)",
|
||||
# Fiscal printer (ΦΗΜ) — product fields
|
||||
"ALTER TABLE products ADD COLUMN fiscal_name VARCHAR",
|
||||
"ALTER TABLE products ADD COLUMN fiscal_vat_group_id INTEGER",
|
||||
# Fiscal status on orders (NULL=fiscal off, pending/success/failed)
|
||||
"ALTER TABLE orders ADD COLUMN fiscal_status VARCHAR",
|
||||
# Drop legacy can_cancel_orders column — data already migrated to perm_cancel_orders
|
||||
"ALTER TABLE users DROP COLUMN can_cancel_orders",
|
||||
# Recovery codes for account self-recovery
|
||||
"""CREATE TABLE IF NOT EXISTS recovery_codes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
code_hash VARCHAR NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
used_at DATETIME
|
||||
)""",
|
||||
"CREATE INDEX IF NOT EXISTS ix_recovery_codes_user_id ON recovery_codes (user_id)",
|
||||
# Multi-select preference sets
|
||||
"ALTER TABLE product_preference_sets ADD COLUMN allow_multi_select INTEGER NOT NULL DEFAULT 0",
|
||||
# Per-choice quantity on multi-select preference sets (x2, x3, …)
|
||||
"ALTER TABLE product_preference_sets ADD COLUMN allow_choice_quantity INTEGER NOT NULL DEFAULT 0",
|
||||
]
|
||||
for sql in migrations:
|
||||
try:
|
||||
@@ -421,14 +742,39 @@ def _run_migrations():
|
||||
pass
|
||||
|
||||
|
||||
def _init_chat():
|
||||
"""Bootstrap the system group chat and sync all active users into it."""
|
||||
from services.chat_service import ensure_system_group, add_user_to_system_group
|
||||
from models.user import User
|
||||
from database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
ensure_system_group(db)
|
||||
active_users = db.query(User).filter(User.is_active == True).all() # noqa: E712
|
||||
for u in active_users:
|
||||
add_user_to_system_group(db, u.id)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
import asyncio
|
||||
from services.sse_bus import init_loop
|
||||
from services.sse_bus import init_loop as sse_init_loop
|
||||
from services.ws_bus import init_loop as ws_init_loop, prune_old_events
|
||||
from services.reservation_tasks import start_reservation_tasks
|
||||
init_loop(asyncio.get_running_loop())
|
||||
from services.printer_service import start_print_retry_thread
|
||||
loop = asyncio.get_running_loop()
|
||||
sse_init_loop(loop)
|
||||
ws_init_loop(loop)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
_run_migrations()
|
||||
start_print_retry_thread()
|
||||
pruned = prune_old_events(hours=24)
|
||||
if pruned:
|
||||
import logging
|
||||
logging.getLogger(__name__).info("ws_bus: pruned %d old sync_events", pruned)
|
||||
_init_chat()
|
||||
sync_task = await start_cloud_sync()
|
||||
reservation_task = await start_reservation_tasks()
|
||||
yield
|
||||
@@ -468,8 +814,9 @@ app.include_router(business_day_router.router, prefix="/api/business-day", tag
|
||||
app.include_router(shifts_router.router, prefix="/api/shifts", tags=["shifts"])
|
||||
app.include_router(settings_router.router, prefix="/api/settings", tags=["settings"])
|
||||
app.include_router(flags_router.router, prefix="/api/flags", tags=["flags"])
|
||||
app.include_router(messages_router.router, prefix="/api/messages", tags=["messages"])
|
||||
app.include_router(messages_router.router, prefix="/api/notifications", tags=["messages"])
|
||||
app.include_router(sse_router.router, prefix="/api/sse", tags=["sse"])
|
||||
app.include_router(ws_router.router, prefix="/api/ws", tags=["ws"])
|
||||
app.include_router(data_transfer_router.router, prefix="/api/data-transfer", tags=["data-transfer"])
|
||||
app.include_router(connect_orders_router.router, prefix="/api/connect", tags=["connect"])
|
||||
app.include_router(reservations_router.router, prefix="/api/reservations", tags=["reservations"])
|
||||
@@ -481,3 +828,9 @@ app.include_router(tabs_router.router, prefix="/api/tabs", tag
|
||||
app.include_router(waste_router.router, prefix="/api/waste", tags=["waste"])
|
||||
app.include_router(kds_router.router, prefix="/api/kds", tags=["kds"])
|
||||
app.include_router(schedule_router.router, prefix="/api/schedule", tags=["schedule"])
|
||||
app.include_router(chat_router.router, prefix="/api/chat", tags=["chat"])
|
||||
app.include_router(prep_zones_router.router, prefix="/api/prep-zones", tags=["prep-zones"])
|
||||
app.include_router(pricing_router.router, prefix="/api/pricing", tags=["pricing"])
|
||||
app.include_router(fiscal_router.router, prefix="/api/fiscal", tags=["fiscal"])
|
||||
app.include_router(phone_router.router, prefix="/api/phone", tags=["phone"])
|
||||
app.include_router(recovery_router.router, prefix="/api/recovery", tags=["recovery"])
|
||||
|
||||
Reference in New Issue
Block a user