feat: Aggiungi supporto per prezzi personalizzati nel listino e modifica la logica di importazione

This commit is contained in:
2026-10-19 17:01:18 +02:00
parent 1e352eb2ef
commit 863e6ca920
6 changed files with 117 additions and 8 deletions
@@ -82,13 +82,20 @@ class ListinoRelationManager extends RelationManager
->step('0.01')
->rules(['required', 'numeric', 'min:0'])
->sortable(),
Tables\Columns\IconColumn::make('personalizzato')
->label('Personalizzato')
->icon(fn (bool $state) => $state ? 'heroicon-o-pencil-square' : null)
->color('warning')
->tooltip(fn (ListinoPrezzo $record) => $record->personalizzato ? 'Aggiunto a mano: può essere preservato alla prossima importazione' : null),
])
->filters([
Tables\Filters\SelectFilter::make('altezza')->label('Altezza')->options($misure('altezza')),
Tables\Filters\SelectFilter::make('larghezza')->label('Larghezza')->options($misure('larghezza')),
Tables\Filters\TernaryFilter::make('personalizzato')->label('Personalizzato'),
])
->headerActions([
Tables\Actions\CreateAction::make(),
Tables\Actions\CreateAction::make()
->mutateFormDataUsing(fn (array $data) => $data + ['personalizzato' => true]),
Tables\Actions\Action::make('svuota')
->label('Svuota listino')
->icon('heroicon-o-trash')
@@ -57,7 +57,7 @@ class ImportaListiniAction
->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.'),
->content('Per ogni tabella trovata nel file scegli il modello a cui assegnare il listino. Le tabelle senza modello non vengono importate; i modelli non associati mantengono il listino attuale.'),
// 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'])
@@ -92,6 +92,13 @@ class ImportaListiniAction
->distinct()
->columnSpan(2),
]),
Forms\Components\Toggle::make('sovrascrivi_tutto')
->label('Sovrascrivi tutti i prezzi')
->helperText(fn (Get $get) => $get('sovrascrivi_tutto')
? 'L\'Excel è la fonte primaria: il listino dei modelli associati viene sostituito per intero, compresi i prezzi aggiunti a mano.'
: 'I prezzi aggiunti a mano (pulsante "Nuovo" nel listino del modello) vengono mantenuti e prevalgono sull\'Excel per la stessa misura.')
->default(true)
->live(),
]),
])
->action(function (array $data, Sistema $record) {
@@ -100,7 +107,7 @@ class ImportaListiniAction
->mapWithKeys(fn (array $a) => [$a['codice'] => $a['modello_id'] ?? null])
->all();
$aggiornati = ListinoImporter::importa($record, $blocchi, $associazioni);
$aggiornati = ListinoImporter::importa($record, $blocchi, $associazioni, preservaPersonalizzati: ! ($data['sovrascrivi_tutto'] ?? true));
Notification::make()
->title('Listini importati')
+2 -1
View File
@@ -10,13 +10,14 @@ class ListinoPrezzo extends Model
protected $table = 'listino_prezzi';
protected $fillable = [
'modello_id', 'larghezza', 'altezza', 'prezzo',
'modello_id', 'larghezza', 'altezza', 'prezzo', 'personalizzato',
];
protected $casts = [
'larghezza' => 'integer',
'altezza' => 'integer',
'prezzo' => 'float',
'personalizzato' => 'boolean',
];
public function modello(): BelongsTo
@@ -46,17 +46,19 @@ class ListinoImporter
/**
* Sostituisce il listino di ogni modello associato con i prezzi del rispettivo blocco.
* Con $preservaPersonalizzati i prezzi inseriti a mano restano e prevalgono sull'Excel
* per la stessa misura; altrimenti l'Excel sostituisce tutto.
*
* @param list<array{codice: string, prezzi: list<array>}> $blocchi
* @param array<string, int|null> $associazioni codice del file → id modello
* @return int numero di modelli aggiornati
*/
public static function importa(Sistema $sistema, array $blocchi, array $associazioni): int
public static function importa(Sistema $sistema, array $blocchi, array $associazioni, bool $preservaPersonalizzati = false): int
{
$prezziPerCodice = collect($blocchi)->keyBy('codice')->map(fn (array $b) => $b['prezzi']);
$modelli = $sistema->modelli()->get()->keyBy('id');
return DB::transaction(function () use ($associazioni, $prezziPerCodice, $modelli) {
return DB::transaction(function () use ($associazioni, $prezziPerCodice, $modelli, $preservaPersonalizzati) {
$aggiornati = 0;
$ora = now();
@@ -66,10 +68,22 @@ class ListinoImporter
continue;
}
$modello->listino()->delete();
$misura = fn ($p) => (int) $p['larghezza'].'x'.(int) $p['altezza'];
$daEliminare = $modello->listino();
$personalizzate = [];
if ($preservaPersonalizzati) {
$daEliminare->where('personalizzato', false);
$personalizzate = $modello->listino()->where('personalizzato', true)->get()
->mapWithKeys(fn ($p) => [$misura($p) => true])
->all();
}
$daEliminare->delete();
// Una stessa misura ripetuta nel blocco: vale l'ultima.
$righe = collect($prezziPerCodice[$codice])
->keyBy(fn (array $p) => $p['larghezza'].'x'.$p['altezza'])
->keyBy($misura)
->reject(fn (array $p, string $chiave) => isset($personalizzate[$chiave]))
->map(fn (array $p) => $p + ['modello_id' => $modello->id, 'created_at' => $ora, 'updated_at' => $ora])
->values();
foreach ($righe->chunk(500) as $chunk) {