From c3efa1754b560735d06abfd1d2c1d5d36b5dc8dd Mon Sep 17 00:00:00 2001 From: michele Date: Sun, 12 Apr 2026 08:06:52 +0000 Subject: [PATCH] Checkpoint Google supplier workflow cleanup --- .../GesconBonificaRubricaCanaliCommand.php | 69 ++++--- .../GmailImportStabileTicketsCommand.php | 9 +- .../GooglePushRubricaContactsCommand.php | 103 +++++----- .../GoogleSyncRubricaContactsCommand.php | 26 +-- .../ImportLegacyFornitoriTagsCommand.php | 2 +- .../Commands/NetgesconGitSyncCommand.php | 38 ++-- app/Console/Commands/SmdrListenCommand.php | 2 +- .../Pages/Fornitore/LavorazioniOperative.php | 9 + .../Pages/Fornitore/TicketOperativi.php | 113 ++++++++++- app/Filament/Pages/Gescon/FornitoreScheda.php | 14 +- .../Pages/Impostazioni/GoogleDashboard.php | 4 +- app/Filament/Pages/Mobile/MobileDashboard.php | 4 +- app/Filament/Pages/Strumenti/PostIt.php | 2 +- .../Pages/Strumenti/PostItGestione.php | 8 +- app/Filament/Pages/Supporto/Modifiche.php | 32 ++-- app/Filament/Pages/Supporto/TicketMobile.php | 17 +- app/Filament/Pages/UnitaImmobiliarePage.php | 176 ++++++++++++++++-- .../Widgets/GoogleWorkspaceOverview.php | 20 +- app/Filament/Widgets/TicketMobileOverview.php | 12 +- .../Integrations/GoogleOAuthController.php | 20 +- app/Models/Fornitore.php | 13 +- .../Anagrafiche/RubricaPhoneLookupService.php | 3 +- .../Support/GmailTicketImportService.php | 53 +++--- .../Support/TicketAttachmentUploadService.php | 2 +- app/Support/GoogleAccountStore.php | 63 +++---- .../Livewire/SupportsRubricaLookup.php | 2 +- public/build/manifest.json | 4 +- .../fornitore/lavorazioni-operative.blade.php | 1 + .../fornitore/ticket-operativi.blade.php | 51 +++-- .../pages/unita-immobiliare.blade.php | 79 +++++--- 30 files changed, 636 insertions(+), 315 deletions(-) diff --git a/app/Console/Commands/GesconBonificaRubricaCanaliCommand.php b/app/Console/Commands/GesconBonificaRubricaCanaliCommand.php index 31db2cd..1d0d7a0 100644 --- a/app/Console/Commands/GesconBonificaRubricaCanaliCommand.php +++ b/app/Console/Commands/GesconBonificaRubricaCanaliCommand.php @@ -1,5 +1,4 @@ option('apply'); - $limit = max(1, min((int) ($this->option('limit') ?? 5000), 50000)); + $apply = (bool) $this->option('apply'); + $limit = max(1, min((int) ($this->option('limit') ?? 5000), 50000)); $adminId = (int) ($this->option('admin-id') ?: 0); $stabile = trim((string) ($this->option('stabile') ?? '')); @@ -60,11 +59,11 @@ public function handle(): int } $stats = [ - 'analizzate' => 0, + 'analizzate' => 0, 'rubriche_aggiornate' => 0, - 'canali_creati' => 0, - 'canali_aggiornati' => 0, - 'saltate' => 0, + 'canali_creati' => 0, + 'canali_aggiornati' => 0, + 'saltate' => 0, ]; foreach ($records as $rubrica) { @@ -103,21 +102,21 @@ public function handle(): int } if ($existing instanceof RubricaContattoCanale) { - $existing->etichetta = $channel['etichetta']; + $existing->etichetta = $channel['etichetta']; $existing->is_principale = $channel['is_principale']; - $existing->meta = $channel['meta']; + $existing->meta = $channel['meta']; $existing->save(); $stats['canali_aggiornati']++; continue; } RubricaContattoCanale::query()->create([ - 'rubrica_id' => (int) $rubrica->id, - 'tipo' => $channel['tipo'], - 'etichetta' => $channel['etichetta'], - 'valore' => $channel['valore'], + 'rubrica_id' => (int) $rubrica->id, + 'tipo' => $channel['tipo'], + 'etichetta' => $channel['etichetta'], + 'valore' => $channel['valore'], 'is_principale' => $channel['is_principale'], - 'meta' => $channel['meta'], + 'meta' => $channel['meta'], ]); $stats['canali_creati']++; } @@ -136,7 +135,7 @@ private function resolveScopedRubricaIds(string $stabileCode): array return []; } - $trimmed = ltrim($stabileCode, '0'); + $trimmed = ltrim($stabileCode, '0'); $stabileIds = Stabile::query() ->where(function (Builder $query) use ($stabileCode, $trimmed): void { $query->where('codice_stabile', $stabileCode); @@ -184,21 +183,21 @@ private function resolveScopedRubricaIds(string $stabileCode): array private function extractStructuredContacts(RubricaUniversale $rubrica): array { $emails = $this->uniqueEmails([ - ...$this->splitEmails((string) ($rubrica->email ?? '')), + ...$this->splitEmails((string) ($rubrica->email ?? '')), ]); $pecs = $this->uniqueEmails([ - ...$this->splitEmails((string) ($rubrica->pec ?? '')), + ...$this->splitEmails((string) ($rubrica->pec ?? '')), ]); $mobilePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_cellulare ?? '')), 'cellulare'); $officePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_ufficio ?? '')), 'telefono'); - $homePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_casa ?? '')), 'telefono'); + $homePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_casa ?? '')), 'telefono'); - $primaryEmail = $emails[0] ?? $this->normalizeEmail((string) ($rubrica->email ?? '')); - $primaryPec = $pecs[0] ?? $this->normalizeEmail((string) ($rubrica->pec ?? '')); + $primaryEmail = $emails[0] ?? $this->normalizeEmail((string) ($rubrica->email ?? '')); + $primaryPec = $pecs[0] ?? $this->normalizeEmail((string) ($rubrica->pec ?? '')); $primaryMobile = $mobilePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_cellulare ?? '')); $primaryOffice = $officePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_ufficio ?? '')); - $primaryHome = $homePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_casa ?? '')); + $primaryHome = $homePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_casa ?? '')); $updates = []; if ($primaryEmail !== $this->normalizeEmail((string) ($rubrica->email ?? ''))) { @@ -208,9 +207,9 @@ private function extractStructuredContacts(RubricaUniversale $rubrica): array $updates['pec'] = $primaryPec; } if ($primaryMobile !== $this->normalizePhoneDisplay((string) ($rubrica->telefono_cellulare ?? ''))) { - $updates['telefono_cellulare'] = $primaryMobile; - [$e164, $prefix, $national] = PhoneNumber::splitItaly($primaryMobile); - $updates['prefisso_cellulare'] = $prefix; + $updates['telefono_cellulare'] = $primaryMobile; + [$e164, $prefix, $national] = PhoneNumber::splitItaly($primaryMobile); + $updates['prefisso_cellulare'] = $prefix; $updates['telefono_cellulare_nazionale'] = $national; } if ($primaryOffice !== $this->normalizePhoneDisplay((string) ($rubrica->telefono_ufficio ?? ''))) { @@ -238,7 +237,7 @@ private function extractStructuredContacts(RubricaUniversale $rubrica): array } return [ - 'updates' => $updates, + 'updates' => $updates, 'channels' => $this->deduplicateChannels($channels), ]; } @@ -320,19 +319,19 @@ private function normalizePhones(array $values, string $defaultType): array $result[] = [ 'value' => $normalizedValue, - 'type' => $type, + 'type' => $type, ]; } $unique = []; - $seen = []; + $seen = []; foreach ($result as $row) { $key = $row['type'] . '|' . PhoneNumber::normalizeDigits($row['value']); if (isset($seen[$key])) { continue; } $seen[$key] = true; - $unique[] = $row; + $unique[] = $row; } return $unique; @@ -359,11 +358,11 @@ private function uniqueEmails(array $values): array private function buildChannel(string $tipo, string $valore, string $etichetta, bool $isPrincipale, string $source): array { return [ - 'tipo' => $tipo, - 'valore' => $valore, - 'etichetta' => $etichetta, + 'tipo' => $tipo, + 'valore' => $valore, + 'etichetta' => $etichetta, 'is_principale' => $isPrincipale, - 'meta' => [ + 'meta' => [ 'source' => $source, ], ]; @@ -375,7 +374,7 @@ private function buildChannel(string $tipo, string $valore, string $etichetta, b private function deduplicateChannels(array $channels): array { $result = []; - $seen = []; + $seen = []; foreach ($channels as $channel) { $key = $channel['tipo'] . '|'; @@ -393,9 +392,9 @@ private function deduplicateChannels(array $channels): array } $seen[$key] = count($result); - $result[] = $channel; + $result[] = $channel; } return $result; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/GmailImportStabileTicketsCommand.php b/app/Console/Commands/GmailImportStabileTicketsCommand.php index fe4b6b6..6df1151 100644 --- a/app/Console/Commands/GmailImportStabileTicketsCommand.php +++ b/app/Console/Commands/GmailImportStabileTicketsCommand.php @@ -1,5 +1,4 @@ configurazione_avanzata ?? [], 'posta', []); $caselle = array_values(array_filter((array) Arr::get($posta, 'caselle', []), function ($mailbox): bool { return is_array($mailbox) - && (bool) ($mailbox['enabled'] ?? false) - && strtolower(trim((string) ($mailbox['tipo'] ?? ''))) === 'gmail'; + && (bool) ($mailbox['enabled'] ?? false) + && strtolower(trim((string) ($mailbox['tipo'] ?? ''))) === 'gmail'; })); $filter = trim((string) $this->option('casella')); if ($filter !== '') { $caselle = array_values(array_filter($caselle, static function (array $mailbox) use ($filter): bool { return strcasecmp((string) ($mailbox['label'] ?? ''), $filter) === 0 - || strcasecmp((string) ($mailbox['email'] ?? ''), $filter) === 0; + || strcasecmp((string) ($mailbox['email'] ?? ''), $filter) === 0; })); } @@ -80,4 +79,4 @@ private function resolveStabile(): ?Stabile return null; } -} \ No newline at end of file +} diff --git a/app/Console/Commands/GooglePushRubricaContactsCommand.php b/app/Console/Commands/GooglePushRubricaContactsCommand.php index 5ff3021..b561e37 100644 --- a/app/Console/Commands/GooglePushRubricaContactsCommand.php +++ b/app/Console/Commands/GooglePushRubricaContactsCommand.php @@ -1,5 +1,4 @@ option('dry-run'); - $limit = max(1, (int) $this->option('limit')); + $dryRun = (bool) $this->option('dry-run'); + $limit = max(1, (int) $this->option('limit')); $categorie = collect((array) $this->option('categoria')) - ->map(fn ($v) => trim((string) $v)) - ->filter(fn (string $v) => $v !== '') + ->map(fn($v) => trim((string) $v)) + ->filter(fn(string $v) => $v !== '') ->values() ->all(); $filterAdminIds = collect((array) $this->option('admin-id')) - ->map(fn ($v) => (int) $v) - ->filter(fn (int $v) => $v > 0) + ->map(fn($v) => (int) $v) + ->filter(fn(int $v) => $v > 0) ->values() ->all(); @@ -51,11 +50,11 @@ public function handle(): int $totalCreated = 0; $totalUpdated = 0; $totalSkipped = 0; - $totalErrors = 0; + $totalErrors = 0; foreach ($amministratori as $amministratore) { $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); - $oauth = Arr::get($google, 'oauth', []); + $oauth = Arr::get($google, 'oauth', []); if (! (bool) Arr::get($oauth, 'connected', false)) { $this->line("Admin {$amministratore->id}: Google non collegato, salto."); @@ -85,13 +84,13 @@ public function handle(): int $created = 0; $updated = 0; $skipped = 0; - $errors = 0; + $errors = 0; foreach ($records as $record) { - $emails = $this->emailsForGoogle($record); - $phones = $this->phonesForGoogle($record); - $email = $emails[0]['value'] ?? ''; - $phone = $phones[0]['value'] ?? ''; + $emails = $this->emailsForGoogle($record); + $phones = $this->phonesForGoogle($record); + $email = $emails[0]['value'] ?? ''; + $phone = $phones[0]['value'] ?? ''; $phoneNormalized = $this->normalizePhone($phone); [$givenName, $familyName, $fullName] = $this->buildGoogleNameParts($record); @@ -99,8 +98,8 @@ public function handle(): int $payload = [ 'names' => [[ 'displayName' => $fullName, - 'givenName' => $givenName, - 'familyName' => $familyName, + 'givenName' => $givenName, + 'familyName' => $familyName, ]], ]; @@ -146,7 +145,7 @@ public function handle(): int ]); if ($personResponse->status() === 401) { - $token = $this->resolveAccessToken($amministratore, $google, $oauth, false, true) ?? $token; + $token = $this->resolveAccessToken($amministratore, $google, $oauth, false, true) ?? $token; $personResponse = Http::withToken($token) ->acceptJson() ->get("https://people.googleapis.com/v1/{$resourceName}", [ @@ -164,7 +163,7 @@ public function handle(): int $etag = (string) Arr::get($personResponse->json(), 'metadata.sources.0.etag', ''); } - $updatePayload = $payload; + $updatePayload = $payload; $updatePayload['resourceName'] = $resourceName; if ($etag !== '') { $updatePayload['etag'] = $etag; @@ -175,7 +174,7 @@ public function handle(): int ->patch("https://people.googleapis.com/v1/{$resourceName}:updateContact?updatePersonFields=names,emailAddresses,phoneNumbers", $updatePayload); if ($updateResponse->status() === 401) { - $token = $this->resolveAccessToken($amministratore, $google, $oauth, false, true) ?? $token; + $token = $this->resolveAccessToken($amministratore, $google, $oauth, false, true) ?? $token; $updateResponse = Http::withToken($token) ->acceptJson() ->patch("https://people.googleapis.com/v1/{$resourceName}:updateContact?updatePersonFields=names,emailAddresses,phoneNumbers", $updatePayload); @@ -206,7 +205,7 @@ public function handle(): int ->post('https://people.googleapis.com/v1/people:createContact', $payload); if ($createResponse->status() === 401) { - $token = $this->resolveAccessToken($amministratore, $google, $oauth, false, true) ?? $token; + $token = $this->resolveAccessToken($amministratore, $google, $oauth, false, true) ?? $token; $createResponse = Http::withToken($token) ->acceptJson() ->post('https://people.googleapis.com/v1/people:createContact', $payload); @@ -234,7 +233,7 @@ public function handle(): int $totalCreated += $created; $totalUpdated += $updated; $totalSkipped += $skipped; - $totalErrors += $errors; + $totalErrors += $errors; $mode = $dryRun ? 'dry-run' : 'apply'; $this->info("Admin {$amministratore->id} ({$mode}): creati {$created}, aggiornati {$updated}, saltati {$skipped}, errori {$errors}."); @@ -253,25 +252,25 @@ private function loadGoogleContactIndex(Amministratore $amministratore, array $g { $existingEmails = []; $existingPhones = []; - $nextPageToken = null; + $nextPageToken = null; for ($page = 0; $page < 5; $page++) { $response = Http::withToken($token) ->acceptJson() ->get('https://people.googleapis.com/v1/people/me/connections', [ 'personFields' => 'emailAddresses,phoneNumbers', - 'pageSize' => 500, - 'pageToken' => $nextPageToken, + 'pageSize' => 500, + 'pageToken' => $nextPageToken, ]); if ($response->status() === 401) { - $token = $this->resolveAccessToken($amministratore, $google, $oauth, $dryRun, true) ?? $token; + $token = $this->resolveAccessToken($amministratore, $google, $oauth, $dryRun, true) ?? $token; $response = Http::withToken($token) ->acceptJson() ->get('https://people.googleapis.com/v1/people/me/connections', [ 'personFields' => 'emailAddresses,phoneNumbers', - 'pageSize' => 500, - 'pageToken' => $nextPageToken, + 'pageSize' => 500, + 'pageToken' => $nextPageToken, ]); } @@ -314,7 +313,7 @@ private function loadGoogleContactIndex(Amministratore $amministratore, array $g private function resolveAccessToken(Amministratore $amministratore, array $google, array $oauth, bool $dryRun, bool $forceRefresh = false): ?string { - $accessToken = trim((string) Arr::get($oauth, 'access_token', '')); + $accessToken = trim((string) Arr::get($oauth, 'access_token', '')); $refreshToken = trim((string) Arr::get($oauth, 'refresh_token', '')); if (! $forceRefresh && $accessToken !== '') { @@ -325,10 +324,10 @@ private function resolveAccessToken(Amministratore $amministratore, array $googl return null; } - $settingsClientId = trim((string) Arr::get($google, 'client_id', '')); + $settingsClientId = trim((string) Arr::get($google, 'client_id', '')); $settingsClientSecret = trim((string) Arr::get($google, 'client_secret', '')); - $configClientId = trim((string) config('services.google.client_id')); - $configClientSecret = trim((string) config('services.google.client_secret')); + $configClientId = trim((string) config('services.google.client_id')); + $configClientSecret = trim((string) config('services.google.client_secret')); $credentialPairs = []; if ($settingsClientId !== '' && $settingsClientSecret !== '') { @@ -346,14 +345,14 @@ private function resolveAccessToken(Amministratore $amministratore, array $googl return null; } - $payload = null; + $payload = null; $lastError = null; foreach ($credentialPairs as [$clientId, $clientSecret, $source]) { $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [ - 'client_id' => $clientId, + 'client_id' => $clientId, 'client_secret' => $clientSecret, 'refresh_token' => $refreshToken, - 'grant_type' => 'refresh_token', + 'grant_type' => 'refresh_token', ]); if ($response->successful()) { @@ -361,7 +360,7 @@ private function resolveAccessToken(Amministratore $amministratore, array $googl break; } - $message = (string) (Arr::get($response->json(), 'error_description') ?: Arr::get($response->json(), 'error.message') ?: 'refresh_failed'); + $message = (string) (Arr::get($response->json(), 'error_description') ?: Arr::get($response->json(), 'error.message') ?: 'refresh_failed'); $lastError = "{$source}:{$response->status()} {$message}"; } @@ -376,19 +375,19 @@ private function resolveAccessToken(Amministratore $amministratore, array $googl } if (! $dryRun) { - $impostazioni = $amministratore->impostazioni ?? []; + $impostazioni = $amministratore->impostazioni ?? []; $impostazioniGoogle = Arr::get($impostazioni, 'google', []); - $impostazioniOauth = Arr::get($impostazioniGoogle, 'oauth', []); + $impostazioniOauth = Arr::get($impostazioniGoogle, 'oauth', []); $impostazioniOauth['access_token'] = $newAccessToken; - $impostazioniOauth['expires_in'] = (int) Arr::get($payload, 'expires_in', 3600); + $impostazioniOauth['expires_in'] = (int) Arr::get($payload, 'expires_in', 3600); $impostazioniOauth['refreshed_at'] = Carbon::now()->toDateTimeString(); if (isset($payload['refresh_token']) && is_string($payload['refresh_token']) && $payload['refresh_token'] !== '') { $impostazioniOauth['refresh_token'] = $payload['refresh_token']; } - $impostazioniGoogle['oauth'] = $impostazioniOauth; - $impostazioni['google'] = $impostazioniGoogle; + $impostazioniGoogle['oauth'] = $impostazioniOauth; + $impostazioni['google'] = $impostazioniGoogle; $amministratore->impostazioni = $impostazioni; $amministratore->save(); } @@ -407,7 +406,7 @@ private function mobileForGoogle(RubricaUniversale $record): ?string if ($candidate === '') { $national = trim((string) ($record->telefono_cellulare_nazionale ?? '')); if ($national !== '') { - $prefix = trim((string) ($record->prefisso_cellulare ?? '+39')); + $prefix = trim((string) ($record->prefisso_cellulare ?? '+39')); $candidate = $prefix . $national; } } @@ -419,7 +418,7 @@ private function mobileForGoogle(RubricaUniversale $record): ?string private function emailsForGoogle(RubricaUniversale $record): array { $emails = []; - $seen = []; + $seen = []; $push = function (?string $value, string $type, string $formattedType) use (&$emails, &$seen): void { $email = strtolower(trim((string) $value)); @@ -427,9 +426,9 @@ private function emailsForGoogle(RubricaUniversale $record): array return; } $seen[$email] = true; - $emails[] = [ - 'value' => $email, - 'type' => $type, + $emails[] = [ + 'value' => $email, + 'type' => $type, 'formattedType' => $formattedType, ]; }; @@ -461,7 +460,7 @@ private function emailsForGoogle(RubricaUniversale $record): array private function phonesForGoogle(RubricaUniversale $record): array { $phones = []; - $seen = []; + $seen = []; $push = function (?string $value, string $type, string $formattedType) use (&$phones, &$seen): void { $phone = trim((string) $value); @@ -475,9 +474,9 @@ private function phonesForGoogle(RubricaUniversale $record): array } $seen[$normalized] = true; - $phones[] = [ - 'value' => $phone, - 'type' => $type, + $phones[] = [ + 'value' => $phone, + 'type' => $type, 'formattedType' => $formattedType, ]; }; @@ -505,7 +504,7 @@ private function phonesForGoogle(RubricaUniversale $record): array foreach ($channels as $channel) { $isMobile = (string) ($channel->tipo ?? '') === 'cellulare'; - $value = $isMobile ? (PhoneNumber::toE164Italy((string) ($channel->valore ?? '')) ?? (string) ($channel->valore ?? '')) : (string) ($channel->valore ?? ''); + $value = $isMobile ? (PhoneNumber::toE164Italy((string) ($channel->valore ?? '')) ?? (string) ($channel->valore ?? '')) : (string) ($channel->valore ?? ''); $push($value, $isMobile ? 'mobile' : 'work', $isMobile ? 'Cellulare' : 'Telefono'); } } @@ -540,14 +539,14 @@ private function splitPhoneValues(string $value): array */ private function buildGoogleNameParts(RubricaUniversale $record): array { - $nome = trim((string) ($record->nome ?? '')); + $nome = trim((string) ($record->nome ?? '')); $cognome = trim((string) ($record->cognome ?? '')); // Legacy imports can contain "Nome Cognome" inside nome with empty cognome. if ($cognome === '' && str_contains($nome, ' ')) { $parts = preg_split('/\s+/', $nome) ?: []; if (count($parts) > 1) { - $nome = trim((string) array_shift($parts)); + $nome = trim((string) array_shift($parts)); $cognome = trim(implode(' ', $parts)); } } diff --git a/app/Console/Commands/GoogleSyncRubricaContactsCommand.php b/app/Console/Commands/GoogleSyncRubricaContactsCommand.php index 77e5fd6..58afee9 100644 --- a/app/Console/Commands/GoogleSyncRubricaContactsCommand.php +++ b/app/Console/Commands/GoogleSyncRubricaContactsCommand.php @@ -424,16 +424,16 @@ private function syncAdditionalGoogleChannels(RubricaUniversale $rubrica, array $tipo = str_contains($value, '@cert.') ? 'pec' : 'email'; RubricaContattoCanale::query()->updateOrCreate( [ - 'rubrica_id' => (int) $rubrica->id, - 'tipo' => $tipo, - 'valore' => $value, - 'stabile_id' => null, + 'rubrica_id' => (int) $rubrica->id, + 'tipo' => $tipo, + 'valore' => $value, + 'stabile_id' => null, 'unita_immobiliare_id' => null, ], [ - 'etichetta' => $tipo === 'pec' ? 'PEC Google' : 'Email Google', + 'etichetta' => $tipo === 'pec' ? 'PEC Google' : 'Email Google', 'is_principale' => $index === 0, - 'meta' => ['source' => 'google_workspace'], + 'meta' => ['source' => 'google_workspace'], ] ); } @@ -450,21 +450,21 @@ private function syncAdditionalGoogleChannels(RubricaUniversale $rubrica, array continue; } - $tipo = str_starts_with($digits, '3') || str_starts_with($digits, '39') ? 'cellulare' : 'telefono'; + $tipo = str_starts_with($digits, '3') || str_starts_with($digits, '39') ? 'cellulare' : 'telefono'; $value = $tipo === 'cellulare' ? (PhoneNumber::toE164Italy($raw) ?? $raw) : $raw; RubricaContattoCanale::query()->updateOrCreate( [ - 'rubrica_id' => (int) $rubrica->id, - 'tipo' => $tipo, - 'valore' => $value, - 'stabile_id' => null, + 'rubrica_id' => (int) $rubrica->id, + 'tipo' => $tipo, + 'valore' => $value, + 'stabile_id' => null, 'unita_immobiliare_id' => null, ], [ - 'etichetta' => $tipo === 'cellulare' ? 'Cellulare Google' : 'Telefono Google', + 'etichetta' => $tipo === 'cellulare' ? 'Cellulare Google' : 'Telefono Google', 'is_principale' => $index === 0, - 'meta' => ['source' => 'google_workspace'], + 'meta' => ['source' => 'google_workspace'], ] ); } diff --git a/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php b/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php index 65c1b13..8312b50 100644 --- a/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php +++ b/app/Console/Commands/ImportLegacyFornitoriTagsCommand.php @@ -312,7 +312,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) { diff --git a/app/Console/Commands/NetgesconGitSyncCommand.php b/app/Console/Commands/NetgesconGitSyncCommand.php index 719422d..e027a21 100644 --- a/app/Console/Commands/NetgesconGitSyncCommand.php +++ b/app/Console/Commands/NetgesconGitSyncCommand.php @@ -101,10 +101,10 @@ public function handle(): int return self::FAILURE; } - $remoteRef = 'refs/remotes/' . $remote . '/' . $branch; - $docsRepoPath = $deployPath; - $newCommit = $currentCommit; - $cleanupWorktree = null; + $remoteRef = 'refs/remotes/' . $remote . '/' . $branch; + $docsRepoPath = $deployPath; + $newCommit = $currentCommit; + $cleanupWorktree = null; $sourceRepoAligned = ! $deploySync; $sourceRepoMessage = null; @@ -321,21 +321,21 @@ public function handle(): int } $this->persistSummary([ - 'status' => 'completed', - 'exit_code' => 0, - 'message' => 'Sync Git completata', - 'remote' => $remote, - 'branch' => $branch, - 'before_commit' => $currentCommit, - 'after_commit' => $newCommit, - 'remote_commit' => $remoteCommit, - 'working_tree_dirty' => $isDirty, - 'repo_path' => $repoPath, - 'deploy_path' => $deployPath, - 'mode' => $deploySync ? 'external-repo-rsync' : 'in-place-git', - 'source_repo_aligned'=> $sourceRepoAligned, - 'source_repo_message'=> $sourceRepoMessage, - 'rubrica_legami_qa' => $rubricaQaExit === 0 ? 'completed' : 'warning', + 'status' => 'completed', + 'exit_code' => 0, + 'message' => 'Sync Git completata', + 'remote' => $remote, + 'branch' => $branch, + 'before_commit' => $currentCommit, + 'after_commit' => $newCommit, + 'remote_commit' => $remoteCommit, + 'working_tree_dirty' => $isDirty, + 'repo_path' => $repoPath, + 'deploy_path' => $deployPath, + 'mode' => $deploySync ? 'external-repo-rsync' : 'in-place-git', + 'source_repo_aligned' => $sourceRepoAligned, + 'source_repo_message' => $sourceRepoMessage, + 'rubrica_legami_qa' => $rubricaQaExit === 0 ? 'completed' : 'warning', ]); $this->info('Sync Git completata con successo.'); diff --git a/app/Console/Commands/SmdrListenCommand.php b/app/Console/Commands/SmdrListenCommand.php index 9f16f93..060d0b7 100644 --- a/app/Console/Commands/SmdrListenCommand.php +++ b/app/Console/Commands/SmdrListenCommand.php @@ -248,7 +248,7 @@ public function handle(): int $lineSuffix = $lineLabel !== '' ? ' - linea ' . $lineLabel : ''; $isMissedResponseGroup = $this->isMissedResponseGroupCall($parsed, $routing); - $caller = $this->resolveCallerDisplay($phone, $amministratoreId); + $caller = $this->resolveCallerDisplay($phone, $amministratoreId); ChiamataPostIt::query()->create([ 'telefono' => $phone, diff --git a/app/Filament/Pages/Fornitore/LavorazioniOperative.php b/app/Filament/Pages/Fornitore/LavorazioniOperative.php index 63eb598..e81643b 100644 --- a/app/Filament/Pages/Fornitore/LavorazioniOperative.php +++ b/app/Filament/Pages/Fornitore/LavorazioniOperative.php @@ -162,6 +162,15 @@ public function getProdottiUrl(): string return ProdottiCatalogo::getUrl(panel: 'admin-filament'); } + public function getImpostazioniUrl(): string + { + if ($this->fornitoreId) { + return ImpostazioniArchivio::getUrl(['fornitore' => $this->fornitoreId], panel: 'admin-filament'); + } + + return ImpostazioniArchivio::getUrl(panel: 'admin-filament'); + } + protected function buildBaseQuery(Fornitore $fornitore, ?FornitoreDipendente $dipendente): Builder { $query = TicketIntervento::query() diff --git a/app/Filament/Pages/Fornitore/TicketOperativi.php b/app/Filament/Pages/Fornitore/TicketOperativi.php index f46e943..31ac3dc 100644 --- a/app/Filament/Pages/Fornitore/TicketOperativi.php +++ b/app/Filament/Pages/Fornitore/TicketOperativi.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages\Fornitore; use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext; +use App\Models\AssistenzaTecnorepairScheda; use App\Models\Fornitore; use App\Models\FornitoreDipendente; use App\Models\TicketAttachment; @@ -95,13 +96,22 @@ public function refreshData(): void $query = $this->buildBaseQuery($fornitore, $dipendente); $this->applyStatusFilter($query, $this->status); - $this->rows = $query + $ticketRows = $query ->limit(100) ->get() ->map(function (TicketIntervento $intervento): array { $base = $this->buildInterventoRow($intervento); return array_merge($base, [ + 'row_type' => 'ticket', + 'row_class' => $this->resolveTicketRowClass((string) $intervento->stato), + 'badge_class' => $this->resolveTicketBadgeClass((string) $intervento->stato), + 'numero' => (string) $intervento->ticket_id, + 'cliente' => $base['contatto'], + 'difetto' => $base['problema'], + 'modello' => $base['apparato'], + 'seriale' => '-', + 'cod_prodotto' => '-', 'id' => (int) $intervento->id, 'ticket_id' => (int) $intervento->ticket_id, 'titolo' => (string) ($intervento->ticket->titolo ?? '-'), @@ -110,17 +120,34 @@ public function refreshData(): void 'operatore' => (string) $intervento->operatore_assegnato_label, 'tempo_minuti' => (int) ($intervento->tempo_minuti ?? 0), 'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-', + 'sort_at' => optional($intervento->created_at)->format('Y-m-d H:i:s') ?: '1970-01-01 00:00:00', + 'origine' => 'Ticket amministratore', 'url' => TicketInterventoScheda::getUrl(['record' => $intervento->id], panel: 'admin-filament'), ]); }) ->all(); + $tecnorepairRows = $this->buildTecnorepairRows($fornitore); + $rows = array_merge($ticketRows, $tecnorepairRows); + + usort($rows, function (array $left, array $right): int { + return strcmp((string) ($right['sort_at'] ?? ''), (string) ($left['sort_at'] ?? '')); + }); + + $this->rows = array_slice($rows, 0, 150); + $this->totals = [ 'aperti' => (clone $this->buildBaseQuery($fornitore, $dipendente)) ->whereNotIn('stato', ['chiuso']) + ->count() + AssistenzaTecnorepairScheda::query() + ->where('fornitore_id', (int) $fornitore->id) + ->whereIn('status_bucket', ['open', 'waiting']) ->count(), 'chiusi' => (clone $this->buildBaseQuery($fornitore, $dipendente)) ->whereIn('stato', ['chiuso', 'fatturato']) + ->count() + AssistenzaTecnorepairScheda::query() + ->where('fornitore_id', (int) $fornitore->id) + ->where('status_bucket', 'closed') ->count(), 'fatturabili' => (clone $this->buildBaseQuery($fornitore, $dipendente)) ->whereIn('stato', ['fatturabile', 'fatturato']) @@ -152,6 +179,15 @@ public function getContabilitaUrl(): string return Contabilita::getUrl(panel: 'admin-filament'); } + public function getImpostazioniUrl(): string + { + if ($this->fornitoreId && Auth::user()?->hasAnyRole(['super-admin', 'admin', 'amministratore'])) { + return ImpostazioniArchivio::getUrl(['fornitore' => $this->fornitoreId], panel: 'admin-filament'); + } + + return ImpostazioniArchivio::getUrl(panel: 'admin-filament'); + } + public function openInterventoModal(int $interventoId): void { if (! $this->fornitoreId) { @@ -248,4 +284,79 @@ protected function applyStatusFilter(Builder $query, string $status): void $query->whereNotIn('stato', ['chiuso']); } + + /** + * @return array> + */ + protected function buildTecnorepairRows(Fornitore $fornitore): array + { + $query = AssistenzaTecnorepairScheda::query() + ->where('fornitore_id', (int) $fornitore->id) + ->orderByDesc('date_received') + ->orderByDesc('updated_at'); + + if ($this->status === 'chiusi') { + $query->where('status_bucket', 'closed'); + } elseif ($this->status === 'aperti') { + $query->whereIn('status_bucket', ['open', 'waiting']); + } elseif ($this->status === 'fatturabili') { + return []; + } + + return $query + ->limit(120) + ->get() + ->map(function (AssistenzaTecnorepairScheda $scheda): array { + return [ + 'row_type' => 'tecnorepair', + 'row_class' => (string) $scheda->row_classes, + 'badge_class' => (string) $scheda->status_badge_classes, + 'numero' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id), + 'ingresso' => optional($scheda->date_received)->format('d/m/Y') ?: '-', + 'cliente' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'), + 'contatto' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'), + 'telefono' => (string) ($scheda->customer_phone ?: $scheda->customer_phone_alt ?: ''), + 'difetto' => (string) ($scheda->defect_reported ?: '-'), + 'problema' => (string) ($scheda->defect_reported ?: '-'), + 'apparato' => (string) ($scheda->product_model ?: '-'), + 'modello' => (string) ($scheda->product_model ?: '-'), + 'seriale' => (string) $scheda->serial_label, + 'cod_prodotto' => (string) ($scheda->product_code ?: '-'), + 'id' => (int) $scheda->id, + 'ticket_id' => null, + 'titolo' => (string) $scheda->display_title, + 'stato' => (string) ($scheda->status_label ?: $scheda->status_bucket ?: '-'), + 'stabile' => 'Archivio TecnoRepair', + 'operatore' => (string) ($scheda->technician_name ?: $scheda->operator_name ?: '-'), + 'tempo_minuti' => 0, + 'updated_at' => optional($scheda->updated_at)->format('d/m/Y H:i') ?: '-', + 'sort_at' => optional($scheda->date_received ?? $scheda->updated_at)->format('Y-m-d H:i:s') ?: '1970-01-01 00:00:00', + 'origine' => 'TecnoRepair MDB', + 'url' => null, + ]; + }) + ->all(); + } + + protected function resolveTicketRowClass(string $status): string + { + $status = strtolower(trim($status)); + + return match (true) { + in_array($status, ['chiuso', 'fatturato'], true) => 'bg-emerald-50/70 hover:bg-emerald-50', + in_array($status, ['fatturabile', 'verifica'], true) => 'bg-amber-50/70 hover:bg-amber-50', + default => 'bg-sky-50/60 hover:bg-sky-50', + }; + } + + protected function resolveTicketBadgeClass(string $status): string + { + $status = strtolower(trim($status)); + + return match (true) { + in_array($status, ['chiuso', 'fatturato'], true) => 'border-emerald-200 bg-emerald-50 text-emerald-800', + in_array($status, ['fatturabile', 'verifica'], true) => 'border-amber-200 bg-amber-50 text-amber-800', + default => 'border-sky-200 bg-sky-50 text-sky-800', + }; + } } diff --git a/app/Filament/Pages/Gescon/FornitoreScheda.php b/app/Filament/Pages/Gescon/FornitoreScheda.php index e91f185..c6b5210 100644 --- a/app/Filament/Pages/Gescon/FornitoreScheda.php +++ b/app/Filament/Pages/Gescon/FornitoreScheda.php @@ -317,13 +317,13 @@ public function collegaDipendenteDaRubrica(): void $dipendente->created_by_user_id = Auth::id(); } - $dipendente->rubrica_id = $rubricaId; - $dipendente->nome = (string) ($rubrica->nome ?: $rubrica->ragione_sociale ?: 'Dipendente'); - $dipendente->cognome = trim((string) ($rubrica->cognome ?? '')) !== '' ? (string) $rubrica->cognome : null; - $dipendente->email = $email !== '' ? $email : null; - $dipendente->telefono = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: $rubrica->telefono_casa)) ?: null; - $dipendente->attivo = true; - $dipendente->updated_by_user_id = Auth::id(); + $dipendente->rubrica_id = $rubricaId; + $dipendente->nome = (string) ($rubrica->nome ?: $rubrica->ragione_sociale ?: 'Dipendente'); + $dipendente->cognome = trim((string) ($rubrica->cognome ?? '')) !== '' ? (string) $rubrica->cognome : null; + $dipendente->email = $email !== '' ? $email : null; + $dipendente->telefono = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: $rubrica->telefono_casa)) ?: null; + $dipendente->attivo = true; + $dipendente->updated_by_user_id = Auth::id(); $dipendente->save(); $this->resetDipendenteRubricaSelection(); diff --git a/app/Filament/Pages/Impostazioni/GoogleDashboard.php b/app/Filament/Pages/Impostazioni/GoogleDashboard.php index 7adeec0..6b491b1 100644 --- a/app/Filament/Pages/Impostazioni/GoogleDashboard.php +++ b/app/Filament/Pages/Impostazioni/GoogleDashboard.php @@ -28,6 +28,6 @@ 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']); } -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Mobile/MobileDashboard.php b/app/Filament/Pages/Mobile/MobileDashboard.php index acee57d..b1900f9 100644 --- a/app/Filament/Pages/Mobile/MobileDashboard.php +++ b/app/Filament/Pages/Mobile/MobileDashboard.php @@ -28,6 +28,6 @@ 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']); } -} \ No newline at end of file +} diff --git a/app/Filament/Pages/Strumenti/PostIt.php b/app/Filament/Pages/Strumenti/PostIt.php index 7cda950..5f6f071 100644 --- a/app/Filament/Pages/Strumenti/PostIt.php +++ b/app/Filament/Pages/Strumenti/PostIt.php @@ -4,10 +4,10 @@ use App\Models\ChiamataPostIt; use App\Models\Fornitore; use App\Models\RubricaUniversale; -use App\Services\Anagrafiche\RubricaPhoneLookupService; use App\Models\Stabile; use App\Models\Ticket; use App\Models\User; +use App\Services\Anagrafiche\RubricaPhoneLookupService; use App\Support\PhoneNumber; use App\Support\StabileContext; use BackedEnum; diff --git a/app/Filament/Pages/Strumenti/PostItGestione.php b/app/Filament/Pages/Strumenti/PostItGestione.php index e9224f6..f688a1f 100644 --- a/app/Filament/Pages/Strumenti/PostItGestione.php +++ b/app/Filament/Pages/Strumenti/PostItGestione.php @@ -23,11 +23,11 @@ class PostItGestione extends Page { /** @var array */ private const SMDR_PUBLIC_LINE_FALLBACKS = [ - '1' => '0639731100', - '01' => '0639731100', + '1' => '0639731100', + '01' => '0639731100', '0001' => '0639731100', - '3' => '0688812703', - '03' => '0688812703', + '3' => '0688812703', + '03' => '0688812703', '0003' => '0688812703', ]; diff --git a/app/Filament/Pages/Supporto/Modifiche.php b/app/Filament/Pages/Supporto/Modifiche.php index bf54ea5..0867aba 100644 --- a/app/Filament/Pages/Supporto/Modifiche.php +++ b/app/Filament/Pages/Supporto/Modifiche.php @@ -1087,17 +1087,17 @@ private function loadGitWorkspaceStatus(): void { $repoPath = $this->resolveGitWorkspacePath(); - $this->gitWorkspacePath = $repoPath; - $this->gitCurrentBranch = $this->runGitProcessAt($repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']); - $this->gitSourceCommit = $this->runGitProcessAt($repoPath, ['rev-parse', '--short', 'HEAD']); - $this->gitSourceCommitDate = $this->runGitProcessAt($repoPath, ['show', '-s', '--date=format:%d/%m/%Y %H:%M', '--format=%cd', 'HEAD']); - $this->gitCurrentCommit = $this->gitSourceCommit; - $this->gitCurrentCommitDate = $this->gitSourceCommitDate; - $this->gitRemoteCommit = $this->runGitProcessAt($repoPath, ['rev-parse', '--short', 'refs/remotes/' . $this->gitRemote . '/' . $this->gitBranch]); - $this->gitRemoteCommitDate = $this->runGitProcessAt($repoPath, ['show', '-s', '--date=format:%d/%m/%Y %H:%M', '--format=%cd', 'refs/remotes/' . $this->gitRemote . '/' . $this->gitBranch]); - $this->gitWorkingTreeDirty = trim((string) ($this->runGitProcessAt($repoPath, ['status', '--porcelain']) ?? '')) !== ''; - $this->gitAheadCount = 0; - $this->gitBehindCount = 0; + $this->gitWorkspacePath = $repoPath; + $this->gitCurrentBranch = $this->runGitProcessAt($repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']); + $this->gitSourceCommit = $this->runGitProcessAt($repoPath, ['rev-parse', '--short', 'HEAD']); + $this->gitSourceCommitDate = $this->runGitProcessAt($repoPath, ['show', '-s', '--date=format:%d/%m/%Y %H:%M', '--format=%cd', 'HEAD']); + $this->gitCurrentCommit = $this->gitSourceCommit; + $this->gitCurrentCommitDate = $this->gitSourceCommitDate; + $this->gitRemoteCommit = $this->runGitProcessAt($repoPath, ['rev-parse', '--short', 'refs/remotes/' . $this->gitRemote . '/' . $this->gitBranch]); + $this->gitRemoteCommitDate = $this->runGitProcessAt($repoPath, ['show', '-s', '--date=format:%d/%m/%Y %H:%M', '--format=%cd', 'refs/remotes/' . $this->gitRemote . '/' . $this->gitBranch]); + $this->gitWorkingTreeDirty = trim((string) ($this->runGitProcessAt($repoPath, ['status', '--porcelain']) ?? '')) !== ''; + $this->gitAheadCount = 0; + $this->gitBehindCount = 0; $this->gitStatusUsesSyncSummary = false; $currentRef = 'HEAD'; @@ -1106,11 +1106,11 @@ private function loadGitWorkspaceStatus(): void if (is_file($summaryPath)) { $summary = json_decode((string) @file_get_contents($summaryPath), true); if (is_array($summary)) { - $summaryMode = (string) ($summary['mode'] ?? ''); - $summaryStatus = (string) ($summary['status'] ?? ''); - $summaryCurrentCommit = isset($summary['after_commit']) ? (string) $summary['after_commit'] : (isset($summary['before_commit']) ? (string) $summary['before_commit'] : ''); - $summaryRemoteCommit = isset($summary['remote_commit']) ? (string) $summary['remote_commit'] : ''; - $syncDate = isset($summary['synced_at']) ? (string) $summary['synced_at'] : null; + $summaryMode = (string) ($summary['mode'] ?? ''); + $summaryStatus = (string) ($summary['status'] ?? ''); + $summaryCurrentCommit = isset($summary['after_commit']) ? (string) $summary['after_commit'] : (isset($summary['before_commit']) ? (string) $summary['before_commit'] : ''); + $summaryRemoteCommit = isset($summary['remote_commit']) ? (string) $summary['remote_commit'] : ''; + $syncDate = isset($summary['synced_at']) ? (string) $summary['synced_at'] : null; $this->gitCurrentBranch = $this->gitCurrentBranch ?: (isset($summary['branch']) ? (string) $summary['branch'] : null); diff --git a/app/Filament/Pages/Supporto/TicketMobile.php b/app/Filament/Pages/Supporto/TicketMobile.php index dc15aa5..6bd8f9c 100644 --- a/app/Filament/Pages/Supporto/TicketMobile.php +++ b/app/Filament/Pages/Supporto/TicketMobile.php @@ -1131,14 +1131,14 @@ private function selectCallerRepresentative(Collection $group, int $selectedCall $representative = $selected instanceof RubricaUniversale ? $selected : $group - ->sortByDesc(fn(RubricaUniversale $match): array => [ - (int) (! empty($match->amministratore_id)), - (int) (! empty($match->riferimento_stabile)), - (int) (! empty($match->riferimento_unita)), - (int) (($match->categoria ?? '') === 'condomino'), - -1 * (int) $match->id, - ]) - ->first(); + ->sortByDesc(fn(RubricaUniversale $match): array=> [ + (int) (! empty($match->amministratore_id)), + (int) (! empty($match->riferimento_stabile)), + (int) (! empty($match->riferimento_unita)), + (int) (($match->categoria ?? '') === 'condomino'), + -1 * (int) $match->id, + ]) + ->first(); $duplicateCategories = $group ->pluck('categoria') @@ -1274,7 +1274,6 @@ private function resolveCallerStabile(RubricaUniversale $caller, ?int $fallbackS return $fallbackStabileId ? Stabile::query()->find($fallbackStabileId) : null; } - private function resolveCallerSoggetto(RubricaUniversale $caller, ?int $stabileId = null): ?Soggetto { $queries = []; diff --git a/app/Filament/Pages/UnitaImmobiliarePage.php b/app/Filament/Pages/UnitaImmobiliarePage.php index 352d661..8c305fd 100644 --- a/app/Filament/Pages/UnitaImmobiliarePage.php +++ b/app/Filament/Pages/UnitaImmobiliarePage.php @@ -23,10 +23,12 @@ use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Select; +use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; +use Filament\Notifications\Notification; use Filament\Pages\Page; use Filament\Schemas\Schema; use Illuminate\Support\Facades\Auth; @@ -56,6 +58,8 @@ public static function canAccess(): bool public ?array $unitaPickerData = []; + public string $unitaVisibilita = 'attive'; + /** * @var array */ @@ -139,6 +143,8 @@ public function mount(): void } $this->stabileId = (int) $stabileId; + $this->unitaVisibilita = $this->normalizeUnitaVisibilita(request()->query('visibilita')); + $this->refreshUnitaOptions(); $this->unitaId = $this->pickUnitaId(request()->integer('unita_id')); @@ -240,6 +246,60 @@ protected function getHeaderActions(): array $nextId = $this->getNextUnitaId(); return [ + Action::make('modifica_unita') + ->label('Modifica unità') + ->icon('heroicon-o-pencil-square') + ->modalWidth('5xl') + ->visible(fn(): bool => (bool) $this->unitaId && $this->unita instanceof UnitaImmobiliare) + ->fillForm(fn(): array=> $this->getEditUnitaFormData()) + ->form([ + TextInput::make('codice_unita') + ->label('Codice unità') + ->maxLength(100) + ->columnSpan(3), + TextInput::make('denominazione') + ->label('Denominazione') + ->maxLength(255) + ->columnSpan(5), + TextInput::make('tipo_unita') + ->label('Tipologia') + ->maxLength(100) + ->columnSpan(2), + TextInput::make('categoria_catastale') + ->label('Categoria catastale') + ->maxLength(20) + ->columnSpan(2), + TextInput::make('scala') + ->label('Scala') + ->maxLength(50) + ->columnSpan(2), + TextInput::make('piano') + ->label('Piano') + ->maxLength(50) + ->columnSpan(2), + TextInput::make('interno') + ->label('Interno') + ->maxLength(50) + ->columnSpan(2), + TextInput::make('stato_occupazione') + ->label('Stato occupazione') + ->maxLength(100) + ->columnSpan(3), + TextInput::make('utilizzo_attuale') + ->label('Utilizzo attuale') + ->maxLength(100) + ->columnSpan(3), + Toggle::make('attiva') + ->label('Unità attiva') + ->columnSpan(2), + Textarea::make('note') + ->label('Note') + ->rows(4) + ->columnSpanFull(), + ]) + ->action(function (array $data): void { + $this->saveEditedUnita($data); + }), Action::make('prev_unita') ->label('Precedente') ->icon('heroicon-o-arrow-left') @@ -529,15 +589,32 @@ public function setTab(string $tab): void $this->tab = $tab; } + public function setUnitaVisibilita(string $visibilita): void + { + $this->unitaVisibilita = $this->normalizeUnitaVisibilita($visibilita); + $this->refreshUnitaOptions(); + $this->unitaId = $this->pickUnitaId($this->unitaId); + + $this->getSchema('unitaPicker')?->fill([ + 'unita_id' => $this->unitaId, + ]); + + if ($this->unitaId) { + $this->redirect($this->makeSelfUrl($this->unitaId)); + return; + } + + $this->loadUnita(); + } + protected function refreshUnitaOptions(): void { - $unita = UnitaImmobiliare::query() - ->where('stabile_id', $this->stabileId) + $unita = $this->buildUnitaIndexQuery() ->orderBy('palazzina_id') ->orderBy('scala') ->orderBy('piano') ->orderBy('id') - ->get(['id', 'palazzina_id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno']); + ->get(['id', 'palazzina_id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno', 'deleted_at']); $unita = $unita->filter(function (UnitaImmobiliare $u): bool { $denominazione = trim((string) ($u->denominazione ?? '')); @@ -649,6 +726,9 @@ protected function refreshUnitaOptions(): void if (is_string($owner) && trim($owner) !== '') { $label .= ' — ' . trim($owner); } + if ($u->trashed()) { + $label .= ' [archiviata]'; + } return [(int) $u->id => $label]; }) ->all(); @@ -730,14 +810,15 @@ protected function loadUnita(): void return; } - $this->unita = UnitaImmobiliare::with([ - 'palazzinaObj', - 'soggetti', - 'stabile', - 'dettagliMillesimi.tabellaMillesimale', - 'relazioniPersoneAttive.persona.emailMultiple', - 'unitaRecapitiServizio.persona', - ]) + $this->unita = UnitaImmobiliare::withTrashed() + ->with([ + 'palazzinaObj', + 'soggetti', + 'stabile', + 'dettagliMillesimi.tabellaMillesimale', + 'relazioniPersoneAttive.persona.emailMultiple', + 'unitaRecapitiServizio.persona', + ]) ->where('stabile_id', $this->stabileId) ->whereKey($this->unitaId) ->first(); @@ -758,6 +839,79 @@ protected function loadUnita(): void $this->tab = 'riepilogo'; } + protected function buildUnitaIndexQuery() + { + $query = UnitaImmobiliare::query() + ->where('stabile_id', $this->stabileId); + + return match ($this->unitaVisibilita) { + 'tutte' => $query->withTrashed(), + 'archiviate' => $query->onlyTrashed(), + default => $query, + }; + } + + protected function normalizeUnitaVisibilita(mixed $value): string + { + $candidate = strtolower(trim((string) $value)); + + return in_array($candidate, ['attive', 'tutte', 'archiviate'], true) + ? $candidate + : 'attive'; + } + + protected function getEditUnitaFormData(): array + { + if (! $this->unita) { + return []; + } + + return [ + 'codice_unita' => (string) ($this->unita->codice_unita ?? ''), + 'denominazione' => (string) ($this->unita->denominazione ?? ''), + 'tipo_unita' => (string) ($this->unita->tipo_unita ?? ''), + 'categoria_catastale' => (string) ($this->unita->categoria_catastale ?? ''), + 'scala' => (string) ($this->unita->scala ?? ''), + 'piano' => (string) ($this->unita->piano ?? ''), + 'interno' => (string) ($this->unita->interno ?? ''), + 'stato_occupazione' => (string) ($this->unita->stato_occupazione ?? ''), + 'utilizzo_attuale' => (string) ($this->unita->utilizzo_attuale ?? ''), + 'attiva' => (bool) ($this->unita->attiva ?? true), + 'note' => (string) ($this->unita->note ?? ''), + ]; + } + + protected function saveEditedUnita(array $data): void + { + if (! $this->unita) { + return; + } + + $this->unita->fill([ + 'codice_unita' => $this->normalizeOptionalString($data['codice_unita'] ?? null), + 'denominazione' => $this->normalizeOptionalString($data['denominazione'] ?? null), + 'tipo_unita' => $this->normalizeOptionalString($data['tipo_unita'] ?? null), + 'categoria_catastale' => $this->normalizeOptionalString($data['categoria_catastale'] ?? null), + 'scala' => $this->normalizeOptionalString($data['scala'] ?? null), + 'piano' => $this->normalizeOptionalString($data['piano'] ?? null), + 'interno' => $this->normalizeOptionalString($data['interno'] ?? null), + 'stato_occupazione' => $this->normalizeOptionalString($data['stato_occupazione'] ?? null), + 'utilizzo_attuale' => $this->normalizeOptionalString($data['utilizzo_attuale'] ?? null), + 'attiva' => (bool) ($data['attiva'] ?? false), + 'note' => $this->normalizeOptionalString($data['note'] ?? null), + ]); + $this->unita->updated_by = Auth::id(); + $this->unita->save(); + + $this->refreshUnitaOptions(); + $this->loadUnita(); + + Notification::make() + ->title('Unità aggiornata') + ->success() + ->send(); + } + public function hydrateNominativiStorici(): void { $this->nominativiStorici = []; diff --git a/app/Filament/Widgets/GoogleWorkspaceOverview.php b/app/Filament/Widgets/GoogleWorkspaceOverview.php index 30deeb7..b5691e6 100644 --- a/app/Filament/Widgets/GoogleWorkspaceOverview.php +++ b/app/Filament/Widgets/GoogleWorkspaceOverview.php @@ -65,10 +65,10 @@ public function mount(): void $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); $oauth = Arr::get($google, 'oauth', []); - $this->googleConnected = (bool) Arr::get($oauth, 'connected', false); - $email = (string) Arr::get($oauth, 'email', ''); - $this->connectionLabel = $email !== '' ? $email : ($this->googleConnected ? 'Collegato' : 'Non collegato'); - $this->calendarId = (string) Arr::get($google, 'calendar_id', 'primary') ?: 'primary'; + $this->googleConnected = (bool) Arr::get($oauth, 'connected', false); + $email = (string) Arr::get($oauth, 'email', ''); + $this->connectionLabel = $email !== '' ? $email : ($this->googleConnected ? 'Collegato' : 'Non collegato'); + $this->calendarId = (string) Arr::get($google, 'calendar_id', 'primary') ?: 'primary'; $this->rubricaSyncCommandPreview = 'php artisan google:sync-rubrica --admin-id=' . (int) $amministratore->id . ' --max-pages=20'; if (! $this->googleConnected) { @@ -144,7 +144,7 @@ private function runRubricaSync(bool $dryRun): void } $params = [ - '--admin-id' => [(string) $amministratore->id], + '--admin-id' => [(string) $amministratore->id], '--max-pages' => 20, ]; @@ -152,11 +152,11 @@ private function runRubricaSync(bool $dryRun): void $params['--dry-run'] = true; } - $exit = Artisan::call('google:sync-rubrica', $params); + $exit = Artisan::call('google:sync-rubrica', $params); $output = trim((string) Artisan::output()); - $this->rubricaSyncLastRunLabel = now()->format('d/m/Y H:i:s') . ($dryRun ? ' · dry-run' : ' · apply'); - $this->rubricaSyncLastOutput = $output !== '' ? $output : 'Nessun output disponibile.'; + $this->rubricaSyncLastRunLabel = now()->format('d/m/Y H:i:s') . ($dryRun ? ' · dry-run' : ' · apply'); + $this->rubricaSyncLastOutput = $output !== '' ? $output : 'Nessun output disponibile.'; $this->rubricaSyncCommandPreview = 'php artisan google:sync-rubrica --admin-id=' . (int) $amministratore->id . ' --max-pages=20' . ($dryRun ? ' --dry-run' : ''); if (! $dryRun) { @@ -165,8 +165,8 @@ private function runRubricaSync(bool $dryRun): void Notification::make() ->title($exit === 0 - ? ($dryRun ? 'Preview import rubrica completata' : 'Import rubrica completato') - : ($dryRun ? 'Preview import rubrica con errori' : 'Import rubrica con errori')) + ? ($dryRun ? 'Preview import rubrica completata' : 'Import rubrica completato') + : ($dryRun ? 'Preview import rubrica con errori' : 'Import rubrica con errori')) ->body((string) collect(preg_split('/\R/', $this->rubricaSyncLastOutput) ?: [])->filter()->last()) ->{$exit === 0 ? 'success' : 'warning'}() ->send(); diff --git a/app/Filament/Widgets/TicketMobileOverview.php b/app/Filament/Widgets/TicketMobileOverview.php index fbfc037..f3a5942 100644 --- a/app/Filament/Widgets/TicketMobileOverview.php +++ b/app/Filament/Widgets/TicketMobileOverview.php @@ -18,10 +18,10 @@ class TicketMobileOverview extends Widget /** @var array */ public array $ticketCounters = [ - 'open' => 0, + 'open' => 0, 'urgent' => 0, 'closed' => 0, - 'all' => 0, + 'all' => 0, ]; public static function canView(): bool @@ -29,7 +29,7 @@ public static function canView(): 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 @@ -53,10 +53,10 @@ public function mount(): void $base = Ticket::query()->whereIn('stabile_id', $stabileIds); $this->ticketCounters = [ - 'open' => (clone $base)->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), + 'open' => (clone $base)->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), 'urgent' => (clone $base)->where('priorita', 'Urgente')->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), 'closed' => (clone $base)->whereIn('stato', ['Risolto', 'Chiuso'])->count(), - 'all' => (clone $base)->count(), + 'all' => (clone $base)->count(), ]; } @@ -99,4 +99,4 @@ public function getHelpUrl(): string { return TicketMobileHelp::getUrl(panel: 'admin-filament'); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Integrations/GoogleOAuthController.php b/app/Http/Controllers/Integrations/GoogleOAuthController.php index 534e622..93cb1c0 100644 --- a/app/Http/Controllers/Integrations/GoogleOAuthController.php +++ b/app/Http/Controllers/Integrations/GoogleOAuthController.php @@ -6,8 +6,8 @@ use App\Models\Fornitore; use App\Models\FornitoreDipendente; use App\Models\Stabile; -use App\Support\GoogleAccountStore; use App\Models\User; +use App\Support\GoogleAccountStore; use App\Support\StabileContext; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Arr; @@ -19,7 +19,7 @@ class GoogleOAuthController extends Controller { private const GOOGLE_SCOPES = [ - ...GoogleAccountStore::SCOPES, + ...GoogleAccountStore::SCOPES, ]; public function redirect(): RedirectResponse @@ -30,8 +30,8 @@ public function redirect(): RedirectResponse abort(403, 'Amministratore non disponibile per questa sessione.'); } - $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); - $store = app(GoogleAccountStore::class); + $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); + $store = app(GoogleAccountStore::class); $accountKey = $store->normalizeAccountKey((string) request()->query('account_key', '')); $accountLabel = trim((string) request()->query('label', '')); $account = $store->get($amministratore, $accountKey !== '' ? $accountKey : null); @@ -86,11 +86,11 @@ public function callback(): RedirectResponse abort(403, 'Amministratore non disponibile per questa sessione.'); } - $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); - $store = app(GoogleAccountStore::class); - $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')); + $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); + $store = app(GoogleAccountStore::class); + $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')); try { $oauthContext = (array) session()->pull('google_oauth_context', []); @@ -124,7 +124,7 @@ public function callback(): RedirectResponse $payload = $tokenResponse->json(); $accessToken = (string) Arr::get($payload, 'access_token', ''); $refreshToken = (string) Arr::get($payload, 'refresh_token', ''); - $expiresIn = (int) Arr::get($payload, 'expires_in', 3600); + $expiresIn = (int) Arr::get($payload, 'expires_in', 3600); $profileResponse = Http::withToken($accessToken) ->acceptJson() diff --git a/app/Models/Fornitore.php b/app/Models/Fornitore.php index f99756e..e84e1c2 100755 --- a/app/Models/Fornitore.php +++ b/app/Models/Fornitore.php @@ -102,7 +102,18 @@ protected static function booted(): void return; } - foreach (['documenti', 'temp/upload', 'temp/processing', 'logs'] as $folder) { + foreach ([ + 'documenti', + 'rubrica', + 'fe', + 'prodotti', + 'comunicazioni', + 'tecnorepair/imports', + 'tecnorepair/allegati', + 'temp/upload', + 'temp/processing', + 'logs', + ] as $folder) { Storage::disk('local')->makeDirectory($base . '/' . $folder); } } catch (\Throwable) { diff --git a/app/Services/Anagrafiche/RubricaPhoneLookupService.php b/app/Services/Anagrafiche/RubricaPhoneLookupService.php index 2a4c5d9..71d0599 100644 --- a/app/Services/Anagrafiche/RubricaPhoneLookupService.php +++ b/app/Services/Anagrafiche/RubricaPhoneLookupService.php @@ -1,5 +1,4 @@ get('https://gmail.googleapis.com/gmail/v1/users/me/messages', array_filter([ 'maxResults' => max(1, min($maxMessages, 50)), 'q' => $query !== '' ? $query : null, - ], static fn ($value) => $value !== null)); + ], static fn($value) => $value !== null)); if (! $response->successful()) { throw new RuntimeException('Gmail API non disponibile: ' . $response->body()); @@ -86,15 +85,15 @@ public function importMailbox(Stabile $stabile, array $mailbox, int $maxMessages $date = $headers['date'] ?? null; $ticket = Ticket::query()->create([ - 'stabile_id' => (int) $stabile->id, - 'aperto_da_user_id' => $openingUserId, - 'titolo' => Str::limit($subject !== '' ? $subject : ('Email importata da ' . ($from !== '' ? $from : 'Gmail')), 255), - 'descrizione' => $this->buildTicketDescription($stabile, $mailbox, $subject, $from, $to, $date, $bodyText), - 'categoria_ticket_id' => null, - 'luogo_intervento' => 'Casella email stabile', - 'data_apertura' => $this->normalizeDate($date) ?? now(), - 'stato' => 'Aperto', - 'priorita' => 'Media', + 'stabile_id' => (int) $stabile->id, + 'aperto_da_user_id' => $openingUserId, + 'titolo' => Str::limit($subject !== '' ? $subject : ('Email importata da ' . ($from !== '' ? $from : 'Gmail')), 255), + 'descrizione' => $this->buildTicketDescription($stabile, $mailbox, $subject, $from, $to, $date, $bodyText), + 'categoria_ticket_id' => null, + 'luogo_intervento' => 'Casella email stabile', + 'data_apertura' => $this->normalizeDate($date) ?? now(), + 'stato' => 'Aperto', + 'priorita' => 'Media', ]); $rawMessage = $this->fetchMessage($token, $gmailId, 'raw'); @@ -113,10 +112,10 @@ public function importMailbox(Stabile $stabile, array $mailbox, int $maxMessages 'inviato_il' => $this->normalizeDate($date), 'eml_documento_id' => $documento->id, 'metadata' => [ - 'gmail_message_id' => $gmailId, - 'gmail_thread_id' => Arr::get($message, 'threadId'), - 'gmail_mailbox_email' => $mailbox['email'] ?? null, - 'google_account_key' => $mailbox['google_account_key'] ?? null, + 'gmail_message_id' => $gmailId, + 'gmail_thread_id' => Arr::get($message, 'threadId'), + 'gmail_mailbox_email' => $mailbox['email'] ?? null, + 'google_account_key' => $mailbox['google_account_key'] ?? null, ], ]); @@ -131,13 +130,13 @@ public function importMailbox(Stabile $stabile, array $mailbox, int $maxMessages 'status' => 'linked_to_ticket', 'received_at' => $this->normalizeDate($date), 'metadata' => [ - 'gmail_message_id' => $gmailId, - 'gmail_thread_id' => Arr::get($message, 'threadId'), - 'gmail_mailbox_email' => $mailbox['email'] ?? null, - 'google_account_key' => $mailbox['google_account_key'] ?? null, - 'subject' => $subject, - 'to' => $to, - 'documento_id' => (int) $documento->id, + 'gmail_message_id' => $gmailId, + 'gmail_thread_id' => Arr::get($message, 'threadId'), + 'gmail_mailbox_email' => $mailbox['email'] ?? null, + 'google_account_key' => $mailbox['google_account_key'] ?? null, + 'subject' => $subject, + 'to' => $to, + 'documento_id' => (int) $documento->id, ], ]); @@ -251,10 +250,10 @@ private function storeAttachments(string $token, string $gmailId, Ticket $ticket 'ticket-email/' . $ticket->id, $fileName, [ - 'mime' => (string) ($part['mimeType'] ?? ''), - 'optimize_image' => false, - 'display_name' => $fileName, - 'stored_basename' => pathinfo($fileName, PATHINFO_FILENAME), + 'mime' => (string) ($part['mimeType'] ?? ''), + 'optimize_image' => false, + 'display_name' => $fileName, + 'stored_basename' => pathinfo($fileName, PATHINFO_FILENAME), ] ); @@ -414,4 +413,4 @@ private function normalizeDate(mixed $value): mixed return null; } } -} \ No newline at end of file +} diff --git a/app/Services/Support/TicketAttachmentUploadService.php b/app/Services/Support/TicketAttachmentUploadService.php index da1f5e2..509cc06 100644 --- a/app/Services/Support/TicketAttachmentUploadService.php +++ b/app/Services/Support/TicketAttachmentUploadService.php @@ -92,7 +92,7 @@ public function storeRawContents(string $contents, string $directory, string $or Storage::disk('public')->put($path, $contents); - $mime = TicketAttachment::normalizeMimeType((string) ($options['mime'] ?? ''), $originalName, $path); + $mime = TicketAttachment::normalizeMimeType((string) ($options['mime'] ?? ''), $originalName, $path); $metadata = []; if (str_starts_with($mime, 'image/')) { diff --git a/app/Support/GoogleAccountStore.php b/app/Support/GoogleAccountStore.php index ff6301f..3551314 100644 --- a/app/Support/GoogleAccountStore.php +++ b/app/Support/GoogleAccountStore.php @@ -1,5 +1,4 @@ hasUsableOauthPayload($legacyOauth)) { $legacyKey = $this->normalizeAccountKey( (string) (Arr::get($google, 'default_account_key') - ?: Arr::get($legacyOauth, 'google_user_id') - ?: Arr::get($legacyOauth, 'email') - ?: 'primary') + ?: Arr::get($legacyOauth, 'google_user_id') + ?: Arr::get($legacyOauth, 'email') + ?: 'primary') ); if (! isset($accounts[$legacyKey])) { @@ -143,7 +142,7 @@ public function upsertAccount(Amministratore $amministratore, string $accountKey $account['scopes'] = self::SCOPES; } - $accounts[$key] = $account; + $accounts[$key] = $account; $google['accounts'] = $accounts; $defaultKey = $this->normalizeAccountKey((string) Arr::get($google, 'default_account_key', '')); @@ -245,17 +244,17 @@ public function resolveAccessToken(Amministratore $amministratore, ?string $acco } $this->upsertAccount($amministratore, (string) ($account['key'] ?? 'primary'), array_filter([ - 'connected' => true, - 'label' => $account['label'] ?? null, - 'email' => $account['email'] ?? null, - 'name' => $account['name'] ?? null, - 'google_user_id'=> $account['google_user_id'] ?? null, - 'access_token' => $newAccessToken, - 'refresh_token' => trim((string) Arr::get($payload, 'refresh_token', '')) ?: $refreshToken, - 'expires_in' => (int) Arr::get($payload, 'expires_in', $account['expires_in'] ?? 3600), - 'refreshed_at' => now()->toDateTimeString(), - 'scopes' => $account['scopes'] ?? self::SCOPES, - ], static fn ($value) => $value !== null), ! empty($account['is_default'])); + 'connected' => true, + 'label' => $account['label'] ?? null, + 'email' => $account['email'] ?? null, + 'name' => $account['name'] ?? null, + 'google_user_id' => $account['google_user_id'] ?? null, + 'access_token' => $newAccessToken, + 'refresh_token' => trim((string) Arr::get($payload, 'refresh_token', '')) ?: $refreshToken, + 'expires_in' => (int) Arr::get($payload, 'expires_in', $account['expires_in'] ?? 3600), + 'refreshed_at' => now()->toDateTimeString(), + 'scopes' => $account['scopes'] ?? self::SCOPES, + ], static fn($value) => $value !== null), ! empty($account['is_default'])); return $newAccessToken; } @@ -269,19 +268,19 @@ private function normalizeAccountPayload(array $account, string $key): array $label = trim((string) ($account['label'] ?? $account['name'] ?? $account['email'] ?? $key)); return [ - 'key' => $key, - 'label' => $label, - 'connected' => (bool) ($account['connected'] ?? false), - 'email' => trim((string) ($account['email'] ?? '')), - 'name' => trim((string) ($account['name'] ?? '')), - 'google_user_id' => trim((string) ($account['google_user_id'] ?? '')), - 'access_token' => trim((string) ($account['access_token'] ?? '')), - 'refresh_token' => trim((string) ($account['refresh_token'] ?? '')), - 'expires_in' => (int) ($account['expires_in'] ?? 0), - 'connected_at' => $account['connected_at'] ?? null, - 'refreshed_at' => $account['refreshed_at'] ?? null, - 'disconnected_at'=> $account['disconnected_at'] ?? null, - 'scopes' => array_values(array_filter((array) ($account['scopes'] ?? []))), + 'key' => $key, + 'label' => $label, + 'connected' => (bool) ($account['connected'] ?? false), + 'email' => trim((string) ($account['email'] ?? '')), + 'name' => trim((string) ($account['name'] ?? '')), + 'google_user_id' => trim((string) ($account['google_user_id'] ?? '')), + 'access_token' => trim((string) ($account['access_token'] ?? '')), + 'refresh_token' => trim((string) ($account['refresh_token'] ?? '')), + 'expires_in' => (int) ($account['expires_in'] ?? 0), + 'connected_at' => $account['connected_at'] ?? null, + 'refreshed_at' => $account['refreshed_at'] ?? null, + 'disconnected_at' => $account['disconnected_at'] ?? null, + 'scopes' => array_values(array_filter((array) ($account['scopes'] ?? []))), ]; } @@ -330,7 +329,7 @@ private function isExpired(array $account): bool private function hasUsableOauthPayload(array $oauth): bool { return (bool) ($oauth['connected'] ?? false) - || trim((string) ($oauth['refresh_token'] ?? '')) !== '' - || trim((string) ($oauth['access_token'] ?? '')) !== ''; + || trim((string) ($oauth['refresh_token'] ?? '')) !== '' + || trim((string) ($oauth['access_token'] ?? '')) !== ''; } -} \ No newline at end of file +} diff --git a/app/Support/Livewire/SupportsRubricaLookup.php b/app/Support/Livewire/SupportsRubricaLookup.php index b1add77..7ec4f2c 100644 --- a/app/Support/Livewire/SupportsRubricaLookup.php +++ b/app/Support/Livewire/SupportsRubricaLookup.php @@ -130,7 +130,7 @@ protected function searchRubricaLookupMatches(string $raw, ?int $selectedId = nu $matches = $query->get(); foreach ($matches as $rubrica) { - $row = $this->mapRubricaLookupRow($rubrica); + $row = $this->mapRubricaLookupRow($rubrica); $row['score'] = $this->scoreRubricaLookupMatch($row, $raw, $digits, $selectedId); $rows[] = $row; } diff --git a/public/build/manifest.json b/public/build/manifest.json index a595932..2cb22e4 100644 --- a/public/build/manifest.json +++ b/public/build/manifest.json @@ -1,11 +1,11 @@ { "resources/css/app.css": { - "file": "assets/app-BGt-S40c.css", + "file": "assets/app-tgbzBtFO.css", "src": "resources/css/app.css", "isEntry": true }, "resources/css/filament/admin/theme.css": { - "file": "assets/theme-D-E95I9r.css", + "file": "assets/theme-EkdZtrh_.css", "src": "resources/css/filament/admin/theme.css", "isEntry": true }, diff --git a/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php b/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php index cc7fcc1..188bc85 100644 --- a/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php +++ b/resources/views/filament/pages/fornitore/lavorazioni-operative.blade.php @@ -16,6 +16,7 @@ Ticket operativi Collaboratori Rubrica clienti + Impostazioni Prodotti diff --git a/resources/views/filament/pages/fornitore/ticket-operativi.blade.php b/resources/views/filament/pages/fornitore/ticket-operativi.blade.php index 98a6327..cff121c 100644 --- a/resources/views/filament/pages/fornitore/ticket-operativi.blade.php +++ b/resources/views/filament/pages/fornitore/ticket-operativi.blade.php @@ -15,6 +15,7 @@ @@ -48,10 +49,14 @@ - - - - + + + + + + + + @@ -59,34 +64,46 @@ @forelse($rows as $row) - - + + + + - + + @empty - + @endforelse diff --git a/resources/views/filament/pages/unita-immobiliare.blade.php b/resources/views/filament/pages/unita-immobiliare.blade.php index 847e77c..5ebb56b 100644 --- a/resources/views/filament/pages/unita-immobiliare.blade.php +++ b/resources/views/filament/pages/unita-immobiliare.blade.php @@ -29,8 +29,56 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s @php $unitaOptions = $this->getUnitaOptions(); + $prevUrl = $this->getPrevUnitaUrl(); + $nextUrl = $this->getNextUnitaUrl(); + $visibilityOptions = [ + 'attive' => 'Attive', + 'tutte' => 'Tutte', + 'archiviate' => 'Archiviate', + ]; @endphp + +
+
+
+ Prec. + @if($prevUrl) + + @else + + @endif +
+ +
+ Unità +
{{ $this->getSchema('unitaPicker') }}
+
+ +
+ Succ. + @if($nextUrl) + + @else + + @endif +
+
+ +
+ Visibilità: + @foreach($visibilityOptions as $key => $label) + + @endforeach +
+
+
+ @if(!$unita)
Nessuna unità immobiliare trovata.
@else @@ -38,8 +86,7 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s {{-- Header compatto --}} @php - $prevUrl = $this->getPrevUnitaUrl(); - $nextUrl = $this->getNextUnitaUrl(); + $isArchivedUnita = method_exists($unita, 'trashed') && $unita->trashed(); @endphp
@@ -48,31 +95,9 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
Unità immobiliare - -
-
- Prec. - @if($prevUrl) - - @else - - @endif -
- -
- Unità -
{{ $this->getSchema('unitaPicker') }}
-
- -
- Succ. - @if($nextUrl) - - @else - - @endif -
-
+ @if($isArchivedUnita) + Archiviata + @endif
@php
TicketContattoProblemaOperatoreNumIngressoClienteTelDifettoModelloSerialeCod. prod Stato Aggiornato
-
#{{ $row['ticket_id'] }} - {{ $row['titolo'] }}
-
{{ $row['stabile'] }} · ingresso {{ $row['ingresso'] }}
+
+ {{ $row['numero'] }} +
{{ $row['origine'] }}
{{ $row['ingresso'] }} -
{{ $row['contatto'] }}
-
{{ $row['telefono'] !== '' ? $row['telefono'] : 'Telefono non disponibile' }}
+
{{ $row['cliente'] }}
+
{{ $row['stabile'] }}
{{ $row['telefono'] !== '' ? $row['telefono'] : '-' }}{{ $row['difetto'] }} -
{{ $row['problema'] }}
-
Apparato: {{ $row['apparato'] }}
+
{{ $row['modello'] }}
+ @if(($row['titolo'] ?? '') !== '') +
{{ $row['titolo'] }}
+ @endif
{{ $row['operatore'] }}{{ $row['seriale'] }}{{ $row['cod_prodotto'] }} - {{ $row['stato'] }} + {{ $row['stato'] }} {{ $row['updated_at'] }}
- - Apri scheda + @if($row['row_type'] === 'ticket') + + @endif + @if($row['url']) + Apri scheda + @else + Gestisci import + @endif
Nessun intervento trovato per questo filtro.Nessun intervento trovato per questo filtro.