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(), ]); $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'); });