#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ADP Mobility Manual CSV to JSON Converter This script reads the ADP Mobility Manual CSV file, cleans the data, and outputs a JSON file that can be imported into Odoo's fusion.adp.device.code model. Usage: python import_adp_mobility_manual.py input.csv output.json Or run without arguments to use default paths. Copyright 2024-2025 Nexa Systems Inc. License OPL-1 (Odoo Proprietary License v1.0) """ import csv import json import re import sys import os def clean_text(text): """Clean text from weird characters, normalize encoding.""" if not text: return '' # Convert to string if not already text = str(text) # Replace curly quotes with straight quotes text = text.replace('"', '"').replace('"', '"') text = text.replace(''', "'").replace(''', "'") # Replace various dashes with standard hyphen text = text.replace('–', '-').replace('—', '-') # Remove non-printable characters except newlines text = ''.join(char if char.isprintable() or char in '\n\r\t' else ' ' for char in text) # Normalize multiple spaces text = re.sub(r'\s+', ' ', text) # Strip leading/trailing whitespace return text.strip() def parse_price(price_str): """Parse price string like '$64.00' or '$2,578.00' to float.""" if not price_str: return 0.0 # Remove currency symbols, commas, spaces, quotes price_str = str(price_str).strip() price_str = re.sub(r'[\$,"\'\s]', '', price_str) try: return float(price_str) except ValueError: return 0.0 def convert_csv_to_json(input_path, output_path): """Convert ADP Mobility Manual CSV to JSON format.""" data = [] errors = [] skipped = 0 # Try different encodings encodings = ['utf-8-sig', 'utf-8', 'latin-1', 'cp1252'] content = None for encoding in encodings: try: with open(input_path, 'r', encoding=encoding) as f: content = f.read() break except UnicodeDecodeError: continue if content is None: print(f"Error: Could not read file with any known encoding") return None # Parse CSV reader = csv.DictReader(content.splitlines()) for idx, row in enumerate(reader, start=2): # Start at 2 (header is line 1) try: # Get device code - skip if empty device_code = clean_text(row.get('Device Code', '')) if not device_code: skipped += 1 continue # Get device type device_type = clean_text(row.get('Device Type', '')) if not device_type: skipped += 1 continue # Get manufacturer manufacturer = clean_text(row.get('Manufacturer', '')) # Get device description - clean it device_description = clean_text(row.get('Device Description', '')) # Parse quantity qty_str = row.get('Qty', '1') or '1' try: quantity = int(qty_str) except ValueError: quantity = 1 # Parse price (handle both ' Approved Price ' with spaces and 'Approved Price') price = 0.0 for key in row.keys(): if 'price' in key.lower() and 'approved' in key.lower(): price = parse_price(row.get(key, '')) break # Parse serial requirement serial_str = clean_text(row.get('Serial', 'No')).upper() sn_required = serial_str in ('YES', 'Y', 'TRUE', '1') data.append({ 'Device Type': device_type, 'Manufacturer': manufacturer, 'Device Description': device_description, 'Device Code': device_code, 'Quantity': quantity, 'ADP Price': price, 'SN Required': 'Yes' if sn_required else 'No', }) except Exception as e: errors.append(f"Row {idx}: {str(e)}") # Write JSON output with open(output_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) # Print summary print(f"\n{'='*60}") print(f"ADP Mobility Manual Import Summary") print(f"{'='*60}") print(f"Input file: {input_path}") print(f"Output file: {output_path}") print(f"Records processed: {len(data)}") print(f"Records skipped: {skipped}") print(f"Errors: {len(errors)}") if errors: print(f"\nFirst 10 errors:") for err in errors[:10]: print(f" - {err}") # Print device type summary device_types = {} for item in data: dt = item['Device Type'] device_types[dt] = device_types.get(dt, 0) + 1 print(f"\nDevice Types ({len(device_types)} unique):") for dt in sorted(device_types.keys())[:20]: print(f" - {dt}: {device_types[dt]} devices") if len(device_types) > 20: print(f" ... and {len(device_types) - 20} more") print(f"\n{'='*60}") print(f"JSON file ready for import into Odoo!") print(f"Use: Sales > Configuration > ADP Device Codes > Import") print(f"{'='*60}\n") return data def main(): # Default paths default_input = r"C:\Users\gur_p\Downloads\ADP-Mobility-Manual.csv" default_output = r"C:\Users\gur_p\Downloads\ADP-Mobility-Manual-cleaned.json" if len(sys.argv) >= 3: input_path = sys.argv[1] output_path = sys.argv[2] elif len(sys.argv) == 2: input_path = sys.argv[1] output_path = os.path.splitext(input_path)[0] + '-cleaned.json' else: input_path = default_input output_path = default_output if not os.path.exists(input_path): print(f"Error: Input file not found: {input_path}") print(f"\nUsage: python {sys.argv[0]} input.csv [output.json]") sys.exit(1) convert_csv_to_json(input_path, output_path) if __name__ == '__main__': main()