fix(anagrafica): Anagrafica Unica canonica filtering, audit history filter, and protected CRUD guards (task-d095b0e23b)

This commit is contained in:
michele 2026-08-26 18:15:18 +02:00
parent 4774ef5751
commit ae8f54dc36
8 changed files with 230 additions and 2 deletions

View File

@ -148,6 +148,7 @@ public function handle(): int
'cognome' => 'Seed',
'user_id' => $userId,
'codice_univoco' => 'ASEED001',
'codice_amministratore' => 'ASEED001',
'created_at' => now(),
'updated_at' => now(),
]);

View File

@ -128,7 +128,39 @@ public function table(Table $table): Table
TextColumn::make('telefono_ufficio')->label('Tel.')->searchable()->toggleable(),
TextColumn::make('telefono_cellulare')->label('Cell.')->searchable()->toggleable(),
TextColumn::make('telefono_casa')->label('Tel. casa')->searchable()->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('stato')->label('Stato')->sortable()->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('stato')->label('Stato')
->sortable()
->badge()
->color(fn (?string $state): string => match ($state) {
'attivo' => 'success',
'inattivo', 'archiviato_duplicato', 'inattivo_duplicato' => 'warning',
default => 'gray',
})
->toggleable(),
TextColumn::make('note')->label('Note / Audit')->wrap()->toggleable(isToggledHiddenByDefault: true),
])
->filters([
\Filament\Tables\Filters\SelectFilter::make('vista_stato')
->label('Vista Anagrafica')
->options([
'attivi' => 'Contatti attivi (Canonica)',
'storico' => 'Storico & bonifiche audit',
'tutti' => 'Tutti i contatti',
])
->default('attivi')
->query(function (Builder $query, array $data): Builder {
$value = $data['value'] ?? 'attivi';
if ($value === 'attivi' || empty($value)) {
return $query->where(function (Builder $q): void {
$q->whereNull('rubrica_universale.stato')
->orWhereNotIn('rubrica_universale.stato', ['inattivo', 'inattivo_duplicato', 'archiviato_duplicato']);
});
}
if ($value === 'storico') {
return $query->whereIn('rubrica_universale.stato', ['inattivo', 'inattivo_duplicato', 'archiviato_duplicato']);
}
return $query;
}),
])
->actions([
Action::make('modifica')
@ -180,6 +212,25 @@ public function table(Table $table): Table
->label('Apri scheda')
->icon('heroicon-o-identification')
->url(fn(RubricaUniversale $record) => RubricaUniversaleScheda::getUrl(['record' => $record->id], panel: 'admin-filament')),
Action::make('elimina')
->label('Elimina')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->modalHeading('Conferma eliminazione contatto')
->modalDescription('Cancellazione protetta: la rimozione fisica verrà bloccata se il contatto ha relazioni attive o storiche.')
->action(function (RubricaUniversale $record): void {
try {
$record->delete();
} catch (\Throwable $e) {
\Filament\Notifications\Notification::make()
->title('Operazione bloccata')
->body($e->getMessage())
->danger()
->send();
}
}),
]);
}

View File

@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Relations\HasMany; // Aggiunto per condomini()
use Illuminate\Database\Eloquent\SoftDeletes; // Aggiunto per soft deletes
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

View File

