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')) { if ($this->targetStabileId !== null) { $parentStabile = DB::table('stabili')->where('id', $this->targetStabileId)->first(); } elseif ($this->option('stabile')) { $stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione'; $parentStabile = DB::table('stabili')->where($stabileCol, $this->option('stabile'))->first(); if (!$parentStabile && $stabileCol === 'codice_stabile') { $alt = ltrim($this->option('stabile'), '0'); if ($alt !== '' && $alt !== $this->option('stabile')) { $parentStabile = DB::table('stabili')->where($stabileCol, $alt)->first(); } } } } $pertinenzeToLink = []; $processed = 0; $batch = $limit ? min(max(1, $limit), 1000) : 1000; $offset = 0; while (true) { $q = DB::connection('gescon_import')->table('condomin'); if ($this->option('stabile')) { $q->where('cod_stabile', $this->option('stabile')); } if ($this->option('scala')) { $q->where('scala', $this->option('scala')); } $q->limit($batch)->offset($offset); $rows = $q->get(); if ($rows->isEmpty()) { break; } foreach ($rows as $r) { // risolvi stabile_id per riga (multi-stabili) $scalaVal = trim((string)($r->scala ?? '')); $scalaVal = $scalaVal !== '' ? $scalaVal : 'A'; $rowStabileId = $this->stabileId; $rowStabileCode = $this->targetStabileCode; if ($this->targetStabileId !== null) { $rowStabileId = $this->targetStabileId; if ($this->splitMode === 'stabili-per-scala' && $parentStabile) { [$childId, $childCode] = $this->ensureStabilePerScala($parentStabile, $scalaVal); if ($childId) { $rowStabileId = $childId; $rowStabileCode = $childCode ?? $rowStabileCode; } } } else { if (Schema::hasTable('stabili')) { $stabileCol = Schema::hasColumn('stabili', 'codice_stabile') ? 'codice_stabile' : 'denominazione'; $stRow = DB::table('stabili')->where($stabileCol, $r->cod_stabile ?? $this->option('stabile'))->first(); $rowStabileId = $stRow->id ?? $this->stabileId; if ($stRow) { $rowStabileCode = $stRow->codice_stabile ?? $stRow->denominazione ?? $rowStabileCode; } if ($this->splitMode === 'stabili-per-scala' && $parentStabile) { [$childId, $childCode] = $this->ensureStabilePerScala($parentStabile, $scalaVal); if ($childId) { $rowStabileId = $childId; $rowStabileCode = $childCode ?? $rowStabileCode; } } } else { $rowStabileCode = $rowStabileCode ?? ($r->cod_stabile ?? $this->option('stabile')); } } if (!$rowStabileId) { continue; } $interno = isset($r->interno) ? (string)$r->interno : null; [$scalaVal, $interno] = $this->normalizeScalaInternoForImport($scalaVal, $interno); $sub = $r->sub ?? $interno; // Riconosci pertinenze nel campo INT o note: cantina, box/garage, posto auto, soffitta/solaio, locale tecnico $internoStr = is_string($interno) ? trim($interno) : (string)$interno; $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] ?? ''); } } // Piano: normalizza (R=0, T=0, 1=1, -1 o S= -1) $pianoVal = 0; if (isset($r->piano)) { $pv = is_string($r->piano) ? strtoupper(trim($r->piano)) : (string)$r->piano; if ($pv === 'T' || $pv === 'PT' || $pv === 'R' || $pv === 'PR' || $pv === '') { $pianoVal = 0; } elseif ($pv === 'S' || $pv === 'PS' || $pv === '-1') { $pianoVal = -1; } elseif (is_numeric($pv)) { $pianoVal = (int)$pv; } } if ($hasDetailedSchema) { $normNumeric = function ($v): ?string { if ($v === null) { return null; } $s = trim((string) $v); if ($s === '') { return null; } $s = str_replace(' ', '', $s); if (str_contains($s, ',') && str_contains($s, '.')) { $s = str_replace('.', '', $s); } $s = str_replace(',', '.', $s); return is_numeric($s) ? $s : null; }; $catSez = property_exists($r, 'catasto_sez_urbana') ? (string) ($r->catasto_sez_urbana ?? '') : ''; $catFoglio = property_exists($r, 'catasto_foglio') ? (string) ($r->catasto_foglio ?? '') : ''; $catPart = property_exists($r, 'catasto_particella') ? (string) ($r->catasto_particella ?? '') : ''; $catSub = property_exists($r, 'catasto_sub') ? (string) ($r->catasto_sub ?? '') : ''; $catCat = property_exists($r, 'catasto_categoria') ? (string) ($r->catasto_categoria ?? '') : ''; $catClasse = property_exists($r, 'catasto_classe') ? (string) ($r->catasto_classe ?? '') : ''; $catCons = property_exists($r, 'catasto_consistenza') ? $normNumeric($r->catasto_consistenza ?? null) : null; $catSup = property_exists($r, 'catasto_superfice') ? $normNumeric($r->catasto_superfice ?? null) : null; $catRend = property_exists($r, 'catasto_rendita') ? $normNumeric($r->catasto_rendita ?? null) : null; // Codice unita coerente: STAB-SCALA-INT (per cantine: STAB-SCALA-CAN) $intPart = $ptype && $pcode ? $pcode : (($interno ?? null) ?: '0'); $codiceBaseStab = $rowStabileCode ?? ($r->cod_stabile ?? $this->option('stabile')) ?: 'S0'; // Se stabili-per-scala, includi comunque la scala nel codice per leggibilità $codice = $codiceBaseStab . '-' . ($scalaVal ?: 'A') . '-' . $intPart; // Filtri post-normalizzazione: pertinenze e piano min/max if ($this->option('only-pertinenze')) { $opt = strtolower((string)$this->option('only-pertinenze')); $isPert = (bool)$ptype; $ok = $opt === 'any' ? $isPert : ($isPert && $ptype === $opt); if (!$ok) { continue; } } $pMin = $this->option('piano-min') !== null ? (int)$this->option('piano-min') : null; $pMax = $this->option('piano-max') !== null ? (int)$this->option('piano-max') : null; if ($pMin !== null && $pianoVal < $pMin) { continue; } if ($pMax !== null && $pianoVal > $pMax) { continue; } $exists = DB::table('unita_immobiliari')->where('codice_unita', $codice)->first(); if ($exists) { // Aggiorna palazzina/scala se richiesto $patch = []; 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++; } continue; } $ownerName = trim(($r->cognome ?? '') . ' ' . ($r->nome ?? '')) ?: null; $tipologiaDef = $this->detectTipologiaFromRow($r, $ptype, $internoStr); $tipologiaId = $hasTipologiaColumn ? $this->resolveTipologiaIdFor($tipologiaDef) : null; $denominazione = $ownerName; if (($tipologiaDef['is_condominiale'] ?? false) && !empty($tipologiaDef['nome'])) { $denominazione = $tipologiaDef['nome']; } $tipoUnitaRaw = $tipologiaDef['tipo_unita'] ?? ($ptype ?: 'abitazione'); $data = [ 'stabile_id' => $rowStabileId, 'palazzina_id' => null, 'codice_unita' => $codice, 'denominazione' => $denominazione, 'descrizione' => null, 'palazzina' => $this->splitMode === 'palazzine' ? $scalaVal : null, 'scala' => $scalaVal, 'piano' => $pianoVal, 'interno' => (string)($ptype && $pcode ? $pcode : $interno), 'subalterno' => (string)$sub, 'categoria_catastale' => null, 'classe' => null, 'consistenza' => null, 'rendita_catastale' => null, 'millesimi_generali' => 0, 'tipo_unita' => $this->normalizeTipoUnita($tipoUnitaRaw), '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); // 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' => $tipologiaDef['tipo_unita'] ?? $ptype, 'owner_signature' => $ownerName, ]; } } 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; } // Collega pertinenze alle unità principali (cumulo) se tabella disponibile if (!empty($pertinenzeToLink) && Schema::hasTable('unita_pertinenze')) { foreach ($pertinenzeToLink as $p) { $mainCandidates = DB::table('unita_immobiliari') ->where('stabile_id', $p['stabile_id']) ->where('scala', $p['scala']) ->orderBy('id') ->get(); $main = $mainCandidates->first(function ($unit) use ($p) { if ($this->isUnitPertinenza($unit) || $this->isUnitCondominiale($unit)) { return false; } if (!empty($p['owner_signature'])) { $normalized = Str::lower(trim($unit->denominazione ?? '')); return $normalized === Str::lower(trim($p['owner_signature'])); } return true; }); if (!$main) { $main = $mainCandidates->first(function ($unit) { return !$this->isUnitPertinenza($unit) && !$this->isUnitCondominiale($unit); }); } $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 detectTipologiaFromRow(object $row, ?string $pertinenzaType, ?string $interno): array { $defaults = $this->tipologiaDefaults(); $interno = $interno ?? ''; $descriptorSources = [ $interno, $row->note_unita ?? null, $row->note ?? null, $row->destinazione ?? null, $row->descrizione ?? null, ]; $descriptor = Str::upper(trim(implode(' ', array_filter(array_map(fn($v) => is_string($v) ? $v : '', $descriptorSources))))); $owner = Str::upper(trim(($row->cognome ?? '') . ' ' . ($row->nome ?? ''))); if ($pertinenzaType) { $map = [ 'cantina' => 'CANT', 'box' => 'BOX', 'garage' => 'BOX', 'posto_auto' => 'POSTO_AUTO', 'soffitta' => 'SOFF', 'locale_tecnico' => 'LOCALE_TEC', ]; $code = $map[$pertinenzaType] ?? null; if ($code && isset($defaults[$code])) { return array_merge( $defaults[$code], ['tipo_unita' => $pertinenzaType] ); } } if ($this->looksLikeCondominio($owner) || $this->looksLikeCondominio($descriptor)) { foreach ( [ 'PORTINERIA' => 'CONDO_PORTINERIA', 'BICI' => 'CONDO_BICI', 'CICLO' => 'CONDO_BICI', 'LAVAND' => 'CONDO_LAVANDERIA', 'PISCINA' => 'CONDO_PISCINA', ] as $needle => $code ) { if (Str::contains($descriptor, $needle)) { return array_merge( $defaults[$code] ?? ['code' => $code, 'nome' => Str::headline(Str::lower($needle)), 'categoria' => 'condominiale', 'is_condominiale' => true], ['tipo_unita' => 'condominiale'] ); } } $code = 'CONDO_GENERIC'; return array_merge( $defaults[$code] ?? ['code' => $code, 'nome' => 'Area Condominiale', 'categoria' => 'condominiale', 'is_condominiale' => true], ['tipo_unita' => 'condominiale'] ); } foreach ( [ 'UFF' => ['UFF', ['UFFICIO', 'STUDIO']], 'NEGOZIO' => ['NEGOZIO', ['NEGOZIO', 'BOTTEGA', 'COMMERCIALE']], ] as $code => [$label, $needles] ) { foreach ($needles as $needle) { if (Str::contains($descriptor, $needle)) { return array_merge( $defaults[$code] ?? ['code' => $code, 'nome' => $label, 'categoria' => 'commerciale'], ['tipo_unita' => Str::lower($label)] ); } } } $code = 'APP'; return array_merge( $defaults[$code] ?? ['code' => $code, 'nome' => 'Appartamento', 'categoria' => 'residenziale'], ['tipo_unita' => 'abitazione'] ); } private function normalizeTipoUnita(?string $value): string { $allowed = [ 'abitazione', 'studio_professionale', 'attivita_commerciale', 'magazzino_deposito', 'box_garage', 'cantina', 'soffitta', 'spazio_comune_redditizio', 'locale_tecnico', 'altro', ]; $normalized = Str::snake(Str::lower(trim((string)$value)), '_'); if ($normalized === '' || $normalized === '_') { return 'abitazione'; } $map = [ 'condominiale' => 'spazio_comune_redditizio', 'spazio_condominiale' => 'spazio_comune_redditizio', 'area_condominiale' => 'spazio_comune_redditizio', 'ufficio' => 'studio_professionale', 'studio' => 'studio_professionale', 'studio_medico' => 'studio_professionale', 'negozio' => 'attivita_commerciale', 'locale_commerciale' => 'attivita_commerciale', 'attivita' => 'attivita_commerciale', 'locale_vendita' => 'attivita_commerciale', 'magazzino' => 'magazzino_deposito', 'deposito' => 'magazzino_deposito', 'box' => 'box_garage', 'garage' => 'box_garage', 'posto_auto' => 'box_garage', 'parcheggio' => 'box_garage', 'solaio' => 'soffitta', 'locale_condominiale' => 'spazio_comune_redditizio', 'locale_tecnico' => 'locale_tecnico', 'centrale_termica' => 'locale_tecnico', ]; $normalized = $map[$normalized] ?? $normalized; if (!in_array($normalized, $allowed, true)) { return 'altro'; } return $normalized; } private function resolveTipologiaIdFor(array $definition): ?int { if (!Schema::hasTable('unita_tipologie')) { return null; } $code = strtoupper($definition['code'] ?? ''); 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) { $this->tipologiaCache[$code] = (int)$row->id; $this->tipologiaMetaCache[(int)$row->id] = $row; return (int)$row->id; } if ($this->isDryRun) { $this->tipologiaCache[$code] = null; return null; } $payload = [ 'codice' => $code, 'nome' => $definition['nome'] ?? $code, 'categoria' => $definition['categoria'] ?? null, 'is_pertinenza' => (bool)($definition['is_pertinenza'] ?? false), 'is_condominiale' => (bool)($definition['is_condominiale'] ?? false), 'meta' => $definition['meta'] ?? null, 'created_at' => now(), 'updated_at' => now(), ]; $id = DB::table('unita_tipologie')->insertGetId($payload); $row = (object)array_merge($payload, ['id' => $id]); $this->tipologiaCache[$code] = $id; $this->tipologiaMetaCache[$id] = $row; return $id; } private function loadTipologiaMeta(?int $id): ?object { if (!$id || $id <= 0) { return null; } if (isset($this->tipologiaMetaCache[$id])) { return $this->tipologiaMetaCache[$id]; } if (!Schema::hasTable('unita_tipologie')) { return null; } $row = DB::table('unita_tipologie')->where('id', $id)->first(); if ($row) { $this->tipologiaMetaCache[$id] = $row; } return $row; } private function isUnitPertinenza(object $unit): bool { $tipo = $unit->tipo_unita ?? null; if (in_array($tipo, ['cantina', 'box', 'garage', 'posto_auto', 'soffitta', 'locale_tecnico'], true)) { return true; } $meta = $this->loadTipologiaMeta($unit->tipologia_id ?? null); return (bool)($meta->is_pertinenza ?? false); } private function isUnitCondominiale(object $unit): bool { $tipo = $unit->tipo_unita ?? null; if ($tipo === 'condominiale') { return true; } $meta = $this->loadTipologiaMeta($unit->tipologia_id ?? null); return (bool)($meta->is_condominiale ?? false); } private function tipologiaDefaults(): array { return [ 'APP' => ['code' => 'APP', 'nome' => 'Appartamento', 'categoria' => 'residenziale'], 'UFF' => ['code' => 'UFF', 'nome' => 'Ufficio', 'categoria' => 'commerciale'], 'NEGOZIO' => ['code' => 'NEGOZIO', 'nome' => 'Negozio', 'categoria' => 'commerciale'], 'CANT' => ['code' => 'CANT', 'nome' => 'Cantina', 'categoria' => 'pertinenza', 'is_pertinenza' => true], 'BOX' => ['code' => 'BOX', 'nome' => 'Box', 'categoria' => 'pertinenza', 'is_pertinenza' => true], 'POSTO_AUTO' => ['code' => 'POSTO_AUTO', 'nome' => 'Posto Auto', 'categoria' => 'pertinenza', 'is_pertinenza' => true], 'SOFF' => ['code' => 'SOFF', 'nome' => 'Soffitta', 'categoria' => 'pertinenza', 'is_pertinenza' => true], 'LOCALE_TEC' => ['code' => 'LOCALE_TEC', 'nome' => 'Locale Tecnico', 'categoria' => 'pertinenza', 'is_pertinenza' => true], 'CONDO_PORTINERIA' => ['code' => 'CONDO_PORTINERIA', 'nome' => 'Portineria Condominiale', 'categoria' => 'condominiale', 'is_condominiale' => true], 'CONDO_BICI' => ['code' => 'CONDO_BICI', 'nome' => 'Locale Bici', 'categoria' => 'condominiale', 'is_condominiale' => true], 'CONDO_LAVANDERIA' => ['code' => 'CONDO_LAVANDERIA', 'nome' => 'Lavanderia Condominiale', 'categoria' => 'condominiale', 'is_condominiale' => true], 'CONDO_PISCINA' => ['code' => 'CONDO_PISCINA', 'nome' => 'Piscina Condominiale', 'categoria' => 'condominiale', 'is_condominiale' => true], 'CONDO_GENERIC' => ['code' => 'CONDO_GENERIC', 'nome' => 'Area Condominiale', 'categoria' => 'condominiale', 'is_condominiale' => true], ]; } private function looksLikeCondominio(string $value): bool { $value = trim($value); if ($value === '') { return false; } return (bool) preg_match('/\bCOND(?:OMIN|\.)/u', $value); } }