feat(sync): handle cancelled GoMag orders (status Anulata / statusId 7)

- Add web_status column to orders table (generic name for platform status)
- Filter cancelled orders during sync, record as CANCELLED in SQLite
- Soft-delete previously-imported cancelled orders in Oracle (if not invoiced)
- Add CANCELLED filter pill + badge in dashboard UI
- New soft_delete_order_in_roa() and mark_order_cancelled() functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-16 21:50:38 +00:00
parent 8020b2d14b
commit 5e01fefd4c
9 changed files with 215 additions and 20 deletions

View File

@@ -109,7 +109,8 @@ CREATE TABLE IF NOT EXISTS orders (
invoice_checked_at TEXT, invoice_checked_at TEXT,
order_total REAL, order_total REAL,
delivery_cost REAL, delivery_cost REAL,
discount_total REAL discount_total REAL,
web_status TEXT
); );
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status); CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
CREATE INDEX IF NOT EXISTS idx_orders_date ON orders(order_date); CREATE INDEX IF NOT EXISTS idx_orders_date ON orders(order_date);
@@ -316,6 +317,7 @@ def init_sqlite():
("order_total", "REAL"), ("order_total", "REAL"),
("delivery_cost", "REAL"), ("delivery_cost", "REAL"),
("discount_total", "REAL"), ("discount_total", "REAL"),
("web_status", "TEXT"),
]: ]:
if col not in order_cols: if col not in order_cols:
conn.execute(f"ALTER TABLE orders ADD COLUMN {col} {typedef}") conn.execute(f"ALTER TABLE orders ADD COLUMN {col} {typedef}")

View File

@@ -294,3 +294,51 @@ def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_se
pass pass
return result return result
def soft_delete_order_in_roa(id_comanda: int) -> dict:
"""Soft-delete an order in Oracle ROA (set sters=1 on comenzi + comenzi_detalii).
Returns {"success": bool, "error": str|None, "details_deleted": int}
"""
result = {"success": False, "error": None, "details_deleted": 0}
if database.pool is None:
result["error"] = "Oracle pool not initialized"
return result
conn = None
try:
conn = database.pool.acquire()
with conn.cursor() as cur:
# Soft-delete order details
cur.execute(
"UPDATE comenzi_detalii SET sters = 1 WHERE id_comanda = :1 AND sters = 0",
[id_comanda]
)
result["details_deleted"] = cur.rowcount
# Soft-delete the order itself
cur.execute(
"UPDATE comenzi SET sters = 1 WHERE id_comanda = :1 AND sters = 0",
[id_comanda]
)
conn.commit()
result["success"] = True
logger.info(f"Soft-deleted order ID={id_comanda} in Oracle ROA ({result['details_deleted']} details)")
except Exception as e:
result["error"] = str(e)
logger.error(f"Error soft-deleting order ID={id_comanda}: {e}")
if conn:
try:
conn.rollback()
except Exception:
pass
finally:
if conn:
try:
database.pool.release(conn)
except Exception:
pass
return result

View File

