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:
@@ -7,23 +7,47 @@ from .. import database
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_mappings(search: str = "", page: int = 1, per_page: int = 50):
|
||||
"""Get paginated mappings with optional search."""
|
||||
def get_mappings(search: str = "", page: int = 1, per_page: int = 50,
|
||||
sort_by: str = "sku", sort_dir: str = "asc",
|
||||
show_deleted: bool = False):
|
||||
"""Get paginated mappings with optional search and sorting."""
|
||||
if database.pool is None:
|
||||
raise HTTPException(status_code=503, detail="Oracle unavailable")
|
||||
|
||||
offset = (page - 1) * per_page
|
||||
|
||||
# Validate and resolve sort parameters
|
||||
allowed_sort = {
|
||||
"sku": "at.sku",
|
||||
"codmat": "at.codmat",
|
||||
"denumire": "na.denumire",
|
||||
"um": "na.um",
|
||||
"cantitate_roa": "at.cantitate_roa",
|
||||
"procent_pret": "at.procent_pret",
|
||||
"activ": "at.activ",
|
||||
}
|
||||
sort_col = allowed_sort.get(sort_by, "at.sku")
|
||||
if sort_dir.lower() not in ("asc", "desc"):
|
||||
sort_dir = "asc"
|
||||
order_clause = f"{sort_col} {sort_dir}"
|
||||
# Always add secondary sort to keep groups together
|
||||
if sort_col not in ("at.sku",):
|
||||
order_clause += ", at.sku"
|
||||
order_clause += ", at.codmat"
|
||||
|
||||
with database.pool.acquire() as conn:
|
||||
with conn.cursor() as cur:
|
||||
# Build WHERE clause
|
||||
where = ""
|
||||
where_clauses = []
|
||||
params = {}
|
||||
if not show_deleted:
|
||||
where_clauses.append("at.sters = 0")
|
||||
if search:
|
||||
where = """WHERE (UPPER(at.sku) LIKE '%' || UPPER(:search) || '%'
|
||||
where_clauses.append("""(UPPER(at.sku) LIKE '%' || UPPER(:search) || '%'
|
||||
OR UPPER(at.codmat) LIKE '%' || UPPER(:search) || '%'
|
||||
OR UPPER(na.denumire) LIKE '%' || UPPER(:search) || '%')"""
|
||||
OR UPPER(na.denumire) LIKE '%' || UPPER(:search) || '%')""")
|
||||
params["search"] = search
|
||||
where = "WHERE " + " AND ".join(where_clauses) if where_clauses else ""
|
||||
|
||||
# Count total
|
||||
count_sql = f"""
|
||||
@@ -36,13 +60,13 @@ def get_mappings(search: str = "", page: int = 1, per_page: int = 50):
|
||||
|
||||
# Get page
|
||||
data_sql = f"""
|
||||
SELECT at.sku, at.codmat, na.denumire, at.cantitate_roa,
|
||||
at.procent_pret, at.activ,
|
||||
SELECT at.sku, at.codmat, na.denumire, na.um, at.cantitate_roa,
|
||||
at.procent_pret, at.activ, at.sters,
|
||||
TO_CHAR(at.data_creare, 'YYYY-MM-DD HH24:MI') as data_creare
|
||||
FROM ARTICOLE_TERTI at
|
||||
LEFT JOIN nom_articole na ON na.codmat = at.codmat
|
||||
{where}
|
||||
ORDER BY at.sku, at.codmat
|
||||
ORDER BY {order_clause}
|
||||
OFFSET :offset ROWS FETCH NEXT :per_page ROWS ONLY
|
||||
"""
|
||||
params["offset"] = offset
|
||||
@@ -68,8 +92,8 @@ def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret
|
||||
with database.pool.acquire() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ, data_creare, id_util_creare)
|
||||
VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, SYSDATE, -3)
|
||||
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ, sters, data_creare, id_util_creare)
|
||||
VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, 0, SYSDATE, -3)
|
||||
""", {"sku": sku, "codmat": codmat, "cantitate_roa": cantitate_roa, "procent_pret": procent_pret})
|
||||
conn.commit()
|
||||
return {"sku": sku, "codmat": codmat}
|
||||
@@ -108,8 +132,68 @@ def update_mapping(sku: str, codmat: str, cantitate_roa: float = None, procent_p
|
||||
return cur.rowcount > 0
|
||||
|
||||
def delete_mapping(sku: str, codmat: str):
|
||||
"""Soft delete (set activ=0)."""
|
||||
return update_mapping(sku, codmat, activ=0)
|
||||
"""Soft delete (set sters=1)."""
|
||||
if database.pool is None:
|
||||
raise HTTPException(status_code=503, detail="Oracle unavailable")
|
||||
|
||||
with database.pool.acquire() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
UPDATE ARTICOLE_TERTI SET sters = 1, data_modif = SYSDATE
|
||||
WHERE sku = :sku AND codmat = :codmat
|
||||
""", {"sku": sku, "codmat": codmat})
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
|
||||
def edit_mapping(old_sku: str, old_codmat: str, new_sku: str, new_codmat: str,
|
||||
cantitate_roa: float = 1, procent_pret: float = 100):
|
||||
"""Edit a mapping. If PK changed, soft-delete old and insert new."""
|
||||
if database.pool is None:
|
||||
raise HTTPException(status_code=503, detail="Oracle unavailable")
|
||||
|
||||
if old_sku == new_sku and old_codmat == new_codmat:
|
||||
# Simple update - only cantitate/procent changed
|
||||
return update_mapping(new_sku, new_codmat, cantitate_roa, procent_pret)
|
||||
else:
|
||||
# PK changed: soft-delete old, upsert new (MERGE handles existing soft-deleted target)
|
||||
with database.pool.acquire() as conn:
|
||||
with conn.cursor() as cur:
|
||||
# Mark old record as deleted
|
||||
cur.execute("""
|
||||
UPDATE ARTICOLE_TERTI SET sters = 1, data_modif = SYSDATE
|
||||
WHERE sku = :sku AND codmat = :codmat
|
||||
""", {"sku": old_sku, "codmat": old_codmat})
|
||||
# Upsert new record (MERGE in case target PK exists as soft-deleted)
|
||||
cur.execute("""
|
||||
MERGE INTO ARTICOLE_TERTI t
|
||||
USING (SELECT :sku AS sku, :codmat AS codmat FROM DUAL) s
|
||||
ON (t.sku = s.sku AND t.codmat = s.codmat)
|
||||
WHEN MATCHED THEN UPDATE SET
|
||||
cantitate_roa = :cantitate_roa,
|
||||
procent_pret = :procent_pret,
|
||||
activ = 1, sters = 0,
|
||||
data_modif = SYSDATE
|
||||
WHEN NOT MATCHED THEN INSERT
|
||||
(sku, codmat, cantitate_roa, procent_pret, activ, sters, data_creare, id_util_creare)
|
||||
VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, 0, SYSDATE, -3)
|
||||
""", {"sku": new_sku, "codmat": new_codmat,
|
||||
"cantitate_roa": cantitate_roa, "procent_pret": procent_pret})
|
||||
conn.commit()
|
||||
return True
|
||||
|
||||
def restore_mapping(sku: str, codmat: str):
|
||||
"""Restore a soft-deleted mapping (set sters=0)."""
|
||||
if database.pool is None:
|
||||
raise HTTPException(status_code=503, detail="Oracle unavailable")
|
||||
|
||||
with database.pool.acquire() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
UPDATE ARTICOLE_TERTI SET sters = 0, data_modif = SYSDATE
|
||||
WHERE sku = :sku AND codmat = :codmat
|
||||
""", {"sku": sku, "codmat": codmat})
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
|
||||
def import_csv(file_content: str):
|
||||
"""Import mappings from CSV content. Returns summary."""
|
||||
@@ -143,10 +227,11 @@ def import_csv(file_content: str):
|
||||
cantitate_roa = :cantitate_roa,
|
||||
procent_pret = :procent_pret,
|
||||
activ = 1,
|
||||
sters = 0,
|
||||
data_modif = SYSDATE
|
||||
WHEN NOT MATCHED THEN INSERT
|
||||
(sku, codmat, cantitate_roa, procent_pret, activ, data_creare, id_util_creare)
|
||||
VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, SYSDATE, -3)
|
||||
(sku, codmat, cantitate_roa, procent_pret, activ, sters, data_creare, id_util_creare)
|
||||
VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, 0, SYSDATE, -3)
|
||||
""", {"sku": sku, "codmat": codmat, "cantitate_roa": cantitate, "procent_pret": procent})
|
||||
|
||||
# Check if it was insert or update by rowcount
|
||||
@@ -172,7 +257,7 @@ def export_csv():
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT sku, codmat, cantitate_roa, procent_pret, activ
|
||||
FROM ARTICOLE_TERTI ORDER BY sku, codmat
|
||||
FROM ARTICOLE_TERTI WHERE sters = 0 ORDER BY sku, codmat
|
||||
""")
|
||||
for row in cur:
|
||||
writer.writerow(row)
|
||||
|
||||
Reference in New Issue
Block a user