792 lines
30 KiB
PHP
792 lines
30 KiB
PHP
<?php
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\AliquotaIva;
|
|
use App\Models\Fornitore;
|
|
use App\Models\FornitoreDipendente;
|
|
use App\Models\RegimeFiscale;
|
|
use App\Models\RubricaUniversale;
|
|
use App\Models\TipologiaCassa;
|
|
use App\Models\Titolo;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class GesconImportFornitoriLegacyMdb extends Command
|
|
{
|
|
protected $signature = 'gescon:import-fornitori-legacy
|
|
{amministratore_id : ID amministratore a cui associare i fornitori}
|
|
{--mdb=/mnt/gescon-archives/gescon/dbc/Fornitori.mdb : Percorso file Fornitori.mdb}
|
|
{--table=Fornitori : Nome tabella Access}
|
|
{--dry-run : Non scrive su DB, mostra solo riepilogo}
|
|
{--limit=0 : Limita righe elaborate (0 = nessun limite)}';
|
|
|
|
protected $description = 'Importa i fornitori legacy da GESCON (Fornitori.mdb) in Rubrica + Fornitori, mappando anche regime fiscale e contatti (tel/email/pec).';
|
|
|
|
/** @var array<string, array<int, string>> */
|
|
private array $columnCache = [];
|
|
|
|
public function handle(): int
|
|
{
|
|
$amministratoreId = (int) $this->argument('amministratore_id');
|
|
$mdb = (string) $this->option('mdb');
|
|
$table = (string) $this->option('table');
|
|
$dry = (bool) $this->option('dry-run');
|
|
$limit = max(0, (int) $this->option('limit'));
|
|
|
|
if ($amministratoreId <= 0) {
|
|
$this->error('amministratore_id non valido');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
if ($mdb === '' || ! is_file($mdb)) {
|
|
$this->error("File MDB non trovato: {$mdb}");
|
|
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;
|
|
}
|
|
|
|
// NOTE: alcuni archivi GESCON contengono doppie-virgolette non escape-ate nei campi.
|
|
// Se usiamo enclosure '"', l'output CSV diventa invalido e fgetcsv() può "mangiare" righe.
|
|
// Usiamo quindi un enclosure alternativo (^) per rendere robusto il parsing.
|
|
$cmd = sprintf(
|
|
'%s -D %s -q %s %s %s',
|
|
escapeshellarg($bin),
|
|
escapeshellarg('%Y-%m-%d %H:%M:%S'),
|
|
escapeshellarg('^'),
|
|
escapeshellarg($mdb),
|
|
escapeshellarg($table),
|
|
);
|
|
|
|
$csv = @shell_exec($cmd . ' 2>/dev/null');
|
|
if (! is_string($csv) || trim($csv) === '') {
|
|
$this->error('Nessun dato esportato (tabella inesistente o file non leggibile)');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
[$rows, $headers] = $this->parseCsv($csv);
|
|
if (empty($rows)) {
|
|
$this->warn('Tabella vuota: nessun fornitore da importare');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
if ($limit > 0) {
|
|
$rows = array_slice($rows, 0, $limit);
|
|
}
|
|
|
|
$this->info('Colonne rilevate: ' . implode(', ', array_slice($headers, 0, 20)) . (count($headers) > 20 ? ' ...' : ''));
|
|
|
|
$stats = [
|
|
'rows' => 0,
|
|
'created_rubrica' => 0,
|
|
'updated_rubrica' => 0,
|
|
'created_fornitori' => 0,
|
|
'updated_fornitori' => 0,
|
|
'created_dipendenti' => 0,
|
|
'updated_dipendenti' => 0,
|
|
'skipped' => 0,
|
|
];
|
|
|
|
$runner = function () use (&$stats, $rows, $amministratoreId, $dry): void {
|
|
foreach ($rows as $row) {
|
|
$stats['rows']++;
|
|
|
|
$codForn = $this->toInt($row['cod_forn'] ?? null);
|
|
$legacyId = $codForn;
|
|
$legacyIdFornitore = $this->toInt($row['id_fornitore'] ?? null);
|
|
|
|
$ragione = $this->clean($row['denominazione'] ?? null);
|
|
// NB: `appoggio` in GESCON è un campo di servizio e spesso contiene blob di contatti.
|
|
// Non deve mai diventare la ragione sociale.
|
|
$appoggio = $this->clean($row['appoggio'] ?? null);
|
|
|
|
// Alcuni archivi hanno il campo denominazione sporco (es. "Tel.: ... Cell. ...").
|
|
// In quel caso NON deve finire come ragione sociale.
|
|
$contactBlob = null;
|
|
if (is_string($ragione) && $ragione !== '' && $this->looksLikeContactBlob($ragione)) {
|
|
$contactBlob = $ragione;
|
|
$ragione = null;
|
|
}
|
|
|
|
$cognome = $this->clean($this->pick($row, ['cognome', 'cognome_fornitore']));
|
|
$nome = $this->clean($this->pick($row, ['nome', 'nome_fornitore']));
|
|
if ($ragione === null) {
|
|
$full = trim(($cognome ?? '') . ' ' . ($nome ?? ''));
|
|
$ragione = $full !== '' ? $full : null;
|
|
}
|
|
|
|
$legacyDtNas = $this->normalizeLegacyDate($this->pick($row, ['dt_nas', 'data_nascita', 'dt_nascita']));
|
|
$legacyLuoNas = $this->clean($this->pick($row, ['luo_nas', 'luogo_nascita']));
|
|
$legacyPrNas = $this->clean($this->pick($row, ['pr_nas', 'prov_nascita', 'provincia_nascita']));
|
|
$legacySesso = $this->normalizeSesso($this->pick($row, ['sesso', 'sex']));
|
|
$legacyTitoloRaw = $this->clean($this->pick($row, ['titolo', 'titolo_fornitore', 'tit']));
|
|
$legacyEmail = $this->clean($this->pick($row, ['indir_email', 'email', 'mail']));
|
|
$legacyIntestazioneCcEsatta = $this->clean($this->pick($row, [
|
|
'intestaz_cc_esatta',
|
|
'intestaz_cc_esatto',
|
|
'intestazione_cc_esatta',
|
|
'intestatario_cc',
|
|
'intestatario_conto',
|
|
]));
|
|
|
|
$note = $this->clean($row['note'] ?? null);
|
|
if ($contactBlob) {
|
|
$note = $note ? ($note . "\n" . $contactBlob) : $contactBlob;
|
|
}
|
|
if ($appoggio) {
|
|
$note = $note ? ($note . "\n" . 'Appoggio: ' . $appoggio) : ('Appoggio: ' . $appoggio);
|
|
}
|
|
$legacyIdentityBits = array_values(array_filter([
|
|
$legacyTitoloRaw ? ('Titolo: ' . $legacyTitoloRaw) : null,
|
|
$legacySesso ? ('Sesso: ' . $legacySesso) : null,
|
|
$legacyDtNas ? ('Data nascita: ' . $legacyDtNas) : null,
|
|
$legacyLuoNas ? ('Luogo nascita: ' . $legacyLuoNas) : null,
|
|
$legacyPrNas ? ('Provincia nascita: ' . $legacyPrNas) : null,
|
|
]));
|
|
|
|
$piva = $this->clean($row['p_iva'] ?? null);
|
|
$cf = $this->clean($row['cod_fisc'] ?? null);
|
|
|
|
if ($legacyId === null && $codForn === null && $ragione === null && $piva === null && $cf === null) {
|
|
$stats['skipped']++;
|
|
continue;
|
|
}
|
|
|
|
$rubricaPayload = [
|
|
'amministratore_id' => $amministratoreId,
|
|
'ragione_sociale' => $ragione,
|
|
'nome' => $nome,
|
|
'cognome' => $cognome,
|
|
'partita_iva' => $piva,
|
|
'codice_fiscale' => $cf,
|
|
'sesso' => $legacySesso,
|
|
'data_nascita' => $legacyDtNas,
|
|
'luogo_nascita' => $legacyLuoNas,
|
|
'provincia_nascita' => $legacyPrNas,
|
|
'indirizzo' => $this->clean($row['indirizzo'] ?? null),
|
|
'cap' => $this->clean($row['cap'] ?? null),
|
|
'citta' => $this->clean($row['citta'] ?? null),
|
|
'provincia' => $this->clean($row['pr'] ?? null),
|
|
'telefono_ufficio' => $this->clean($row['telef_1'] ?? $row['telef_2'] ?? null),
|
|
'telefono_cellulare' => $this->clean($row['cellulare'] ?? null),
|
|
'fax' => $this->clean($row['fax'] ?? null),
|
|
'email' => $legacyEmail,
|
|
'pec' => $this->clean($row['pec_fornitore'] ?? null),
|
|
'note' => $note,
|
|
'categoria' => 'fornitore',
|
|
'stato' => 'attivo',
|
|
'tipo_contatto' => ($piva || ($ragione && ! $nome && ! $cognome)) ? 'persona_giuridica' : 'persona_fisica',
|
|
];
|
|
|
|
$titoloId = $this->resolveTitoloId($legacyTitoloRaw);
|
|
if ($titoloId !== null) {
|
|
$rubricaPayload['titolo_id'] = $titoloId;
|
|
}
|
|
|
|
$rubricaPayload = $this->filterPayloadForTable('rubrica_universale', $rubricaPayload);
|
|
|
|
$rubricaResult = $this->upsertRubrica($rubricaPayload, $legacyId);
|
|
$rubrica = $rubricaResult['rubrica'] ?? null;
|
|
if (! $rubrica) {
|
|
$stats['skipped']++;
|
|
continue;
|
|
}
|
|
|
|
// Se il codice_univoco viene assegnato via trigger MySQL, il model Eloquent potrebbe non averlo valorizzato.
|
|
// Lo rileggo dal DB per usarlo in dedup/merge e per evitare violazioni di unique sui fornitori.
|
|
$rubricaCode = $rubrica->codice_univoco;
|
|
if ((! is_string($rubricaCode) || $rubricaCode === '') && (int) $rubrica->id > 0) {
|
|
$rubricaCode = RubricaUniversale::query()->whereKey((int) $rubrica->id)->value('codice_univoco');
|
|
if (is_string($rubricaCode) && $rubricaCode !== '') {
|
|
$rubrica->codice_univoco = $rubricaCode;
|
|
}
|
|
}
|
|
|
|
if (($rubricaResult['created'] ?? false) === true) {
|
|
$stats['created_rubrica']++;
|
|
} elseif (($rubricaResult['updated'] ?? false) === true) {
|
|
$stats['updated_rubrica']++;
|
|
}
|
|
|
|
$regimeCode = $this->clean($row['regime_fiscale'] ?? null);
|
|
$regimeId = $this->resolveRegimeFiscaleId($regimeCode);
|
|
|
|
$tipoCassaCode = $this->clean($row['tipo_cassa'] ?? null);
|
|
$tipoCassaId = $this->resolveTipologiaCassaId($tipoCassaCode);
|
|
|
|
$aliqIvaAbituale = $this->clean($row['aliq_iva_abituale'] ?? $row['aliq_iva'] ?? null);
|
|
$aliquotaIvaId = $this->resolveAliquotaIvaId($aliqIvaAbituale);
|
|
|
|
$fornitorePayload = [
|
|
'amministratore_id' => $amministratoreId,
|
|
'cod_forn' => $codForn,
|
|
'rubrica_id' => $rubrica->id,
|
|
'codice_univoco' => (is_string($rubricaCode) && $rubricaCode !== '') ? $rubricaCode : null,
|
|
'ragione_sociale' => $ragione ?? $rubrica->ragione_sociale,
|
|
'nome' => $nome ?? $rubrica->nome,
|
|
'cognome' => $cognome ?? $rubrica->cognome,
|
|
'partita_iva' => $piva ?? $rubrica->partita_iva,
|
|
'codice_fiscale' => $cf ?? $rubrica->codice_fiscale,
|
|
'indirizzo' => $rubricaPayload['indirizzo'] ?? $rubrica->indirizzo,
|
|
'cap' => $rubricaPayload['cap'] ?? $rubrica->cap,
|
|
'citta' => $rubricaPayload['citta'] ?? $rubrica->citta,
|
|
'provincia' => $rubricaPayload['provincia'] ?? $rubrica->provincia,
|
|
'email' => $rubricaPayload['email'] ?? $rubrica->email,
|
|
'pec' => $rubricaPayload['pec'] ?? $rubrica->pec,
|
|
'iban' => $this->clean($row['cod_iban'] ?? null),
|
|
'intestazione_cc_esatta' => $legacyIntestazioneCcEsatta,
|
|
'telefono' => $rubricaPayload['telefono_ufficio'] ?? $rubrica->telefono_ufficio,
|
|
'cellulare' => $rubricaPayload['telefono_cellulare'] ?? $rubrica->telefono_cellulare,
|
|
'note' => $rubricaPayload['note'] ?? $rubrica->note,
|
|
'regime_fiscale_id' => $regimeId,
|
|
'tipologia_cassa_id' => $tipoCassaId,
|
|
'aliquota_iva_id' => $aliquotaIvaId,
|
|
'old_id' => $legacyId,
|
|
];
|
|
|
|
$fornitorePayload = $this->filterPayloadForTable('fornitori', $fornitorePayload);
|
|
|
|
$existing = null;
|
|
if ($legacyId !== null) {
|
|
$existing = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('old_id', $legacyId)
|
|
->first();
|
|
}
|
|
if (! $existing && $codForn !== null && Schema::hasColumn('fornitori', 'cod_forn')) {
|
|
$existing = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('cod_forn', $codForn)
|
|
->first();
|
|
}
|
|
if (! $existing && ! empty($fornitorePayload['rubrica_id'])) {
|
|
$existing = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('rubrica_id', $fornitorePayload['rubrica_id'])
|
|
->first();
|
|
}
|
|
if (! $existing && is_string($rubricaCode) && $rubricaCode !== '') {
|
|
// Evita violazioni di unique(amministratore_id, codice_univoco)
|
|
// Se esiste già un fornitore con lo stesso codice_univoco della rubrica, aggiorniamo quello.
|
|
$existing = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('codice_univoco', $rubricaCode)
|
|
->first();
|
|
}
|
|
if (! $existing && $piva) {
|
|
$existing = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('partita_iva', $piva)
|
|
->first();
|
|
}
|
|
if (! $existing && $cf) {
|
|
$existing = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('codice_fiscale', $cf)
|
|
->first();
|
|
}
|
|
|
|
$fornitorePayload = array_filter(
|
|
$fornitorePayload,
|
|
fn($v) => ! is_string($v) ? $v !== null : trim($v) !== ''
|
|
);
|
|
|
|
$fornitoreModel = null;
|
|
if ($existing) {
|
|
if (! empty($fornitorePayload['codice_univoco'])) {
|
|
$dup = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('codice_univoco', $fornitorePayload['codice_univoco'])
|
|
->where('id', '!=', $existing->id)
|
|
->exists();
|
|
if ($dup) {
|
|
unset($fornitorePayload['codice_univoco']);
|
|
}
|
|
}
|
|
|
|
$existing->fill($fornitorePayload);
|
|
if ($existing->isDirty()) {
|
|
if (! $dry) {
|
|
$existing->save();
|
|
}
|
|
$stats['updated_fornitori']++;
|
|
}
|
|
$fornitoreModel = $existing;
|
|
} else {
|
|
if (! empty($fornitorePayload['codice_univoco'])) {
|
|
$dup = Fornitore::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where('codice_univoco', $fornitorePayload['codice_univoco'])
|
|
->exists();
|
|
if ($dup) {
|
|
unset($fornitorePayload['codice_univoco']);
|
|
}
|
|
}
|
|
if ($dry) {
|
|
$fornitoreModel = new Fornitore($fornitorePayload);
|
|
} else {
|
|
$fornitoreModel = Fornitore::create($fornitorePayload);
|
|
}
|
|
$stats['created_fornitori']++;
|
|
}
|
|
|
|
if (! $fornitoreModel instanceof Fornitore) {
|
|
continue;
|
|
}
|
|
|
|
$dipResult = $this->upsertDipendenteCollegato(
|
|
$fornitoreModel,
|
|
$legacyEmail,
|
|
$nome,
|
|
$cognome,
|
|
$rubricaPayload['telefono_cellulare'] ?? null,
|
|
$legacyTitoloRaw,
|
|
$legacySesso,
|
|
$legacyDtNas,
|
|
$legacyLuoNas,
|
|
$legacyPrNas,
|
|
$dry,
|
|
);
|
|
if ($dipResult === 'created') {
|
|
$stats['created_dipendenti']++;
|
|
} elseif ($dipResult === 'updated') {
|
|
$stats['updated_dipendenti']++;
|
|
}
|
|
}
|
|
};
|
|
|
|
if ($dry) {
|
|
$runner();
|
|
} else {
|
|
DB::transaction($runner);
|
|
}
|
|
|
|
$this->info('✅ Import completato' . ($dry ? ' (dry-run)' : ''));
|
|
$this->line(json_encode($stats, JSON_UNESCAPED_UNICODE));
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/** @return array{0: array<int,array<string,mixed>>, 1: array<int,string>} */
|
|
private function parseCsv(string $csv): array
|
|
{
|
|
$fh = fopen('php://temp', 'r+');
|
|
fwrite($fh, $csv);
|
|
rewind($fh);
|
|
|
|
$headers = fgetcsv($fh, 0, ',', '^');
|
|
if (! $headers) {
|
|
return [[], []];
|
|
}
|
|
$headers = array_map(fn($h) => strtolower(trim((string) $h)), $headers);
|
|
|
|
$rows = [];
|
|
while (($cols = fgetcsv($fh, 0, ',', '^')) !== false) {
|
|
if (empty($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));
|
|
}
|
|
$assoc = @array_combine($headers, $cols);
|
|
if (! $assoc) {
|
|
continue;
|
|
}
|
|
$rows[] = $assoc;
|
|
}
|
|
|
|
return [$rows, $headers];
|
|
}
|
|
|
|
/** @param array<string, mixed> $payload
|
|
* @return array{rubrica: RubricaUniversale|null, created: bool, updated: bool}
|
|
*/
|
|
private function upsertRubrica(array $payload, ?int $legacyId): array
|
|
{
|
|
$amministratoreId = $this->toInt($payload['amministratore_id'] ?? null);
|
|
|
|
// Rubrica multi-tenant "soft": match per amministratore o globale (NULL).
|
|
// Se amministratore_id non è presente nello schema, lo scope non viene applicato.
|
|
$query = RubricaUniversale::query();
|
|
if (Schema::hasColumn('rubrica_universale', 'amministratore_id') && $amministratoreId !== null) {
|
|
$query->where(function ($q) use ($amministratoreId) {
|
|
$q->where('amministratore_id', $amministratoreId)
|
|
->orWhereNull('amministratore_id');
|
|
});
|
|
}
|
|
|
|
$piva = $payload['partita_iva'] ?? null;
|
|
$cf = $payload['codice_fiscale'] ?? null;
|
|
$ragione = $payload['ragione_sociale'] ?? null;
|
|
|
|
if ($piva) {
|
|
$existing = (clone $query)->where('partita_iva', $piva)->first();
|
|
} elseif ($cf) {
|
|
$existing = (clone $query)->where('codice_fiscale', $cf)->first();
|
|
} elseif ($ragione) {
|
|
$existing = (clone $query)->whereRaw('LOWER(ragione_sociale) = ?', [mb_strtolower($ragione)])
|
|
->first();
|
|
} else {
|
|
$existing = null;
|
|
}
|
|
|
|
$payload = array_filter(
|
|
$payload,
|
|
fn($v) => ! is_string($v) ? $v !== null : trim($v) !== ''
|
|
);
|
|
|
|
if ($this->option('dry-run')) {
|
|
if ($existing) {
|
|
$existing->fill($payload);
|
|
return [
|
|
'rubrica' => $existing,
|
|
'created' => false,
|
|
'updated' => $existing->isDirty(),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'rubrica' => new RubricaUniversale($payload),
|
|
'created' => true,
|
|
'updated' => false,
|
|
];
|
|
}
|
|
|
|
if ($existing) {
|
|
$existing->fill($payload);
|
|
$dirty = $existing->isDirty();
|
|
if ($dirty) {
|
|
$existing->save();
|
|
}
|
|
return [
|
|
'rubrica' => $existing,
|
|
'created' => false,
|
|
'updated' => $dirty,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'rubrica' => RubricaUniversale::create($payload),
|
|
'created' => true,
|
|
'updated' => false,
|
|
];
|
|
}
|
|
|
|
/** @param array<string, mixed> $payload
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function filterPayloadForTable(string $table, array $payload): array
|
|
{
|
|
if (! Schema::hasTable($table)) {
|
|
return $payload;
|
|
}
|
|
|
|
if (! isset($this->columnCache[$table])) {
|
|
$this->columnCache[$table] = Schema::getColumnListing($table);
|
|
}
|
|
|
|
$allowed = array_flip($this->columnCache[$table]);
|
|
$filtered = array_intersect_key($payload, $allowed);
|
|
|
|
return array_filter(
|
|
$filtered,
|
|
fn($v) => ! is_string($v) ? $v !== null : trim($v) !== ''
|
|
);
|
|
}
|
|
|
|
private function resolveRegimeFiscaleId(?string $code): ?int
|
|
{
|
|
$code = $this->clean($code);
|
|
if ($code === null) {
|
|
return null;
|
|
}
|
|
|
|
$key = strtolower($code);
|
|
|
|
$regime = RegimeFiscale::query()
|
|
->whereRaw('LOWER(codice) = ?', [$key])
|
|
->orWhereRaw('LOWER(descrizione) = ?', [$key])
|
|
->first();
|
|
|
|
return $regime?->id;
|
|
}
|
|
|
|
private function resolveTipologiaCassaId(?string $code): ?int
|
|
{
|
|
$code = $this->clean($code);
|
|
if ($code === null) {
|
|
return null;
|
|
}
|
|
|
|
$key = strtolower($code);
|
|
$record = TipologiaCassa::query()
|
|
->whereRaw('LOWER(codice) = ?', [$key])
|
|
->orWhereRaw('LOWER(descrizione) = ?', [$key])
|
|
->first();
|
|
|
|
return $record?->id;
|
|
}
|
|
|
|
private function resolveAliquotaIvaId(?string $valueOrCode): ?int
|
|
{
|
|
$valueOrCode = $this->clean($valueOrCode);
|
|
if ($valueOrCode === null) {
|
|
return null;
|
|
}
|
|
|
|
// Supporta sia codici (es. IVA22) sia valori numerici (es. 22)
|
|
$key = strtolower($valueOrCode);
|
|
|
|
$byCode = AliquotaIva::query()
|
|
->whereRaw('LOWER(codice) = ?', [$key])
|
|
->first();
|
|
if ($byCode) {
|
|
return $byCode->id;
|
|
}
|
|
|
|
$numeric = str_replace(',', '.', $valueOrCode);
|
|
if (is_numeric($numeric)) {
|
|
$val = (float) $numeric;
|
|
$byValue = AliquotaIva::query()
|
|
->where('valore', $val)
|
|
->first();
|
|
return $byValue?->id;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function clean($v): ?string
|
|
{
|
|
if ($v === null) {
|
|
return null;
|
|
}
|
|
$s = (string) $v;
|
|
// Normalizza newline/controlli e compatta spazi, mantenendo una stringa leggibile.
|
|
$s = str_replace(["\r\n", "\r"], "\n", $s);
|
|
$s = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $s) ?? $s;
|
|
$s = preg_replace('/\s+/u', ' ', $s) ?? $s;
|
|
$s = trim($s);
|
|
return $s === '' ? null : $s;
|
|
}
|
|
|
|
private function looksLikeContactBlob(string $s): bool
|
|
{
|
|
$s = trim($s);
|
|
if ($s === '') {
|
|
return false;
|
|
}
|
|
|
|
// Esempi tipici legacy: "Tel.: ...", "Cell. ...", "Fax: ..."
|
|
if (preg_match('/^(tel\.?|cell\.?|fax\.?|telefono|mobile)\b\s*[:\.]/i', $s)) {
|
|
return true;
|
|
}
|
|
|
|
// Stringhe composte solo da numeri/segni (telefono senza label)
|
|
if (preg_match('/^[\d\s+().\-\/]+$/', $s)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function toInt($v): ?int
|
|
{
|
|
if ($v === null) {
|
|
return null;
|
|
}
|
|
$s = trim((string) $v);
|
|
if ($s === '' || ! is_numeric($s)) {
|
|
return null;
|
|
}
|
|
return (int) $s;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
*/
|
|
private function pick(array $row, array $keys): mixed
|
|
{
|
|
foreach ($keys as $key) {
|
|
if (array_key_exists($key, $row)) {
|
|
return $row[$key];
|
|
}
|
|
$lower = strtolower((string) $key);
|
|
if (array_key_exists($lower, $row)) {
|
|
return $row[$lower];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function normalizeLegacyDate(mixed $value): ?string
|
|
{
|
|
$raw = $this->clean($value);
|
|
if ($raw === null) {
|
|
return null;
|
|
}
|
|
|
|
$raw = trim($raw);
|
|
foreach (['d/m/Y', 'd-m-Y', 'Y-m-d', 'm/d/Y', 'd.m.Y', 'd/m/y', 'd-m-y', 'm/d/y', 'd.m.y'] as $format) {
|
|
$dt = \DateTime::createFromFormat($format, $raw);
|
|
if ($dt instanceof \DateTime) {
|
|
return $this->normalizeBirthDateYear($dt);
|
|
}
|
|
}
|
|
|
|
$ts = strtotime($raw);
|
|
if ($ts !== false) {
|
|
return $this->normalizeBirthDateYear((new \DateTime())->setTimestamp($ts));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function normalizeBirthDateYear(\DateTime $date): ?string
|
|
{
|
|
$year = (int) $date->format('Y');
|
|
$currentYear = (int) now()->format('Y');
|
|
|
|
if ($year > $currentYear + 1) {
|
|
$date->modify('-100 years');
|
|
$year = (int) $date->format('Y');
|
|
}
|
|
|
|
if ($year < 1900 || $year > $currentYear + 1) {
|
|
return null;
|
|
}
|
|
|
|
return $date->format('Y-m-d');
|
|
}
|
|
|
|
private function normalizeSesso(mixed $value): ?string
|
|
{
|
|
$raw = strtoupper((string) $this->clean($value));
|
|
if ($raw === 'M' || $raw === 'F') {
|
|
return $raw;
|
|
}
|
|
if (str_starts_with($raw, 'MAS')) {
|
|
return 'M';
|
|
}
|
|
if (str_starts_with($raw, 'FEM')) {
|
|
return 'F';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function resolveTitoloId(?string $raw): ?int
|
|
{
|
|
$raw = $this->clean($raw);
|
|
if ($raw === null || ! Schema::hasTable('titoli')) {
|
|
return null;
|
|
}
|
|
|
|
$key = mb_strtolower($raw);
|
|
|
|
$title = Titolo::query()
|
|
->whereRaw('LOWER(sigla) = ?', [$key])
|
|
->orWhereRaw('LOWER(nome) = ?', [$key])
|
|
->first();
|
|
|
|
return $title?->id;
|
|
}
|
|
|
|
private function upsertDipendenteCollegato(
|
|
Fornitore $fornitore,
|
|
?string $email,
|
|
?string $nome,
|
|
?string $cognome,
|
|
?string $telefono,
|
|
?string $titolo,
|
|
?string $sesso,
|
|
?string $dataNascita,
|
|
?string $luogoNascita,
|
|
?string $provinciaNascita,
|
|
bool $dry,
|
|
): ?string {
|
|
$email = $this->clean($email);
|
|
$nome = $this->clean($nome);
|
|
$cognome = $this->clean($cognome);
|
|
$telefono = $this->clean($telefono);
|
|
|
|
if ($email === null && $nome === null && $cognome === null) {
|
|
return null;
|
|
}
|
|
|
|
$displayName = trim((string) (($nome ?? '') . ' ' . ($cognome ?? '')));
|
|
if ($displayName === '' && $email !== null) {
|
|
$displayName = strtok($email, '@') ?: 'Referente';
|
|
$displayName = str_replace(['.', '_', '-'], ' ', $displayName);
|
|
}
|
|
|
|
$resolvedNome = $nome ?: trim((string) strtok($displayName, ' '));
|
|
$resolvedCognome = $cognome;
|
|
if ($resolvedCognome === null) {
|
|
$parts = preg_split('/\s+/', trim($displayName)) ?: [];
|
|
if (count($parts) > 1) {
|
|
array_shift($parts);
|
|
$resolvedCognome = trim((string) implode(' ', $parts)) ?: null;
|
|
}
|
|
}
|
|
|
|
$meta = array_values(array_filter([
|
|
$titolo ? ('Titolo: ' . $titolo) : null,
|
|
$sesso ? ('Sesso: ' . $sesso) : null,
|
|
$dataNascita ? ('Data nascita: ' . $dataNascita) : null,
|
|
$luogoNascita ? ('Luogo nascita: ' . $luogoNascita) : null,
|
|
$provinciaNascita ? ('Provincia nascita: ' . $provinciaNascita) : null,
|
|
]));
|
|
$notes = $meta !== [] ? ('Import legacy GESCON. ' . implode(' | ', $meta)) : 'Import legacy GESCON.';
|
|
|
|
$query = FornitoreDipendente::query()->where('fornitore_id', (int) $fornitore->id);
|
|
if ($email !== null) {
|
|
$query->whereRaw('LOWER(email) = ?', [mb_strtolower($email)]);
|
|
} elseif ($resolvedNome !== null) {
|
|
$query->whereRaw('LOWER(nome) = ?', [mb_strtolower($resolvedNome)]);
|
|
if ($resolvedCognome !== null) {
|
|
$query->whereRaw('LOWER(cognome) = ?', [mb_strtolower($resolvedCognome)]);
|
|
}
|
|
}
|
|
|
|
$existing = $query->first();
|
|
|
|
$payload = [
|
|
'nome' => $resolvedNome ?: 'Referente',
|
|
'cognome' => $resolvedCognome,
|
|
'email' => $email,
|
|
'telefono' => $telefono,
|
|
'attivo' => true,
|
|
'note' => $notes,
|
|
];
|
|
|
|
if ($existing) {
|
|
$existing->fill($payload);
|
|
if (! $existing->isDirty()) {
|
|
return null;
|
|
}
|
|
if (! $dry) {
|
|
$existing->save();
|
|
}
|
|
|
|
return 'updated';
|
|
}
|
|
|
|
if (! $dry) {
|
|
FornitoreDipendente::query()->create(array_merge($payload, [
|
|
'fornitore_id' => (int) $fornitore->id,
|
|
]));
|
|
}
|
|
|
|
return 'created';
|
|
}
|
|
}
|