@ -333,6 +333,15 @@ protected static function boot()
}
}
});
static::deleting(function ($persona) {
$hasRelazioni = \Illuminate\Support\Facades\DB::table('persone_unita_relazioni')->where('persona_id', $persona->id)->exists();
$hasRappresentanti = \Illuminate\Support\Facades\DB::table('rappresentanti_legali')->where('persona_id', $persona->id)->orWhere('societa_id', $persona->id)->exists();
if ($hasRelazioni || $hasRappresentanti) {
throw new \Exception("Cancellazione fisica bloccata: la persona ha relazioni attive o storiche con unità immobiliari o società.");
}
});
}
/**

View File

@ -164,4 +164,24 @@ public function scopeRicerca($query, $termine)
->orWhere('telefono_cellulare', 'like', "%{$termine}%");
});
}
protected static function boot()
{
parent::boot();
static::deleting(function ($rubrica) {
$isForce = method_exists($rubrica, 'isForceDeleting') ? $rubrica->isForceDeleting() : true;
if ($isForce) {
$hasRoles = \Illuminate\Support\Facades\DB::table('rubrica_ruoli')->where('rubrica_id', $rubrica->id)->exists();
$hasFornitori = \Illuminate\Support\Facades\DB::table('fornitori')->where('rubrica_id', $rubrica->id)->exists();
$hasStabili = \Illuminate\Support\Facades\Schema::hasColumn('stabili', 'rubrica_id') && \Illuminate\Support\Facades\DB::table('stabili')->where('rubrica_id', $rubrica->id)->exists();
$hasDatiBancari = \Illuminate\Support\Facades\DB::table('dati_bancari')->where('contatto_id', $rubrica->id)->exists();
if ($hasRoles || $hasFornitori || $hasStabili || $hasDatiBancari) {
throw new \Exception("Cancellazione fisica bloccata: il contatto ha relazioni attive o storiche (ruoli, stabili, fornitori o dati bancari).");
}
}
});
}
}

View File

@ -40,6 +40,7 @@ public function panel(Panel $panel): Panel
->brandName('NetGescon')
->spa()
->login(\App\Filament\Auth\Login::class)
->passwordReset()
->registration()
->colors([
'primary' => Color::Amber,

View File

@ -17,7 +17,7 @@ public function up(): void
return;
}
DB::statement("ALTER TABLE `dati_bancari` MODIFY COLUMN `tipo_conto` ENUM('corrente','deposito','risparmio','cassa') NOT NULL DEFAULT 'corrente'");
DB::statement("ALTER TABLE `dati_bancari` MODIFY COLUMN `tipo_conto` ENUM('corrente','deposito','risparmio','cassa','posta','postale') NOT NULL DEFAULT 'corrente'");
}
/**

View File

@ -0,0 +1,145 @@
<?php
use App\Livewire\Gescon\RubricaUniversaleTable;
use App\Models\Persona;
use App\Models\PersonaUnitaRelazione;
use App\Models\RubricaUniversale;
use App\Models\User;
use App\Models\Stabile;
use App\Support\StabileContext;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
beforeEach(function () {
$user = User::first();
if (! $user) {
$user = User::factory()->create(['email' => 'cecilia.tordini@gmail.com']);
}
try {
\Spatie\Permission\Models\Role::firstOrCreate(['name' => 'amministratore', 'guard_name' => 'web']);
$user->assignRole('amministratore');
} catch (\Throwable $e) {}
$adminId = DB::table('amministratori')->insertGetId([
'user_id' => $user->id,
'nome' => 'Admin Test',
'cognome' => 'Test',
'codice_amministratore' => 'ADM00001',
'created_at' => now(),
'updated_at' => now(),
]);
$stabile = Stabile::firstOrCreate(['codice_stabile' => '0021'], [
'denominazione' => 'SUPERCONDOMINIO MILIZIE 3',
'amministratore_id' => $adminId,
'indirizzo' => 'Viale delle Milizie 3',
'cap' => '00192',
'citta' => 'Roma',
'provincia' => 'RM',
]);
if ($stabile->amministratore_id !== $adminId) {
$stabile->amministratore_id = $adminId;
$stabile->save();
}
$canonical = RubricaUniversale::where('codice_fiscale', 'BNDDNL86M58H224O')->first();
if (! $canonical) {
RubricaUniversale::create([
'amministratore_id' => $adminId,
'codice_univoco' => '000000JC',
'nome' => 'DANIELA',
'cognome' => 'BENEDETTO',
'codice_fiscale' => 'BNDDNL86M58H224O',
'stato' => 'attivo',
'note' => '[MDB_0021] CF Validato',
]);
RubricaUniversale::create([
'amministratore_id' => $adminId,
'codice_univoco' => '000000I2',
'nome' => 'DANIELA',
'cognome' => 'BENEDETTO',
'stato' => 'inattivo',
'note' => '[ARCHIVIATO_DUPLICATO] Soft archived verso Rubrica 697',
]);
RubricaUniversale::create([
'amministratore_id' => $adminId,
'codice_univoco' => '000000I3',
'nome' => 'DANIELA',
'cognome' => 'BENEDETTO',
'stato' => 'inattivo',
'note' => '[ARCHIVIATO_DUPLICATO] Soft archived verso Rubrica 697',
]);
} else {
$canonical->amministratore_id = $adminId;
$canonical->save();
RubricaUniversale::where('cognome', 'LIKE', '%BENEDETTO%')->update(['amministratore_id' => $adminId]);
}
});
test('anagrafica unica default view shows only active canonical contacts', function () {
$user = User::first();
$stabile = Stabile::where('codice_stabile', '0021')->first();
Auth::login($user);
StabileContext::setFromStabile($stabile, $user);
$canonical = RubricaUniversale::where('codice_fiscale', 'BNDDNL86M58H224O')->first();
expect($canonical)->not->toBeNull();
expect($canonical->stato)->toBe('attivo');
Livewire::test(RubricaUniversaleTable::class)
->assertSee('DANIELA')
->assertSee('BENEDETTO')
->assertDontSee('000000I2');
});
test('anagrafica unica storico filter shows archived duplicate records', function () {
$user = User::first();
$stabile = Stabile::where('codice_stabile', '0021')->first();
Auth::login($user);
StabileContext::setFromStabile($stabile, $user);
Livewire::test(RubricaUniversaleTable::class)
->filterTable('vista_stato', 'storico')
->assertSee('BENEDETTO');
});
test('physical delete blocked when relations exist', function () {
$persona = Persona::where('codice_fiscale', 'BNDDNL86M58H224O')->first();
if (! $persona) {
$persona = Persona::create([
'cognome' => 'BENEDETTO',
'nome' => 'DANIELA',
'codice_fiscale' => 'BNDDNL86M58H224O',
'attivo' => true,
]);
PersonaUnitaRelazione::create([
'persona_id' => $persona->id,
'unita_id' => 1,
'tipo_relazione' => 'proprietario',
'data_inizio' => now(),
'attivo' => true,
]);
} else {
if (! PersonaUnitaRelazione::where('persona_id', $persona->id)->exists()) {
PersonaUnitaRelazione::create([
'persona_id' => $persona->id,
'unita_id' => 1,
'tipo_relazione' => 'proprietario',
'data_inizio' => now(),
'attivo' => true,
]);
}
}
$hasRelations = PersonaUnitaRelazione::where('persona_id', $persona->id)->exists();
expect($hasRelations)->toBeTrue();
expect(function () use ($persona) {
$persona->delete();
})->toThrow(\Exception::class, 'Cancellazione fisica bloccata');
});