4679 lines
220 KiB
PHP
4679 lines
220 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Arr;
|
|
use Throwable;
|
|
|
|
class ImportGesconFullPipeline extends Command
|
|
{
|
|
protected $signature = 'gescon:import-full
|
|
{--stabile= : Codice stabile (cartella es. 0021)}
|
|
{--anno= : Anno gestione da importare (es. 2024)}
|
|
{--path= : Path base contenente i file singolo_anno.mdb}
|
|
{--mdb= : Percorso esplicito a Stabili.mdb per arricchimento metadati}
|
|
{--mdb-condomin= : Percorso esplicito al file MDB contenente la tabella condomin (singolo_anno.mdb)}
|
|
{--split-mode=palazzine : Strategia divisione UI: none|palazzine|stabili-per-scala}
|
|
{--scala= : Filtra per scala/palazzina specifica}
|
|
{--piano-min= : Filtra piano minimo normalizzato}
|
|
{--piano-max= : Filtra piano massimo normalizzato}
|
|
{--only-pertinenze= : Solo pertinenze (any|cantina|box|posto_auto|soffitta|locale_tecnico)}
|
|
{--dry-run : Simula senza scrivere sulle tabelle dominio}
|
|
{--limit= : Limita numero record per debug}
|
|
{--reset-temp : Svuota staging prima di importare}
|
|
{--solo= : Step singolo: unita|soggetti|diritti|millesimi|voci|operazioni|rate|incassi|giroconti|straord|addebiti|detrazioni|tutto}
|
|
{--no-views : Non ricrea le viste QA}
|
|
{--force-mapping : Applica mapping anche sovrascrivendo valori esistenti}
|
|
{--amministratore-id= : Forza l\'assegnazione dello stabile a questo amministratore}
|
|
';
|
|
|
|
protected $description = 'Pipeline orchestrata di import completo da archivio Gescon (MDB singolo anno) verso schema dominio.';
|
|
|
|
private array $stepsOrder = [
|
|
'stabili',
|
|
'unita',
|
|
'soggetti',
|
|
'diritti',
|
|
'millesimi',
|
|
'voci',
|
|
// Importa conti bancari per stabile dalle tabelle legacy (Anagr_casse)
|
|
'banche',
|
|
'operazioni',
|
|
'rate',
|
|
'incassi',
|
|
'giroconti',
|
|
'straord',
|
|
'addebiti',
|
|
'detrazioni'
|
|
];
|
|
|
|
private array $legacyYearMap = [];
|
|
|
|
private bool $legacyYearMapLoaded = false;
|
|
|
|
// Mapping campi configurato via UI (per-utente/per-stabile). Caricato on-demand.
|
|
private const BOOLEAN_TARGET_KEYS = [
|
|
'stabile.ascensore',
|
|
'stabile.presenza_ascensore',
|
|
'stabile.riscaldamento_centralizzato',
|
|
'stabile.acqua_centralizzata',
|
|
'stabile.gas_centralizzato',
|
|
'stabile.servizio_portineria',
|
|
'stabile.videocitofono',
|
|
'stabile.antenna_tv_centralizzata',
|
|
'stabile.internet_condominiale',
|
|
'stabile.piano_seminterrato',
|
|
'stabile.piano_sottotetto',
|
|
];
|
|
|
|
private array $fieldMapping = [];
|
|
private array $banksMapping = [];
|
|
private array $manualFieldValues = [];
|
|
private bool $forceMapping = false;
|
|
private string $splitMode = 'palazzine';
|
|
// Cache per risoluzione conto bancario di default per stabile
|
|
private array $defaultContoCache = [];
|
|
// Legacy row cache per stabili (usata da mapField migliorata)
|
|
private ?array $stabileLegacyRow = null;
|
|
|
|
public function handle(): int
|
|
{
|
|
if (!$this->option('stabile')) {
|
|
$this->error('Devi specificare --stabile=<codice cartella> (es. --stabile=0001).');
|
|
return 1;
|
|
}
|
|
$solo = $this->option('solo') ?: 'tutto';
|
|
$dry = (bool)$this->option('dry-run');
|
|
$limit = $this->option('limit') ? (int)$this->option('limit') : null;
|
|
$this->isDryRun = $dry;
|
|
$this->forceMapping = (bool)$this->option('force-mapping');
|
|
$this->splitMode = in_array($this->option('split-mode') ?? 'palazzine', ['none', 'palazzine', 'stabili-per-scala'], true)
|
|
? (string)$this->option('split-mode')
|
|
: 'palazzine';
|
|
$this->stabileId = $this->resolveStabileId();
|
|
|
|
$steps = $solo === 'tutto' ? $this->stepsOrder : array_intersect($this->stepsOrder, [$solo]);
|
|
if (empty($steps)) {
|
|
$this->error('Nessuno step valido.');
|
|
return 1;
|
|
}
|
|
|
|
$this->info('🚀 Avvio pipeline import Gescon');
|
|
$this->line('Steps: ' . implode(' -> ', $steps) . ($dry ? ' (DRY RUN)' : ''));
|
|
|
|
// Carica mapping se disponibile per lo stabile richiesto
|
|
$this->loadMappingIfAny();
|
|
|
|
// Preflight: assicura dati minimi di staging per lo stabile (condomin) se fornito un MDB esplicito
|
|
$this->preflightStaging();
|
|
|
|
foreach ($steps as $step) {
|
|
$method = 'step' . ucfirst($step);
|
|
if (!method_exists($this, $method)) {
|
|
$this->warn("Skip step sconosciuto: {$step}");
|
|
continue;
|
|
}
|
|
$this->newLine();
|
|
$this->line("▶️ Step {$step}...");
|
|
if ($dry) {
|
|
DB::beginTransaction();
|
|
}
|
|
try {
|
|
$count = $this->{$method}($limit);
|
|
$this->info(" → {$step}: {$count} record");
|
|
if ($dry && DB::transactionLevel() > 0) {
|
|
DB::rollBack();
|
|
}
|
|
} catch (Throwable $e) {
|
|
if ($dry && DB::transactionLevel() > 0) {
|
|
DB::rollBack();
|
|
}
|
|
$this->error("Errore step {$step}: " . $e->getMessage());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (!$this->option('no-views')) {
|
|
$this->callSilently('migrate', ['--path' => 'database/migrations/2025_08_14_120000_create_gescon_domain_tables_and_views.php']);
|
|
$this->info('🔄 Viste QA aggiornate.');
|
|
}
|
|
|
|
$this->info('✅ Pipeline completata.');
|
|
return 0;
|
|
}
|
|
|
|
private function stepStabili(?int $limit): int
|
|
{
|
|
// Crea stabili di dominio partendo da staging.stabili (se presente) o da condomin.cod_stabile
|
|
$created = 0;
|
|
$updated = 0;
|
|
$stagingHasStabili = Schema::connection('gescon_import')->hasTable('stabili');
|
|
$src = $stagingHasStabili
|
|
? DB::connection('gescon_import')->table('stabili')
|
|
: (Schema::connection('gescon_import')->hasTable('condomin') ? DB::connection('gescon_import')->table('condomin')->select('cod_stabile as cod_stabile')->distinct() : null);
|
|
if (!$src) {
|
|
// Fallback: se non c'è staging e abbiamo --mdb e --stabile, prova a creare lo stabile dai metadati di Stabili.mdb
|
|
$codRichiesto = $this->option('stabile');
|
|
$mdbPath = $this->option('mdb')
|
|
?: ($this->option('path') ? rtrim($this->option('path'), '/') . '/Stabili.mdb' : null);
|
|
if ($codRichiesto && $mdbPath && is_file($mdbPath) && Schema::hasTable('stabili')) {
|
|
$created += $this->createStabileFromMdbIfNeeded($mdbPath, $codRichiesto);
|
|
return $created;
|
|
}
|
|
return 0;
|
|
}
|
|
if ($this->option('stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
foreach ($src->get() as $row) {
|
|
$cod = $row->cod_stabile ?? null;
|
|
if (!$cod) continue;
|
|
if (!Schema::hasTable('stabili')) continue;
|
|
$col = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$exists = DB::table('stabili')->where($col, $cod)->first();
|
|
$amminId = null;
|
|
if (Schema::hasColumn('stabili', 'amministratore_id') && Schema::hasTable('amministratori')) {
|
|
$optAdm = $this->option('amministratore-id');
|
|
if ($optAdm) {
|
|
$existsAdm = DB::table('amministratori')->where('id', (int)$optAdm)->exists();
|
|
$amminId = $existsAdm ? (int)$optAdm : null;
|
|
}
|
|
if (!$amminId) {
|
|
$amminId = DB::table('amministratori')->value('id') ?? null;
|
|
}
|
|
}
|
|
$payload = [
|
|
$col => $cod,
|
|
'denominazione' => $this->mapField('stabile.denominazione', $cod),
|
|
'indirizzo' => $this->mapField('stabile.indirizzo', 'ND'),
|
|
'citta' => 'ND',
|
|
'cap' => $this->mapField('stabile.cap', '00000'),
|
|
'provincia' => $this->mapField('stabile.provincia', 'XX'),
|
|
'amministratore_id' => $amminId,
|
|
'attivo' => true,
|
|
'updated_at' => now()
|
|
];
|
|
if (!$exists) {
|
|
$payload['created_at'] = now();
|
|
// Tag provenienza per visibilità in UI
|
|
$payload['note'] = trim((string)($payload['note'] ?? ''));
|
|
$payload['note'] = ($payload['note'] ? ($payload['note'] . ' ') : '') . '[GESCON]';
|
|
$this->dynamicInsert('stabili', $payload);
|
|
$created++;
|
|
} else {
|
|
// Upsert non distruttivo: aggiorna solo se vuoti e se mapping prevede un valore
|
|
$updates = [];
|
|
foreach (['denominazione', 'indirizzo', 'cap', 'provincia'] as $k) {
|
|
if (!empty($payload[$k])) {
|
|
if (empty($exists->$k)) $updates[$k] = $payload[$k];
|
|
}
|
|
}
|
|
// Se richiesto esplicitamente, riassegna amministratore dello stabile esistente
|
|
if ($amminId && Schema::hasColumn('stabili', 'amministratore_id')) {
|
|
$cur = (int)($exists->amministratore_id ?? 0);
|
|
if ($cur !== $amminId) {
|
|
if ($this->isDryRun) {
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(" (dry-run) riassegnazione stabile {$cod} → amministratore #{$amminId}");
|
|
}
|
|
} else {
|
|
DB::table('stabili')->where('id', $exists->id)->update([
|
|
'amministratore_id' => $amminId,
|
|
'updated_at' => now(),
|
|
]);
|
|
$updated++;
|
|
$this->line(" -> riassegnato stabile {$cod} all'amministratore #{$amminId}");
|
|
}
|
|
}
|
|
}
|
|
if (!empty($updates)) {
|
|
$updates['updated_at'] = now();
|
|
// Aggiungi tag GESCON se manca
|
|
if (Schema::hasColumn('stabili', 'note')) {
|
|
$note = (string)($exists->note ?? '');
|
|
if (stripos($note, 'GESCON') === false) {
|
|
$updates['note'] = trim($note . ' [GESCON]');
|
|
}
|
|
}
|
|
if ($this->isDryRun) {
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(' (dry-run) aggiornamento stabili skip');
|
|
}
|
|
} else {
|
|
DB::table('stabili')->where('id', $exists->id)->update($updates);
|
|
$updated++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Aggiorna metadati da Stabili.mdb se disponibile (nome_directory corrisponde a --stabile, es. 0021)
|
|
$mdbPath = $this->option('mdb')
|
|
?: ($this->option('path') ? rtrim($this->option('path'), '/') . '/Stabili.mdb' : '/mnt/gescon-archives/gescon/dbc/Stabili.mdb');
|
|
$codRichiesto = $this->option('stabile');
|
|
if ($codRichiesto && is_file($mdbPath)) {
|
|
$row = $this->mdbStabiliFindByDirectory($mdbPath, $codRichiesto);
|
|
if ($row) {
|
|
$col = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
// Match robusto: gestisci eventuali zeri iniziali nel codice
|
|
$stab = DB::table('stabili')->where($col, $codRichiesto)->first();
|
|
if (!$stab && $col === 'codice_stabile') {
|
|
$alt = ltrim($codRichiesto, '0');
|
|
if ($alt !== '' && $alt !== $codRichiesto) {
|
|
$stab = DB::table('stabili')->where($col, $alt)->first();
|
|
}
|
|
}
|
|
if ($stab) {
|
|
// Separa CF condominio (11 cifre) e CF amministratore (16 alfanumerico tipico persona fisica)
|
|
$cfCondo = $row['cf_condominio']
|
|
?? (isset($row['codice_fiscale']) && preg_match('/^\d{11}$/', (string)$row['codice_fiscale']) ? $row['codice_fiscale'] : null);
|
|
$cfAmm = $row['cf_amministratore']
|
|
?? (isset($row['cin']) && preg_match('/^(?=.*[A-Z])[A-Z0-9]{16}$/i', (string)$row['cin']) ? $row['cin'] : null);
|
|
|
|
// Base update dai valori MDB
|
|
$update = [
|
|
'denominazione' => $row['denominazione'] ?? $codRichiesto,
|
|
'indirizzo' => $row['indirizzo'] ?? null,
|
|
'cap' => $row['cap'] ?? null,
|
|
'citta' => $row['citta'] ?? null,
|
|
'provincia' => $row['provincia'] ?? null,
|
|
'codice_fiscale' => $cfCondo ?? null,
|
|
'updated_at' => now(),
|
|
];
|
|
if ($cfAmm && Schema::hasColumn('stabili', 'cod_fisc_amministratore')) {
|
|
$update['cod_fisc_amministratore'] = $cfAmm;
|
|
}
|
|
// Applica mapping se configurato (rispetta --force-mapping per overwrite)
|
|
$update = $this->applyStabileMapping($update, $row, $stab);
|
|
$cols = Schema::getColumnListing('stabili');
|
|
$update = array_intersect_key($update, array_flip($cols));
|
|
// Non inserire chiavi con valore NULL per evitare violazioni NOT NULL
|
|
$update = array_filter($update, function ($v) {
|
|
return $v !== null;
|
|
});
|
|
// Verbose log: evidenzia cambio CF (o potenziale in dry-run)
|
|
if ($this->output->isVerbose()) {
|
|
$oldCf = $stab->codice_fiscale ?? null;
|
|
$newCf = $update['codice_fiscale'] ?? null;
|
|
if ($newCf && $newCf !== $oldCf) {
|
|
$this->line(sprintf(
|
|
' -> CF %s: %s => %s%s%s',
|
|
$codRichiesto,
|
|
$oldCf !== null && $oldCf !== '' ? $oldCf : 'NULL',
|
|
$newCf,
|
|
$this->option('dry-run') ? ' (DRY-RUN)' : '',
|
|
$this->forceMapping ? ' [forced]' : ''
|
|
));
|
|
}
|
|
if (isset($update['cod_fisc_amministratore'])) {
|
|
$oldCfa = $stab->cod_fisc_amministratore ?? null;
|
|
$newCfa = $update['cod_fisc_amministratore'];
|
|
if ($newCfa && $newCfa !== $oldCfa) {
|
|
$this->line(sprintf(
|
|
' -> CF Amm. %s: %s => %s%s%s',
|
|
$codRichiesto,
|
|
$oldCfa !== null && $oldCfa !== '' ? $oldCfa : 'NULL',
|
|
$newCfa,
|
|
$this->option('dry-run') ? ' (DRY-RUN)' : '',
|
|
$this->forceMapping ? ' [forced]' : ''
|
|
));
|
|
}
|
|
}
|
|
}
|
|
if (!empty($update)) {
|
|
if (Schema::hasColumn('stabili', 'note')) {
|
|
$note = DB::table('stabili')->where('id', $stab->id)->value('note');
|
|
if (stripos((string)$note, 'GESCON') === false) {
|
|
$update['note'] = trim((string)$note . ' [GESCON]');
|
|
}
|
|
}
|
|
$this->safeUpdateStabile($stab->id, $update);
|
|
if (!$this->isDryRun) {
|
|
$updated++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Se disponibile la tabella staging.stabili, importa CF e Catasto
|
|
if ($codRichiesto && Schema::connection('gescon_import')->hasTable('stabili')) {
|
|
$stage = DB::connection('gescon_import')->table('stabili')->where('cod_stabile', $codRichiesto)->first();
|
|
if ($stage && Schema::hasTable('stabili')) {
|
|
$stabCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$stab = DB::table('stabili')->where($stabCol, $codRichiesto)->first();
|
|
if (!$stab && $stabCol === 'codice_stabile') {
|
|
$alt = ltrim($codRichiesto, '0');
|
|
if ($alt !== '' && $alt !== $codRichiesto) {
|
|
$stab = DB::table('stabili')->where($stabCol, $alt)->first();
|
|
}
|
|
}
|
|
if ($stab) {
|
|
$cols = Schema::getColumnListing('stabili');
|
|
$map = [];
|
|
// CF condominio
|
|
$map['codice_fiscale'] = $stage->codice_fiscale
|
|
?? ($stage->cf ?? ($stage->cf_condominio ?? null));
|
|
// CF amministratore (se presente in staging)
|
|
$map['cod_fisc_amministratore'] = $stage->cod_fisc_amministratore ?? ($stage->cf_amministratore ?? null);
|
|
// Dati catasto (normalizza verso colonne effettive)
|
|
$foglioVal = $stage->foglio ?? ($stage->catasto_foglio ?? null);
|
|
if ($foglioVal !== null) {
|
|
if (in_array('foglio', $cols, true)) {
|
|
$map['foglio'] = $foglioVal;
|
|
} elseif (in_array('foglio_catasto', $cols, true)) {
|
|
$map['foglio_catasto'] = $foglioVal;
|
|
}
|
|
}
|
|
$partVal = $stage->particella ?? ($stage->catasto_particella ?? ($stage->mappale ?? null));
|
|
if ($partVal !== null) {
|
|
if (in_array('mappale', $cols, true)) {
|
|
$map['mappale'] = $partVal;
|
|
} elseif (in_array('particella_catasto', $cols, true)) {
|
|
$map['particella_catasto'] = $partVal;
|
|
}
|
|
}
|
|
$subVal = $stage->subalterno ?? ($stage->catasto_sub ?? null);
|
|
if ($subVal !== null && in_array('subalterno', $cols, true)) {
|
|
$map['subalterno'] = $subVal;
|
|
}
|
|
// Categoria catastale (se presente in staging)
|
|
$catVal = $stage->categoria ?? ($stage->categoria_catastale ?? null);
|
|
if ($catVal !== null) {
|
|
if (in_array('categoria', $cols, true)) {
|
|
$map['categoria'] = $catVal;
|
|
} elseif (in_array('categoria_catastale', $cols, true)) {
|
|
$map['categoria_catastale'] = $catVal;
|
|
}
|
|
}
|
|
// Filtra solo colonne esistenti e valori non null
|
|
$update = [];
|
|
foreach ($map as $k => $v) {
|
|
if ($v !== null && in_array($k, $cols, true)) {
|
|
$update[$k] = $v;
|
|
}
|
|
}
|
|
// Applica mapping UI anche sui dati staging (consente override esplicito CF quando forzato)
|
|
$update = $this->applyStabileMapping($update, (array)$stage, $stab);
|
|
if (!empty($update)) {
|
|
// Verbose log: evidenzia cambio CF da staging
|
|
if ($this->output->isVerbose()) {
|
|
$oldCf = $stab->codice_fiscale ?? null;
|
|
$newCf = $update['codice_fiscale'] ?? null;
|
|
if ($newCf && $newCf !== $oldCf) {
|
|
$this->line(sprintf(
|
|
' -> CF %s (staging): %s => %s%s%s',
|
|
$codRichiesto,
|
|
$oldCf !== null && $oldCf !== '' ? $oldCf : 'NULL',
|
|
$newCf,
|
|
$this->option('dry-run') ? ' (DRY-RUN)' : '',
|
|
$this->forceMapping ? ' [forced]' : ''
|
|
));
|
|
}
|
|
}
|
|
$update['updated_at'] = now();
|
|
$this->safeUpdateStabile($stab->id, $update);
|
|
if (!$this->isDryRun) {
|
|
$updated++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($created > 0 || $updated > 0) {
|
|
$this->line(" → stabili creati: {$created}, aggiornati: {$updated}");
|
|
}
|
|
return $created + $updated;
|
|
}
|
|
|
|
private function stepUnita(?int $limit): int
|
|
{
|
|
// Fonte: gescon_import.condomin (elenco soggetti/unità)
|
|
if (!Schema::connection('gescon_import')->hasTable('condomin')) return 0;
|
|
$count = 0;
|
|
$updated = 0;
|
|
$hasDetailedSchema = Schema::hasColumn('unita_immobiliari', 'codice_unita');
|
|
$parentStabile = null;
|
|
if (Schema::hasTable('stabili') && $this->option('stabile')) {
|
|
$stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$parentStabile = DB::table('stabili')->where($stabileCol, $this->option('stabile'))->first();
|
|
if (!$parentStabile && $stabileCol === 'codice_stabile') {
|
|
$alt = ltrim($this->option('stabile'), '0');
|
|
if ($alt !== '' && $alt !== $this->option('stabile')) {
|
|
$parentStabile = DB::table('stabili')->where($stabileCol, $alt)->first();
|
|
}
|
|
}
|
|
}
|
|
$pertinenzeToLink = [];
|
|
$seenCondIds = [];
|
|
$processed = 0;
|
|
$batch = $limit ? min(max(1, $limit), 1000) : 1000;
|
|
$offset = 0;
|
|
while (true) {
|
|
$q = DB::connection('gescon_import')->table('condomin');
|
|
if ($this->option('stabile')) {
|
|
$q->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($this->option('scala')) {
|
|
$q->where('scala', $this->option('scala'));
|
|
}
|
|
$q->limit($batch)->offset($offset);
|
|
$rows = $q->get();
|
|
if ($rows->isEmpty()) break;
|
|
foreach ($rows as $r) {
|
|
$legacyCondId = null;
|
|
if (isset($r->cod_cond)) {
|
|
$legacyCondId = trim((string) $r->cod_cond);
|
|
} elseif (isset($r->id_cond)) {
|
|
$legacyCondId = trim((string) $r->id_cond);
|
|
}
|
|
if ($legacyCondId !== null && $legacyCondId !== '') {
|
|
if (isset($seenCondIds[$legacyCondId])) {
|
|
continue;
|
|
}
|
|
$seenCondIds[$legacyCondId] = true;
|
|
}
|
|
|
|
// risolvi stabile_id per riga (multi-stabili)
|
|
$rowStabileId = null;
|
|
if (Schema::hasTable('stabili')) {
|
|
$stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$stRow = DB::table('stabili')->where($stabileCol, $r->cod_stabile ?? $this->option('stabile'))->first();
|
|
$rowStabileId = $stRow->id ?? $this->stabileId;
|
|
// Split per scala: crea/usa stabile per scala
|
|
$scalaCur = trim((string)($r->scala ?? 'A')) ?: 'A';
|
|
if ($this->splitMode === 'stabili-per-scala' && $parentStabile) {
|
|
[$childId, $childCode] = $this->ensureStabilePerScala($parentStabile, $scalaCur);
|
|
if ($childId) {
|
|
$rowStabileId = $childId;
|
|
// opzionale: potremmo voler usare childCode in codice_unita per coerenza
|
|
}
|
|
}
|
|
} else {
|
|
$rowStabileId = $this->stabileId;
|
|
}
|
|
$interno = $r->interno ?? null;
|
|
$sub = $r->sub ?? $interno;
|
|
// Riconosci pertinenze nel campo INT o note: cantina, box/garage, posto auto, soffitta/solaio, locale tecnico
|
|
$internoStr = is_string($interno) ? trim($interno) : (string)$interno;
|
|
if ($internoStr !== '' && preg_match('/APP\.?\s*COND\.?/i', $internoStr)) {
|
|
continue;
|
|
}
|
|
$ptype = null;
|
|
$pcode = null; // tipo pertinenza e codice interno normalizzato
|
|
if ($internoStr !== '') {
|
|
if (preg_match('/^CAN\s*(\d+)$/i', preg_replace('/\s+/', ' ', $internoStr), $m)) {
|
|
$ptype = 'cantina';
|
|
$pcode = 'CAN' . $m[1];
|
|
} elseif (preg_match('/^(BOX|GAR|GARAGE)\s*(\d+)?$/i', preg_replace('/\s+/', ' ', $internoStr), $m)) {
|
|
$ptype = 'box';
|
|
$pcode = 'BOX' . ($m[2] ?? '');
|
|
} elseif (preg_match('/^(PA|POSTO\s*AUTO)\s*(\d+)?$/i', preg_replace('/\s+/', ' ', $internoStr), $m)) {
|
|
$ptype = 'posto_auto';
|
|
$pcode = 'PA' . ($m[2] ?? '');
|
|
} elseif (preg_match('/^(SOF|SOFFITTA|SOLAIO)\s*(\d+)?$/i', preg_replace('/\s+/', ' ', $internoStr), $m)) {
|
|
$ptype = 'soffitta';
|
|
$pcode = 'SOF' . ($m[2] ?? '');
|
|
} elseif (preg_match('/^(LT|LOCALE\s*TECNICO)\s*(\d+)?$/i', preg_replace('/\s+/', ' ', $internoStr), $m)) {
|
|
$ptype = 'locale_tecnico';
|
|
$pcode = 'LT' . ($m[2] ?? '');
|
|
}
|
|
}
|
|
$scalaVal = trim((string)($r->scala ?? 'A')) ?: 'A';
|
|
// Piano: normalizza (R=0, T=0, 1=1, -1 o S= -1)
|
|
$pianoVal = 0;
|
|
if (isset($r->piano)) {
|
|
$pv = is_string($r->piano) ? strtoupper(trim($r->piano)) : (string)$r->piano;
|
|
if ($pv === 'T' || $pv === 'PT' || $pv === 'R' || $pv === 'PR' || $pv === '') {
|
|
$pianoVal = 0;
|
|
} elseif ($pv === 'S' || $pv === 'PS' || $pv === '-1') {
|
|
$pianoVal = -1;
|
|
} elseif (is_numeric($pv)) {
|
|
$pianoVal = (int)$pv;
|
|
}
|
|
}
|
|
if ($hasDetailedSchema) {
|
|
// Codice unita coerente: STAB-SCALA-INT (per cantine: STAB-SCALA-CAN<n>)
|
|
$intPart = $ptype && $pcode ? $pcode : (($interno ?? null) ?: '0');
|
|
$codiceBaseStab = ($r->cod_stabile ?? $this->option('stabile')) ?: 'S0';
|
|
// Se stabili-per-scala, includi comunque la scala nel codice per leggibilità
|
|
$codice = $codiceBaseStab . '-' . ($scalaVal ?: 'A') . '-' . $intPart;
|
|
// Filtri post-normalizzazione: pertinenze e piano min/max
|
|
if ($this->option('only-pertinenze')) {
|
|
$opt = strtolower((string)$this->option('only-pertinenze'));
|
|
$isPert = (bool)$ptype;
|
|
$ok = $opt === 'any' ? $isPert : ($isPert && $ptype === $opt);
|
|
if (!$ok) {
|
|
continue;
|
|
}
|
|
}
|
|
$pMin = $this->option('piano-min') !== null ? (int)$this->option('piano-min') : null;
|
|
$pMax = $this->option('piano-max') !== null ? (int)$this->option('piano-max') : null;
|
|
if ($pMin !== null && $pianoVal < $pMin) {
|
|
continue;
|
|
}
|
|
if ($pMax !== null && $pianoVal > $pMax) {
|
|
continue;
|
|
}
|
|
$exists = null;
|
|
if ($legacyCondId !== null && $legacyCondId !== '' && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
$exists = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->where('legacy_cond_id', $legacyCondId)
|
|
->first();
|
|
}
|
|
if (!$exists) {
|
|
$exists = DB::table('unita_immobiliari')->where('codice_unita', $codice)->first();
|
|
if ($exists && $legacyCondId !== null && $legacyCondId !== '' && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
if (!empty($exists->legacy_cond_id) && (string) $exists->legacy_cond_id !== (string) $legacyCondId) {
|
|
$codice = $codice . '-C' . $legacyCondId;
|
|
$exists = DB::table('unita_immobiliari')->where('codice_unita', $codice)->first();
|
|
}
|
|
}
|
|
}
|
|
if ($exists) {
|
|
// Aggiorna palazzina/scala se richiesto
|
|
$patch = [];
|
|
if ($legacyCondId !== null && $legacyCondId !== '' && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
if (empty($exists->legacy_cond_id) || (string) $exists->legacy_cond_id !== (string) $legacyCondId) {
|
|
$patch['legacy_cond_id'] = $legacyCondId;
|
|
}
|
|
}
|
|
if ($this->splitMode === 'palazzine') {
|
|
if (Schema::hasColumn('unita_immobiliari', 'palazzina')) {
|
|
if (empty($exists->palazzina) || $exists->palazzina !== $scalaVal) {
|
|
$patch['palazzina'] = $scalaVal;
|
|
}
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'scala') && (empty($exists->scala) || $exists->scala !== $scalaVal)) {
|
|
$patch['scala'] = $scalaVal;
|
|
}
|
|
}
|
|
if (!empty($patch)) {
|
|
$patch['updated_at'] = now();
|
|
if (!$this->isDryRun) {
|
|
DB::table('unita_immobiliari')->where('id', $exists->id)->update($patch);
|
|
}
|
|
$updated++;
|
|
}
|
|
if (!$this->isDryRun && $exists->id) {
|
|
$this->upsertUnitaNominativiFromLegacy((int) $exists->id, (int) $rowStabileId, $legacyCondId, $r);
|
|
}
|
|
continue;
|
|
}
|
|
$data = [
|
|
'stabile_id' => $rowStabileId,
|
|
'palazzina_id' => null,
|
|
'codice_unita' => $codice,
|
|
'denominazione' => trim(($r->cognome ?? '') . ' ' . ($r->nome ?? '')) ?: null,
|
|
'descrizione' => null,
|
|
'palazzina' => $this->splitMode === 'palazzine' ? $scalaVal : null,
|
|
'scala' => $scalaVal,
|
|
'piano' => $pianoVal,
|
|
'interno' => (string)($ptype && $pcode ? $pcode : $interno),
|
|
'subalterno' => (string)$sub,
|
|
'categoria_catastale' => null,
|
|
'classe' => null,
|
|
'consistenza' => null,
|
|
'rendita_catastale' => null,
|
|
'millesimi_generali' => 0,
|
|
'legacy_cond_id' => $legacyCondId,
|
|
'tipo_unita' => $ptype ?: 'abitazione',
|
|
'stato_conservazione' => 'buono',
|
|
'stato_occupazione' => 'occupata_proprietario',
|
|
'attiva' => true,
|
|
'unita_demo' => false,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('unita_immobiliari', $data);
|
|
$unitaInserted = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->when(Schema::hasColumn('unita_immobiliari', 'legacy_cond_id') && $legacyCondId !== null && $legacyCondId !== '', function ($q) use ($legacyCondId) {
|
|
$q->where('legacy_cond_id', $legacyCondId);
|
|
})
|
|
->when($legacyCondId === null || $legacyCondId === '', function ($q) use ($codice) {
|
|
$q->where('codice_unita', $codice);
|
|
})
|
|
->orderByDesc('id')
|
|
->first();
|
|
if (!$this->isDryRun && $unitaInserted?->id) {
|
|
$this->upsertUnitaNominativiFromLegacy((int) $unitaInserted->id, (int) $rowStabileId, $legacyCondId, $r);
|
|
}
|
|
// Se è pertinenza, memorizza relazione da collegare in un secondo pass
|
|
if ($ptype) {
|
|
$pertinenzeToLink[] = [
|
|
'stabile_id' => $rowStabileId,
|
|
'scala' => $r->scala ?? 'A',
|
|
'interno' => (string)($pcode ?: $interno),
|
|
'tipo' => $ptype,
|
|
'proprietario_cognome' => $r->cognome ?? null,
|
|
'proprietario_nome' => $r->nome ?? null,
|
|
];
|
|
}
|
|
} else { // schema semplice (anagrafiche base)
|
|
$criteria = ['stabile_id' => $this->stabileId];
|
|
if ($interno !== null) $criteria['interno'] = (string)$interno;
|
|
$exists = DB::table('unita_immobiliari')->where($criteria)->first();
|
|
if ($exists) {
|
|
// Aggiorna palazzina/scala se in split palazzine
|
|
if ($this->splitMode === 'palazzine') {
|
|
$patch = [];
|
|
if (Schema::hasColumn('unita_immobiliari', 'palazzina') && (empty($exists->palazzina) || $exists->palazzina !== $scalaVal)) {
|
|
$patch['palazzina'] = $scalaVal;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'scala') && (empty($exists->scala) || $exists->scala !== $scalaVal)) {
|
|
$patch['scala'] = $scalaVal;
|
|
}
|
|
if (!empty($patch)) {
|
|
$patch['updated_at'] = now();
|
|
if (!$this->isDryRun) DB::table('unita_immobiliari')->where('id', $exists->id)->update($patch);
|
|
$updated++;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
$data = [
|
|
'stabile_id' => $rowStabileId,
|
|
'interno' => (string)$interno,
|
|
'scala' => $scalaVal,
|
|
'piano' => $pianoVal,
|
|
'subalterno' => (string)$sub,
|
|
'categoria_catastale' => null,
|
|
'superficie' => null,
|
|
'vani' => null,
|
|
'indirizzo' => $r->indirizzo_corrispondenza ?? null,
|
|
'note' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('unita_immobiliari', $data);
|
|
}
|
|
$count++;
|
|
$processed++;
|
|
if ($limit && $processed >= $limit) {
|
|
break 2;
|
|
}
|
|
}
|
|
$offset += $batch;
|
|
}
|
|
|
|
// Cleanup: rimuovi unità non presenti in condomin (evita "alieni")
|
|
if (!$this->isDryRun && $this->option('stabile')) {
|
|
$condRows = DB::connection('gescon_import')->table('condomin')
|
|
->where('cod_stabile', $this->option('stabile'))
|
|
->select('scala', 'interno', 'cod_cond')
|
|
->get();
|
|
$condSet = [];
|
|
foreach ($condRows as $c) {
|
|
$internoStr = is_string($c->interno ?? null) ? trim((string) $c->interno) : (string) ($c->interno ?? '');
|
|
if ($internoStr !== '' && preg_match('/APP\.?\s*COND\.?/i', $internoStr)) {
|
|
continue;
|
|
}
|
|
if (isset($c->cod_cond)) {
|
|
$condSet[(string) $c->cod_cond] = true;
|
|
}
|
|
$key = strtoupper(trim((string) $c->scala)) . '|' . strtoupper(trim((string) $c->interno));
|
|
$condSet[$key] = true;
|
|
}
|
|
|
|
if (!empty($condSet)) {
|
|
$unitRows = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $this->stabileId)
|
|
->whereNull('deleted_at')
|
|
->select('id', 'scala', 'interno', 'legacy_cond_id')
|
|
->get();
|
|
$alienIds = [];
|
|
foreach ($unitRows as $u) {
|
|
if (Schema::hasColumn('unita_immobiliari', 'legacy_cond_id') && !empty($u->legacy_cond_id)) {
|
|
if (array_key_exists((string) $u->legacy_cond_id, $condSet)) {
|
|
continue;
|
|
}
|
|
} else {
|
|
$key = strtoupper(trim((string) $u->scala)) . '|' . strtoupper(trim((string) $u->interno));
|
|
if (array_key_exists($key, $condSet)) {
|
|
continue;
|
|
}
|
|
}
|
|
$alienIds[] = (int) $u->id;
|
|
}
|
|
|
|
if (!empty($alienIds)) {
|
|
$now = now();
|
|
DB::table('unita_immobiliari')->whereIn('id', $alienIds)->update([
|
|
'attiva' => 0,
|
|
'deleted_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
if (Schema::hasTable('dettaglio_millesimi')) {
|
|
DB::table('dettaglio_millesimi')->whereIn('unita_immobiliare_id', $alienIds)->delete();
|
|
}
|
|
if (Schema::hasTable('dettaglio_importi_tabella')) {
|
|
DB::table('dettaglio_importi_tabella')->whereIn('unita_immobiliare_id', $alienIds)->delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Collega pertinenze alle unità principali (cumulo) se tabella disponibile
|
|
if (!empty($pertinenzeToLink) && Schema::hasTable('unita_pertinenze')) {
|
|
foreach ($pertinenzeToLink as $p) {
|
|
// Heuristic: cerca unità principale con stesso stabile e scala; usa primo match non pertinenza
|
|
$main = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $p['stabile_id'])
|
|
->where('scala', $p['scala'])
|
|
->whereNotIn('tipo_unita', ['cantina', 'box', 'garage', 'posto_auto', 'soffitta', 'locale_tecnico'])
|
|
->orderBy('id')
|
|
->first();
|
|
$cant = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $p['stabile_id'])
|
|
->where('scala', $p['scala'])
|
|
->where('interno', $p['interno'])
|
|
->first();
|
|
if ($main && $cant) {
|
|
$this->dynamicInsert('unita_pertinenze', [
|
|
'stabile_id' => $p['stabile_id'],
|
|
'unita_principale_id' => $main->id,
|
|
'unita_accessoria_id' => $cant->id,
|
|
'tipo' => $p['tipo'] ?? 'pertinenza',
|
|
'cumulo' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
if ($updated > 0) {
|
|
$this->line(" → unita aggiornate: {$updated}");
|
|
}
|
|
return $count + $updated;
|
|
}
|
|
private function stepSoggetti(?int $limit): int
|
|
{
|
|
// Deriva da proprietari/inquilini nei campi condomin
|
|
if (!Schema::connection('gescon_import')->hasTable('condomin')) return 0;
|
|
$stabileId = $this->resolveStabileId();
|
|
$count = 0;
|
|
$updated = 0;
|
|
// Se nel mapping sono presenti codici cassa target (es. CCB, CDL), assicurali nelle casse
|
|
try {
|
|
if (!empty($this->banksMapping) && Schema::hasTable('casse') && $stabileId) {
|
|
foreach ($this->banksMapping as $bm) {
|
|
if (!is_array($bm)) {
|
|
continue;
|
|
}
|
|
$code = $this->resolveMappingCodiceCassa($bm);
|
|
if ($code) {
|
|
$exists = DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', $code)->first();
|
|
if (!$exists) {
|
|
DB::table('casse')->insert([
|
|
'tenant_id' => null,
|
|
'stabile_id' => $stabileId,
|
|
'cod_cassa' => $code,
|
|
'descrizione' => 'Cassa ' . $code,
|
|
'tipo' => ($code === 'CON') ? 'cassa_contanti' : (($code === 'CDL') ? 'altro' : 'conto_bancario_locale'),
|
|
'iban' => null,
|
|
'intestazione_conto' => null,
|
|
'attiva' => true,
|
|
'meta' => json_encode(['from_mapping' => true]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
}
|
|
$processed = 0;
|
|
$batch = $limit ? min(max(1, $limit), 1000) : 1000;
|
|
$offset = 0;
|
|
while (true) {
|
|
$q = DB::connection('gescon_import')->table('condomin');
|
|
if ($this->option('stabile')) {
|
|
$q->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($this->option('scala')) {
|
|
$q->where('scala', $this->option('scala'));
|
|
}
|
|
$q->limit($batch)->offset($offset);
|
|
$rows = $q->get();
|
|
if ($rows->isEmpty()) break;
|
|
foreach ($rows as $r) {
|
|
$cf = trim((string)($r->codice_fiscale ?? '')) ?: null;
|
|
if ($cf) $cf = strtoupper($cf);
|
|
$email = trim((string)($r->email ?? '')) ?: null;
|
|
$nome = $r->nome ?? null;
|
|
$cognome = $r->cognome ?? null;
|
|
// Unique/dedup: preferisci CF -> email -> nome+cognome normalizzati (no uso old_id per evitare duplicati per UI)
|
|
$useDbTrigger = (bool)env('GESCON_USE_DB_TRIGGER_CODES', false);
|
|
$exists = $this->findExistingSoggetto($cf, $email, $nome, $cognome);
|
|
if ($exists) {
|
|
// Upsert non distruttivo: completa campi mancanti
|
|
$patch = [];
|
|
foreach (
|
|
[
|
|
'nome' => $nome,
|
|
'cognome' => $cognome,
|
|
'codice_fiscale' => $cf,
|
|
'email' => $email,
|
|
'telefono' => $r->telefono ?? null,
|
|
'indirizzo' => $r->indirizzo_corrispondenza ?? null,
|
|
] as $k => $v
|
|
) {
|
|
if (!empty($v) && (empty($exists->$k) || $exists->$k === 'ND')) {
|
|
$patch[$k] = $v;
|
|
}
|
|
}
|
|
if (!empty($patch)) {
|
|
$patch['updated_at'] = now();
|
|
DB::table('soggetti')->where('id', $exists->id)->update($patch);
|
|
$updated++;
|
|
}
|
|
continue;
|
|
}
|
|
// Tipo soggetto derivato da flag proprietario/inquilino
|
|
$tipo = null;
|
|
if (property_exists($r, 'inquilino') && $r->inquilino) {
|
|
$tipo = 'inquilino';
|
|
} elseif (property_exists($r, 'proprietario') && $r->proprietario) {
|
|
$tipo = 'proprietario';
|
|
}
|
|
$data = [
|
|
'old_id' => $this->safeOldId($r->cod_cond ?? null),
|
|
'nome' => $nome,
|
|
'cognome' => $cognome,
|
|
'ragione_sociale' => null,
|
|
'codice_fiscale' => $cf,
|
|
'partita_iva' => null,
|
|
'email' => $email,
|
|
'telefono' => $r->telefono ?? null,
|
|
'indirizzo' => $r->indirizzo_corrispondenza ?? null,
|
|
'cap' => null,
|
|
'citta' => null,
|
|
'provincia' => null,
|
|
'tipo' => $tipo ?? 'proprietario',
|
|
'attivo' => Schema::hasColumn('soggetti', 'attivo') ? true : null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
if (!$useDbTrigger) {
|
|
$baseKey = $this->normalizeNameKey($nome, $cognome) ?: ($email ?: 'ND');
|
|
$code = $this->generateStableCondKey('S', $r->cod_stabile ?? 'S0', $baseKey);
|
|
// Ensure uniqueness by retrying with cond code salt
|
|
$tries = 0;
|
|
while (Schema::hasTable('soggetti') && DB::table('soggetti')->where('codice_univoco', $code)->exists() && $tries < 3) {
|
|
$salt = ($r->cod_cond ?? '') . '-' . ($tries + 1);
|
|
$code = $this->generateStableCondKey('S', $r->cod_stabile ?? 'S0', $baseKey . '|' . $salt);
|
|
$tries++;
|
|
}
|
|
$data['codice_univoco'] = $code;
|
|
}
|
|
// Final guard: skip if same unique code already exists with same name/email
|
|
if (Schema::hasTable('soggetti') && isset($data['codice_univoco'])) {
|
|
$ex = DB::table('soggetti')->where('codice_univoco', $data['codice_univoco'])->first();
|
|
if ($ex) {
|
|
continue;
|
|
}
|
|
}
|
|
$this->dynamicInsert('soggetti', $data);
|
|
$count++;
|
|
$processed++;
|
|
if ($limit && $processed >= $limit) {
|
|
break 2;
|
|
}
|
|
}
|
|
$offset += $batch;
|
|
}
|
|
// Dedup dopo inserimento: unisci per CF e per email
|
|
$this->dedupSoggettiBy('codice_fiscale');
|
|
$this->dedupSoggettiBy('email');
|
|
return $count + $updated;
|
|
}
|
|
private function stepDiritti(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('condomin')) return 0;
|
|
if (!Schema::hasTable('proprieta') || !Schema::hasTable('unita_immobiliari') || !Schema::hasTable('soggetti')) return 0;
|
|
$created = 0;
|
|
$updated = 0;
|
|
$skipped = 0;
|
|
$processed = 0;
|
|
$batch = $limit ? min(max(1, $limit), 1000) : 1000;
|
|
$offset = 0;
|
|
while (true) {
|
|
$q = DB::connection('gescon_import')->table('condomin');
|
|
if ($this->option('stabile')) {
|
|
$q->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($this->option('scala')) {
|
|
$q->where('scala', $this->option('scala'));
|
|
}
|
|
$q->limit($batch)->offset($offset);
|
|
$rows = $q->get();
|
|
if ($rows->isEmpty()) break;
|
|
foreach ($rows as $r) {
|
|
// Resolve unità
|
|
$unita = $this->findUnitaByStagingRow($r);
|
|
if (!$unita) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
// Resolve soggetto (già creato in stepSoggetti)
|
|
$sog = $this->findExistingSoggetto(trim($r->codice_fiscale ?? '') ?: null, trim($r->email ?? '') ?: null, $r->nome ?? null, $r->cognome ?? null);
|
|
if (!$sog) {
|
|
// crea minimale se proprio assente
|
|
$payload = [
|
|
'nome' => $r->nome ?? null,
|
|
'cognome' => $r->cognome ?? null,
|
|
'codice_fiscale' => (trim($r->codice_fiscale ?? '') ?: null),
|
|
'email' => (trim($r->email ?? '') ?: null),
|
|
'tipo' => ($r->inquilino ?? 0) ? 'inquilino' : 'proprietario',
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
if (Schema::hasColumn('soggetti', 'codice_univoco')) {
|
|
$baseKey = $this->normalizeNameKey($payload['nome'] ?? '', $payload['cognome'] ?? '') ?: (($payload['email'] ?? null) ?: 'ND');
|
|
$code = $this->generateStableCondKey('S', $r->cod_stabile ?? 'S0', $baseKey);
|
|
$tries = 0;
|
|
while (DB::table('soggetti')->where('codice_univoco', $code)->exists() && $tries < 5) {
|
|
$salt = ($r->cod_cond ?? '') . '-' . ($tries + 1);
|
|
$code = $this->generateStableCondKey('S', $r->cod_stabile ?? 'S0', $baseKey . '|' . $salt);
|
|
$tries++;
|
|
}
|
|
$payload['codice_univoco'] = $code;
|
|
}
|
|
if (isset($payload['codice_univoco'])) {
|
|
$existingByCode = DB::table('soggetti')->where('codice_univoco', $payload['codice_univoco'])->first();
|
|
if ($existingByCode) {
|
|
$sog = $existingByCode;
|
|
}
|
|
}
|
|
if (!$sog) {
|
|
$sid = DB::table('soggetti')->insertGetId($payload);
|
|
$sog = DB::table('soggetti')->where('id', $sid)->first();
|
|
}
|
|
}
|
|
$tipo = ($r->proprietario ?? 0) ? 'proprieta' : (($r->inquilino ?? 0) ? 'locazione' : 'proprieta');
|
|
$percPossesso = isset($r->perc_diritto_reale) && is_numeric($r->perc_diritto_reale)
|
|
? (float) $r->perc_diritto_reale
|
|
: null;
|
|
$percDetrazione = isset($r->perc_detrazione) && is_numeric($r->perc_detrazione)
|
|
? (float) $r->perc_detrazione
|
|
: null;
|
|
$exists = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->where('soggetto_id', $sog->id)
|
|
->first();
|
|
if ($exists) {
|
|
$patch = [];
|
|
if (($exists->tipo_diritto ?? '') !== $tipo) {
|
|
$patch['tipo_diritto'] = $tipo;
|
|
}
|
|
if ($tipo === 'proprieta' && $percPossesso !== null && (float) ($exists->percentuale_possesso ?? 0) !== $percPossesso) {
|
|
$patch['percentuale_possesso'] = $percPossesso;
|
|
}
|
|
if ($tipo === 'proprieta' && Schema::hasColumn('proprieta', 'percentuale_detrazione') && $percDetrazione !== null && (float) ($exists->percentuale_detrazione ?? 0) !== $percDetrazione) {
|
|
$patch['percentuale_detrazione'] = $percDetrazione;
|
|
}
|
|
if ($patch !== []) {
|
|
$patch['updated_at'] = now();
|
|
DB::table('proprieta')->where('id', $exists->id)->update($patch);
|
|
$updated++;
|
|
} else {
|
|
$skipped++;
|
|
}
|
|
} else {
|
|
DB::table('proprieta')->insert([
|
|
'unita_immobiliare_id' => $unita->id,
|
|
'soggetto_id' => $sog->id,
|
|
'tipo_diritto' => $tipo,
|
|
'percentuale_possesso' => $tipo === 'proprieta' ? $percPossesso : null,
|
|
'percentuale_detrazione' => ($tipo === 'proprieta' && Schema::hasColumn('proprieta', 'percentuale_detrazione')) ? $percDetrazione : null,
|
|
'data_inizio' => null,
|
|
'data_fine' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
]);
|
|
$created++;
|
|
}
|
|
$processed++;
|
|
if ($limit && $processed >= $limit) {
|
|
break 2;
|
|
}
|
|
}
|
|
$offset += $batch;
|
|
}
|
|
|
|
// Import comproprietari (se presenti)
|
|
if (Schema::connection('gescon_import')->hasTable('comproprietari')) {
|
|
$q = DB::connection('gescon_import')->table('comproprietari');
|
|
if ($this->option('stabile')) {
|
|
$q->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
|
|
$comprRows = $q->limit(50000)->get();
|
|
foreach ($comprRows as $cr) {
|
|
$legacyCond = trim((string) ($cr->id_cond ?? ''));
|
|
if ($legacyCond === '') {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$unita = null;
|
|
if (Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $this->stabileId)
|
|
->whereNull('deleted_at')
|
|
->where('legacy_cond_id', $legacyCond)
|
|
->first();
|
|
}
|
|
|
|
if (! $unita) {
|
|
$fake = (object) [
|
|
'scala' => $cr->ex_scala ?? null,
|
|
'interno' => $cr->ex_int ?? null,
|
|
'cod_cond' => $legacyCond,
|
|
];
|
|
$unita = $this->findUnitaByStagingRow($fake);
|
|
}
|
|
|
|
if (! $unita) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$nom = trim((string) ($cr->nom_cond ?? ''));
|
|
$nome = null;
|
|
$cognome = null;
|
|
if ($nom !== '') {
|
|
if (str_contains($nom, ',')) {
|
|
[$cognome, $nome] = array_map('trim', explode(',', $nom, 2));
|
|
} else {
|
|
$parts = preg_split('/\s+/', $nom);
|
|
$cognome = $parts[0] ?? null;
|
|
$nome = isset($parts[1]) ? implode(' ', array_slice($parts, 1)) : null;
|
|
}
|
|
}
|
|
|
|
$cf = trim((string) ($cr->cond_cod_fisc ?? '')) ?: null;
|
|
$email = trim((string) ($cr->e_mail_condomino ?? '')) ?: null;
|
|
|
|
$sog = $this->findExistingSoggetto($cf, $email, $nome, $cognome);
|
|
if (! $sog) {
|
|
$payload = [
|
|
'nome' => $nome,
|
|
'cognome' => $cognome,
|
|
'codice_fiscale' => $cf,
|
|
'email' => $email,
|
|
'tipo' => 'proprietario',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (Schema::hasColumn('soggetti', 'codice_univoco')) {
|
|
$baseKey = $this->normalizeNameKey($payload['nome'] ?? '', $payload['cognome'] ?? '') ?: (($payload['email'] ?? null) ?: 'ND');
|
|
$code = $this->generateStableCondKey('S', $cr->cod_stabile ?? 'S0', $baseKey);
|
|
$tries = 0;
|
|
while (DB::table('soggetti')->where('codice_univoco', $code)->exists() && $tries < 5) {
|
|
$salt = ($legacyCond ?? '') . '-' . ($tries + 1);
|
|
$code = $this->generateStableCondKey('S', $cr->cod_stabile ?? 'S0', $baseKey . '|' . $salt);
|
|
$tries++;
|
|
}
|
|
$payload['codice_univoco'] = $code;
|
|
}
|
|
|
|
if (isset($payload['codice_univoco'])) {
|
|
$existingByCode = DB::table('soggetti')->where('codice_univoco', $payload['codice_univoco'])->first();
|
|
if ($existingByCode) {
|
|
$sog = $existingByCode;
|
|
}
|
|
}
|
|
|
|
if (! $sog) {
|
|
$sid = DB::table('soggetti')->insertGetId($payload);
|
|
$sog = DB::table('soggetti')->where('id', $sid)->first();
|
|
}
|
|
}
|
|
|
|
if (! $sog) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$percentuale = isset($cr->perc_diritto_reale) && is_numeric($cr->perc_diritto_reale)
|
|
? (float) $cr->perc_diritto_reale
|
|
: null;
|
|
|
|
$exists = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->where('soggetto_id', $sog->id)
|
|
->first();
|
|
|
|
if ($exists) {
|
|
$update = [];
|
|
if (($exists->tipo_diritto ?? '') !== 'proprieta') {
|
|
$update['tipo_diritto'] = 'proprieta';
|
|
}
|
|
if ($percentuale !== null && (float) ($exists->percentuale_possesso ?? 0) !== $percentuale) {
|
|
$update['percentuale_possesso'] = $percentuale;
|
|
}
|
|
if ($update !== []) {
|
|
$update['updated_at'] = now();
|
|
DB::table('proprieta')->where('id', $exists->id)->update($update);
|
|
$updated++;
|
|
} else {
|
|
$skipped++;
|
|
}
|
|
} else {
|
|
DB::table('proprieta')->insert([
|
|
'unita_immobiliare_id' => $unita->id,
|
|
'soggetto_id' => $sog->id,
|
|
'tipo_diritto' => 'proprieta',
|
|
'percentuale_possesso' => $percentuale,
|
|
'data_inizio' => null,
|
|
'data_fine' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
$created++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->line(" → diritti inseriti: $created, aggiornati: $updated, ignorati: $skipped");
|
|
return $created + $updated;
|
|
}
|
|
private function stepMillesimi(?int $limit): int
|
|
{
|
|
$headersCreated = 0;
|
|
$detailsInserted = 0;
|
|
$detailsUpdated = 0;
|
|
$nordAvailable = Schema::connection('gescon_import')->hasTable('dett_tab') && Schema::connection('gescon_import')->hasColumn('dett_tab', 'nord');
|
|
|
|
// 1) Prepara elenco intestazioni tabella da importare (preferisci tabelle_millesimali, altrimenti deriva da dett_tab)
|
|
$headerRows = collect();
|
|
if (Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) {
|
|
$q = DB::connection('gescon_import')->table('tabelle_millesimali');
|
|
if ($this->option('stabile')) {
|
|
$q->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($limit) $q->limit($limit);
|
|
$headerRows = $q->get();
|
|
}
|
|
if ($headerRows->isEmpty() && Schema::connection('gescon_import')->hasTable('dett_tab')) {
|
|
$q = DB::connection('gescon_import')->table('dett_tab as d');
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$q = $q->join('condomin as c', 'c.cod_cond', '=', 'd.id_cond');
|
|
if ($this->option('stabile')) {
|
|
$q->where('c.cod_stabile', $this->option('stabile'));
|
|
}
|
|
}
|
|
$headerRows = $q->select('d.cod_tab as cod_tabella', 'd.legacy_year')->distinct()->get();
|
|
}
|
|
if ($headerRows->isEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
// 2) Calcola importi preventivo/consuntivo per tabella/anno (se disponibili)
|
|
$importiMap = [];
|
|
$nordMap = [];
|
|
$totMmMap = [];
|
|
if (Schema::connection('gescon_import')->hasTable('dett_tab')) {
|
|
$q = DB::connection('gescon_import')->table('dett_tab as d')
|
|
->select(
|
|
'd.cod_tab',
|
|
'd.legacy_year',
|
|
DB::raw('SUM(d.prev) as tot_prev'),
|
|
DB::raw('SUM(d.prev_euro) as tot_prev_euro'),
|
|
DB::raw('SUM(d.cons) as tot_cons'),
|
|
DB::raw('SUM(d.cons_euro) as tot_cons_euro'),
|
|
DB::raw('SUM(d.ex_cons_euro) as tot_ex_cons_euro')
|
|
);
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$q = $q->join('condomin as c', 'c.cod_cond', '=', 'd.id_cond');
|
|
if ($this->option('stabile')) {
|
|
$q->where('c.cod_stabile', $this->option('stabile'));
|
|
}
|
|
}
|
|
$q->groupBy('d.cod_tab', 'd.legacy_year');
|
|
foreach ($q->get() as $row) {
|
|
$key = strtoupper(trim((string) ($row->cod_tab ?? ''))) . '|' . (string) ($row->legacy_year ?? '');
|
|
$importiMap[$key] = $row;
|
|
}
|
|
|
|
$mmSub = DB::connection('gescon_import')->table('dett_tab')
|
|
->select('cod_tab', 'legacy_year', 'id_cond', DB::raw('MAX(mm) as mm'))
|
|
->groupBy('cod_tab', 'legacy_year', 'id_cond');
|
|
$mmQuery = DB::connection('gescon_import')->query()->fromSub($mmSub, 't')
|
|
->select('cod_tab', 'legacy_year', DB::raw('SUM(mm) as tot_mm'))
|
|
->groupBy('cod_tab', 'legacy_year');
|
|
foreach ($mmQuery->get() as $row) {
|
|
$key = strtoupper(trim((string) ($row->cod_tab ?? ''))) . '|' . (string) ($row->legacy_year ?? '');
|
|
$totMmMap[$key] = is_numeric($row->tot_mm ?? null) ? (float) $row->tot_mm : null;
|
|
}
|
|
|
|
if ($nordAvailable) {
|
|
$nq = DB::connection('gescon_import')->table('dett_tab as d')
|
|
->select('d.cod_tab', 'd.legacy_year', DB::raw('MIN(d.nord) as nord'));
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$nq = $nq->join('condomin as c', 'c.cod_cond', '=', 'd.id_cond');
|
|
if ($this->option('stabile')) {
|
|
$nq->where('c.cod_stabile', $this->option('stabile'));
|
|
}
|
|
}
|
|
$nq->groupBy('d.cod_tab', 'd.legacy_year');
|
|
foreach ($nq->get() as $row) {
|
|
$key = strtoupper(trim((string) ($row->cod_tab ?? ''))) . '|' . (string) ($row->legacy_year ?? '');
|
|
$nordMap[$key] = is_numeric($row->nord ?? null) ? (int) $row->nord : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3) Crea/aggiorna intestazioni su dominio usando colonne effettive (codice_tabella/nome_tabella)
|
|
$tableCols = Schema::getColumnListing('tabelle_millesimali');
|
|
foreach ($headerRows as $h) {
|
|
$code = trim((string) ($h->cod_tabella ?? $h->cod_tab ?? ''));
|
|
if ($code === '') {
|
|
continue;
|
|
}
|
|
$code = strtoupper($code);
|
|
$legacyYear = isset($h->legacy_year) ? trim((string) $h->legacy_year) : null;
|
|
$annoGestione = Schema::hasColumn('tabelle_millesimali', 'anno_gestione')
|
|
? $this->resolveAnnoFromLegacyYear($legacyYear)
|
|
: null;
|
|
|
|
$rawCalcolo = $h->tipo_calcolo ?? $h->calcolo ?? null;
|
|
$calcolo = $this->normalizeLegacyCalcolo($rawCalcolo);
|
|
$calcoloDb = in_array($calcolo, ['millesimi', 'parti', 'fisso'], true) ? $calcolo : 'millesimi';
|
|
$tipoLegacy = $this->normalizeLegacyTipo($h->tipologia ?? $h->tipo ?? null);
|
|
$tipoTabella = $this->resolveTipoTabellaFromLegacy($tipoLegacy, $calcolo, $code);
|
|
|
|
$ordine = null;
|
|
foreach (['ordine_visualizzazione', 'nord', 'ordinamento'] as $orderKey) {
|
|
if (isset($h->{$orderKey}) && is_numeric($h->{$orderKey})) {
|
|
$ordine = (int) $h->{$orderKey};
|
|
break;
|
|
}
|
|
}
|
|
if ($ordine === null) {
|
|
$ordine = $nordMap[$code . '|' . (string) ($legacyYear ?? '')] ?? null;
|
|
}
|
|
|
|
$meta = [];
|
|
$importKey = $code . '|' . (string) ($legacyYear ?? '');
|
|
$importi = $importiMap[$importKey] ?? null;
|
|
$meta['legacy_year'] = $legacyYear;
|
|
if ($tipoLegacy) $meta['tipo'] = $tipoLegacy;
|
|
if ($rawCalcolo) $meta['calcolo'] = $rawCalcolo;
|
|
if ($importi) {
|
|
$meta['tot_prev'] = is_numeric($importi->tot_prev ?? null) ? (float) $importi->tot_prev : null;
|
|
$meta['tot_prev_euro'] = is_numeric($importi->tot_prev_euro ?? null) ? (float) $importi->tot_prev_euro : null;
|
|
$meta['tot_cons'] = is_numeric($importi->tot_cons ?? null) ? (float) $importi->tot_cons : null;
|
|
$meta['tot_cons_euro'] = is_numeric($importi->tot_cons_euro ?? null) ? (float) $importi->tot_cons_euro : null;
|
|
$meta['tot_ex_cons_euro'] = is_numeric($importi->tot_ex_cons_euro ?? null) ? (float) $importi->tot_ex_cons_euro : null;
|
|
}
|
|
|
|
$existsQuery = DB::table('tabelle_millesimali');
|
|
if (Schema::hasColumn('tabelle_millesimali', 'stabile_id')) {
|
|
$existsQuery->where('stabile_id', $this->stabileId);
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
|
|
$existsQuery->where('codice_tabella', $code);
|
|
} elseif (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) {
|
|
$existsQuery->where('nome_tabella', $code);
|
|
} elseif (Schema::hasColumn('tabelle_millesimali', 'denominazione')) {
|
|
$existsQuery->where('denominazione', 'like', "%{$code}%");
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'anno_gestione')) {
|
|
if ($annoGestione) {
|
|
$existsQuery->where('anno_gestione', (int) $annoGestione);
|
|
} else {
|
|
$existsQuery->whereNull('anno_gestione');
|
|
}
|
|
}
|
|
$exists = $existsQuery->first();
|
|
if (! $exists) {
|
|
$fallback = DB::table('tabelle_millesimali');
|
|
if (Schema::hasColumn('tabelle_millesimali', 'stabile_id')) {
|
|
$fallback->where('stabile_id', $this->stabileId);
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
|
|
$fallback->where('codice_tabella', $code);
|
|
} elseif (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) {
|
|
$fallback->where('nome_tabella', $code);
|
|
} elseif (Schema::hasColumn('tabelle_millesimali', 'denominazione')) {
|
|
$fallback->where('denominazione', 'like', "%{$code}%");
|
|
}
|
|
$exists = $fallback->first();
|
|
}
|
|
|
|
$totaleMillesimi = 1000.0;
|
|
if (isset($h->totale_millesimi) && is_numeric($h->totale_millesimi)) {
|
|
$tm = (float) $h->totale_millesimi;
|
|
if ($tm <= 999999.9999) {
|
|
$totaleMillesimi = $tm;
|
|
}
|
|
}
|
|
if ($totaleMillesimi <= 0) {
|
|
$mmKey = $code . '|' . (string) ($legacyYear ?? '');
|
|
$mmFallback = $totMmMap[$mmKey] ?? null;
|
|
if (is_numeric($mmFallback) && $mmFallback > 0 && $mmFallback <= 999999.9999) {
|
|
$totaleMillesimi = (float) $mmFallback;
|
|
}
|
|
}
|
|
|
|
$payload = [
|
|
'stabile_id' => $this->stabileId,
|
|
'codice_tabella' => $code,
|
|
'nome_tabella' => $code,
|
|
'denominazione' => trim((string) ($h->denominazione ?? $h->descrizione ?? '')) ?: ('Tab ' . $code),
|
|
'descrizione' => trim((string) ($h->descrizione ?? $h->descr ?? '')) ?: null,
|
|
'tipo_tabella' => $tipoTabella ?: 'custom',
|
|
'tipo_calcolo' => $calcoloDb,
|
|
'totale_millesimi' => $totaleMillesimi,
|
|
'attiva' => true,
|
|
'legacy_codice' => $code,
|
|
'meta_legacy' => $meta,
|
|
];
|
|
if ($ordine !== null) {
|
|
$payload['ordine_visualizzazione'] = $ordine;
|
|
$payload['nord'] = $ordine;
|
|
$payload['ordinamento'] = $ordine;
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'anno_gestione') && $annoGestione) {
|
|
$payload['anno_gestione'] = (int) $annoGestione;
|
|
}
|
|
|
|
$filtered = array_intersect_key($payload, array_flip($tableCols));
|
|
if (Schema::hasColumn('tabelle_millesimali', 'meta_legacy')) {
|
|
$filtered['meta_legacy'] = json_encode($meta);
|
|
}
|
|
if ($exists) {
|
|
if (Schema::hasColumn('tabelle_millesimali', 'meta_legacy')) {
|
|
$currentMeta = is_array($exists->meta_legacy)
|
|
? $exists->meta_legacy
|
|
: (is_string($exists->meta_legacy) ? json_decode($exists->meta_legacy, true) : []);
|
|
$currentMeta = is_array($currentMeta) ? $currentMeta : [];
|
|
$filtered['meta_legacy'] = json_encode(array_merge($currentMeta, $meta));
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'updated_at')) {
|
|
$filtered['updated_at'] = now();
|
|
}
|
|
if (!empty($filtered)) {
|
|
DB::table('tabelle_millesimali')->where('id', $exists->id)->update($filtered);
|
|
}
|
|
} else {
|
|
if (Schema::hasColumn('tabelle_millesimali', 'created_at')) {
|
|
$filtered['created_at'] = now();
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'updated_at')) {
|
|
$filtered['updated_at'] = now();
|
|
}
|
|
$this->dynamicInsert('tabelle_millesimali', $filtered);
|
|
$headersCreated++;
|
|
}
|
|
}
|
|
|
|
// 3) Inserisci le righe (dettagli) se presenti in staging
|
|
if (!Schema::connection('gescon_import')->hasTable('dett_tab')) return $headersCreated;
|
|
|
|
$detailsTable = Schema::hasTable('dettaglio_millesimi')
|
|
? 'dettaglio_millesimi'
|
|
: (Schema::hasTable('tabelle_millesimali_righe') ? 'tabelle_millesimali_righe' : null);
|
|
if (!$detailsTable) return $headersCreated;
|
|
|
|
$detailsCols = Schema::getColumnListing($detailsTable);
|
|
$hasRuoloLegacy = ($detailsTable === 'dettaglio_millesimi') && in_array('ruolo_legacy', $detailsCols, true);
|
|
|
|
$src = DB::connection('gescon_import')->table('dett_tab as d');
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$src = $src->join('condomin as c', 'c.cod_cond', '=', 'd.id_cond');
|
|
if ($this->option('stabile')) {
|
|
$src->where('c.cod_stabile', $this->option('stabile'));
|
|
}
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
|
|
$canImporti = Schema::hasTable('dettaglio_importi_tabella');
|
|
$importiUpdated = 0;
|
|
$importiInserted = 0;
|
|
$importiSkipped = 0;
|
|
|
|
foreach ($src->get() as $r) {
|
|
// Trova la tabella dominio per codice
|
|
$scalaRow = isset($r->scala) ? (string)$r->scala : null;
|
|
$targetStabileId = $this->resolveStabileIdForScala($scalaRow);
|
|
$legacyYear = isset($r->legacy_year) ? trim((string) $r->legacy_year) : null;
|
|
$annoGestione = Schema::hasColumn('tabelle_millesimali', 'anno_gestione')
|
|
? $this->resolveAnnoFromLegacyYear($legacyYear)
|
|
: null;
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
|
|
$tab = DB::table('tabelle_millesimali')
|
|
->where('codice_tabella', $r->cod_tab)
|
|
->where('stabile_id', $targetStabileId)
|
|
->when(Schema::hasColumn('tabelle_millesimali', 'anno_gestione'), function ($q) use ($annoGestione) {
|
|
if ($annoGestione) {
|
|
$q->where('anno_gestione', (int) $annoGestione);
|
|
} else {
|
|
$q->whereNull('anno_gestione');
|
|
}
|
|
})
|
|
->first();
|
|
} elseif (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) {
|
|
$tab = DB::table('tabelle_millesimali')
|
|
->where('nome_tabella', $r->cod_tab)
|
|
->where('stabile_id', $targetStabileId)
|
|
->when(Schema::hasColumn('tabelle_millesimali', 'anno_gestione'), function ($q) use ($annoGestione) {
|
|
if ($annoGestione) {
|
|
$q->where('anno_gestione', (int) $annoGestione);
|
|
} else {
|
|
$q->whereNull('anno_gestione');
|
|
}
|
|
})
|
|
->first();
|
|
} else {
|
|
$tab = null;
|
|
}
|
|
if (! $tab) {
|
|
$fallback = DB::table('tabelle_millesimali')->where('stabile_id', $targetStabileId);
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
|
|
$fallback->where('codice_tabella', $r->cod_tab);
|
|
} elseif (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) {
|
|
$fallback->where('nome_tabella', $r->cod_tab);
|
|
}
|
|
$tab = $fallback->first();
|
|
}
|
|
// Crea intestazione on-demand se mancante per questo stabile
|
|
if (!$tab) {
|
|
$payloadH = [
|
|
'stabile_id' => $targetStabileId,
|
|
'codice_tabella' => $r->cod_tab,
|
|
'nome_tabella' => $r->cod_tab,
|
|
'denominazione' => 'Tab ' . $r->cod_tab,
|
|
'tipo_tabella' => 'custom',
|
|
'totale_millesimi' => 1000,
|
|
'attiva' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('tabelle_millesimali', 'anno_gestione') && $annoGestione) {
|
|
$payloadH['anno_gestione'] = (int) $annoGestione;
|
|
}
|
|
$this->dynamicInsert('tabelle_millesimali', $payloadH);
|
|
$tab = DB::table('tabelle_millesimali')
|
|
->where('stabile_id', $targetStabileId)
|
|
->where(function ($q) use ($r) {
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) $q->orWhere('codice_tabella', $r->cod_tab);
|
|
if (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) $q->orWhere('nome_tabella', $r->cod_tab);
|
|
})
|
|
->when(Schema::hasColumn('tabelle_millesimali', 'anno_gestione'), function ($q) use ($annoGestione) {
|
|
if ($annoGestione) {
|
|
$q->where('anno_gestione', (int) $annoGestione);
|
|
} else {
|
|
$q->whereNull('anno_gestione');
|
|
}
|
|
})
|
|
->first();
|
|
$headersCreated++;
|
|
}
|
|
if (!$tab) continue;
|
|
|
|
// Best-effort: risolvi unita_immobiliare per legacy_cond_id (id_cond), fallback scala + interno
|
|
$unitaId = null;
|
|
if (Schema::hasTable('unita_immobiliari')) {
|
|
if (Schema::hasColumn('unita_immobiliari', 'legacy_cond_id') && isset($r->id_cond)) {
|
|
$legacyCond = trim((string) $r->id_cond);
|
|
if ($legacyCond !== '') {
|
|
$u = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $targetStabileId)
|
|
->whereNull('deleted_at')
|
|
->where('legacy_cond_id', $legacyCond)
|
|
->first();
|
|
$unitaId = $u->id ?? null;
|
|
}
|
|
}
|
|
|
|
if (!$unitaId) {
|
|
$crit = [];
|
|
if (Schema::hasColumn('unita_immobiliari', 'stabile_id')) {
|
|
$crit['stabile_id'] = $targetStabileId;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'scala') && isset($r->scala)) {
|
|
$crit['scala'] = $r->scala;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'interno')) {
|
|
// Normalizza interno: CANxx per cantine
|
|
$int = isset($r->interno) ? (string)$r->interno : null;
|
|
if (is_string($int) && preg_match('/^\s*CAN\s*(\d+)\s*$/i', $int, $m)) {
|
|
$int = 'CAN' . $m[1];
|
|
}
|
|
if ($int !== null) {
|
|
$crit['interno'] = $int;
|
|
}
|
|
}
|
|
if (!empty($crit)) {
|
|
$u = DB::table('unita_immobiliari')
|
|
->whereNull('deleted_at')
|
|
->where($crit)
|
|
->first();
|
|
$unitaId = $u->id ?? null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$unitaId) {
|
|
continue;
|
|
}
|
|
|
|
$mm = (float)($r->mm ?? 0);
|
|
if ($mm < 0) {
|
|
$mm = 0;
|
|
}
|
|
$roleLegacy = null;
|
|
if (isset($r->cond_inquil)) {
|
|
$roleLegacy = strtoupper(trim((string) $r->cond_inquil));
|
|
if ($roleLegacy === '') {
|
|
$roleLegacy = null;
|
|
}
|
|
}
|
|
// NORD (ordine legacy per colonne/righe, utile per ordinamento rendering)
|
|
$nord = null;
|
|
if ($nordAvailable && isset($r->nord)) {
|
|
// Normalizza come intero
|
|
$n = is_numeric($r->nord) ? (int)$r->nord : null;
|
|
$nord = $n !== null ? $n : null;
|
|
}
|
|
if ($detailsTable === 'dettaglio_millesimi') {
|
|
// Upsert by (tabella, unita): ignora ruolo C/I e usa il massimo mm
|
|
$exists = DB::table('dettaglio_millesimi')
|
|
->where('tabella_millesimale_id', $tab->id)
|
|
->where('unita_immobiliare_id', $unitaId)
|
|
->first();
|
|
if ($exists) {
|
|
$currentMm = is_numeric($exists->millesimi ?? null) ? (float) $exists->millesimi : 0.0;
|
|
$upd = [
|
|
'millesimi' => max($currentMm, $mm),
|
|
'updated_at' => now(),
|
|
];
|
|
// Se la tabella ha una colonna per ordine (es. posizione, ordinale, nord), prova ad aggiornarla
|
|
if ($nord !== null) {
|
|
foreach (['nord', 'ordine', 'posizione', 'ordinale'] as $c) {
|
|
if (in_array($c, $detailsCols, true)) {
|
|
$upd[$c] = $nord;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
DB::table('dettaglio_millesimi')
|
|
->where('id', $exists->id)
|
|
->update($upd);
|
|
$detailsUpdated++;
|
|
} else {
|
|
$payload = [
|
|
'tabella_millesimale_id' => $tab->id,
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'millesimi' => $mm,
|
|
'note' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if ($nord !== null) {
|
|
foreach (['nord', 'ordine', 'posizione', 'ordinale'] as $c) {
|
|
if (in_array($c, $detailsCols, true)) {
|
|
$payload[$c] = $nord;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
$this->dynamicInsert('dettaglio_millesimi', $payload);
|
|
$detailsInserted++;
|
|
}
|
|
} else { // tabelle_millesimali_righe (fallback compatibilità vecchio schema)
|
|
$exists = DB::table('tabelle_millesimali_righe')
|
|
->where('tabella_millesimale_id', $tab->id)
|
|
->where('unita_immobiliare_id', $unitaId)
|
|
->first();
|
|
if ($exists) {
|
|
$upd = [
|
|
'millesimi' => (float)($exists->millesimi ?? 0) + $mm,
|
|
'valore_prev' => $r->prev ?? $exists->valore_prev,
|
|
'valore_cons' => $r->cons ?? $exists->valore_cons,
|
|
'updated_at' => now(),
|
|
];
|
|
if ($nord !== null) {
|
|
$cols = Schema::getColumnListing('tabelle_millesimali_righe');
|
|
foreach (['nord', 'ordine', 'posizione', 'ordinale'] as $c) {
|
|
if (in_array($c, $cols, true)) {
|
|
$upd[$c] = $nord;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
DB::table('tabelle_millesimali_righe')
|
|
->where('id', $exists->id)
|
|
->update($upd);
|
|
$detailsUpdated++;
|
|
} else {
|
|
$payload = [
|
|
'tabella_millesimale_id' => $tab->id,
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'ruolo' => $r->cond_inquil ?? null,
|
|
'millesimi' => $mm,
|
|
'valore_prev' => $r->prev ?? null,
|
|
'valore_cons' => $r->cons ?? null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if ($nord !== null) {
|
|
$cols = Schema::getColumnListing('tabelle_millesimali_righe');
|
|
foreach (['nord', 'ordine', 'posizione', 'ordinale'] as $c) {
|
|
if (in_array($c, $cols, true)) {
|
|
$payload[$c] = $nord;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
$this->dynamicInsert('tabelle_millesimali_righe', $payload);
|
|
$detailsInserted++;
|
|
}
|
|
}
|
|
|
|
if ($canImporti && $unitaId && $tab) {
|
|
$legacyYear = '0000';
|
|
if (isset($r->legacy_year) && is_string($r->legacy_year) && trim($r->legacy_year) !== '') {
|
|
$legacyYear = trim($r->legacy_year);
|
|
}
|
|
$role = null;
|
|
if (isset($r->cond_inquil) && is_string($r->cond_inquil)) {
|
|
$role = strtoupper(trim($r->cond_inquil));
|
|
if ($role === '') {
|
|
$role = null;
|
|
}
|
|
}
|
|
|
|
$payloadImporti = [
|
|
'millesimi' => $mm,
|
|
'prev_euro' => isset($r->prev_euro) && is_numeric($r->prev_euro) ? (float) $r->prev_euro : null,
|
|
'cons_euro' => isset($r->cons_euro) && is_numeric($r->cons_euro) ? (float) $r->cons_euro : null,
|
|
'ex_cons_euro' => isset($r->ex_cons_euro) && is_numeric($r->ex_cons_euro) ? (float) $r->ex_cons_euro : null,
|
|
'n_stra' => isset($r->n_stra) && is_numeric($r->n_stra) ? (int) $r->n_stra : null,
|
|
'unico' => isset($r->unico) ? (int) $r->unico : null,
|
|
'proviene_ors' => isset($r->proviene_ors) ? (string) $r->proviene_ors : null,
|
|
'proviene_n_stra' => isset($r->proviene_n_stra) && is_numeric($r->proviene_n_stra) ? (int) $r->proviene_n_stra : null,
|
|
'proviene_eserc' => isset($r->proviene_eserc) && is_numeric($r->proviene_eserc) ? (int) $r->proviene_eserc : null,
|
|
'legacy_payload' => isset($r->payload) ? (array) $r->payload : null,
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$match = [
|
|
'tabella_millesimale_id' => $tab->id,
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'ruolo_legacy' => $role,
|
|
'legacy_year' => $legacyYear,
|
|
];
|
|
|
|
$exists = DB::table('dettaglio_importi_tabella')->where($match)->first();
|
|
if ($exists) {
|
|
DB::table('dettaglio_importi_tabella')->where('id', $exists->id)->update($payloadImporti);
|
|
$importiUpdated++;
|
|
} else {
|
|
$payloadImporti = array_merge($match, $payloadImporti, [
|
|
'created_at' => now(),
|
|
]);
|
|
$this->dynamicInsert('dettaglio_importi_tabella', $payloadImporti);
|
|
$importiInserted++;
|
|
}
|
|
} else {
|
|
if ($canImporti) {
|
|
$importiSkipped++;
|
|
}
|
|
}
|
|
}
|
|
$affected = $detailsInserted + $detailsUpdated;
|
|
|
|
// 4) Completa righe mancanti: ogni unità deve avere una riga per ogni tabella
|
|
if ($detailsTable === 'dettaglio_millesimi' && Schema::hasTable('unita_immobiliari')) {
|
|
$detailCols = Schema::getColumnListing('dettaglio_millesimi');
|
|
$unitaIds = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $this->stabileId)
|
|
->whereNull('deleted_at')
|
|
->pluck('id')
|
|
->map(fn($v) => (int) $v)
|
|
->filter(fn($v) => $v > 0)
|
|
->values()
|
|
->all();
|
|
|
|
if (!empty($unitaIds)) {
|
|
$tabIds = DB::table('tabelle_millesimali')
|
|
->where('stabile_id', $this->stabileId)
|
|
->pluck('id')
|
|
->map(fn($v) => (int) $v)
|
|
->all();
|
|
|
|
foreach ($tabIds as $tabId) {
|
|
$existing = DB::table('dettaglio_millesimi')
|
|
->where('tabella_millesimale_id', $tabId)
|
|
->pluck('unita_immobiliare_id')
|
|
->map(fn($v) => (int) $v)
|
|
->all();
|
|
|
|
$missing = array_values(array_diff($unitaIds, $existing));
|
|
if ($missing === []) {
|
|
continue;
|
|
}
|
|
|
|
$now = now();
|
|
$rows = [];
|
|
foreach ($missing as $uid) {
|
|
if (!$uid) {
|
|
continue;
|
|
}
|
|
$row = [
|
|
'tabella_millesimale_id' => $tabId,
|
|
'unita_immobiliare_id' => $uid,
|
|
'millesimi' => 0,
|
|
'note' => null,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
if (in_array('partecipa', $detailCols, true)) {
|
|
$row['partecipa'] = 0;
|
|
}
|
|
$rows[] = $row;
|
|
}
|
|
|
|
foreach (array_chunk($rows, 500) as $chunk) {
|
|
DB::table('dettaglio_millesimi')->insert($chunk);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($canImporti && ($importiInserted + $importiUpdated) > 0) {
|
|
$this->line(" → importi tabella: inseriti {$importiInserted}, aggiornati {$importiUpdated}, saltati {$importiSkipped}");
|
|
}
|
|
if ($affected > 0) {
|
|
$this->line(" → intestazioni: $headersCreated, dettagli inseriti: $detailsInserted, aggiornati: $detailsUpdated");
|
|
}
|
|
return $affected ?: $headersCreated;
|
|
}
|
|
private function stepVoci(?int $limit): int
|
|
{
|
|
$stabileId = $this->resolveStabileId();
|
|
$src = DB::connection('gescon_import')->table('voc_spe');
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
$updated = 0;
|
|
foreach ($src->get() as $v) {
|
|
$cod = $v->cod ?? null;
|
|
if (!$cod) continue;
|
|
$legacyYear = isset($v->legacy_year) ? (string) $v->legacy_year : null;
|
|
$exists = null;
|
|
if (Schema::hasColumn('voci_spesa', 'codice')) {
|
|
$q = DB::table('voci_spesa')->where('codice', $cod);
|
|
if ($stabileId && Schema::hasColumn('voci_spesa', 'stabile_id')) {
|
|
$q->where('stabile_id', $stabileId);
|
|
}
|
|
$tipoLegacy = match ($v->v_ors ?? $v->V_ORS ?? '') {
|
|
'R' => 'riscaldamento',
|
|
'S' => 'straordinaria',
|
|
default => 'ordinaria'
|
|
};
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile($stabileId, $legacyYear, $tipoLegacy, null);
|
|
if ($gestioneId && Schema::hasColumn('voci_spesa', 'gestione_contabile_id')) {
|
|
$q->where('gestione_contabile_id', $gestioneId);
|
|
}
|
|
$exists = $q->orderBy('id')->first();
|
|
|
|
if (!$exists) {
|
|
$exists = DB::table('voci_spesa')
|
|
->where('codice', $cod)
|
|
->when($stabileId && Schema::hasColumn('voci_spesa', 'stabile_id'), function ($qq) use ($stabileId) {
|
|
$qq->where('stabile_id', $stabileId);
|
|
})
|
|
->orderBy('id')
|
|
->first();
|
|
}
|
|
}
|
|
$tabellaId = null;
|
|
if (Schema::hasTable('tabelle_millesimali') && !empty($v->tabella)) {
|
|
$tabellaId = DB::table('tabelle_millesimali')
|
|
->where('stabile_id', $stabileId)
|
|
->where(function ($q) use ($v) {
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
|
|
$q->orWhere('codice_tabella', $v->tabella);
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) {
|
|
$q->orWhere('nome_tabella', $v->tabella);
|
|
}
|
|
})
|
|
->value('id');
|
|
}
|
|
$tipoLegacy = match ($v->v_ors ?? $v->V_ORS ?? '') {
|
|
'R' => 'riscaldamento',
|
|
'S' => 'straordinaria',
|
|
default => 'ordinaria'
|
|
};
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile($stabileId, $legacyYear, $tipoLegacy, null);
|
|
$data = [
|
|
'stabile_id' => (Schema::hasColumn('voci_spesa', 'stabile_id') ? $stabileId : null),
|
|
'gestione_contabile_id' => Schema::hasColumn('voci_spesa', 'gestione_contabile_id') ? $gestioneId : null,
|
|
'codice' => $cod,
|
|
'legacy_codice' => Schema::hasColumn('voci_spesa', 'legacy_codice') ? $cod : null,
|
|
'descrizione' => $v->descriz ?? '',
|
|
'categoria' => (Schema::hasColumn('voci_spesa', 'categoria') ? $tipoLegacy : null),
|
|
'tipo_gestione' => (Schema::hasColumn('voci_spesa', 'tipo_gestione') ? 'ordinaria' : null),
|
|
'tipo' => $tipoLegacy,
|
|
'tabella_millesimale_default_id' => (Schema::hasColumn('voci_spesa', 'tabella_millesimale_default_id') ? $tabellaId : null),
|
|
'importo_default' => Schema::hasColumn('voci_spesa', 'importo_default')
|
|
? (isset($v->preventivo_euro) && is_numeric($v->preventivo_euro) ? (float) $v->preventivo_euro : (isset($v->importo_euro) && is_numeric($v->importo_euro) ? (float) $v->importo_euro : null))
|
|
: null,
|
|
'importo_consuntivo' => Schema::hasColumn('voci_spesa', 'importo_consuntivo')
|
|
? (isset($v->consuntivo_euro) && is_numeric($v->consuntivo_euro) ? (float) $v->consuntivo_euro : null)
|
|
: null,
|
|
'percentuale_condomino' => Schema::hasColumn('voci_spesa', 'percentuale_condomino')
|
|
? (isset($v->perc_proprietario) && is_numeric($v->perc_proprietario) ? (float) $v->perc_proprietario : 100.0)
|
|
: null,
|
|
'percentuale_inquilino' => Schema::hasColumn('voci_spesa', 'percentuale_inquilino')
|
|
? (isset($v->perc_inquilino) && is_numeric($v->perc_inquilino) ? (float) $v->perc_inquilino : 0.0)
|
|
: null,
|
|
'note' => $v->note ?? null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
if ($exists) {
|
|
$patch = [];
|
|
foreach (['tabella_millesimale_default_id', 'importo_default', 'importo_consuntivo', 'percentuale_condomino', 'percentuale_inquilino', 'legacy_codice', 'gestione_contabile_id'] as $k) {
|
|
if (array_key_exists($k, $data) && $data[$k] !== null && (empty($exists->$k) && $exists->$k !== 0)) {
|
|
$patch[$k] = $data[$k];
|
|
}
|
|
}
|
|
if (!empty($patch)) {
|
|
$patch['updated_at'] = now();
|
|
DB::table('voci_spesa')->where('id', $exists->id)->update($patch);
|
|
$updated++;
|
|
}
|
|
continue;
|
|
}
|
|
$this->dynamicInsert('voci_spesa', $data);
|
|
$count++;
|
|
}
|
|
if ($updated > 0) {
|
|
$this->line(" → voci aggiornate: {$updated}");
|
|
}
|
|
return $count + $updated;
|
|
}
|
|
private function stepBanche(?int $limit): int
|
|
{
|
|
// Importa conti bancari per lo stabile corrente leggendo Anagr_casse da generale_stabile.mdb
|
|
if (!Schema::hasTable('dati_bancari')) return 0;
|
|
$stabileCode = (string)($this->option('stabile') ?? '');
|
|
if ($stabileCode === '') return 0;
|
|
// Risolvi stabile_id
|
|
$stabileId = $this->resolveStabileId();
|
|
// Determina root archivi: usa --path come radice (default noto)
|
|
$root = rtrim((string)($this->option('path') ?: '/mnt/gescon-archives/gescon'), '/');
|
|
$stableDir = $root . '/' . $stabileCode;
|
|
$generale = $stableDir . '/generale_stabile.mdb';
|
|
$rows = [];
|
|
|
|
// 0) Preferisci staging anag_casse se presente
|
|
$stagingTable = null;
|
|
foreach (['anag_casse', 'Anag_casse', 'anagr_casse', 'Anagr_casse'] as $t) {
|
|
if (Schema::connection('gescon_import')->hasTable($t)) {
|
|
$stagingTable = $t;
|
|
break;
|
|
}
|
|
}
|
|
if ($stagingTable) {
|
|
$q = DB::connection('gescon_import')->table($stagingTable);
|
|
if ($stabileCode && Schema::connection('gescon_import')->hasColumn($stagingTable, 'cod_stabile')) {
|
|
$q->where('cod_stabile', $stabileCode);
|
|
}
|
|
if ($limit) $q->limit($limit);
|
|
$rows = $q->get()->map(function ($r): array {
|
|
$out = [];
|
|
foreach ((array) $r as $k => $v) {
|
|
$out[strtolower((string) $k)] = $v;
|
|
}
|
|
return $out;
|
|
})->all();
|
|
}
|
|
|
|
if (empty($rows) && is_file($generale) && is_readable($generale)) {
|
|
$rows = $this->mdbExportToRows($generale, ['Anagr_casse', 'anagr_casse', 'ANAGR_CASSE']);
|
|
}
|
|
// Fallback: cerca Anagr_casse nei singolo_anno.mdb (può contenere info ridondanti)
|
|
if (empty($rows) && is_dir($stableDir)) {
|
|
foreach ((array)glob($stableDir . '/*/singolo_anno.mdb') as $mdb) {
|
|
$part = $this->mdbExportToRows($mdb, ['Anagr_casse', 'anagr_casse', 'ANAGR_CASSE']);
|
|
if (!empty($part)) {
|
|
$rows = array_merge($rows, $part);
|
|
}
|
|
if ($limit && count($rows) >= $limit) break;
|
|
}
|
|
}
|
|
if (empty($rows)) return 0;
|
|
// Applica mapping personalizzato (se presente) per individuare le colonne legacy
|
|
$mapLegacy = [
|
|
'nome_banca' => ['banca', 'istituto', 'nome_banca', 'denominazione', 'denom_banca'],
|
|
'iban' => ['iban', 'iban_conto', 'iban1'],
|
|
'swift' => ['swift', 'bic', 'bic_swift'],
|
|
'conto' => ['n_conto', 'numero_conto', 'conto'],
|
|
'intestatario' => ['intestatario', 'intestazione', 'titolare'],
|
|
'cod_cassa' => ['cod_cassa', 'codcassa', 'codice_cassa', 'modalita_pagamento', 'codice'],
|
|
'descrizione' => ['descrizione', 'descr', 'denominazione', 'note']
|
|
];
|
|
if (!empty($this->banksMapping) && is_array($this->banksMapping)) {
|
|
// banksMapping formato: [{legacy:{...}, target:{...}}, ...] → usa chiavi legacy quando compilate
|
|
foreach ($this->banksMapping as $bm) {
|
|
if (!is_array($bm)) {
|
|
continue;
|
|
}
|
|
foreach (['nome_banca', 'iban', 'swift', 'conto', 'intestatario', 'cod_cassa'] as $k) {
|
|
$legacyField = $this->resolveMappingLegacyField($bm, $k);
|
|
if ($legacyField) {
|
|
$lk = strtolower(trim($legacyField));
|
|
if (!isset($mapLegacy[$k])) {
|
|
$mapLegacy[$k] = [];
|
|
}
|
|
array_unshift($mapLegacy[$k], $lk);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$now = now();
|
|
// Verifica se esiste già un conto marcato come "nostro" per questo stabile
|
|
$nostroAlready = false;
|
|
if (Schema::hasTable('dati_bancari') && $stabileId) {
|
|
try {
|
|
$nostroAlready = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('is_nostro_conto', true)
|
|
->exists();
|
|
} catch (\Throwable $e) {
|
|
$nostroAlready = false;
|
|
}
|
|
}
|
|
$created = 0;
|
|
$updated = 0;
|
|
$seenKeys = [];
|
|
foreach ($rows as $r) {
|
|
// Costruisci payload best-effort cercando tra alias
|
|
$getFirst = function (array $aliases) use ($r) {
|
|
foreach ($aliases as $a) {
|
|
if (array_key_exists($a, $r) && trim((string)$r[$a]) !== '') return trim((string)$r[$a]);
|
|
}
|
|
return null;
|
|
};
|
|
$denom = $getFirst($mapLegacy['nome_banca']);
|
|
$iban = strtoupper(preg_replace('/\s+/', '', (string)($getFirst($mapLegacy['iban']) ?? '')));
|
|
$swift = strtoupper((string)($getFirst($mapLegacy['swift']) ?? ''));
|
|
$numero = $getFirst($mapLegacy['conto']);
|
|
$intest = $getFirst($mapLegacy['intestatario']);
|
|
$codCassa = $getFirst($mapLegacy['cod_cassa']);
|
|
$descr = $getFirst($mapLegacy['descrizione']);
|
|
if (!$denom && !$iban && !$numero && !$codCassa && !$descr) continue; // riga non informativa
|
|
// Chiave dedup per stabile: IBAN preferito, altrimenti numero conto
|
|
$key = $iban ?: ($numero ? ('N:' . $numero) : ($codCassa ? ('C:' . strtoupper(trim($codCassa))) : null));
|
|
if (!$key) continue;
|
|
if (isset($seenKeys[$key])) continue; // evita duplicati da diverse sorgenti
|
|
$seenKeys[$key] = true;
|
|
// Caso 1: IBAN presente → tratta come conto bancario (dati_bancari)
|
|
if (!$iban) {
|
|
// Nessun IBAN → registra come cassa generica in tabella 'casse'
|
|
if (Schema::hasTable('casse')) {
|
|
$code = strtoupper(trim((string)($codCassa ?: '')));
|
|
if ($code === '') {
|
|
// fallback generico evitando collisioni: GEN, GEN1, ...
|
|
$base = 'GEN';
|
|
$code = $base;
|
|
$idx = 1;
|
|
while (DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', $code)->exists()) {
|
|
$code = $base . (++$idx);
|
|
}
|
|
}
|
|
$existsC = DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', $code)->first();
|
|
if (!$existsC) {
|
|
DB::table('casse')->insert([
|
|
'tenant_id' => null,
|
|
'stabile_id' => $stabileId,
|
|
'cod_cassa' => $code,
|
|
'descrizione' => ($descr ?: ($denom ?: 'Cassa generica')),
|
|
'tipo' => ($code === 'CON') ? 'cassa_contanti' : 'altro',
|
|
'iban' => null,
|
|
'intestazione_conto' => $intest ?: null,
|
|
'attiva' => true,
|
|
'meta' => json_encode(['from' => 'Anagr_casse', 'numero' => $numero]),
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
$created++;
|
|
} else {
|
|
$upd = [];
|
|
if ($descr && (empty($existsC->descrizione) || $existsC->descrizione === 'ND')) $upd['descrizione'] = $descr;
|
|
if (!empty($upd)) {
|
|
$upd['updated_at'] = $now;
|
|
DB::table('casse')->where('id', $existsC->id)->update($upd);
|
|
$updated++;
|
|
}
|
|
}
|
|
}
|
|
// Per la cassa contanti "CON" crea/aggiorna anche un DatiBancari di tipo cassa per renderlo visibile nell'UI
|
|
$codeUp = strtoupper(trim((string)$codCassa));
|
|
if ($codeUp === 'CON' && Schema::hasTable('dati_bancari') && $stabileId) {
|
|
$existingDb = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where(function ($q) {
|
|
$q->whereNull('iban')->orWhere('iban', '');
|
|
})
|
|
->where(function ($q) use ($codeUp) {
|
|
$q->where('legacy_cod_cassa', $codeUp)
|
|
->orWhere('numero_conto', $codeUp);
|
|
})
|
|
->first();
|
|
$payloadCassa = [
|
|
'stabile_id' => $stabileId,
|
|
'contatto_id' => null,
|
|
'tipo_conto' => 'cassa',
|
|
'denominazione_banca' => $denom ?: ($descr ?: 'Cassa contanti'),
|
|
'numero_conto' => $codeUp,
|
|
'iban' => null,
|
|
'legacy_cod_cassa' => $codeUp,
|
|
'abi' => null,
|
|
'cab' => null,
|
|
'cin' => null,
|
|
'bic_swift' => null,
|
|
'intestazione_conto' => $intest ?: null,
|
|
'data_saldo_iniziale' => $now->toDateString(),
|
|
'saldo_iniziale' => 0,
|
|
'valuta' => 'EUR',
|
|
'stato_conto' => 'attivo',
|
|
'is_nostro_conto' => !$nostroAlready,
|
|
'note' => '[GESCON] Cassa contanti',
|
|
'updated_at' => $now,
|
|
];
|
|
if ($existingDb) {
|
|
DB::table('dati_bancari')->where('id', $existingDb->id)->update($payloadCassa);
|
|
$updated++;
|
|
} else {
|
|
$payloadCassa['created_at'] = $now;
|
|
DB::table('dati_bancari')->insert($payloadCassa);
|
|
$created++;
|
|
$nostroAlready = $nostroAlready || $payloadCassa['is_nostro_conto'];
|
|
}
|
|
}
|
|
// Non creare dati_bancari in assenza di IBAN, passa alla prossima riga
|
|
if ($limit && ($created + $updated) >= $limit) break;
|
|
continue;
|
|
}
|
|
// Cerca esistente per stabile_id + IBAN/numero
|
|
$exists = null;
|
|
if ($iban) {
|
|
$exists = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->whereRaw('REPLACE(UPPER(iban)," ","") = ?', [$iban])
|
|
->first();
|
|
}
|
|
if (!$exists && $numero) {
|
|
$exists = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('numero_conto', $numero)
|
|
->first();
|
|
}
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'contatto_id' => null,
|
|
'tipo_conto' => 'corrente',
|
|
'denominazione_banca' => $denom ?: 'Banca',
|
|
'numero_conto' => $numero,
|
|
'iban' => $iban ?: null,
|
|
'legacy_cod_cassa' => $codCassa ?: null,
|
|
'abi' => null,
|
|
'cab' => null,
|
|
'cin' => null,
|
|
'bic_swift' => $swift ?: null,
|
|
'intestazione_conto' => $intest ?: null,
|
|
'data_saldo_iniziale' => $now->toDateString(),
|
|
'saldo_iniziale' => 0,
|
|
'valuta' => 'EUR',
|
|
'stato_conto' => 'attivo',
|
|
'is_nostro_conto' => false,
|
|
'note' => '[GESCON]',
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
if (!$nostroAlready) {
|
|
// Marca il primo che inseriamo come nostro conto
|
|
$payload['is_nostro_conto'] = true;
|
|
$nostroAlready = true;
|
|
}
|
|
if ($exists) {
|
|
// Aggiorna campi vuoti
|
|
$upd = [];
|
|
foreach (['denominazione_banca', 'numero_conto', 'iban', 'bic_swift', 'intestazione_conto', 'legacy_cod_cassa'] as $c) {
|
|
$newV = $payload[$c] ?? null;
|
|
$oldV = $exists->$c ?? null;
|
|
if ($newV && (!$oldV || $oldV === 'ND')) $upd[$c] = $newV;
|
|
}
|
|
if (!empty($upd)) {
|
|
$upd['updated_at'] = $now;
|
|
if (!$this->isDryRun) DB::table('dati_bancari')->where('id', $exists->id)->update($upd);
|
|
$updated++;
|
|
}
|
|
continue;
|
|
}
|
|
$this->dynamicInsert('dati_bancari', $payload);
|
|
// Reperisci il record appena inserito e applica eventuale codice cassa dal mapping
|
|
try {
|
|
$inserted = null;
|
|
if ($iban) {
|
|
$inserted = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->whereRaw('REPLACE(UPPER(iban)," ","") = ?', [$iban])
|
|
->orderByDesc('id')
|
|
->first();
|
|
}
|
|
if (!$inserted && $numero) {
|
|
$inserted = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('numero_conto', $numero)
|
|
->orderByDesc('id')
|
|
->first();
|
|
}
|
|
if ($inserted && !empty($this->banksMapping)) {
|
|
$assigned = null;
|
|
foreach ($this->banksMapping as $bm) {
|
|
if (!is_array($bm)) {
|
|
continue;
|
|
}
|
|
$targetCode = $this->resolveMappingCodiceCassa($bm);
|
|
if (!$targetCode) {
|
|
continue;
|
|
}
|
|
$legacyCode = $bm['legacy_cod_cassa'] ?? $this->resolveMappingLegacyField($bm, 'cod_cassa');
|
|
$legacyCode = $legacyCode ? strtoupper(trim((string)$legacyCode)) : null;
|
|
$currentLegacyCode = isset($inserted->legacy_cod_cassa) ? strtoupper(trim((string)$inserted->legacy_cod_cassa)) : null;
|
|
if ($legacyCode && $currentLegacyCode && $legacyCode === $currentLegacyCode) {
|
|
$assigned = $targetCode;
|
|
break;
|
|
}
|
|
// Compatibilità formato precedente: confronta IBAN/numero reali se disponibili
|
|
$legacyIban = isset($bm['iban']) ? strtoupper(preg_replace('/\s+/', '', (string)$bm['iban'])) : null;
|
|
$legacyNum = isset($bm['conto']) ? (string)$bm['conto'] : null;
|
|
if ($legacyIban && $iban && $legacyIban === $iban) {
|
|
$assigned = $targetCode;
|
|
break;
|
|
}
|
|
if ($legacyNum && $numero && $legacyNum === (string)$numero) {
|
|
$assigned = $targetCode;
|
|
break;
|
|
}
|
|
}
|
|
if ($assigned) {
|
|
DB::table('dati_bancari')->where('id', $inserted->id)->update([
|
|
'legacy_cod_cassa' => $assigned,
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
}
|
|
$created++;
|
|
if ($limit && ($created + $updated) >= $limit) break;
|
|
}
|
|
if (($created + $updated) > 0) {
|
|
$this->line(" → conti bancari creati: {$created}, aggiornati: {$updated}");
|
|
}
|
|
// Assicura casse di base per lo stabile: CON (cassa contanti) sempre presente, CDL (cassa lavoro) opzionale
|
|
try {
|
|
if (Schema::hasTable('casse') && $stabileId) {
|
|
// CON: Cassa contanti, sempre
|
|
$existsCon = DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', 'CON')->first();
|
|
if (!$existsCon) {
|
|
DB::table('casse')->insert([
|
|
'tenant_id' => null,
|
|
'stabile_id' => $stabileId,
|
|
'cod_cassa' => 'CON',
|
|
'descrizione' => 'Cassa contanti',
|
|
'tipo' => 'cassa_contanti',
|
|
'iban' => null,
|
|
'intestazione_conto' => null,
|
|
'attiva' => true,
|
|
'meta' => json_encode(['seeded' => true]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
// CDL: Cassa di lavoro (crea se non esiste; opzionale ma utile)
|
|
$existsCdl = DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', 'CDL')->first();
|
|
if (!$existsCdl) {
|
|
DB::table('casse')->insert([
|
|
'tenant_id' => null,
|
|
'stabile_id' => $stabileId,
|
|
'cod_cassa' => 'CDL',
|
|
'descrizione' => 'Cassa di lavoro',
|
|
'tipo' => 'altro',
|
|
'iban' => null,
|
|
'intestazione_conto' => null,
|
|
'attiva' => true,
|
|
'meta' => json_encode(['seeded' => true]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// non bloccare la pipeline per errori di seeding casse
|
|
}
|
|
return $created + $updated;
|
|
}
|
|
private function stepOperazioni(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('operazioni')) return 0;
|
|
$src = DB::connection('gescon_import')->table('operazioni');
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $o) {
|
|
$legacy = $o->id_operaz ?? null;
|
|
if (!$legacy) continue;
|
|
// Se la tabella dominio non esiste, in dry-run consideriamo solo il conteggio e saltiamo le scritture
|
|
if (!Schema::hasTable('operazioni_contabili')) {
|
|
$count++;
|
|
continue;
|
|
}
|
|
$exists = DB::table('operazioni_contabili')->where('legacy_id', $legacy)->first();
|
|
if ($exists) continue;
|
|
$data = [
|
|
'tenant_id' => null,
|
|
'gestione_id' => $this->resolveGestioneId($o->anno ?? null, $o->compet ?? null, $o->gestione ?? null),
|
|
'legacy_id' => $legacy,
|
|
'descrizione' => $o->note ?? ($o->natura ?? 'Operazione'),
|
|
'data_operazione' => $o->dt_spe ?? now()->toDateString(),
|
|
'compet' => in_array($o->compet ?? 'C', ['C', 'P']) ? $o->compet : 'C',
|
|
'natura2' => $o->natura2 ?? null,
|
|
'n_stra' => $o->n_stra ?? 0,
|
|
'protocollo_numero' => $this->nextProtocolNumber(),
|
|
'protocollo_completo' => null,
|
|
'dare' => (float)($o->importo_spese ?? 0),
|
|
'avere' => (float)($o->importo_entrate ?? 0),
|
|
'conto_contabile' => $o->cod_spe ?? null,
|
|
'stato_operazione' => 'confermata',
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
// Collega conto bancario se la colonna esiste nello schema
|
|
if (Schema::hasColumn('operazioni_contabili', 'conto_bancario_id')) {
|
|
$contoId = null;
|
|
// 1) prova via cod_cassa della riga (se presente in staging operazioni)
|
|
$codCassa = $o->cod_cassa ?? $o->modalita_pagamento ?? null;
|
|
if ($codCassa) {
|
|
$contoId = $this->resolveContoByCodCassa($this->stabileId, (string)$codCassa);
|
|
}
|
|
// 2) fallback al default per stabile
|
|
if (!$contoId) {
|
|
$contoId = $this->resolveDefaultContoBancarioId($this->stabileId);
|
|
}
|
|
if ($contoId) {
|
|
$data['conto_bancario_id'] = $contoId;
|
|
}
|
|
}
|
|
// Se previsto, collega cassa (CON/CDL/altre) quando non c'è un conto bancario
|
|
if (Schema::hasColumn('operazioni_contabili', 'cassa_id') && empty($data['conto_bancario_id'] ?? null)) {
|
|
$codCassa = $o->cod_cassa ?? $o->modalita_pagamento ?? null;
|
|
if ($codCassa) {
|
|
$cassaId = $this->resolveCassaByCodCassa($this->stabileId, (string)$codCassa);
|
|
if ($cassaId) {
|
|
$data['cassa_id'] = $cassaId;
|
|
}
|
|
}
|
|
}
|
|
$this->dynamicInsert('operazioni_contabili', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
private function stepRate(?int $limit): int
|
|
{
|
|
if (Schema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio') && Schema::hasTable('rate_emesse')) {
|
|
return $this->importRateEmesseDaEmissioniDettaglio($limit);
|
|
}
|
|
|
|
if (!Schema::connection('gescon_import')->hasTable('rate')) return 0;
|
|
$src = DB::connection('gescon_import')->table('rate as r');
|
|
// Filtro per stabile: se rate.cod_stabile non esiste, prova join con condomin usando r.cod_cond o r.id_cond
|
|
if ($this->option('stabile')) {
|
|
$stab = $this->option('stabile');
|
|
if (Schema::connection('gescon_import')->hasColumn('rate', 'cod_stabile')) {
|
|
$src->where('r.cod_stabile', $stab);
|
|
} elseif (
|
|
Schema::connection('gescon_import')->hasTable('condomin') &&
|
|
(Schema::connection('gescon_import')->hasColumn('rate', 'cod_cond') || Schema::connection('gescon_import')->hasColumn('rate', 'id_cond')) &&
|
|
Schema::connection('gescon_import')->hasColumn('condomin', 'cod_stabile')
|
|
) {
|
|
$joinLeft = Schema::connection('gescon_import')->hasColumn('rate', 'cod_cond') ? 'cod_cond' : 'id_cond';
|
|
$src = $src->join('condomin as c', 'c.' . $joinLeft, '=', 'r.' . $joinLeft)
|
|
->where('c.cod_stabile', $stab)
|
|
->select('r.*');
|
|
}
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $r) {
|
|
$legacy = $r->id
|
|
?? ($r->n_rata ?? null)
|
|
?? ($r->numero_rata ?? null)
|
|
?? ($r->n_emissione ?? null);
|
|
if (!$legacy) {
|
|
// Fallback deterministico: hash di campi tipici
|
|
$key = implode('|', [
|
|
(string)($r->cod_stabile ?? $this->option('stabile') ?? ''),
|
|
(string)($r->cod_cond ?? $r->id_cond ?? ''),
|
|
(string)($r->data_emissione ?? ''),
|
|
(string)($r->importo ?? $r->importo_euro ?? ''),
|
|
(string)($r->causale ?? '')
|
|
]);
|
|
$legacy = hexdec(substr(hash('crc32b', $key), 0, 8));
|
|
}
|
|
if (!$legacy) continue;
|
|
if (Schema::hasColumn('rate_emesse', 'legacy_id')) { // tabella evoluta con legacy
|
|
$exists = DB::table('rate_emesse')->where('legacy_id', $legacy)->first();
|
|
if ($exists) continue;
|
|
$data = [
|
|
'legacy_id' => $legacy,
|
|
'periodo_anno' => $r->data_emissione ? (int)date('Y', strtotime($r->data_emissione)) : null,
|
|
'periodo_mese' => $r->data_emissione ? (int)date('n', strtotime($r->data_emissione)) : null,
|
|
'importo' => $r->importo ?? 0,
|
|
'descrizione' => $r->causale ?? null,
|
|
'competenza_da' => $r->data_emissione ?? null,
|
|
'competenza_a' => $r->data_scadenza ?? null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('rate_emesse', $data);
|
|
} else {
|
|
// Fallback: tabella rate_emesse originale (richiede molti campi); skip per ora
|
|
continue;
|
|
}
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
private function importRateEmesseDaEmissioniDettaglio(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio')) return 0;
|
|
if (!Schema::connection('gescon_import')->hasTable('rate_emissioni')) return 0;
|
|
|
|
$stab = $this->option('stabile');
|
|
$src = DB::connection('gescon_import')->table('rate_emissioni_dettaglio as d');
|
|
if ($stab) {
|
|
$src->where('d.cod_stabile', $stab);
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
|
|
$emissioni = DB::connection('gescon_import')->table('rate_emissioni')
|
|
->when($stab, fn($q) => $q->where('cod_stabile', $stab))
|
|
->get()
|
|
->groupBy(fn($r) => (string) $r->numero_emissione);
|
|
|
|
$piani = [];
|
|
$count = 0;
|
|
$cumuloCache = [];
|
|
$hasCondomin = Schema::connection('gescon_import')->hasTable('condomin');
|
|
|
|
foreach ($src->get() as $row) {
|
|
$codStabile = (string) ($row->cod_stabile ?? $stab ?? '');
|
|
$numeroEmissione = (int) ($row->numero_emissione ?? 0);
|
|
if ($codStabile === '' || $numeroEmissione <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$codCond = (string) ($row->cod_cond ?? '');
|
|
$unitaId = $this->resolveUnitaIdFromCodCond($codStabile, $codCond);
|
|
if (!$unitaId) {
|
|
continue;
|
|
}
|
|
|
|
$soggettoId = $this->resolveSoggettoResponsabileId($unitaId, (string) ($row->tipo_soggetto ?? ''));
|
|
if (!$soggettoId) {
|
|
continue;
|
|
}
|
|
|
|
if (!isset($piani[$numeroEmissione])) {
|
|
$piani[$numeroEmissione] = $this->ensurePianoRateizzazionePerEmissione($codStabile, $numeroEmissione, $emissioni);
|
|
}
|
|
$pianoId = $piani[$numeroEmissione];
|
|
if (!$pianoId) {
|
|
continue;
|
|
}
|
|
|
|
$importo = is_numeric($row->importo_dovuto_euro ?? null)
|
|
? (float) $row->importo_dovuto_euro
|
|
: (is_numeric($row->importo_dovuto ?? null) ? (float) $row->importo_dovuto : 0.0);
|
|
|
|
$numeroRata = is_numeric($row->numero_ricevuta ?? null)
|
|
? (int) $row->numero_ricevuta
|
|
: (is_numeric($row->numero_mese ?? null) ? (int) $row->numero_mese : 1);
|
|
|
|
$dataEmissione = $row->data_emissione ?? null;
|
|
$dataScadenza = $row->data_scadenza ?? null;
|
|
|
|
$emissioneRows = $emissioni[(string) $numeroEmissione] ?? null;
|
|
$emissioneRow = $emissioneRows ? $emissioneRows->first() : null;
|
|
if (!$dataEmissione && $emissioneRow?->data_emissione) {
|
|
$dataEmissione = $emissioneRow->data_emissione;
|
|
}
|
|
if (!$dataScadenza && $emissioneRow?->data_scadenza) {
|
|
$dataScadenza = $emissioneRow->data_scadenza;
|
|
}
|
|
|
|
$numeroRataEff = $numeroRata;
|
|
|
|
$condInq = trim((string) ($row->cond_inq ?? $row->tipo_soggetto ?? ''));
|
|
$raggruppamento = trim((string) ($row->raggruppamento ?? ''));
|
|
if ($raggruppamento === '' && $hasCondomin && $codCond !== '') {
|
|
$cacheKey = $codStabile . '|' . $codCond;
|
|
if (!array_key_exists($cacheKey, $cumuloCache)) {
|
|
$cumuloCache[$cacheKey] = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('cod_cond', $codCond)
|
|
->first(['cumulo_cond', 'cumulo_inq']);
|
|
}
|
|
$cumuloRow = $cumuloCache[$cacheKey];
|
|
if ($cumuloRow) {
|
|
$raggruppamento = trim((string) (
|
|
strtoupper($condInq) === 'I' ? ($cumuloRow->cumulo_inq ?? '') : ($cumuloRow->cumulo_cond ?? '')
|
|
));
|
|
}
|
|
}
|
|
|
|
$noteParts = [
|
|
'n_emissione=' . $numeroEmissione,
|
|
'anno_emissione=' . (string) ($row->anno_emissione ?? ''),
|
|
'cod_cond_src=' . $codCond,
|
|
'o_r_s=' . (string) ($row->tipo_quota ?? $row->tipo_soggetto ?? ''),
|
|
'n_mese=' . (string) ($row->numero_mese ?? ''),
|
|
$condInq !== '' ? 'cond_inq=' . $condInq : '',
|
|
$raggruppamento !== '' ? 'raggruppamento=' . $raggruppamento : '',
|
|
];
|
|
|
|
$existing = DB::table('rate_emesse')
|
|
->where('piano_rateizzazione_id', $pianoId)
|
|
->where('unita_immobiliare_id', $unitaId)
|
|
->where('soggetto_responsabile_id', $soggettoId)
|
|
->where('numero_rata_progressivo', $numeroRataEff)
|
|
->first();
|
|
if ($existing) {
|
|
$descr = (string) ($row->descrizione ?? $emissioneRow?->descrizione ?? '');
|
|
$descrizione = (string) ($existing->descrizione ?? '');
|
|
if ($descr !== '' && $descrizione !== '' && stripos($descrizione, $descr) === false) {
|
|
$descrizione = trim($descrizione . ' | ' . $descr);
|
|
} elseif ($descrizione === '' && $descr !== '') {
|
|
$descrizione = $descr;
|
|
}
|
|
|
|
$noteAgg = implode('|', array_filter($noteParts, fn($v) => $v !== ''));
|
|
$note = (string) ($existing->note ?? '');
|
|
if ($note === '') {
|
|
$note = $noteAgg;
|
|
} elseif ($noteAgg !== '' && stripos($note, $noteAgg) === false) {
|
|
$note = trim($note . ' | ' . $noteAgg);
|
|
}
|
|
|
|
DB::table('rate_emesse')
|
|
->where('id', $existing->id)
|
|
->update([
|
|
'descrizione' => $descrizione,
|
|
'importo_originario_unita' => (float) ($existing->importo_originario_unita ?? 0) + $importo,
|
|
'importo_addebitato_soggetto' => (float) ($existing->importo_addebitato_soggetto ?? 0) + $importo,
|
|
'note' => $note,
|
|
'updated_at' => now(),
|
|
]);
|
|
continue;
|
|
}
|
|
|
|
// Evita duplicati: stessa emissione, unità, soggetto, rata e date
|
|
$noteNeedle = '%n_emissione=' . $numeroEmissione . '%'
|
|
. 'cod_cond_src=' . $codCond . '%'
|
|
. 'o_r_s=' . (string) ($row->tipo_quota ?? $row->tipo_soggetto ?? '') . '%'
|
|
. 'n_mese=' . (string) ($row->numero_mese ?? '') . '%';
|
|
$dupExists = DB::table('rate_emesse')
|
|
->where('piano_rateizzazione_id', $pianoId)
|
|
->where('unita_immobiliare_id', $unitaId)
|
|
->where('soggetto_responsabile_id', $soggettoId)
|
|
->where('numero_rata_progressivo', $numeroRataEff)
|
|
->whereDate('data_emissione', $dataEmissione ?? now()->toDateString())
|
|
->whereDate('data_scadenza', $dataScadenza ?? ($dataEmissione ?? now()->toDateString()))
|
|
->where('note', 'like', $noteNeedle)
|
|
->exists();
|
|
if ($dupExists) {
|
|
continue;
|
|
}
|
|
|
|
$payload = [
|
|
'piano_rateizzazione_id' => $pianoId,
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'soggetto_responsabile_id' => $soggettoId,
|
|
'numero_rata_progressivo' => $numeroRataEff,
|
|
'descrizione' => $row->descrizione ?? ($emissioneRow?->descrizione ?? null),
|
|
'importo_originario_unita' => $importo,
|
|
'percentuale_addebito_soggetto' => 100.0,
|
|
'importo_addebitato_soggetto' => $importo,
|
|
'data_emissione' => $dataEmissione ?? now()->toDateString(),
|
|
'data_scadenza' => $dataScadenza ?? ($dataEmissione ?? now()->toDateString()),
|
|
'stato_rata' => 'EMESSA',
|
|
'data_ultimo_pagamento' => null,
|
|
'importo_pagato' => 0,
|
|
'note' => implode('|', array_filter($noteParts, fn($v) => $v !== '')),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$this->dynamicInsert('rate_emesse', $payload);
|
|
$count++;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
private function resolveUnitaIdFromCodCond(string $codStabile, string $codCond): ?int
|
|
{
|
|
$codCond = trim($codCond);
|
|
if ($codCond === '') return null;
|
|
|
|
$stabileId = $this->stabileId;
|
|
if (Schema::hasTable('stabili')) {
|
|
$stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$stRow = DB::table('stabili')->where($stabileCol, $codStabile)->first();
|
|
if ($stRow) {
|
|
$stabileId = $stRow->id;
|
|
}
|
|
}
|
|
if (!$stabileId) {
|
|
return null;
|
|
}
|
|
|
|
$cond = null;
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$cond = DB::connection('gescon_import')->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('cod_cond', $codCond)
|
|
->first(['scala', 'interno', 'codice_fiscale', 'email', 'nome', 'cognome']);
|
|
}
|
|
|
|
$scala = $cond?->scala ? trim((string) $cond->scala) : 'A';
|
|
$interno = $cond?->interno ? trim((string) $cond->interno) : '';
|
|
if ($interno === '') {
|
|
$interno = $codCond;
|
|
}
|
|
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('scala', $scala)
|
|
->where('interno', $interno)
|
|
->first(['id']);
|
|
if ($unita?->id) {
|
|
return (int) $unita->id;
|
|
}
|
|
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('interno', $interno)
|
|
->orderBy('id')
|
|
->first(['id']);
|
|
if ($unita?->id) {
|
|
return (int) $unita->id;
|
|
}
|
|
|
|
if ($cond) {
|
|
$cf = trim((string) ($cond->codice_fiscale ?? '')) ?: null;
|
|
$email = trim((string) ($cond->email ?? '')) ?: null;
|
|
$nome = $cond->nome ?? null;
|
|
$cognome = $cond->cognome ?? null;
|
|
$sog = $this->findExistingSoggetto($cf, $email, $nome, $cognome);
|
|
if ($sog && Schema::hasTable('proprieta')) {
|
|
$row = DB::table('proprieta')
|
|
->where('soggetto_id', $sog->id)
|
|
->whereIn('tipo_diritto', ['proprieta', 'locazione'])
|
|
->orderByRaw("CASE WHEN tipo_diritto = 'proprieta' THEN 0 ELSE 1 END")
|
|
->orderByDesc('percentuale_possesso')
|
|
->orderByDesc('id')
|
|
->first(['unita_immobiliare_id']);
|
|
if ($row?->unita_immobiliare_id) {
|
|
return (int) $row->unita_immobiliare_id;
|
|
}
|
|
}
|
|
|
|
if (Schema::hasTable('unita_immobiliari')) {
|
|
$codice = $codStabile . '-' . $scala . '-' . $interno;
|
|
$existing = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('codice_unita', $codice)
|
|
->first(['id']);
|
|
if (!$existing) {
|
|
$uid = DB::table('unita_immobiliari')->insertGetId([
|
|
'stabile_id' => $stabileId,
|
|
'codice_unita' => $codice,
|
|
'scala' => $scala,
|
|
'interno' => $interno,
|
|
'denominazione' => trim((string)($nome ?? '') . ' ' . (string)($cognome ?? '')) ?: null,
|
|
'tipo_unita' => 'abitazione',
|
|
'stato_occupazione' => 'occupata_proprietario',
|
|
'attiva' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
$existing = $uid ? (object) ['id' => $uid] : null;
|
|
}
|
|
|
|
if ($existing && Schema::hasTable('soggetti')) {
|
|
if (!$sog) {
|
|
$payload = [
|
|
'old_id' => $this->safeOldId($codCond),
|
|
'nome' => $nome,
|
|
'cognome' => $cognome,
|
|
'codice_fiscale' => $cf,
|
|
'email' => $email,
|
|
'tipo' => 'proprietario',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('soggetti', 'codice_univoco')) {
|
|
$baseKey = $this->normalizeNameKey($nome ?? '', $cognome ?? '') ?: ($email ?: 'ND');
|
|
$code = $this->generateStableCondKey('S', $codStabile ?: 'S0', $baseKey);
|
|
$tries = 0;
|
|
while (DB::table('soggetti')->where('codice_univoco', $code)->exists() && $tries < 5) {
|
|
$salt = ($codCond ?? '') . '-' . ($tries + 1);
|
|
$code = $this->generateStableCondKey('S', $codStabile ?: 'S0', $baseKey . '|' . $salt);
|
|
$tries++;
|
|
}
|
|
$payload['codice_univoco'] = $code;
|
|
}
|
|
$sid = DB::table('soggetti')->insertGetId($payload);
|
|
$sog = $sid ? (object) ['id' => $sid] : null;
|
|
}
|
|
|
|
if ($sog && Schema::hasTable('proprieta')) {
|
|
$existsProp = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $existing->id)
|
|
->where('soggetto_id', $sog->id)
|
|
->first(['id']);
|
|
if (!$existsProp) {
|
|
DB::table('proprieta')->insert([
|
|
'unita_immobiliare_id' => $existing->id,
|
|
'soggetto_id' => $sog->id,
|
|
'tipo_diritto' => 'proprieta',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($existing?->id) {
|
|
return (int) $existing->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
$codice = $codStabile . '-' . $scala . '-' . $interno;
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('codice_unita', $codice)
|
|
->first(['id']);
|
|
|
|
if ($unita?->id) {
|
|
return (int) $unita->id;
|
|
}
|
|
|
|
if (!$cond && Schema::hasTable('unita_immobiliari')) {
|
|
$scala = 'A';
|
|
$interno = $codCond;
|
|
$codice = $codStabile . '-' . $scala . '-' . $interno;
|
|
$uid = DB::table('unita_immobiliari')->insertGetId([
|
|
'stabile_id' => $stabileId,
|
|
'codice_unita' => $codice,
|
|
'scala' => $scala,
|
|
'interno' => $interno,
|
|
'denominazione' => 'Unità legacy ' . $codCond,
|
|
'tipo_unita' => 'abitazione',
|
|
'stato_occupazione' => 'occupata_proprietario',
|
|
'attiva' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
if ($uid) {
|
|
if (Schema::hasTable('soggetti')) {
|
|
$payload = [
|
|
'old_id' => $this->safeOldId($codCond),
|
|
'nome' => 'Condomino',
|
|
'cognome' => $codCond,
|
|
'tipo' => 'proprietario',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('soggetti', 'codice_univoco')) {
|
|
$baseKey = 'COND-' . $codCond;
|
|
$code = $this->generateStableCondKey('S', $codStabile ?: 'S0', $baseKey);
|
|
$tries = 0;
|
|
while (DB::table('soggetti')->where('codice_univoco', $code)->exists() && $tries < 5) {
|
|
$code = $this->generateStableCondKey('S', $codStabile ?: 'S0', $baseKey . '|' . ($tries + 1));
|
|
$tries++;
|
|
}
|
|
$payload['codice_univoco'] = $code;
|
|
}
|
|
$sid = DB::table('soggetti')->insertGetId($payload);
|
|
if ($sid && Schema::hasTable('proprieta')) {
|
|
DB::table('proprieta')->insert([
|
|
'unita_immobiliare_id' => $uid,
|
|
'soggetto_id' => $sid,
|
|
'tipo_diritto' => 'proprieta',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
return (int) $uid;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function resolveSoggettoResponsabileId(int $unitaId, string $tipoSoggetto = ''): ?int
|
|
{
|
|
if (!Schema::hasTable('proprieta')) return null;
|
|
|
|
$tipoSoggetto = strtoupper(trim($tipoSoggetto));
|
|
$prefer = $tipoSoggetto === 'I' ? 'locazione' : 'proprieta';
|
|
|
|
$row = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $unitaId)
|
|
->orderByRaw("CASE WHEN tipo_diritto = ? THEN 0 ELSE 1 END", [$prefer])
|
|
->orderByDesc('percentuale_possesso')
|
|
->orderByDesc('id')
|
|
->first(['soggetto_id']);
|
|
|
|
return $row?->soggetto_id ? (int) $row->soggetto_id : null;
|
|
}
|
|
|
|
private function ensurePianoRateizzazionePerEmissione(string $codStabile, int $numeroEmissione, $emissioni): ?int
|
|
{
|
|
if (!Schema::hasTable('piano_rateizzazione')) return null;
|
|
|
|
$stabileId = $this->stabileId;
|
|
if (Schema::hasTable('stabili')) {
|
|
$stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$stRow = DB::table('stabili')->where($stabileCol, $codStabile)->first();
|
|
if ($stRow) {
|
|
$stabileId = $stRow->id;
|
|
}
|
|
}
|
|
if (!$stabileId) return null;
|
|
|
|
$noteKey = 'emissione=' . $numeroEmissione;
|
|
$exists = DB::table('piano_rateizzazione')
|
|
->where('stabile_id', $stabileId)
|
|
->where('note', 'like', '%' . $noteKey . '%')
|
|
->first(['id']);
|
|
if ($exists?->id) {
|
|
return (int) $exists->id;
|
|
}
|
|
|
|
$emissioneRows = $emissioni[(string) $numeroEmissione] ?? null;
|
|
$emissioneRow = $emissioneRows ? $emissioneRows->first() : null;
|
|
$descr = $emissioneRow?->descrizione ?: ('Emissione ' . $numeroEmissione);
|
|
$dataInizio = $emissioneRow?->data_emissione ?? now()->toDateString();
|
|
$dataFine = $emissioneRow?->data_scadenza ?? $dataInizio;
|
|
|
|
$totale = 0.0;
|
|
if (Schema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio')) {
|
|
$totale = (float) DB::connection('gescon_import')->table('rate_emissioni_dettaglio')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('numero_emissione', $numeroEmissione)
|
|
->sum(DB::raw('COALESCE(importo_dovuto_euro, importo_dovuto, 0)'));
|
|
}
|
|
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'unita_immobiliare_id' => null,
|
|
'descrizione' => $descr,
|
|
'importo_totale' => $totale,
|
|
'numero_rate' => 1,
|
|
'importo_rata' => $totale,
|
|
'data_inizio' => $dataInizio,
|
|
'data_fine' => $dataFine,
|
|
'frequenza' => 'MENSILE',
|
|
'stato' => 'ATTIVO',
|
|
'importo_pagato' => 0,
|
|
'importo_residuo' => $totale,
|
|
'note' => 'emissione=' . $numeroEmissione . '|cod_stabile=' . $codStabile,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (!$this->isDryRun) {
|
|
$id = DB::table('piano_rateizzazione')->insertGetId($payload);
|
|
return is_numeric($id) ? (int) $id : null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
private function stepIncassi(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('incassi')) return 0;
|
|
$src = DB::connection('gescon_import')->table('incassi');
|
|
if ($this->option('stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $i) {
|
|
$legacy = $i->id ?? null;
|
|
if (!$legacy) continue;
|
|
if (!Schema::hasTable('incassi')) continue;
|
|
$legacyCol = Schema::hasColumn('incassi', 'legacy_id') ? 'legacy_id' : (Schema::hasColumn('incassi', 'id_incasso_gescon') ? 'id_incasso_gescon' : null);
|
|
if (!$legacyCol) continue;
|
|
$existsQuery = DB::table('incassi')->where($legacyCol, $legacy);
|
|
if ($this->stabileId && Schema::hasColumn('incassi', 'condominio_id')) {
|
|
$existsQuery->where('condominio_id', $this->stabileId);
|
|
}
|
|
$exists = $existsQuery->first();
|
|
if ($exists) continue;
|
|
$data = [];
|
|
$data[$legacyCol] = $legacy;
|
|
$codStabile = (string) ($i->cod_stabile ?? ($this->option('stabile') ?? ''));
|
|
$codCond = (string) ($i->cod_cond ?? '');
|
|
$condInq = (string) ($i->cond_inq ?? $i->cond_inquil ?? '');
|
|
$unitaId = $codCond !== '' ? $this->resolveUnitaIdFromCodCond($codStabile, $codCond) : null;
|
|
$soggettoId = $unitaId ? $this->resolveSoggettoResponsabileId($unitaId, $condInq) : null;
|
|
if (Schema::hasColumn('incassi', 'data_incasso')) {
|
|
$data['data_incasso'] = $i->data_incasso ?? $i->dt_empag ?? now()->toDateString();
|
|
$data['importo'] = $i->importo_pagato_euro ?? $i->importo_euro ?? $i->importo ?? 0;
|
|
$data['descrizione'] = $i->descrizione ?? $i->note ?? null;
|
|
$data['riferimento_orig'] = $i->riferimento_pagamento ?? null;
|
|
$data['ruolo_quota'] = $condInq !== '' ? $condInq : null;
|
|
if (Schema::hasColumn('incassi', 'cod_cond_gescon')) {
|
|
$data['cod_cond_gescon'] = $codCond !== '' ? $codCond : null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'cond_inquil')) {
|
|
$data['cond_inquil'] = $condInq !== '' ? $condInq : null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'n_mese')) {
|
|
$data['n_mese'] = $i->n_mese ?? $i->n_rata_riferimento ?? null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'o_r_s')) {
|
|
$data['o_r_s'] = $i->o_r_s ?? null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'anno_rif')) {
|
|
$data['anno_rif'] = $i->anno_rif ?? null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'dt_empag')) {
|
|
$data['dt_empag'] = $i->dt_empag ?? $i->data_incasso ?? null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'importo_pagato')) {
|
|
$data['importo_pagato'] = $i->importo_pagato_euro ?? $i->importo_euro ?? $i->importo ?? 0;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'importo_pagato_euro')) {
|
|
$data['importo_pagato_euro'] = $i->importo_pagato_euro ?? $i->importo_euro ?? $i->importo ?? 0;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'n_riferimento')) {
|
|
$data['n_riferimento'] = $i->n_riferimento ?? $i->riferimento_pagamento ?? null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'cod_cassa_gescon')) {
|
|
$data['cod_cassa_gescon'] = $i->cod_cassa ?? $i->modalita_pagamento ?? null;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'proviene_n_stra') && !empty($i->n_stra)) {
|
|
$data['proviene_n_stra'] = $i->n_stra;
|
|
}
|
|
if (Schema::hasColumn('incassi', 'condomino_id') && $soggettoId) {
|
|
$data['condomino_id'] = $soggettoId;
|
|
}
|
|
// collega eventuale conto per incasso se colonna esiste
|
|
if (Schema::hasColumn('incassi', 'conto_bancario_id')) {
|
|
$contoId = null;
|
|
$codCassa = $i->cod_cassa ?? $i->modalita_pagamento ?? null;
|
|
if ($codCassa) {
|
|
$contoId = $this->resolveContoByCodCassa($this->stabileId, (string)$codCassa);
|
|
}
|
|
if (!$contoId) {
|
|
$contoId = $this->resolveDefaultContoBancarioId($this->stabileId);
|
|
}
|
|
if ($contoId) $data['conto_bancario_id'] = $contoId;
|
|
}
|
|
// collega cassa per incasso se disponibile e nessun conto bancario è stato risolto
|
|
if (Schema::hasColumn('incassi', 'cassa_id') && empty($data['conto_bancario_id'] ?? null)) {
|
|
$codCassa = $i->cod_cassa ?? $i->modalita_pagamento ?? null;
|
|
if ($codCassa) {
|
|
$cassaId = $this->resolveCassaByCodCassa($this->stabileId, (string)$codCassa);
|
|
if ($cassaId) {
|
|
$data['cassa_id'] = $cassaId;
|
|
if (Schema::hasColumn('incassi', 'cod_cassa_legacy')) {
|
|
$data['cod_cassa_legacy'] = (string)$codCassa;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else { // schema complesso (nuova tabella incassi)
|
|
$data = array_merge($data, [
|
|
'tenant_id' => 'T1',
|
|
'gestione_id' => null,
|
|
'condominio_id' => $this->stabileId ?? 1,
|
|
'conto_bancario_id' => (function () use ($i) {
|
|
$cod = $i->cod_cassa ?? $i->modalita_pagamento ?? null;
|
|
$id = $cod ? $this->resolveContoByCodCassa($this->stabileId, (string)$cod) : null;
|
|
return $id ?: ($this->resolveDefaultContoBancarioId($this->stabileId) ?? null);
|
|
})(),
|
|
'cod_cond_gescon' => $i->cod_cond ?? $i->cod_cond ?? null,
|
|
'cond_inquil' => $i->cond_inq ?? null,
|
|
'n_riferimento' => $i->riferimento_pagamento ?? null,
|
|
'anno_rif' => $i->data_incasso ? (int)date('Y', strtotime($i->data_incasso)) : 0,
|
|
'n_mese' => $i->n_mese ?? null,
|
|
'o_r_s' => null,
|
|
'importo_pagato' => $i->importo ?? 0,
|
|
'importo_pagato_euro' => $i->importo_euro ?? 0,
|
|
'dt_empag' => $i->data_incasso ?? null,
|
|
'descrizione' => $i->note ?? null,
|
|
'cod_cassa_gescon' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
]);
|
|
// Se lo schema esteso prevedesse cassa_id, potremmo valorizzarlo qui se conto bancario non risolto
|
|
}
|
|
$data['created_at'] = $data['created_at'] ?? now();
|
|
$data['updated_at'] = now();
|
|
$this->dynamicInsert('incassi', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Risolve un conto bancario per stabile utilizzando il legacy cod_cassa (o codice cassa equivalente)
|
|
*/
|
|
private function resolveContoByCodCassa(?int $stabileId, string $codCassa): ?int
|
|
{
|
|
if (!$stabileId || !Schema::hasTable('dati_bancari')) return null;
|
|
$cod = strtoupper(trim($codCassa));
|
|
// Normalizza numeri conto da formati esterni (es. "CC N.000104951005" → "000104951005")
|
|
$normNumero = null;
|
|
if ($cod !== '') {
|
|
if (preg_match('/([0-9]{6,})/', $cod, $m)) {
|
|
$normNumero = ltrim($m[1], '0');
|
|
}
|
|
}
|
|
if ($cod === '') return null;
|
|
$row = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where(function ($q) use ($cod) {
|
|
$q->where('legacy_cod_cassa', $cod)
|
|
->orWhere('numero_conto', $cod)
|
|
->orWhereRaw('REPLACE(UPPER(iban)," ","") = ?', [$cod]);
|
|
})
|
|
->orderByDesc('is_nostro_conto')
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
if (!$row && $normNumero) {
|
|
$row = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where(function ($q) use ($normNumero) {
|
|
$q->where('numero_conto', $normNumero)
|
|
->orWhere('numero_conto', str_pad($normNumero, 12, '0', STR_PAD_LEFT));
|
|
})
|
|
->orderByDesc('is_nostro_conto')
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
}
|
|
return $row?->id ? (int)$row->id : null;
|
|
}
|
|
|
|
/**
|
|
* Risolve ID cassa (tabella casse) per cod_cassa legacy (es. CON, CCB, CDL, altri codici locali)
|
|
*/
|
|
private function resolveCassaByCodCassa(?int $stabileId, string $codCassa): ?int
|
|
{
|
|
if (!$stabileId || !Schema::hasTable('casse')) return null;
|
|
$cod = strtoupper(trim($codCassa));
|
|
if ($cod === '') return null;
|
|
$row = DB::table('casse')
|
|
->where('stabile_id', $stabileId)
|
|
->where('cod_cassa', $cod)
|
|
->first();
|
|
// Mappature comuni: "CCB" potrebbe indicare il conto corrente bancario associato all'IBAN; in tal caso priorità al conto_bancario, ma lasciamo anche una cassa con stesso codice se creata
|
|
if (!$row) return null;
|
|
return (int)$row->id;
|
|
}
|
|
private function stepGiroconti(?int $limit): int
|
|
{
|
|
if (Schema::connection('gescon_import')->hasTable('Giri_conti')) {
|
|
$src = DB::connection('gescon_import')->table('Giri_conti');
|
|
} elseif (Schema::connection('gescon_import')->hasTable('giri_conti')) {
|
|
$src = DB::connection('gescon_import')->table('giri_conti');
|
|
} else {
|
|
return 0;
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $g) {
|
|
$rif = $g->riferimento ?? null;
|
|
if (!$rif) continue;
|
|
$exists = DB::table('movimenti_interni')->where('riferimento', $rif)->first();
|
|
if ($exists) continue;
|
|
$data = [
|
|
'legacy_id' => null,
|
|
'riferimento' => $rif,
|
|
'data' => $g->Data_giroconto ?? now()->toDateString(),
|
|
'descrizione' => $g->Descrizione ?? '',
|
|
'importo' => $g->Importo ?? 0,
|
|
'conto_from' => $g->Cod_uscita ?? '',
|
|
'conto_to' => $g->Cod_entrata ?? '',
|
|
'tipo' => $g->tipo_riga ?? null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('movimenti_interni', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
private function stepStraord(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('straordinarie')) return 0;
|
|
$src = DB::connection('gescon_import')->table('straordinarie');
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $s) {
|
|
$legacy = $s->id_stra ?? null;
|
|
if (!$legacy) continue;
|
|
$exists = DB::table('lavori_straordinari')->where('legacy_id', $legacy)->first();
|
|
if ($exists) continue;
|
|
$data = [
|
|
'legacy_id' => $legacy,
|
|
'codice' => $s->codice ?? ('STRA-' . $legacy),
|
|
'descrizione' => $s->descriz_prev_cons ?? '',
|
|
'numero_rate' => $s->num_rate ?? 0,
|
|
'data_inizio' => isset($s->inizio_anno, $s->inizio_mese) ? sprintf('%04d-%02d-01', $s->inizio_anno, $s->inizio_mese) : null,
|
|
'natura' => $s->natura ?? 'spesa',
|
|
'gruppo' => $s->aggregazione ?? null,
|
|
'periodicita' => $s->periodicita ?? null,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('lavori_straordinari', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
private function stepAddebiti(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('dett_pers')) return 0;
|
|
$src = DB::connection('gescon_import')->table('dett_pers as d');
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$src = $src->leftJoin('condomin as c', 'c.cod_cond', '=', 'd.id_cond')
|
|
->addSelect('d.*', 'c.cod_stabile', 'c.scala', 'c.interno', 'c.cod_cond');
|
|
if ($this->option('stabile')) {
|
|
$src->where('c.cod_stabile', $this->option('stabile'));
|
|
}
|
|
}
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $d) {
|
|
$legacySeed = implode('|', [
|
|
(string) ($d->legacy_year ?? ''),
|
|
(string) ($d->id ?? ''),
|
|
(string) ($d->id_cond ?? ''),
|
|
(string) ($d->n_spe ?? ''),
|
|
(string) ($d->n_stra ?? ''),
|
|
(string) ($d->tipo_gestione ?? ''),
|
|
]);
|
|
$legacyUnsigned = sprintf('%u', crc32($legacySeed));
|
|
$legacy = (int) ($legacyUnsigned % 2147483647);
|
|
if ($legacy <= 0) {
|
|
$legacy = (int) $legacyUnsigned;
|
|
}
|
|
$exists = DB::table('addebiti_personalizzati')->where('legacy_id', $legacy)->first();
|
|
if ($exists) continue;
|
|
$unitaId = null;
|
|
if (Schema::hasTable('unita_immobiliari')) {
|
|
$unita = $this->findUnitaByStagingRow($d);
|
|
$unitaId = $unita->id ?? null;
|
|
}
|
|
$tabellaId = null;
|
|
if (Schema::hasTable('tabelle_millesimali') && !empty($d->tabella)) {
|
|
$tabellaId = DB::table('tabelle_millesimali')
|
|
->where('stabile_id', $this->resolveStabileIdForScala($d->scala ?? null))
|
|
->where(function ($q) use ($d) {
|
|
if (Schema::hasColumn('tabelle_millesimali', 'codice_tabella')) {
|
|
$q->orWhere('codice_tabella', $d->tabella);
|
|
}
|
|
if (Schema::hasColumn('tabelle_millesimali', 'nome_tabella')) {
|
|
$q->orWhere('nome_tabella', $d->tabella);
|
|
}
|
|
})
|
|
->value('id');
|
|
}
|
|
$ruolo = null;
|
|
if (isset($d->cond_inq)) {
|
|
$ruolo = strtoupper(trim((string) $d->cond_inq));
|
|
$ruolo = ($ruolo === 'I') ? 'I' : 'P';
|
|
}
|
|
$gestioneId = null;
|
|
if (Schema::hasTable('gestioni_contabili') && Schema::hasColumn('addebiti_personalizzati', 'gestione_id')) {
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile(
|
|
$this->resolveStabileIdForScala($d->scala ?? null),
|
|
isset($d->legacy_year) ? (string) $d->legacy_year : null,
|
|
$d->tipo_gestione ?? null,
|
|
isset($d->n_stra) ? (int) $d->n_stra : null
|
|
);
|
|
}
|
|
$data = [
|
|
'legacy_id' => $legacy,
|
|
'gestione_id' => $gestioneId,
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'tipo_gestione' => $d->tipo_gestione ?? null,
|
|
'numero_straordinaria' => $d->n_stra ?? 0,
|
|
'codice_spesa' => $d->n_spe ?? null,
|
|
'natura2' => $d->natura2 ?? null,
|
|
'importo' => $d->importo ?? 0,
|
|
'ruolo' => $ruolo,
|
|
'tabella_millesimale_id' => $tabellaId,
|
|
'is_unico' => ($d->unico ?? 0) ? true : false,
|
|
'meta' => json_encode([
|
|
'legacy_year' => $d->legacy_year ?? null,
|
|
'legacy_file' => $d->legacy_file ?? null,
|
|
'legacy_source_id' => $d->id ?? null,
|
|
'id_cond' => $d->id_cond ?? null,
|
|
'tabella' => $d->tabella ?? null,
|
|
'payload' => $d->payload ?? null,
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('addebiti_personalizzati', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
private function stepDetrazioni(?int $limit): int
|
|
{
|
|
if (!Schema::connection('gescon_import')->hasTable('detrazioni_fiscali')) return 0;
|
|
$src = DB::connection('gescon_import')->table('detrazioni_fiscali');
|
|
if ($limit) $src->limit($limit);
|
|
$count = 0;
|
|
foreach ($src->get() as $d) {
|
|
$legacy = $d->id ?? null;
|
|
if (!$legacy) continue;
|
|
$exists = DB::table('detrazioni_fiscali_dom')->where('legacy_rif', $legacy)->first();
|
|
if ($exists) continue;
|
|
$data = [
|
|
'legacy_rif' => $legacy,
|
|
'descrizione' => $d->tipo_detrazione ?? '',
|
|
'tipologia' => $d->tipo_detrazione ?? null,
|
|
'importo' => $d->importo_euro ?? 0,
|
|
'cessione_flag' => false,
|
|
'importi_json' => json_encode([
|
|
'tot' => $d->importo_detraibile ?? null,
|
|
'bon' => null,
|
|
'non_bon' => null
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('detrazioni_fiscali_dom', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
private function stub(string $table, ?int $limit): int
|
|
{
|
|
// Placeholder: sostituire con logica di lettura da MDB / staging
|
|
$exists = DB::table($table)->count();
|
|
return $limit ? min($exists, $limit) : $exists;
|
|
}
|
|
|
|
private function preflightStaging(): void
|
|
{
|
|
try {
|
|
$stab = $this->option('stabile');
|
|
if (!$stab) return;
|
|
// Autoderiva --mdb-condomin se non fornito e abbiamo --path/--anno
|
|
if (!$this->option('mdb-condomin')) {
|
|
$root = rtrim((string)($this->option('path') ?: '/mnt/gescon-archives/gescon'), '/');
|
|
$anno = $this->option('anno') ? sprintf('%04d', (int)$this->option('anno')) : null;
|
|
if ($anno) {
|
|
$cand = $root . '/' . $stab . '/' . $anno . '/singolo_anno.mdb';
|
|
if (is_file($cand)) {
|
|
// Hack: set option at runtime using Symfony input if available
|
|
try {
|
|
$this->input->setOption('mdb-condomin', $cand);
|
|
} catch (\Throwable $e) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Se manca la tabella o non ha righe per lo stabile, e abbiamo un MDB esplicito per condomin, caricalo
|
|
$hasCondTable = Schema::connection('gescon_import')->hasTable('condomin');
|
|
if (!$hasCondTable) return;
|
|
$hasCodCol = Schema::connection('gescon_import')->hasColumn('condomin', 'cod_stabile');
|
|
$needsLoad = false;
|
|
if ($hasCodCol) {
|
|
$q = DB::connection('gescon_import')->table('condomin')->where('cod_stabile', $stab);
|
|
$needsLoad = !$q->exists();
|
|
} else {
|
|
$needsLoad = DB::connection('gescon_import')->table('condomin')->count() === 0;
|
|
}
|
|
$legacyYearOpt = $this->option('anno') ? sprintf('%04d', (int)$this->option('anno')) : null;
|
|
if ($needsLoad) {
|
|
$mdbCondomin = $this->option('mdb-condomin');
|
|
if ($mdbCondomin && is_file($mdbCondomin)) {
|
|
$this->line(" ↪︎ Precarico staging condomin da MDB per stabile {$stab}...");
|
|
// Propaga limit se presente
|
|
$args = [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'condomin',
|
|
'--stabile' => $stab,
|
|
];
|
|
if ($legacyYearOpt) $args['--legacy-year'] = (string) $legacyYearOpt;
|
|
if ($this->option('limit')) $args['--limit'] = (string)$this->option('limit');
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
|
|
// Nota: partizionamento logico di condomin (unità, proprietari, inquilini) avviene negli step senza creare VIEW.
|
|
|
|
// Carica tabelle aggiuntive utili (millesimi, voci) se vuote per lo stabile
|
|
$mdbCondomin = $this->option('mdb-condomin');
|
|
if ($mdbCondomin && is_file($mdbCondomin)) {
|
|
// dett_tab
|
|
if (Schema::connection('gescon_import')->hasTable('dett_tab')) {
|
|
$need = DB::connection('gescon_import')->table('dett_tab')->count() === 0;
|
|
if ($need) {
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'dett_tab',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
]);
|
|
}
|
|
}
|
|
// tabelle_millesimali
|
|
if (Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) {
|
|
$need = DB::connection('gescon_import')->table('tabelle_millesimali')->count() === 0;
|
|
if ($need) {
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'tabelle_millesimali',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
]);
|
|
}
|
|
}
|
|
// voc_spe (voci spesa)
|
|
if (Schema::connection('gescon_import')->hasTable('voc_spe')) {
|
|
$need = DB::connection('gescon_import')->table('voc_spe')->count() === 0;
|
|
if ($need) {
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'voc_spe',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
]);
|
|
}
|
|
}
|
|
// Cre_Deb_preced (crediti/debiti pregressi)
|
|
if (Schema::connection('gescon_import')->hasTable('cre_deb_preced')) {
|
|
if (Schema::connection('gescon_import')->hasColumn('cre_deb_preced', 'cod_stabile')) {
|
|
$need = !DB::connection('gescon_import')->table('cre_deb_preced')->where('cod_stabile', $stab)->exists();
|
|
} else {
|
|
$need = DB::connection('gescon_import')->table('cre_deb_preced')->count() === 0;
|
|
}
|
|
if ($need) {
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'cre_deb_preced',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
]);
|
|
}
|
|
}
|
|
// Altre tabelle gestionali principali: operazioni, rate, incassi, giroconti, straordinarie, addebiti personalizzati, detrazioni
|
|
$more = [
|
|
'operazioni',
|
|
'rate',
|
|
'incassi',
|
|
'giri_conti',
|
|
'straordinarie',
|
|
'dett_pers',
|
|
'detrazioni_fiscali',
|
|
];
|
|
foreach ($more as $tb) {
|
|
if (Schema::connection('gescon_import')->hasTable($tb)) {
|
|
$need = DB::connection('gescon_import')->table($tb)->count() === 0;
|
|
if ($need) {
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => $tb,
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Non bloccare la pipeline se il precaricamento fallisce
|
|
if ($this->output->isVerbose()) {
|
|
$this->warn('Preflight staging fallito: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private function createStabileFromMdbIfNeeded(string $mdbPath, string $codRichiesto): int
|
|
{
|
|
$col = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : (Schema::hasColumn('stabili', 'denominazione') ? 'denominazione' : null);
|
|
if (!$col) return 0;
|
|
$exists = DB::table('stabili')->where($col, $codRichiesto)->first();
|
|
if (!$exists && $col === 'codice_stabile') {
|
|
$alt = ltrim($codRichiesto, '0');
|
|
if ($alt !== '' && $alt !== $codRichiesto) {
|
|
$exists = DB::table('stabili')->where($col, $alt)->first();
|
|
}
|
|
}
|
|
if ($exists) return 0;
|
|
$row = $this->mdbStabiliFindByDirectory($mdbPath, $codRichiesto);
|
|
if (!$row) return 0;
|
|
$payload = [
|
|
$col => $codRichiesto,
|
|
'denominazione' => $row['denominazione'] ?? $codRichiesto,
|
|
'indirizzo' => $row['indirizzo'] ?? 'ND',
|
|
'citta' => $row['citta'] ?? 'ND',
|
|
'cap' => $row['cap'] ?? '00000',
|
|
'provincia' => $row['provincia'] ?? 'XX',
|
|
'codice_fiscale' => $row['cf_condominio'] ?? null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
// Tag provenienza
|
|
if (Schema::hasColumn('stabili', 'note')) {
|
|
$payload['note'] = '[GESCON]';
|
|
}
|
|
// Inserisci (no-op in dry-run)
|
|
$this->dynamicInsert('stabili', $payload);
|
|
// Conta comunque come creato per feedback
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(" -> creato stabile da MDB: {$codRichiesto}" . ($this->isDryRun ? ' (DRY-RUN)' : ''));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// === Helper contestuali ===
|
|
private ?int $stabileId = null;
|
|
private int $protocolCounter = 0;
|
|
private bool $isDryRun = false;
|
|
private array $scalaStabileCache = [];
|
|
|
|
private function normalizeNameKey(?string $nome, ?string $cognome): string
|
|
{
|
|
$full = trim(strtoupper(trim((string)($cognome ?? '')) . ' ' . trim((string)($nome ?? ''))));
|
|
$full = preg_replace('/\s+/', ' ', $full);
|
|
$full = preg_replace('/[^A-Z0-9 ]/', '', $full);
|
|
return trim($full);
|
|
}
|
|
|
|
private function findExistingSoggetto(?string $cf, ?string $email, ?string $nome, ?string $cognome)
|
|
{
|
|
$q = DB::table('soggetti');
|
|
if ($cf) {
|
|
$found = (clone $q)->where('codice_fiscale', $cf)->first();
|
|
if ($found) return $found;
|
|
}
|
|
if ($email) {
|
|
$found = DB::table('soggetti')->where('email', $email)->first();
|
|
if ($found) return $found;
|
|
}
|
|
$key = $this->normalizeNameKey($nome, $cognome);
|
|
if ($key) {
|
|
$found = DB::table('soggetti')->whereRaw("UPPER(CONCAT(TRIM(COALESCE(cognome,'')),' ',TRIM(COALESCE(nome,'')))) = ?", [$key])->first();
|
|
if ($found) return $found;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function dedupSoggettiBy(string $field): void
|
|
{
|
|
if (!Schema::hasTable('soggetti') || !in_array($field, Schema::getColumnListing('soggetti'))) return;
|
|
$dups = DB::table('soggetti')
|
|
->select($field, DB::raw('COUNT(*) as c'))
|
|
->whereNotNull($field)
|
|
->where($field, '!=', '')
|
|
->groupBy($field)
|
|
->having('c', '>', 1)
|
|
->get();
|
|
foreach ($dups as $d) {
|
|
$rows = DB::table('soggetti')->where($field, $d->$field)->orderBy('id')->get();
|
|
$keep = $rows->first();
|
|
foreach ($rows->skip(1) as $rm) {
|
|
// Riassegna proprieta
|
|
if (Schema::hasTable('proprieta')) {
|
|
DB::table('proprieta')->where('soggetto_id', $rm->id)->update(['soggetto_id' => $keep->id]);
|
|
}
|
|
// Unisci eventuali campi mancanti
|
|
$update = [];
|
|
foreach (['nome', 'cognome', 'email', 'telefono', 'indirizzo', 'codice_fiscale', 'tipo'] as $f) {
|
|
if (empty($keep->$f) && !empty($rm->$f)) {
|
|
$update[$f] = $rm->$f;
|
|
}
|
|
}
|
|
if (!empty($update)) {
|
|
DB::table('soggetti')->where('id', $keep->id)->update($update);
|
|
}
|
|
// Elimina duplicato
|
|
DB::table('soggetti')->where('id', $rm->id)->delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function findUnitaByStagingRow($r)
|
|
{
|
|
if (!Schema::hasTable('unita_immobiliari')) return null;
|
|
$crit = [];
|
|
if ($this->stabileId !== null && Schema::hasColumn('unita_immobiliari', 'stabile_id')) {
|
|
$crit['stabile_id'] = $this->stabileId;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'scala') && isset($r->scala)) {
|
|
$crit['scala'] = $r->scala;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'interno')) {
|
|
$int = isset($r->interno) ? (string)$r->interno : null;
|
|
if (is_string($int) && preg_match('/^\s*CAN\s*(\d+)\s*$/i', $int, $m)) {
|
|
$int = 'CAN' . $m[1];
|
|
}
|
|
if (($int === null || trim($int) === '') && isset($r->cod_cond)) {
|
|
$int = (string) $r->cod_cond;
|
|
}
|
|
if ($int !== null) {
|
|
$crit['interno'] = $int;
|
|
}
|
|
}
|
|
if (!empty($crit)) {
|
|
return DB::table('unita_immobiliari')->where($crit)->first();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function queryMdbSingle(string $mdbPath, string $sql): ?array
|
|
{
|
|
$cmd = 'mdb-sql ' . escapeshellarg($mdbPath) . ' <<< ' . escapeshellarg($sql);
|
|
$out = shell_exec($cmd);
|
|
if (!$out) return null;
|
|
$lines = array_values(array_filter(array_map('trim', explode("\n", $out))));
|
|
if (count($lines) < 3) return null;
|
|
// Try to detect header and data rows (very naive parser)
|
|
$headers = [];
|
|
$data = [];
|
|
foreach ($lines as $i => $line) {
|
|
if ($i === 0 || stripos($line, '\t') !== false) {
|
|
$headers = array_map('trim', preg_split('/\s+/', $line));
|
|
break;
|
|
}
|
|
}
|
|
// Fallback: use space-split headers might be unreliable; try tab-split on last 2 lines
|
|
$last = end($lines);
|
|
$prev = prev($lines);
|
|
if (!$headers && $prev) {
|
|
$headers = array_map('trim', preg_split('/\t+/', $prev));
|
|
$data = array_map('trim', preg_split('/\t+/', $last));
|
|
} else {
|
|
$data = array_map('trim', preg_split('/\s+/', $last));
|
|
}
|
|
if (!$headers || !$data || count($headers) !== count($data)) return null;
|
|
return array_combine($headers, $data);
|
|
}
|
|
|
|
private function mdbStabiliFindByDirectory(string $mdbPath, string $directoryCode): ?array
|
|
{
|
|
// Robust export: pipe delimiter and quoted fields to preserve newlines within fields
|
|
$table = 'Stabili';
|
|
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
|
$cmd = sprintf(
|
|
'mdb-export -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
|
|
escapeshellarg('%Y-%m-%d'),
|
|
escapeshellarg('|'),
|
|
escapeshellarg('"'),
|
|
escapeshellarg($mdbPath),
|
|
escapeshellarg($table),
|
|
escapeshellarg($tmp)
|
|
);
|
|
@shell_exec($cmd);
|
|
if (!is_file($tmp) || filesize($tmp) === 0) {
|
|
@unlink($tmp);
|
|
return null;
|
|
}
|
|
$fh = fopen($tmp, 'r');
|
|
if (!$fh) {
|
|
@unlink($tmp);
|
|
return null;
|
|
}
|
|
$headers = fgetcsv($fh, 0, '|');
|
|
if (!$headers) {
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
return null;
|
|
}
|
|
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
|
|
$code = trim($directoryCode);
|
|
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
|
|
if ($cols === [null] || $cols === false) continue;
|
|
$countH = count($headers);
|
|
if (count($cols) < $countH) $cols = array_pad($cols, $countH, null);
|
|
if (count($cols) > $countH) $cols = array_slice($cols, 0, $countH);
|
|
$row = @array_combine($headers, $cols);
|
|
if (!$row) continue;
|
|
$nomeDir = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null));
|
|
if (!$nomeDir || trim((string)$nomeDir) !== $code) continue;
|
|
|
|
// Prefer direct columns if exist
|
|
$cfCondo = $row['cf_condominio'] ?? $row['cod_fisc'] ?? null;
|
|
$cfAmm = $row['cin'] ?? $row['cod_fisc_amministratore'] ?? null;
|
|
|
|
// Heuristics over all values to fill missing
|
|
$allText = strtoupper(implode(' ', array_map(fn($v) => (string)$v, $row)));
|
|
if (!$cfCondo && preg_match('/(\d\D*){11}/', preg_replace('/\D+/', '', $allText))) {
|
|
// Extract contiguous 11 digits
|
|
if (preg_match('/\d{11}/', $allText, $m)) {
|
|
$cfCondo = $m[0];
|
|
}
|
|
}
|
|
if (!$cfAmm) {
|
|
if (preg_match('/(?=.*[A-Z])[A-Z0-9]{16}/', $allText, $m)) {
|
|
$cfAmm = $m[0];
|
|
}
|
|
}
|
|
|
|
$den = $row['denominazione'] ?? ($row['fe_denominazione'] ?? null);
|
|
$indir = $row['indirizzo'] ?? ($row['indir_autorizzazione'] ?? ($row['indir_autorizzazione_2'] ?? null));
|
|
$cap = $row['cap'] ?? null;
|
|
$citta = $row['citta'] ?? null;
|
|
$pr = $row['pr'] ?? ($row['catasto_pr'] ?? null);
|
|
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
return [
|
|
'denominazione' => $den,
|
|
'indirizzo' => $indir,
|
|
'cap' => $cap,
|
|
'citta' => $citta,
|
|
'provincia' => $pr,
|
|
// Provide both when possible
|
|
'cf_condominio' => $cfCondo,
|
|
'cf_amministratore' => $cfAmm,
|
|
// Keep a generic for mapping
|
|
'codice_fiscale' => $cfCondo ?? $cfAmm,
|
|
'cin' => $cfAmm,
|
|
];
|
|
}
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
return null;
|
|
}
|
|
|
|
private function resolveStabileId(): ?int
|
|
{
|
|
$cod = $this->option('stabile');
|
|
if (!$cod) return null;
|
|
if (!Schema::hasTable('stabili')) return null;
|
|
$col = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : (Schema::hasColumn('stabili', 'denominazione') ? 'denominazione' : null);
|
|
if (!$col) return null;
|
|
$row = DB::table('stabili')->where($col, $cod)->first();
|
|
if (!$row && $col === 'codice_stabile') {
|
|
$alt = ltrim($cod, '0');
|
|
if ($alt !== '' && $alt !== $cod) {
|
|
$row = DB::table('stabili')->where($col, $alt)->first();
|
|
}
|
|
}
|
|
// Se esiste ma è richiesto un amministratore specifico, aggiorna l'owner (solo se cambia)
|
|
if ($row) {
|
|
$optAdm = $this->option('amministratore-id');
|
|
if ($optAdm && Schema::hasColumn('stabili', 'amministratore_id')) {
|
|
$target = (int)$optAdm;
|
|
if ((int)($row->amministratore_id ?? 0) !== $target && !$this->option('dry-run')) {
|
|
DB::table('stabili')->where('id', $row->id)->update([
|
|
'amministratore_id' => $target,
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
return $row->id;
|
|
}
|
|
// In dry-run do not create anything, just return null and let steps continue.
|
|
if ($this->option('dry-run')) {
|
|
return null;
|
|
}
|
|
// Ensure minimal amministratore if schema requires it
|
|
$amminId = null;
|
|
if (Schema::hasColumn('stabili', 'amministratore_id')) {
|
|
// Preferisci un amministratore passato via opzione
|
|
$optAdm = $this->option('amministratore-id');
|
|
if ($optAdm && DB::table('amministratori')->where('id', (int)$optAdm)->exists()) {
|
|
$amminId = (int)$optAdm;
|
|
} else {
|
|
$ammin = DB::table('amministratori')->first();
|
|
if (!$ammin) {
|
|
// Ensure there is at least one user
|
|
$user = DB::table('users')->first();
|
|
if (!$user) {
|
|
$userId = DB::table('users')->insertGetId([
|
|
'name' => 'Admin Seed',
|
|
'email' => 'admin-seed@example.local',
|
|
'password' => bcrypt('password'),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
} else {
|
|
$userId = $user->id;
|
|
}
|
|
$amminId = DB::table('amministratori')->insertGetId([
|
|
'nome' => 'Admin',
|
|
'cognome' => 'Seed',
|
|
'user_id' => $userId,
|
|
'denominazione_studio' => 'Seed Studio',
|
|
'codice_univoco' => $this->generateCode('A', 8),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
} else {
|
|
$amminId = $ammin->id;
|
|
}
|
|
}
|
|
}
|
|
// Create minimal stabile row compatible with either schema
|
|
$data = [
|
|
$col => $cod,
|
|
'denominazione' => $cod,
|
|
'indirizzo' => 'ND',
|
|
'citta' => 'ND',
|
|
'cap' => '00000',
|
|
'provincia' => 'XX',
|
|
'attivo' => true,
|
|
'amministratore_id' => $amminId,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
$this->dynamicInsert('stabili', $data);
|
|
$row = DB::table('stabili')->where($col, $cod)->first();
|
|
return $row?->id;
|
|
}
|
|
|
|
private function generateCode(string $prefix, int $len = 6): string
|
|
{
|
|
return $prefix . str_pad((string)random_int(0, 10 ** $len - 1), $len, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
private function generateStableCondKey(string $prefix, $stabile, $cond, int $len = 8): string
|
|
{
|
|
$len = max(1, $len);
|
|
$prefix = (string)$prefix;
|
|
$usable = max(1, $len - strlen($prefix));
|
|
$raw = (string)$stabile . '|' . (string)$cond;
|
|
// Use CRC32B for a short, deterministic hex digest; uppercase for consistency
|
|
$digest = strtoupper(substr(hash('crc32b', $raw), 0, $usable));
|
|
// Ensure exact length
|
|
if (strlen($digest) < $usable) {
|
|
$digest = str_pad($digest, $usable, '0');
|
|
}
|
|
return $prefix . $digest;
|
|
}
|
|
|
|
private function nextProtocolNumber(): int
|
|
{
|
|
return ++$this->protocolCounter;
|
|
}
|
|
|
|
private function safeOldId($old)
|
|
{
|
|
if (!$old) return null;
|
|
// If already used by another soggetto, skip to avoid unique violation
|
|
$exists = Schema::hasTable('soggetti') ? DB::table('soggetti')->where('old_id', $old)->exists() : false;
|
|
return $exists ? null : $old;
|
|
}
|
|
|
|
private function resolveAnnoFromLegacyYear(?string $legacyYear): ?string
|
|
{
|
|
$legacyYear = is_string($legacyYear) ? trim($legacyYear) : '';
|
|
if ($legacyYear === '') {
|
|
return null;
|
|
}
|
|
if (preg_match('/^\d{4}$/', $legacyYear) && (int) $legacyYear >= 1900) {
|
|
return $legacyYear;
|
|
}
|
|
|
|
if (!$this->legacyYearMapLoaded) {
|
|
$this->legacyYearMapLoaded = true;
|
|
$this->legacyYearMap = [];
|
|
|
|
$stabileCode = (string) ($this->option('stabile') ?? '');
|
|
$root = rtrim((string) ($this->option('path') ?: '/mnt/gescon-archives/gescon'), '/');
|
|
if ($stabileCode !== '') {
|
|
$generale = $root . '/' . $stabileCode . '/generale_stabile.mdb';
|
|
$rows = $this->mdbExportToRows($generale, ['anni', 'Anni', 'ANNI']);
|
|
foreach ($rows as $row) {
|
|
$dir = trim((string) ($row['nome_dir'] ?? $row['nome_dir '] ?? ''));
|
|
$anno = trim((string) ($row['anno_o'] ?? $row['anno'] ?? $row['anno_gestione'] ?? ''));
|
|
if ($dir !== '' && $anno !== '') {
|
|
$this->legacyYearMap[$dir] = $anno;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->legacyYearMap[$legacyYear] ?? null;
|
|
}
|
|
|
|
private function normalizeLegacyDate(?string $value): ?string
|
|
{
|
|
if (!is_string($value)) {
|
|
return null;
|
|
}
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
$ts = strtotime($value);
|
|
if ($ts === false) {
|
|
return null;
|
|
}
|
|
return date('Y-m-d', $ts);
|
|
}
|
|
|
|
private function upsertUnitaNominativiFromLegacy(int $unitaId, int $stabileId, ?string $legacyCondId, object $row): void
|
|
{
|
|
if (!Schema::hasTable('unita_immobiliare_nominativi')) {
|
|
return;
|
|
}
|
|
|
|
$payloads = [];
|
|
|
|
$ownerStart = $this->normalizeLegacyDate($row->subentrato_dal ?? $row->proprietario_subentrato_dal ?? null);
|
|
$ownerEnd = $this->normalizeLegacyDate($row->attivo_fino_al ?? $row->proprietario_attivo_fino_al ?? null);
|
|
|
|
$subentroPrima = trim((string) ($row->subentro_prima_cera ?? ''));
|
|
$subentroAdesso = trim((string) ($row->subentro_adesso_ce ?? ''));
|
|
|
|
$resolveOwnerName = function (?object $source) use ($row): ?string {
|
|
$src = $source ?: $row;
|
|
$name = trim((string) ($src->proprietario_denominazione ?? $src->nom_cond ?? ''));
|
|
if ($name === '') {
|
|
$name = trim((string) (($src->cognome ?? '') . ' ' . ($src->nome ?? '')));
|
|
}
|
|
return $name !== '' ? $name : null;
|
|
};
|
|
|
|
$resolveLegacyRow = function (string $idCond) use ($row): ?object {
|
|
if ($idCond === '') {
|
|
return null;
|
|
}
|
|
$query = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->where('id_cond', $idCond);
|
|
|
|
if (!empty($row->cod_stabile)) {
|
|
$query->where('cod_stabile', $row->cod_stabile);
|
|
}
|
|
|
|
if (property_exists($row, 'legacy_year') && !empty($row->legacy_year)) {
|
|
$query->where('legacy_year', $row->legacy_year);
|
|
}
|
|
|
|
return $query->first();
|
|
};
|
|
|
|
$ownerPerc = isset($row->proprietario_diritto_percentuale) && is_numeric($row->proprietario_diritto_percentuale)
|
|
? (float) $row->proprietario_diritto_percentuale
|
|
: null;
|
|
|
|
if ($subentroPrima !== '') {
|
|
$oldRow = $resolveLegacyRow($subentroPrima);
|
|
$oldName = $resolveOwnerName($oldRow);
|
|
$oldStart = $this->normalizeLegacyDate($oldRow?->subentrato_dal ?? null);
|
|
$oldEnd = $ownerStart ?: $this->normalizeLegacyDate($oldRow?->attivo_fino_al ?? null);
|
|
|
|
if ($oldName || $oldStart || $oldEnd) {
|
|
$payloads[] = [
|
|
'ruolo' => 'C',
|
|
'nominativo' => $oldName,
|
|
'data_inizio' => $oldStart,
|
|
'data_fine' => $oldEnd,
|
|
'percentuale' => $ownerPerc,
|
|
'legacy_cond_id' => $subentroPrima,
|
|
];
|
|
}
|
|
}
|
|
|
|
$newOwnerRow = $subentroAdesso !== '' ? $resolveLegacyRow($subentroAdesso) : null;
|
|
$newOwnerName = $resolveOwnerName($newOwnerRow);
|
|
$newOwnerStart = $ownerStart ?: $this->normalizeLegacyDate($newOwnerRow?->subentrato_dal ?? null);
|
|
$newOwnerEnd = $ownerEnd ?: $this->normalizeLegacyDate($newOwnerRow?->attivo_fino_al ?? null);
|
|
|
|
if (!empty($newOwnerName) || $newOwnerStart || $newOwnerEnd) {
|
|
$payloads[] = [
|
|
'ruolo' => 'C',
|
|
'nominativo' => $newOwnerName,
|
|
'data_inizio' => $newOwnerStart,
|
|
'data_fine' => $newOwnerEnd,
|
|
'percentuale' => $ownerPerc,
|
|
'legacy_cond_id' => $subentroAdesso !== '' ? $subentroAdesso : $legacyCondId,
|
|
];
|
|
}
|
|
|
|
$fallbackOwnerName = $resolveOwnerName(null);
|
|
if (empty($payloads) && ($fallbackOwnerName || $ownerStart || $ownerEnd)) {
|
|
$payloads[] = [
|
|
'ruolo' => 'C',
|
|
'nominativo' => $fallbackOwnerName,
|
|
'data_inizio' => $ownerStart,
|
|
'data_fine' => $ownerEnd,
|
|
'percentuale' => $ownerPerc,
|
|
'legacy_cond_id' => $legacyCondId,
|
|
];
|
|
}
|
|
|
|
$tenantName = trim((string) ($row->inquilino_denominazione ?? $row->inquil_nome ?? $row->inquil ?? ''));
|
|
$tenantStart = $this->normalizeLegacyDate($row->inquil_dal ?? $row->inquilino_subentrato_dal ?? null);
|
|
$tenantEnd = $this->normalizeLegacyDate($row->inquil_al ?? $row->inquilino_attivo_fino_al ?? null);
|
|
$tenantPerc = isset($row->inquilino_diritto_percentuale) && is_numeric($row->inquilino_diritto_percentuale)
|
|
? (float) $row->inquilino_diritto_percentuale
|
|
: null;
|
|
|
|
if (!empty($tenantName) || $tenantStart || $tenantEnd) {
|
|
$payloads[] = [
|
|
'ruolo' => 'I',
|
|
'nominativo' => $tenantName !== '' ? $tenantName : null,
|
|
'data_inizio' => $tenantStart,
|
|
'data_fine' => $tenantEnd,
|
|
'percentuale' => $tenantPerc,
|
|
'legacy_cond_id' => $legacyCondId,
|
|
];
|
|
}
|
|
|
|
foreach ($payloads as $p) {
|
|
$match = [
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'stabile_id' => $stabileId,
|
|
'legacy_cond_id' => $p['legacy_cond_id'] ?? $legacyCondId,
|
|
'ruolo' => $p['ruolo'],
|
|
'data_inizio' => $p['data_inizio'],
|
|
'data_fine' => $p['data_fine'],
|
|
];
|
|
|
|
$exists = DB::table('unita_immobiliare_nominativi')->where($match)->first();
|
|
$values = array_merge($match, [
|
|
'nominativo' => $p['nominativo'],
|
|
'percentuale' => $p['percentuale'],
|
|
'fonte' => 'legacy_condomin',
|
|
'legacy_payload' => json_encode([
|
|
'legacy_year' => $row->legacy_year ?? null,
|
|
'cod_cond' => $match['legacy_cond_id'],
|
|
'subentro_prima_cera' => $subentroPrima !== '' ? $subentroPrima : null,
|
|
'subentro_adesso_ce' => $subentroAdesso !== '' ? $subentroAdesso : null,
|
|
]),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
if ($exists) {
|
|
DB::table('unita_immobiliare_nominativi')->where('id', $exists->id)->update($values);
|
|
} else {
|
|
$values['created_at'] = now();
|
|
DB::table('unita_immobiliare_nominativi')->insert($values);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function normalizeLegacyCalcolo(?string $raw): ?string
|
|
{
|
|
$v = strtolower(trim((string) ($raw ?? '')));
|
|
if ($v === '') {
|
|
return null;
|
|
}
|
|
|
|
return match ($v) {
|
|
'm' => 'millesimi',
|
|
'a', 'c' => 'a_consumo',
|
|
'x' => 'conguagli',
|
|
'p' => 'personali',
|
|
default => $v,
|
|
};
|
|
}
|
|
|
|
private function normalizeLegacyTipo(?string $raw): ?string
|
|
{
|
|
$v = strtoupper(trim((string) ($raw ?? '')));
|
|
if ($v === '') {
|
|
return null;
|
|
}
|
|
|
|
return match ($v) {
|
|
'O', 'ORD', 'ORDINARIA' => 'O',
|
|
'R', 'RISC', 'RISCALDAMENTO' => 'R',
|
|
'S', 'STRA', 'STRAORDINARIA' => 'S',
|
|
default => $v,
|
|
};
|
|
}
|
|
|
|
private function resolveTipoTabellaFromLegacy(?string $tipoLegacy, ?string $calcolo, ?string $codice): ?string
|
|
{
|
|
$c = strtolower(trim((string) ($calcolo ?? '')));
|
|
$code = strtoupper(trim((string) ($codice ?? '')));
|
|
if ($code === 'ACQUA' || in_array($c, ['a_consumo', 'acqua', 'consumo', 'c', 'a'], true)) {
|
|
return 'custom';
|
|
}
|
|
|
|
return match ($tipoLegacy) {
|
|
'R' => 'riscaldamento',
|
|
default => 'custom',
|
|
};
|
|
}
|
|
|
|
private function resolveGestioneContabileIdForStabile(?int $stabileId, ?string $legacyYear, ?string $tipoLegacy, ?int $numeroStraordinaria = null): ?int
|
|
{
|
|
if (!Schema::hasTable('gestioni_contabili')) {
|
|
return null;
|
|
}
|
|
|
|
$anno = $this->resolveAnnoFromLegacyYear($legacyYear) ?: (is_string($this->option('anno')) ? (string) $this->option('anno') : null);
|
|
if (!$anno || !is_numeric($anno)) {
|
|
return null;
|
|
}
|
|
|
|
$tipoLegacy = is_string($tipoLegacy) ? strtoupper(trim($tipoLegacy)) : null;
|
|
$tipo = match ($tipoLegacy) {
|
|
'R', 'RISCALDAMENTO' => 'riscaldamento',
|
|
'S', 'STRAORDINARIA' => 'straordinaria',
|
|
default => 'ordinaria',
|
|
};
|
|
|
|
$q = DB::table('gestioni_contabili')
|
|
->where('anno_gestione', (int) $anno)
|
|
->where('tipo_gestione', $tipo);
|
|
|
|
if ($stabileId && Schema::hasColumn('gestioni_contabili', 'stabile_id')) {
|
|
$q->where('stabile_id', $stabileId);
|
|
}
|
|
if ($tipo === 'straordinaria' && $numeroStraordinaria !== null && Schema::hasColumn('gestioni_contabili', 'numero_straordinaria')) {
|
|
$q->where('numero_straordinaria', $numeroStraordinaria);
|
|
}
|
|
|
|
$match = $q->orderByDesc('gestione_attiva')->orderByDesc('id')->first();
|
|
if ($match) {
|
|
return $match->id;
|
|
}
|
|
|
|
if ($this->option('dry-run')) {
|
|
return null;
|
|
}
|
|
|
|
$payload = [
|
|
'tenant_id' => 'T1',
|
|
'stabile_id' => $stabileId,
|
|
'anno_gestione' => (int) $anno,
|
|
'tipo_gestione' => $tipo,
|
|
'numero_straordinaria' => $tipo === 'straordinaria' ? ($numeroStraordinaria ?? 1) : null,
|
|
'denominazione' => strtoupper(substr($tipo, 0, 1)) . $anno,
|
|
'data_inizio' => date('Y-01-01', strtotime($anno . '-01-01')),
|
|
'data_fine' => date('Y-12-31', strtotime($anno . '-12-31')),
|
|
'stato' => 'aperta',
|
|
'protocollo_prefix' => strtoupper(substr($tipo, 0, 1)) . $anno . ($tipo === 'straordinaria' ? '-' . str_pad((string) ($numeroStraordinaria ?? 1), 2, '0', STR_PAD_LEFT) : ''),
|
|
'ultimo_protocollo' => 0,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$cols = Schema::getColumnListing('gestioni_contabili');
|
|
$insert = array_intersect_key($payload, array_flip($cols));
|
|
DB::table('gestioni_contabili')->insert($insert);
|
|
|
|
$match = DB::table('gestioni_contabili')
|
|
->where('anno_gestione', (int) $anno)
|
|
->where('tipo_gestione', $tipo)
|
|
->when($stabileId && Schema::hasColumn('gestioni_contabili', 'stabile_id'), fn($qq) => $qq->where('stabile_id', $stabileId))
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
return $match?->id;
|
|
}
|
|
|
|
private function resolveGestioneId($anno, $compet, $tipo): ?int
|
|
{
|
|
if (!Schema::hasTable('gestioni_contabili')) return null;
|
|
// Normalize tipo from O/R/S to descriptive if needed
|
|
$map = ['O' => 'ordinaria', 'R' => 'riscaldamento', 'S' => 'straordinaria'];
|
|
$tipo = $map[$tipo ?? 'O'] ?? ($tipo ?: 'ordinaria');
|
|
$anno = $anno ?: date('Y');
|
|
$row = DB::table('gestioni_contabili')
|
|
->where(['anno_gestione' => (int)$anno, 'tipo_gestione' => $tipo])
|
|
->first();
|
|
if ($row) return $row->id;
|
|
// In dry-run, avoid creating missing gestione
|
|
if ($this->option('dry-run')) return null;
|
|
// Create minimal compatible record satisfying not-null constraints
|
|
$data = [
|
|
'tenant_id' => 'T1',
|
|
'anno_gestione' => (int)$anno,
|
|
'tipo_gestione' => $tipo,
|
|
'numero_straordinaria' => null,
|
|
'denominazione' => strtoupper(substr($tipo, 0, 1)) . $anno,
|
|
'data_inizio' => date('Y-01-01', strtotime($anno . '-01-01')),
|
|
'data_fine' => date('Y-12-31', strtotime($anno . '-12-31')),
|
|
'stato' => 'aperta',
|
|
'protocollo_prefix' => strtoupper(substr($tipo, 0, 1)) . $anno,
|
|
'ultimo_protocollo' => 0,
|
|
'created_at' => now(),
|
|
'updated_at' => now()
|
|
];
|
|
DB::table('gestioni_contabili')->insert($data);
|
|
$row = DB::table('gestioni_contabili')->where(['anno_gestione' => (int)$anno, 'tipo_gestione' => $tipo])->first();
|
|
return $row?->id;
|
|
}
|
|
|
|
private function dynamicInsert(string $table, array $data): void
|
|
{
|
|
if (!Schema::hasTable($table)) return; // silently skip
|
|
if ($this->isDryRun) return; // skip writes in dry-run
|
|
$cols = Schema::getColumnListing($table);
|
|
$filtered = array_intersect_key($data, array_flip($cols));
|
|
if (!isset($filtered['created_at']) && in_array('created_at', $cols)) $filtered['created_at'] = now();
|
|
if (!isset($filtered['updated_at']) && in_array('updated_at', $cols)) $filtered['updated_at'] = now();
|
|
if (!empty($filtered)) DB::table($table)->insert($filtered);
|
|
}
|
|
|
|
/**
|
|
* Risolve l'ID del conto bancario di default per uno stabile.
|
|
* Preferisce il conto con IBAN che coincide con l'IBAN dello stabile,
|
|
* altrimenti il primo conto attivo per lo stabile.
|
|
*/
|
|
private function resolveDefaultContoBancarioId(?int $stabileId): ?int
|
|
{
|
|
if (!$stabileId || !Schema::hasTable('dati_bancari')) return null;
|
|
if (isset($this->defaultContoCache[$stabileId])) return $this->defaultContoCache[$stabileId];
|
|
|
|
// 1) Prova match per IBAN tra stabile e dati_bancari
|
|
$ibanStabile = $this->getIbanForStabile($stabileId);
|
|
if ($ibanStabile) {
|
|
$row = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->whereRaw('REPLACE(UPPER(iban)," ","") = ?', [$ibanStabile])
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
if ($row) {
|
|
return $this->defaultContoCache[$stabileId] = (int)$row->id;
|
|
}
|
|
}
|
|
|
|
// 2) Fallback: primo conto attivo per stabile
|
|
$row = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->when(Schema::hasColumn('dati_bancari', 'stato_conto'), function ($q) {
|
|
$q->where('stato_conto', 'attivo');
|
|
})
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
if ($row) {
|
|
return $this->defaultContoCache[$stabileId] = (int)$row->id;
|
|
}
|
|
|
|
// 3) Ultimo fallback: qualsiasi conto dello stabile
|
|
$row = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
return $this->defaultContoCache[$stabileId] = ($row ? (int)$row->id : null);
|
|
}
|
|
|
|
/**
|
|
* Recupera IBAN normalizzato per lo stabile dalle colonne disponibili.
|
|
*/
|
|
private function getIbanForStabile(?int $stabileId): ?string
|
|
{
|
|
if (!$stabileId || !Schema::hasTable('stabili')) return null;
|
|
$cols = Schema::getColumnListing('stabili');
|
|
$candidate = null;
|
|
foreach (['iban_condominio', 'iban_principale', 'iban'] as $col) {
|
|
if (in_array($col, $cols, true)) {
|
|
$val = DB::table('stabili')->where('id', $stabileId)->value($col);
|
|
if ($val) {
|
|
$candidate = $val;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!$candidate) return null;
|
|
$iban = strtoupper(preg_replace('/\s+/', '', (string)$candidate));
|
|
return $iban ?: null;
|
|
}
|
|
|
|
private function ensureStabilePerScala($parentStabile, string $scala): array
|
|
{
|
|
// Ritorna [childId, childCode]
|
|
if (!Schema::hasTable('stabili')) return [null, null];
|
|
$col = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$parentCode = $col === 'codice_stabile' ? ($parentStabile->codice_stabile ?? null) : ($parentStabile->denominazione ?? null);
|
|
if (!$parentCode) return [$parentStabile->id ?? null, $parentCode];
|
|
$childCode = rtrim($parentCode, '-') . '-' . strtoupper($scala ?: 'A');
|
|
if (isset($this->scalaStabileCache[$childCode])) return $this->scalaStabileCache[$childCode];
|
|
$child = DB::table('stabili')->where($col, $childCode)->first();
|
|
if (!$child && !$this->isDryRun) {
|
|
// Crea minimale ereditando alcuni campi
|
|
$payload = [
|
|
$col => $childCode,
|
|
'denominazione' => ($parentStabile->denominazione ?? $childCode) . ' - Scala ' . strtoupper($scala ?: 'A'),
|
|
'indirizzo' => $parentStabile->indirizzo ?? 'ND',
|
|
'citta' => $parentStabile->citta ?? 'ND',
|
|
'cap' => $parentStabile->cap ?? '00000',
|
|
'provincia' => $parentStabile->provincia ?? 'XX',
|
|
'amministratore_id' => $parentStabile->amministratore_id ?? null,
|
|
'attivo' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('stabili', 'note')) {
|
|
$payload['note'] = trim((string)($parentStabile->note ?? '')) . ' [GESCON SCALA]';
|
|
}
|
|
$this->dynamicInsert('stabili', $payload);
|
|
$child = DB::table('stabili')->where($col, $childCode)->first();
|
|
}
|
|
$res = [$child->id ?? null, $childCode];
|
|
$this->scalaStabileCache[$childCode] = $res;
|
|
return $res;
|
|
}
|
|
|
|
private function resolveStabileIdForScala(?string $scala): ?int
|
|
{
|
|
if ($this->splitMode !== 'stabili-per-scala') return $this->stabileId;
|
|
$scala = trim((string)($scala ?? 'A')) ?: 'A';
|
|
if (!Schema::hasTable('stabili')) return $this->stabileId;
|
|
$col = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$parentCode = $this->option('stabile');
|
|
if (!$parentCode) return $this->stabileId;
|
|
$childCode = rtrim($parentCode, '-') . '-' . strtoupper($scala);
|
|
$row = DB::table('stabili')->where($col, $childCode)->first();
|
|
if ($row) return $row->id;
|
|
return $this->stabileId;
|
|
}
|
|
|
|
private function safeUpdateStabile(int $id, array $update): void
|
|
{
|
|
if (empty($update)) return;
|
|
if ($this->isDryRun) {
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(" (dry-run) update stabili skip for id {$id}");
|
|
}
|
|
return;
|
|
}
|
|
// Gestisci unicità codice_fiscale con riassegnazione se --force-mapping
|
|
if (isset($update['codice_fiscale']) && Schema::hasColumn('stabili', 'codice_fiscale')) {
|
|
$cf = trim((string)$update['codice_fiscale']);
|
|
if ($cf !== '') {
|
|
$other = DB::table('stabili')->where('codice_fiscale', $cf)->where('id', '!=', $id)->first();
|
|
if ($other) {
|
|
if ($this->forceMapping) {
|
|
// Libera il CF dall'altro stabile
|
|
try {
|
|
DB::table('stabili')->where('id', $other->id)->update(['codice_fiscale' => null, 'updated_at' => now()]);
|
|
} catch (\Throwable $e) {
|
|
// Fallback a 'ND' se NOT NULL
|
|
DB::table('stabili')->where('id', $other->id)->update(['codice_fiscale' => 'ND', 'updated_at' => now()]);
|
|
}
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(sprintf(' -> riassegnazione CF: liberato da stabile #%d per assegnare a #%d', $other->id, $id));
|
|
}
|
|
} else {
|
|
// Evita violazione: non aggiornare CF se già in uso
|
|
unset($update['codice_fiscale']);
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(' -> CF già in uso da altro stabile: aggiornamento CF ignorato (no force).');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!empty($update)) {
|
|
DB::table('stabili')->where('id', $id)->update($update);
|
|
}
|
|
}
|
|
|
|
private function applyStabileMapping(array $update, array $legacyRow, $existing): array
|
|
{
|
|
try {
|
|
$aliases = [
|
|
'comune' => 'citta',
|
|
'codice_fiscale' => 'codice_fiscale',
|
|
'cod_fisc_amministratore' => 'cod_fisc_amministratore',
|
|
];
|
|
$columnMap = [
|
|
// Identità e indirizzo
|
|
'stabile.denominazione' => 'denominazione',
|
|
'stabile.indirizzo' => 'indirizzo',
|
|
'stabile.cap' => 'cap',
|
|
'stabile.citta' => 'citta',
|
|
'stabile.provincia' => 'provincia',
|
|
'stabile.comune_cod' => 'citta', // best-effort legacy code
|
|
'stabile.codice_fiscale' => 'codice_fiscale',
|
|
// Catasto (scegli colonne presenti)
|
|
'stabile.foglio' => (Schema::hasColumn('stabili', 'foglio') ? 'foglio' : (Schema::hasColumn('stabili', 'foglio_catasto') ? 'foglio_catasto' : 'foglio')),
|
|
'stabile.foglio_catasto' => (Schema::hasColumn('stabili', 'foglio_catasto') ? 'foglio_catasto' : (Schema::hasColumn('stabili', 'foglio') ? 'foglio' : 'foglio_catasto')),
|
|
'stabile.particella' => (Schema::hasColumn('stabili', 'particella') ? 'particella' : (Schema::hasColumn('stabili', 'particella_catasto') ? 'particella_catasto' : (Schema::hasColumn('stabili', 'mappale') ? 'mappale' : 'particella'))),
|
|
'stabile.particella_catasto' => (Schema::hasColumn('stabili', 'particella_catasto') ? 'particella_catasto' : (Schema::hasColumn('stabili', 'particella') ? 'particella' : (Schema::hasColumn('stabili', 'mappale') ? 'mappale' : 'particella_catasto'))),
|
|
'stabile.mappale' => (Schema::hasColumn('stabili', 'mappale') ? 'mappale' : (Schema::hasColumn('stabili', 'particella') ? 'particella' : (Schema::hasColumn('stabili', 'particella_catasto') ? 'particella_catasto' : 'mappale'))),
|
|
'stabile.subalterno' => 'subalterno',
|
|
'stabile.categoria' => (Schema::hasColumn('stabili', 'categoria') ? 'categoria' : (Schema::hasColumn('stabili', 'categoria_catastale') ? 'categoria_catastale' : 'categoria')),
|
|
'stabile.categoria_catastale' => (Schema::hasColumn('stabili', 'categoria_catastale') ? 'categoria_catastale' : (Schema::hasColumn('stabili', 'categoria') ? 'categoria' : 'categoria_catastale')),
|
|
'stabile.classe' => (Schema::hasColumn('stabili', 'classe_catastale') ? 'classe_catastale' : (Schema::hasColumn('stabili', 'classe') ? 'classe' : 'classe_catastale')),
|
|
'stabile.consistenza' => (Schema::hasColumn('stabili', 'consistenza_catastale') ? 'consistenza_catastale' : (Schema::hasColumn('stabili', 'consistenza') ? 'consistenza' : 'consistenza_catastale')),
|
|
'stabile.rendita_catastale' => 'rendita_catastale',
|
|
'stabile.superficie_catastale' => 'superficie_catastale',
|
|
'stabile.codice_comune_catasto' => 'codice_comune_catasto',
|
|
// Caratteristiche edificio
|
|
'stabile.numero_palazzine' => 'numero_palazzine',
|
|
'stabile.numero_scale_per_palazzina' => 'numero_scale_per_palazzina',
|
|
'stabile.numero_piani' => 'numero_piani',
|
|
'stabile.piano_seminterrato' => 'piano_seminterrato',
|
|
'stabile.piano_sottotetto' => 'piano_sottotetto',
|
|
'stabile.ascensore' => 'presenza_ascensore',
|
|
'stabile.presenza_ascensore' => 'presenza_ascensore',
|
|
'stabile.cortile_giardino' => 'cortile_giardino',
|
|
'stabile.superficie_cortile' => 'superficie_cortile',
|
|
'stabile.riscaldamento_centralizzato' => 'riscaldamento_centralizzato',
|
|
'stabile.acqua_centralizzata' => 'acqua_centralizzata',
|
|
'stabile.gas_centralizzato' => 'gas_centralizzato',
|
|
'stabile.servizio_portineria' => 'servizio_portineria',
|
|
'stabile.orari_portineria' => 'orari_portineria',
|
|
'stabile.videocitofono' => 'videocitofono',
|
|
'stabile.antenna_tv_centralizzata' => 'antenna_tv_centralizzata',
|
|
'stabile.internet_condominiale' => 'internet_condominiale',
|
|
// Avanzate / stato
|
|
'stabile.configurazione_avanzata' => 'configurazione_avanzata',
|
|
'stabile.ultima_generazione_unita' => 'ultima_generazione_unita',
|
|
'stabile.stato' => 'stato',
|
|
'stabile.registro_note' => 'registro_note',
|
|
// Amministratore (1: campi su stabile, storicizzazione arriverà dopo)
|
|
'stabile.cod_fisc_amministratore' => 'cod_fisc_amministratore',
|
|
'stabile.data_nomina_amministratore' => 'data_nomina',
|
|
'stabile.data_scadenza_mandato' => 'scadenza_mandato',
|
|
];
|
|
|
|
$inverse = [];
|
|
if (!empty($this->fieldMapping) && is_array($this->fieldMapping)) {
|
|
foreach ($this->fieldMapping as $legacyKey => $target) {
|
|
if (!is_string($legacyKey) || !is_string($target)) {
|
|
continue;
|
|
}
|
|
$inverse[$target] = $legacyKey;
|
|
}
|
|
}
|
|
|
|
if (!empty($inverse)) {
|
|
foreach ($columnMap as $tKey => $colName) {
|
|
if (!isset($inverse[$tKey])) {
|
|
continue;
|
|
}
|
|
$legacyKey = (string)$inverse[$tKey];
|
|
$lk = strtolower($legacyKey);
|
|
$lk = $aliases[$lk] ?? $lk;
|
|
if ($lk === 'codice_fiscale') {
|
|
$val = $legacyRow['codice_fiscale']
|
|
?? ($legacyRow['cin'] ?? ($legacyRow['cod_fiscale'] ?? ($legacyRow['cf'] ?? ($legacyRow['cf_condominio'] ?? null))));
|
|
} elseif ($lk === 'cod_fisc_amministratore') {
|
|
$val = $legacyRow['cf_amministratore']
|
|
?? ($legacyRow['cod_fisc_amministratore'] ?? ($legacyRow['cin'] ?? null));
|
|
} elseif ($tKey === 'banca.iban') {
|
|
$val = $legacyRow['iban'] ?? ($legacyRow['iban_condominio'] ?? null);
|
|
} else {
|
|
$val = is_array($legacyRow) && array_key_exists($lk, $legacyRow) ? $legacyRow[$lk] : null;
|
|
}
|
|
if ($val === null || $val === '') {
|
|
continue;
|
|
}
|
|
if (in_array($tKey, self::BOOLEAN_TARGET_KEYS, true)) {
|
|
$valStr = is_string($val) ? trim(strtolower($val)) : $val;
|
|
$val = in_array($valStr, ['1', 'y', 'yes', 'si', 'sì', 'true', 'vero', 1, true], true) ? 1 : 0;
|
|
}
|
|
$current = $existing->$colName ?? null;
|
|
if ($this->forceMapping || empty($current) || $current === 'ND') {
|
|
$update[$colName] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($this->manualFieldValues)) {
|
|
foreach ($this->manualFieldValues as $target => $rawValue) {
|
|
if (!is_string($target)) {
|
|
continue;
|
|
}
|
|
$manualValue = $this->getManualValueForTarget($target);
|
|
if ($manualValue === null) {
|
|
continue;
|
|
}
|
|
$colName = $columnMap[$target] ?? null;
|
|
if (!$colName && strpos($target, 'stabile.') === 0) {
|
|
$candidate = substr($target, strlen('stabile.'));
|
|
if ($candidate !== '' && Schema::hasColumn('stabili', $candidate)) {
|
|
$colName = $candidate;
|
|
}
|
|
}
|
|
if (!$colName) {
|
|
continue;
|
|
}
|
|
$current = $existing->$colName ?? null;
|
|
if ($this->forceMapping || empty($current) || $current === 'ND') {
|
|
$update[$colName] = $manualValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($update)) {
|
|
$update['updated_at'] = now();
|
|
}
|
|
return $update;
|
|
} catch (\Throwable $e) {
|
|
if ($this->output->isVerbose()) {
|
|
$this->warn('Mapping stabili ignorato per errore: ' . $e->getMessage());
|
|
}
|
|
return $update;
|
|
}
|
|
}
|
|
|
|
private function resolveMappingCodiceCassa(array $mapping): ?string
|
|
{
|
|
$target = isset($mapping['target']) && is_array($mapping['target']) ? $mapping['target'] : [];
|
|
$legacy = isset($mapping['legacy']) && is_array($mapping['legacy']) ? $mapping['legacy'] : [];
|
|
$candidates = [
|
|
$target['codice_cassa'] ?? null,
|
|
$mapping['target_cod_cassa'] ?? null,
|
|
$mapping['target_cod_cassa_select'] ?? null,
|
|
$mapping['codice_legacy'] ?? null,
|
|
$legacy['cod_cassa'] ?? null,
|
|
$mapping['legacy_cod_cassa'] ?? null,
|
|
$mapping['codice_cassa'] ?? null,
|
|
];
|
|
foreach ($candidates as $candidate) {
|
|
if (is_string($candidate) && trim($candidate) !== '') {
|
|
return strtoupper(trim($candidate));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function resolveMappingLegacyField(array $mapping, string $field): ?string
|
|
{
|
|
$legacy = isset($mapping['legacy']) && is_array($mapping['legacy']) ? $mapping['legacy'] : [];
|
|
$candidates = [
|
|
$legacy[$field] ?? null,
|
|
$mapping['legacy_' . $field] ?? null,
|
|
];
|
|
foreach ($candidates as $candidate) {
|
|
if (is_string($candidate) && trim($candidate) !== '') {
|
|
return trim($candidate);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function loadMappingIfAny(): void
|
|
{
|
|
try {
|
|
$this->fieldMapping = [];
|
|
$this->banksMapping = [];
|
|
$this->manualFieldValues = [];
|
|
if (!Schema::hasTable('gescon_import_mappings')) {
|
|
return;
|
|
}
|
|
|
|
$legacy = $this->option('stabile');
|
|
|
|
$globalRow = DB::table('gescon_import_mappings')
|
|
->whereNull('legacy_code')
|
|
->orWhere('legacy_code', '*')
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
if ($globalRow && isset($globalRow->meta)) {
|
|
$globalMeta = is_array($globalRow->meta) ? $globalRow->meta : json_decode($globalRow->meta, true);
|
|
if (is_array($globalMeta)) {
|
|
if (isset($globalMeta['mapping']) && is_array($globalMeta['mapping'])) {
|
|
$this->fieldMapping = $globalMeta['mapping'];
|
|
}
|
|
if (isset($globalMeta['banche_mapping']) && is_array($globalMeta['banche_mapping'])) {
|
|
$this->banksMapping = $globalMeta['banche_mapping'];
|
|
}
|
|
if (isset($globalMeta['manual_target_values']) && is_array($globalMeta['manual_target_values'])) {
|
|
$this->manualFieldValues = $globalMeta['manual_target_values'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($legacy) {
|
|
$row = DB::table('gescon_import_mappings')
|
|
->where('legacy_code', $legacy)
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
if ($row && isset($row->meta)) {
|
|
$meta = is_array($row->meta) ? $row->meta : json_decode($row->meta, true);
|
|
if (is_array($meta)) {
|
|
if (isset($meta['mapping']) && is_array($meta['mapping'])) {
|
|
$this->fieldMapping = $meta['mapping'];
|
|
}
|
|
if (isset($meta['banche_mapping']) && is_array($meta['banche_mapping'])) {
|
|
$this->banksMapping = $meta['banche_mapping'];
|
|
}
|
|
if (isset($meta['manual_target_values']) && is_array($meta['manual_target_values'])) {
|
|
$this->manualFieldValues = array_replace(
|
|
$this->manualFieldValues,
|
|
$meta['manual_target_values']
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Ignora errori mapping
|
|
}
|
|
}
|
|
|
|
private function getManualValueForTarget(string $targetKey)
|
|
{
|
|
if (empty($this->manualFieldValues) || !array_key_exists($targetKey, $this->manualFieldValues)) {
|
|
return null;
|
|
}
|
|
$value = $this->manualFieldValues[$targetKey];
|
|
if (is_string($value)) {
|
|
$value = trim($value);
|
|
}
|
|
if ($value === '' || $value === null) {
|
|
return null;
|
|
}
|
|
if (in_array($targetKey, self::BOOLEAN_TARGET_KEYS, true)) {
|
|
if (is_bool($value)) {
|
|
return $value ? 1 : 0;
|
|
}
|
|
if (is_numeric($value)) {
|
|
return (int)$value === 1 ? 1 : 0;
|
|
}
|
|
$normalized = strtolower((string)$value);
|
|
$truthy = ['1', 'y', 'yes', 'si', 'sì', 'true', 'vero', 'on'];
|
|
return in_array($normalized, $truthy, true) ? 1 : 0;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private function mapField(string $targetKey, $default = null)
|
|
{
|
|
$manualValue = $this->getManualValueForTarget($targetKey);
|
|
if ($manualValue !== null) {
|
|
return $manualValue;
|
|
}
|
|
// Migliorato: se mapping presente e targetKey è stabile.*, tenta di leggere dal record legacy cache
|
|
if (empty($this->fieldMapping) || strpos($targetKey, 'stabile.') !== 0) return $default;
|
|
// Carica record legacy una sola volta
|
|
if ($this->stabileLegacyRow === null) {
|
|
$this->stabileLegacyRow = $this->loadStabileLegacyRow();
|
|
}
|
|
if (!$this->stabileLegacyRow) return $default;
|
|
$legacyKeyMatched = null;
|
|
foreach ($this->fieldMapping as $legacyKey => $target) {
|
|
if ($target === $targetKey) {
|
|
$legacyKeyMatched = $legacyKey;
|
|
break;
|
|
}
|
|
}
|
|
if (!$legacyKeyMatched) return $default;
|
|
$row = $this->stabileLegacyRow;
|
|
$targetAttr = substr($targetKey, strlen('stabile.'));
|
|
// Mappa candidate column names per attributi target
|
|
$candidatesMap = [
|
|
'denominazione' => ['denominazione', 'nome', 'ragione_sociale', 'descrizione', 'descr', 'nome_condominio'],
|
|
'indirizzo' => ['indirizzo', 'via', 'indir', 'address'],
|
|
'numero_civico' => ['numero_civico', 'civico', 'n_civico', 'nciv'],
|
|
'cap' => ['cap', 'cap_condominio', 'zip'],
|
|
'comune_cod' => ['comune', 'comune_cod', 'citta', 'città', 'localita', 'loc'],
|
|
'provincia' => ['provincia', 'prov', 'pr'],
|
|
'foglio' => ['foglio', 'ac_foglio', 'foglio_catasto'],
|
|
'particella' => ['particella', 'mappale', 'ac_particella', 'part'],
|
|
'subalterno' => ['subalterno', 'sub', 'ac_sub', 'ac_subalterno'],
|
|
'categoria_catastale' => ['categoria_catastale', 'categoria', 'cat_categoria'],
|
|
'classe' => ['classe', 'cat_classe'],
|
|
'consistenza' => ['consistenza', 'cat_consistenza'],
|
|
'rendita_catastale' => ['rendita_catastale', 'rendita', 'cat_rendita'],
|
|
'cod_fisc_amministratore' => ['cod_fisc_amministratore', 'cf_amministratore', 'amministratore_cf', 'cf_amm', 'cod_fiscale_amm'],
|
|
];
|
|
$value = $default;
|
|
if (isset($candidatesMap[$targetAttr])) {
|
|
foreach ($candidatesMap[$targetAttr] as $cand) {
|
|
if (isset($row[$cand]) && $row[$cand] !== '') {
|
|
$value = $row[$cand];
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// fallback: prova legacyKey directly
|
|
if (isset($row[$legacyKeyMatched]) && $row[$legacyKeyMatched] !== '') $value = $row[$legacyKeyMatched];
|
|
}
|
|
return $value ?? $default;
|
|
}
|
|
|
|
private function loadStabileLegacyRow(): ?array
|
|
{
|
|
try {
|
|
$code = $this->option('stabile');
|
|
if (!$code) return null;
|
|
// 1) Staging stabili
|
|
if (Schema::connection('gescon_import')->hasTable('stabili')) {
|
|
$r = DB::connection('gescon_import')->table('stabili')->where('cod_stabile', $code)->first();
|
|
if ($r) return (array)$r;
|
|
}
|
|
// 2) Staging condomin
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$r = DB::connection('gescon_import')->table('condomin')->where('cod_stabile', $code)->first();
|
|
if ($r) return (array)$r;
|
|
}
|
|
// 3) MDB fallback (usa stessa logica di mdbExportToRows ridotta)
|
|
$base = $this->option('path');
|
|
if ($base) {
|
|
$mdb = rtrim($base, '/') . '/Stabili.mdb';
|
|
if (is_file($mdb) && is_readable($mdb)) {
|
|
$rows = $this->mdbExportToRows($mdb, ['Stabili', 'stabili', 'Condomin', 'condomin']);
|
|
foreach ($rows as $row) {
|
|
foreach (['cod_stabile', 'codice', 'codice_stabile', 'stabile', 'cod'] as $cf) {
|
|
if (isset($row[$cf]) && trim((string)$row[$cf]) === $code) {
|
|
return $row;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// ignore
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Esporta una tabella MDB in array di righe associative (headers normalizzati lower-case).
|
|
*/
|
|
private function mdbExportToRows(string $mdbPath, array $tableCandidates): array
|
|
{
|
|
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
|
|
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
|
|
if (!is_file($mdbPath) || !is_readable($mdbPath)) return [];
|
|
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
|
|
$table = null;
|
|
if ($tables) {
|
|
$names = array_filter(preg_split('/\r?\n/', trim($tables)));
|
|
foreach ($tableCandidates as $cand) {
|
|
foreach ($names as $n) {
|
|
if (strcasecmp($n, $cand) === 0) {
|
|
$table = $n;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
if (!$table) {
|
|
foreach ($tableCandidates as $cand) {
|
|
foreach ($names as $n) {
|
|
if (stripos($n, $cand) !== false) {
|
|
$table = $n;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!$table) return [];
|
|
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
|
|
$cmd = sprintf(
|
|
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
|
|
escapeshellarg($binExport),
|
|
escapeshellarg('%Y-%m-%d'),
|
|
escapeshellarg('|'),
|
|
escapeshellarg('"'),
|
|
escapeshellarg($mdbPath),
|
|
escapeshellarg($table),
|
|
escapeshellarg($tmp)
|
|
);
|
|
@shell_exec($cmd);
|
|
if (!is_file($tmp) || filesize($tmp) === 0) {
|
|
@unlink($tmp);
|
|
return [];
|
|
}
|
|
$fh = fopen($tmp, 'r');
|
|
if (!$fh) {
|
|
@unlink($tmp);
|
|
return [];
|
|
}
|
|
$headers = fgetcsv($fh, 0, '|');
|
|
if (!$headers) {
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
return [];
|
|
}
|
|
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
|
|
$rows = [];
|
|
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
|
|
if ($cols === [null] || $cols === false) continue;
|
|
$cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols);
|
|
$cnt = count($headers);
|
|
if (count($cols) < $cnt) $cols = array_pad($cols, $cnt, null);
|
|
if (count($cols) > $cnt) $cols = array_slice($cols, 0, $cnt);
|
|
$row = @array_combine($headers, $cols) ?: [];
|
|
if (!empty($row)) $rows[] = $row;
|
|
}
|
|
fclose($fh);
|
|
@unlink($tmp);
|
|
return $rows;
|
|
}
|
|
}
|