Reorganize supplier scheda and harden legacy support flows
This commit is contained in:
parent
ab074eb321
commit
7c74942d0f
|
|
@ -5,6 +5,7 @@ # Changelog
|
|||
## [Unreleased]
|
||||
|
||||
- Initial open-source release prep.
|
||||
- Reorganized the supplier scheda into operational tabs, linked supplier rubrica-clienti back to anagrafica unica, fixed the supplier ticket-intervento Blade parse error, and hardened legacy supplier matching plus SMDR/Post-it labeling for Git-first rollout.
|
||||
- Reworked Supporto > Aggiornamento Nodo to show local/remote commit dates, explicit pending commit counts, and a clear note that Git updates do not move storage archives or private uploaded data.
|
||||
- Reorganized the Importazione Archivi operational tab so the action buttons come first, followed by the explanatory blocks and command fields.
|
||||
- Removed active-stabile filtering from topbar avvisi/urgenze counters and from the Google ticket-operativi box so cross-stabile operational tickets stay visible.
|
||||
|
|
|
|||
|
|
@ -250,20 +250,6 @@ private function resolveLocalFornitore(object $legacy): ?Fornitore
|
|||
$cf = trim((string) ($legacy->codice_fiscale ?? ''));
|
||||
$ragione = trim((string) ($legacy->ragione_sociale ?? ''));
|
||||
|
||||
if ($legacyId > 0) {
|
||||
$found = Fornitore::query()->where('old_id', $legacyId)->first();
|
||||
if ($found instanceof Fornitore) {
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
|
||||
if ($legacyCod !== '' && ctype_digit($legacyCod)) {
|
||||
$found = Fornitore::query()->where('old_id', (int) $legacyCod)->first();
|
||||
if ($found instanceof Fornitore) {
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
|
||||
if ($piva !== '') {
|
||||
$found = Fornitore::query()->where('partita_iva', $piva)->first();
|
||||
if ($found instanceof Fornitore) {
|
||||
|
|
@ -284,11 +270,54 @@ private function resolveLocalFornitore(object $legacy): ?Fornitore
|
|||
|
||||
$normalizedRagione = $this->normalizeComparableString($ragione);
|
||||
if ($normalizedRagione !== '') {
|
||||
return Fornitore::query()
|
||||
$found = Fornitore::query()
|
||||
->get(['id', 'ragione_sociale'])
|
||||
->first(function (Fornitore $fornitore) use ($normalizedRagione): bool {
|
||||
return $this->normalizeComparableString((string) ($fornitore->ragione_sociale ?? '')) === $normalizedRagione;
|
||||
});
|
||||
if ($found instanceof Fornitore) {
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->resolveLocalFornitoreByLegacyIds($legacyId, $legacyCod, $piva, $cf, $normalizedRagione);
|
||||
}
|
||||
|
||||
private function resolveLocalFornitoreByLegacyIds(int $legacyId, string $legacyCod, string $piva, string $cf, string $normalizedRagione): ?Fornitore
|
||||
{
|
||||
$candidateIds = collect([
|
||||
$legacyId > 0 ? $legacyId : null,
|
||||
($legacyCod !== '' && ctype_digit($legacyCod)) ? (int) $legacyCod : null,
|
||||
])
|
||||
->filter(fn($value): bool => is_int($value) && $value > 0)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
foreach ($candidateIds as $candidateId) {
|
||||
$matches = Fornitore::query()
|
||||
->where('old_id', (int) $candidateId)
|
||||
->get(['id', 'partita_iva', 'codice_fiscale', 'ragione_sociale']);
|
||||
|
||||
if ($matches->count() === 1) {
|
||||
return $matches->first();
|
||||
}
|
||||
|
||||
$filtered = $matches->filter(function (Fornitore $fornitore) use ($piva, $cf, $normalizedRagione): bool {
|
||||
if ($piva !== '' && trim((string) ($fornitore->partita_iva ?? '')) === $piva) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($cf !== '' && trim((string) ($fornitore->codice_fiscale ?? '')) === $cf) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $normalizedRagione !== ''
|
||||
&& $this->normalizeComparableString((string) ($fornitore->ragione_sociale ?? '')) === $normalizedRagione;
|
||||
})->values();
|
||||
|
||||
if ($filtered->count() === 1) {
|
||||
return $filtered->first();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use App\Models\ChiamataPostIt;
|
||||
use App\Models\CommunicationMessage;
|
||||
use App\Services\Anagrafiche\RubricaPhoneLookupService;
|
||||
use App\Services\Cti\PbxRoutingService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
|
@ -243,15 +244,17 @@ public function handle(): int
|
|||
|
||||
if ($writePostIt && Schema::hasTable('chiamate_post_it') && $this->shouldCreateOperationalPostIt($parsed)) {
|
||||
try {
|
||||
$lineSuffix = ! empty($parsed['co']) ? ' - linea ' . (string) $parsed['co'] : '';
|
||||
$lineLabel = $this->resolveDisplayLineLabel($parsed, $routing);
|
||||
$lineSuffix = $lineLabel !== '' ? ' - linea ' . $lineLabel : '';
|
||||
|
||||
$isMissedResponseGroup = $this->isMissedResponseGroupCall($parsed, $routing);
|
||||
$caller = $this->resolveCallerDisplay($phone, $amministratoreId);
|
||||
|
||||
ChiamataPostIt::query()->create([
|
||||
'telefono' => $phone,
|
||||
'stabile_id' => $stabileId,
|
||||
'stabile_id' => $caller['has_rubrica'] ? $stabileId : null,
|
||||
'creato_da_user_id' => $assignedUserId,
|
||||
'nome_chiamante' => 'SMDR INBOUND',
|
||||
'nome_chiamante' => $caller['label'],
|
||||
'oggetto' => 'Chiamata in entrata da SMDR' . $lineSuffix,
|
||||
'nota' => $line,
|
||||
'direzione' => 'in_arrivo',
|
||||
|
|
@ -292,6 +295,42 @@ public function handle(): int
|
|||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{label:string,has_rubrica:bool}
|
||||
*/
|
||||
private function resolveCallerDisplay(?string $phone, ?int $amministratoreId): array
|
||||
{
|
||||
$digits = preg_replace('/\D+/', '', (string) $phone) ?: '';
|
||||
if ($digits === '') {
|
||||
return ['label' => 'Contatto non in Rubrica', 'has_rubrica' => false];
|
||||
}
|
||||
|
||||
$rubrica = app(RubricaPhoneLookupService::class)->findByPhone($digits, (int) ($amministratoreId ?? 0));
|
||||
if ($rubrica) {
|
||||
$label = trim((string) ($rubrica->ragione_sociale ?: (($rubrica->nome ?? '') . ' ' . ($rubrica->cognome ?? ''))));
|
||||
if ($label !== '') {
|
||||
return ['label' => $label, 'has_rubrica' => true];
|
||||
}
|
||||
}
|
||||
|
||||
return ['label' => 'Contatto non in Rubrica', 'has_rubrica' => false];
|
||||
}
|
||||
|
||||
private function resolveDisplayLineLabel(array $parsed, array $routing): string
|
||||
{
|
||||
$lineLabel = trim((string) ($routing['line_label'] ?? ''));
|
||||
if ($lineLabel !== '') {
|
||||
return $lineLabel;
|
||||
}
|
||||
|
||||
$rawLine = preg_replace('/\D+/', '', (string) ($parsed['co'] ?? '')) ?: '';
|
||||
if ($rawLine === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::CO_PUBLIC_LINES[$rawLine] ?? (string) ($parsed['co'] ?? '');
|
||||
}
|
||||
|
||||
private function parseSmdrLine(string $line): ?array
|
||||
{
|
||||
$normalized = trim((string) preg_replace('/^\[\d+\]\s*/', '', $line));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
namespace App\Filament\Pages\Fornitore;
|
||||
|
||||
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
|
||||
use App\Filament\Pages\Gescon\RubricaUniversaleScheda;
|
||||
use App\Models\Fornitore;
|
||||
use App\Models\TicketIntervento;
|
||||
use App\Models\User;
|
||||
|
|
@ -118,6 +119,8 @@ public function refreshRows(): void
|
|||
'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') ?: '-',
|
||||
|
|
@ -164,6 +167,8 @@ protected function resolveCallerDataForRubrica(TicketIntervento $intervento): ar
|
|||
$telefono = $parsed['telefono'];
|
||||
$email = '';
|
||||
$origine = 'Descrizione ticket';
|
||||
$rubricaUrl = null;
|
||||
$rubricaLabel = '';
|
||||
|
||||
if ($ticket?->soggettoRichiedente) {
|
||||
$soggetto = $ticket->soggettoRichiedente;
|
||||
|
|
@ -171,6 +176,8 @@ protected function resolveCallerDataForRubrica(TicketIntervento $intervento): ar
|
|||
$telefono = trim((string) ($soggetto->telefono ?? '')) ?: $telefono;
|
||||
$email = trim((string) ($soggetto->email ?? ''));
|
||||
$origine = 'Richiedente ticket';
|
||||
$rubricaUrl = RubricaUniversaleScheda::getUrl(['record' => (int) $soggetto->id], panel: 'admin-filament');
|
||||
$rubricaLabel = trim((string) ($soggetto->categoria ?? ''));
|
||||
} elseif ($ticket?->apertoDaUser) {
|
||||
$contatto = trim((string) ($ticket->apertoDaUser->name ?? '')) ?: $contatto;
|
||||
$email = trim((string) ($ticket->apertoDaUser->email ?? ''));
|
||||
|
|
@ -192,6 +199,8 @@ protected function resolveCallerDataForRubrica(TicketIntervento $intervento): ar
|
|||
'email' => $email,
|
||||
'origine' => $origine,
|
||||
'stabile' => (string) ($ticket?->stabile?->denominazione ?? '-'),
|
||||
'rubrica_url' => $rubricaUrl,
|
||||
'rubrica_label'=> $rubricaLabel,
|
||||
'identity_key' => $identityKey,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ class FornitoreScheda extends Page
|
|||
|
||||
public ?int $dipendenteRubricaId = null;
|
||||
|
||||
public string $sectionTab = 'profilo';
|
||||
|
||||
/** @var array<int, array<string, mixed>> */
|
||||
public array $dipendenteRubricaMatches = [];
|
||||
|
||||
|
|
@ -194,6 +196,23 @@ public function mount(int | string $record): void
|
|||
$this->loadTagSuggestions();
|
||||
$this->refreshDipendentiRows();
|
||||
$this->refreshCatalogRows();
|
||||
|
||||
$requestedTab = (string) request()->query('tab', 'profilo');
|
||||
if ($this->isValidSectionTab($requestedTab)) {
|
||||
$this->sectionTab = $requestedTab;
|
||||
}
|
||||
}
|
||||
|
||||
public function setSectionTab(string $tab): void
|
||||
{
|
||||
if ($this->isValidSectionTab($tab)) {
|
||||
$this->sectionTab = $tab;
|
||||
}
|
||||
}
|
||||
|
||||
private function isValidSectionTab(string $tab): bool
|
||||
{
|
||||
return in_array($tab, ['profilo', 'accessi', 'assistenza', 'catalogo', 'contabilita'], true);
|
||||
}
|
||||
|
||||
public function creaDipendenteFornitore(): void
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use App\Services\Anagrafiche\RubricaPhoneLookupService;
|
||||
use App\Services\Cti\PbxRoutingService;
|
||||
use BackedEnum;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
|
|
@ -20,6 +21,16 @@
|
|||
|
||||
class PostItGestione extends Page
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
private const SMDR_PUBLIC_LINE_FALLBACKS = [
|
||||
'1' => '0639731100',
|
||||
'01' => '0639731100',
|
||||
'0001' => '0639731100',
|
||||
'3' => '0688812703',
|
||||
'03' => '0688812703',
|
||||
'0003' => '0688812703',
|
||||
];
|
||||
|
||||
protected static ?string $title = 'Post-it Gestione';
|
||||
|
||||
protected static ?string $navigationLabel = 'Post-it Gestione';
|
||||
|
|
@ -508,6 +519,7 @@ public function creaPostItDaMessaggio(int $messageId): void
|
|||
$phone = (string) ($message->phone_number ?: ($smdr['dial_number'] ?? ''));
|
||||
$nominativo = $this->getTecnicoNominativo($message);
|
||||
$lineLabel = $this->getTecnicoLineaLabel($message);
|
||||
$callerLabel = $this->resolveTecnicoCallerLabel($message, $phone, $nominativo);
|
||||
$direction = match (strtolower(trim((string) $message->direction))) {
|
||||
'inbound' => 'Chiamata ricevuta',
|
||||
'outbound' => 'Chiamata effettuata',
|
||||
|
|
@ -534,10 +546,10 @@ public function creaPostItDaMessaggio(int $messageId): void
|
|||
}
|
||||
|
||||
$postIt = ChiamataPostIt::query()->create([
|
||||
'stabile_id' => $message->stabile_id,
|
||||
'stabile_id' => $this->resolveOperationalPostItStabileId($message, $phone),
|
||||
'creato_da_user_id' => Auth::id(),
|
||||
'telefono' => $message->phone_number,
|
||||
'nome_chiamante' => $nominativo ?: ($phone !== '' ? $phone : ($sourceLabel . ' ' . strtoupper((string) $message->direction))),
|
||||
'telefono' => $phone !== '' ? $phone : $message->phone_number,
|
||||
'nome_chiamante' => $callerLabel !== '' ? $callerLabel : ($sourceLabel . ' ' . strtoupper((string) $message->direction)),
|
||||
'direzione' => $this->normalizePostItDirection((string) $message->direction),
|
||||
'origine' => $sourceKey . '_table',
|
||||
'origine_id' => (string) $message->id,
|
||||
|
|
@ -634,8 +646,7 @@ public function getTecnicoProviderLabel(CommunicationMessage $message): string
|
|||
public function getTecnicoLineaLabel(CommunicationMessage $message): string
|
||||
{
|
||||
if ((string) $message->channel === 'smdr') {
|
||||
$line = trim((string) data_get($message->metadata, 'smdr.co', ''));
|
||||
return $line !== '' ? $line : '-';
|
||||
return $this->resolveSmdrLineLabel($message);
|
||||
}
|
||||
|
||||
$eventType = trim((string) data_get($message->metadata, 'event_type', ''));
|
||||
|
|
@ -645,6 +656,24 @@ public function getTecnicoLineaLabel(CommunicationMessage $message): string
|
|||
return $parts !== [] ? implode(' / ', $parts) : '-';
|
||||
}
|
||||
|
||||
public function shouldShowPostItStabile(ChiamataPostIt $postIt): bool
|
||||
{
|
||||
if (! $postIt->stabile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->resolveGroupedRubricaId($postIt)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = $this->resolveMessageFromPostIt($postIt);
|
||||
if ($message instanceof CommunicationMessage && ! $this->isInternalSmdrMessage($message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->isGenericCallerLabel((string) ($postIt->nome_chiamante ?? ''), (string) ($postIt->telefono ?? ''));
|
||||
}
|
||||
|
||||
private function matchesTecnicoLineFilter(CommunicationMessage $message, string $filter): bool
|
||||
{
|
||||
$needle = mb_strtolower(trim($filter));
|
||||
|
|
@ -817,12 +846,12 @@ private function resolveGroupedCallerLabel(array $items): string
|
|||
}
|
||||
|
||||
$label = trim((string) ($item->nome_chiamante ?? ''));
|
||||
if ($label !== '') {
|
||||
if ($label !== '' && ! $this->isGenericCallerLabel($label, (string) ($item->telefono ?? ''))) {
|
||||
return $label;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Chiamante non identificato';
|
||||
return 'Contatto non in Rubrica';
|
||||
}
|
||||
|
||||
private function resolveGroupedPhoneLabel(array $items): string
|
||||
|
|
@ -895,6 +924,79 @@ private function buildGroupedCollection($items)
|
|||
->values();
|
||||
}
|
||||
|
||||
private function resolveOperationalPostItStabileId(CommunicationMessage $message, string $phone): ?int
|
||||
{
|
||||
if ($this->isInternalSmdrMessage($message)) {
|
||||
return (int) ($message->stabile_id ?? 0) ?: null;
|
||||
}
|
||||
|
||||
if ($this->resolveRubricaIdByPhone($phone)) {
|
||||
return (int) ($message->stabile_id ?? 0) ?: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveTecnicoCallerLabel(CommunicationMessage $message, string $phone, ?string $nominativo): string
|
||||
{
|
||||
$label = trim((string) $nominativo);
|
||||
if ($label !== '') {
|
||||
return $label;
|
||||
}
|
||||
|
||||
if ($this->isInternalSmdrMessage($message)) {
|
||||
$extension = $this->extractExtensionFromMessage($message);
|
||||
|
||||
return $extension ? 'Interno ' . $extension : 'Interno PBX';
|
||||
}
|
||||
|
||||
return 'Contatto non in Rubrica';
|
||||
}
|
||||
|
||||
private function resolveSmdrLineLabel(CommunicationMessage $message): string
|
||||
{
|
||||
$stored = trim((string) data_get($message->metadata, 'smdr.line_label', ''));
|
||||
if ($stored !== '') {
|
||||
return $stored;
|
||||
}
|
||||
|
||||
$rawLine = trim((string) data_get($message->metadata, 'smdr.co', ''));
|
||||
if ($rawLine === '') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$routing = app(PbxRoutingService::class)->resolveByIncomingLine($rawLine);
|
||||
$lineLabel = trim((string) ($routing['line_label'] ?? ''));
|
||||
if ($lineLabel !== '') {
|
||||
return $lineLabel;
|
||||
}
|
||||
|
||||
$normalized = app(PbxRoutingService::class)->normalizeLineNumber($rawLine);
|
||||
|
||||
return self::SMDR_PUBLIC_LINE_FALLBACKS[$normalized] ?? $rawLine;
|
||||
}
|
||||
|
||||
private function isGenericCallerLabel(string $label, string $phone = ''): bool
|
||||
{
|
||||
$normalizedLabel = mb_strtolower(trim($label));
|
||||
if ($normalizedLabel === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($normalizedLabel, ['chiamante non identificato', 'contatto non in rubrica'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_starts_with($normalizedLabel, 'smdr ') || str_starts_with($normalizedLabel, 'panasonic cti ')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$labelDigits = preg_replace('/\D+/', '', $label) ?: '';
|
||||
$phoneDigits = preg_replace('/\D+/', '', $phone) ?: '';
|
||||
|
||||
return $labelDigits !== '' && $phoneDigits !== '' && $labelDigits === $phoneDigits;
|
||||
}
|
||||
|
||||
public function richiediClickToCallDaMessaggio(int $messageId): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
|
@ -1044,8 +1146,8 @@ private function resolveRubricaIdByPhone(?string $phone): ?int
|
|||
return $this->rubricaPhoneCache[$digits];
|
||||
}
|
||||
|
||||
$adminId = (int) (Auth::user()?->amministratore?->id ?? 0);
|
||||
$result = app(RubricaPhoneLookupService::class)->findByPhone($digits, $adminId)?->id;
|
||||
$adminId = (int) (Auth::user()?->amministratore?->id ?? 0);
|
||||
$result = app(RubricaPhoneLookupService::class)->findByPhone($digits, $adminId)?->id;
|
||||
$result = is_numeric($result) ? (int) $result : null;
|
||||
$this->rubricaPhoneCache[$digits] = $result;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="text-lg font-semibold">Rubrica clienti fornitore</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
@if($this->fornitoreLabel)
|
||||
Contatti emersi dai ticket e dalle lavorazioni di {{ $this->fornitoreLabel }}.
|
||||
Contatti emersi dai ticket e dalle lavorazioni di {{ $this->fornitoreLabel }}, agganciati quando possibile all'anagrafica unica.
|
||||
@else
|
||||
Seleziona un fornitore per aprire la rubrica clienti.
|
||||
@endif
|
||||
|
|
@ -19,6 +19,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700">
|
||||
Questa vista resta stretta sui contatti del singolo fornitore, ma il punto di verità anagrafico rimane la scheda dell'anagrafica unica. Da qui puoi filtrare i contatti operativi e saltare direttamente sulla scheda condivisa quando il legame esiste gia.
|
||||
</div>
|
||||
|
||||
@if($this->missingAdminContext)
|
||||
<div class="rounded-xl border border-amber-300 bg-amber-50 p-4 text-sm text-amber-800">
|
||||
Questa vista richiede un fornitore selezionato.
|
||||
|
|
@ -38,10 +42,16 @@
|
|||
<div>
|
||||
<div class="font-semibold">{{ $row['contatto'] }}</div>
|
||||
<div class="mt-1 text-xs text-gray-500">Origine: {{ $row['origine'] }} · Interventi: {{ $row['totale_interventi'] }}</div>
|
||||
@if($row['rubrica_url'])
|
||||
<div class="mt-1 text-xs text-indigo-700">Anagrafica unica collegata{{ $row['rubrica_label'] !== '' ? ' · ' . $row['rubrica_label'] : '' }}</div>
|
||||
@endif
|
||||
<div class="mt-2 text-xs text-gray-500">Stabile: {{ $row['stabile'] }}</div>
|
||||
<div class="mt-1 text-xs text-gray-500">Ultimo aggiornamento: {{ $row['updated_at'] }}</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if($row['rubrica_url'])
|
||||
<a href="{{ $row['rubrica_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-300 hover:bg-indigo-50">Scheda anagrafica</a>
|
||||
@endif
|
||||
@if($row['telefono'] !== '')
|
||||
<a href="tel:{{ preg_replace('/\s+/', '', (string) $row['telefono']) }}" class="inline-flex items-center rounded-md bg-emerald-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-emerald-500">Chiama</a>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@
|
|||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 whitespace-pre-wrap rounded-lg bg-gray-50 p-3 text-sm text-gray-700">{{ $ticket->descrizione }}</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,57 @@
|
|||
<x-filament-panels::page>
|
||||
@php
|
||||
$tabs = [
|
||||
'profilo' => 'Dati condivisi',
|
||||
'accessi' => 'Dipendenti e accessi',
|
||||
'assistenza' => 'Assistenza interna',
|
||||
'catalogo' => 'Catalogo e prodotti',
|
||||
'contabilita' => 'Contabilita',
|
||||
];
|
||||
@endphp
|
||||
|
||||
<div class="space-y-6" x-data="{ tab: @js($this->sectionTab) }">
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<div class="text-lg font-semibold">Scheda fornitore</div>
|
||||
<div class="mt-1 text-sm text-slate-600">La scheda e divisa in tab operative per separare dati condivisi, accessi, assistenza, catalogo e contabilita senza perdere il contesto del fornitore.</div>
|
||||
</div>
|
||||
<div class="rounded-lg bg-slate-900 px-3 py-2 text-right text-white">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] text-slate-300">Fornitore</div>
|
||||
<div class="text-sm font-semibold">{{ $fornitore->ragione_sociale ?? trim(($fornitore->nome ?? '') . ' ' . ($fornitore->cognome ?? '')) ?: ('Fornitore #' . $fornitore->id) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
@foreach($tabs as $tabKey => $tabLabel)
|
||||
<button
|
||||
type="button"
|
||||
wire:click="setSectionTab('{{ $tabKey }}')"
|
||||
x-on:click="tab = '{{ $tabKey }}'"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium"
|
||||
x-bind:class="tab === '{{ $tabKey }}' ? 'bg-slate-900 text-white shadow-sm' : 'bg-slate-100 text-slate-700 hover:bg-slate-200'"
|
||||
>
|
||||
{{ $tabLabel }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700" x-show="tab === 'catalogo'" x-cloak>
|
||||
Qui restano visibili modulo fornitore e catalogo operativo. Il magazzino e la gestione completa del prodotto possono essere spostati in menu dedicati mantenendo qui il quadro sintetico e i collegamenti rapidi.
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-900" x-show="tab === 'assistenza'" x-cloak>
|
||||
Questa tab concentra TecnoRepair, seriali e automazioni collegate al fornitore. I ticket operativi e il protocollo comunicazioni possono poi evolvere come viste dedicate, senza ricadere in una scheda unica confusa.
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900" x-show="tab === 'contabilita'" x-cloak>
|
||||
Qui si prepara il perimetro economico del fornitore: pagamenti, fatture, anteprime contabili e ritenute. Preventivi, ordini e fatturazione completa possono innestarsi in questa area senza mischiarsi con catalogo o assistenza.
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<div class="space-y-6 lg:col-span-2">
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'profilo'" x-cloak>
|
||||
<div class="text-lg font-semibold">Fornitore</div>
|
||||
<div class="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<div><span class="font-medium">Codice:</span> {{ $fornitore->codice_univoco ?? '-' }}</div>
|
||||
|
|
@ -15,7 +65,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'profilo'" x-cloak>
|
||||
<div class="text-lg font-semibold">Contatti</div>
|
||||
|
||||
<div class="mt-3 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
|
|
@ -43,7 +93,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'accessi'" x-cloak>
|
||||
<div class="text-lg font-semibold">Dipendenti e accessi fornitore</div>
|
||||
<div class="mt-1 text-xs text-gray-500">La gestione accessi dei dipendenti fornitore e centralizzata qui.</div>
|
||||
|
||||
|
|
@ -168,7 +218,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'catalogo'" x-cloak>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div class="text-lg font-semibold">Modulo fornitore separato</div>
|
||||
|
|
@ -220,7 +270,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'catalogo'" x-cloak>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div class="text-lg font-semibold">Catalogo operativo interno</div>
|
||||
|
|
@ -352,7 +402,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'profilo' || tab === 'contabilita'" x-cloak>
|
||||
<div class="text-lg font-semibold">Pagamenti</div>
|
||||
<div class="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<div><span class="font-medium">IBAN:</span> {{ $fornitore->iban ?? '-' }}</div>
|
||||
|
|
@ -361,7 +411,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'profilo'" x-cloak>
|
||||
<div class="text-lg font-semibold">Anagrafica unica (Rubrica)</div>
|
||||
@if($fornitore->rubrica)
|
||||
<div class="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
|
|
@ -375,7 +425,7 @@
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4" x-data="fornitoreTagsBox(@js($tagSuggestions))">
|
||||
<div class="rounded-lg border p-4" x-data="fornitoreTagsBox(@js($tagSuggestions))" x-show="tab === 'profilo'" x-cloak>
|
||||
<div class="text-lg font-semibold">TAG Fornitore</div>
|
||||
<div class="mt-1 text-xs text-gray-500">Digita tag separati da virgola. Premi TAB per accettare il suggerimento corrente e aggiungere automaticamente una virgola.</div>
|
||||
|
||||
|
|
@ -415,7 +465,7 @@ class="w-full rounded-lg border-gray-300 text-sm"
|
|||
$acquaScan = (bool) ($acqua['scan_enabled'] ?? false);
|
||||
@endphp
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'assistenza'" x-cloak>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div class="text-base font-semibold">TecnoRepair / seriali</div>
|
||||
|
|
@ -468,7 +518,7 @@ class="w-full rounded-lg border-gray-300 text-sm"
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'assistenza'" x-cloak>
|
||||
<div class="text-base font-semibold">Automazioni / Consumi</div>
|
||||
<div class="mt-1 text-sm text-gray-600">Configura acquisizione dati (PDF) e scansione massiva sulle FE già importate.</div>
|
||||
|
||||
|
|
@ -500,7 +550,7 @@ class="w-full rounded-lg border-gray-300 text-sm"
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'contabilita'" x-cloak>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div class="text-lg font-semibold">Fatture (stabile attivo)</div>
|
||||
|
|
@ -562,7 +612,7 @@ class="text-sm font-medium text-primary-600 hover:underline"
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'contabilita'" x-cloak>
|
||||
<div class="text-base font-semibold">Anteprima AdE</div>
|
||||
@if(!empty($anteprimaAde))
|
||||
<div class="mt-3 space-y-2 text-sm">
|
||||
|
|
@ -581,7 +631,7 @@ class="text-sm font-medium text-primary-600 hover:underline"
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'contabilita'" x-cloak>
|
||||
<div class="text-base font-semibold">Anteprima FE</div>
|
||||
@if(!empty($anteprimaFe))
|
||||
<div class="mt-3 space-y-2 text-sm">
|
||||
|
|
@ -605,7 +655,7 @@ class="block rounded-md px-2 py-1 hover:bg-gray-50"
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'contabilita'" x-cloak>
|
||||
<div class="text-base font-semibold">Anteprima contabilità</div>
|
||||
@if(!empty($anteprimaContabilita))
|
||||
<div class="mt-3 space-y-2 text-sm">
|
||||
|
|
@ -629,7 +679,7 @@ class="block rounded-md px-2 py-1 hover:bg-gray-50"
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'contabilita'" x-cloak>
|
||||
<div class="text-base font-semibold">Fatture associate (fornitore)</div>
|
||||
@if(!empty($fattureAssociate))
|
||||
<div class="mt-3 space-y-2 text-sm">
|
||||
|
|
@ -656,7 +706,7 @@ class="block rounded-md px-2 py-1 hover:bg-gray-50"
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<div class="rounded-lg border p-4" x-show="tab === 'contabilita'" x-cloak>
|
||||
<div class="text-base font-semibold">RA versate/compensate</div>
|
||||
@if(!empty($raVersateRows))
|
||||
<div class="mt-3 space-y-2 text-sm">
|
||||
|
|
@ -678,6 +728,7 @@ class="block rounded-md px-2 py-1 hover:bg-gray-50"
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function fornitoreTagsBox(suggestions) {
|
||||
|
|
|
|||
|
|
@ -8,17 +8,17 @@
|
|||
<input type="text" wire:model.live.debounce.250ms="collaboratoreRubricaSearch" class="w-full rounded-lg border-gray-300" placeholder="Ricerca per telefono, nome, cognome, ragione sociale o email e seleziona il contatto con un click. Es. +39 333 1234567 oppure Mario Rossi" />
|
||||
</label>
|
||||
|
||||
@if($collaboratoreRubricaId)
|
||||
@if($this->collaboratoreRubricaId)
|
||||
<div class="mt-3 rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-xs text-emerald-800">
|
||||
Nominativo selezionato: <span class="font-semibold">{{ $collaboratoreRubricaSearch }}</span>
|
||||
Nominativo selezionato: <span class="font-semibold">{{ $this->collaboratoreRubricaSearch }}</span>
|
||||
<button type="button" wire:click="resetCollaboratoreRubricaSelection" class="ml-2 inline-flex items-center rounded-md bg-white px-2 py-1 text-[11px] font-medium text-emerald-700 ring-1 ring-inset ring-emerald-300 hover:bg-emerald-100">Cambia</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(count($collaboratoreRubricaMatches) > 0)
|
||||
@if(count($this->collaboratoreRubricaMatches) > 0)
|
||||
<div class="mt-3 space-y-2">
|
||||
@foreach($collaboratoreRubricaMatches as $match)
|
||||
<button type="button" wire:click="selezionaCollaboratoreRubrica({{ (int) $match['id'] }})" class="w-full rounded-lg border p-3 text-left {{ (int) $collaboratoreRubricaId === (int) $match['id'] ? 'border-emerald-400 bg-emerald-50 ring-1 ring-emerald-200' : 'bg-white hover:bg-gray-50' }}">
|
||||
@foreach($this->collaboratoreRubricaMatches as $match)
|
||||
<button type="button" wire:click="selezionaCollaboratoreRubrica({{ (int) $match['id'] }})" class="w-full rounded-lg border p-3 text-left {{ (int) $this->collaboratoreRubricaId === (int) $match['id'] ? 'border-emerald-400 bg-emerald-50 ring-1 ring-emerald-200' : 'bg-white hover:bg-gray-50' }}">
|
||||
<div class="text-sm font-medium">{{ $match['label'] }}</div>
|
||||
<div class="mt-1 text-xs text-gray-600">
|
||||
{{ $match['phone'] ?: 'Telefono non valorizzato' }}
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
@elseif(filled($collaboratoreRubricaSearch) && ! $collaboratoreRubricaId)
|
||||
@elseif(filled($this->collaboratoreRubricaSearch) && ! $this->collaboratoreRubricaId)
|
||||
<div class="mt-3 text-xs text-gray-500">Nessun nominativo trovato con questa ricerca.</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
- <a href="{{ $rubricaUrl }}" class="text-indigo-700 hover:underline">Apri scheda rubrica</a>
|
||||
@endif
|
||||
@endif
|
||||
@if($riga->stabile)
|
||||
@if($this->shouldShowPostItStabile($riga))
|
||||
- {{ $riga->stabile->denominazione ?: ('Stabile #' . $riga->stabile->id) }}
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
<span class="rounded-md px-2 py-1 text-[11px] font-semibold {{ $callState['class'] }}">{{ $callState['label'] }}</span>
|
||||
</div>
|
||||
|
||||
@if($item->stabile)
|
||||
@if($this->shouldShowPostItStabile($item))
|
||||
<div class="mt-1 text-[11px] text-amber-900">{{ $item->stabile->denominazione ?: ('Stabile #' . $item->stabile->id) }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -435,7 +435,7 @@
|
|||
<span class="rounded-md px-2 py-1 text-[11px] font-semibold {{ $callState['class'] }}">{{ $callState['label'] }}</span>
|
||||
</div>
|
||||
|
||||
@if($item->stabile)
|
||||
@if($this->shouldShowPostItStabile($item))
|
||||
<div class="mt-1 text-xs text-slate-700">{{ $item->stabile->denominazione ?: ('Stabile #' . $item->stabile->id) }}</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user