From 09022d57530d81b6330efee925d154f2086d5b25 Mon Sep 17 00:00:00 2001 From: michele Date: Thu, 16 Apr 2026 08:12:41 +0000 Subject: [PATCH] Stabilize supplier ops and strict legacy sync --- .../GesconSyncOrdinariaPreventivoCommand.php | 584 ++++++++++++++++++ .../ImportLegacyFornitoriTagsCommand.php | 12 +- .../Commands/ProvisionFornitoriArchives.php | 15 +- ...TecnoRepairImportRubricaClientiCommand.php | 169 +++-- .../Condomini/LettureServiziArchivio.php | 4 +- .../Condomini/TabelleMillesimaliArchivio.php | 366 +++++------ .../Pages/Fornitore/ImpostazioniArchivio.php | 9 +- app/Models/FornitoreCliente.php | 3 +- app/Services/TenantArchivePathService.php | 2 +- app/Support/ArchivioPaths.php | 3 +- config/netgescon.php | 4 +- ...150000_add_cod_forn_to_fornitori_table.php | 2 +- ..._190000_create_fornitore_clienti_table.php | 2 +- ...glio_millesimi_unique_for_ruolo_legacy.php | 49 ++ docs/ai/SESSION_HANDOFF.md | 2 +- docs/fornitori/tecnorepair-operativita.md | 51 ++ .../ticket-intervento-scheda.blade.php | 29 +- 17 files changed, 1006 insertions(+), 300 deletions(-) create mode 100644 app/Console/Commands/GesconSyncOrdinariaPreventivoCommand.php create mode 100644 database/migrations/2026_04_15_220000_update_dettaglio_millesimi_unique_for_ruolo_legacy.php create mode 100644 docs/fornitori/tecnorepair-operativita.md diff --git a/app/Console/Commands/GesconSyncOrdinariaPreventivoCommand.php b/app/Console/Commands/GesconSyncOrdinariaPreventivoCommand.php new file mode 100644 index 0000000..9a67aad --- /dev/null +++ b/app/Console/Commands/GesconSyncOrdinariaPreventivoCommand.php @@ -0,0 +1,584 @@ +argument('stabile_code')); + $year = (int) $this->argument('year'); + + if ($stableCode === '' || $year < 1900) { + $this->error('Parametri non validi.'); + + return self::FAILURE; + } + + $stableId = $this->resolveStableId($stableCode); + $gestioneId = $this->resolveGestioneId($stableId, $year); + $legacyVoci = $this->loadLegacyVoci($stableCode, $year); + + if ($legacyVoci->isEmpty()) { + $this->error('Nessuna voce legacy ordinaria trovata per stabile ' . $stableCode . ' anno ' . $year . '.'); + + return self::FAILURE; + } + + $tableCodes = $legacyVoci + ->pluck('tabella') + ->filter(fn ($value): bool => is_string($value) && trim($value) !== '') + ->map(fn (string $value): string => trim($value)) + ->unique() + ->values(); + + $legacyDetails = $this->loadLegacyDettagli($stableCode, $year, $tableCodes); + $legacyHeaders = $this->loadLegacyTableHeaders($stableCode, $tableCodes); + + DB::transaction(function () use ($stableCode, $stableId, $year, $gestioneId, $legacyVoci, $legacyDetails, $tableCodes, $legacyHeaders): void { + $this->ensureLegacyUnitCoverage($stableCode, $stableId, $legacyDetails); + $tableMap = $this->syncTabelleMillesimali($stableId, $year, $tableCodes, $legacyDetails, $legacyHeaders); + $this->syncDettagliMillesimali($stableId, $year, $legacyDetails, $tableMap); + $this->syncVociSpesa($stableId, $gestioneId, $year, $legacyVoci, $tableMap); + }); + + $totalBudget = (float) DB::table('voci_spesa') + ->where('stabile_id', $stableId) + ->where('gestione_contabile_id', $gestioneId) + ->sum('importo_default'); + + $this->info('Sync completato per stabile ' . $stableCode . ' anno ' . $year . '.'); + $this->line('Gestione ordinaria: #' . $gestioneId); + $this->line('Voci spesa sincronizzate: ' . DB::table('voci_spesa')->where('stabile_id', $stableId)->where('gestione_contabile_id', $gestioneId)->count()); + $this->line('Tabelle millesimali sincronizzate: ' . DB::table('tabelle_millesimali')->where('stabile_id', $stableId)->where('anno_gestione', $year)->count()); + $this->line('Totale preventivo importato: ' . number_format($totalBudget, 2, '.', '')); + + return self::SUCCESS; + } + + private function resolveStableId(string $stableCode): int + { + $optionStableId = $this->option('stabile-id'); + if (is_numeric($optionStableId) && (int) $optionStableId > 0) { + return (int) $optionStableId; + } + + $query = DB::table('stabili'); + $stable = $query + ->where('codice_stabile', $stableCode) + ->orWhere('codice_stabile', ltrim($stableCode, '0')) + ->orderBy('id') + ->first(['id']); + + if (! $stable) { + throw new \RuntimeException('Stabile NetGescon non trovato per codice ' . $stableCode . '.'); + } + + return (int) $stable->id; + } + + private function resolveGestioneId(int $stableId, int $year): int + { + $optionGestioneId = $this->option('gestione-id'); + if (is_numeric($optionGestioneId) && (int) $optionGestioneId > 0) { + return (int) $optionGestioneId; + } + + $gestione = DB::table('gestioni_contabili') + ->where('stabile_id', $stableId) + ->where('anno_gestione', $year) + ->where('tipo_gestione', 'ordinaria') + ->orderByDesc('gestione_attiva') + ->orderByDesc('id') + ->first(['id']); + + if (! $gestione) { + throw new \RuntimeException('Gestione ordinaria non trovata per stabile #' . $stableId . ' anno ' . $year . '.'); + } + + return (int) $gestione->id; + } + + private function loadLegacyVoci(string $stableCode, int $year): Collection + { + return DB::connection('gescon_import') + ->table('voc_spe') + ->where('cod_stabile', $stableCode) + ->where('legacy_year', $year) + ->where(function ($query): void { + $query->where('v_ors', 'O')->orWhereNull('v_ors'); + }) + ->orderBy('tabella') + ->orderBy('cod') + ->get([ + 'cod', + 'descriz', + 'tabella', + 'preventivo_euro', + 'consuntivo_euro', + 'perc_proprietario', + 'perc_inquilino', + 'note', + 'v_ors', + ]); + } + + private function loadLegacyDettagli(string $stableCode, int $year, Collection $tableCodes): Collection + { + return DB::connection('gescon_import') + ->table('dett_tab') + ->where('cod_stabile', $stableCode) + ->where('legacy_year', $year) + ->whereIn('cod_tab', $tableCodes->all()) + ->orderBy('cod_tab') + ->orderBy('id_cond') + ->orderBy('cond_inquil') + ->get([ + 'cod_tab', + 'id_cond', + 'cond_inquil', + 'mm', + 'prev_euro', + 'cons_euro', + ]); + } + + /** + * @return array + */ + private function loadLegacyTableHeaders(string $stableCode, Collection $tableCodes): array + { + if ($tableCodes->isEmpty() || ! Schema::connection('gescon_import')->hasTable('tabelle_millesimali')) { + return []; + } + + $columns = Schema::connection('gescon_import')->getColumnListing('tabelle_millesimali'); + $codeColumn = in_array('cod_tabella', $columns, true) + ? 'cod_tabella' + : (in_array('cod_tab', $columns, true) ? 'cod_tab' : null); + + if (! $codeColumn) { + return []; + } + + $query = DB::connection('gescon_import')->table('tabelle_millesimali'); + if (in_array('cod_stabile', $columns, true)) { + $query->where('cod_stabile', $stableCode); + } + + $rows = $query + ->whereIn($codeColumn, $tableCodes->all()) + ->orderBy($codeColumn) + ->get(); + + $headers = []; + + foreach ($rows as $row) { + $code = strtoupper(trim((string) ($row->{$codeColumn} ?? ''))); + if ($code === '' || isset($headers[$code])) { + continue; + } + + $headers[$code] = $row; + } + + return $headers; + } + + private function ensureLegacyUnitCoverage(string $stableCode, int $stableId, Collection $legacyDetails): void + { + $legacyIds = $legacyDetails + ->pluck('id_cond') + ->filter(fn ($value): bool => is_string($value) && trim($value) !== '') + ->map(fn (string $value): string => trim($value)) + ->unique() + ->values(); + + $existingIds = DB::table('unita_immobiliari') + ->where('stabile_id', $stableId) + ->whereNull('deleted_at') + ->whereIn('legacy_cond_id', $legacyIds->all()) + ->pluck('legacy_cond_id') + ->map(fn ($value): string => trim((string) $value)) + ->all(); + + $existingSet = array_fill_keys($existingIds, true); + + foreach ($legacyIds as $legacyId) { + if (isset($existingSet[$legacyId])) { + continue; + } + + $sourceRow = $this->resolveCondominSourceRow($stableCode, $legacyId); + $this->createMissingLegacyUnit($stableCode, $stableId, $legacyId, $sourceRow); + } + } + + private function resolveCondominSourceRow(string $stableCode, string $legacyId): object + { + $idCondRows = DB::connection('gescon_import') + ->table('condomin') + ->where('cod_stabile', $stableCode) + ->where('id_cond', $legacyId) + ->orderByDesc('legacy_year') + ->orderByDesc('id') + ->get(); + + if ($idCondRows->isNotEmpty()) { + return $idCondRows->first(); + } + + $codCondRows = DB::connection('gescon_import') + ->table('condomin') + ->where('cod_stabile', $stableCode) + ->where('cod_cond', $legacyId) + ->orderByDesc('legacy_year') + ->orderByDesc('id') + ->get(); + + if ($codCondRows->isNotEmpty()) { + return $codCondRows->first(); + } + + throw new \RuntimeException('Impossibile risolvere la fonte condomin legacy per id_cond ' . $legacyId . '.'); + } + + private function createMissingLegacyUnit(string $stableCode, int $stableId, string $legacyId, object $sourceRow): void + { + $internal = trim((string) ($sourceRow->interno ?? '')); + if ($internal === '') { + throw new \RuntimeException('Interno mancante per il legacy id ' . $legacyId . '.'); + } + + $scale = trim((string) ($sourceRow->scala ?? '')); + $scale = $scale !== '' ? strtoupper($scale) : 'A'; + $floor = $this->normalizeFloor($sourceRow->piano ?? null); + $displayName = trim((string) (($sourceRow->cognome ?? '') . ' ' . ($sourceRow->nome ?? ''))); + if ($displayName === '') { + $displayName = trim((string) ($sourceRow->nom_cond ?? '')); + } + + $code = $this->buildUnitCode($stableCode, $scale, $internal, $legacyId); + + if (DB::table('unita_immobiliari')->where('codice_unita', $code)->exists()) { + throw new \RuntimeException('Codice unita gia presente durante il backfill rigoroso: ' . $code . '.'); + } + + DB::table('unita_immobiliari')->insert([ + 'stabile_id' => $stableId, + 'codice_unita' => $code, + 'denominazione' => $displayName !== '' ? $displayName : ('Unita legacy ' . $legacyId), + 'scala' => $scale, + 'piano' => $floor, + 'interno' => $internal, + 'legacy_cond_id' => $legacyId, + 'tipo_unita' => 'abitazione', + 'stato_occupazione' => 'occupata_proprietario', + 'stato_conservazione' => 'buono', + 'millesimi_generali' => 0, + 'attiva' => true, + 'unita_demo' => false, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + /** + * @return array + */ + private function syncTabelleMillesimali(int $stableId, int $year, Collection $tableCodes, Collection $legacyDetails, array $legacyHeaders): array + { + $detailSummary = $legacyDetails + ->groupBy('cod_tab') + ->map(function (Collection $rows): array { + return [ + 'total_mm' => (float) $rows->sum(fn ($row): float => is_numeric($row->mm ?? null) ? (float) $row->mm : 0.0), + 'rows' => $rows->count(), + ]; + }); + + $tableMap = []; + $sortOrder = 1; + + foreach ($tableCodes as $tableCode) { + $normalizedCode = strtoupper(trim((string) $tableCode)); + $summary = $detailSummary->get($tableCode, ['total_mm' => 0.0, 'rows' => 0]); + $header = $legacyHeaders[$normalizedCode] ?? null; + $rawCalcolo = $header->tipo_calcolo ?? $header->calcolo ?? null; + $calcolo = $this->normalizeLegacyCalcolo($rawCalcolo); + $tipoLegacy = $this->normalizeLegacyTipo($header->tipologia ?? $header->tipo ?? null) ?? 'O'; + $tipoTabella = $this->resolveTipoTabellaFromLegacy($tipoLegacy, $calcolo, $normalizedCode); + $tipoCalcoloDb = in_array($calcolo, ['millesimi', 'parti', 'fisso'], true) + ? $calcolo + : ($normalizedCode === 'ACQUA' ? 'parti' : 'millesimi'); + $denominazione = trim((string) ($header->denominazione ?? $header->descrizione ?? $tableCode)); + $ordine = null; + + foreach (['nord', 'ordinamento'] as $orderKey) { + if (isset($header->{$orderKey}) && is_numeric($header->{$orderKey})) { + $ordine = (int) $header->{$orderKey}; + break; + } + } + + $sortValue = $ordine ?? $sortOrder; + + $payload = [ + 'stabile_id' => $stableId, + 'anno_gestione' => $year, + 'nome_tabella' => $tableCode, + 'denominazione' => $denominazione !== '' ? $denominazione : $tableCode, + 'codice_tabella' => $tableCode, + 'legacy_codice' => $tableCode, + 'ordine_visualizzazione' => $sortValue, + 'tipo_tabella' => $tipoTabella, + 'tipo_calcolo' => $tipoCalcoloDb, + 'attiva' => true, + 'ordinamento' => $sortValue, + 'nord' => $sortValue, + 'totale_millesimi' => $summary['total_mm'], + 'meta_legacy' => json_encode([ + 'strict_source' => 'gescon_import', + 'legacy_year' => $year, + 'cod_tab' => $normalizedCode, + 'tipo' => $tipoLegacy, + 'calcolo' => is_string($rawCalcolo) && trim($rawCalcolo) !== '' ? strtoupper(trim($rawCalcolo)) : null, + 'detail_rows' => $summary['rows'], + ]), + 'updated_at' => now(), + ]; + + $existing = DB::table('tabelle_millesimali') + ->where('stabile_id', $stableId) + ->where('codice_tabella', $tableCode) + ->first(['id']); + + if ($existing) { + DB::table('tabelle_millesimali')->where('id', (int) $existing->id)->update($payload); + $tableMap[$tableCode] = (int) $existing->id; + } else { + $payload['created_at'] = now(); + DB::table('tabelle_millesimali')->insert($payload); + $tableMap[$tableCode] = (int) DB::table('tabelle_millesimali') + ->where('stabile_id', $stableId) + ->where('codice_tabella', $tableCode) + ->value('id'); + } + + $sortOrder++; + } + + return $tableMap; + } + + /** + * @param array $tableMap + */ + private function syncDettagliMillesimali(int $stableId, int $year, Collection $legacyDetails, array $tableMap): void + { + $tableIds = array_values($tableMap); + if ($tableIds === []) { + return; + } + + DB::table('dettaglio_millesimi') + ->whereIn('tabella_millesimale_id', $tableIds) + ->delete(); + + $groupedRows = $legacyDetails + ->groupBy(function ($row): string { + return implode('|', [ + trim((string) ($row->cod_tab ?? '')), + trim((string) ($row->id_cond ?? '')), + strtoupper(trim((string) ($row->cond_inquil ?? ''))), + ]); + }); + + foreach ($groupedRows as $groupKey => $rows) { + [$tableCode, $legacyId, $role] = explode('|', $groupKey); + $tableId = $tableMap[$tableCode] ?? null; + if (! $tableId) { + throw new \RuntimeException('Tabella millesimale non risolta per codice ' . $tableCode . '.'); + } + + $unitId = DB::table('unita_immobiliari') + ->where('stabile_id', $stableId) + ->whereNull('deleted_at') + ->where('legacy_cond_id', $legacyId) + ->value('id'); + + if (! $unitId) { + throw new \RuntimeException('Unita NetGescon non risolta per legacy_cond_id ' . $legacyId . '.'); + } + + $millesimi = (float) $rows->sum(fn ($row): float => is_numeric($row->mm ?? null) ? (float) $row->mm : 0.0); + $valorePrev = (float) $rows->sum(fn ($row): float => is_numeric($row->prev_euro ?? null) ? (float) $row->prev_euro : 0.0); + $valoreCons = (float) $rows->sum(fn ($row): float => is_numeric($row->cons_euro ?? null) ? (float) $row->cons_euro : 0.0); + + DB::table('dettaglio_millesimi')->insert([ + 'tabella_millesimale_id' => $tableId, + 'unita_immobiliare_id' => (int) $unitId, + 'millesimi' => $millesimi, + 'valore_prev' => $valorePrev, + 'valore_cons' => $valoreCons, + 'ruolo_legacy' => $role !== '' ? $role : null, + 'legacy_interno' => $legacyId, + 'partecipa' => true, + 'note' => null, + 'legacy_payload' => json_encode([ + 'strict_source' => 'gescon_import', + 'legacy_year' => $year, + 'cod_tab' => $tableCode, + 'id_cond' => $legacyId, + 'cond_inquil' => $role !== '' ? $role : null, + ]), + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + + /** + * @param array $tableMap + */ + private function syncVociSpesa(int $stableId, int $gestioneId, int $year, Collection $legacyVoci, array $tableMap): void + { + $sortOrder = 1; + + foreach ($legacyVoci as $voce) { + $code = trim((string) ($voce->cod ?? '')); + if ($code === '') { + continue; + } + + $tableCode = trim((string) ($voce->tabella ?? '')); + $tableId = $tableCode !== '' ? ($tableMap[$tableCode] ?? null) : null; + + if ($tableCode !== '' && ! $tableId) { + throw new \RuntimeException('Tabella legacy non risolta per la voce ' . $code . ': ' . $tableCode . '.'); + } + + $payload = [ + 'stabile_id' => $stableId, + 'gestione_contabile_id' => $gestioneId, + 'codice' => $code, + 'legacy_codice' => $code, + 'descrizione' => trim((string) ($voce->descriz ?? '')), + 'categoria' => 'ordinaria', + 'tipo_gestione' => 'ordinaria', + 'tabella_millesimale_default_id' => $tableId, + 'importo_default' => is_numeric($voce->preventivo_euro ?? null) ? (float) $voce->preventivo_euro : 0.0, + 'importo_consuntivo' => is_numeric($voce->consuntivo_euro ?? null) ? (float) $voce->consuntivo_euro : 0.0, + 'percentuale_condomino' => is_numeric($voce->perc_proprietario ?? null) ? (float) $voce->perc_proprietario : 0.0, + 'percentuale_inquilino' => is_numeric($voce->perc_inquilino ?? null) ? (float) $voce->perc_inquilino : 0.0, + 'attiva' => true, + 'ordinamento' => $sortOrder, + 'nord' => $sortOrder, + 'tipo' => 'ordinaria', + 'legacy_tipo' => trim((string) ($voce->v_ors ?? 'O')) ?: 'O', + 'note' => trim((string) ($voce->note ?? '')) !== '' ? trim((string) ($voce->note ?? '')) : null, + 'meta_legacy' => json_encode([ + 'strict_source' => 'gescon_import', + 'legacy_year' => $year, + 'cod_tab' => $tableCode !== '' ? $tableCode : null, + ]), + 'updated_at' => now(), + ]; + + $existing = DB::table('voci_spesa') + ->where('stabile_id', $stableId) + ->where('gestione_contabile_id', $gestioneId) + ->where('codice', $code) + ->first(['id']); + + if ($existing) { + DB::table('voci_spesa')->where('id', (int) $existing->id)->update($payload); + } else { + $payload['created_at'] = now(); + DB::table('voci_spesa')->insert($payload); + } + + $sortOrder++; + } + } + + private function normalizeFloor(mixed $rawFloor): int + { + $value = strtoupper(trim((string) $rawFloor)); + + return match ($value) { + '', 'T', 'PT', 'R', 'PR' => 0, + 'S', 'PS', '-1' => -1, + default => is_numeric($value) ? (int) $value : 0, + }; + } + + private function buildUnitCode(string $stableCode, string $scale, string $internal, string $legacyId): string + { + $normalizedStable = trim($stableCode); + $normalizedScale = trim($scale) !== '' ? trim($scale) : 'A'; + $normalizedInternal = strtoupper(trim(preg_replace('/\s+/', '', $internal) ?? $internal)); + + return $normalizedStable . '-' . $normalizedScale . '-' . $normalizedInternal . '-C' . $legacyId; + } + + private function normalizeLegacyCalcolo(?string $raw): ?string + { + $value = strtolower(trim((string) ($raw ?? ''))); + if ($value === '') { + return null; + } + + return match ($value) { + 'm' => 'millesimi', + 'a', 'c' => 'a_consumo', + 'x' => 'conguagli', + 'p' => 'personali', + default => $value, + }; + } + + private function normalizeLegacyTipo(?string $raw): ?string + { + $value = strtoupper(trim((string) ($raw ?? ''))); + if ($value === '') { + return null; + } + + return match ($value) { + 'O', 'ORD', 'ORDINARIA' => 'O', + 'R', 'RISC', 'RISCALDAMENTO' => 'R', + 'S', 'STRA', 'STRAORDINARIA' => 'S', + default => $value, + }; + } + + private function resolveTipoTabellaFromLegacy(?string $tipoLegacy, ?string $calcolo, ?string $codice): string + { + $normalizedCalcolo = strtolower(trim((string) ($calcolo ?? ''))); + $normalizedCode = strtoupper(trim((string) ($codice ?? ''))); + + if ($normalizedCode === 'ACQUA' || in_array($normalizedCalcolo, ['a_consumo', 'acqua', 'consumo', 'c', 'a'], true)) { + return 'custom'; + } + + return match ($tipoLegacy) { + 'R' => 'riscaldamento', + 'S' => 'straordinaria', + default => 'custom', + }; + } +} \ No newline at end of file diff --git a/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php b/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php index 90a13f9..bbb8ee3 100644 --- a/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php +++ b/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php @@ -318,7 +318,7 @@ private function resolveLocalFornitoreByLegacyIds(int $legacyId, string $legacyC } return $normalizedRagione !== '' - && $this->normalizeComparableString((string) ($fornitore->ragione_sociale ?? '')) === $normalizedRagione; + && $this->normalizeComparableString((string) ($fornitore->ragione_sociale ?? '')) === $normalizedRagione; })->values(); if ($filtered->count() === 1) { @@ -395,11 +395,11 @@ private function extractKeywordTags(string $input): array } $keywords = [ - 'informat' => 'informatica', - 'assist' => 'assistenza', - 'computer' => 'pc', - ' pc ' => 'pc', - 'apple' => 'apple', + 'informat' => 'informatica', + 'assist' => 'assistenza', + 'computer' => 'pc', + ' pc ' => 'pc', + 'apple' => 'apple', 'idr' => 'idraulico', 'termoidraul' => 'termoidraulico', 'elettric' => 'elettricista', diff --git a/app/Console/Commands/ProvisionFornitoriArchives.php b/app/Console/Commands/ProvisionFornitoriArchives.php index 036bc4a..afc6487 100644 --- a/app/Console/Commands/ProvisionFornitoriArchives.php +++ b/app/Console/Commands/ProvisionFornitoriArchives.php @@ -1,5 +1,4 @@ with('amministratore:id,codice_amministratore,codice_univoco'); $fornitoreId = $this->argument('fornitore_id'); - $adminId = (int) $this->option('admin-id'); + $adminId = (int) $this->option('admin-id'); if ($fornitoreId !== null) { $query->whereKey((int) $fornitoreId); @@ -35,7 +34,7 @@ public function handle(): int } $processed = 0; - $created = 0; + $created = 0; $query->orderBy('amministratore_id')->orderBy('id')->chunkById(100, function ($fornitori) use (&$processed, &$created): void { foreach ($fornitori as $fornitore) { @@ -46,7 +45,7 @@ public function handle(): int $processed++; $canonicalBase = ArchivioPaths::fornitoreBase($fornitore); - $legacyBase = ArchivioPaths::fornitoreLegacyBase($fornitore); + $legacyBase = ArchivioPaths::fornitoreLegacyBase($fornitore); if (! is_string($canonicalBase) || $canonicalBase === '') { $this->warn('Saltato fornitore #' . (int) $fornitore->id . ': codice archivio non disponibile.'); @@ -61,9 +60,9 @@ public function handle(): int $created++; } - $message = '#'. (int) $fornitore->id - . ' ' . trim((string) ($fornitore->ragione_sociale ?: ('Fornitore ' . $fornitore->id))) - . ' -> ' . Storage::disk('local')->path($canonicalBase) + $message = '#' . (int) $fornitore->id + . ' ' . trim((string) ($fornitore->ragione_sociale ?: ('Fornitore ' . $fornitore->id))) + . ' -> ' . Storage::disk('local')->path($canonicalBase) . ' [' . ($after ? ($before ? 'gia_esistente' : 'creata') : 'non_creata') . ']'; $this->line($message); @@ -78,4 +77,4 @@ public function handle(): int return self::SUCCESS; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/TecnoRepairImportRubricaClientiCommand.php b/app/Console/Commands/TecnoRepairImportRubricaClientiCommand.php index dbf77e8..a47f072 100644 --- a/app/Console/Commands/TecnoRepairImportRubricaClientiCommand.php +++ b/app/Console/Commands/TecnoRepairImportRubricaClientiCommand.php @@ -1,5 +1,4 @@ updateOrCreate( [ - 'fornitore_id' => (int) $fornitore->id, - 'legacy_cliente_id' => $customer['legacy_cliente_id'], + 'fornitore_id' => (int) $fornitore->id, + 'legacy_cliente_id' => $customer['legacy_cliente_id'], ], [ - 'amministratore_id' => (int) $admin->id, - 'rubrica_id' => $rubrica?->id, - 'display_name' => $customer['display_name'], - 'phone' => $customer['phone'], - 'phone_alt' => $customer['phone_alt'], - 'email' => $customer['email'], - 'indirizzo' => $customer['indirizzo'], - 'cap' => $customer['cap'], - 'citta' => $customer['citta'], - 'provincia' => $customer['provincia'], - 'partita_iva' => $customer['partita_iva'], - 'codice_fiscale' => $customer['codice_fiscale'], - 'note' => $customer['note'], - 'source' => 'tecnorepair_tclienti', - 'imported_from_path' => $customer['imported_from_path'], - 'imported_at' => now(), - 'metadata' => $customer['metadata'], + 'amministratore_id' => (int) $admin->id, + 'rubrica_id' => $rubrica?->id, + 'display_name' => $customer['display_name'], + 'phone' => $customer['phone'], + 'phone_alt' => $customer['phone_alt'], + 'email' => $customer['email'], + 'indirizzo' => $customer['indirizzo'], + 'cap' => $customer['cap'], + 'citta' => $customer['citta'], + 'provincia' => $customer['provincia'], + 'partita_iva' => $customer['partita_iva'], + 'codice_fiscale' => $customer['codice_fiscale'], + 'note' => $customer['note'], + 'source' => 'tecnorepair_tclienti', + 'imported_from_path' => $customer['imported_from_path'], + 'imported_at' => now(), + 'metadata' => $customer['metadata'], ] ); @@ -170,17 +169,17 @@ private function collectUniqueCustomers(Collection $rows): Collection $cliente = is_array($scheda->metadata['cliente'] ?? null) ? $scheda->metadata['cliente'] : []; $legacyClienteId = $this->toInt($cliente['ID'] ?? $scheda->legacy_cliente_id); - $displayName = $this->cleanString($cliente['NomeCognome'] ?? $scheda->customer_name); - $phone = $this->cleanString($cliente['NumeroTelefono'] ?? $scheda->customer_phone); - $phoneAlt = $this->cleanString($cliente['TelFisso'] ?? $scheda->customer_phone_alt); - $email = $this->cleanString($cliente['Email'] ?? $scheda->customer_email); - $indirizzo = $this->cleanString($cliente['Indirizzo'] ?? null); - $cap = $this->cleanString($cliente['Cap'] ?? null); - $citta = $this->cleanString($cliente['Citta'] ?? null); - $provincia = $this->cleanString($cliente['Prov'] ?? null); - $partitaIva = $this->cleanString($cliente['PIVA'] ?? null); - $codiceFiscale = $this->cleanString($cliente['CodFis'] ?? null); - $note = $this->cleanString($cliente['Annotazioni'] ?? null); + $displayName = $this->cleanString($cliente['NomeCognome'] ?? $scheda->customer_name); + $phone = $this->cleanString($cliente['NumeroTelefono'] ?? $scheda->customer_phone); + $phoneAlt = $this->cleanString($cliente['TelFisso'] ?? $scheda->customer_phone_alt); + $email = $this->cleanString($cliente['Email'] ?? $scheda->customer_email); + $indirizzo = $this->cleanString($cliente['Indirizzo'] ?? null); + $cap = $this->cleanString($cliente['Cap'] ?? null); + $citta = $this->cleanString($cliente['Citta'] ?? null); + $provincia = $this->cleanString($cliente['Prov'] ?? null); + $partitaIva = $this->cleanString($cliente['PIVA'] ?? null); + $codiceFiscale = $this->cleanString($cliente['CodFis'] ?? null); + $note = $this->cleanString($cliente['Annotazioni'] ?? null); $identity = $legacyClienteId !== null ? 'legacy-' . $legacyClienteId @@ -192,23 +191,23 @@ private function collectUniqueCustomers(Collection $rows): Collection if (! isset($customers[$identity])) { $customers[$identity] = [ - 'legacy_cliente_id' => $legacyClienteId, - 'display_name' => $displayName, - 'phone' => $phone, - 'phone_alt' => $phoneAlt, - 'email' => $email, - 'indirizzo' => $indirizzo, - 'cap' => $cap, - 'citta' => $citta, - 'provincia' => $provincia, - 'partita_iva' => $partitaIva, - 'codice_fiscale' => $codiceFiscale, - 'note' => $note, - 'imported_from_path'=> $this->cleanString($scheda->imported_from_path), - 'metadata' => [ - 'cliente' => $cliente, - 'ultima_scheda_id' => (int) $scheda->id, - 'ultima_scheda_num' => (string) ($scheda->legacy_numero_scheda ?? ''), + 'legacy_cliente_id' => $legacyClienteId, + 'display_name' => $displayName, + 'phone' => $phone, + 'phone_alt' => $phoneAlt, + 'email' => $email, + 'indirizzo' => $indirizzo, + 'cap' => $cap, + 'citta' => $citta, + 'provincia' => $provincia, + 'partita_iva' => $partitaIva, + 'codice_fiscale' => $codiceFiscale, + 'note' => $note, + 'imported_from_path' => $this->cleanString($scheda->imported_from_path), + 'metadata' => [ + 'cliente' => $cliente, + 'ultima_scheda_id' => (int) $scheda->id, + 'ultima_scheda_num' => (string) ($scheda->legacy_numero_scheda ?? ''), ], ]; @@ -233,15 +232,15 @@ private function resolveOrCreateRubrica(Amministratore $admin, array $customer, { $query = RubricaUniversale::query()->where('amministratore_id', (int) $admin->id); - $email = $this->cleanString($customer['email'] ?? null); - $phone = $this->normalizePhone($customer['phone'] ?? null); - $phoneAlt = $this->normalizePhone($customer['phone_alt'] ?? null); - $phoneForRubrica = $this->normalizePhoneField($customer['phone'] ?? null); + $email = $this->cleanString($customer['email'] ?? null); + $phone = $this->normalizePhone($customer['phone'] ?? null); + $phoneAlt = $this->normalizePhone($customer['phone_alt'] ?? null); + $phoneForRubrica = $this->normalizePhoneField($customer['phone'] ?? null); $phoneAltForRubrica = $this->normalizePhoneField($customer['phone_alt'] ?? null); - $cf = $this->cleanString($customer['codice_fiscale'] ?? null); - $piva = $this->cleanString($customer['partita_iva'] ?? null); - $name = $this->cleanString($customer['display_name'] ?? null); - $provincia = $this->normalizeProvince($customer['provincia'] ?? null); + $cf = $this->cleanString($customer['codice_fiscale'] ?? null); + $piva = $this->cleanString($customer['partita_iva'] ?? null); + $name = $this->cleanString($customer['display_name'] ?? null); + $provincia = $this->normalizeProvince($customer['provincia'] ?? null); $rubrica = null; @@ -281,19 +280,19 @@ private function resolveOrCreateRubrica(Amministratore $admin, array $customer, $dirty = false; foreach ([ - 'email' => $email, + 'email' => $email, 'telefono_cellulare' => $phoneForRubrica, - 'telefono_ufficio' => $phoneAltForRubrica, - 'indirizzo' => $this->cleanString($customer['indirizzo'] ?? null), - 'cap' => $this->cleanString($customer['cap'] ?? null), - 'citta' => $this->cleanString($customer['citta'] ?? null), - 'provincia' => $provincia, - 'partita_iva' => $piva, - 'codice_fiscale' => $cf, + 'telefono_ufficio' => $phoneAltForRubrica, + 'indirizzo' => $this->cleanString($customer['indirizzo'] ?? null), + 'cap' => $this->cleanString($customer['cap'] ?? null), + 'citta' => $this->cleanString($customer['citta'] ?? null), + 'provincia' => $provincia, + 'partita_iva' => $piva, + 'codice_fiscale' => $cf, ] as $field => $value) { if ($value !== null && ! filled($rubrica->{$field})) { $rubrica->{$field} = $value; - $dirty = true; + $dirty = true; } } @@ -314,26 +313,26 @@ private function resolveOrCreateRubrica(Amministratore $admin, array $customer, [$nome, $cognome] = $this->splitDisplayName($name); return RubricaUniversale::query()->create([ - 'amministratore_id' => (int) $admin->id, - 'nome' => $nome, - 'cognome' => $cognome, - 'tipo_contatto' => 'persona_fisica', - 'codice_fiscale' => $cf, - 'partita_iva' => $piva, - 'indirizzo' => $this->cleanString($customer['indirizzo'] ?? null), - 'cap' => $this->cleanString($customer['cap'] ?? null), - 'citta' => $this->cleanString($customer['citta'] ?? null), - 'provincia' => $provincia, - 'telefono_cellulare' => $phoneForRubrica, - 'telefono_ufficio' => $phoneAltForRubrica, - 'email' => $email, - 'note' => $this->buildRubricaNote($customer), - 'categoria' => 'cliente', - 'stato' => 'attivo', - 'data_inserimento' => now(), + 'amministratore_id' => (int) $admin->id, + 'nome' => $nome, + 'cognome' => $cognome, + 'tipo_contatto' => 'persona_fisica', + 'codice_fiscale' => $cf, + 'partita_iva' => $piva, + 'indirizzo' => $this->cleanString($customer['indirizzo'] ?? null), + 'cap' => $this->cleanString($customer['cap'] ?? null), + 'citta' => $this->cleanString($customer['citta'] ?? null), + 'provincia' => $provincia, + 'telefono_cellulare' => $phoneForRubrica, + 'telefono_ufficio' => $phoneAltForRubrica, + 'email' => $email, + 'note' => $this->buildRubricaNote($customer), + 'categoria' => 'cliente', + 'stato' => 'attivo', + 'data_inserimento' => now(), 'data_ultima_modifica' => now(), - 'creato_da' => auth()->id(), - 'modificato_da' => auth()->id(), + 'creato_da' => auth()->id(), + 'modificato_da' => auth()->id(), ]); } @@ -438,4 +437,4 @@ private function toInt(mixed $value): ?int return (int) $value; } -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Condomini/LettureServiziArchivio.php b/app/Filament/Pages/Condomini/LettureServiziArchivio.php index b141442..d975869 100644 --- a/app/Filament/Pages/Condomini/LettureServiziArchivio.php +++ b/app/Filament/Pages/Condomini/LettureServiziArchivio.php @@ -680,8 +680,8 @@ public function table(Table $table): Table ->button() ->color('primary') ->url(fn(StabileServizioLettura $record): ?string => $record->fattura_elettronica_id - ? FatturaElettronicaScheda::getUrl(['record' => (int) $record->fattura_elettronica_id], panel: 'admin-filament') - : null) + ? FatturaElettronicaScheda::getUrl(['record' => (int) $record->fattura_elettronica_id], panel : 'admin-filament') + : null) ->openUrlInNewTab() ->visible(fn(StabileServizioLettura $record): bool => (int) ($record->fattura_elettronica_id ?? 0) > 0), diff --git a/app/Filament/Pages/Condomini/TabelleMillesimaliArchivio.php b/app/Filament/Pages/Condomini/TabelleMillesimaliArchivio.php index 4a3ba4d..5582f61 100644 --- a/app/Filament/Pages/Condomini/TabelleMillesimaliArchivio.php +++ b/app/Filament/Pages/Condomini/TabelleMillesimaliArchivio.php @@ -1,24 +1,23 @@ hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); + && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } public function mount(): void @@ -108,15 +107,15 @@ public function mount(): void return; } - $this->stabileId = (int) $stabileId; + $this->stabileId = (int) $stabileId; $this->annoGestione = AnnoGestioneContext::resolveActiveAnno($user); $this->codiceStabile = Stabile::query() ->whereKey($this->stabileId) ->value('codice_stabile') ?: Stabile::query() - ->whereKey($this->stabileId) - ->value('codice_interno'); + ->whereKey($this->stabileId) + ->value('codice_interno'); $requestedTab = request()->query('tab'); if (in_array($requestedTab, ['archivio', 'prospetto', 'voci'], true)) { @@ -125,7 +124,7 @@ public function mount(): void $this->loadTabelle(); - $requested = request()->integer('tabella_id') ?: null; + $requested = request()->integer('tabella_id') ?: null; $this->tabellaId = $this->pickTabellaId($requested); $this->loadDettaglioTabella(); @@ -183,7 +182,7 @@ protected function getTableQuery(): Builder return TabellaMillesimale::query() ->where('stabile_id', $activeStabileId) - // In UI il conteggio "Unità" deve riflettere quante unità hanno righe millesimi + // In UI il conteggio "Unità" deve riflettere quante unità hanno righe millesimi ->withCount([ 'dettagliMillesimali as unita_partecipanti_count' => function (Builder $q): void { $q @@ -194,7 +193,7 @@ protected function getTableQuery(): Builder ->select(DB::raw('count(distinct unita_immobiliare_id)')); }, ]) - // Nascondi le tabelle generate automaticamente da GESCON (es. *.ST) + // Nascondi le tabelle generate automaticamente da GESCON (es. *.ST) ->where(function (Builder $q) { $q->whereNull('descrizione')->orWhere('descrizione', 'not like', '%generata automaticamente%'); }) @@ -217,8 +216,8 @@ public function table(Table $table): Table 'a_consumo' => 'A CONSUMO', 'conguagli' => 'CONGUAGLI', 'personali' => 'PERSONALI', - 'parti' => 'PARTI', - 'fisso' => 'FISSO', + 'parti' => 'PARTI', + 'fisso' => 'FISSO', ]; return $table @@ -286,32 +285,32 @@ public function table(Table $table): Table return; } - $gruppo = strtoupper(trim((string) ($data['gruppo'] ?? 'O'))); + $gruppo = strtoupper(trim((string) ($data['gruppo'] ?? 'O'))); $tipoTabella = match ($gruppo) { - 'R' => 'riscaldamento', - 'S' => 'straordinaria', + 'R' => 'riscaldamento', + 'S' => 'straordinaria', default => 'ordinaria', }; $tipoCalcolo = strtolower(trim((string) ($data['tipo_calcolo'] ?? 'millesimi'))); $meta = [ - 'tipo' => $gruppo, + 'tipo' => $gruppo, 'calcolo' => $tipoCalcolo, ]; $payload = [ - 'stabile_id' => (int) $stabileId, - 'codice_tabella' => strtoupper(trim((string) ($data['codice_tabella'] ?? ''))), - 'denominazione' => trim((string) ($data['denominazione'] ?? '')), - 'tipo_tabella' => $tipoTabella, - 'tipo_calcolo' => $tipoCalcolo, - 'totale_millesimi' => is_numeric($data['totale_millesimi'] ?? null) ? (float) $data['totale_millesimi'] : null, + 'stabile_id' => (int) $stabileId, + 'codice_tabella' => strtoupper(trim((string) ($data['codice_tabella'] ?? ''))), + 'denominazione' => trim((string) ($data['denominazione'] ?? '')), + 'tipo_tabella' => $tipoTabella, + 'tipo_calcolo' => $tipoCalcolo, + 'totale_millesimi' => is_numeric($data['totale_millesimi'] ?? null) ? (float) $data['totale_millesimi'] : null, 'ordine_visualizzazione' => is_numeric($data['ordine_visualizzazione'] ?? null) ? (int) $data['ordine_visualizzazione'] : null, - 'ordinamento' => is_numeric($data['ordinamento'] ?? null) ? (int) $data['ordinamento'] : null, - 'attiva' => (bool) ($data['attiva'] ?? true), - 'note' => trim((string) ($data['note'] ?? '')) ?: null, - 'meta_legacy' => $meta, + 'ordinamento' => is_numeric($data['ordinamento'] ?? null) ? (int) $data['ordinamento'] : null, + 'attiva' => (bool) ($data['attiva'] ?? true), + 'note' => trim((string) ($data['note'] ?? '')) ?: null, + 'meta_legacy' => $meta, ]; if (Schema::hasColumn('tabelle_millesimali', 'creato_da')) { @@ -354,10 +353,13 @@ public function table(Table $table): Table ? $record->meta_legacy : (is_string($record->meta_legacy) ? json_decode($record->meta_legacy, true) : null); $v = is_array($meta) ? ($meta['calcolo'] ?? null) : null; - if (!$v) { + if (! $v) { $v = $record->tipo_calcolo; } - if (!$v) return '—'; + if (! $v) { + return '—'; + } + $v = strtolower((string) $v); return match ($v) { 'acqua', 'a', 'consumo', 'c', 'a_consumo' => 'A CONSUMO', @@ -377,7 +379,7 @@ public function table(Table $table): Table $meta = is_array($record->meta_legacy) ? $record->meta_legacy : (is_string($record->meta_legacy) ? json_decode($record->meta_legacy, true) : null); - $codice = strtoupper((string) ($record->codice_tabella ?? '')); + $codice = strtoupper((string) ($record->codice_tabella ?? '')); $tipoCalcolo = is_array($meta) ? ($meta['calcolo'] ?? null) : null; $tipoCalcolo = $tipoCalcolo ?: $record->tipo_calcolo; $tipoCalcolo = strtolower((string) ($tipoCalcolo ?? '')); @@ -389,9 +391,9 @@ public function table(Table $table): Table if ($ors) { $u = strtoupper((string) $ors); return match ($u) { - 'O' => 'O (Ordinaria)', - 'R' => 'R (Riscaldamento)', - 'S' => 'S (Straordinaria)', + 'O' => 'O (Ordinaria)', + 'R' => 'R (Riscaldamento)', + 'S' => 'S (Straordinaria)', default => strtoupper((string) $ors), }; } @@ -399,15 +401,21 @@ public function table(Table $table): Table $tipologia = strtoupper((string) ($record->tipologia ?? '')); if (in_array($tipologia, ['O', 'R', 'S'], true)) { return match ($tipologia) { - 'R' => 'R (Riscaldamento)', - 'S' => 'S (Straordinaria)', + 'R' => 'R (Riscaldamento)', + 'S' => 'S (Straordinaria)', default => 'O (Ordinaria)', }; } $t = strtolower((string) ($record->tipo_tabella ?? '')); - if ($t === 'riscaldamento') return 'R (Riscaldamento)'; - if ($t === '') return '—'; + if ($t === 'riscaldamento') { + return 'R (Riscaldamento)'; + } + + if ($t === '') { + return '—'; + } + return 'O (Ordinaria)'; }) ->sortable() @@ -522,7 +530,7 @@ public function table(Table $table): Table $gruppo = is_array($meta) ? ($meta['tipo'] ?? null) : null; if (! $gruppo) { - $t = strtolower((string) ($record->tipo_tabella ?? '')); + $t = strtolower((string) ($record->tipo_tabella ?? '')); $gruppo = $t === 'riscaldamento' ? 'R' : ($t === 'straordinaria' ? 'S' : 'O'); } @@ -532,24 +540,24 @@ public function table(Table $table): Table } return [ - 'codice_tabella' => (string) ($record->codice_tabella ?? ''), - 'denominazione' => (string) ($record->denominazione ?? ($record->descrizione ?? '')), - 'gruppo' => strtoupper((string) ($gruppo ?? 'O')), - 'tipo_calcolo' => strtolower((string) ($calcolo ?? 'millesimi')), - 'totale_millesimi' => $record->getRawOriginal('totale_millesimi'), + 'codice_tabella' => (string) ($record->codice_tabella ?? ''), + 'denominazione' => (string) ($record->denominazione ?? ($record->descrizione ?? '')), + 'gruppo' => strtoupper((string) ($gruppo ?? 'O')), + 'tipo_calcolo' => strtolower((string) ($calcolo ?? 'millesimi')), + 'totale_millesimi' => $record->getRawOriginal('totale_millesimi'), 'ordine_visualizzazione' => $record->ordine_visualizzazione, - 'ordinamento' => $record->ordinamento, - 'attiva' => (bool) ($record->attiva ?? true), - 'note' => (string) ($record->note ?? ''), + 'ordinamento' => $record->ordinamento, + 'attiva' => (bool) ($record->attiva ?? true), + 'note' => (string) ($record->note ?? ''), ]; }) ->action(function (TabellaMillesimale $record, array $data): void { $user = Auth::user(); - $gruppo = strtoupper(trim((string) ($data['gruppo'] ?? 'O'))); + $gruppo = strtoupper(trim((string) ($data['gruppo'] ?? 'O'))); $tipoTabella = match ($gruppo) { - 'R' => 'riscaldamento', - 'S' => 'straordinaria', + 'R' => 'riscaldamento', + 'S' => 'straordinaria', default => 'ordinaria', }; @@ -558,21 +566,21 @@ public function table(Table $table): Table $meta = is_array($record->meta_legacy) ? $record->meta_legacy : (is_string($record->meta_legacy) ? json_decode($record->meta_legacy, true) : []); - $meta = is_array($meta) ? $meta : []; - $meta['tipo'] = $gruppo; + $meta = is_array($meta) ? $meta : []; + $meta['tipo'] = $gruppo; $meta['calcolo'] = $tipoCalcolo; $payload = [ - 'codice_tabella' => strtoupper(trim((string) ($data['codice_tabella'] ?? ''))), - 'denominazione' => trim((string) ($data['denominazione'] ?? '')), - 'tipo_tabella' => $tipoTabella, - 'tipo_calcolo' => $tipoCalcolo, - 'totale_millesimi' => is_numeric($data['totale_millesimi'] ?? null) ? (float) $data['totale_millesimi'] : null, + 'codice_tabella' => strtoupper(trim((string) ($data['codice_tabella'] ?? ''))), + 'denominazione' => trim((string) ($data['denominazione'] ?? '')), + 'tipo_tabella' => $tipoTabella, + 'tipo_calcolo' => $tipoCalcolo, + 'totale_millesimi' => is_numeric($data['totale_millesimi'] ?? null) ? (float) $data['totale_millesimi'] : null, 'ordine_visualizzazione' => is_numeric($data['ordine_visualizzazione'] ?? null) ? (int) $data['ordine_visualizzazione'] : null, - 'ordinamento' => is_numeric($data['ordinamento'] ?? null) ? (int) $data['ordinamento'] : null, - 'attiva' => (bool) ($data['attiva'] ?? true), - 'note' => trim((string) ($data['note'] ?? '')) ?: null, - 'meta_legacy' => $meta, + 'ordinamento' => is_numeric($data['ordinamento'] ?? null) ? (int) $data['ordinamento'] : null, + 'attiva' => (bool) ($data['attiva'] ?? true), + 'note' => trim((string) ($data['note'] ?? '')) ?: null, + 'meta_legacy' => $meta, ]; if ($user instanceof User && Schema::hasColumn('tabelle_millesimali', 'modificato_da')) { @@ -617,13 +625,13 @@ protected function loadTabelle(): void ->get(['id', 'codice_tabella', 'nome_tabella', 'denominazione', 'tipo_tabella', 'tipo_calcolo', 'totale_millesimi', 'ordinamento', 'ordine_visualizzazione', 'nord', 'meta_legacy']) ->map(function (TabellaMillesimale $t) { $codice = $t->codice_tabella ?: ($t->nome_tabella ?: ('TAB ' . $t->id)); - $nome = $t->denominazione ?: ($t->nome_tabella_millesimale ?: ($t->nome_tabella ?: 'Tabella')); + $nome = $t->denominazione ?: ($t->nome_tabella_millesimale ?: ($t->nome_tabella ?: 'Tabella')); - $calcoloRaw = $this->resolveCalcoloRaw($t); - $isConsumo = $this->isConsumoCalcolo($calcoloRaw); + $calcoloRaw = $this->resolveCalcoloRaw($t); + $isConsumo = $this->isConsumoCalcolo($calcoloRaw); $calcoloLabel = $this->formatCalcoloLabel($calcoloRaw); - $tipoLabel = $this->resolveTipoLabel($t, $calcoloRaw); - $meta = is_array($t->meta_legacy) + $tipoLabel = $this->resolveTipoLabel($t, $calcoloRaw); + $meta = is_array($t->meta_legacy) ? $t->meta_legacy : (is_string($t->meta_legacy) ? json_decode($t->meta_legacy, true) : null); $meta = is_array($meta) ? $meta : []; @@ -631,32 +639,32 @@ protected function loadTabelle(): void $cons = $meta['tot_cons_euro'] ?? $meta['tot_cons'] ?? null; return [ - 'id' => (int) $t->id, - 'codice' => $codice, - 'nome' => $nome, - 'nord' => $this->resolveNordValue($t), - 'tipo' => $tipoLabel, - 'calcolo' => $calcoloLabel, - 'is_consumo' => $isConsumo, - 'totale' => is_numeric($t->totale_millesimi) ? (float) $t->totale_millesimi : null, - 'ordinamento' => $this->resolveNordValue($t) ?? (int) ($t->ordine_visualizzazione ?? $t->ordinamento ?? 999999), + 'id' => (int) $t->id, + 'codice' => $codice, + 'nome' => $nome, + 'nord' => $this->resolveNordValue($t), + 'tipo' => $tipoLabel, + 'calcolo' => $calcoloLabel, + 'is_consumo' => $isConsumo, + 'totale' => is_numeric($t->totale_millesimi) ? (float) $t->totale_millesimi : null, + 'ordinamento' => $this->resolveNordValue($t) ?? (int) ($t->ordine_visualizzazione ?? $t->ordinamento ?? 999999), 'is_bilanciata' => (bool) ($t->is_bilanciata ?? false), - 'preventivo' => is_numeric($prev) ? (float) $prev : null, - 'consuntivo' => is_numeric($cons) ? (float) $cons : null, + 'preventivo' => is_numeric($prev) ? (float) $prev : null, + 'consuntivo' => is_numeric($cons) ? (float) $cons : null, ]; }) ->values() ->all(); usort($this->tabelle, static function (array $left, array $right): int { - $leftNord = is_numeric($left['ordinamento'] ?? null) ? (int) $left['ordinamento'] : 999999; + $leftNord = is_numeric($left['ordinamento'] ?? null) ? (int) $left['ordinamento'] : 999999; $rightNord = is_numeric($right['ordinamento'] ?? null) ? (int) $right['ordinamento'] : 999999; if ($leftNord !== $rightNord) { return $leftNord <=> $rightNord; } - $leftTipo = (string) ($left['tipo'] ?? ''); + $leftTipo = (string) ($left['tipo'] ?? ''); $rightTipo = (string) ($right['tipo'] ?? ''); if ($leftTipo !== $rightTipo) { return strnatcasecmp($leftTipo, $rightTipo); @@ -678,7 +686,7 @@ protected function pickTabellaId(?int $candidate): ?int protected function loadDettaglioTabella(): void { - $this->righe = []; + $this->righe = []; $this->tabellaInfo = null; if (! $this->stabileId || ! $this->tabellaId) { @@ -700,27 +708,27 @@ protected function loadDettaglioTabella(): void return; } - $rawTotale = $tabella->getRawOriginal('totale_millesimi'); + $rawTotale = $tabella->getRawOriginal('totale_millesimi'); $totaleCalcolato = (float) ($tabella->calcolaTotaleMillesimi() ?? 0); - $totale = is_numeric($rawTotale) ? (float) $rawTotale : $totaleCalcolato; - $codice = $tabella->codice_tabella ?: ($tabella->nome_tabella ?: ('TAB ' . $tabella->id)); - $nome = $tabella->denominazione ?: ($tabella->nome_tabella_millesimale ?: ($tabella->nome_tabella ?: 'Tabella')); + $totale = is_numeric($rawTotale) ? (float) $rawTotale : $totaleCalcolato; + $codice = $tabella->codice_tabella ?: ($tabella->nome_tabella ?: ('TAB ' . $tabella->id)); + $nome = $tabella->denominazione ?: ($tabella->nome_tabella_millesimale ?: ($tabella->nome_tabella ?: 'Tabella')); $isBilanciata = is_numeric($rawTotale) ? (abs(((float) $rawTotale) - 1000) < 0.01) : (bool) $tabella->isBilanciata(); $this->tabellaInfo = [ - 'id' => (int) $tabella->id, - 'codice' => $codice, - 'nome' => $nome, - 'tipo' => $this->resolveTipoLabel($tabella, $this->resolveCalcoloRaw($tabella)), - 'calcolo' => $this->formatCalcoloLabel($this->resolveCalcoloRaw($tabella)), - 'is_consumo' => $this->isConsumoCalcolo($this->resolveCalcoloRaw($tabella)), - 'totale' => $totale, + 'id' => (int) $tabella->id, + 'codice' => $codice, + 'nome' => $nome, + 'tipo' => $this->resolveTipoLabel($tabella, $this->resolveCalcoloRaw($tabella)), + 'calcolo' => $this->formatCalcoloLabel($this->resolveCalcoloRaw($tabella)), + 'is_consumo' => $this->isConsumoCalcolo($this->resolveCalcoloRaw($tabella)), + 'totale' => $totale, 'is_bilanciata' => $isBilanciata, - 'preventivo' => $this->resolveLegacyImporto($tabella, 'tot_prev_euro', 'tot_prev'), - 'consuntivo' => $this->resolveLegacyImporto($tabella, 'tot_cons_euro', 'tot_cons'), + 'preventivo' => $this->resolveLegacyImporto($tabella, 'tot_prev_euro', 'tot_prev'), + 'consuntivo' => $this->resolveLegacyImporto($tabella, 'tot_cons_euro', 'tot_cons'), ]; $this->righe = $tabella->dettagliMillesimali @@ -737,22 +745,22 @@ protected function loadDettaglioTabella(): void ); }) ->map(function ($d) use ($totale) { - $u = $d->unitaImmobiliare; - $millesimi = (float) ($d->millesimi ?? 0); + $u = $d->unitaImmobiliare; + $millesimi = (float) ($d->millesimi ?? 0); $percentuale = $totale > 0 ? ($millesimi / $totale) * 100 : 0; return [ - 'unita_id' => (int) ($u?->id ?? 0), - 'codice_unita' => $u?->codice_unita ?? ($u?->codice_completo ?? null), + 'unita_id' => (int) ($u?->id ?? 0), + 'codice_unita' => $u?->codice_unita ?? ($u?->codice_completo ?? null), 'codice_unita_display' => $this->formatCodiceUnita($u?->codice_unita ?? ($u?->codice_completo ?? null)), - 'denominazione' => $u?->denominazione, - 'palazzina' => $u?->palazzina, - 'scala' => $u?->scala, - 'piano' => $u?->piano, - 'interno' => $u?->interno, - 'millesimi' => $millesimi, - 'percentuale' => round($percentuale, 4), - 'partecipa' => (bool) ($d->partecipa ?? true), + 'denominazione' => $u?->denominazione, + 'palazzina' => $u?->palazzina, + 'scala' => $u?->scala, + 'piano' => $u?->piano, + 'interno' => $u?->interno, + 'millesimi' => $millesimi, + 'percentuale' => round($percentuale, 4), + 'partecipa' => (bool) ($d->partecipa ?? true), ]; }) ->values() @@ -772,7 +780,7 @@ protected function loadVociSpesa(): void } $hasCond = Schema::hasColumn('voci_spesa', 'percentuale_condomino'); - $hasInq = Schema::hasColumn('voci_spesa', 'percentuale_inquilino'); + $hasInq = Schema::hasColumn('voci_spesa', 'percentuale_inquilino'); $rows = VoceSpesa::query() ->where('stabile_id', $this->stabileId) @@ -783,15 +791,15 @@ protected function loadVociSpesa(): void $this->vociSpesa = $rows->map(function (VoceSpesa $v) use ($hasCond, $hasInq): array { return [ - 'id' => (int) $v->id, - 'codice' => $v->codice, - 'descrizione' => $v->descrizione, - 'categoria' => $v->categoria, - 'tipo_gestione' => $v->tipo_gestione, + 'id' => (int) $v->id, + 'codice' => $v->codice, + 'descrizione' => $v->descrizione, + 'categoria' => $v->categoria, + 'tipo_gestione' => $v->tipo_gestione, 'percentuale_condomino' => $hasCond ? (float) ($v->percentuale_condomino ?? 0) : null, 'percentuale_inquilino' => $hasInq ? (float) ($v->percentuale_inquilino ?? 0) : null, - 'attiva' => (bool) ($v->attiva ?? true), - 'modifica' => false, + 'attiva' => (bool) ($v->attiva ?? true), + 'modifica' => false, ]; })->all(); @@ -830,11 +838,11 @@ public function applyPresetRipartizione(): void foreach ($this->vociSpesa as $i => $voce) { $categoria = $this->mapVoceCategoriaToConfedilizia((string) ($voce['categoria'] ?? '')); - $default = RipartizioneSpeseInquilini::getDefaultConfedilizia($categoria); + $default = RipartizioneSpeseInquilini::getDefaultConfedilizia($categoria); $this->vociSpesa[$i]['percentuale_condomino'] = (float) ($default['proprietario'] ?? 100); $this->vociSpesa[$i]['percentuale_inquilino'] = (float) ($default['inquilino'] ?? 0); - $this->vociSpesa[$i]['modifica'] = true; + $this->vociSpesa[$i]['modifica'] = true; } $this->presetDirty = true; @@ -856,7 +864,7 @@ public function applyCustomPreset(): void } $data = is_array($preset->dati) ? $preset->dati : []; - $map = []; + $map = []; foreach ($data as $row) { $codice = isset($row['codice']) ? (string) $row['codice'] : ''; if ($codice !== '') { @@ -870,7 +878,7 @@ public function applyCustomPreset(): void continue; } - $row = $map[$codice]; + $row = $map[$codice]; $cond = isset($row['percentuale_condomino']) && is_numeric($row['percentuale_condomino']) ? (float) $row['percentuale_condomino'] : 100.0; @@ -880,7 +888,7 @@ public function applyCustomPreset(): void $this->vociSpesa[$i]['percentuale_condomino'] = $cond; $this->vociSpesa[$i]['percentuale_inquilino'] = $inq; - $this->vociSpesa[$i]['modifica'] = true; + $this->vociSpesa[$i]['modifica'] = true; } $this->presetDirty = false; @@ -900,7 +908,7 @@ public function savePresetAsNew(): void $data = array_values(array_map(function ($row): array { return [ - 'codice' => $row['codice'] ?? null, + 'codice' => $row['codice'] ?? null, 'percentuale_condomino' => is_numeric($row['percentuale_condomino'] ?? null) ? (float) $row['percentuale_condomino'] : null, 'percentuale_inquilino' => is_numeric($row['percentuale_inquilino'] ?? null) ? (float) $row['percentuale_inquilino'] : null, ]; @@ -908,15 +916,15 @@ public function savePresetAsNew(): void $preset = RipartizionePreset::query()->create([ 'stabile_id' => $this->stabileId, - 'nome' => $nome, - 'dati' => $data, + 'nome' => $nome, + 'dati' => $data, ]); $this->presetNome = ''; $this->loadPresetOptions(); - $this->presetId = (int) $preset->id; + $this->presetId = (int) $preset->id; $this->presetRipartizione = 'custom'; - $this->presetDirty = false; + $this->presetDirty = false; Notification::make()->title('Preset creato')->success()->send(); } @@ -938,7 +946,7 @@ public function updatePreset(): void $data = array_values(array_map(function ($row): array { return [ - 'codice' => $row['codice'] ?? null, + 'codice' => $row['codice'] ?? null, 'percentuale_condomino' => is_numeric($row['percentuale_condomino'] ?? null) ? (float) $row['percentuale_condomino'] : null, 'percentuale_inquilino' => is_numeric($row['percentuale_inquilino'] ?? null) ? (float) $row['percentuale_inquilino'] : null, ]; @@ -988,23 +996,23 @@ public function updatedVociSpesa($value, string $name): void private function mapVoceCategoriaToConfedilizia(string $categoriaVoce): string { return match ($categoriaVoce) { - 'riscaldamento' => 'B', - 'ascensore' => 'C', - 'illuminazione' => 'D', - 'pulizia' => 'E', - 'acqua' => 'G', - 'energia_elettrica' => 'L', - 'gas' => 'B', - 'giardino_verde' => 'I', - 'sicurezza' => 'N', - 'amministrazione' => 'O', - 'assicurazioni' => 'A', - 'tasse_tributi' => 'O', - 'spese_legali' => 'O', - 'manutenzione_ordinaria' => 'A', + 'riscaldamento' => 'B', + 'ascensore' => 'C', + 'illuminazione' => 'D', + 'pulizia' => 'E', + 'acqua' => 'G', + 'energia_elettrica' => 'L', + 'gas' => 'B', + 'giardino_verde' => 'I', + 'sicurezza' => 'N', + 'amministrazione' => 'O', + 'assicurazioni' => 'A', + 'tasse_tributi' => 'O', + 'spese_legali' => 'O', + 'manutenzione_ordinaria' => 'A', 'manutenzione_straordinaria' => 'A', - 'lavori_miglioramento' => 'A', - default => 'A', + 'lavori_miglioramento' => 'A', + default => 'A', }; } @@ -1019,13 +1027,13 @@ public function saveVociSpesa(): void } $hasCond = Schema::hasColumn('voci_spesa', 'percentuale_condomino'); - $hasInq = Schema::hasColumn('voci_spesa', 'percentuale_inquilino'); + $hasInq = Schema::hasColumn('voci_spesa', 'percentuale_inquilino'); $validated = Validator::make( ['voci' => $this->vociSpesa], [ - 'voci' => ['array'], - 'voci.*.id' => ['required', 'integer', 'min:1'], + 'voci' => ['array'], + 'voci.*.id' => ['required', 'integer', 'min:1'], 'voci.*.percentuale_condomino' => ['nullable', 'numeric', 'min:0', 'max:100'], 'voci.*.percentuale_inquilino' => ['nullable', 'numeric', 'min:0', 'max:100'], ] @@ -1095,7 +1103,7 @@ private function formatCodiceUnita(?string $codice): ?string if ($prefix !== '') { $pattern = '/^' . preg_quote($prefix, '/') . '[\s\-\/]+/'; - $codice = preg_replace($pattern, '', $codice) ?? $codice; + $codice = preg_replace($pattern, '', $codice) ?? $codice; } return $codice; @@ -1150,12 +1158,12 @@ public function inizializzaRighe(): void $rows = array_map(function (int $unitaId) use ($userId): array { return [ 'tabella_millesimale_id' => (int) $this->tabellaId, - 'unita_immobiliare_id' => $unitaId, - 'millesimi' => 0, - 'partecipa' => 1, - 'created_by' => $userId, - 'created_at' => now(), - 'updated_at' => now(), + 'unita_immobiliare_id' => $unitaId, + 'millesimi' => 0, + 'partecipa' => 1, + 'created_by' => $userId, + 'created_at' => now(), + 'updated_at' => now(), ]; }, $missing); @@ -1179,15 +1187,15 @@ public function saveRighe(): void $validated = Validator::make( ['righe' => $this->righe], [ - 'righe' => ['array'], - 'righe.*.unita_id' => ['required', 'integer', 'min:1'], + 'righe' => ['array'], + 'righe.*.unita_id' => ['required', 'integer', 'min:1'], 'righe.*.millesimi' => ['required', 'numeric', 'min:0'], 'righe.*.partecipa' => ['nullable', 'boolean'], ] )->validate(); /** @var array> $rows */ - $rows = (array) ($validated['righe'] ?? []); + $rows = (array) ($validated['righe'] ?? []); $unitaIds = array_values(array_unique(array_map(fn($r) => (int) ($r['unita_id'] ?? 0), $rows))); $unitaIds = array_values(array_filter($unitaIds, fn(int $v) => $v > 0)); @@ -1203,8 +1211,8 @@ public function saveRighe(): void ->all(); $allowedSet = array_fill_keys($allowedUnita, true); - $now = now(); - $userId = Auth::id(); + $now = now(); + $userId = Auth::id(); DB::transaction(function () use ($rows, $allowedSet, $now, $userId): void { foreach ($rows as $r) { @@ -1231,12 +1239,12 @@ public function saveRighe(): void DettaglioMillesimi::query()->create([ 'tabella_millesimale_id' => (int) $this->tabellaId, - 'unita_immobiliare_id' => $unitaId, - 'millesimi' => $millesimi, - 'partecipa' => $partecipa, - 'created_by' => $userId, - 'created_at' => $now, - 'updated_at' => $now, + 'unita_immobiliare_id' => $unitaId, + 'millesimi' => $millesimi, + 'partecipa' => $partecipa, + 'created_by' => $userId, + 'created_at' => $now, + 'updated_at' => $now, ]); } }); @@ -1275,10 +1283,10 @@ private function resolveCalcoloRaw(TabellaMillesimale $t): ?string } return match ($v) { - 'm' => 'millesimi', + 'm' => 'millesimi', 'a', 'c' => 'a_consumo', - 'x' => 'conguagli', - 'p' => 'personali', + 'x' => 'conguagli', + 'p' => 'personali', default => $v, }; } @@ -1316,9 +1324,9 @@ private function resolveTipoLabel(TabellaMillesimale $t, ?string $calcoloRaw): ? $ors = isset($meta['tipo']) ? strtoupper((string) $meta['tipo']) : null; if ($ors) { return match ($ors) { - 'O' => 'O (Ordinaria)', - 'R' => 'R (Riscaldamento)', - 'S' => 'S (Straordinaria)', + 'O' => 'O (Ordinaria)', + 'R' => 'R (Riscaldamento)', + 'S' => 'S (Straordinaria)', default => $ors, }; } @@ -1326,16 +1334,24 @@ private function resolveTipoLabel(TabellaMillesimale $t, ?string $calcoloRaw): ? $tipologia = strtoupper((string) ($t->tipologia ?? '')); if (in_array($tipologia, ['O', 'R', 'S'], true)) { return match ($tipologia) { - 'R' => 'R (Riscaldamento)', - 'S' => 'S (Straordinaria)', + 'R' => 'R (Riscaldamento)', + 'S' => 'S (Straordinaria)', default => 'O (Ordinaria)', }; } $tipo = strtolower((string) ($t->tipo_tabella ?? '')); - if ($tipo === 'riscaldamento') return 'R (Riscaldamento)'; - if ($tipo === 'straordinaria') return 'S (Straordinaria)'; - if ($tipo === 'ordinaria') return 'O (Ordinaria)'; + if ($tipo === 'riscaldamento') { + return 'R (Riscaldamento)'; + } + + if ($tipo === 'straordinaria') { + return 'S (Straordinaria)'; + } + + if ($tipo === 'ordinaria') { + return 'O (Ordinaria)'; + } return $tipo !== '' ? strtoupper($tipo) : null; } @@ -1345,7 +1361,7 @@ private function resolveLegacyImporto(TabellaMillesimale $t, string $primary, st $meta = is_array($t->meta_legacy) ? $t->meta_legacy : (is_string($t->meta_legacy) ? json_decode($t->meta_legacy, true) : null); - $meta = is_array($meta) ? $meta : []; + $meta = is_array($meta) ? $meta : []; $value = $meta[$primary] ?? $meta[$fallback] ?? null; return is_numeric($value) ? (float) $value : null; } diff --git a/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php b/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php index 2fedd0f..4d06512 100644 --- a/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php +++ b/app/Filament/Pages/Fornitore/ImpostazioniArchivio.php @@ -8,7 +8,6 @@ use App\Models\Stabile; use App\Models\User; use App\Services\TenantArchivePathService; -use App\Support\ArchivioPaths; use App\Support\GoogleAccountStore; use BackedEnum; use Filament\Notifications\Notification; @@ -575,10 +574,10 @@ private function resolveTecnoRepairMdbPath(?string $rawPath): ?string return null; } - $diskRoot = str_replace('\\', '/', Storage::disk('local')->path('')); - $storageBase = str_replace('\\', '/', storage_path('app')); - $normalizedInput = str_replace('\\', '/', $path); - $candidatePaths = []; + $diskRoot = str_replace('\\', '/', Storage::disk('local')->path('')); + $storageBase = str_replace('\\', '/', storage_path('app')); + $normalizedInput = str_replace('\\', '/', $path); + $candidatePaths = []; if (is_file($path)) { $candidatePaths[] = $path; diff --git a/app/Models/FornitoreCliente.php b/app/Models/FornitoreCliente.php index 95be34d..79b33d8 100644 --- a/app/Models/FornitoreCliente.php +++ b/app/Models/FornitoreCliente.php @@ -1,5 +1,4 @@ belongsTo(RubricaUniversale::class, 'rubrica_id'); } -} \ No newline at end of file +} diff --git a/app/Services/TenantArchivePathService.php b/app/Services/TenantArchivePathService.php index 0ffc4ad..5e94b34 100644 --- a/app/Services/TenantArchivePathService.php +++ b/app/Services/TenantArchivePathService.php @@ -4,8 +4,8 @@ use App\Models\Amministratore; use App\Models\Fornitore; use App\Models\Stabile; -use InvalidArgumentException; use Illuminate\Support\Facades\Storage; +use InvalidArgumentException; class TenantArchivePathService { diff --git a/app/Support/ArchivioPaths.php b/app/Support/ArchivioPaths.php index 5c21a3d..376ace7 100644 --- a/app/Support/ArchivioPaths.php +++ b/app/Support/ArchivioPaths.php @@ -1,5 +1,4 @@ codice_univoco ?: $admin->codice_amministratore ?: '') diff --git a/config/netgescon.php b/config/netgescon.php index f598372..c8725f5 100755 --- a/config/netgescon.php +++ b/config/netgescon.php @@ -137,7 +137,7 @@ ], 'google' => [ - 'drive_template_folders' => [ + 'drive_template_folders' => [ 'bilanci', 'riscaldamento', 'visure_catastali', @@ -171,7 +171,7 @@ 'informative', 'verifiche_periodiche', ], - 'drive_template_year_root' => 'archivio_per_anno', + 'drive_template_year_root' => 'archivio_per_anno', 'drive_template_year_model' => '_anno_modello', ], ]; diff --git a/database/migrations/2026_04_14_150000_add_cod_forn_to_fornitori_table.php b/database/migrations/2026_04_14_150000_add_cod_forn_to_fornitori_table.php index db168e4..2038546 100644 --- a/database/migrations/2026_04_14_150000_add_cod_forn_to_fornitori_table.php +++ b/database/migrations/2026_04_14_150000_add_cod_forn_to_fornitori_table.php @@ -53,4 +53,4 @@ public function down(): void } }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_04_14_190000_create_fornitore_clienti_table.php b/database/migrations/2026_04_14_190000_create_fornitore_clienti_table.php index 594f450..239ef6a 100644 --- a/database/migrations/2026_04_14_190000_create_fornitore_clienti_table.php +++ b/database/migrations/2026_04_14_190000_create_fornitore_clienti_table.php @@ -45,4 +45,4 @@ public function down(): void { Schema::dropIfExists('fornitore_clienti'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_04_15_220000_update_dettaglio_millesimi_unique_for_ruolo_legacy.php b/database/migrations/2026_04_15_220000_update_dettaglio_millesimi_unique_for_ruolo_legacy.php new file mode 100644 index 0000000..1ab30fa --- /dev/null +++ b/database/migrations/2026_04_15_220000_update_dettaglio_millesimi_unique_for_ruolo_legacy.php @@ -0,0 +1,49 @@ +unique( + ['tabella_millesimale_id', 'unita_immobiliare_id', 'ruolo_legacy'], + 'unique_tabella_unita_ruolo_legacy' + ); + }); + + Schema::table('dettaglio_millesimi', function (Blueprint $table): void { + try { + DB::statement('ALTER TABLE `dettaglio_millesimi` DROP INDEX `unique_tabella_unita`'); + } catch (\Throwable) { + // Already dropped or not present. + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('dettaglio_millesimi')) { + return; + } + + Schema::table('dettaglio_millesimi', function (Blueprint $table): void { + $table->unique(['tabella_millesimale_id', 'unita_immobiliare_id'], 'unique_tabella_unita'); + }); + + Schema::table('dettaglio_millesimi', function (Blueprint $table): void { + try { + DB::statement('ALTER TABLE `dettaglio_millesimi` DROP INDEX `unique_tabella_unita_ruolo_legacy`'); + } catch (\Throwable) { + // Already dropped or not present. + } + }); + } +}; \ No newline at end of file diff --git a/docs/ai/SESSION_HANDOFF.md b/docs/ai/SESSION_HANDOFF.md index 5d57aa5..30fcf46 100644 --- a/docs/ai/SESSION_HANDOFF.md +++ b/docs/ai/SESSION_HANDOFF.md @@ -85,7 +85,7 @@ ## Punti ancora aperti da riprendere ## Prompt consigliato per un altro agent -"Riparti da `docs/ai/SESSION_HANDOFF.md`. Prima verifica `git status`, poi esegui `php artisan view:clear && php artisan view:cache`. Controlla staging su `tabelle-millesimali`, `ticket-acqua` e `ticket-acqua-generale`. Se il deploy e allineato, passa al raffinamento del QA millesimi/voci per gestioni straordinarie con anno/esercizio, senza fare bonifiche contabili distruttive." +"Riparti da `docs/ai/SESSION_HANDOFF.md`. Prima verifica `git status`, poi esegui `php artisan view:clear && php artisan view:cache`. Controlla staging su `tabelle-millesimali`, `ticket-acqua` e `ticket-acqua-generale`. Se il deploy e allineato, passa al raffinamento del QA millesimi/voci per gestioni straordinarie con anno/esercizio, senza fare bonifiche contabili distruttive." --- diff --git a/docs/fornitori/tecnorepair-operativita.md b/docs/fornitori/tecnorepair-operativita.md new file mode 100644 index 0000000..c38dee4 --- /dev/null +++ b/docs/fornitori/tecnorepair-operativita.md @@ -0,0 +1,51 @@ +# Operativita fornitore TecnoRepair + +Questa nota riassume i punti introdotti nel flusso fornitore per archivio, rubrica clienti, Google e fatture elettroniche manuali. + +## Archivio fornitore + +- Il root reale del disco locale applicativo e `storage/app/private`. +- I percorsi mostrati nelle pagine fornitore devono essere risolti tramite `Storage::disk('local')->path(...)`, non costruiti a mano con `storage/app/...`. +- Per riallineare le cartelle fisiche dei fornitori esistenti usare: + +```bash +php artisan netgescon:provision-fornitori-archives +``` + +## Rubrica clienti fornitore + +- La rubrica clienti fornitore e materializzata in `fornitore_clienti`. +- La sorgente attuale non e una tabella MDB separata a runtime: il dato cliente e ricostruito da `AssistenzaTecnorepairScheda.metadata['cliente']` gia importato dal legacy TecnoRepair. +- Il comando operativo e: + +```bash +php artisan tecnorepair:import-rubrica-clienti {amministratore_id} --fornitore-id={fornitore_id} +``` + +- Durante l import vengono normalizzati: + - province legacy troppo lunghe + - campi telefono contaminati da note testuali + +## Google fornitore + +- Gli account Google fornitore usano chiavi dedicate `fornitore-{id}`. +- Regola importante: un account fornitore non deve diventare il default Google dell amministratore. +- Il blocco legacy `google.oauth` va aggiornato solo dal default amministratore, non dagli account scoped del fornitore. +- In caso di collegamento/scollegamento fornitore, la UI amministratore deve continuare a riflettere solo l account default amministratore. + +## FE manuale lato fornitore + +- La pagina `admin-filament/fornitore/contabilita` espone un import manuale FE dal menu azioni in alto. +- File ammessi: XML, P7M, ZIP. +- Il file viene accettato solo se il cedente della FE coincide con P.IVA/CF del fornitore corrente. +- Lo stabile viene risolto in modo automatico dai dati destinatario della FE, ma ristretto agli stabili dell amministratore del fornitore. +- L import riusa `FatturaElettronicaImporter`; non crea fornitori nuovi e non introduce acquisizioni automatiche. + +## Scheda intervento + +- La Blade fornitore e stata resa piu compatta con tab operative: + - `Segnalazione` + - `Operativo` + - `Allegati` + - `Storico` +- L obiettivo e avvicinare la navigazione al flusso desktop TecnoRepair senza riscrivere la logica Livewire esistente. diff --git a/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php b/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php index 5387d67..593b1e4 100644 --- a/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php +++ b/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php @@ -5,6 +5,7 @@
+
+ + + + +
Vista compatta TecnoRepair-like
+
+
+
-
+
Scheda segnalazione
@@ -158,7 +169,7 @@
@if(count($accessSummaryRows) > 0 || filled($dispatchBoardNote)) -
+
Accesso, chiavi e reperibilità
@if(filled($dispatchBoardNote))
{{ $dispatchBoardNote }}
@@ -211,7 +222,7 @@
@endif -
+
Operativo e rapportino
@@ -571,7 +582,7 @@
-
+
Comunicazioni ticket
@forelse($ticket->messages as $msg) @@ -588,7 +599,7 @@
-
+
Stato operativo
Stato: {{ $this->intervento->stato }}
@@ -599,7 +610,7 @@
-
+
Sessioni e presenza
{{ count($sessionRows) }} eventi
@@ -631,7 +642,7 @@
-
+
Google workspace
@if($googleWorkspaceStatus)
@@ -655,7 +666,7 @@ @endif
-
+
Allegati ticket
@forelse($ticket->attachments as $attachment) @@ -686,7 +697,7 @@
-
+
Storico correlato
@forelse($storicoRows as $row)