netgescon-day0/app/Console/Commands/GoogleSyncTicketCalendarCommand.php

272 lines
9.9 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Amministratore;
use App\Models\Ticket;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
class GoogleSyncTicketCalendarCommand extends Command
{
protected $signature = 'google:sync-ticket-calendar
{--admin-id=* : Limita la sincronizzazione a uno o piu ID amministratore}
{--calendar-id= : Sovrascrive il Calendar ID configurato (default: primary)}
{--limit=50 : Numero massimo ticket aperti per admin}
{--dry-run : Simula la sync senza scrivere su Google Calendar}';
protected $description = 'Sincronizza i ticket aperti NetGescon su Google Calendar (create/update con chiave ticket).';
public function handle(): int
{
$dryRun = (bool) $this->option('dry-run');
$limit = max(1, (int) $this->option('limit'));
$forcedCalendarId = trim((string) $this->option('calendar-id'));
$filterAdminIds = collect((array) $this->option('admin-id'))
->map(fn($v) => (int) $v)
->filter(fn(int $v) => $v > 0)
->values()
->all();
$adminsQuery = Amministratore::query();
if ($filterAdminIds !== []) {
$adminsQuery->whereIn('id', $filterAdminIds);
}
$admins = $adminsQuery->get();
if ($admins->isEmpty()) {
$this->warn('Nessun amministratore trovato per i filtri richiesti.');
return self::SUCCESS;
}
$sumCreated = 0;
$sumUpdated = 0;
$sumErrors = 0;
foreach ($admins as $admin) {
$google = Arr::get($admin->impostazioni ?? [], 'google', []);
$oauth = Arr::get($google, 'oauth', []);
if (! (bool) Arr::get($oauth, 'connected', false)) {
$this->line("Admin {$admin->id}: Google non collegato, salto.");
continue;
}
$calendarId = $forcedCalendarId !== ''
? $forcedCalendarId
: ((string) Arr::get($google, 'calendar_id', 'primary') ?: 'primary');
$token = $this->resolveAccessToken($admin, $google, $oauth, $dryRun);
if ($token === null) {
$this->warn("Admin {$admin->id}: token non disponibile, salto.");
continue;
}
$tickets = Ticket::query()
->with('stabile:id,amministratore_id,denominazione')
->whereHas('stabile', function ($q) use ($admin): void {
$q->where('amministratore_id', $admin->id);
})
->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])
->latest('created_at')
->limit($limit)
->get();
$created = 0;
$updated = 0;
$errors = 0;
foreach ($tickets as $ticket) {
$payload = $this->buildEventPayload($ticket);
$event = $this->findEventByTicketId($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++;
}
$sumCreated += $created;
$sumUpdated += $updated;
$sumErrors += $errors;
$mode = $dryRun ? 'dry-run' : 'apply';
$this->info("Admin {$admin->id} ({$mode}): creati {$created}, aggiornati {$updated}, errori {$errors}.");
}
$this->newLine();
$this->info("Totale sync calendario: creati {$sumCreated}, aggiornati {$sumUpdated}, errori {$sumErrors}.");
return self::SUCCESS;
}
private function findEventByTicketId(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 buildEventPayload(Ticket $ticket): 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: ' . (string) ($ticket->stabile->denominazione ?? ('Stabile #' . (int) $ticket->stabile_id)),
'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 resolveAccessToken(Amministratore $amministratore, array $google, array $oauth, bool $dryRun, 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 === '') {
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 === []) {
return null;
}
$payload = null;
foreach ($credentialPairs as [$clientId, $clientSecret]) {
$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;
}
}
if (! is_array($payload)) {
return null;
}
$newAccessToken = trim((string) Arr::get($payload, 'access_token', ''));
if ($newAccessToken === '') {
return null;
}
if (! $dryRun) {
$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;
}
}