331 lines
11 KiB
PHP
Executable File
331 lines
11 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ImportGesconFornitoriMdbCommand extends Command
|
|
{
|
|
protected $signature = 'import:gescon-fornitori
|
|
{tenant : Tenant ID}
|
|
{--mdb= : Percorso file Fornitori.mdb}
|
|
{--table=Fornitori : Nome tabella Access}
|
|
{--into=stg_fornitori_gescon : Tabella staging destinazione}
|
|
{--truncate : Svuota la tabella staging prima dell\'import}
|
|
{--preview : Mostra una preview e non importa}
|
|
{--limit=10 : Limite righe per preview}';
|
|
|
|
protected $description = 'Importa fornitori da GESCON Fornitori.mdb in tabella staging (idempotente per legacy_id)';
|
|
|
|
public function handle(): int
|
|
{
|
|
$tenant = (string) $this->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;
|
|
}
|
|
}
|