From f345abc3bac416c5255471745ce869ea22f39ccb Mon Sep 17 00:00:00 2001 From: michele Date: Thu, 27 Aug 2026 14:08:01 +0200 Subject: [PATCH] fix(unita): enforce deterministic timeline gestion spans, import tabelle/dett_tab millesimal tables from legacy MDB and document Commercialista rule (task-d095b0e23b) --- app/Filament/Pages/UnitaImmobiliarePage.php | 156 +++++++++++++++--- docs/ui-wireframes/unita-immobiliare.md | 55 ++++-- .../directives/ANAGRAFICHE_MAPPING_LEGACY.md | 13 ++ 3 files changed, 185 insertions(+), 39 deletions(-) diff --git a/app/Filament/Pages/UnitaImmobiliarePage.php b/app/Filament/Pages/UnitaImmobiliarePage.php index 9c0b38c..3c3e381 100755 --- a/app/Filament/Pages/UnitaImmobiliarePage.php +++ b/app/Filament/Pages/UnitaImmobiliarePage.php @@ -1377,13 +1377,8 @@ private function populateNominativiStoriciFallback(): void $grouped = []; foreach ($mirrorRows as $m) { - $yearStr = match ($m->source_year) { - '0004', '0914' => '2026', - '0003', '0913' => '2025', - '0001', '0912' => '2024', - default => 'Gestione ' . $m->source_year, - }; - $yLabel = $yearStr . ' (' . $m->source_year . ')'; + $gCode = (string) $m->source_year; + $yLabel = 'Gestione ' . $gCode; $owner = trim((string) ($m->nom_cond ?? $m->proprietario ?? '')); if ($owner !== '') { @@ -1394,17 +1389,15 @@ private function populateNominativiStoriciFallback(): void 'nome' => $owner, 'codice_fiscale'=> ! empty($m->codice_fiscale) ? $m->codice_fiscale : null, 'tipo_relazione'=> 'proprietario', - 'gestioni' => [$yLabel], - 'years' => [$yearStr], + 'gestioni' => [$gCode], 'quota' => ($m->source_table === 'comproprietari' || (float)($m->perc_diritto_reale ?? 0) > 0) ? (float)($m->perc_diritto_reale ?? 50) : 100.0, 'quota_label' => ($m->source_table === 'comproprietari' || (float)($m->perc_diritto_reale ?? 0) > 0) ? number_format((float)($m->perc_diritto_reale ?? 50), 2, ',', '.') : '100,00', 'attivo' => false, 'fonte' => $m->source_table === 'comproprietari' ? 'Legacy comproprietari' : 'Archivio storico legacy', ]; } else { - if (! in_array($yLabel, $grouped[$key]['gestioni'])) { - $grouped[$key]['gestioni'][] = $yLabel; - $grouped[$key]['years'][] = $yearStr; + if (! in_array($gCode, $grouped[$key]['gestioni'])) { + $grouped[$key]['gestioni'][] = $gCode; } } } @@ -1419,26 +1412,25 @@ private function populateNominativiStoriciFallback(): void 'nome' => $inquil, 'codice_fiscale'=> $inqCf !== '' ? $inqCf : null, 'tipo_relazione'=> 'inquilino', - 'gestioni' => [$yLabel], - 'years' => [$yearStr], + 'gestioni' => [$gCode], 'quota' => 100.0, 'quota_label' => '100,00', 'attivo' => false, 'fonte' => 'Archivio storico legacy', ]; } else { - if (! in_array($yLabel, $grouped[$key]['gestioni'])) { - $grouped[$key]['gestioni'][] = $yLabel; - $grouped[$key]['years'][] = $yearStr; + if (! in_array($gCode, $grouped[$key]['gestioni'])) { + $grouped[$key]['gestioni'][] = $gCode; } } } } foreach ($grouped as $item) { - $gSpan = count($item['gestioni']) > 1 - ? implode(' → ', [$item['years'][0], end($item['years'])]) . ' (Gestioni ' . implode(', ', array_map(fn($g) => explode(' ', $g)[1] ?? $g, $item['gestioni'])) . ')' - : $item['gestioni'][0]; + $gList = $item['gestioni']; + $gSpan = count($gList) > 1 + ? 'Gestioni ' . implode(', ', $gList) . ' (Titolare storico)' + : 'Gestione ' . $gList[0]; $fallbackList[] = [ 'persona_id' => $item['persona_id'], @@ -2814,6 +2806,128 @@ protected function hydrateMillesimi(): void }) ->values() ->all(); + + if (empty($this->millesimiPerTabella)) { + $this->millesimiPerTabella = $this->loadMillesimiFromLegacyMdb(); + } + } + + protected function loadMillesimiFromLegacyMdb(): array + { + if (! $this->unita || ! $this->unita->stabile) { + return []; + } + + $codStabile = trim((string) $this->unita->stabile->codice_stabile); + $scala = trim((string) $this->unita->scala); + $interno = trim((string) $this->unita->interno); + + if ($codStabile === '' || $interno === '') { + return []; + } + + $idCond = null; + if ($this->hasImportConnection() && DbSchema::connection('gescon_import')->hasTable('condomin_mirror')) { + $q = DB::connection('gescon_import')->table('condomin_mirror') + ->where('cod_stabile', $codStabile) + ->where('interno', $interno); + if ($scala !== '') { + $q->where('scala', $scala); + } + $mRow = $q->first(); + if ($mRow) { + $idCond = $mRow->id_cond ?: $mRow->cod_cond; + } + } + + if (! $idCond) { + return []; + } + + $candidatePaths = [ + "/mnt/gescon-archives/gescon/{$codStabile}/0914/singolo_anno.mdb", + "/mnt/gescon-archives/gescon/{$codStabile}/0004/singolo_anno.mdb", + ]; + + $mdbPath = null; + foreach ($candidatePaths as $p) { + if (file_exists($p) && is_readable($p)) { + $mdbPath = $p; + break; + } + } + + if (! $mdbPath) { + return []; + } + + try { + $tabRows = $this->extractMdbTableRows($mdbPath, 'tabelle'); + $dettRows = $this->extractMdbTableRows($mdbPath, 'dett_tab'); + + $tabMap = []; + foreach ($tabRows as $tr) { + $c = isset($tr['cod_tab']) ? (string) $tr['cod_tab'] : null; + if ($c) { + $tabMap[$c] = isset($tr['des_tab']) && trim((string) $tr['des_tab']) !== '' ? (string) $tr['des_tab'] : $c; + } + } + + $res = []; + foreach ($dettRows as $dr) { + $drIdCond = isset($dr['id_cond']) ? (string) $dr['id_cond'] : null; + $drInq = isset($dr['cond_inquil']) ? (string) $dr['cond_inquil'] : 'C'; + $mm = isset($dr['mm']) && is_numeric($dr['mm']) ? (float) $dr['mm'] : 0.0; + + if ($drIdCond === (string) $idCond && strtoupper($drInq) === 'C' && $mm > 0) { + $cTab = isset($dr['cod_tab']) ? (string) $dr['cod_tab'] : 'TAB'; + $nomeTab = $tabMap[$cTab] ?? $cTab; + $res[] = [ + 'tipo' => str_contains(strtolower($cTab), 'risc') ? 'riscaldamento' : (str_contains(strtolower($cTab), 'stra') ? 'straordinaria' : 'ordinaria'), + 'gruppo' => $this->mapTipoToGruppo($cTab), + 'id' => null, + 'codice' => $cTab, + 'nome' => $nomeTab, + 'millesimi' => $mm, + ]; + } + } + + return $res; + } catch (\Throwable $e) { + return []; + } + } + + private function extractMdbTableRows(string $mdbPath, string $tableName): array + { + $proc = new \Symfony\Component\Process\Process(['mdb-export', $mdbPath, $tableName]); + $proc->run(); + + if (! $proc->isSuccessful()) { + return []; + } + + $csvContent = $proc->getOutput(); + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $csvContent); + rewind($stream); + + $header = fgetcsv($stream); + if (! $header) { + fclose($stream); + return []; + } + + $rows = []; + while (($data = fgetcsv($stream)) !== false) { + if (count($data) === count($header)) { + $rows[] = array_combine($header, $data); + } + } + + fclose($stream); + return $rows; } public function setMillesimiGruppo(string $gruppo): void @@ -3326,7 +3440,7 @@ protected function hydrateRelazioni(): void 'ruolo_label' => 'Proprietario 100%', 'quota' => 100.0, 'quota_label' => '100,00', - 'data_inizio' => '01/01/' . $activeAnno, + 'data_inizio' => 'Gestione ' . $activeAnno, 'data_fine' => null, 'attivo' => true, 'fonte' => 'legacy_condomin_mirror', diff --git a/docs/ui-wireframes/unita-immobiliare.md b/docs/ui-wireframes/unita-immobiliare.md index ae1d285..4c48f35 100644 --- a/docs/ui-wireframes/unita-immobiliare.md +++ b/docs/ui-wireframes/unita-immobiliare.md @@ -1,36 +1,55 @@ -# ASCII Wireframe - Scheda Unità Immobiliare +# ASCII Wireframe - Scheda Unità Immobiliare (Timeline e Tabelle Millesimali) -URL: `http://192.168.0.205:8000/admin-filament/unita-immobiliare` +URL: `http://192.168.0.205:8000/admin-filament/unita-immobiliare?unita_id=321` ```text +-----------------------------------------------------------------------------------------------------------------------+ | NETGESCON · CENTRO STELLA [ Utente: Cecilia Tordini v ] | +-----------------------------------------------------------------------------------------------------------------------+ -| UNITA IMMOBILIARE | +| UNITA IMMOBILIARI > SCHEDA UNITA IMMOBILIARE (Scala B - Int. 4 - Piano 2) | | | -| BARRA DI SELEZIONE E TIMELINE | -| [<- Prec.] Unità: [ Scala A - Int. 11 (Piano 4 - Abitazione) [A-11] — BENEDETTO DANIELA v ] [Succ. ->] | -| | -| [ Riepilogo ] [ Tabelle Millesimali ] [ Catasto ] [ Economico ] [ Estratto Conto ] [ Acqua ] | +| Stabile: [ 0016 - GERMANICO 96 v ] Anno Gestione: [ 0914 (Anno 2026 - Attiva) v ] | +| Barra selezione unità: [<- Prec.] Unità: [ Scala B - Int. 4 (ID 321) v ] [Succ. ->] | | | | +-----------------------------------------------------------------------------------------------------------------+ | -| | DETTAGLI UNITA IMMOBILIARE & ANAGRAFICA ATTIVA | | -| +-----------------------------------------------------------------------------------------------------------------+ | -| | Codice: 0021-A-11 | Scala: A | Piano: 4 | Interno: 11 | Tipo: Abitazione | | -| | Proprietario Attivo: BENEDETTO DANIELA (Quota 100,00%) - Dal 09/06/2026 | | +| | TAB: [ Riepilogo (Active) ] [ Tabelle Millesimali ] [ Estratto Conto ] [ Ripartizione Spese ] [ Dati Catasto ] | | | +-----------------------------------------------------------------------------------------------------------------+ | | | | +-----------------------------------------------------------------------------------------------------------------+ | -| | TIMELINE SUBENTRI E GESTIONI ANTERIORE (Cronistoria Titolari) | | +| | TITOLARI E COMPROPRIETARI IN CARICA (Gestione 0914 - Anno 2026) | | | +-----------------------------------------------------------------------------------------------------------------+ | -| | [ Da 09/06/2026 ] BENEDETTO DANIELA (Proprietario 100%) · Fonte: Anagrafica consolidata | | -| | Dettaglio: CF BNDDNL86M58H224O | | +| | - BARONE MICHELE | Proprietario | Quota: 50,00% | CF: BRNMHL69M11H501K | [Apri Anagrafica] | | +| | - FRANCIONI CLAUDIA / | Comproprietario | Quota: 50,00% | CF: FRNCLD49P51H501B | [Apri Anagrafica] | | +| | [Email: cecilia.tordini@gmail.com] | | +| +-----------------------------------------------------------------------------------------------------------------+ | +| | +| +-----------------------------------------------------------------------------------------------------------------+ | +| | TIMELINE SUBENTRI E GESTIONI ANTERIORI (Determinismo Certificato Legacy) | | +| +-----------------------------------------------------------------------------------------------------------------+ | +| | Cronistoria titolari ed avvicendamenti per questa unità immobiliare tra le diverse gestioni | | | | | | -| | [ Fino a 08/06/2026 ] ATER C/O UFFICIO CONDOMINI PASSIVI (Proprietario 100%) · Fonte: Legacy condomin | | -| | Dettaglio: CF 01234567890 · Gestione 2026 (0004) | | +| | [•] Proprietario: BARONE MICHELE (Quota: 50,00%) | | +| | Intervallo: Gestioni 0004, 0005, 0909, 0912, 0913, 0914 (Titolare storico consolidato) | | | | | | -| | [ Gestione 0003 ] ATER C/O UFFICIO CONDOMINI PASSIVI (Proprietario 100%) · Anno 2025 | | -| | [ Gestione 0001 ] ATER C/O UFFICIO CONDOMINI PASSIVI (Proprietario 100%) · Anno 2024 | | +| | [•] Comproprietario: FRANCIONI CLAUDIA (Quota: 50,00%) | | +| | Intervallo: Gestioni 0004, 0005, 0909, 0912, 0913, 0914 (Legacy comproprietari) | | +| | CF: FRNCLD49P51H501B · Email: cecilia.tordini@gmail.com | | +| +-----------------------------------------------------------------------------------------------------------------+ | +| | +| +-----------------------------------------------------------------------------------------------------------------+ | +| | TABELLE MILLESIMALI E ATTRIBUZIONE QUOTE (Da singolo_anno.mdb: tabelle & dett_tab) | | +| +-----------------------------------------------------------------------------------------------------------------+ | +| | CODICE TABELLA | NOME TABELLA / CATEGORIA | VALORE MILLESIMI (mm) | SOGGETTO ATTRIBUTO | | +| +-----------------+------------------------------------+-----------------------+------------------------------------+ | +| | TAB.A | Tabella Millesimale A (Proprietà) | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | TAB.BB | Tabella Millesimale Scala B | 21,6060 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | TABA.P | Tabella Millesimale Proprietà P. | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | DET_18 | Detrazione Fiscali 18% | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | REND | Rendita Fabbricati | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | RI.ASS | Ripartizione Assicurazione | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | ANTICI | Anticipi Condominiali | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | L02.ST | Lavori Straordinari L02 | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | +| | L03.ST | Lavori Straordinari L03 | 15,2780 mm | Condomino (BARONE MICHELE/FRANCIONI| | | +-----------------------------------------------------------------------------------------------------------------+ | +-----------------------------------------------------------------------------------------------------------------------+ ``` diff --git a/skill-netgescon/directives/ANAGRAFICHE_MAPPING_LEGACY.md b/skill-netgescon/directives/ANAGRAFICHE_MAPPING_LEGACY.md index 27f41fd..0f8b85c 100644 --- a/skill-netgescon/directives/ANAGRAFICHE_MAPPING_LEGACY.md +++ b/skill-netgescon/directives/ANAGRAFICHE_MAPPING_LEGACY.md @@ -27,3 +27,16 @@ ## 4. Dati Catastali Stabile (`Stabili.mdb` - Prefisso `AC_`) - Negli stabili articolati o supercondomini (es. Milizie 3 / 0021 con Palazzina D in altra sezione/particella), la configurazione avanzata dello stabile conserva l'array `altri_riferimenti_catastali`. - Ciascun riferimento sezionale o di palazzina specifica `descrizione`, `foglio`, `particella`, `sezione`, `subalterno`, consentendo l'aggancio diretto alle rispettive unità immobiliari. +## 5. Tabelle Millesimali da Legacy (`tabelle` e `dett_tab` in `singolo_anno.mdb`) +- **Tabella definizioni**: `tabelle` con campi `cod_tab`, `des_tab`, `totale`. +- **Tabella dettagli ed attribuzione**: `dett_tab` con campi `cod_tab`, `id_cond`, `cond_inquil`, `mm`. +- **Regola di collegamento**: + - `tabelle.cod_tab` si lega a `dett_tab.cod_tab`. + - `dett_tab.id_cond` si lega tassativamente a `condomin.id_cond` dell'unità immobiliare per quella specifica gestione. + - `dett_tab.cond_inquil` (`'C'` Condomino, `'I'` Inquilino) attribuisce i millesimi (`mm`) al soggetto titolare. + +## 6. Principio di Deterministicità Assoluta (Regola Commercialista) +- **Divieto di Fallback o Date Fittizie**: È tassativamente vietato inventare date fittizie (es. `01/01/2000` o `01/01/2026` ripetute) o stimare ruoli non presenti negli archivi MDB. +- **Trattamento dati mancanti**: Se una relazione o una data di decorrenza non è presente negli archivi consolidati, il sistema deve riportare unicamente le gestioni storiche certe (es. `Gestioni 0004, 0005, 0909, 0912, 0913, 0914`) oppure restituire `BLOCCO_DATI`. I dati devono essere certificabili e certi al 100%. + +