1165 lines
46 KiB
PHP
Executable File
1165 lines
46 KiB
PHP
Executable File
<?php
|
|
namespace App\Filament\Pages\Gescon;
|
|
|
|
use App\Filament\Pages\Contabilita\FornitoriArchivio as ContabilitaFornitoriArchivio;
|
|
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
|
|
use App\Filament\Pages\Fornitore\ProdottiCatalogo;
|
|
use App\Filament\Pages\Fornitore\SerialiCatalogo;
|
|
use App\Models\Documento;
|
|
use App\Models\FatturaElettronica;
|
|
use App\Models\Fornitore;
|
|
use App\Models\FornitoreDipendente;
|
|
use App\Models\FornitoreStabileImpostazione;
|
|
use App\Models\Product;
|
|
use App\Models\RegistroRitenuteAcconto;
|
|
use App\Models\RubricaUniversale;
|
|
use App\Models\Stabile;
|
|
use App\Models\StabileServizio;
|
|
use App\Models\StgFatturaAde;
|
|
use App\Models\User;
|
|
use App\Models\VoceSpesa;
|
|
use App\Modules\Contabilita\Models\FatturaFornitore as ContabilitaFatturaFornitore;
|
|
use App\Modules\Contabilita\Models\PianoConti;
|
|
use App\Services\Catalog\FornitoreProductCatalogService;
|
|
use App\Services\Catalog\ProductAssetIngestionService;
|
|
use App\Services\Catalog\ProductHubViewService;
|
|
use App\Services\Catalog\ProductOfferService;
|
|
use App\Services\Consumi\AcquaPdfTextParser;
|
|
use App\Services\Consumi\ConsumiAcquaIngestionService;
|
|
use App\Services\Consumi\ConsumiAcquaTariffeIngestionService;
|
|
use App\Services\Documenti\PdfTextExtractionService;
|
|
use App\Services\FattureElettroniche\FatturaElettronicaProtocolloService;
|
|
use App\Support\Livewire\SupportsRubricaLookup;
|
|
use App\Support\StabileContext;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\Hidden;
|
|
use Filament\Forms\Components\Repeater;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
|
|
class FornitoreScheda extends Page
|
|
{
|
|
use ResolvesOperatoreContext;
|
|
use SupportsRubricaLookup;
|
|
|
|
protected static ?string $title = 'Scheda fornitore';
|
|
|
|
protected static ?string $slug = 'anagrafica/fornitori/{record}';
|
|
|
|
protected static bool $shouldRegisterNavigation = false;
|
|
|
|
protected string $view = 'filament.pages.gescon.fornitore-scheda';
|
|
|
|
public Fornitore $fornitore;
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $box = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $anteprimaAde = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $anteprimaFe = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $anteprimaContabilita = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $fattureAssociate = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $raVersateRows = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $catalogoProdottiRows = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $tecnorepairRows = [];
|
|
|
|
public string $tagsInput = '';
|
|
|
|
/** @var array<int, string> */
|
|
public array $tagSuggestions = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $dipendentiRows = [];
|
|
|
|
public string $nuovoDipendenteNome = '';
|
|
|
|
public string $nuovoDipendenteCognome = '';
|
|
|
|
public string $nuovoDipendenteEmail = '';
|
|
|
|
public string $nuovoDipendenteTelefono = '';
|
|
|
|
public string $dipendenteRubricaSearch = '';
|
|
|
|
public ?int $dipendenteRubricaId = null;
|
|
|
|
public string $sectionTab = 'profilo';
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $dipendenteRubricaMatches = [];
|
|
|
|
public ?string $lastGeneratedPassword = null;
|
|
|
|
public function cleanDisplayValue(?string $value, string $fallback = '-'): string
|
|
{
|
|
$v = trim((string) $value);
|
|
if ($v === '') {
|
|
return $fallback;
|
|
}
|
|
|
|
$upper = strtoupper($v);
|
|
if (in_array($upper, ['NULL', 'N/D', 'ND', 'N.A.', '-', '--', '[]', '{}'], true)) {
|
|
return $fallback;
|
|
}
|
|
|
|
return $v;
|
|
}
|
|
|
|
public function mount(int | string $record): void
|
|
{
|
|
$user = Auth::user();
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
if (! $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore', 'fornitore'])) {
|
|
abort(403);
|
|
}
|
|
|
|
if ($user->hasRole('fornitore') && ! $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore'])) {
|
|
[$resolvedFornitore] = $this->resolveOperatoreContext((int) $record);
|
|
$this->fornitore = $resolvedFornitore->loadMissing(['rubrica', 'amministratore']);
|
|
} else {
|
|
$this->fornitore = Fornitore::query()->with(['rubrica', 'amministratore'])->findOrFail((int) $record);
|
|
|
|
// Tenant guard: impedisce accesso cross-amministratore via URL.
|
|
if (! $user->hasAnyRole(['super-admin', 'admin'])) {
|
|
$allowedAdminIds = [];
|
|
|
|
$activeStabile = StabileContext::getActiveStabile($user);
|
|
$activeAdminId = (int) ($activeStabile?->amministratore_id ?: 0);
|
|
if ($activeAdminId > 0) {
|
|
$allowedAdminIds[] = $activeAdminId;
|
|
}
|
|
|
|
$userAdminId = (int) ($user->amministratore?->id ?: 0);
|
|
if ($userAdminId > 0) {
|
|
$allowedAdminIds[] = $userAdminId;
|
|
}
|
|
|
|
$ownerAdminId = (int) ($user->amministratoreOwner?->id ?: 0);
|
|
if ($ownerAdminId > 0) {
|
|
$allowedAdminIds[] = $ownerAdminId;
|
|
}
|
|
|
|
// Collaboratori: includi anche gli amministratori degli stabili assegnati.
|
|
try {
|
|
$assigned = $user->stabiliAssegnati()->pluck('amministratore_id')->all();
|
|
foreach ($assigned as $aid) {
|
|
$aid = (int) $aid;
|
|
if ($aid > 0) {
|
|
$allowedAdminIds[] = $aid;
|
|
}
|
|
}
|
|
} catch (\Throwable) {
|
|
// ignore
|
|
}
|
|
|
|
$allowedAdminIds = array_values(array_unique(array_filter($allowedAdminIds, fn($v) => (int) $v > 0)));
|
|
$fornitoreAdminIds = array_values(array_unique(array_filter([
|
|
(int) ($this->fornitore->amministratore_id ?? 0),
|
|
(int) ($this->fornitore->rubrica?->amministratore_id ?? 0),
|
|
], fn($v) => (int) $v > 0)));
|
|
|
|
if ($fornitoreAdminIds === []) {
|
|
if (! $user->hasAnyRole(['admin', 'amministratore'])) {
|
|
abort(404);
|
|
}
|
|
} else {
|
|
$hasIntersection = count(array_intersect($fornitoreAdminIds, $allowedAdminIds)) > 0;
|
|
if (! $hasIntersection) {
|
|
abort(404);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->hydrateBoxData($user);
|
|
$this->tagsInput = (string) ($this->fornitore->tags ?? '');
|
|
$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;
|
|
}
|
|
}
|
|
|
|
public function getProdottiHubUrl(): string
|
|
{
|
|
return ProdottiCatalogo::getUrl(['fornitore' => (int) $this->fornitore->id], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getSerialiHubUrl(): string
|
|
{
|
|
return SerialiCatalogo::getUrl(['fornitore' => (int) $this->fornitore->id], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getContabilitaFornitoriUrl(): string
|
|
{
|
|
return ContabilitaFornitoriArchivio::getUrl(panel: 'admin-filament');
|
|
}
|
|
|
|
private function isValidSectionTab(string $tab): bool
|
|
{
|
|
return in_array($tab, ['profilo', 'accessi', 'assistenza', 'catalogo', 'contabilita'], true);
|
|
}
|
|
|
|
public function creaDipendenteFornitore(): void
|
|
{
|
|
$nome = trim($this->nuovoDipendenteNome);
|
|
$email = mb_strtolower(trim($this->nuovoDipendenteEmail));
|
|
|
|
if ($nome === '' || $email === '') {
|
|
Notification::make()->title('Nome ed email sono obbligatori')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$exists = FornitoreDipendente::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->whereRaw('LOWER(email) = ?', [$email])
|
|
->exists();
|
|
|
|
if ($exists) {
|
|
Notification::make()->title('Dipendente gia presente per questo fornitore')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
FornitoreDipendente::query()->create([
|
|
'fornitore_id' => (int) $this->fornitore->id,
|
|
'nome' => $nome,
|
|
'cognome' => trim($this->nuovoDipendenteCognome) ?: null,
|
|
'email' => $email,
|
|
'telefono' => trim($this->nuovoDipendenteTelefono) ?: null,
|
|
'attivo' => true,
|
|
'created_by_user_id' => Auth::id(),
|
|
'updated_by_user_id' => Auth::id(),
|
|
]);
|
|
|
|
$this->nuovoDipendenteNome = '';
|
|
$this->nuovoDipendenteCognome = '';
|
|
$this->nuovoDipendenteEmail = '';
|
|
$this->nuovoDipendenteTelefono = '';
|
|
|
|
$this->refreshDipendentiRows();
|
|
Notification::make()->title('Dipendente aggiunto')->success()->send();
|
|
}
|
|
|
|
public function updatedDipendenteRubricaSearch(): void
|
|
{
|
|
$this->searchDipendenteRubricaMatches();
|
|
}
|
|
|
|
public function selezionaDipendenteRubrica(int $rubricaId): void
|
|
{
|
|
$rubrica = RubricaUniversale::query()->find($rubricaId);
|
|
if (! $rubrica instanceof RubricaUniversale) {
|
|
return;
|
|
}
|
|
|
|
$this->dipendenteRubricaId = (int) $rubrica->id;
|
|
$this->dipendenteRubricaSearch = $this->formatRubricaLookupLabel($rubrica);
|
|
$this->dipendenteRubricaMatches = [$this->mapRubricaLookupRow($rubrica)];
|
|
}
|
|
|
|
public function resetDipendenteRubricaSelection(): void
|
|
{
|
|
$this->dipendenteRubricaId = null;
|
|
$this->dipendenteRubricaSearch = '';
|
|
$this->dipendenteRubricaMatches = [];
|
|
}
|
|
|
|
public function collegaDipendenteDaRubrica(): void
|
|
{
|
|
$rubricaId = (int) ($this->dipendenteRubricaId ?? 0);
|
|
if ($rubricaId <= 0) {
|
|
Notification::make()->title('Seleziona un nominativo rubrica')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$rubrica = RubricaUniversale::query()->find($rubricaId);
|
|
if (! $rubrica instanceof RubricaUniversale) {
|
|
Notification::make()->title('Nominativo rubrica non trovato')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$email = mb_strtolower(trim((string) ($rubrica->email ?? '')));
|
|
|
|
$query = FornitoreDipendente::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->where(function ($builder) use ($rubricaId, $email, $rubrica): void {
|
|
$builder->where('rubrica_id', $rubricaId);
|
|
|
|
if ($email !== '') {
|
|
$builder->orWhereRaw('LOWER(email) = ?', [$email]);
|
|
}
|
|
|
|
$builder->orWhere(function ($sub) use ($rubrica): void {
|
|
$sub->where('nome', (string) ($rubrica->nome ?? ''))
|
|
->where('cognome', (string) ($rubrica->cognome ?? ''));
|
|
});
|
|
});
|
|
|
|
$dipendente = $query->first();
|
|
if (! $dipendente instanceof FornitoreDipendente) {
|
|
$dipendente = new FornitoreDipendente();
|
|
$dipendente->fornitore_id = (int) $this->fornitore->id;
|
|
$dipendente->created_by_user_id = Auth::id();
|
|
}
|
|
|
|
$dipendente->rubrica_id = $rubricaId;
|
|
$dipendente->nome = (string) ($rubrica->nome ?: $rubrica->ragione_sociale ?: 'Dipendente');
|
|
$dipendente->cognome = trim((string) ($rubrica->cognome ?? '')) !== '' ? (string) $rubrica->cognome : null;
|
|
$dipendente->email = $email !== '' ? $email : null;
|
|
$dipendente->telefono = trim((string) ($rubrica->telefono_cellulare ?: $rubrica->telefono_ufficio ?: $rubrica->telefono_casa)) ?: null;
|
|
$dipendente->attivo = true;
|
|
$dipendente->updated_by_user_id = Auth::id();
|
|
$dipendente->save();
|
|
|
|
$this->resetDipendenteRubricaSelection();
|
|
$this->refreshDipendentiRows();
|
|
|
|
Notification::make()->title('Dipendente collegato dalla rubrica')->success()->send();
|
|
}
|
|
|
|
public function abilitaAccessoDipendente(int $dipendenteId): void
|
|
{
|
|
$dipendente = FornitoreDipendente::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->find($dipendenteId);
|
|
|
|
if (! $dipendente instanceof FornitoreDipendente) {
|
|
Notification::make()->title('Dipendente non trovato')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$email = trim((string) ($dipendente->email ?? ''));
|
|
if ($email === '') {
|
|
Notification::make()->title('Email dipendente mancante')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$targetUser = User::query()->whereRaw('LOWER(email) = ?', [mb_strtolower($email)])->first();
|
|
$created = false;
|
|
|
|
if (! $targetUser instanceof User) {
|
|
$generatedPassword = Str::random(12);
|
|
$targetUser = User::query()->create([
|
|
'name' => trim((string) ($dipendente->nome_completo ?: 'Utente fornitore')),
|
|
'email' => $email,
|
|
'password' => Hash::make($generatedPassword),
|
|
'email_verified_at' => now(),
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$this->lastGeneratedPassword = $generatedPassword;
|
|
$created = true;
|
|
}
|
|
|
|
$targetUser->assignRole('fornitore');
|
|
|
|
$dipendente->user_id = (int) $targetUser->id;
|
|
$dipendente->attivo = true;
|
|
$dipendente->updated_by_user_id = Auth::id();
|
|
$dipendente->save();
|
|
|
|
$this->refreshDipendentiRows();
|
|
|
|
Notification::make()
|
|
->title('Accesso dipendente abilitato')
|
|
->body($created
|
|
? 'Accesso creato. Password temporanea: ' . ($this->lastGeneratedPassword ?? '(non disponibile)')
|
|
: 'Accesso collegato ad utente esistente.')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function resetPasswordDipendenteUser(int $userId): void
|
|
{
|
|
$dipendente = FornitoreDipendente::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->where('user_id', $userId)
|
|
->first();
|
|
|
|
if (! $dipendente instanceof FornitoreDipendente) {
|
|
Notification::make()->title('Utente non collegato a questo fornitore')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$target = User::query()->find($userId);
|
|
if (! $target instanceof User) {
|
|
Notification::make()->title('Utente non trovato')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$newPassword = Str::random(12);
|
|
$target->password = Hash::make($newPassword);
|
|
$target->save();
|
|
|
|
$this->lastGeneratedPassword = $newPassword;
|
|
|
|
Notification::make()
|
|
->title('Password dipendente resettata')
|
|
->body('Nuova password temporanea: ' . $newPassword)
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function toggleDipendenteAttivo(int $dipendenteId): void
|
|
{
|
|
$dipendente = FornitoreDipendente::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->find($dipendenteId);
|
|
|
|
if (! $dipendente instanceof FornitoreDipendente) {
|
|
Notification::make()->title('Dipendente non trovato')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$dipendente->attivo = ! (bool) $dipendente->attivo;
|
|
$dipendente->updated_by_user_id = Auth::id();
|
|
$dipendente->save();
|
|
|
|
$this->refreshDipendentiRows();
|
|
Notification::make()->title('Stato dipendente aggiornato')->success()->send();
|
|
}
|
|
|
|
public function saveTags(): void
|
|
{
|
|
if (! Schema::hasColumn('fornitori', 'tags')) {
|
|
Notification::make()->title('Colonna tags non presente')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$normalized = $this->normalizeTags($this->tagsInput);
|
|
$this->fornitore->tags = implode(', ', $normalized);
|
|
$this->fornitore->save();
|
|
|
|
$this->tagsInput = (string) ($this->fornitore->tags ?? '');
|
|
$this->loadTagSuggestions();
|
|
|
|
Notification::make()->title('Tag fornitore salvati')->success()->send();
|
|
}
|
|
|
|
public function importLegacyTags(): void
|
|
{
|
|
if (! Schema::hasColumn('fornitori', 'tags')) {
|
|
Notification::make()->title('Colonna tags non presente')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Artisan::call('fornitori:import-legacy-tags', [
|
|
'--fornitore-id' => [(int) $this->fornitore->id],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Import tag legacy fallito')->body($e->getMessage())->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$this->fornitore->refresh();
|
|
$this->tagsInput = (string) ($this->fornitore->tags ?? '');
|
|
$this->loadTagSuggestions();
|
|
|
|
Notification::make()->title('Tag legacy importati')->success()->send();
|
|
}
|
|
|
|
public function syncRubricaFromFornitore(): void
|
|
{
|
|
$payload = [
|
|
'tipo_contatto' => 'persona_giuridica',
|
|
'categoria' => 'fornitore',
|
|
'stato' => 'attivo',
|
|
'ragione_sociale' => $this->fornitore->ragione_sociale ?: trim(($this->fornitore->nome ?? '') . ' ' . ($this->fornitore->cognome ?? '')),
|
|
'nome' => $this->fornitore->nome,
|
|
'cognome' => $this->fornitore->cognome,
|
|
'partita_iva' => $this->fornitore->partita_iva,
|
|
'codice_fiscale' => $this->fornitore->codice_fiscale,
|
|
'indirizzo' => $this->fornitore->indirizzo,
|
|
'civico' => $this->fornitore->civico,
|
|
'cap' => $this->fornitore->cap,
|
|
'citta' => $this->fornitore->citta,
|
|
'provincia' => $this->fornitore->provincia,
|
|
'nazione' => $this->fornitore->nazione,
|
|
'email' => $this->fornitore->email,
|
|
'pec' => $this->fornitore->pec,
|
|
'telefono_ufficio' => $this->fornitore->telefono,
|
|
'telefono_cellulare' => $this->fornitore->cellulare,
|
|
'sito_web' => $this->fornitore->sito_web,
|
|
'note' => $this->fornitore->note,
|
|
'data_ultima_modifica' => now()->toDateString(),
|
|
];
|
|
|
|
$rubrica = null;
|
|
if ((int) ($this->fornitore->rubrica_id ?? 0) > 0) {
|
|
$rubrica = RubricaUniversale::query()->find((int) $this->fornitore->rubrica_id);
|
|
}
|
|
|
|
if (! $rubrica instanceof RubricaUniversale) {
|
|
$rubrica = RubricaUniversale::query()->create(array_merge($payload, [
|
|
'data_inserimento' => now()->toDateString(),
|
|
]));
|
|
} else {
|
|
$rubrica->fill($payload);
|
|
$rubrica->save();
|
|
}
|
|
|
|
if ((int) ($this->fornitore->rubrica_id ?? 0) !== (int) $rubrica->id) {
|
|
$this->fornitore->rubrica_id = (int) $rubrica->id;
|
|
$this->fornitore->save();
|
|
}
|
|
|
|
$this->fornitore->refresh();
|
|
|
|
Notification::make()->title('Rubrica aggiornata dal fornitore')->success()->send();
|
|
}
|
|
|
|
private function loadTagSuggestions(): void
|
|
{
|
|
if (! Schema::hasColumn('fornitori', 'tags')) {
|
|
$this->tagSuggestions = [];
|
|
return;
|
|
}
|
|
|
|
$all = Fornitore::query()
|
|
->whereNotNull('tags')
|
|
->pluck('tags')
|
|
->all();
|
|
|
|
$pool = [];
|
|
foreach ($all as $row) {
|
|
foreach ($this->splitTags((string) $row) as $tag) {
|
|
$pool[] = $tag;
|
|
}
|
|
}
|
|
|
|
$pool = array_values(array_unique($pool));
|
|
sort($pool, SORT_NATURAL | SORT_FLAG_CASE);
|
|
$this->tagSuggestions = array_slice($pool, 0, 300);
|
|
}
|
|
|
|
private function refreshDipendentiRows(): void
|
|
{
|
|
$this->dipendentiRows = FornitoreDipendente::query()
|
|
->with(['user:id,name,email', 'rubrica:id,nome,cognome,ragione_sociale'])
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->orderByDesc('attivo')
|
|
->orderBy('nome')
|
|
->orderBy('cognome')
|
|
->get()
|
|
->map(function (FornitoreDipendente $d): array {
|
|
$userId = (int) ($d->user_id ?? 0);
|
|
return [
|
|
'id' => (int) $d->id,
|
|
'nome' => (string) ($d->nome_completo ?: 'Dipendente #' . $d->id),
|
|
'email' => (string) ($d->email ?? ''),
|
|
'telefono' => (string) ($d->telefono ?? ''),
|
|
'attivo' => (bool) $d->attivo,
|
|
'rubrica_id' => (int) ($d->rubrica_id ?? 0),
|
|
'rubrica' => $d->rubrica ? $this->formatRubricaLookupLabel($d->rubrica) : '',
|
|
'user_id' => $userId,
|
|
'user_label' => $userId > 0 ? ((string) ($d->user?->name ?: ('Utente #' . $userId))): '-',
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function searchDipendenteRubricaMatches(): void
|
|
{
|
|
$this->dipendenteRubricaMatches = $this->searchRubricaLookupMatches(
|
|
$this->dipendenteRubricaSearch,
|
|
$this->dipendenteRubricaId,
|
|
null,
|
|
12,
|
|
(int) ($this->fornitore->amministratore_id ?? 0)
|
|
);
|
|
}
|
|
|
|
private function refreshCatalogRows(): void
|
|
{
|
|
$this->catalogoProdottiRows = [];
|
|
$this->tecnorepairRows = [];
|
|
|
|
$this->catalogoProdottiRows = app(ProductHubViewService::class)
|
|
->buildRows($this->fornitore, null, 20);
|
|
|
|
if (Schema::hasTable('assistenza_tecnorepair_schede_legacy')) {
|
|
$this->tecnorepairRows = $this->fornitore->tecnorepairSchede()
|
|
->withCount('allegati')
|
|
->orderByRaw("CASE status_bucket WHEN 'open' THEN 0 WHEN 'waiting' THEN 1 WHEN 'closed' THEN 2 ELSE 3 END")
|
|
->orderByDesc('date_received')
|
|
->limit(12)
|
|
->get(['id', 'legacy_numero_scheda', 'product_model', 'product_code', 'serial_number', 'status_bucket', 'status_label'])
|
|
->map(fn($scheda) => [
|
|
'id' => (int) $scheda->id,
|
|
'legacy_numero' => (string) ($scheda->legacy_numero_scheda ?? ''),
|
|
'product_model' => (string) ($scheda->product_model ?? ''),
|
|
'product_code' => (string) ($scheda->product_code ?? ''),
|
|
'serial_number' => (string) ($scheda->serial_number ?? ''),
|
|
'status_bucket' => (string) ($scheda->status_bucket ?? ''),
|
|
'status_label' => (string) ($scheda->status_label ?? ''),
|
|
'allegati_count' => (int) ($scheda->allegati_count ?? 0),
|
|
])
|
|
->all();
|
|
}
|
|
}
|
|
|
|
private function resolveFornitoreAdminCode(): ?string
|
|
{
|
|
$admin = $this->fornitore->amministratore;
|
|
if (! $admin) {
|
|
return null;
|
|
}
|
|
|
|
$code = trim((string) ($admin->codice_amministratore ?? ''));
|
|
|
|
return $code !== '' ? $code : (string) $admin->id;
|
|
}
|
|
|
|
private function suggestWholesaleCsvPath(): string
|
|
{
|
|
$basePath = base_path('Miki-Bug-workspace/Fornitori/Listini da importare');
|
|
if (! is_dir($basePath)) {
|
|
return '';
|
|
}
|
|
|
|
$normalizedName = Str::of((string) ($this->fornitore->ragione_sociale ?? ''))
|
|
->ascii()
|
|
->lower()
|
|
->replaceMatches('/[^a-z0-9]+/', ' ')
|
|
->trim()
|
|
->value();
|
|
|
|
$candidates = glob($basePath . '/*/wholesale.csv') ?: [];
|
|
foreach ($candidates as $candidate) {
|
|
$segment = Str::of((string) basename(dirname($candidate)))
|
|
->ascii()
|
|
->lower()
|
|
->replaceMatches('/[^a-z0-9]+/', ' ')
|
|
->trim()
|
|
->value();
|
|
|
|
if ($segment !== '' && $normalizedName !== '' && Str::contains($normalizedName, $segment)) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return (string) ($candidates[0] ?? '');
|
|
}
|
|
|
|
private function suggestWholesaleXlsxPath(): string
|
|
{
|
|
$basePath = base_path('Miki-Bug-workspace/Fornitori/Listini da importare');
|
|
if (! is_dir($basePath)) {
|
|
return '';
|
|
}
|
|
|
|
$normalizedName = Str::of((string) ($this->fornitore->ragione_sociale ?? ''))
|
|
->ascii()
|
|
->lower()
|
|
->replaceMatches('/[^a-z0-9]+/', ' ')
|
|
->trim()
|
|
->value();
|
|
|
|
$candidates = glob($basePath . '/*/*.xlsx') ?: [];
|
|
foreach ($candidates as $candidate) {
|
|
$segment = Str::of((string) basename(dirname($candidate)))
|
|
->ascii()
|
|
->lower()
|
|
->replaceMatches('/[^a-z0-9]+/', ' ')
|
|
->trim()
|
|
->value();
|
|
|
|
if ($segment !== '' && $normalizedName !== '' && (Str::contains($normalizedName, $segment) || Str::contains($segment, $normalizedName))) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return (string) ($candidates[0] ?? '');
|
|
}
|
|
|
|
private function refreshOperationalBoxes(): void
|
|
{
|
|
$user = Auth::user();
|
|
if ($user instanceof User) {
|
|
$this->hydrateBoxData($user);
|
|
}
|
|
|
|
$this->fornitore->refresh();
|
|
$this->refreshCatalogRows();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function normalizeTags(string $input): array
|
|
{
|
|
$tags = [];
|
|
foreach ($this->splitTags($input) as $tag) {
|
|
$normalized = $this->canonicalizeTag($tag);
|
|
if ($normalized !== null) {
|
|
$tags[] = $normalized;
|
|
}
|
|
}
|
|
|
|
$tags = array_values(array_unique($tags));
|
|
sort($tags, SORT_NATURAL | SORT_FLAG_CASE);
|
|
|
|
return $tags;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function splitTags(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 $p): bool => $p !== ''));
|
|
}
|
|
|
|
private function canonicalizeTag(string $raw): ?string
|
|
{
|
|
$clean = trim(mb_strtolower($raw));
|
|
if ($clean === 'pc') {
|
|
return 'pc';
|
|
}
|
|
|
|
if ($clean === '' || mb_strlen($clean) < 3) {
|
|
return null;
|
|
}
|
|
|
|
$map = [
|
|
'informat' => 'informatica',
|
|
'assist' => 'assistenza',
|
|
'computer' => 'pc',
|
|
'apple' => 'apple',
|
|
'idr' => 'idraulico',
|
|
'idraul' => 'idraulico',
|
|
'elett' => 'elettricista',
|
|
'elettric' => 'elettricista',
|
|
'ascens' => 'ascensorista',
|
|
'puliz' => 'pulizie',
|
|
'giardin' => 'giardiniere',
|
|
'assicur' => 'assicurazione',
|
|
'manut' => 'manutenzione',
|
|
];
|
|
|
|
foreach ($map as $prefix => $canonical) {
|
|
if (str_starts_with($clean, $prefix)) {
|
|
return $canonical;
|
|
}
|
|
}
|
|
|
|
return $clean;
|
|
}
|
|
|
|
private function hydrateBoxData(User $user): void
|
|
{
|
|
$this->fattureAssociate = [];
|
|
$this->raVersateRows = [];
|
|
$identityCandidates = $this->getSupplierIdentityCandidates();
|
|
|
|
$this->box = [
|
|
'stabile_id' => null,
|
|
'fornitore_features' => [],
|
|
'fornitore_defaults' => [
|
|
'voce_spesa_default_id' => null,
|
|
'conto_costo_default_id' => null,
|
|
],
|
|
'catalogo_modulo' => [
|
|
'scope_code' => null,
|
|
'fe_products' => 0,
|
|
'csv_products' => 0,
|
|
'manual_products' => 0,
|
|
'internal_only' => 0,
|
|
'private_links' => 0,
|
|
'offers' => 0,
|
|
'amazon_links' => 0,
|
|
],
|
|
'prodotti' => [
|
|
'count' => 0,
|
|
'serializzati' => 0,
|
|
'identifiers' => 0,
|
|
'with_media' => 0,
|
|
],
|
|
'tecnorepair' => [
|
|
'schede' => 0,
|
|
'aperte' => 0,
|
|
'chiuse' => 0,
|
|
'seriali' => 0,
|
|
],
|
|
'ade' => ['count' => 0, 'lordo' => 0.0],
|
|
'fe' => ['count' => 0, 'totale' => 0.0, 'contabilizzate' => 0],
|
|
'contabilita' => [
|
|
'aperte_count' => 0,
|
|
'aperte_netto' => 0.0,
|
|
'pagate_count' => 0,
|
|
'pagate_netto' => 0.0,
|
|
],
|
|
'da_pagare' => ['source' => null, 'importo' => 0.0],
|
|
'ra' => [
|
|
'da_versare' => 0.0,
|
|
'versata' => 0.0,
|
|
'compensata' => 0.0,
|
|
],
|
|
];
|
|
|
|
$activeStabileId = StabileContext::resolveActiveStabileId($user);
|
|
if (! $activeStabileId) {
|
|
return;
|
|
}
|
|
|
|
$stabile = Stabile::query()->select(['id', 'amministratore_id'])->find($activeStabileId);
|
|
$amministratoreId = (int) ($stabile?->amministratore_id ?: 0);
|
|
if ($amministratoreId <= 0) {
|
|
return;
|
|
}
|
|
|
|
$this->box['stabile_id'] = $activeStabileId;
|
|
|
|
if (Schema::hasTable('products')) {
|
|
$productsBase = Product::query()->where('default_fornitore_id', (int) $this->fornitore->id);
|
|
$catalogProducts = (clone $productsBase)->get(['id', 'meta']);
|
|
|
|
$this->box['prodotti']['count'] = (int) (clone $productsBase)->count();
|
|
$this->box['prodotti']['serializzati'] = (int) (clone $productsBase)->where('track_serials', true)->count();
|
|
$this->box['prodotti']['with_media'] = (int) (clone $productsBase)->whereHas('media')->count();
|
|
$this->box['prodotti']['identifiers'] = (int) (clone $productsBase)->withCount('identifiers')->get()->sum('identifiers_count');
|
|
|
|
$scopeCode = trim((string) ($this->fornitore->codice_univoco ?? ''));
|
|
$this->box['catalogo_modulo']['scope_code'] = $scopeCode !== '' ? $scopeCode : ('FORN-' . str_pad((string) $this->fornitore->id, 6, '0', STR_PAD_LEFT));
|
|
|
|
foreach ($catalogProducts as $product) {
|
|
$meta = is_array($product->meta ?? null) ? $product->meta : [];
|
|
$source = (string) ($meta['source'] ?? 'manual');
|
|
$catalog = is_array($meta['catalog'] ?? null) ? $meta['catalog'] : [];
|
|
$privateLinks = is_array($catalog['private_source_links'] ?? null) ? $catalog['private_source_links'] : [];
|
|
|
|
if ($source === 'fattura_elettronica') {
|
|
$this->box['catalogo_modulo']['fe_products']++;
|
|
} elseif ($source === 'ncom_wholesale_csv') {
|
|
$this->box['catalogo_modulo']['csv_products']++;
|
|
} else {
|
|
$this->box['catalogo_modulo']['manual_products']++;
|
|
}
|
|
|
|
if ((string) ($catalog['publication_mode'] ?? '') === 'internal_only') {
|
|
$this->box['catalogo_modulo']['internal_only']++;
|
|
}
|
|
|
|
$this->box['catalogo_modulo']['private_links'] += count(array_filter($privateLinks, fn($value) => filled($value)));
|
|
}
|
|
|
|
if (Schema::hasTable('product_offers')) {
|
|
$offersBase = $this->fornitore->productOffers()->where('is_active', true);
|
|
$this->box['catalogo_modulo']['offers'] = (int) (clone $offersBase)->count();
|
|
$this->box['catalogo_modulo']['amazon_links'] = (int) (clone $offersBase)->whereIn('source_type', ['amazon_referral', 'amazon_creators_api'])->count();
|
|
}
|
|
}
|
|
|
|
if (Schema::hasTable('assistenza_tecnorepair_schede_legacy')) {
|
|
$stats = $this->fornitore->tecnorepair_stats;
|
|
$this->box['tecnorepair']['schede'] = (int) ($stats['total'] ?? 0);
|
|
$this->box['tecnorepair']['aperte'] = (int) ($stats['open'] ?? 0);
|
|
$this->box['tecnorepair']['chiuse'] = (int) ($stats['closed'] ?? 0);
|
|
$this->box['tecnorepair']['seriali'] = (int) ($stats['serials'] ?? 0);
|
|
}
|
|
|
|
// Feature flags & defaults (per stabile) + fallback (per fornitore)
|
|
$settings = null;
|
|
if (Schema::hasTable('fornitore_stabile_impostazioni')) {
|
|
$settings = FornitoreStabileImpostazione::query()
|
|
->where('stabile_id', (int) $activeStabileId)
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->first();
|
|
|
|
if ($settings) {
|
|
$this->box['fornitore_defaults']['voce_spesa_default_id'] = $settings->voce_spesa_default_id ? (int) $settings->voce_spesa_default_id : null;
|
|
$this->box['fornitore_defaults']['conto_costo_default_id'] = $settings->conto_costo_default_id ? (int) $settings->conto_costo_default_id : null;
|
|
}
|
|
}
|
|
|
|
$features = [];
|
|
if (Schema::hasColumn('fornitori', 'fe_features')) {
|
|
$features = is_array($this->fornitore->fe_features ?? null) ? $this->fornitore->fe_features : [];
|
|
} elseif ($settings && is_array($settings->meta ?? null)) {
|
|
$features = is_array($settings->meta['fe_features'] ?? null) ? $settings->meta['fe_features'] : [];
|
|
}
|
|
$this->box['fornitore_features'] = $features;
|
|
|
|
$normalize = static function (?string $value): string {
|
|
$value = strtoupper(trim((string) $value));
|
|
$value = str_replace(' ', '', $value);
|
|
return $value;
|
|
};
|
|
|
|
$ids = [];
|
|
$piva = $normalize($this->fornitore->partita_iva);
|
|
$cf = $normalize($this->fornitore->codice_fiscale);
|
|
if ($piva !== '') {
|
|
$ids[] = $piva;
|
|
if (str_starts_with($piva, 'IT') && strlen($piva) > 2) {
|
|
$ids[] = substr($piva, 2);
|
|
}
|
|
}
|
|
if ($cf !== '') {
|
|
$ids[] = $cf;
|
|
}
|
|
$ids = array_values(array_unique(array_filter($ids, fn($v) => $v !== '')));
|
|
|
|
// AdE (staging)
|
|
if (! empty($ids) && Schema::hasTable('stg_fatture_ade')) {
|
|
$adeBase = StgFatturaAde::query()
|
|
->where('amministratore_id', $amministratoreId)
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $q) use ($activeStabileId): void {
|
|
$q->where('stabile_id', $activeStabileId)->orWhereNull('stabile_id');
|
|
})
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $q) use ($ids): void {
|
|
foreach ($ids as $id) {
|
|
$q->orWhereRaw("REPLACE(UPPER(identificativo_fornitore), ' ', '') = ?", [$id]);
|
|
}
|
|
});
|
|
|
|
$this->box['ade']['count'] = (int) (clone $adeBase)->count();
|
|
$lordo = (float) (clone $adeBase)->selectRaw('COALESCE(SUM(imponibile + imposta), 0) as lordo')->value('lordo');
|
|
$this->box['ade']['lordo'] = $lordo;
|
|
|
|
$this->anteprimaAde = (clone $adeBase)
|
|
->orderByDesc('data_emissione')
|
|
->orderByDesc('id')
|
|
->limit(8)
|
|
->get(['id', 'data_emissione', 'numero_documento', 'imponibile', 'imposta', 'sdi_file'])
|
|
->map(fn(StgFatturaAde $r) => [
|
|
'id' => $r->id,
|
|
'data' => $r->data_emissione?->format('d/m/Y'),
|
|
'numero' => (string) ($r->numero_documento ?? ''),
|
|
'lordo' => (float) ($r->imponibile ?? 0) + (float) ($r->imposta ?? 0),
|
|
'sdi_file' => (string) ($r->sdi_file ?? ''),
|
|
])
|
|
->all();
|
|
}
|
|
|
|
// FE importate
|
|
if (Schema::hasTable('fatture_elettroniche')) {
|
|
$feBase = FatturaElettronica::query()
|
|
->where('stabile_id', $activeStabileId)
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $q) use ($identityCandidates): void {
|
|
$this->applySupplierIdentityFilterToFeQuery($q, $identityCandidates);
|
|
});
|
|
|
|
$this->box['fe']['count'] = (int) (clone $feBase)->count();
|
|
$this->box['fe']['totale'] = (float) (clone $feBase)->selectRaw('COALESCE(SUM(totale), 0) as totale')->value('totale');
|
|
$this->box['fe']['contabilizzate'] = (int) (clone $feBase)
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $q): void {
|
|
$q->whereNotNull('registrazione_contabile_id')->orWhere('stato', 'contabilizzata');
|
|
})
|
|
->count();
|
|
|
|
$this->anteprimaFe = (clone $feBase)
|
|
->orderByDesc('data_fattura')
|
|
->orderByDesc('id')
|
|
->limit(8)
|
|
->get(['id', 'data_fattura', 'numero_fattura', 'totale', 'sdi_file', 'stato'])
|
|
->map(fn(FatturaElettronica $r) => [
|
|
'id' => $r->id,
|
|
'data' => $r->data_fattura?->format('d/m/Y'),
|
|
'numero' => (string) ($r->numero_fattura ?? ''),
|
|
'totale' => (float) ($r->totale ?? 0),
|
|
'sdi_file' => (string) ($r->sdi_file ?? ''),
|
|
'stato' => (string) ($r->stato ?? ''),
|
|
])
|
|
->all();
|
|
}
|
|
|
|
// Contabilità (fatture fornitori)
|
|
if (Schema::hasTable('contabilita_fatture_fornitori')) {
|
|
$contabBase = ContabilitaFatturaFornitore::query()
|
|
->where('stabile_id', $activeStabileId)
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->when($identityCandidates !== [], function ($query) use ($identityCandidates): void {
|
|
$query->where(function (\Illuminate\Database\Eloquent\Builder $inner) use ($identityCandidates): void {
|
|
$inner->whereNull('fattura_elettronica_id')
|
|
->orWhereDoesntHave('fatturaElettronica')
|
|
->orWhereHas('fatturaElettronica', function (\Illuminate\Database\Eloquent\Builder $feQuery) use ($identityCandidates): void {
|
|
$this->applySupplierIdentityFilterToFeQuery($feQuery, $identityCandidates);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Coerente con la logica della scheda contabile: aperto = totale - pagato.
|
|
$totNetto = (float) (clone $contabBase)->selectRaw('COALESCE(SUM(netto_da_pagare), 0) as netto')->value('netto');
|
|
$this->box['contabilita']['pagate_count'] = (int) (clone $contabBase)->where('stato', 'pagato')->count();
|
|
$this->box['contabilita']['pagate_netto'] = (float) (clone $contabBase)->where('stato', 'pagato')->selectRaw('COALESCE(SUM(netto_da_pagare), 0) as netto')->value('netto');
|
|
$this->box['contabilita']['aperte_netto'] = round($totNetto - (float) $this->box['contabilita']['pagate_netto'], 2);
|
|
$this->box['contabilita']['aperte_count'] = (int) (clone $contabBase)->where('stato', '!=', 'pagato')->count();
|
|
|
|
$this->anteprimaContabilita = (clone $contabBase)
|
|
->orderByDesc('data_documento')
|
|
->orderByDesc('id')
|
|
->limit(8)
|
|
->get(['id', 'data_documento', 'numero_documento', 'netto_da_pagare', 'stato'])
|
|
->map(fn(ContabilitaFatturaFornitore $r) => [
|
|
'id' => $r->id,
|
|
'data' => $r->data_documento?->format('d/m/Y'),
|
|
'numero' => (string) ($r->numero_documento ?? ''),
|
|
'netto' => (float) ($r->netto_da_pagare ?? 0),
|
|
'stato' => (string) ($r->stato ?? ''),
|
|
])
|
|
->all();
|
|
|
|
$this->fattureAssociate = (clone $contabBase)
|
|
->orderByDesc('data_documento')
|
|
->orderByDesc('id')
|
|
->limit(30)
|
|
->get(['id', 'data_documento', 'numero_documento', 'totale', 'netto_da_pagare', 'stato', 'fattura_elettronica_id'])
|
|
->map(fn(ContabilitaFatturaFornitore $r) => [
|
|
'id' => (int) $r->id,
|
|
'data' => $r->data_documento?->format('d/m/Y'),
|
|
'numero' => (string) ($r->numero_documento ?? ''),
|
|
'totale' => (float) ($r->totale ?? 0),
|
|
'netto' => (float) ($r->netto_da_pagare ?? 0),
|
|
'stato' => (string) ($r->stato ?? ''),
|
|
'source' => ((int) ($r->fattura_elettronica_id ?? 0) > 0) ? 'FE' : 'manuale',
|
|
])
|
|
->all();
|
|
}
|
|
|
|
// Registro RA
|
|
if (Schema::hasTable('registro_ritenute_acconto')) {
|
|
$raBase = RegistroRitenuteAcconto::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->whereHas('gestione', fn(\Illuminate\Database\Eloquent\Builder $q) => $q->where('stabile_id', (int) $activeStabileId));
|
|
|
|
foreach (['da_versare', 'versata', 'compensata'] as $stato) {
|
|
$this->box['ra'][$stato] = (float) (clone $raBase)
|
|
->where('stato_versamento', $stato)
|
|
->selectRaw('COALESCE(SUM(importo_ritenuta), 0) as ra')
|
|
->value('ra');
|
|
}
|
|
|
|
$this->raVersateRows = (clone $raBase)
|
|
->whereIn('stato_versamento', ['versata', 'compensata'])
|
|
->orderByDesc('data_versamento')
|
|
->orderByDesc('id')
|
|
->limit(30)
|
|
->get(['id', 'data_versamento', 'importo_ritenuta', 'stato_versamento', 'codice_tributo', 'f24_riferimento'])
|
|
->map(fn(RegistroRitenuteAcconto $r) => [
|
|
'id' => (int) $r->id,
|
|
'data_versamento' => $r->data_versamento?->format('d/m/Y'),
|
|
'importo' => (float) ($r->importo_ritenuta ?? 0),
|
|
'stato' => (string) ($r->stato_versamento ?? ''),
|
|
'tributo' => (string) ($r->codice_tributo ?? ''),
|
|
'f24' => (string) ($r->f24_riferimento ?? ''),
|
|
])
|
|
->all();
|
|
}
|
|
|
|
// Totale da pagare (priorità: contabilità → FE → AdE)
|
|
if ((float) ($this->box['contabilita']['aperte_netto'] ?? 0) > 0) {
|
|
$this->box['da_pagare'] = ['source' => 'contabilita', 'importo' => (float) $this->box['contabilita']['aperte_netto']];
|
|
} elseif ((int) ($this->box['fe']['count'] ?? 0) > 0) {
|
|
$this->box['da_pagare'] = ['source' => 'fe', 'importo' => (float) ($this->box['fe']['totale'] ?? 0)];
|
|
} elseif ((int) ($this->box['ade']['count'] ?? 0) > 0) {
|
|
$this->box['da_pagare'] = ['source' => 'ade', 'importo' => (float) ($this->box['ade']['lordo'] ?? 0)];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function getSupplierIdentityCandidates(): array
|
|
{
|
|
$values = [];
|
|
|
|
foreach ([(string) ($this->fornitore->partita_iva ?? ''), (string) ($this->fornitore->codice_fiscale ?? '')] as $raw) {
|
|
$normalized = strtoupper(str_replace(' ', '', trim($raw)));
|
|
if ($normalized === '' || $normalized === 'ND') {
|
|
continue;
|
|
}
|
|
|
|
$values[] = $normalized;
|
|
if (str_starts_with($normalized, 'IT') && strlen($normalized) > 2) {
|
|
$values[] = substr($normalized, 2);
|
|
}
|
|
}
|
|
|
|
$values = array_values(array_unique(array_filter($values, fn(string $value): bool => $value !== '')));
|
|
sort($values, SORT_NATURAL | SORT_FLAG_CASE);
|
|
|
|
return $values;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $identityCandidates
|
|
*/
|
|
private function applySupplierIdentityFilterToFeQuery(\Illuminate\Database\Eloquent\Builder $query, array $identityCandidates): void
|
|
{
|
|
$fornitoreId = (int) $this->fornitore->id;
|
|
|
|
if ($identityCandidates === []) {
|
|
$query->where('fornitore_id', $fornitoreId);
|
|
return;
|
|
}
|
|
|
|
$query->where(function (\Illuminate\Database\Eloquent\Builder $outer) use ($identityCandidates, $fornitoreId): void {
|
|
$outer->where(function (\Illuminate\Database\Eloquent\Builder $identityQuery) use ($identityCandidates): void {
|
|
foreach ($identityCandidates as $identity) {
|
|
$identityQuery->orWhereRaw("REPLACE(UPPER(fornitore_piva), ' ', '') = ?", [$identity])
|
|
->orWhereRaw("REPLACE(UPPER(fornitore_cf), ' ', '') = ?", [$identity]);
|
|
}
|
|
})->orWhere(function (\Illuminate\Database\Eloquent\Builder $fallbackQuery) use ($fornitoreId): void {
|
|
$fallbackQuery->where('fornitore_id', $fornitoreId)
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $missingPiva): void {
|
|
$missingPiva->whereNull('fornitore_piva')
|
|
->orWhere('fornitore_piva', '')
|
|
->orWhere('fornitore_piva', 'ND');
|
|
})
|
|
->where(function (\Illuminate\Database\Eloquent\Builder $missingCf): void {
|
|
$missingCf->whereNull('fornitore_cf')
|
|
->orWhere('fornitore_cf', '')
|
|
->orWhere('fornitore_cf', 'ND');
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [];
|
|
}
|
|
}
|