466 lines
18 KiB
PHP
466 lines
18 KiB
PHP
<?php
|
|
namespace App\Filament\Pages\Fornitore;
|
|
|
|
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
|
|
use App\Filament\Pages\Gescon\RubricaUniversaleScheda;
|
|
use App\Models\AssistenzaTecnorepairScheda;
|
|
use App\Models\Fornitore;
|
|
use App\Models\FornitoreCliente;
|
|
use App\Models\TicketIntervento;
|
|
use App\Models\User;
|
|
use BackedEnum;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Str;
|
|
use UnitEnum;
|
|
|
|
class RubricaClienti extends Page
|
|
{
|
|
use ResolvesOperatoreContext;
|
|
|
|
protected static ?string $navigationLabel = 'Rubrica clienti';
|
|
|
|
protected static ?string $title = 'Rubrica clienti fornitore';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-phone';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Fornitore';
|
|
|
|
protected static ?int $navigationSort = 3;
|
|
|
|
protected static ?string $slug = 'fornitore/rubrica-clienti';
|
|
|
|
protected string $view = 'filament.pages.fornitore.rubrica-clienti';
|
|
|
|
public ?int $fornitoreId = null;
|
|
|
|
public ?string $fornitoreLabel = null;
|
|
|
|
public bool $missingAdminContext = false;
|
|
|
|
public string $search = '';
|
|
|
|
public string $activeTab = 'elenco';
|
|
|
|
public ?int $selectedClienteId = null;
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $rows = [];
|
|
|
|
/** @var array<string, int> */
|
|
public array $counters = [
|
|
'totali' => 0,
|
|
'collegati' => 0,
|
|
'con_email' => 0,
|
|
'con_cellulare' => 0,
|
|
];
|
|
|
|
/** @var array<string, mixed>|null */
|
|
public ?array $googleWorkspaceStatus = null;
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = Auth::user();
|
|
|
|
return $user instanceof User
|
|
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']);
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
[$fornitore] = $this->resolveOperatoreContext(allowAdminWithoutSupplier: true);
|
|
|
|
if (! $fornitore instanceof Fornitore) {
|
|
$this->missingAdminContext = true;
|
|
return;
|
|
}
|
|
|
|
$this->fornitoreId = (int) $fornitore->id;
|
|
$this->fornitoreLabel = $this->getFornitoreLabel($fornitore);
|
|
$this->googleWorkspaceStatus = $this->resolveGoogleWorkspaceStatus($fornitore);
|
|
$this->refreshRows();
|
|
}
|
|
|
|
public function updatedSearch(): void
|
|
{
|
|
$this->refreshRows();
|
|
}
|
|
|
|
public function selectTab(string $tab): void
|
|
{
|
|
if (! in_array($tab, ['elenco', 'scheda'], true)) {
|
|
return;
|
|
}
|
|
|
|
$this->activeTab = $tab;
|
|
}
|
|
|
|
public function selectCliente(int $clienteId): void
|
|
{
|
|
$this->selectedClienteId = $clienteId > 0 ? $clienteId : null;
|
|
$this->activeTab = 'scheda';
|
|
}
|
|
|
|
public function refreshRows(): void
|
|
{
|
|
if (! $this->fornitoreId) {
|
|
$this->rows = [];
|
|
$this->counters = ['totali' => 0, 'collegati' => 0, 'con_email' => 0, 'con_cellulare' => 0];
|
|
return;
|
|
}
|
|
|
|
$term = Str::lower(trim($this->search));
|
|
|
|
$legacyCounts = AssistenzaTecnorepairScheda::query()
|
|
->where('fornitore_id', $this->fornitoreId)
|
|
->selectRaw('legacy_cliente_id, COUNT(*) as totale_schede, MAX(date_received) as ultima_data')
|
|
->groupBy('legacy_cliente_id')
|
|
->get()
|
|
->mapWithKeys(fn(AssistenzaTecnorepairScheda $row): array => [
|
|
(int) ($row->legacy_cliente_id ?? 0) => [
|
|
'totale_schede' => (int) ($row->totale_schede ?? 0),
|
|
'ultima_data' => optional($row->ultima_data)->format('d/m/Y') ?: '-',
|
|
],
|
|
])
|
|
->all();
|
|
|
|
$items = FornitoreCliente::query()
|
|
->with('rubrica:id,nome,cognome,ragione_sociale,email,telefono_cellulare,telefono_ufficio,categoria')
|
|
->where('fornitore_id', $this->fornitoreId)
|
|
->when($term !== '', function ($query) use ($term): void {
|
|
$like = '%' . $term . '%';
|
|
|
|
$query->where(function ($inner) use ($like): void {
|
|
$inner->where('display_name', 'like', $like)
|
|
->orWhere('phone', 'like', $like)
|
|
->orWhere('phone_alt', 'like', $like)
|
|
->orWhere('email', 'like', $like)
|
|
->orWhere('citta', 'like', $like)
|
|
->orWhere('indirizzo', 'like', $like);
|
|
});
|
|
})
|
|
->orderByRaw('CASE WHEN rubrica_id IS NULL THEN 1 ELSE 0 END')
|
|
->orderBy('display_name')
|
|
->limit(500)
|
|
->get();
|
|
|
|
if ($items->isEmpty()) {
|
|
$this->rows = $this->buildTicketFallbackRows($term);
|
|
$this->counters = [
|
|
'totali' => count($this->rows),
|
|
'collegati' => (int) collect($this->rows)->filter(fn(array $row): bool => filled($row['rubrica_id'] ?? null))->count(),
|
|
'con_email' => (int) collect($this->rows)->filter(fn(array $row): bool => filled($row['email'] ?? null))->count(),
|
|
'con_cellulare' => (int) collect($this->rows)->filter(fn(array $row): bool => filled($row['phone'] ?? null))->count(),
|
|
];
|
|
|
|
$this->selectedClienteId = null;
|
|
return;
|
|
}
|
|
|
|
$this->rows = $items->map(function (FornitoreCliente $cliente) use ($legacyCounts): array {
|
|
$legacy = $legacyCounts[(int) ($cliente->legacy_cliente_id ?? 0)] ?? ['totale_schede' => 0, 'ultima_data' => '-'];
|
|
|
|
return [
|
|
'id' => (int) $cliente->id,
|
|
'legacy_cliente_id'=> (int) ($cliente->legacy_cliente_id ?? 0),
|
|
'display_name' => (string) ($cliente->display_name ?: 'Cliente TecnoRepair'),
|
|
'phone' => (string) ($cliente->phone ?? ''),
|
|
'phone_alt' => (string) ($cliente->phone_alt ?? ''),
|
|
'email' => (string) ($cliente->email ?? ''),
|
|
'indirizzo' => (string) ($cliente->indirizzo ?? ''),
|
|
'citta' => (string) ($cliente->citta ?? ''),
|
|
'provincia' => (string) ($cliente->provincia ?? ''),
|
|
'rubrica_id' => (int) ($cliente->rubrica_id ?? 0),
|
|
'rubrica_url' => $cliente->rubrica_id ? RubricaUniversaleScheda::getUrl(['record' => (int) $cliente->rubrica_id], panel: 'admin-filament') : null,
|
|
'rubrica_label' => (string) ($cliente->rubrica?->categoria ?? ''),
|
|
'totale_schede' => (int) ($legacy['totale_schede'] ?? 0),
|
|
'ultima_data' => (string) ($legacy['ultima_data'] ?? '-'),
|
|
'updated_at' => optional($cliente->updated_at)->format('d/m/Y H:i') ?: '-',
|
|
'source' => 'tecnorepair_tclienti',
|
|
];
|
|
})->all();
|
|
|
|
$this->counters = [
|
|
'totali' => count($this->rows),
|
|
'collegati' => (int) collect($this->rows)->filter(fn(array $row): bool => (int) ($row['rubrica_id'] ?? 0) > 0)->count(),
|
|
'con_email' => (int) collect($this->rows)->filter(fn(array $row): bool => trim((string) ($row['email'] ?? '')) !== '')->count(),
|
|
'con_cellulare' => (int) collect($this->rows)->filter(fn(array $row): bool => trim((string) ($row['phone'] ?? '')) !== '')->count(),
|
|
];
|
|
|
|
if ((int) ($this->selectedClienteId ?? 0) <= 0 && $this->rows !== []) {
|
|
$this->selectedClienteId = (int) $this->rows[0]['id'];
|
|
}
|
|
}
|
|
|
|
public function getSelectedClienteProperty(): ?FornitoreCliente
|
|
{
|
|
$selectedId = (int) ($this->selectedClienteId ?? 0);
|
|
if ($selectedId <= 0 || ! $this->fornitoreId) {
|
|
return null;
|
|
}
|
|
|
|
return FornitoreCliente::query()
|
|
->with('rubrica')
|
|
->where('fornitore_id', $this->fornitoreId)
|
|
->find($selectedId);
|
|
}
|
|
|
|
/** @return array<int, array<string, mixed>> */
|
|
public function getSelectedClienteSchedeProperty(): array
|
|
{
|
|
$cliente = $this->selectedCliente;
|
|
if (! $cliente instanceof FornitoreCliente || ! $this->fornitoreId) {
|
|
return [];
|
|
}
|
|
|
|
return AssistenzaTecnorepairScheda::query()
|
|
->where('fornitore_id', $this->fornitoreId)
|
|
->when((int) ($cliente->legacy_cliente_id ?? 0) > 0, fn($query) => $query->where('legacy_cliente_id', (int) $cliente->legacy_cliente_id))
|
|
->orderByDesc('date_received')
|
|
->orderByDesc('id')
|
|
->limit(30)
|
|
->get()
|
|
->map(fn(AssistenzaTecnorepairScheda $scheda): array => [
|
|
'id' => (int) $scheda->id,
|
|
'numero' => (string) ($scheda->legacy_numero_scheda ?: ('#' . $scheda->id)),
|
|
'prodotto' => (string) ($scheda->product_model ?: '-'),
|
|
'codice' => (string) ($scheda->product_code ?: ''),
|
|
'seriali' => (string) $scheda->serial_label,
|
|
'stato' => (string) ($scheda->status_label ?: '-'),
|
|
'status_badge' => (string) $scheda->status_badge_classes,
|
|
'ingresso' => optional($scheda->date_received)->format('d/m/Y') ?: '-',
|
|
])->all();
|
|
|
|
$items = TicketIntervento::query()
|
|
->with(['ticket.stabile', 'ticket.soggettoRichiedente', 'ticket.apertoDaUser'])
|
|
->where('fornitore_id', $this->fornitoreId)
|
|
->latest('updated_at')
|
|
->limit(250)
|
|
->get();
|
|
|
|
$grouped = [];
|
|
|
|
foreach ($items as $intervento) {
|
|
$contact = $this->resolveCallerDataForRubrica($intervento);
|
|
$key = $contact['identity_key'];
|
|
|
|
if ($key === '') {
|
|
$key = 'ticket-' . (int) $intervento->ticket_id;
|
|
}
|
|
|
|
if ($term !== '') {
|
|
$haystack = Str::lower(implode(' ', [
|
|
$contact['contatto'],
|
|
$contact['telefono'],
|
|
$contact['email'],
|
|
$contact['stabile'],
|
|
$contact['origine'],
|
|
]));
|
|
|
|
if (! Str::contains($haystack, $term)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (! isset($grouped[$key])) {
|
|
$grouped[$key] = [
|
|
'contatto' => $contact['contatto'],
|
|
'telefono' => $contact['telefono'],
|
|
'email' => $contact['email'],
|
|
'stabile' => $contact['stabile'],
|
|
'origine' => $contact['origine'],
|
|
'rubrica_url' => $contact['rubrica_url'],
|
|
'rubrica_label' => $contact['rubrica_label'],
|
|
'ultimo_ticket_id' => (int) $intervento->ticket_id,
|
|
'ultima_scheda_url' => TicketInterventoScheda::getUrl(['record' => (int) $intervento->id], panel: 'admin-filament'),
|
|
'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-',
|
|
'totale_interventi' => 1,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$grouped[$key]['totale_interventi']++;
|
|
}
|
|
|
|
$this->rows = array_values($grouped);
|
|
}
|
|
|
|
public function getLavorazioniUrl(): string
|
|
{
|
|
return LavorazioniOperative::getUrl(['fornitore' => (int) $this->fornitoreId], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getProdottiUrl(): string
|
|
{
|
|
return ProdottiCatalogo::getUrl(['fornitore' => (int) $this->fornitoreId], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getImportCommandHintProperty(): string
|
|
{
|
|
$fornitore = (int) ($this->fornitoreId ?? 0) > 0
|
|
? Fornitore::query()->select(['id', 'amministratore_id'])->find((int) $this->fornitoreId)
|
|
: null;
|
|
|
|
if (! $fornitore instanceof Fornitore) {
|
|
return 'php artisan tecnorepair:import-rubrica-clienti {amministratore} --fornitore-id={id}';
|
|
}
|
|
|
|
return 'php artisan tecnorepair:import-rubrica-clienti ' . (int) ($fornitore->amministratore_id ?? 0) . ' --fornitore-id=' . (int) $fornitore->id;
|
|
}
|
|
|
|
public function getGoogleConnectUrl(): string
|
|
{
|
|
return route('oauth.google.redirect', [
|
|
'account_key' => 'fornitore-' . (int) ($this->fornitoreId ?? 0),
|
|
'label' => trim((string) ($this->fornitoreLabel ?? '')) ?: ('Fornitore #' . (int) ($this->fornitoreId ?? 0)),
|
|
'return_to' => request()->getRequestUri(),
|
|
]);
|
|
}
|
|
|
|
public function getGoogleDisconnectUrl(): string
|
|
{
|
|
return route('oauth.google.disconnect', [
|
|
'account_key' => 'fornitore-' . (int) ($this->fornitoreId ?? 0),
|
|
'return_to' => request()->getRequestUri(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function resolveCallerDataForRubrica(TicketIntervento $intervento): array
|
|
{
|
|
$ticket = $intervento->ticket;
|
|
$parsed = $this->extractCallerData((string) ($ticket?->descrizione ?? ''));
|
|
|
|
$contatto = $parsed['contatto'];
|
|
$telefono = $parsed['telefono'];
|
|
$email = '';
|
|
$origine = 'Descrizione ticket';
|
|
$rubricaUrl = null;
|
|
$rubricaLabel = '';
|
|
|
|
if ($ticket?->soggettoRichiedente) {
|
|
$soggetto = $ticket->soggettoRichiedente;
|
|
$contatto = trim((string) ($soggetto->ragione_sociale ?: trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? '')))) ?: $contatto;
|
|
$telefono = trim((string) ($soggetto->telefono ?? '')) ?: $telefono;
|
|
$email = trim((string) ($soggetto->email ?? ''));
|
|
$origine = 'Richiedente ticket';
|
|
$rubricaUrl = $this->buildRubricaUrlForCurrentUser((int) $soggetto->id);
|
|
$rubricaLabel = trim((string) ($soggetto->categoria ?? ''));
|
|
} elseif ($ticket?->apertoDaUser) {
|
|
$contatto = trim((string) ($ticket->apertoDaUser->name ?? '')) ?: $contatto;
|
|
$email = trim((string) ($ticket->apertoDaUser->email ?? ''));
|
|
$origine = 'Utente apertura ticket';
|
|
}
|
|
|
|
$identityKey = '';
|
|
if ($ticket?->soggetto_richiedente_id) {
|
|
$identityKey = 'soggetto-' . (int) $ticket->soggetto_richiedente_id;
|
|
} elseif ($email !== '') {
|
|
$identityKey = 'email-' . Str::lower($email);
|
|
} elseif ($telefono !== '') {
|
|
$identityKey = 'phone-' . preg_replace('/\D+/', '', $telefono);
|
|
}
|
|
|
|
return [
|
|
'contatto' => $contatto !== '' ? $contatto : 'Contatto non identificato',
|
|
'telefono' => $telefono,
|
|
'email' => $email,
|
|
'origine' => $origine,
|
|
'stabile' => (string) ($ticket?->stabile?->denominazione ?? '-'),
|
|
'rubrica_url' => $rubricaUrl,
|
|
'rubrica_label' => $rubricaLabel,
|
|
'identity_key' => $identityKey,
|
|
];
|
|
}
|
|
|
|
protected function buildRubricaUrlForCurrentUser(int $rubricaId): ?string
|
|
{
|
|
$user = Auth::user();
|
|
|
|
if (! $user instanceof User) {
|
|
return null;
|
|
}
|
|
|
|
if ($user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore'])) {
|
|
return \App\Filament\Pages\Gescon\RubricaUniversaleScheda::getUrl(['record' => $rubricaId], panel: 'admin-filament');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function buildTicketFallbackRows(string $term = ''): array
|
|
{
|
|
$items = TicketIntervento::query()
|
|
->with(['ticket.stabile', 'ticket.soggettoRichiedente', 'ticket.apertoDaUser'])
|
|
->where('fornitore_id', $this->fornitoreId)
|
|
->latest('updated_at')
|
|
->limit(250)
|
|
->get();
|
|
|
|
$grouped = [];
|
|
|
|
foreach ($items as $intervento) {
|
|
$contact = $this->resolveCallerDataForRubrica($intervento);
|
|
$key = $contact['identity_key'] !== '' ? $contact['identity_key'] : ('ticket-' . (int) $intervento->ticket_id);
|
|
|
|
if ($term !== '') {
|
|
$haystack = Str::lower(implode(' ', [$contact['contatto'], $contact['telefono'], $contact['email'], $contact['stabile'], $contact['origine']]));
|
|
if (! Str::contains($haystack, $term)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (! isset($grouped[$key])) {
|
|
$grouped[$key] = [
|
|
'id' => 0,
|
|
'legacy_cliente_id' => 0,
|
|
'display_name' => $contact['contatto'],
|
|
'phone' => $contact['telefono'],
|
|
'phone_alt' => '',
|
|
'email' => $contact['email'],
|
|
'indirizzo' => '',
|
|
'citta' => '',
|
|
'provincia' => '',
|
|
'rubrica_id' => 0,
|
|
'rubrica_url' => $contact['rubrica_url'],
|
|
'rubrica_label' => $contact['rubrica_label'],
|
|
'totale_schede' => 1,
|
|
'ultima_data' => optional($intervento->updated_at)->format('d/m/Y') ?: '-',
|
|
'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-',
|
|
'source' => 'ticket_fallback',
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$grouped[$key]['totale_schede']++;
|
|
}
|
|
|
|
return array_values($grouped);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
protected function resolveGoogleWorkspaceStatus(Fornitore $fornitore): ?array
|
|
{
|
|
$amministratore = $fornitore->amministratore;
|
|
if (! $amministratore) {
|
|
return null;
|
|
}
|
|
|
|
$oauth = (array) (($amministratore->impostazioni['google']['oauth'] ?? null) ?: []);
|
|
|
|
return [
|
|
'connected' => (bool) ($oauth['connected'] ?? false),
|
|
'email' => (string) ($oauth['email'] ?? ''),
|
|
'name' => (string) ($oauth['name'] ?? ''),
|
|
'connected_at' => (string) ($oauth['connected_at'] ?? ''),
|
|
];
|
|
}
|
|
}
|