feat(sync): add SSE live feed, unified logs page, fix Oracle connection

- Add SSE event bus in sync_service (subscribe/unsubscribe/_emit)
- Add GET /api/sync/stream SSE endpoint for real-time sync progress
- Rewrite logs.html: unified runs table + live feed + summary + filters
- Rewrite logs.js: SSE EventSource client, run selection, pagination
- Dashboard: clickable runs navigate to /logs?run=, sync started banner
- Remove "Import Comenzi" nav item, delete sync_detail.html
- Add error_message column to sync_runs table with migration
- Fix: export TNS_ADMIN as OS env var so oracledb finds tnsnames.ora
- Fix: use get_oracle_connection() instead of direct pool.acquire()
- Fix: CRM_POLITICI_PRET_ART INSERT to match actual table schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 18:08:09 +02:00
parent 97699fa0e5
commit 650e98539e
13 changed files with 638 additions and 359 deletions

View File

@@ -17,7 +17,8 @@ def validate_skus(skus: set[str]) -> dict:
direct = set()
sku_list = list(skus)
with database.pool.acquire() as conn:
conn = database.get_oracle_connection()
try:
with conn.cursor() as cur:
# Check in batches of 500
for i in range(0, len(sku_list), 500):
@@ -44,6 +45,8 @@ def validate_skus(skus: set[str]) -> dict:
""", params2)
for row in cur:
direct.add(row[0])
finally:
database.pool.release(conn)
missing = skus - mapped - direct
@@ -80,7 +83,8 @@ def find_new_orders(order_numbers: list[str]) -> set[str]:
existing = set()
num_list = list(order_numbers)
with database.pool.acquire() as conn:
conn = database.get_oracle_connection()
try:
with conn.cursor() as cur:
for i in range(0, len(num_list), 500):
batch = num_list[i:i+500]
@@ -93,6 +97,8 @@ def find_new_orders(order_numbers: list[str]) -> set[str]:
""", params)
for row in cur:
existing.add(row[0])
finally:
database.pool.release(conn)
new_orders = set(order_numbers) - existing
logger.info(f"Order check: {len(new_orders)} new, {len(existing)} already exist out of {len(order_numbers)} total")
@@ -109,7 +115,8 @@ def validate_prices(codmats: set[str], id_pol: int) -> dict:
ids_with_price = set()
codmat_list = list(codmats)
with database.pool.acquire() as conn:
conn = database.get_oracle_connection()
try:
with conn.cursor() as cur:
# Step 1: Get ID_ARTICOL for each CODMAT
for i in range(0, len(codmat_list), 500):
@@ -138,6 +145,8 @@ def validate_prices(codmats: set[str], id_pol: int) -> dict:
""", params)
for row in cur:
ids_with_price.add(row[0])
finally:
database.pool.release(conn)
# Map back to CODMATs
has_price = {cm for cm, aid in codmat_to_id.items() if aid in ids_with_price}
@@ -151,7 +160,8 @@ def ensure_prices(codmats: set[str], id_pol: int):
if not codmats:
return
with database.pool.acquire() as conn:
conn = database.get_oracle_connection()
try:
with conn.cursor() as cur:
# Get ID_VALUTA for this policy
cur.execute("""
@@ -176,16 +186,18 @@ def ensure_prices(codmats: set[str], id_pol: int):
cur.execute("""
INSERT INTO CRM_POLITICI_PRET_ART
(ID_POL_ART, ID_POL, ID_ARTICOL, PRET, ID_COMANDA, ID_VALUTA,
ID_UTIL, DATAORA, PROC_TVAV, ID_PARTR, ID_PARTZ,
PRETFTVA, PRETCTVA, CANTITATE, ID_UM, PRET_MIN, PRET_MIN_TVA)
(ID_POL_ART, ID_POL, ID_ARTICOL, PRET, ID_VALUTA,
ID_UTIL, DATAORA, PROC_TVAV,
PRETFTVA, PRETCTVA)
VALUES
(SEQ_CRM_POLITICI_PRET_ART.NEXTVAL, :id_pol, :id_articol, 0, NULL, :id_valuta,
-3, SYSDATE, 1.19, NULL, NULL,
0, 0, 0, NULL, 0, 0)
(SEQ_CRM_POLITICI_PRET_ART.NEXTVAL, :id_pol, :id_articol, 0, :id_valuta,
-3, SYSDATE, 1.19,
0, 0)
""", {"id_pol": id_pol, "id_articol": id_articol, "id_valuta": id_valuta})
logger.info(f"Pret 0 adaugat pentru CODMAT {codmat} in politica {id_pol}")
conn.commit()
finally:
database.pool.release(conn)
logger.info(f"Ensure prices done: {len(codmats)} CODMATs processed for policy {id_pol}")