insertOrIgnore([ 'id' => 1, 'nome' => 'Admin', 'cognome' => 'Test', 'created_at' => now(), 'updated_at' => now(), ]); $this->user = User::factory()->create(); if (method_exists($this->user, 'assignRole')) { \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']); $this->user->assignRole('admin'); } DB::table('stabili')->insertOrIgnore([ 'id' => 19, 'amministratore_id' => 1, 'codice_stabile' => '0016', 'denominazione' => 'CONDOMINIO VIA GERMANICO 96', 'indirizzo' => 'Via Germanico 96', 'citta' => 'Roma', 'cap' => '00192', 'provincia' => 'RM', 'attivo' => 1, 'created_at' => now(), 'updated_at' => now(), ]); $this->stabile = Stabile::find(19); DB::table('unita_immobiliari')->insertOrIgnore([ [ 'id' => 321, 'stabile_id' => 19, 'scala' => 'B', 'interno' => '4', 'codice_unita' => '0016-B-4', 'denominazione' => 'Barone Michele', 'legacy_cond_id' => '47', 'created_at' => now(), 'updated_at' => now(), ], [ 'id' => 287, 'stabile_id' => 19, 'scala' => 'A', 'interno' => '13', 'codice_unita' => '0016-A-13', 'denominazione' => 'Lucaferri Antonella', 'legacy_cond_id' => '13', 'created_at' => now(), 'updated_at' => now(), ], [ 'id' => 295, 'stabile_id' => 19, 'scala' => 'A', 'interno' => '21', 'codice_unita' => '0016-A-21', 'denominazione' => 'Volpetti Elisabetta', 'legacy_cond_id' => '21', 'created_at' => now(), 'updated_at' => now(), ], ]); $this->unita = UnitaImmobiliare::find(321); DB::table('unita_immobiliare_nominativi')->insertOrIgnore([ [ 'unita_immobiliare_id' => 321, 'nominativo' => 'Barone Michele', 'ruolo' => 'P', 'created_at' => now(), 'updated_at' => now(), ], [ 'unita_immobiliare_id' => 321, 'nominativo' => 'Francioni Claudia', 'ruolo' => 'CP', 'created_at' => now(), 'updated_at' => now(), ], ]); StabileContext::setActiveStabileId($this->user, 19); }); test('unit 321 owner picker includes barone michele and francioni claudia', function () { $component = Livewire::actingAs($this->user) ->withQueryParams(['unita_id' => 321]) ->test(UnitaImmobiliarePage::class, ['unita_id' => 321]); $unitaOptions = $component->instance()->getUnitaOptions(); expect($unitaOptions)->toBeArray() ->and(isset($unitaOptions[321]))->toBeTrue(); $label321 = $unitaOptions[321]; expect($label321)->toContain('Barone Michele') ->and($label321)->toContain('Scala B') ->and($label321)->toContain('Int. 4'); }); test('bank reconciliation service reconciles over 85 percent of germanico bank transactions', function () { $service = app(GermanicoBancaReconciliationService::class); $result = $service->syncAndReconcileAll(); expect($result)->toBeArray() ->and($result['total_movimenti'])->toBeGreaterThanOrEqual(1900) ->and($result['totale_riconciliati'])->toBeGreaterThanOrEqual(1600) ->and($result['percentuale'])->toBeGreaterThanOrEqual(85.0); // Verifica che l'unita 321 abbia transazioni riconciliate $unitaMovs = $service->getMovimentiForUnita(321); expect($unitaMovs->count())->toBeGreaterThanOrEqual(7); // Verifica bonifico del 23/12/2025 di 960,79 euro $bonifico960 = $unitaMovs->first(fn($m) => abs((float)$m->importo - 960.79) < 0.01); expect($bonifico960)->not->toBeNull() ->and($bonifico960->stato_riconciliazione)->toBe('riconciliato'); $matchData = is_string($bonifico960->match_data) ? json_decode($bonifico960->match_data, true) : (array) $bonifico960->match_data; expect($matchData['pdf_ricevuta'])->toBe('Inc_EC_20260310_184901.pdf'); }); test('gescon estratto conto service computes exact benchmark 885.00 for germanico unit 321', function () { // Esegue sync/reconcile per popolare i movimenti bancari $bankService = app(GermanicoBancaReconciliationService::class); $bankService->syncAndReconcileAll(); $unita = UnitaImmobiliare::find(321); expect($unita)->not->toBeNull(); $service = app(GesconEstrattoContoService::class); $ec = $service->getEstrattoConto($unita, 'C', true); expect($ec)->toBeArray() ->and($ec['codice_stabile'])->toBe('0016') ->and($ec['totali']['totale_dovuto'])->toBe(885.00) ->and($ec['totali']['totale_ordinaria_corrente_residuo'])->toBe(885.00) ->and($ec['totali']['totale_ordinarie_pregresse_residuo'])->toBe(0.0) ->and($ec['totali']['totale_straordinarie_residuo'])->toBe(0.0); // Verifica ordinaria corrente 2025/26: 6 rate da 177.00 di cui rata 1 saldata $ord = $ec['gestione_ordinaria_corrente']; expect($ord['totale_dovuto'])->toBe(1062.00) ->and($ord['totale_pagato'])->toBe(177.00) ->and($ord['residuo'])->toBe(885.00) ->and(count($ord['rate']))->toBe(6); $rata1 = $ord['rate'][0]; expect($rata1['stato'])->toBe('Saldata') ->and($rata1['pagato'])->toBe(177.00) ->and($rata1['residuo'])->toBe(0.0) ->and($rata1['data_pagamento'])->toBe('23/12/2025') ->and($rata1['movimento_bancario'])->not->toBeNull() ->and($rata1['movimento_bancario']['importo'])->toBe(960.79) ->and($rata1['ricevuta_pdf'])->toBe('Inc_EC_20260310_184901.pdf'); // Verifica movimenti bancari riconciliati allegati all'unita expect($ec['movimenti_bancari'])->toBeArray() ->and(count($ec['movimenti_bancari']))->toBeGreaterThanOrEqual(7); $mov960 = collect($ec['movimenti_bancari'])->first(fn($m) => abs($m['importo'] - 960.79) < 0.01); expect($mov960)->not->toBeNull() ->and($mov960['stato'])->toBe('riconciliato') ->and($mov960['pdf_ricevuta'])->toBe('Inc_EC_20260310_184901.pdf'); }); test('ordinarie page renders banca tab and matches incassi from singolo_anno mdb', function () { $bankService = app(GermanicoBancaReconciliationService::class); $bankService->syncAndReconcileAll(); auth()->login($this->user); $page = new Ordinarie(); $page->mount(); // Test KPI banca $kpi = $page->bancaKpi; expect($kpi)->toBeArray() ->and($kpi['total'])->toBeGreaterThanOrEqual(1900) ->and($kpi['riconciliati'])->toBeGreaterThanOrEqual(1600) ->and($kpi['iban'])->toBe(GermanicoBancaReconciliationService::IBAN_GERMANICO); // Test ricerca movimento Barone Michele $page->bancaSearch = 'Barone Michele'; $movimenti = $page->movimentiBanca; expect($movimenti->total())->toBeGreaterThanOrEqual(7); $firstMov = $movimenti->first(); expect($firstMov->estratto_conto_url)->toContain('unita-immobiliare?unita_id=321') ->and($firstMov->match_pdf)->toContain('.pdf'); // Test incassi da MDB con arricchimento bancario $page->search = null; $incassi = $page->incassi; expect($incassi)->not->toBeNull() ->and($incassi->total())->toBeGreaterThan(0); // Verifica che gli incassi abbiano il matching bancario se presente $matched = collect($incassi->items())->filter(fn($i) => !empty($i->movimento_banca)); expect($matched->count())->toBeGreaterThan(0); }); test('receipt pdf route streams valid pdf document for inc_ec', function () { $response = $this->actingAs($this->user) ->get(route('admin.gescon.inc_ec.pdf', [ 'stabile' => '0016', 'file' => 'Inc_EC_20260310_184901.pdf', ])); $response->assertStatus(200); $response->assertHeader('content-type', 'application/pdf'); }); test('unit 321 multicanale contacts displays real barone michele data and germanico address fallback', function () { auth()->login($this->user); StabileContext::setActiveStabileId($this->user, 19); request()->merge(['unita_id' => 321]); $page = new UnitaImmobiliarePage(); $page->mount(); $recapiti = $page->recapitiMulticanaleTable; expect($recapiti)->toBeArray()->and(count($recapiti))->toBeGreaterThan(0); $valori = collect($recapiti)->pluck('valore')->all(); $valoriStr = implode(' | ', $valori); // Non deve mostrare i recapiti estranei di Skynet Solutions SRL expect(str_contains($valoriStr, 'gmpskynet@gmail.com'))->toBeFalse(); expect(str_contains($valoriStr, '33583314342'))->toBeFalse(); expect(str_contains($valoriStr, 'Via Andrea Bafilr'))->toBeFalse(); // Deve mostrare i recapiti reali di Barone Michele / Cecilia Tordini expect($valoriStr)->toContain('barone.michele@gmail.com') ->and($valoriStr)->toContain('3388155553'); // Indirizzo di residenza assente nel profilo cade sullo stabile expect($valoriStr)->toContain('Via Germanico 96 00192 Roma (RM)'); }); test('gestione affitti page mounts and renders cleanly without 500 error', function () { $component = Livewire::actingAs($this->user) ->test(\App\Filament\Pages\Affitti\GestioneAffitti::class); $component->assertSuccessful(); expect($component->instance()->tabs)->toBeArray() ->and(count($component->instance()->tabs))->toBe(6); }); test('electronic invoice fe 255 allows access to amministratore user', function () { $adminUser = User::factory()->create(); \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']); $adminUser->assignRole('amministratore'); auth()->login($adminUser); expect(\App\Filament\Pages\Contabilita\FatturaElettronicaScheda::canAccess())->toBeTrue(); }); test('ordinarie page bank table links lucaferri a13 to unit 287 and unit 295 does not leak 0013 straordinarie', function () { auth()->login($this->user); StabileContext::setActiveStabileId($this->user, 19); $page = new Ordinarie(); $page->mount(); $page->search = 'Lucaferri'; // Verifica matching incassi MDB: Lucaferri Antonella (A-13) punta all'unita 287 $itemA13 = $page->incassi->first(); expect($itemA13)->not->toBeNull(); expect(str_contains((string) $itemA13->nominativo_pagante, 'Lucaferri'))->toBeTrue(); expect(str_contains((string) $itemA13->unita_ec_url, 'unita_id=287'))->toBeTrue(); expect(str_contains((string) $itemA13->unita_ec_url, 'unita_id=295'))->toBeFalse(); // Verifica che l'estratto conto dell'unita 295 non contenga dati dello Stabile 0013 Spadavecchia $unita295 = UnitaImmobiliare::findOrFail(295); $service = app(GesconEstrattoContoService::class); $ec295 = $service->getEstrattoConto($unita295, 'C', true); expect($ec295['codice_stabile'])->toBe('0016'); $straordTitles = collect($ec295['gestioni_straordinarie'] ?? [])->pluck('titolo')->all(); $straordStr = implode(' | ', $straordTitles); expect(str_contains($straordStr, 'PASSERELLA'))->toBeFalse(); expect(str_contains($straordStr, 'CORNICIONE'))->toBeFalse(); expect(str_contains($straordStr, 'SPADAVECCHIA'))->toBeFalse(); }); test('bank documents upload list and delete functionality in ordinarie', function () { auth()->login($this->user); StabileContext::setActiveStabileId($this->user, 19); $page = new Ordinarie(); $page->mount(); $initialCount = $page->bankDocuments->count(); $fakeFile = \Illuminate\Http\UploadedFile::fake()->create('estratto_conto_fucino_2025.pdf', 120, 'application/pdf'); $page->bankDocumentTipo = 'estratto_conto'; $page->bankDocumentDescrizione = 'Estratto Conto Ufficiale Q4 2025'; $page->bankDocumentFile = $fakeFile; $page->uploadBankDocument(); $docs = $page->getBankDocumentsProperty(); expect($docs->count())->toBe($initialCount + 1); $uploaded = $docs->first(); expect($uploaded->nome_originale)->toBe('estratto_conto_fucino_2025.pdf') ->and($uploaded->categoria)->toBe('bancari') ->and($uploaded->stabile_id)->toBe(19); // Test eliminazione documento $page->deleteBankDocument($uploaded->id); $docsAfterDelete = $page->getBankDocumentsProperty(); expect($docsAfterDelete->count())->toBe($initialCount); });