@@ -52,7 +52,8 @@ async def upsert_order(sync_run_id: str, order_number: str, order_date: str,
shipping_name: str = None, billing_name: str = None, shipping_name: str = None, billing_name: str = None,
payment_method: str = None, delivery_method: str = None, payment_method: str = None, delivery_method: str = None,
order_total: float = None, order_total: float = None,
delivery_cost: float = None, discount_total: float = None): delivery_cost: float = None, discount_total: float = None,
web_status: str = None):
"""Upsert a single order — one row per order_number, status updated in place.""" """Upsert a single order — one row per order_number, status updated in place."""
db = await get_sqlite() db = await get_sqlite()
try: try:
@@ -62,8 +63,8 @@ async def upsert_order(sync_run_id: str, order_number: str, order_date: str,
id_comanda, id_partener, error_message, missing_skus, items_count, id_comanda, id_partener, error_message, missing_skus, items_count,
last_sync_run_id, shipping_name, billing_name, last_sync_run_id, shipping_name, billing_name,
payment_method, delivery_method, order_total, payment_method, delivery_method, order_total,
delivery_cost, discount_total) delivery_cost, discount_total, web_status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(order_number) DO UPDATE SET ON CONFLICT(order_number) DO UPDATE SET
customer_name = excluded.customer_name, customer_name = excluded.customer_name,
status = CASE status = CASE
@@ -87,13 +88,14 @@ async def upsert_order(sync_run_id: str, order_number: str, order_date: str,
order_total = COALESCE(excluded.order_total, orders.order_total), order_total = COALESCE(excluded.order_total, orders.order_total),
delivery_cost = COALESCE(excluded.delivery_cost, orders.delivery_cost), delivery_cost = COALESCE(excluded.delivery_cost, orders.delivery_cost),
discount_total = COALESCE(excluded.discount_total, orders.discount_total), discount_total = COALESCE(excluded.discount_total, orders.discount_total),
web_status = COALESCE(excluded.web_status, orders.web_status),
updated_at = datetime('now') updated_at = datetime('now')
""", (order_number, order_date, customer_name, status, """, (order_number, order_date, customer_name, status,
id_comanda, id_partener, error_message, id_comanda, id_partener, error_message,
json.dumps(missing_skus) if missing_skus else None, json.dumps(missing_skus) if missing_skus else None,
items_count, sync_run_id, shipping_name, billing_name, items_count, sync_run_id, shipping_name, billing_name,
payment_method, delivery_method, order_total, payment_method, delivery_method, order_total,
delivery_cost, discount_total)) delivery_cost, discount_total, web_status))
await db.commit() await db.commit()
finally: finally:
await db.close() await db.close()
@@ -118,7 +120,8 @@ async def save_orders_batch(orders_data: list[dict]):
Each dict must have: sync_run_id, order_number, order_date, customer_name, status, Each dict must have: sync_run_id, order_number, order_date, customer_name, status,
id_comanda, id_partener, error_message, missing_skus (list|None), items_count, id_comanda, id_partener, error_message, missing_skus (list|None), items_count,
shipping_name, billing_name, payment_method, delivery_method, status_at_run, shipping_name, billing_name, payment_method, delivery_method, status_at_run,
items (list of item dicts), delivery_cost (optional), discount_total (optional). items (list of item dicts), delivery_cost (optional), discount_total (optional),
web_status (optional).
""" """
if not orders_data: if not orders_data:
return return
@@ -131,8 +134,8 @@ async def save_orders_batch(orders_data: list[dict]):
id_comanda, id_partener, error_message, missing_skus, items_count, id_comanda, id_partener, error_message, missing_skus, items_count,
last_sync_run_id, shipping_name, billing_name, last_sync_run_id, shipping_name, billing_name,
payment_method, delivery_method, order_total, payment_method, delivery_method, order_total,
delivery_cost, discount_total) delivery_cost, discount_total, web_status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(order_number) DO UPDATE SET ON CONFLICT(order_number) DO UPDATE SET
customer_name = excluded.customer_name, customer_name = excluded.customer_name,
status = CASE status = CASE
@@ -156,6 +159,7 @@ async def save_orders_batch(orders_data: list[dict]):
order_total = COALESCE(excluded.order_total, orders.order_total), order_total = COALESCE(excluded.order_total, orders.order_total),
delivery_cost = COALESCE(excluded.delivery_cost, orders.delivery_cost), delivery_cost = COALESCE(excluded.delivery_cost, orders.delivery_cost),
discount_total = COALESCE(excluded.discount_total, orders.discount_total), discount_total = COALESCE(excluded.discount_total, orders.discount_total),
web_status = COALESCE(excluded.web_status, orders.web_status),
updated_at = datetime('now') updated_at = datetime('now')
""", [ """, [
(d["order_number"], d["order_date"], d["customer_name"], d["status"], (d["order_number"], d["order_date"], d["customer_name"], d["status"],
@@ -165,7 +169,8 @@ async def save_orders_batch(orders_data: list[dict]):
d.get("shipping_name"), d.get("billing_name"), d.get("shipping_name"), d.get("billing_name"),
d.get("payment_method"), d.get("delivery_method"), d.get("payment_method"), d.get("delivery_method"),
d.get("order_total"), d.get("order_total"),
d.get("delivery_cost"), d.get("discount_total")) d.get("delivery_cost"), d.get("discount_total"),
d.get("web_status"))
for d in orders_data for d in orders_data
]) ])
@@ -619,6 +624,7 @@ async def get_run_orders_filtered(run_id: str, status_filter: str = "all",
"skipped": status_counts.get("SKIPPED", 0), "skipped": status_counts.get("SKIPPED", 0),
"error": status_counts.get("ERROR", 0), "error": status_counts.get("ERROR", 0),
"already_imported": status_counts.get("ALREADY_IMPORTED", 0), "already_imported": status_counts.get("ALREADY_IMPORTED", 0),
"cancelled": status_counts.get("CANCELLED", 0),
"total": sum(status_counts.values()) "total": sum(status_counts.values())
} }
} }
@@ -715,6 +721,7 @@ async def get_orders(page: int = 1, per_page: int = 50,
"imported_all": status_counts.get("IMPORTED", 0) + status_counts.get("ALREADY_IMPORTED", 0), "imported_all": status_counts.get("IMPORTED", 0) + status_counts.get("ALREADY_IMPORTED", 0),
"skipped": status_counts.get("SKIPPED", 0), "skipped": status_counts.get("SKIPPED", 0),
"error": status_counts.get("ERROR", 0), "error": status_counts.get("ERROR", 0),
"cancelled": status_counts.get("CANCELLED", 0),
"total": sum(status_counts.values()), "total": sum(status_counts.values()),
"uninvoiced_sqlite": uninvoiced_sqlite, "uninvoiced_sqlite": uninvoiced_sqlite,
} }
@@ -860,6 +867,32 @@ async def mark_order_deleted_in_roa(order_number: str):
await db.close() await db.close()
async def mark_order_cancelled(order_number: str, web_status: str = "Anulata"):
"""Mark an order as cancelled from GoMag. Clears id_comanda and invoice cache."""
db = await get_sqlite()
try:
await db.execute("""
UPDATE orders SET
status = 'CANCELLED',
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,
web_status = ?,
error_message = 'Comanda anulata in GoMag',
updated_at = datetime('now')
WHERE order_number = ?
""", (web_status, order_number))
await db.commit()
finally:
await db.close()
# ── App Settings ───────────────────────────────── # ── App Settings ─────────────────────────────────
async def get_app_settings() -> dict: async def get_app_settings() -> dict:

