with(['modelli' => fn ($q) => $q->nelConfiguratore()]) ->get(); return response()->json($sistemi->map(fn (Sistema $s) => $this->sistema($s))->values()); } /** * GET /api/sistemi/{codice} */ public function sistemaPerCodice(string $codice): JsonResponse { $sistema = Sistema::attivi() ->where('codice', $codice) ->with(['modelli' => fn ($q) => $q->nelConfiguratore()]) ->firstOrFail(); return response()->json($this->sistema($sistema)); } /** * GET /api/sistemi/{codice}/modelli/{modello}/listino * Griglia prezzi del modello (misure in mm), ordinata per altezza e larghezza. */ public function listino(string $codice, string $modello): JsonResponse { $prezzi = $this->trovaModello($codice, $modello)->listino() ->orderBy('altezza') ->orderBy('larghezza') ->get(['altezza', 'larghezza', 'prezzo']) ->map(fn ($p) => ['altezza' => $p->altezza, 'larghezza' => $p->larghezza, 'prezzo' => $p->prezzo]); return response()->json($prezzi); } /** * GET /api/sistemi/{codice}/modelli/{modello}/configurazione * Menu, finiture, vetri, opzioni e misure del modello per il configuratore. */ public function configurazione(string $codice, string $modello, ConfigurazioneModello $configurazione): JsonResponse { return response()->json($configurazione->perModello($this->trovaModello($codice, $modello))); } /** * POST /api/sistemi/{codice}/modelli/{modello}/prezzo * Prezzo (IVA esclusa) della configurazione scelta; 422 se la misura è fuori listino. */ public function prezzo(Request $request, string $codice, string $modello, CalcolatorePrezzo $calcolatore): JsonResponse { $scelte = $request->validate(self::regoleScelte()); return response()->json($calcolatore->calcola($this->trovaModello($codice, $modello), $scelte)); } /** Validazione delle scelte del configuratore, condivisa con il carrello. */ public static function regoleScelte(string $prefisso = ''): array { return [ "{$prefisso}larghezza" => ['required', 'integer', 'min:1', 'max:100000'], "{$prefisso}altezza" => ['required', 'integer', 'min:1', 'max:100000'], "{$prefisso}finitura_interna" => ['nullable', 'string', 'max:50'], "{$prefisso}finitura_esterna" => ['nullable', 'string', 'max:50'], "{$prefisso}maniglia" => ['nullable', 'string', 'max:50'], "{$prefisso}vetro" => ['nullable', 'string', 'max:50'], "{$prefisso}opzioni" => ['nullable', 'array'], // Un codice per i gruppi a scelta singola, una lista di codici per quelli multipli. "{$prefisso}opzioni.*" => ['nullable', function (string $campo, mixed $valore, \Closure $errore) { $codici = is_array($valore) ? $valore : [$valore]; if (count($codici) > 50 || collect($codici)->contains(fn ($c) => ! is_string($c) || mb_strlen($c) > 100)) { $errore('Scelta non valida.'); } }], ]; } /** Modello attivo e con listino di un sistema attivo, cercato per codici (404 altrimenti). */ public static function trovaModello(string $codice, string $modello): Modello { return Modello::nelConfiguratore() ->where('codice', $modello) ->whereHas('sistema', fn ($q) => $q->where('codice', $codice)->where('attivo', true)) ->with('sistema', 'regolaVetro') ->firstOrFail(); } private function sistema(Sistema $sistema): array { return [ 'codice' => $sistema->codice, 'nome' => $sistema->nome, 'modelli' => $sistema->modelli->map(fn (Modello $m) => $this->modello($m))->values(), ]; } // Stessa forma del vecchio modelli.json (id, nome, cartella, modello, ...) più descrizione e icona. private function modello(Modello $modello): array { return [ 'id' => $modello->codice, 'nome' => $modello->nome, 'descrizione' => $modello->descrizione, 'icona' => $modello->icona_url, 'cartella' => $modello->cartella, // Modello 3D compresso con Draco, caricato dal backoffice. 'modello_url' => $modello->modello_3d_url, 'categoria' => $modello->categoria, 'cartella_texture' => $modello->cartella_texture, 'moduli' => $modello->moduli ?? [], 'enabled' => true, ]; } }