argument('tenant'); $mdb = (string) ($this->option('mdb') ?: ''); $table = (string) ($this->option('table') ?: 'Fornitori'); $into = (string) ($this->option('into') ?: 'stg_fornitori_gescon'); if ($mdb === '' || !is_file($mdb)) { $this->error('File MDB non trovato. Passa --mdb=/path/Fornitori.mdb'); return self::FAILURE; } if (!DB::getSchemaBuilder()->hasTable($into)) { $this->error("Tabella staging non trovata: {$into} (esegui migrate)"); return self::FAILURE; } $bin = trim((string) @shell_exec('command -v mdb-export')) ?: ''; if ($bin === '') { $this->error('mdb-export non trovato (installa mdbtools)'); return self::FAILURE; } $csv = $this->runShell(sprintf( "%s -D %s -d %s -q %s -R \"\\n\" %s %s", escapeshellarg($bin), escapeshellarg('%Y-%m-%d %H:%M:%S'), escapeshellarg('|'), // Usa enclosure alternativo per evitare CSV invalido se i campi contengono doppie-virgolette non escape-ate. escapeshellarg('^'), escapeshellarg($mdb), escapeshellarg($table), )); if ($csv === null || trim($csv) === '') { $this->error('Nessun dato esportato (tabella inesistente o file non leggibile)'); return self::FAILURE; } $rows = $this->parseDelimited($csv, '|'); if ($this->option('preview')) { $limit = max(1, (int) $this->option('limit')); $this->line('Preview:'); foreach (array_slice($rows, 0, $limit) as $r) { $this->line(json_encode($this->mapRow($tenant, $mdb, $r), JSON_UNESCAPED_UNICODE)); } $this->line('Totale righe: ' . count($rows)); return self::SUCCESS; } if ($this->option('truncate')) { DB::table($into)->truncate(); } $now = now(); $payloads = []; $count = 0; foreach ($rows as $row) { $payload = $this->mapRow($tenant, $mdb, $row); if ($payload['legacy_id_fornitore'] === null) { continue; } $payload['created_at'] = $now; $payload['updated_at'] = $now; $payloads[] = $payload; if (count($payloads) >= 500) { DB::table($into)->upsert($payloads, ['tenant_id', 'legacy_id_fornitore'], [ 'cod_forn', 'denominazione', 'cognome', 'nome', 'codice_fiscale', 'partita_iva', 'natura', 'sesso', 'indirizzo', 'cap', 'citta', 'provincia', 'data_nascita', 'luogo_nascita', 'provincia_nascita', 'telefono_1', 'telefono_2', 'telefono_3', 'telefono_4', 'fax', 'cellulare', 'email', 'pec', 'iban', 'regime_fiscale', 'tipo_cassa', 'aliq_iva_abituale', 'note', 'raw', 'sorgente_archivio', 'updated_at', ]); $count += count($payloads); $payloads = []; } } if (!empty($payloads)) { DB::table($into)->upsert($payloads, ['tenant_id', 'legacy_id_fornitore'], [ 'cod_forn', 'denominazione', 'cognome', 'nome', 'codice_fiscale', 'partita_iva', 'natura', 'sesso', 'indirizzo', 'cap', 'citta', 'provincia', 'data_nascita', 'luogo_nascita', 'provincia_nascita', 'telefono_1', 'telefono_2', 'telefono_3', 'telefono_4', 'fax', 'cellulare', 'email', 'pec', 'iban', 'regime_fiscale', 'tipo_cassa', 'aliq_iva_abituale', 'note', 'raw', 'sorgente_archivio', 'updated_at', ]); $count += count($payloads); } $this->info("✅ Import completato: {$count} righe elaborate (upsert)"); return self::SUCCESS; } private function runShell(string $cmd): ?string { $out = shell_exec($cmd . ' 2>/dev/null'); if ($out === null || $out === '') { return null; } return $out; } private function parseDelimited(string $text, string $sep): array { // Usa fgetcsv su stream per gestire correttamente campi con newline dentro virgolette. $fh = fopen('php://temp', 'r+'); fwrite($fh, $text); rewind($fh); $headers = fgetcsv($fh, 0, $sep, '^'); if (!$headers) { return []; } $headers = array_map(fn($h) => strtolower(trim((string) $h)), $headers); $rows = []; while (($cols = fgetcsv($fh, 0, $sep, '^')) !== false) { if ($cols === [null] || $cols === []) { continue; } if (count($cols) < count($headers)) { $cols = array_pad($cols, count($headers), null); } if (count($cols) > count($headers)) { $cols = array_slice($cols, 0, count($headers)); } $row = @array_combine($headers, $cols); if (!$row) { continue; } $rows[] = $row; } return $rows; } private function mapRow(string $tenant, string $mdb, array $row): array { $get = function (string ...$keys) use ($row) { foreach ($keys as $k) { $lk = strtolower($k); if (array_key_exists($lk, $row)) { $v = $row[$lk]; return is_string($v) ? trim($v) : $v; } } return null; }; $legacyId = $this->toInt($get('id_fornitore')); $denominazione = $this->nullIfEmpty($get('denominazione')); $cognome = $this->nullIfEmpty($get('cognome')); $nome = $this->nullIfEmpty($get('nome')); if (!$denominazione) { $full = trim(($cognome ?? '') . ' ' . ($nome ?? '')); $denominazione = $full !== '' ? $full : null; } $raw = $row; return [ 'tenant_id' => $tenant, 'sorgente_archivio' => $mdb, 'legacy_id_fornitore' => $legacyId, 'cod_forn' => $this->toInt($get('cod_forn')), 'denominazione' => $denominazione, 'cognome' => $cognome, 'nome' => $nome, 'codice_fiscale' => $this->nullIfEmpty($get('cod_fisc', 'codice_fiscale')), 'partita_iva' => $this->nullIfEmpty($get('p_iva', 'partita_iva')), 'natura' => $this->nullIfEmpty($get('natura')), 'sesso' => $this->nullIfEmpty($get('sesso')), 'indirizzo' => $this->nullIfEmpty($get('indirizzo')), 'cap' => $this->nullIfEmpty($get('cap')), 'citta' => $this->nullIfEmpty($get('citta')), 'provincia' => $this->nullIfEmpty($get('pr', 'provincia')), 'data_nascita' => $this->toDate($get('dt_nas', 'data_nascita')), 'luogo_nascita' => $this->nullIfEmpty($get('luo_nas', 'luogo_nascita')), 'provincia_nascita' => $this->nullIfEmpty($get('pr_nas', 'pr_na', 'provincia_nascita')), 'telefono_1' => $this->nullIfEmpty($get('telef_1', 'Telef_1')), 'telefono_2' => $this->nullIfEmpty($get('telef_2', 'Telef_2')), 'telefono_3' => $this->nullIfEmpty($get('telef_3', 'Telef_3')), 'telefono_4' => $this->nullIfEmpty($get('telef_4', 'Telef_4')), 'fax' => $this->nullIfEmpty($get('fax')), 'cellulare' => $this->nullIfEmpty($get('cellulare')), 'email' => $this->nullIfEmpty($get('indir_email', 'email')), 'pec' => $this->nullIfEmpty($get('pec_fornitore', 'pec')), 'iban' => $this->nullIfEmpty($get('cod_iban', 'iban')), 'regime_fiscale' => $this->nullIfEmpty($get('regime_fiscale')), 'tipo_cassa' => $this->nullIfEmpty($get('tipo_cassa')), 'aliq_iva_abituale' => $this->toInt($get('aliq_iva_abituale')), 'note' => $this->nullIfEmpty($get('note')), 'raw' => json_encode($raw, JSON_UNESCAPED_UNICODE), ]; } private function nullIfEmpty($v): ?string { if ($v === null) { return null; } $s = trim((string) $v); return $s === '' ? null : $s; } private function toInt($v): ?int { if ($v === null) { return null; } $s = trim((string) $v); if ($s === '' || !is_numeric($s)) { return null; } return (int) $s; } private function toDate($v): ?string { if ($v === null) { return null; } $s = trim((string) $v); if ($s === '') { return null; } // mdb-export with -D '%Y-%m-%d %H:%M:%S' if (preg_match('/^\d{4}-\d{2}-\d{2}/', $s)) { return substr($s, 0, 10); } // fallback: MM/DD/YY HH:MM:SS if (preg_match('/^\d{2}\/\d{2}\/\d{2} /', $s)) { $dt = \DateTime::createFromFormat('m/d/y H:i:s', $s); if (!$dt) { return null; } $yy = (int) $dt->format('y'); $curYY = (int) now()->format('y'); $year = ($yy > $curYY) ? (1900 + $yy) : (2000 + $yy); return sprintf('%04d-%02d-%02d', $year, (int) $dt->format('m'), (int) $dt->format('d')); } // fallback: DD/MM/YYYY if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $s)) { $dt = \DateTime::createFromFormat('d/m/Y', $s); return $dt?->format('Y-m-d'); } return null; } }