diff --git a/app/Filament/Pages/Affitti/GestioneAffitti.php b/app/Filament/Pages/Affitti/GestioneAffitti.php index db789d8..a4033b6 100755 --- a/app/Filament/Pages/Affitti/GestioneAffitti.php +++ b/app/Filament/Pages/Affitti/GestioneAffitti.php @@ -102,12 +102,21 @@ class GestioneAffitti extends Page public static function canAccess(): bool { $user = Auth::user(); - if (! $user instanceof \App\Models\User) { - return false; - } return $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } + public function getTabsProperty(): array + { + return [ + 'elenco' => ['label' => 'Elenco Immobili a Reddito', 'icon' => '🏢'], + 'anagrafica' => ['label' => 'Anagrafica & Catasto Unità', 'icon' => '🏠'], + 'banca' => ['label' => 'Banca & Incassi', 'icon' => '🏦'], + 'scadenze' => ['label' => 'Contratto & Clausole', 'icon' => '📜'], + 'canoni' => ['label' => 'Canoni & Rate', 'icon' => '💶'], + 'documenti' => ['label' => 'Documenti & Allegati', 'icon' => '📁'], + ]; + } + public function mount(): void { $this->initContractTemplates(); diff --git a/app/Filament/Pages/Contabilita/FatturaElettronicaScheda.php b/app/Filament/Pages/Contabilita/FatturaElettronicaScheda.php index adae74e..dfbe86c 100755 --- a/app/Filament/Pages/Contabilita/FatturaElettronicaScheda.php +++ b/app/Filament/Pages/Contabilita/FatturaElettronicaScheda.php @@ -196,6 +196,18 @@ public function getPagamentoModalitaXsdLabel(): ?string return is_string($doc) && trim($doc) !== '' ? trim($doc) : null; } + public static function canAccess(): bool + { + $user = Auth::user(); + if (! $user instanceof User) { + return false; + } + + return $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']) + || $user->hasRole('fornitore') + || $user->can('contabilita.fatture-elettroniche'); + } + public function mount(int | string $record): void { $user = Auth::user(); @@ -203,7 +215,7 @@ public function mount(int | string $record): void abort(403); } - if (! $user->hasAnyRole(['super-admin', 'admin']) && ! $user->hasRole('fornitore') && ! $user->can('contabilita.fatture-elettroniche')) { + if (! $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']) && ! $user->hasRole('fornitore') && ! $user->can('contabilita.fatture-elettroniche')) { abort(403); } diff --git a/app/Filament/Pages/Gescon/Ordinarie.php b/app/Filament/Pages/Gescon/Ordinarie.php index 0a8628b..f7bbd7e 100755 --- a/app/Filament/Pages/Gescon/Ordinarie.php +++ b/app/Filament/Pages/Gescon/Ordinarie.php @@ -1,6 +1,7 @@ runMdbExport($singoloMdb, 'condomin'); $condMap = []; foreach ($condominRows as $c) { - if (! empty($c['id_cond'])) { - $condMap[trim((string) $c['id_cond'])] = $c; - } if (! empty($c['cod_cond'])) { $condMap[trim((string) $c['cod_cond'])] = $c; } @@ -2024,6 +2029,8 @@ public function getIncassiFromMdb() 'cod_cond' => $condId, 'cond_inquil' => $tipo, 'nominativo_pagante' => $nominativo, + 'scala' => $scala, + 'interno' => $interno, 'scala_interno_short' => $scalaInternoShort, 'data_visuale' => $dataVisuale, 'riferimento_visuale' => $riferimentoVisuale !== '' ? $riferimentoVisuale : '—', @@ -2032,6 +2039,7 @@ public function getIncassiFromMdb() 'descrizione' => $r['descrizione'] ?? '—', 'rata_match' => null, 'movimento_banca' => null, + 'unita_id' => null, 'unita_ec_url' => null, ]; @@ -2049,7 +2057,29 @@ public function getIncassiFromMdb() $total = count($filtered); $sliced = array_slice($filtered, ($currentPage - 1) * $this->perPage, $this->perPage); - // Collegamento con contabilita_movimenti_banca + // Risoluzione Unità Immobiliare da Scala e Interno + $unitaMap = []; + if ($activeStabileId && Schema::hasTable('unita_immobiliari')) { + $unitaList = DB::table('unita_immobiliari') + ->where('stabile_id', $activeStabileId) + ->whereNull('deleted_at') + ->get(['id', 'scala', 'interno']); + foreach ($unitaList as $u) { + $scKey = strtoupper(trim((string)$u->scala)) . '|' . trim((string)$u->interno); + $unitaMap[$scKey] = (int) $u->id; + } + } + + foreach ($sliced as $item) { + $scKey = strtoupper(trim((string)($item->scala ?? ''))) . '|' . trim((string)($item->interno ?? '')); + $uId = $unitaMap[$scKey] ?? null; + if ($uId) { + $item->unita_id = $uId; + $item->unita_ec_url = \App\Filament\Pages\UnitaImmobiliarePage::getUrl(['unita_id' => $uId], panel: 'admin-filament') . '&tab=estratto_conto'; + } + } + + // Collegamento con contabilita_movimenti_banca (con vincolo su unità e anno) $riferimenti = array_filter(array_map(fn($o) => trim((string)$o->riferimento_visuale), $sliced), fn($v) => $v !== '' && $v !== '—'); if (! empty($riferimenti) && Schema::hasTable('contabilita_movimenti_banca')) { $bankRows = DB::table('contabilita_movimenti_banca') @@ -2057,31 +2087,62 @@ public function getIncassiFromMdb() ->whereIn('match_data->num_incasso', array_values($riferimenti)) ->get(); - $bankByRif = []; + $bankGrouped = []; foreach ($bankRows as $b) { $m = is_string($b->match_data) ? json_decode($b->match_data, true) : (array) $b->match_data; $numInc = (string) ($m['num_incasso'] ?? ''); + $annoInc = (string) ($m['anno_incasso'] ?? ''); + $scInt = strtoupper(trim((string) ($m['sc_int'] ?? ''))); + if ($numInc !== '') { - $bankByRif[$numInc] = [ + $bankGrouped[$numInc][] = [ 'movimento_id' => $b->id, 'data' => ! empty($b->data) ? Carbon::parse($b->data)->format('d/m/Y') : '—', + 'data_raw' => $b->data, + 'anno' => ! empty($b->data) ? date('Y', strtotime($b->data)) : $annoInc, 'importo' => (float) $b->importo, 'cro' => $b->rif_disposizione, 'descrizione' => $b->descrizione, 'pdf_ricevuta' => $m['pdf_ricevuta'] ?? null, 'pdf_url' => $m['pdf_url'] ?? (! empty($m['pdf_ricevuta']) ? "/admin/gescon-inc-ec-pdf/{$codStabile}/{$m['pdf_ricevuta']}" : null), 'unita_id' => $b->unita_immobiliare_id, + 'sc_int' => $scInt, ]; } } foreach ($sliced as $item) { $rif = trim((string) ($item->riferimento_visuale ?? '')); - if (isset($bankByRif[$rif])) { - $item->movimento_banca = $bankByRif[$rif]; - if (! empty($item->movimento_banca['unita_id'])) { - $item->unita_ec_url = \App\Filament\Pages\UnitaImmobiliarePage::getUrl(['unita_id' => $item->movimento_banca['unita_id']], panel: 'admin-filament') . '&tab=estratto_conto'; + if (! isset($bankGrouped[$rif])) { + continue; + } + + $itemScInt = strtoupper(trim(($item->scala ?? '') . '/' . ($item->interno ?? ''))); + $itemYear = ! empty($item->data_visuale) ? date('Y', strtotime($item->data_visuale)) : (string) ($activeYear ?? 2026); + $itemUnitId = $item->unita_id ?? null; + + $matchedB = null; + foreach ($bankGrouped[$rif] as $cand) { + $unitMatch = false; + if ($itemUnitId && (int) $cand['unita_id'] === (int) $itemUnitId) { + $unitMatch = true; + } elseif ($cand['sc_int'] !== '' && ($cand['sc_int'] === $itemScInt || str_replace(' ', '', $cand['sc_int']) === str_replace(' ', '', $itemScInt))) { + $unitMatch = true; } + + $yearMatch = false; + if (! empty($cand['anno']) && abs((int) $cand['anno'] - (int) $itemYear) <= 1) { + $yearMatch = true; + } + + if ($unitMatch && $yearMatch) { + $matchedB = $cand; + break; + } + } + + if ($matchedB) { + $item->movimento_banca = $matchedB; } } } @@ -4496,4 +4557,77 @@ public function getMovimentiBancaProperty() return $paginator; } + + /** + * Carica un documento ufficiale bancario (PDF estratto conto, contabili, ecc.) per lo stabile attivo. + */ + public function uploadBankDocument(): void + { + $this->validate([ + 'bankDocumentFile' => 'required|file|max:20480', + 'bankDocumentDescrizione' => 'nullable|string|max:255', + ]); + + $user = Auth::user(); + $activeStabileId = ($user instanceof User) ? (StabileContext::resolveActiveStabileId($user) ?: 19) : 19; + if (! $activeStabileId) { + Notification::make()->title('Nessuno stabile attivo')->warning()->send(); + return; + } + + $origName = $this->bankDocumentFile->getClientOriginalName(); + $ext = strtolower($this->bankDocumentFile->getClientOriginalExtension()); + $fileName = 'banca_' . date('Ymd_His') . '_' . Str::random(6) . '.' . $ext; + $path = $this->bankDocumentFile->storeAs('documenti/bancari/' . $activeStabileId, $fileName, 'public'); + + DocumentoStabile::create([ + 'stabile_id' => $activeStabileId, + 'nome_file' => $fileName, + 'nome_originale' => $origName, + 'percorso_file' => $path, + 'categoria' => 'bancari', + 'tipo_mime' => $this->bankDocumentFile->getMimeType() ?: 'application/pdf', + 'dimensione' => $this->bankDocumentFile->getSize() ?: 0, + 'descrizione' => $this->bankDocumentDescrizione ?: 'Documento Ufficiale Bancario (' . ucfirst(str_replace('_', ' ', $this->bankDocumentTipo ?? 'estratto')) . ')', + 'tags' => 'banca,estratto_conto,conto_corrente,' . $this->bankDocumentTipo, + 'caricato_da' => Auth::id(), + 'versione' => 1, + 'pubblico' => true, + ]); + + $this->reset(['bankDocumentFile', 'bankDocumentDescrizione']); + Notification::make()->title('Documento bancario caricato con successo')->success()->send(); + } + + /** + * Elimina un documento ufficiale bancario associato allo stabile. + */ + public function deleteBankDocument(int $docId): void + { + $user = Auth::user(); + $activeStabileId = ($user instanceof User) ? (StabileContext::resolveActiveStabileId($user) ?: 19) : 19; + $doc = DocumentoStabile::query() + ->where('stabile_id', $activeStabileId) + ->where('categoria', 'bancari') + ->find($docId); + + if ($doc) { + $doc->delete(); + Notification::make()->title('Documento bancario eliminato')->success()->send(); + } + } + + /** + * Elenco documenti bancari ufficiali per lo stabile attivo. + */ + public function getBankDocumentsProperty() + { + $user = Auth::user(); + $activeStabileId = ($user instanceof User) ? (StabileContext::resolveActiveStabileId($user) ?: 19) : 19; + return DocumentoStabile::query() + ->where('stabile_id', $activeStabileId) + ->where('categoria', 'bancari') + ->orderByDesc('id') + ->get(); + } } diff --git a/app/Filament/Pages/UnitaImmobiliarePage.php b/app/Filament/Pages/UnitaImmobiliarePage.php index 184aa62..10689f1 100755 --- a/app/Filament/Pages/UnitaImmobiliarePage.php +++ b/app/Filament/Pages/UnitaImmobiliarePage.php @@ -548,46 +548,61 @@ public function getRecapitiMulticanaleTableProperty(): array $tipoSoggetto = in_array(strtolower($ruolo), ['inquilino', 'conduttore', 'locatario'], true) || (string) ($entity['tipo_relazione'] ?? '') === 'inquilino' ? 'inquilino' : 'condomino'; - $anagId = (int) ($entity['persona_id'] ?? ($entity['soggetto_id'] ?? 0)); - $anag = null; - if ($anagId > 0) { - if (DbSchema::hasTable('anagrafiche')) { - $anag = DB::table('anagrafiche')->find($anagId); - } - if (! $anag && DbSchema::hasTable('persone')) { - $anag = DB::table('persone')->find($anagId); - } + $personaId = (int) ($entity['persona_id'] ?? 0); + $soggettoId = (int) ($entity['soggetto_id'] ?? 0); + $cf = trim((string) ($entity['codice_fiscale'] ?? '')); + + $persona = null; + if ($personaId > 0 && DbSchema::hasTable('persone')) { + $persona = DB::table('persone')->find($personaId); } - if (! $anag) { - if (DbSchema::hasTable('anagrafiche')) { + if (! $persona && $cf !== '' && DbSchema::hasTable('persone')) { + $persona = DB::table('persone')->where('codice_fiscale', $cf)->first(); + } + + $anag = null; + if (DbSchema::hasTable('anagrafiche')) { + if ($personaId > 0 && DbSchema::hasColumn('anagrafiche', 'persona_id')) { + $anag = DB::table('anagrafiche')->where('persona_id', $personaId)->first(); + } + if (! $anag && $cf !== '') { + $anag = DB::table('anagrafiche')->where('codice_fiscale', $cf)->first(); + } + if (! $anag && $soggettoId > 0) { + $anag = DB::table('anagrafiche')->find($soggettoId); + } + if (! $anag && $nome !== '') { $anag = DB::table('anagrafiche') - ->where(function ($q) use ($nome, $entity) { + ->where(function ($q) use ($nome, $cf) { $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 && DbSchema::hasTable('persone')) { - $anag = DB::table('persone') - ->where(function ($q) use ($nome, $entity) { - $q->where('cognome', 'like', '%' . $nome . '%') - ->orWhere('nome', 'like', '%' . $nome . '%'); - if (! empty($entity['codice_fiscale'])) { - $q->orWhere('codice_fiscale', trim((string) $entity['codice_fiscale'])); + if ($cf !== '') { + $q->orWhere('codice_fiscale', $cf); } }) ->first(); } } - $rawEmail = ! empty($anag->email) ? (string) $anag->email : (! empty($anag->email_principale) ? (string) $anag->email_principale : (! empty($entity['email']) ? (string) $entity['email'] : '')); - $rawPec = ! empty($anag->pec) ? (string) $anag->pec : (! empty($anag->email_pec) ? (string) $anag->email_pec : (! empty($entity['pec']) ? (string) $entity['pec'] : '')); - $rawTel = ! empty($anag->telefono) ? (string) $anag->telefono : (! empty($anag->telefono_principale) ? (string) $anag->telefono_principale : (! empty($entity['telefono']) ? (string) $entity['telefono'] : '')); - $rawInd = ! empty($anag->indirizzo) ? trim($anag->indirizzo . ' ' . ($anag->cap ?? '') . ' ' . ($anag->citta ?? '') . ' ' . ($anag->provincia ? '(' . $anag->provincia . ')' : '')) : (! empty($anag->residenza_via) ? (string) $anag->residenza_via : (! empty($entity['indirizzo']) ? (string) $entity['indirizzo'] : '')); + // Legacy mirror per recapiti mancanti + $legacyRow = $this->getLegacyCondominRow(); + $legacyPayload = is_array($legacyRow?->legacy_payload ?? null) ? $legacyRow->legacy_payload : []; + + $rawEmail = ! empty($persona->email_principale) ? (string) $persona->email_principale : (! empty($anag->email) ? (string) $anag->email : (! empty($anag->email_principale) ? (string) $anag->email_principale : (! empty($entity['email']) ? (string) $entity['email'] : (! empty($legacyPayload['E_mail_condomino']) ? (string) $legacyPayload['E_mail_condomino'] : '')))); + $rawPec = ! empty($persona->email_pec) ? (string) $persona->email_pec : (! empty($anag->pec) ? (string) $anag->pec : (! empty($anag->email_pec) ? (string) $anag->email_pec : (! empty($entity['pec']) ? (string) $entity['pec'] : (! empty($legacyPayload['PEC_condomino']) ? (string) $legacyPayload['PEC_condomino'] : '')))); + $rawTel = ! empty($persona->telefono_principale) ? (string) $persona->telefono_principale : (! empty($anag->cellulare) ? (string) $anag->cellulare : (! empty($anag->telefono) ? (string) $anag->telefono : (! empty($persona->telefono_secondario) ? (string) $persona->telefono_secondario : (! empty($entity['telefono']) ? (string) $entity['telefono'] : (! empty($legacyPayload['Cell_cond']) ? (string) $legacyPayload['Cell_cond'] : ''))))); + $rawInd = ! empty($legacyPayload['ind']) ? trim($legacyPayload['ind'] . ' ' . ($legacyPayload['cap'] ?? '') . ' ' . ($legacyPayload['citta'] ?? '') . ' ' . (! empty($legacyPayload['pr']) ? '(' . $legacyPayload['pr'] . ')' : '')) : (! empty($anag->indirizzo) ? trim($anag->indirizzo . ' ' . ($anag->cap ?? '') . ' ' . ($anag->citta ?? '') . ' ' . ($anag->provincia ? '(' . $anag->provincia . ')' : '')) : (! empty($persona->residenza_via) ? (string) $persona->residenza_via : (! empty($entity['indirizzo']) ? (string) $entity['indirizzo'] : ''))); + + // Regola utente: se la residenza non e indicata, e nello stabile dove viene visualizzato il condomino o inquilino + if ($rawInd === '') { + $stabile = $this->unita->stabile ?? null; + if ($stabile) { + $stabileAddr = trim(($stabile->indirizzo ?? '') . ($stabile->civico ? ', ' . $stabile->civico : '')); + $loc = trim(($stabile->cap ?? '') . ' ' . ($stabile->citta ?? '') . ($stabile->provincia ? ' (' . $stabile->provincia . ')' : '')); + $rawInd = trim($stabileAddr . ' ' . $loc); + } + } if ($rawEmail !== '') { $parts = preg_split('/[,;\s]+/', $rawEmail, -1, PREG_SPLIT_NO_EMPTY); @@ -4706,6 +4721,7 @@ private function getLegacyCondominRow(): ?object 'inquil_cod_fisc', 'source_year', 'legacy_year', + 'legacy_payload', ]; $actualCols = \Illuminate\Support\Facades\Schema::connection('gescon_import')->getColumnListing($tableName); $selectCols = array_intersect($desiredCols, $actualCols); @@ -4714,6 +4730,10 @@ private function getLegacyCondominRow(): ?object ->orderByDesc('id') ->first($selectCols); + if ($row && is_string($row->legacy_payload ?? null)) { + $row->legacy_payload = json_decode($row->legacy_payload, true) ?: []; + } + if (! $row && $codStabile === '0016') { $mdbPath = "/mnt/gescon-archives/gescon/{$codStabile}/{$legacyYear}/singolo_anno.mdb"; if (file_exists($mdbPath)) { @@ -4737,6 +4757,7 @@ private function getLegacyCondominRow(): ?object 'attivo_fino_al' => $c['attivo_fino_al'] ?? null, 'inquil_dal' => $c['inquil_dal'] ?? null, 'inquil_al' => $c['inquil_al'] ?? null, + 'legacy_payload' => $c, ]; return $this->legacyCondominRow = $obj; } diff --git a/app/Services/Gescon/GesconEstrattoContoService.php b/app/Services/Gescon/GesconEstrattoContoService.php index 1ba96b8..3e53a25 100644 --- a/app/Services/Gescon/GesconEstrattoContoService.php +++ b/app/Services/Gescon/GesconEstrattoContoService.php @@ -776,8 +776,275 @@ protected function buildForStabile0016(string $genMdb, string $stabileDir, strin ]; } - // Fallback per altre unità di Stabile 0016 - return $this->buildFromDatabase($unita, $ruolo); + // Calcolo per altre unità di Stabile 0016 direttamente dai file MDB di Germanico 96 + return $this->buildForStabile0016Generic($genMdb, $stabileDir, $scala, $interno, $ruolo, $condInfo, $unita); + } + + /** + * Calcolo dinamico per tutte le altre unità di Stabile 0016 da MDB (emes_det, condomin, incassi). + */ + protected function buildForStabile0016Generic(string $genMdb, string $stabileDir, string $scala, string $interno, string $ruolo, ?array $condInfo, UnitaImmobiliare $unita): array + { + $activeYear = '2025/26'; + $activeAnno = 2026; + + if (empty($condInfo['cod_cond'])) { + $singoloMdb = $stabileDir . '/0914/singolo_anno.mdb'; + if (file_exists($singoloMdb)) { + $condRows = $this->runMdbExport($singoloMdb, 'condomin'); + foreach ($condRows as $c) { + $sc = strtoupper(trim((string) ($c['scala'] ?? ''))); + $in = trim((string) ($c['int'] ?? $c['interno'] ?? '')); + if ($sc === strtoupper($scala) && $in === $interno) { + $condInfo = $c; + break; + } + } + } + } + + $unitCodCond = trim((string) ($condInfo['cod_cond'] ?? '')); + + $soggettoNome = $ruolo === 'I' + ? trim((string) ($condInfo['inquil_nome'] ?? 'Conduttore / Inquilino')) + : trim((string) ($condInfo['nom_cond'] ?? $unita->denominazione ?? 'Condòmino')); + if ($soggettoNome === '') { + $soggettoNome = $ruolo === 'I' ? 'Conduttore / Inquilino' : 'Condòmino'; + } + + // Lettura emes_det da generale_stabile.mdb per cod_cond + $emesRows = $this->runMdbExport($genMdb, 'emes_det'); + $unitEmesRows = []; + foreach ($emesRows as $e) { + $cCond = trim((string) ($e['cod_cond'] ?? '')); + $cInq = strtoupper(trim((string) ($e['cond_inq'] ?? 'C'))); + if ($cCond === $unitCodCond && $cInq === $ruolo) { + $unitEmesRows[] = $e; + } + } + + // Lettura incassi da 0914/singolo_anno.mdb + $incassi0914 = []; + $singoloMdb = $stabileDir . '/0914/singolo_anno.mdb'; + if (file_exists($singoloMdb)) { + $rawInc = $this->runMdbExport($singoloMdb, 'incassi'); + foreach ($rawInc as $inc) { + $cCond = trim((string) ($inc['cod_cond'] ?? '')); + $cInq = strtoupper(trim((string) ($inc['cond_inquil'] ?? $inc['cond_inq'] ?? 'C'))); + if ($cCond === $unitCodCond && $cInq === $ruolo) { + $incassi0914[] = $inc; + } + } + } + + $incByRif = []; + foreach ($incassi0914 as $inc) { + $rif = trim((string) ($inc['n_riferimento'] ?? '')); + if ($rif !== '') { + $incByRif[$rif] = $inc; + } + } + + // 1. Gestioni ordinarie pregresse + $ordByYear = []; + foreach ($unitEmesRows as $r) { + $ors = strtoupper(trim((string) ($r['o_r_s'] ?? 'O'))); + $yr = trim((string) ($r['anno_gestione'] ?? '')); + $nStra = trim((string) ($r['n_stra'] ?? '0')); + $dovuto = is_numeric($r['importo_dovuto_euro'] ?? null) ? (float) $r['importo_dovuto_euro'] : 0.0; + + if (($ors === 'O' || ($ors !== 'S' && $nStra === '0')) && $yr !== $activeYear && $yr !== '') { + $ordByYear[$yr] = ($ordByYear[$yr] ?? 0.0) + $dovuto; + } + } + + $gestioniPregresseOrd = []; + ksort($ordByYear); + foreach ($ordByYear as $yr => $totDovuto) { + $gestioniPregresseOrd[] = [ + 'anno' => $yr, + 'titolo' => "Gestione Ordinaria - Es. {$yr}", + 'dovuto' => round($totDovuto, 2), + 'pagato' => round($totDovuto, 2), + 'saldo' => 0.00, + 'stato' => 'Chiusa Definitiva', + ]; + } + + // 2. Gestione ordinaria corrente (2025/26) + $ordCorrenteRate = []; + $totOrdCorrenteDovuto = 0.0; + $totOrdCorrentePagato = 0.0; + + foreach ($unitEmesRows as $r) { + $ors = strtoupper(trim((string) ($r['o_r_s'] ?? 'O'))); + $yr = trim((string) ($r['anno_gestione'] ?? '')); + $nStra = trim((string) ($r['n_stra'] ?? '0')); + $dovuto = is_numeric($r['importo_dovuto_euro'] ?? null) ? (float) $r['importo_dovuto_euro'] : 0.0; + + if (($yr === $activeYear || trim((string)($r['anno_emissione'] ?? '')) === '0914') && ($ors === 'O' || ($ors !== 'S' && $nStra === '0'))) { + $rif = trim((string) ($r['n_ricevuta'] ?? '')); + $desc = trim((string) ($r['descrizione'] ?? '')); + $dtEm = $this->formatDate($r['dt_emissione_pagamento'] ?? null); + + $dtPag = null; + $pagato = 0.0; + $residuo = $dovuto; + $stato = 'Da Pagare'; + + $incMatch = null; + if ($rif !== '' && isset($incByRif[$rif])) { + $incMatch = $incByRif[$rif]; + } else { + foreach ($incassi0914 as $inc) { + $incDesc = trim((string)($inc['descrizione'] ?? '')); + if (str_contains(mb_strtolower($incDesc), mb_strtolower(substr($desc, 0, 15)))) { + $incMatch = $inc; + break; + } + } + } + + if ($incMatch) { + $pagato = is_numeric($incMatch['importo_pagato_euro'] ?? null) ? (float) $incMatch['importo_pagato_euro'] : $dovuto; + $dtPag = $this->formatDate($incMatch['dt_empag'] ?? null); + $residuo = max(0.0, round($dovuto - $pagato, 2)); + $stato = $residuo <= 0.01 ? 'Saldata' : 'Parziale'; + } + + $ordCorrenteRate[] = [ + 'data' => $dtEm, + 'rif' => $rif, + 'descrizione' => $desc, + 'dovuto' => round($dovuto, 2), + 'data_pagamento' => $dtPag, + 'pagato' => round($pagato, 2), + 'residuo' => round($residuo, 2), + 'stato' => $stato, + ]; + + $totOrdCorrenteDovuto += $dovuto; + $totOrdCorrentePagato += $pagato; + } + } + + $totOrdResiduo = max(0.0, round($totOrdCorrenteDovuto - $totOrdCorrentePagato, 2)); + + // 3. Gestioni Straordinarie (SOLO Condomino 'C') + $straordinarie = []; + $totStraordinarieResiduo = 0.0; + + if ($ruolo === 'C') { + $straGroups = []; + foreach ($unitEmesRows as $r) { + $ors = strtoupper(trim((string) ($r['o_r_s'] ?? 'O'))); + $nStra = trim((string) ($r['n_stra'] ?? '0')); + $yr = trim((string) ($r['anno_gestione'] ?? '')); + + if ($ors === 'S' || $nStra !== '0') { + $groupKey = $yr . '_' . $nStra; + $straGroups[$groupKey][] = $r; + } + } + + foreach ($straGroups as $key => $rows) { + [$yr, $nStra] = explode('_', $key); + $firstRow = $rows[0]; + $titolo = "Gestione Straordinaria {$nStra}/{$yr} (Sc. {$scala} / Int. {$interno})"; + $descBreve = trim((string)($firstRow['descrizione'] ?? "Straord. {$nStra}/{$yr}")); + + $rateList = []; + $gDovuto = 0.0; + $gPagato = 0.0; + + foreach ($rows as $r) { + $dov = is_numeric($r['importo_dovuto_euro'] ?? null) ? (float) $r['importo_dovuto_euro'] : 0.0; + $rif = trim((string) ($r['n_ricevuta'] ?? '')); + $desc = trim((string) ($r['descrizione'] ?? '')); + $dtEm = $this->formatDate($r['dt_emissione_pagamento'] ?? null); + + $pag = is_numeric($r['gia_pagato'] ?? null) ? (float) $r['gia_pagato'] : 0.0; + $res = is_numeric($r['residuo_emesso'] ?? null) ? (float) $r['residuo_emesso'] : ($dov - $pag); + + if ($yr !== $activeYear) { + $pag = $dov; + $res = 0.0; + } + + $rateList[] = [ + 'data' => $dtEm, + 'rif' => $rif, + 'descrizione' => $desc, + 'dovuto' => round($dov, 2), + 'data_pagamento' => $pag > 0 ? $dtEm : null, + 'pagato' => round($pag, 2), + 'residuo' => round($res, 2), + 'stato' => $res <= 0.01 ? 'Saldata' : 'Da Pagare', + ]; + + $gDovuto += $dov; + $gPagato += $pag; + } + + $gResiduo = max(0.0, round($gDovuto - $gPagato, 2)); + $totStraordinarieResiduo += $gResiduo; + + $straordinarie[] = [ + 'chiave' => $key, + 'anno' => $yr, + 'num_spesa' => (int) $nStra, + 'titolo' => $titolo, + 'descrizione_completa' => $descBreve, + 'num_rate' => count($rows), + 'rate' => $rateList, + 'totale_dovuto' => round($gDovuto, 2), + 'totale_pagato' => round($gPagato, 2), + 'residuo' => round($gResiduo, 2), + 'stato_gestione' => $gResiduo <= 0.01 ? 'Chiusa Definitiva' : 'In Corso', + ]; + } + } + + $totaleDovutoFinale = round($totOrdResiduo + $totStraordinarieResiduo, 2); + $storicoPdf = $this->getStoricoPdfEc($genMdb, '0016', $scala, $interno, $ruolo, $soggettoNome); + + return [ + 'fonte' => 'MDB_LIVE', + 'codice_stabile' => '0016', + 'soggetto' => [ + 'nominativo' => $soggettoNome, + 'ruolo' => $ruolo, + 'ruolo_label' => $ruolo === 'I' ? 'Conduttore / Inquilino' : 'Condòmino / Proprietario', + 'cod_cond' => $unitCodCond, + 'scala' => $scala, + 'interno' => $interno, + 'locatario' => '', + ], + 'has_riscaldamento' => false, + 'totali' => [ + 'totale_dovuto' => $totaleDovutoFinale, + 'totale_ordinaria_corrente_residuo' => $totOrdResiduo, + 'totale_ordinarie_pregresse_residuo' => 0.00, + 'totale_straordinarie_residuo' => $totStraordinarieResiduo, + 'totale_riscaldamento_residuo' => 0.00, + ], + 'gestioni_pregresse_ordinarie' => [ + 'items' => $gestioniPregresseOrd, + 'totale_dovuto' => round(array_sum(array_column($gestioniPregresseOrd, 'dovuto')), 2), + 'totale_versato' => round(array_sum(array_column($gestioniPregresseOrd, 'pagato')), 2), + 'saldo_residuo' => 0.00, + ], + 'gestione_ordinaria_corrente' => [ + 'anno' => $activeAnno, + 'titolo' => "GESTIONE ORDINARIA - Es.{$activeYear} - (Sc. {$scala} / Int. {$interno})", + 'rate' => $ordCorrenteRate, + 'totale_dovuto' => round($totOrdCorrenteDovuto, 2), + 'totale_pagato' => round($totOrdCorrentePagato, 2), + 'residuo' => $totOrdResiduo, + ], + 'gestioni_straordinarie' => $straordinarie, + 'storico_pdf_ec' => $storicoPdf, + ]; } /** @@ -929,6 +1196,11 @@ protected function buildFromDatabase(UnitaImmobiliare $unita, string $ruolo): ar return $this->buildForStabile0021Fallback($scala, $interno, $ruolo); } + // Se lo stabile non è 0013, non iniettare dati 0013 di Spadavecchia! + if ($codStabile !== '0013') { + return $this->buildCleanEmptyFallback($unita, $ruolo); + } + $soggettoNome = $unita->denominazione ?: 'SPADAVECCHIA ALDO'; if ($ruolo === 'I') { $soggettoNome = 'KIWA CERMET SPA'; @@ -1319,4 +1591,54 @@ public function runMdbExport(string $mdbPath, string $table): array return $rows; } + + /** + * Fallback pulito per unità che non hanno archivi Gescon validi o per evitare inquinamento dati cross-stabile. + */ + protected function buildCleanEmptyFallback(UnitaImmobiliare $unita, string $ruolo): array + { + $stabile = $unita->stabile; + $codStabile = (string) ($stabile?->codice_stabile ?? ''); + $scala = $unita->scala ?: '—'; + $interno = $unita->interno ?: '—'; + $soggettoNome = $unita->denominazione ?: 'Condòmino'; + + return [ + 'fonte' => 'DATABASE_CLEAN', + 'codice_stabile' => $codStabile, + 'soggetto' => [ + 'nominativo' => $soggettoNome, + 'ruolo' => $ruolo, + 'ruolo_label' => $ruolo === 'I' ? 'Conduttore / Inquilino' : 'Condòmino / Proprietario', + 'cod_cond' => (string) ($unita->legacy_cond_id ?? '1'), + 'scala' => $scala, + 'interno' => $interno, + 'locatario' => '', + ], + 'has_riscaldamento' => false, + 'totali' => [ + 'totale_dovuto' => 0.00, + 'totale_ordinaria_corrente_residuo' => 0.00, + 'totale_ordinarie_pregresse_residuo' => 0.00, + 'totale_straordinarie_residuo' => 0.00, + 'totale_riscaldamento_residuo' => 0.00, + ], + 'gestioni_pregresse_ordinarie' => [ + 'items' => [], + 'totale_dovuto' => 0.00, + 'totale_versato' => 0.00, + 'saldo_residuo' => 0.00, + ], + 'gestione_ordinaria_corrente' => [ + 'anno' => 2026, + 'titolo' => "GESTIONE ORDINARIA - Es.2026 - (Sc. {$scala} / Int. {$interno})", + 'rate' => [], + 'totale_dovuto' => 0.00, + 'totale_pagato' => 0.00, + 'residuo' => 0.00, + ], + 'gestioni_straordinarie' => [], + 'storico_pdf_ec' => [], + ]; + } } diff --git a/resources/views/filament/pages/affitti/gestione-affitti.blade.php b/resources/views/filament/pages/affitti/gestione-affitti.blade.php index 4bf0307..9f5bbc7 100755 --- a/resources/views/filament/pages/affitti/gestione-affitti.blade.php +++ b/resources/views/filament/pages/affitti/gestione-affitti.blade.php @@ -70,7 +70,7 @@ class="inline-flex items-center gap-1 rounded-lg border border-amber-300 bg-ambe {{-- KPI SUMMARY CARDS --}} - @php($kpi = $this->kpiStats) + kpiStats; ?>