8508 lines
381 KiB
PHP
Executable File
8508 lines
381 KiB
PHP
Executable File
<?php
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
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: assemblee|unita|relazioni|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}
|
|
{--fill-missing-millesimi : Crea righe mancanti a zero per ogni unita/tabella (uso eccezionale)}
|
|
{--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',
|
|
'assemblee',
|
|
'unita',
|
|
'relazioni',
|
|
'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 array $legacyUnitTypeMappings = [];
|
|
|
|
private array $tipologiaCache = [];
|
|
|
|
private bool $legacyYearMapLoaded = false;
|
|
|
|
private function requestedLegacyYear(): ?string
|
|
{
|
|
$anno = $this->option('anno');
|
|
if ($anno === null) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim((string) $anno);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
return sprintf('%04d', (int) $value);
|
|
}
|
|
|
|
private function scopeCondominQuery($query)
|
|
{
|
|
$legacyYear = $this->requestedLegacyYear();
|
|
if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$query->where('legacy_year', $legacyYear);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function extractStableLegacyCondId(mixed $row, ?string $fallback = null): ?string
|
|
{
|
|
foreach (['legacy_id_cond', 'id_cond', 'legacy_cond_id'] as $field) {
|
|
$value = is_array($row) ? ($row[$field] ?? null) : ($row->{$field} ?? null);
|
|
$value = trim((string) $value);
|
|
if ($value !== '') {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
$fallback = trim((string) ($fallback ?? ''));
|
|
if ($fallback !== '') {
|
|
return $fallback;
|
|
}
|
|
|
|
$codCond = is_array($row) ? ($row['cod_cond'] ?? null) : ($row->cod_cond ?? null);
|
|
$codCond = trim((string) $codCond);
|
|
|
|
return $codCond !== '' ? $codCond : null;
|
|
}
|
|
|
|
private function extractLegacyPartyName(mixed $row): ?string
|
|
{
|
|
$candidates = [
|
|
is_array($row) ? ($row['proprietario_denominazione'] ?? null) : ($row->proprietario_denominazione ?? null),
|
|
is_array($row) ? ($row['nom_cond'] ?? null) : ($row->nom_cond ?? null),
|
|
trim((string) ((is_array($row) ? ($row['cognome'] ?? '') : ($row->cognome ?? '')) . ' ' . (is_array($row) ? ($row['nome'] ?? '') : ($row->nome ?? '')))),
|
|
];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
$candidate = trim((string) $candidate);
|
|
if ($candidate !== '') {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function normalizeLegacyInternoLookup(mixed $value): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
$interno = trim((string) $value);
|
|
if ($interno === '' || preg_match('/^0+$/', $interno)) {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/^\s*CAN\s*(\d+)\s*$/i', $interno, $match)) {
|
|
return 'CAN' . $match[1];
|
|
}
|
|
|
|
return $interno;
|
|
}
|
|
|
|
private function applyLegacyYearFilterToCondominQuery($query, ?string $legacyYear)
|
|
{
|
|
$legacyYear = trim((string) ($legacyYear ?? ''));
|
|
if ($legacyYear !== '' && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$query->where('legacy_year', $legacyYear);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function resolveCondominRowByReference(string $codStabile, string $codCond, ?string $legacyYear = null, ?string $partyName = null): ?object
|
|
{
|
|
if ($codStabile === '' || ! Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
return null;
|
|
}
|
|
|
|
$partyName = trim((string) ($partyName ?? ''));
|
|
$yearsToTry = array_values(array_unique(array_filter([
|
|
trim((string) ($legacyYear ?? '')),
|
|
trim((string) ($this->requestedLegacyYear() ?? '')),
|
|
], static fn(string $value): bool => $value !== '')));
|
|
|
|
foreach ($yearsToTry as $year) {
|
|
if ($codCond !== '') {
|
|
$row = $this->applyLegacyYearFilterToCondominQuery(
|
|
DB::connection('gescon_import')->table('condomin')->where('cod_stabile', $codStabile),
|
|
$year,
|
|
)
|
|
->where('cod_cond', $codCond)
|
|
->orderByDesc('id')
|
|
->first();
|
|
if ($row) {
|
|
return $row;
|
|
}
|
|
}
|
|
|
|
if ($partyName !== '') {
|
|
$query = $this->applyLegacyYearFilterToCondominQuery(
|
|
DB::connection('gescon_import')->table('condomin')->where('cod_stabile', $codStabile),
|
|
$year,
|
|
);
|
|
$query->where(function ($subQuery) use ($partyName): void {
|
|
$subQuery->where('nom_cond', $partyName);
|
|
if (Schema::connection('gescon_import')->hasColumn('condomin', 'proprietario_denominazione')) {
|
|
$subQuery->orWhere('proprietario_denominazione', $partyName);
|
|
}
|
|
});
|
|
$row = $query->orderByDesc('id')->first();
|
|
if ($row) {
|
|
return $row;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($codCond !== '') {
|
|
$row = DB::connection('gescon_import')->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('cod_cond', $codCond)
|
|
->orderByDesc('legacy_year')
|
|
->orderByDesc('id')
|
|
->first();
|
|
if ($row) {
|
|
return $row;
|
|
}
|
|
}
|
|
|
|
if ($partyName !== '') {
|
|
$query = DB::connection('gescon_import')->table('condomin')
|
|
->where('cod_stabile', $codStabile);
|
|
$query->where(function ($subQuery) use ($partyName): void {
|
|
$subQuery->where('nom_cond', $partyName);
|
|
if (Schema::connection('gescon_import')->hasColumn('condomin', 'proprietario_denominazione')) {
|
|
$subQuery->orWhere('proprietario_denominazione', $partyName);
|
|
}
|
|
});
|
|
|
|
return $query
|
|
->orderByDesc('legacy_year')
|
|
->orderByDesc('id')
|
|
->first();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private array $stagingLatestLegacyYearCache = [];
|
|
|
|
private function resolveLatestStagingLegacyYear(string $table, ?string $stabile = null): ?string
|
|
{
|
|
$cacheKey = $table . '|' . ($stabile ?? '*');
|
|
if (array_key_exists($cacheKey, $this->stagingLatestLegacyYearCache)) {
|
|
return $this->stagingLatestLegacyYearCache[$cacheKey];
|
|
}
|
|
|
|
if (! Schema::connection('gescon_import')->hasTable($table)) {
|
|
return $this->stagingLatestLegacyYearCache[$cacheKey] = null;
|
|
}
|
|
|
|
if (! Schema::connection('gescon_import')->hasColumn($table, 'legacy_year')) {
|
|
return $this->stagingLatestLegacyYearCache[$cacheKey] = $this->requestedLegacyYear();
|
|
}
|
|
|
|
$query = DB::connection('gescon_import')->table($table)
|
|
->whereNotNull('legacy_year')
|
|
->where('legacy_year', '!=', '');
|
|
|
|
if ($stabile !== null && $stabile !== '' && Schema::connection('gescon_import')->hasColumn($table, 'cod_stabile')) {
|
|
$query->where('cod_stabile', $stabile);
|
|
}
|
|
|
|
$value = $query->orderByDesc('legacy_year')->value('legacy_year');
|
|
|
|
return $this->stagingLatestLegacyYearCache[$cacheKey] = ($value !== null ? trim((string) $value) : null);
|
|
}
|
|
|
|
private function currentSnapshotCondominQuery()
|
|
{
|
|
$query = DB::connection('gescon_import')->table('condomin');
|
|
if ($this->option('stabile')) {
|
|
$query->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($this->option('scala')) {
|
|
$query->where('scala', $this->option('scala'));
|
|
}
|
|
|
|
$latestYear = $this->resolveLatestStagingLegacyYear('condomin', (string) ($this->option('stabile') ?? ''));
|
|
if ($latestYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$query->where('legacy_year', $latestYear);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function currentSnapshotComproprietariQuery()
|
|
{
|
|
$query = DB::connection('gescon_import')->table('comproprietari');
|
|
if ($this->option('stabile')) {
|
|
$query->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
|
|
$latestYear = $this->resolveLatestStagingLegacyYear('comproprietari', (string) ($this->option('stabile') ?? ''))
|
|
?: $this->resolveLatestStagingLegacyYear('condomin', (string) ($this->option('stabile') ?? ''));
|
|
if ($latestYear !== null && Schema::connection('gescon_import')->hasColumn('comproprietari', 'legacy_year')) {
|
|
$query->where('legacy_year', $latestYear);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
// 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;
|
|
|
|
private function stepRelazioni(?int $limit = null): int
|
|
{
|
|
$params = [
|
|
'--stabile' => (string) $this->option('stabile'),
|
|
'--limit' => $limit ?? 10000,
|
|
];
|
|
|
|
if (! $this->option('dry-run')) {
|
|
$params['--apply'] = true;
|
|
}
|
|
|
|
$exit = $this->call('gescon:materialize-persone-relazioni', $params);
|
|
if ($exit !== 0) {
|
|
throw new \RuntimeException('Materializzazione persone/relazioni non riuscita.');
|
|
}
|
|
|
|
return (int) DB::table('unita_immobiliare_nominativi as uin')
|
|
->join('unita_immobiliari as ui', 'ui.id', '=', 'uin.unita_immobiliare_id')
|
|
->join('stabili as s', 's.id', '=', 'ui.stabile_id')
|
|
->when($this->option('stabile'), function ($query): void {
|
|
$stabile = trim((string) $this->option('stabile'));
|
|
$trimmed = ltrim($stabile, '0');
|
|
$query->where(function ($builder) use ($stabile, $trimmed): void {
|
|
$builder->where('s.codice_stabile', $stabile);
|
|
if ($trimmed !== '' && $trimmed !== $stabile) {
|
|
$builder->orWhere('s.codice_stabile', $trimmed);
|
|
}
|
|
});
|
|
})
|
|
->count();
|
|
}
|
|
|
|
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();
|
|
|
|
if (! $dry) {
|
|
$this->purgeAuthoritativeLegacyReplica($steps);
|
|
}
|
|
|
|
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.');
|
|
}
|
|
|
|
if ($this->stabileId && in_array('operazioni', $steps, true)) {
|
|
$this->newLine();
|
|
$this->info('🔄 Sincronizzazione operazioni in Prima Nota...');
|
|
$this->call('gescon:sync-operazioni-prima-nota', [
|
|
'--stabile-id' => $this->stabileId,
|
|
'--tipo' => 'all',
|
|
]);
|
|
}
|
|
|
|
$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');
|
|
if (! $mdbPath && $this->option('path')) {
|
|
$base = rtrim($this->option('path'), '/');
|
|
if (is_file($base . '/dbc/Stabili.mdb')) {
|
|
$mdbPath = $base . '/dbc/Stabili.mdb';
|
|
} elseif (is_file($base . '/Stabili.mdb')) {
|
|
$mdbPath = $base . '/Stabili.mdb';
|
|
}
|
|
}
|
|
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';
|
|
$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;
|
|
}
|
|
}
|
|
$exists = $this->findStabileRowByCodeForAdmin($col, $cod, $amminId);
|
|
$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];
|
|
}
|
|
|
|
}
|
|
}
|
|
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->resolveStabiliMdbPath((string) ($this->option('path') ?: ''));
|
|
$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 = $this->findStabileRowByCodeForAdmin($col, $codRichiesto, $this->resolveImportAmministratoreId());
|
|
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);
|
|
$update = $this->applyLegacyStabileDerivedFields($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->codice_fiscale_cond ?? ($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;
|
|
}
|
|
}
|
|
$map['iban_principale'] = $stage->iban_principale ?? null;
|
|
$map['banca_principale'] = $stage->denominazione_banca ?? ($stage->banca ?? null);
|
|
// 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');
|
|
$hasTipologiaColumn = Schema::hasColumn('unita_immobiliari', 'tipologia_id');
|
|
$parentStabile = null;
|
|
if (Schema::hasTable('stabili') && $this->option('stabile')) {
|
|
$stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione';
|
|
$parentStabile = $this->findStabileRowByCodeForAdmin($stabileCol, (string) $this->option('stabile'), $this->resolveImportAmministratoreId());
|
|
}
|
|
$pertinenzeToLink = [];
|
|
$seenCondIds = [];
|
|
$processed = 0;
|
|
$batch = $limit ? min(max(1, $limit), 1000) : 1000;
|
|
$offset = 0;
|
|
while (true) {
|
|
$q = $this->currentSnapshotCondominQuery();
|
|
$q->limit($batch)->offset($offset);
|
|
$rows = $q->get();
|
|
if ($rows->isEmpty()) {
|
|
break;
|
|
}
|
|
|
|
$normalizePartyName = static function (?string $value): string {
|
|
$value = Str::upper(trim((string) $value));
|
|
$value = preg_replace('/\s+/u', ' ', $value ?? '');
|
|
|
|
return $value ?? '';
|
|
};
|
|
foreach ($rows as $r) {
|
|
$legacyCondId = $this->extractStableLegacyCondId($r);
|
|
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 = $this->findStabileRowByCodeForAdmin($stabileCol, (string) ($r->cod_stabile ?? $this->option('stabile')), $this->resolveImportAmministratoreId());
|
|
$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 = isset($r->interno) ? trim((string) $r->interno) : null;
|
|
if ($interno === '') {
|
|
$interno = null;
|
|
}
|
|
$sub = isset($r->catasto_sub) ? trim((string) $r->catasto_sub) : (isset($r->sub) ? trim((string) $r->sub) : $interno);
|
|
if ($sub === '') {
|
|
$sub = null;
|
|
}
|
|
// Riconosci pertinenze nel campo INT o note: cantina, box/garage, posto auto, soffitta/solaio, locale tecnico
|
|
$ownerDescriptor = trim((string) (($r->cognome ?? '') . ' ' . ($r->nome ?? '')));
|
|
$internoStr = is_string($interno) ? trim($interno) : (string) $interno;
|
|
$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] ?? '');
|
|
}
|
|
}
|
|
$unitClassification = $this->resolveLegacyUnitClassification($r, $internoStr, $ownerDescriptor, $ptype);
|
|
if ($ptype === null && ($unitClassification['pertinenza_type'] ?? null)) {
|
|
$ptype = (string) $unitClassification['pertinenza_type'];
|
|
}
|
|
$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;
|
|
}
|
|
}
|
|
$normalizedInterno = $ptype && $pcode ? $pcode : $interno;
|
|
if ($normalizedInterno !== null) {
|
|
$normalizedInterno = trim((string) $normalizedInterno);
|
|
if ($normalizedInterno === '' || (! $ptype && preg_match('/^0+$/', $normalizedInterno))) {
|
|
$normalizedInterno = null;
|
|
}
|
|
}
|
|
if ($normalizedInterno === null) {
|
|
$normalizedInterno = $this->buildLegacyFallbackInterno($legacyCondId, $unitClassification);
|
|
}
|
|
if ($hasDetailedSchema) {
|
|
$exists = null;
|
|
if ($normalizedInterno !== null) {
|
|
$physicalMatch = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->when(Schema::hasColumn('unita_immobiliari', 'deleted_at'), fn($q) => $q->whereNull('deleted_at'))
|
|
->where('scala', $scalaVal)
|
|
->where('interno', $normalizedInterno)
|
|
->first();
|
|
if (! $physicalMatch && $sub !== null && $sub !== '' && Schema::hasColumn('unita_immobiliari', 'subalterno')) {
|
|
$physicalMatch = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->when(Schema::hasColumn('unita_immobiliari', 'deleted_at'), fn($q) => $q->whereNull('deleted_at'))
|
|
->where('scala', $scalaVal)
|
|
->where('subalterno', $sub)
|
|
->first();
|
|
}
|
|
if ($physicalMatch) {
|
|
$exists = $physicalMatch;
|
|
}
|
|
}
|
|
if (! $exists && $legacyCondId !== null && $legacyCondId !== '' && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
$exists = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->when(Schema::hasColumn('unita_immobiliari', 'deleted_at'), fn($q) => $q->whereNull('deleted_at'))
|
|
->where('legacy_cond_id', $legacyCondId)
|
|
->first();
|
|
}
|
|
|
|
if ($normalizedInterno === null && ! $exists) {
|
|
$this->warn(sprintf(
|
|
'Unita saltata per stabile %s cod_cond %s: interno mancante nello snapshot corrente autorevole.',
|
|
(string) ($r->cod_stabile ?? $this->option('stabile') ?? '-'),
|
|
$legacyCondId !== null && $legacyCondId !== '' ? $legacyCondId : '-'
|
|
));
|
|
continue;
|
|
}
|
|
|
|
// Codice unita coerente: STAB-SCALA-INT (per pertinenze: STAB-SCALA-CAN<n>/BOX<n>/...)
|
|
$intPart = $normalizedInterno;
|
|
$codiceBaseStab = ($r->cod_stabile ?? $this->option('stabile')) ?: 'S0';
|
|
$codice = $intPart !== null
|
|
? $this->buildLegacyUnitCode($codiceBaseStab, $scalaVal ?: 'A', (string) $intPart, $legacyCondId)
|
|
: (string) ($exists->codice_unita ?? '');
|
|
// 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;
|
|
}
|
|
if (! $exists && $codice !== '') {
|
|
$exists = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->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 = $this->buildLegacyUnitCode($codiceBaseStab, $scalaVal ?: 'A', (string) $intPart, 'C' . $legacyCondId);
|
|
$exists = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->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 ($codice !== '' && Schema::hasColumn('unita_immobiliari', 'codice_unita') && (string) ($exists->codice_unita ?? '') !== $codice) {
|
|
$codeOwner = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->where('codice_unita', $codice)
|
|
->where('id', '!=', $exists->id)
|
|
->first();
|
|
if (! $codeOwner) {
|
|
$patch['codice_unita'] = $codice;
|
|
} else {
|
|
$codeOwnerScala = strtoupper(trim((string) ($codeOwner->scala ?? '')));
|
|
$codeOwnerInterno = strtoupper(trim((string) ($codeOwner->interno ?? '')));
|
|
$targetScala = strtoupper(trim((string) $scalaVal));
|
|
$targetInterno = strtoupper(trim((string) ($normalizedInterno ?? '')));
|
|
$codeOwnerLegacy = trim((string) ($codeOwner->legacy_cond_id ?? ''));
|
|
$codeConflicts = $codeOwnerLegacy !== (string) ($legacyCondId ?? '')
|
|
|| $codeOwnerScala !== $targetScala
|
|
|| $codeOwnerInterno !== $targetInterno;
|
|
|
|
if ($codeConflicts) {
|
|
$conflictCode = $this->buildLegacyCollisionUnitCode(
|
|
(string) ($codeOwner->codice_unita ?? $codice),
|
|
$codeOwnerLegacy !== '' ? 'C' . $codeOwnerLegacy : 'U' . (string) $codeOwner->id
|
|
);
|
|
while (DB::table('unita_immobiliari')
|
|
->where('stabile_id', $rowStabileId)
|
|
->where('id', '!=', $codeOwner->id)
|
|
->where('codice_unita', $conflictCode)
|
|
->exists()) {
|
|
$conflictCode = $this->buildLegacyCollisionUnitCode($conflictCode, substr(strtoupper(hash('crc32b', $conflictCode)), 0, 4));
|
|
}
|
|
|
|
if (! $this->isDryRun) {
|
|
DB::table('unita_immobiliari')
|
|
->where('id', $codeOwner->id)
|
|
->update([
|
|
'codice_unita' => $conflictCode,
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
$patch['codice_unita'] = $codice;
|
|
}
|
|
}
|
|
}
|
|
if ($normalizedInterno !== null && Schema::hasColumn('unita_immobiliari', 'interno') && (string) ($exists->interno ?? '') !== $normalizedInterno) {
|
|
$patch['interno'] = $normalizedInterno;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'subalterno') && (string) ($exists->subalterno ?? '') !== (string) ($sub ?? '')) {
|
|
$patch['subalterno'] = $sub;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'piano') && (int) ($exists->piano ?? 0) !== $pianoVal) {
|
|
$patch['piano'] = $pianoVal;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'denominazione')) {
|
|
$denominazione = trim(($r->cognome ?? '') . ' ' . ($r->nome ?? '')) ?: null;
|
|
if ($denominazione !== null && (string) ($exists->denominazione ?? '') !== $denominazione) {
|
|
$patch['denominazione'] = $denominazione;
|
|
}
|
|
}
|
|
$resolvedTipoUnita = $this->normalizeLegacyTipoUnita($unitClassification['tipo_unita'] ?? ($ptype ?: 'abitazione'));
|
|
if (Schema::hasColumn('unita_immobiliari', 'tipo_unita') && (string) ($exists->tipo_unita ?? '') !== $resolvedTipoUnita) {
|
|
$patch['tipo_unita'] = $resolvedTipoUnita;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'categoria_catastale')) {
|
|
$categoriaCatastale = $this->normalizeCategoriaCatastale($this->firstNonEmptyStr([
|
|
$r->catasto_categoria ?? null,
|
|
$r->categoria_catastale ?? null,
|
|
$unitClassification['categoria_catastale'] ?? null,
|
|
], 20));
|
|
if ($categoriaCatastale !== null && (string) ($exists->categoria_catastale ?? '') !== $categoriaCatastale) {
|
|
$patch['categoria_catastale'] = $categoriaCatastale;
|
|
}
|
|
}
|
|
if ($hasTipologiaColumn) {
|
|
$tipologiaId = $this->resolveLegacyTipologiaId($unitClassification);
|
|
if ($tipologiaId !== null && (int) ($exists->tipologia_id ?? 0) !== $tipologiaId) {
|
|
$patch['tipologia_id'] = $tipologiaId;
|
|
}
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'attiva') && ! (bool) ($exists->attiva ?? false)) {
|
|
$patch['attiva'] = true;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'deleted_at') && ! empty($exists->deleted_at)) {
|
|
$patch['deleted_at'] = null;
|
|
}
|
|
if (($unitClassification['is_condominiale'] ?? false) && Schema::hasColumn('unita_immobiliari', 'denominazione')) {
|
|
$condoName = $this->firstNonEmptyStr([
|
|
$unitClassification['denominazione'] ?? null,
|
|
$ownerDescriptor,
|
|
], 255);
|
|
if ($condoName !== null && (string) ($exists->denominazione ?? '') !== $condoName) {
|
|
$patch['denominazione'] = $condoName;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
$resolvedTipoUnita = $this->normalizeLegacyTipoUnita($unitClassification['tipo_unita'] ?? ($ptype ?: 'abitazione'));
|
|
$categoriaCatastale = $this->normalizeCategoriaCatastale($this->firstNonEmptyStr([
|
|
$r->catasto_categoria ?? null,
|
|
$r->categoria_catastale ?? null,
|
|
$unitClassification['categoria_catastale'] ?? null,
|
|
], 20));
|
|
$tipologiaId = $hasTipologiaColumn ? $this->resolveLegacyTipologiaId($unitClassification) : null;
|
|
$data = [
|
|
'stabile_id' => $rowStabileId,
|
|
'palazzina_id' => null,
|
|
'codice_unita' => $codice,
|
|
'denominazione' => $this->firstNonEmptyStr([
|
|
$unitClassification['denominazione'] ?? null,
|
|
$ownerDescriptor,
|
|
], 255),
|
|
'descrizione' => null,
|
|
'palazzina' => $this->splitMode === 'palazzine' ? $scalaVal : null,
|
|
'scala' => $scalaVal,
|
|
'piano' => $pianoVal,
|
|
'interno' => $normalizedInterno,
|
|
'subalterno' => $sub,
|
|
'categoria_catastale' => $categoriaCatastale,
|
|
'classe' => null,
|
|
'consistenza' => null,
|
|
'rendita_catastale' => null,
|
|
'millesimi_generali' => 0,
|
|
'legacy_cond_id' => $legacyCondId,
|
|
'tipo_unita' => $resolvedTipoUnita,
|
|
'tipologia_id' => $tipologiaId,
|
|
'stato_conservazione' => 'buono',
|
|
'stato_occupazione' => 'occupata_proprietario',
|
|
'attiva' => true,
|
|
'unita_demo' => false,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (! $hasTipologiaColumn) {
|
|
unset($data['tipologia_id']);
|
|
}
|
|
$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 = $this->currentSnapshotCondominQuery()
|
|
->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;
|
|
}
|
|
$stableCondId = $this->extractStableLegacyCondId($c);
|
|
if ($stableCondId !== null && $stableCondId !== '') {
|
|
$condSet[$stableCondId] = 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();
|
|
$updatePayload = [
|
|
'deleted_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
if (Schema::hasColumn('unita_immobiliari', 'attiva')) {
|
|
$updatePayload['attiva'] = 0;
|
|
}
|
|
DB::table('unita_immobiliari')->whereIn('id', $alienIds)->update($updatePayload);
|
|
|
|
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 = $this->currentSnapshotCondominQuery();
|
|
$q->limit($batch)->offset($offset);
|
|
$rows = $q->get();
|
|
if ($rows->isEmpty()) {
|
|
break;
|
|
}
|
|
|
|
foreach ($rows as $r) {
|
|
$parties = [];
|
|
|
|
$ownerCf = trim((string) ($r->codice_fiscale ?? $r->cond_cod_fisc ?? '')) ?: null;
|
|
if ($ownerCf) {
|
|
$ownerCf = strtoupper($ownerCf);
|
|
}
|
|
$ownerEmail = trim((string) ($r->email ?? $r->e_mail_condomino ?? '')) ?: null;
|
|
$ownerNome = isset($r->nome) ? trim((string) $r->nome) : null;
|
|
$ownerCognome = isset($r->cognome) ? trim((string) $r->cognome) : null;
|
|
$ownerNome = $ownerNome !== '' ? $ownerNome : null;
|
|
$ownerCognome = $ownerCognome !== '' ? $ownerCognome : null;
|
|
|
|
// Add condominium suffix to avoid global unification
|
|
if ($this->isCondominioSubject($ownerNome, $ownerCognome)) {
|
|
$ownerNome = ($ownerNome ? trim($ownerNome) : '') . ' (' . trim($r->cod_stabile) . ')';
|
|
}
|
|
|
|
if ($ownerCf || $ownerEmail || $ownerNome || $ownerCognome) {
|
|
$parties[] = [
|
|
'suffix' => 'owner',
|
|
'old_id' => $r->cod_cond ?? null,
|
|
'nome' => $ownerNome,
|
|
'cognome' => $ownerCognome,
|
|
'cf' => $ownerCf,
|
|
'email' => $ownerEmail,
|
|
'telefono' => $r->telefono ?? null,
|
|
'indirizzo' => $r->indirizzo_corrispondenza ?? null,
|
|
'tipo' => 'proprietario',
|
|
];
|
|
}
|
|
|
|
$tenantName = trim((string) ($r->inquil_nome ?? $r->inquilino_denominazione ?? '')) ?: null;
|
|
$tenantCf = trim((string) ($r->inquil_cod_fisc ?? '')) ?: null;
|
|
if ($tenantCf) {
|
|
$tenantCf = strtoupper($tenantCf);
|
|
}
|
|
$tenantEmail = trim((string) ($r->e_mail_inquilino ?? '')) ?: null;
|
|
$tenantPhone = trim((string) ($r->inquil_tel1 ?? '')) ?: null;
|
|
|
|
// Add condominium suffix to avoid global unification
|
|
if ($this->isCondominioSubject($tenantName, null)) {
|
|
$tenantName = ($tenantName ? trim($tenantName) : '') . ' (' . trim($r->cod_stabile) . ')';
|
|
}
|
|
|
|
if ($tenantName || $tenantCf || $tenantEmail || $tenantPhone) {
|
|
$parties[] = [
|
|
'suffix' => 'tenant',
|
|
'old_id' => null,
|
|
'nome' => $tenantName,
|
|
'cognome' => null,
|
|
'cf' => $tenantCf,
|
|
'email' => $tenantEmail,
|
|
'telefono' => $tenantPhone,
|
|
'indirizzo' => $r->inquil_indir ?? null,
|
|
'tipo' => 'inquilino',
|
|
];
|
|
}
|
|
|
|
$useDbTrigger = (bool) env('GESCON_USE_DB_TRIGGER_CODES', false);
|
|
|
|
foreach ($parties as $party) {
|
|
$exists = $this->findExistingSoggetto($party['cf'], $party['email'], $party['nome'], $party['cognome']);
|
|
if ($exists) {
|
|
$patch = [];
|
|
foreach (
|
|
[
|
|
'nome' => $party['nome'],
|
|
'cognome' => $party['cognome'],
|
|
'codice_fiscale' => $party['cf'],
|
|
'email' => $party['email'],
|
|
'telefono' => $party['telefono'],
|
|
'indirizzo' => $party['indirizzo'],
|
|
] 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;
|
|
}
|
|
|
|
$data = [
|
|
'old_id' => $this->safeOldId($party['old_id']),
|
|
'nome' => $party['nome'],
|
|
'cognome' => $party['cognome'],
|
|
'ragione_sociale' => null,
|
|
'codice_fiscale' => $party['cf'],
|
|
'partita_iva' => null,
|
|
'email' => $party['email'],
|
|
'telefono' => $party['telefono'],
|
|
'indirizzo' => $party['indirizzo'],
|
|
'cap' => null,
|
|
'citta' => null,
|
|
'provincia' => null,
|
|
'tipo' => $party['tipo'],
|
|
'attivo' => Schema::hasColumn('soggetti', 'attivo') ? true : null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (! $useDbTrigger) {
|
|
$baseKey = $this->normalizeNameKey($party['nome'], $party['cognome']) ?: ($party['email'] ?: ($party['cf'] ?: 'ND'));
|
|
$code = $this->generateStableCondKey('S', $r->cod_stabile ?? 'S0', $baseKey);
|
|
$tries = 0;
|
|
while (Schema::hasTable('soggetti') && DB::table('soggetti')->where('codice_univoco', $code)->exists() && $tries < 3) {
|
|
$salt = ($r->cod_cond ?? '') . '-' . $party['suffix'] . '-' . ($tries + 1);
|
|
$code = $this->generateStableCondKey('S', $r->cod_stabile ?? 'S0', $baseKey . '|' . $salt);
|
|
$tries++;
|
|
}
|
|
$data['codice_univoco'] = $code;
|
|
}
|
|
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, email e nome/cognome
|
|
$this->dedupSoggettiBy('codice_fiscale');
|
|
$this->dedupSoggettiBy('email');
|
|
$this->dedupSoggettiByName();
|
|
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;
|
|
$normalizePartyName = static function (?string $value): string {
|
|
$value = Str::upper(trim((string) $value));
|
|
$value = preg_replace('/\s+/u', ' ', $value ?? '');
|
|
|
|
return $value ?? '';
|
|
};
|
|
while (true) {
|
|
$q = $this->currentSnapshotCondominQuery();
|
|
$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;
|
|
}
|
|
$tasks = [];
|
|
|
|
$ownerCf = trim((string) ($r->codice_fiscale ?? $r->cond_cod_fisc ?? '')) ?: null;
|
|
$ownerEmail = trim((string) ($r->email ?? $r->e_mail_condomino ?? '')) ?: null;
|
|
$ownerNome = isset($r->nome) ? trim((string) $r->nome) : null;
|
|
$ownerCognome = isset($r->cognome) ? trim((string) $r->cognome) : null;
|
|
$ownerNome = $ownerNome !== '' ? $ownerNome : null;
|
|
$ownerCognome = $ownerCognome !== '' ? $ownerCognome : null;
|
|
|
|
// Add condominium suffix to avoid global unification
|
|
if ($this->isCondominioSubject($ownerNome, $ownerCognome)) {
|
|
$ownerNome = ($ownerNome ? trim($ownerNome) : '') . ' (' . trim($r->cod_stabile) . ')';
|
|
}
|
|
|
|
if (($r->proprietario ?? 0) || $ownerCf || $ownerEmail || $ownerNome || $ownerCognome) {
|
|
$tasks[] = [
|
|
'tipo' => 'proprieta',
|
|
'nome' => $ownerNome,
|
|
'cognome' => $ownerCognome,
|
|
'cf' => $ownerCf,
|
|
'email' => $ownerEmail,
|
|
'telefono' => trim((string) ($r->telefono ?? $r->tel1 ?? '')) ?: null,
|
|
'percentuale_possesso' => isset($r->proprietario_diritto_percentuale) && is_numeric($r->proprietario_diritto_percentuale)
|
|
? (float) $r->proprietario_diritto_percentuale
|
|
: (isset($r->perc_diritto_reale) && is_numeric($r->perc_diritto_reale) ? (float) $r->perc_diritto_reale : null),
|
|
'percentuale_detrazione' => isset($r->perc_detrazione) && is_numeric($r->perc_detrazione)
|
|
? (float) $r->perc_detrazione
|
|
: null,
|
|
'data_inizio' => $this->normalizeLegacyDate($r->proprietario_subentrato_dal ?? $r->subentrato_dal ?? null),
|
|
'data_fine' => $this->normalizeLegacyDate($r->proprietario_attivo_fino_al ?? $r->attivo_fino_al ?? null),
|
|
'legacy_suffix' => 'owner',
|
|
'soggetto_tipo' => 'proprietario',
|
|
];
|
|
}
|
|
|
|
$tenantName = trim((string) ($r->inquil_nome ?? $r->inquilino_denominazione ?? '')) ?: null;
|
|
$tenantCf = trim((string) ($r->inquil_cod_fisc ?? '')) ?: null;
|
|
$tenantEmail = trim((string) ($r->e_mail_inquilino ?? '')) ?: null;
|
|
$tenantPhone = trim((string) ($r->inquil_tel1 ?? '')) ?: null;
|
|
|
|
// Add condominium suffix to avoid global unification
|
|
if ($this->isCondominioSubject($tenantName, null)) {
|
|
$tenantName = ($tenantName ? trim($tenantName) : '') . ' (' . trim($r->cod_stabile) . ')';
|
|
}
|
|
|
|
if (($r->inquilino ?? 0) || $tenantName || $tenantCf || $tenantEmail || $tenantPhone) {
|
|
$tasks[] = [
|
|
'tipo' => 'locazione',
|
|
'nome' => $tenantName,
|
|
'cognome' => null,
|
|
'cf' => $tenantCf,
|
|
'email' => $tenantEmail,
|
|
'telefono' => $tenantPhone,
|
|
'percentuale_possesso' => null,
|
|
'percentuale_detrazione' => null,
|
|
'data_inizio' => $this->normalizeLegacyDate($r->inquil_dal ?? $r->inquilino_subentrato_dal ?? null),
|
|
'data_fine' => $this->normalizeLegacyDate($r->inquil_al ?? $r->inquilino_attivo_fino_al ?? null),
|
|
'legacy_suffix' => 'tenant',
|
|
'soggetto_tipo' => 'inquilino',
|
|
];
|
|
}
|
|
|
|
if ($tasks === []) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
foreach ($tasks as $task) {
|
|
$sog = $this->findExistingSoggetto($task['cf'], $task['email'], $task['nome'], $task['cognome']);
|
|
if ($sog && $task['tipo'] === 'locazione' && ! empty($task['nome'])) {
|
|
$matchedName = trim((string) (($sog->cognome ?? '') . ' ' . ($sog->nome ?? '')));
|
|
if ($matchedName === '' && ! empty($sog->ragione_sociale)) {
|
|
$matchedName = trim((string) $sog->ragione_sociale);
|
|
}
|
|
|
|
$tenantName = trim((string) (($task['cognome'] ?? '') . ' ' . ($task['nome'] ?? '')));
|
|
$sameUnitOwner = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->where('soggetto_id', $sog->id)
|
|
->where('tipo_diritto', 'proprieta')
|
|
->exists();
|
|
|
|
if ($sameUnitOwner && $normalizePartyName($matchedName) !== $normalizePartyName($tenantName)) {
|
|
$sog = null;
|
|
}
|
|
}
|
|
if (! $sog) {
|
|
$payload = [
|
|
'nome' => $task['nome'],
|
|
'cognome' => $task['cognome'],
|
|
'codice_fiscale' => $task['cf'],
|
|
'email' => $task['email'],
|
|
'telefono' => $task['telefono'],
|
|
'tipo' => $task['soggetto_tipo'],
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('soggetti', 'codice_univoco')) {
|
|
$baseKey = $this->normalizeNameKey($payload['nome'] ?? '', $payload['cognome'] ?? '')
|
|
?: (($payload['codice_fiscale'] ?? null) ?: (($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 ?? '') . '-' . ($task['legacy_suffix'] ?? 'rel') . '-' . ($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();
|
|
}
|
|
}
|
|
|
|
if (! $sog) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$tipo = (string) $task['tipo'];
|
|
if ($tipo === 'locazione') {
|
|
$legacyWrongLocazione = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->where('tipo_diritto', 'locazione')
|
|
->where('soggetto_id', '!=', $sog->id)
|
|
->whereIn('soggetto_id', function ($query) use ($unita) {
|
|
$query->select('soggetto_id')
|
|
->from('proprieta')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->where('tipo_diritto', 'proprieta');
|
|
})
|
|
->orderBy('id')
|
|
->first();
|
|
|
|
if ($legacyWrongLocazione) {
|
|
$this->moveProprietaRowToSoggetto(
|
|
(int) $legacyWrongLocazione->id,
|
|
(int) $sog->id,
|
|
$task['data_inizio'] ?? null,
|
|
$task['data_fine'] ?? null
|
|
);
|
|
$updated++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$exists = DB::table('proprieta')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->where('soggetto_id', $sog->id)
|
|
->where('tipo_diritto', $tipo)
|
|
->first();
|
|
if ($exists) {
|
|
$patch = [];
|
|
if ($tipo === 'proprieta' && $task['percentuale_possesso'] !== null && (float) ($exists->percentuale_possesso ?? 0) !== (float) $task['percentuale_possesso']) {
|
|
$patch['percentuale_possesso'] = $task['percentuale_possesso'];
|
|
}
|
|
if ($tipo === 'proprieta' && Schema::hasColumn('proprieta', 'percentuale_detrazione') && $task['percentuale_detrazione'] !== null && (float) ($exists->percentuale_detrazione ?? 0) !== (float) $task['percentuale_detrazione']) {
|
|
$patch['percentuale_detrazione'] = $task['percentuale_detrazione'];
|
|
}
|
|
if (($task['data_inizio'] ?? null) && (string) ($exists->data_inizio ?? '') !== (string) $task['data_inizio']) {
|
|
$patch['data_inizio'] = $task['data_inizio'];
|
|
}
|
|
if (array_key_exists('data_fine', $task) && (string) ($exists->data_fine ?? '') !== (string) ($task['data_fine'] ?? '')) {
|
|
$patch['data_fine'] = $task['data_fine'];
|
|
}
|
|
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' ? $task['percentuale_possesso'] : null,
|
|
'percentuale_detrazione' => ($tipo === 'proprieta' && Schema::hasColumn('proprieta', 'percentuale_detrazione')) ? $task['percentuale_detrazione'] : null,
|
|
'data_inizio' => $task['data_inizio'],
|
|
'data_fine' => $task['data_fine'],
|
|
'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 = $this->currentSnapshotComproprietariQuery();
|
|
|
|
$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;
|
|
|
|
// Add condominium suffix to avoid global unification
|
|
if ($this->isCondominioSubject($nome, $cognome)) {
|
|
$nome = ($nome ? trim($nome) : '') . ' (' . trim($cr->cod_stabile) . ')';
|
|
}
|
|
|
|
$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');
|
|
$requestedLegacyYear = $this->requestedLegacyYear();
|
|
|
|
// 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 ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('tabelle_millesimali', 'legacy_year')) {
|
|
$q->where('legacy_year', $requestedLegacyYear);
|
|
}
|
|
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 ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
|
|
$q->where('d.legacy_year', $requestedLegacyYear);
|
|
}
|
|
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 ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
|
|
$q->where('d.legacy_year', $requestedLegacyYear);
|
|
}
|
|
if ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) {
|
|
$q->where('d.cod_stabile', $this->option('stabile'));
|
|
}
|
|
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'));
|
|
}
|
|
if ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$q->where('c.legacy_year', $requestedLegacyYear);
|
|
}
|
|
}
|
|
$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'))
|
|
->when($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year'), function ($query) use ($requestedLegacyYear) {
|
|
$query->where('legacy_year', $requestedLegacyYear);
|
|
})
|
|
->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 ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
|
|
$nq->where('d.legacy_year', $requestedLegacyYear);
|
|
}
|
|
if ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) {
|
|
$nq->where('d.cod_stabile', $this->option('stabile'));
|
|
}
|
|
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'));
|
|
}
|
|
if ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$nq->where('c.legacy_year', $requestedLegacyYear);
|
|
}
|
|
}
|
|
$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 ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) {
|
|
$src->where('d.legacy_year', $requestedLegacyYear);
|
|
}
|
|
if ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('dett_tab', 'cod_stabile')) {
|
|
$src->where('d.cod_stabile', $this->option('stabile'));
|
|
}
|
|
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 ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) {
|
|
$src->where('c.legacy_year', $requestedLegacyYear);
|
|
}
|
|
}
|
|
if ($limit) {
|
|
$src->limit($limit);
|
|
}
|
|
|
|
$canImporti = Schema::hasTable('dettaglio_importi_tabella');
|
|
$importiUpdated = 0;
|
|
$importiInserted = 0;
|
|
$importiSkipped = 0;
|
|
|
|
$stagingRows = $src->get()->unique(function ($row) {
|
|
return implode('|', [
|
|
strtoupper(trim((string) ($row->cod_tab ?? ''))),
|
|
trim((string) ($row->id_cond ?? '')),
|
|
strtoupper(trim((string) ($row->cond_inquil ?? ''))),
|
|
trim((string) ($row->legacy_year ?? '')),
|
|
trim((string) ($row->mm ?? '')),
|
|
trim((string) ($row->prev_euro ?? '')),
|
|
trim((string) ($row->cons_euro ?? '')),
|
|
trim((string) ($row->ex_cons_euro ?? '')),
|
|
]);
|
|
})->values();
|
|
|
|
$headerOnlyPreferredImportRows = $this->buildHeaderOnlyPreferredImportRows($stagingRows);
|
|
|
|
foreach ($stagingRows 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;
|
|
$legacyTableCode = strtoupper(trim((string) ($r->cod_tab ?? '')));
|
|
$headerOnlyTable = $this->isExpectedHeaderOnlyLegacyTable($legacyTableCode);
|
|
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') {
|
|
if ($headerOnlyTable && $mm <= 0) {
|
|
goto importi_tabella;
|
|
}
|
|
// 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++;
|
|
}
|
|
}
|
|
|
|
importi_tabella:
|
|
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;
|
|
}
|
|
}
|
|
$preferredImportSignature = $this->resolveHeaderOnlyPreferredImportSignature($headerOnlyPreferredImportRows, $r);
|
|
if ($preferredImportSignature !== null && $preferredImportSignature !== $this->headerOnlyImportRowSignature($r)) {
|
|
continue;
|
|
}
|
|
|
|
$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 solo se richiesto esplicitamente.
|
|
// Di default evitiamo placeholder a zero che spezzano il collegamento reale
|
|
// tra millesimi, nominativi e unita immobiliari.
|
|
if ($this->option('fill-missing-millesimi') && $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,
|
|
'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 inferStraordinariaSequence(mixed ...$candidates): ?int
|
|
{
|
|
foreach ($candidates as $candidate) {
|
|
$value = strtoupper(trim((string) ($candidate ?? '')));
|
|
if ($value === '') {
|
|
continue;
|
|
}
|
|
|
|
if (preg_match('/([0-9]{4})$/', $value, $match)) {
|
|
$sequence = (int) substr($match[1], -2);
|
|
if ($sequence > 0) {
|
|
return $sequence;
|
|
}
|
|
}
|
|
|
|
if (preg_match('/(?:^|[^0-9])([0-9]{2})$/', $value, $match)) {
|
|
$sequence = (int) $match[1];
|
|
if ($sequence > 0) {
|
|
return $sequence;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function buildScopedLegacyCode(?string $legacyCode, ?int $annoGestione, ?int $numeroStraordinaria = null, ?string $fallbackPrefix = null): string
|
|
{
|
|
$base = trim((string) $legacyCode);
|
|
if ($base === '') {
|
|
$base = trim((string) ($fallbackPrefix ?: 'LEGACY'));
|
|
}
|
|
|
|
if (! $annoGestione) {
|
|
return $base;
|
|
}
|
|
|
|
if ($numeroStraordinaria !== null) {
|
|
return sprintf('%s@%d-%02d', $base, $annoGestione, $numeroStraordinaria);
|
|
}
|
|
|
|
return sprintf('%s@%d', $base, $annoGestione);
|
|
}
|
|
|
|
private function isFiscalStraordinaria(object $row): bool
|
|
{
|
|
return $this->normalizeLegacyPartyName($row->descriz_ccp ?? null) === 'DETRAZ.FISC.';
|
|
}
|
|
|
|
private function sumStraordinariaImporto(object $row): ?float
|
|
{
|
|
$sum = 0.0;
|
|
$found = false;
|
|
foreach (range(1, 12) as $idx) {
|
|
$field = 'rata_' . $idx;
|
|
if (isset($row->{$field}) && is_numeric($row->{$field})) {
|
|
$sum += (float) $row->{$field};
|
|
$found = true;
|
|
}
|
|
}
|
|
|
|
return $found ? $sum : null;
|
|
}
|
|
|
|
private function stepVoci(?int $limit): int
|
|
{
|
|
$stabileId = $this->resolveStabileId();
|
|
$src = DB::connection('gescon_import')->table('voc_spe');
|
|
$requestedLegacyYear = $this->requestedLegacyYear();
|
|
if ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) {
|
|
$src->where('legacy_year', $requestedLegacyYear);
|
|
}
|
|
if ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('voc_spe', 'cod_stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($limit) {
|
|
$src->limit($limit);
|
|
}
|
|
|
|
$count = 0;
|
|
$updated = 0;
|
|
$stagingRows = $src->get()->unique(function ($row) {
|
|
return implode('|', [
|
|
strtoupper(trim((string) ($row->cod ?? ''))),
|
|
trim((string) ($row->legacy_year ?? '')),
|
|
trim((string) ($row->cod_stabile ?? '')),
|
|
]);
|
|
})->values();
|
|
|
|
foreach ($stagingRows as $v) {
|
|
$cod = $v->cod ?? null;
|
|
if (! $cod) {
|
|
continue;
|
|
}
|
|
|
|
$legacyYear = isset($v->legacy_year) ? (string) $v->legacy_year : null;
|
|
$annoGestione = $this->resolveLegacySnapshotYear($legacyYear, $v);
|
|
$tipoLegacy = match ($v->v_ors ?? $v->V_ORS ?? '') {
|
|
'R' => 'riscaldamento',
|
|
'S' => 'straordinaria',
|
|
default => 'ordinaria'
|
|
};
|
|
$numeroStraordinaria = $tipoLegacy === 'straordinaria'
|
|
? $this->inferStraordinariaSequence($v->tabella ?? null, $v->cod ?? null, $v->descriz ?? null)
|
|
: null;
|
|
$codiceDominio = $tipoLegacy === 'straordinaria'
|
|
? $this->buildScopedLegacyCode((string) $cod, $annoGestione, $numeroStraordinaria, 'VOCSPE')
|
|
: (string) $cod;
|
|
$exists = null;
|
|
if (Schema::hasColumn('voci_spesa', 'codice')) {
|
|
$q = DB::table('voci_spesa')->where('codice', $codiceDominio);
|
|
if ($stabileId && Schema::hasColumn('voci_spesa', 'stabile_id')) {
|
|
$q->where('stabile_id', $stabileId);
|
|
}
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile($stabileId, $legacyYear, $tipoLegacy, $numeroStraordinaria);
|
|
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', $codiceDominio)
|
|
->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)) {
|
|
$tabellaQuery = 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);
|
|
}
|
|
});
|
|
if ($annoGestione && Schema::hasColumn('tabelle_millesimali', 'anno_gestione')) {
|
|
$tabellaQuery->where('anno_gestione', $annoGestione);
|
|
}
|
|
$tabellaId = $tabellaQuery->value('id');
|
|
if (! $tabellaId && $annoGestione && Schema::hasColumn('tabelle_millesimali', 'anno_gestione')) {
|
|
$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');
|
|
}
|
|
}
|
|
$legacyTabella = trim((string) ($v->tabella ?? ''));
|
|
$legacyFondoRaw = trim((string) ($v->fondo ?? $v->Fondo ?? ''));
|
|
$legacyMeta = [
|
|
'legacy_tabella' => $legacyTabella !== '' ? $legacyTabella : null,
|
|
'legacy_fondo_raw' => $legacyFondoRaw !== '' ? $legacyFondoRaw : null,
|
|
'legacy_is_fondo' => in_array(strtolower($legacyFondoRaw), ['si', 's', '1', 'true'], true),
|
|
'legacy_perc_proprietario' => isset($v->perc_proprietario) && is_numeric($v->perc_proprietario) ? (float) $v->perc_proprietario : null,
|
|
'legacy_perc_inquilino' => isset($v->perc_inquilino) && is_numeric($v->perc_inquilino) ? (float) $v->perc_inquilino : null,
|
|
'legacy_numero_straordinaria' => $numeroStraordinaria,
|
|
'legacy_tipo_gestione' => $tipoLegacy,
|
|
];
|
|
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile($stabileId, $legacyYear, $tipoLegacy, $numeroStraordinaria);
|
|
$data = [
|
|
'stabile_id' => (Schema::hasColumn('voci_spesa', 'stabile_id') ? $stabileId : null),
|
|
'gestione_contabile_id' => Schema::hasColumn('voci_spesa', 'gestione_contabile_id') ? $gestioneId : null,
|
|
'codice' => $codiceDominio,
|
|
'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') ? $tipoLegacy : 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,
|
|
'meta_legacy' => Schema::hasColumn('voci_spesa', 'meta_legacy') ? json_encode($legacyMeta) : 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 (Schema::hasColumn('voci_spesa', '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 : [];
|
|
$patch['meta_legacy'] = json_encode(array_merge($currentMeta, array_filter($legacyMeta, static fn($value) => $value !== null)));
|
|
}
|
|
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 isExpectedHeaderOnlyLegacyTable(?string $code): bool
|
|
{
|
|
$normalized = strtoupper(trim((string) $code));
|
|
|
|
return in_array($normalized, ['ACQUA', 'INDIV.', 'RI.ASS', 'RIPART'], true);
|
|
}
|
|
|
|
private function buildHeaderOnlyPreferredImportRows($rows): array
|
|
{
|
|
$preferred = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$tableCode = strtoupper(trim((string) ($row->cod_tab ?? '')));
|
|
if ($tableCode !== 'ACQUA') {
|
|
continue;
|
|
}
|
|
|
|
$groupKey = $this->headerOnlyImportGroupKey($row);
|
|
if ($groupKey === null) {
|
|
continue;
|
|
}
|
|
|
|
$candidate = [
|
|
'signature' => $this->headerOnlyImportRowSignature($row),
|
|
'amount' => $this->headerOnlyImportAmountScore($row),
|
|
'role' => strtoupper(trim((string) ($row->cond_inquil ?? ''))),
|
|
];
|
|
|
|
$current = $preferred[$groupKey] ?? null;
|
|
if ($current === null) {
|
|
$preferred[$groupKey] = $candidate;
|
|
continue;
|
|
}
|
|
|
|
if ($candidate['amount'] > $current['amount']) {
|
|
$preferred[$groupKey] = $candidate;
|
|
continue;
|
|
}
|
|
|
|
if ($candidate['amount'] === $current['amount'] && $current['role'] !== 'C' && $candidate['role'] === 'C') {
|
|
$preferred[$groupKey] = $candidate;
|
|
}
|
|
}
|
|
|
|
return $preferred;
|
|
}
|
|
|
|
private function resolveHeaderOnlyPreferredImportSignature(array $preferredRows, object $row): ?string
|
|
{
|
|
$tableCode = strtoupper(trim((string) ($row->cod_tab ?? '')));
|
|
if ($tableCode !== 'ACQUA') {
|
|
return null;
|
|
}
|
|
|
|
$groupKey = $this->headerOnlyImportGroupKey($row);
|
|
if ($groupKey === null) {
|
|
return null;
|
|
}
|
|
|
|
return $preferredRows[$groupKey]['signature'] ?? null;
|
|
}
|
|
|
|
private function headerOnlyImportGroupKey(object $row): ?string
|
|
{
|
|
$tableCode = strtoupper(trim((string) ($row->cod_tab ?? '')));
|
|
$condId = trim((string) ($row->id_cond ?? ''));
|
|
$legacyYear = trim((string) ($row->legacy_year ?? ''));
|
|
|
|
if ($tableCode === '' || $condId === '') {
|
|
return null;
|
|
}
|
|
|
|
return implode('|', [$tableCode, $condId, $legacyYear]);
|
|
}
|
|
|
|
private function headerOnlyImportRowSignature(object $row): string
|
|
{
|
|
return implode('|', [
|
|
strtoupper(trim((string) ($row->cond_inquil ?? ''))),
|
|
trim((string) ($row->prev_euro ?? '')),
|
|
trim((string) ($row->cons_euro ?? '')),
|
|
trim((string) ($row->ex_cons_euro ?? '')),
|
|
]);
|
|
}
|
|
|
|
private function headerOnlyImportAmountScore(object $row): float
|
|
{
|
|
return max(
|
|
abs((float) ($row->prev_euro ?? 0)),
|
|
abs((float) ($row->cons_euro ?? 0)),
|
|
abs((float) ($row->ex_cons_euro ?? 0))
|
|
);
|
|
}
|
|
|
|
private function stepBanche(?int $limit): int
|
|
{
|
|
// Importa conti bancari e casse solo da Anagr_casse dei singolo_anno.mdb / staging dedicato.
|
|
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;
|
|
$rows = [];
|
|
|
|
// 0) Preferisci staging anag_casse caricato dai singolo_anno.mdb.
|
|
$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)) {
|
|
$dirs = array_filter([$stableDir, $root], 'is_dir');
|
|
foreach ($dirs as $dir) {
|
|
$candidates = array_merge(
|
|
(array) glob($dir . '/singolo_anno.mdb'),
|
|
(array) glob($dir . '/*/singolo_anno.mdb')
|
|
);
|
|
foreach ($candidates 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 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 = [];
|
|
$stabileRecord = $stabileId
|
|
? DB::table('stabili')->where('id', $stabileId)->first(['denominazione', 'banca_principale', 'iban_principale'])
|
|
: null;
|
|
|
|
if ($stabileRecord && !empty($stabileRecord->iban_principale)) {
|
|
$hasCcb = false;
|
|
foreach ($rows as $r) {
|
|
foreach (['cod_cassa', 'codcassa', 'codice_cassa', 'modalita_pagamento', 'codice'] as $alias) {
|
|
if (isset($r[$alias]) && strtoupper(trim((string) $r[$alias])) === 'CCB') {
|
|
$hasCcb = true;
|
|
break;
|
|
}
|
|
}
|
|
if ($hasCcb) {
|
|
break;
|
|
}
|
|
}
|
|
if (!$hasCcb) {
|
|
$rows[] = [
|
|
'banca' => $stabileRecord->banca_principale ?: 'Banca principale',
|
|
'iban' => $stabileRecord->iban_principale,
|
|
'cod_cassa' => 'CCB',
|
|
'conto' => 'CCB',
|
|
'intestatario' => $stabileRecord->denominazione ?? null,
|
|
];
|
|
}
|
|
}
|
|
|
|
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 = strtoupper(trim((string) ($getFirst($mapLegacy['cod_cassa']) ?? '')));
|
|
$descr = $getFirst($mapLegacy['descrizione']);
|
|
if (! $denom && ! $iban && ! $numero && $codCassa === '' && ! $descr) {
|
|
continue;
|
|
}
|
|
// Sorgente unica: senza cod_cassa legacy non creo nessun collegamento persistente.
|
|
if ($codCassa === '') {
|
|
continue;
|
|
}
|
|
// Chiave dedup per stabile: cod_cassa, che e il riferimento usato da incassi e operazioni.
|
|
$key = 'C:' . $codCassa;
|
|
if (! $key) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($seenKeys[$key])) {
|
|
continue;
|
|
}
|
|
// evita duplicati da diverse sorgenti
|
|
$seenKeys[$key] = true;
|
|
$resolvedIban = $iban;
|
|
$resolvedDenom = $denom ?: ($descr ?: 'Banca');
|
|
|
|
if ($resolvedIban === '' && $codCassa !== 'CON') {
|
|
$candidateIban = strtoupper(preg_replace('/\s+/', '', (string) ($stabileRecord->iban_principale ?? '')));
|
|
if ($candidateIban !== '') {
|
|
$resolvedIban = $candidateIban;
|
|
$resolvedDenom = trim((string) ($stabileRecord->banca_principale ?? $resolvedDenom)) ?: $resolvedDenom;
|
|
}
|
|
}
|
|
|
|
if (! $iban && Schema::hasTable('casse')) {
|
|
if ($resolvedIban !== '') {
|
|
DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', $codCassa)->delete();
|
|
}
|
|
}
|
|
|
|
if ($resolvedIban === '' && Schema::hasTable('casse')) {
|
|
$existsC = DB::table('casse')->where('stabile_id', $stabileId)->where('cod_cassa', $codCassa)->first();
|
|
$payloadCassa = [
|
|
'tenant_id' => null,
|
|
'stabile_id' => $stabileId,
|
|
'cod_cassa' => $codCassa,
|
|
'descrizione' => ($descr ?: ($denom ?: $codCassa)),
|
|
'tipo' => ($codCassa === 'CON') ? 'cassa_contanti' : 'altro',
|
|
'iban' => $iban ?: null,
|
|
'intestazione_conto' => $intest ?: null,
|
|
'attiva' => true,
|
|
'meta' => json_encode(['from' => 'Anagr_casse', 'numero' => $numero]),
|
|
'updated_at' => $now,
|
|
];
|
|
if (! $existsC) {
|
|
$payloadCassa['created_at'] = $now;
|
|
DB::table('casse')->insert($payloadCassa);
|
|
$created++;
|
|
} else {
|
|
DB::table('casse')->where('id', $existsC->id)->update($payloadCassa);
|
|
$updated++;
|
|
}
|
|
}
|
|
$tipoConto = 'corrente';
|
|
if ($resolvedIban === '') {
|
|
$tipoConto = 'cassa';
|
|
}
|
|
$resolvedNumero = $numero ?: $codCassa;
|
|
|
|
// Cerca esistente per stabile_id + IBAN/numero effettivi
|
|
$exists = null;
|
|
if ($resolvedIban !== '') {
|
|
$exists = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->whereRaw('REPLACE(UPPER(iban)," ","") = ?', [$resolvedIban])
|
|
->orderByDesc('is_nostro_conto')
|
|
->orderBy('id')
|
|
->first();
|
|
}
|
|
if (! $exists && $resolvedNumero !== '') {
|
|
$exists = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('numero_conto', $resolvedNumero)
|
|
->orderByDesc('is_nostro_conto')
|
|
->orderBy('id')
|
|
->first();
|
|
}
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'contatto_id' => null,
|
|
'tipo_conto' => $tipoConto,
|
|
'denominazione_banca' => $resolvedDenom,
|
|
'numero_conto' => $resolvedNumero,
|
|
'iban' => $resolvedIban ?: null,
|
|
'legacy_cod_cassa' => $codCassa,
|
|
'abi' => null,
|
|
'cab' => null,
|
|
'cin' => null,
|
|
'bic_swift' => $swift ?: null,
|
|
'intestazione_conto' => $intest ?: ($stabileRecord->denominazione ?? 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) {
|
|
$duplicateQuery = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('id', '<>', $exists->id);
|
|
if ($resolvedIban !== '') {
|
|
$duplicateQuery->whereRaw('REPLACE(UPPER(iban)," ","") = ?', [$resolvedIban]);
|
|
}
|
|
if ($resolvedNumero !== '') {
|
|
$duplicateQuery->where('numero_conto', $resolvedNumero);
|
|
}
|
|
if ($codCassa !== '') {
|
|
$duplicateQuery->where('legacy_cod_cassa', $codCassa);
|
|
}
|
|
|
|
// Aggiorna campi vuoti
|
|
$upd = [];
|
|
foreach (['denominazione_banca', 'numero_conto', 'iban', 'bic_swift', 'intestazione_conto', 'legacy_cod_cassa', 'tipo_conto'] as $c) {
|
|
$newV = $payload[$c] ?? null;
|
|
$oldV = $exists->$c ?? null;
|
|
if ($newV && (! $oldV || $oldV === 'ND' || ($c === 'tipo_conto' && $oldV === 'cassa' && $newV === 'corrente'))) {
|
|
$upd[$c] = $newV;
|
|
}
|
|
|
|
}
|
|
if (! empty($upd)) {
|
|
$upd['updated_at'] = $now;
|
|
if (! $this->isDryRun) {
|
|
DB::table('dati_bancari')->where('id', $exists->id)->update($upd);
|
|
}
|
|
|
|
$updated++;
|
|
}
|
|
if (Schema::hasTable('conti_bancari')) {
|
|
$fullPayload = array_merge((array) $exists, $upd, $payload);
|
|
$this->syncToContiBancari($fullPayload, $stabileCode);
|
|
}
|
|
if (! $this->isDryRun) {
|
|
$duplicateQuery->delete();
|
|
}
|
|
continue;
|
|
}
|
|
$this->dynamicInsert('dati_bancari', $payload);
|
|
if (Schema::hasTable('conti_bancari')) {
|
|
$this->syncToContiBancari($payload, $stabileCode);
|
|
}
|
|
// 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)," ","") = ?', [$resolvedIban])
|
|
->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 && $resolvedIban && $legacyIban === $resolvedIban) {
|
|
$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(" → casse/conti creati: {$created}, aggiornati: {$updated}");
|
|
}
|
|
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;
|
|
$touchedExistingRateIds = [];
|
|
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;
|
|
}
|
|
|
|
$gestioneId = $this->resolveGestioneId($o->legacy_year ?? ($o->anno ?? null), $o->compet ?? null, $o->gestione ?? null, $o->n_stra ?? null);
|
|
$protoNum = $this->nextProtocolNumber();
|
|
$prefix = null;
|
|
if ($gestioneId) {
|
|
$prefix = DB::table('gestioni_contabili')->where('id', $gestioneId)->value('protocollo_prefix');
|
|
}
|
|
if (empty($prefix)) {
|
|
$prefix = 'O2024';
|
|
}
|
|
$protoCompleto = $prefix . '-' . str_pad((string) $protoNum, 4, '0', STR_PAD_LEFT);
|
|
|
|
$voceSpesaSnapshot = null;
|
|
$fornitoreSnapshot = null;
|
|
if (! empty($o->cod_for)) {
|
|
$forn = DB::table('fornitori')
|
|
->where('cod_forn', (string) $o->cod_for)
|
|
->orWhere('old_id', (string) $o->cod_for)
|
|
->first();
|
|
if ($forn) {
|
|
$fornitoreSnapshot = json_encode($forn);
|
|
|
|
if (! empty($o->cod_spe)) {
|
|
$voce = DB::table('voci_spesa')
|
|
->where('stabile_id', $this->stabileId)
|
|
->where(function ($q) use ($o): void {
|
|
$q->where('legacy_codice', (string) $o->cod_spe)
|
|
->orWhere('codice', (string) $o->cod_spe);
|
|
})
|
|
->first();
|
|
if ($voce) {
|
|
$voceSpesaSnapshot = json_encode($voce);
|
|
if (Schema::hasTable('fornitore_stabile_impostazioni')) {
|
|
DB::table('fornitore_stabile_impostazioni')->updateOrInsert(
|
|
[
|
|
'stabile_id' => $this->stabileId,
|
|
'fornitore_id' => $forn->id,
|
|
],
|
|
[
|
|
'voce_spesa_default_id' => $voce->id,
|
|
'conto_costo_default_id' => null,
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($voceSpesaSnapshot === null && ! empty($o->cod_spe)) {
|
|
$voce = DB::table('voci_spesa')
|
|
->where('stabile_id', $this->stabileId)
|
|
->where(function ($q) use ($o): void {
|
|
$q->where('legacy_codice', (string) $o->cod_spe)
|
|
->orWhere('codice', (string) $o->cod_spe);
|
|
})
|
|
->first();
|
|
if ($voce) {
|
|
$voceSpesaSnapshot = json_encode($voce);
|
|
}
|
|
}
|
|
|
|
$data = [
|
|
'tenant_id' => null,
|
|
'gestione_id' => $gestioneId,
|
|
'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' => $protoNum,
|
|
'protocollo_completo' => $protoCompleto,
|
|
'voce_spesa_snapshot' => $voceSpesaSnapshot,
|
|
'fornitore_snapshot' => $fornitoreSnapshot,
|
|
'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 purgeAuthoritativeLegacyReplica(array $steps): void
|
|
{
|
|
$stabileId = (int) ($this->stabileId ?? 0);
|
|
if ($stabileId <= 0) {
|
|
return;
|
|
}
|
|
|
|
$relevantSteps = array_intersect($steps, ['operazioni', 'rate', 'incassi', 'giroconti', 'straord', 'addebiti', 'detrazioni']);
|
|
if ($relevantSteps === []) {
|
|
return;
|
|
}
|
|
|
|
$gestioneIds = $this->collectGestioneIdsForStabile($stabileId);
|
|
$unitaIds = $this->collectUnitaIdsForStabile($stabileId);
|
|
$lavoroIds = $this->collectLavoriStraordinariIds($gestioneIds);
|
|
$detrazioneIds = $this->collectDetrazioneIds($gestioneIds, $lavoroIds);
|
|
|
|
$this->line('↻ Replica locale legacy: pulizia scoped prima del reimport per stabile ID ' . $stabileId);
|
|
|
|
if (in_array('detrazioni', $relevantSteps, true) && Schema::hasTable('detrazioni_partecipanti') && $detrazioneIds !== []) {
|
|
$deleted = DB::table('detrazioni_partecipanti')->whereIn('detrazione_id', $detrazioneIds)->delete();
|
|
$this->line(' detrazioni_partecipanti rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('detrazioni', $relevantSteps, true) && Schema::hasTable('detrazioni_fiscali_dom')) {
|
|
$deleted = $this->deleteScopedByCandidates('detrazioni_fiscali_dom', [
|
|
['column' => 'gestione_id', 'ids' => $gestioneIds],
|
|
['column' => 'lavoro_straordinario_id', 'ids' => $lavoroIds],
|
|
]);
|
|
$this->line(' detrazioni_fiscali_dom rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('straord', $relevantSteps, true) && Schema::hasTable('rate_lavori_straord') && $lavoroIds !== []) {
|
|
$deleted = DB::table('rate_lavori_straord')->whereIn('lavoro_straordinario_id', $lavoroIds)->delete();
|
|
$this->line(' rate_lavori_straord rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('addebiti', $relevantSteps, true) && Schema::hasTable('addebiti_personalizzati')) {
|
|
$deleted = $this->deleteScopedByCandidates('addebiti_personalizzati', [
|
|
['column' => 'gestione_id', 'ids' => $gestioneIds],
|
|
['column' => 'unita_immobiliare_id', 'ids' => $unitaIds],
|
|
]);
|
|
$this->line(' addebiti_personalizzati rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('giroconti', $relevantSteps, true) && Schema::hasTable('movimenti_interni')) {
|
|
$deleted = $this->deleteScopedByCandidates('movimenti_interni', [
|
|
['column' => 'gestione_id', 'ids' => $gestioneIds],
|
|
]);
|
|
$this->line(' movimenti_interni rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('incassi', $relevantSteps, true) && Schema::hasTable('incassi_estratto_conto')) {
|
|
$deleted = $this->deleteScopedByCandidates('incassi_estratto_conto', [
|
|
['column' => 'gestione_id', 'ids' => $gestioneIds],
|
|
]);
|
|
$this->line(' incassi_estratto_conto rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('incassi', $relevantSteps, true) && Schema::hasTable('incassi')) {
|
|
$deleted = $this->deleteScopedByCandidates('incassi', [
|
|
['column' => 'condominio_id', 'ids' => [$stabileId]],
|
|
['column' => 'stabile_id', 'ids' => [$stabileId]],
|
|
]);
|
|
$this->line(' incassi rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('rate', $relevantSteps, true) && Schema::hasTable('rate_emesse')) {
|
|
$deleted = $this->deleteScopedByCandidates('rate_emesse', [
|
|
['column' => 'stabile_id', 'ids' => [$stabileId]],
|
|
['column' => 'unita_immobiliare_id', 'ids' => $unitaIds],
|
|
]);
|
|
$this->line(' rate_emesse rimosse: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('straord', $relevantSteps, true) && Schema::hasTable('lavori_straordinari')) {
|
|
$deleted = $this->deleteScopedByCandidates('lavori_straordinari', [
|
|
['column' => 'gestione_id', 'ids' => $gestioneIds],
|
|
]);
|
|
$this->line(' lavori_straordinari rimossi: ' . $deleted);
|
|
}
|
|
|
|
if (in_array('operazioni', $relevantSteps, true) && Schema::hasTable('operazioni_contabili')) {
|
|
$deleted = $this->deleteScopedByCandidates('operazioni_contabili', [
|
|
['column' => 'gestione_id', 'ids' => $gestioneIds],
|
|
]);
|
|
$this->line(' operazioni_contabili rimosse: ' . $deleted);
|
|
}
|
|
}
|
|
|
|
private function collectGestioneIdsForStabile(int $stabileId): array
|
|
{
|
|
if ($stabileId <= 0 || ! Schema::hasTable('gestioni_contabili') || ! Schema::hasColumn('gestioni_contabili', 'stabile_id')) {
|
|
return [];
|
|
}
|
|
|
|
return DB::table('gestioni_contabili')
|
|
->where('stabile_id', $stabileId)
|
|
->pluck('id')
|
|
->map(fn($id) => (int) $id)
|
|
->filter(fn(int $id): bool => $id > 0)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function collectUnitaIdsForStabile(int $stabileId): array
|
|
{
|
|
if ($stabileId <= 0 || ! Schema::hasTable('unita_immobiliari') || ! Schema::hasColumn('unita_immobiliari', 'stabile_id')) {
|
|
return [];
|
|
}
|
|
|
|
return DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->pluck('id')
|
|
->map(fn($id) => (int) $id)
|
|
->filter(fn(int $id): bool => $id > 0)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function collectLavoriStraordinariIds(array $gestioneIds): array
|
|
{
|
|
if ($gestioneIds === [] || ! Schema::hasTable('lavori_straordinari') || ! Schema::hasColumn('lavori_straordinari', 'gestione_id')) {
|
|
return [];
|
|
}
|
|
|
|
return DB::table('lavori_straordinari')
|
|
->whereIn('gestione_id', $gestioneIds)
|
|
->pluck('id')
|
|
->map(fn($id) => (int) $id)
|
|
->filter(fn(int $id): bool => $id > 0)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function collectDetrazioneIds(array $gestioneIds, array $lavoroIds): array
|
|
{
|
|
if (! Schema::hasTable('detrazioni_fiscali_dom')) {
|
|
return [];
|
|
}
|
|
|
|
$query = DB::table('detrazioni_fiscali_dom');
|
|
$hasFilter = false;
|
|
|
|
if ($gestioneIds !== [] && Schema::hasColumn('detrazioni_fiscali_dom', 'gestione_id')) {
|
|
$query->whereIn('gestione_id', $gestioneIds);
|
|
$hasFilter = true;
|
|
}
|
|
|
|
if (! $hasFilter && $lavoroIds !== [] && Schema::hasColumn('detrazioni_fiscali_dom', 'lavoro_straordinario_id')) {
|
|
$query->whereIn('lavoro_straordinario_id', $lavoroIds);
|
|
$hasFilter = true;
|
|
}
|
|
|
|
if (! $hasFilter) {
|
|
return [];
|
|
}
|
|
|
|
return $query->pluck('id')
|
|
->map(fn($id) => (int) $id)
|
|
->filter(fn(int $id): bool => $id > 0)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function deleteScopedByCandidates(string $table, array $candidates): int
|
|
{
|
|
if (! Schema::hasTable($table)) {
|
|
return 0;
|
|
}
|
|
|
|
$applied = [];
|
|
foreach ($candidates as $candidate) {
|
|
$column = $candidate['column'] ?? null;
|
|
$ids = $candidate['ids'] ?? [];
|
|
if (! is_string($column) || $column === '' || $ids === [] || ! Schema::hasColumn($table, $column)) {
|
|
continue;
|
|
}
|
|
$applied[] = ['column' => $column, 'ids' => array_values(array_unique(array_map('intval', $ids)))];
|
|
}
|
|
|
|
if ($applied === []) {
|
|
return 0;
|
|
}
|
|
|
|
$query = DB::table($table);
|
|
$query->where(function ($builder) use ($applied): void {
|
|
foreach ($applied as $index => $filter) {
|
|
if ($index === 0) {
|
|
$builder->whereIn($filter['column'], $filter['ids']);
|
|
} else {
|
|
$builder->orWhereIn($filter['column'], $filter['ids']);
|
|
}
|
|
}
|
|
});
|
|
|
|
return $query->delete();
|
|
}
|
|
|
|
private function stepRate(?int $limit): int
|
|
{
|
|
if ($this->option('stabile')) {
|
|
$this->syncGestioniAnnualiFromGenerale((string) $this->option('stabile'));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if ($this->option('stabile')) {
|
|
$this->syncGestioniAnnualiFromGenerale((string) $this->option('stabile'));
|
|
}
|
|
|
|
$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,
|
|
trim((string) ($row->anno_emissione ?? $this->option('anno') ?? '')) ?: null,
|
|
);
|
|
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);
|
|
|
|
$annoGestione = trim((string) ($row->anno_gestione ?? ''));
|
|
$avvisoCode = $this->buildLegacyAvvisoCode($annoGestione !== '' ? $annoGestione : (string) ($row->anno_emissione ?? ''), is_numeric($row->numero_mese ?? null) ? (int) $row->numero_mese : null, $row->data_emissione ?? null);
|
|
|
|
$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 ?? ''),
|
|
$annoGestione !== '' ? 'anno_gestione=' . $annoGestione : '',
|
|
'cod_cond_src=' . $codCond,
|
|
'o_r_s=' . (string) ($row->tipo_quota ?? $row->tipo_soggetto ?? ''),
|
|
'n_mese=' . (string) ($row->numero_mese ?? ''),
|
|
is_numeric($row->numero_ricevuta ?? null) ? 'numero_ricevuta=' . (int) $row->numero_ricevuta : '',
|
|
$avvisoCode !== null ? 'avviso_code=' . $avvisoCode : '',
|
|
! empty($row->legacy_year) ? 'legacy_year=' . trim((string) $row->legacy_year) : '',
|
|
$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);
|
|
}
|
|
|
|
$alreadyTouchedThisRun = isset($touchedExistingRateIds[$existing->id]);
|
|
$baseOriginario = $alreadyTouchedThisRun ? (float) ($existing->importo_originario_unita ?? 0) : 0.0;
|
|
$baseAddebitato = $alreadyTouchedThisRun ? (float) ($existing->importo_addebitato_soggetto ?? 0) : 0.0;
|
|
|
|
DB::table('rate_emesse')
|
|
->where('id', $existing->id)
|
|
->update([
|
|
'descrizione' => $descrizione,
|
|
'importo_originario_unita' => $baseOriginario + $importo,
|
|
'importo_addebitato_soggetto' => $baseAddebitato + $importo,
|
|
'note' => $note,
|
|
'updated_at' => now(),
|
|
]);
|
|
$touchedExistingRateIds[$existing->id] = true;
|
|
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, ?string $legacyYear = null, ?string $partyName = null): ?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 = $this->findStabileRowByCodeForAdmin($stabileCol, $codStabile, $this->resolveImportAmministratoreId());
|
|
if ($stRow) {
|
|
$stabileId = $stRow->id;
|
|
}
|
|
}
|
|
if (! $stabileId) {
|
|
return null;
|
|
}
|
|
|
|
$cond = null;
|
|
if (Schema::connection('gescon_import')->hasTable('condomin')) {
|
|
$cond = $this->resolveCondominRowByReference($codStabile, $codCond, $legacyYear, $partyName);
|
|
}
|
|
|
|
$stableLegacyCondId = $this->extractStableLegacyCondId($cond, $codCond);
|
|
if ($stableLegacyCondId !== null && $stableLegacyCondId !== '' && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('legacy_cond_id', $stableLegacyCondId)
|
|
->first(['id']);
|
|
if ($unita?->id) {
|
|
return (int) $unita->id;
|
|
}
|
|
}
|
|
|
|
$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;
|
|
|
|
// Add condominium suffix to avoid global unification
|
|
if ($this->isCondominioSubject($nome, $cognome)) {
|
|
$nome = ($nome ? trim($nome) : '') . ' (' . trim($codStabile) . ')';
|
|
}
|
|
|
|
$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) {
|
|
$insertData = [
|
|
'stabile_id' => $stabileId,
|
|
'codice_unita' => $codice,
|
|
'legacy_cond_id' => Schema::hasColumn('unita_immobiliari', 'legacy_cond_id') ? $stableLegacyCondId : null,
|
|
'scala' => $scala,
|
|
'interno' => $interno,
|
|
'denominazione' => trim((string) ($nome ?? '') . ' ' . (string) ($cognome ?? '')) ?: null,
|
|
'tipo_unita' => 'abitazione',
|
|
'stato_occupazione' => 'occupata_proprietario',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('unita_immobiliari', 'attiva')) {
|
|
$insertData['attiva'] = true;
|
|
}
|
|
$uid = DB::table('unita_immobiliari')->insertGetId($insertData);
|
|
$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;
|
|
$insertData = [
|
|
'stabile_id' => $stabileId,
|
|
'codice_unita' => $codice,
|
|
'legacy_cond_id' => Schema::hasColumn('unita_immobiliari', 'legacy_cond_id') ? $stableLegacyCondId : null,
|
|
'scala' => $scala,
|
|
'interno' => $interno,
|
|
'denominazione' => 'Unità legacy ' . $codCond,
|
|
'tipo_unita' => 'abitazione',
|
|
'stato_occupazione' => 'occupata_proprietario',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
if (Schema::hasColumn('unita_immobiliari', 'attiva')) {
|
|
$insertData['attiva'] = true;
|
|
}
|
|
$uid = DB::table('unita_immobiliari')->insertGetId($insertData);
|
|
|
|
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 = $this->findStabileRowByCodeForAdmin($stabileCol, $codStabile, $this->resolveImportAmministratoreId());
|
|
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;
|
|
$detailRow = Schema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio')
|
|
? DB::connection('gescon_import')->table('rate_emissioni_dettaglio')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('numero_emissione', $numeroEmissione)
|
|
->orderBy('id')
|
|
->first(['anno_gestione', 'anno_emissione'])
|
|
: null;
|
|
$annoGestione = trim((string) ($detailRow->anno_gestione ?? ''));
|
|
$descr = $emissioneRow?->descrizione ?: ('Emissione ' . $numeroEmissione);
|
|
if ($annoGestione !== '' && stripos($descr, $annoGestione) === false) {
|
|
$descr .= ' · Gestione ' . $annoGestione;
|
|
}
|
|
|
|
$periodo = $this->resolveGestioneAnnualePeriod($codStabile, (string) ($emissioneRow?->anno_emissione ?? $detailRow->anno_emissione ?? ''));
|
|
$dataInizio = $periodo['ordinaria_dal'] ?? ($emissioneRow?->data_emissione ?? now()->toDateString());
|
|
$dataFine = $periodo['ordinaria_al'] ?? ($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 syncGestioniAnnualiFromGenerale(string $codStabile): void
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
|
|
return;
|
|
}
|
|
|
|
$root = rtrim((string) ($this->option('path') ?: '/mnt/gescon-archives/gescon'), '/');
|
|
$generale = $root . '/' . trim($codStabile) . '/generale_stabile.mdb';
|
|
$rows = $this->mdbExportToRows($generale, ['anni', 'Anni', 'ANNI']);
|
|
if ($rows === []) {
|
|
return;
|
|
}
|
|
|
|
foreach ($rows as $row) {
|
|
$cartella = trim((string) ($row['nome_dir'] ?? $row['nome_dir '] ?? $row['cartella'] ?? ''));
|
|
if ($cartella === '') {
|
|
continue;
|
|
}
|
|
|
|
$ordinariaDal = $this->normalizeLegacyDate($row['ordinarie_dal'] ?? $row['ordinaria_dal'] ?? null);
|
|
$ordinariaAl = $this->normalizeLegacyDate($row['ordinarie_al'] ?? $row['ordinaria_al'] ?? null);
|
|
$riscDal = $this->normalizeLegacyDate($row['riscald_dal'] ?? $row['riscaldamento_dal'] ?? null);
|
|
$riscAl = $this->normalizeLegacyDate($row['riscald_al'] ?? $row['riscaldamento_al'] ?? null);
|
|
|
|
$annoOrdinario = trim((string) ($row['anno_o'] ?? $row['anno'] ?? $row['anno_gestione'] ?? ''));
|
|
if ($annoOrdinario === '' && $ordinariaDal && $ordinariaAl) {
|
|
$startYear = date('Y', strtotime($ordinariaDal));
|
|
$endYear = date('Y', strtotime($ordinariaAl));
|
|
$annoOrdinario = $startYear === $endYear
|
|
? $startYear
|
|
: ($startYear . '/' . substr($endYear, -2));
|
|
}
|
|
|
|
DB::connection('gescon_import')->table('gestioni_annuali')->updateOrInsert(
|
|
[
|
|
'cod_stabile' => $codStabile,
|
|
'cartella' => $cartella,
|
|
],
|
|
[
|
|
'anno_ordinario' => $annoOrdinario !== '' ? $annoOrdinario : null,
|
|
'anno_riscaldamento' => trim((string) ($row['anno_r'] ?? '')) ?: null,
|
|
'descrizione' => trim((string) ($row['descrizione'] ?? $annoOrdinario)) ?: null,
|
|
'ordinaria_dal' => $ordinariaDal,
|
|
'ordinaria_al' => $ordinariaAl,
|
|
'riscaldamento_dal' => $riscDal,
|
|
'riscaldamento_al' => $riscAl,
|
|
'payload' => json_encode($row),
|
|
'updated_at' => now(),
|
|
'created_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
private function resolveGestioneAnnualePeriod(string $codStabile, string $cartella): array
|
|
{
|
|
$cartella = trim($cartella);
|
|
if ($cartella === '' || ! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
|
|
return [];
|
|
}
|
|
|
|
$row = DB::connection('gescon_import')->table('gestioni_annuali')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('cartella', $cartella)
|
|
->first(['ordinaria_dal', 'ordinaria_al', 'riscaldamento_dal', 'riscaldamento_al']);
|
|
|
|
if (! $row) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
'ordinaria_dal' => $row->ordinaria_dal ?? null,
|
|
'ordinaria_al' => $row->ordinaria_al ?? null,
|
|
'riscaldamento_dal' => $row->riscaldamento_dal ?? null,
|
|
'riscaldamento_al' => $row->riscaldamento_al ?? null,
|
|
];
|
|
}
|
|
|
|
private function buildLegacyAvvisoCode(?string $gestioneLabel, ?int $numeroMese, ?string $dataEmissione = null): ?string
|
|
{
|
|
if ($numeroMese === null || $numeroMese <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$year = null;
|
|
if ($dataEmissione !== null && trim($dataEmissione) !== '') {
|
|
try {
|
|
$year = (int) Carbon::parse($dataEmissione)->format('Y');
|
|
} catch (\Throwable $e) {
|
|
$year = null;
|
|
}
|
|
}
|
|
|
|
if ($year === null) {
|
|
$year = $this->extractEffectiveGestioneEndYear($gestioneLabel);
|
|
}
|
|
|
|
if ($year === null) {
|
|
return null;
|
|
}
|
|
|
|
return sprintf('%04d%02d', $year, $numeroMese);
|
|
}
|
|
|
|
private function extractEffectiveGestioneEndYear(?string $gestioneLabel): ?int
|
|
{
|
|
$value = trim((string) ($gestioneLabel ?? ''));
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/(19|20)(\d{2})\s*\/\s*(\d{2,4})/', $value, $m)) {
|
|
$startYear = (int) ($m[1] . $m[2]);
|
|
$suffix = trim((string) ($m[3] ?? ''));
|
|
if (strlen($suffix) === 2) {
|
|
$century = (int) floor($startYear / 100) * 100;
|
|
return $century + (int) $suffix;
|
|
}
|
|
|
|
return (int) $suffix;
|
|
}
|
|
|
|
if (preg_match('/(19|20)\d{2}/', $value, $m)) {
|
|
return (int) $m[0];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
private function stepIncassi(?int $limit): int
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('incassi')) {
|
|
return $this->stepIncassiEstrattoConto($limit);
|
|
}
|
|
|
|
$src = DB::connection('gescon_import')->table('incassi');
|
|
if ($this->option('stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
|
|
if (Schema::connection('gescon_import')->hasColumn('incassi', 'legacy_year')) {
|
|
$taggedRowsExist = DB::connection('gescon_import')
|
|
->table('incassi')
|
|
->where('cod_stabile', $this->option('stabile'))
|
|
->whereNotNull('legacy_year')
|
|
->exists();
|
|
|
|
if ($taggedRowsExist) {
|
|
$src->whereNotNull('legacy_year');
|
|
}
|
|
}
|
|
}
|
|
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, trim((string) ($i->anno_rif ?? $this->option('anno') ?? '')) ?: null)
|
|
: 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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
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 {
|
|
$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(),
|
|
]);
|
|
}
|
|
$data['created_at'] = $data['created_at'] ?? now();
|
|
$data['updated_at'] = now();
|
|
$this->dynamicInsert('incassi', $data);
|
|
$count++;
|
|
}
|
|
|
|
$count += $this->stepIncassiEstrattoConto($limit);
|
|
|
|
return $count;
|
|
}
|
|
|
|
private function stepIncassiEstrattoConto(?int $limit): int
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('in_da_ec') || ! Schema::hasTable('incassi_estratto_conto')) {
|
|
return 0;
|
|
}
|
|
|
|
$src = DB::connection('gescon_import')->table('in_da_ec');
|
|
if ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('in_da_ec', 'cod_stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
|
|
$requestedLegacyYear = $this->requestedLegacyYear();
|
|
if ($requestedLegacyYear !== null && Schema::connection('gescon_import')->hasColumn('in_da_ec', 'legacy_year')) {
|
|
$src->where('legacy_year', $requestedLegacyYear);
|
|
}
|
|
|
|
if ($limit) {
|
|
$src->limit($limit);
|
|
}
|
|
|
|
$columns = array_flip(Schema::getColumnListing('incassi_estratto_conto'));
|
|
$count = 0;
|
|
|
|
foreach ($src->orderBy('data_pag')->orderBy('protocollo')->get() as $row) {
|
|
$importo = (float) ($row->importo ?? 0);
|
|
if (abs($importo) < 0.0001) {
|
|
continue;
|
|
}
|
|
|
|
$codStabile = trim((string) ($row->cod_stabile ?? ($this->option('stabile') ?? '')));
|
|
$legacyYear = trim((string) ($row->legacy_year ?? $requestedLegacyYear ?? '')) ?: null;
|
|
$stabileId = $this->stabileId ?: $this->resolveStabileId();
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile($stabileId, $legacyYear, 'O');
|
|
if (! $gestioneId) {
|
|
continue;
|
|
}
|
|
|
|
$dataPagamento = $this->normalizeLegacyInDaEcDate($row->data_pag ?? null);
|
|
$descrizione = $this->composeLegacyInDaEcDescription($row);
|
|
$pattern = $this->extractLegacyScalaInterno((string) ($row->sc_int ?? ''));
|
|
|
|
$hashSeed = json_encode([
|
|
'stabile' => $codStabile,
|
|
'legacy_year' => $legacyYear,
|
|
'protocollo' => $row->protocollo ?? null,
|
|
'num_incasso' => $row->num_incasso ?? null,
|
|
'data_pag' => $dataPagamento,
|
|
'id_condomino' => $row->id_condomino ?? null,
|
|
'importo' => round($importo, 4),
|
|
'nome_condomino' => trim((string) ($row->nome_condomino ?? '')),
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
$hash = hash('sha256', (string) $hashSeed);
|
|
|
|
$exists = DB::table('incassi_estratto_conto')
|
|
->where('tenant_id', 'T1')
|
|
->where('hash_movimento', $hash)
|
|
->exists();
|
|
if ($exists) {
|
|
continue;
|
|
}
|
|
|
|
$payload = [
|
|
'source' => 'gescon_import.in_da_ec',
|
|
'cod_stabile' => $codStabile,
|
|
'legacy_year' => $legacyYear,
|
|
'protocollo' => $row->protocollo ?? null,
|
|
'data_pag' => $dataPagamento,
|
|
'num_incasso' => $row->num_incasso ?? null,
|
|
'id_condomino' => trim((string) ($row->id_condomino ?? '')) ?: null,
|
|
'sc_int' => trim((string) ($row->sc_int ?? '')) ?: null,
|
|
'nome_condomino' => trim((string) ($row->nome_condomino ?? '')) ?: null,
|
|
'descrizione_aggintiva' => trim((string) ($row->descrizione_aggintiva ?? '')) ?: null,
|
|
'importo' => $importo,
|
|
'legacy_file' => $row->legacy_file ?? null,
|
|
];
|
|
|
|
$insert = [
|
|
'tenant_id' => 'T1',
|
|
'gestione_id' => $gestioneId,
|
|
'id_gescon' => is_numeric($row->protocollo ?? null) ? (int) $row->protocollo : null,
|
|
'data_operazione' => $dataPagamento,
|
|
'data_valuta' => $dataPagamento,
|
|
'importo' => $importo,
|
|
'descrizione' => $descrizione,
|
|
'tipo_movimento' => 'entrata',
|
|
'ordinante' => trim((string) ($row->nome_condomino ?? '')) ?: null,
|
|
'riferimento_bancario' => trim((string) ($row->num_incasso ?? $row->protocollo ?? '')) ?: null,
|
|
'stato' => 'da_riconciliare',
|
|
'pattern_scala' => $pattern['scala'],
|
|
'pattern_interno' => $pattern['interno'],
|
|
'pattern_nome_estratto' => trim((string) ($row->nome_condomino ?? '')) ?: null,
|
|
'pattern_keywords' => json_encode(array_values(array_filter([
|
|
! empty($pattern['scala']) && ! empty($pattern['interno']) ? 'scala_interno' : null,
|
|
! empty($row->descrizione_aggintiva) ? 'descrizione_legacy' : null,
|
|
]))),
|
|
'file_origine' => $row->legacy_file ?? null,
|
|
'formato_file' => 'GESCON_MDB',
|
|
'hash_movimento' => $hash,
|
|
'metadati_gescon' => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$insert = array_intersect_key($insert, $columns);
|
|
DB::table('incassi_estratto_conto')->insert($insert);
|
|
$count++;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
private function normalizeLegacyInDaEcDate(mixed $value): ?string
|
|
{
|
|
$raw = trim((string) ($value ?? ''));
|
|
if ($raw === '') {
|
|
return null;
|
|
}
|
|
|
|
foreach (['Y-m-d', 'Y-m-d H:i:s', 'd/m/Y', 'd/m/y', 'm/d/Y', 'm/d/y'] as $format) {
|
|
$date = \DateTime::createFromFormat($format, $raw);
|
|
if ($date instanceof \DateTimeInterface) {
|
|
return $date->format('Y-m-d');
|
|
}
|
|
}
|
|
|
|
$timestamp = strtotime($raw);
|
|
|
|
return $timestamp ? date('Y-m-d', $timestamp) : null;
|
|
}
|
|
|
|
private function composeLegacyInDaEcDescription(object $row): string
|
|
{
|
|
$parts = array_values(array_filter([
|
|
trim((string) ($row->descrizione_aggintiva ?? '')),
|
|
trim((string) ($row->nome_condomino ?? '')),
|
|
trim((string) ($row->sc_int ?? '')),
|
|
! empty($row->protocollo) ? 'Prot. ' . $row->protocollo : null,
|
|
! empty($row->num_incasso) ? 'Incasso ' . $row->num_incasso : null,
|
|
]));
|
|
|
|
return $parts !== [] ? implode(' · ', $parts) : 'Movimento da estratto conto legacy';
|
|
}
|
|
|
|
private function extractLegacyScalaInterno(string $value): array
|
|
{
|
|
$raw = strtoupper(trim($value));
|
|
if ($raw !== '' && preg_match('/([A-Z])\s*[-\/]?\s*(\d+[A-Z]?)/', $raw, $matches)) {
|
|
return [
|
|
'scala' => $matches[1],
|
|
'interno' => $matches[2],
|
|
];
|
|
}
|
|
|
|
return ['scala' => null, 'interno' => null];
|
|
}
|
|
|
|
/**
|
|
* 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 $this->resolveContiBancariId($stabileId, $row);
|
|
}
|
|
|
|
/**
|
|
* 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 ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('straordinarie', 'cod_stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
if ($limit) {
|
|
$src->limit($limit);
|
|
}
|
|
|
|
$count = 0;
|
|
foreach ($src->get() as $s) {
|
|
$legacy = $s->id_stra ?? null;
|
|
if (! $legacy) {
|
|
continue;
|
|
}
|
|
|
|
$legacyYear = isset($s->legacy_year) ? (string) $s->legacy_year : null;
|
|
$annoGestione = $this->resolveLegacySnapshotYear($legacyYear, $s);
|
|
$numeroStraordinaria = $this->inferStraordinariaSequence($s->codice ?? null, $s->descriz_prev_cons ?? null, $legacy);
|
|
$gestioneId = $this->resolveGestioneContabileIdForStabile($this->resolveStabileId(), $legacyYear, 'S', $numeroStraordinaria);
|
|
$codiceDominio = $this->buildScopedLegacyCode((string) ($s->codice ?? ''), $annoGestione, $numeroStraordinaria, 'STRA');
|
|
$exists = DB::table('lavori_straordinari')
|
|
->when($gestioneId, fn($q) => $q->where('gestione_id', $gestioneId))
|
|
->where('codice', $codiceDominio)
|
|
->first();
|
|
if ($exists) {
|
|
continue;
|
|
}
|
|
|
|
$isFiscal = $this->isFiscalStraordinaria($s);
|
|
$descrizione = trim((string) ($s->descriz_prev_cons ?? ''));
|
|
if ($descrizione === '') {
|
|
$descrizione = trim((string) ($s->descriz_ccp ?? '')) ?: ('Straordinaria ' . $legacy);
|
|
}
|
|
$data = [
|
|
'legacy_id' => $legacy,
|
|
'gestione_id' => $gestioneId,
|
|
'codice' => $codiceDominio,
|
|
'descrizione' => $descrizione,
|
|
'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' => $this->normalizeStraordinariaNatura($s->natura ?? null),
|
|
'gruppo' => $s->aggregazione ?? null,
|
|
'periodicita' => $s->periodicita ?? null,
|
|
'note_json' => json_encode([
|
|
'legacy_code' => $s->codice ?? null,
|
|
'legacy_year' => $legacyYear,
|
|
'anno_gestione' => $annoGestione,
|
|
'numero_straordinaria' => $numeroStraordinaria,
|
|
'descriz_ccp' => $s->descriz_ccp ?? null,
|
|
'is_fiscal_declaration' => $isFiscal,
|
|
'fiscal_declaration_key' => $isFiscal && $annoGestione ? sprintf('%d-%02d', $annoGestione, (int) ($numeroStraordinaria ?? 1)) : null,
|
|
]),
|
|
'importo_previsto' => $this->sumStraordinariaImporto($s),
|
|
'chiuso' => false,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
$this->dynamicInsert('lavori_straordinari', $data);
|
|
$count++;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
private function normalizeStraordinariaNatura($value): string
|
|
{
|
|
$normalized = strtoupper(trim((string) $value));
|
|
|
|
if (in_array($normalized, ['E', 'ENTRATA', 'ENTRATE'], true)) {
|
|
return 'entrata';
|
|
}
|
|
|
|
return 'spesa';
|
|
}
|
|
|
|
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 ($this->option('stabile') && Schema::connection('gescon_import')->hasColumn('detrazioni_fiscali', 'cod_stabile')) {
|
|
$src->where('cod_stabile', $this->option('stabile'));
|
|
}
|
|
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 stepAssemblee(?int $limit): int
|
|
{
|
|
if (! Schema::hasTable('assemblee')) {
|
|
return 0;
|
|
}
|
|
|
|
$stabileId = $this->stabileId ?: $this->resolveStabileId();
|
|
$stabile = trim((string) ($this->option('stabile') ?? ''));
|
|
if (! $stabileId || $stabile === '') {
|
|
return 0;
|
|
}
|
|
|
|
$sources = $this->resolveAssembleeMdbSources($stabile);
|
|
if ($sources === []) {
|
|
return 0;
|
|
}
|
|
|
|
$assembleeColumns = Schema::getColumnListing('assemblee');
|
|
$canMatch = in_array('stabile_id', $assembleeColumns, true)
|
|
&& in_array('tipo', $assembleeColumns, true)
|
|
&& in_array('data_prima_convocazione', $assembleeColumns, true)
|
|
&& in_array('data_seconda_convocazione', $assembleeColumns, true);
|
|
|
|
$processed = 0;
|
|
foreach ($sources as $source) {
|
|
$rows = $this->mdbExportToRows($source['path'], ['Singolo_anno_assemblee', 'singolo_anno_assemblee', 'assemblee']);
|
|
if ($rows === []) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($rows as $row) {
|
|
if ($limit !== null && $processed >= $limit) {
|
|
return $processed;
|
|
}
|
|
|
|
$firstDate = $this->normalizeLegacyDateTime($row['dt_1_convoc'] ?? null, $row['ora_1_convoc'] ?? null);
|
|
$secondDate = $this->normalizeLegacyDateTime($row['dt_2_convoc'] ?? null, $row['ora_2_convoc'] ?? null);
|
|
if ($firstDate === null && $secondDate === null) {
|
|
continue;
|
|
}
|
|
|
|
$tipo = $this->normalizeLegacyAssembleaType($row['ordin_straord'] ?? null);
|
|
$luogo = $this->firstNonEmptyStr([
|
|
$row['luogo_2_convoc'] ?? null,
|
|
$row['luogo_1_convoc'] ?? null,
|
|
], 255);
|
|
$reference = $secondDate ?? $firstDate;
|
|
$referenceTs = $reference ? strtotime($reference) : false;
|
|
$note = $this->buildLegacyAssembleaNote($row, (string) ($source['legacy_year'] ?? ''));
|
|
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'tipo' => $tipo,
|
|
'data_assemblea' => $reference,
|
|
'data_prima_convocazione' => $firstDate,
|
|
'data_seconda_convocazione' => $secondDate,
|
|
'luogo' => $luogo,
|
|
'note' => $note,
|
|
'stato' => ($referenceTs !== false && $referenceTs < time()) ? 'svolta' : 'convocata',
|
|
'data_convocazione' => $firstDate ? substr($firstDate, 0, 10) : null,
|
|
'data_svolgimento' => ($referenceTs !== false && $referenceTs < time()) ? substr($reference, 0, 10) : null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if ($canMatch) {
|
|
$match = [
|
|
'stabile_id' => $stabileId,
|
|
'tipo' => $tipo,
|
|
'data_prima_convocazione' => $firstDate,
|
|
'data_seconda_convocazione' => $secondDate,
|
|
];
|
|
|
|
if (! $this->isDryRun) {
|
|
DB::table('assemblee')->updateOrInsert($match, array_merge($payload, [
|
|
'updated_at' => now(),
|
|
]));
|
|
|
|
$inserted = DB::table('assemblee')->where($match)->first();
|
|
if ($inserted) {
|
|
$this->importLegacySubsystemForAssemblea($inserted->id, $row, $source['path'], $stabileId);
|
|
}
|
|
}
|
|
} else {
|
|
if (! $this->isDryRun) {
|
|
$id = $this->dynamicInsert('assemblee', $payload);
|
|
if ($id) {
|
|
$this->importLegacySubsystemForAssemblea($id, $row, $source['path'], $stabileId);
|
|
}
|
|
}
|
|
}
|
|
|
|
$processed++;
|
|
}
|
|
}
|
|
|
|
return $processed;
|
|
}
|
|
|
|
private function importLegacySubsystemForAssemblea(int $assembleaId, array $row, string $mdbPath, int $stabileId): void
|
|
{
|
|
// 1. Import OdG
|
|
$odgText = trim((string) ($row['ordine_del_giorno'] ?? ''));
|
|
if ($odgText !== '') {
|
|
$punti = preg_split('/\r?\n/', $odgText);
|
|
$idx = 1;
|
|
foreach ($punti as $punto) {
|
|
$p = trim((string) $punto);
|
|
if ($p === '') continue;
|
|
|
|
DB::table('assemblee_ordine_giorno')->updateOrInsert([
|
|
'assemblea_id' => $assembleaId,
|
|
'numero_punto' => $idx,
|
|
], [
|
|
'titolo' => Str::limit($p, 100),
|
|
'descrizione' => $p,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
$idx++;
|
|
}
|
|
}
|
|
|
|
// 2. Import Presenze (pres_assemblee)
|
|
$numAss = $row['num_ass'] ?? null;
|
|
if ($numAss !== null) {
|
|
$presRows = $this->mdbExportToRows($mdbPath, ['pres_assemblee']);
|
|
foreach ($presRows as $pr) {
|
|
if ((int)($pr['num_assemblea'] ?? 0) !== (int)$numAss) {
|
|
continue;
|
|
}
|
|
|
|
$oldCondId = $pr['id_condomino'] ?? null;
|
|
if (!$oldCondId) continue;
|
|
|
|
// Trova soggetto ed unita per stabile
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('legacy_cond_id', $oldCondId)
|
|
->first();
|
|
|
|
if (!$unita) continue;
|
|
|
|
// Trova il soggetto principale legato all'unità
|
|
$soggettoId = DB::table('diritti_reali_unita')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->value('soggetto_id');
|
|
|
|
if (!$soggettoId) {
|
|
$soggettoId = DB::table('soggetti_unita_immobiliari')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->value('soggetto_id');
|
|
}
|
|
|
|
if (!$soggettoId) continue;
|
|
|
|
$tipoPartecipazione = 'personale';
|
|
$pda = strtolower(trim((string)($pr['p_d_a'] ?? '')));
|
|
if ($pda === 'd' || strpos($pda, 'delega') !== false) {
|
|
$tipoPartecipazione = 'delega';
|
|
}
|
|
|
|
DB::table('assemblee_presenze')->updateOrInsert([
|
|
'assemblea_id' => $assembleaId,
|
|
'soggetto_id' => $soggettoId,
|
|
'unita_immobiliare_id' => $unita->id,
|
|
], [
|
|
'tipo_partecipazione' => $tipoPartecipazione,
|
|
'ora_ingresso' => now(),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
// 3. Import Votazioni (votazioni_gen e votazioni_dett)
|
|
$votGenRows = $this->mdbExportToRows($mdbPath, ['votazioni_gen']);
|
|
$votDettRows = $this->mdbExportToRows($mdbPath, ['votazioni_dett']);
|
|
|
|
foreach ($votGenRows as $vg) {
|
|
if ((int)($vg['num_assemblea'] ?? 0) !== (int)$numAss) {
|
|
continue;
|
|
}
|
|
|
|
$idVotazione = $vg['id_votazione'] ?? null;
|
|
if (!$idVotazione) continue;
|
|
|
|
// Trova o crea un punto fittizio all'OdG per ospitare il voto
|
|
$descVot = trim((string)($vg['descrizione'] ?? ''));
|
|
$puntoOdg = DB::table('assemblee_ordine_giorno')
|
|
->where('assemblea_id', $assembleaId)
|
|
->where('titolo', $descVot ?: 'Votazione ordinaria')
|
|
->first();
|
|
|
|
if (!$puntoOdg) {
|
|
$nextPunto = (DB::table('assemblee_ordine_giorno')->where('assemblea_id', $assembleaId)->max('numero_punto') ?? 0) + 1;
|
|
$odgId = DB::table('assemblee_ordine_giorno')->insertGetId([
|
|
'assemblea_id' => $assembleaId,
|
|
'numero_punto' => $nextPunto,
|
|
'titolo' => Str::limit($descVot ?: 'Votazione ordinaria', 100),
|
|
'descrizione' => $descVot ?: 'Importata dall\'archivio storico',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
} else {
|
|
$odgId = $puntoOdg->id;
|
|
}
|
|
|
|
// Importa voti dettagliati
|
|
foreach ($votDettRows as $vd) {
|
|
if ((int)($vd['id_votazione'] ?? 0) !== (int)$idVotazione) {
|
|
continue;
|
|
}
|
|
|
|
$oldCondId = $vd['id_condomino'] ?? null;
|
|
if (!$oldCondId) continue;
|
|
|
|
$unita = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->where('legacy_cond_id', $oldCondId)
|
|
->first();
|
|
|
|
if (!$unita) continue;
|
|
|
|
$soggettoId = DB::table('diritti_reali_unita')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->value('soggetto_id');
|
|
|
|
if (!$soggettoId) {
|
|
$soggettoId = DB::table('soggetti_unita_immobiliari')
|
|
->where('unita_immobiliare_id', $unita->id)
|
|
->value('soggetto_id');
|
|
}
|
|
|
|
if (!$soggettoId) continue;
|
|
|
|
// Determina scelta voto
|
|
$votoScelta = 'astenuto';
|
|
if ((float)($vd['mill_si'] ?? 0) > 0) {
|
|
$votoScelta = 'favorevole';
|
|
} elseif ((float)($vd['mill_no'] ?? 0) > 0) {
|
|
$votoScelta = 'contrario';
|
|
}
|
|
|
|
DB::table('assemblee_voti')->updateOrInsert([
|
|
'ordine_giorno_id' => $odgId,
|
|
'soggetto_id' => $soggettoId,
|
|
'unita_immobiliare_id' => $unita->id,
|
|
], [
|
|
'assemblea_id' => $assembleaId,
|
|
'voto' => $votoScelta,
|
|
'millesimi_voto' => (float)($vd['millesimi'] ?? $unita->millesimi_generali ?? 0),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if ($this->option('reset-temp')) {
|
|
$this->line(" ↪︎ Reset staging (svuotamento dati temporanei per stabile {$stab})...");
|
|
$stagingTables = [
|
|
'stabili',
|
|
'condomin',
|
|
'comproprietari',
|
|
'dett_tab',
|
|
'tabelle_millesimali',
|
|
'voc_spe',
|
|
's_cassa',
|
|
'operazioni',
|
|
'rate',
|
|
'incassi',
|
|
'in_da_ec',
|
|
'rate_emissioni',
|
|
'rate_generale_gescon',
|
|
'rate_emissioni_dettaglio',
|
|
'rate_dettaglio_gescon',
|
|
'giri_conti',
|
|
'straordinarie',
|
|
'dett_pers',
|
|
'detrazioni_fiscali',
|
|
'gestioni_annuali',
|
|
];
|
|
\Illuminate\Support\Facades\DB::connection('gescon_import')->statement('SET FOREIGN_KEY_CHECKS=0;');
|
|
try {
|
|
foreach ($stagingTables as $table) {
|
|
if (\Illuminate\Support\Facades\Schema::connection('gescon_import')->hasTable($table)) {
|
|
if (\Illuminate\Support\Facades\Schema::connection('gescon_import')->hasColumn($table, 'cod_stabile')) {
|
|
\Illuminate\Support\Facades\DB::connection('gescon_import')->table($table)->where('cod_stabile', $stab)->delete();
|
|
} else {
|
|
\Illuminate\Support\Facades\DB::connection('gescon_import')->table($table)->truncate();
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
\Illuminate\Support\Facades\DB::connection('gescon_import')->statement('SET FOREIGN_KEY_CHECKS=1;');
|
|
}
|
|
}
|
|
|
|
// Assicura che lo stabile sia presente nella tabella staging stabili per evitare violazioni FK in condomin
|
|
$hasStabiliTable = Schema::connection('gescon_import')->hasTable('stabili');
|
|
if ($hasStabiliTable) {
|
|
$stabileExistsInStaging = DB::connection('gescon_import')
|
|
->table('stabili')
|
|
->where('cod_stabile', $stab)
|
|
->exists();
|
|
|
|
if (! $stabileExistsInStaging) {
|
|
$this->line(" ↪︎ Precarico staging stabile {$stab} da Stabili.mdb...");
|
|
$mdbStabili = $this->option('mdb');
|
|
if (! $mdbStabili && $this->option('path')) {
|
|
$base = rtrim($this->option('path'), '/');
|
|
if (is_file($base . '/dbc/Stabili.mdb')) {
|
|
$mdbStabili = $base . '/dbc/Stabili.mdb';
|
|
} elseif (is_file($base . '/Stabili.mdb')) {
|
|
$mdbStabili = $base . '/Stabili.mdb';
|
|
}
|
|
}
|
|
|
|
if ($mdbStabili && is_file($mdbStabili)) {
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $mdbStabili,
|
|
'--table' => 'stabili',
|
|
'--stabile' => $stab,
|
|
]);
|
|
} else {
|
|
$this->warn("Impossibile trovare Stabili.mdb per precaricare lo stabile {$stab} in staging.");
|
|
}
|
|
}
|
|
}
|
|
|
|
$legacyYearOpt = $this->option('anno') ? sprintf('%04d', (int) $this->option('anno')) : null;
|
|
$authoritativeCondomin = $this->resolveCondominMdbForStabile($stab, null);
|
|
$authoritativeLegacyYear = (string) ($authoritativeCondomin['legacy_year'] ?? '');
|
|
|
|
// Autoderiva --mdb-condomin se non fornito, preferendo l'anno richiesto e poi l'ultima annualità disponibile.
|
|
if (! $this->option('mdb-condomin')) {
|
|
$resolved = $this->resolveCondominMdbForStabile($stab, $legacyYearOpt);
|
|
if ($resolved !== null) {
|
|
try {
|
|
$this->input->setOption('mdb-condomin', $resolved['path']);
|
|
} catch (\Throwable $e) {
|
|
}
|
|
|
|
if ($this->output->isVerbose()) {
|
|
$this->line(sprintf(
|
|
' ↪︎ MDB condomin risolto da anni: %s (dir=%s, anno_o=%s, anno_r=%s)',
|
|
$resolved['path'],
|
|
(string) ($resolved['legacy_year'] ?? '-'),
|
|
(string) ($resolved['anno_o'] ?? '-'),
|
|
(string) ($resolved['anno_r'] ?? '-')
|
|
));
|
|
}
|
|
|
|
if (! empty($resolved['legacy_year'])) {
|
|
$legacyYearOpt = (string) $resolved['legacy_year'];
|
|
} elseif ($legacyYearOpt === null && ! empty($resolved['anno_o'])) {
|
|
$legacyYearOpt = (string) $resolved['anno_o'];
|
|
}
|
|
}
|
|
}
|
|
// 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;
|
|
}
|
|
|
|
$needsLoad = $this->stagingTableNeedsLoadForStabile('condomin', $stab, $legacyYearOpt);
|
|
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);
|
|
}
|
|
}
|
|
|
|
if ($authoritativeLegacyYear !== '' && $authoritativeLegacyYear !== $legacyYearOpt) {
|
|
$authoritativePath = (string) ($authoritativeCondomin['path'] ?? '');
|
|
if ($authoritativePath !== '' && is_file($authoritativePath)) {
|
|
foreach (['condomin', 'comproprietari'] as $table) {
|
|
if (! Schema::connection('gescon_import')->hasTable($table)) {
|
|
continue;
|
|
}
|
|
if (! $this->stagingTableNeedsLoadForStabile($table, $stab, $authoritativeLegacyYear)) {
|
|
continue;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', [
|
|
'--mdb' => $authoritativePath,
|
|
'--table' => $table,
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $authoritativeLegacyYear,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)) {
|
|
$refreshSingoloAnnoSlice = $legacyYearOpt !== null && $legacyYearOpt !== '';
|
|
$mdbGenerale = null;
|
|
$basePath = trim((string) $this->option('path'));
|
|
if ($basePath !== '' && $stab !== '') {
|
|
$candidate = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $stab . DIRECTORY_SEPARATOR . 'generale_stabile.mdb';
|
|
if (is_file($candidate)) {
|
|
$mdbGenerale = $candidate;
|
|
}
|
|
}
|
|
// dett_tab
|
|
if (Schema::connection('gescon_import')->hasTable('dett_tab')) {
|
|
$need = $refreshSingoloAnnoSlice || $this->stagingTableNeedsLoadForStabile('dett_tab', $stab, $legacyYearOpt);
|
|
if ($need) {
|
|
$args = [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'dett_tab',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
];
|
|
if ($refreshSingoloAnnoSlice) {
|
|
$args['--refresh-slice'] = true;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
// tabelle_millesimali
|
|
if (Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) {
|
|
$need = $refreshSingoloAnnoSlice || $this->stagingTableNeedsLoadForStabile('tabelle_millesimali', $stab, $legacyYearOpt);
|
|
if ($need) {
|
|
$args = [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'tabelle_millesimali',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
];
|
|
if ($refreshSingoloAnnoSlice) {
|
|
$args['--refresh-slice'] = true;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
// voc_spe (voci spesa)
|
|
if (Schema::connection('gescon_import')->hasTable('voc_spe')) {
|
|
$need = $refreshSingoloAnnoSlice || $this->stagingTableNeedsLoadForStabile('voc_spe', $stab, $legacyYearOpt);
|
|
if ($need) {
|
|
$args = [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'voc_spe',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
];
|
|
if ($refreshSingoloAnnoSlice) {
|
|
$args['--refresh-slice'] = true;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
// comproprietari
|
|
if (Schema::connection('gescon_import')->hasTable('comproprietari')) {
|
|
$need = $refreshSingoloAnnoSlice || $this->stagingTableNeedsLoadForStabile('comproprietari', $stab, $legacyYearOpt);
|
|
if ($need) {
|
|
$args = [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'comproprietari',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
];
|
|
if ($refreshSingoloAnnoSlice) {
|
|
$args['--refresh-slice'] = true;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
// Cre_Deb_preced (crediti/debiti pregressi)
|
|
if (Schema::connection('gescon_import')->hasTable('cre_deb_preced')) {
|
|
$need = $refreshSingoloAnnoSlice || $this->stagingTableNeedsLoadForStabile('cre_deb_preced', $stab, $legacyYearOpt);
|
|
if ($need) {
|
|
$args = [
|
|
'--mdb' => $mdbCondomin,
|
|
'--table' => 'cre_deb_preced',
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
];
|
|
if ($refreshSingoloAnnoSlice) {
|
|
$args['--refresh-slice'] = true;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
// Altre tabelle gestionali principali: operazioni, rate, incassi, giroconti, straordinarie, addebiti personalizzati, detrazioni
|
|
$more = [
|
|
['table' => 's_cassa', 'check' => 's_cassa', 'mdb' => $mdbCondomin],
|
|
['table' => 'operazioni', 'check' => 'operazioni', 'mdb' => $mdbCondomin],
|
|
['table' => 'rate', 'check' => 'rate', 'mdb' => $mdbCondomin],
|
|
['table' => 'incassi', 'check' => 'incassi', 'mdb' => $mdbCondomin],
|
|
['table' => 'in_da_ec', 'check' => 'in_da_ec', 'mdb' => $mdbGenerale],
|
|
['table' => 'emes_gen', 'check' => Schema::connection('gescon_import')->hasTable('rate_emissioni') ? 'rate_emissioni' : 'rate_generale_gescon', 'mdb' => $mdbGenerale],
|
|
['table' => 'emes_det', 'check' => Schema::connection('gescon_import')->hasTable('rate_emissioni_dettaglio') ? 'rate_emissioni_dettaglio' : 'rate_dettaglio_gescon', 'mdb' => $mdbGenerale],
|
|
['table' => 'giri_conti', 'check' => 'giri_conti', 'mdb' => $mdbCondomin],
|
|
['table' => 'straordinarie', 'check' => 'straordinarie', 'mdb' => $mdbCondomin],
|
|
['table' => 'dett_pers', 'check' => 'dett_pers', 'mdb' => $mdbCondomin],
|
|
['table' => 'detrazioni_fiscali', 'check' => 'detrazioni_fiscali', 'mdb' => $mdbCondomin],
|
|
];
|
|
foreach ($more as $item) {
|
|
$tableToLoad = (string) $item['table'];
|
|
$tableToCheck = (string) $item['check'];
|
|
$sourceMdb = $item['mdb'] ?? null;
|
|
if ($sourceMdb && Schema::connection('gescon_import')->hasTable($tableToCheck)) {
|
|
$need = $refreshSingoloAnnoSlice || $this->stagingTableNeedsLoadForStabile($tableToCheck, $stab, $legacyYearOpt);
|
|
if ($need) {
|
|
$args = [
|
|
'--mdb' => $sourceMdb,
|
|
'--table' => $tableToLoad,
|
|
'--stabile' => $stab,
|
|
'--legacy-year' => $legacyYearOpt,
|
|
];
|
|
if ($refreshSingoloAnnoSlice) {
|
|
$args['--refresh-slice'] = true;
|
|
}
|
|
$this->callSilently('gescon:load-mdb', $args);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($stab) {
|
|
$this->syncGestioniAnnualiFromGenerale($stab);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Non bloccare la pipeline se il precaricamento fallisce
|
|
if ($this->output->isVerbose()) {
|
|
$this->warn('Preflight staging fallito: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private function resolveCondominMdbForStabile(string $stab, ?string $legacyYear = null): ?array
|
|
{
|
|
$root = rtrim((string) ($this->option('path') ?: '/mnt/gescon-archives/gescon'), '/');
|
|
$stableDir = $root . '/' . $stab;
|
|
if (! is_dir($stableDir)) {
|
|
return null;
|
|
}
|
|
|
|
if ($legacyYear) {
|
|
$preferred = $stableDir . '/' . $legacyYear . '/singolo_anno.mdb';
|
|
if (is_file($preferred)) {
|
|
return [
|
|
'path' => $preferred,
|
|
'legacy_year' => $legacyYear,
|
|
'anno_o' => ctype_digit($legacyYear) && strlen($legacyYear) === 4 ? $legacyYear : null,
|
|
'anno_r' => ctype_digit($legacyYear) && strlen($legacyYear) === 4 ? $legacyYear : null,
|
|
];
|
|
}
|
|
}
|
|
|
|
$anniRows = [];
|
|
$generale = $stableDir . '/generale_stabile.mdb';
|
|
foreach ($this->mdbExportToRows($generale, ['anni', 'Anni', 'ANNI']) as $row) {
|
|
$dir = trim((string) ($row['nome_dir'] ?? $row['nome_dir '] ?? ''));
|
|
if ($dir === '') {
|
|
continue;
|
|
}
|
|
|
|
$candidatePath = $stableDir . '/' . $dir . '/singolo_anno.mdb';
|
|
if (! is_file($candidatePath)) {
|
|
continue;
|
|
}
|
|
|
|
$annoO = trim((string) ($row['anno_o'] ?? $row['anno'] ?? $row['anno_gestione'] ?? ''));
|
|
$annoR = trim((string) ($row['anno_r'] ?? ''));
|
|
$anniRows[] = [
|
|
'path' => $candidatePath,
|
|
'legacy_year' => $dir,
|
|
'anno_o' => $annoO !== '' ? $annoO : null,
|
|
'anno_r' => $annoR !== '' ? $annoR : null,
|
|
];
|
|
}
|
|
|
|
if ($legacyYear) {
|
|
foreach ($anniRows as $candidate) {
|
|
if ($legacyYear === ($candidate['anno_o'] ?? null) || $legacyYear === ($candidate['anno_r'] ?? null)) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($anniRows !== []) {
|
|
usort($anniRows, static function (array $left, array $right): int {
|
|
$leftAnnoO = is_numeric($left['anno_o'] ?? null) ? (int) $left['anno_o'] : -1;
|
|
$rightAnnoO = is_numeric($right['anno_o'] ?? null) ? (int) $right['anno_o'] : -1;
|
|
if ($leftAnnoO !== $rightAnnoO) {
|
|
return $rightAnnoO <=> $leftAnnoO;
|
|
}
|
|
|
|
$leftAnnoR = is_numeric($left['anno_r'] ?? null) ? (int) $left['anno_r'] : -1;
|
|
$rightAnnoR = is_numeric($right['anno_r'] ?? null) ? (int) $right['anno_r'] : -1;
|
|
if ($leftAnnoR !== $rightAnnoR) {
|
|
return $rightAnnoR <=> $leftAnnoR;
|
|
}
|
|
|
|
return strcmp((string) ($right['legacy_year'] ?? ''), (string) ($left['legacy_year'] ?? ''));
|
|
});
|
|
|
|
return $anniRows[0];
|
|
}
|
|
|
|
$candidates = [];
|
|
foreach ((array) glob($stableDir . '/*/singolo_anno.mdb') as $candidate) {
|
|
if (! is_file($candidate)) {
|
|
continue;
|
|
}
|
|
|
|
$dirName = basename(dirname($candidate));
|
|
$sortKey = ctype_digit($dirName) ? (int) $dirName : -1;
|
|
$candidates[] = [
|
|
'path' => $candidate,
|
|
'legacy_year' => $dirName !== '' ? $dirName : null,
|
|
'anno_o' => null,
|
|
'anno_r' => null,
|
|
'sort_key' => $sortKey,
|
|
];
|
|
}
|
|
|
|
if ($candidates === []) {
|
|
return null;
|
|
}
|
|
|
|
usort($candidates, static function (array $left, array $right): int {
|
|
if ($left['sort_key'] === $right['sort_key']) {
|
|
return strcmp((string) $right['legacy_year'], (string) $left['legacy_year']);
|
|
}
|
|
|
|
return $right['sort_key'] <=> $left['sort_key'];
|
|
});
|
|
|
|
return Arr::except($candidates[0], ['sort_key']);
|
|
}
|
|
|
|
private function stagingTableNeedsLoadForStabile(string $table, string $stab, ?string $legacyYear = null): bool
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable($table)) {
|
|
return false;
|
|
}
|
|
|
|
$conn = DB::connection('gescon_import');
|
|
if (Schema::connection('gescon_import')->hasColumn($table, 'cod_stabile')) {
|
|
$query = $conn->table($table)->where('cod_stabile', $stab);
|
|
if ($legacyYear !== null && $legacyYear !== '' && Schema::connection('gescon_import')->hasColumn($table, 'legacy_year')) {
|
|
$query->where('legacy_year', $legacyYear);
|
|
}
|
|
|
|
return ! $query->exists();
|
|
}
|
|
|
|
return $conn->table($table)->count() === 0;
|
|
}
|
|
|
|
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 = $this->findStabileRowByCodeForAdmin($col, $codRichiesto, $this->resolveImportAmministratoreId());
|
|
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,
|
|
'amministratore_id' => $this->resolveImportAmministratoreId(),
|
|
'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("TRIM(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();
|
|
$isCondo = false;
|
|
foreach ($rows as $row) {
|
|
$fullName = strtoupper(trim(($row->cognome ?? '') . ' ' . ($row->nome ?? '')));
|
|
if (str_contains($fullName, 'CONDOMINIO') || str_contains($fullName, 'PARTI COMUNI')) {
|
|
$isCondo = true;
|
|
break;
|
|
}
|
|
}
|
|
if ($isCondo) {
|
|
$groups = $rows->groupBy(function ($row) {
|
|
return $this->normalizeNameKey($row->nome, $row->cognome);
|
|
});
|
|
foreach ($groups as $group) {
|
|
if ($group->count() > 1) {
|
|
$this->mergeSoggettiGroup($group, $field);
|
|
}
|
|
}
|
|
} else {
|
|
$this->mergeSoggettiGroup($rows, $field);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function mergeSoggettiGroup($rows, string $field): void
|
|
{
|
|
$keep = $rows->first();
|
|
foreach ($rows->skip(1) as $rm) {
|
|
// Riassegna proprieta
|
|
if (Schema::hasTable('proprieta')) {
|
|
$this->mergeProprietaForDuplicateSoggetto((int) $rm->id, (int) $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 dedupSoggettiByName(): void
|
|
{
|
|
if (! Schema::hasTable('soggetti')) {
|
|
return;
|
|
}
|
|
|
|
$all = DB::table('soggetti')->orderBy('id')->get();
|
|
// Raggruppa per normalized name key
|
|
$groups = $all->groupBy(function ($row) {
|
|
return $this->normalizeNameKey($row->nome, $row->cognome);
|
|
});
|
|
|
|
foreach ($groups as $key => $rows) {
|
|
if ($key === '' || $rows->count() <= 1) {
|
|
continue;
|
|
}
|
|
|
|
// Escludiamo i soggetti condominio dal merge globale
|
|
$first = $rows->first();
|
|
$fullName = strtoupper(trim(($first->cognome ?? '') . ' ' . ($first->nome ?? '')));
|
|
if (str_contains($fullName, 'CONDOMINIO') || str_contains($fullName, 'PARTI COMUNI')) {
|
|
continue;
|
|
}
|
|
|
|
// Ordina i soggetti in modo che quello da mantenere ("keep") sia preferito:
|
|
// 1. Quello con codice_fiscale non vuoto
|
|
// 2. Quello con email non vuota
|
|
// 3. Quello con ID più basso
|
|
$sorted = $rows->sort(function ($a, $b) {
|
|
$aCf = !empty($a->codice_fiscale) ? 1 : 0;
|
|
$bCf = !empty($b->codice_fiscale) ? 1 : 0;
|
|
if ($aCf !== $bCf) {
|
|
return $bCf <=> $aCf; // prima con CF
|
|
}
|
|
|
|
$aEmail = !empty($a->email) ? 1 : 0;
|
|
$bEmail = !empty($b->email) ? 1 : 0;
|
|
if ($aEmail !== $bEmail) {
|
|
return $bEmail <=> $aEmail; // prima con Email
|
|
}
|
|
|
|
return $a->id <=> $b->id; // prima con ID minore
|
|
});
|
|
|
|
$keep = $sorted->first();
|
|
foreach ($sorted->skip(1) as $rm) {
|
|
// Riassegna proprieta
|
|
if (Schema::hasTable('proprieta')) {
|
|
$this->mergeProprietaForDuplicateSoggetto((int) $rm->id, (int) $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 mergeProprietaForDuplicateSoggetto(int $removeId, int $keepId): void
|
|
{
|
|
$rows = DB::table('proprieta')
|
|
->where('soggetto_id', $removeId)
|
|
->orderBy('id')
|
|
->get();
|
|
|
|
foreach ($rows as $row) {
|
|
$this->moveProprietaRowToSoggetto((int) $row->id, $keepId);
|
|
}
|
|
}
|
|
|
|
private function moveProprietaRowToSoggetto(int $rowId, int $targetSoggettoId, ?string $forcedDataInizio = null, ?string $forcedDataFine = null): void
|
|
{
|
|
$row = DB::table('proprieta')->where('id', $rowId)->first();
|
|
if (! $row) {
|
|
return;
|
|
}
|
|
|
|
$existing = DB::table('proprieta')
|
|
->where('soggetto_id', $targetSoggettoId)
|
|
->where('unita_immobiliare_id', $row->unita_immobiliare_id)
|
|
->where('tipo_diritto', $row->tipo_diritto)
|
|
->where('id', '!=', $rowId)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$patch = [];
|
|
|
|
if (empty($existing->percentuale_possesso) && ! empty($row->percentuale_possesso)) {
|
|
$patch['percentuale_possesso'] = $row->percentuale_possesso;
|
|
}
|
|
|
|
if (empty($existing->percentuale_detrazione) && ! empty($row->percentuale_detrazione)) {
|
|
$patch['percentuale_detrazione'] = $row->percentuale_detrazione;
|
|
}
|
|
|
|
$existingStart = $this->normalizeLegacyDate($existing->data_inizio ?? null);
|
|
$rowStart = $forcedDataInizio ?? $this->normalizeLegacyDate($row->data_inizio ?? null);
|
|
if ($rowStart && (! $existingStart || $rowStart < $existingStart)) {
|
|
$patch['data_inizio'] = $rowStart;
|
|
}
|
|
|
|
$existingEnd = $this->normalizeLegacyDate($existing->data_fine ?? null);
|
|
$rowEnd = $forcedDataFine ?? $this->normalizeLegacyDate($row->data_fine ?? null);
|
|
if ($rowEnd && (! $existingEnd || $rowEnd > $existingEnd)) {
|
|
$patch['data_fine'] = $rowEnd;
|
|
}
|
|
|
|
if ($patch !== []) {
|
|
$patch['updated_at'] = now();
|
|
DB::table('proprieta')->where('id', $existing->id)->update($patch);
|
|
}
|
|
|
|
DB::table('proprieta')->where('id', $rowId)->delete();
|
|
return;
|
|
}
|
|
|
|
$patch = [
|
|
'soggetto_id' => $targetSoggettoId,
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if ($forcedDataInizio !== null) {
|
|
$patch['data_inizio'] = $forcedDataInizio;
|
|
}
|
|
|
|
if ($forcedDataFine !== null) {
|
|
$patch['data_fine'] = $forcedDataFine;
|
|
}
|
|
|
|
DB::table('proprieta')->where('id', $rowId)->update($patch);
|
|
}
|
|
|
|
private function findUnitaByStagingRow($r)
|
|
{
|
|
if (! Schema::hasTable('unita_immobiliari')) {
|
|
return null;
|
|
}
|
|
|
|
$stabileId = $this->stabileId;
|
|
$crit = [];
|
|
if ($stabileId !== null && Schema::hasColumn('unita_immobiliari', 'stabile_id')) {
|
|
$crit['stabile_id'] = $stabileId;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'scala') && isset($r->scala)) {
|
|
$crit['scala'] = $r->scala;
|
|
}
|
|
if (Schema::hasColumn('unita_immobiliari', 'interno')) {
|
|
$int = $this->normalizeLegacyInternoLookup($r->interno ?? null);
|
|
if ($int !== null && $int !== '' && ! preg_match('/^0+$/', $int)) {
|
|
$crit['interno'] = $int;
|
|
}
|
|
}
|
|
if (! empty($crit)) {
|
|
$match = DB::table('unita_immobiliari')
|
|
->where($crit)
|
|
->when(Schema::hasColumn('unita_immobiliari', 'deleted_at'), fn($q) => $q->whereNull('deleted_at'))
|
|
->first();
|
|
if ($match) {
|
|
return $match;
|
|
}
|
|
}
|
|
|
|
if ($stabileId !== null && Schema::hasColumn('unita_immobiliari', 'stabile_id')) {
|
|
$legacyCond = $this->extractStableLegacyCondId($r);
|
|
if ($legacyCond !== null && $legacyCond !== '' && Schema::hasColumn('unita_immobiliari', 'legacy_cond_id')) {
|
|
return DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->when(Schema::hasColumn('unita_immobiliari', 'deleted_at'), fn($q) => $q->whereNull('deleted_at'))
|
|
->where('legacy_cond_id', $legacyCond)
|
|
->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['codice_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null)));
|
|
if (! $nomeDir || trim((string) $nomeDir) !== $code) {
|
|
continue;
|
|
}
|
|
|
|
// Prefer direct columns if exist
|
|
$cfCondo = $row['codice_fisc'] ?? $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 array_merge($row, [
|
|
'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 resolveStabiliMdbPath(string $basePath = ''): ?string
|
|
{
|
|
$candidates = [];
|
|
$explicit = trim((string) ($this->option('mdb') ?? ''));
|
|
if ($explicit !== '') {
|
|
$candidates[] = $explicit;
|
|
}
|
|
|
|
$basePath = trim($basePath);
|
|
if ($basePath !== '') {
|
|
$normalizedBase = rtrim($basePath, '/');
|
|
$candidates[] = $normalizedBase . '/dbc/Stabili.mdb';
|
|
$candidates[] = $normalizedBase . '/dbc/stabili.mdb';
|
|
$candidates[] = $normalizedBase . '/Stabili.mdb';
|
|
$candidates[] = $normalizedBase . '/stabili.mdb';
|
|
}
|
|
|
|
$candidates[] = '/mnt/gescon-archives/gescon/dbc/Stabili.mdb';
|
|
$candidates[] = '/mnt/gescon-archives/gescon/Stabili.mdb';
|
|
|
|
foreach (array_values(array_unique(array_filter($candidates))) as $candidate) {
|
|
if (is_file($candidate) && is_readable($candidate)) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return $explicit !== '' ? $explicit : null;
|
|
}
|
|
|
|
private function applyLegacyStabileDerivedFields(array $update, array $legacyRow, $existing): array
|
|
{
|
|
$ordMonths = $this->extractLegacyRateMonths($legacyRow, 'ord_rata_');
|
|
$risMonths = $this->extractLegacyRateMonths($legacyRow, 'ris_rata_');
|
|
|
|
if ($ordMonths !== [] && Schema::hasColumn('stabili', 'rate_ordinarie_mesi')) {
|
|
$current = $this->decodeJsonArray($existing->rate_ordinarie_mesi ?? null);
|
|
if ($this->forceMapping || $current === []) {
|
|
$update['rate_ordinarie_mesi'] = json_encode($ordMonths);
|
|
}
|
|
}
|
|
|
|
if ($risMonths !== [] && Schema::hasColumn('stabili', 'rate_riscaldamento_mesi')) {
|
|
$current = $this->decodeJsonArray($existing->rate_riscaldamento_mesi ?? null);
|
|
if ($this->forceMapping || $current === []) {
|
|
$update['rate_riscaldamento_mesi'] = json_encode($risMonths);
|
|
}
|
|
}
|
|
|
|
$ibanBanca = $this->normalizeIbanValue($legacyRow['iban_banca'] ?? ($legacyRow['iban_principale'] ?? null));
|
|
$ibanPosta = $this->normalizeIbanValue($legacyRow['iban_posta'] ?? null);
|
|
$bancaPrincipale = trim((string) ($legacyRow['banca'] ?? ($legacyRow['denominazione_banca'] ?? '')));
|
|
$bancaSecondaria = trim((string) ($legacyRow['banca2'] ?? ''));
|
|
if ($bancaSecondaria === '' && ($ibanPosta || ! empty($legacyRow['num_ccp'] ?? null))) {
|
|
$bancaSecondaria = 'Conto postale';
|
|
}
|
|
|
|
if ($ibanBanca && Schema::hasColumn('stabili', 'iban_principale') && ($this->forceMapping || empty($existing->iban_principale))) {
|
|
$update['iban_principale'] = $ibanBanca;
|
|
}
|
|
if ($bancaPrincipale !== '' && Schema::hasColumn('stabili', 'banca_principale') && ($this->forceMapping || empty($existing->banca_principale))) {
|
|
$update['banca_principale'] = $bancaPrincipale;
|
|
}
|
|
if ($ibanPosta && Schema::hasColumn('stabili', 'iban_secondario') && ($this->forceMapping || empty($existing->iban_secondario))) {
|
|
$update['iban_secondario'] = $ibanPosta;
|
|
}
|
|
if ($bancaSecondaria !== '' && Schema::hasColumn('stabili', 'banca_secondaria') && ($this->forceMapping || empty($existing->banca_secondaria))) {
|
|
$update['banca_secondaria'] = $bancaSecondaria;
|
|
}
|
|
if (Schema::hasColumn('stabili', 'iban_condominio') && ($this->forceMapping || empty($existing->iban_condominio))) {
|
|
$ibanCondominio = $ibanBanca ?: $ibanPosta;
|
|
if ($ibanCondominio) {
|
|
$update['iban_condominio'] = $ibanCondominio;
|
|
}
|
|
}
|
|
|
|
return $update;
|
|
}
|
|
|
|
private function extractLegacyRateMonths(array $legacyRow, string $prefix): array
|
|
{
|
|
$months = [];
|
|
foreach (range(1, 12) as $month) {
|
|
$key = $prefix . $month;
|
|
if (! array_key_exists($key, $legacyRow)) {
|
|
continue;
|
|
}
|
|
$value = strtolower(trim((string) $legacyRow[$key]));
|
|
if ($value !== '' && ! in_array($value, ['0', 'false', 'no', 'off', 'n'], true)) {
|
|
$months[] = $month;
|
|
}
|
|
}
|
|
|
|
return $months;
|
|
}
|
|
|
|
private function decodeJsonArray(mixed $value): array
|
|
{
|
|
if (is_array($value)) {
|
|
return array_values(array_map('intval', $value));
|
|
}
|
|
if (! is_string($value) || trim($value) === '') {
|
|
return [];
|
|
}
|
|
$decoded = json_decode($value, true);
|
|
if (! is_array($decoded)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_map('intval', $decoded));
|
|
}
|
|
|
|
private function normalizeIbanValue(mixed $value): ?string
|
|
{
|
|
if (! is_string($value)) {
|
|
return null;
|
|
}
|
|
$normalized = strtoupper(preg_replace('/\s+/', '', $value));
|
|
|
|
return $normalized !== '' ? $normalized : null;
|
|
}
|
|
|
|
private function buildBancheRowsFromStabileConfig(mixed $configRaw): array
|
|
{
|
|
if (is_string($configRaw) && trim($configRaw) !== '') {
|
|
$configRaw = json_decode($configRaw, true);
|
|
}
|
|
if (! is_array($configRaw)) {
|
|
return [];
|
|
}
|
|
|
|
$banca = is_array($configRaw['banca'] ?? null) ? $configRaw['banca'] : [];
|
|
if ($banca === []) {
|
|
return [];
|
|
}
|
|
|
|
$rows = [];
|
|
$ibanBanca = $this->normalizeIbanValue($banca['iban_banca'] ?? null);
|
|
if ($ibanBanca) {
|
|
$rows[] = [
|
|
'banca' => trim((string) ($banca['banca'] ?? 'Banca condominio')),
|
|
'iban' => $ibanBanca,
|
|
'cod_cassa' => 'CCB',
|
|
'intestatario' => trim((string) ($banca['intestaz_ccp'] ?? '')),
|
|
];
|
|
}
|
|
|
|
$ibanPosta = $this->normalizeIbanValue($banca['iban_posta'] ?? null);
|
|
$numeroPosta = trim((string) ($banca['num_ccp'] ?? ''));
|
|
if ($ibanPosta || $numeroPosta !== '') {
|
|
$rows[] = [
|
|
'banca' => trim((string) ($banca['banca2'] ?? 'Conto postale')),
|
|
'iban' => $ibanPosta,
|
|
'n_conto' => $numeroPosta,
|
|
'cod_cassa' => 'CCP',
|
|
'intestatario' => trim((string) ($banca['intestaz_ccp'] ?? '')),
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function mergeLegacyBancheRows(array $rows, array $configRows): array
|
|
{
|
|
$seen = [];
|
|
foreach ($rows as $row) {
|
|
if (! is_array($row)) {
|
|
continue;
|
|
}
|
|
$iban = $this->normalizeIbanValue($row['iban'] ?? ($row['iban_banca'] ?? ($row['iban_posta'] ?? null)));
|
|
$numero = trim((string) ($row['n_conto'] ?? ($row['numero_conto'] ?? '')));
|
|
$codCassa = strtoupper(trim((string) ($row['cod_cassa'] ?? '')));
|
|
$key = $iban ?: ($numero !== '' ? 'N:' . $numero : ($codCassa !== '' ? 'C:' . $codCassa : null));
|
|
if ($key) {
|
|
$seen[$key] = true;
|
|
}
|
|
}
|
|
|
|
foreach ($configRows as $row) {
|
|
if (! is_array($row)) {
|
|
continue;
|
|
}
|
|
$iban = $this->normalizeIbanValue($row['iban'] ?? null);
|
|
$numero = trim((string) ($row['n_conto'] ?? ($row['numero_conto'] ?? '')));
|
|
$codCassa = strtoupper(trim((string) ($row['cod_cassa'] ?? '')));
|
|
$key = $iban ?: ($numero !== '' ? 'N:' . $numero : ($codCassa !== '' ? 'C:' . $codCassa : null));
|
|
if (! $key || isset($seen[$key])) {
|
|
continue;
|
|
}
|
|
|
|
$rows[] = $row;
|
|
$seen[$key] = true;
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
$amminId = $this->resolveImportAmministratoreId();
|
|
$row = $this->findStabileRowByCodeForAdmin($col, $cod, $amminId);
|
|
if ($row) {
|
|
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
|
|
if (Schema::hasColumn('stabili', 'amministratore_id') && ! $amminId) {
|
|
// 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 = $this->findStabileRowByCodeForAdmin($col, $cod, $amminId);
|
|
return $row?->id;
|
|
}
|
|
|
|
private function resolveImportAmministratoreId(): ?int
|
|
{
|
|
if (! Schema::hasColumn('stabili', 'amministratore_id')) {
|
|
return null;
|
|
}
|
|
|
|
$optAdm = $this->option('amministratore-id');
|
|
|
|
return $optAdm && DB::table('amministratori')->where('id', (int) $optAdm)->exists()
|
|
? (int) $optAdm
|
|
: null;
|
|
}
|
|
|
|
private function findStabileRowByCodeForAdmin(string $column, string $code, ?int $amministratoreId)
|
|
{
|
|
$normalized = trim($code);
|
|
$variants = array_values(array_unique(array_filter([
|
|
$normalized,
|
|
ltrim($normalized, '0'),
|
|
], static fn(?string $value): bool => $value !== null && $value !== '')));
|
|
|
|
$query = DB::table('stabili');
|
|
if ($amministratoreId && Schema::hasColumn('stabili', 'amministratore_id')) {
|
|
$query->where('amministratore_id', $amministratoreId);
|
|
}
|
|
|
|
return $query
|
|
->where(function ($inner) use ($column, $variants): void {
|
|
foreach ($variants as $index => $variant) {
|
|
if ($index === 0) {
|
|
$inner->where($column, $variant);
|
|
continue;
|
|
}
|
|
|
|
$inner->orWhere($column, $variant);
|
|
}
|
|
})
|
|
->orderBy('id')
|
|
->first();
|
|
}
|
|
|
|
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 buildLegacyUnitCode(string $stabileCode, string $scala, string $interno, ?string $salt = null): string
|
|
{
|
|
$maxLen = 20;
|
|
|
|
$stablePart = $this->sanitizeUnitCodeSegment($stabileCode, 4) ?: 'S0';
|
|
$scalaPart = $this->sanitizeUnitCodeSegment($scala, 4) ?: 'A';
|
|
$internoPart = $this->sanitizeUnitCodeSegment($interno, 32) ?: 'INT';
|
|
|
|
$base = $stablePart . '-' . $scalaPart . '-' . $internoPart;
|
|
if (strlen($base) <= $maxLen) {
|
|
return $base;
|
|
}
|
|
|
|
$hash = strtoupper(substr(hash('crc32b', implode('|', [$stabileCode, $scala, $interno, (string) $salt])), 0, 4));
|
|
$reserved = strlen($stablePart) + strlen($scalaPart) + strlen($hash) + 3;
|
|
$availableInterno = max(1, $maxLen - $reserved);
|
|
$internoCompact = substr($internoPart, 0, $availableInterno);
|
|
|
|
return $stablePart . '-' . $scalaPart . '-' . $internoCompact . '-' . $hash;
|
|
}
|
|
|
|
private function buildLegacyCollisionUnitCode(string $baseCode, mixed $suffixSource): string
|
|
{
|
|
$suffix = strtoupper(trim((string) $suffixSource));
|
|
$suffix = preg_replace('/[^A-Z0-9]/', '', $suffix ?? '');
|
|
$suffix = $suffix !== '' ? substr($suffix, 0, 6) : substr(strtoupper(hash('crc32b', $baseCode)), 0, 6);
|
|
|
|
$available = max(1, 20 - strlen($suffix) - 1);
|
|
$base = substr($baseCode, 0, $available);
|
|
|
|
return rtrim($base, '-') . '-' . $suffix;
|
|
}
|
|
|
|
private function resolveLegacyUnitClassification(object $row, ?string $interno, ?string $ownerDescriptor, ?string $explicitPertinenzaType = null): array
|
|
{
|
|
$descriptor = Str::upper(trim(implode(' ', array_filter([
|
|
$interno,
|
|
$ownerDescriptor,
|
|
is_string($row->note ?? null) ? $row->note : null,
|
|
is_string($row->descrizione ?? null) ? $row->descrizione : null,
|
|
is_string($row->destinazione ?? null) ? $row->destinazione : null,
|
|
], fn($value) => is_string($value) && trim($value) !== ''))));
|
|
|
|
$mapping = $this->findLegacyUnitTypeMapping(
|
|
'condomin',
|
|
'tipo_pr',
|
|
isset($row->tipo_pr) ? trim((string) $row->tipo_pr) : null,
|
|
$descriptor,
|
|
);
|
|
|
|
if (str_contains($descriptor, 'MAGAZZ')) {
|
|
$mapping = array_merge($mapping, [
|
|
'tipo_unita' => 'magazzino_deposito',
|
|
'tipologia_codice' => $mapping['tipologia_codice'] ?? 'MAG',
|
|
'tipologia_nome' => $mapping['tipologia_nome'] ?? 'Magazzino',
|
|
'tipologia_categoria' => $mapping['tipologia_categoria'] ?? 'deposito',
|
|
'categoria_catastale' => $mapping['categoria_catastale'] ?? 'C/2',
|
|
'fallback_interno_prefix' => $mapping['fallback_interno_prefix'] ?? 'MAG',
|
|
'denominazione' => $this->looksLikeCondominialeDescriptor($descriptor)
|
|
? 'Magazzino Condominiale'
|
|
: ($mapping['denominazione'] ?? null),
|
|
]);
|
|
}
|
|
|
|
if (str_contains($descriptor, 'BOX') || str_contains($descriptor, 'GARAGE') || str_contains($descriptor, 'AUTORIM')) {
|
|
$mapping = array_merge($mapping, [
|
|
'tipo_unita' => 'box_garage',
|
|
'tipologia_codice' => $mapping['tipologia_codice'] ?? 'BOX',
|
|
'tipologia_nome' => $mapping['tipologia_nome'] ?? 'Box',
|
|
'tipologia_categoria' => $mapping['tipologia_categoria'] ?? 'pertinenza',
|
|
'categoria_catastale' => $mapping['categoria_catastale'] ?? 'C/6',
|
|
'is_pertinenza' => true,
|
|
'fallback_interno_prefix' => $mapping['fallback_interno_prefix'] ?? 'BOX',
|
|
]);
|
|
}
|
|
|
|
if (str_contains($descriptor, 'CANTIN')) {
|
|
$mapping = array_merge($mapping, [
|
|
'tipo_unita' => 'cantina',
|
|
'tipologia_codice' => $mapping['tipologia_codice'] ?? 'CANT',
|
|
'tipologia_nome' => $mapping['tipologia_nome'] ?? 'Cantina',
|
|
'tipologia_categoria' => $mapping['tipologia_categoria'] ?? 'pertinenza',
|
|
'categoria_catastale' => $mapping['categoria_catastale'] ?? 'C/2',
|
|
'is_pertinenza' => true,
|
|
'fallback_interno_prefix' => $mapping['fallback_interno_prefix'] ?? 'CAN',
|
|
]);
|
|
}
|
|
|
|
if ($explicitPertinenzaType !== null && $explicitPertinenzaType !== '') {
|
|
$mapping['pertinenza_type'] = $explicitPertinenzaType;
|
|
} elseif (($mapping['tipo_unita'] ?? null) === 'box_garage') {
|
|
$mapping['pertinenza_type'] = 'box';
|
|
} elseif (($mapping['tipo_unita'] ?? null) === 'cantina') {
|
|
$mapping['pertinenza_type'] = 'cantina';
|
|
} elseif (($mapping['tipo_unita'] ?? null) === 'soffitta') {
|
|
$mapping['pertinenza_type'] = 'soffitta';
|
|
} elseif (($mapping['tipo_unita'] ?? null) === 'locale_tecnico') {
|
|
$mapping['pertinenza_type'] = 'locale_tecnico';
|
|
}
|
|
|
|
if (($mapping['is_condominiale'] ?? false) && empty($mapping['denominazione'])) {
|
|
$mapping['denominazione'] = trim((string) ($ownerDescriptor ?? '')) ?: 'Unita Condominiale';
|
|
}
|
|
|
|
return $mapping;
|
|
}
|
|
|
|
private function buildLegacyFallbackInterno(?string $legacyCondId, array $classification): ?string
|
|
{
|
|
$prefix = trim((string) ($classification['fallback_interno_prefix'] ?? ''));
|
|
$condId = trim((string) ($legacyCondId ?? ''));
|
|
if ($prefix === '') {
|
|
return null;
|
|
}
|
|
|
|
return $condId !== '' ? strtoupper($prefix . $condId) : strtoupper($prefix);
|
|
}
|
|
|
|
private function findLegacyUnitTypeMapping(string $source, string $field, ?string $value, string $descriptor): array
|
|
{
|
|
$cacheKey = implode('|', [$source, $field, strtoupper(trim((string) $value)), $descriptor]);
|
|
if (array_key_exists($cacheKey, $this->legacyUnitTypeMappings)) {
|
|
return $this->legacyUnitTypeMappings[$cacheKey];
|
|
}
|
|
|
|
$default = [
|
|
'tipo_unita' => 'abitazione',
|
|
'tipologia_codice' => 'APP',
|
|
'tipologia_nome' => 'Appartamento',
|
|
'tipologia_categoria' => 'residenziale',
|
|
'categoria_catastale' => null,
|
|
'is_pertinenza' => false,
|
|
'is_condominiale' => false,
|
|
'fallback_interno_prefix' => null,
|
|
'denominazione' => null,
|
|
];
|
|
|
|
if (! Schema::hasTable('legacy_unita_tipo_mappings')) {
|
|
return $this->legacyUnitTypeMappings[$cacheKey] = $default;
|
|
}
|
|
|
|
$rows = DB::table('legacy_unita_tipo_mappings')
|
|
->where('legacy_source', $source)
|
|
->where('legacy_field', $field)
|
|
->where(function ($query) use ($value) {
|
|
if ($value !== null && trim((string) $value) !== '') {
|
|
$query->where('legacy_value', strtoupper(trim((string) $value)))->orWhereNull('legacy_value');
|
|
} else {
|
|
$query->whereNull('legacy_value');
|
|
}
|
|
})
|
|
->orderBy('sort_order')
|
|
->orderBy('id')
|
|
->get();
|
|
|
|
foreach ($rows as $row) {
|
|
$regex = trim((string) ($row->match_regex ?? ''));
|
|
if ($regex !== '' && @preg_match('/' . $regex . '/i', $descriptor) !== 1) {
|
|
continue;
|
|
}
|
|
|
|
return $this->legacyUnitTypeMappings[$cacheKey] = array_merge($default, [
|
|
'tipo_unita' => trim((string) ($row->tipo_unita ?? '')) ?: $default['tipo_unita'],
|
|
'tipologia_codice' => trim((string) ($row->tipologia_codice ?? '')) ?: $default['tipologia_codice'],
|
|
'tipologia_nome' => trim((string) ($row->tipologia_nome ?? '')) ?: $default['tipologia_nome'],
|
|
'tipologia_categoria' => trim((string) ($row->tipologia_categoria ?? '')) ?: $default['tipologia_categoria'],
|
|
'categoria_catastale' => trim((string) ($row->categoria_catastale ?? '')) ?: null,
|
|
'is_pertinenza' => (bool) ($row->is_pertinenza ?? false),
|
|
'is_condominiale' => (bool) ($row->is_condominiale ?? false),
|
|
'fallback_interno_prefix' => trim((string) ($row->fallback_interno_prefix ?? '')) ?: null,
|
|
'denominazione' => trim((string) ($row->descrizione ?? '')) ?: null,
|
|
]);
|
|
}
|
|
|
|
return $this->legacyUnitTypeMappings[$cacheKey] = $default;
|
|
}
|
|
|
|
private function normalizeLegacyTipoUnita(?string $value): string
|
|
{
|
|
$normalized = Str::snake(Str::lower(trim((string) $value)), '_');
|
|
|
|
return match ($normalized) {
|
|
'box', 'garage', 'posto_auto', 'box_garage' => 'box_garage',
|
|
'cantina' => 'cantina',
|
|
'soffitta', 'solaio' => 'soffitta',
|
|
'locale_tecnico', 'centrale_termica' => 'locale_tecnico',
|
|
'magazzino', 'deposito', 'magazzino_deposito' => 'magazzino_deposito',
|
|
'condominiale', 'spazio_comune_redditizio', 'area_condominiale' => 'spazio_comune_redditizio',
|
|
'studio', 'ufficio', 'studio_professionale' => 'studio_professionale',
|
|
'negozio', 'locale_commerciale', 'attivita_commerciale' => 'attivita_commerciale',
|
|
'abitazione', '' => 'abitazione',
|
|
default => 'altro',
|
|
};
|
|
}
|
|
|
|
private function resolveLegacyTipologiaId(array $classification): ?int
|
|
{
|
|
if (! Schema::hasTable('unita_tipologie')) {
|
|
return null;
|
|
}
|
|
|
|
$code = strtoupper(trim((string) ($classification['tipologia_codice'] ?? '')));
|
|
if ($code === '') {
|
|
return null;
|
|
}
|
|
|
|
if (array_key_exists($code, $this->tipologiaCache)) {
|
|
return $this->tipologiaCache[$code];
|
|
}
|
|
|
|
$row = DB::table('unita_tipologie')->where('codice', $code)->first();
|
|
if ($row) {
|
|
return $this->tipologiaCache[$code] = (int) $row->id;
|
|
}
|
|
|
|
if ($this->isDryRun) {
|
|
return $this->tipologiaCache[$code] = null;
|
|
}
|
|
|
|
$id = DB::table('unita_tipologie')->insertGetId([
|
|
'codice' => $code,
|
|
'nome' => trim((string) ($classification['tipologia_nome'] ?? $code)) ?: $code,
|
|
'categoria' => trim((string) ($classification['tipologia_categoria'] ?? '')) ?: null,
|
|
'is_pertinenza' => (bool) ($classification['is_pertinenza'] ?? false),
|
|
'is_condominiale' => (bool) ($classification['is_condominiale'] ?? false),
|
|
'meta' => json_encode([
|
|
'source' => 'legacy_unita_tipo_mappings',
|
|
'categoria_catastale' => $classification['categoria_catastale'] ?? null,
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
return $this->tipologiaCache[$code] = (int) $id;
|
|
}
|
|
|
|
private function looksLikeCondominialeDescriptor(string $descriptor): bool
|
|
{
|
|
return str_contains($descriptor, 'CONDOMINIO') || str_contains($descriptor, 'PARTI COMUNI');
|
|
}
|
|
|
|
private function isCondominioSubject(?string $nome, ?string $cognome): bool
|
|
{
|
|
$fullName = strtoupper(trim(($cognome ?? '') . ' ' . ($nome ?? '')));
|
|
return str_contains($fullName, 'CONDOMINIO') || str_contains($fullName, 'PARTI COMUNI');
|
|
}
|
|
|
|
private function normalizeCategoriaCatastale(?string $value): ?string
|
|
{
|
|
$value = strtoupper(trim((string) ($value ?? '')));
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/^([A-Z])(\d+)$/', $value, $matches) === 1) {
|
|
return $matches[1] . '/' . $matches[2];
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
private function sanitizeUnitCodeSegment(?string $value, int $maxLen): string
|
|
{
|
|
$normalized = strtoupper(trim((string) $value));
|
|
$normalized = preg_replace('/[^A-Z0-9]+/u', '', $normalized) ?? '';
|
|
|
|
return substr($normalized, 0, max(1, $maxLen));
|
|
}
|
|
|
|
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 (preg_match('/(19|20)\d{2}/', $legacyYear, $match)) {
|
|
return $match[0];
|
|
}
|
|
|
|
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 !== '') {
|
|
if (preg_match('/(19|20)\d{2}/', $anno, $match)) {
|
|
$anno = $match[0];
|
|
}
|
|
$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 normalizeLegacyDateTime(?string $dateValue, ?string $timeValue = null): ?string
|
|
{
|
|
if (! is_string($dateValue)) {
|
|
return null;
|
|
}
|
|
|
|
$dateValue = trim($dateValue);
|
|
if ($dateValue === '') {
|
|
return null;
|
|
}
|
|
|
|
$dateToken = preg_split('/\s+/', $dateValue)[0] ?? '';
|
|
$dateToken = trim((string) $dateToken);
|
|
if ($dateToken === '') {
|
|
return null;
|
|
}
|
|
|
|
$datePart = null;
|
|
foreach (['!m/d/y', '!d/m/y', '!m/d/Y', '!d/m/Y', '!Y-m-d'] as $format) {
|
|
$parsed = \DateTime::createFromFormat($format, $dateToken);
|
|
if ($parsed instanceof \DateTime) {
|
|
$datePart = $parsed->format('Y-m-d');
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($datePart === null) {
|
|
$ts = strtotime($dateValue);
|
|
if ($ts === false) {
|
|
return null;
|
|
}
|
|
$datePart = date('Y-m-d', $ts);
|
|
}
|
|
|
|
return $datePart . ' ' . ($this->normalizeLegacyTime($timeValue) ?? '00:00:00');
|
|
}
|
|
|
|
private function normalizeLegacyTime(?string $value): ?string
|
|
{
|
|
if (! is_string($value)) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
$value = str_replace([',', '.'], ':', $value);
|
|
$value = preg_replace('/\s+/', '', $value ?? '');
|
|
if ($value === null || $value === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/^(\d{1,2})$/', $value, $matches)) {
|
|
return sprintf('%02d:00:00', (int) $matches[1]);
|
|
}
|
|
|
|
if (preg_match('/^(\d{1,2}):(\d{1,2})$/', $value, $matches)) {
|
|
return sprintf('%02d:%02d:00', (int) $matches[1], (int) $matches[2]);
|
|
}
|
|
|
|
if (preg_match('/^(\d{1,2}):(\d{1,2}):(\d{1,2})$/', $value, $matches)) {
|
|
return sprintf('%02d:%02d:%02d', (int) $matches[1], (int) $matches[2], (int) $matches[3]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function normalizeLegacyAssembleaType(?string $value): string
|
|
{
|
|
$normalized = Str::lower(trim((string) $value));
|
|
|
|
return str_contains($normalized, 'stra') ? 'straordinaria' : 'ordinaria';
|
|
}
|
|
|
|
private function buildLegacyAssembleaNote(array $row, string $legacyYear): ?string
|
|
{
|
|
$parts = [];
|
|
|
|
$numero = trim((string) ($row['num_ass'] ?? ''));
|
|
if ($numero !== '') {
|
|
$parts[] = 'Assemblea legacy n. ' . $numero;
|
|
}
|
|
|
|
if ($legacyYear !== '') {
|
|
$parts[] = 'Cartella legacy: ' . $legacyYear;
|
|
}
|
|
|
|
$noteConvocazione = trim((string) ($row['note_convocaz'] ?? ''));
|
|
if ($noteConvocazione !== '') {
|
|
$parts[] = 'Note convocazione: ' . $noteConvocazione;
|
|
}
|
|
|
|
$noteAssemblea = trim((string) ($row['note_assemblea'] ?? ''));
|
|
if ($noteAssemblea !== '') {
|
|
$parts[] = 'Note assemblea: ' . $noteAssemblea;
|
|
}
|
|
|
|
$noteInterne = trim((string) ($row['note_assemblea_int'] ?? ''));
|
|
if ($noteInterne !== '') {
|
|
$parts[] = 'Note interne legacy: ' . $noteInterne;
|
|
}
|
|
|
|
$ordineGiorno = trim((string) ($row['ordine_del_giorno'] ?? ''));
|
|
if ($ordineGiorno !== '') {
|
|
$parts[] = 'Ordine del giorno legacy:' . PHP_EOL . $ordineGiorno;
|
|
}
|
|
|
|
if ($parts === []) {
|
|
return null;
|
|
}
|
|
|
|
return implode(PHP_EOL . PHP_EOL, $parts);
|
|
}
|
|
|
|
private function resolveAssembleeMdbSources(string $stabile): array
|
|
{
|
|
$root = rtrim((string) ($this->option('path') ?: '/mnt/gescon-archives/gescon'), '/');
|
|
$stableDir = $root . '/' . $stabile;
|
|
if (! is_dir($stableDir)) {
|
|
return [];
|
|
}
|
|
|
|
$anno = trim((string) ($this->option('anno') ?? ''));
|
|
$sources = [];
|
|
if ($anno !== '' && strcasecmp($anno, 'all') !== 0) {
|
|
$candidates = array_unique(array_filter([
|
|
$stableDir . '/' . $anno . '/singolo_anno.mdb',
|
|
is_numeric($anno) ? $stableDir . '/' . sprintf('%04d', (int) $anno) . '/singolo_anno.mdb' : null,
|
|
]));
|
|
|
|
foreach ($candidates as $candidate) {
|
|
if (is_string($candidate) && is_file($candidate)) {
|
|
$sources[] = [
|
|
'path' => $candidate,
|
|
'legacy_year' => basename(dirname($candidate)),
|
|
];
|
|
}
|
|
}
|
|
} else {
|
|
foreach ((array) glob($stableDir . '/*/singolo_anno.mdb') as $candidate) {
|
|
if (! is_string($candidate) || ! is_file($candidate)) {
|
|
continue;
|
|
}
|
|
$sources[] = [
|
|
'path' => $candidate,
|
|
'legacy_year' => basename(dirname($candidate)),
|
|
];
|
|
}
|
|
}
|
|
|
|
if ($sources === []) {
|
|
$resolved = $this->resolveCondominMdbForStabile($stabile, is_numeric($anno) ? sprintf('%04d', (int) $anno) : null);
|
|
if ($resolved !== null && ! empty($resolved['path']) && is_file((string) $resolved['path'])) {
|
|
$sources[] = [
|
|
'path' => (string) $resolved['path'],
|
|
'legacy_year' => (string) ($resolved['legacy_year'] ?? basename(dirname((string) $resolved['path']))),
|
|
];
|
|
}
|
|
}
|
|
|
|
usort($sources, static function (array $left, array $right): int {
|
|
return strcmp((string) ($left['legacy_year'] ?? ''), (string) ($right['legacy_year'] ?? ''));
|
|
});
|
|
|
|
return array_values(array_unique($sources, SORT_REGULAR));
|
|
}
|
|
|
|
private function resolveLegacySnapshotYear(?string $legacyYear, ?object $row = null): ?int
|
|
{
|
|
$resolved = $this->resolveAnnoFromLegacyYear($legacyYear);
|
|
if ($resolved !== null && is_numeric($resolved)) {
|
|
return (int) $resolved;
|
|
}
|
|
|
|
if ($row) {
|
|
foreach (['inizio_anno', 'anno_gestione', 'anno'] as $field) {
|
|
$value = trim((string) ($row->{$field} ?? ''));
|
|
if ($value !== '' && preg_match('/\d{4}/', $value, $m)) {
|
|
return (int) $m[0];
|
|
}
|
|
}
|
|
|
|
foreach (['subentrato_dal', 'attivo_fino_al', 'inquil_dal', 'inquil_al'] as $field) {
|
|
$normalized = $this->normalizeLegacyDate($row->{$field} ?? null);
|
|
if ($normalized) {
|
|
return (int) substr($normalized, 0, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
$optAnno = trim((string) ($this->option('anno') ?? ''));
|
|
if ($optAnno !== '' && is_numeric($optAnno)) {
|
|
return (int) $optAnno;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function startOfYear(?int $year): ?string
|
|
{
|
|
return $year ? sprintf('%04d-01-01', $year) : null;
|
|
}
|
|
|
|
private function previousDay(?string $date): ?string
|
|
{
|
|
if (! $date) {
|
|
return null;
|
|
}
|
|
|
|
$ts = strtotime($date . ' -1 day');
|
|
|
|
return $ts !== false ? date('Y-m-d', $ts) : null;
|
|
}
|
|
|
|
private function normalizeLegacyPartyName(?string $value): string
|
|
{
|
|
$value = Str::upper(trim((string) $value));
|
|
$value = preg_replace('/\s+/u', ' ', $value ?? '');
|
|
|
|
return trim((string) ($value ?? ''));
|
|
}
|
|
|
|
private function extractLegacySnapshotParty(object $row, string $role, ?string $defaultLegacyCondId = null): array
|
|
{
|
|
$legacyYear = isset($row->legacy_year) ? trim((string) $row->legacy_year) : null;
|
|
$snapshotYear = $this->resolveLegacySnapshotYear($legacyYear, $row);
|
|
|
|
if ($role === 'C') {
|
|
$name = trim((string) ($row->proprietario_denominazione ?? $row->nom_cond ?? ''));
|
|
if ($name === '') {
|
|
$name = trim((string) (($row->cognome ?? '') . ' ' . ($row->nome ?? '')));
|
|
}
|
|
|
|
return [
|
|
'ruolo' => 'C',
|
|
'legacy_year' => $legacyYear,
|
|
'snapshot_year' => $snapshotYear,
|
|
'legacy_cond_id' => $this->extractStableLegacyCondId($row, $defaultLegacyCondId),
|
|
'nominativo' => $name !== '' ? $name : null,
|
|
'data_inizio' => $this->normalizeLegacyDate($row->proprietario_subentrato_dal ?? $row->subentrato_dal ?? null),
|
|
'data_fine' => $this->normalizeLegacyDate($row->proprietario_attivo_fino_al ?? $row->attivo_fino_al ?? null),
|
|
'percentuale' => isset($row->proprietario_diritto_percentuale) && is_numeric($row->proprietario_diritto_percentuale)
|
|
? (float) $row->proprietario_diritto_percentuale
|
|
: (isset($row->perc_diritto_reale) && is_numeric($row->perc_diritto_reale) ? (float) $row->perc_diritto_reale : null),
|
|
'meta' => $this->extractLegacySnapshotPartyMeta($row, 'C'),
|
|
];
|
|
}
|
|
|
|
$tenantName = trim((string) ($row->inquilino_denominazione ?? $row->inquil_nome ?? $row->inquil ?? ''));
|
|
|
|
return [
|
|
'ruolo' => 'I',
|
|
'legacy_year' => $legacyYear,
|
|
'snapshot_year' => $snapshotYear,
|
|
'legacy_cond_id' => $this->extractStableLegacyCondId($row, $defaultLegacyCondId),
|
|
'nominativo' => $tenantName !== '' ? $tenantName : null,
|
|
'data_inizio' => $this->normalizeLegacyDate($row->inquilin_contratto_dal ?? $row->inquil_contratto_dal ?? $row->inquil_dal ?? $row->inquilino_subentrato_dal ?? null),
|
|
'data_fine' => $this->normalizeLegacyDate($row->inquil_al ?? $row->inquilino_attivo_fino_al ?? null),
|
|
'percentuale' => isset($row->inquilino_diritto_percentuale) && is_numeric($row->inquilino_diritto_percentuale)
|
|
? (float) $row->inquilino_diritto_percentuale
|
|
: null,
|
|
'meta' => $this->extractLegacySnapshotPartyMeta($row, 'I'),
|
|
];
|
|
}
|
|
|
|
private function extractLegacySnapshotPartyMeta(object $row, string $role): array
|
|
{
|
|
$isTenant = strtoupper($role) === 'I';
|
|
|
|
$meta = [
|
|
'cumulo_group' => trim((string) ($isTenant ? ($row->cumulo_inq ?? '') : ($row->cumulo_cond ?? ''))) ?: null,
|
|
'cumulo_ec' => trim((string) ($isTenant ? ($row->cumulo_inq_ec ?? $row->cumulo_inq_ec ?? '') : ($row->cumulo_cond_ec ?? ''))) ?: null,
|
|
'cumulo_orig' => trim((string) ($isTenant ? ($row->cumulo_inq_orig ?? '') : ($row->cumulo_cond_orig ?? ''))) ?: null,
|
|
'cumulo_elenchi' => trim((string) ($row->cumulo_elenchi ?? '')) ?: null,
|
|
'cumulo_ass' => trim((string) ($row->cumulo_ass ?? '')) ?: null,
|
|
'cumulo_raccomand' => trim((string) ($row->cumulo_raccomand ?? '')) ?: null,
|
|
'cumulo_old' => trim((string) ($row->cumulo_old ?? '')) ?: null,
|
|
'stampa_attuale' => trim((string) ($row->stampa_attuale ?? '')) ?: null,
|
|
'email' => trim((string) ($isTenant ? ($row->e_mail_inquilino ?? '') : ($row->e_mail_condomino ?? $row->email ?? ''))) ?: null,
|
|
'pec' => trim((string) ($isTenant ? ($row->pec_inquilino ?? '') : ($row->pec_condomino ?? ''))) ?: null,
|
|
'telefono_1' => trim((string) ($isTenant ? ($row->inquil_tel1 ?? '') : ($row->tel1 ?? ''))) ?: null,
|
|
'telefono_2' => trim((string) ($isTenant ? ($row->inquil_tel2 ?? '') : ($row->tel2 ?? ''))) ?: null,
|
|
'cellulare' => trim((string) ($isTenant ? ($row->cell_inq ?? '') : ($row->cell_cond ?? ''))) ?: null,
|
|
'fax' => trim((string) ($isTenant ? ($row->fax_inq ?? '') : ($row->fax_cond ?? ''))) ?: null,
|
|
'presso' => trim((string) ($isTenant ? ($row->inquil_presso ?? '') : ($row->presso ?? ''))) ?: null,
|
|
'indirizzo' => trim((string) ($isTenant ? ($row->inquil_indir ?? '') : ($row->ind ?? $row->indir ?? ''))) ?: null,
|
|
'cap' => trim((string) ($isTenant ? ($row->inquil_cap ?? '') : ($row->cap ?? ''))) ?: null,
|
|
'citta' => trim((string) ($isTenant ? ($row->inquil_citta ?? '') : ($row->citta ?? ''))) ?: null,
|
|
'provincia' => trim((string) ($isTenant ? ($row->inquil_pr ?? '') : ($row->pr ?? ''))) ?: null,
|
|
'titolo' => trim((string) ($isTenant ? ($row->titolo_inq ?? '') : ($row->titolo_cond ?? ''))) ?: null,
|
|
'note' => trim((string) ($isTenant ? ($row->inquil_note ?? '') : ($row->note_cond ?? ''))) ?: null,
|
|
'e_lostesso_di' => $isTenant ? null : (trim((string) ($row->e_lostesso_di ?? '')) ?: null),
|
|
'codice_fiscale' => $isTenant
|
|
? (trim((string) ($row->inquilin_cod_fisc ?? $row->inquil_cod_fisc ?? '')) ?: null)
|
|
: (trim((string) ($row->cond_cod_fisc ?? $row->codice_fiscale ?? '')) ?: null),
|
|
'data_nascita' => $isTenant ? null : $this->normalizeLegacyDate($row->cond_dt_nasc ?? null),
|
|
'luogo_nascita' => $isTenant ? null : (trim((string) ($row->cond_luogo_nasc ?? '')) ?: null),
|
|
'provincia_nascita' => $isTenant ? null : (trim((string) ($row->cond_pr_nasc ?? '')) ?: null),
|
|
];
|
|
|
|
$meta = array_merge(
|
|
$this->extractLegacyPrefixedPayload($row, ['cond_', 'catasto_', 'pertinenze_', 'diritti_']),
|
|
$meta,
|
|
$this->extractLegacyPrefixedPayload($row, $isTenant ? ['inquilin_', 'inquil_'] : [])
|
|
);
|
|
|
|
return array_filter($meta, static fn($value) => $value !== null && $value !== '');
|
|
}
|
|
|
|
private function extractLegacyPrefixedPayload(object $row, array $prefixes): array
|
|
{
|
|
if ($prefixes === []) {
|
|
return [];
|
|
}
|
|
|
|
$payload = [];
|
|
foreach (get_object_vars($row) as $key => $value) {
|
|
if (! is_string($key)) {
|
|
continue;
|
|
}
|
|
|
|
$matched = false;
|
|
foreach ($prefixes as $prefix) {
|
|
if ($prefix !== '' && str_starts_with($key, $prefix)) {
|
|
$matched = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! $matched) {
|
|
continue;
|
|
}
|
|
|
|
if ($value instanceof \DateTimeInterface) {
|
|
$payload[$key] = $value->format('Y-m-d');
|
|
continue;
|
|
}
|
|
|
|
if (is_bool($value) || is_numeric($value)) {
|
|
$payload[$key] = $value;
|
|
continue;
|
|
}
|
|
|
|
if (! is_scalar($value) && ! $value instanceof \Stringable) {
|
|
continue;
|
|
}
|
|
|
|
$normalized = trim((string) $value);
|
|
if ($normalized !== '') {
|
|
$payload[$key] = $normalized;
|
|
}
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function loadLegacyCondominHistoryRows(object $row, ?string $legacyCondId): array
|
|
{
|
|
$codStabile = trim((string) ($row->cod_stabile ?? ''));
|
|
$codCond = trim((string) ($row->cod_cond ?? ''));
|
|
$stableCondId = $this->extractStableLegacyCondId($row, $legacyCondId);
|
|
|
|
if ($codStabile === '' || ($stableCondId === null && $codCond === '')) {
|
|
return [$row];
|
|
}
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->orderByDesc('legacy_year')
|
|
->orderByDesc('id');
|
|
|
|
if ($stableCondId !== null && $stableCondId !== '') {
|
|
$colName = Schema::connection('gescon_import')->hasColumn('condomin', 'cod_cond') ? 'cod_cond' : 'id_cond';
|
|
if (Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_id_cond')) {
|
|
$query->where(function ($subQuery) use ($stableCondId, $colName): void {
|
|
$subQuery->where($colName, $stableCondId)
|
|
->orWhere('legacy_id_cond', $stableCondId);
|
|
});
|
|
} else {
|
|
$query->where($colName, $stableCondId);
|
|
}
|
|
} elseif ($codCond !== '') {
|
|
$query->where('cod_cond', $codCond);
|
|
}
|
|
|
|
$historyRows = $query->get()->all();
|
|
|
|
if ($historyRows === [] && $codCond !== '') {
|
|
$historyRows = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->where('cod_cond', $codCond)
|
|
->orderByDesc('legacy_year')
|
|
->orderByDesc('id')
|
|
->get()
|
|
->all();
|
|
}
|
|
|
|
if ($historyRows === []) {
|
|
$partyName = $this->extractLegacyPartyName($row);
|
|
if ($partyName !== null) {
|
|
$historyRows = DB::connection('gescon_import')
|
|
->table('condomin')
|
|
->where('cod_stabile', $codStabile)
|
|
->where(function ($subQuery) use ($partyName): void {
|
|
$subQuery->where('nom_cond', $partyName);
|
|
if (Schema::connection('gescon_import')->hasColumn('condomin', 'proprietario_denominazione')) {
|
|
$subQuery->orWhere('proprietario_denominazione', $partyName);
|
|
}
|
|
})
|
|
->orderByDesc('legacy_year')
|
|
->orderByDesc('id')
|
|
->get()
|
|
->all();
|
|
}
|
|
}
|
|
|
|
if ($historyRows === []) {
|
|
return [$row];
|
|
}
|
|
|
|
$byYear = [];
|
|
foreach ($historyRows as $historyRow) {
|
|
$snapshotYear = $this->resolveLegacySnapshotYear(isset($historyRow->legacy_year) ? (string) $historyRow->legacy_year : null, $historyRow);
|
|
$bucket = $snapshotYear ? (string) $snapshotYear : ('legacy:' . (string) ($historyRow->legacy_year ?? '0'));
|
|
if (! isset($byYear[$bucket])) {
|
|
$byYear[$bucket] = $historyRow;
|
|
}
|
|
}
|
|
|
|
$historyRows = array_values($byYear);
|
|
usort($historyRows, function (object $a, object $b): int {
|
|
$yearA = $this->resolveLegacySnapshotYear(isset($a->legacy_year) ? (string) $a->legacy_year : null, $a) ?? 0;
|
|
$yearB = $this->resolveLegacySnapshotYear(isset($b->legacy_year) ? (string) $b->legacy_year : null, $b) ?? 0;
|
|
if ($yearA !== $yearB) {
|
|
return $yearB <=> $yearA;
|
|
}
|
|
|
|
return ((int) ($b->id ?? 0)) <=> ((int) ($a->id ?? 0));
|
|
});
|
|
|
|
return $historyRows;
|
|
}
|
|
|
|
private function buildRoleHistoryPayloads(array $historyRows, string $role, ?string $defaultLegacyCondId = null): array
|
|
{
|
|
$snapshots = [];
|
|
foreach ($historyRows as $historyRow) {
|
|
$snapshot = $this->extractLegacySnapshotParty($historyRow, $role, $defaultLegacyCondId);
|
|
if (! $snapshot['nominativo'] && ! $snapshot['data_inizio'] && ! $snapshot['data_fine']) {
|
|
continue;
|
|
}
|
|
$snapshots[] = $snapshot;
|
|
}
|
|
|
|
if ($snapshots === []) {
|
|
return [];
|
|
}
|
|
|
|
$payloads = [];
|
|
$current = null;
|
|
foreach ($snapshots as $snapshot) {
|
|
$snapshotNameKey = $this->normalizeLegacyPartyName($snapshot['nominativo'] ?? null);
|
|
$snapshotStart = $snapshot['data_inizio'] ?? $this->startOfYear($snapshot['snapshot_year']);
|
|
$snapshotEnd = $snapshot['data_fine'];
|
|
|
|
if ($current === null) {
|
|
$current = [
|
|
'ruolo' => $role,
|
|
'nominativo' => $snapshot['nominativo'],
|
|
'data_inizio' => $snapshotStart,
|
|
'data_fine' => $snapshotEnd,
|
|
'percentuale' => $snapshot['percentuale'],
|
|
'legacy_cond_id' => $snapshot['legacy_cond_id'] ?? $defaultLegacyCondId,
|
|
'legacy_years' => array_values(array_filter([$snapshot['legacy_year']])),
|
|
'meta' => $snapshot['meta'] ?? [],
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$currentNameKey = $this->normalizeLegacyPartyName($current['nominativo'] ?? null);
|
|
$sameName = $snapshotNameKey !== '' && $snapshotNameKey === $currentNameKey;
|
|
$samePercent = $snapshot['percentuale'] === null || $current['percentuale'] === null
|
|
|| abs((float) $snapshot['percentuale'] - (float) $current['percentuale']) < 0.0005;
|
|
|
|
if ($sameName && $samePercent) {
|
|
$current['data_inizio'] = $snapshot['data_inizio'] ?? $this->startOfYear($snapshot['snapshot_year']) ?? $current['data_inizio'];
|
|
$current['percentuale'] = $current['percentuale'] ?? $snapshot['percentuale'];
|
|
$current['meta'] = array_merge($snapshot['meta'] ?? [], $current['meta'] ?? []);
|
|
if (! empty($snapshot['legacy_year'])) {
|
|
$current['legacy_years'][] = $snapshot['legacy_year'];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$payloads[] = $current;
|
|
$current = [
|
|
'ruolo' => $role,
|
|
'nominativo' => $snapshot['nominativo'],
|
|
'data_inizio' => $snapshotStart,
|
|
'data_fine' => $snapshotEnd ?: $this->previousDay($current['data_inizio']),
|
|
'percentuale' => $snapshot['percentuale'],
|
|
'legacy_cond_id' => $snapshot['legacy_cond_id'] ?? $defaultLegacyCondId,
|
|
'legacy_years' => array_values(array_filter([$snapshot['legacy_year']])),
|
|
'meta' => $snapshot['meta'] ?? [],
|
|
];
|
|
}
|
|
|
|
if ($current !== null) {
|
|
$payloads[] = $current;
|
|
}
|
|
|
|
return $payloads;
|
|
}
|
|
|
|
private function loadLegacyComproprietariHistoryRows(object $row, ?string $legacyCondId): array
|
|
{
|
|
if (! Schema::connection('gescon_import')->hasTable('comproprietari')) {
|
|
return [];
|
|
}
|
|
|
|
$codStabile = trim((string) ($row->cod_stabile ?? ''));
|
|
$idCondRaw = trim((string) ($this->extractStableLegacyCondId($row, $legacyCondId) ?? $row->cod_cond ?? ''));
|
|
|
|
if ($codStabile === '' || $idCondRaw === '') {
|
|
return [];
|
|
}
|
|
|
|
$query = DB::connection('gescon_import')
|
|
->table('comproprietari')
|
|
->where('cod_stabile', $codStabile)
|
|
->orderByDesc('legacy_year')
|
|
->orderByDesc('id');
|
|
|
|
if (is_numeric($idCondRaw)) {
|
|
$query->where('id_cond', (int) $idCondRaw);
|
|
} else {
|
|
$query->where('id_cond', $idCondRaw);
|
|
}
|
|
|
|
$historyRows = $query->get()->all();
|
|
if ($historyRows === []) {
|
|
return [];
|
|
}
|
|
|
|
$byYear = [];
|
|
foreach ($historyRows as $historyRow) {
|
|
$snapshotYear = $this->resolveLegacySnapshotYear(isset($historyRow->legacy_year) ? (string) $historyRow->legacy_year : null, $historyRow);
|
|
$bucket = $snapshotYear ? (string) $snapshotYear : ('legacy:' . (string) ($historyRow->legacy_year ?? '0'));
|
|
$nameKey = $this->normalizeLegacyPartyName($historyRow->nom_cond ?? null);
|
|
$rightKey = $this->normalizeLegacyPartyName($historyRow->diritto_reale ?? $historyRow->descriz ?? null);
|
|
$groupKey = $bucket . '|' . $nameKey . '|' . $rightKey;
|
|
if (! isset($byYear[$groupKey])) {
|
|
$byYear[$groupKey] = $historyRow;
|
|
}
|
|
}
|
|
|
|
$historyRows = array_values($byYear);
|
|
usort($historyRows, function (object $a, object $b): int {
|
|
$yearA = $this->resolveLegacySnapshotYear(isset($a->legacy_year) ? (string) $a->legacy_year : null, $a) ?? 0;
|
|
$yearB = $this->resolveLegacySnapshotYear(isset($b->legacy_year) ? (string) $b->legacy_year : null, $b) ?? 0;
|
|
if ($yearA !== $yearB) {
|
|
return $yearB <=> $yearA;
|
|
}
|
|
|
|
return ((int) ($b->id ?? 0)) <=> ((int) ($a->id ?? 0));
|
|
});
|
|
|
|
return $historyRows;
|
|
}
|
|
|
|
private function buildComproprietariHistoryPayloads(array $historyRows, ?string $defaultLegacyCondId = null): array
|
|
{
|
|
if ($historyRows === []) {
|
|
return [];
|
|
}
|
|
|
|
$snapshots = [];
|
|
foreach ($historyRows as $historyRow) {
|
|
$legacyYear = isset($historyRow->legacy_year) ? trim((string) $historyRow->legacy_year) : null;
|
|
$snapshotYear = $this->resolveLegacySnapshotYear($legacyYear, $historyRow);
|
|
$name = trim((string) ($historyRow->nom_cond ?? ''));
|
|
$rightCode = trim((string) ($historyRow->diritto_reale ?? ''));
|
|
$rightLabel = trim((string) ($historyRow->descriz ?? '')) ?: ($rightCode !== '' ? $rightCode : 'Comproprietario');
|
|
$percentuale = isset($historyRow->perc_diritto_reale) && is_numeric($historyRow->perc_diritto_reale)
|
|
? (float) $historyRow->perc_diritto_reale
|
|
: null;
|
|
|
|
if ($name === '') {
|
|
continue;
|
|
}
|
|
|
|
$snapshots[] = [
|
|
'ruolo' => 'C',
|
|
'legacy_year' => $legacyYear,
|
|
'snapshot_year' => $snapshotYear,
|
|
'legacy_cond_id' => trim((string) ($historyRow->id_cond ?? $defaultLegacyCondId ?? '')) ?: $defaultLegacyCondId,
|
|
'nominativo' => $name,
|
|
'data_inizio' => $this->startOfYear($snapshotYear),
|
|
'data_fine' => null,
|
|
'percentuale' => $percentuale,
|
|
'diritto_reale' => $rightCode !== '' ? $rightCode : null,
|
|
'diritto_label' => $rightLabel,
|
|
'id_compr' => isset($historyRow->id_compr) ? (int) $historyRow->id_compr : null,
|
|
'meta' => array_merge(
|
|
[
|
|
'cond_cod_fisc' => trim((string) ($historyRow->cond_cod_fisc ?? '')) ?: null,
|
|
'email' => trim((string) ($historyRow->e_mail_condomino ?? '')) ?: null,
|
|
],
|
|
$this->extractLegacyPrefixedPayload($historyRow, ['cond_', 'diritti_'])
|
|
),
|
|
];
|
|
}
|
|
|
|
if ($snapshots === []) {
|
|
return [];
|
|
}
|
|
|
|
$payloads = [];
|
|
$current = null;
|
|
foreach ($snapshots as $snapshot) {
|
|
$snapshotNameKey = $this->normalizeLegacyPartyName($snapshot['nominativo'] ?? null);
|
|
$snapshotRightKey = $this->normalizeLegacyPartyName($snapshot['diritto_reale'] ?? $snapshot['diritto_label'] ?? null);
|
|
$snapshotStart = $snapshot['data_inizio'] ?? $this->startOfYear($snapshot['snapshot_year']);
|
|
|
|
if ($current === null) {
|
|
$current = $snapshot + [
|
|
'legacy_years' => array_values(array_filter([$snapshot['legacy_year']])),
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$currentNameKey = $this->normalizeLegacyPartyName($current['nominativo'] ?? null);
|
|
$currentRightKey = $this->normalizeLegacyPartyName($current['diritto_reale'] ?? $current['diritto_label'] ?? null);
|
|
$sameSeries = ! empty($snapshot['id_compr']) && ! empty($current['id_compr'])
|
|
&& (int) $snapshot['id_compr'] === (int) $current['id_compr'];
|
|
$sameName = $snapshotNameKey !== '' && $snapshotNameKey === $currentNameKey;
|
|
$sameRight = $snapshotRightKey === $currentRightKey;
|
|
$samePercent = $snapshot['percentuale'] === null || $current['percentuale'] === null
|
|
|| abs((float) $snapshot['percentuale'] - (float) $current['percentuale']) < 0.0005;
|
|
|
|
if (($sameName && $sameRight && $samePercent) || ($sameSeries && $sameRight)) {
|
|
$current['data_inizio'] = $snapshotStart ?? $current['data_inizio'];
|
|
$current['percentuale'] = $current['percentuale'] ?? $snapshot['percentuale'];
|
|
$current['meta'] = array_merge($snapshot['meta'] ?? [], $current['meta'] ?? []);
|
|
if (! empty($snapshot['legacy_year'])) {
|
|
$current['legacy_years'][] = $snapshot['legacy_year'];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$payloads[] = $current;
|
|
$current = $snapshot + [
|
|
'data_fine' => $this->previousDay($current['data_inizio'] ?? null),
|
|
'legacy_years' => array_values(array_filter([$snapshot['legacy_year']])),
|
|
];
|
|
}
|
|
|
|
if ($current !== null) {
|
|
$payloads[] = $current;
|
|
}
|
|
|
|
return $payloads;
|
|
}
|
|
|
|
private function upsertUnitaNominativiFromLegacy(int $unitaId, int $stabileId, ?string $legacyCondId, object $row): void
|
|
{
|
|
if (! Schema::hasTable('unita_immobiliare_nominativi')) {
|
|
return;
|
|
}
|
|
|
|
$historyRowsCondomin = $this->loadLegacyCondominHistoryRows($row, $legacyCondId);
|
|
$historyRowsComproprietari = $this->loadLegacyComproprietariHistoryRows($row, $legacyCondId);
|
|
$payloadsCondomin = array_merge(
|
|
$this->buildRoleHistoryPayloads($historyRowsCondomin, 'C', $legacyCondId),
|
|
$this->buildRoleHistoryPayloads($historyRowsCondomin, 'I', $legacyCondId)
|
|
);
|
|
$payloadsComproprietari = $this->buildComproprietariHistoryPayloads($historyRowsComproprietari, $legacyCondId);
|
|
|
|
DB::table('unita_immobiliare_nominativi')
|
|
->where('unita_immobiliare_id', $unitaId)
|
|
->where('stabile_id', $stabileId)
|
|
->whereIn('fonte', ['legacy_condomin', 'legacy_comproprietari'])
|
|
->delete();
|
|
|
|
foreach ($payloadsCondomin as $payload) {
|
|
$legacyPayload = [
|
|
'cod_cond' => $payload['legacy_cond_id'] ?? $legacyCondId,
|
|
'legacy_years' => array_values(array_unique($payload['legacy_years'] ?? [])),
|
|
'history_mode' => 'latest-year-backfill',
|
|
];
|
|
if (! empty($payload['meta']) && is_array($payload['meta'])) {
|
|
$legacyPayload = array_merge($legacyPayload, $payload['meta']);
|
|
}
|
|
|
|
DB::table('unita_immobiliare_nominativi')->insert([
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'stabile_id' => $stabileId,
|
|
'legacy_cond_id' => $payload['legacy_cond_id'] ?? $legacyCondId,
|
|
'ruolo' => $payload['ruolo'],
|
|
'nominativo' => $payload['nominativo'],
|
|
'data_inizio' => $payload['data_inizio'],
|
|
'data_fine' => $payload['data_fine'],
|
|
'percentuale' => $payload['percentuale'],
|
|
'fonte' => 'legacy_condomin',
|
|
'legacy_payload' => json_encode($legacyPayload),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
foreach ($payloadsComproprietari as $payload) {
|
|
DB::table('unita_immobiliare_nominativi')->insert([
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'stabile_id' => $stabileId,
|
|
'legacy_cond_id' => $payload['legacy_cond_id'] ?? $legacyCondId,
|
|
'ruolo' => 'C',
|
|
'nominativo' => $payload['nominativo'],
|
|
'data_inizio' => $payload['data_inizio'],
|
|
'data_fine' => $payload['data_fine'],
|
|
'percentuale' => $payload['percentuale'],
|
|
'fonte' => 'legacy_comproprietari',
|
|
'legacy_payload' => json_encode([
|
|
'cod_cond' => $payload['legacy_cond_id'] ?? $legacyCondId,
|
|
'id_compr' => $payload['id_compr'] ?? null,
|
|
'diritto_reale' => $payload['diritto_reale'] ?? null,
|
|
'diritto_label' => $payload['diritto_label'] ?? null,
|
|
'legacy_years' => array_values(array_unique($payload['legacy_years'] ?? [])),
|
|
'history_mode' => 'comproprietari-series',
|
|
] + (is_array($payload['meta'] ?? null) ? $payload['meta'] : [])),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
$legacyMeta = $this->resolveGestioneContabileLegacyMeta($stabileId, $legacyYear, $tipo, $numeroStraordinaria);
|
|
|
|
$match = $q
|
|
->orderByRaw("case when denominazione like '%/%' then 1 else 0 end desc")
|
|
->orderByDesc('data_fine')
|
|
->orderByDesc('gestione_attiva')
|
|
->orderByDesc('id')
|
|
->first();
|
|
if ($match) {
|
|
$patch = [];
|
|
if (($legacyMeta['denominazione'] ?? null) && (string) ($match->denominazione ?? '') !== (string) $legacyMeta['denominazione']) {
|
|
$patch['denominazione'] = $legacyMeta['denominazione'];
|
|
}
|
|
if (($legacyMeta['data_inizio'] ?? null) && (string) ($match->data_inizio ?? '') !== (string) $legacyMeta['data_inizio']) {
|
|
$patch['data_inizio'] = $legacyMeta['data_inizio'];
|
|
}
|
|
if (($legacyMeta['data_fine'] ?? null) && (string) ($match->data_fine ?? '') !== (string) $legacyMeta['data_fine']) {
|
|
$patch['data_fine'] = $legacyMeta['data_fine'];
|
|
}
|
|
if ($patch !== [] && ! $this->option('dry-run')) {
|
|
$patch['updated_at'] = now();
|
|
DB::table('gestioni_contabili')->where('id', $match->id)->update($patch);
|
|
}
|
|
return (int) $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' => $legacyMeta['denominazione'] ?? (strtoupper(substr($tipo, 0, 1)) . $anno),
|
|
'data_inizio' => $legacyMeta['data_inizio'] ?? date('Y-01-01', strtotime($anno . '-01-01')),
|
|
'data_fine' => $legacyMeta['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(),
|
|
];
|
|
|
|
return (int) DB::table('gestioni_contabili')->insertGetId($payload);
|
|
}
|
|
|
|
private function resolveGestioneId($anno, $compet, $tipo, $numeroStraordinaria = null): ?int
|
|
{
|
|
if (! Schema::hasTable('gestioni_contabili')) {
|
|
return null;
|
|
}
|
|
|
|
$legacyYear = $anno !== null && $anno !== '' ? (string) $anno : null;
|
|
$stabileId = $this->stabileId ?: $this->resolveStabileId();
|
|
|
|
return $this->resolveGestioneContabileIdForStabile(
|
|
$stabileId,
|
|
$legacyYear,
|
|
$tipo !== null ? (string) $tipo : null,
|
|
is_numeric($numeroStraordinaria) ? (int) $numeroStraordinaria : null,
|
|
);
|
|
}
|
|
|
|
private function resolveGestioneContabileLegacyMeta(?int $stabileId, ?string $legacyYear, string $tipo, ?int $numeroStraordinaria = null): array
|
|
{
|
|
if (! $stabileId) {
|
|
return [];
|
|
}
|
|
|
|
$stabileCode = DB::table('stabili')->where('id', $stabileId)->value('codice_stabile');
|
|
$stabileCode = trim((string) ($stabileCode ?? ''));
|
|
if ($stabileCode === '') {
|
|
return [];
|
|
}
|
|
|
|
$legacyYear = trim((string) ($legacyYear ?? ''));
|
|
$resolvedAnno = $this->resolveAnnoFromLegacyYear($legacyYear);
|
|
|
|
if ($tipo === 'straordinaria') {
|
|
if (Schema::connection('gescon_import')->hasTable('straordinarie')) {
|
|
$straRow = DB::connection('gescon_import')->table('straordinarie')
|
|
->where('cod_stabile', $stabileCode)
|
|
->when($legacyYear !== '', fn($q) => $q->where('legacy_year', $legacyYear))
|
|
->when($numeroStraordinaria !== null, fn($q) => $q->where('codice', (string) $numeroStraordinaria))
|
|
->first();
|
|
if ($straRow) {
|
|
$descrizione = trim((string) ($straRow->descriz_prev_cons ?? $straRow->descriz_ricev ?? ''));
|
|
$inizioMese = (int) ($straRow->inizio_mese ?? 1);
|
|
$inizioAnno = (int) ($straRow->inizio_anno ?? $resolvedAnno ?? 2024);
|
|
$numRate = (int) ($straRow->num_rate ?? 1);
|
|
|
|
$dataInizio = sprintf('%04d-%02d-01', $inizioAnno, $inizioMese);
|
|
$durataMesi = max(1, $numRate);
|
|
$dataFine = date('Y-m-d', strtotime("+$durataMesi months -1 day", strtotime($dataInizio)));
|
|
|
|
return [
|
|
'denominazione' => 'Straordinaria #' . ($straRow->codice ?? $numeroStraordinaria) . ' ' . $descrizione,
|
|
'data_inizio' => $dataInizio,
|
|
'data_fine' => $dataFine,
|
|
];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
if (! Schema::connection('gescon_import')->hasTable('gestioni_annuali')) {
|
|
return [];
|
|
}
|
|
|
|
$query = DB::connection('gescon_import')->table('gestioni_annuali')
|
|
->where('cod_stabile', $stabileCode);
|
|
|
|
if ($legacyYear !== '') {
|
|
$query->where(function ($q) use ($legacyYear, $resolvedAnno, $tipo): void {
|
|
$q->where('cartella', $legacyYear);
|
|
if ($resolvedAnno !== null) {
|
|
$field = $tipo === 'riscaldamento' ? 'anno_riscaldamento' : 'anno_ordinario';
|
|
$q->orWhere($field, $resolvedAnno);
|
|
}
|
|
});
|
|
} elseif ($resolvedAnno !== null) {
|
|
$field = $tipo === 'riscaldamento' ? 'anno_riscaldamento' : 'anno_ordinario';
|
|
$query->where($field, $resolvedAnno);
|
|
}
|
|
|
|
$row = $query->orderByDesc('anno_ordinario')->first();
|
|
if (! $row) {
|
|
return [];
|
|
}
|
|
|
|
$label = $tipo === 'riscaldamento'
|
|
? trim((string) ($row->anno_riscaldamento ?? ''))
|
|
: trim((string) ($row->descrizione ?? $row->anno_ordinario ?? ''));
|
|
if ($label === '') {
|
|
$label = trim((string) ($tipo === 'riscaldamento' ? ($row->anno_riscaldamento ?? '') : ($row->anno_ordinario ?? '')));
|
|
}
|
|
$label = $label !== '' ? preg_replace('/\s+/', '', $label) : null;
|
|
|
|
return [
|
|
'denominazione' => ucfirst($tipo) . ' ' . ($label ?: (string) ($resolvedAnno ?? '')),
|
|
'data_inizio' => $tipo === 'riscaldamento'
|
|
? ($row->riscaldamento_dal ?: $row->ordinaria_dal)
|
|
: $row->ordinaria_dal,
|
|
'data_fine' => $tipo === 'riscaldamento'
|
|
? ($row->riscaldamento_al ?: $row->ordinaria_al)
|
|
: $row->ordinaria_al,
|
|
];
|
|
}
|
|
|
|
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 resolveContiBancariId(?int $stabileId, $datiBancariRow): ?int
|
|
{
|
|
if (! $datiBancariRow) {
|
|
return null;
|
|
}
|
|
if (Schema::hasTable('conti_bancari')) {
|
|
$stRow = DB::table('stabili')->where('id', $stabileId)->first(['codice_stabile']);
|
|
$stCode = $stRow?->codice_stabile ?: '';
|
|
$legacyCassa = $datiBancariRow->legacy_cod_cassa ?? '';
|
|
$codice = substr($stCode, -4) . '-' . substr($legacyCassa, 0, 5);
|
|
$cb = DB::table('conti_bancari')->where('codice', $codice)->first();
|
|
if ($cb) {
|
|
return (int) $cb->id;
|
|
}
|
|
}
|
|
return (int) $datiBancariRow->id;
|
|
}
|
|
|
|
private function syncToContiBancari(array $datiBancariPayload, string $codStabile): int
|
|
{
|
|
$tenantId = 'T1';
|
|
$legacyCassa = $datiBancariPayload['legacy_cod_cassa'] ?? '';
|
|
$codice = substr($codStabile, -4) . '-' . substr($legacyCassa, 0, 5);
|
|
|
|
$tipoConto = 'bancario';
|
|
if (($datiBancariPayload['tipo_conto'] ?? '') === 'cassa') {
|
|
$tipoConto = 'contanti';
|
|
}
|
|
|
|
$payload = [
|
|
'tenant_id' => $tenantId,
|
|
'codice' => $codice,
|
|
'id_cassa_gescon' => null,
|
|
'descrizione' => $datiBancariPayload['denominazione_banca'] ?? $datiBancariPayload['numero_conto'],
|
|
'saldo_iniziale' => 0,
|
|
'saldo_corrente' => 0,
|
|
'tipo_conto' => $tipoConto,
|
|
'banca' => $datiBancariPayload['denominazione_banca'] ?? null,
|
|
'numero_conto' => $datiBancariPayload['numero_conto'] ?? null,
|
|
'iban' => $datiBancariPayload['iban'] ?? null,
|
|
'swift' => $datiBancariPayload['bic_swift'] ?? null,
|
|
'attivo' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$exists = DB::table('conti_bancari')
|
|
->where('tenant_id', $tenantId)
|
|
->where('codice', $codice)
|
|
->first();
|
|
|
|
$id = null;
|
|
if ($exists) {
|
|
unset($payload['created_at']);
|
|
DB::table('conti_bancari')->where('id', $exists->id)->update($payload);
|
|
$id = $exists->id;
|
|
} else {
|
|
$id = DB::table('conti_bancari')->insertGetId($payload);
|
|
}
|
|
return $id;
|
|
}
|
|
|
|
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] = $this->resolveContiBancariId($stabileId, $row);
|
|
}
|
|
}
|
|
|
|
// 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] = $this->resolveContiBancariId($stabileId, $row);
|
|
}
|
|
|
|
// 3) Ultimo fallback: qualsiasi conto dello stabile
|
|
$row = DB::table('dati_bancari')
|
|
->where('stabile_id', $stabileId)
|
|
->orderByDesc('updated_at')
|
|
->first();
|
|
return $this->defaultContoCache[$stabileId] = $this->resolveContiBancariId($stabileId, $row);
|
|
}
|
|
|
|
/**
|
|
* 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 = $this->scopeCondominQuery(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;
|
|
}
|
|
private function firstNonEmptyStr(array $values, int $maxLen): ?string
|
|
{
|
|
foreach ($values as $v) {
|
|
if ($v === null) {
|
|
continue;
|
|
}
|
|
$s = trim((string) $v);
|
|
if ($s === '') {
|
|
continue;
|
|
}
|
|
return mb_substr($s, 0, $maxLen);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
}
|