Files
xenia-pos-local/local_backend/schemas/customers.py
bonamin e9bdf61a4d 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>
2026-06-08 02:59:53 +03:00

42 lines
1002 B
Python

from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class CustomerCreate(BaseModel):
name: str
nickname: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
notes: Optional[str] = None
class CustomerUpdate(BaseModel):
name: Optional[str] = None
nickname: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
notes: Optional[str] = None
is_active: Optional[bool] = None
class CustomerOut(BaseModel):
id: int
name: str
nickname: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
notes: Optional[str] = None
is_active: bool
created_at: datetime
created_by_id: int
# computed stats (set by router)
visit_count: int = 0
total_spent: float = 0.0
model_config = {"from_attributes": True}
class AssignCustomerRequest(BaseModel):
customer_id: Optional[int] = None # null = unassign