Compare commits
No commits in common. "dd825b4450ff4d3b8e2bf72f0f88df6c1ce02853" and "b3e626b6137eb91b604176bd5cb46716094e1129" have entirely different histories.
dd825b4450
...
b3e626b613
|
|
@ -1,6 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Filament\Pages\Strumenti;
|
namespace App\Filament\Pages\Strumenti;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
use App\Models\MovimentoContabile;
|
use App\Models\MovimentoContabile;
|
||||||
use App\Models\Stabile;
|
use App\Models\Stabile;
|
||||||
|
|
@ -8,14 +10,13 @@
|
||||||
use App\Services\Documenti\PdfTextExtractionService;
|
use App\Services\Documenti\PdfTextExtractionService;
|
||||||
use App\Support\StabileContext;
|
use App\Support\StabileContext;
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Carbon\Carbon;
|
|
||||||
use Dompdf\Dompdf;
|
use Dompdf\Dompdf;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Forms\Components\DatePicker;
|
use Filament\Forms\Components\DatePicker;
|
||||||
use Filament\Forms\Components\FileUpload;
|
use Filament\Forms\Components\FileUpload;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\Textarea;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Pages\Page;
|
use Filament\Pages\Page;
|
||||||
use Filament\Tables\Columns\BadgeColumn;
|
use Filament\Tables\Columns\BadgeColumn;
|
||||||
|
|
@ -29,16 +30,12 @@
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
use UnitEnum;
|
use UnitEnum;
|
||||||
|
|
||||||
class DocumentiArchivio extends Page implements HasTable
|
class DocumentiArchivio extends Page implements HasTable
|
||||||
{
|
{
|
||||||
use InteractsWithTable;
|
use InteractsWithTable;
|
||||||
|
|
||||||
#[Url( as : 'tab')]
|
|
||||||
public string $hubTab = 'archivio-digitale';
|
|
||||||
|
|
||||||
protected static ?string $navigationLabel = 'Documenti (PDF)';
|
protected static ?string $navigationLabel = 'Documenti (PDF)';
|
||||||
|
|
||||||
protected static ?string $title = 'Documenti (PDF)';
|
protected static ?string $title = 'Documenti (PDF)';
|
||||||
|
|
@ -51,7 +48,7 @@ class DocumentiArchivio extends Page implements HasTable
|
||||||
|
|
||||||
protected static ?string $slug = 'strumenti/documenti';
|
protected static ?string $slug = 'strumenti/documenti';
|
||||||
|
|
||||||
protected string $view = 'filament.pages.strumenti.documenti-archivio';
|
protected string $view = 'filament.pages.gescon.table';
|
||||||
|
|
||||||
public static function canAccess(): bool
|
public static function canAccess(): bool
|
||||||
{
|
{
|
||||||
|
|
@ -62,15 +59,9 @@ public static function canAccess(): bool
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->hubTab = $this->normalizeHubTab($this->hubTab);
|
|
||||||
$this->mountInteractsWithTable();
|
$this->mountInteractsWithTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectHubTab(string $tab): void
|
|
||||||
{
|
|
||||||
$this->hubTab = $this->normalizeHubTab($tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
@ -88,8 +79,8 @@ protected function getHeaderActions(): array
|
||||||
->form([
|
->form([
|
||||||
Select::make('stabile_id')
|
Select::make('stabile_id')
|
||||||
->label('Stabile')
|
->label('Stabile')
|
||||||
->options(fn() => $this->getStabiliOptions())
|
->options(fn () => $this->getStabiliOptions())
|
||||||
->default(fn() => $this->getDefaultStabileId())
|
->default(fn () => $this->getDefaultStabileId())
|
||||||
->searchable()
|
->searchable()
|
||||||
->required(),
|
->required(),
|
||||||
TextInput::make('faldone')
|
TextInput::make('faldone')
|
||||||
|
|
@ -123,7 +114,7 @@ protected function getTableQuery(): Builder
|
||||||
|
|
||||||
$allowedIds = StabileContext::accessibleStabili($user)
|
$allowedIds = StabileContext::accessibleStabili($user)
|
||||||
->pluck('id')
|
->pluck('id')
|
||||||
->map(fn($v) => (int) $v)
|
->map(fn ($v) => (int) $v)
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
if ($allowedIds->isEmpty()) {
|
if ($allowedIds->isEmpty()) {
|
||||||
|
|
@ -150,7 +141,7 @@ public function table(Table $table): Table
|
||||||
BadgeColumn::make('tipo_documento')
|
BadgeColumn::make('tipo_documento')
|
||||||
->label('Categoria')
|
->label('Categoria')
|
||||||
->toggleable()
|
->toggleable()
|
||||||
->formatStateUsing(fn($state, Documento $record) => $state ?: ($record->tipologia ?: '—')),
|
->formatStateUsing(fn ($state, Documento $record) => $state ?: ($record->tipologia ?: '—')),
|
||||||
TextColumn::make('descrizione')
|
TextColumn::make('descrizione')
|
||||||
->label('Descrizione')
|
->label('Descrizione')
|
||||||
->limit(50)
|
->limit(50)
|
||||||
|
|
@ -189,40 +180,23 @@ public function table(Table $table): Table
|
||||||
->options($this->getCategorieOptions()),
|
->options($this->getCategorieOptions()),
|
||||||
])
|
])
|
||||||
->actions([
|
->actions([
|
||||||
Action::make('visualizza_pdf')
|
|
||||||
->label('Vedi PDF')
|
|
||||||
->icon('heroicon-o-eye')
|
|
||||||
->visible(fn(Documento $record) => $this->hasPdf($record))
|
|
||||||
->modalHeading(fn(Documento $record): string => trim((string) ($record->nome ?: 'Documento PDF')))
|
|
||||||
->modalWidth('7xl')
|
|
||||||
->modalContent(function (Documento $record) {
|
|
||||||
$openUrl = route('filament.documenti.download', $record);
|
|
||||||
|
|
||||||
return view('filament.modals.pdf-viewer', [
|
|
||||||
'url' => $openUrl . '?inline=1',
|
|
||||||
'openUrl' => $openUrl,
|
|
||||||
'title' => trim((string) ($record->nome ?: 'Documento PDF')),
|
|
||||||
]);
|
|
||||||
})
|
|
||||||
->modalSubmitAction(false)
|
|
||||||
->modalCancelActionLabel('Chiudi'),
|
|
||||||
Action::make('scarica_pdf')
|
Action::make('scarica_pdf')
|
||||||
->label('PDF')
|
->label('PDF')
|
||||||
->icon('heroicon-o-arrow-down-tray')
|
->icon('heroicon-o-arrow-down-tray')
|
||||||
->url(fn(Documento $record) => route('filament.documenti.download', $record), true)
|
->url(fn (Documento $record) => route('filament.documenti.download', $record), true)
|
||||||
->visible(fn(Documento $record) => $this->hasPdf($record)),
|
->visible(fn (Documento $record) => $this->hasPdf($record)),
|
||||||
Action::make('etichetta_11354')
|
Action::make('etichetta_11354')
|
||||||
->label('Etichetta 11354')
|
->label('Etichetta 11354')
|
||||||
->icon('heroicon-o-tag')
|
->icon('heroicon-o-tag')
|
||||||
->url(fn(Documento $record) => route('filament.documenti.etichetta', $record) . '?format=11354&dymo_fix=0&dymo_rot=0&dymo_dx=0&dymo_dy=3&autoprint=1', false),
|
->url(fn (Documento $record) => route('filament.documenti.etichetta', $record) . '?format=11354&dymo_fix=0&dymo_rot=0&dymo_dx=0&dymo_dy=3&autoprint=1', false),
|
||||||
Action::make('etichetta_11354_driver_verticale')
|
Action::make('etichetta_11354_driver_verticale')
|
||||||
->label('Etichetta 11354 (driver verticale)')
|
->label('Etichetta 11354 (driver verticale)')
|
||||||
->icon('heroicon-o-tag')
|
->icon('heroicon-o-tag')
|
||||||
->url(fn(Documento $record) => route('filament.documenti.etichetta', $record) . '?format=11354&dymo_fix=1&dymo_rot=-90&dymo_dx=0&dymo_dy=0&autoprint=1', false),
|
->url(fn (Documento $record) => route('filament.documenti.etichetta', $record) . '?format=11354&dymo_fix=1&dymo_rot=-90&dymo_dx=0&dymo_dy=0&autoprint=1', false),
|
||||||
Action::make('etichetta_99014')
|
Action::make('etichetta_99014')
|
||||||
->label('Etichetta 99014')
|
->label('Etichetta 99014')
|
||||||
->icon('heroicon-o-tag')
|
->icon('heroicon-o-tag')
|
||||||
->url(fn(Documento $record) => route('filament.documenti.etichetta', $record) . '?format=99014&autoprint=1', false),
|
->url(fn (Documento $record) => route('filament.documenti.etichetta', $record) . '?format=99014&autoprint=1', false),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,80 +213,13 @@ private function getCategorieOptions(): array
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHubStatsProperty(): array
|
|
||||||
{
|
|
||||||
$query = $this->getTableQuery();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'documenti' => (clone $query)->count(),
|
|
||||||
'archiviati' => (clone $query)->where('archiviato', true)->count(),
|
|
||||||
'in_scadenza' => (clone $query)->whereNotNull('data_scadenza')->whereDate('data_scadenza', '<=', now()->addDays(30)->toDateString())->count(),
|
|
||||||
'template_drive' => count($this->getDriveTemplateFoldersProperty()),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<int, string> */
|
|
||||||
public function getDriveTemplateFoldersProperty(): array
|
|
||||||
{
|
|
||||||
$items = config('netgescon.google.drive_template_folders', []);
|
|
||||||
$yearRoot = trim((string) config('netgescon.google.drive_template_year_root', ''));
|
|
||||||
$yearModel = trim((string) config('netgescon.google.drive_template_year_model', ''));
|
|
||||||
|
|
||||||
$folders = is_array($items)
|
|
||||||
? array_values(array_filter(array_map('strval', $items), fn(string $item) : bool => trim($item) !== ''))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if ($yearRoot !== '' && $yearModel !== '') {
|
|
||||||
foreach ($folders as $folder) {
|
|
||||||
$folders[] = $yearRoot . '/' . $yearModel . '/' . $folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_values(array_unique($folders));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<int, string> */
|
|
||||||
public function getAudienceGroupsProperty(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'Condomini',
|
|
||||||
'Inquilini',
|
|
||||||
'Fornitori',
|
|
||||||
'Collaboratori interni',
|
|
||||||
'Amministratore',
|
|
||||||
'Studio completo',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<int, string> */
|
|
||||||
public function getArchivioFisicoTypesProperty(): array
|
|
||||||
{
|
|
||||||
return ['Faldone', 'Scatola', 'Cartella', 'Busta'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMnemonicCodeHintProperty(): ?string
|
|
||||||
{
|
|
||||||
$stabile = $this->resolveActiveStabile();
|
|
||||||
if (! $stabile instanceof Stabile) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$label = Str::upper((string) ($stabile->denominazione ?? 'STABILE'));
|
|
||||||
$letters = preg_replace('/[^A-Z]/', '', Str::ascii($label)) ?: 'STB';
|
|
||||||
$digits = preg_replace('/\D/', '', $label);
|
|
||||||
$prefix = str_pad(substr($letters, 0, 3), 3, 'X');
|
|
||||||
$suffix = str_pad(substr($digits !== '' ? $digits : (string) $stabile->id, -5), 5, '0', STR_PAD_LEFT);
|
|
||||||
|
|
||||||
return $prefix . $suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getDocumentoFormSchema(): array
|
private function getDocumentoFormSchema(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Select::make('stabile_id')
|
Select::make('stabile_id')
|
||||||
->label('Stabile')
|
->label('Stabile')
|
||||||
->options(fn() => $this->getStabiliOptions())
|
->options(fn () => $this->getStabiliOptions())
|
||||||
->default(fn() => $this->getDefaultStabileId())
|
->default(fn () => $this->getDefaultStabileId())
|
||||||
->searchable()
|
->searchable()
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('categoria')
|
Select::make('categoria')
|
||||||
|
|
@ -344,7 +251,7 @@ private function getDocumentoFormSchema(): array
|
||||||
->nullable(),
|
->nullable(),
|
||||||
Select::make('movimento_contabile_id')
|
Select::make('movimento_contabile_id')
|
||||||
->label('Movimento contabile (opzionale)')
|
->label('Movimento contabile (opzionale)')
|
||||||
->options(fn(callable $get) => $this->getMovimentiOptions((int) ($get('stabile_id') ?: 0)))
|
->options(fn (callable $get) => $this->getMovimentiOptions((int) ($get('stabile_id') ?: 0)))
|
||||||
->searchable()
|
->searchable()
|
||||||
->nullable(),
|
->nullable(),
|
||||||
TextInput::make('faldone')
|
TextInput::make('faldone')
|
||||||
|
|
@ -372,15 +279,15 @@ private function getStabiliOptions(): array
|
||||||
->orderBy('denominazione')
|
->orderBy('denominazione')
|
||||||
->limit(500)
|
->limit(500)
|
||||||
->get(['id', 'denominazione'])
|
->get(['id', 'denominazione'])
|
||||||
->mapWithKeys(fn(Stabile $s) => [(string) $s->id => (($s->denominazione ?: ('Stabile #' . $s->id)))])
|
->mapWithKeys(fn (Stabile $s) => [(string) $s->id => (($s->denominazione ?: ('Stabile #' . $s->id)))])
|
||||||
->all();
|
->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowed = StabileContext::accessibleStabili($user);
|
$allowed = StabileContext::accessibleStabili($user);
|
||||||
return $allowed
|
return $allowed
|
||||||
->sortBy(fn(Stabile $s) => $s->denominazione)
|
->sortBy(fn (Stabile $s) => $s->denominazione)
|
||||||
->take(500)
|
->take(500)
|
||||||
->mapWithKeys(fn(Stabile $s) => [(string) $s->id => (($s->denominazione ?: ('Stabile #' . $s->id)))])
|
->mapWithKeys(fn (Stabile $s) => [(string) $s->id => (($s->denominazione ?: ('Stabile #' . $s->id)))])
|
||||||
->all();
|
->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,23 +306,6 @@ private function getDefaultStabileId(): ?int
|
||||||
return StabileContext::accessibleStabili($user)->first()?->id;
|
return StabileContext::accessibleStabili($user)->first()?->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeHubTab(string $tab): string
|
|
||||||
{
|
|
||||||
$allowed = ['archivio-digitale', 'template-drive', 'archivio-fisico', 'permessi'];
|
|
||||||
|
|
||||||
return in_array($tab, $allowed, true) ? $tab : 'archivio-digitale';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolveActiveStabile(): ?Stabile
|
|
||||||
{
|
|
||||||
$user = Auth::user();
|
|
||||||
if (! $user instanceof User) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return StabileContext::getActiveStabile($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getMovimentiOptions(int $stabileId): array
|
private function getMovimentiOptions(int $stabileId): array
|
||||||
{
|
{
|
||||||
if ($stabileId <= 0) {
|
if ($stabileId <= 0) {
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace App\Http\Controllers\Filament;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\DocumentoStabile;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class DocumentoStabileDownloadController extends Controller
|
|
||||||
{
|
|
||||||
public function download(Request $request, DocumentoStabile $documentoStabile)
|
|
||||||
{
|
|
||||||
$path = is_string($documentoStabile->percorso_file) ? trim((string) $documentoStabile->percorso_file) : '';
|
|
||||||
if ($path === '') {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$selectedDisk = null;
|
|
||||||
if (Storage::disk('public')->exists($path)) {
|
|
||||||
$selectedDisk = 'public';
|
|
||||||
} elseif (Storage::disk('local')->exists($path)) {
|
|
||||||
$selectedDisk = 'local';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $selectedDisk) {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$filename = is_string($documentoStabile->nome_originale) && trim((string) $documentoStabile->nome_originale) !== ''
|
|
||||||
? trim((string) $documentoStabile->nome_originale)
|
|
||||||
: ((is_string($documentoStabile->nome_file) && trim((string) $documentoStabile->nome_file) !== '')
|
|
||||||
? trim((string) $documentoStabile->nome_file)
|
|
||||||
: ('documento-stabile-' . (int) $documentoStabile->id . '.pdf'));
|
|
||||||
|
|
||||||
$filename = str_replace(["\r", "\n", '"'], '', $filename);
|
|
||||||
|
|
||||||
$documentoStabile->incrementaDownload();
|
|
||||||
|
|
||||||
if ($request->boolean('inline')) {
|
|
||||||
return response()->file(Storage::disk($selectedDisk)->path($path), [
|
|
||||||
'Content-Disposition' => 'inline; filename="' . $filename . '"',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Storage::disk($selectedDisk)->download($path, $filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
@ -28,7 +29,7 @@ class DocumentoStabile extends Model
|
||||||
'versione',
|
'versione',
|
||||||
'downloads',
|
'downloads',
|
||||||
'ultimo_accesso',
|
'ultimo_accesso',
|
||||||
'caricato_da',
|
'caricato_da'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|
@ -38,14 +39,14 @@ class DocumentoStabile extends Model
|
||||||
'downloads' => 'integer',
|
'downloads' => 'integer',
|
||||||
'versione' => 'integer',
|
'versione' => 'integer',
|
||||||
'dimensione' => 'integer',
|
'dimensione' => 'integer',
|
||||||
'ultimo_accesso' => 'datetime',
|
'ultimo_accesso' => 'datetime'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $dates = [
|
protected $dates = [
|
||||||
'data_scadenza',
|
'data_scadenza',
|
||||||
'ultimo_accesso',
|
'ultimo_accesso',
|
||||||
'created_at',
|
'created_at',
|
||||||
'updated_at',
|
'updated_at'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -105,9 +106,7 @@ public function scopeScaduti($query)
|
||||||
public function getDimensioneFormattataAttribute()
|
public function getDimensioneFormattataAttribute()
|
||||||
{
|
{
|
||||||
$bytes = $this->dimensione;
|
$bytes = $this->dimensione;
|
||||||
if ($bytes === 0) {
|
if ($bytes === 0) return '0 Bytes';
|
||||||
return '0 Bytes';
|
|
||||||
}
|
|
||||||
|
|
||||||
$k = 1024;
|
$k = 1024;
|
||||||
$sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
$sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
|
@ -147,7 +146,7 @@ public function getIconaFileAttribute()
|
||||||
*/
|
*/
|
||||||
public function getStatoScadenzaAttribute()
|
public function getStatoScadenzaAttribute()
|
||||||
{
|
{
|
||||||
if (! $this->data_scadenza) {
|
if (!$this->data_scadenza) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,11 +170,11 @@ public function getStatoScadenzaAttribute()
|
||||||
*/
|
*/
|
||||||
public function getUrlDownloadAttribute()
|
public function getUrlDownloadAttribute()
|
||||||
{
|
{
|
||||||
if (! $this->fileEsiste()) {
|
if (is_string($this->percorso_file) && trim($this->percorso_file) !== '' && Storage::disk('public')->exists($this->percorso_file)) {
|
||||||
return null;
|
return Storage::disk('public')->url($this->percorso_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return route('filament.documenti-stabili.download', $this);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,13 +182,7 @@ public function getUrlDownloadAttribute()
|
||||||
*/
|
*/
|
||||||
public function getUrlViewAttribute()
|
public function getUrlViewAttribute()
|
||||||
{
|
{
|
||||||
$downloadUrl = $this->url_download;
|
return $this->url_download;
|
||||||
|
|
||||||
if (! is_string($downloadUrl) || trim($downloadUrl) === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $downloadUrl . '?inline=1';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -244,7 +237,7 @@ public static function categorie()
|
||||||
'bancari' => 'Documenti Bancari',
|
'bancari' => 'Documenti Bancari',
|
||||||
'legali' => 'Documenti Legali',
|
'legali' => 'Documenti Legali',
|
||||||
'assemblee' => 'Verbali Assemblee',
|
'assemblee' => 'Verbali Assemblee',
|
||||||
'altri' => 'Altri',
|
'altri' => 'Altri'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +254,7 @@ public static function coloriCategorie()
|
||||||
'bancari' => 'secondary',
|
'bancari' => 'secondary',
|
||||||
'legali' => 'danger',
|
'legali' => 'danger',
|
||||||
'assemblee' => 'dark',
|
'assemblee' => 'dark',
|
||||||
'altri' => 'light',
|
'altri' => 'light'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Support\Modules\ModuleManifest;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ public function __construct()
|
||||||
*/
|
*/
|
||||||
protected function loadActiveModules()
|
protected function loadActiveModules()
|
||||||
{
|
{
|
||||||
if (! $this->modulesTableExists()) {
|
if (!$this->modulesTableExists()) {
|
||||||
$this->activeModules = [];
|
$this->activeModules = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +61,7 @@ public function installModule($zipPath, $validateLicense = true)
|
||||||
$config = $this->validateModuleConfig($extractPath);
|
$config = $this->validateModuleConfig($extractPath);
|
||||||
|
|
||||||
// 2. Verifica licenza (se richiesta)
|
// 2. Verifica licenza (se richiesta)
|
||||||
if ($validateLicense && ! $this->validateLicense($config)) {
|
if ($validateLicense && !$this->validateLicense($config)) {
|
||||||
throw new \Exception("Licenza non valida per il modulo {$config['name']}");
|
throw new \Exception("Licenza non valida per il modulo {$config['name']}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ public function installModule($zipPath, $validateLicense = true)
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => "Modulo {$config['display_name']} installato con successo",
|
'message' => "Modulo {$config['display_name']} installato con successo",
|
||||||
'module' => $config,
|
'module' => $config
|
||||||
];
|
];
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error("Errore installazione modulo: " . $e->getMessage());
|
Log::error("Errore installazione modulo: " . $e->getMessage());
|
||||||
|
|
@ -108,7 +109,7 @@ public function installModule($zipPath, $validateLicense = true)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +126,12 @@ public function uninstallModule($moduleName, $keepData = false)
|
||||||
|
|
||||||
// 1. Controlla dipendenze inverse
|
// 1. Controlla dipendenze inverse
|
||||||
$dependentModules = $this->getDependentModules($moduleName);
|
$dependentModules = $this->getDependentModules($moduleName);
|
||||||
if (! empty($dependentModules)) {
|
if (!empty($dependentModules)) {
|
||||||
throw new \Exception("Impossibile disinstallare: moduli dipendenti attivi: " . implode(', ', $dependentModules));
|
throw new \Exception("Impossibile disinstallare: moduli dipendenti attivi: " . implode(', ', $dependentModules));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Backup prima della disinstallazione
|
// 2. Backup prima della disinstallazione
|
||||||
if (! $keepData) {
|
if (!$keepData) {
|
||||||
$this->createBackup($moduleName);
|
$this->createBackup($moduleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +148,7 @@ public function uninstallModule($moduleName, $keepData = false)
|
||||||
$this->removeMenuItems($config);
|
$this->removeMenuItems($config);
|
||||||
|
|
||||||
// 7. Esegui migrazioni rollback (se non si tengono i dati)
|
// 7. Esegui migrazioni rollback (se non si tengono i dati)
|
||||||
if (! $keepData) {
|
if (!$keepData) {
|
||||||
$this->rollbackMigrations($moduleName);
|
$this->rollbackMigrations($moduleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,14 +168,14 @@ public function uninstallModule($moduleName, $keepData = false)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => "Modulo {$moduleName} disinstallato con successo",
|
'message' => "Modulo {$moduleName} disinstallato con successo"
|
||||||
];
|
];
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error("Errore disinstallazione modulo: " . $e->getMessage());
|
Log::error("Errore disinstallazione modulo: " . $e->getMessage());
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +198,7 @@ public function updateModule($moduleName, $zipPath)
|
||||||
$newConfig = $this->validateModuleConfig($extractPath);
|
$newConfig = $this->validateModuleConfig($extractPath);
|
||||||
|
|
||||||
// 3. Verifica compatibilità versioni
|
// 3. Verifica compatibilità versioni
|
||||||
if (! $this->isCompatibleVersion($oldConfig, $newConfig)) {
|
if (!$this->isCompatibleVersion($oldConfig, $newConfig)) {
|
||||||
throw new \Exception("Versione non compatibile");
|
throw new \Exception("Versione non compatibile");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,7 +226,7 @@ public function updateModule($moduleName, $zipPath)
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => "Modulo {$moduleName} aggiornato alla versione {$newConfig['version']}",
|
'message' => "Modulo {$moduleName} aggiornato alla versione {$newConfig['version']}",
|
||||||
'old_version' => $oldConfig['version'],
|
'old_version' => $oldConfig['version'],
|
||||||
'new_version' => $newConfig['version'],
|
'new_version' => $newConfig['version']
|
||||||
];
|
];
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error("Errore aggiornamento modulo: " . $e->getMessage());
|
Log::error("Errore aggiornamento modulo: " . $e->getMessage());
|
||||||
|
|
@ -237,7 +238,7 @@ public function updateModule($moduleName, $zipPath)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -257,45 +258,16 @@ public function getActiveModules()
|
||||||
*/
|
*/
|
||||||
public function getAllModules()
|
public function getAllModules()
|
||||||
{
|
{
|
||||||
if (! $this->modulesTableExists()) {
|
if (!$this->modulesTableExists()) {
|
||||||
return collect();
|
return collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::table('modules')->get()->map(function ($module) {
|
return DB::table('modules')->get()->map(function ($module) {
|
||||||
$config = json_decode($module->config, true);
|
$module->config = json_decode($module->config, true);
|
||||||
$module->config = is_array($config) ? ModuleManifest::normalize($config) : [];
|
|
||||||
return $module;
|
return $module;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ottieni il catalogo locale dei moduli con manifest normalizzato.
|
|
||||||
*/
|
|
||||||
public function discoverLocalModules()
|
|
||||||
{
|
|
||||||
if (! File::isDirectory($this->modulesPath)) {
|
|
||||||
return collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return collect(File::directories($this->modulesPath))
|
|
||||||
->map(function (string $modulePath) {
|
|
||||||
$moduleName = basename($modulePath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (object) $this->getModuleConfig($moduleName);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
Log::warning('ModuleManager: manifest locale non leggibile', [
|
|
||||||
'module' => $moduleName,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
->filter()
|
|
||||||
->values();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifica licenza modulo (connessione server licenze)
|
* Verifica licenza modulo (connessione server licenze)
|
||||||
*/
|
*/
|
||||||
|
|
@ -321,13 +293,13 @@ public function validateLicense($config)
|
||||||
*/
|
*/
|
||||||
public function toggleModule($moduleName)
|
public function toggleModule($moduleName)
|
||||||
{
|
{
|
||||||
if (! $this->modulesTableExists()) {
|
if (!$this->modulesTableExists()) {
|
||||||
throw new \Exception('Tabella modules non disponibile');
|
throw new \Exception('Tabella modules non disponibile');
|
||||||
}
|
}
|
||||||
|
|
||||||
$module = DB::table('modules')->where('name', $moduleName)->first();
|
$module = DB::table('modules')->where('name', $moduleName)->first();
|
||||||
|
|
||||||
if (! $module) {
|
if (!$module) {
|
||||||
throw new \Exception("Modulo {$moduleName} non trovato");
|
throw new \Exception("Modulo {$moduleName} non trovato");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +307,7 @@ public function toggleModule($moduleName)
|
||||||
|
|
||||||
DB::table('modules')->where('name', $moduleName)->update([
|
DB::table('modules')->where('name', $moduleName)->update([
|
||||||
'status' => $newStatus,
|
'status' => $newStatus,
|
||||||
'updated_at' => now(),
|
'updated_at' => now()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->clearModuleCache();
|
$this->clearModuleCache();
|
||||||
|
|
@ -352,7 +324,7 @@ private function extractModule($zipPath)
|
||||||
$zip = new ZipArchive;
|
$zip = new ZipArchive;
|
||||||
$extractPath = storage_path('app/modules/temp/' . uniqid());
|
$extractPath = storage_path('app/modules/temp/' . uniqid());
|
||||||
|
|
||||||
if ($zip->open($zipPath) === true) {
|
if ($zip->open($zipPath) === TRUE) {
|
||||||
$zip->extractTo($extractPath);
|
$zip->extractTo($extractPath);
|
||||||
$zip->close();
|
$zip->close();
|
||||||
return $extractPath;
|
return $extractPath;
|
||||||
|
|
@ -365,27 +337,27 @@ private function validateModuleConfig($extractPath)
|
||||||
{
|
{
|
||||||
$configPath = $extractPath . '/module.json';
|
$configPath = $extractPath . '/module.json';
|
||||||
|
|
||||||
if (! File::exists($configPath)) {
|
if (!File::exists($configPath)) {
|
||||||
throw new \Exception("File module.json non trovato");
|
throw new \Exception("File module.json non trovato");
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = json_decode(File::get($configPath), true);
|
$config = json_decode(File::get($configPath), true);
|
||||||
|
|
||||||
if (! $config || ! isset($config['name'], $config['version'])) {
|
if (!$config || !isset($config['name'], $config['version'])) {
|
||||||
throw new \Exception("Configurazione modulo non valida");
|
throw new \Exception("Configurazione modulo non valida");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ModuleManifest::normalize($config);
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkDependencies($config)
|
private function checkDependencies($config)
|
||||||
{
|
{
|
||||||
if (! isset($config['dependencies'])) {
|
if (!isset($config['dependencies'])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($config['dependencies'] as $dependency) {
|
foreach ($config['dependencies'] as $dependency) {
|
||||||
if (! in_array($dependency, $this->activeModules)) {
|
if (!in_array($dependency, $this->activeModules)) {
|
||||||
throw new \Exception("Dipendenza mancante: {$dependency}");
|
throw new \Exception("Dipendenza mancante: {$dependency}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -395,22 +367,20 @@ private function checkDependencies($config)
|
||||||
|
|
||||||
private function registerModule($config)
|
private function registerModule($config)
|
||||||
{
|
{
|
||||||
if (! $this->modulesTableExists()) {
|
if (!$this->modulesTableExists()) {
|
||||||
throw new \Exception('Tabella modules non disponibile');
|
throw new \Exception('Tabella modules non disponibile');
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = ModuleManifest::normalize(is_array($config) ? $config : []);
|
|
||||||
|
|
||||||
DB::table('modules')->updateOrInsert(
|
DB::table('modules')->updateOrInsert(
|
||||||
['name' => $config['name']],
|
['name' => $config['name']],
|
||||||
[
|
[
|
||||||
'name' => $config['name'],
|
'name' => $config['name'],
|
||||||
'display_name' => $config['display_name'] ?? $config['name'],
|
'display_name' => $config['display_name'],
|
||||||
'version' => $config['version'],
|
'version' => $config['version'],
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'config' => json_encode($config),
|
'config' => json_encode($config),
|
||||||
'installed_at' => now(),
|
'installed_at' => now(),
|
||||||
'updated_at' => now(),
|
'updated_at' => now()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -429,16 +399,11 @@ private function getModuleConfig($moduleName)
|
||||||
{
|
{
|
||||||
$configPath = $this->modulesPath . "/{$moduleName}/module.json";
|
$configPath = $this->modulesPath . "/{$moduleName}/module.json";
|
||||||
|
|
||||||
if (! File::exists($configPath)) {
|
if (!File::exists($configPath)) {
|
||||||
throw new \Exception("Configurazione modulo {$moduleName} non trovata");
|
throw new \Exception("Configurazione modulo {$moduleName} non trovata");
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = json_decode(File::get($configPath), true);
|
return json_decode(File::get($configPath), true);
|
||||||
if (! is_array($config)) {
|
|
||||||
throw new \Exception("Configurazione modulo {$moduleName} non valida");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ModuleManifest::normalize($config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getModuleInfo($moduleName)
|
private function getModuleInfo($moduleName)
|
||||||
|
|
@ -460,7 +425,7 @@ private function getModuleInfo($moduleName)
|
||||||
return (object) array_merge($config, [
|
return (object) array_merge($config, [
|
||||||
'status' => $dbInfo->status ?? 'unknown',
|
'status' => $dbInfo->status ?? 'unknown',
|
||||||
'installed_at' => $dbInfo->installed_at ?? null,
|
'installed_at' => $dbInfo->installed_at ?? null,
|
||||||
'updated_at' => $dbInfo->updated_at ?? null,
|
'updated_at' => $dbInfo->updated_at ?? null
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,9 +451,7 @@ private function registerRoutes($moduleName)
|
||||||
}
|
}
|
||||||
private function installPermissions($config)
|
private function installPermissions($config)
|
||||||
{
|
{
|
||||||
$config = ModuleManifest::normalize(is_array($config) ? $config : []);
|
if (!isset($config['permissions']) || empty($config['permissions'])) {
|
||||||
|
|
||||||
if (! isset($config['permissions']) || empty($config['permissions'])) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,7 +463,7 @@ private function installPermissions($config)
|
||||||
// Crea il permesso se non esiste
|
// Crea il permesso se non esiste
|
||||||
$existingPermission = \Spatie\Permission\Models\Permission::where('name', $permission['name'])->first();
|
$existingPermission = \Spatie\Permission\Models\Permission::where('name', $permission['name'])->first();
|
||||||
|
|
||||||
if (! $existingPermission) {
|
if (!$existingPermission) {
|
||||||
$newPermission = \Spatie\Permission\Models\Permission::create([
|
$newPermission = \Spatie\Permission\Models\Permission::create([
|
||||||
'name' => $permission['name'],
|
'name' => $permission['name'],
|
||||||
'guard_name' => $permission['guard_name'] ?? 'web',
|
'guard_name' => $permission['guard_name'] ?? 'web',
|
||||||
|
|
@ -539,7 +502,7 @@ private function assignPermissionsToRoles($permissions)
|
||||||
|
|
||||||
if ($role) {
|
if ($role) {
|
||||||
// Assegna il permesso al ruolo se non ce l'ha già
|
// Assegna il permesso al ruolo se non ce l'ha già
|
||||||
if (! $role->hasPermissionTo($permName)) {
|
if (!$role->hasPermissionTo($permName)) {
|
||||||
$role->givePermissionTo($permName);
|
$role->givePermissionTo($permName);
|
||||||
Log::info("Permesso {$permName} assegnato al ruolo {$roleName}");
|
Log::info("Permesso {$permName} assegnato al ruolo {$roleName}");
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +513,7 @@ private function assignPermissionsToRoles($permissions)
|
||||||
|
|
||||||
private function uninstallPermissions($config)
|
private function uninstallPermissions($config)
|
||||||
{
|
{
|
||||||
if (! isset($config['permissions']) || empty($config['permissions'])) {
|
if (!isset($config['permissions']) || empty($config['permissions'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace App\Support\Modules;
|
|
||||||
|
|
||||||
final class ModuleManifest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $manifest
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public static function normalize(array $manifest): array
|
|
||||||
{
|
|
||||||
$normalized = $manifest;
|
|
||||||
|
|
||||||
$name = trim((string) ($manifest['name'] ?? ''));
|
|
||||||
$normalized['name'] = $name;
|
|
||||||
$normalized['display_name'] = self::normalizeNullableString($manifest['display_name'] ?? null) ?? self::normalizeNullableString($manifest['title'] ?? null) ?? $name;
|
|
||||||
$normalized['description'] = self::normalizeNullableString($manifest['description'] ?? null);
|
|
||||||
$normalized['version'] = self::normalizeNullableString($manifest['version'] ?? null) ?? '0.0.0';
|
|
||||||
$normalized['author'] = self::normalizeNullableString($manifest['author'] ?? null);
|
|
||||||
$normalized['license'] = self::normalizeNullableString($manifest['license'] ?? null) ?? 'free';
|
|
||||||
$normalized['category'] = self::normalizeNullableString($manifest['category'] ?? null);
|
|
||||||
$normalized['dependencies'] = self::normalizeStringList($manifest['dependencies'] ?? []);
|
|
||||||
$normalized['optional_dependencies'] = self::normalizeStringList($manifest['optional_dependencies'] ?? []);
|
|
||||||
$normalized['hooks'] = self::normalizeStringList($manifest['hooks'] ?? []);
|
|
||||||
$normalized['features'] = self::normalizeStringList($manifest['features'] ?? []);
|
|
||||||
$normalized['permissions'] = self::normalizePermissions($manifest['permissions'] ?? []);
|
|
||||||
$normalized['menu_items'] = self::normalizeMenuItems($manifest['menu_items'] ?? []);
|
|
||||||
$normalized['requirements'] = is_array($manifest['requirements'] ?? null) ? $manifest['requirements'] : [];
|
|
||||||
$normalized['config'] = is_array($manifest['config'] ?? null) ? $manifest['config'] : [];
|
|
||||||
$normalized['manifest_version'] = (int) ($manifest['manifest_version'] ?? 1);
|
|
||||||
|
|
||||||
return $normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $value
|
|
||||||
*/
|
|
||||||
private static function normalizeNullableString(mixed $value): ?string
|
|
||||||
{
|
|
||||||
if (! is_scalar($value)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$normalized = trim((string) $value);
|
|
||||||
|
|
||||||
return $normalized !== '' ? $normalized : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $value
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
private static function normalizeStringList(mixed $value): array
|
|
||||||
{
|
|
||||||
if (! is_array($value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$normalized = [];
|
|
||||||
foreach ($value as $item) {
|
|
||||||
$item = self::normalizeNullableString($item);
|
|
||||||
if ($item !== null) {
|
|
||||||
$normalized[] = $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_values(array_unique($normalized));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $value
|
|
||||||
* @return array<int, array{name:string,display_name:string,description:?string,guard_name:string}>
|
|
||||||
*/
|
|
||||||
private static function normalizePermissions(mixed $value): array
|
|
||||||
{
|
|
||||||
if (! is_array($value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$permissions = [];
|
|
||||||
|
|
||||||
if (self::isAssociative($value)) {
|
|
||||||
foreach ($value as $name => $description) {
|
|
||||||
$permissionName = self::normalizeNullableString($name);
|
|
||||||
if ($permissionName === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$display = self::normalizeNullableString($description) ?? self::humanizePermissionName($permissionName);
|
|
||||||
|
|
||||||
$permissions[] = [
|
|
||||||
'name' => $permissionName,
|
|
||||||
'display_name' => $display,
|
|
||||||
'description' => self::normalizeNullableString($description) ?? $display,
|
|
||||||
'guard_name' => 'web',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($value as $permission) {
|
|
||||||
if (is_string($permission)) {
|
|
||||||
$permissionName = self::normalizeNullableString($permission);
|
|
||||||
if ($permissionName === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$permissions[] = [
|
|
||||||
'name' => $permissionName,
|
|
||||||
'display_name' => self::humanizePermissionName($permissionName),
|
|
||||||
'description' => null,
|
|
||||||
'guard_name' => 'web',
|
|
||||||
];
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_array($permission)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$permissionName = self::normalizeNullableString($permission['name'] ?? null);
|
|
||||||
if ($permissionName === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$permissions[] = [
|
|
||||||
'name' => $permissionName,
|
|
||||||
'display_name' => self::normalizeNullableString($permission['display_name'] ?? null) ?? self::humanizePermissionName($permissionName),
|
|
||||||
'description' => self::normalizeNullableString($permission['description'] ?? null),
|
|
||||||
'guard_name' => self::normalizeNullableString($permission['guard_name'] ?? null) ?? 'web',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $value
|
|
||||||
* @return array<int, array<string, mixed>>
|
|
||||||
*/
|
|
||||||
private static function normalizeMenuItems(mixed $value): array
|
|
||||||
{
|
|
||||||
if (! is_array($value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$menuItems = [];
|
|
||||||
foreach ($value as $item) {
|
|
||||||
if (! is_array($item)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$title = self::normalizeNullableString($item['title'] ?? null);
|
|
||||||
$route = self::normalizeNullableString($item['route'] ?? null);
|
|
||||||
if ($title === null || $route === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$menuItems[] = [
|
|
||||||
'title' => $title,
|
|
||||||
'route' => $route,
|
|
||||||
'icon' => self::normalizeNullableString($item['icon'] ?? null),
|
|
||||||
'permission' => self::normalizeNullableString($item['permission'] ?? null),
|
|
||||||
'parent' => self::normalizeNullableString($item['parent'] ?? null),
|
|
||||||
'description' => self::normalizeNullableString($item['description'] ?? null),
|
|
||||||
'order' => isset($item['order']) && is_numeric($item['order']) ? (int) $item['order'] : 0,
|
|
||||||
'badge' => $item['badge'] ?? null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
usort($menuItems, static fn(array $left, array $right): int => ($left['order'] ?? 0) <=> ($right['order'] ?? 0));
|
|
||||||
|
|
||||||
return $menuItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<mixed> $value
|
|
||||||
*/
|
|
||||||
private static function isAssociative(array $value): bool
|
|
||||||
{
|
|
||||||
return array_keys($value) !== range(0, count($value) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function humanizePermissionName(string $permissionName): string
|
|
||||||
{
|
|
||||||
$label = str_replace(['.', '_', '-'], ' ', $permissionName);
|
|
||||||
|
|
||||||
return ucfirst(trim(preg_replace('/\s+/', ' ', $label) ?? $label));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
<div class="-m-6 space-y-3 p-2">
|
<div class="-m-6 space-y-3 p-2">
|
||||||
@php
|
|
||||||
$title = isset($title) && is_string($title) && trim($title) !== '' ? trim($title) : 'Documento PDF';
|
|
||||||
$openUrl = isset($openUrl) && is_string($openUrl) && trim($openUrl) !== '' ? trim($openUrl) : $url;
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-2 px-4 pt-4">
|
<div class="flex items-center justify-end gap-2 px-4 pt-4">
|
||||||
<div class="mr-auto pr-4 text-sm font-semibold text-slate-800">{{ $title }}</div>
|
|
||||||
<a
|
<a
|
||||||
href="{{ $openUrl }}"
|
href="{{ $url }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
class="inline-flex items-center rounded-md bg-slate-800 px-3 py-1.5 text-xs font-medium text-white hover:bg-slate-700"
|
class="inline-flex items-center rounded-md bg-slate-800 px-3 py-1.5 text-xs font-medium text-white hover:bg-slate-700"
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
<x-filament-panels::page>
|
|
||||||
@php
|
|
||||||
$tabs = [
|
|
||||||
'archivio-digitale' => 'Archivio digitale',
|
|
||||||
'template-drive' => 'Template Drive',
|
|
||||||
'archivio-fisico' => 'Archivio fisico',
|
|
||||||
'permessi' => 'Permessi',
|
|
||||||
];
|
|
||||||
$stats = $this->hubStats;
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div class="rounded-2xl border border-slate-200 bg-gradient-to-r from-amber-50 via-white to-sky-50 p-5">
|
|
||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-semibold uppercase tracking-[0.18em] text-slate-500">Hub Documenti</div>
|
|
||||||
<h2 class="mt-2 text-2xl font-semibold text-slate-900">Archivio digitale e fisico nello stesso flusso</h2>
|
|
||||||
<p class="mt-2 max-w-3xl text-sm text-slate-600">Questa pagina diventa la base unica per PDF, struttura Drive, contenitori fisici e regole di visibilita. Il modello resta coerente con il passaggio consegne dello stabile e con la futura gestione studio.</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-xl border border-white/70 bg-white/80 px-4 py-3 text-right shadow-sm">
|
|
||||||
<div class="text-xs uppercase tracking-wide text-slate-500">Codice mnemonico suggerito</div>
|
|
||||||
<div class="mt-1 text-xl font-semibold text-slate-900">{{ $this->mnemonicCodeHint ?? 'STB00000' }}</div>
|
|
||||||
<div class="mt-1 text-xs text-slate-500">Formato guida per etichette, QR e archivio fisico.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
|
||||||
<div class="text-xs uppercase tracking-wide text-slate-500">Documenti</div>
|
|
||||||
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ $stats['documenti'] ?? 0 }}</div>
|
|
||||||
<div class="mt-1 text-sm text-slate-500">Archivio PDF censito.</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
|
||||||
<div class="text-xs uppercase tracking-wide text-slate-500">Archiviati</div>
|
|
||||||
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ $stats['archiviati'] ?? 0 }}</div>
|
|
||||||
<div class="mt-1 text-sm text-slate-500">Documenti chiusi e consolidati.</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
|
||||||
<div class="text-xs uppercase tracking-wide text-slate-500">In scadenza</div>
|
|
||||||
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ $stats['in_scadenza'] ?? 0 }}</div>
|
|
||||||
<div class="mt-1 text-sm text-slate-500">Finestra dei prossimi 30 giorni.</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
|
||||||
<div class="text-xs uppercase tracking-wide text-slate-500">Template Drive</div>
|
|
||||||
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ $stats['template_drive'] ?? 0 }}</div>
|
|
||||||
<div class="mt-1 text-sm text-slate-500">Cartelle base replicabili per stabile.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
@foreach($tabs as $tabKey => $tabLabel)
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
wire:click="selectHubTab('{{ $tabKey }}')"
|
|
||||||
@class([
|
|
||||||
'rounded-full px-4 py-2 text-sm font-medium transition',
|
|
||||||
'bg-slate-900 text-white shadow-sm' => $this->hubTab === $tabKey,
|
|
||||||
'bg-white text-slate-600 ring-1 ring-inset ring-slate-300 hover:bg-slate-50' => $this->hubTab !== $tabKey,
|
|
||||||
])
|
|
||||||
>
|
|
||||||
{{ $tabLabel }}
|
|
||||||
</button>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if($this->hubTab === 'archivio-digitale')
|
|
||||||
<x-filament::section>
|
|
||||||
<x-slot name="heading">Archivio digitale</x-slot>
|
|
||||||
<x-slot name="description">La tabella esistente resta il punto operativo per registrazione, OCR, stampa etichette e apertura PDF in modal.</x-slot>
|
|
||||||
|
|
||||||
{{ $this->table }}
|
|
||||||
</x-filament::section>
|
|
||||||
@elseif($this->hubTab === 'template-drive')
|
|
||||||
<x-filament::section>
|
|
||||||
<x-slot name="heading">Template cartelle Drive</x-slot>
|
|
||||||
<x-slot name="description">Questa struttura resta la base comune per archivio digitale, passaggio consegne e futura sincronizzazione Google.</x-slot>
|
|
||||||
|
|
||||||
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
||||||
@foreach($this->driveTemplateFolders as $folder)
|
|
||||||
<div class="rounded-xl border bg-slate-50 px-4 py-3 text-sm text-slate-700">{{ $folder }}</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</x-filament::section>
|
|
||||||
@elseif($this->hubTab === 'archivio-fisico')
|
|
||||||
<x-filament::section>
|
|
||||||
<x-slot name="heading">Archivio fisico</x-slot>
|
|
||||||
<x-slot name="description">Prima base per faldoni, scatole, cartelle e buste collegate ai PDF. QR e NFC si innesteranno su questo schema.</x-slot>
|
|
||||||
|
|
||||||
<div class="grid gap-4 lg:grid-cols-[minmax(0,1.2fr)_minmax(280px,0.8fr)]">
|
|
||||||
<div class="rounded-xl border bg-white p-4">
|
|
||||||
<div class="text-sm font-semibold text-slate-900">Contenitori previsti</div>
|
|
||||||
<div class="mt-3 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
|
||||||
@foreach($this->archivioFisicoTypes as $type)
|
|
||||||
<div class="rounded-lg border border-slate-200 bg-slate-50 px-3 py-3">
|
|
||||||
<div class="text-sm font-semibold text-slate-900">{{ $type }}</div>
|
|
||||||
<div class="mt-1 text-xs text-slate-500">Collegabile a codice {{ $this->mnemonicCodeHint ?? 'STB00000' }} e documento digitale.</div>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-xl border border-amber-200 bg-amber-50 p-4">
|
|
||||||
<div class="text-sm font-semibold text-amber-900">Stato di avanzamento</div>
|
|
||||||
<div class="mt-2 text-sm text-amber-800">In questa iterazione il modello e preparato lato HUB e codifica. Il prossimo step e aggiungere entita fisiche persistenti, posizione archivio e stampa QR/etichetta.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-filament::section>
|
|
||||||
@else
|
|
||||||
<x-filament::section>
|
|
||||||
<x-slot name="heading">Permessi e visibilita</x-slot>
|
|
||||||
<x-slot name="description">Qui prepariamo la classificazione dei documenti per audience senza mischiare condomini, inquilini, fornitori e uso interno studio.</x-slot>
|
|
||||||
|
|
||||||
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
||||||
@foreach($this->audienceGroups as $group)
|
|
||||||
<div class="rounded-xl border bg-white px-4 py-3">
|
|
||||||
<div class="text-sm font-semibold text-slate-900">{{ $group }}</div>
|
|
||||||
<div class="mt-1 text-xs text-slate-500">Audience candidata per visibilita documentale, export e portali dedicati.</div>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</x-filament::section>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</x-filament-panels::page>
|
|
||||||
|
|
@ -586,8 +586,6 @@
|
||||||
Route::middleware(['role:super-admin|admin|amministratore|collaboratore'])->prefix('admin-filament')->group(function () {
|
Route::middleware(['role:super-admin|admin|amministratore|collaboratore'])->prefix('admin-filament')->group(function () {
|
||||||
Route::get('documenti/{documento}/download', [\App\Http\Controllers\Filament\DocumentiPrintController::class, 'download'])
|
Route::get('documenti/{documento}/download', [\App\Http\Controllers\Filament\DocumentiPrintController::class, 'download'])
|
||||||
->name('filament.documenti.download');
|
->name('filament.documenti.download');
|
||||||
Route::get('documenti-stabili/{documentoStabile}/download', [\App\Http\Controllers\Filament\DocumentoStabileDownloadController::class, 'download'])
|
|
||||||
->name('filament.documenti-stabili.download');
|
|
||||||
Route::get('documenti/{documento}/etichetta', [\App\Http\Controllers\Filament\DocumentiPrintController::class, 'etichetta'])
|
Route::get('documenti/{documento}/etichetta', [\App\Http\Controllers\Filament\DocumentiPrintController::class, 'etichetta'])
|
||||||
->name('filament.documenti.etichetta');
|
->name('filament.documenti.etichetta');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user