diff --git a/app/Filament/Pages/Condomini/LettureServiziArchivio.php b/app/Filament/Pages/Condomini/LettureServiziArchivio.php index d78e5c1..52463d6 100644 --- a/app/Filament/Pages/Condomini/LettureServiziArchivio.php +++ b/app/Filament/Pages/Condomini/LettureServiziArchivio.php @@ -69,6 +69,18 @@ public function mount(): void $scope = strtolower(trim((string) request()->query('scope', 'active'))); $this->archivioScope = in_array($scope, ['active', 'all'], true) ? $scope : 'active'; + $user = Auth::user(); + if ($this->servizioFilter && $user instanceof User) { + $serviceStabileId = StabileServizio::query() + ->whereKey((int) $this->servizioFilter) + ->value('stabile_id'); + + $activeStabileId = StabileContext::resolveActiveStabileId($user); + if (is_numeric($serviceStabileId) && (int) $serviceStabileId > 0 && (int) $activeStabileId > 0 && (int) $serviceStabileId !== (int) $activeStabileId) { + $this->archivioScope = 'all'; + } + } + $this->mountInteractsWithTable(); } diff --git a/app/Filament/Pages/Contabilita/FattureElettronicheP7mRicevute.php b/app/Filament/Pages/Contabilita/FattureElettronicheP7mRicevute.php index c675f38..ec4e605 100644 --- a/app/Filament/Pages/Contabilita/FattureElettronicheP7mRicevute.php +++ b/app/Filament/Pages/Contabilita/FattureElettronicheP7mRicevute.php @@ -66,6 +66,32 @@ public function getActiveStabileLabelProperty(): string return $parts !== [] ? implode(' - ', $parts) : ('Stabile #' . (int) $stabile->id); } + public function getActiveStabileDestinationSummaryProperty(): string + { + $user = Auth::user(); + if (! $user instanceof User) { + return 'Stabile non selezionato'; + } + + $stabile = StabileContext::getActiveStabile($user); + if (! $stabile instanceof Stabile) { + return 'Stabile non selezionato'; + } + + $summary = $this->getActiveStabileLabelProperty(); + $cf = $this->resolveStabileSoggettoCf($stabile); + + if ($cf !== '') { + $summary .= "\nCodice fiscale soggetto usato per lo scarico: {$cf}"; + } + + if ($this->isLikelyTechnicalFeShell($stabile)) { + $summary .= "\nATTENZIONE: lo stabile attivo sembra uno shell tecnico FE separato. Per scaricare le fatture del condominio cambia lo stabile in topbar prima di procedere."; + } + + return $summary; + } + private function safeUtf8(string $value): string { $value = preg_replace('/^\xEF\xBB\xBF/', '', $value) ?? $value; @@ -230,6 +256,12 @@ public function downloadCassettoQuarter(string $quarter, string $dal, string $al return; } + if ($this->isLikelyTechnicalFeShell($stabile)) { + $this->notifyTechnicalFeShellBlocked(); + $this->quarterDownloads[$key] = false; + return; + } + $amministratoreId = (int) ($stabile->amministratore_id ?? 0); if ($amministratoreId <= 0) { Notification::make()->title('Amministratore non valido')->danger()->send(); @@ -240,7 +272,7 @@ public function downloadCassettoQuarter(string $quarter, string $dal, string $al if ($this->isCassettoQuarterCompleteFor($amministratoreId, (int) $stabile->id, $dal, $al)) { Notification::make() ->title('Trimestre già completo') - ->body($this->safeUtf8("{$quarter} {$anno} risulta già scaricato (COMPLETO).")) + ->body($this->safeUtf8("{$quarter} {$anno} risulta già scaricato (COMPLETO). Per rieseguire lo stesso periodo usa 'Scarica da Cassetto Fiscale' e abilita 'Reimport / completa' oppure 'Non saltare'.")) ->warning() ->send(); return; @@ -305,6 +337,44 @@ private function isCassettoQuarterCompleteFor(int $amministratoreId, int $stabil return $errors === 0 && $filesTotal > 0 && ($imported + $duplicates) >= $filesTotal; } + private function resolveStabileSoggettoCf(Stabile $stabile): string + { + $cf = trim((string) ($stabile->codice_fiscale ?? '')); + + if ($cf === '') { + try { + $stabile->loadMissing('rubrica'); + $cf = trim((string) ($stabile->rubrica?->codice_fiscale ?? '')); + } catch (\Throwable) { + // ignore + } + } + + return strtoupper(trim($cf)); + } + + private function isLikelyTechnicalFeShell(Stabile $stabile): bool + { + $stabileCf = $this->resolveStabileSoggettoCf($stabile); + $adminCf = strtoupper(trim((string) ($stabile->amministratore?->codice_fiscale_studio ?? ''))); + $code = strtoupper(trim((string) ($stabile->codice_stabile ?? ''))); + $legacyCode = trim((string) ($stabile->cod_stabile ?? '')); + + return $stabileCf !== '' + && $adminCf !== '' + && $stabileCf === $adminCf + && (str_starts_with($code, 'FE') || $legacyCode === ''); + } + + private function notifyTechnicalFeShellBlocked(): void + { + Notification::make() + ->title('Stabile tecnico FE separato') + ->body('Lo scarico è bloccato perché lo stabile attivo usa il codice fiscale dello studio/amministratore. Se devi scaricare le fatture del condominio, cambia lo stabile attivo in topbar e seleziona il condominio reale.') + ->warning() + ->send(); + } + public function getArchivioRigheVisteCount(): int { $records = $this->getTableRecords(); @@ -602,7 +672,7 @@ protected function getHeaderActions(): array ->form([ Placeholder::make('stabile_attivo_info') ->label('Stabile di destinazione') - ->content(fn(): string => $this->getActiveStabileLabelProperty()), + ->content(fn(): string => $this->getActiveStabileDestinationSummaryProperty()), Select::make('anno') ->label('Anno') @@ -665,6 +735,9 @@ protected function getHeaderActions(): array Toggle::make('force_update') ->label('Reimport / completa anche se già presente') ->default(false), + Toggle::make('no_skip') + ->label('Non saltare se lo stesso periodo ha già un log OK') + ->default(false), Select::make('importa_righe') ->label('Import righe') ->options([ @@ -691,6 +764,11 @@ protected function getHeaderActions(): array return; } + if ($this->isLikelyTechnicalFeShell($stabile)) { + $this->notifyTechnicalFeShellBlocked(); + return; + } + $amministratore = $stabile->amministratore; if (! $amministratore) { Notification::make()->title('Amministratore non trovato')->danger()->send(); @@ -707,11 +785,13 @@ protected function getHeaderActions(): array } $forceUpdate = (bool) ($data['force_update'] ?? false); + $noSkip = (bool) ($data['no_skip'] ?? false); + $allowRerun = $forceUpdate || $noSkip; // Blocco trimestri: se il trimestre è già completo, evita ri-scarico (salvo force_update). $periodo = (string) ($data['periodo'] ?? ''); $anno = is_numeric($data['anno'] ?? null) ? (int) $data['anno'] : (int) $dal->format('Y'); - if (! $forceUpdate && in_array($periodo, ['Q1', 'Q2', 'Q3', 'Q4'], true)) { + if (! $allowRerun && in_array($periodo, ['Q1', 'Q2', 'Q3', 'Q4'], true)) { [$expectedDal, $expectedAl] = match ($periodo) { 'Q2' => [$anno . '-04-01', $anno . '-06-30'], 'Q3' => [$anno . '-07-01', $anno . '-09-30'], @@ -732,7 +812,7 @@ protected function getHeaderActions(): array } // Anti-duplicati veloce: se esiste già un OK per lo stesso periodo, non avviamo niente. - if (! $forceUpdate) { + if (! $allowRerun) { $cf = trim((string) ($stabile->codice_fiscale ?? '')); if ($cf === '') { try { @@ -797,6 +877,7 @@ protected function getHeaderActions(): array (int) $user->id, [ 'force_update' => (bool) ($data['force_update'] ?? false), + 'no_skip' => (bool) ($data['no_skip'] ?? false), 'importa_righe' => (string) ($data['importa_righe'] ?? 'auto'), 'create_fornitore_if_missing' => (bool) ($data['create_fornitore_if_missing'] ?? true), ], @@ -810,7 +891,7 @@ protected function getHeaderActions(): array Notification::make() ->title('Scarico avviato') - ->body("Operazione avviata in background. Se già scaricato, verrà saltato automaticamente.\nAggiorna tra poco e controlla la sezione 'Ultimi scarichi'.") + ->body("Operazione avviata in background. Se hai abilitato 'Reimport / completa' o 'Non saltare', lo stesso periodo verrà rieseguito.\nAggiorna tra poco e controlla la sezione 'Ultimi scarichi'.") ->success() ->send(); }), diff --git a/app/Filament/Pages/Supporto/TicketAcqua.php b/app/Filament/Pages/Supporto/TicketAcqua.php index ecddf43..d4bfb26 100644 --- a/app/Filament/Pages/Supporto/TicketAcqua.php +++ b/app/Filament/Pages/Supporto/TicketAcqua.php @@ -128,6 +128,24 @@ public function updatedUnitaId(): void $this->syncSuggestedReaderName(); } + public function updatedUnitaSearch(): void + { + $needle = trim($this->unitaSearch); + if ($needle === '') { + return; + } + + $filtered = $this->getFilteredUnitaOptionsProperty(); + if (count($filtered) === 1) { + $this->applySelectedUnita((int) ($filtered[0]['id'] ?? 0)); + } + } + + public function selectUnita(int $unitaId): void + { + $this->applySelectedUnita($unitaId, true); + } + public function updatedPendingWaterPhotos(): void { $this->appendPendingWaterPhotos(); @@ -567,6 +585,14 @@ public function getFilteredUnitaOptionsProperty(): array return array_values(array_filter($this->unitaOptions, fn(array $option): bool => str_contains((string) ($option['search_text'] ?? ''), $needle))); } + /** + * @return array + */ + public function getLightUnitaSearchResultsProperty(): array + { + return array_slice($this->getFilteredUnitaOptionsProperty(), 0, 10); + } + private function loadPreviousReading(): void { $this->previousReading = null; @@ -875,12 +901,7 @@ private function resolveUnitReaderLabel(UnitaImmobiliare $unita): string } } - $historic = $unita->nominativiStorici - ?->sortByDesc(fn($item) => (string) ($item->data_fine ?? '9999-12-31')) - ->sortByDesc(fn($item) => (string) ($item->data_inizio ?? '')) - ->first(); - - $historicLabel = trim((string) ($historic->nominativo ?? '')); + $historicLabel = $this->resolveRelevantHistoricNames($unita)[0] ?? ''; if ($historicLabel !== '') { return $historicLabel; } @@ -907,12 +928,7 @@ private function resolveUnitSearchNames(UnitaImmobiliare $unita): array } } - foreach (($unita->nominativiStorici ?? collect()) as $historic) { - $label = trim((string) ($historic->nominativo ?? '')); - if ($label !== '') { - $names[] = $label; - } - } + $names = array_merge($names, $this->resolveRelevantHistoricNames($unita)); $fallback = trim((string) ($unita->denominazione ?? '')); if ($fallback !== '') { @@ -922,6 +938,70 @@ private function resolveUnitSearchNames(UnitaImmobiliare $unita): array return array_values(array_unique(array_filter($names))); } + /** + * @return array + */ + private function resolveRelevantHistoricNames(UnitaImmobiliare $unita): array + { + $today = now()->toDateString(); + $historic = collect($unita->nominativiStorici ?? []); + + $current = $historic + ->filter(function ($item) use ($today): bool { + $start = trim((string) ($item->data_inizio ?? '')); + $end = trim((string) ($item->data_fine ?? '')); + + return ($start === '' || $start <= $today) + && ($end === '' || $end >= $today); + }) + ->map(fn($item): string => trim((string) ($item->nominativo ?? ''))) + ->filter() + ->unique() + ->values() + ->all(); + + if ($current !== []) { + return $current; + } + + return $historic + ->sortByDesc(fn($item): string => sprintf( + '%s|%s|%010d', + (string) ($item->data_fine ?? '9999-12-31'), + (string) ($item->data_inizio ?? ''), + (int) ($item->id ?? 0) + )) + ->map(fn($item): string => trim((string) ($item->nominativo ?? ''))) + ->filter() + ->unique() + ->take(1) + ->values() + ->all(); + } + + private function applySelectedUnita(int $unitaId, bool $syncSearchLabel = false): void + { + if ($unitaId <= 0) { + return; + } + + foreach ($this->unitaOptions as $option) { + if ((int) ($option['id'] ?? 0) !== $unitaId) { + continue; + } + + $this->unitaId = $unitaId; + if ($syncSearchLabel) { + $this->unitaSearch = (string) ($option['label'] ?? ''); + } + + $this->loadPreviousReading(); + $this->syncSuggestedReaderName(); + + return; + } + } + private function buildUnitNaturalSortKey(UnitaImmobiliare $unita, string $readerLabel): string { $parts = array_filter([ diff --git a/app/Filament/Pages/Supporto/TicketAcquaLight.php b/app/Filament/Pages/Supporto/TicketAcquaLight.php index 73eeaa5..110b02f 100644 --- a/app/Filament/Pages/Supporto/TicketAcquaLight.php +++ b/app/Filament/Pages/Supporto/TicketAcquaLight.php @@ -1,7 +1,9 @@ resetLightUnitSelection(); + } + + public function updatedStabileId(): void + { + parent::updatedStabileId(); + + $this->resetLightUnitSelection(); + } + + private function resetLightUnitSelection(): void + { + $user = Auth::user(); + + $this->unitaId = null; + $this->unitaSearch = ''; + $this->previousReading = null; + $this->suggestedReaderName = null; + + if ($user instanceof User) { + $this->rilevatoreNome = trim((string) $user->name) ?: $this->rilevatoreNome; + } + } } diff --git a/app/Services/Consumi/ConsumiAcquaIngestionService.php b/app/Services/Consumi/ConsumiAcquaIngestionService.php index dc04c24..a871010 100644 --- a/app/Services/Consumi/ConsumiAcquaIngestionService.php +++ b/app/Services/Consumi/ConsumiAcquaIngestionService.php @@ -32,6 +32,25 @@ public function ingest( return ['status' => 'error', 'message' => 'Stabile non disponibile']; } + $storedPayload = []; + $storedRaw = is_string($fattura->consumo_raw ?? null) ? trim((string) $fattura->consumo_raw) : ''; + if ($storedRaw !== '') { + $decoded = json_decode($storedRaw, true); + if (is_array($decoded) && (($decoded['type'] ?? null) === 'acqua')) { + $storedPayload = $decoded; + } + } + + if ((empty($parsed['codici']) || ! is_array($parsed['codici'])) && is_array($storedPayload['codici'] ?? null)) { + $parsed['codici'] = $storedPayload['codici']; + } + if ((empty($parsed['contatore']) || ! is_array($parsed['contatore'])) && is_array($storedPayload['contatore'] ?? null)) { + $parsed['contatore'] = $storedPayload['contatore']; + } + if ((empty($parsed['consumi']) || ! is_array($parsed['consumi'])) && is_array($storedPayload['consumi'] ?? null)) { + $parsed['consumi'] = $storedPayload['consumi']; + } + $codici = is_array($parsed['codici'] ?? null) ? $parsed['codici'] : []; $contatore = is_array($parsed['contatore'] ?? null) ? $parsed['contatore'] : []; $consumi = is_array($parsed['consumi'] ?? null) ? $parsed['consumi'] : []; diff --git a/resources/views/filament/components/footer.blade.php b/resources/views/filament/components/footer.blade.php index 7be5041..8b13789 100644 --- a/resources/views/filament/components/footer.blade.php +++ b/resources/views/filament/components/footer.blade.php @@ -1,48 +1 @@ -@php - $user = \Illuminate\Support\Facades\Auth::user(); - $version = config('netgescon.version') ?? config('app.version'); -@endphp -@once - -@endonce - - diff --git a/resources/views/filament/pages/supporto/ticket-acqua-light.blade.php b/resources/views/filament/pages/supporto/ticket-acqua-light.blade.php index a8fce41..93f7588 100644 --- a/resources/views/filament/pages/supporto/ticket-acqua-light.blade.php +++ b/resources/views/filament/pages/supporto/ticket-acqua-light.blade.php @@ -46,19 +46,44 @@ - + @php + $searchResults = $this->lightUnitaSearchResults; + $selectedUnit = collect($this->unitaOptions)->firstWhere('id', (int) ($unitaId ?? 0)); + $searchTerm = trim((string) $unitaSearch); + @endphp +
+ @if($selectedUnit) +
+
Unità selezionata
+
{{ $selectedUnit['label'] }}
+ @if(filled($selectedUnit['reader_label'] ?? null)) +
Nominativo collegato: {{ $selectedUnit['reader_label'] }}
+ @endif +
+ @elseif($searchTerm === '') +
Digita almeno parte del nominativo o del codice unità per selezionare rapidamente il destinatario corretto.
+ @endif + + @if($searchTerm !== '' && $searchResults !== []) +
+ @foreach($searchResults as $option) + + @endforeach +
+ @elseif($searchTerm !== '' && $searchResults === []) +
Nessuna unità trovata per questa ricerca nello stabile selezionato.
+ @endif +