.json). public const CATEGORIE = [ 'finestre' => 'Finestre', 'porte' => 'Porte', 'scorrevoli' => 'Scorrevoli', 'scuretti' => 'Scuretti', 'accessori' => 'Accessori', ]; // Moduli base del configuratore (sezioni del menu e voci di prezzo); quelli dei gruppi // di opzioni si aggiungono da GruppoOpzioni (vedi moduliDisponibili). public const MODULI = [ 'listino' => 'Listino', 'modello' => 'Finiture', 'finitura_esterna' => 'Finitura esterna (bicolore)', 'maniglia' => 'Maniglia', 'altezza_maniglia' => 'Altezza maniglia', 'vetro' => 'Vetro', 'avvolgibile' => 'Avvolgibile', 'select_finiture' => 'Select finiture', ]; protected $fillable = [ 'sistema_id', 'codice', 'nome', 'descrizione', 'titolo', 'icona', 'ordine', 'attivo', 'cartella', 'file_modello', 'categoria', 'cartella_texture', 'moduli', 'regola_vetro_id', 'listino_aggiornato_at', ]; protected $casts = [ 'ordine' => 'integer', 'attivo' => 'boolean', 'moduli' => 'array', 'listino_aggiornato_at' => 'datetime', 'modello_3d_dimensione' => 'integer', 'modello_3d_draco_dimensione' => 'integer', 'modello_3d_aggiornato_at' => 'datetime', ]; public function sistema(): BelongsTo { return $this->belongsTo(Sistema::class); } public function listino(): HasMany { return $this->hasMany(ListinoPrezzo::class); } public function regolaVetro(): BelongsTo { return $this->belongsTo(RegolaVetro::class); } /** Opzioni personalizzate del modello (sostituiscono i gruppi del sistema). */ public function opzioni(): HasMany { return $this->hasMany(Opzione::class); } /** Tutte le finiture del profilo del sistema (per scegliere quali usare nel modello). */ public function finitureDelSistema(): HasMany { return $this->hasMany(Finitura::class, 'sistema_id', 'sistema_id')->where('tipo', 'profilo'); } /** Moduli selezionabili: quelli base più uno per ogni gruppo di opzioni con un controllo. */ public static function moduliDisponibili(): array { $gruppi = GruppoOpzioni::query() ->where('ui', '!=', 'nessuna') ->orderBy('ordine') ->pluck('etichetta', 'modulo') ->all(); return self::MODULI + $gruppi; } public function haModulo(string $modulo): bool { return in_array($modulo, $this->moduli ?? [], true); } public function scopeAttivi(Builder $query): Builder { return $query->where('attivo', true)->orderBy('ordine')->orderBy('id'); } /** * Modelli mostrati nel configuratore: attivi, con un listino (senza prezzi non si possono * vendere) e con il modello 3D caricato. */ public function scopeNelConfiguratore(Builder $query): Builder { return $query->attivi()->whereNotNull('modello_3d_draco')->whereHas('listino'); } /** URL del modello 3D compresso (Draco) letto dal configuratore. */ public function getModello3dUrlAttribute(): ?string { return Media::url($this->modello_3d_draco); } public function getIconaUrlAttribute(): ?string { return $this->icona ? Storage::disk('public')->url($this->icona) : null; } /** * Rinumera l'ordine dei modelli del sistema mettendo i disattivi in fondo, * senza cambiare l'ordine relativo all'interno dei due gruppi. */ public static function normalizzaOrdine(int $sistemaId): void { $ids = static::query() ->where('sistema_id', $sistemaId) ->orderByDesc('attivo') ->orderBy('ordine') ->orderBy('id') ->pluck('id'); DB::transaction(function () use ($ids) { foreach ($ids as $i => $id) { // Query diretta: niente eventi del modello (e niente ricorsione). static::query()->whereKey($id)->update(['ordine' => $i + 1]); } }); } protected static function booted(): void { // Attivando o disattivando un modello (o creandone uno), i disattivi tornano in fondo. static::saved(function (Modello $modello): void { if ($modello->wasRecentlyCreated || $modello->wasChanged(['attivo', 'sistema_id'])) { static::normalizzaOrdine($modello->sistema_id); } }); // Rimuove il file dell'icona sostituita o del modello eliminato. static::updated(function (Modello $modello): void { $vecchia = $modello->getOriginal('icona'); if ($modello->wasChanged('icona') && $vecchia) { Storage::disk('public')->delete($vecchia); } }); static::deleted(function (Modello $modello): void { if ($modello->icona) { Storage::disk('public')->delete($modello->icona); } app(Modelli3d::class)->elimina($modello); }); } }