netgescon-day0/app/Filament/Pages/Gescon/FornitoriArchivio.php

1054 lines
40 KiB
PHP

<?php
namespace App\Filament\Pages\Gescon;
use App\Console\Commands\ImportLegacyFornitoriTagsCommand;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\RubricaUniversale;
use App\Models\User;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use UnitEnum;
class FornitoriArchivio extends Page
{
public int $fornitoriCount = 0;
public string $fornitoriSearch = '';
public string $activeTab = 'elenco';
public ?int $selectedFornitoreId = null;
/** @var Collection<int, Fornitore> */
public $fornitoreMatches;
/** @var array<string, string> */
public array $automazioni = [
'fornitore_acqua' => '0',
'abilita_ticket' => '1',
'abilita_allegati_camera' => '1',
];
public ?string $contoIban = null;
public ?string $contoIntestazioneEsatta = null;
public string $dipendentiRubricaSearch = '';
public string $searchInput = '';
public string $editRagioneSociale = '';
public string $editTitolo = '';
public string $editNome = '';
public string $editCognome = '';
public string $editEmail = '';
public string $editPec = '';
public string $editTelefono = '';
public string $editCellulare = '';
public string $editPartitaIva = '';
public string $editCodiceFiscale = '';
public string $editSitoWeb = '';
public string $editTags = '';
public string $editNote = '';
public string $newFornitoreRagioneSociale = '';
public string $newFornitoreNome = '';
public string $newFornitoreCognome = '';
public string $newFornitoreEmail = '';
public string $newFornitoreTelefono = '';
public string $newFornitorePartitaIva = '';
public string $newFornitoreCodiceFiscale = '';
public string $newDipendenteNome = '';
public string $newDipendenteCognome = '';
public string $newDipendenteEmail = '';
public string $newDipendenteTelefono = '';
/** @var array<int, array<string, mixed>> */
public array $dipendenteInline = [];
protected static ?string $navigationLabel = 'Fornitori';
protected static ?string $title = 'Fornitori';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-building-storefront';
protected static UnitEnum|string|null $navigationGroup = 'NetGescon';
protected static ?int $navigationSort = 12;
protected static ?string $slug = 'gescon/anagrafica/fornitori';
protected string $view = 'filament.pages.gescon.table';
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
}
public function mount(): void
{
try {
$this->fornitoriCount = (int) $this->getTableQuery()->count();
} catch (\Throwable) {
$this->fornitoriCount = 0;
}
$this->fornitoreMatches = new Collection();
$this->searchInput = $this->fornitoriSearch;
$this->searchFornitoreMatches();
}
protected function getTableQuery(): Builder
{
$user = Auth::user();
if (! $user instanceof User) {
return Fornitore::query()->whereRaw('1 = 0');
}
if ($user->hasAnyRole(['super-admin', 'admin'])) {
return Fornitore::query();
}
$amministratoreId = $user->amministratore?->id;
if (! $amministratoreId) {
$stabile = StabileContext::getActiveStabile($user);
$amministratoreId = $stabile?->amministratore_id;
}
if (! $amministratoreId) {
return Fornitore::query()->whereRaw('1 = 0');
}
return Fornitore::query()->where('amministratore_id', (int) $amministratoreId);
}
/**
* @return Collection<int, Fornitore>
*/
public function getFornitoriRowsProperty(): Collection
{
$query = $this->getTableQuery()
->withCount('dipendenti')
->orderBy('ragione_sociale')
->orderBy('cognome')
->orderBy('nome');
$this->applyFornitoreSearch($query, trim($this->fornitoriSearch));
return $query->limit(400)->get();
}
public function updatedFornitoriSearch(): void
{
$this->searchInput = $this->fornitoriSearch;
$this->searchFornitoreMatches();
}
public function updatedSearchInput(): void
{
$this->fornitoriSearch = trim($this->searchInput);
$this->searchFornitoreMatches();
}
public function getFornitoreLabel(Fornitore $fornitore): string
{
$label = trim((string) ($fornitore->ragione_sociale ?: trim(($fornitore->nome ?? '') . ' ' . ($fornitore->cognome ?? ''))));
return $label !== '' ? $label : ('Fornitore #' . $fornitore->id);
}
public function getFornitoreHubUrl(int $fornitoreId): string
{
return FornitoreScheda::getUrl(['record' => $fornitoreId], panel: 'admin-filament');
}
public function getFornitoreAnagraficaUrl(int $fornitoreId): string
{
return url('/admin-filament/anagrafica/fornitori/' . $fornitoreId);
}
public function getSelectedFornitoreProperty(): ?Fornitore
{
if ((int) ($this->selectedFornitoreId ?? 0) <= 0) {
return null;
}
return $this->getTableQuery()
->with(['rubrica', 'dipendenti.user'])
->withCount('dipendenti')
->find((int) $this->selectedFornitoreId);
}
/**
* @return array<int, string>
*/
public function getActiveSearchFiltersProperty(): array
{
return $this->extractSearchTokens((string) $this->fornitoriSearch);
}
/**
* @return Collection<int, FornitoreDipendente>
*/
public function getDipendentiRowsProperty(): Collection
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
return new Collection();
}
return FornitoreDipendente::query()
->with('user')
->where('fornitore_id', (int) $fornitore->id)
->orderByDesc('attivo')
->orderBy('cognome')
->orderBy('nome')
->get();
}
/**
* @return Collection<int, RubricaUniversale>
*/
public function getRubricaDipendentiCandidatesProperty(): Collection
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
return new Collection();
}
$query = RubricaUniversale::query()
->whereNull('deleted_at')
->where(function (Builder $q): void {
$q->where('tipo_contatto', 'persona_fisica')
->orWhereNull('tipo_contatto');
})
->where(function (Builder $q): void {
$q->whereNotNull('nome')
->where('nome', '!=', '')
->orWhereNotNull('cognome')
->where('cognome', '!=', '')
->orWhereNotNull('email')
->where('email', '!=', '');
});
if (Schema::hasColumn('rubrica_universale', 'amministratore_id')) {
$adminId = (int) ($fornitore->amministratore_id ?? 0);
if ($adminId > 0) {
$query->where(function (Builder $q) use ($adminId): void {
$q->where('amministratore_id', $adminId)
->orWhereNull('amministratore_id');
});
}
}
$term = trim($this->dipendentiRubricaSearch);
if ($term !== '') {
$tokens = $this->extractSearchTokens($term);
foreach ($tokens as $token) {
$like = '%' . $token . '%';
$query->where(function (Builder $sub) use ($like): void {
$sub->where('nome', 'like', $like)
->orWhere('cognome', 'like', $like)
->orWhere('ragione_sociale', 'like', $like)
->orWhere('email', 'like', $like)
->orWhere('telefono_cellulare', 'like', $like)
->orWhere('telefono_ufficio', 'like', $like)
->orWhere('codice_fiscale', 'like', $like);
});
}
}
return $query
->orderBy('cognome')
->orderBy('nome')
->limit(80)
->get(['id', 'nome', 'cognome', 'ragione_sociale', 'email', 'telefono_cellulare', 'telefono_ufficio', 'codice_fiscale']);
}
public function apriElenco(): void
{
$this->activeTab = 'elenco';
}
public function apriNuovoFornitore(): void
{
$this->selectedFornitoreId = null;
$this->activeTab = 'scheda';
}
public function apriTabScheda(): void
{
if ((int) ($this->selectedFornitoreId ?? 0) <= 0) {
Notification::make()
->title('Seleziona prima un fornitore dalla Tab 1')
->warning()
->send();
$this->activeTab = 'elenco';
return;
}
$this->hydrateSchedaFromSelected();
$this->activeTab = 'scheda';
}
public function apriTabDipendenti(): void
{
if ((int) ($this->selectedFornitoreId ?? 0) <= 0) {
Notification::make()
->title('Seleziona prima un fornitore dalla Tab 1')
->warning()
->send();
$this->activeTab = 'elenco';
return;
}
$this->hydrateSchedaFromSelected();
$this->activeTab = 'dipendenti';
}
public function applicaRicerca(): void
{
$this->fornitoriSearch = trim($this->searchInput);
$this->searchFornitoreMatches();
$this->activeTab = 'elenco';
}
public function pulisciRicerca(): void
{
$this->fornitoriSearch = '';
$this->searchInput = '';
$this->searchFornitoreMatches();
$this->activeTab = 'elenco';
}
public function importaTagLegacy(bool $soloSelezionato = false, bool $dryRun = false): void
{
if (! Schema::hasColumn('fornitori', 'tags')) {
Notification::make()->title('Colonna tags non presente')->warning()->send();
return;
}
$params = [];
if ($dryRun) {
$params['--dry-run'] = true;
}
if ($soloSelezionato) {
$fornitoreId = (int) ($this->selectedFornitoreId ?? 0);
if ($fornitoreId <= 0) {
Notification::make()->title('Seleziona prima un fornitore')->warning()->send();
return;
}
$params['--fornitore-id'] = [$fornitoreId];
}
try {
Artisan::call(ImportLegacyFornitoriTagsCommand::class, $params);
$output = trim(Artisan::output());
} catch (\Throwable $e) {
Notification::make()->title('Riallineamento tag legacy fallito')->body($e->getMessage())->danger()->send();
return;
}
if ((int) ($this->selectedFornitoreId ?? 0) > 0) {
$this->hydrateSchedaFromSelected();
}
$this->searchFornitoreMatches();
Notification::make()
->title($dryRun ? 'Anteprima riallineamento completata' : 'Riallineamento tag legacy completato')
->body($output !== '' ? $output : 'Operazione eseguita.')
->success()
->send();
}
public function removeSearchFilter(string $token): void
{
$tokens = array_values(array_filter(
$this->extractSearchTokens((string) $this->fornitoriSearch),
static fn(string $value): bool => mb_strtolower($value) !== mb_strtolower($token)
));
$this->fornitoriSearch = implode(' e ', $tokens);
$this->searchInput = $this->fornitoriSearch;
$this->searchFornitoreMatches();
$this->activeTab = 'elenco';
}
public function apriScheda(int $fornitoreId): void
{
$fornitore = $this->getTableQuery()->find($fornitoreId);
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Fornitore non trovato')->danger()->send();
return;
}
$this->selectedFornitoreId = (int) $fornitore->id;
$this->activeTab = 'scheda';
$this->loadSchedaState($fornitore);
$this->loadDipendentiInlineState();
}
public function updatedSelectedFornitoreId(): void
{
if (in_array($this->activeTab, ['scheda', 'dipendenti'], true)) {
$this->hydrateSchedaFromSelected();
}
}
public function createFornitoreRapido(): void
{
$adminId = $this->resolveAmministratoreId();
if ($adminId <= 0) {
Notification::make()->title('Amministratore non risolto')->danger()->send();
return;
}
$data = Validator::make([
'ragione_sociale' => trim($this->newFornitoreRagioneSociale),
'nome' => trim($this->newFornitoreNome),
'cognome' => trim($this->newFornitoreCognome),
'email' => trim($this->newFornitoreEmail),
'telefono' => trim($this->newFornitoreTelefono),
'partita_iva' => trim($this->newFornitorePartitaIva),
'codice_fiscale' => trim($this->newFornitoreCodiceFiscale),
], [
'ragione_sociale' => ['nullable', 'string', 'max:255'],
'nome' => ['nullable', 'string', 'max:255'],
'cognome' => ['nullable', 'string', 'max:255'],
'email' => ['nullable', 'email', 'max:255'],
'telefono' => ['nullable', 'string', 'max:64'],
'partita_iva' => ['nullable', 'string', 'max:32'],
'codice_fiscale' => ['nullable', 'string', 'max:32'],
])->validate();
if (($data['ragione_sociale'] ?? '') === '' && (($data['nome'] ?? '') === '' && ($data['cognome'] ?? '') === '')) {
Notification::make()->title('Inserisci almeno ragione sociale o nominativo')->warning()->send();
return;
}
$fornitore = Fornitore::query()->create([
'amministratore_id' => $adminId,
'ragione_sociale' => $this->cleanNullable($data['ragione_sociale'] ?? null),
'nome' => $this->cleanNullable($data['nome'] ?? null),
'cognome' => $this->cleanNullable($data['cognome'] ?? null),
'email' => $this->cleanNullable($data['email'] ?? null),
'telefono' => $this->cleanNullable($data['telefono'] ?? null),
'partita_iva' => $this->cleanNullable($data['partita_iva'] ?? null),
'codice_fiscale' => $this->cleanNullable($data['codice_fiscale'] ?? null),
]);
$this->newFornitoreRagioneSociale = '';
$this->newFornitoreNome = '';
$this->newFornitoreCognome = '';
$this->newFornitoreEmail = '';
$this->newFornitoreTelefono = '';
$this->newFornitorePartitaIva = '';
$this->newFornitoreCodiceFiscale = '';
$this->fornitoriCount = (int) $this->getTableQuery()->count();
$this->apriScheda((int) $fornitore->id);
$this->searchInput = $this->getFornitoreLabel($fornitore);
$this->fornitoriSearch = $this->searchInput;
$this->searchFornitoreMatches();
Notification::make()->title('Fornitore creato')->success()->send();
}
public function saveSchedaAnagrafica(): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona un fornitore')->danger()->send();
return;
}
$data = Validator::make([
'ragione_sociale' => trim($this->editRagioneSociale),
'titolo' => trim($this->editTitolo),
'nome' => trim($this->editNome),
'cognome' => trim($this->editCognome),
'email' => trim($this->editEmail),
'pec' => trim($this->editPec),
'telefono' => trim($this->editTelefono),
'cellulare' => trim($this->editCellulare),
'partita_iva' => trim($this->editPartitaIva),
'codice_fiscale' => trim($this->editCodiceFiscale),
'sito_web' => trim($this->editSitoWeb),
'tags' => trim($this->editTags),
'note' => trim($this->editNote),
], [
'ragione_sociale' => ['nullable', 'string', 'max:255'],
'titolo' => ['nullable', 'string', 'max:50'],
'nome' => ['nullable', 'string', 'max:255'],
'cognome' => ['nullable', 'string', 'max:255'],
'email' => ['nullable', 'email', 'max:255'],
'pec' => ['nullable', 'email', 'max:255'],
'telefono' => ['nullable', 'string', 'max:64'],
'cellulare' => ['nullable', 'string', 'max:64'],
'partita_iva' => ['nullable', 'string', 'max:32'],
'codice_fiscale' => ['nullable', 'string', 'max:32'],
'sito_web' => ['nullable', 'string', 'max:255'],
'tags' => ['nullable', 'string', 'max:1000'],
'note' => ['nullable', 'string', 'max:5000'],
])->validate();
$fornitore->ragione_sociale = $this->cleanNullable($data['ragione_sociale'] ?? null);
if (Schema::hasColumn('fornitori', 'titolo')) {
$fornitore->titolo = $this->cleanNullable($data['titolo'] ?? null);
}
$fornitore->nome = $this->cleanNullable($data['nome'] ?? null);
$fornitore->cognome = $this->cleanNullable($data['cognome'] ?? null);
$fornitore->email = $this->cleanNullable($data['email'] ?? null);
$fornitore->pec = $this->cleanNullable($data['pec'] ?? null);
$fornitore->telefono = $this->cleanNullable($data['telefono'] ?? null);
$fornitore->cellulare = $this->cleanNullable($data['cellulare'] ?? null);
$fornitore->partita_iva = $this->cleanNullable($data['partita_iva'] ?? null);
$fornitore->codice_fiscale = $this->cleanNullable($data['codice_fiscale'] ?? null);
$fornitore->sito_web = $this->cleanNullable($data['sito_web'] ?? null);
$fornitore->tags = $this->cleanNullable($data['tags'] ?? null);
$fornitore->note = $this->cleanNullable($data['note'] ?? null);
$fornitore->save();
$this->loadSchedaState($fornitore->fresh());
$this->searchFornitoreMatches();
Notification::make()->title('Anagrafica fornitore aggiornata')->success()->send();
}
public function addDipendenteManuale(): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona un fornitore')->warning()->send();
return;
}
$nome = trim($this->newDipendenteNome);
$email = trim($this->newDipendenteEmail);
if ($nome === '') {
Notification::make()->title('Nome dipendente obbligatorio')->warning()->send();
return;
}
if ($email !== '') {
$exists = FornitoreDipendente::query()
->where('fornitore_id', (int) $fornitore->id)
->whereRaw('LOWER(email) = ?', [mb_strtolower($email)])
->exists();
if ($exists) {
Notification::make()->title('Dipendente con stessa email gia presente')->warning()->send();
return;
}
}
FornitoreDipendente::query()->create([
'fornitore_id' => (int) $fornitore->id,
'nome' => $nome,
'cognome' => $this->cleanNullable($this->newDipendenteCognome),
'email' => $this->cleanNullable($email),
'telefono' => $this->cleanNullable($this->newDipendenteTelefono),
'attivo' => true,
'created_by_user_id' => Auth::id(),
'updated_by_user_id' => Auth::id(),
]);
$this->newDipendenteNome = '';
$this->newDipendenteCognome = '';
$this->newDipendenteEmail = '';
$this->newDipendenteTelefono = '';
$this->loadDipendentiInlineState();
Notification::make()->title('Dipendente aggiunto')->success()->send();
}
public function createDipendenteRubricaAndLink(): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona prima un fornitore')->warning()->send();
return;
}
$nome = trim($this->newDipendenteNome);
if ($nome === '') {
Notification::make()->title('Nome nominativo obbligatorio')->warning()->send();
return;
}
$rubrica = RubricaUniversale::query()->create([
'nome' => $nome,
'cognome' => $this->cleanNullable($this->newDipendenteCognome),
'email' => $this->cleanNullable($this->newDipendenteEmail),
'telefono_cellulare' => $this->cleanNullable($this->newDipendenteTelefono),
'tipo_contatto' => 'persona_fisica',
'categoria' => 'fornitore',
'stato' => 'attivo',
'note' => 'Creato da Fornitori > Dipendenti per il fornitore #' . (int) $fornitore->id,
'data_inserimento' => now()->toDateString(),
'data_ultima_modifica' => now()->toDateString(),
'creato_da' => Auth::id(),
'modificato_da' => Auth::id(),
]);
$this->addDipendenteFromRubrica((int) $rubrica->id);
}
public function addDipendenteFromRubrica(int $rubricaId): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona prima un fornitore')->warning()->send();
$this->activeTab = 'elenco';
return;
}
$rubrica = RubricaUniversale::query()->find((int) $rubricaId);
if (! $rubrica instanceof RubricaUniversale) {
Notification::make()->title('Contatto rubrica non trovato')->danger()->send();
return;
}
$email = trim((string) ($rubrica->email ?? ''));
$nome = trim((string) ($rubrica->nome ?? ''));
$cognome = trim((string) ($rubrica->cognome ?? ''));
$telefono = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: ''));
if ($nome === '' && $cognome === '') {
$fromRs = trim((string) ($rubrica->ragione_sociale ?? ''));
if ($fromRs !== '') {
$nome = $fromRs;
} else {
Notification::make()->title('Rubrica senza nominativo utile')->warning()->send();
return;
}
}
$exists = FornitoreDipendente::query()
->where('fornitore_id', (int) $fornitore->id)
->where(function (Builder $q) use ($email, $nome, $cognome, $telefono): void {
if ($email !== '') {
$q->orWhereRaw('LOWER(email) = ?', [mb_strtolower($email)]);
}
$full = mb_strtolower(trim($nome . ' ' . $cognome));
if ($full !== '') {
$q->orWhereRaw("LOWER(TRIM(CONCAT(COALESCE(nome, ''), ' ', COALESCE(cognome, '')))) = ?", [$full]);
}
if ($telefono !== '') {
$normalized = preg_replace('/\D+/', '', $telefono) ?? '';
if ($normalized !== '') {
$q->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono, ''), ' ', ''), '-', ''), '.', ''), '(', ''), ')', '') = ?", [$normalized]);
}
}
})
->exists();
if ($exists) {
Notification::make()->title('Dipendente gia collegato a questo fornitore')->warning()->send();
return;
}
$dipendente = new FornitoreDipendente();
$dipendente->fornitore_id = (int) $fornitore->id;
$dipendente->nome = $nome;
$dipendente->cognome = $cognome !== '' ? $cognome : null;
$dipendente->email = $email !== '' ? $email : null;
$dipendente->telefono = $telefono !== '' ? $telefono : null;
$dipendente->attivo = true;
$dipendente->created_by_user_id = Auth::id();
$dipendente->updated_by_user_id = Auth::id();
$existingNote = trim((string) ($rubrica->note ?? ''));
$linkNote = 'Agganciato da rubrica ID #' . (int) $rubrica->id;
$dipendente->note = Str::limit(trim($linkNote . ($existingNote !== '' ? (' | ' . $existingNote) : '')), 2000, '');
$dipendente->save();
$this->loadDipendentiInlineState();
Notification::make()->title('Dipendente collegato al fornitore')->success()->send();
$this->activeTab = 'dipendenti';
}
public function saveDipendenteInline(int $dipendenteId): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona prima un fornitore')->warning()->send();
return;
}
$dipendente = FornitoreDipendente::query()
->where('fornitore_id', (int) $fornitore->id)
->find($dipendenteId);
if (! $dipendente instanceof FornitoreDipendente) {
Notification::make()->title('Dipendente non trovato')->danger()->send();
return;
}
$state = $this->dipendenteInline[$dipendenteId] ?? [];
$data = Validator::make($state, [
'nome' => ['required', 'string', 'max:255'],
'cognome' => ['nullable', 'string', 'max:255'],
'email' => ['nullable', 'email', 'max:255'],
'telefono' => ['nullable', 'string', 'max:64'],
'attivo' => ['nullable', 'boolean'],
])->validate();
$dipendente->nome = trim((string) ($data['nome'] ?? ''));
$dipendente->cognome = $this->cleanNullable($data['cognome'] ?? null);
$dipendente->email = $this->cleanNullable($data['email'] ?? null);
$dipendente->telefono = $this->cleanNullable($data['telefono'] ?? null);
$dipendente->attivo = (bool) ($data['attivo'] ?? false);
$dipendente->updated_by_user_id = Auth::id();
$dipendente->save();
$this->loadDipendentiInlineState();
Notification::make()->title('Dipendente aggiornato')->success()->send();
}
public function saveSchedaAutomazioni(): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona un fornitore')->danger()->send();
return;
}
$features = is_array($fornitore->fe_features ?? null) ? $fornitore->fe_features : [];
$features['automazioni'] = [
'fornitore_acqua' => ($this->automazioni['fornitore_acqua'] ?? '0') === '1',
'abilita_ticket' => ($this->automazioni['abilita_ticket'] ?? '0') === '1',
'abilita_allegati_camera' => ($this->automazioni['abilita_allegati_camera'] ?? '0') === '1',
];
$fornitore->fe_features = $features;
$fornitore->save();
Notification::make()->title('Automazioni aggiornate')->success()->send();
}
public function saveSchedaBancaria(): void
{
$fornitore = $this->selectedFornitore;
if (! $fornitore instanceof Fornitore) {
Notification::make()->title('Seleziona un fornitore')->danger()->send();
return;
}
$fornitore->iban = $this->cleanNullable($this->contoIban);
$fornitore->intestazione_cc_esatta = $this->cleanNullable($this->contoIntestazioneEsatta);
$fornitore->save();
Notification::make()->title('Dati conto aggiornati')->success()->send();
}
private function loadSchedaState(Fornitore $fornitore): void
{
$features = is_array($fornitore->fe_features ?? null) ? $fornitore->fe_features : [];
$auto = (array) ($features['automazioni'] ?? []);
$this->automazioni = [
'fornitore_acqua' => ! empty($auto['fornitore_acqua']) ? '1' : '0',
'abilita_ticket' => ! empty($auto['abilita_ticket']) ? '1' : '0',
'abilita_allegati_camera' => ! empty($auto['abilita_allegati_camera']) ? '1' : '0',
];
$this->contoIban = is_string($fornitore->iban) ? $fornitore->iban : null;
$this->contoIntestazioneEsatta = is_string($fornitore->intestazione_cc_esatta ?? null)
? $fornitore->intestazione_cc_esatta
: null;
$this->editRagioneSociale = (string) ($fornitore->ragione_sociale ?? '');
$this->editTitolo = Schema::hasColumn('fornitori', 'titolo')
? (string) ($fornitore->titolo ?? '')
: '';
$this->editNome = (string) ($fornitore->nome ?? '');
$this->editCognome = (string) ($fornitore->cognome ?? '');
$this->editEmail = (string) ($fornitore->email ?? '');
$this->editPec = (string) ($fornitore->pec ?? '');
$this->editTelefono = (string) ($fornitore->telefono ?? '');
$this->editCellulare = (string) ($fornitore->cellulare ?? '');
$this->editPartitaIva = (string) ($fornitore->partita_iva ?? '');
$this->editCodiceFiscale = (string) ($fornitore->codice_fiscale ?? '');
$this->editSitoWeb = (string) ($fornitore->sito_web ?? '');
$this->editTags = (string) ($fornitore->tags ?? '');
$this->editNote = (string) ($fornitore->note ?? '');
}
private function loadDipendentiInlineState(): void
{
$rows = $this->getDipendentiRowsProperty();
$this->dipendenteInline = $rows->mapWithKeys(function (FornitoreDipendente $dipendente): array {
return [
(int) $dipendente->id => [
'nome' => (string) ($dipendente->nome ?? ''),
'cognome' => (string) ($dipendente->cognome ?? ''),
'email' => (string) ($dipendente->email ?? ''),
'telefono' => (string) ($dipendente->telefono ?? ''),
'attivo' => (bool) ($dipendente->attivo ?? false),
],
];
})->all();
}
private function resolveAmministratoreId(): int
{
$user = Auth::user();
if (! $user instanceof User) {
return 0;
}
$adminId = (int) ($user->amministratore?->id ?? 0);
if ($adminId > 0) {
return $adminId;
}
$stabile = StabileContext::getActiveStabile($user);
return (int) ($stabile?->amministratore_id ?? 0);
}
public function hydrateSchedaFromSelected(): void
{
if ((int) ($this->selectedFornitoreId ?? 0) <= 0) {
return;
}
$fornitore = $this->getTableQuery()->find((int) $this->selectedFornitoreId);
if (! $fornitore instanceof Fornitore) {
return;
}
$this->loadSchedaState($fornitore);
$this->loadDipendentiInlineState();
}
private function applyFornitoreSearch(Builder $query, string $raw): Builder
{
$tokens = $this->extractSearchTokens($raw);
if ($tokens === []) {
return $query;
}
$hasTags = Schema::hasColumn('fornitori', 'tags');
foreach ($tokens as $token) {
$term = trim($token);
if ($term === '') {
continue;
}
$digits = preg_replace('/\D+/', '', $term) ?: '';
$needleText = '%' . mb_strtolower($term) . '%';
$needle = '%' . $digits . '%';
$query->where(function (Builder $sub) use ($needleText, $needle, $digits, $hasTags): void {
$sub->whereRaw("LOWER(COALESCE(ragione_sociale, '')) LIKE ?", [$needleText])
->orWhereRaw("LOWER(COALESCE(nome, '')) LIKE ?", [$needleText])
->orWhereRaw("LOWER(COALESCE(cognome, '')) LIKE ?", [$needleText])
->orWhereRaw("LOWER(COALESCE(email, '')) LIKE ?", [$needleText])
->orWhereRaw("LOWER(COALESCE(partita_iva, '')) LIKE ?", [$needleText])
->orWhereRaw("LOWER(COALESCE(codice_fiscale, '')) LIKE ?", [$needleText]);
if ($hasTags) {
$sub->orWhereRaw("LOWER(COALESCE(tags, '')) LIKE ?", [$needleText]);
}
if ($digits !== '') {
$sub->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(telefono, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') LIKE ?", [$needle])
->orWhereRaw("REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(cellulare, ''), ' ', ''), '+', ''), '-', ''), '(', ''), ')', '') LIKE ?", [$needle]);
}
});
}
return $query;
}
private function searchFornitoreMatches(): void
{
$raw = trim($this->searchInput !== '' ? $this->searchInput : $this->fornitoriSearch);
if ($raw === '') {
$this->fornitoreMatches = new Collection();
return;
}
$digits = preg_replace('/\D+/', '', $raw) ?: '';
$canonicalNeedles = $this->normalizeSearchTags($raw);
$query = $this->getTableQuery()
->withCount('dipendenti')
->orderByRaw('CASE WHEN id = ? THEN 0 ELSE 1 END', [(int) ($this->selectedFornitoreId ?? 0)])
->orderBy('ragione_sociale')
->orderBy('cognome')
->orderBy('nome');
$matches = $this->applyFornitoreSearch($query, $raw)
->limit(60)
->get()
->sortByDesc(function (Fornitore $fornitore) use ($raw, $digits, $canonicalNeedles): int {
return $this->scoreFornitoreMatch($fornitore, $raw, $digits, $canonicalNeedles);
})
->take(12)
->values();
if ((int) ($this->selectedFornitoreId ?? 0) > 0 && ! $matches->contains('id', (int) $this->selectedFornitoreId)) {
$selected = $this->getTableQuery()->find((int) $this->selectedFornitoreId);
if ($selected instanceof Fornitore) {
$matches->prepend($selected);
$matches = $matches->unique('id')->take(12)->values();
}
}
$this->fornitoreMatches = $matches;
}
/**
* @param array<int, string> $canonicalNeedles
*/
private function scoreFornitoreMatch(Fornitore $fornitore, string $raw, string $digits, array $canonicalNeedles): int
{
$score = (int) ($fornitore->id === (int) ($this->selectedFornitoreId ?? 0) ? 1000 : 0);
$label = $this->getFornitoreLabel($fornitore);
$phone = (string) ($fornitore->telefono ?: $fornitore->cellulare);
$tags = $this->splitFornitoreTags((string) ($fornitore->tags ?? ''));
$haystack = mb_strtolower(implode(' ', array_filter([
$label,
(string) ($fornitore->email ?? ''),
$phone,
implode(', ', $tags),
(string) ($fornitore->note ?? ''),
(string) ($fornitore->partita_iva ?? ''),
(string) ($fornitore->codice_fiscale ?? ''),
])));
if ($raw !== '' && str_contains($haystack, mb_strtolower($raw))) {
$score += 60;
}
if ($digits !== '') {
$phoneDigits = preg_replace('/\D+/', '', $phone) ?: '';
if ($phoneDigits !== '' && str_contains($phoneDigits, $digits)) {
$score += 45;
}
}
$rowTags = array_map(fn(string $tag): string => mb_strtolower($tag), $tags);
foreach ($canonicalNeedles as $canonicalNeedle) {
foreach ($rowTags as $rowTag) {
if ($rowTag === $canonicalNeedle) {
$score += 90;
} elseif (str_contains($rowTag, $canonicalNeedle) || str_contains($canonicalNeedle, $rowTag)) {
$score += 55;
}
}
}
return $score;
}
/**
* @return array<int, string>
*/
private function normalizeSearchTags(string $input): array
{
$tags = [];
foreach ($this->splitFornitoreTags($input) as $tag) {
$normalized = $this->canonicalizeFornitoreTag($tag);
if ($normalized !== null) {
$tags[] = $normalized;
}
}
return array_values(array_unique($tags));
}
/**
* @return array<int, string>
*/
private function splitFornitoreTags(string $value): array
{
$parts = preg_split('/[,;|\n\r\/]+/', $value) ?: [];
return array_values(array_filter(array_map(function (string $part): string {
$clean = trim($part);
$clean = preg_replace('/\s+/', ' ', $clean) ?? '';
return $clean;
}, $parts), fn(string $part): bool => $part !== ''));
}
private function canonicalizeFornitoreTag(string $raw): ?string
{
$clean = trim(mb_strtolower($raw));
if ($clean === '' || mb_strlen($clean) < 3) {
return null;
}
$map = [
'idr' => 'idraulico',
'idraul' => 'idraulico',
'elett' => 'elettricista',
'elettric' => 'elettricista',
'ascens' => 'ascensorista',
'puliz' => 'pulizie',
'giardin' => 'giardiniere',
'assicur' => 'assicurazione',
'manut' => 'manutenzione',
'spurgh' => 'spurghi',
'fogn' => 'spurghi',
'serr' => 'serrature',
'cald' => 'caldaia',
];
foreach ($map as $prefix => $normalized) {
if (str_starts_with($clean, $prefix)) {
return $normalized;
}
}
return $clean;
}
private function cleanNullable(?string $value): ?string
{
$value = trim((string) $value);
return $value !== '' ? $value : null;
}
/**
* @return array<int, string>
*/
private function extractSearchTokens(string $raw): array
{
$term = trim(preg_replace('/\s+/', ' ', $raw) ?? '');
if ($term === '') {
return [];
}
$hasLogicalSeparator = preg_match('/[,;|+]|\s(?:e|ed|and)\s/ui', $term) === 1;
if (! $hasLogicalSeparator) {
return [$term];
}
$parts = preg_split('/(?:[,;|+]|\s(?:e|ed|and)\s)+/ui', $term) ?: [];
$parts = array_values(array_filter(array_map(static fn(string $value): string => trim($value), $parts), static fn(string $value): bool => $value !== ''));
return $parts !== [] ? array_values(array_unique($parts)) : [$term];
}
}