""" 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)")