feat: Implement price list management and import functionality
- Added ListinoRelationManager for managing price lists associated with models. - Introduced ImportaListiniAction for importing price lists from Excel files. - Created ListinoPrezzo model to represent price entries in the database. - Developed ListinoExcelParser for parsing Excel files containing price lists. - Implemented ListinoImporter to handle the import logic and associations. - Added migration for creating listino_prezzi table and converting measures to mm. - Updated frontend logic to handle new price list features and validations. - Enhanced tests for price list import and conversion functionalities.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\SistemaResource\Actions;
|
||||
|
||||
use App\Models\Sistema;
|
||||
use App\Support\Listini\ListinoExcelParser;
|
||||
use App\Support\Listini\ListinoImporter;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Importa i listini di un sistema da Excel in due passi: caricamento del file e
|
||||
* revisione delle associazioni tabella del file → modello prima di salvare.
|
||||
*/
|
||||
class ImportaListiniAction
|
||||
{
|
||||
public static function make(): Action
|
||||
{
|
||||
return Action::make('importaListini')
|
||||
->label('Importa listini')
|
||||
->icon('heroicon-o-arrow-up-tray')
|
||||
->color('gray')
|
||||
->modalHeading(fn (Sistema $record) => "Importa listini {$record->nome}")
|
||||
->modalWidth('3xl')
|
||||
->modalSubmitActionLabel('Importa listini')
|
||||
->steps(fn (Sistema $record) => [
|
||||
Forms\Components\Wizard\Step::make('File')
|
||||
->description('Carica il file Excel')
|
||||
->schema([
|
||||
Forms\Components\FileUpload::make('file')
|
||||
->label('File listini')
|
||||
->helperText('File .xls o .xlsx: ogni tabella ha nella prima colonna il codice dell\'infisso, nella prima riga le larghezze e nella prima colonna delle righe successive le altezze (in mm).')
|
||||
->required()
|
||||
->storeFiles(false)
|
||||
->maxSize(10240),
|
||||
])
|
||||
->afterValidation(function (Get $get, Set $set) use ($record) {
|
||||
$blocchi = self::leggi($get('file'));
|
||||
|
||||
$associazioni = ListinoImporter::suggerisciAssociazioni($record, array_column($blocchi, 'codice'));
|
||||
|
||||
$set('associazioni', collect($blocchi)->mapWithKeys(fn (array $b) => [(string) Str::uuid() => [
|
||||
'codice' => $b['codice'],
|
||||
'prezzi' => count($b['prezzi']),
|
||||
'modello_id' => $associazioni[$b['codice']],
|
||||
]])->all());
|
||||
}),
|
||||
Forms\Components\Wizard\Step::make('Associazioni')
|
||||
->description('Verifica i modelli associati')
|
||||
->schema([
|
||||
Forms\Components\Placeholder::make('istruzioni')
|
||||
->hiddenLabel()
|
||||
->content('Per ogni tabella trovata nel file scegli il modello a cui assegnare il listino. Le tabelle senza modello non vengono importate; il listino dei modelli associati viene sostituito.'),
|
||||
// Intestazione unica: le righe sotto sono senza etichette, in forma di tabella.
|
||||
Forms\Components\Grid::make(5)
|
||||
->extraAttributes(['class' => 'px-4 text-sm font-medium text-gray-950 dark:text-white'])
|
||||
->schema([
|
||||
Forms\Components\Placeholder::make('h_codice')->hiddenLabel()->content('Codice nel file')->columnSpan(2),
|
||||
Forms\Components\Placeholder::make('h_prezzi')->hiddenLabel()->content('Prezzi')->columnSpan(1),
|
||||
Forms\Components\Placeholder::make('h_modello')->hiddenLabel()->content('Modello')->columnSpan(2),
|
||||
]),
|
||||
Forms\Components\Repeater::make('associazioni')
|
||||
->hiddenLabel()
|
||||
->addable(false)
|
||||
->deletable(false)
|
||||
->reorderable(false)
|
||||
->columns(5)
|
||||
->extraAttributes(['class' => '-mt-4'])
|
||||
->schema([
|
||||
Forms\Components\Hidden::make('codice'),
|
||||
Forms\Components\Placeholder::make('codice_visibile')
|
||||
->hiddenLabel()
|
||||
->content(fn (Get $get) => $get('codice'))
|
||||
->extraAttributes(['class' => 'font-mono break-all'])
|
||||
->columnSpan(2),
|
||||
Forms\Components\Placeholder::make('prezzi')
|
||||
->hiddenLabel()
|
||||
->content(fn (Get $get) => $get('prezzi'))
|
||||
->columnSpan(1),
|
||||
Forms\Components\Select::make('modello_id')
|
||||
->hiddenLabel()
|
||||
->placeholder('— Non importare —')
|
||||
->options(fn () => $record->modelli()->orderBy('ordine')->pluck('nome', 'id'))
|
||||
->searchable()
|
||||
->distinct()
|
||||
->columnSpan(2),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->action(function (array $data, Sistema $record) {
|
||||
$blocchi = self::leggi($data['file']);
|
||||
$associazioni = collect($data['associazioni'] ?? [])
|
||||
->mapWithKeys(fn (array $a) => [$a['codice'] => $a['modello_id'] ?? null])
|
||||
->all();
|
||||
|
||||
$aggiornati = ListinoImporter::importa($record, $blocchi, $associazioni);
|
||||
|
||||
Notification::make()
|
||||
->title('Listini importati')
|
||||
->body("Aggiornati i listini di {$aggiornati} modelli.")
|
||||
->success()
|
||||
->send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{codice: string, prezzi: list<array>}>
|
||||
*/
|
||||
private static function leggi(mixed $file): array
|
||||
{
|
||||
if (is_array($file)) {
|
||||
$file = reset($file);
|
||||
}
|
||||
|
||||
try {
|
||||
$blocchi = $file instanceof TemporaryUploadedFile ? ListinoExcelParser::parse($file->getRealPath()) : [];
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
$blocchi = [];
|
||||
}
|
||||
|
||||
if (! $blocchi) {
|
||||
Notification::make()
|
||||
->title('Nessun listino trovato')
|
||||
->body('Il file non è un Excel valido o non contiene tabelle nel formato atteso.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
throw new Halt;
|
||||
}
|
||||
|
||||
return $blocchi;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user