#!/usr/bin/env python3 """ Import Atlas data into Nexus POS tenant_refaccionaria_atlas. Sources (expected in /home/): - Articulos Atlas.xlsx -> inventory catalog (stock = 0) - Clientes.xlsx -> customers - Historico V.xlsx -> historical_sales (read-only reference) """ import os import sys import re from datetime import datetime from decimal import Decimal import psycopg2 import pandas as pd BASE_DIR = "/home" TENANT_DB = "tenant_refaccionaria_atlas" def get_tenant_conn(): dsn = os.environ.get("TENANT_DB_URL", f"postgresql://postgres@localhost/{TENANT_DB}") return psycopg2.connect(dsn) def normalize_text(val, max_len=None): if pd.isna(val): return None s = str(val).strip() if s in ("", "nan", "None"): return None if max_len: s = s[:max_len] return s def normalize_price(val): if pd.isna(val): return Decimal("0") try: return Decimal(str(float(val))).quantize(Decimal("0.01")) except (ValueError, TypeError): return Decimal("0") def clean_part_number(val): s = normalize_text(val, max_len=100) if not s: return None # Remove problematic chars but keep basic alphanumeric + dash/underscore s = re.sub(r"[^\w\-./]", "", s) return s[:100] or None def import_inventory(conn): path = os.path.join(BASE_DIR, "Articulos Atlas.xlsx") df = pd.read_excel(path) print(f"[inventory] Read {len(df)} rows from {path}") cur = conn.cursor() inserted = 0 skipped_dup = 0 seen = set() for _, row in df.iterrows(): part_number = clean_part_number(row.get("Clave")) if not part_number: continue if part_number in seen: skipped_dup += 1 continue seen.add(part_number) name = normalize_text(row.get("Producto"), max_len=300) or part_number description = normalize_text(row.get("Nombre Alterno"), max_len=500) category = normalize_text(row.get("Categoría 1"), max_len=100) subcategory = normalize_text(row.get("Categoría 2"), max_len=100) unit = normalize_text(row.get("Unidad"), max_len=20) barcode = clean_part_number(row.get("Código de barras")) price = normalize_price(row.get("Precio lista")) # Concatenate category info into description if present extra_info = " | ".join(filter(None, [category, subcategory, unit])) if extra_info: description = f"{description or ''} [{extra_info}]".strip() if description else f"[{extra_info}]" cur.execute( """ INSERT INTO inventory (part_number, name, description, cost, price_1, tax_rate, unit, barcode, brand, is_active) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (part_number) DO NOTHING RETURNING id """, ( part_number, name, description, Decimal("0"), price, Decimal("0.16"), unit, barcode, category, True, ), ) if cur.fetchone(): inserted += 1 conn.commit() cur.close() print(f"[inventory] Inserted: {inserted}, Duplicates skipped: {skipped_dup}") def import_customers(conn): path = os.path.join(BASE_DIR, "Clientes.xlsx") df = pd.read_excel(path) print(f"[customers] Read {len(df)} rows from {path}") cur = conn.cursor() inserted = 0 skipped = 0 for _, row in df.iterrows(): name = normalize_text(row.get("Empresa"), max_len=200) if not name: skipped += 1 continue # Skip generic "Publico" row if it has no real name if name.lower() in ("publico", "publico en general", "público", "público en general"): skipped += 1 continue credit_limit = normalize_price(row.get("Límite de crédito")) customer_type = normalize_text(row.get("Tipo de cliente"), max_len=50) segment = normalize_text(row.get("Segmento"), max_len=50) payment_terms = normalize_text(row.get("Condiciones de Pago"), max_len=100) notes = " | ".join(filter(None, [customer_type, segment, payment_terms])) # Avoid duplicates by name cur.execute("SELECT 1 FROM customers WHERE name = %s LIMIT 1", (name,)) if cur.fetchone(): skipped += 1 continue cur.execute( """ INSERT INTO customers (name, razon_social, credit_limit, price_tier, is_active) VALUES (%s, %s, %s, %s, %s) RETURNING id """, ( name, name, credit_limit, 1, True, ), ) if cur.fetchone(): inserted += 1 conn.commit() cur.close() print(f"[customers] Inserted: {inserted}, Skipped: {skipped}") def create_historical_sales_table(conn): cur = conn.cursor() cur.execute( """ CREATE TABLE IF NOT EXISTS historical_sales ( id SERIAL PRIMARY KEY, external_document_id VARCHAR(50), document_no VARCHAR(50), sale_date DATE, customer_name VARCHAR(200), total NUMERIC(12,2), subtotal NUMERIC(12,2), amount_paid NUMERIC(12,2), payment_method VARCHAR(50), discount NUMERIC(12,2) DEFAULT 0, balance NUMERIC(12,2) DEFAULT 0, raw_payment_code VARCHAR(20), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_historical_sales_date ON historical_sales(sale_date); CREATE INDEX IF NOT EXISTS idx_historical_sales_customer ON historical_sales(customer_name); CREATE INDEX IF NOT EXISTS idx_historical_sales_document ON historical_sales(document_no); """ ) conn.commit() cur.close() def payment_label(code): mapping = { "1": "Efectivo", "3": "Tarjeta", "4": "Transferencia", "6": "Cheque", "28": "Crédito", "99": "Por definir", } return mapping.get(str(code).strip(), f"Código {code}") def import_historical_sales(conn): path = os.path.join(BASE_DIR, "Historico V.xlsx") df = pd.read_excel(path) print(f"[historical_sales] Read {len(df)} rows from {path}") create_historical_sales_table(conn) cur = conn.cursor() inserted = 0 for _, row in df.iterrows(): doc_id = normalize_text(row.get("ID Documento"), max_len=50) doc_no = normalize_text(row.get("Documento No."), max_len=50) fecha = row.get("Fecha") if pd.isna(fecha): sale_date = None else: try: sale_date = pd.to_datetime(fecha).date() except Exception: sale_date = None customer = normalize_text(row.get("Cliente"), max_len=200) total = normalize_price(row.get("Total")) subtotal = normalize_price(row.get("SubTotal")) paid = normalize_price(row.get("Total Pagado")) discount = normalize_price(row.get("Descuento")) balance = normalize_price(row.get("Saldo")) raw_payment = normalize_text(row.get("Forma de Pago"), max_len=20) payment_label_str = payment_label(raw_payment) if raw_payment else None cur.execute( """ INSERT INTO historical_sales (external_document_id, document_no, sale_date, customer_name, total, subtotal, amount_paid, payment_method, discount, balance, raw_payment_code) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT DO NOTHING """, ( doc_id, doc_no, sale_date, customer, total, subtotal, paid, payment_label_str, discount, balance, raw_payment, ), ) inserted += cur.rowcount conn.commit() cur.close() print(f"[historical_sales] Inserted: {inserted}") def main(): print(f"Connecting to tenant {TENANT_DB}...") conn = get_tenant_conn() try: import_inventory(conn) import_customers(conn) import_historical_sales(conn) print("\nImport completed successfully.") except Exception as e: print(f"ERROR: {e}") conn.rollback() raise finally: conn.close() if __name__ == "__main__": main()