874 lines
30 KiB
PHP
Executable File
874 lines
30 KiB
PHP
Executable File
<?php
|
|
namespace App\Filament\Widgets;
|
|
|
|
use App\Filament\Pages\Gescon\RubricaUniversaleArchivio;
|
|
use App\Filament\Pages\Strumenti\Calendario;
|
|
use App\Filament\Pages\Supporto\TicketGestione;
|
|
use App\Filament\Pages\Supporto\TicketMobile;
|
|
use App\Models\Amministratore;
|
|
use App\Models\RubricaUniversale;
|
|
use App\Models\Stabile;
|
|
use App\Models\Ticket;
|
|
use App\Models\User;
|
|
use App\Support\StabileContext;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Widgets\Widget;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
class GoogleWorkspaceOverview extends Widget
|
|
{
|
|
protected string $view = 'filament.widgets.google-workspace-overview';
|
|
|
|
protected int|string|array $columnSpan = 'full';
|
|
|
|
public bool $googleConnected = false;
|
|
|
|
public string $connectionLabel = 'Non collegato';
|
|
|
|
public string $calendarId = 'primary';
|
|
|
|
/** @var array<int, array<string, string>> */
|
|
public array $calendarEvents = [];
|
|
|
|
/** @var array<int, array<string, string>> */
|
|
public array $contacts = [];
|
|
|
|
/** @var array<int, array<string, string|int>> */
|
|
public array $openTickets = [];
|
|
|
|
/** @var array<int, string> */
|
|
public array $driveTemplateFolders = [];
|
|
|
|
public ?string $errorMessage = null;
|
|
|
|
public bool $showTechnicalBoxes = true;
|
|
|
|
public ?string $rubricaSyncCommandPreview = null;
|
|
|
|
public ?string $rubricaSyncLastOutput = null;
|
|
|
|
public ?string $rubricaSyncLastRunLabel = null;
|
|
|
|
public function mount(): void
|
|
{
|
|
$amministratore = $this->resolveAmministratore();
|
|
|
|
if (! $amministratore instanceof Amministratore) {
|
|
$this->errorMessage = 'Amministratore non disponibile nella sessione corrente.';
|
|
return;
|
|
}
|
|
|
|
$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->rubricaSyncCommandPreview = 'php artisan google:sync-rubrica --admin-id=' . (int) $amministratore->id . ' --max-pages=20';
|
|
|
|
if (! $this->googleConnected) {
|
|
$this->errorMessage = 'Google non e collegato. Vai su Scheda amministratore per completare il collegamento.';
|
|
return;
|
|
}
|
|
|
|
$token = $this->resolveAccessToken($amministratore, $google, $oauth);
|
|
if ($token === null) {
|
|
return;
|
|
}
|
|
|
|
$this->loadCalendarEvents($token, $this->calendarId);
|
|
$this->loadContactsPreview($token);
|
|
$this->loadOpenTickets();
|
|
$this->loadDriveTemplateFolders();
|
|
}
|
|
|
|
public function getCalendarioUrl(): string
|
|
{
|
|
return Calendario::getUrl(panel: 'admin-filament');
|
|
}
|
|
|
|
public function getRubricaUrl(): string
|
|
{
|
|
return RubricaUniversaleArchivio::getUrl(panel: 'admin-filament');
|
|
}
|
|
|
|
public function getTicketApertiUrl(): string
|
|
{
|
|
return TicketMobile::getUrl(panel: 'admin-filament');
|
|
}
|
|
|
|
public function getTicketGestioneUrl(int $ticketId): string
|
|
{
|
|
return TicketGestione::getUrl(['ticket' => $ticketId, 'tab' => 'scheda'], panel: 'admin-filament');
|
|
}
|
|
|
|
public function previewImportContactsToRubrica(): void
|
|
{
|
|
$this->runRubricaSync(true);
|
|
}
|
|
|
|
public function importContactsToRubrica(): void
|
|
{
|
|
$this->runRubricaSync(false);
|
|
}
|
|
|
|
private function runRubricaSync(bool $dryRun): void
|
|
{
|
|
$amministratore = $this->resolveAmministratore();
|
|
|
|
if (! $amministratore instanceof Amministratore) {
|
|
Notification::make()
|
|
->title('Import rubrica non disponibile')
|
|
->warning()
|
|
->body('Amministratore non disponibile nella sessione corrente.')
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
|
$oauth = Arr::get($google, 'oauth', []);
|
|
$token = $this->resolveAccessToken($amministratore, $google, $oauth);
|
|
|
|
if ($token === null) {
|
|
Notification::make()
|
|
->title($dryRun ? 'Preview rubrica non disponibile' : 'Import rubrica non riuscito')
|
|
->warning()
|
|
->body($this->errorMessage ?? 'Token Google non disponibile.')
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$params = [
|
|
'--admin-id' => [(string) $amministratore->id],
|
|
'--max-pages' => 20,
|
|
];
|
|
|
|
if ($dryRun) {
|
|
$params['--dry-run'] = true;
|
|
}
|
|
|
|
$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->rubricaSyncCommandPreview = 'php artisan google:sync-rubrica --admin-id=' . (int) $amministratore->id . ' --max-pages=20' . ($dryRun ? ' --dry-run' : '');
|
|
|
|
if (! $dryRun) {
|
|
$this->loadContactsPreview($token);
|
|
}
|
|
|
|
Notification::make()
|
|
->title($exit === 0
|
|
? ($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();
|
|
}
|
|
|
|
public function exportRubricaToGoogle(): void
|
|
{
|
|
$amministratore = $this->resolveAmministratore();
|
|
|
|
if (! $amministratore instanceof Amministratore) {
|
|
Notification::make()
|
|
->title('Export Google non disponibile')
|
|
->warning()
|
|
->body('Amministratore non disponibile nella sessione corrente.')
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
|
$oauth = Arr::get($google, 'oauth', []);
|
|
$token = $this->resolveAccessToken($amministratore, $google, $oauth);
|
|
|
|
if ($token === null) {
|
|
Notification::make()
|
|
->title('Export Google non riuscito')
|
|
->warning()
|
|
->body($this->errorMessage ?? 'Token Google non disponibile.')
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$existingEmails = [];
|
|
$existingPhones = [];
|
|
$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,
|
|
]);
|
|
|
|
if ($response->status() === 401) {
|
|
$token = $this->resolveAccessToken($amministratore, $google, $oauth, true) ?? $token;
|
|
$response = Http::withToken($token)
|
|
->acceptJson()
|
|
->get('https://people.googleapis.com/v1/people/me/connections', [
|
|
'personFields' => 'emailAddresses,phoneNumbers',
|
|
'pageSize' => 500,
|
|
'pageToken' => $nextPageToken,
|
|
]);
|
|
}
|
|
|
|
if (! $response->successful()) {
|
|
Notification::make()
|
|
->title('Export Google interrotto')
|
|
->warning()
|
|
->body($this->googleErrorBody($response, 'Impossibile leggere i contatti Google esistenti.'))
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$connections = $response->json('connections');
|
|
if (! is_array($connections) || count($connections) === 0) {
|
|
break;
|
|
}
|
|
|
|
foreach ($connections as $person) {
|
|
$email = trim((string) Arr::get($person, 'emailAddresses.0.value', ''));
|
|
$phone = trim((string) Arr::get($person, 'phoneNumbers.0.value', ''));
|
|
|
|
if ($email !== '') {
|
|
$existingEmails[strtolower($email)] = true;
|
|
}
|
|
if ($phone !== '') {
|
|
$normalized = $this->normalizePhone($phone);
|
|
if ($normalized !== '') {
|
|
$existingPhones[$normalized] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
$nextPageToken = $response->json('nextPageToken');
|
|
if (! is_string($nextPageToken) || $nextPageToken === '') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$records = RubricaUniversale::query()
|
|
->where(function ($q): void {
|
|
$q->whereNotNull('email')->orWhereNotNull('telefono_cellulare');
|
|
})
|
|
->where('stato', 'attivo')
|
|
->orderByDesc('updated_at')
|
|
->limit(300)
|
|
->get();
|
|
|
|
$created = 0;
|
|
$skipped = 0;
|
|
$errors = 0;
|
|
|
|
foreach ($records as $record) {
|
|
$email = trim((string) ($record->email ?? ''));
|
|
$phone = trim((string) ($record->telefono_cellulare ?? ''));
|
|
$phoneNormalized = $this->normalizePhone($phone);
|
|
|
|
if (
|
|
($email !== '' && isset($existingEmails[strtolower($email)]))
|
|
|| ($phoneNormalized !== '' && isset($existingPhones[$phoneNormalized]))
|
|
) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
[$givenName, $familyName, $fullName] = $this->buildContactNameParts(
|
|
(string) ($record->nome ?? ''),
|
|
(string) ($record->cognome ?? ''),
|
|
(string) ($record->ragione_sociale ?? '')
|
|
);
|
|
|
|
$payload = [
|
|
'names' => [
|
|
[
|
|
'displayName' => $fullName,
|
|
'givenName' => $givenName,
|
|
'familyName' => $familyName,
|
|
],
|
|
],
|
|
];
|
|
|
|
if ($email !== '') {
|
|
$payload['emailAddresses'] = [['value' => $email]];
|
|
}
|
|
if ($phone !== '') {
|
|
$payload['phoneNumbers'] = [['value' => $phone]];
|
|
}
|
|
|
|
$createResponse = Http::withToken($token)
|
|
->acceptJson()
|
|
->post('https://people.googleapis.com/v1/people:createContact', $payload);
|
|
|
|
if ($createResponse->status() === 401) {
|
|
$token = $this->resolveAccessToken($amministratore, $google, $oauth, true) ?? $token;
|
|
$createResponse = Http::withToken($token)
|
|
->acceptJson()
|
|
->post('https://people.googleapis.com/v1/people:createContact', $payload);
|
|
}
|
|
|
|
if (! $createResponse->successful()) {
|
|
$errors++;
|
|
continue;
|
|
}
|
|
|
|
$created++;
|
|
if ($email !== '') {
|
|
$existingEmails[strtolower($email)] = true;
|
|
}
|
|
if ($phoneNormalized !== '') {
|
|
$existingPhones[$phoneNormalized] = true;
|
|
}
|
|
}
|
|
|
|
$this->loadContactsPreview($token);
|
|
|
|
Notification::make()
|
|
->title('Export Rubrica -> Google completato')
|
|
->success()
|
|
->body("Creati {$created} contatti, saltati {$skipped}, errori {$errors}.")
|
|
->send();
|
|
}
|
|
|
|
public function provaSincronizzazioneCalendarioTicket(): void
|
|
{
|
|
$this->sincronizzaTicketSuGoogleCalendar(true);
|
|
}
|
|
|
|
public function sincronizzaCalendarioTicket(): void
|
|
{
|
|
$this->sincronizzaTicketSuGoogleCalendar(false);
|
|
}
|
|
|
|
private function resolveAccessToken(Amministratore $amministratore, array $google, array $oauth, bool $forceRefresh = false): ?string
|
|
{
|
|
$accessToken = trim((string) Arr::get($oauth, 'access_token', ''));
|
|
$refreshToken = trim((string) Arr::get($oauth, 'refresh_token', ''));
|
|
|
|
if (! $forceRefresh && $accessToken !== '') {
|
|
return $accessToken;
|
|
}
|
|
|
|
if ($refreshToken === '') {
|
|
$this->errorMessage = 'Token Google non disponibile. Ricollega l account Google.';
|
|
return null;
|
|
}
|
|
|
|
$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'));
|
|
|
|
$credentialPairs = [];
|
|
if ($settingsClientId !== '' && $settingsClientSecret !== '') {
|
|
$credentialPairs[] = [$settingsClientId, $settingsClientSecret, 'settings'];
|
|
}
|
|
if (
|
|
$configClientId !== ''
|
|
&& $configClientSecret !== ''
|
|
&& ($configClientId !== $settingsClientId || $configClientSecret !== $settingsClientSecret)
|
|
) {
|
|
$credentialPairs[] = [$configClientId, $configClientSecret, 'config'];
|
|
}
|
|
|
|
if ($credentialPairs === []) {
|
|
$this->errorMessage = 'Client ID/Secret Google mancanti. Controlla Scheda amministratore.';
|
|
return null;
|
|
}
|
|
|
|
$payload = null;
|
|
$lastError = null;
|
|
foreach ($credentialPairs as [$clientId, $clientSecret, $source]) {
|
|
$response = Http::asForm()->post('https://oauth2.googleapis.com/token', [
|
|
'client_id' => $clientId,
|
|
'client_secret' => $clientSecret,
|
|
'refresh_token' => $refreshToken,
|
|
'grant_type' => 'refresh_token',
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
$payload = $response->json();
|
|
break;
|
|
}
|
|
|
|
$message = (string) (Arr::get($response->json(), 'error_description') ?: Arr::get($response->json(), 'error.message') ?: 'refresh_failed');
|
|
$lastError = "{$source}:{$response->status()} {$message}";
|
|
}
|
|
|
|
if (! is_array($payload)) {
|
|
$this->errorMessage = 'Refresh token Google non riuscito: ' . $lastError;
|
|
return null;
|
|
}
|
|
|
|
$newAccessToken = trim((string) Arr::get($payload, 'access_token', ''));
|
|
|
|
if ($newAccessToken === '') {
|
|
$this->errorMessage = 'Google ha risposto senza access token valido.';
|
|
return null;
|
|
}
|
|
|
|
$impostazioni = $amministratore->impostazioni ?? [];
|
|
$impostazioniGoogle = Arr::get($impostazioni, 'google', []);
|
|
$impostazioniOauth = Arr::get($impostazioniGoogle, 'oauth', []);
|
|
|
|
$impostazioniOauth['access_token'] = $newAccessToken;
|
|
$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;
|
|
$amministratore->impostazioni = $impostazioni;
|
|
$amministratore->save();
|
|
|
|
return $newAccessToken;
|
|
}
|
|
|
|
private function loadCalendarEvents(string $token, string $calendarId): void
|
|
{
|
|
$response = Http::withToken($token)
|
|
->acceptJson()
|
|
->get('https://www.googleapis.com/calendar/v3/calendars/' . rawurlencode($calendarId) . '/events', [
|
|
'singleEvents' => 'true',
|
|
'orderBy' => 'startTime',
|
|
'timeMin' => Carbon::now()->toIso8601String(),
|
|
'maxResults' => 8,
|
|
]);
|
|
|
|
if (! $response->successful() && $calendarId !== 'primary') {
|
|
$this->calendarId = 'primary';
|
|
$response = Http::withToken($token)
|
|
->acceptJson()
|
|
->get('https://www.googleapis.com/calendar/v3/calendars/primary/events', [
|
|
'singleEvents' => 'true',
|
|
'orderBy' => 'startTime',
|
|
'timeMin' => Carbon::now()->toIso8601String(),
|
|
'maxResults' => 8,
|
|
]);
|
|
}
|
|
|
|
if (! $response->successful()) {
|
|
$this->errorMessage = 'Calendario Google non disponibile al momento. Verifica calendar_id e permessi OAuth.';
|
|
return;
|
|
}
|
|
|
|
$items = $response->json('items');
|
|
if (! is_array($items)) {
|
|
return;
|
|
}
|
|
|
|
$events = [];
|
|
foreach ($items as $item) {
|
|
$start = (string) (Arr::get($item, 'start.dateTime') ?: Arr::get($item, 'start.date') ?: '');
|
|
$summary = (string) Arr::get($item, 'summary', 'Evento senza titolo');
|
|
$location = (string) Arr::get($item, 'location', '');
|
|
|
|
$events[] = [
|
|
'title' => $summary,
|
|
'start' => $start,
|
|
'location' => $location,
|
|
];
|
|
}
|
|
|
|
$this->calendarEvents = $events;
|
|
}
|
|
|
|
private function sincronizzaTicketSuGoogleCalendar(bool $dryRun): void
|
|
{
|
|
$amministratore = $this->resolveAmministratore();
|
|
if (! $amministratore instanceof Amministratore) {
|
|
Notification::make()->title('Sync calendario non disponibile')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$google = Arr::get($amministratore->impostazioni ?? [], 'google', []);
|
|
$oauth = Arr::get($google, 'oauth', []);
|
|
$token = $this->resolveAccessToken($amministratore, $google, $oauth);
|
|
|
|
if ($token === null) {
|
|
Notification::make()->title('Sync calendario non riuscito')->warning()->body($this->errorMessage ?? 'Token Google non disponibile.')->send();
|
|
return;
|
|
}
|
|
|
|
$stabile = StabileContext::getActiveStabile(Auth::user());
|
|
if (! $stabile instanceof Stabile) {
|
|
Notification::make()->title('Sync calendario non riuscito')->warning()->body('Seleziona prima uno stabile attivo.')->send();
|
|
return;
|
|
}
|
|
|
|
$tickets = Ticket::query()
|
|
->where('stabile_id', $stabile->id)
|
|
->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])
|
|
->orderByDesc('created_at')
|
|
->limit(30)
|
|
->get();
|
|
|
|
$created = 0;
|
|
$updated = 0;
|
|
$errors = 0;
|
|
$calendarId = (string) ($this->calendarId ?: 'primary');
|
|
|
|
foreach ($tickets as $ticket) {
|
|
$payload = $this->buildCalendarEventPayload($ticket, (string) ($stabile->denominazione ?? ('Stabile #' . $stabile->id)));
|
|
$event = $this->findCalendarEventByTicketId($token, $calendarId, (int) $ticket->id);
|
|
|
|
if ($event === null) {
|
|
if ($dryRun) {
|
|
$created++;
|
|
continue;
|
|
}
|
|
|
|
$createResponse = Http::withToken($token)
|
|
->acceptJson()
|
|
->post('https://www.googleapis.com/calendar/v3/calendars/' . rawurlencode($calendarId) . '/events', $payload);
|
|
|
|
if (! $createResponse->successful()) {
|
|
$errors++;
|
|
continue;
|
|
}
|
|
|
|
$created++;
|
|
continue;
|
|
}
|
|
|
|
if ($dryRun) {
|
|
$updated++;
|
|
continue;
|
|
}
|
|
|
|
$eventId = trim((string) Arr::get($event, 'id', ''));
|
|
if ($eventId === '') {
|
|
$errors++;
|
|
continue;
|
|
}
|
|
|
|
$updateResponse = Http::withToken($token)
|
|
->acceptJson()
|
|
->patch('https://www.googleapis.com/calendar/v3/calendars/' . rawurlencode($calendarId) . '/events/' . rawurlencode($eventId), $payload);
|
|
|
|
if (! $updateResponse->successful()) {
|
|
$errors++;
|
|
continue;
|
|
}
|
|
|
|
$updated++;
|
|
}
|
|
|
|
$mode = $dryRun ? 'Prova sync' : 'Sync calendario';
|
|
Notification::make()
|
|
->title($mode . ' completata')
|
|
->success()
|
|
->body("Creazioni: {$created}, aggiornamenti: {$updated}, errori: {$errors}.")
|
|
->send();
|
|
|
|
if (! $dryRun) {
|
|
$this->loadCalendarEvents($token, $calendarId);
|
|
}
|
|
}
|
|
|
|
private function findCalendarEventByTicketId(string $token, string $calendarId, int $ticketId): ?array
|
|
{
|
|
$response = Http::withToken($token)
|
|
->acceptJson()
|
|
->get('https://www.googleapis.com/calendar/v3/calendars/' . rawurlencode($calendarId) . '/events', [
|
|
'privateExtendedProperty' => 'netgescon_ticket_id=' . $ticketId,
|
|
'maxResults' => 1,
|
|
]);
|
|
|
|
if (! $response->successful()) {
|
|
return null;
|
|
}
|
|
|
|
$items = $response->json('items');
|
|
if (! is_array($items) || count($items) === 0) {
|
|
return null;
|
|
}
|
|
|
|
$first = $items[0];
|
|
return is_array($first) ? $first : null;
|
|
}
|
|
|
|
private function buildCalendarEventPayload(Ticket $ticket, string $stabileName): array
|
|
{
|
|
$startAt = Carbon::parse($ticket->data_apertura ?? $ticket->created_at ?? now());
|
|
$endAt = (clone $startAt)->addMinutes(30);
|
|
|
|
return [
|
|
'summary' => 'Ticket #' . (int) $ticket->id . ' - ' . (string) ($ticket->titolo ?? 'Segnalazione'),
|
|
'description' => implode("\n", [
|
|
'Stabile: ' . $stabileName,
|
|
'Stato: ' . (string) ($ticket->stato ?? '-'),
|
|
'Priorita: ' . (string) ($ticket->priorita ?? '-'),
|
|
'Luogo: ' . (string) ($ticket->luogo_intervento ?? '-'),
|
|
'',
|
|
(string) ($ticket->descrizione ?? ''),
|
|
]),
|
|
'start' => [
|
|
'dateTime' => $startAt->toIso8601String(),
|
|
'timeZone' => 'Europe/Rome',
|
|
],
|
|
'end' => [
|
|
'dateTime' => $endAt->toIso8601String(),
|
|
'timeZone' => 'Europe/Rome',
|
|
],
|
|
'extendedProperties' => [
|
|
'private' => [
|
|
'netgescon_ticket_id' => (string) $ticket->id,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
private function loadContactsPreview(string $token): void
|
|
{
|
|
$response = Http::withToken($token)
|
|
->acceptJson()
|
|
->get('https://people.googleapis.com/v1/people/me/connections', [
|
|
'personFields' => 'names,emailAddresses,phoneNumbers',
|
|
'pageSize' => 8,
|
|
]);
|
|
|
|
if (! $response->successful()) {
|
|
return;
|
|
}
|
|
|
|
$connections = $response->json('connections');
|
|
if (! is_array($connections)) {
|
|
return;
|
|
}
|
|
|
|
$contacts = [];
|
|
foreach ($connections as $person) {
|
|
$name = (string) Arr::get($person, 'names.0.displayName', 'Contatto');
|
|
$email = (string) Arr::get($person, 'emailAddresses.0.value', '');
|
|
$phone = (string) Arr::get($person, 'phoneNumbers.0.value', '');
|
|
|
|
$contacts[] = [
|
|
'name' => $name,
|
|
'email' => $email,
|
|
'phone' => $phone,
|
|
];
|
|
}
|
|
|
|
$this->contacts = $contacts;
|
|
}
|
|
|
|
private function loadOpenTickets(): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
$this->openTickets = [];
|
|
return;
|
|
}
|
|
|
|
$stabileIds = StabileContext::accessibleStabili($user)
|
|
->pluck('id')
|
|
->map(fn($value) => (int) $value)
|
|
->filter(fn(int $value) => $value > 0)
|
|
->values()
|
|
->all();
|
|
|
|
if ($stabileIds === []) {
|
|
$this->openTickets = [];
|
|
return;
|
|
}
|
|
|
|
$items = Ticket::query()
|
|
->with('stabile:id,denominazione')
|
|
->whereIn('stabile_id', $stabileIds)
|
|
->aperti()
|
|
->orderByDesc('created_at')
|
|
->limit(8)
|
|
->get();
|
|
|
|
$tickets = [];
|
|
foreach ($items as $ticket) {
|
|
$tickets[] = [
|
|
'id' => (int) $ticket->id,
|
|
'titolo' => (string) ($ticket->titolo ?? 'Ticket senza titolo'),
|
|
'stato' => (string) ($ticket->stato ?? '-'),
|
|
'priorita' => (string) ($ticket->priorita ?? '-'),
|
|
'stabile' => (string) ($ticket->stabile->denominazione ?? 'N/A'),
|
|
];
|
|
}
|
|
|
|
$this->openTickets = $tickets;
|
|
}
|
|
|
|
private function normalizePhone(string $phone): string
|
|
{
|
|
return preg_replace('/[^0-9+]/', '', trim($phone)) ?? '';
|
|
}
|
|
|
|
/**
|
|
* @return array{0:string,1:string,2:string}
|
|
*/
|
|
private function buildContactNameParts(string $nomeRaw, string $cognomeRaw, string $ragioneSociale): array
|
|
{
|
|
$nome = trim($nomeRaw);
|
|
$cognome = trim($cognomeRaw);
|
|
|
|
if ($cognome === '' && str_contains($nome, ' ')) {
|
|
$parts = preg_split('/\s+/', $nome) ?: [];
|
|
if (count($parts) > 1) {
|
|
$nome = trim((string) array_shift($parts));
|
|
$cognome = trim(implode(' ', $parts));
|
|
}
|
|
}
|
|
|
|
$display = trim($nome . ' ' . $cognome);
|
|
if ($display === '') {
|
|
$display = trim($ragioneSociale);
|
|
}
|
|
if ($display === '') {
|
|
$display = 'Contatto NetGescon';
|
|
}
|
|
|
|
return [$nome, $cognome, $display];
|
|
}
|
|
|
|
private function loadDriveTemplateFolders(): void
|
|
{
|
|
$items = config('netgescon.google.drive_template_folders', []);
|
|
$yearRoot = trim((string) config('netgescon.google.drive_template_year_root', ''));
|
|
$yearModel = trim((string) config('netgescon.google.drive_template_year_model', ''));
|
|
|
|
$folders = is_array($items)
|
|
? array_values(array_filter(array_map('strval', $items), fn(string $v) : bool => trim($v) !== ''))
|
|
: [];
|
|
|
|
if ($yearRoot !== '' && $yearModel !== '') {
|
|
$baseFolders = $folders;
|
|
|
|
foreach ($baseFolders as $folder) {
|
|
$folders[] = $yearRoot . '/' . $yearModel . '/' . $folder;
|
|
}
|
|
}
|
|
|
|
$this->driveTemplateFolders = array_values(array_unique($folders));
|
|
}
|
|
|
|
private function googleErrorBody($response, string $fallback): string
|
|
{
|
|
try {
|
|
$status = (int) $response->status();
|
|
$message = (string) Arr::get($response->json(), 'error.message', '');
|
|
if ($message !== '') {
|
|
return $fallback . " (HTTP {$status}: {$message})";
|
|
}
|
|
return $fallback . " (HTTP {$status})";
|
|
} catch (\Throwable) {
|
|
return $fallback;
|
|
}
|
|
}
|
|
|
|
private function resolveAmministratore(): ?Amministratore
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
return null;
|
|
}
|
|
|
|
$amministratore = $user->amministratore;
|
|
|
|
if (! $amministratore instanceof Amministratore && $user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$stabile = StabileContext::getActiveStabile($user);
|
|
if ($stabile instanceof Stabile && $stabile->amministratore instanceof Amministratore) {
|
|
$amministratore = $stabile->amministratore;
|
|
}
|
|
}
|
|
|
|
return $amministratore instanceof Amministratore ? $amministratore : null;
|
|
}
|
|
|
|
/**
|
|
* @return array{0:string,1:string}
|
|
*/
|
|
private function splitDisplayName(string $displayName, string $fallbackEmail): array
|
|
{
|
|
$displayName = trim($displayName);
|
|
if ($displayName === '') {
|
|
$local = trim((string) strtok($fallbackEmail, '@'));
|
|
return [$local !== '' ? $local : 'Contatto', ''];
|
|
}
|
|
|
|
$parts = preg_split('/\s+/', $displayName) ?: [];
|
|
if (count($parts) <= 1) {
|
|
return [$parts[0] ?? $displayName, ''];
|
|
}
|
|
|
|
$nome = array_shift($parts) ?: '';
|
|
$cognome = trim(implode(' ', $parts));
|
|
|
|
return [$nome, $cognome];
|
|
}
|
|
|
|
private function inferCategoria(string $displayName, string $email): string
|
|
{
|
|
$haystack = strtolower(trim($displayName . ' ' . $email));
|
|
|
|
if ($haystack === '') {
|
|
return 'altro';
|
|
}
|
|
|
|
if (str_contains($haystack, 'assicur') || str_contains($haystack, 'insurance')) {
|
|
return 'assicurazione';
|
|
}
|
|
|
|
if (str_contains($haystack, 'banca') || str_contains($haystack, 'bank') || str_contains($haystack, 'unicredit') || str_contains($haystack, 'intesa')) {
|
|
return 'banca';
|
|
}
|
|
|
|
if (str_contains($haystack, 'condominio') || str_contains($haystack, 'condom')) {
|
|
return 'condomino';
|
|
}
|
|
|
|
if (
|
|
str_contains($haystack, 'srl')
|
|
|| str_contains($haystack, 'spa')
|
|
|| str_contains($haystack, 'sas')
|
|
|| str_contains($haystack, 'snc')
|
|
|| str_contains($haystack, 'impianti')
|
|
|| str_contains($haystack, 'servizi')
|
|
|| str_contains($haystack, 'edil')
|
|
|| str_contains($haystack, 'idraul')
|
|
|| str_contains($haystack, 'elettric')
|
|
) {
|
|
return 'fornitore';
|
|
}
|
|
|
|
return 'cliente';
|
|
}
|
|
|
|
private function inferTipoContatto(string $displayName, string $email): string
|
|
{
|
|
$haystack = strtolower(trim($displayName . ' ' . $email));
|
|
|
|
if (
|
|
str_contains($haystack, 'srl')
|
|
|| str_contains($haystack, 'spa')
|
|
|| str_contains($haystack, 'sas')
|
|
|| str_contains($haystack, 'snc')
|
|
|| str_contains($haystack, 'stp')
|
|
|| str_contains($haystack, 'studio')
|
|
|| str_contains($haystack, 'impresa')
|
|
) {
|
|
return 'persona_giuridica';
|
|
}
|
|
|
|
return 'persona_fisica';
|
|
}
|
|
}
|