631 lines
25 KiB
PHP
631 lines
25 KiB
PHP
<?php
|
|
namespace App\Filament\Pages\Fornitore;
|
|
|
|
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
|
|
use App\Filament\Pages\Gescon\FornitoreScheda;
|
|
use App\Models\Fornitore;
|
|
use App\Models\FornitoreStabileImpostazione;
|
|
use App\Models\Stabile;
|
|
use App\Models\User;
|
|
use App\Support\ArchivioPaths;
|
|
use App\Support\GoogleAccountStore;
|
|
use BackedEnum;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
|
use Livewire\WithFileUploads;
|
|
use UnitEnum;
|
|
|
|
class ImpostazioniArchivio extends Page
|
|
{
|
|
use ResolvesOperatoreContext;
|
|
use WithFileUploads;
|
|
|
|
protected static ?string $navigationLabel = 'Impostazioni';
|
|
|
|
protected static ?string $title = 'Impostazioni fornitore';
|
|
|
|
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-cog-6-tooth';
|
|
|
|
protected static UnitEnum|string|null $navigationGroup = 'Fornitore';
|
|
|
|
protected static ?int $navigationSort = 5;
|
|
|
|
protected static ?string $slug = 'fornitore/impostazioni';
|
|
|
|
protected string $view = 'filament.pages.fornitore.impostazioni-archivio';
|
|
|
|
public ?Fornitore $fornitore = null;
|
|
|
|
public bool $missingAdminContext = false;
|
|
|
|
public string $tecnorepairMdbPath = '';
|
|
|
|
public string $lastImportOutput = '';
|
|
|
|
public $tecnorepairMdbUpload;
|
|
|
|
/** @var array<int, array<string, string>> */
|
|
public array $archiveFolders = [];
|
|
|
|
/** @var array<int, array<string, string>> */
|
|
public array $moduleRows = [];
|
|
|
|
/** @var array<string, mixed>|null */
|
|
public ?array $googleWorkspaceStatus = null;
|
|
|
|
public bool $operationalConfigAvailable = false;
|
|
|
|
/** @var array<int, string> */
|
|
public array $stabileOptions = [];
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $stabileAccessRows = [];
|
|
|
|
public string $dispatchBoardNote = '';
|
|
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $preferredMaterialRows = [];
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = auth()->user();
|
|
|
|
return $user instanceof User
|
|
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'fornitore']);
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
[$fornitore] = $this->resolveOperatoreContext(allowAdminWithoutSupplier: true);
|
|
|
|
if (! $fornitore instanceof Fornitore) {
|
|
$this->missingAdminContext = true;
|
|
return;
|
|
}
|
|
|
|
$this->fornitore = $fornitore;
|
|
$this->refreshArchiveSummary();
|
|
}
|
|
|
|
public function saveUploadedTecnoRepairMdb(): void
|
|
{
|
|
if (! $this->fornitore instanceof Fornitore) {
|
|
return;
|
|
}
|
|
|
|
$upload = $this->tecnorepairMdbUpload;
|
|
if (! $upload instanceof TemporaryUploadedFile) {
|
|
Notification::make()->title('Seleziona prima un file MDB')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$extension = strtolower((string) $upload->getClientOriginalExtension());
|
|
if ($extension !== 'mdb') {
|
|
Notification::make()->title('Carica un file .mdb')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$relativeBase = $this->getArchiveRelativeBase();
|
|
if ($relativeBase === null) {
|
|
Notification::make()->title('Archivio fornitore non disponibile')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$filename = 'TecnoRepairDB-' . now()->format('Ymd-His') . '.mdb';
|
|
$stored = $upload->storeAs($relativeBase . '/tecnorepair/imports', $filename, 'local');
|
|
|
|
$this->tecnorepairMdbPath = storage_path('app/' . $stored);
|
|
$this->tecnorepairMdbUpload = null;
|
|
$this->refreshArchiveSummary();
|
|
|
|
Notification::make()->title('Archivio TecnoRepair caricato')->body($filename)->success()->send();
|
|
}
|
|
|
|
public function importTecnoRepairFromArchive(bool $dryRun = false): void
|
|
{
|
|
if (! $this->fornitore instanceof Fornitore) {
|
|
return;
|
|
}
|
|
|
|
$mdbPath = $this->resolveTecnoRepairMdbPath($this->tecnorepairMdbPath);
|
|
if ($mdbPath === null) {
|
|
Notification::make()
|
|
->title('Percorso MDB non valido')
|
|
->body('Indica un file .mdb esistente sul server oppure salva prima l\'upload nell\'archivio fornitore.')
|
|
->warning()
|
|
->send();
|
|
return;
|
|
}
|
|
|
|
$this->tecnorepairMdbPath = $mdbPath;
|
|
|
|
$adminId = (int) ($this->fornitore->amministratore_id ?? 0);
|
|
if ($adminId <= 0) {
|
|
Notification::make()->title('Amministratore del fornitore non trovato')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Artisan::call('tecnorepair:import-legacy', [
|
|
'amministratore' => $adminId,
|
|
'--mdb' => $mdbPath,
|
|
'--fornitore-id' => (int) $this->fornitore->id,
|
|
'--force-primary' => true,
|
|
'--dry-run' => $dryRun,
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
Notification::make()->title('Import TecnoRepair fallito')->body($e->getMessage())->danger()->send();
|
|
return;
|
|
}
|
|
|
|
$this->lastImportOutput = trim(Artisan::output());
|
|
$this->refreshArchiveSummary();
|
|
|
|
Notification::make()
|
|
->title($dryRun ? 'Simulazione TecnoRepair completata' : 'Import TecnoRepair completato')
|
|
->body($this->lastImportOutput !== '' ? $this->lastImportOutput : 'Operazione completata.')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
public function getSchedaFornitoreUrl(): string
|
|
{
|
|
return FornitoreScheda::getUrl(['record' => (int) ($this->fornitore?->id ?? 0)], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getTicketsUrl(): string
|
|
{
|
|
return TicketOperativi::getUrl(['fornitore' => (int) ($this->fornitore?->id ?? 0)], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getProdottiUrl(): string
|
|
{
|
|
return ProdottiCatalogo::getUrl(['fornitore' => (int) ($this->fornitore?->id ?? 0)], panel: 'admin-filament');
|
|
}
|
|
|
|
public function getGoogleConnectUrl(): string
|
|
{
|
|
return route('oauth.google.redirect', [
|
|
'account_key' => 'fornitore-' . (int) ($this->fornitore?->id ?? 0),
|
|
'label' => trim((string) ($this->fornitore?->ragione_sociale ?? '')) ?: ('Fornitore #' . (int) ($this->fornitore?->id ?? 0)),
|
|
'return_to' => request()->getRequestUri(),
|
|
]);
|
|
}
|
|
|
|
public function getGoogleDisconnectUrl(): string
|
|
{
|
|
return route('oauth.google.disconnect', [
|
|
'account_key' => 'fornitore-' . (int) ($this->fornitore?->id ?? 0),
|
|
'return_to' => request()->getRequestUri(),
|
|
]);
|
|
}
|
|
|
|
public function addStabileAccessRow(): void
|
|
{
|
|
$unusedStableId = null;
|
|
$usedIds = collect($this->stabileAccessRows)
|
|
->pluck('stabile_id')
|
|
->filter(fn($value): bool => (int) $value > 0)
|
|
->map(fn($value): int => (int) $value)
|
|
->all();
|
|
|
|
foreach (array_keys($this->stabileOptions) as $stabileId) {
|
|
if (! in_array((int) $stabileId, $usedIds, true)) {
|
|
$unusedStableId = (int) $stabileId;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->stabileAccessRows[] = [
|
|
'stabile_id' => $unusedStableId,
|
|
'chi_suonare' => '',
|
|
'contatto_nome' => '',
|
|
'contatto_telefono' => '',
|
|
'dove_sono_chiavi' => '',
|
|
'istruzioni_accesso' => '',
|
|
'fascia_reperibilita' => '',
|
|
'note_urgenza' => '',
|
|
'is_priority' => false,
|
|
];
|
|
}
|
|
|
|
public function removeStabileAccessRow(int $index): void
|
|
{
|
|
unset($this->stabileAccessRows[$index]);
|
|
$this->stabileAccessRows = array_values($this->stabileAccessRows);
|
|
}
|
|
|
|
public function addPreferredMaterialRow(): void
|
|
{
|
|
$this->preferredMaterialRows[] = [
|
|
'label' => '',
|
|
'brand_model' => '',
|
|
'barcode' => '',
|
|
'internal_code' => '',
|
|
'preferred_source' => '',
|
|
'preferred_url' => '',
|
|
'default_qty' => 1,
|
|
'last_price' => null,
|
|
'currency' => 'EUR',
|
|
'note' => '',
|
|
];
|
|
}
|
|
|
|
public function removePreferredMaterialRow(int $index): void
|
|
{
|
|
unset($this->preferredMaterialRows[$index]);
|
|
$this->preferredMaterialRows = array_values($this->preferredMaterialRows);
|
|
}
|
|
|
|
public function saveOperationalWorkflow(): void
|
|
{
|
|
if (! $this->fornitore instanceof Fornitore) {
|
|
return;
|
|
}
|
|
|
|
$normalizedAccessRows = collect($this->stabileAccessRows)
|
|
->map(fn(array $row): ?array=> $this->normalizeStabileAccessRow($row))
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
$selectedStableIds = collect($normalizedAccessRows)
|
|
->pluck('stabile_id')
|
|
->map(fn($value): int => (int) $value)
|
|
->all();
|
|
|
|
$existingSettings = FornitoreStabileImpostazione::query()
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->get()
|
|
->keyBy(fn(FornitoreStabileImpostazione $setting): int => (int) $setting->stabile_id);
|
|
|
|
foreach ($normalizedAccessRows as $row) {
|
|
$setting = $existingSettings->get((int) $row['stabile_id']) ?? new FornitoreStabileImpostazione([
|
|
'fornitore_id' => (int) $this->fornitore->id,
|
|
'stabile_id' => (int) $row['stabile_id'],
|
|
]);
|
|
|
|
$meta = is_array($setting->meta ?? null) ? $setting->meta : [];
|
|
$meta['access_operativo'] = $row;
|
|
$setting->fornitore_id = (int) $this->fornitore->id;
|
|
$setting->stabile_id = (int) $row['stabile_id'];
|
|
$setting->meta = $meta;
|
|
$setting->save();
|
|
}
|
|
|
|
foreach ($existingSettings as $stabileId => $setting) {
|
|
if (in_array((int) $stabileId, $selectedStableIds, true)) {
|
|
continue;
|
|
}
|
|
|
|
$meta = is_array($setting->meta ?? null) ? $setting->meta : [];
|
|
if (! array_key_exists('access_operativo', $meta)) {
|
|
continue;
|
|
}
|
|
|
|
unset($meta['access_operativo']);
|
|
|
|
if ($meta === [] && ! $setting->voce_spesa_default_id && ! $setting->conto_costo_default_id) {
|
|
$setting->delete();
|
|
continue;
|
|
}
|
|
|
|
$setting->meta = $meta;
|
|
$setting->save();
|
|
}
|
|
|
|
if ($this->operationalConfigAvailable) {
|
|
$config = $this->fornitore->operational_config_safe;
|
|
|
|
$dispatchBoardNote = trim($this->dispatchBoardNote);
|
|
if ($dispatchBoardNote === '') {
|
|
unset($config['dispatch_board_note']);
|
|
} else {
|
|
$config['dispatch_board_note'] = $dispatchBoardNote;
|
|
}
|
|
|
|
$config['preferred_materials'] = collect($this->preferredMaterialRows)
|
|
->map(fn(array $row): ?array=> $this->normalizePreferredMaterialRow($row))
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
$this->fornitore->operational_config = $config;
|
|
$this->fornitore->save();
|
|
}
|
|
|
|
$this->refreshArchiveSummary();
|
|
|
|
Notification::make()
|
|
->title('Workflow operativo aggiornato')
|
|
->body($this->operationalConfigAvailable
|
|
? 'Chiavi, reperibilità e memoria materiali sono stati salvati.'
|
|
: 'Chiavi e reperibilità per stabile salvate. Esegui la migrazione per attivare anche i preferiti materiali.')
|
|
->success()
|
|
->send();
|
|
}
|
|
|
|
private function refreshArchiveSummary(): void
|
|
{
|
|
$relativeBase = $this->getArchiveRelativeBase();
|
|
$absoluteBase = $relativeBase ? storage_path('app/' . $relativeBase) : null;
|
|
$this->operationalConfigAvailable = Schema::hasColumn('fornitori', 'operational_config');
|
|
|
|
if ($relativeBase !== null) {
|
|
foreach ([
|
|
'documenti',
|
|
'rubrica',
|
|
'fe',
|
|
'prodotti',
|
|
'comunicazioni',
|
|
'tecnorepair/imports',
|
|
'tecnorepair/allegati',
|
|
'temp/upload',
|
|
'temp/processing',
|
|
'logs',
|
|
] as $folder) {
|
|
Storage::disk('local')->makeDirectory($relativeBase . '/' . $folder);
|
|
}
|
|
}
|
|
|
|
$this->archiveFolders = $relativeBase === null ? [] : [
|
|
['label' => 'Base archivio', 'path' => (string) $absoluteBase],
|
|
['label' => 'Rubrica fornitore', 'path' => storage_path('app/' . $relativeBase . '/rubrica')],
|
|
['label' => 'FE fornitore', 'path' => storage_path('app/' . $relativeBase . '/fe')],
|
|
['label' => 'Prodotti e catalogo', 'path' => storage_path('app/' . $relativeBase . '/prodotti')],
|
|
['label' => 'Comunicazioni / Post-it', 'path' => storage_path('app/' . $relativeBase . '/comunicazioni')],
|
|
['label' => 'TecnoRepair import', 'path' => storage_path('app/' . $relativeBase . '/tecnorepair/imports')],
|
|
];
|
|
|
|
if ($relativeBase !== null && $this->tecnorepairMdbPath === '') {
|
|
$latest = collect(Storage::disk('local')->files($relativeBase . '/tecnorepair/imports'))
|
|
->filter(fn(string $path): bool => str_ends_with(strtolower($path), '.mdb'))
|
|
->sortDesc()
|
|
->first();
|
|
|
|
if (is_string($latest) && $latest !== '') {
|
|
$this->tecnorepairMdbPath = storage_path('app/' . $latest);
|
|
}
|
|
}
|
|
|
|
$stats = $this->fornitore?->tecnorepair_stats ?? ['total' => 0, 'open' => 0, 'closed' => 0, 'serials' => 0];
|
|
$this->googleWorkspaceStatus = $this->resolveGoogleWorkspaceStatus();
|
|
$this->loadOperationalWorkflowSettings();
|
|
$this->moduleRows = [
|
|
['module' => 'Rubrica fornitore', 'status' => 'pronto', 'note' => 'Vista fornitore filtrata ma agganciata all\'anagrafica unica.'],
|
|
['module' => 'Google Workspace', 'status' => data_get($this->googleWorkspaceStatus, 'connected', false) ? 'collegato' : 'da collegare', 'note' => 'Account dedicato al fornitore, isolato con chiave tecnica separata pur restando governato dal backend amministratore.'],
|
|
['module' => 'TecnoRepair MDB', 'status' => ((int) ($stats['total'] ?? 0) > 0) ? 'importato' : 'da importare', 'note' => 'Schede: ' . (int) ($stats['total'] ?? 0) . ' · aperte: ' . (int) ($stats['open'] ?? 0) . ' · chiuse: ' . (int) ($stats['closed'] ?? 0)],
|
|
['module' => 'Catalogo fornitore', 'status' => 'attivo', 'note' => 'Il catalogo vero resta nella pagina Prodotti.'],
|
|
['module' => 'Chiavi e reperibilità', 'status' => count($this->stabileAccessRows) > 0 ? 'configurato' : 'da configurare', 'note' => 'Istruzioni per accesso, chi suonare e dove recuperare chiavi per ciascuno stabile.'],
|
|
['module' => 'Memoria materiali', 'status' => count($this->preferredMaterialRows) > 0 ? 'attiva' : ($this->operationalConfigAvailable ? 'da compilare' : 'migrazione richiesta'), 'note' => 'Preferiti ricorrenti per lampade, ricambi e prodotti ripetuti dal fornitore.'],
|
|
['module' => 'Comunicazioni / Post-it', 'status' => 'predisposto', 'note' => 'Cartelle e contesto archivio pronti per agganciare chiamate, post-it ed email.'],
|
|
['module' => 'FE e documenti', 'status' => 'predisposto', 'note' => 'Archivio separato per FE, documenti e flussi contabili del fornitore.'],
|
|
];
|
|
}
|
|
|
|
private function loadOperationalWorkflowSettings(): void
|
|
{
|
|
if (! $this->fornitore instanceof Fornitore) {
|
|
$this->stabileOptions = [];
|
|
$this->stabileAccessRows = [];
|
|
$this->dispatchBoardNote = '';
|
|
$this->preferredMaterialRows = [];
|
|
return;
|
|
}
|
|
|
|
$adminId = (int) ($this->fornitore->amministratore_id ?? 0);
|
|
|
|
$stabili = Stabile::query()
|
|
->when($adminId > 0, fn($query) => $query->where('amministratore_id', $adminId))
|
|
->when(Schema::hasColumn('stabili', 'attivo'), fn($query) => $query->where('attivo', true))
|
|
->orderBy('codice_stabile')
|
|
->orderBy('denominazione')
|
|
->get(['id', 'codice_stabile', 'denominazione']);
|
|
|
|
$this->stabileOptions = $stabili
|
|
->mapWithKeys(fn(Stabile $stabile): array=> [
|
|
(int) $stabile->id => trim((string) ($stabile->codice_stabile ?: ('#' . $stabile->id)) . ' - ' . (string) ($stabile->denominazione ?: 'Stabile')),
|
|
])
|
|
->all();
|
|
|
|
$accessRows = FornitoreStabileImpostazione::query()
|
|
->with('stabile:id,codice_stabile,denominazione')
|
|
->where('fornitore_id', (int) $this->fornitore->id)
|
|
->when($stabili->isNotEmpty(), fn($query) => $query->whereIn('stabile_id', $stabili->pluck('id')->all()))
|
|
->get()
|
|
->map(function (FornitoreStabileImpostazione $setting): ?array {
|
|
$meta = is_array($setting->meta ?? null) ? $setting->meta : [];
|
|
$access = is_array($meta['access_operativo'] ?? null) ? $meta['access_operativo'] : null;
|
|
|
|
if (! is_array($access)) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'stabile_id' => (int) ($setting->stabile_id ?? 0),
|
|
'chi_suonare' => trim((string) ($access['chi_suonare'] ?? '')),
|
|
'contatto_nome' => trim((string) ($access['contatto_nome'] ?? '')),
|
|
'contatto_telefono' => trim((string) ($access['contatto_telefono'] ?? '')),
|
|
'dove_sono_chiavi' => trim((string) ($access['dove_sono_chiavi'] ?? '')),
|
|
'istruzioni_accesso' => trim((string) ($access['istruzioni_accesso'] ?? '')),
|
|
'fascia_reperibilita' => trim((string) ($access['fascia_reperibilita'] ?? '')),
|
|
'note_urgenza' => trim((string) ($access['note_urgenza'] ?? '')),
|
|
'is_priority' => (bool) ($access['is_priority'] ?? false),
|
|
];
|
|
})
|
|
->filter()
|
|
->sortByDesc(fn(array $row): int => $row['is_priority'] ? 1 : 0)
|
|
->values()
|
|
->all();
|
|
|
|
$this->stabileAccessRows = $accessRows;
|
|
|
|
$config = $this->operationalConfigAvailable ? $this->fornitore->operational_config_safe : [];
|
|
|
|
$this->dispatchBoardNote = trim((string) ($config['dispatch_board_note'] ?? ''));
|
|
$this->preferredMaterialRows = collect((array) ($config['preferred_materials'] ?? []))
|
|
->map(function ($row): ?array {
|
|
if (! is_array($row)) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'label' => trim((string) ($row['label'] ?? '')),
|
|
'brand_model' => trim((string) ($row['brand_model'] ?? '')),
|
|
'barcode' => trim((string) ($row['barcode'] ?? '')),
|
|
'internal_code' => trim((string) ($row['internal_code'] ?? '')),
|
|
'preferred_source' => trim((string) ($row['preferred_source'] ?? '')),
|
|
'preferred_url' => trim((string) ($row['preferred_url'] ?? '')),
|
|
'default_qty' => isset($row['default_qty']) && is_numeric($row['default_qty']) ? (float) $row['default_qty'] : 1,
|
|
'last_price' => isset($row['last_price']) && is_numeric($row['last_price']) ? (float) $row['last_price'] : null,
|
|
'currency' => trim((string) ($row['currency'] ?? 'EUR')) ?: 'EUR',
|
|
'note' => trim((string) ($row['note'] ?? '')),
|
|
];
|
|
})
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function normalizeStabileAccessRow(array $row): ?array
|
|
{
|
|
$stabileId = (int) ($row['stabile_id'] ?? 0);
|
|
if ($stabileId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$normalized = [
|
|
'stabile_id' => $stabileId,
|
|
'chi_suonare' => trim((string) ($row['chi_suonare'] ?? '')),
|
|
'contatto_nome' => trim((string) ($row['contatto_nome'] ?? '')),
|
|
'contatto_telefono' => trim((string) ($row['contatto_telefono'] ?? '')),
|
|
'dove_sono_chiavi' => trim((string) ($row['dove_sono_chiavi'] ?? '')),
|
|
'istruzioni_accesso' => trim((string) ($row['istruzioni_accesso'] ?? '')),
|
|
'fascia_reperibilita' => trim((string) ($row['fascia_reperibilita'] ?? '')),
|
|
'note_urgenza' => trim((string) ($row['note_urgenza'] ?? '')),
|
|
'is_priority' => (bool) ($row['is_priority'] ?? false),
|
|
];
|
|
|
|
$hasContent = collect($normalized)
|
|
->except(['stabile_id', 'is_priority'])
|
|
->contains(fn($value): bool => is_string($value) && $value !== '');
|
|
|
|
return $hasContent ? $normalized : null;
|
|
}
|
|
|
|
private function normalizePreferredMaterialRow(array $row): ?array
|
|
{
|
|
$label = trim((string) ($row['label'] ?? ''));
|
|
$brandModel = trim((string) ($row['brand_model'] ?? ''));
|
|
$barcode = trim((string) ($row['barcode'] ?? ''));
|
|
$internalCode = trim((string) ($row['internal_code'] ?? ''));
|
|
|
|
if ($label === '' && $brandModel === '' && $barcode === '' && $internalCode === '') {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'label' => $label,
|
|
'brand_model' => $brandModel,
|
|
'barcode' => $barcode,
|
|
'internal_code' => $internalCode,
|
|
'preferred_source' => trim((string) ($row['preferred_source'] ?? '')),
|
|
'preferred_url' => trim((string) ($row['preferred_url'] ?? '')),
|
|
'default_qty' => isset($row['default_qty']) && is_numeric($row['default_qty']) ? max(0.01, round((float) $row['default_qty'], 2)) : 1.0,
|
|
'last_price' => isset($row['last_price']) && is_numeric($row['last_price']) ? round((float) $row['last_price'], 2) : null,
|
|
'currency' => trim((string) ($row['currency'] ?? 'EUR')) ?: 'EUR',
|
|
'note' => trim((string) ($row['note'] ?? '')),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
private function resolveGoogleWorkspaceStatus(): ?array
|
|
{
|
|
$amministratore = $this->fornitore?->amministratore;
|
|
if (! $amministratore) {
|
|
return null;
|
|
}
|
|
|
|
$account = app(GoogleAccountStore::class)->get($amministratore, $this->getSupplierGoogleAccountKey());
|
|
|
|
if (! is_array($account)) {
|
|
return [
|
|
'connected' => false,
|
|
'email' => '',
|
|
'name' => '',
|
|
'connected_at' => '',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'connected' => (bool) ($account['connected'] ?? false),
|
|
'email' => (string) ($account['email'] ?? ''),
|
|
'name' => (string) ($account['name'] ?? ''),
|
|
'connected_at' => (string) ($account['connected_at'] ?? ''),
|
|
];
|
|
}
|
|
|
|
private function getSupplierGoogleAccountKey(): string
|
|
{
|
|
return 'fornitore-' . (int) ($this->fornitore?->id ?? 0);
|
|
}
|
|
|
|
private function resolveTecnoRepairMdbPath(?string $rawPath): ?string
|
|
{
|
|
$path = trim((string) $rawPath);
|
|
if ($path === '') {
|
|
return null;
|
|
}
|
|
|
|
$storageBase = str_replace('\\', '/', storage_path('app'));
|
|
$normalizedInput = str_replace('\\', '/', $path);
|
|
$candidatePaths = [];
|
|
|
|
if (is_file($path)) {
|
|
$candidatePaths[] = $path;
|
|
}
|
|
|
|
if (str_starts_with($normalizedInput, $storageBase . '/')) {
|
|
$relative = ltrim(substr($normalizedInput, strlen($storageBase)), '/');
|
|
if ($relative !== '' && Storage::disk('local')->exists($relative)) {
|
|
$candidatePaths[] = storage_path('app/' . $relative);
|
|
}
|
|
}
|
|
|
|
if (! str_starts_with($normalizedInput, '/')) {
|
|
$relative = ltrim($normalizedInput, '/');
|
|
if (Storage::disk('local')->exists($relative)) {
|
|
$candidatePaths[] = storage_path('app/' . $relative);
|
|
}
|
|
}
|
|
|
|
foreach (array_values(array_unique($candidatePaths)) as $candidate) {
|
|
$real = realpath($candidate);
|
|
if (is_string($real) && $real !== '' && is_file($real)) {
|
|
return $real;
|
|
}
|
|
|
|
if (is_file($candidate)) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getArchiveRelativeBase(): ?string
|
|
{
|
|
if (! $this->fornitore instanceof Fornitore) {
|
|
return null;
|
|
}
|
|
|
|
return ArchivioPaths::fornitoreBase($this->fornitore, $this->fornitore->amministratore);
|
|
}
|
|
}
|