fix: completa contesto ticket per fornitore

This commit is contained in:
michele 2026-04-06 23:01:29 +00:00
parent 0820e06772
commit 8e8f95b7c6
7 changed files with 529 additions and 54 deletions

View File

@ -3,6 +3,8 @@
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\RubricaUniversale;
use App\Models\Ticket;
use App\Models\TicketIntervento;
use Illuminate\Support\Facades\Auth;
@ -140,9 +142,42 @@ protected function authorizeDipendenteIntervento(TicketIntervento $intervento, ?
*/
protected function buildInterventoRow(TicketIntervento $intervento): array
{
$descrizione = (string) ($intervento->ticket->descrizione ?? '');
$ticket = $intervento->ticket;
$descrizione = (string) ($ticket->descrizione ?? '');
$caller = $this->extractCallerData($descrizione);
if (($caller['contatto'] ?? '-') === '-' || trim((string) ($caller['contatto'] ?? '')) === '') {
$titleContact = $this->extractCallerNameFromTitle((string) ($ticket->titolo ?? ''));
if ($titleContact !== '') {
$caller['contatto'] = $titleContact;
}
}
if ($ticket?->soggettoRichiedente) {
$soggetto = $ticket->soggettoRichiedente;
$label = trim((string) ($soggetto->ragione_sociale ?: trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? ''))));
if ($label !== '') {
$caller['contatto'] = $label;
}
$telefono = trim((string) ($soggetto->telefono ?? ''));
if ($telefono !== '') {
$caller['telefono'] = $telefono;
}
}
$rubrica = $this->resolveCallerRubricaFromTicket($ticket, (string) ($caller['contatto'] ?? ''));
if ($rubrica instanceof RubricaUniversale) {
if (($caller['contatto'] ?? '-') === '-' || trim((string) ($caller['contatto'] ?? '')) === '') {
$caller['contatto'] = trim((string) ($rubrica->nome_completo ?: $rubrica->ragione_sociale ?: '-')) ?: '-';
}
if (($caller['telefono'] ?? '') === '') {
$caller['telefono'] = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: $rubrica->telefono_casa ?: ''));
}
}
return [
'ingresso' => optional($intervento->created_at)->format('d/m/Y H:i') ?: '-',
'contatto' => $caller['contatto'],
@ -153,13 +188,15 @@ protected function buildInterventoRow(TicketIntervento $intervento): array
}
/**
* @return array{contatto:string,telefono:string,problema:string}
* @return array{contatto:string,telefono:string,problema:string,email:string,riferimento:string}
*/
protected function extractCallerData(string $descrizione): array
{
$contatto = '-';
$telefono = '';
$problema = '';
$email = '';
$riferimento = '';
$lines = preg_split('/\r\n|\r|\n/', $descrizione) ?: [];
foreach ($lines as $line) {
@ -172,15 +209,38 @@ protected function extractCallerData(string $descrizione): array
$contatto = trim((string) str_replace('Chiamante selezionato:', '', $trim));
}
if (str_starts_with($trim, 'Contatto associato:')) {
$contatto = trim((string) str_replace('Contatto associato:', '', $trim));
}
if (str_starts_with($trim, 'Telefono:')) {
$telefono = trim((string) str_replace('Telefono:', '', $trim));
}
if (str_starts_with($trim, 'Telefono richiamabile:')) {
$telefono = trim((string) str_replace('Telefono richiamabile:', '', $trim));
}
if (str_starts_with($trim, 'Email richiedente:')) {
$email = trim((string) str_replace('Email richiedente:', '', $trim));
}
if (str_starts_with($trim, 'Riferimento stabile:')) {
$riferimento = trim((string) str_replace('Riferimento stabile:', '', $trim));
}
if (str_starts_with($trim, 'Riferimento unità:')) {
$unita = trim((string) str_replace('Riferimento unità:', '', $trim));
$riferimento = $riferimento !== '' ? ($riferimento . ' · ' . $unita) : $unita;
}
}
return [
'contatto' => $contatto,
'telefono' => $telefono,
'problema' => $problema,
'contatto' => $contatto,
'telefono' => $telefono,
'problema' => $problema,
'email' => $email,
'riferimento' => $riferimento,
];
}
@ -196,6 +256,75 @@ protected function extractApparato(string $rapporto): string
return '-';
}
protected function extractCallerNameFromTitle(string $title): string
{
$title = trim($title);
if ($title === '') {
return '';
}
if (preg_match('/^Segnalazione da\s+(.+)$/iu', $title, $matches)) {
return trim((string) ($matches[1] ?? ''));
}
return '';
}
protected function resolveCallerRubricaFromTicket(?Ticket $ticket, string $fallbackName = ''): ?RubricaUniversale
{
if (! $ticket instanceof Ticket) {
return null;
}
$adminId = (int) ($ticket->stabile?->amministratore_id ?? 0);
$soggetto = $ticket->soggettoRichiedente;
$queries = [];
if ($soggetto && filled($soggetto->codice_fiscale)) {
$queries[] = fn() => RubricaUniversale::query()->where('codice_fiscale', strtoupper(trim((string) $soggetto->codice_fiscale)));
}
if ($soggetto && filled($soggetto->email)) {
$queries[] = fn() => RubricaUniversale::query()->whereRaw('LOWER(email) = ?', [mb_strtolower(trim((string) $soggetto->email))]);
}
if ($soggetto && filled($soggetto->telefono)) {
$phone = preg_replace('/\D+/', '', (string) $soggetto->telefono) ?: '';
if ($phone !== '') {
$queries[] = fn() => RubricaUniversale::query()->where(function ($builder) use ($phone): void {
$builder->whereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_cellulare, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone])
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_ufficio, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone])
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_casa, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone]);
});
}
}
$name = trim($fallbackName);
if ($name !== '' && $name !== '-') {
$queries[] = fn() => RubricaUniversale::query()->where(function ($builder) use ($name): void {
$builder->whereRaw("LOWER(COALESCE(ragione_sociale, '')) = ?", [mb_strtolower($name)])
->orWhereRaw("LOWER(TRIM(CONCAT(COALESCE(nome, ''), ' ', COALESCE(cognome, '')))) = ?", [mb_strtolower($name)]);
});
}
foreach ($queries as $factory) {
$query = $factory();
if ($adminId > 0) {
$query->where(function ($builder) use ($adminId): void {
$builder->where('amministratore_id', $adminId)
->orWhereNull('amministratore_id');
});
}
$match = $query->orderByDesc('updated_at')->orderBy('id')->first();
if ($match instanceof RubricaUniversale) {
return $match;
}
}
return null;
}
protected function buildApparatoSummary(string $marca, string $modello, string $seriale): string
{
$marca = trim($marca);

View File

@ -43,9 +43,9 @@ class LavorazioniOperative extends Page
/** @var array<string, int> */
public array $totals = [
'tutte' => 0,
'tutte' => 0,
'ticket_amministratore' => 0,
'interne' => 0,
'interne' => 0,
];
public static function canAccess(): bool
@ -53,7 +53,7 @@ public static function canAccess(): bool
$user = Auth::user();
return $user instanceof User
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']);
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']);
}
public function mount(): void
@ -165,7 +165,7 @@ public function getProdottiUrl(): string
protected function buildBaseQuery(Fornitore $fornitore, ?FornitoreDipendente $dipendente): Builder
{
$query = TicketIntervento::query()
->with(['ticket.stabile', 'eseguitoDaDipendente'])
->with(['ticket.stabile', 'ticket.unitaImmobiliare', 'ticket.soggettoRichiedente', 'eseguitoDaDipendente'])
->where('fornitore_id', (int) $fornitore->id)
->orderByDesc('created_at');
@ -178,4 +178,4 @@ protected function buildBaseQuery(Fornitore $fornitore, ?FornitoreDipendente $di
return $query;
}
}
}

