From 06f8fa58422e444e2a9de3a69a275deb66896131 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Tue, 17 Mar 2026 12:23:40 +0000 Subject: [PATCH] cleanup: remove 5 duplicate scripts from scripts/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed scripts covered by more complete alternatives: - match_by_price.py, match_invoices.py → covered by match_all.py - compare_detail.py → covered by match_all.py - reset_sqlite.py → covered by delete_imported.py (includes Oracle + dry-run) - debug_match.py → one-off hardcoded debug script Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/compare_detail.py | 325 ---------------------------- scripts/debug_match.py | 112 ---------- scripts/match_by_price.py | 414 ----------------------------------- scripts/match_invoices.py | 442 -------------------------------------- scripts/reset_sqlite.py | 42 ---- 5 files changed, 1335 deletions(-) delete mode 100644 scripts/compare_detail.py delete mode 100644 scripts/debug_match.py delete mode 100644 scripts/match_by_price.py delete mode 100644 scripts/match_invoices.py delete mode 100644 scripts/reset_sqlite.py diff --git a/scripts/compare_detail.py b/scripts/compare_detail.py deleted file mode 100644 index 306f3f4..0000000 --- a/scripts/compare_detail.py +++ /dev/null @@ -1,325 +0,0 @@ -""" -Generate detailed comparison CSV: GoMag orders vs Oracle invoices -Side-by-side view for manual analysis. -""" -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() - -# --- Load GoMag 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, 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()] - -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() -print(f"Loaded {len(orders)} GoMag orders") - -# --- Load Oracle invoices --- -conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA') -cur = conn.cursor() - -min_date = min(str(o['order_date'])[:10] for o in orders) -max_date = max(str(o['order_date'])[:10] for o in orders) - -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') - 3 - AND v.data_act <= TO_DATE(:2, 'YYYY-MM-DD') + 3 - AND v.total_cu_tva > 0 - ORDER BY v.data_act DESC -""", [min_date, max_date]) - -invoices = [] -inv_map = {} -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(), - 'items': [], - } - invoices.append(inv) - inv_map[inv['id_vanzare']] = inv - -# Batch fetch details -inv_ids = [inv['id_vanzare'] for inv in invoices] -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() -print(f"Loaded {len(invoices)} Oracle invoices") - -# --- Fuzzy matching orders → invoices --- -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 - 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 = [] -used_invoices = set() -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 - try: - od = int(order_date.replace('-','')) - id_ = int(inv['data_act'].replace('-','')) - date_diff = abs(od - id_) - except: - continue - if date_diff > 3: - continue - - 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 - - sim = name_similarity(order_name, inv['partener']) - 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) - - date_score = 1 if date_diff == 0 else (0.7 if date_diff == 1 else (0.4 if date_diff == 2 else 0.2)) - 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: - matches.append({'order': order, 'invoice': None, 'score': 0}) - -# Sort by order date -matches.sort(key=lambda m: str(m['order']['order_date']), reverse=True) - -# --- Write detailed comparison CSV --- -out_dir = r'C:\gomag-vending\scripts\output' -os.makedirs(out_dir, exist_ok=True) - -with open(os.path.join(out_dir, 'comparatie_detaliata.csv'), 'w', newline='', encoding='utf-8-sig') as f: - w = csv.writer(f, delimiter=';') - - w.writerow([ - 'NR_COMANDA_GOMAG', 'DATA_COMANDA', 'CLIENT_GOMAG', 'STATUS_IMPORT', - 'TOTAL_COMANDA_GOMAG', - 'NR_ARTICOLE_GOMAG', 'SKU_GOMAG', 'PRODUS_GOMAG', 'QTY_GOMAG', 'PRET_GOMAG', 'TVA_GOMAG', - 'LINIE_TOTAL_GOMAG', - '|', - 'FACTURA_ROA', 'DATA_FACTURA', 'CLIENT_ROA', 'TOTAL_FACTURA_ROA', - 'NR_ARTICOLE_ROA', 'CODMAT_ROA', 'PRODUS_ROA', 'QTY_ROA', 'PRET_ROA', 'TVA_ROA', - 'LINIE_TOTAL_ROA', - '|', - 'MATCH_SCORE', 'DIFF_TOTAL', 'SKU_EQ_CODMAT', - ]) - - for m in matches: - o = m['order'] - inv = m['invoice'] - go_items = o['items'] - roa_items = inv['items'] if inv else [] - - # Filter out transport/discount for comparison count - roa_real = [ri for ri in roa_items if ri['codmat'] not in ('TRANSPORT', 'DISCOUNT', None, '') and ri['cantitate'] > 0] - roa_extra = [ri for ri in roa_items if ri['codmat'] in ('TRANSPORT', 'DISCOUNT') or ri['cantitate'] < 0] - - max_lines = max(len(go_items), len(roa_items), 1) - - order_total = o['order_total'] or 0 - inv_total = inv['total_cu_tva'] if inv else 0 - diff_total = round(order_total - inv_total, 2) if inv else '' - - for idx in range(max_lines): - gi = go_items[idx] if idx < len(go_items) else None - ri = roa_items[idx] if idx < len(roa_items) else None - - # GoMag side - if idx == 0: - go_order = o['order_number'] - go_date = str(o['order_date'])[:10] - go_client = o['customer_name'] or '' - go_status = o['status'] - go_total = order_total - go_nr_art = len(go_items) - else: - go_order = '' - go_date = '' - go_client = '' - go_status = '' - go_total = '' - go_nr_art = '' - - if gi: - go_sku = gi['sku'] or '' - go_prod = gi['product_name'] or '' - go_qty = gi['quantity'] - go_price = gi['price'] - go_vat = gi['vat'] - go_line_total = round(gi['quantity'] * gi['price'], 2) - else: - go_sku = go_prod = go_qty = go_price = go_vat = go_line_total = '' - - # ROA side - if idx == 0 and inv: - roa_fact = f"{inv['serie_act']}{inv['numar_act']}" - roa_date = inv['data_act'] - roa_client = inv['partener'] - roa_total = inv_total - roa_nr_art = len(roa_items) - else: - roa_fact = '' - roa_date = '' - roa_client = '' - roa_total = '' - roa_nr_art = '' - - if ri: - roa_codmat = ri['codmat'] or '' - roa_prod = ri['denumire'] or '' - roa_qty = ri['cantitate'] - roa_price = ri['pret'] - roa_vat = ri['tva_pct'] - roa_line_total = round(ri['cantitate'] * ri['pret'], 2) if ri['cantitate'] > 0 else round(-ri['cantitate'] * ri['pret'], 2) - else: - roa_codmat = roa_prod = roa_qty = roa_price = roa_vat = roa_line_total = '' - - # Match indicators - if idx == 0: - score = round(m['score'], 2) if m['score'] else '' - diff = diff_total - else: - score = '' - diff = '' - - # Check SKU == CODMAT - sku_eq = '' - if gi and ri and go_sku and roa_codmat: - if go_sku == roa_codmat: - sku_eq = 'DA' - else: - sku_eq = '' - - w.writerow([ - go_order, go_date, go_client, go_status, - go_total, - go_nr_art, go_sku, go_prod, go_qty, go_price, go_vat, - go_line_total, - '|', - roa_fact, roa_date, roa_client, roa_total, - roa_nr_art, roa_codmat, roa_prod, roa_qty, roa_price, roa_vat, - roa_line_total, - '|', - score, diff, sku_eq, - ]) - - # Empty separator row before unmatched invoice summary - w.writerow([]) - w.writerow(['--- FACTURI ROA FARA COMANDA GOMAG ---']) - w.writerow([]) - - unmatched_inv = [inv for inv in invoices if inv['id_vanzare'] not in used_invoices] - unmatched_inv.sort(key=lambda x: x['data_act'], reverse=True) - - for inv in unmatched_inv: - for idx, ri in enumerate(inv['items']): - if idx == 0: - w.writerow([ - '', '', '', '', '', '', '', '', '', '', '', '', - '|', - f"{inv['serie_act']}{inv['numar_act']}", inv['data_act'], - inv['partener'], inv['total_cu_tva'], - len(inv['items']), - ri['codmat'] or '', ri['denumire'] or '', - ri['cantitate'], ri['pret'], ri['tva_pct'], - round(ri['cantitate'] * ri['pret'], 2), - '|', '', '', '', - ]) - else: - w.writerow([ - '', '', '', '', '', '', '', '', '', '', '', '', - '|', - '', '', '', '', - '', - ri['codmat'] or '', ri['denumire'] or '', - ri['cantitate'], ri['pret'], ri['tva_pct'], - round(ri['cantitate'] * ri['pret'], 2), - '|', '', '', '', - ]) - -print(f"\nDone!") -print(f"Matched: {sum(1 for m in matches if m['invoice'])} / {len(orders)} orders") -print(f"Unmatched invoices: {len(unmatched_inv)}") -print(f"\nOutput: {os.path.join(out_dir, 'comparatie_detaliata.csv')}") -print(f"Open in Excel (separator: ;, encoding: UTF-8)") diff --git a/scripts/debug_match.py b/scripts/debug_match.py deleted file mode 100644 index 2d52496..0000000 --- a/scripts/debug_match.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Debug matching for specific order 480102897""" -import oracledb -import os -import sys -import sqlite3 -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() - -# Get GoMag order -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 = '480102897'") -order = dict(c.fetchone()) -c.execute("SELECT * FROM order_items WHERE order_number = '480102897'") -items = [dict(r) for r in c.fetchall()] -db.close() - -print(f"=== GoMag Order 480102897 ===") -print(f" Client: {order['customer_name']}") -print(f" Billing: {order['billing_name']}") -print(f" Shipping: {order['shipping_name']}") -print(f" Date: {order['order_date']}") -print(f" Total: {order['order_total']}") -print(f" Status: {order['status']}") -print(f" Items ({len(items)}):") -for it in items: - print(f" SKU={it['sku']:20s} qty={it['quantity']:6.1f} price={it['price']:8.2f} {it['product_name']}") - -# Now search Oracle for ALL invoices around that date with similar total -conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA') -cur = conn.cursor() - -order_date = str(order['order_date'])[:10] -order_total = order['order_total'] - -print(f"\n=== Oracle invoices near date {order_date}, total ~{order_total} ===") -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_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') - 3 - AND v.data_act <= TO_DATE(:2, 'YYYY-MM-DD') + 3 - AND v.total_cu_tva > 0 - ORDER BY ABS(v.total_cu_tva - :3), v.data_act -""", [order_date, order_date, order_total]) - -print(f"{'NR_FACT':>8s} {'SERIE':>5s} {'DATA':>12s} {'TOTAL_CU':>10s} {'DIFF':>10s} {'PARTENER':40s}") -candidates = [] -for r in cur: - total = float(r[4] or 0) - diff = total - order_total - partener = ((r[6] or '') + ' ' + (r[7] or '')).strip() - print(f"{str(r[1]):>8s} {str(r[2] or ''):>5s} {r[3]:>12s} {total:10.2f} {diff:+10.2f} {partener:40s}") - candidates.append({'numar_act': r[1], 'serie_act': r[2], 'data_act': r[3], - 'total': total, 'partener': partener, 'id_vanzare': r[0]}) - if len(candidates) >= 20: - break - -# Also search by client name -print(f"\n=== Oracle invoices by name 'STOICA' or 'LIVIU' near that date ===") -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_cu_tva, - 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') - 5 - AND v.data_act <= TO_DATE(:2, 'YYYY-MM-DD') + 5 - AND (UPPER(p.denumire) LIKE '%STOICA%' OR UPPER(p.prenume) LIKE '%STOICA%' - OR UPPER(p.denumire) LIKE '%LIVIU%' OR UPPER(p.prenume) LIKE '%LIVIU%' - OR UPPER(p.denumire) LIKE '%SLM%') - ORDER BY v.data_act DESC -""", [order_date, order_date]) - -for r in cur: - total = float(r[4] or 0) - partener = ((r[5] or '') + ' ' + (r[6] or '')).strip() - print(f" {str(r[1]):>8s} {str(r[2] or ''):>5s} {r[3]:>12s} {total:10.2f} {partener}") - -# Show details of invoice 4035 -print(f"\n=== Details of invoice VM4035 ===") -cur.execute(""" - SELECT v.id_vanzare, TO_CHAR(v.data_act, 'YYYY-MM-DD'), 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 = 4035 AND v.serie_act = 'VM' AND v.sters = 0 -""") -row = cur.fetchone() -if row: - print(f" Date: {row[1]}, Total: {float(row[2]):.2f}, Client: {row[3]} {row[4]}") - cur.execute(""" - SELECT a.codmat, a.denumire, vd.cantitate, vd.pret, vd.pret_cu_tva - 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 - """, [row[0]]) - for r in cur: - print(f" COD={r[0] or '':20s} qty={float(r[2]):6.1f} pret={float(r[3]):8.2f} pretcu={float(r[4]):8.2f} {r[1]}") - -conn.close() diff --git a/scripts/match_by_price.py b/scripts/match_by_price.py deleted file mode 100644 index e2ff6f4..0000000 --- a/scripts/match_by_price.py +++ /dev/null @@ -1,414 +0,0 @@ -""" -Match GoMag SKUs → ROA id_articol by matching order lines on unit price. -For each matched order-invoice pair, compare lines by price to discover mappings. -Output: SQL for nom_articole codmat updates + CSV for ARTICOLE_TERTI mappings. -""" -import oracledb -import os -import sys -import sqlite3 -import csv -from collections import defaultdict -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() - -# --- Load GoMag 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, order_date, customer_name, status, order_total FROM orders ORDER BY order_date DESC") -orders = [dict(r) for r in c.fetchall()] -for order in orders: - c.execute("SELECT sku, product_name, quantity, price, vat FROM order_items WHERE order_number = ? ORDER BY sku", (order['order_number'],)) - order['items'] = [dict(r) for r in c.fetchall()] -db.close() -print(f"Loaded {len(orders)} GoMag orders") - -# --- Load Oracle invoices --- -conn = oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA') -cur = conn.cursor() -min_date = min(str(o['order_date'])[:10] for o in orders) -max_date = max(str(o['order_date'])[:10] for o in orders) - -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') - 3 - AND v.data_act <= TO_DATE(:2, 'YYYY-MM-DD') + 3 AND v.total_cu_tva > 0 - ORDER BY v.data_act DESC -""", [min_date, max_date]) - -invoices = [] -inv_map = {} -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(), - 'items': [], - } - invoices.append(inv) - inv_map[inv['id_vanzare']] = inv - -inv_ids = [inv['id_vanzare'] for inv in invoices] -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), - }) - -print(f"Loaded {len(invoices)} Oracle invoices") - -# --- Match orders → invoices (same as before) --- -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 - sim1 = SequenceMatcher(None, nn1, nn2).ratio() - words1 = nn1.split() - if len(words1) >= 2: - sim2 = SequenceMatcher(None, ' '.join(reversed(words1)), nn2).ratio() - return max(sim1, sim2) - return sim1 - -matches = [] -used_invoices = set() -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 - - for inv in invoices: - if inv['id_vanzare'] in used_invoices: - continue - try: - date_diff = abs(int(order_date.replace('-','')) - int(inv['data_act'].replace('-',''))) - except: - continue - if date_diff > 3: - continue - 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 - sim = name_similarity(order['customer_name'] or '', inv['partener']) - date_score = 1 if date_diff == 0 else (0.7 if date_diff == 1 else (0.4 if date_diff == 2 else 0.2)) - 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']) - -print(f"Matched: {len(matches)} orders → invoices") - -# --- Match line items by PRICE --- -# For each matched pair, match GoMag items → ROA items by line total (qty * price) -# Discovery: SKU → (id_articol, codmat, denumire, qty_ratio) - -# Collect all discovered mappings: sku → list of observations -sku_observations = defaultdict(list) - -for m in matches: - o = m['order'] - inv = m['invoice'] - go_items = o['items'] - # Exclude transport/discount from ROA - roa_items = [ri for ri in inv['items'] if ri['cantitate'] > 0 - and ri['codmat'] not in ('TRANSPORT', 'DISCOUNT')] - roa_transport = [ri for ri in inv['items'] - if ri['codmat'] in ('TRANSPORT', 'DISCOUNT') or ri['cantitate'] < 0] - - go_remaining = list(range(len(go_items))) - roa_remaining = list(range(len(roa_items))) - item_matches = [] - - # Pass 1: match by line total (qty * unit_price_fara_tva) - for gi_idx in list(go_remaining): - gi = go_items[gi_idx] - go_line = gi['quantity'] * gi['price'] # cu TVA - go_line_fara = go_line / (1 + gi['vat']/100) if gi['vat'] else go_line - - for ri_idx in list(roa_remaining): - ri = roa_items[ri_idx] - roa_line = ri['cantitate'] * ri['pret'] # fara TVA - - if abs(go_line_fara - roa_line) < 0.50: - item_matches.append((gi_idx, [ri_idx])) - go_remaining.remove(gi_idx) - roa_remaining.remove(ri_idx) - break - - # Pass 2: match by unit price (for items where qty might differ but price is same) - for gi_idx in list(go_remaining): - gi = go_items[gi_idx] - go_price_fara = gi['price'] / (1 + gi['vat']/100) if gi['vat'] else gi['price'] - - for ri_idx in list(roa_remaining): - ri = roa_items[ri_idx] - if abs(go_price_fara - ri['pret']) < 0.02: - item_matches.append((gi_idx, [ri_idx])) - go_remaining.remove(gi_idx) - roa_remaining.remove(ri_idx) - break - - # Pass 3: 1:1 positional if same count remaining - if len(go_remaining) == 1 and len(roa_remaining) == 1: - item_matches.append((go_remaining[0], [roa_remaining[0]])) - go_remaining = [] - roa_remaining = [] - - # Pass 4: 1:N — one GoMag item matches multiple ROA items by combined total - for gi_idx in list(go_remaining): - gi = go_items[gi_idx] - go_line_fara = (gi['quantity'] * gi['price']) / (1 + gi['vat']/100) if gi['vat'] else gi['quantity'] * gi['price'] - - if len(roa_remaining) >= 2: - 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 = ri1['cantitate'] * ri1['pret'] + ri2['cantitate'] * ri2['pret'] - if abs(go_line_fara - combined) < 1.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) - break - else: - continue - break - - # Record observations - 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] - qty_ratio = ri['cantitate'] / gi['quantity'] if gi['quantity'] else 1 - sku_observations[gi['sku']].append({ - 'type': 'simple' if abs(qty_ratio - round(qty_ratio)) < 0.01 and abs(qty_ratio - 1) < 0.01 else 'repack', - 'id_articol': ri['id_articol'], - 'codmat': ri['codmat'], - 'denumire': ri['denumire'], - 'go_qty': gi['quantity'], - 'roa_qty': ri['cantitate'], - 'qty_ratio': round(qty_ratio, 4), - 'go_price': gi['price'], - 'roa_pret': ri['pret'], - 'product_name': gi['product_name'], - 'order': o['order_number'], - 'factura': f"VM{inv['numar_act']}", - }) - else: - # Complex set - go_line_fara = (gi['quantity'] * gi['price']) / (1 + gi['vat']/100) if gi['vat'] else gi['quantity'] * gi['price'] - for ri in ris: - ri_line = ri['cantitate'] * ri['pret'] - pct = round(ri_line / go_line_fara * 100, 2) if go_line_fara else 0 - qty_ratio = ri['cantitate'] / gi['quantity'] if gi['quantity'] else 1 - sku_observations[gi['sku']].append({ - 'type': 'set', - 'id_articol': ri['id_articol'], - 'codmat': ri['codmat'], - 'denumire': ri['denumire'], - 'go_qty': gi['quantity'], - 'roa_qty': ri['cantitate'], - 'qty_ratio': round(qty_ratio, 4), - 'procent_pret': pct, - 'go_price': gi['price'], - 'roa_pret': ri['pret'], - 'product_name': gi['product_name'], - 'order': o['order_number'], - 'factura': f"VM{inv['numar_act']}", - }) - -conn.close() - -# --- Analyze observations: find consistent mappings --- -print(f"\n{'='*80}") -print(f"ANALYSIS: {len(sku_observations)} unique SKUs with observations") -print(f"{'='*80}") - -# For each SKU, check if all observations agree on the same id_articol -simple_update = {} # SKU → {id_articol, codmat, denumire} — for nom_articole UPDATE -repack_csv = {} # (SKU, codmat) → {cantitate_roa} — for ARTICOLE_TERTI -set_csv = {} # (SKU, codmat) → {cantitate_roa, procent_pret} -inconsistent = {} # SKU → list of conflicting observations -already_has_codmat = {} # SKU already equals codmat - -for sku, obs_list in sorted(sku_observations.items()): - # Group by id_articol - by_articol = defaultdict(list) - for obs in obs_list: - by_articol[obs['id_articol']].append(obs) - - # Check if any observation shows SKU == CODMAT already - if any(obs.get('codmat') == sku for obs in obs_list): - already_has_codmat[sku] = obs_list[0] - continue - - # Filter to types - types = set(obs['type'] for obs in obs_list) - - if 'set' in types: - # Complex set — collect all components - components = {} - for obs in obs_list: - if obs['type'] == 'set': - key = obs['id_articol'] - if key not in components: - components[key] = obs - # Check consistency across observations - if len(components) >= 2: - for art_id, obs in components.items(): - codmat = obs['codmat'] or f"ID:{art_id}" - set_csv[(sku, codmat)] = { - 'id_articol': art_id, - 'cantitate_roa': obs['qty_ratio'], - 'procent_pret': obs['procent_pret'], - 'denumire': obs['denumire'], - 'product_name': obs['product_name'], - } - continue - - if len(by_articol) == 1: - # All observations point to same article - art_id = list(by_articol.keys())[0] - obs = by_articol[art_id][0] - - # Check qty ratios are consistent - ratios = [o['qty_ratio'] for o in by_articol[art_id]] - avg_ratio = sum(ratios) / len(ratios) - - if all(abs(r - avg_ratio) < 0.01 for r in ratios): - if abs(avg_ratio - 1.0) < 0.01: - # Simple 1:1 - simple_update[sku] = { - 'id_articol': art_id, - 'codmat_actual': obs['codmat'], - 'denumire': obs['denumire'], - 'product_name': obs['product_name'], - 'observations': len(by_articol[art_id]), - } - else: - # Repackaging - codmat = obs['codmat'] or f"ID:{art_id}" - repack_csv[(sku, codmat)] = { - 'id_articol': art_id, - 'cantitate_roa': round(avg_ratio, 3), - 'denumire': obs['denumire'], - 'product_name': obs['product_name'], - 'observations': len(by_articol[art_id]), - } - else: - inconsistent[sku] = obs_list - else: - # Multiple different articles for same SKU across orders - if len(by_articol) == 1: - pass # handled above - else: - inconsistent[sku] = obs_list - -# --- Output --- -out_dir = r'C:\gomag-vending\scripts\output' -os.makedirs(out_dir, exist_ok=True) - -print(f"\n{'='*80}") -print(f"RESULTS") -print(f"{'='*80}") - -print(f"\n--- Already mapped (SKU == CODMAT): {len(already_has_codmat)} ---") - -print(f"\n--- Simple 1:1 → UPDATE nom_articole SET codmat = SKU: {len(simple_update)} ---") -for sku, info in sorted(simple_update.items()): - print(f" {sku:25s} → id_articol={info['id_articol']:6d} codmat_actual='{info['codmat_actual'] or ''}' [{info['denumire'][:40]}] ({info['observations']} obs)") - -print(f"\n--- Repackaging → ARTICOLE_TERTI: {len(repack_csv)} ---") -for (sku, codmat), info in sorted(repack_csv.items()): - print(f" {sku:25s} → {codmat:15s} x{info['cantitate_roa']} id_art={info['id_articol']} [{info['denumire'][:35]}] ({info['observations']} obs)") - -print(f"\n--- Complex sets → ARTICOLE_TERTI: {len(set_csv)} ---") -for (sku, codmat), info in sorted(set_csv.items()): - print(f" {sku:25s} → {codmat:15s} {info['procent_pret']:6.2f}% x{info['cantitate_roa']} [{info['denumire'][:35]}]") - -print(f"\n--- Inconsistent (different articles across orders): {len(inconsistent)} ---") -for sku, obs_list in sorted(inconsistent.items()): - arts = set((o['id_articol'], o['denumire'][:30]) for o in obs_list) - print(f" {sku:25s} → {len(arts)} different articles: {'; '.join(f'id={a[0]}({a[1]})' for a in arts)}") - -# Write SQL for simple updates -with open(os.path.join(out_dir, 'update_codmat.sql'), 'w', encoding='utf-8') as f: - f.write("-- UPDATE nom_articole: set codmat = GoMag SKU for 1:1 mappings\n") - f.write("-- Generated from invoice-order matching\n") - f.write("-- VERIFY BEFORE RUNNING!\n\n") - for sku, info in sorted(simple_update.items()): - f.write(f"-- {info['product_name'][:60]} → {info['denumire'][:60]}\n") - f.write(f"-- Current codmat: '{info['codmat_actual'] or ''}' | {info['observations']} order matches\n") - f.write(f"UPDATE nom_articole SET codmat = '{sku}' WHERE id_articol = {info['id_articol']} AND sters = 0;\n\n") - -# Write CSV for repackaging (ARTICOLE_TERTI format) -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', 'id_articol', 'product_name_gomag', 'denumire_roa', 'observations']) - for (sku, codmat), info in sorted(repack_csv.items()): - w.writerow([sku, codmat, info['cantitate_roa'], 100, info['id_articol'], info['product_name'], info['denumire'], info['observations']]) - -# Write CSV for sets -with open(os.path.join(out_dir, 'set_mappings.csv'), 'w', newline='', encoding='utf-8') as f: - w = csv.writer(f) - w.writerow(['sku', 'codmat', 'cantitate_roa', 'procent_pret', 'id_articol', 'product_name_gomag', 'denumire_roa']) - for (sku, codmat), info in sorted(set_csv.items()): - w.writerow([sku, codmat, info['cantitate_roa'], info['procent_pret'], info['id_articol'], info['product_name'], info['denumire']]) - -# Write inconsistent for manual review -with open(os.path.join(out_dir, 'inconsistent_skus.csv'), 'w', newline='', encoding='utf-8') as f: - w = csv.writer(f) - w.writerow(['sku', 'product_name', 'id_articol', 'codmat', 'denumire_roa', 'qty_ratio', 'type', 'order', 'factura']) - for sku, obs_list in sorted(inconsistent.items()): - for obs in obs_list: - w.writerow([sku, obs['product_name'], obs['id_articol'], obs['codmat'] or '', - obs['denumire'], obs['qty_ratio'], obs['type'], obs['order'], obs['factura']]) - -print(f"\nOutput written to {out_dir}:") -print(f" update_codmat.sql - {len(simple_update)} SQL updates for nom_articole") -print(f" repack_mappings.csv - {len(repack_csv)} repackaging mappings") -print(f" set_mappings.csv - {len(set_csv)} complex set mappings") -print(f" inconsistent_skus.csv - {len(inconsistent)} SKUs needing manual review") diff --git a/scripts/match_invoices.py b/scripts/match_invoices.py deleted file mode 100644 index 86f7420..0000000 --- a/scripts/match_invoices.py +++ /dev/null @@ -1,442 +0,0 @@ -""" -Match 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 - -# Fix Windows console encoding -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 GoMag orders from SQLite --- -print("=" * 80) -print("STEP 1: Loading 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() - -# Get orders with status IMPORTED or ALREADY_IMPORTED -c.execute(""" - SELECT order_number, order_date, customer_name, status, - id_comanda, order_total, billing_name, shipping_name - FROM orders - WHERE status IN ('IMPORTED', 'ALREADY_IMPORTED') - AND order_date >= date('now', '-10 days') - 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() -print(f"Loaded {len(orders)} GoMag orders") -for o in orders: - print(f" {o['order_number']:>10s} | {str(o['order_date'])[:10]} | {(o['customer_name'] or '')[:35]:35s} | {o['order_total'] or 0:10.2f} | {len(o['items'])} items") - -# --- Step 2: Get Oracle invoices (vanzari) with detail lines --- -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 vanzari header + partner name -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 >= SYSDATE - 10 - ORDER BY v.data_act DESC -""") - -invoices = [] -for r in cur: - inv = { - 'id_vanzare': r[0], - 'numar_act': r[1], - 'serie_act': r[2], - '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] if r[8] else ''), - } - invoices.append(inv) - -# Get detail lines for each invoice -for inv in invoices: - 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 - """, [inv['id_vanzare']]) - inv['items'] = [] - for r in cur: - inv['items'].append({ - 'id_articol': r[0], - 'codmat': r[1], - 'denumire': r[2], - 'cantitate': float(r[3] or 0), - 'pret': float(r[4] or 0), - 'pret_cu_tva': float(r[5] or 0), - 'tva_pct': float(r[6] or 0), - }) - -conn.close() -print(f"Loaded {len(invoices)} Oracle invoices") -for inv in invoices: - print(f" {inv['serie_act']}{str(inv['numar_act']):>6s} | {inv['data_act']} | {inv['partener'][:35]:35s} | {inv['total_cu_tva']:10.2f} | {len(inv['items'])} items") - -# --- 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 '' - return name.strip().upper().replace('S.R.L.', 'SRL').replace('S.R.L', 'SRL') - -def name_similarity(n1, n2): - return SequenceMatcher(None, normalize_name(n1), normalize_name(n2)).ratio() - -matches = [] -unmatched_orders = [] -used_invoices = set() - -for order in orders: - 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 same day or +/- 1 day) - inv_date = inv['data_act'] - date_diff = abs( - (int(order_date.replace('-','')) - int(inv_date.replace('-',''))) - ) - if date_diff > 1: - continue - - # Total match (within 5% or 5 lei) - total_diff = abs(order_total - inv['total_cu_tva']) - total_pct = total_diff / max(order_total, 0.01) * 100 - if total_pct > 5 and total_diff > 5: - continue - - # Name similarity - sim = name_similarity(order_name, inv['partener']) - - # Score: name similarity (0-1) + total closeness (0-1) + date match (0-1) - total_score = sim * 0.5 + (1 - min(total_pct/100, 1)) * 0.4 + (1 if date_diff == 0 else 0.5) * 0.1 - - if total_score > best_score: - best_score = total_score - best_match = inv - - if best_match and best_score > 0.3: - matches.append({ - 'order': order, - 'invoice': best_match, - 'score': best_score, - }) - used_invoices.add(best_match['id_vanzare']) - else: - unmatched_orders.append(order) - -print(f"\nMatched: {len(matches)} | Unmatched orders: {len(unmatched_orders)}") -print() - -for m in matches: - o = m['order'] - inv = m['invoice'] - print(f" ORDER {o['order_number']} ({(o['customer_name'] or '')[:25]}, {o['order_total']:.2f})") - print(f" ↔ FACT {inv['serie_act']}{inv['numar_act']} ({inv['partener'][:25]}, {inv['total_cu_tva']:.2f}) [score={m['score']:.2f}]") - print() - -if unmatched_orders: - print("Unmatched orders:") - for o in unmatched_orders: - print(f" {o['order_number']} | {(o['customer_name'] or '')[:35]} | {o['order_total'] or 0:.2f}") - -# --- Step 4: Compare line items for matched pairs --- -print() -print("=" * 80) -print("STEP 4: Line item comparison for matched orders") -print("=" * 80) - -simple_mappings = [] # SKU → CODMAT, same qty/price → update nom_articole -repack_mappings = [] # SKU → CODMAT, different qty → ARTICOLE_TERTI -complex_mappings = [] # 1 SKU → N CODMATs → ARTICOLE_TERTI with procent_pret -unresolved = [] # Cannot determine mapping - -for m in matches: - o = m['order'] - inv = m['invoice'] - go_items = o['items'] - roa_items = inv['items'] - - print(f"\n--- ORDER {o['order_number']} ↔ FACT {inv['serie_act']}{inv['numar_act']} ---") - print(f" GoMag: {len(go_items)} items | ROA: {len(roa_items)} items") - - # Show items side by side - print(f" GoMag items:") - for gi in go_items: - print(f" SKU={gi['sku']:20s} qty={gi['quantity']:6.1f} price={gi['price']:10.2f} [{gi['product_name'][:40]}]") - print(f" ROA items:") - for ri in roa_items: - print(f" COD={str(ri['codmat'] or ''):20s} qty={ri['cantitate']:6.1f} pret={ri['pret']:10.4f} [{(ri['denumire'] or '')[:40]}]") - - # Try matching by price (unit price with TVA) - # GoMag price is usually with TVA, ROA pret can be fara TVA - # Let's try both - go_remaining = list(range(len(go_items))) - roa_remaining = list(range(len(roa_items))) - item_matches = [] - - # First pass: exact 1:1 by total value (qty * price) - for gi_idx in list(go_remaining): - gi = go_items[gi_idx] - go_total = gi['quantity'] * gi['price'] - go_total_fara = go_total / (1 + gi['vat']/100) if gi['vat'] else go_total - - for ri_idx in list(roa_remaining): - ri = roa_items[ri_idx] - roa_total = ri['cantitate'] * ri['pret'] - roa_total_cu = ri['cantitate'] * ri['pret_cu_tva'] - - # Match by total (fara TVA or cu TVA) - if (abs(go_total_fara - roa_total) < 0.5 or - abs(go_total - roa_total_cu) < 0.5 or - abs(go_total - roa_total) < 0.5): - item_matches.append((gi_idx, [ri_idx])) - go_remaining.remove(gi_idx) - roa_remaining.remove(ri_idx) - break - - # Second pass: 1:N matching (one GoMag item → multiple ROA items) - for gi_idx in list(go_remaining): - gi = go_items[gi_idx] - go_total = gi['quantity'] * gi['price'] - go_total_fara = go_total / (1 + gi['vat']/100) if gi['vat'] else go_total - - # Try combinations of remaining ROA items - if len(roa_remaining) >= 2: - # Try pairs - for i, ri_idx1 in enumerate(roa_remaining): - for ri_idx2 in roa_remaining[i+1:]: - ri1 = roa_items[ri_idx1] - ri2 = roa_items[ri_idx2] - combined_total = ri1['cantitate'] * ri1['pret'] + ri2['cantitate'] * ri2['pret'] - combined_total_cu = ri1['cantitate'] * ri1['pret_cu_tva'] + ri2['cantitate'] * ri2['pret_cu_tva'] - if (abs(go_total_fara - combined_total) < 1.0 or - abs(go_total - combined_total_cu) < 1.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) - break - else: - continue - break - - # Report 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] - # Same quantity? - if abs(gi['quantity'] - ri['cantitate']) < 0.01: - # Simple 1:1 - entry = { - 'sku': gi['sku'], - 'codmat': ri['codmat'], - 'id_articol': ri['id_articol'], - 'product_name': gi['product_name'], - 'denumire': ri['denumire'], - 'go_qty': gi['quantity'], - 'roa_qty': ri['cantitate'], - 'go_price': gi['price'], - 'roa_pret': ri['pret'], - 'order': o['order_number'], - 'factura': f"{inv['serie_act']}{inv['numar_act']}", - } - simple_mappings.append(entry) - print(f" ✓ SIMPLE: {gi['sku']} → {ri['codmat']} (qty {gi['quantity']}={ri['cantitate']})") - else: - # Repackaging - cantitate_roa = ri['cantitate'] / gi['quantity'] if gi['quantity'] else 1 - entry = { - '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'], - 'order': o['order_number'], - 'factura': f"{inv['serie_act']}{inv['numar_act']}", - } - repack_mappings.append(entry) - print(f" ✓ REPACK: {gi['sku']} → {ri['codmat']} (qty {gi['quantity']}→{ri['cantitate']}, ratio={cantitate_roa:.3f})") - else: - # Complex set - go_total = gi['quantity'] * gi['price'] - go_total_fara = go_total / (1 + gi['vat']/100) if gi['vat'] else go_total - 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 - entry = { - 'sku': gi['sku'], - 'codmat': ri['codmat'], - 'id_articol': ri['id_articol'], - 'cantitate_roa': ri['cantitate'] / gi['quantity'] if gi['quantity'] else 1, - 'procent_pret': pct, - 'product_name': gi['product_name'], - 'denumire': ri['denumire'], - 'order': o['order_number'], - 'factura': f"{inv['serie_act']}{inv['numar_act']}", - } - complex_mappings.append(entry) - print(f" ✓ SET: {gi['sku']} → {ri['codmat']} ({pct}%)") - - # Unresolved - for gi_idx in go_remaining: - gi = go_items[gi_idx] - 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': [roa_items[i] for i in roa_remaining], - }) - print(f" ? UNRESOLVED: {gi['sku']} ({gi['product_name'][:40]})") - -# --- Step 5: Summary and output --- -print() -print("=" * 80) -print("STEP 5: SUMMARY") -print("=" * 80) - -# Deduplicate mappings -seen_simple = {} -for m in simple_mappings: - key = (m['sku'], m['codmat']) - if key not in seen_simple: - seen_simple[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 - -print(f"\nSimple 1:1 (update nom_articole.codmat = SKU): {len(seen_simple)} unique") -for key, m in seen_simple.items(): - print(f" {m['sku']:25s} → {m['codmat']:15s} | {m['product_name'][:35]} ↔ {(m['denumire'] or '')[:35]}") - -print(f"\nRepackaging (ARTICOLE_TERTI with cantitate_roa): {len(seen_repack)} unique") -for key, m in seen_repack.items(): - print(f" {m['sku']:25s} → {m['codmat']:15s} x{m['cantitate_roa']} | {m['product_name'][:30]} ↔ {(m['denumire'] or '')[:30]}") - -print(f"\nComplex sets (ARTICOLE_TERTI with procent_pret): {len(seen_complex)} unique") -for key, m in seen_complex.items(): - print(f" {m['sku']:25s} → {m['codmat']:15s} {m['procent_pret']}% | {m['product_name'][:30]} ↔ {(m['denumire'] or '')[:30]}") - -print(f"\nUnresolved: {len(unresolved)}") -for u in unresolved: - print(f" {u['sku']:25s} | {u['product_name'][:40]} | order={u['order']}") - -# --- Write CSVs --- -out_dir = r'C:\gomag-vending\scripts\output' -os.makedirs(out_dir, exist_ok=True) - -# Simple mappings CSV (for verification before SQL update) -with open(os.path.join(out_dir, 'simple_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', 'order', 'factura']) - for m in seen_simple.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'], m['order'], m['factura']]) - -# Repackaging CSV (for ARTICOLE_TERTI import) -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 (for ARTICOLE_TERTI import) -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 CSV -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: - roa_str = '; '.join([f"{r['codmat']}({r['cantitate']}x{r['pret']:.2f})" for r in u['roa_remaining']]) - w.writerow([u['sku'], u['product_name'], u['quantity'], u['price'], u['order'], u['factura'], roa_str]) - -# SQL script for simple mappings (update nom_articole) -with open(os.path.join(out_dir, 'update_codmat.sql'), 'w', encoding='utf-8') as f: - f.write("-- Simple SKU → CODMAT: set SKU as CODMAT in nom_articole\n") - f.write("-- VERIFY BEFORE RUNNING!\n\n") - for m in seen_simple.values(): - codmat = m['codmat'] - sku = m['sku'] - f.write(f"-- {m['product_name'][:50]} → {m['denumire'][:50]}\n") - f.write(f"UPDATE nom_articole SET codmat = '{sku}' WHERE codmat = '{codmat}' AND sters = 0;\n\n") - -print(f"\nOutput written to {out_dir}:") -print(f" simple_mappings.csv - {len(seen_simple)} rows (verify, then run update_codmat.sql)") -print(f" repack_mappings.csv - {len(seen_repack)} rows (import via /api/mappings/import-csv)") -print(f" complex_mappings.csv - {len(seen_complex)} rows (import via /api/mappings/import-csv)") -print(f" unresolved.csv - {len(unresolved)} rows (manual review needed)") -print(f" update_codmat.sql - SQL for simple mappings") diff --git a/scripts/reset_sqlite.py b/scripts/reset_sqlite.py deleted file mode 100644 index 0d9d852..0000000 --- a/scripts/reset_sqlite.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Reset imported orders in SQLite back to SKIPPED after Oracle deletion""" -import sys, sqlite3 -sys.stdout.reconfigure(encoding='utf-8', errors='replace') - -db = sqlite3.connect(r'C:\gomag-vending\api\data\import.db') -c = db.cursor() - -# Show before -c.execute("SELECT order_number, customer_name, id_comanda, status FROM orders WHERE status = 'IMPORTED'") -rows = c.fetchall() -print(f"Orders to reset: {len(rows)}") -for r in rows: - print(f" {r[0]} | {r[1]} | id_comanda={r[2]} | {r[3]}") - -# Reset -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' -""") -print(f"\nReset: {c.rowcount} orders → SKIPPED") -db.commit() - -# Verify -c.execute("SELECT status, COUNT(*) FROM orders GROUP BY status") -print("\nStatus after reset:") -for r in c.fetchall(): - print(f" {r[0]:20s} {r[1]}") - -db.close()