feat: Phase 2F — customer CRM
Backend:
- New model: Customer (customers table) — name, nickname, phone, email, notes, is_active
- Order model: add customer_id nullable FK → customers
- New router: /api/customers/ — list (with ?search=), create, get, update, soft-delete,
GET /{id}/orders (visit history with totals)
- Each CustomerOut includes visit_count + total_spent (computed from closed/paid orders)
- PUT /api/orders/{id}/customer — assign or unassign a customer; manager-only;
validates customer is active; broadcasts order_updated SSE event
- GET /api/orders/{id} now returns customer_id, customer_name (with nickname), customer_phone
- Migration: CREATE TABLE customers + ALTER TABLE orders ADD COLUMN customer_id
Frontend:
- CustomersPage (/customers): searchable list (name/nickname/phone), avatar initials,
visit count + total_spent on each row; click → detail panel slides in showing stats
(visits, total spent, avg ticket), contact info, notes, full visit history
- OrderDetailPage: new Πελάτης card — shows assigned customer in blue if set;
for open orders shows "+ Ανάθεση πελάτη" button → inline search dropdown → assign;
Αφαίρεση button to unassign
- Sidebar: Users icon for Πελάτες (between Επαφές and Ρυθμίσεις)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import models.message # noqa: F401 — registers StaffMessage, StaffMessag
|
||||
import models.reservation # noqa: F401
|
||||
import models.notes # noqa: F401 — registers SiteNote, SiteTodo
|
||||
import models.expenses # noqa: F401 — registers Contact, Expense, ExpensePayment
|
||||
import models.customers # noqa: F401 — registers Customer
|
||||
|
||||
from routers import auth, tables, products, orders, waiters, reports, system, setup as setup_router
|
||||
from routers import business_day as business_day_router
|
||||
@@ -35,6 +36,7 @@ from routers import connect_orders as connect_orders_router
|
||||
from routers import reservations as reservations_router
|
||||
from routers import notes as notes_router
|
||||
from routers.expenses import contacts_router, expenses_router
|
||||
from routers import customers as customers_router
|
||||
|
||||
|
||||
def _run_migrations():
|
||||
@@ -273,6 +275,19 @@ def _run_migrations():
|
||||
# Phase 2B — staff payroll
|
||||
"ALTER TABLE users ADD COLUMN hourly_rate REAL",
|
||||
"ALTER TABLE waiter_shifts ADD COLUMN hourly_rate_snapshot REAL",
|
||||
# Phase 2F — customer CRM
|
||||
"""CREATE TABLE IF NOT EXISTS customers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
nickname VARCHAR,
|
||||
phone VARCHAR,
|
||||
email VARCHAR,
|
||||
notes TEXT,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by_id INTEGER NOT NULL REFERENCES users(id)
|
||||
)""",
|
||||
"ALTER TABLE orders ADD COLUMN customer_id INTEGER REFERENCES customers(id)",
|
||||
# Phase 2E — cash drawer reconciliation
|
||||
"ALTER TABLE waiter_shifts ADD COLUMN counted_cash_end REAL",
|
||||
"ALTER TABLE waiter_shifts ADD COLUMN cash_discrepancy REAL",
|
||||
@@ -396,3 +411,4 @@ app.include_router(reservations_router.router, prefix="/api/reservations", tag
|
||||
app.include_router(notes_router.router, prefix="/api/notes", tags=["notes"])
|
||||
app.include_router(contacts_router, prefix="/api/contacts", tags=["contacts"])
|
||||
app.include_router(expenses_router, prefix="/api/expenses", tags=["expenses"])
|
||||
app.include_router(customers_router.router, prefix="/api/customers", tags=["customers"])
|
||||
|
||||
Reference in New Issue
Block a user