feat(sqlite): refactor orders schema + dashboard period filter

Replace import_orders (insert-per-run) with orders table (one row per
order, upsert on conflict). Eliminates dedup CTE on every dashboard
query and prevents unbounded row growth at 4-500 orders/sync.

Key changes:
- orders table: PK order_number, upsert via ON CONFLICT DO UPDATE;
  COALESCE preserves id_comanda once set; times_skipped auto-increments
- sync_run_orders: lightweight junction (sync_run_id, order_number)
  replaces sync_run_id column on orders
- order_items: PK changed to (order_number, sku), INSERT OR IGNORE
- Auto-migration in init_sqlite(): import_orders → orders on first boot,
  old table renamed to import_orders_bak
- /api/dashboard/orders: period_days param (3/7/30/0=all, default 7)
- Dashboard: period selector buttons in orders card header
- start.sh: stop existing process on port 5003 before restart;
  remove --reload (broken on WSL2 /mnt/e/)
- Add invoice_service, E2E Playwright tests, Oracle package updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 16:18:57 +02:00
parent 650e98539e
commit 82196b9dc0
32 changed files with 4164 additions and 1192 deletions

View File

@@ -76,19 +76,31 @@ CREATE TABLE IF NOT EXISTS sync_runs (
error_message TEXT
);
CREATE TABLE IF NOT EXISTS import_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_run_id TEXT REFERENCES sync_runs(run_id),
order_number TEXT,
order_date TEXT,
customer_name TEXT,
status TEXT,
id_comanda INTEGER,
id_partener INTEGER,
error_message TEXT,
missing_skus TEXT,
items_count INTEGER,
created_at TEXT DEFAULT (datetime('now'))
CREATE TABLE IF NOT EXISTS orders (
order_number TEXT PRIMARY KEY,
order_date TEXT,
customer_name TEXT,
status TEXT,
id_comanda INTEGER,
id_partener INTEGER,
id_adresa_facturare INTEGER,
id_adresa_livrare INTEGER,
error_message TEXT,
missing_skus TEXT,
items_count INTEGER,
times_skipped INTEGER DEFAULT 0,
first_seen_at TEXT DEFAULT (datetime('now')),
last_sync_run_id TEXT REFERENCES sync_runs(run_id),
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
CREATE INDEX IF NOT EXISTS idx_orders_date ON orders(order_date);
CREATE TABLE IF NOT EXISTS sync_run_orders (
sync_run_id TEXT REFERENCES sync_runs(run_id),
order_number TEXT REFERENCES orders(order_number),
status_at_run TEXT,
PRIMARY KEY (sync_run_id, order_number)
);
CREATE TABLE IF NOT EXISTS missing_skus (
@@ -106,6 +118,30 @@ CREATE TABLE IF NOT EXISTS scheduler_config (
key TEXT PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS web_products (
sku TEXT PRIMARY KEY,
product_name TEXT,
first_seen TEXT DEFAULT (datetime('now')),
last_seen TEXT DEFAULT (datetime('now')),
order_count INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS order_items (
order_number TEXT,
sku TEXT,
product_name TEXT,
quantity REAL,
price REAL,
vat REAL,
mapping_status TEXT,
codmat TEXT,
id_articol INTEGER,
cantitate_roa REAL,
created_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (order_number, sku)
);
CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_number);
"""
_sqlite_db_path = None
@@ -122,6 +158,101 @@ def init_sqlite():
# Create tables synchronously
conn = sqlite3.connect(_sqlite_db_path)
# Check existing tables before running schema
cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
existing_tables = {row[0] for row in cursor.fetchall()}
# Migration: import_orders → orders (one row per order)
if 'import_orders' in existing_tables and 'orders' not in existing_tables:
logger.info("Migrating import_orders → orders schema...")
conn.executescript("""
CREATE TABLE orders (
order_number TEXT PRIMARY KEY,
order_date TEXT,
customer_name TEXT,
status TEXT,
id_comanda INTEGER,
id_partener INTEGER,
id_adresa_facturare INTEGER,
id_adresa_livrare INTEGER,
error_message TEXT,
missing_skus TEXT,
items_count INTEGER,
times_skipped INTEGER DEFAULT 0,
first_seen_at TEXT DEFAULT (datetime('now')),
last_sync_run_id TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
CREATE INDEX IF NOT EXISTS idx_orders_date ON orders(order_date);
CREATE TABLE sync_run_orders (
sync_run_id TEXT,
order_number TEXT,
status_at_run TEXT,
PRIMARY KEY (sync_run_id, order_number)
);
""")
# Copy latest record per order_number into orders
conn.execute("""
INSERT INTO orders
(order_number, order_date, customer_name, status,
id_comanda, id_partener, id_adresa_facturare, id_adresa_livrare,
error_message, missing_skus, items_count, last_sync_run_id)
SELECT io.order_number, io.order_date, io.customer_name, io.status,
io.id_comanda, io.id_partener,
CASE WHEN io.order_number IN (SELECT order_number FROM import_orders WHERE id_adresa_facturare IS NOT NULL) THEN
(SELECT id_adresa_facturare FROM import_orders WHERE order_number = io.order_number AND id_adresa_facturare IS NOT NULL LIMIT 1) ELSE NULL END,
CASE WHEN io.order_number IN (SELECT order_number FROM import_orders WHERE id_adresa_livrare IS NOT NULL) THEN
(SELECT id_adresa_livrare FROM import_orders WHERE order_number = io.order_number AND id_adresa_livrare IS NOT NULL LIMIT 1) ELSE NULL END,
io.error_message, io.missing_skus, io.items_count, io.sync_run_id
FROM import_orders io
INNER JOIN (
SELECT order_number, MAX(id) as max_id
FROM import_orders
GROUP BY order_number
) latest ON io.id = latest.max_id
""")
# Populate sync_run_orders from all import_orders rows
conn.execute("""
INSERT OR IGNORE INTO sync_run_orders (sync_run_id, order_number, status_at_run)
SELECT sync_run_id, order_number, status
FROM import_orders
WHERE sync_run_id IS NOT NULL
""")
# Migrate order_items: drop sync_run_id, change PK to (order_number, sku)
if 'order_items' in existing_tables:
conn.executescript("""
CREATE TABLE order_items_new (
order_number TEXT,
sku TEXT,
product_name TEXT,
quantity REAL,
price REAL,
vat REAL,
mapping_status TEXT,
codmat TEXT,
id_articol INTEGER,
cantitate_roa REAL,
created_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (order_number, sku)
);
INSERT OR IGNORE INTO order_items_new
(order_number, sku, product_name, quantity, price, vat,
mapping_status, codmat, id_articol, cantitate_roa, created_at)
SELECT order_number, sku, product_name, quantity, price, vat,
mapping_status, codmat, id_articol, cantitate_roa, created_at
FROM order_items;
DROP TABLE order_items;
ALTER TABLE order_items_new RENAME TO order_items;
CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_number);
""")
# Rename old table instead of dropping (safety backup)
conn.execute("ALTER TABLE import_orders RENAME TO import_orders_bak")
conn.commit()
logger.info("Migration complete: import_orders → orders")
conn.executescript(SQLITE_SCHEMA)
# Migrate: add columns if missing (for existing databases)
@@ -140,6 +271,7 @@ def init_sqlite():
if "error_message" not in sync_cols:
conn.execute("ALTER TABLE sync_runs ADD COLUMN error_message TEXT")
logger.info("Migrated sync_runs: added column error_message")
conn.commit()
except Exception as e:
logger.warning(f"Migration check failed: {e}")