feat: add previous configuration handling for user selections in quotes
- Implemented session storage management for the last configuration choices made by the user. - Added functionality to prompt users when changing models to retain their previous selections. - Introduced methods to apply previous choices to the newly loaded model, including finishes, handles, glass types, and options. - Enhanced user experience by ensuring selections are automatically applied where applicable.
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Catalogo del configuratore per sistema (finiture, vetri, regole vetro, opzioni con
|
||||
* supplementi): sostituisce i JSON e le immagini sotto src/assets/data.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Finiture del profilo e colori maniglia/cerniere: stesse proprietà 3D.
|
||||
Schema::create('finiture', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('sistema_id')->constrained('sistemi')->cascadeOnDelete();
|
||||
$table->string('tipo', 20); // profilo | maniglia
|
||||
// Valore inviato dal configuratore (es. "2", "M1").
|
||||
$table->string('codice', 50);
|
||||
$table->string('descrizione');
|
||||
$table->string('codice_mazzetta', 50)->nullable();
|
||||
$table->string('colore', 20)->nullable();
|
||||
$table->string('colore_corpo_base', 20)->nullable();
|
||||
$table->decimal('roughness', 4, 2)->nullable();
|
||||
$table->decimal('metalness', 4, 2)->nullable();
|
||||
$table->string('anteprima')->nullable();
|
||||
$table->string('texture')->nullable();
|
||||
$table->string('texture_bump')->nullable();
|
||||
$table->decimal('bump_scala', 6, 2)->nullable();
|
||||
$table->boolean('predefinita')->default(false);
|
||||
$table->unsignedInteger('ordine')->default(0);
|
||||
$table->boolean('attivo')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['sistema_id', 'tipo', 'codice']);
|
||||
});
|
||||
|
||||
// Riempimenti (vetri e pannelli): prezzo a m² sulle lastre calcolate dalla regola del modello.
|
||||
Schema::create('vetri', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('sistema_id')->constrained('sistemi')->cascadeOnDelete();
|
||||
$table->string('codice', 50);
|
||||
$table->string('descrizione');
|
||||
$table->decimal('maggiorazione_mq', 10, 2)->default(0);
|
||||
$table->decimal('minimo_mq', 8, 3)->default(0);
|
||||
$table->string('colore', 20)->nullable();
|
||||
$table->decimal('roughness', 4, 2)->nullable();
|
||||
$table->decimal('metalness', 4, 2)->nullable();
|
||||
$table->decimal('opacita', 4, 2)->nullable();
|
||||
$table->string('anteprima')->nullable();
|
||||
$table->string('popup')->nullable();
|
||||
$table->string('texture')->nullable();
|
||||
$table->string('texture_bump')->nullable();
|
||||
$table->decimal('bump_scala', 6, 2)->nullable();
|
||||
$table->boolean('predefinito')->default(false);
|
||||
$table->unsignedInteger('ordine')->default(0);
|
||||
$table->boolean('attivo')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['sistema_id', 'codice']);
|
||||
});
|
||||
|
||||
// Geometria delle lastre per tipologia (es. F2: 2 lastre (L - 312) / 2 x (H - 189)).
|
||||
Schema::create('regole_vetro', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('sistema_id')->constrained('sistemi')->cascadeOnDelete();
|
||||
$table->string('codice', 50);
|
||||
$table->string('descrizione');
|
||||
// [{descrizione, quantita, divisore, detrazione_larghezza, detrazione_altezza}]
|
||||
$table->json('lastre');
|
||||
$table->text('note')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['sistema_id', 'codice']);
|
||||
});
|
||||
|
||||
// Opzioni del configuratore (radio/select) e supplementi di prezzo. Con modello_id
|
||||
// valorizzato sostituiscono, per quel modello, l'intero gruppo definito sul sistema.
|
||||
Schema::create('opzioni', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('sistema_id')->constrained('sistemi')->cascadeOnDelete();
|
||||
$table->foreignId('modello_id')->nullable()->constrained('modelli')->cascadeOnDelete();
|
||||
$table->string('gruppo', 50);
|
||||
$table->string('valore', 100);
|
||||
$table->string('etichetta');
|
||||
$table->string('icona')->nullable();
|
||||
$table->string('supplemento_tipo', 20)->nullable(); // percentuale | fisso
|
||||
$table->decimal('supplemento', 10, 2)->default(0);
|
||||
$table->json('parametri')->nullable();
|
||||
$table->unsignedInteger('ordine')->default(0);
|
||||
$table->boolean('attivo')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['sistema_id', 'gruppo']);
|
||||
$table->index(['modello_id', 'gruppo']);
|
||||
});
|
||||
|
||||
Schema::table('modelli', function (Blueprint $table) {
|
||||
// Titolo esteso mostrato nel configuratore e nel preventivo.
|
||||
$table->string('titolo')->nullable()->after('descrizione');
|
||||
$table->foreignId('regola_vetro_id')->nullable()->after('moduli')
|
||||
->constrained('regole_vetro')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('modelli', function (Blueprint $table) {
|
||||
$table->dropConstrainedForeignId('regola_vetro_id');
|
||||
$table->dropColumn('titolo');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('opzioni');
|
||||
Schema::dropIfExists('regole_vetro');
|
||||
Schema::dropIfExists('vetri');
|
||||
Schema::dropIfExists('finiture');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Lato delle finiture del profilo (interno, esterno o entrambi) sul sistema, con
|
||||
* personalizzazione per modello: una riga in finiture_modelli sostituisce, per quel
|
||||
* modello, attivazione e lato del sistema.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('finiture', function (Blueprint $table) {
|
||||
$table->string('lato', 20)->default('entrambi')->after('tipo'); // entrambi | interno | esterno
|
||||
});
|
||||
|
||||
Schema::create('finiture_modelli', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('modello_id')->constrained('modelli')->cascadeOnDelete();
|
||||
$table->foreignId('finitura_id')->constrained('finiture')->cascadeOnDelete();
|
||||
$table->string('lato', 20); // entrambi | interno | esterno | nessuno
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['modello_id', 'finitura_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('finiture_modelli');
|
||||
|
||||
Schema::table('finiture', function (Blueprint $table) {
|
||||
$table->dropColumn('lato');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Supplementi al metro lineare (es. profili extra): metri = (L x ml_per_larghezza +
|
||||
* H x ml_per_altezza + ml_aggiunta_mm) / 1000. Le opzioni con la stessa `alternativa`
|
||||
* si escludono a vicenda nei gruppi a scelta multipla.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('opzioni', function (Blueprint $table) {
|
||||
$table->string('alternativa', 50)->nullable()->after('icona');
|
||||
$table->decimal('ml_per_larghezza', 8, 3)->default(0)->after('supplemento');
|
||||
$table->decimal('ml_per_altezza', 8, 3)->default(0)->after('ml_per_larghezza');
|
||||
$table->decimal('ml_aggiunta_mm', 10, 2)->default(0)->after('ml_per_altezza');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('opzioni', function (Blueprint $table) {
|
||||
$table->dropColumn(['alternativa', 'ml_per_larghezza', 'ml_per_altezza', 'ml_aggiunta_mm']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Gruppi di opzioni del configuratore gestiti dal backoffice (prima erano fissi nel codice):
|
||||
* controllo del menu, sezione in cui compare e modulo del modello che lo rende visibile.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('gruppi_opzioni', function (Blueprint $table) {
|
||||
$table->id();
|
||||
// Chiave usata da opzioni.gruppo e dal configuratore.
|
||||
$table->string('codice', 50)->unique();
|
||||
$table->string('etichetta');
|
||||
$table->string('ui', 20); // nessuna | radio | select | multipla
|
||||
$table->string('sezione', 20)->default('accessori'); // sistema | accessori
|
||||
// Modulo del modello che rende visibile il gruppo (Modello::moduli).
|
||||
$table->string('modulo', 50);
|
||||
$table->unsignedInteger('ordine')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$adesso = now();
|
||||
DB::table('gruppi_opzioni')->insert(array_map(fn (array $g) => $g + ['created_at' => $adesso, 'updated_at' => $adesso], [
|
||||
['codice' => 'tipo_finitura', 'etichetta' => 'Tipo finitura', 'ui' => 'nessuna', 'sezione' => 'sistema', 'modulo' => 'modello', 'ordine' => 1],
|
||||
['codice' => 'telaio', 'etichetta' => 'Seleziona il telaio del serramento', 'ui' => 'radio', 'sezione' => 'sistema', 'modulo' => 'telaio', 'ordine' => 2],
|
||||
['codice' => 'apertura', 'etichetta' => 'Seleziona il verso di apertura del serramento', 'ui' => 'radio', 'sezione' => 'sistema', 'modulo' => 'apertura', 'ordine' => 3],
|
||||
['codice' => 'profili_extra', 'etichetta' => 'Profili extra', 'ui' => 'multipla', 'sezione' => 'sistema', 'modulo' => 'profili_extra', 'ordine' => 4],
|
||||
['codice' => 'cerniere_a_scomparsa', 'etichetta' => 'Cerniere a scomparsa', 'ui' => 'select', 'sezione' => 'accessori', 'modulo' => 'cerniere', 'ordine' => 5],
|
||||
['codice' => 'soglia_ribassata', 'etichetta' => 'Soglia ribassata', 'ui' => 'select', 'sezione' => 'accessori', 'modulo' => 'soglia_ribassata', 'ordine' => 6],
|
||||
['codice' => 'motore', 'etichetta' => 'Motore', 'ui' => 'select', 'sezione' => 'accessori', 'modulo' => 'motore', 'ordine' => 7],
|
||||
['codice' => 'azionamento', 'etichetta' => 'Azionamento', 'ui' => 'select', 'sezione' => 'accessori', 'modulo' => 'azionamento', 'ordine' => 8],
|
||||
]));
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('gruppi_opzioni');
|
||||
}
|
||||
};
|
||||
@@ -18,6 +18,8 @@ class CatalogoSeeder extends Seeder
|
||||
$pvc = Sistema::firstOrCreate(['codice' => 'pvc'], ['nome' => 'PVC', 'ordine' => 1]);
|
||||
|
||||
$this->seedModelli($pvc, require __DIR__.'/data/modelli_pvc.php');
|
||||
|
||||
Modello::normalizzaOrdine($pvc->id);
|
||||
}
|
||||
|
||||
private function seedModelli(Sistema $sistema, array $modelli): void
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Finitura;
|
||||
use App\Models\Modello;
|
||||
use App\Models\Opzione;
|
||||
use App\Models\RegolaVetro;
|
||||
use App\Models\Sistema;
|
||||
use App\Models\Vetro;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Catalogo iniziale del configuratore PVC (finiture, maniglie, vetri, regole vetro,
|
||||
* opzioni e supplementi) con le immagini della nuova grafica. Gira una volta sola: se il
|
||||
* sistema ha già finiture o vetri non tocca nulla, per non sovrascrivere il backoffice.
|
||||
*/
|
||||
class ConfiguratoreSeeder extends Seeder
|
||||
{
|
||||
private const SORGENTE = __DIR__.'/data/configuratore/pvc';
|
||||
|
||||
private const DESTINAZIONE = 'configuratore/pvc';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$pvc = Sistema::where('codice', 'pvc')->first();
|
||||
if (! $pvc || $pvc->finiture()->exists() || $pvc->vetri()->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$catalogo = require __DIR__.'/data/configuratore_pvc_catalogo.php';
|
||||
$regole = require __DIR__.'/data/configuratore_pvc.php';
|
||||
|
||||
DB::transaction(function () use ($pvc, $catalogo, $regole) {
|
||||
$this->finiture($pvc, 'profilo', $catalogo['finiture']);
|
||||
$this->finiture($pvc, 'maniglia', $catalogo['maniglie']);
|
||||
$this->vetri($pvc, $regole['vetri']);
|
||||
$this->opzioni($pvc, $regole['opzioni']);
|
||||
$this->modelli($pvc, $regole, $catalogo['titoli']);
|
||||
});
|
||||
}
|
||||
|
||||
private function finiture(Sistema $sistema, string $tipo, array $finiture): void
|
||||
{
|
||||
foreach ($finiture as $i => $f) {
|
||||
Finitura::create([
|
||||
'sistema_id' => $sistema->id,
|
||||
'tipo' => $tipo,
|
||||
'ordine' => $i + 1,
|
||||
'anteprima' => $this->copia($f['anteprima']),
|
||||
'texture' => $this->copia($f['texture'] ?? null),
|
||||
] + $f);
|
||||
}
|
||||
}
|
||||
|
||||
private function vetri(Sistema $sistema, array $vetri): void
|
||||
{
|
||||
foreach ($vetri as $i => [$codice, $descrizione, $maggiorazione, $minimo, $icona, $aspetto]) {
|
||||
Vetro::create([
|
||||
'sistema_id' => $sistema->id,
|
||||
'codice' => $codice,
|
||||
'descrizione' => $descrizione,
|
||||
'maggiorazione_mq' => $maggiorazione,
|
||||
'minimo_mq' => $minimo,
|
||||
'ordine' => $i + 1,
|
||||
'anteprima' => $this->copia("vetri/icone/{$icona}", "vetri/{$codice}/{$icona}"),
|
||||
// Ogni vetro ha la sua copia della texture: i file non sono mai condivisi tra record.
|
||||
'texture_bump' => isset($aspetto['texture_bump'])
|
||||
? $this->copia($aspetto['texture_bump'], "vetri/{$codice}/".basename($aspetto['texture_bump']))
|
||||
: null,
|
||||
] + array_diff_key($aspetto, ['texture_bump' => true]));
|
||||
}
|
||||
}
|
||||
|
||||
private function opzioni(Sistema $sistema, array $gruppi): void
|
||||
{
|
||||
foreach ($gruppi as $gruppo => $voci) {
|
||||
foreach ($voci as $i => $voce) {
|
||||
[$valore, $etichetta, $tipo, $supplemento, $icona, $parametri] = $voce;
|
||||
|
||||
Opzione::create([
|
||||
'sistema_id' => $sistema->id,
|
||||
'gruppo' => $gruppo,
|
||||
'valore' => $valore,
|
||||
'etichetta' => $etichetta,
|
||||
'supplemento_tipo' => $tipo,
|
||||
'supplemento' => $supplemento,
|
||||
'icona' => $icona ? $this->copia("opzioni/{$icona}", "opzioni/sistema/{$icona}") : null,
|
||||
'parametri' => $parametri,
|
||||
'ordine' => $i + 1,
|
||||
'attivo' => $voce[6] ?? true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function modelli(Sistema $sistema, array $regole, array $titoli): void
|
||||
{
|
||||
$regoleVetro = collect($regole['regole_vetro'])->map(fn (array $r, string $codice) => RegolaVetro::create([
|
||||
'sistema_id' => $sistema->id,
|
||||
'codice' => $codice,
|
||||
'descrizione' => $r[0],
|
||||
'lastre' => $r[1],
|
||||
]));
|
||||
|
||||
foreach ($sistema->modelli()->get() as $modello) {
|
||||
[$regola, $moduli] = $regole['modelli'][$modello->codice] ?? [null, []];
|
||||
|
||||
$modello->update([
|
||||
'titolo' => $modello->titolo ?: ($titoli[$modello->cartella ?? $modello->codice] ?? null),
|
||||
'regola_vetro_id' => $modello->regola_vetro_id ?? $regoleVetro->get($regola)?->id,
|
||||
'moduli' => array_values(array_unique([...($modello->moduli ?? []), ...$moduli])),
|
||||
]);
|
||||
|
||||
$this->apertura($modello);
|
||||
$this->aggiornaIcona($modello);
|
||||
}
|
||||
}
|
||||
|
||||
/** Icone DX/SX proprie del modello: override del gruppo apertura del sistema. */
|
||||
private function apertura(Modello $modello): void
|
||||
{
|
||||
$cartella = "opzioni/apertura/{$modello->codice}";
|
||||
if (! is_dir(self::SORGENTE."/{$cartella}")) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ([['dx', 'DX', 'destra.jpg', 1], ['sx', 'SX', 'sinistra.jpg', -1]] as $i => [$valore, $etichetta, $file, $scala]) {
|
||||
Opzione::create([
|
||||
'sistema_id' => $modello->sistema_id,
|
||||
'modello_id' => $modello->id,
|
||||
'gruppo' => 'apertura',
|
||||
'valore' => $valore,
|
||||
'etichetta' => $etichetta,
|
||||
'icona' => $this->copia("{$cartella}/{$file}"),
|
||||
'parametri' => ['scale_x' => $scala],
|
||||
'ordine' => $i + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sostituisce l'icona del modello con quella della nuova grafica, se non è stata cambiata dal pannello. */
|
||||
private function aggiornaIcona(Modello $modello): void
|
||||
{
|
||||
$sorgente = __DIR__."/data/icone/pvc/{$modello->codice}.jpg";
|
||||
$seminata = "modelli/icone/pvc/{$modello->codice}.jpg";
|
||||
|
||||
if (is_file($sorgente) && in_array($modello->icona, [null, $seminata], true)) {
|
||||
Storage::disk('public')->put($seminata, file_get_contents($sorgente));
|
||||
$modello->updateQuietly(['icona' => $seminata]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Copia un file della grafica sul disco public; restituisce il percorso salvato. */
|
||||
private function copia(?string $relativo, ?string $destinazione = null): ?string
|
||||
{
|
||||
if (! $relativo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$percorso = self::DESTINAZIONE.'/'.($destinazione ?? $relativo);
|
||||
Storage::disk('public')->put($percorso, file_get_contents(self::SORGENTE."/{$relativo}"));
|
||||
|
||||
return $percorso;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,6 @@ class DatabaseSeeder extends Seeder
|
||||
]
|
||||
);
|
||||
|
||||
$this->call(CatalogoSeeder::class);
|
||||
$this->call([CatalogoSeeder::class, ConfiguratoreSeeder::class, ProfiliExtraSeeder::class, NuoveOpzioniSeeder::class]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Modello;
|
||||
use App\Models\Opzione;
|
||||
use App\Models\Sistema;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Cerniere a scomparsa, soglia ribassata e motore per modello, dal foglio "Nomi" di
|
||||
* reference/OrdineSerramenti_Descrizioni_NuoveOpzioni.xlsx. Gira una volta sola: se il
|
||||
* sistema ha già le opzioni della soglia ribassata non tocca nulla.
|
||||
*/
|
||||
class NuoveOpzioniSeeder extends Seeder
|
||||
{
|
||||
/** Modelli con cerniere a scomparsa e relativo sovrapprezzo (gli altri: NO). */
|
||||
private const CERNIERE = [
|
||||
'finestra_1_anta' => 50, 'finestra_2_ante' => 100, 'finestra_3_ante' => 150, 'finestra_vasistas' => 50,
|
||||
'portafinestra_1_anta' => 50, 'portafinestra_2_ante' => 100, 'portafinestra_3_ante' => 150,
|
||||
];
|
||||
|
||||
/** Modelli con soglia ribassata e relativo sovrapprezzo. */
|
||||
private const SOGLIA = [
|
||||
'portafinestra_1_anta' => 65, 'portafinestra_2_ante' => 105, 'portafinestra_3_ante' => 145,
|
||||
'scorrevole_sf_2_ante' => 105, 'scorrevole_sf_3_ante' => 145, 'scorrevole_sf_4_ante' => 185,
|
||||
];
|
||||
|
||||
/** Motore: "opzione select motore, togliere maniglia". */
|
||||
private const MOTORE = ['finestra_vasistas' => 150];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$pvc = Sistema::where('codice', 'pvc')->first();
|
||||
if (! $pvc || Opzione::where('sistema_id', $pvc->id)->where('gruppo', 'soglia_ribassata')->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($pvc) {
|
||||
// Prezzo del sistema = il più comune; gli altri modelli hanno il gruppo personalizzato.
|
||||
$this->siNo($pvc, null, 'cerniere_a_scomparsa', 50, ['categoria' => 'cerniere', 'func' => 'mostraCerniere'], ['categoria' => 'cerniere', 'func' => 'nascondiCerniere']);
|
||||
$this->siNo($pvc, null, 'soglia_ribassata', 65);
|
||||
$this->siNo($pvc, null, 'motore', 150, ['categoria' => 'maniglia', 'func' => 'mostraCategoria'], ['categoria' => 'maniglia', 'func' => 'nascondiCategoria']);
|
||||
|
||||
foreach ($pvc->modelli()->get() as $modello) {
|
||||
$moduli = array_values(array_diff($modello->moduli ?? [], ['cerniere']));
|
||||
foreach (['cerniere' => self::CERNIERE, 'soglia_ribassata' => self::SOGLIA, 'motore' => self::MOTORE] as $modulo => $prezzi) {
|
||||
if (isset($prezzi[$modello->codice])) {
|
||||
$moduli[] = $modulo;
|
||||
}
|
||||
}
|
||||
$modello->update(['moduli' => array_values(array_unique($moduli))]);
|
||||
|
||||
if ((self::CERNIERE[$modello->codice] ?? 50) !== 50) {
|
||||
$this->siNo($pvc, $modello, 'cerniere_a_scomparsa', self::CERNIERE[$modello->codice], ['categoria' => 'cerniere', 'func' => 'mostraCerniere'], ['categoria' => 'cerniere', 'func' => 'nascondiCerniere'], sostituisci: true);
|
||||
}
|
||||
if ((self::SOGLIA[$modello->codice] ?? 65) !== 65) {
|
||||
$this->siNo($pvc, $modello, 'soglia_ribassata', self::SOGLIA[$modello->codice]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Opzioni NO (senza supplemento) e SI (importo fisso); per il modello sostituiscono il gruppo del sistema. */
|
||||
private function siNo(Sistema $sistema, ?Modello $modello, string $gruppo, float $prezzo, ?array $parametriNo = null, ?array $parametriSi = null, bool $sostituisci = false): void
|
||||
{
|
||||
$esistenti = Opzione::where('sistema_id', $sistema->id)->where('gruppo', $gruppo)->where('modello_id', $modello?->id);
|
||||
if ($sostituisci) {
|
||||
$esistenti->delete();
|
||||
} elseif ($esistenti->exists()) {
|
||||
// Cerniere del sistema già create dal catalogo iniziale: si aggiorna solo il prezzo.
|
||||
$esistenti->where('valore', 'si')->update(['supplemento' => $prezzo]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ([['no', 'NO', 0, $parametriNo], ['si', 'SI', $prezzo, $parametriSi]] as $i => [$valore, $etichetta, $supplemento, $parametri]) {
|
||||
Opzione::create([
|
||||
'sistema_id' => $sistema->id,
|
||||
'modello_id' => $modello?->id,
|
||||
'gruppo' => $gruppo,
|
||||
'valore' => $valore,
|
||||
'etichetta' => $etichetta,
|
||||
'supplemento_tipo' => 'fisso',
|
||||
'supplemento' => $supplemento,
|
||||
'parametri' => $parametri,
|
||||
'ordine' => $i + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Modello;
|
||||
use App\Models\Opzione;
|
||||
use App\Models\Sistema;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Profili extra PVC: codici e descrizioni dal foglio "Profili Extra" di
|
||||
* reference/OrdineSerramenti_Descrizioni_NuoveOpzioni.xlsx, prezzi da
|
||||
* reference/Regole_Profili Extra.xls, icone da reference/icone_profili_extra.
|
||||
*
|
||||
* Gira una volta sola: se il sistema ha già profili extra con un prezzo non tocca nulla.
|
||||
* Sostituisce le righe segnaposto (senza prezzo) create dalla prima versione del catalogo.
|
||||
*/
|
||||
class ProfiliExtraSeeder extends Seeder
|
||||
{
|
||||
private const ICONE = __DIR__.'/data/configuratore/pvc/opzioni/profili_extra';
|
||||
|
||||
/** Modelli senza profili extra (foglio "Nomi"; regole: "tutti tranne scuretto, cassonetto, cielino"). */
|
||||
private const ESCLUSI = ['scuretto_base', 'scuretto_pannello_alessandrite', 'scuretto_pannello_malachite', 'cassonetto_80', 'cassonetto_139', 'cassonetto_150'];
|
||||
|
||||
/** Il cielino ha solo il frontale 4062000 (regole: "solo cielino"). */
|
||||
private const CIELINO = 'celino';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$pvc = Sistema::where('codice', 'pvc')->first();
|
||||
$esistenti = Opzione::where('gruppo', 'profili_extra')->where('sistema_id', $pvc?->id);
|
||||
if (! $pvc || (clone $esistenti)->whereNotNull('supplemento_tipo')->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($pvc, $esistenti) {
|
||||
// Segnaposto senza prezzo: eliminati uno per uno per rimuovere anche le icone.
|
||||
$esistenti->get()->each->delete();
|
||||
|
||||
foreach (self::profili() as $i => $profilo) {
|
||||
if ($profilo['valore'] === '406200') {
|
||||
continue;
|
||||
}
|
||||
$this->crea($pvc, null, $profilo, $i + 1);
|
||||
}
|
||||
|
||||
foreach ($pvc->modelli()->get() as $modello) {
|
||||
if (in_array($modello->codice, self::ESCLUSI, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$modello->update(['moduli' => array_values(array_unique([...($modello->moduli ?? []), 'profili_extra']))]);
|
||||
|
||||
if ($modello->codice === self::CIELINO) {
|
||||
$this->crea($pvc, $modello, collect(self::profili())->firstWhere('valore', '406200'), 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ml = (L x ml_per_larghezza + H x ml_per_altezza + ml_aggiunta_mm) / 1000.
|
||||
* Guide: "H+100mm x 2" -> 2H + 200. Frontale: "solo larghezza (1 pz)" -> L.
|
||||
* Allargamenti NP: "ML" senza misura di riferimento -> perimetro 2L + 2H (da confermare).
|
||||
*/
|
||||
public static function profili(): array
|
||||
{
|
||||
$ml = fn (float $l, float $h, float $mm) => ['ml_per_larghezza' => $l, 'ml_per_altezza' => $h, 'ml_aggiunta_mm' => $mm];
|
||||
|
||||
return [
|
||||
['valore' => 'coprifili_piatti', 'etichetta' => 'COPRIFILI PIATTI', 'supplemento_tipo' => 'percentuale', 'supplemento' => 1, 'alternativa' => 'coprifili'],
|
||||
['valore' => 'coprifili_scatolati', 'etichetta' => 'COPRIFILI SCATOLATI', 'supplemento_tipo' => 'percentuale', 'supplemento' => 3, 'alternativa' => 'coprifili'],
|
||||
['valore' => '50811', 'etichetta' => '50811_GUIDA 34', 'supplemento_tipo' => 'metro_lineare', 'supplemento' => 15, 'alternativa' => 'guida'] + $ml(0, 2, 200),
|
||||
['valore' => '50811_50801', 'etichetta' => '50811+50801_GUIDA 87', 'supplemento_tipo' => 'metro_lineare', 'supplemento' => 15, 'alternativa' => 'guida'] + $ml(0, 2, 200),
|
||||
['valore' => '9320', 'etichetta' => 'HO9320_T/M', 'supplemento_tipo' => 'fisso', 'supplemento' => 85],
|
||||
['valore' => 'NP8300', 'etichetta' => 'NP8300_ALL.30', 'supplemento_tipo' => 'metro_lineare', 'supplemento' => 25, 'alternativa' => 'allargamento'] + $ml(2, 2, 0),
|
||||
['valore' => 'NP8500', 'etichetta' => 'NP8500_ALL.50', 'supplemento_tipo' => 'metro_lineare', 'supplemento' => 33, 'alternativa' => 'allargamento'] + $ml(2, 2, 0),
|
||||
['valore' => 'NP8700', 'etichetta' => 'NP8700_ALL.150', 'supplemento_tipo' => 'metro_lineare', 'supplemento' => 45, 'alternativa' => 'allargamento'] + $ml(2, 2, 0),
|
||||
['valore' => '406200', 'etichetta' => '4062000_FRONTALE', 'supplemento_tipo' => 'metro_lineare', 'supplemento' => 145] + $ml(1, 0, 0),
|
||||
];
|
||||
}
|
||||
|
||||
private function crea(Sistema $sistema, ?Modello $modello, array $profilo, int $ordine): void
|
||||
{
|
||||
$file = "{$profilo['valore']}.jpg";
|
||||
$icona = 'configuratore/pvc/opzioni/'.($modello ? $modello->codice : 'sistema').'/profili_extra/'.$file;
|
||||
Storage::disk('public')->put($icona, file_get_contents(self::ICONE."/{$file}"));
|
||||
|
||||
Opzione::create($profilo + [
|
||||
'sistema_id' => $sistema->id,
|
||||
'modello_id' => $modello?->id,
|
||||
'gruppo' => 'profili_extra',
|
||||
'icona' => $icona,
|
||||
'ordine' => $ordine,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 436 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 794 KiB |
|
After Width: | Height: | Size: 732 KiB |
|
After Width: | Height: | Size: 478 KiB |
|
After Width: | Height: | Size: 623 KiB |
|
After Width: | Height: | Size: 721 KiB |
|
After Width: | Height: | Size: 437 KiB |
|
After Width: | Height: | Size: 759 KiB |
|
After Width: | Height: | Size: 792 KiB |
|
After Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 675 KiB |
|
After Width: | Height: | Size: 751 KiB |
|
After Width: | Height: | Size: 867 KiB |
|
After Width: | Height: | Size: 818 KiB |
|
After Width: | Height: | Size: 810 KiB |
|
After Width: | Height: | Size: 852 KiB |
|
After Width: | Height: | Size: 879 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |