""" Import machines from Cantaloupe Excel export into assets.db. Maps Excel columns to DB fields, creates/finds customers and locations. Usage: python3 import_machines.py """ import openpyxl, sqlite3, sys, re, os from datetime import datetime DB = '/home/oplabs/projects/canteen-asset-tracker/assets.db' def norm(s): """Normalize a string for comparison.""" if s is None: return '' return str(s).strip().lower() def _sanitize_machine_id(mid): if not mid: return '' return str(mid).strip()[:100] def main(xlsx_path): if not os.path.exists(xlsx_path): print(f'ERROR: File not found: {xlsx_path}') return 1 wb = openpyxl.load_workbook(xlsx_path, data_only=True) ws = wb['Machine List'] # Read all rows rows = [] for row in ws.iter_rows(min_row=2, values_only=True): if row[2] is None: continue rows.append(row) print(f'Loaded {len(rows)} machines from Excel') conn = sqlite3.connect(DB) conn.execute("PRAGMA journal_mode=WAL") # Pre-load existing customers existing_customers = {} for r in conn.execute("SELECT id, name FROM customers").fetchall(): existing_customers[norm(r[1])] = r[0] # Pre-load existing assets by machine_id for upsert existing_assets = {} for r in conn.execute("SELECT id, machine_id FROM assets WHERE machine_id IS NOT NULL").fetchall(): existing_assets[_sanitize_machine_id(r[1])] = r[0] # Stats stats = {'created': 0, 'updated': 0, 'skipped': 0, 'errors': 0} # Build customer cache - keep a set of customer names we've already seen in this import customer_cache = set() # Process in a single transaction for idx, row in enumerate(rows): try: machine_id = _sanitize_machine_id(str(int(row[2]))) location_str = str(row[1]) if row[1] else '' # "Company - Address" customer_name = str(row[21]) if row[21] else '' # Customer address = str(row[6]) if row[6] else '' city = str(row[5]) if row[5] else '' state = str(row[33]) if row[33] else '' postal = str(row[34]) if row[34] else '' serial = str(row[37]) if row[37] else '' make = str(row[39]) if row[39] else 'Unknown' model = str(row[40]) if row[40] else 'Unknown' cls = str(row[38]) if row[38] else 'Other' # Class → category branch = str(row[51]) if row[51] else '' device = str(row[0]) if row[0] else '' # Asset name: use location string if available, else customer + address name = location_str if location_str else (customer_name + ' - ' + address if address else machine_id) name = name.strip()[:200] # Build full address full_address_parts = [p for p in [address, city, state, postal] if p] full_address = ', '.join(full_address_parts) if full_address_parts else '' full_address = full_address[:200] # Category: map from class category_map = { 'snack': 'Snack', 'drink': 'Drink', 'cold drink': 'Drink', 'combo': 'Combo', 'food': 'Food', 'other': 'Other', 'unknown': 'Other', } cat = category_map.get(cls.strip().lower(), cls.capitalize() if cls else 'Other') # Status: always active for import (Cantaloupe RPC status is telemetry, not condition) status = 'active' # ----- Customer resolution ----- customer_id = None if customer_name: key = norm(customer_name) if key in existing_customers: customer_id = existing_customers[key] else: # Create new customer c = conn.execute( "INSERT INTO customers (name) VALUES (?)", (customer_name.strip()[:100],) ) customer_id = c.lastrowid existing_customers[key] = customer_id print(f' Created customer: {customer_name.strip()[:60]}') # ----- Upsert logic ----- if machine_id in existing_assets: aid = existing_assets[machine_id] # Update existing - preserve disney_park, GPS, keys, badges conn.execute(""" UPDATE assets SET name = ?, serial_number = ?, make = ?, model = ?, category = ?, address = ?, customer_id = COALESCE(?, customer_id), description = ?, updated_at = datetime('now') WHERE id = ? """, (name, serial, make, model, cat, full_address, customer_id, device + ' | Branch: ' + branch if branch else device, aid)) stats['updated'] += 1 else: # Insert new cursor = conn.execute(""" INSERT INTO assets (machine_id, name, serial_number, make, model, category, status, address, customer_id, created_at, updated_at, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'), ?) """, (machine_id, name, serial, make, model, cat, status, full_address, customer_id, device + ' | Branch: ' + branch if branch else device)) aid = cursor.lastrowid existing_assets[machine_id] = aid stats['created'] += 1 except Exception as e: print(f'ERROR row {idx+2} (machine_id={row[2]}): {e}') stats['errors'] += 1 conn.commit() conn.close() print() print(f'=== Import Complete ===') print(f' Created: {stats["created"]} new assets') print(f' Updated: {stats["updated"]} existing assets') print(f' Skipped: {stats["skipped"]}') print(f' Errors: {stats["errors"]}') print(f' Total rows: {len(rows)}') return 0 if stats['errors'] == 0 else 1 if __name__ == '__main__': if len(sys.argv) < 2: print('Usage: python3 import_machines.py ') sys.exit(1) sys.exit(main(sys.argv[1]))