View File

@@ -72,7 +72,7 @@ async def prepare_sync(id_pol: int = None, id_sectie: int = None) -> dict:
"phase_text": "Starting...", "phase_text": "Starting...",
"progress_current": 0, "progress_current": 0,
"progress_total": 0, "progress_total": 0,
"counts": {"imported": 0, "skipped": 0, "errors": 0, "already_imported": 0}, "counts": {"imported": 0, "skipped": 0, "errors": 0, "already_imported": 0, "cancelled": 0},
} }
return {"run_id": run_id, "status": "starting"} return {"run_id": run_id, "status": "starting"}
@@ -152,7 +152,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
"phase_text": "Reading JSON files...", "phase_text": "Reading JSON files...",
"progress_current": 0, "progress_current": 0,
"progress_total": 0, "progress_total": 0,
"counts": {"imported": 0, "skipped": 0, "errors": 0, "already_imported": 0}, "counts": {"imported": 0, "skipped": 0, "errors": 0, "already_imported": 0, "cancelled": 0},
} }
_update_progress("reading", "Reading JSON files...") _update_progress("reading", "Reading JSON files...")
@@ -212,6 +212,104 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
summary = {"run_id": run_id, "status": "completed", "message": "No orders found", "json_files": json_count} summary = {"run_id": run_id, "status": "completed", "message": "No orders found", "json_files": json_count}
return summary return summary
# ── Separate cancelled orders (GoMag status "Anulata" / statusId "7") ──
cancelled_orders = [o for o in orders if o.status_id == "7" or (o.status and o.status.lower() == "anulata")]
active_orders = [o for o in orders if o not in cancelled_orders]
cancelled_count = len(cancelled_orders)
if cancelled_orders:
_log_line(run_id, f"Comenzi anulate in GoMag: {cancelled_count}")
# Record cancelled orders in SQLite
cancelled_batch = []
for order in cancelled_orders:
shipping_name, billing_name, customer, payment_method, delivery_method = _derive_customer_info(order)
order_items_data = [
{"sku": item.sku, "product_name": item.name,
"quantity": item.quantity, "price": item.price, "vat": item.vat,
"mapping_status": "unknown", "codmat": None,
"id_articol": None, "cantitate_roa": None}
for item in order.items
]
cancelled_batch.append({
"sync_run_id": run_id, "order_number": order.number,
"order_date": order.date, "customer_name": customer,
"status": "CANCELLED", "status_at_run": "CANCELLED",
"id_comanda": None, "id_partener": None,
"error_message": "Comanda anulata in GoMag",
"missing_skus": None,
"items_count": len(order.items),
"shipping_name": shipping_name, "billing_name": billing_name,
"payment_method": payment_method, "delivery_method": delivery_method,
"order_total": order.total or None,
"delivery_cost": order.delivery_cost or None,
"discount_total": order.discount_total or None,
"web_status": order.status or "Anulata",
"items": order_items_data,
})
_log_line(run_id, f"#{order.number} [{order.date or '?'}] {customer} → ANULAT in GoMag")
await sqlite_service.save_orders_batch(cancelled_batch)
# Check if any cancelled orders were previously imported
from ..database import get_sqlite as _get_sqlite
db_check = await _get_sqlite()
try:
cancelled_numbers = [o.number for o in cancelled_orders]
placeholders = ",".join("?" for _ in cancelled_numbers)
cursor = await db_check.execute(f"""
SELECT order_number, id_comanda FROM orders
WHERE order_number IN ({placeholders})
AND id_comanda IS NOT NULL
AND status = 'CANCELLED'
""", cancelled_numbers)
previously_imported = [dict(r) for r in await cursor.fetchall()]
finally:
await db_check.close()
if previously_imported:
_log_line(run_id, f"Verificare {len(previously_imported)} comenzi anulate care erau importate in Oracle...")
# Check which have invoices
id_comanda_list = [o["id_comanda"] for o in previously_imported]
invoice_data = await asyncio.to_thread(
invoice_service.check_invoices_for_orders, id_comanda_list
)
for o in previously_imported:
idc = o["id_comanda"]
order_num = o["order_number"]
if idc in invoice_data:
# Invoiced — keep in Oracle, just log warning
_log_line(run_id,
f"#{order_num} → ANULAT dar FACTURAT (factura {invoice_data[idc].get('serie_act', '')}"
f"{invoice_data[idc].get('numar_act', '')}) — NU se sterge din Oracle")
# Update web_status but keep CANCELLED status (already set by batch above)
else:
# Not invoiced — soft-delete in Oracle
del_result = await asyncio.to_thread(
import_service.soft_delete_order_in_roa, idc
)
if del_result["success"]:
# Clear id_comanda via mark_order_cancelled
await sqlite_service.mark_order_cancelled(order_num, "Anulata")
_log_line(run_id,
f"#{order_num} → ANULAT + STERS din Oracle (ID: {idc}, "
f"{del_result['details_deleted']} detalii)")
else:
_log_line(run_id,
f"#{order_num} → ANULAT dar EROARE la stergere Oracle: {del_result['error']}")
orders = active_orders
if not orders:
_log_line(run_id, "Nicio comanda activa dupa filtrare anulate.")
await sqlite_service.update_sync_run(run_id, "completed", cancelled_count, 0, 0, 0)
_update_progress("completed", f"No active orders ({cancelled_count} cancelled)")
summary = {"run_id": run_id, "status": "completed",
"message": f"No active orders ({cancelled_count} cancelled)",
"json_files": json_count, "cancelled": cancelled_count}
return summary
_update_progress("validation", f"Validating {len(orders)} orders...", 0, len(orders)) _update_progress("validation", f"Validating {len(orders)} orders...", 0, len(orders))
# ── Single Oracle connection for entire validation phase ── # ── Single Oracle connection for entire validation phase ──
@@ -351,6 +449,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
"order_total": order.total or None, "order_total": order.total or None,
"delivery_cost": order.delivery_cost or None, "delivery_cost": order.delivery_cost or None,
"discount_total": order.discount_total or None, "discount_total": order.discount_total or None,
"web_status": order.status or None,
"items": order_items_data, "items": order_items_data,
}) })
_log_line(run_id, f"#{order.number} [{order.date or '?'}] {customer} → DEJA IMPORTAT (ID: {id_comanda_roa})") _log_line(run_id, f"#{order.number} [{order.date or '?'}] {customer} → DEJA IMPORTAT (ID: {id_comanda_roa})")
@@ -381,6 +480,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
"order_total": order.total or None, "order_total": order.total or None,
"delivery_cost": order.delivery_cost or None, "delivery_cost": order.delivery_cost or None,
"discount_total": order.discount_total or None, "discount_total": order.discount_total or None,
"web_status": order.status or None,
"items": order_items_data, "items": order_items_data,
}) })
_log_line(run_id, f"#{order.number} [{order.date or '?'}] {customer} → OMIS (lipsa: {', '.join(missing_skus)})") _log_line(run_id, f"#{order.number} [{order.date or '?'}] {customer} → OMIS (lipsa: {', '.join(missing_skus)})")
@@ -437,6 +537,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
order_total=order.total or None, order_total=order.total or None,
delivery_cost=order.delivery_cost or None, delivery_cost=order.delivery_cost or None,
discount_total=order.discount_total or None, discount_total=order.discount_total or None,
web_status=order.status or None,
) )
await sqlite_service.add_sync_run_order(run_id, order.number, "IMPORTED") await sqlite_service.add_sync_run_order(run_id, order.number, "IMPORTED")
# Store ROA address IDs (R9) # Store ROA address IDs (R9)
@@ -465,6 +566,7 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
order_total=order.total or None, order_total=order.total or None,
delivery_cost=order.delivery_cost or None, delivery_cost=order.delivery_cost or None,
discount_total=order.discount_total or None, discount_total=order.discount_total or None,
web_status=order.status or None,
) )
await sqlite_service.add_sync_run_order(run_id, order.number, "ERROR") await sqlite_service.add_sync_run_order(run_id, order.number, "ERROR")
await sqlite_service.add_order_items(order.number, order_items_data) await sqlite_service.add_order_items(order.number, order_items_data)
@@ -548,13 +650,14 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
"run_id": run_id, "run_id": run_id,
"status": status, "status": status,
"json_files": json_count, "json_files": json_count,
"total_orders": len(orders), "total_orders": len(orders) + cancelled_count,
"new_orders": len(truly_importable), "new_orders": len(truly_importable),
"imported": total_imported, "imported": total_imported,
"new_imported": imported_count, "new_imported": imported_count,
"already_imported": already_imported_count, "already_imported": already_imported_count,
"skipped": len(skipped), "skipped": len(skipped),
"errors": error_count, "errors": error_count,
"cancelled": cancelled_count,
"missing_skus": len(validation["missing"]), "missing_skus": len(validation["missing"]),
"invoices_updated": invoices_updated, "invoices_updated": invoices_updated,
"invoices_cleared": invoices_cleared, "invoices_cleared": invoices_cleared,
@@ -562,24 +665,25 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
} }
_update_progress("completed", _update_progress("completed",
f"Completed: {imported_count} new, {already_imported_count} already, {len(skipped)} skipped, {error_count} errors", f"Completed: {imported_count} new, {already_imported_count} already, {len(skipped)} skipped, {error_count} errors, {cancelled_count} cancelled",
len(truly_importable), len(truly_importable), len(truly_importable), len(truly_importable),
{"imported": imported_count, "skipped": len(skipped), "errors": error_count, {"imported": imported_count, "skipped": len(skipped), "errors": error_count,
"already_imported": already_imported_count}) "already_imported": already_imported_count, "cancelled": cancelled_count})
if _current_sync: if _current_sync:
_current_sync["status"] = status _current_sync["status"] = status
_current_sync["finished_at"] = datetime.now().isoformat() _current_sync["finished_at"] = datetime.now().isoformat()
logger.info( logger.info(
f"Sync {run_id} completed: {imported_count} new, {already_imported_count} already imported, " f"Sync {run_id} completed: {imported_count} new, {already_imported_count} already imported, "
f"{len(skipped)} skipped, {error_count} errors" f"{len(skipped)} skipped, {error_count} errors, {cancelled_count} cancelled"
) )
duration = (datetime.now() - started_dt).total_seconds() duration = (datetime.now() - started_dt).total_seconds()
_log_line(run_id, "") _log_line(run_id, "")
cancelled_text = f", {cancelled_count} anulate" if cancelled_count else ""
_run_logs[run_id].append( _run_logs[run_id].append(
f"Finalizat: {imported_count} importate, {already_imported_count} deja importate, " f"Finalizat: {imported_count} importate, {already_imported_count} deja importate, "
f"{len(skipped)} nemapate, {error_count} erori din {len(orders)} comenzi | Durata: {int(duration)}s" f"{len(skipped)} nemapate, {error_count} erori{cancelled_text} din {len(orders) + cancelled_count} comenzi | Durata: {int(duration)}s"
) )
return summary return summary