View File

@ -1,12 +1,16 @@
<?php
namespace App\Filament\Pages\Fornitore;
use App\Filament\Pages\Contabilita\EstrattoContoSoggetto;
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
use App\Filament\Pages\Gescon\RubricaUniversaleScheda;
use App\Filament\Pages\Gescon\StabileScheda;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\Product;
use App\Models\ProductIdentifier;
use App\Models\ProductOffer;
use App\Models\RubricaUniversale;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use App\Models\TicketIntervento;
@ -44,7 +48,7 @@ class TicketInterventoScheda extends Page
public ?FornitoreDipendente $dipendente = null;
/** @var array{contatto:string,telefono:string,email:string,problema:string,sorgente:string,riferimento:string} */
/** @var array{contatto:string,telefono:string,email:string,problema:string,sorgente:string,riferimento:string,rubrica_url:?string,estratto_url:?string,stabile_url:?string,unita_label:string} */
public array $caller = [
'contatto' => '-',
'telefono' => '',
@ -52,6 +56,10 @@ class TicketInterventoScheda extends Page
'problema' => '',
'sorgente' => '',
'riferimento' => '',
'rubrica_url' => null,
'estratto_url'=> null,
'stabile_url' => null,
'unita_label' => '',
];
/** @var array<int, array<string, mixed>> */
@ -382,8 +390,16 @@ public function addSuggestedMaterial(int $productId): void
'external_url' => (string) ($offer?->referral_url ?: $offer?->external_url ?: $this->buildAmazonSearchUrl((string) $product->name)),
];
$description = (string) ($this->materialiUtilizzati[array_key_last($this->materialiUtilizzati)]['descrizione'] ?? 'Materiale');
$this->materialSearch = '';
$this->materialSuggestions = [];
Notification::make()
->title('Materiale aggiunto al rapportino')
->body($description)
->success()
->send();
}
public function addManualMaterial(): void
@ -408,6 +424,12 @@ public function addManualMaterial(): void
$this->materialSearch = '';
$this->materialSuggestions = [];
Notification::make()
->title('Materiale manuale aggiunto')
->body($term)
->success()
->send();
}
public function removeMateriale(int $index): void
@ -1065,7 +1087,7 @@ protected function resolveGoogleWorkspaceStatus(): ?array
}
/**
* @return array{contatto:string,telefono:string,email:string,problema:string,sorgente:string,riferimento:string}
* @return array{contatto:string,telefono:string,email:string,problema:string,sorgente:string,riferimento:string,rubrica_url:?string,estratto_url:?string,stabile_url:?string,unita_label:string}
*/
protected function resolveCallerData(?Ticket $ticket): array
{
@ -1074,12 +1096,23 @@ protected function resolveCallerData(?Ticket $ticket): array
$caller = [
'contatto' => $parsed['contatto'],
'telefono' => $parsed['telefono'],
'email' => '',
'email' => $parsed['email'] ?? '',
'problema' => $parsed['problema'] !== '' ? $parsed['problema'] : (string) ($ticket?->titolo ?? ''),
'sorgente' => 'Descrizione ticket',
'riferimento' => $this->buildCallerReference($ticket),
'riferimento' => $this->buildCallerReference($ticket, (string) ($parsed['riferimento'] ?? '')),
'rubrica_url' => null,
'estratto_url'=> null,
'stabile_url' => $ticket?->stabile ? StabileScheda::getUrl(['record' => (int) $ticket->stabile->id], panel: 'admin-filament') : null,
'unita_label' => $ticket?->unitaImmobiliare ? $this->formatUnitaLabel($ticket->unitaImmobiliare) : '',
];
if (($caller['contatto'] ?? '-') === '-' || trim((string) ($caller['contatto'] ?? '')) === '') {
$titleContact = $this->extractCallerNameFromTitle((string) ($ticket?->titolo ?? ''));
if ($titleContact !== '') {
$caller['contatto'] = $titleContact;
}
}
$soggetto = $ticket?->soggettoRichiedente;
if ($soggetto) {
$label = trim((string) ($soggetto->ragione_sociale ?: trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? ''))));
@ -1088,12 +1121,33 @@ protected function resolveCallerData(?Ticket $ticket): array
}
$caller['telefono'] = trim((string) ($soggetto->telefono ?? '')) ?: $caller['telefono'];
$caller['email'] = trim((string) ($soggetto->email ?? ''));
$caller['email'] = trim((string) ($soggetto->email ?? '')) ?: $caller['email'];
$caller['sorgente'] = 'Richiedente collegato al ticket';
$caller['estratto_url'] = EstrattoContoSoggetto::getUrl(['record' => (int) $soggetto->id], panel: 'admin-filament');
$rubrica = $this->resolveCallerRubrica($ticket, $caller);
if ($rubrica instanceof RubricaUniversale) {
$caller['rubrica_url'] = RubricaUniversaleScheda::getUrl(['record' => (int) $rubrica->id], panel: 'admin-filament');
$caller['telefono'] = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: $rubrica->telefono_casa ?: '')) ?: $caller['telefono'];
$caller['email'] = trim((string) ($rubrica->email ?? '')) ?: $caller['email'];
}
return $caller;
}
$rubrica = $this->resolveCallerRubrica($ticket, $caller);
if ($rubrica instanceof RubricaUniversale) {
$label = trim((string) ($rubrica->nome_completo ?: $rubrica->ragione_sociale ?: ''));
if ($label !== '') {
$caller['contatto'] = $label;
}
$caller['telefono'] = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: $rubrica->telefono_casa ?: '')) ?: $caller['telefono'];
$caller['email'] = trim((string) ($rubrica->email ?? '')) ?: $caller['email'];
$caller['rubrica_url'] = RubricaUniversaleScheda::getUrl(['record' => (int) $rubrica->id], panel: 'admin-filament');
$caller['sorgente'] = 'Rubrica dedotta dal ticket';
}
$openedBy = $ticket?->apertoDaUser;
if ($openedBy instanceof User) {
$caller['contatto'] = trim((string) ($openedBy->name ?? '')) ?: $caller['contatto'];
@ -1104,10 +1158,10 @@ protected function resolveCallerData(?Ticket $ticket): array
return $caller;
}
protected function buildCallerReference(?Ticket $ticket): string
protected function buildCallerReference(?Ticket $ticket, string $parsedReference = ''): string
{
if (! $ticket instanceof Ticket) {
return '';
return $parsedReference;
}
$parts = [];
@ -1135,6 +1189,85 @@ protected function buildCallerReference(?Ticket $ticket): string
$parts[] = 'Luogo: ' . $luogoIntervento;
}
if ($ticket->unitaImmobiliare) {
$parts[] = 'Unità: ' . $this->formatUnitaLabel($ticket->unitaImmobiliare);
}
if ($parsedReference !== '') {
$parts[] = $parsedReference;
}
return implode(' · ', array_unique($parts));
}
private function resolveCallerRubrica(?Ticket $ticket, array $caller): ?RubricaUniversale
{
$adminId = (int) ($ticket?->stabile?->amministratore_id ?? 0);
$queries = [];
$soggetto = $ticket?->soggettoRichiedente;
if ($soggetto && filled($soggetto->codice_fiscale)) {
$queries[] = fn() => RubricaUniversale::query()->where('codice_fiscale', strtoupper(trim((string) $soggetto->codice_fiscale)));
}
$email = mb_strtolower(trim((string) ($caller['email'] ?? '')));
if ($email !== '') {
$queries[] = fn() => RubricaUniversale::query()->whereRaw('LOWER(email) = ?', [$email]);
}
$phone = $this->normalizePhoneDigits((string) ($caller['telefono'] ?? ''));
if ($phone !== '') {
$queries[] = fn() => RubricaUniversale::query()->where(function ($builder) use ($phone): void {
$builder->whereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_cellulare, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone])
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_ufficio, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone])
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono_casa, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone]);
});
}
$name = trim((string) ($caller['contatto'] ?? ''));
if ($name !== '' && $name !== '-') {
$queries[] = fn() => RubricaUniversale::query()->where(function ($builder) use ($name): void {
$builder->whereRaw("LOWER(COALESCE(ragione_sociale, '')) = ?", [mb_strtolower($name)])
->orWhereRaw("LOWER(TRIM(CONCAT(COALESCE(nome, ''), ' ', COALESCE(cognome, '')))) = ?", [mb_strtolower($name)]);
});
}
foreach ($queries as $factory) {
$query = $factory();
if ($adminId > 0) {
$query->where(function ($builder) use ($adminId): void {
$builder->where('amministratore_id', $adminId)
->orWhereNull('amministratore_id');
});
}
$match = $query->orderByDesc('updated_at')->orderBy('id')->first();
if ($match instanceof RubricaUniversale) {
return $match;
}
}
return null;
}
private function formatUnitaLabel(mixed $unita): string
{
if (! $unita) {
return '';
}
$parts = array_filter([
(string) ($unita->denominazione ?? ''),
! empty($unita->scala) ? 'Scala ' . $unita->scala : null,
! empty($unita->interno) ? 'Int. ' . $unita->interno : null,
! empty($unita->piano) ? 'Piano ' . $unita->piano : null,
]);
return implode(' · ', $parts) ?: ('Unità #' . (int) ($unita->id ?? 0));
}
private function normalizePhoneDigits(string $value): string
{
return preg_replace('/\D+/', '', $value) ?: '';
}
}

