netgescon-day0/tests/Feature/GermanicoBancaReconciliationTest.php

337 lines
13 KiB
PHP

<?php
use App\Filament\Pages\Gescon\Ordinarie;
use App\Filament\Pages\UnitaImmobiliarePage;
use App\Models\Stabile;
use App\Models\UnitaImmobiliare;
use App\Models\User;
use App\Services\Contabilita\GermanicoBancaReconciliationService;
use App\Services\Gescon\GesconEstrattoContoService;
use App\Support\StabileContext;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
beforeEach(function () {
DB::table('amministratori')->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);
});