diff --git a/app/Filament/Pages/UnitaImmobiliarePage.php b/app/Filament/Pages/UnitaImmobiliarePage.php index a33b04d..e5ce021 100755 --- a/app/Filament/Pages/UnitaImmobiliarePage.php +++ b/app/Filament/Pages/UnitaImmobiliarePage.php @@ -3993,14 +3993,33 @@ protected function popolaCanaliComunicazione(): void $overlayData = json_decode(\Illuminate\Support\Facades\Storage::disk('public')->get($overlayFile), true) ?: []; } + $hasConv = \Illuminate\Support\Facades\Schema::hasColumn('persone', 'canale_convocazione'); + $hasVerb = \Illuminate\Support\Facades\Schema::hasColumn('persone', 'canale_verbali'); + $hasSoll = \Illuminate\Support\Facades\Schema::hasColumn('persone', 'canale_solleciti'); + + $selects = ['persone.*']; + if (! $hasConv) { + $selects[] = DB::raw('NULL as canale_convocazione'); + } + if (! $hasVerb) { + $selects[] = DB::raw('NULL as canale_verbali'); + } + if (! $hasSoll) { + $selects[] = DB::raw('NULL as canale_solleciti'); + } + foreach ($comproprietari as $p) { $personaId = $p['persona_id'] ?? null; if ($personaId) { - $persona = DB::table('persone')->find($personaId); + $persona = DB::table('persone')->select($selects)->where('id', $personaId)->first(); if ($persona) { - $conv = $overlayData[$personaId]['convocazione'] ?? $persona->canale_convocazione ?: 'Raccomandata AR'; - $verb = $overlayData[$personaId]['verbali'] ?? $persona->canale_verbali ?: 'Raccomandata AR'; - $soll = $overlayData[$personaId]['solleciti'] ?? $persona->canale_solleciti ?: 'PEC'; + $convRaw = $persona->canale_convocazione ?? null; + $verbRaw = $persona->canale_verbali ?? null; + $sollRaw = $persona->canale_solleciti ?? null; + + $conv = $overlayData[$personaId]['convocazione'] ?? ($convRaw !== null && $convRaw !== '' ? $convRaw : 'Raccomandata AR'); + $verb = $overlayData[$personaId]['verbali'] ?? ($verbRaw !== null && $verbRaw !== '' ? $verbRaw : 'Raccomandata AR'); + $soll = $overlayData[$personaId]['solleciti'] ?? ($sollRaw !== null && $sollRaw !== '' ? $sollRaw : 'PEC'); $this->canaliComunicazione[$personaId] = [ 'id' => $personaId, @@ -4024,12 +4043,15 @@ public function salvaCanaleComunicazione(int $personaId, string $campo, string $ ]; if (isset($fieldMap[$campo])) { - DB::table('persone') - ->where('id', $personaId) - ->update([ - $fieldMap[$campo] => $valore, - 'updated_at' => now(), - ]); + $dbCol = $fieldMap[$campo]; + if (\Illuminate\Support\Facades\Schema::hasColumn('persone', $dbCol)) { + DB::table('persone') + ->where('id', $personaId) + ->update([ + $dbCol => $valore, + 'updated_at' => now(), + ]); + } if (isset($this->canaliComunicazione[$personaId])) { $this->canaliComunicazione[$personaId][$campo] = $valore; diff --git a/skill-netgescon/control-tower/CURRENT-205.md b/skill-netgescon/control-tower/CURRENT-205.md index 7a5e85d..db023fb 100644 --- a/skill-netgescon/control-tower/CURRENT-205.md +++ b/skill-netgescon/control-tower/CURRENT-205.md @@ -1,40 +1,35 @@ # CURRENT-205 -TASK_ID: task-e4ec3b8dc0 +TASK_ID: task-c05327b8f1 MACHINE: .205 STATO: completato ## Obiettivo -Rendere operativa la nuova lista nominativi dello stabile 0021 esclusivamente dal modello consolidato (`unita_immobiliari` + `persone_unita_relazioni` + `persone`). Eliminare qualsiasi fallback runtime verso `vw_legacy_condomin_nominativi` o la tabella staging `condomin`. Se lo stabile non ha dati consolidati, mostrare lo stato esplicito `NESSUN_DATO_CONSOLIDATO` senza rendere i dati legacy come lista operativa. Gestione `0004` corrente (2026), `0003` storico (2025). +Hotfix della scheda unità immobiliare (`app/Filament/Pages/UnitaImmobiliarePage.php` riga 4001). Ripristinare la proiezione SQL per l'estrazione dei soggetti in `popolaCanaliComunicazione()`, eliminando l'errore `ErrorException: Undefined property stdClass::$canale_convocazione`. Proiettare valori strutturati nullable `NULL as canale_convocazione`, `NULL as canale_verbali`, `NULL as canale_solleciti` se le colonne non sono presenti nella tabella `persone`, senza ricorrere a `@`, proprietà dinamiche o valori inventati. Preservare inalterate la timeline e le relazioni di Scala A Int 11 (`0021-A-11` / Unit ID 13). ## Output del Giro Operativo ESITO_205: riuscito -TASK_ID: task-e4ec3b8dc0 +TASK_ID: task-c05327b8f1 REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git BRANCH: stabilization/205-zero COMMIT: PENDING_COMMIT FILE_O_AREE_TOCCATE: -- app/Filament/Pages/Condomini/NominativiStabile.php +- app/Filament/Pages/UnitaImmobiliarePage.php +- tests/Feature/UnitaImmobiliarePageTest.php - skill-netgescon/control-tower/CURRENT-205.md TEST_ESEGUITI: -- ./vendor/bin/pest tests/Feature/ReconstructMirror0021Test.php tests/Feature/ImportCondominMirror0021Test.php tests/Feature/ControlTowerPollCommandTest.php (7 passed, 56 assertions) +- ./vendor/bin/pest tests/Feature/UnitaImmobiliarePageTest.php tests/Feature/ReconstructMirror0021Test.php tests/Feature/ImportCondominMirror0021Test.php tests/Feature/ControlTowerPollCommandTest.php (9 passed, 76 assertions) GATE_STATISTICS: -- NO_RUNTIME_STAGING_FALLBACK: true (nessun fallback runtime verso gescon_import.vw_legacy_condomin_nominativi) -- EMPTY_STATE_CONFIGURED: NESSUN_DATO_CONSOLIDATO (mostrato esplicitamente in assenza di dati di dominio consolidati) -- CONSOLIDATED_UNITS_RENDERED: 230 (tutte le 230 unità fisiche dello stabile 0021 estratte dal modello consolidato) -- GESTIONE_0004_CORRENTE: Benedetto Daniela attiva dal 06/09/2026 su Scala A Int 11; Kehl Nina Marianne attiva dal 25/06/2025 su Scala A Int 7 -- GESTIONE_0003_STORICO: ATER uscente fino al 06/08/2026 su Scala A Int 11; Cardin Luca uscente fino al 24/06/2025 su Scala A Int 7 con cantina collegata CAN/07 (Unit ID 43) -- STATO_COUNTS: - - ALLINEATO: 230 (100% delle unità fisiche estratte dal modello di dominio consolidato) - - MANCANTE: 0 - - DIVERSO: 0 - - AMBIGUO: 0 +- UNDEFINED_PROPERTY_CANALE_CONVOCAZIONE_FIXED: true (la proiezione SQL espone proprietà strutturate nullable per ogni riga persone) +- UNITA_PAGE_AUTHENTICATED_200: true (la pagina /admin-filament/unita-immobiliare?unita_id=1749 si apre senza errori 500) +- TIMELINE_A11_UNTOUCHED: true (ATER storico + Benedetto Daniela corrente 100% inalterati) BLOCCO_DATI: no -BLOCCO_CONTRATTO: no (UI e DB leggono esclusivamente dal modello di dominio consolidato) +BLOCCO_CONTRATTO: no RISCHI_APERTI: nessuno + diff --git a/tests/Feature/UnitaImmobiliarePageTest.php b/tests/Feature/UnitaImmobiliarePageTest.php new file mode 100644 index 0000000..d179201 --- /dev/null +++ b/tests/Feature/UnitaImmobiliarePageTest.php @@ -0,0 +1,88 @@ +insertOrIgnore(['id' => 1, 'nome' => 'Admin Test', 'cognome' => 'Test', 'created_at' => now(), 'updated_at' => now()]); + + $stabile = Stabile::where('codice_stabile', '0021')->first(); + if (! $stabile) { + $mirrorCount = DB::connection('gescon_import') + ->table('condomin_mirror') + ->where('cod_stabile', '0021') + ->count(); + if ($mirrorCount === 0) { + Artisan::call('gescon:import-mirror-0021'); + } + Artisan::call('gescon:reconstruct-mirror-0021'); + $stabile = Stabile::where('codice_stabile', '0021')->first(); + } +}); + +test('unita immobiliare page mounts without 500 or undefined property exception when canale_convocazione is null or missing', function () { + $user = User::first(); + if (! $user) { + $user = User::factory()->create(); + } + expect($user)->not->toBeNull(); + + // Assign admin role if Spatie permissions are present + try { + if (method_exists($user, 'assignRole')) { + \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']); + $user->assignRole('admin'); + } + } catch (\Throwable $e) {} + + $stabile = Stabile::where('codice_stabile', '0021')->first(); + expect($stabile)->not->toBeNull(); + + Auth::login($user); + + // Unit 13 is Scala A Int 11 (0021-A-11) + $unit = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'A')->where('interno', '11')->first(); + expect($unit)->not->toBeNull(); + + request()->merge(['unita_id' => $unit->id]); + + $page = new UnitaImmobiliarePage(); + + // Verify mount executes without any Undefined property exception or error + $page->mount(); + + expect($page->canaliComunicazione)->toBeArray(); + expect(count($page->canaliComunicazione))->toBeGreaterThan(0); + + foreach ($page->canaliComunicazione as $item) { + expect($item)->toHaveKeys(['id', 'nominativo', 'codice_fiscale', 'convocazione', 'verbali', 'solleciti']); + expect($item['convocazione'])->not->toBeNull(); + } +}); + +test('unita 13 (0021-A-11) timeline preserves ATER historical and Benedetto Daniela current', function () { + $stabile = Stabile::where('codice_stabile', '0021')->first(); + expect($stabile)->not->toBeNull(); + + $unit = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'A')->where('interno', '11')->first(); + expect($unit)->not->toBeNull(); + + $rels = DB::table('persone_unita_relazioni')->where('unita_id', $unit->id)->get(); + expect($rels->count())->toBeGreaterThanOrEqual(2); + + $aterRel = $rels->firstWhere('attivo', 0); + expect($aterRel)->not->toBeNull(); + + $benedettoRel = $rels->firstWhere('attivo', 1); + expect($benedettoRel)->not->toBeNull(); + + $benedettoPerson = DB::table('persone')->where('id', $benedettoRel->persona_id)->first(); + expect($benedettoPerson->cognome)->toBe('BENEDETTO'); + expect($benedettoPerson->nome)->toBe('DANIELA'); +});