"""CSV/JSON inlezen voor de import, en het herkennen van kolomnamen. CSV: scheidingsteken `;`, `,` of tab (automatisch), UTF-8 met of zonder BOM (of Windows-1252), regels die met `#` beginnen worden overgeslagen — zoals de CSV's van de configurator. JSON: een lijst met objecten, een object met daarin een lijst (bijv. {"contacten": [...]}) of één object. """ import csv import json FIELD_ALIASES = { 'name': ['name', 'naam', 'full_name', 'fullname', 'volledige_naam', 'your-name', 'klant', 'klantnaam'], 'first_name': ['first_name', 'firstname', 'voornaam'], 'last_name': ['last_name', 'lastname', 'achternaam'], 'email': ['email', 'e-mail', 'e_mail', 'emailadres', 'e-mailadres', 'mail', 'your-email'], 'phone': ['phone', 'telefoon', 'tel', 'telefoonnummer', 'phone_number', 'mobiel', 'your-phone'], 'company': ['company', 'bedrijf', 'bedrijfsnaam', 'organisatie'], 'relation': ['relatie', 'relation', 'account', 'huishouden', 'familie', 'account_name'], 'relation_role': ['rol', 'role', 'relatie_rol', 'functie'], 'address': ['address', 'adres', 'straat', 'street'], 'postcode': ['postcode', 'zip', 'zipcode', 'postal_code'], 'city': ['city', 'plaats', 'woonplaats', 'stad'], 'message': ['message', 'bericht', 'opmerking', 'opmerkingen', 'vraag', 'comments', 'your-message'], 'source': ['source', 'bron', 'formulier', 'form', 'form_name', 'lead_source'], 'status': ['status', 'fase'], 'value': ['value', 'waarde', 'bedrag', 'budget', 'verwachte_waarde'], 'notes': ['notes', 'notities', 'notitie'], } PRODUCT_ALIASES = { 'name': ['name', 'naam', 'product', 'productnaam', 'titel', 'omschrijving'], 'sku': ['sku', 'code', 'artikelnummer', 'artikelnr', 'artikel'], 'description': ['description', 'beschrijving', 'toelichting', 'details'], 'unit': ['unit', 'eenheid'], 'price': ['price', 'prijs', 'waarde', 'verkoopprijs', 'prijs_excl'], 'vat': ['vat', 'btw', 'btw%', 'btw_percentage'], 'supplier': ['supplier', 'leverancier'], 'active': ['active', 'actief'], } RELATION_ALIASES = { 'name': ['name', 'naam', 'bedrijf', 'bedrijfsnaam', 'relatie', 'organisatie', 'leverancier', 'familie'], 'kind': ['kind', 'soort'], 'type': ['type', 'categorie', 'branche'], 'email': ['email', 'e-mail', 'emailadres', 'e-mailadres', 'mail'], 'phone': ['phone', 'telefoon', 'tel', 'telefoonnummer'], 'website': ['website', 'url', 'site', 'web'], 'address': ['address', 'adres', 'straat'], 'postcode': ['postcode', 'zip', 'zipcode'], 'city': ['city', 'plaats', 'woonplaats', 'vestigingsplaats'], 'kvk': ['kvk', 'kvk-nummer', 'kvk_nummer', 'kvknummer', 'coc'], 'notes': ['notes', 'notities', 'opmerking', 'opmerkingen'], } TARGETS = { 'contacts': [('', '— overslaan —'), ('name', 'Naam'), ('first_name', 'Voornaam'), ('last_name', 'Achternaam'), ('email', 'E-mail'), ('phone', 'Telefoon'), ('company', 'Bedrijf'), ('address', 'Adres'), ('postcode', 'Postcode'), ('city', 'Plaats'), ('source', 'Bron'), ('status', 'Status'), ('value', 'Verwachte waarde'), ('notes', 'Notities'), ('message', 'Bericht (in tijdlijn)'), ('relation', 'Relatie (naam)'), ('relation_role', 'Rol binnen relatie'), ('extra', 'Extra veld (bewaren)')], 'relations': [('', '— overslaan —'), ('name', 'Naam'), ('kind', 'Soort (klant/partner)'), ('type', 'Type'), ('email', 'E-mail'), ('phone', 'Telefoon'), ('website', 'Website'), ('address', 'Adres'), ('postcode', 'Postcode'), ('city', 'Plaats'), ('kvk', 'KvK-nummer'), ('notes', 'Notities')], 'products': [('', '— overslaan —'), ('name', 'Naam'), ('sku', 'Artikelnummer / code'), ('description', 'Omschrijving'), ('unit', 'Eenheid'), ('price', 'Prijs excl. btw'), ('vat', 'Btw %'), ('supplier', 'Leverancier'), ('active', 'Actief (ja/nee)')], } class ImportFout(ValueError): pass def _key(name): return str(name).strip().lower().replace(' ', '_') def lookup(aliases): return {alias: field for field, names in aliases.items() for alias in names} def guess(cols, kind): """Stel per kolom een doelveld voor; elk doelveld maar één keer (de eerste kolom wint).""" table = lookup({'contacts': FIELD_ALIASES, 'products': PRODUCT_ALIASES, 'relations': RELATION_ALIASES}[kind]) used, mapping = set(), [] for col in cols: target = table.get(_key(col), '') if target in used: target = '' if target: used.add(target) mapping.append(target or ('extra' if kind == 'contacts' else '')) return mapping def read(path): """Geef (kolommen, rijen) terug; rijen zijn dicts.""" with open(path, 'rb') as f: raw = f.read() try: text = raw.decode('utf-8-sig') except UnicodeDecodeError: text = raw.decode('cp1252') if path.endswith('.json'): try: data = json.loads(text) except json.JSONDecodeError as e: raise ImportFout(f'Geen geldige JSON: {e}') from e if isinstance(data, dict): if data.get('type') == 'house-design': raise ImportFout('Dit is een ontwerp uit de configurator. Upload het bij een contact onder Bestanden.') lists = [v for v in data.values() if isinstance(v, list)] data = lists[0] if lists else [data] if not isinstance(data, list): raise ImportFout('Verwacht een lijst met objecten.') rows = [r for r in data if isinstance(r, dict)] cols = [] for row in rows: cols += [k for k in row if k not in cols] return cols, rows lines = [line for line in text.splitlines() if line.strip() and not line.lstrip().startswith('#')] if not lines: raise ImportFout('Het bestand is leeg.') delimiter = max([';', ',', '\t'], key=lines[0].count) reader = csv.reader(lines, delimiter=delimiter) cols = [c.strip() for c in next(reader)] rows = [dict(zip(cols, (v.strip() for v in r))) for r in reader if any(v.strip() for v in r)] return cols, rows