View File

@ -220,7 +220,7 @@ public function closeInterventoModal(): void
protected function buildBaseQuery(Fornitore $fornitore, ?FornitoreDipendente $dipendente): Builder
{
$query = TicketIntervento::query()
->with(['ticket.stabile', 'eseguitoDaDipendente'])
->with(['ticket.stabile', 'ticket.unitaImmobiliare', 'ticket.soggettoRichiedente', 'eseguitoDaDipendente'])
->where('fornitore_id', (int) $fornitore->id)
->orderByDesc('created_at');

View File

@ -8,9 +8,11 @@
use App\Models\ChiamataPostIt;
use App\Models\PbxClickToCallRequest;
use App\Models\RubricaUniversale;
use App\Models\Soggetto;
use App\Models\Stabile;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use App\Models\UnitaImmobiliare;
use App\Models\User;
use App\Services\Cti\LiveIncomingCallService;
use App\Services\Support\TicketAttachmentUploadService;
@ -564,20 +566,23 @@ public function creaTicketRapido(): void
return;
}
$stabileId = StabileContext::resolveActiveStabileId($user);
if (! $stabileId) {
Notification::make()
->title('Seleziona prima uno stabile attivo')
->danger()
->send();
return;
}
$caller = null;
if ($this->selectedCallerId) {
$caller = RubricaUniversale::query()->find($this->selectedCallerId);
}
$activeStabileId = StabileContext::resolveActiveStabileId($user);
$callerContext = $this->resolveCallerTicketContext($caller, $activeStabileId);
$stabileId = $callerContext['stabile_id'] ?? $activeStabileId;
if (! $stabileId) {
Notification::make()
->title('Seleziona prima uno stabile attivo oppure un contatto legato a uno stabile')
->danger()
->send();
return;
}
$descrizione = trim((string) $this->newTicketDescrizione);
if ($caller) {
$telefono = $caller->telefono_cellulare ?: ($caller->telefono_ufficio ?: $caller->telefono_casa);
@ -585,6 +590,15 @@ public function creaTicketRapido(): void
if ($telefono) {
$descrizione .= "\nTelefono richiamabile: {$telefono}";
}
if (filled($caller->email)) {
$descrizione .= "\nEmail richiedente: " . trim((string) $caller->email);
}
if (($callerContext['stabile_label'] ?? '') !== '') {
$descrizione .= "\nRiferimento stabile: " . $callerContext['stabile_label'];
}
if (($callerContext['unita_label'] ?? '') !== '') {
$descrizione .= "\nRiferimento unità: " . $callerContext['unita_label'];
}
}
if (filled($this->newTicketFotoNote)) {
@ -598,6 +612,8 @@ public function creaTicketRapido(): void
$ticket = Ticket::query()->create([
'stabile_id' => $stabileId,
'unita_immobiliare_id'=> $callerContext['unita_id'] ?? null,
'soggetto_richiedente_id' => $callerContext['soggetto_id'] ?? null,
'aperto_da_user_id' => $user->id,
'titolo' => (string) $this->newTicketTitolo,
'descrizione' => $descrizione,
@ -1162,7 +1178,11 @@ public function excerpt(?string $text, int $limit = 160): string
public function getCallerStabileLabel(int $rubricaId): ?string
{
$stabile = Stabile::query()->where('rubrica_id', $rubricaId)->first();
$caller = RubricaUniversale::query()->find($rubricaId);
$stabile = $caller instanceof RubricaUniversale
? $this->resolveCallerStabile($caller)
: null;
if (! $stabile) {
return null;
}
@ -1170,6 +1190,185 @@ public function getCallerStabileLabel(int $rubricaId): ?string
return $stabile->denominazione ?: ('Stabile #' . $stabile->id);
}
/**
* @return array{stabile_id:?int,stabile_label:string,soggetto_id:?int,unita_id:?int,unita_label:string}
*/
private function resolveCallerTicketContext(?RubricaUniversale $caller, ?int $fallbackStabileId = null): array
{
$stabile = $caller instanceof RubricaUniversale
? $this->resolveCallerStabile($caller, $fallbackStabileId)
: ($fallbackStabileId ? Stabile::query()->find($fallbackStabileId) : null);
$soggetto = $caller instanceof RubricaUniversale
? $this->resolveCallerSoggetto($caller, $stabile?->id)
: null;
$unita = $soggetto instanceof Soggetto
? $this->resolveCallerUnita($soggetto, $stabile?->id, (string) ($caller?->riferimento_unita ?? ''))
: null;
if (! $stabile && $unita instanceof UnitaImmobiliare) {
$stabile = $unita->stabile;
}
return [
'stabile_id' => $stabile?->id,
'stabile_label' => $stabile ? (string) ($stabile->denominazione ?: ('Stabile #' . $stabile->id)) : '',
'soggetto_id' => $soggetto?->id,
'unita_id' => $unita?->id,
'unita_label' => $unita ? $this->formatUnitaLabel($unita) : '',
];
}
private function resolveCallerStabile(RubricaUniversale $caller, ?int $fallbackStabileId = null): ?Stabile
{
$stabile = Stabile::query()->where('rubrica_id', (int) $caller->id)->first();
if ($stabile instanceof Stabile) {
return $stabile;
}
$reference = trim((string) ($caller->riferimento_stabile ?? ''));
if ($reference !== '') {
$query = Stabile::query();
if ((int) ($caller->amministratore_id ?? 0) > 0) {
$query->where('amministratore_id', (int) $caller->amministratore_id);
}
$exact = (clone $query)
->where(function ($builder) use ($reference): void {
$builder->where('denominazione', $reference)
->orWhere('codice_stabile', $reference);
})
->first();
if ($exact instanceof Stabile) {
return $exact;
}
$loose = (clone $query)
->where(function ($builder) use ($reference): void {
$builder->where('denominazione', 'like', '%' . $reference . '%')
->orWhere('indirizzo', 'like', '%' . $reference . '%');
})
->orderBy('id')
->first();
if ($loose instanceof Stabile) {
return $loose;
}
}
return $fallbackStabileId ? Stabile::query()->find($fallbackStabileId) : null;
}
private function resolveCallerSoggetto(RubricaUniversale $caller, ?int $stabileId = null): ?Soggetto
{
$queries = [];
$fiscalCode = strtoupper(trim((string) ($caller->codice_fiscale ?? '')));
if ($fiscalCode !== '') {
$queries[] = fn() => Soggetto::query()->where('codice_fiscale', $fiscalCode);
}
$email = mb_strtolower(trim((string) ($caller->email ?? '')));
if ($email !== '') {
$queries[] = fn() => Soggetto::query()->whereRaw('LOWER(email) = ?', [$email]);
}
$phone = $this->normalizePhoneDigits((string) ($caller->telefono_cellulare ?: $caller->telefono_ufficio ?: $caller->telefono_casa ?: ''));
if ($phone !== '') {
$queries[] = fn() => Soggetto::query()->whereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') = ?", [$phone]);
}
$name = trim((string) ($caller->ragione_sociale ?: $caller->nome_completo ?: ''));
if ($name !== '') {
$queries[] = fn() => Soggetto::query()->where(function ($builder) use ($caller, $name): void {
$builder->whereRaw("LOWER(COALESCE(ragione_sociale, '')) = ?", [mb_strtolower($name)]);
if (filled($caller->nome) || filled($caller->cognome)) {
$builder->orWhere(function ($inner) use ($caller): void {
if (filled($caller->nome)) {
$inner->whereRaw("LOWER(COALESCE(nome, '')) = ?", [mb_strtolower(trim((string) $caller->nome))]);
}
if (filled($caller->cognome)) {
$inner->whereRaw("LOWER(COALESCE(cognome, '')) = ?", [mb_strtolower(trim((string) $caller->cognome))]);
}
});
}
});
}
foreach ($queries as $factory) {
$query = $factory();
if ($stabileId) {
$match = (clone $query)
->whereHas('unitaImmobiliari', fn($builder) => $builder->where('stabile_id', (int) $stabileId))
->first();
if ($match instanceof Soggetto) {
return $match;
}
}
$match = $query->first();
if ($match instanceof Soggetto) {
return $match;
}
}
return null;
}
private function resolveCallerUnita(Soggetto $soggetto, ?int $stabileId = null, string $reference = ''): ?UnitaImmobiliare
{
$query = $soggetto->unitaImmobiliari();
if ($stabileId) {
$query->where('unita_immobiliari.stabile_id', (int) $stabileId);
}
$unita = $query->with('stabile')->get();
if ($unita->count() <= 1) {
return $unita->first();
}
$normalizedReference = $this->normalizeReferenceText($reference);
if ($normalizedReference === '') {
return $unita->first();
}
return $unita->first(function (UnitaImmobiliare $item) use ($normalizedReference): bool {
foreach ([$item->codice_unita, $item->denominazione, $item->scala, $item->interno, $item->numero_interno, $item->descrizione] as $candidate) {
if ($this->normalizeReferenceText((string) $candidate) === '') {
continue;
}
if (str_contains($this->normalizeReferenceText((string) $candidate), $normalizedReference)
|| str_contains($normalizedReference, $this->normalizeReferenceText((string) $candidate))) {
return true;
}
}
return false;
}) ?? $unita->first();
}
private function formatUnitaLabel(UnitaImmobiliare $unita): string
{
$parts = array_filter([
$unita->denominazione ?: null,
$unita->scala ? 'Scala ' . $unita->scala : null,
$unita->interno ? 'Int. ' . $unita->interno : null,
$unita->piano ? 'Piano ' . $unita->piano : null,
]);
return implode(' · ', $parts) ?: ('Unità #' . $unita->id);
}
private function normalizeReferenceText(string $value): string
{
return preg_replace('/[^A-Z0-9]+/', '', strtoupper(trim($value))) ?: '';
}
private function aggiornaStatoTicket(int $ticketId, string $nuovoStato, bool $assegnaAUtente = false): void
{
$user = Auth::user();

View File

@ -84,7 +84,13 @@
<div class="mt-3 grid gap-3 md:grid-cols-2">
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Richiedente</div>
<div class="mt-1 text-sm">{{ $caller['contatto'] ?: '-' }}</div>
<div class="mt-1 text-sm">
@if(!empty($caller['rubrica_url']))
<a href="{{ $caller['rubrica_url'] }}" class="text-primary-600 hover:underline">{{ $caller['contatto'] ?: '-' }}</a>
@else
{{ $caller['contatto'] ?: '-' }}
@endif
</div>
</div>
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Telefono</div>
@ -120,7 +126,31 @@
</div>
<div class="md:col-span-2">
<div class="text-xs uppercase tracking-wide text-gray-500">Riferimento stabile</div>
<div class="mt-1 text-sm">{{ $caller['riferimento'] !== '' ? $caller['riferimento'] : '-' }}</div>
<div class="mt-1 text-sm">
@if(!empty($caller['stabile_url']) && $caller['riferimento'] !== '')
<a href="{{ $caller['stabile_url'] }}" class="text-primary-600 hover:underline">{{ $caller['riferimento'] }}</a>
@else
{{ $caller['riferimento'] !== '' ? $caller['riferimento'] : '-' }}
@endif
</div>
@if($caller['unita_label'] !== '')
<div class="mt-1 text-xs text-gray-500">Unità collegata: {{ $caller['unita_label'] }}</div>
@endif
</div>
@if(!empty($caller['rubrica_url']) || !empty($caller['estratto_url']) || !empty($caller['stabile_url']))
<div class="md:col-span-2">
<div class="mt-2 flex flex-wrap gap-2">
@if(!empty($caller['rubrica_url']))
<a href="{{ $caller['rubrica_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri scheda contatto</a>
@endif
@if(!empty($caller['estratto_url']))
<a href="{{ $caller['estratto_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri estratto conto</a>
@endif
@if(!empty($caller['stabile_url']))
<a href="{{ $caller['stabile_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri stabile</a>
@endif
</div>
</div>
</div>
</div>
<div class="mt-4 whitespace-pre-wrap rounded-lg bg-gray-50 p-3 text-sm text-gray-700">{{ $ticket->descrizione }}</div>

View File

@ -411,33 +411,17 @@
@if(count($this->selectedUploads) > 0)
<div class="md:col-span-2">
<div class="mb-2 text-xs font-semibold text-gray-700">Coda allegati pronta per il ticket</div>
<div class="mb-2 text-[11px] text-gray-500">Le immagini locali restano visibili fino all'invio del ticket. La rinomina definitiva viene applicata solo al momento del salvataggio.</div>
<div class="mb-2 text-[11px] text-gray-500">Qui sotto restano solo i dati operativi del file. L'anteprima immagine resta nel riquadro sopra fino all'invio del ticket.</div>
<div class="grid gap-3 sm:grid-cols-2">
@foreach($this->selectedUploads as $upload)
<div class="rounded-xl border bg-white p-3 text-xs shadow-sm">
<div class="aspect-[4/3] rounded-lg border bg-gray-50 p-2">
@if($upload['is_image'] && !empty($upload['preview_url']))
<img src="{{ $upload['preview_url'] }}" alt="{{ $upload['name'] }}" class="h-full w-full rounded object-cover" />
@elseif($upload['is_image'])
<template x-if="previewFor(@js($upload['name']), @js($upload['original_name']))">
<img :src="previewFor(@js($upload['name']), @js($upload['original_name']))" alt="{{ $upload['name'] }}" class="h-full w-full rounded object-cover" />
</template>
<template x-if="!previewFor(@js($upload['name']), @js($upload['original_name']))">
<div class="flex h-full items-center justify-center text-center text-gray-500">
<div>
<div class="text-sm font-semibold">IMG</div>
<div class="mt-1 text-[11px]">Anteprima locale in attesa</div>
</div>
</div>
</template>
@else
<div class="flex h-full items-center justify-center text-center text-gray-500">
<div>
<div class="text-sm font-semibold">{{ strtoupper(pathinfo($upload['name'], PATHINFO_EXTENSION) ?: 'FILE') }}</div>
<div class="mt-1 text-[11px]">{{ $upload['name'] }}</div>
</div>
</div>
@endif
<div class="flex flex-wrap items-center gap-2 rounded-lg border bg-gray-50 px-3 py-2">
<span class="inline-flex items-center rounded-full px-2 py-1 text-[10px] font-semibold {{ $upload['is_image'] ? 'bg-sky-100 text-sky-700' : 'bg-slate-200 text-slate-700' }}">
{{ $upload['is_image'] ? 'Immagine' : (strtoupper(pathinfo($upload['name'], PATHINFO_EXTENSION) ?: 'FILE')) }}
</span>
<div class="text-[11px] text-gray-600">
{{ $upload['is_image'] ? 'Anteprima visibile sopra' : 'Allegato pronto per l\'invio' }}
</div>
</div>
<div class="mt-2 font-medium">{{ $upload['name'] }}</div>