feat: Phase 2D — expense tracking + supplier contact book
Backend: - New models: Contact, Expense, ExpensePayment (3 new tables) - Expense status (paid/partial/due) is always computed from paid_amount vs total_amount — never stored - paid_amount recomputed from sum of all payments on each payment write (no client trust) - New router /api/contacts/: list, create, update, soft-delete (is_active=false) - New router /api/expenses/: list (filter by status/category/contact), create, update, delete, record payment, summary (total_due, total_paid, by_category) - Migrations: CREATE TABLE IF NOT EXISTS for contacts, expenses, expense_payments Frontend: - ContactsPage (/contacts): searchable table, type badge (Προμηθευτής/Προσωπικό/Κοινή Χρεία/Άλλο), create/edit modal, soft-delete - ExpensesPage (/expenses): filter bar (status + category), expandable rows with payment history, summary header showing total outstanding, inline Payment modal (defaults to full due amount), create/edit expense modal - Sidebar: Receipt icon for Έξοδα, BookUser icon for Επαφές Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import models.flag # noqa: F401 — registers TableFlagDef, TableFlagAs
|
||||
import models.message # noqa: F401 — registers StaffMessage, StaffMessageAck, QuickMessageTemplate
|
||||
import models.reservation # noqa: F401
|
||||
import models.notes # noqa: F401 — registers SiteNote, SiteTodo
|
||||
import models.expenses # noqa: F401 — registers Contact, Expense, ExpensePayment
|
||||
|
||||
from routers import auth, tables, products, orders, waiters, reports, system, setup as setup_router
|
||||
from routers import business_day as business_day_router
|
||||
@@ -33,6 +34,7 @@ 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
|
||||
from routers import notes as notes_router
|
||||
from routers.expenses import contacts_router, expenses_router
|
||||
|
||||
|
||||
def _run_migrations():
|
||||
@@ -271,6 +273,39 @@ 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 2D — expense tracking + supplier contacts
|
||||
"""CREATE TABLE IF NOT EXISTS contacts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
type VARCHAR NOT NULL DEFAULT 'supplier',
|
||||
phone VARCHAR,
|
||||
email VARCHAR,
|
||||
address TEXT,
|
||||
notes TEXT,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS expenses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
description VARCHAR NOT NULL,
|
||||
category VARCHAR NOT NULL,
|
||||
contact_id INTEGER REFERENCES contacts(id),
|
||||
total_amount REAL NOT NULL,
|
||||
paid_amount REAL NOT NULL DEFAULT 0.0,
|
||||
due_date DATETIME,
|
||||
business_day_id INTEGER REFERENCES business_days(id),
|
||||
created_by_id INTEGER NOT NULL REFERENCES users(id),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
notes TEXT
|
||||
)""",
|
||||
"""CREATE TABLE IF NOT EXISTS expense_payments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
expense_id INTEGER NOT NULL REFERENCES expenses(id),
|
||||
amount REAL NOT NULL,
|
||||
paid_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
paid_by_id INTEGER NOT NULL REFERENCES users(id),
|
||||
notes TEXT
|
||||
)""",
|
||||
# Phase 2C — notes & todos
|
||||
"""CREATE TABLE IF NOT EXISTS site_notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -353,3 +388,5 @@ app.include_router(data_transfer_router.router, prefix="/api/data-transfer", ta
|
||||
app.include_router(connect_orders_router.router, prefix="/api/connect", tags=["connect"])
|
||||
app.include_router(reservations_router.router, prefix="/api/reservations", tags=["reservations"])
|
||||
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"])
|
||||
|
||||
Reference in New Issue
Block a user