From 432f0565ab43f0df8f59d9725c8190978df8a198 Mon Sep 17 00:00:00 2001 From: michele Date: Wed, 8 Apr 2026 16:04:23 +0000 Subject: [PATCH] Supporto/fornitori sync and Google OAuth refresh hardening --- .../GesconRepairNominativiContactsCommand.php | 14 +- .../NetgesconQaMillesimiSpeseCommand.php | 399 ++++++++++++++++++ app/Console/Commands/SmdrListenCommand.php | 36 +- .../SyncGesconFornitoriLegacyCommand.php | 103 +++++ .../Concerns/ResolvesOperatoreContext.php | 10 +- .../Pages/Fornitore/ProdottiCatalogo.php | 26 +- .../Pages/Fornitore/RubricaClienti.php | 54 +-- .../Fornitore/TicketInterventoScheda.php | 48 +-- .../Pages/Gescon/ImportazioneArchivi.php | 52 +-- .../Pages/Supporto/AggiornamentoLauncher.php | 2 +- .../Pages/Supporto/GestioneAssicurazioni.php | 36 +- app/Filament/Pages/Supporto/Modifiche.php | 4 +- app/Filament/Pages/Supporto/TicketMobile.php | 26 +- .../Integrations/GoogleOAuthController.php | 17 +- app/Models/TicketInterventoSessione.php | 2 +- app/Services/Cti/PbxRoutingService.php | 10 +- .../Support/TicketAttachmentUploadService.php | 4 +- ...ons_and_materials_to_ticket_interventi.php | 2 +- docs/00-TODO-COPILOT-TRACKER.md | 23 +- docs/ai/TOPICS_INDEX.md | 1 + docs/ai/procedure/current-topic.txt | 2 +- docs/ai/topics/import-unita-cod-cond.md | 57 +++ docs/support/AGGIORNAMENTI-STAGING-GIT.md | 19 +- .../FORNITORE-OPERATIVITA-ROADMAP-2026-04.md | 2 +- .../components/stabile-attivo.blade.php | 33 +- .../supporto/aggiornamento-launcher.blade.php | 6 +- .../supporto/assistenza-legacy.blade.php | 8 +- .../pages/supporto/ticket-scheda.blade.php | 4 +- 28 files changed, 816 insertions(+), 184 deletions(-) create mode 100644 app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php create mode 100644 app/Console/Commands/SyncGesconFornitoriLegacyCommand.php create mode 100644 docs/ai/topics/import-unita-cod-cond.md diff --git a/app/Console/Commands/GesconRepairNominativiContactsCommand.php b/app/Console/Commands/GesconRepairNominativiContactsCommand.php index ff7d796..ae99996 100644 --- a/app/Console/Commands/GesconRepairNominativiContactsCommand.php +++ b/app/Console/Commands/GesconRepairNominativiContactsCommand.php @@ -67,10 +67,10 @@ public function handle(): int } $stats = [ - 'processed' => 0, + 'processed' => 0, 'persona_updated' => 0, 'rubrica_updated' => 0, - 'no_match' => 0, + 'no_match' => 0, 'phone_conflicts' => 0, ]; @@ -100,9 +100,9 @@ public function handle(): int } /** @var RubricaUniversale $best */ - $best = $rubriche->first(); + $best = $rubriche->first(); $canonicalRubricaId = $this->extractRubricaIdFromPersonaNote((string) ($row->note ?? '')); - $canonicalRubrica = $canonicalRubricaId > 0 ? RubricaUniversale::query()->find($canonicalRubricaId) : null; + $canonicalRubrica = $canonicalRubricaId > 0 ? RubricaUniversale::query()->find($canonicalRubricaId) : null; if (! $canonicalRubrica instanceof RubricaUniversale) { $canonicalRubrica = $best; } @@ -223,8 +223,8 @@ private function buildPersonaUpdates(object $row, RubricaUniversale $best, array $bestMobile = $this->normalizePhone((string) ($best->telefono_cellulare ?: $best->telefono_ufficio ?: $best->telefono_casa)); $bestOffice = $this->normalizePhone((string) ($best->telefono_ufficio ?: $best->telefono_casa)); - $bestEmail = $this->normalizeEmail((string) ($best->email ?? '')); - $bestPec = $this->normalizeEmail((string) ($best->pec ?? '')); + $bestEmail = $this->normalizeEmail((string) ($best->email ?? '')); + $bestPec = $this->normalizeEmail((string) ($best->pec ?? '')); if (! filled($row->telefono_principale) && $bestMobile !== null && ! $this->phoneExistsOnOtherPersona($bestMobile, (int) $row->persona_id)) { $updates['telefono_principale'] = $bestMobile; @@ -267,4 +267,4 @@ private function buildRubricaUpdates(RubricaUniversale $canonical, RubricaUniver return $updates; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php b/app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php new file mode 100644 index 0000000..0830eb8 --- /dev/null +++ b/app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php @@ -0,0 +1,399 @@ +error('Tabella stabili non disponibile.'); + return self::FAILURE; + } + + [$stabileId, $codiceStabile] = $this->resolveStabileScope(); + if (! $stabileId || $codiceStabile === '') { + $this->error('Serve --stabile=CODICE oppure --stabile_id=ID valido.'); + return self::FAILURE; + } + + $legacyYear = $this->normalizeOption($this->option('anno')); + $showMissing = max(1, (int) ($this->option('show-missing') ?: 20)); + + $this->info('QA millesimi/spese per stabile ' . $codiceStabile . ' (#' . $stabileId . ')' . ($legacyYear ? ' anno ' . $legacyYear : '')); + + $activeUnitsQuery = DB::table('unita_immobiliari') + ->where('stabile_id', $stabileId) + ->whereNull('deleted_at'); + + $activeUnits = (clone $activeUnitsQuery)->count(); + + $unitsWithNominativi = 0; + if (Schema::hasTable('unita_immobiliare_nominativi')) { + $unitsWithNominativi = DB::table('unita_immobiliare_nominativi as uin') + ->join('unita_immobiliari as ui', 'ui.id', '=', 'uin.unita_immobiliare_id') + ->where('ui.stabile_id', $stabileId) + ->whereNull('ui.deleted_at') + ->distinct('uin.unita_immobiliare_id') + ->count('uin.unita_immobiliare_id'); + } + $unitsWithoutNominativi = max(0, $activeUnits - $unitsWithNominativi); + + $tabelleCount = Schema::hasTable('tabelle_millesimali') + ? DB::table('tabelle_millesimali')->where('stabile_id', $stabileId)->count() + : 0; + + $detailRows = 0; + $detailZeroRows = 0; + $detailZeroOnUnitsWithoutNominativi = 0; + $tablesWithoutPositiveRows = collect(); + if (Schema::hasTable('dettaglio_millesimi') && Schema::hasTable('tabelle_millesimali')) { + $detailBase = DB::table('dettaglio_millesimi as dm') + ->join('tabelle_millesimali as tm', 'tm.id', '=', 'dm.tabella_millesimale_id') + ->where('tm.stabile_id', $stabileId); + + $detailRows = (clone $detailBase)->count(); + $detailZeroRows = (clone $detailBase) + ->where(function ($q): void { + $q->whereNull('dm.millesimi')->orWhere('dm.millesimi', '<=', 0); + }) + ->count(); + + if (Schema::hasTable('unita_immobiliare_nominativi')) { + $detailZeroOnUnitsWithoutNominativi = (clone $detailBase) + ->join('unita_immobiliari as ui', 'ui.id', '=', 'dm.unita_immobiliare_id') + ->where(function ($q): void { + $q->whereNull('dm.millesimi')->orWhere('dm.millesimi', '<=', 0); + }) + ->whereNotExists(function ($sub): void { + $sub->selectRaw('1') + ->from('unita_immobiliare_nominativi as uin') + ->whereColumn('uin.unita_immobiliare_id', 'dm.unita_immobiliare_id'); + }) + ->count(); + } + + $tablesWithoutPositiveRows = DB::table('tabelle_millesimali as tm') + ->leftJoin('dettaglio_millesimi as dm', function ($join): void { + $join->on('dm.tabella_millesimale_id', '=', 'tm.id') + ->where('dm.millesimi', '>', 0); + }) + ->where('tm.stabile_id', $stabileId) + ->groupBy('tm.id', 'tm.codice_tabella', 'tm.nome_tabella', 'tm.denominazione') + ->havingRaw('COUNT(dm.id) = 0') + ->selectRaw('COALESCE(tm.codice_tabella, tm.nome_tabella, tm.denominazione, CAST(tm.id AS CHAR)) as codice') + ->pluck('codice'); + } + + $vociCount = Schema::hasTable('voci_spesa') + ? DB::table('voci_spesa')->where('stabile_id', $stabileId)->count() + : 0; + $vociSenzaTabella = Schema::hasTable('voci_spesa') + ? DB::table('voci_spesa') + ->where('stabile_id', $stabileId) + ->where(function ($q) : void { + $q->whereNull('tabella_millesimale_default_id')->orWhere('tabella_millesimale_default_id', 0); + }) + ->count() + : 0; + $vociConTabellaOrfana = (Schema::hasTable('voci_spesa') && Schema::hasTable('tabelle_millesimali')) + ? DB::table('voci_spesa as vs') + ->leftJoin('tabelle_millesimali as tm', 'tm.id', '=', 'vs.tabella_millesimale_default_id') + ->where('vs.stabile_id', $stabileId) + ->whereNotNull('vs.tabella_millesimale_default_id') + ->whereNull('tm.id') + ->count() + : 0; + + $staging = $this->collectStagingMetrics($stabileId, $codiceStabile, $legacyYear); + + $this->line('Unita attive: ' . $activeUnits); + $this->line('Unita con nominativi: ' . $unitsWithNominativi); + $this->line('Unita senza nominativi: ' . $unitsWithoutNominativi); + $this->line('Tabelle millesimali dominio: ' . $tabelleCount); + $this->line('Righe dettaglio millesimi: ' . $detailRows); + $this->line('Righe dettaglio a zero/null: ' . $detailZeroRows); + $this->line('Righe a zero su unita senza nominativi: ' . $detailZeroOnUnitsWithoutNominativi); + $this->line('Voci spesa dominio: ' . $vociCount); + $this->line('Voci senza tabella default: ' . $vociSenzaTabella); + $this->line('Voci con tabella orfana: ' . $vociConTabellaOrfana); + + if ($staging['available']) { + $this->line('Staging tabelle legacy: ' . $staging['headers_count'] . ' intestazioni, ' . $staging['detail_codes_count'] . ' codici con dettagli'); + $this->line('Staging voci legacy: ' . $staging['voci_count']); + $this->line('Staging straordinarie legacy: ' . $staging['straord_count']); + $this->line('Staging frazionamenti: Fraz_gen=' . $staging['fraz_gen_count'] . ', fraz_dett=' . $staging['fraz_dett_count']); + } else { + $this->warn('Connessione/tabelle staging non disponibili per il confronto completo.'); + } + + $exitCode = self::SUCCESS; + + if ($tablesWithoutPositiveRows->isNotEmpty()) { + $exitCode = self::INVALID; + $this->warn('Tabelle senza alcuna riga millesimale positiva: ' . $tablesWithoutPositiveRows->take($showMissing)->implode(', ')); + } + + if ($staging['available']) { + if (! empty($staging['missing_table_codes'])) { + $exitCode = self::INVALID; + $this->warn('Codici tabella presenti in staging ma mancanti nel dominio: ' . implode(', ', array_slice($staging['missing_table_codes'], 0, $showMissing))); + } + if (! empty($staging['missing_voci_codes'])) { + $exitCode = self::INVALID; + $this->warn('Codici voce presenti in staging ma mancanti nel dominio: ' . implode(', ', array_slice($staging['missing_voci_codes'], 0, $showMissing))); + } + if ($staging['fraz_gen_count'] > 0 || $staging['fraz_dett_count'] > 0) { + $this->warn('Archivio frazionamenti legacy presente: verificare Z01/Z02 prima del via libera contabile.'); + if (($staging['z_fraz_voci_missing'] ?? []) !== []) { + $exitCode = self::INVALID; + $this->warn('Voci Z con frazionamento presenti in staging ma non allineate nel dominio: ' . implode(', ', array_slice($staging['z_fraz_voci_missing'], 0, $showMissing))); + } + } + } + + if ($unitsWithoutNominativi > 0 || $detailZeroOnUnitsWithoutNominativi > 0 || $vociConTabellaOrfana > 0) { + $exitCode = self::INVALID; + } + + if ($exitCode === self::SUCCESS) { + $this->info('QA completato senza anomalie bloccanti.'); + } else { + $this->warn('QA completato con anomalie da correggere prima del riallineamento definitivo.'); + } + + return $exitCode; + } + + private function resolveStabileScope(): array + { + $stabileId = (int) ($this->option('stabile_id') ?: 0); + if ($stabileId > 0) { + $codice = DB::table('stabili')->where('id', $stabileId)->value('codice_stabile'); + return [$stabileId, $this->normalizeOption($codice) ?? '']; + } + + $codiceInput = $this->normalizeCode((string) ($this->option('stabile') ?? '')); + if ($codiceInput === '') { + return [0, '']; + } + + $trimmed = ltrim($codiceInput, '0'); + $stabile = DB::table('stabili') + ->where(function ($q) use ($codiceInput, $trimmed): void { + $q->where('codice_stabile', $codiceInput); + if ($trimmed !== '' && $trimmed !== $codiceInput) { + $q->orWhere('codice_stabile', $trimmed); + } + }) + ->select('id', 'codice_stabile') + ->first(); + + if (! $stabile) { + return [0, '']; + } + + return [(int) $stabile->id, (string) $stabile->codice_stabile]; + } + + private function collectStagingMetrics(int $stabileId, string $codiceStabile, ?string $legacyYear): array + { + $result = [ + 'available' => false, + 'headers_count' => 0, + 'detail_codes_count' => 0, + 'voci_count' => 0, + 'straord_count' => 0, + 'fraz_gen_count' => 0, + 'fraz_dett_count' => 0, + 'missing_table_codes' => [], + 'missing_voci_codes' => [], + 'z_fraz_voci_missing' => [], + ]; + + if (! config('database.connections.gescon_import')) { + return $result; + } + + $result['available'] = true; + $code = $this->normalizeCode($codiceStabile); + + $domainTableCodes = Schema::hasTable('tabelle_millesimali') + ? DB::table('tabelle_millesimali') + ->where('stabile_id', $stabileId) + ->selectRaw('UPPER(TRIM(COALESCE(codice_tabella, nome_tabella, denominazione))) as codice') + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all() + : []; + + $domainVoceCodes = Schema::hasTable('voci_spesa') + ? DB::table('voci_spesa') + ->where('stabile_id', $stabileId) + ->selectRaw('UPPER(TRIM(codice)) as codice') + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all() + : []; + if (Schema::hasTable('voci_spesa') && Schema::hasColumn('voci_spesa', 'legacy_codice')) { + $domainLegacyVoceCodes = DB::table('voci_spesa') + ->where('stabile_id', $stabileId) + ->selectRaw('UPPER(TRIM(legacy_codice)) as codice') + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all(); + $domainVoceCodes = array_values(array_unique(array_merge($domainVoceCodes, $domainLegacyVoceCodes))); + } + + if (Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) { + $headersQuery = DB::connection('gescon_import')->table('tabelle_millesimali'); + if (Schema::connection('gescon_import')->hasColumn('tabelle_millesimali', 'cod_stabile')) { + $headersQuery->where('cod_stabile', $code); + } + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('tabelle_millesimali', 'legacy_year')) { + $headersQuery->where('legacy_year', $legacyYear); + } + $headerCodes = $headersQuery + ->selectRaw('UPPER(TRIM(COALESCE(cod_tabella, denominazione, descrizione))) as codice') + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all(); + $result['headers_count'] = count($headerCodes); + $result['missing_table_codes'] = array_values(array_diff($headerCodes, $domainTableCodes)); + } + + if (Schema::connection('gescon_import')->hasTable('dett_tab')) { + $detailQuery = DB::connection('gescon_import')->table('dett_tab as d'); + if (Schema::connection('gescon_import')->hasTable('condomin')) { + $detailQuery->join('condomin as c', 'c.cod_cond', '=', 'd.id_cond') + ->where('c.cod_stabile', $code); + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_year')) { + $detailQuery->where('c.legacy_year', $legacyYear); + } + } + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('dett_tab', 'legacy_year')) { + $detailQuery->where('d.legacy_year', $legacyYear); + } + $detailCodes = $detailQuery + ->selectRaw('UPPER(TRIM(d.cod_tab)) as codice') + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all(); + $result['detail_codes_count'] = count($detailCodes); + if ($result['missing_table_codes'] === []) { + $result['missing_table_codes'] = array_values(array_diff($detailCodes, $domainTableCodes)); + } + } + + if (Schema::connection('gescon_import')->hasTable('voc_spe')) { + $vociQuery = DB::connection('gescon_import')->table('voc_spe'); + if (Schema::connection('gescon_import')->hasColumn('voc_spe', 'cod_stabile')) { + $vociQuery->where('cod_stabile', $code); + } + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) { + $vociQuery->where('legacy_year', $legacyYear); + } + $vocCodes = $vociQuery + ->selectRaw('UPPER(TRIM(cod)) as codice') + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all(); + $result['voci_count'] = count($vocCodes); + $result['missing_voci_codes'] = array_values(array_diff($vocCodes, $domainVoceCodes)); + + $zFractionsQuery = DB::connection('gescon_import')->table('voc_spe') + ->where('cod_stabile', $code); + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('voc_spe', 'legacy_year')) { + $zFractionsQuery->where('legacy_year', $legacyYear); + } + $zFractions = $zFractionsQuery + ->selectRaw('UPPER(TRIM(cod)) as codice') + ->whereIn('cod', ['Z01', 'Z02']) + ->pluck('codice') + ->filter() + ->unique() + ->values() + ->all(); + $result['z_fraz_voci_missing'] = array_values(array_diff($zFractions, $domainVoceCodes)); + } + + if (Schema::connection('gescon_import')->hasTable('straordinarie')) { + $straordQuery = DB::connection('gescon_import')->table('straordinarie'); + if (Schema::connection('gescon_import')->hasColumn('straordinarie', 'cod_stabile')) { + $straordQuery->where('cod_stabile', $code); + } + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('straordinarie', 'legacy_year')) { + $straordQuery->where('legacy_year', $legacyYear); + } + $result['straord_count'] = $straordQuery->count(); + } + + if (Schema::connection('gescon_import')->hasTable('fraz_gen')) { + $query = DB::connection('gescon_import')->table('fraz_gen'); + if (Schema::connection('gescon_import')->hasColumn('fraz_gen', 'cod_stabile')) { + $query->where('cod_stabile', $code); + } + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('fraz_gen', 'legacy_year')) { + $query->where('legacy_year', $legacyYear); + } + $result['fraz_gen_count'] = $query->count(); + } + + if (Schema::connection('gescon_import')->hasTable('fraz_dett')) { + $query = DB::connection('gescon_import')->table('fraz_dett'); + if ($legacyYear !== null && Schema::connection('gescon_import')->hasColumn('fraz_dett', 'legacy_year')) { + $query->where('legacy_year', $legacyYear); + } + $result['fraz_dett_count'] = $query->count(); + } + + return $result; + } + + private function normalizeOption(mixed $value): ?string + { + if (! is_string($value) && ! is_numeric($value)) { + return null; + } + + $normalized = trim((string) $value); + return $normalized === '' ? null : $normalized; + } + + private function normalizeCode(string $code): string + { + $code = trim($code); + if ($code === '') { + return ''; + } + + if (is_numeric($code)) { + return str_pad((string) ((int) $code), 4, '0', STR_PAD_LEFT); + } + + return strtoupper($code); + } +} diff --git a/app/Console/Commands/SmdrListenCommand.php b/app/Console/Commands/SmdrListenCommand.php index f99cb6b..9f0d91f 100644 --- a/app/Console/Commands/SmdrListenCommand.php +++ b/app/Console/Commands/SmdrListenCommand.php @@ -193,15 +193,15 @@ public function handle(): int 'status' => 'received', 'received_at' => $receivedAt, 'metadata' => [ - 'raw_line' => $line, - 'source_host' => $host, - 'source_port' => $port, - 'assigned_user_id' => $assignedUserId, - 'stabile_id' => $stabileId, - 'amministratore_id'=> $amministratoreId, - 'studio_role' => $routing['studio_role'], - 'studio_mode' => $routing['studio_mode'], - 'smdr' => [ + 'raw_line' => $line, + 'source_host' => $host, + 'source_port' => $port, + 'assigned_user_id' => $assignedUserId, + 'stabile_id' => $stabileId, + 'amministratore_id' => $amministratoreId, + 'studio_role' => $routing['studio_role'], + 'studio_mode' => $routing['studio_mode'], + 'smdr' => [ 'date' => $parsed['date'], 'time' => $parsed['time'], 'extension' => $parsed['extension'], @@ -245,6 +245,8 @@ public function handle(): int try { $lineSuffix = ! empty($parsed['co']) ? ' - linea ' . (string) $parsed['co'] : ''; + $isMissedResponseGroup = $this->isMissedResponseGroupCall($parsed, $routing); + ChiamataPostIt::query()->create([ 'telefono' => $phone, 'stabile_id' => $stabileId, @@ -253,7 +255,7 @@ public function handle(): int 'oggetto' => 'Chiamata in entrata da SMDR' . $lineSuffix, 'nota' => $line, 'direzione' => 'in_arrivo', - 'esito' => ($durationSeconds ?? 0) > 0 ? 'answered' : 'missed', + 'esito' => $isMissedResponseGroup || ($durationSeconds ?? 0) <= 0 ? 'missed' : 'answered', 'origine' => 'smdr', 'origine_id' => $fingerprint, 'durata_secondi' => $durationSeconds, @@ -518,4 +520,18 @@ private function shouldCreateOperationalPostIt(array $parsed): bool return ! empty($parsed['co']); } + + /** @param array $parsed */ + private function isMissedResponseGroupCall(array $parsed, array $routing): bool + { + $groupExtensions = ['601', '603']; + $groupExtension = preg_replace('/\D+/', '', (string) ($parsed['extension'] ?? '')) ?: ''; + if (! in_array($groupExtension, $groupExtensions, true)) { + return false; + } + + $targetExtension = preg_replace('/\D+/', '', (string) ($routing['target_extension'] ?? '')) ?: ''; + + return $targetExtension === '' || in_array($targetExtension, $groupExtensions, true); + } } diff --git a/app/Console/Commands/SyncGesconFornitoriLegacyCommand.php b/app/Console/Commands/SyncGesconFornitoriLegacyCommand.php new file mode 100644 index 0000000..87bd846 --- /dev/null +++ b/app/Console/Commands/SyncGesconFornitoriLegacyCommand.php @@ -0,0 +1,103 @@ + staging -> archivio locale -> tag legacy.'; + + public function handle(): int + { + $adminId = (int) $this->argument('amministratore_id'); + $mdb = (string) $this->option('mdb'); + $table = (string) $this->option('table'); + $dryRun = (bool) $this->option('dry-run'); + $skipTags = (bool) $this->option('skip-tags'); + $truncateStaging = (bool) $this->option('truncate-staging'); + $previewLimit = max(1, (int) ($this->option('preview-limit') ?: 10)); + + if ($adminId <= 0) { + $this->error('amministratore_id non valido.'); + return self::FAILURE; + } + + $this->info('Sync fornitori legacy per amministratore #' . $adminId . ($dryRun ? ' [DRY-RUN]' : '')); + + $stageParams = [ + 'tenant' => (string) $adminId, + '--mdb' => $mdb, + '--table' => $table, + ]; + if ($dryRun) { + $stageParams['--preview'] = true; + $stageParams['--limit'] = $previewLimit; + } + if ($truncateStaging && ! $dryRun) { + $stageParams['--truncate'] = true; + } + + $stageExit = Artisan::call('import:gescon-fornitori', $stageParams); + $stageOutput = trim((string) Artisan::output()); + $this->line('Step staging exit=' . $stageExit); + if ($stageOutput !== '') { + $this->line($stageOutput); + } + if ($stageExit !== 0) { + return self::FAILURE; + } + + $importParams = [ + 'amministratore_id' => (string) $adminId, + '--mdb' => $mdb, + '--table' => $table, + ]; + if ($dryRun) { + $importParams['--dry-run'] = true; + } + + $importExit = Artisan::call('gescon:import-fornitori-legacy', $importParams); + $importOutput = trim((string) Artisan::output()); + $this->line('Step archivio locale exit=' . $importExit); + if ($importOutput !== '') { + $this->line($importOutput); + } + if ($importExit !== 0) { + return self::FAILURE; + } + + if (! $skipTags) { + $tagParams = []; + if ($dryRun) { + $tagParams['--dry-run'] = true; + } + + $tagExit = Artisan::call('fornitori:import-legacy-tags', $tagParams); + $tagOutput = trim((string) Artisan::output()); + $this->line('Step tag legacy exit=' . $tagExit); + if ($tagOutput !== '') { + $this->line($tagOutput); + } + if ($tagExit !== 0) { + return self::FAILURE; + } + } + + $this->info($dryRun + ? 'Dry-run sync fornitori legacy completato.' + : 'Sync fornitori legacy completato.'); + + return self::SUCCESS; + } +} \ No newline at end of file diff --git a/app/Filament/Pages/Fornitore/Concerns/ResolvesOperatoreContext.php b/app/Filament/Pages/Fornitore/Concerns/ResolvesOperatoreContext.php index 54d42f9..be2b233 100644 --- a/app/Filament/Pages/Fornitore/Concerns/ResolvesOperatoreContext.php +++ b/app/Filament/Pages/Fornitore/Concerns/ResolvesOperatoreContext.php @@ -192,10 +192,10 @@ protected function buildInterventoRow(TicketIntervento $intervento): array */ protected function extractCallerData(string $descrizione): array { - $contatto = '-'; - $telefono = ''; - $problema = ''; - $email = ''; + $contatto = '-'; + $telefono = ''; + $problema = ''; + $email = ''; $riferimento = ''; $lines = preg_split('/\r\n|\r|\n/', $descrizione) ?: []; @@ -230,7 +230,7 @@ protected function extractCallerData(string $descrizione): array } if (str_starts_with($trim, 'Riferimento unità:')) { - $unita = trim((string) str_replace('Riferimento unità:', '', $trim)); + $unita = trim((string) str_replace('Riferimento unità:', '', $trim)); $riferimento = $riferimento !== '' ? ($riferimento . ' · ' . $unita) : $unita; } } diff --git a/app/Filament/Pages/Fornitore/ProdottiCatalogo.php b/app/Filament/Pages/Fornitore/ProdottiCatalogo.php index 5d5d0e2..a3592da 100644 --- a/app/Filament/Pages/Fornitore/ProdottiCatalogo.php +++ b/app/Filament/Pages/Fornitore/ProdottiCatalogo.php @@ -46,7 +46,7 @@ public static function canAccess(): bool $user = Auth::user(); return $user instanceof User - && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']); + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']); } public function mount(): void @@ -58,7 +58,7 @@ public function mount(): void return; } - $this->fornitoreId = (int) $fornitore->id; + $this->fornitoreId = (int) $fornitore->id; $this->fornitoreLabel = $this->getFornitoreLabel($fornitore); $this->refreshRows(); } @@ -75,7 +75,7 @@ public function refreshRows(): void return; } - $term = trim($this->search); + $term = trim($this->search); $normalized = preg_replace('/[^A-Za-z0-9]+/', '', strtoupper($term)) ?? ''; $products = Product::query() @@ -106,20 +106,18 @@ public function refreshRows(): void $this->rows = $products->map(function (Product $product): array { $identifier = $product->identifiers - ->first(fn(ProductIdentifier $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId) - ?? $product->identifiers->first(); + ->first(fn(ProductIdentifier $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId) ?? $product->identifiers->first(); $offer = $product->offers - ->first(fn(ProductOffer $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId) - ?? $product->offers->first(); + ->first(fn(ProductOffer $item): bool => (int) ($item->fornitore_id ?? 0) === (int) $this->fornitoreId) ?? $product->offers->first(); return [ - 'id' => (int) $product->id, + 'id' => (int) $product->id, 'internal_code' => (string) ($product->internal_code ?? ''), - 'label' => trim(implode(' ', array_filter([(string) $product->name, (string) ($product->brand ?? ''), (string) ($product->model ?? '')]))), - 'barcode' => (string) ($identifier->code_value ?? ''), - 'source' => (string) ($offer?->source_name ?: 'Catalogo interno'), - 'price' => $offer?->price_amount !== null ? number_format((float) $offer->price_amount, 2, ',', '.') . ' ' . (string) ($offer->currency ?? 'EUR') : '-', - 'external_url' => (string) ($offer?->referral_url ?: $offer?->external_url ?: $this->buildAmazonSearchUrl((string) $product->name)), + 'label' => trim(implode(' ', array_filter([(string) $product->name, (string) ($product->brand ?? ''), (string) ($product->model ?? '')]))), + 'barcode' => (string) ($identifier->code_value ?? ''), + 'source' => (string) ($offer?->source_name ?: 'Catalogo interno'), + 'price' => $offer?->price_amount !== null ? number_format((float) $offer->price_amount, 2, ',', '.') . ' ' . (string) ($offer->currency ?? 'EUR') : '-', + 'external_url' => (string) ($offer?->referral_url ?: $offer?->external_url ?: $this->buildAmazonSearchUrl((string) $product->name)), ]; })->all(); } @@ -144,4 +142,4 @@ protected function buildAmazonSearchUrl(string $term): string return 'https://www.amazon.it/s?' . http_build_query($query); } -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Fornitore/RubricaClienti.php b/app/Filament/Pages/Fornitore/RubricaClienti.php index 97e3cfc..531d061 100644 --- a/app/Filament/Pages/Fornitore/RubricaClienti.php +++ b/app/Filament/Pages/Fornitore/RubricaClienti.php @@ -48,7 +48,7 @@ public static function canAccess(): bool $user = Auth::user(); return $user instanceof User - && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']); + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']); } public function mount(): void @@ -60,9 +60,9 @@ public function mount(): void return; } - $this->fornitoreId = (int) $fornitore->id; - $this->fornitoreLabel = $this->getFornitoreLabel($fornitore); - $this->googleWorkspaceStatus = $this->resolveGoogleWorkspaceStatus($fornitore); + $this->fornitoreId = (int) $fornitore->id; + $this->fornitoreLabel = $this->getFornitoreLabel($fornitore); + $this->googleWorkspaceStatus = $this->resolveGoogleWorkspaceStatus($fornitore); $this->refreshRows(); } @@ -91,7 +91,7 @@ public function refreshRows(): void foreach ($items as $intervento) { $contact = $this->resolveCallerDataForRubrica($intervento); - $key = $contact['identity_key']; + $key = $contact['identity_key']; if ($key === '') { $key = 'ticket-' . (int) $intervento->ticket_id; @@ -113,14 +113,14 @@ public function refreshRows(): void if (! isset($grouped[$key])) { $grouped[$key] = [ - 'contatto' => $contact['contatto'], - 'telefono' => $contact['telefono'], - 'email' => $contact['email'], - 'stabile' => $contact['stabile'], - 'origine' => $contact['origine'], - 'ultimo_ticket_id' => (int) $intervento->ticket_id, + 'contatto' => $contact['contatto'], + 'telefono' => $contact['telefono'], + 'email' => $contact['email'], + 'stabile' => $contact['stabile'], + 'origine' => $contact['origine'], + 'ultimo_ticket_id' => (int) $intervento->ticket_id, 'ultima_scheda_url' => TicketInterventoScheda::getUrl(['record' => (int) $intervento->id], panel: 'admin-filament'), - 'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-', + 'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-', 'totale_interventi' => 1, ]; continue; @@ -162,19 +162,19 @@ protected function resolveCallerDataForRubrica(TicketIntervento $intervento): ar $contatto = $parsed['contatto']; $telefono = $parsed['telefono']; - $email = ''; - $origine = 'Descrizione ticket'; + $email = ''; + $origine = 'Descrizione ticket'; if ($ticket?->soggettoRichiedente) { $soggetto = $ticket->soggettoRichiedente; $contatto = trim((string) ($soggetto->ragione_sociale ?: trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? '')))) ?: $contatto; $telefono = trim((string) ($soggetto->telefono ?? '')) ?: $telefono; - $email = trim((string) ($soggetto->email ?? '')); - $origine = 'Richiedente ticket'; + $email = trim((string) ($soggetto->email ?? '')); + $origine = 'Richiedente ticket'; } elseif ($ticket?->apertoDaUser) { $contatto = trim((string) ($ticket->apertoDaUser->name ?? '')) ?: $contatto; - $email = trim((string) ($ticket->apertoDaUser->email ?? '')); - $origine = 'Utente apertura ticket'; + $email = trim((string) ($ticket->apertoDaUser->email ?? '')); + $origine = 'Utente apertura ticket'; } $identityKey = ''; @@ -187,11 +187,11 @@ protected function resolveCallerDataForRubrica(TicketIntervento $intervento): ar } return [ - 'contatto' => $contatto !== '' ? $contatto : 'Contatto non identificato', - 'telefono' => $telefono, - 'email' => $email, - 'origine' => $origine, - 'stabile' => (string) ($ticket?->stabile?->denominazione ?? '-'), + 'contatto' => $contatto !== '' ? $contatto : 'Contatto non identificato', + 'telefono' => $telefono, + 'email' => $email, + 'origine' => $origine, + 'stabile' => (string) ($ticket?->stabile?->denominazione ?? '-'), 'identity_key' => $identityKey, ]; } @@ -209,10 +209,10 @@ protected function resolveGoogleWorkspaceStatus(Fornitore $fornitore): ?array $oauth = (array) (($amministratore->impostazioni['google']['oauth'] ?? null) ?: []); return [ - 'connected' => (bool) ($oauth['connected'] ?? false), - 'email' => (string) ($oauth['email'] ?? ''), - 'name' => (string) ($oauth['name'] ?? ''), + 'connected' => (bool) ($oauth['connected'] ?? false), + 'email' => (string) ($oauth['email'] ?? ''), + 'name' => (string) ($oauth['name'] ?? ''), 'connected_at' => (string) ($oauth['connected_at'] ?? ''), ]; } -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Fornitore/TicketInterventoScheda.php b/app/Filament/Pages/Fornitore/TicketInterventoScheda.php index 5306d2d..7556b01 100644 --- a/app/Filament/Pages/Fornitore/TicketInterventoScheda.php +++ b/app/Filament/Pages/Fornitore/TicketInterventoScheda.php @@ -50,16 +50,16 @@ class TicketInterventoScheda extends Page /** @var array{contatto:string,telefono:string,email:string,problema:string,sorgente:string,riferimento:string,rubrica_url:?string,estratto_url:?string,stabile_url:?string,unita_label:string} */ public array $caller = [ - 'contatto' => '-', - 'telefono' => '', - 'email' => '', - 'problema' => '', - 'sorgente' => '', - 'riferimento' => '', - 'rubrica_url' => null, - 'estratto_url'=> null, - 'stabile_url' => null, - 'unita_label' => '', + 'contatto' => '-', + 'telefono' => '', + 'email' => '', + 'problema' => '', + 'sorgente' => '', + 'riferimento' => '', + 'rubrica_url' => null, + 'estratto_url' => null, + 'stabile_url' => null, + 'unita_label' => '', ]; /** @var array> */ @@ -1094,16 +1094,16 @@ protected function resolveCallerData(?Ticket $ticket): array $parsed = $this->extractCallerData((string) ($ticket?->descrizione ?? '')); $caller = [ - 'contatto' => $parsed['contatto'], - 'telefono' => $parsed['telefono'], - 'email' => $parsed['email'] ?? '', - 'problema' => $parsed['problema'] !== '' ? $parsed['problema'] : (string) ($ticket?->titolo ?? ''), - 'sorgente' => 'Descrizione ticket', - 'riferimento' => $this->buildCallerReference($ticket, (string) ($parsed['riferimento'] ?? '')), - 'rubrica_url' => null, - 'estratto_url'=> null, - 'stabile_url' => $ticket?->stabile ? StabileScheda::getUrl(['record' => (int) $ticket->stabile->id], panel: 'admin-filament') : null, - 'unita_label' => $ticket?->unitaImmobiliare ? $this->formatUnitaLabel($ticket->unitaImmobiliare) : '', + 'contatto' => $parsed['contatto'], + 'telefono' => $parsed['telefono'], + 'email' => $parsed['email'] ?? '', + 'problema' => $parsed['problema'] !== '' ? $parsed['problema'] : (string) ($ticket?->titolo ?? ''), + 'sorgente' => 'Descrizione ticket', + 'riferimento' => $this->buildCallerReference($ticket, (string) ($parsed['riferimento'] ?? '')), + 'rubrica_url' => null, + 'estratto_url' => null, + 'stabile_url' => $ticket?->stabile ? StabileScheda::getUrl(['record' => (int) $ticket->stabile->id], panel : 'admin-filament'): null, + 'unita_label' => $ticket?->unitaImmobiliare ? $this->formatUnitaLabel($ticket->unitaImmobiliare) : '', ]; if (($caller['contatto'] ?? '-') === '-' || trim((string) ($caller['contatto'] ?? '')) === '') { @@ -1120,9 +1120,9 @@ protected function resolveCallerData(?Ticket $ticket): array $caller['contatto'] = $label; } - $caller['telefono'] = trim((string) ($soggetto->telefono ?? '')) ?: $caller['telefono']; - $caller['email'] = trim((string) ($soggetto->email ?? '')) ?: $caller['email']; - $caller['sorgente'] = 'Richiedente collegato al ticket'; + $caller['telefono'] = trim((string) ($soggetto->telefono ?? '')) ?: $caller['telefono']; + $caller['email'] = trim((string) ($soggetto->email ?? '')) ?: $caller['email']; + $caller['sorgente'] = 'Richiedente collegato al ticket'; $caller['estratto_url'] = EstrattoContoSoggetto::getUrl(['record' => (int) $soggetto->id], panel: 'admin-filament'); $rubrica = $this->resolveCallerRubrica($ticket, $caller); @@ -1204,7 +1204,7 @@ private function resolveCallerRubrica(?Ticket $ticket, array $caller): ?RubricaU { $adminId = (int) ($ticket?->stabile?->amministratore_id ?? 0); - $queries = []; + $queries = []; $soggetto = $ticket?->soggettoRichiedente; if ($soggetto && filled($soggetto->codice_fiscale)) { $queries[] = fn() => RubricaUniversale::query()->where('codice_fiscale', strtoupper(trim((string) $soggetto->codice_fiscale))); diff --git a/app/Filament/Pages/Gescon/ImportazioneArchivi.php b/app/Filament/Pages/Gescon/ImportazioneArchivi.php index 31325ad..dad1680 100644 --- a/app/Filament/Pages/Gescon/ImportazioneArchivi.php +++ b/app/Filament/Pages/Gescon/ImportazioneArchivi.php @@ -717,29 +717,23 @@ public function aggiornaFornitoriGescon(): void } try { - $exit1 = Artisan::call('import:gescon-fornitori', [ - 'tenant' => (string) $amm->id, - '--mdb' => $mdb, - ]); - $out1 = trim((string) Artisan::output()); - - $exit2 = Artisan::call('gescon:import-fornitori-legacy', [ + $exit = Artisan::call('gescon:sync-fornitori-legacy', [ 'amministratore_id' => (string) $amm->id, '--mdb' => $mdb, ]); - $out2 = trim((string) Artisan::output()); + $out = trim((string) Artisan::output()); } catch (\Throwable $e) { Notification::make()->title('Aggiornamento fornitori fallito')->body($e->getMessage())->danger()->send(); return; } - if ($exit1 !== 0 || $exit2 !== 0) { - $snippet = $out2 !== '' ? $out2 : $out1; + if ($exit !== 0) { + $snippet = $out !== '' ? $out : ''; $snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : ''; Notification::make() ->title('Aggiornamento fornitori: errore') - ->body('Exit staging=' . $exit1 . ' | exit import=' . $exit2 . ($snippet !== '' ? "\n\n" . $snippet : '')) + ->body('Exit sync=' . $exit . ($snippet !== '' ? "\n\n" . $snippet : '')) ->danger() ->send(); return; @@ -792,24 +786,12 @@ public function refreshImportTagLegacyFornitoriGlobale(): void } try { - $exit1 = Artisan::call('import:gescon-fornitori', [ - 'tenant' => (string) $amm->id, - '--mdb' => $mdb, - ]); - $out1 = trim((string) Artisan::output()); - - $exit2 = Artisan::call('gescon:import-fornitori-legacy', [ + $exit = Artisan::call('gescon:sync-fornitori-legacy', [ 'amministratore_id' => (string) $amm->id, '--mdb' => $mdb, + '--dry-run' => $dryRun, ]); - $out2 = trim((string) Artisan::output()); - - $tagParams = []; - if ($dryRun) { - $tagParams['--dry-run'] = true; - } - $exit3 = Artisan::call('fornitori:import-legacy-tags', $tagParams); - $out3 = trim((string) Artisan::output()); + $out = trim((string) Artisan::output()); } catch (\Throwable $e) { Notification::make() ->title('Refresh + import TAG fallito') @@ -819,13 +801,13 @@ public function refreshImportTagLegacyFornitoriGlobale(): void return; } - if ($exit1 !== 0 || $exit2 !== 0 || $exit3 !== 0) { - $snippet = $out3 !== '' ? $out3 : ($out2 !== '' ? $out2 : $out1); + if ($exit !== 0) { + $snippet = $out !== '' ? $out : ''; $snippet = $snippet !== '' ? mb_substr($snippet, 0, 800) : ''; Notification::make() ->title('Refresh + import TAG: errore') - ->body('Exit staging=' . $exit1 . ' | import=' . $exit2 . ' | tags=' . $exit3 . ($snippet !== '' ? "\n\n" . $snippet : '')) + ->body('Exit sync=' . $exit . ($snippet !== '' ? "\n\n" . $snippet : '')) ->danger() ->send(); return; @@ -1013,22 +995,16 @@ public function eseguiImportSelezionato(EssentialImportService $service): void if (! empty($options['with_operazioni'])) { try { - $exit1 = Artisan::call('import:gescon-fornitori', [ - 'tenant' => (string) $amm->id, - '--mdb' => (string) ($state['fornitori_mdb'] ?? rtrim($options['path'], '/') . '/dbc/Fornitori.mdb'), - ]); - $outputs[] = trim((string) Artisan::output()); - - $exit2 = Artisan::call('gescon:import-fornitori-legacy', [ + $exit = Artisan::call('gescon:sync-fornitori-legacy', [ 'amministratore_id' => (string) $amm->id, '--mdb' => (string) ($state['fornitori_mdb'] ?? rtrim($options['path'], '/') . '/dbc/Fornitori.mdb'), ]); $outputs[] = trim((string) Artisan::output()); - if ($exit1 !== 0 || $exit2 !== 0) { + if ($exit !== 0) { Notification::make() ->title('Aggiornamento fornitori: errore') - ->body('Exit staging=' . $exit1 . ' | exit import=' . $exit2) + ->body('Exit sync=' . $exit) ->danger() ->send(); return; diff --git a/app/Filament/Pages/Supporto/AggiornamentoLauncher.php b/app/Filament/Pages/Supporto/AggiornamentoLauncher.php index 5fbc048..bc1e539 100644 --- a/app/Filament/Pages/Supporto/AggiornamentoLauncher.php +++ b/app/Filament/Pages/Supporto/AggiornamentoLauncher.php @@ -19,4 +19,4 @@ class AggiornamentoLauncher extends Modifiche protected static ?string $slug = 'supporto/aggiornamento-nodo'; protected string $view = 'filament.pages.supporto.aggiornamento-launcher'; -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Supporto/GestioneAssicurazioni.php b/app/Filament/Pages/Supporto/GestioneAssicurazioni.php index 57cc93a..ec52886 100644 --- a/app/Filament/Pages/Supporto/GestioneAssicurazioni.php +++ b/app/Filament/Pages/Supporto/GestioneAssicurazioni.php @@ -36,7 +36,7 @@ class GestioneAssicurazioni extends Page public array $totals = [ 'aperte' => 0, 'chiuse' => 0, - 'tutte' => 0, + 'tutte' => 0, ]; public static function canAccess(): bool @@ -44,12 +44,12 @@ public static function canAccess(): bool $user = Auth::user(); return $user instanceof User - && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } public function mount(): void { - $this->status = (string) request()->query('status', 'aperte'); + $this->status = (string) request()->query('status', 'aperte'); $this->focusTicketId = (int) request()->query('ticket', 0) ?: null; $this->refreshData(); } @@ -69,7 +69,7 @@ public function refreshData(): void { $stabileIds = $this->resolveAccessibleStabileIds(); if ($stabileIds === []) { - $this->rows = []; + $this->rows = []; $this->totals = ['aperte' => 0, 'chiuse' => 0, 'tutte' => 0]; return; } @@ -96,23 +96,23 @@ public function refreshData(): void ->get() ->map(function (InsuranceClaim $claim): array { return [ - 'id' => (int) $claim->id, - 'ticket_id' => (int) $claim->ticket_id, - 'ticket_title' => (string) ($claim->ticket->titolo ?? '-'), - 'stabile' => (string) ($claim->ticket->stabile->denominazione ?? '-'), - 'claim_number' => (string) ($claim->claim_number ?? ''), - 'policy_reference' => (string) ($claim->policy_reference ?? ''), - 'policy_label' => trim(implode(' · ', array_filter([ + 'id' => (int) $claim->id, + 'ticket_id' => (int) $claim->ticket_id, + 'ticket_title' => (string) ($claim->ticket->titolo ?? '-'), + 'stabile' => (string) ($claim->ticket->stabile->denominazione ?? '-'), + 'claim_number' => (string) ($claim->claim_number ?? ''), + 'policy_reference' => (string) ($claim->policy_reference ?? ''), + 'policy_label' => trim(implode(' · ', array_filter([ (string) ($claim->insurancePolicy->company_name ?? ''), (string) ($claim->insurancePolicy->policy_number ?? ''), ]))), - 'status' => (string) ($claim->status ?? 'aperta'), - 'opened_at' => optional($claim->opened_at)->format('d/m/Y H:i') ?: '-', - 'next_action' => (string) (data_get($claim->metadata, 'next_action') ?? ''), + 'status' => (string) ($claim->status ?? 'aperta'), + 'opened_at' => optional($claim->opened_at)->format('d/m/Y H:i') ?: '-', + 'next_action' => (string) (data_get($claim->metadata, 'next_action') ?? ''), 'documents_sent_at' => filled(data_get($claim->metadata, 'documents_sent_at')) ? (string) data_get($claim->metadata, 'documents_sent_at') : '', - 'ticket_url' => TicketGestione::getUrl([ + 'ticket_url' => TicketGestione::getUrl([ 'ticket' => (int) $claim->ticket_id, - 'tab' => 'assicurazione', + 'tab' => 'assicurazione', ], panel: 'admin-filament'), ]; }) @@ -121,7 +121,7 @@ public function refreshData(): void $this->totals = [ 'aperte' => (clone $base)->whereNotIn('status', ['chiusa', 'chiuso', 'archiviata'])->count(), 'chiuse' => (clone $base)->whereIn('status', ['chiusa', 'chiuso', 'archiviata'])->count(), - 'tutte' => (clone $base)->count(), + 'tutte' => (clone $base)->count(), ]; } @@ -142,4 +142,4 @@ private function resolveAccessibleStabileIds(): array ->values() ->all(); } -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Supporto/Modifiche.php b/app/Filament/Pages/Supporto/Modifiche.php index a598aa2..f85ce46 100644 --- a/app/Filament/Pages/Supporto/Modifiche.php +++ b/app/Filament/Pages/Supporto/Modifiche.php @@ -231,8 +231,8 @@ public function canRunUpdate(): bool $user = Auth::user(); return is_object($user) - && method_exists($user, 'hasAnyRole') - && $user->hasAnyRole(['super-admin', 'admin', 'amministratore']); + && method_exists($user, 'hasAnyRole') + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore']); } public function reloadDashboardData(): void diff --git a/app/Filament/Pages/Supporto/TicketMobile.php b/app/Filament/Pages/Supporto/TicketMobile.php index 7fca0a2..5650e92 100644 --- a/app/Filament/Pages/Supporto/TicketMobile.php +++ b/app/Filament/Pages/Supporto/TicketMobile.php @@ -610,18 +610,18 @@ public function creaTicketRapido(): void $descrizione .= "\nTipo intervento: " . $label; } - $ticket = Ticket::query()->create([ - 'stabile_id' => $stabileId, - 'unita_immobiliare_id'=> $callerContext['unita_id'] ?? null, + $ticket = Ticket::query()->create([ + 'stabile_id' => $stabileId, + 'unita_immobiliare_id' => $callerContext['unita_id'] ?? null, 'soggetto_richiedente_id' => $callerContext['soggetto_id'] ?? null, - 'aperto_da_user_id' => $user->id, - 'titolo' => (string) $this->newTicketTitolo, - 'descrizione' => $descrizione, - 'categoria_ticket_id' => $this->newTicketCategoriaId, - 'luogo_intervento' => $this->newTicketLuogo, - 'data_apertura' => now(), - 'stato' => 'Aperto', - 'priorita' => $this->newTicketPriorita, + 'aperto_da_user_id' => $user->id, + 'titolo' => (string) $this->newTicketTitolo, + 'descrizione' => $descrizione, + 'categoria_ticket_id' => $this->newTicketCategoriaId, + 'luogo_intervento' => $this->newTicketLuogo, + 'data_apertura' => now(), + 'stato' => 'Aperto', + 'priorita' => $this->newTicketPriorita, ]); $savedAttachments = $this->salvaAllegatiTicket($ticket, $user->id); @@ -1178,7 +1178,7 @@ public function excerpt(?string $text, int $limit = 160): string public function getCallerStabileLabel(int $rubricaId): ?string { - $caller = RubricaUniversale::query()->find($rubricaId); + $caller = RubricaUniversale::query()->find($rubricaId); $stabile = $caller instanceof RubricaUniversale ? $this->resolveCallerStabile($caller) : null; @@ -1213,7 +1213,7 @@ private function resolveCallerTicketContext(?RubricaUniversale $caller, ?int $fa return [ 'stabile_id' => $stabile?->id, - 'stabile_label' => $stabile ? (string) ($stabile->denominazione ?: ('Stabile #' . $stabile->id)) : '', + 'stabile_label' => $stabile ? (string) ($stabile->denominazione ?: ('Stabile #' . $stabile->id)): '', 'soggetto_id' => $soggetto?->id, 'unita_id' => $unita?->id, 'unita_label' => $unita ? $this->formatUnitaLabel($unita) : '', diff --git a/app/Http/Controllers/Integrations/GoogleOAuthController.php b/app/Http/Controllers/Integrations/GoogleOAuthController.php index 36e5bc9..082811c 100644 --- a/app/Http/Controllers/Integrations/GoogleOAuthController.php +++ b/app/Http/Controllers/Integrations/GoogleOAuthController.php @@ -35,6 +35,7 @@ public function redirect(): RedirectResponse } $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); + $oauth = Arr::get($google, 'oauth', []); $clientId = (string) ($google['client_id'] ?? config('services.google.client_id')); $clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret')); @@ -48,16 +49,21 @@ public function redirect(): RedirectResponse $state = bin2hex(random_bytes(20)); session()->put('google_oauth_state', $state); - $query = http_build_query([ + $queryParams = [ 'client_id' => $clientId, 'redirect_uri' => $redirectUri, 'response_type' => 'code', 'scope' => implode(' ', self::GOOGLE_SCOPES), 'access_type' => 'offline', - 'prompt' => 'consent', 'include_granted_scopes' => 'true', 'state' => $state, - ]); + ]; + + if (trim((string) Arr::get($oauth, 'refresh_token', '')) === '') { + $queryParams['prompt'] = 'consent'; + } + + $query = http_build_query($queryParams); return redirect()->away('https://accounts.google.com/o/oauth2/v2/auth?' . $query); } @@ -71,6 +77,7 @@ public function callback(): RedirectResponse } $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); + $existingOauth = Arr::get($google, 'oauth', []); $clientId = (string) ($google['client_id'] ?? config('services.google.client_id')); $clientSecret = (string) ($google['client_secret'] ?? config('services.google.client_secret')); $redirectUri = (string) ($google['redirect_uri'] ?? config('services.google.redirect')); @@ -106,6 +113,9 @@ public function callback(): RedirectResponse $payload = $tokenResponse->json(); $accessToken = (string) Arr::get($payload, 'access_token', ''); $refreshToken = (string) Arr::get($payload, 'refresh_token', ''); + if ($refreshToken === '') { + $refreshToken = (string) Arr::get($existingOauth, 'refresh_token', ''); + } $expiresIn = (int) Arr::get($payload, 'expires_in', 3600); $profileResponse = Http::withToken($accessToken) @@ -128,6 +138,7 @@ public function callback(): RedirectResponse 'refresh_token' => $refreshToken, 'expires_in' => $expiresIn, 'connected_at' => now()->toDateTimeString(), + 'refreshed_at' => now()->toDateTimeString(), ], ]); diff --git a/app/Models/TicketInterventoSessione.php b/app/Models/TicketInterventoSessione.php index 589a039..594fb23 100644 --- a/app/Models/TicketInterventoSessione.php +++ b/app/Models/TicketInterventoSessione.php @@ -77,4 +77,4 @@ public function closedByUser() { return $this->belongsTo(User::class, 'closed_by_user_id'); } -} \ No newline at end of file +} diff --git a/app/Services/Cti/PbxRoutingService.php b/app/Services/Cti/PbxRoutingService.php index 4ced311..300279f 100644 --- a/app/Services/Cti/PbxRoutingService.php +++ b/app/Services/Cti/PbxRoutingService.php @@ -109,17 +109,17 @@ public function resolveByIncomingLine(?string $line): array $targetRouting = $targetExtension !== '' ? $this->resolveByExtension($targetExtension) : ($routeGroup !== '' ? $this->resolveByExtension($routeGroup) : [ - 'user_id' => null, - 'stabile_id' => null, - 'user' => null, - ]); + 'user_id' => null, + 'stabile_id' => null, + 'user' => null, + ]); if (($targetRouting['user'] ?? null) === null) { $mappedUser = $this->findUserByLineMapping($amministratore, $normalized); if ($mappedUser instanceof User) { $targetRouting = [ 'user_id' => (int) $mappedUser->id, - 'stabile_id' => method_exists($mappedUser, 'stabiliAssegnati') ? ((int) ($mappedUser->stabiliAssegnati()->value('stabili.id') ?? 0) ?: null) : null, + 'stabile_id' => method_exists($mappedUser, 'stabiliAssegnati') ? ((int) ($mappedUser->stabiliAssegnati()->value('stabili.id') ?? 0) ?: null): null, 'user' => $mappedUser, ]; } diff --git a/app/Services/Support/TicketAttachmentUploadService.php b/app/Services/Support/TicketAttachmentUploadService.php index ae13ee7..6168784 100644 --- a/app/Services/Support/TicketAttachmentUploadService.php +++ b/app/Services/Support/TicketAttachmentUploadService.php @@ -83,8 +83,8 @@ private function shouldOptimizeImage(array $metadata, array $options = []): bool private function hasEmbeddedImageMetadata(array $metadata): bool { return filled($metadata['exif_datetime'] ?? null) - || filled($metadata['device'] ?? null) - || isset($metadata['gps_decimal']) + || filled($metadata['device'] ?? null) + || isset($metadata['gps_decimal']) || ((int) ($metadata['orientation'] ?? 1)) !== 1; } diff --git a/database/migrations/2026_04_06_160000_add_sessions_and_materials_to_ticket_interventi.php b/database/migrations/2026_04_06_160000_add_sessions_and_materials_to_ticket_interventi.php index 96b2c7a..fb650e7 100644 --- a/database/migrations/2026_04_06_160000_add_sessions_and_materials_to_ticket_interventi.php +++ b/database/migrations/2026_04_06_160000_add_sessions_and_materials_to_ticket_interventi.php @@ -57,4 +57,4 @@ public function down(): void Schema::dropIfExists('ticket_intervento_sessioni'); } } -}; \ No newline at end of file +}; diff --git a/docs/00-TODO-COPILOT-TRACKER.md b/docs/00-TODO-COPILOT-TRACKER.md index 3223a37..c5a9b24 100644 --- a/docs/00-TODO-COPILOT-TRACKER.md +++ b/docs/00-TODO-COPILOT-TRACKER.md @@ -3,6 +3,7 @@ # NetGescon — TODO Copilot Tracker Data aggiornamento: 6 aprile 2026 Questo file tiene traccia di: + - cosa è stato fatto (implementato e/o corretto) - cosa resta da fare - priorità e dipendenze @@ -12,22 +13,27 @@ # NetGescon — TODO Copilot Tracker ## 1) Fatto (stato attuale) ### UX / Filament (navigazione “web-like”) + - Breadcrumbs compatibili Filament: wrapper view per ``. - Pagine Filament con back/breadcrumb coerenti (dove già applicato). ### Anagrafica + - `Persona`: gestione multi-ruolo via JSON `persone.ruoli` + UI di editing in Filament. ### Contabilità — base + - Tabelle contabilità e migrazioni sbloccate (incl. `contabilita_regole_prima_nota`, movimenti banca). - Import movimenti banca UniCredit `.wri` con dedup (`row_hash`). - Azione Filament su movimenti banca per generare registrazione/movimenti contabili da regole prima nota (scaffold funzionante). ### Fatture ricevute XML (FatturaPA) + - Archivio + scheda Filament per fatture ricevute XML. - Import multiplo XML con dedup (hash + chiave logica) e persistenza su `fatture_elettroniche`. ### Scheda Stabile (riabilitata) + - Ripristinato accesso dalla Filament “Cruscotto stabile” alla scheda stabile completa (UI classica) con: - Dati stabile - Banche/dati finanziari @@ -37,10 +43,12 @@ ### Scheda Stabile (riabilitata) - Aggiunto deep-link `?tab=...` sulla scheda stabile UI classica per aprire direttamente una tab specifica. ### “Vista Excel” (riabilitata) + - Ripristinato accesso immediato dalla Filament “Cruscotto stabile” alla tab “Tabelle millesimali” (UI classica) tramite deep-link. - Esiste anche la Filament “Tabelle millesimali · Prospetto” che è già una vista tabellare/Excel-like. ### Nuovo lavoro già avviato (base dati + UI) + - Nuove tabelle per “fatture fornitori” (testata + righe, importi `_euro`, ritenuta, tab pagamento). - Prime pagine Filament: - Archivio fatture fornitori @@ -51,17 +59,21 @@ ### Nuovo lavoro già avviato (base dati + UI) ## 2) In corso (priorità alta) ### A) Riallineare le “Gestioni” per la contabilità + Obiettivo: avere gestione ordinaria/riscaldamento/straordinaria *coerenti* e selezionabili per stabile, perché la contabilità deve agganciarsi a: + - anno gestione / esercizio - tipo gestione - conti/casse/banca - registrazioni/movimenti Output atteso: + - schermate Filament “Gestione ordinaria / riscaldamento / straordinaria” che leggono e usano la gestione attiva (non solo lo stabile attivo) - link e contesti coerenti tra: Prima Nota, Casse e banche, Fatture, Movimenti banca Dipendenze: + - definire/standardizzare “gestione attiva” (session/key, o tabella/relazione) --- @@ -69,6 +81,7 @@ ### A) Riallineare le “Gestioni” per la contabilità ## 3) Da fare (funzionalità richieste, in ordine consigliato) ### 3.0 Area fornitore/manutentore 2026 + - Unificare la rubrica operativa usando la stessa maschera principale condivisa tra amministratore, fornitore e moduli collegati; differenziare accesso e persistenza per ruolo, non l impianto della view. - Aggiungere una pagina ad hoc per i ticket interni del fornitore con elenco unico di lavorazioni: ticket passati dall amministratore + lavorazioni interne + storico operativo. - Estendere `fornitore/collaboratori` con gestione orari di lavoro, disponibilita e futura assegnazione lavori ricorrenti. @@ -77,6 +90,7 @@ ### 3.0 Area fornitore/manutentore 2026 - Portare avanti catalogo prodotti unificato con vari fornitori, listini acquisto/vendita multipli, seriali, varianti modello/colore, foto, allegati e riferimenti esterni tipo ICECAT. ### 3.1 Contabilità — fatture fornitori “alla Gescon” + - Rendere la maschera fattura fornitore più fedele allo screenshot (nomi/ordine tab + colonna azioni). - Aggancio/creazione fornitore (contabilità + anagrafica unica/rubrica) direttamente dalla maschera. - Contabilizzazione: @@ -85,6 +99,7 @@ ### 3.1 Contabilità — fatture fornitori “alla Gescon” - RA (ritenuta) come debito separato da versare ### 3.2 Ritenute d’acconto + F24 + - Gestione codici tributo: 1019/1020 e 1040. - Regole: - RA si versa con F24 entro il 16 del mese successivo al pagamento. @@ -94,6 +109,7 @@ ### 3.2 Ritenute d’acconto + F24 - (fase successiva) aggregazione per mese/tributo e stampa/distinta ### 3.3 Import MDB (GESCON) → contabilità operativa + - Predisporre import da `singolo_stabile.MDB` e `generale_stabile.mdb`: - operazioni/fatture/fornitori - rispettare sempre i campi `*_euro` @@ -103,6 +119,7 @@ ### 3.3 Import MDB (GESCON) → contabilità operativa - collegamenti a tabelle millesimali/voci spesa ### 3.4 XML multiplo → passaggio in contabilità + - Oggi: import XML alimenta archivio `fatture_elettroniche`. - Da fare: - creazione/aggancio fornitore automatico (rubrica + fornitore) @@ -113,31 +130,35 @@ ### 3.4 XML multiplo → passaggio in contabilità ## 4) Parti discusse ma non ancora implementate (da non perdere) ### Ticket + - Esiste base legacy (controller/CRUD e categorie ticket). Da definire: - UI Filament per ticket (lista, scheda, stati, priorità) - permessi/ruoli - collegamento ticket ↔ stabile ↔ documenti ### Gestione documentale + - Esiste sistema “Documenti collegati” (con rotte e API statistiche/scadenze). Da definire: - UI Filament per documenti del condominio - workflow scadenze/rinnovi - permessi e visibilità per ruolo ### Protocollo documentazione + - Da definire: - numerazione protocolli per stabile/anno - registrazione (data, mittente/destinatario, oggetto) - collegamento a documenti/pec ### Consumi / acqua / contatori + - Backend (tabelle contatori/letture/algoritmi) esiste. - UI Filament non ancora esposta: oggi la gestione è in view legacy. --- ## 5) Note operative / rischi + - Coesistenza Filament + UI classica: va mantenuta, ma i punti chiave devono essere raggiungibili dal “Cruscotto stabile”. - “Gestione attiva” è il nodo per rendere contabilità consistente (oltre a “stabile attivo”). - Import MDB richiede mapping rigoroso (soprattutto `_euro`) per evitare contabilità sbilanciata. - diff --git a/docs/ai/TOPICS_INDEX.md b/docs/ai/TOPICS_INDEX.md index 46a0dc9..1941939 100644 --- a/docs/ai/TOPICS_INDEX.md +++ b/docs/ai/TOPICS_INDEX.md @@ -6,4 +6,5 @@ ## Topics - [Fatture Fornitori](topics/fatture-fornitori.md) - [Filo Logico Contabilita](topics/filo-logico-contabilita.md) +- [Import Unita Cod Cond](topics/import-unita-cod-cond.md) - [Affitti ISTAT e Contratti](topics/affitti-istat-contratti.md) diff --git a/docs/ai/procedure/current-topic.txt b/docs/ai/procedure/current-topic.txt index a6c3e88..08cc272 100644 --- a/docs/ai/procedure/current-topic.txt +++ b/docs/ai/procedure/current-topic.txt @@ -1 +1 @@ -Filo Logico Contabilita +Import Unita Cod Cond diff --git a/docs/ai/topics/import-unita-cod-cond.md b/docs/ai/topics/import-unita-cod-cond.md new file mode 100644 index 0000000..262e4ef --- /dev/null +++ b/docs/ai/topics/import-unita-cod-cond.md @@ -0,0 +1,57 @@ +# Topic: Import Unita Cod Cond + +## Obiettivo + +- Rendere l'import legacy di unita, nominativi, millesimi e voci coerente con la realta archivistica Gescon, evitando fallback vuoti o identificazioni deboli. + +## Regole vincolanti + +- L'ultimo anno disponibile e la fotografia autorevole dello stato corrente. +- La chiave certa dell'unita e `condomin.cod_cond`. +- `scala`, `interno`, `catasto_sub` e il codice mnemonico servono per riconciliazione e visualizzazione, non come chiave inventata in fallback. +- Il codice visibile all'operatore deve restare leggibile in forma `Palazzina/Scala/Interno`. +- Gli anni precedenti servono a costruire la cronologia dei soggetti collegati all'unita, non a sostituire il nominativo corrente trovato nell'ultimo anno. +- Non creare righe di completamento artificiale a zero per millesimi o nominativi se la sorgente non le fornisce. + +## Modello archivistico chiarito dall'utente + +- `condomin.cod_cond` identifica l'unita legacy in modo stabile. +- `comproprietari` integra la storia proprietari e quote. +- `emes_gen` definisce il contesto di emissione e l'anno gestionale. +- `emes_det.cod_cond` collega la rata emessa all'unita. +- `incassi.cod_cond` collega l'incasso all'unita. +- `cond_inq` distingue il ruolo storico: condomino/proprietario (`C`) o inquilino (`I`). + +## Stato attuale del codice + +- `app/Console/Commands/ImportGesconFullPipeline.php` + - fixato il riempimento automatico di dettagli millesimali a zero: ora e opt-in con `--fill-missing-millesimi` + - fixato il fallback di aggancio tabella per `stepVoci()` + - fixato il preload staging per `legacy_year` +- `app/Console/Commands/NetgesconQaMillesimiSpeseCommand.php` + - aggiunto per QA su allineamento unita/nominativi/millesimi/voci + +## Evidenze validate su 0021 + +- Lo staging dell'ultimo anno utile non veniva caricato correttamente finche il preflight controllava solo `cod_stabile`. +- Dopo il fix sul preload, `0021/0004` viene caricato e reso disponibile per confronto/import. +- Le unita ancora senza nominativo corrente non vanno risolte con fallback automatici: almeno i casi `A/0`, `A/42`, `A/43`, `A/215`, `A/216` non hanno corrispondenza trovata nel controllo staging eseguito. + +## Prossimi step operativi + +1. Rifattorizzare la costruzione snapshot corrente unita/nominativi a partire dall'ultimo anno disponibile. +2. Usare `cod_cond` come chiave di merge tra staging e dominio. +3. Inserire gli anni precedenti solo come variazioni storiche in `unita_immobiliare_nominativi`. +4. Verificare che rate, incassi e ruoli `C`/`I` restino riconciliabili per anno e unita. +5. Gestire le unita dominio senza sorgente corrente con una decisione esplicita di review/disattivazione, non con dati inventati. + +## Comandi utili gia usati + +- `php artisan netgescon:qa-millesimi-spese --stabile=0021 --anno=0003` +- `php artisan netgescon:qa-unita-nominativi --stabile_id=25` +- `php artisan gescon:import-align --stabile=0021 --path=/mnt/gescon-archives/gescon --years=2025,2026 --water-years=2025,2026 --open-years=2025,2026` +- `php artisan gescon:import-full --stabile=0021 --solo=unita --anno=0004 --dry-run` + +## Vincolo di continuita + +- Le vecchie note in `docs/ai` o nei chat export che propongono fallback tipo unita inventate, `interno=0` come riempitivo o codici sintetici non fondati sulla sorgente vanno considerate superate se confliggono con questo topic. diff --git a/docs/support/AGGIORNAMENTI-STAGING-GIT.md b/docs/support/AGGIORNAMENTI-STAGING-GIT.md index 65014f7..cd9064b 100644 --- a/docs/support/AGGIORNAMENTI-STAGING-GIT.md +++ b/docs/support/AGGIORNAMENTI-STAGING-GIT.md @@ -121,6 +121,16 @@ ## Rilascio operativo 2026-04-06 Questo e il blocco che puo essere aggiornato adesso con la procedura Git-first senza correzioni manuali extra su staging. +### Sync fornitori legacy 2026-04-08 + +- La procedura fornitori va eseguita con un solo entry point: `php artisan gescon:sync-fornitori-legacy --mdb=/mnt/gescon-archives/gescon/dbc/Fornitori.mdb` +- La sync esegue in sequenza: + - import `Fornitori.mdb` in staging + - aggiornamento archivio locale `fornitori` + `rubrica_universale` + - aggiornamento TAG da descrizione legacy +- In `--dry-run` la staging resta in preview e i passaggi locali non persistono, cosi il legacy resta la sorgente da verificare prima del consolidamento. +- Regola operativa: per i fornitori il legacy aggiorna l archivio locale, non il contrario. + Incluso nel rilascio: - Ticket Mobile: anteprima immediata locale di foto e allegati prima dell invio definitivo. @@ -136,10 +146,11 @@ ## Rilascio operativo 2026-04-06 3. usare `Aggiorna staging da Gitea` 4. aspettare la conclusione del sync Git e della QA sicura inclusa 5. validare subito i punti visibili: - - Ticket Mobile mostra l anteprima prima di inviare - - Ticket Gestione mostra `Tab 4 - Assicurazioni` - - `/admin-filament/fornitore/tickets/{id}` non produce 403 per i casi operativi normali - - la lista ticket fornitore apre anche la modal rapida + +- Ticket Mobile mostra l anteprima prima di inviare +- Ticket Gestione mostra `Tab 4 - Assicurazioni` +- `/admin-filament/fornitore/tickets/{id}` non produce 403 per i casi operativi normali +- la lista ticket fornitore apre anche la modal rapida Regola per evitare nuovi problemi: diff --git a/docs/support/FORNITORE-OPERATIVITA-ROADMAP-2026-04.md b/docs/support/FORNITORE-OPERATIVITA-ROADMAP-2026-04.md index f6ad211..3022fc3 100644 --- a/docs/support/FORNITORE-OPERATIVITA-ROADMAP-2026-04.md +++ b/docs/support/FORNITORE-OPERATIVITA-ROADMAP-2026-04.md @@ -162,4 +162,4 @@ ## 10. Prossimi slice consigliati 2. estendere checkpoint presenza con QR/BLE/NFC dove il device/browser lo consente davvero 3. estensione collaboratori con disponibilita/orari 4. ticket interni/lavorazioni ricorrenti dentro `Lavorazioni operative` -5. staging SQL TecnoRepair per rubrica, schede e seriali \ No newline at end of file +5. staging SQL TecnoRepair per rubrica, schede e seriali diff --git a/resources/views/filament/components/stabile-attivo.blade.php b/resources/views/filament/components/stabile-attivo.blade.php index b45630b..a4bf8c9 100644 --- a/resources/views/filament/components/stabile-attivo.blade.php +++ b/resources/views/filament/components/stabile-attivo.blade.php @@ -3,6 +3,37 @@ /** @var \App\Models\Stabile|null $stabileAttivo */ $activeId = $stabileAttivo?->id; + $stabileHasRiscaldamento = static function (?\App\Models\Stabile $stabile): bool { + if (! $stabile) { + return false; + } + + if ((bool) ($stabile->riscaldamento_centralizzato ?? false)) { + return true; + } + + $rateRiscaldamento = $stabile->rate_riscaldamento_mesi ?? []; + if (is_string($rateRiscaldamento)) { + $rateRiscaldamento = json_decode($rateRiscaldamento, true); + } + if (is_array($rateRiscaldamento) && $rateRiscaldamento !== []) { + return true; + } + + $config = $stabile->configurazione_avanzata ?? []; + if (is_string($config)) { + $config = json_decode($config, true); + } + if (is_array($config) && array_key_exists('mostra_riscaldamento', $config) && $config['mostra_riscaldamento']) { + return true; + } + + return \Illuminate\Support\Facades\DB::table('gestioni_contabili') + ->where('stabile_id', $stabile->id) + ->where('tipo_gestione', 'riscaldamento') + ->exists(); + }; + $stabileLabel = static function (?\App\Models\Stabile $stabile): string { if (! $stabile) { return ''; @@ -28,7 +59,7 @@
Ordinari: {{ ($stabileAttivo->numero_rate_ordinarie ?? 0) > 0 ? 'Sì' : 'No' }} · - Riscaldamento: {{ (bool) ($stabileAttivo->riscaldamento_centralizzato ?? false) ? 'Sì' : 'No' }} + Riscaldamento: {{ $stabileHasRiscaldamento($stabileAttivo) ? 'Sì' : 'No' }}
@endif diff --git a/resources/views/filament/pages/supporto/aggiornamento-launcher.blade.php b/resources/views/filament/pages/supporto/aggiornamento-launcher.blade.php index 7f873d2..36b3f00 100644 --- a/resources/views/filament/pages/supporto/aggiornamento-launcher.blade.php +++ b/resources/views/filament/pages/supporto/aggiornamento-launcher.blade.php @@ -1,6 +1,8 @@ - @php($supportUpdateAuthUser = auth()->user()) - @php($supportCanRunUpdate = $supportUpdateAuthUser && method_exists($supportUpdateAuthUser, 'hasAnyRole') && $supportUpdateAuthUser->hasAnyRole(['super-admin', 'admin', 'amministratore'])) + @php + $supportUpdateAuthUser = auth()->user(); + $supportCanRunUpdate = $supportUpdateAuthUser && method_exists($supportUpdateAuthUser, 'hasAnyRole') && $supportUpdateAuthUser->hasAnyRole(['super-admin', 'admin', 'amministratore']); + @endphp
diff --git a/resources/views/filament/pages/supporto/assistenza-legacy.blade.php b/resources/views/filament/pages/supporto/assistenza-legacy.blade.php index c5b4948..fccd983 100644 --- a/resources/views/filament/pages/supporto/assistenza-legacy.blade.php +++ b/resources/views/filament/pages/supporto/assistenza-legacy.blade.php @@ -88,7 +88,9 @@
- @php($selected = $this->selectedScheda) + @php + $selected = $this->selectedScheda; + @endphp @if(! $selected)
Seleziona una riga dall'elenco per aprire la scheda tecnica legacy con cliente, seriali, allegati e note operative. @@ -135,7 +137,9 @@
Fornitore e allegati
@if($selected->fornitore) - @php($fornitoreUrl = $this->getFornitoreUrl((int) $selected->fornitore->id)) + @php + $fornitoreUrl = $this->getFornitoreUrl((int) $selected->fornitore->id); + @endphp @if($fornitoreUrl) {{ $selected->fornitore->ragione_sociale ?: ('Fornitore #' . $selected->fornitore->id) }} @else diff --git a/resources/views/filament/pages/supporto/ticket-scheda.blade.php b/resources/views/filament/pages/supporto/ticket-scheda.blade.php index ab43320..a544303 100644 --- a/resources/views/filament/pages/supporto/ticket-scheda.blade.php +++ b/resources/views/filament/pages/supporto/ticket-scheda.blade.php @@ -18,7 +18,9 @@ Ticket non selezionato o non accessibile. Apri questa pagina da Gestione Ticket.
@else - @php($ticket = $this->ticket) + @php + $ticket = $this->ticket; + @endphp
#{{ $ticket->id }} - {{ $ticket->titolo }}