diff --git a/api/app/routers/sync.py b/api/app/routers/sync.py
index d416589..cd47a19 100644
--- a/api/app/routers/sync.py
+++ b/api/app/routers/sync.py
@@ -492,34 +492,73 @@ async def dashboard_orders(page: int = 1, per_page: int = 50,
@router.post("/api/dashboard/refresh-invoices")
async def refresh_invoices():
- """Force-refresh invoice status from Oracle for all uninvoiced imported orders."""
- try:
- uninvoiced = await sqlite_service.get_uninvoiced_imported_orders()
- if not uninvoiced:
- return {"updated": 0, "message": "Nicio comanda de verificat"}
+ """Force-refresh invoice/order status from Oracle.
- id_comanda_list = [o["id_comanda"] for o in uninvoiced]
- invoice_data = await asyncio.to_thread(
- invoice_service.check_invoices_for_orders, id_comanda_list
- )
- id_to_order = {o["id_comanda"]: o["order_number"] for o in uninvoiced}
- updated = 0
- for idc, inv in invoice_data.items():
- order_num = id_to_order.get(idc)
- if order_num and inv.get("facturat"):
- await sqlite_service.update_order_invoice(
- order_num,
- serie=inv.get("serie_act"),
- numar=str(inv.get("numar_act", "")),
- total_fara_tva=inv.get("total_fara_tva"),
- total_tva=inv.get("total_tva"),
- total_cu_tva=inv.get("total_cu_tva"),
- data_act=inv.get("data_act"),
- )
- updated += 1
- return {"updated": updated, "checked": len(uninvoiced)}
+ Checks:
+ 1. Uninvoiced orders → did they get invoiced?
+ 2. Invoiced orders → was the invoice deleted?
+ 3. All imported orders → was the order deleted from ROA?
+ """
+ try:
+ invoices_added = 0
+ invoices_cleared = 0
+ orders_deleted = 0
+
+ # 1. Check uninvoiced → new invoices
+ uninvoiced = await sqlite_service.get_uninvoiced_imported_orders()
+ if uninvoiced:
+ id_comanda_list = [o["id_comanda"] for o in uninvoiced]
+ invoice_data = await asyncio.to_thread(
+ invoice_service.check_invoices_for_orders, id_comanda_list
+ )
+ id_to_order = {o["id_comanda"]: o["order_number"] for o in uninvoiced}
+ for idc, inv in invoice_data.items():
+ order_num = id_to_order.get(idc)
+ if order_num and inv.get("facturat"):
+ await sqlite_service.update_order_invoice(
+ order_num,
+ serie=inv.get("serie_act"),
+ numar=str(inv.get("numar_act", "")),
+ total_fara_tva=inv.get("total_fara_tva"),
+ total_tva=inv.get("total_tva"),
+ total_cu_tva=inv.get("total_cu_tva"),
+ data_act=inv.get("data_act"),
+ )
+ invoices_added += 1
+
+ # 2. Check invoiced → deleted invoices
+ invoiced = await sqlite_service.get_invoiced_imported_orders()
+ if invoiced:
+ id_comanda_list = [o["id_comanda"] for o in invoiced]
+ invoice_data = await asyncio.to_thread(
+ invoice_service.check_invoices_for_orders, id_comanda_list
+ )
+ for o in invoiced:
+ if o["id_comanda"] not in invoice_data:
+ await sqlite_service.clear_order_invoice(o["order_number"])
+ invoices_cleared += 1
+
+ # 3. Check all imported → deleted orders in ROA
+ all_imported = await sqlite_service.get_all_imported_orders()
+ if all_imported:
+ id_comanda_list = [o["id_comanda"] for o in all_imported]
+ existing_ids = await asyncio.to_thread(
+ invoice_service.check_orders_exist, id_comanda_list
+ )
+ for o in all_imported:
+ if o["id_comanda"] not in existing_ids:
+ await sqlite_service.mark_order_deleted_in_roa(o["order_number"])
+ orders_deleted += 1
+
+ checked = len(uninvoiced) + len(invoiced) + len(all_imported)
+ return {
+ "checked": checked,
+ "invoices_added": invoices_added,
+ "invoices_cleared": invoices_cleared,
+ "orders_deleted": orders_deleted,
+ }
except Exception as e:
- return {"error": str(e), "updated": 0}
+ return {"error": str(e), "invoices_added": 0}
@router.put("/api/sync/schedule")
diff --git a/api/app/services/invoice_service.py b/api/app/services/invoice_service.py
index 3c41f16..05b4e89 100644
--- a/api/app/services/invoice_service.py
+++ b/api/app/services/invoice_service.py
@@ -43,3 +43,33 @@ def check_invoices_for_orders(id_comanda_list: list) -> dict:
database.pool.release(conn)
return result
+
+
+def check_orders_exist(id_comanda_list: list) -> set:
+ """Check which id_comanda values still exist in Oracle COMENZI (sters=0).
+ Returns set of id_comanda that exist.
+ """
+ if not id_comanda_list or database.pool is None:
+ return set()
+
+ existing = set()
+ conn = database.get_oracle_connection()
+ try:
+ with conn.cursor() as cur:
+ for i in range(0, len(id_comanda_list), 500):
+ batch = id_comanda_list[i:i+500]
+ placeholders = ",".join([f":c{j}" for j in range(len(batch))])
+ params = {f"c{j}": cid for j, cid in enumerate(batch)}
+
+ cur.execute(f"""
+ SELECT id_comanda FROM COMENZI
+ WHERE id_comanda IN ({placeholders}) AND sters = 0
+ """, params)
+ for row in cur:
+ existing.add(row[0])
+ except Exception as e:
+ logger.warning(f"Order existence check failed: {e}")
+ finally:
+ database.pool.release(conn)
+
+ return existing
diff --git a/api/app/services/sqlite_service.py b/api/app/services/sqlite_service.py
index a872470..e96d1d9 100644
--- a/api/app/services/sqlite_service.py
+++ b/api/app/services/sqlite_service.py
@@ -781,6 +781,83 @@ async def update_order_invoice(order_number: str, serie: str = None,
await db.close()
+async def get_invoiced_imported_orders() -> list:
+ """Get imported orders that HAVE cached invoice data (for re-verification)."""
+ db = await get_sqlite()
+ try:
+ cursor = await db.execute("""
+ SELECT order_number, id_comanda FROM orders
+ WHERE status IN ('IMPORTED', 'ALREADY_IMPORTED')
+ AND id_comanda IS NOT NULL
+ AND factura_numar IS NOT NULL AND factura_numar != ''
+ """)
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+ finally:
+ await db.close()
+
+
+async def get_all_imported_orders() -> list:
+ """Get ALL imported orders with id_comanda (for checking if deleted in ROA)."""
+ db = await get_sqlite()
+ try:
+ cursor = await db.execute("""
+ SELECT order_number, id_comanda FROM orders
+ WHERE status IN ('IMPORTED', 'ALREADY_IMPORTED')
+ AND id_comanda IS NOT NULL
+ """)
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+ finally:
+ await db.close()
+
+
+async def clear_order_invoice(order_number: str):
+ """Clear cached invoice data when invoice was deleted in ROA."""
+ db = await get_sqlite()
+ try:
+ await db.execute("""
+ UPDATE orders SET
+ factura_serie = NULL,
+ factura_numar = NULL,
+ factura_total_fara_tva = NULL,
+ factura_total_tva = NULL,
+ factura_total_cu_tva = NULL,
+ factura_data = NULL,
+ invoice_checked_at = datetime('now'),
+ updated_at = datetime('now')
+ WHERE order_number = ?
+ """, (order_number,))
+ await db.commit()
+ finally:
+ await db.close()
+
+
+async def mark_order_deleted_in_roa(order_number: str):
+ """Mark an order as deleted in ROA — clears id_comanda and invoice cache."""
+ db = await get_sqlite()
+ try:
+ await db.execute("""
+ UPDATE orders SET
+ status = 'DELETED_IN_ROA',
+ id_comanda = NULL,
+ id_partener = NULL,
+ factura_serie = NULL,
+ factura_numar = NULL,
+ factura_total_fara_tva = NULL,
+ factura_total_tva = NULL,
+ factura_total_cu_tva = NULL,
+ factura_data = NULL,
+ invoice_checked_at = NULL,
+ error_message = 'Comanda stearsa din ROA',
+ updated_at = datetime('now')
+ WHERE order_number = ?
+ """, (order_number,))
+ await db.commit()
+ finally:
+ await db.close()
+
+
# ── App Settings ─────────────────────────────────
async def get_app_settings() -> dict:
diff --git a/api/app/services/sync_service.py b/api/app/services/sync_service.py
index 6b8104a..1c52892 100644
--- a/api/app/services/sync_service.py
+++ b/api/app/services/sync_service.py
@@ -463,17 +463,19 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
logger.warning("Too many errors, stopping sync")
break
- # Step 4b: Invoice check — update cached invoice data
- _update_progress("invoices", "Checking invoices...", 0, 0)
+ # Step 4b: Invoice & order status check — sync with Oracle
+ _update_progress("invoices", "Checking invoices & order status...", 0, 0)
invoices_updated = 0
+ invoices_cleared = 0
+ orders_deleted = 0
try:
+ # 4b-1: Uninvoiced → check for new invoices
uninvoiced = await sqlite_service.get_uninvoiced_imported_orders()
if uninvoiced:
id_comanda_list = [o["id_comanda"] for o in uninvoiced]
invoice_data = await asyncio.to_thread(
invoice_service.check_invoices_for_orders, id_comanda_list
)
- # Build reverse map: id_comanda → order_number
id_to_order = {o["id_comanda"]: o["order_number"] for o in uninvoiced}
for idc, inv in invoice_data.items():
order_num = id_to_order.get(idc)
@@ -488,10 +490,39 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
data_act=inv.get("data_act"),
)
invoices_updated += 1
- if invoices_updated:
- _log_line(run_id, f"Facturi actualizate: {invoices_updated} comenzi facturate")
+
+ # 4b-2: Invoiced → check for deleted invoices
+ invoiced = await sqlite_service.get_invoiced_imported_orders()
+ if invoiced:
+ id_comanda_list = [o["id_comanda"] for o in invoiced]
+ invoice_data = await asyncio.to_thread(
+ invoice_service.check_invoices_for_orders, id_comanda_list
+ )
+ for o in invoiced:
+ if o["id_comanda"] not in invoice_data:
+ await sqlite_service.clear_order_invoice(o["order_number"])
+ invoices_cleared += 1
+
+ # 4b-3: All imported → check for deleted orders in ROA
+ all_imported = await sqlite_service.get_all_imported_orders()
+ if all_imported:
+ id_comanda_list = [o["id_comanda"] for o in all_imported]
+ existing_ids = await asyncio.to_thread(
+ invoice_service.check_orders_exist, id_comanda_list
+ )
+ for o in all_imported:
+ if o["id_comanda"] not in existing_ids:
+ await sqlite_service.mark_order_deleted_in_roa(o["order_number"])
+ orders_deleted += 1
+
+ if invoices_updated:
+ _log_line(run_id, f"Facturi noi: {invoices_updated} comenzi facturate")
+ if invoices_cleared:
+ _log_line(run_id, f"Facturi sterse: {invoices_cleared} facturi eliminate din cache")
+ if orders_deleted:
+ _log_line(run_id, f"Comenzi sterse din ROA: {orders_deleted}")
except Exception as e:
- logger.warning(f"Invoice check failed: {e}")
+ logger.warning(f"Invoice/order status check failed: {e}")
# Step 5: Update sync run
total_imported = imported_count + already_imported_count # backward-compat
@@ -514,6 +545,8 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
"errors": error_count,
"missing_skus": len(validation["missing"]),
"invoices_updated": invoices_updated,
+ "invoices_cleared": invoices_cleared,
+ "orders_deleted_in_roa": orders_deleted,
}
_update_progress("completed",
diff --git a/api/app/static/js/dashboard.js b/api/app/static/js/dashboard.js
index 0e3bf5a..9cb5fe3 100644
--- a/api/app/static/js/dashboard.js
+++ b/api/app/static/js/dashboard.js
@@ -437,6 +437,7 @@ function orderStatusBadge(status) {
case 'ALREADY_IMPORTED': return 'Deja importat';
case 'SKIPPED': return 'Omis';
case 'ERROR': return 'Eroare';
+ case 'DELETED_IN_ROA': return 'Sters din ROA';
default: return `${esc(status)}`;
}
}
diff --git a/api/app/static/js/logs.js b/api/app/static/js/logs.js
index 0a3f0e3..cc17658 100644
--- a/api/app/static/js/logs.js
+++ b/api/app/static/js/logs.js
@@ -38,6 +38,7 @@ function orderStatusBadge(status) {
case 'ALREADY_IMPORTED': return 'Deja importat';
case 'SKIPPED': return 'Omis';
case 'ERROR': return 'Eroare';
+ case 'DELETED_IN_ROA': return 'Sters din ROA';
default: return `${esc(status)}`;
}
}