View File

@@ -289,6 +289,7 @@ body {
.fc-red { color: #dc2626; } .fc-red { color: #dc2626; }
.fc-neutral { color: #6b7280; } .fc-neutral { color: #6b7280; }
.fc-blue { color: #2563eb; } .fc-blue { color: #2563eb; }
.fc-dark { color: #374151; }
/* ── Log viewer (dark theme — keep as-is) ────────── */ /* ── Log viewer (dark theme — keep as-is) ────────── */
.log-viewer { .log-viewer {

View File

@@ -283,6 +283,7 @@ async function loadDashOrders() {
if (el('cntErr')) el('cntErr').textContent = c.error || c.errors || 0; if (el('cntErr')) el('cntErr').textContent = c.error || c.errors || 0;
if (el('cntFact')) el('cntFact').textContent = c.facturate || 0; if (el('cntFact')) el('cntFact').textContent = c.facturate || 0;
if (el('cntNef')) el('cntNef').textContent = c.nefacturate || c.uninvoiced || 0; if (el('cntNef')) el('cntNef').textContent = c.nefacturate || c.uninvoiced || 0;
if (el('cntCanc')) el('cntCanc').textContent = c.cancelled || 0;
const tbody = document.getElementById('dashOrdersBody'); const tbody = document.getElementById('dashOrdersBody');
const orders = data.orders || []; const orders = data.orders || [];
@@ -340,7 +341,8 @@ async function loadDashOrders() {
{ label: 'Omise', count: c.skipped || 0, value: 'SKIPPED', active: activeStatus === 'SKIPPED', colorClass: 'fc-yellow' }, { label: 'Omise', count: c.skipped || 0, value: 'SKIPPED', active: activeStatus === 'SKIPPED', colorClass: 'fc-yellow' },
{ label: 'Erori', count: c.error || c.errors || 0, value: 'ERROR', active: activeStatus === 'ERROR', colorClass: 'fc-red' }, { label: 'Erori', count: c.error || c.errors || 0, value: 'ERROR', active: activeStatus === 'ERROR', colorClass: 'fc-red' },
{ label: 'Fact.', count: c.facturate || 0, value: 'INVOICED', active: activeStatus === 'INVOICED', colorClass: 'fc-green' }, { label: 'Fact.', count: c.facturate || 0, value: 'INVOICED', active: activeStatus === 'INVOICED', colorClass: 'fc-green' },
{ label: 'Nefact.', count: c.nefacturate || c.uninvoiced || 0, value: 'UNINVOICED', active: activeStatus === 'UNINVOICED', colorClass: 'fc-red' } { label: 'Nefact.', count: c.nefacturate || c.uninvoiced || 0, value: 'UNINVOICED', active: activeStatus === 'UNINVOICED', colorClass: 'fc-red' },
{ label: 'Anulate', count: c.cancelled || 0, value: 'CANCELLED', active: activeStatus === 'CANCELLED', colorClass: 'fc-dark' }
], (val) => { ], (val) => {
document.querySelectorAll('.filter-pill[data-status]').forEach(b => b.classList.remove('active')); document.querySelectorAll('.filter-pill[data-status]').forEach(b => b.classList.remove('active'));
const pill = document.querySelector(`.filter-pill[data-status="${val}"]`); const pill = document.querySelector(`.filter-pill[data-status="${val}"]`);
@@ -438,6 +440,7 @@ function orderStatusBadge(status) {
case 'ALREADY_IMPORTED': return '<span class="badge bg-info">Deja importat</span>'; case 'ALREADY_IMPORTED': return '<span class="badge bg-info">Deja importat</span>';
case 'SKIPPED': return '<span class="badge bg-warning">Omis</span>'; case 'SKIPPED': return '<span class="badge bg-warning">Omis</span>';
case 'ERROR': return '<span class="badge bg-danger">Eroare</span>'; case 'ERROR': return '<span class="badge bg-danger">Eroare</span>';
case 'CANCELLED': return '<span class="badge bg-secondary">Anulat</span>';
case 'DELETED_IN_ROA': return '<span class="badge bg-dark">Sters din ROA</span>'; case 'DELETED_IN_ROA': return '<span class="badge bg-dark">Sters din ROA</span>';
default: return `<span class="badge bg-secondary">${esc(status)}</span>`; default: return `<span class="badge bg-secondary">${esc(status)}</span>`;
} }

View File

@@ -219,6 +219,9 @@ function statusDot(status) {
case 'ERROR': case 'ERROR':
case 'FAILED': case 'FAILED':
return '<span class="dot dot-red"></span>'; return '<span class="dot dot-red"></span>';
case 'CANCELLED':
case 'DELETED_IN_ROA':
return '<span class="dot dot-gray"></span>';
default: default:
return '<span class="dot dot-gray"></span>'; return '<span class="dot dot-gray"></span>';
} }

View File

@@ -7,7 +7,7 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet">
{% set rp = request.scope.get('root_path', '') %} {% set rp = request.scope.get('root_path', '') %}
<link href="{{ rp }}/static/css/style.css?v=10" rel="stylesheet"> <link href="{{ rp }}/static/css/style.css?v=11" rel="stylesheet">
</head> </head>
<body> <body>
<!-- Top Navbar --> <!-- Top Navbar -->
@@ -29,7 +29,7 @@
<script>window.ROOT_PATH = "{{ rp }}";</script> <script>window.ROOT_PATH = "{{ rp }}";</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{ rp }}/static/js/shared.js?v=10"></script> <script src="{{ rp }}/static/js/shared.js?v=11"></script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -69,6 +69,7 @@
<button class="filter-pill d-none d-md-inline-flex" data-status="ERROR">Erori <span class="filter-count fc-red" id="cntErr">0</span></button> <button class="filter-pill d-none d-md-inline-flex" data-status="ERROR">Erori <span class="filter-count fc-red" id="cntErr">0</span></button>
<button class="filter-pill d-none d-md-inline-flex" data-status="INVOICED">Facturate <span class="filter-count fc-green" id="cntFact">0</span></button> <button class="filter-pill d-none d-md-inline-flex" data-status="INVOICED">Facturate <span class="filter-count fc-green" id="cntFact">0</span></button>
<button class="filter-pill d-none d-md-inline-flex" data-status="UNINVOICED">Nefacturate <span class="filter-count fc-red" id="cntNef">0</span></button> <button class="filter-pill d-none d-md-inline-flex" data-status="UNINVOICED">Nefacturate <span class="filter-count fc-red" id="cntNef">0</span></button>
<button class="filter-pill d-none d-md-inline-flex" data-status="CANCELLED">Anulate <span class="filter-count fc-dark" id="cntCanc">0</span></button>
<button class="btn btn-sm btn-outline-secondary d-none d-md-inline-flex align-items-center gap-1" id="btnRefreshInvoices" onclick="refreshInvoices()" title="Actualizeaza status facturi din Oracle">&#8635; Facturi</button> <button class="btn btn-sm btn-outline-secondary d-none d-md-inline-flex align-items-center gap-1" id="btnRefreshInvoices" onclick="refreshInvoices()" title="Actualizeaza status facturi din Oracle">&#8635; Facturi</button>
<!-- Search (integrated, end of row) --> <!-- Search (integrated, end of row) -->
<input type="search" id="orderSearch" placeholder="Cauta..." class="search-input"> <input type="search" id="orderSearch" placeholder="Cauta..." class="search-input">
@@ -199,5 +200,5 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=13"></script> <script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=14"></script>
{% endblock %} {% endblock %}