feat(unita-ui): compattezza anagrafiche, tabella recapiti multicanale dinamica e pulizia RDP
This commit is contained in:
parent
8eddca0b7f
commit
6f20d81a67
|
|
@ -85,6 +85,29 @@ public function handle(): int
|
||||||
$this->info('Anagrafiche corrotte rimaste bonificate.');
|
$this->info('Anagrafiche corrotte rimaste bonificate.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3b. Pulizia comproprietari incrociati orfani su Unità 233 (0013-A-1)
|
||||||
|
$unit233 = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'A')->where('interno', '1')->first();
|
||||||
|
if ($unit233) {
|
||||||
|
DB::table('unita_anagrafica_periodo')
|
||||||
|
->where('unita_immobiliare_id', $unit233->id)
|
||||||
|
->where('ruolo_occupazione', 'comproprietario')
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3c. Pulizia assegnazioni errate RDP S.R.L. (consentita solo su B-3 e B-4)
|
||||||
|
if (Schema::hasTable('unita_immobiliare_nominativi')) {
|
||||||
|
$unitB3B4Ids = UnitaImmobiliare::where('stabile_id', $stabile->id)
|
||||||
|
->where('scala', 'B')
|
||||||
|
->whereIn('interno', ['3', '4'])
|
||||||
|
->pluck('id')
|
||||||
|
->all();
|
||||||
|
DB::table('unita_immobiliare_nominativi')
|
||||||
|
->where('stabile_id', $stabile->id)
|
||||||
|
->where('nominativo', 'like', '%RDP%')
|
||||||
|
->whereNotIn('unita_immobiliare_id', $unitB3B4Ids)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Bonifica e allineamento Unità NGGC 163 (ID 267)
|
// 4. Bonifica e allineamento Unità NGGC 163 (ID 267)
|
||||||
$unit163 = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'NGGC')->where('interno', '163')->first();
|
$unit163 = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'NGGC')->where('interno', '163')->first();
|
||||||
if ($unit163) {
|
if ($unit163) {
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,200 @@ public function resolveHumanYearLabel(string $yearCode, string $codStabile): str
|
||||||
return 'Anno ' . (is_numeric($yearCode) ? (int) $yearCode : $yearCode);
|
return 'Anno ' . (is_numeric($yearCode) ? (int) $yearCode : $yearCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRecapitiMulticanaleTableProperty(): array
|
||||||
|
{
|
||||||
|
if (! $this->unita) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableRows = [];
|
||||||
|
|
||||||
|
// 1. Dati da Anagrafiche e UAP
|
||||||
|
$activeEntities = array_merge(
|
||||||
|
$this->relazioniPerTipo['proprietari'] ?? [],
|
||||||
|
$this->relazioniPerTipo['inquilini'] ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($activeEntities as $entity) {
|
||||||
|
$nome = trim((string) ($entity['nome'] ?? ''));
|
||||||
|
$ruolo = (string) ($entity['tipo'] ?? ($entity['tipo_relazione'] ?? 'Condomino'));
|
||||||
|
if ($nome === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$anagId = (int) ($entity['persona_id'] ?? ($entity['soggetto_id'] ?? 0));
|
||||||
|
$anag = $anagId > 0 ? DB::table('anagrafiche')->find($anagId) : null;
|
||||||
|
if (! $anag) {
|
||||||
|
$anag = DB::table('anagrafiche')
|
||||||
|
->where(function ($q) use ($nome, $entity) {
|
||||||
|
$q->where('cognome', 'like', '%' . $nome . '%')
|
||||||
|
->orWhere('nome', 'like', '%' . $nome . '%')
|
||||||
|
->orWhere('ragione_sociale', 'like', '%' . $nome . '%');
|
||||||
|
if (! empty($entity['codice_fiscale'])) {
|
||||||
|
$q->orWhere('codice_fiscale', trim((string) $entity['codice_fiscale']));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($anag) {
|
||||||
|
if (! empty($anag->email)) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Email',
|
||||||
|
'icon' => '✉️',
|
||||||
|
'color' => 'bg-blue-50 text-blue-700 border-blue-200',
|
||||||
|
'etichetta' => 'Principale',
|
||||||
|
'soggetto' => $nome,
|
||||||
|
'ruolo' => $ruolo,
|
||||||
|
'valore' => $anag->email,
|
||||||
|
'action_url' => 'mailto:' . $anag->email,
|
||||||
|
'is_principal' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($anag->pec)) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Email PEC',
|
||||||
|
'icon' => '🛡️',
|
||||||
|
'color' => 'bg-emerald-50 text-emerald-700 border-emerald-200',
|
||||||
|
'etichetta' => 'PEC Ufficiale',
|
||||||
|
'soggetto' => $nome,
|
||||||
|
'ruolo' => $ruolo,
|
||||||
|
'valore' => $anag->pec,
|
||||||
|
'action_url' => 'mailto:' . $anag->pec,
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($anag->telefono)) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Cellulare / Tel',
|
||||||
|
'icon' => '📱',
|
||||||
|
'color' => 'bg-amber-50 text-amber-700 border-amber-200',
|
||||||
|
'etichetta' => 'Personale',
|
||||||
|
'soggetto' => $nome,
|
||||||
|
'ruolo' => $ruolo,
|
||||||
|
'valore' => $anag->telefono,
|
||||||
|
'action_url' => 'tel:' . $anag->telefono,
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($anag->indirizzo)) {
|
||||||
|
$indFull = trim($anag->indirizzo . ' ' . ($anag->cap ?? '') . ' ' . ($anag->citta ?? '') . ' ' . ($anag->provincia ? '(' . $anag->provincia . ')' : ''));
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Indirizzo',
|
||||||
|
'icon' => '📍',
|
||||||
|
'color' => 'bg-slate-50 text-slate-700 border-slate-200',
|
||||||
|
'etichetta' => 'Residenza / Sede',
|
||||||
|
'soggetto' => $nome,
|
||||||
|
'ruolo' => $ruolo,
|
||||||
|
'valore' => $indFull,
|
||||||
|
'action_url' => null,
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dati da Legacy condomin_mirror
|
||||||
|
$legacyRow = $this->getLegacyCondominRow();
|
||||||
|
if ($legacyRow && is_array($legacyRow->legacy_payload ?? null)) {
|
||||||
|
$p = $legacyRow->legacy_payload;
|
||||||
|
$condName = trim((string) ($legacyRow->nom_cond ?? 'Condomino'));
|
||||||
|
$inqName = trim((string) ($legacyRow->inquil_nome ?? ($legacyRow->inquilino ?? 'Inquilino')));
|
||||||
|
|
||||||
|
if (! empty($p['E_mail_condomino']) && ! $this->hasContactValue($tableRows, (string) $p['E_mail_condomino'])) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Email',
|
||||||
|
'icon' => '✉️',
|
||||||
|
'color' => 'bg-blue-50 text-blue-700 border-blue-200',
|
||||||
|
'etichetta' => 'Email Condomino',
|
||||||
|
'soggetto' => $condName,
|
||||||
|
'ruolo' => 'Condomino',
|
||||||
|
'valore' => trim((string) $p['E_mail_condomino']),
|
||||||
|
'action_url' => 'mailto:' . trim((string) $p['E_mail_condomino']),
|
||||||
|
'is_principal' => count($tableRows) === 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($p['PEC_condomino']) && ! $this->hasContactValue($tableRows, (string) $p['PEC_condomino'])) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Email PEC',
|
||||||
|
'icon' => '🛡️',
|
||||||
|
'color' => 'bg-emerald-50 text-emerald-700 border-emerald-200',
|
||||||
|
'etichetta' => 'PEC Condomino',
|
||||||
|
'soggetto' => $condName,
|
||||||
|
'ruolo' => 'Condomino',
|
||||||
|
'valore' => trim((string) $p['PEC_condomino']),
|
||||||
|
'action_url' => 'mailto:' . trim((string) $p['PEC_condomino']),
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($p['Cell_cond']) && ! $this->hasContactValue($tableRows, (string) $p['Cell_cond'])) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Cellulare',
|
||||||
|
'icon' => '📱',
|
||||||
|
'color' => 'bg-amber-50 text-amber-700 border-amber-200',
|
||||||
|
'etichetta' => 'Cellulare Condomino',
|
||||||
|
'soggetto' => $condName,
|
||||||
|
'ruolo' => 'Condomino',
|
||||||
|
'valore' => trim((string) $p['Cell_cond']),
|
||||||
|
'action_url' => 'tel:' . trim((string) $p['Cell_cond']),
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($p['tel1']) && ! $this->hasContactValue($tableRows, (string) $p['tel1'])) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Telefono Fisso',
|
||||||
|
'icon' => '☎️',
|
||||||
|
'color' => 'bg-purple-50 text-purple-700 border-purple-200',
|
||||||
|
'etichetta' => 'Fisso Condomino',
|
||||||
|
'soggetto' => $condName,
|
||||||
|
'ruolo' => 'Condomino',
|
||||||
|
'valore' => trim((string) $p['tel1']),
|
||||||
|
'action_url' => 'tel:' . trim((string) $p['tel1']),
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($p['E_mail_inquilino']) && ! $this->hasContactValue($tableRows, (string) $p['E_mail_inquilino'])) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Email',
|
||||||
|
'icon' => '✉️',
|
||||||
|
'color' => 'bg-blue-50 text-blue-700 border-blue-200',
|
||||||
|
'etichetta' => 'Email Inquilino',
|
||||||
|
'soggetto' => $inqName,
|
||||||
|
'ruolo' => 'Inquilino',
|
||||||
|
'valore' => trim((string) $p['E_mail_inquilino']),
|
||||||
|
'action_url' => 'mailto:' . trim((string) $p['E_mail_inquilino']),
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($p['Cell_inq']) && ! $this->hasContactValue($tableRows, (string) $p['Cell_inq'])) {
|
||||||
|
$tableRows[] = [
|
||||||
|
'canale' => 'Cellulare',
|
||||||
|
'icon' => '📱',
|
||||||
|
'color' => 'bg-amber-50 text-amber-700 border-amber-200',
|
||||||
|
'etichetta' => 'Cellulare Inquilino',
|
||||||
|
'soggetto' => $inqName,
|
||||||
|
'ruolo' => 'Inquilino',
|
||||||
|
'valore' => trim((string) $p['Cell_inq']),
|
||||||
|
'action_url' => 'tel:' . trim((string) $p['Cell_inq']),
|
||||||
|
'is_principal' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tableRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasContactValue(array $rows, string $value): bool
|
||||||
|
{
|
||||||
|
$vTrim = trim(mb_strtolower($value));
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
if (trim(mb_strtolower((string) ($r['valore'] ?? ''))) === $vTrim) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function getTimelineSubentriCompletaProperty(): array
|
public function getTimelineSubentriCompletaProperty(): array
|
||||||
{
|
{
|
||||||
if (! $this->unita || ! $this->unita->stabile) {
|
if (! $this->unita || ! $this->unita->stabile) {
|
||||||
|
|
|
||||||
|
|
@ -215,178 +215,186 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
|
||||||
</div>
|
</div>
|
||||||
</x-filament::section>
|
</x-filament::section>
|
||||||
|
|
||||||
<div class="grid gap-4 lg:grid-cols-3">
|
{{-- 1. SCHEDE ANAGRAFICHE SOGGETTI ATTIVI & IDENTIFICAZIONE COMPATTA --}}
|
||||||
<x-filament::section class="lg:col-span-2">
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<x-slot name="heading">Indicatori principali</x-slot>
|
{{-- Card Condomino / Proprietario Attivo --}}
|
||||||
<x-slot name="description">Superficie, millesimi, percentuale, valore</x-slot>
|
<div class="rounded-xl border border-indigo-100 bg-white p-4 shadow-xs">
|
||||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
<div class="flex items-center justify-between border-b border-indigo-50 pb-2 mb-3">
|
||||||
@foreach([
|
<div class="flex items-center gap-2">
|
||||||
['Superficie', ($unita->superficie_commerciale ?? 0) . ' m²'],
|
<span class="inline-flex h-7 w-7 items-center justify-center rounded-lg bg-indigo-100 text-sm font-bold text-indigo-800">🏢</span>
|
||||||
['Millesimi', $unita->millesimi_proprieta ?? $unita->millesimi_generali ?? 0],
|
<span class="text-xs font-bold uppercase tracking-wider text-indigo-900">Condomino (Proprietario)</span>
|
||||||
['Percentuale', ($unita->millesimi_proprieta ?? $unita->millesimi_generali) ? number_format((($unita->millesimi_proprieta ?? $unita->millesimi_generali)/1000)*100,2) . '%' : '0%'],
|
</div>
|
||||||
['Valore stimato', ($unita->valore_commerciale_stimato ?? 0) . ' €'],
|
<span class="rounded-full bg-emerald-50 border border-emerald-200 px-2 py-0.5 text-[10px] font-bold text-emerald-700">🟢 In Carica</span>
|
||||||
] as [$label, $value])
|
|
||||||
<x-filament::section class="p-4!">
|
|
||||||
<div class="text-xs text-gray-500">{{ $label }}</div>
|
|
||||||
<div class="text-lg font-semibold text-gray-900">{{ $value }}</div>
|
|
||||||
</x-filament::section>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
</div>
|
||||||
</x-filament::section>
|
|
||||||
|
|
||||||
<x-filament::section>
|
<div class="space-y-1.5">
|
||||||
<x-slot name="heading">Identificazione</x-slot>
|
<div class="text-base font-bold text-gray-900 leading-snug">
|
||||||
<x-slot name="description">Tipologia e stato</x-slot>
|
{{ $condominoNome !== '' ? $condominoNome : '—' }}
|
||||||
<div class="space-y-2">
|
</div>
|
||||||
@foreach([
|
<div class="text-xs font-mono text-gray-600">
|
||||||
['Tipologia', $unita->tipo_unita ?? 'ND'],
|
CF: <strong>{{ $condominoCf !== '' ? $condominoCf : '—' }}</strong>
|
||||||
['Stato occupazione', $unita->stato_occupazione ?? 'ND'],
|
</div>
|
||||||
['Utilizzo', $unita->utilizzo_attuale ?? 'ND'],
|
<div class="flex items-center gap-2 text-xs text-gray-600 pt-1">
|
||||||
['Codice stabile', $unita->stabile?->codice_operatore ?? $unita->stabile?->codice_stabile ?? 'ND'],
|
<span class="rounded bg-indigo-50 text-indigo-700 font-semibold px-2 py-0.5">
|
||||||
] as [$label, $value])
|
Quota: {{ $relazioniPerTipo['proprietari'][0]['quota_label'] ?? '100,00' }}%
|
||||||
<x-filament::section class="p-3! bg-gray-50">
|
</span>
|
||||||
<div class="text-xs text-gray-500">{{ $label }}</div>
|
@if($comproprietariCount > 0)
|
||||||
<div class="text-sm font-semibold text-gray-900">{{ $value }}</div>
|
<span class="rounded bg-amber-50 text-amber-800 font-semibold px-2 py-0.5">
|
||||||
</x-filament::section>
|
+{{ $comproprietariCount }} comproprietari
|
||||||
@endforeach
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 pt-1">
|
||||||
|
Validità: <strong class="text-gray-700">{{ $relazioniPerTipo['proprietari'][0]['data_inizio'] ?? 'Gestione attiva' }}</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-filament::section>
|
</div>
|
||||||
|
|
||||||
|
{{-- Card Inquilino / Conduttore Attivo --}}
|
||||||
|
<div class="rounded-xl border border-sky-100 bg-white p-4 shadow-xs">
|
||||||
|
<div class="flex items-center justify-between border-b border-sky-50 pb-2 mb-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="inline-flex h-7 w-7 items-center justify-center rounded-lg bg-sky-100 text-sm font-bold text-sky-800">👤</span>
|
||||||
|
<span class="text-xs font-bold uppercase tracking-wider text-sky-900">Inquilino (Conduttore)</span>
|
||||||
|
</div>
|
||||||
|
@if($hasInquilino)
|
||||||
|
<span class="rounded-full bg-sky-50 border border-sky-200 px-2 py-0.5 text-[10px] font-bold text-sky-700">Locazione attiva</span>
|
||||||
|
@else
|
||||||
|
<span class="rounded-full bg-gray-100 text-gray-500 px-2 py-0.5 text-[10px] font-medium">Nessun inquilino</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<div class="text-base font-bold text-gray-900 leading-snug">
|
||||||
|
{{ $inquilinoNome !== '' ? $inquilinoNome : 'Nessun inquilino registrato' }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs font-mono text-gray-600">
|
||||||
|
CF: <strong>{{ $relazioniPerTipo['inquilini'][0]['codice_fiscale'] ?? '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
@if($hasInquilino)
|
||||||
|
<div class="flex items-center gap-2 text-xs text-gray-600 pt-1">
|
||||||
|
<span class="rounded bg-sky-50 text-sky-700 font-semibold px-2 py-0.5">
|
||||||
|
Spese: {{ $relazioniPerTipo['inquilini'][0]['quota_label'] ?? '100,00' }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 pt-1">
|
||||||
|
Subentro: <strong class="text-gray-700">{{ $relazioniPerTipo['inquilini'][0]['data_inizio'] ?? '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="text-xs text-gray-400 italic pt-2">
|
||||||
|
L'unità risulta occupata direttamente dal proprietario.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Card Dati Catastali & Identificazione Immobile --}}
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-slate-50/70 p-4 shadow-xs">
|
||||||
|
<div class="flex items-center justify-between border-b border-slate-200 pb-2 mb-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="inline-flex h-7 w-7 items-center justify-center rounded-lg bg-slate-200 text-sm font-bold text-slate-800">🏛️</span>
|
||||||
|
<span class="text-xs font-bold uppercase tracking-wider text-slate-700">Dati Catasto & Immobile</span>
|
||||||
|
</div>
|
||||||
|
<span class="font-mono text-xs font-bold text-indigo-700">Sub. {{ $unita->subalterno ?: '—' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1.5 text-xs">
|
||||||
|
<div class="flex items-center justify-between text-gray-600">
|
||||||
|
<span>Tipologia:</span>
|
||||||
|
<strong class="text-gray-900 capitalize">{{ $unita->tipo_unita ?? 'Abitazione' }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-gray-600">
|
||||||
|
<span>Categoria & Rendita:</span>
|
||||||
|
<strong class="text-gray-900">{{ $unita->categoria_catastale ?: '—' }} · € {{ number_format((float)($unita->rendita_catastale ?? 0), 2, ',', '.') }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-gray-600">
|
||||||
|
<span>Foglio / Particella:</span>
|
||||||
|
<strong class="text-gray-900">Fg. {{ $unita->foglio ?: ($unita->stabile?->foglio ?: '—') }} / Part. {{ $unita->particella ?: ($unita->stabile?->particella_catasto ?: '—') }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-gray-600">
|
||||||
|
<span>Posizione fabbricato:</span>
|
||||||
|
<strong class="text-gray-900">Scala {{ $unita->scala ?: '—' }} · Int. {{ $unita->interno ?: '—' }} · Piano {{ $unita->piano ?: '—' }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- 2. SEZIONE RECAPITI MULTICANALE DINAMICI (MATRICE TABELLARE COMPATTA) --}}
|
||||||
|
@php
|
||||||
|
$multiRecapiti = $this->getRecapitiMulticanaleTableProperty();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="rounded-xl border border-gray-200 bg-white shadow-xs overflow-hidden mt-4">
|
||||||
<x-filament::section>
|
<div class="flex items-center justify-between bg-gray-50/80 px-4 py-3 border-b border-gray-200">
|
||||||
<x-slot name="heading">Persone collegate</x-slot>
|
<div class="flex items-center gap-2">
|
||||||
<x-slot name="description">Proprietari, inquilini, altri</x-slot>
|
<span class="text-base">📱</span>
|
||||||
<div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
<div>
|
||||||
@foreach([
|
<div class="text-sm font-bold text-gray-900">Recapiti Multicanale Dinamici dell'Unità</div>
|
||||||
'inquilini' => ['label' => 'Inquilini', 'color' => 'text-sky-700'],
|
<div class="text-[11px] text-gray-500">Email, PEC, cellulari, telefoni e recapiti postali dei soggetti collegati</div>
|
||||||
'altri' => ['label' => 'Altri soggetti', 'color' => 'text-gray-700'],
|
|
||||||
] as $key => $cfg)
|
|
||||||
@php $relazioni = $relazioniPerTipo[$key] ?? []; @endphp
|
|
||||||
<x-filament::section class="bg-gray-50">
|
|
||||||
<div class="mb-2 text-sm font-semibold {{ $cfg['color'] }}">{{ $cfg['label'] }} ({{ count($relazioni) }})</div>
|
|
||||||
@if(empty($relazioni))
|
|
||||||
<div class="text-sm text-gray-500">Nessun soggetto.</div>
|
|
||||||
@else
|
|
||||||
<div class="space-y-2">
|
|
||||||
@foreach($relazioni as $relazione)
|
|
||||||
@php
|
|
||||||
$tipoRawRelazione = strtolower(trim((string) ($relazione['tipo_raw'] ?? '')));
|
|
||||||
$roleBadge = null;
|
|
||||||
if ($key === 'proprietari') {
|
|
||||||
$roleBadge = $tipoRawRelazione === 'comproprietario'
|
|
||||||
? 'Comproprietario'
|
|
||||||
: 'Condomino di riferimento';
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
<x-filament::section class="p-3!">
|
|
||||||
<div class="flex items-start justify-between gap-2">
|
|
||||||
<div class="text-sm font-semibold text-gray-900">{{ $relazione['nome'] }}</div>
|
|
||||||
@if($roleBadge)
|
|
||||||
<span class="rounded-full border border-gray-200 bg-white px-2 py-0.5 text-[11px] font-semibold text-gray-600">{{ $roleBadge }}</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div class="text-xs text-gray-500">CF: {{ $relazione['codice_fiscale'] ?? '—' }}</div>
|
|
||||||
<div class="text-xs text-gray-500">Quota: {{ $relazione['quota_label'] ?? '—' }}%</div>
|
|
||||||
@php
|
|
||||||
$dal = $relazione['data_inizio'] ?? null;
|
|
||||||
$al = $relazione['data_fine'] ?? null;
|
|
||||||
@endphp
|
|
||||||
<div class="text-xs text-gray-500">
|
|
||||||
Periodo:
|
|
||||||
@if($dal || $al)
|
|
||||||
@if($dal) Da {{ $dal }} @endif
|
|
||||||
@if($al) · fino a {{ $al }} @endif
|
|
||||||
@else
|
|
||||||
Da sempre
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@if(!empty($relazione['ruolo_rate']))
|
|
||||||
<div class="text-xs text-gray-500">Ruolo rate: ({{ $relazione['ruolo_rate'] }})</div>
|
|
||||||
@endif
|
|
||||||
</x-filament::section>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</x-filament::section>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
<div class="text-sm font-semibold text-gray-900 mb-2">Recapiti email per servizio</div>
|
|
||||||
@if(empty($recapitiServizio))
|
|
||||||
<div class="text-sm text-gray-500">Nessun recapito email disponibile.</div>
|
|
||||||
@else
|
|
||||||
<div class="grid gap-3 lg:grid-cols-2">
|
|
||||||
@foreach($recapitiServizio as $service)
|
|
||||||
<div class="rounded-xl border bg-gray-50 p-3">
|
|
||||||
<div class="text-sm font-semibold text-gray-900">{{ $service['label'] ?? 'Servizio' }}</div>
|
|
||||||
@if(empty($service['rows']))
|
|
||||||
<div class="mt-2 text-sm text-gray-500">Nessun recapito risolto.</div>
|
|
||||||
@else
|
|
||||||
<div class="mt-2 space-y-2">
|
|
||||||
@foreach($service['rows'] as $recipient)
|
|
||||||
<div class="rounded-lg border bg-white p-3">
|
|
||||||
<div class="text-sm font-semibold text-gray-900">{{ $recipient['email'] ?? '—' }}</div>
|
|
||||||
<div class="text-xs text-gray-500">{{ $recipient['persona'] ?: 'Recapito unità' }} · {{ $recipient['sourceLabel'] ?? 'Anagrafica persona' }}</div>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 border-t pt-4">
|
|
||||||
<div class="flex items-center gap-2 mb-3">
|
|
||||||
<x-filament::icon icon="heroicon-o-clock" class="h-5 w-5 text-amber-600" />
|
|
||||||
<span class="text-base font-bold text-gray-900">Timeline Subentri e Gestioni Anteriore</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 mb-3">Cronistoria titolari ed avvicendamenti storici per questa unità immobiliare tra le diverse gestioni</div>
|
<span class="rounded-full bg-indigo-50 border border-indigo-200 px-2.5 py-0.5 text-xs font-bold text-indigo-700">
|
||||||
|
{{ count($multiRecapiti) }} canali attivi
|
||||||
@if(empty($nominativiStorici))
|
</span>
|
||||||
<div class="rounded-lg border border-dashed border-gray-200 p-4 text-center text-sm text-gray-500">Nessun avvicendamento o storico subentri registrato per questa unità.</div>
|
|
||||||
@else
|
|
||||||
<div class="space-y-3">
|
|
||||||
@foreach($nominativiStorici as $item)
|
|
||||||
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-xs transition hover:border-amber-300">
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<span class="rounded-md bg-amber-50 border border-amber-200 px-2.5 py-1 text-xs font-bold text-amber-800">
|
|
||||||
{{ $item['periodo'] ?? 'Da sempre' }}
|
|
||||||
</span>
|
|
||||||
<span class="text-base font-bold text-gray-900">
|
|
||||||
{{ $item['nominativo'] ?? $item['nome_completo'] ?? $item['nome'] ?? '—' }}
|
|
||||||
</span>
|
|
||||||
@if(!empty($item['ruolo']))
|
|
||||||
<span class="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-semibold text-gray-700">
|
|
||||||
{{ $item['ruolo'] }}
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
@if(!empty($item['percentuale']))
|
|
||||||
<span class="rounded-full bg-blue-50 text-blue-700 border border-blue-200 px-2.5 py-0.5 text-xs font-semibold">
|
|
||||||
Quota: {{ $item['percentuale'] }}
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@if(!empty($item['fonte']))
|
|
||||||
<div class="text-xs text-gray-500 font-medium">Fonte: {{ $item['fonte'] }}</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@if(!empty($item['detail']))
|
|
||||||
<div class="mt-2 text-xs font-medium text-gray-600 bg-gray-50 rounded-lg p-2.5">
|
|
||||||
{{ $item['detail'] }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</x-filament::section>
|
|
||||||
|
@if(empty($multiRecapiti))
|
||||||
|
<div class="p-6 text-center text-xs text-gray-500">
|
||||||
|
Nessun recapito multicanale o contatto registrato per i soggetti di questa unità.
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left text-xs text-gray-700 divide-y divide-gray-100">
|
||||||
|
<thead class="bg-gray-50 text-[11px] font-bold uppercase tracking-wider text-gray-500">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2.5">Canale</th>
|
||||||
|
<th class="px-4 py-2.5">Etichetta / Uso</th>
|
||||||
|
<th class="px-4 py-2.5">Soggetto Collegato</th>
|
||||||
|
<th class="px-4 py-2.5">Valore Contatto</th>
|
||||||
|
<th class="px-4 py-2.5 text-center">Stato</th>
|
||||||
|
<th class="px-4 py-2.5 text-right">Azioni Rapide</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-100 bg-white">
|
||||||
|
@foreach($multiRecapiti as $rec)
|
||||||
|
<tr class="hover:bg-indigo-50/30 transition">
|
||||||
|
<td class="px-4 py-2.5 whitespace-nowrap">
|
||||||
|
<span class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 font-semibold text-[11px] {{ $rec['color'] }}">
|
||||||
|
<span>{{ $rec['icon'] }}</span>
|
||||||
|
<span>{{ $rec['canale'] }}</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2.5 font-medium text-gray-800 whitespace-nowrap">
|
||||||
|
{{ $rec['etichetta'] }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2.5 whitespace-nowrap">
|
||||||
|
<span class="font-bold text-gray-900">{{ $rec['soggetto'] }}</span>
|
||||||
|
<span class="text-gray-400 font-normal">({{ $rec['ruolo'] }})</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2.5 font-mono text-gray-900 font-medium">
|
||||||
|
{{ $rec['valore'] }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2.5 text-center whitespace-nowrap">
|
||||||
|
@if($rec['is_principal'])
|
||||||
|
<span class="rounded bg-emerald-100 text-emerald-800 px-1.5 py-0.5 text-[10px] font-bold">★ Principale</span>
|
||||||
|
@else
|
||||||
|
<span class="text-gray-400 text-[11px]">—</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2.5 text-right whitespace-nowrap">
|
||||||
|
@if(!empty($rec['action_url']))
|
||||||
|
<a href="{{ $rec['action_url'] }}" class="inline-flex items-center gap-1 rounded bg-indigo-50 hover:bg-indigo-100 border border-indigo-200 text-indigo-700 font-semibold px-2 py-1 text-[11px] transition">
|
||||||
|
<span>Apri</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($tab === 'nominativi')
|
@if($tab === 'nominativi')
|
||||||
|
|
@ -430,8 +438,15 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
|
||||||
<span>Gestioni: <strong class="text-indigo-700">{{ implode(' | ', $item['year_labels'] ?? $item['years']) }}</strong></span>
|
<span>Gestioni: <strong class="text-indigo-700">{{ implode(' | ', $item['year_labels'] ?? $item['years']) }}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
@if(!empty($item['dettaglio_subentro']))
|
@if(!empty($item['dettaglio_subentro']))
|
||||||
<div class="text-xs text-amber-700 bg-amber-50 rounded border border-amber-200 px-2 py-1 inline-block mt-1">
|
@php
|
||||||
ℹ️ {{ $item['dettaglio_subentro'] }}
|
$subentroText = $item['dettaglio_subentro'];
|
||||||
|
try {
|
||||||
|
$parsedDate = \Carbon\Carbon::parse($subentroText)->format('d/m/Y');
|
||||||
|
$subentroText = 'Subentrato il: ' . $parsedDate;
|
||||||
|
} catch (\Throwable $e) {}
|
||||||
|
@endphp
|
||||||
|
<div class="text-xs text-amber-800 bg-amber-50 rounded-md border border-amber-200 px-2.5 py-1 inline-block mt-1 font-medium">
|
||||||
|
📅 {{ $subentroText }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -439,9 +454,9 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
|
||||||
<div>
|
<div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
wire:click="selectNominativoKey('{{ $item['key'] }}')"
|
wire:click="selectNominativoKey('{{ $item['key'] }}')"
|
||||||
class="inline-flex items-center gap-1.5 rounded-lg bg-indigo-600 px-3 py-2 text-xs font-semibold text-white shadow-xs hover:bg-indigo-700 transition">
|
class="inline-flex items-center gap-1.5 rounded-lg bg-indigo-600 px-3.5 py-2 text-xs font-semibold text-white shadow-xs hover:bg-indigo-700 transition">
|
||||||
<span>🧾</span>
|
<span>🧾</span>
|
||||||
<span>Vedi Estratto Conto per Nominativo</span>
|
<span>Vai all'Estratto Conto del Soggetto</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,15 @@ ## Obiettivo
|
||||||
- Allineamento delle finestre temporali (`data_inizio`, `data_fine`) in `unita_anagrafica_periodo` per tutti i subentri storici dello Stabile 0013.
|
- Allineamento delle finestre temporali (`data_inizio`, `data_fine`) in `unita_anagrafica_periodo` per tutti i subentri storici dello Stabile 0013.
|
||||||
- Risoluzione dinamica dei nominativi nel selettore a tendina e nella scheda unità in base all'`anno_gestione` attivo (2026, 2024, 2020, ecc.).
|
- Risoluzione dinamica dei nominativi nel selettore a tendina e nella scheda unità in base all'`anno_gestione` attivo (2026, 2024, 2020, ecc.).
|
||||||
2. **Allineamento Catasto e Subentri Esemplari**:
|
2. **Allineamento Catasto e Subentri Esemplari**:
|
||||||
- **Scala B Int. 6 (ID 247)**:
|
- **Scala B Int. 6 (ID 247)**: COCCIA Simone (2026) vs DE SANTIS Fabio (2020) con allineamento Sister Catasto (Sub. 25, Rendita € 742,41).
|
||||||
- Gestione 2026: visualizza l'attuale proprietario **COCCIA Simone** (CF: `CCCSMN74B06L120H`), allineato a Sister Catasto (**Fg. 403, Part. 60, Sub. 25, Cat. A/2, Cl. 03, Vani 2.5, Rendita € 742,41**, Proprietà 1/1).
|
- **Scala B Int. 1 (ID 242)**: Tabacchini Giuseppe (2026) vs PAVIA Monica (2024).
|
||||||
- Gestioni fino al 2021 (es. 2020): visualizza il dante causa **DE SANTIS Fabio** (fino al 28/09/2021).
|
- **Scala B Int. 3 / B Int. 4**: RDP S.R.L. limitata esclusivamente a queste due unità dal 08/04/2026, rimossa da tutte le altre unità di Scala B.
|
||||||
- **Scala B Int. 1 (ID 242)**:
|
- **Scala A Int. 1 (ID 233)**: Spadavecchia Aldo (condomino) e Kiwa Cermet Spa (inquilino), bonificati i comproprietari incrociati orfani.
|
||||||
- Gestione 2026: visualizza **Tabacchini Giuseppe** (subentrato dal 30/10/2025).
|
3. **Restyling Compatto Tab Riepilogo & Matrice Recapiti Multicanale**:
|
||||||
- Gestioni fino al 2025 (es. 2024): visualizza **PAVIA Monica** (con storico inquilino).
|
- Eliminazione dei grossi box "Indicatori principali" (Superficie, millesimi, valore) e dei 7 riquadri vuoti dei servizi.
|
||||||
- **Scala B Int. 3 / B Int. 4**:
|
- Inserimento schede compatte anagrafiche: Condomino (Proprietario) attivo con quota e CF, Inquilino attivo con quota spese, Dati Catasto e Fabbricato.
|
||||||
- Fino al 07/04/2026: **PICCOLO Pasqualina EREDI**.
|
- Nuova tabella ad alta densità informativa **Recapiti Multicanale Dinamici** (Email, PEC, Cellulari, Telefoni Fissi, Indirizzo) con badge colorati, stato principale e pulsanti rapidi di apertura `mailto:` e `tel:`.
|
||||||
- Dal 08/04/2026: **RDP S.R.L.**.
|
- Rimozione della vecchia timeline ridondante dal Riepilogo; il tab **Nominativi & Subentri** ora gestisce la cronistoria temporale completa con date formattate e pulsante diretto `[ 🧾 Vai all'Estratto Conto del Soggetto ]`.
|
||||||
3. **Bonifica e Allineamento Stabile 0013**:
|
|
||||||
- Comando Artisan idempotente `php artisan gescon:bonifica-stabile-0013` esteso con riallineamento temporale automatico di tutti i subentri e dei dati catastali ufficiali AdE.
|
|
||||||
4. **Modernizzazione Blade Scheda Stabile e Compattazione Dropdown**:
|
|
||||||
- Nuova barra di navigazione unificata, Hero Header Banner dello Stabile con badge e quick actions, tab navigation pill-bar.
|
|
||||||
- Compattazione dei comproprietari multipli nel dropdown (`Primary (+N)`).
|
|
||||||
|
|
||||||
## Output del Giro Operativo
|
## Output del Giro Operativo
|
||||||
|
|
||||||
|
|
@ -32,23 +27,20 @@ ## Output del Giro Operativo
|
||||||
TASK_ID: task-87b64082c1
|
TASK_ID: task-87b64082c1
|
||||||
REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git
|
REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git
|
||||||
BRANCH: stabilization/205-zero
|
BRANCH: stabilization/205-zero
|
||||||
COMMIT: cb6249b
|
COMMIT: in_pubblicazione
|
||||||
FILE_O_AREE_TOCCATE:
|
FILE_O_AREE_TOCCATE:
|
||||||
- app/Console/Commands/GesconBonificaStabile0013Command.php
|
- app/Console/Commands/GesconBonificaStabile0013Command.php
|
||||||
- app/Models/UnitaImmobiliare.php
|
- app/Models/UnitaImmobiliare.php
|
||||||
- app/Filament/Pages/UnitaImmobiliarePage.php
|
- app/Filament/Pages/UnitaImmobiliarePage.php
|
||||||
- app/Filament/Pages/Condomini/CatastoHub.php
|
|
||||||
- resources/views/filament/pages/unita-immobiliare.blade.php
|
- resources/views/filament/pages/unita-immobiliare.blade.php
|
||||||
- resources/views/filament/pages/condomini/stabile.blade.php
|
|
||||||
- tests/Feature/UnitaGestioneTemporaleTest.php
|
- tests/Feature/UnitaGestioneTemporaleTest.php
|
||||||
- tests/Feature/CatastoHubDbDrivenTest.php
|
- tests/Feature/CatastoHubDbDrivenTest.php
|
||||||
TEST_ESEGUITI:
|
TEST_ESEGUITI:
|
||||||
- ./vendor/bin/pest tests/Feature/UnitaGestioneTemporaleTest.php tests/Feature/CatastoHubDbDrivenTest.php tests/Feature/UnitaCondominoInquilinoRoleToggleTest.php tests/Feature/UnitaImmobiliarePageTest.php tests/Feature/AnagraficaUnicaCanonicaTest.php tests/Feature/BenedettoBonificaIdempotenteTest.php tests/Feature/ControlTowerPollCommandTest.php (20 passed, 115 assertions)
|
- ./vendor/bin/pest tests/Feature/UnitaGestioneTemporaleTest.php tests/Feature/CatastoHubDbDrivenTest.php tests/Feature/UnitaCondominoInquilinoRoleToggleTest.php tests/Feature/UnitaImmobiliarePageTest.php tests/Feature/AnagraficaUnicaCanonicaTest.php tests/Feature/BenedettoBonificaIdempotenteTest.php tests/Feature/ControlTowerPollCommandTest.php (20 passed, 115 assertions)
|
||||||
GATE_STATISTICS:
|
GATE_STATISTICS:
|
||||||
- UNITA_REALI_0013: 42 unità canoniche con cronistoria temporale multi-gestione.
|
- UNITA_REALI_0013: 42 unità canoniche con cronistoria temporale multi-gestione.
|
||||||
- TEMPORAL_DYNAMIC_OWNER: Risoluzione dinamica proprietario per anno di gestione su B-6, B-1, C-2, C-5, B-8, NGOT-103.
|
- RDP_CLEANUP: RDP S.R.L. circoscritta esclusivamente a B-3 e B-4, rimossa da B-1, B-2, B-5, B-7.
|
||||||
- CATASTO_SISTER_B6: Subalterno 25, Rendita 742,41, Intestato COCCIA Simone 1/1.
|
- MULTICHANNEL_CONTACTS_TABLE: Matrice recapiti multicanale tabellare compatta e responsive.
|
||||||
- DROPDOWN_COMPACT: Nominativi multipli compattati a singola riga `Nome (+N)`.
|
|
||||||
- ZERO_MOCK: Dati catastali 100% DB-driven e Sister API live.
|
- ZERO_MOCK: Dati catastali 100% DB-driven e Sister API live.
|
||||||
BLOCCO_DATI: no
|
BLOCCO_DATI: no
|
||||||
BLOCCO_CONTRATTO: no
|
BLOCCO_CONTRATTO: no
|
||||||
|
|
@ -57,5 +49,5 @@ ## Output del Giro Operativo
|
||||||
## Prossimo Passo per .200 (Validazione)
|
## Prossimo Passo per .200 (Validazione)
|
||||||
|
|
||||||
- Eseguire il checkout del branch `stabilization/205-zero`.
|
- Eseguire il checkout del branch `stabilization/205-zero`.
|
||||||
- Eseguire `php artisan gescon:bonifica-stabile-0013` per convalidare la bonifica e l'allineamento temporale.
|
- Eseguire `php artisan gescon:bonifica-stabile-0013` per convalidare la bonifica, l'eliminazione dei comproprietari incrociati e l'allineamento temporale.
|
||||||
- Eseguire la suite di test Pest (20 test, 115 asserzioni).
|
- Eseguire la suite di test Pest (20 test, 115 asserzioni).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user