chore: move working scripts to scripts/work/ (gitignored)

Prevents untracked file conflicts on git pull on Windows server.
Scripts are development/analysis tools, not part of the deployed app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-17 12:34:34 +00:00
parent 06f8fa5842
commit f5ef9e0811
11 changed files with 1 additions and 1422 deletions

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ output/
vfp/*.json
*.~pck
.claude/HANDOFF.md
scripts/work/
# Virtual environments
venv/

View File

@@ -1,179 +0,0 @@
"""Analyze billing vs shipping patterns across all GoMag orders"""
import sys, json, httpx
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
API_KEY = '4c5e46df8f6c4f054fe2787de7a13d4a'
API_SHOP = 'https://coffeepoint.ro'
API_URL = 'https://api.gomag.ro/api/v1/order/read/json'
headers = {
'Apikey': API_KEY,
'ApiShop': API_SHOP,
'User-Agent': 'Mozilla/5.0',
'Content-Type': 'application/json',
}
params = {'startDate': '2026-03-08', 'page': 1, 'limit': 250}
all_orders = []
for page in range(1, 20):
params['page'] = page
resp = httpx.get(API_URL, headers=headers, params=params, timeout=60)
data = resp.json()
pages = data.get('pages', 1)
orders_raw = data.get('orders', {})
if isinstance(orders_raw, dict):
for key, order in orders_raw.items():
if isinstance(order, dict):
all_orders.append(order)
print(f"Page {page}/{pages}: {len(orders_raw)} orders")
if page >= pages:
break
print(f"\nTotal orders: {len(all_orders)}")
# Analyze patterns
company_orders = []
person_orders = []
diff_person_orders = [] # shipping != billing person
for order in all_orders:
billing = order.get('billing', {}) or {}
shipping = order.get('shipping', {}) or {}
company = billing.get('company', {})
is_company = isinstance(company, dict) and bool(company.get('name'))
b_first = billing.get('firstname', '') or ''
b_last = billing.get('lastname', '') or ''
s_first = shipping.get('firstname', '') or ''
s_last = shipping.get('lastname', '') or ''
billing_person = f"{b_first} {b_last}".strip()
shipping_person = f"{s_first} {s_last}".strip()
entry = {
'number': order.get('number', ''),
'total': order.get('total', ''),
'billing_person': billing_person,
'shipping_person': shipping_person,
'company_name': company.get('name', '') if is_company else '',
'company_code': company.get('code', '') if is_company else '',
'is_company': is_company,
'same_person': billing_person.upper() == shipping_person.upper(),
}
if is_company:
company_orders.append(entry)
else:
person_orders.append(entry)
if not entry['same_person'] and shipping_person and billing_person:
diff_person_orders.append(entry)
print(f"\n{'='*80}")
print(f"COMPANY orders (billing has company): {len(company_orders)}")
print(f"PERSON orders (no company): {len(person_orders)}")
print(f" - same billing/shipping person: {len(person_orders) - len(diff_person_orders)}")
print(f" - DIFFERENT billing/shipping person: {len(diff_person_orders)}")
# Show company examples
print(f"\n{'='*80}")
print(f"COMPANY ORDERS — first 20 examples")
print(f"{'ORDER':>12s} {'COMPANY':35s} {'CUI':20s} {'BILLING_PERSON':25s} {'SHIPPING_PERSON':25s} {'SAME':5s}")
for e in company_orders[:20]:
same = 'DA' if e['same_person'] else 'NU'
print(f"{e['number']:>12s} {e['company_name'][:35]:35s} {e['company_code'][:20]:20s} {e['billing_person'][:25]:25s} {e['shipping_person'][:25]:25s} {same:5s}")
# Show different person examples
print(f"\n{'='*80}")
print(f"DIFFERENT PERSON (no company, billing != shipping) — ALL {len(diff_person_orders)} examples")
print(f"{'ORDER':>12s} {'BILLING_PERSON':30s} {'SHIPPING_PERSON':30s}")
for e in diff_person_orders:
print(f"{e['number']:>12s} {e['billing_person'][:30]:30s} {e['shipping_person'][:30]:30s}")
# Show person orders with same name
print(f"\n{'='*80}")
print(f"PERSON ORDERS (same billing/shipping) — first 10 examples")
print(f"{'ORDER':>12s} {'BILLING_PERSON':30s} {'SHIPPING_PERSON':30s} {'TOTAL':>10s}")
same_person = [e for e in person_orders if e['same_person']]
for e in same_person[:10]:
print(f"{e['number']:>12s} {e['billing_person'][:30]:30s} {e['shipping_person'][:30]:30s} {e['total']:>10s}")
# Now cross-reference with Oracle to verify import logic
# For company orders, check what partner name was created in ROA
import oracledb, os, sqlite3
os.environ['PATH'] = r'C:\app\Server\product\18.0.0\dbhomeXE\bin' + ';' + os.environ.get('PATH','')
oracledb.init_oracle_client()
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
db.row_factory = sqlite3.Row
c = db.cursor()
conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA')
cur = conn.cursor()
print(f"\n{'='*80}")
print(f"VERIFICATION: Company orders — GoMag vs SQLite vs Oracle ROA")
print(f"{'ORDER':>12s} | {'GOMAG_COMPANY':30s} | {'SQLITE_CUSTOMER':30s} | {'ROA_PARTNER':30s} | MATCH?")
# Build lookup from GoMag orders
gomag_lookup = {}
for order in all_orders:
num = order.get('number', '')
gomag_lookup[num] = order
# Get all imported orders from SQLite with id_partener
c.execute("""
SELECT order_number, customer_name, id_partener, billing_name, shipping_name
FROM orders WHERE id_partener IS NOT NULL
""")
for row in c.fetchall():
on = row['order_number']
gomag = gomag_lookup.get(on)
if not gomag:
continue
billing = gomag.get('billing', {}) or {}
company = billing.get('company', {})
is_company = isinstance(company, dict) and bool(company.get('name'))
company_name = company.get('name', '') if is_company else ''
# Get ROA partner name
roa_partner = ''
if row['id_partener']:
cur.execute("SELECT denumire, prenume FROM nom_parteneri WHERE id_part = :1", [row['id_partener']])
r = cur.fetchone()
if r:
roa_partner = ((r[0] or '') + ' ' + (r[1] or '')).strip()
gomag_label = company_name if is_company else f"{billing.get('firstname','')} {billing.get('lastname','')}"
match = 'OK' if company_name and company_name.upper()[:15] in roa_partner.upper() else ('OK' if not company_name else 'DIFF')
print(f"{on:>12s} | {gomag_label[:30]:30s} | {(row['customer_name'] or '')[:30]:30s} | {roa_partner[:30]:30s} | {match}")
# Also show some SKIPPED company orders to see what customer_name we stored
print(f"\n{'='*80}")
print(f"SKIPPED company orders — GoMag company vs SQLite customer_name")
print(f"{'ORDER':>12s} | {'GOMAG_COMPANY':30s} | {'SQLITE_CUSTOMER':30s} | {'BILLING_PERSON':25s} | {'SHIPPING_PERSON':25s}")
c.execute("SELECT order_number, customer_name, billing_name, shipping_name FROM orders WHERE status = 'SKIPPED' LIMIT 200")
for row in c.fetchall():
on = row['order_number']
gomag = gomag_lookup.get(on)
if not gomag:
continue
billing = gomag.get('billing', {}) or {}
company = billing.get('company', {})
is_company = isinstance(company, dict) and bool(company.get('name'))
if not is_company:
continue
company_name = company.get('name', '')
b_person = f"{billing.get('firstname','')} {billing.get('lastname','')}".strip()
shipping = gomag.get('shipping', {}) or {}
s_person = f"{shipping.get('firstname','')} {shipping.get('lastname','')}".strip()
print(f"{on:>12s} | {company_name[:30]:30s} | {(row['customer_name'] or '')[:30]:30s} | {b_person[:25]:25s} | {s_person[:25]:25s}")
db.close()
conn.close()

View File

@@ -1,31 +0,0 @@
import sqlite3, sys, importlib
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
# Check SQLite current state
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
c = db.cursor()
c.execute("SELECT order_number, customer_name, shipping_name, billing_name FROM orders WHERE order_number='480102897'")
r = c.fetchone()
print(f"SQLite: customer={r[1]}, shipping={r[2]}, billing={r[3]}")
db.close()
# Check deployed code version
sys.path.insert(0, r'C:\gomag-vending\api')
from app.services.sync_service import _derive_customer_info
from app.services.order_reader import OrderData, OrderBilling, OrderShipping
# Simulate the order
billing = OrderBilling(firstname='Liviu', lastname='Stoica', is_company=True, company_name='SLM COMERCE SRL')
shipping = OrderShipping(firstname='Liviu', lastname='Stoica')
order = OrderData(id='1', number='480102897', date='2026-03-09', billing=billing, shipping=shipping)
s, b, customer, _, _ = _derive_customer_info(order)
print(f"Code: _derive_customer_info returns customer={customer!r}")
# Check if the sqlite_service has the fix
import inspect
from app.services.sqlite_service import upsert_order
source = inspect.getsource(upsert_order)
if 'customer_name = excluded.customer_name' in source:
print("sqlite_service: upsert has customer_name update ✓")
else:
print("sqlite_service: upsert MISSING customer_name update ✗")

View File

@@ -1,61 +0,0 @@
"""Check imported orders in SQLite and Oracle — what needs to be deleted"""
import sys, sqlite3, oracledb, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
os.environ['PATH'] = r'C:\app\Server\product\18.0.0\dbhomeXE\bin' + ';' + os.environ.get('PATH','')
oracledb.init_oracle_client()
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
db.row_factory = sqlite3.Row
c = db.cursor()
# Get imported orders with id_comanda
c.execute("""
SELECT order_number, customer_name, id_comanda, id_partener, order_date, order_total
FROM orders
WHERE status = 'IMPORTED' AND id_comanda IS NOT NULL
ORDER BY order_date
""")
imported = [dict(r) for r in c.fetchall()]
db.close()
print(f"Imported orders in SQLite: {len(imported)}")
# Check Oracle status
conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA')
cur = conn.cursor()
print(f"\n{'ORDER_NR':>12s} {'ID_CMD':>8s} {'SQLITE_CLIENT':30s} {'ROA_PARTNER':30s} {'ROA_STERS':>9s} {'FACTURAT':>8s} {'DATA':>12s}")
for o in imported:
id_cmd = o['id_comanda']
# Check COMENZI
cur.execute("""
SELECT c.sters, p.denumire, p.prenume,
(SELECT COUNT(*) FROM vanzari v WHERE v.id_comanda = c.id_comanda AND v.sters = 0) as nr_facturi
FROM comenzi c
LEFT JOIN nom_parteneri p ON c.id_part = p.id_part
WHERE c.id_comanda = :1
""", [id_cmd])
row = cur.fetchone()
if row:
sters = row[0]
partner = ((row[1] or '') + ' ' + (row[2] or '')).strip()
nr_fact = row[3]
print(f"{o['order_number']:>12s} {id_cmd:>8d} {(o['customer_name'] or '')[:30]:30s} {partner[:30]:30s} {'DA' if sters else 'NU':>9s} {nr_fact:>8d} {str(o['order_date'])[:10]:>12s}")
else:
print(f"{o['order_number']:>12s} {id_cmd:>8d} {(o['customer_name'] or '')[:30]:30s} {'NOT FOUND':30s}")
# Check if there's a delete/sters mechanism
print(f"\n--- COMENZI table columns for delete ---")
cur.execute("SELECT column_name FROM all_tab_columns WHERE table_name='COMENZI' AND owner='VENDING' AND column_name IN ('STERS','ID_UTILS','DATAORAS') ORDER BY column_id")
for r in cur:
print(f" {r[0]}")
# Check comenzi_detalii
cur.execute("SELECT column_name FROM all_tab_columns WHERE table_name='COMENZI_DETALII' AND owner='VENDING' ORDER BY column_id")
print(f"\n--- COMENZI_DETALII columns ---")
for r in cur:
print(f" {r[0]}")
conn.close()

View File

@@ -1,76 +0,0 @@
"""Compare specific GoMag order vs Oracle invoice"""
import sys, sqlite3, oracledb, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
os.environ['PATH'] = r'C:\app\Server\product\18.0.0\dbhomeXE\bin' + ';' + os.environ.get('PATH','')
oracledb.init_oracle_client()
ORDER = sys.argv[1] if len(sys.argv) > 1 else '480104185'
FACT_NR = sys.argv[2] if len(sys.argv) > 2 else '4105'
# GoMag order from SQLite
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
db.row_factory = sqlite3.Row
c = db.cursor()
c.execute("SELECT * FROM orders WHERE order_number = ?", (ORDER,))
order = dict(c.fetchone())
c.execute("SELECT * FROM order_items WHERE order_number = ? ORDER BY sku", (ORDER,))
items = [dict(r) for r in c.fetchall()]
db.close()
print(f"=== GoMag Order {ORDER} ===")
print(f" Client: {order['customer_name']}")
print(f" Shipping: {order['shipping_name']}")
print(f" Billing: {order['billing_name']}")
print(f" Date: {order['order_date']}")
print(f" Total: {order['order_total']}")
print(f" Status: {order['status']}")
print(f" Items ({len(items)}):")
go_total = 0
for it in items:
line = it['quantity'] * it['price']
go_total += line
print(f" SKU={it['sku']:25s} qty={it['quantity']:6.1f} x {it['price']:8.2f} = {line:8.2f} {it['product_name']}")
print(f" Sum lines: {go_total:.2f}")
# Oracle invoice
conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA')
cur = conn.cursor()
cur.execute("""
SELECT v.id_vanzare, TO_CHAR(v.data_act, 'YYYY-MM-DD'), v.total_fara_tva, v.total_cu_tva,
p.denumire, p.prenume
FROM vanzari v
LEFT JOIN nom_parteneri p ON v.id_part = p.id_part
WHERE v.numar_act = :1 AND v.serie_act = 'VM' AND v.sters = 0
AND v.data_act >= TO_DATE('2026-03-01','YYYY-MM-DD')
""", [int(FACT_NR)])
rows = cur.fetchall()
for row in rows:
id_vanz = row[0]
print(f"\n=== Oracle Invoice VM{FACT_NR} (id_vanzare={id_vanz}) ===")
print(f" Client: {row[4]} {row[5] or ''}")
print(f" Date: {row[1]}")
print(f" Total fara TVA: {float(row[2]):.2f}")
print(f" Total cu TVA: {float(row[3]):.2f}")
cur.execute("""
SELECT vd.id_articol, a.codmat, a.denumire,
vd.cantitate, vd.pret, vd.pret_cu_tva, vd.proc_tvav
FROM vanzari_detalii vd
LEFT JOIN nom_articole a ON vd.id_articol = a.id_articol
WHERE vd.id_vanzare = :1 AND vd.sters = 0
ORDER BY vd.id_articol
""", [id_vanz])
det = cur.fetchall()
print(f" Items ({len(det)}):")
roa_total = 0
for d in det:
line = float(d[3]) * float(d[4])
roa_total += line
print(f" COD={str(d[1] or ''):25s} qty={float(d[3]):6.1f} x {float(d[4]):8.2f} = {line:8.2f} TVA={float(d[6]):.0f}% {d[2]}")
print(f" Sum lines (fara TVA): {roa_total:.2f}")
conn.close()

View File

@@ -1,30 +0,0 @@
import sqlite3, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
c = db.cursor()
c.execute("SELECT COUNT(*), MIN(order_date), MAX(order_date) FROM orders")
total, min_d, max_d = c.fetchone()
print(f"Total orders: {total} (from {min_d} to {max_d})")
c.execute("SELECT status, COUNT(*) FROM orders GROUP BY status ORDER BY COUNT(*) DESC")
print("\nBy status:")
for r in c:
print(f" {r[0]:20s} {r[1]:5d}")
c.execute("SELECT COUNT(*) FROM orders WHERE status IN ('IMPORTED','ALREADY_IMPORTED')")
print(f"\nImported (matchable): {c.fetchone()[0]}")
c.execute("SELECT COUNT(*) FROM order_items")
print(f"Total order_items: {c.fetchone()[0]}")
c.execute("""
SELECT COUNT(DISTINCT oi.sku)
FROM order_items oi
JOIN orders o ON oi.order_number = o.order_number
WHERE o.status IN ('IMPORTED','ALREADY_IMPORTED')
""")
print(f"Unique SKUs in imported orders: {c.fetchone()[0]}")
db.close()

View File

@@ -1,98 +0,0 @@
"""
Delete all imported orders from Oracle ROA and reset SQLite status.
Soft-delete: SET sters=1 on comenzi + comenzi_detalii.
Reset SQLite: clear id_comanda, id_partener, set status back to allow re-import.
"""
import sys, sqlite3, oracledb, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
DRY_RUN = '--execute' not in sys.argv
os.environ['PATH'] = r'C:\app\Server\product\18.0.0\dbhomeXE\bin' + ';' + os.environ.get('PATH','')
oracledb.init_oracle_client()
# Get imported orders
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
db.row_factory = sqlite3.Row
c = db.cursor()
c.execute("""
SELECT order_number, id_comanda, id_partener, customer_name
FROM orders
WHERE status = 'IMPORTED' AND id_comanda IS NOT NULL
""")
imported = [dict(r) for r in c.fetchall()]
print(f"Orders to delete: {len(imported)}")
if DRY_RUN:
print("*** DRY RUN — add --execute to actually delete ***\n")
# Step 1: Soft-delete in Oracle
conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA')
cur = conn.cursor()
id_comandas = [o['id_comanda'] for o in imported]
# Verify none are invoiced
for id_cmd in id_comandas:
cur.execute("SELECT COUNT(*) FROM vanzari WHERE id_comanda = :1 AND sters = 0", [id_cmd])
cnt = cur.fetchone()[0]
if cnt > 0:
print(f" ERROR: comanda {id_cmd} has {cnt} active invoices! Aborting.")
sys.exit(1)
print("Oracle: no invoices found on any order — safe to delete")
for id_cmd in id_comandas:
order_num = [o['order_number'] for o in imported if o['id_comanda'] == id_cmd][0]
if DRY_RUN:
# Just show what would happen
cur.execute("SELECT COUNT(*) FROM comenzi_detalii WHERE id_comanda = :1 AND sters = 0", [id_cmd])
det_cnt = cur.fetchone()[0]
print(f" Would delete: comanda {id_cmd} (order {order_num}) + {det_cnt} detail lines")
else:
# Soft-delete detail lines
cur.execute("UPDATE comenzi_detalii SET sters = 1 WHERE id_comanda = :1 AND sters = 0", [id_cmd])
det_deleted = cur.rowcount
# Soft-delete order header
cur.execute("UPDATE comenzi SET sters = 1 WHERE id_comanda = :1 AND sters = 0", [id_cmd])
hdr_deleted = cur.rowcount
print(f" Deleted: comanda {id_cmd} (order {order_num}): header={hdr_deleted}, details={det_deleted}")
if not DRY_RUN:
conn.commit()
print(f"\nOracle: {len(id_comandas)} orders soft-deleted (sters=1)")
else:
print(f"\nOracle: DRY RUN — nothing changed")
conn.close()
# Step 2: Reset SQLite
if not DRY_RUN:
c.execute("""
UPDATE orders SET
status = 'SKIPPED',
id_comanda = NULL,
id_partener = NULL,
id_adresa_facturare = NULL,
id_adresa_livrare = NULL,
error_message = 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
WHERE status = 'IMPORTED' AND id_comanda IS NOT NULL
""")
db.commit()
print(f"SQLite: {c.rowcount} orders reset to SKIPPED (id_comanda/id_partener cleared)")
else:
print(f"SQLite: DRY RUN — would reset {len(imported)} orders to SKIPPED")
db.close()
print("\nDone!" if not DRY_RUN else "\nDone (dry run). Run with --execute to apply.")

View File

@@ -1,78 +0,0 @@
"""Explore Oracle structure for invoice matching."""
import oracledb
import os
os.environ['PATH'] = r'C:\app\Server\product\18.0.0\dbhomeXE\bin' + ';' + os.environ.get('PATH','')
oracledb.init_oracle_client()
conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA')
cur = conn.cursor()
# Recent vanzari (last 10 days)
cur.execute("""
SELECT v.id_vanzare, v.numar_act, v.serie_act,
TO_CHAR(v.data_act, 'YYYY-MM-DD') as data_act,
v.total_fara_tva, v.total_cu_tva, v.id_part, v.id_comanda,
p.denumire as partener
FROM vanzari v
LEFT JOIN nom_parteneri p ON v.id_part = p.id_part
WHERE v.sters = 0 AND v.data_act >= SYSDATE - 10
ORDER BY v.data_act DESC
""")
print('=== Recent VANZARI (last 10 days) ===')
print(f'{"ID_VANZ":>8s} {"NR_ACT":>8s} {"SERIE":>6s} {"DATA":>12s} {"TOTAL_FARA":>12s} {"TOTAL_CU":>12s} {"ID_PART":>8s} {"ID_CMD":>8s} PARTENER')
for r in cur:
print(f'{r[0]:8d} {str(r[1] or ""):>8s} {str(r[2] or ""):>6s} {str(r[3]):>12s} {float(r[4] or 0):12.2f} {float(r[5] or 0):12.2f} {r[6] or 0:8d} {str(r[7] or ""):>8s} {r[8] or ""}')
print()
# Vanzari_detalii for those invoices
cur.execute("""
SELECT vd.id_vanzare, vd.id_articol, a.codmat, a.denumire,
vd.cantitate, vd.pret, vd.pret_cu_tva, vd.proc_tvav
FROM vanzari_detalii vd
JOIN vanzari v ON vd.id_vanzare = v.id_vanzare
LEFT JOIN nom_articole a ON vd.id_articol = a.id_articol
WHERE v.sters = 0 AND vd.sters = 0 AND v.data_act >= SYSDATE - 10
ORDER BY vd.id_vanzare, vd.id_articol
""")
print('=== Recent VANZARI_DETALII (last 10 days) ===')
print(f'{"ID_VANZ":>8s} {"ID_ART":>8s} {"CODMAT":>15s} {"DENUMIRE":>40s} {"QTY":>8s} {"PRET":>10s} {"PRET_CU":>10s} {"TVA%":>6s}')
for r in cur:
print(f'{r[0]:8d} {r[1]:8d} {str(r[2] or ""):>15s} {str((r[3] or "")[:40]):>40s} {float(r[4] or 0):8.2f} {float(r[5] or 0):10.4f} {float(r[6] or 0):10.4f} {float(r[7] or 0):6.1f}')
print()
# Also get SQLite orders for comparison
print('=== SQLite orders (imported, last 10 days) ===')
import sqlite3
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
c = db.cursor()
c.execute("""
SELECT o.order_number, o.order_date, o.customer_name, o.status,
o.id_comanda, o.order_total,
o.factura_serie, o.factura_numar, o.factura_data
FROM orders o
WHERE o.order_date >= date('now', '-10 days')
ORDER BY o.order_date DESC
""")
print(f'{"ORDER_NR":>10s} {"DATE":>12s} {"CLIENT":>30s} {"STATUS":>10s} {"ID_CMD":>8s} {"TOTAL":>10s} {"F_SERIE":>8s} {"F_NR":>8s} {"F_DATA":>12s}')
for r in c:
print(f'{str(r[0]):>10s} {str(r[1])[:10]:>12s} {str((r[2] or "")[:30]):>30s} {str(r[3]):>10s} {str(r[4] or ""):>8s} {float(r[5] or 0):10.2f} {str(r[6] or ""):>8s} {str(r[7] or ""):>8s} {str(r[8] or ""):>12s}')
print()
# Order items
c.execute("""
SELECT oi.order_number, oi.sku, oi.product_name, oi.quantity, oi.price, oi.vat, oi.mapping_status
FROM order_items oi
JOIN orders o ON oi.order_number = o.order_number
WHERE o.order_date >= date('now', '-10 days')
ORDER BY oi.order_number, oi.sku
""")
print('=== SQLite order_items (last 10 days) ===')
print(f'{"ORDER_NR":>10s} {"SKU":>20s} {"PRODUCT":>40s} {"QTY":>6s} {"PRICE":>10s} {"VAT":>6s} {"MAP":>8s}')
for r in c:
print(f'{str(r[0]):>10s} {str(r[1] or ""):>20s} {str((r[2] or "")[:40]):>40s} {float(r[3] or 0):6.1f} {float(r[4] or 0):10.2f} {float(r[5] or 0):6.1f} {str(r[6] or ""):>8s}')
db.close()
conn.close()

View File

@@ -1,30 +0,0 @@
import sys, json, httpx
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
API_URL = 'https://api.gomag.ro/api/v1/order/read/json'
headers = {
'Apikey': '4c5e46df8f6c4f054fe2787de7a13d4a',
'ApiShop': 'https://coffeepoint.ro',
'User-Agent': 'Mozilla/5.0',
'Content-Type': 'application/json',
}
target = sys.argv[1] if len(sys.argv) > 1 else '480700091'
for page in range(1, 20):
resp = httpx.get(API_URL, headers=headers, params={'startDate': '2026-03-08', 'page': page, 'limit': 250}, timeout=60)
data = resp.json()
orders = data.get('orders', {})
if isinstance(orders, dict) and target in orders:
print(json.dumps(orders[target], indent=2, ensure_ascii=False))
sys.exit(0)
# Also check by 'number' field
if isinstance(orders, dict):
for key, order in orders.items():
if isinstance(order, dict) and str(order.get('number', '')) == target:
print(json.dumps(order, indent=2, ensure_ascii=False))
sys.exit(0)
if page >= data.get('pages', 1):
break
print(f"Order {target} not found")

View File

@@ -1,533 +0,0 @@
"""
Match ALL GoMag orders (SQLite) with manual invoices (Oracle vanzari)
by date + client name + total value.
Then compare line items to discover SKU → CODMAT mappings.
"""
import oracledb
import os
import sys
import sqlite3
import csv
from difflib import SequenceMatcher
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
os.environ['PATH'] = r'C:\app\Server\product\18.0.0\dbhomeXE\bin' + ';' + os.environ.get('PATH','')
oracledb.init_oracle_client()
# --- Step 1: Get ALL GoMag orders from SQLite ---
print("=" * 80)
print("STEP 1: Loading ALL GoMag orders from SQLite")
print("=" * 80)
db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db')
db.row_factory = sqlite3.Row
c = db.cursor()
# ALL orders, not just IMPORTED
c.execute("""
SELECT order_number, order_date, customer_name, status,
id_comanda, order_total, billing_name, shipping_name
FROM orders
ORDER BY order_date DESC
""")
orders = [dict(r) for r in c.fetchall()]
# Get order items
for order in orders:
c.execute("""
SELECT sku, product_name, quantity, price, vat, mapping_status
FROM order_items
WHERE order_number = ?
ORDER BY sku
""", (order['order_number'],))
order['items'] = [dict(r) for r in c.fetchall()]
db.close()
by_status = {}
for o in orders:
by_status.setdefault(o['status'], 0)
by_status[o['status']] += 1
print(f"Loaded {len(orders)} GoMag orders: {by_status}")
# --- Step 2: Get Oracle invoices with date range matching orders ---
print()
print("=" * 80)
print("STEP 2: Loading Oracle invoices (vanzari + detalii)")
print("=" * 80)
conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA')
cur = conn.cursor()
# Get date range from orders
min_date = min(str(o['order_date'])[:10] for o in orders)
max_date = max(str(o['order_date'])[:10] for o in orders)
print(f"Order date range: {min_date} to {max_date}")
# Get vanzari in that range (with some margin)
cur.execute("""
SELECT v.id_vanzare, v.numar_act, v.serie_act,
TO_CHAR(v.data_act, 'YYYY-MM-DD') as data_act,
v.total_fara_tva, v.total_cu_tva, v.id_part,
p.denumire as partener, p.prenume
FROM vanzari v
LEFT JOIN nom_parteneri p ON v.id_part = p.id_part
WHERE v.sters = 0
AND v.data_act >= TO_DATE(:1, 'YYYY-MM-DD') - 2
AND v.data_act <= TO_DATE(:2, 'YYYY-MM-DD') + 2
AND v.total_cu_tva > 0
ORDER BY v.data_act DESC
""", [min_date, max_date])
invoices = []
for r in cur:
inv = {
'id_vanzare': r[0],
'numar_act': r[1],
'serie_act': r[2] or '',
'data_act': r[3],
'total_fara_tva': float(r[4] or 0),
'total_cu_tva': float(r[5] or 0),
'id_part': r[6],
'partener': ((r[7] or '') + ' ' + (r[8] or '')).strip(),
}
invoices.append(inv)
print(f"Loaded {len(invoices)} Oracle invoices in range {min_date} - {max_date}")
# Get detail lines for ALL invoices in one batch
inv_ids = [inv['id_vanzare'] for inv in invoices]
inv_map = {inv['id_vanzare']: inv for inv in invoices}
for inv in invoices:
inv['items'] = []
# Batch fetch details
for i in range(0, len(inv_ids), 500):
batch = inv_ids[i:i+500]
placeholders = ",".join([f":d{j}" for j in range(len(batch))])
params = {f"d{j}": did for j, did in enumerate(batch)}
cur.execute(f"""
SELECT vd.id_vanzare, vd.id_articol, a.codmat, a.denumire,
vd.cantitate, vd.pret, vd.pret_cu_tva, vd.proc_tvav
FROM vanzari_detalii vd
LEFT JOIN nom_articole a ON vd.id_articol = a.id_articol
WHERE vd.id_vanzare IN ({placeholders}) AND vd.sters = 0
ORDER BY vd.id_vanzare, vd.id_articol
""", params)
for r in cur:
inv_map[r[0]]['items'].append({
'id_articol': r[1],
'codmat': r[2],
'denumire': r[3],
'cantitate': float(r[4] or 0),
'pret': float(r[5] or 0),
'pret_cu_tva': float(r[6] or 0),
'tva_pct': float(r[7] or 0),
})
conn.close()
# --- Step 3: Fuzzy matching ---
print()
print("=" * 80)
print("STEP 3: Matching orders → invoices (date + name + total)")
print("=" * 80)
def normalize_name(name):
if not name:
return ''
n = name.strip().upper()
for old, new in [('S.R.L.', 'SRL'), ('S.R.L', 'SRL'), ('SC ', ''), ('PFA ', ''), ('PF ', '')]:
n = n.replace(old, new)
return n
def name_similarity(n1, n2):
nn1 = normalize_name(n1)
nn2 = normalize_name(n2)
if not nn1 or not nn2:
return 0
# Also try reversed word order (GoMag: "Popescu Ion", ROA: "ION POPESCU")
sim1 = SequenceMatcher(None, nn1, nn2).ratio()
words1 = nn1.split()
if len(words1) >= 2:
reversed1 = ' '.join(reversed(words1))
sim2 = SequenceMatcher(None, reversed1, nn2).ratio()
return max(sim1, sim2)
return sim1
matches = []
unmatched_orders = []
used_invoices = set()
# Sort orders by total descending (match big orders first - more unique)
orders_sorted = sorted(orders, key=lambda o: -(o['order_total'] or 0))
for order in orders_sorted:
best_match = None
best_score = 0
order_date = str(order['order_date'])[:10]
order_total = order['order_total'] or 0
order_name = order['customer_name'] or ''
for inv in invoices:
if inv['id_vanzare'] in used_invoices:
continue
# Date match (must be within +/- 2 days)
try:
od = int(order_date.replace('-',''))
id_ = int(inv['data_act'].replace('-',''))
date_diff = abs(od - id_)
except:
continue
if date_diff > 2:
continue
# Total match (within 10% or 10 lei — more lenient for transport/discount)
total_diff = abs(order_total - inv['total_cu_tva'])
total_pct = total_diff / max(order_total, 0.01) * 100
if total_pct > 15 and total_diff > 15:
continue
# Name similarity
sim = name_similarity(order_name, inv['partener'])
# Also check billing_name/shipping_name
sim2 = name_similarity(order.get('billing_name') or '', inv['partener'])
sim3 = name_similarity(order.get('shipping_name') or '', inv['partener'])
sim = max(sim, sim2, sim3)
# Score
date_score = 1 if date_diff == 0 else (0.7 if date_diff == 1 else 0.3)
total_score = 1 - min(total_pct / 100, 1)
score = sim * 0.45 + total_score * 0.40 + date_score * 0.15
if score > best_score:
best_score = score
best_match = inv
if best_match and best_score > 0.45:
matches.append({
'order': order,
'invoice': best_match,
'score': best_score,
})
used_invoices.add(best_match['id_vanzare'])
else:
unmatched_orders.append(order)
print(f"Matched: {len(matches)} | Unmatched orders: {len(unmatched_orders)}")
matched_statuses = {}
for m in matches:
s = m['order']['status']
matched_statuses.setdefault(s, 0)
matched_statuses[s] += 1
print(f"Matched by status: {matched_statuses}")
# --- Step 4: Compare line items ---
print()
print("=" * 80)
print("STEP 4: Line item comparison")
print("=" * 80)
simple_mappings = []
repack_mappings = []
complex_mappings = []
unresolved = []
match_details = []
for m in matches:
o = m['order']
inv = m['invoice']
go_items = o['items']
# Filter out TRANSPORT and DISCOUNT from ROA items
roa_items = [ri for ri in inv['items']
if ri['codmat'] not in ('TRANSPORT', 'DISCOUNT', None, '')
and ri['cantitate'] > 0]
roa_transport = [ri for ri in inv['items']
if ri['codmat'] in ('TRANSPORT', 'DISCOUNT') or ri['cantitate'] < 0]
detail = {
'order_number': o['order_number'],
'customer': o['customer_name'],
'order_total': o['order_total'],
'factura': f"{inv['serie_act']}{inv['numar_act']}",
'inv_total': inv['total_cu_tva'],
'score': m['score'],
'go_items': len(go_items),
'roa_items': len(roa_items),
'matched_items': [],
'unresolved_items': [],
}
go_remaining = list(range(len(go_items)))
roa_remaining = list(range(len(roa_items)))
item_matches = []
# Pass 1: exact match by codmat (SKU == CODMAT)
for gi_idx in list(go_remaining):
gi = go_items[gi_idx]
for ri_idx in list(roa_remaining):
ri = roa_items[ri_idx]
if ri['codmat'] and gi['sku'] == ri['codmat']:
item_matches.append((gi_idx, [ri_idx]))
go_remaining.remove(gi_idx)
roa_remaining.remove(ri_idx)
break
# Pass 2: match by total value (qty * price)
for gi_idx in list(go_remaining):
gi = go_items[gi_idx]
go_total_cu = gi['quantity'] * gi['price']
go_total_fara = go_total_cu / (1 + gi['vat']/100) if gi['vat'] else go_total_cu
for ri_idx in list(roa_remaining):
ri = roa_items[ri_idx]
roa_total_fara = ri['cantitate'] * ri['pret']
roa_total_cu = ri['cantitate'] * ri['pret_cu_tva']
if (abs(go_total_fara - roa_total_fara) < 1.0 or
abs(go_total_cu - roa_total_cu) < 1.0 or
abs(go_total_cu - roa_total_fara) < 1.0):
item_matches.append((gi_idx, [ri_idx]))
go_remaining.remove(gi_idx)
roa_remaining.remove(ri_idx)
break
# Pass 3: 1:1 positional match (if same count remaining)
if len(go_remaining) == len(roa_remaining) == 1:
item_matches.append((go_remaining[0], [roa_remaining[0]]))
go_remaining = []
roa_remaining = []
# Pass 4: 1:N by combined total
for gi_idx in list(go_remaining):
gi = go_items[gi_idx]
go_total_cu = gi['quantity'] * gi['price']
go_total_fara = go_total_cu / (1 + gi['vat']/100) if gi['vat'] else go_total_cu
if len(roa_remaining) >= 2:
# Try all pairs
found = False
for i_pos, ri_idx1 in enumerate(roa_remaining):
for ri_idx2 in roa_remaining[i_pos+1:]:
ri1 = roa_items[ri_idx1]
ri2 = roa_items[ri_idx2]
combined_fara = ri1['cantitate'] * ri1['pret'] + ri2['cantitate'] * ri2['pret']
combined_cu = ri1['cantitate'] * ri1['pret_cu_tva'] + ri2['cantitate'] * ri2['pret_cu_tva']
if (abs(go_total_fara - combined_fara) < 2.0 or
abs(go_total_cu - combined_cu) < 2.0):
item_matches.append((gi_idx, [ri_idx1, ri_idx2]))
go_remaining.remove(gi_idx)
roa_remaining.remove(ri_idx1)
roa_remaining.remove(ri_idx2)
found = True
break
if found:
break
# Classify matches
for gi_idx, ri_indices in item_matches:
gi = go_items[gi_idx]
ris = [roa_items[i] for i in ri_indices]
if len(ris) == 1:
ri = ris[0]
if gi['sku'] == ri['codmat']:
# Already mapped (SKU == CODMAT)
detail['matched_items'].append(f"ALREADY: {gi['sku']} == {ri['codmat']}")
simple_mappings.append({
'sku': gi['sku'], 'codmat': ri['codmat'],
'id_articol': ri['id_articol'],
'type': 'already_equal',
'product_name': gi['product_name'], 'denumire': ri['denumire'],
'go_qty': gi['quantity'], 'roa_qty': ri['cantitate'],
'go_price': gi['price'], 'roa_pret': ri['pret'],
})
elif abs(gi['quantity'] - ri['cantitate']) < 0.01:
# Simple 1:1 different codmat
detail['matched_items'].append(f"SIMPLE: {gi['sku']}{ri['codmat']}")
simple_mappings.append({
'sku': gi['sku'], 'codmat': ri['codmat'],
'id_articol': ri['id_articol'],
'type': 'simple',
'product_name': gi['product_name'], 'denumire': ri['denumire'],
'go_qty': gi['quantity'], 'roa_qty': ri['cantitate'],
'go_price': gi['price'], 'roa_pret': ri['pret'],
})
else:
# Repackaging
cantitate_roa = ri['cantitate'] / gi['quantity'] if gi['quantity'] else 1
detail['matched_items'].append(f"REPACK: {gi['sku']}{ri['codmat']} x{cantitate_roa:.3f}")
repack_mappings.append({
'sku': gi['sku'], 'codmat': ri['codmat'],
'id_articol': ri['id_articol'],
'cantitate_roa': round(cantitate_roa, 3),
'product_name': gi['product_name'], 'denumire': ri['denumire'],
'go_qty': gi['quantity'], 'roa_qty': ri['cantitate'],
})
else:
# Complex set
go_total_cu = gi['quantity'] * gi['price']
go_total_fara = go_total_cu / (1 + gi['vat']/100) if gi['vat'] else go_total_cu
for ri in ris:
ri_total = ri['cantitate'] * ri['pret']
pct = round(ri_total / go_total_fara * 100, 2) if go_total_fara else 0
cantitate_roa = ri['cantitate'] / gi['quantity'] if gi['quantity'] else 1
detail['matched_items'].append(f"SET: {gi['sku']}{ri['codmat']} {pct}%")
complex_mappings.append({
'sku': gi['sku'], 'codmat': ri['codmat'],
'id_articol': ri['id_articol'],
'cantitate_roa': round(cantitate_roa, 3),
'procent_pret': pct,
'product_name': gi['product_name'], 'denumire': ri['denumire'],
})
for gi_idx in go_remaining:
gi = go_items[gi_idx]
remaining_roa = [roa_items[i] for i in roa_remaining]
detail['unresolved_items'].append(gi['sku'])
unresolved.append({
'sku': gi['sku'],
'product_name': gi['product_name'],
'quantity': gi['quantity'],
'price': gi['price'],
'order': o['order_number'],
'factura': f"{inv['serie_act']}{inv['numar_act']}",
'roa_remaining': '; '.join([f"{r['codmat'] or '?'}({r['cantitate']}x{r['pret']:.2f}={r['denumire'][:30]})"
for r in remaining_roa]),
})
match_details.append(detail)
# --- Step 5: Deduplicate and summarize ---
print()
print("=" * 80)
print("STEP 5: SUMMARY")
print("=" * 80)
# Deduplicate simple
seen_simple_equal = {}
seen_simple_new = {}
for m in simple_mappings:
key = (m['sku'], m['codmat'])
if m['type'] == 'already_equal':
seen_simple_equal[key] = m
else:
seen_simple_new[key] = m
seen_repack = {}
for m in repack_mappings:
key = (m['sku'], m['codmat'])
if key not in seen_repack:
seen_repack[key] = m
seen_complex = {}
for m in complex_mappings:
key = (m['sku'], m['codmat'])
if key not in seen_complex:
seen_complex[key] = m
# Deduplicate unresolved SKUs
seen_unresolved_skus = {}
for u in unresolved:
if u['sku'] not in seen_unresolved_skus:
seen_unresolved_skus[u['sku']] = u
print(f"\n--- Already mapped (SKU == CODMAT in nom_articole): {len(seen_simple_equal)} unique ---")
for key, m in sorted(seen_simple_equal.items()):
print(f" {m['sku']:25s} = {m['codmat']:15s} | {(m['product_name'] or '')[:40]}")
print(f"\n--- NEW simple 1:1 (SKU != CODMAT, same qty): {len(seen_simple_new)} unique ---")
for key, m in sorted(seen_simple_new.items()):
print(f" {m['sku']:25s}{m['codmat']:15s} | GoMag: {(m['product_name'] or '')[:30]} → ROA: {(m['denumire'] or '')[:30]}")
print(f"\n--- Repackaging (different qty): {len(seen_repack)} unique ---")
for key, m in sorted(seen_repack.items()):
print(f" {m['sku']:25s}{m['codmat']:15s} x{m['cantitate_roa']} | {(m['product_name'] or '')[:30]}{(m['denumire'] or '')[:30]}")
print(f"\n--- Complex sets (1 SKU → N CODMATs): {len(seen_complex)} unique ---")
for key, m in sorted(seen_complex.items()):
print(f" {m['sku']:25s}{m['codmat']:15s} {m['procent_pret']:6.2f}% | {(m['product_name'] or '')[:30]}{(m['denumire'] or '')[:30]}")
print(f"\n--- Unresolved (unique SKUs): {len(seen_unresolved_skus)} ---")
for sku, u in sorted(seen_unresolved_skus.items()):
print(f" {sku:25s} | {(u['product_name'] or '')[:40]} | example: order={u['order']}")
print(f"\n--- Unmatched orders (no invoice found): {len(unmatched_orders)} ---")
for o in unmatched_orders[:20]:
print(f" {o['order_number']:>12s} | {str(o['order_date'])[:10]} | {(o['customer_name'] or '')[:30]:30s} | {o['order_total'] or 0:10.2f} | {o['status']}")
if len(unmatched_orders) > 20:
print(f" ... and {len(unmatched_orders) - 20} more")
# --- Write output files ---
out_dir = r'C:\gomag-vending\scripts\output'
os.makedirs(out_dir, exist_ok=True)
# Full match report
with open(os.path.join(out_dir, 'match_report.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['order_number', 'customer', 'order_total', 'factura', 'inv_total', 'score',
'go_items', 'roa_items', 'matched', 'unresolved'])
for d in match_details:
w.writerow([d['order_number'], d['customer'], d['order_total'],
d['factura'], d['inv_total'], f"{d['score']:.2f}",
d['go_items'], d['roa_items'],
'; '.join(d['matched_items']),
'; '.join(d['unresolved_items'])])
# New simple mappings (SKU → CODMAT where SKU != CODMAT)
with open(os.path.join(out_dir, 'simple_new_mappings.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['sku', 'codmat', 'id_articol', 'product_name_gomag', 'denumire_roa', 'go_qty', 'roa_qty', 'go_price', 'roa_pret'])
for m in seen_simple_new.values():
w.writerow([m['sku'], m['codmat'], m['id_articol'], m['product_name'], m['denumire'],
m['go_qty'], m['roa_qty'], m['go_price'], m['roa_pret']])
# Repackaging CSV
with open(os.path.join(out_dir, 'repack_mappings.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['sku', 'codmat', 'cantitate_roa', 'procent_pret', 'product_name_gomag', 'denumire_roa'])
for m in seen_repack.values():
w.writerow([m['sku'], m['codmat'], m['cantitate_roa'], 100, m['product_name'], m['denumire']])
# Complex sets CSV
with open(os.path.join(out_dir, 'complex_mappings.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['sku', 'codmat', 'cantitate_roa', 'procent_pret', 'product_name_gomag', 'denumire_roa'])
for m in seen_complex.values():
w.writerow([m['sku'], m['codmat'], round(m['cantitate_roa'], 3), m['procent_pret'],
m['product_name'], m['denumire']])
# Unresolved
with open(os.path.join(out_dir, 'unresolved.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['sku', 'product_name', 'quantity', 'price', 'order', 'factura', 'roa_remaining_items'])
for u in unresolved:
w.writerow([u['sku'], u['product_name'], u['quantity'], u['price'],
u['order'], u['factura'], u['roa_remaining']])
# Already equal (for reference)
with open(os.path.join(out_dir, 'already_mapped.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['sku', 'codmat', 'id_articol', 'product_name_gomag', 'denumire_roa'])
for m in seen_simple_equal.values():
w.writerow([m['sku'], m['codmat'], m['id_articol'], m['product_name'], m['denumire']])
# Unmatched orders
with open(os.path.join(out_dir, 'unmatched_orders.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['order_number', 'order_date', 'customer_name', 'status', 'order_total', 'items_count'])
for o in unmatched_orders:
w.writerow([o['order_number'], str(o['order_date'])[:10], o['customer_name'],
o['status'], o['order_total'], len(o['items'])])
print(f"\nOutput written to {out_dir}:")
print(f" match_report.csv - {len(match_details)} matched order-invoice pairs")
print(f" already_mapped.csv - {len(seen_simple_equal)} SKU==CODMAT (already OK)")
print(f" simple_new_mappings.csv - {len(seen_simple_new)} new SKU→CODMAT (need codmat in nom_articole or ARTICOLE_TERTI)")
print(f" repack_mappings.csv - {len(seen_repack)} repackaging")
print(f" complex_mappings.csv - {len(seen_complex)} complex sets")
print(f" unresolved.csv - {len(unresolved)} unresolved item lines")
print(f" unmatched_orders.csv - {len(unmatched_orders)} orders without invoice match")

View File

@@ -1,306 +0,0 @@
#!/usr/bin/env python3
"""
Parser pentru log-urile sync_comenzi_web.
Extrage comenzi esuate, SKU-uri lipsa, si genereaza un sumar.
Suporta atat formatul vechi (verbose) cat si formatul nou (compact).
Utilizare:
python parse_sync_log.py # Ultimul log din vfp/log/
python parse_sync_log.py <fisier.log> # Log specific
python parse_sync_log.py --skus # Doar lista SKU-uri lipsa
python parse_sync_log.py --dir /path/to/logs # Director custom
"""
import os
import sys
import re
import glob
import argparse
# Regex pentru linii cu timestamp (intrare noua in log)
RE_TIMESTAMP = re.compile(r'^\[(\d{2}:\d{2}:\d{2})\]\s+\[(\w+\s*)\]\s*(.*)')
# Regex format NOU: [N/Total] OrderNumber P:X A:Y/Z -> OK/ERR details
RE_COMPACT_OK = re.compile(r'\[(\d+)/(\d+)\]\s+(\S+)\s+.*->\s+OK\s+ID:(\S+)')
RE_COMPACT_ERR = re.compile(r'\[(\d+)/(\d+)\]\s+(\S+)\s+.*->\s+ERR\s+(.*)')
# Regex format VECHI (backwards compat)
RE_SKU_NOT_FOUND = re.compile(r'SKU negasit.*?:\s*(\S+)')
RE_PRICE_POLICY = re.compile(r'Pretul pentru acest articol nu a fost gasit')
RE_FAILED_ORDER = re.compile(r'Import comanda esuat pentru\s+(\S+)')
RE_ARTICOL_ERR = re.compile(r'Eroare adaugare articol\s+(\S+)')
RE_ORDER_PROCESS = re.compile(r'Procesez comanda:\s+(\S+)\s+din\s+(\S+)')
RE_ORDER_SUCCESS = re.compile(r'SUCCES: Comanda importata.*?ID Oracle:\s+(\S+)')
# Regex comune
RE_SYNC_END = re.compile(r'SYNC END\s*\|.*?(\d+)\s+processed.*?(\d+)\s+ok.*?(\d+)\s+err')
RE_STATS_LINE = re.compile(r'Duration:\s*(\S+)\s*\|\s*Orders:\s*(\S+)')
RE_STOPPED_EARLY = re.compile(r'Peste \d+.*ero|stopped early')
def find_latest_log(log_dir):
"""Gaseste cel mai recent log sync_comenzi din directorul specificat."""
pattern = os.path.join(log_dir, 'sync_comenzi_*.log')
files = glob.glob(pattern)
if not files:
return None
return max(files, key=os.path.getmtime)
def parse_log_entries(lines):
"""Parseaza liniile log-ului in intrari structurate."""
entries = []
current = None
for line in lines:
line = line.rstrip('\n\r')
m = RE_TIMESTAMP.match(line)
if m:
if current:
entries.append(current)
current = {
'time': m.group(1),
'level': m.group(2).strip(),
'text': m.group(3),
'full': line,
'continuation': []
}
elif current is not None:
current['continuation'].append(line)
current['text'] += '\n' + line
if current:
entries.append(current)
return entries
def extract_sku_from_error(err_text):
"""Extrage SKU din textul erorii (diverse formate)."""
# SKU_NOT_FOUND: 8714858424056
m = re.search(r'SKU_NOT_FOUND:\s*(\S+)', err_text)
if m:
return ('SKU_NOT_FOUND', m.group(1))
# PRICE_POLICY: 8000070028685
m = re.search(r'PRICE_POLICY:\s*(\S+)', err_text)
if m:
return ('PRICE_POLICY', m.group(1))
# Format vechi: SKU negasit...NOM_ARTICOLE: xxx
m = RE_SKU_NOT_FOUND.search(err_text)
if m:
return ('SKU_NOT_FOUND', m.group(1))
# Format vechi: Eroare adaugare articol xxx
m = RE_ARTICOL_ERR.search(err_text)
if m:
return ('ARTICOL_ERROR', m.group(1))
# Format vechi: Pretul...
if RE_PRICE_POLICY.search(err_text):
return ('PRICE_POLICY', '(SKU necunoscut)')
return (None, None)
def analyze_entries(entries):
"""Analizeaza intrarile si extrage informatii relevante."""
result = {
'start_time': None,
'end_time': None,
'duration': None,
'total_orders': 0,
'success_orders': 0,
'error_orders': 0,
'stopped_early': False,
'failed': [],
'missing_skus': [],
}
seen_skus = set()
current_order = None
for entry in entries:
text = entry['text']
level = entry['level']
# Start/end time
if entry['time']:
if result['start_time'] is None:
result['start_time'] = entry['time']
result['end_time'] = entry['time']
# Format NOU: SYNC END line cu statistici
m = RE_SYNC_END.search(text)
if m:
result['total_orders'] = int(m.group(1))
result['success_orders'] = int(m.group(2))
result['error_orders'] = int(m.group(3))
# Format NOU: compact OK line
m = RE_COMPACT_OK.search(text)
if m:
continue
# Format NOU: compact ERR line
m = RE_COMPACT_ERR.search(text)
if m:
order_nr = m.group(3)
err_detail = m.group(4).strip()
err_type, sku = extract_sku_from_error(err_detail)
if err_type and sku:
result['failed'].append((order_nr, err_type, sku))
if sku not in seen_skus and sku != '(SKU necunoscut)':
seen_skus.add(sku)
result['missing_skus'].append(sku)
else:
result['failed'].append((order_nr, 'ERROR', err_detail[:60]))
continue
# Stopped early
if RE_STOPPED_EARLY.search(text):
result['stopped_early'] = True
# Format VECHI: statistici din sumar
if 'Total comenzi procesate:' in text:
try:
result['total_orders'] = int(text.split(':')[-1].strip())
except ValueError:
pass
if 'Comenzi importate cu succes:' in text:
try:
result['success_orders'] = int(text.split(':')[-1].strip())
except ValueError:
pass
if 'Comenzi cu erori:' in text:
try:
result['error_orders'] = int(text.split(':')[-1].strip())
except ValueError:
pass
# Format VECHI: Duration line
m = RE_STATS_LINE.search(text)
if m:
result['duration'] = m.group(1)
# Format VECHI: erori
if level == 'ERROR':
m_fail = RE_FAILED_ORDER.search(text)
if m_fail:
current_order = m_fail.group(1)
m = RE_ORDER_PROCESS.search(text)
if m:
current_order = m.group(1)
err_type, sku = extract_sku_from_error(text)
if err_type and sku:
order_nr = current_order or '?'
result['failed'].append((order_nr, err_type, sku))
if sku not in seen_skus and sku != '(SKU necunoscut)':
seen_skus.add(sku)
result['missing_skus'].append(sku)
# Duration din SYNC END
m = re.search(r'\|\s*(\d+)s\s*$', text)
if m:
result['duration'] = m.group(1) + 's'
return result
def format_report(result, log_path):
"""Formateaza raportul complet."""
lines = []
lines.append('=== SYNC LOG REPORT ===')
lines.append(f'File: {os.path.basename(log_path)}')
duration = result["duration"] or "?"
start = result["start_time"] or "?"
end = result["end_time"] or "?"
lines.append(f'Run: {start} - {end} ({duration})')
lines.append('')
stopped = 'YES' if result['stopped_early'] else 'NO'
lines.append(
f'SUMMARY: {result["total_orders"]} processed, '
f'{result["success_orders"]} success, '
f'{result["error_orders"]} errors '
f'(stopped early: {stopped})'
)
lines.append('')
if result['failed']:
lines.append('FAILED ORDERS:')
seen = set()
for order_nr, err_type, sku in result['failed']:
key = (order_nr, err_type, sku)
if key not in seen:
seen.add(key)
lines.append(f' {order_nr:<12} {err_type:<18} {sku}')
lines.append('')
if result['missing_skus']:
lines.append(f'MISSING SKUs ({len(result["missing_skus"])} unique):')
for sku in sorted(result['missing_skus']):
lines.append(f' {sku}')
lines.append('')
return '\n'.join(lines)
def main():
parser = argparse.ArgumentParser(
description='Parser pentru log-urile sync_comenzi_web'
)
parser.add_argument(
'logfile', nargs='?', default=None,
help='Fisier log specific (default: ultimul din vfp/log/)'
)
parser.add_argument(
'--skus', action='store_true',
help='Afiseaza doar lista SKU-uri lipsa (una pe linie)'
)
parser.add_argument(
'--dir', default=None,
help='Director cu log-uri (default: vfp/log/ relativ la script)'
)
args = parser.parse_args()
if args.logfile:
log_path = args.logfile
else:
if args.dir:
log_dir = args.dir
else:
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(script_dir)
log_dir = os.path.join(project_dir, 'vfp', 'log')
log_path = find_latest_log(log_dir)
if not log_path:
print(f'Nu am gasit fisiere sync_comenzi_*.log in {log_dir}',
file=sys.stderr)
sys.exit(1)
if not os.path.isfile(log_path):
print(f'Fisierul nu exista: {log_path}', file=sys.stderr)
sys.exit(1)
with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
entries = parse_log_entries(lines)
result = analyze_entries(entries)
if args.skus:
for sku in sorted(result['missing_skus']):
print(sku)
else:
print(format_report(result, log_path))
if __name__ == '__main__':
main()