Files
kreios/backoffice/tests/Feature/ConfiguratorePrezzoTest.php
T
carmine.savino 8bef57b64b feat: implement discount and transport features in the quote system
- Added maximum discount settings configurable by the administrator.
- Implemented discount calculations in the quote confirmation process.
- Introduced transport options with associated costs, including handling for exceeding maximum piece limits.
- Updated frontend to display discounts and transport information in the quote summary.
- Created new database migrations for storing discount and transport settings.
- Developed a new Filament page for managing global discount settings.
- Enhanced email templates to include discount and transport details.
- Added tests to ensure proper functionality of discounts and transport calculations.
2026-10-20 17:45:50 +02:00

434 lines
23 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Finitura;
use App\Models\ListinoPrezzo;
use App\Models\Modello;
use App\Models\Opzione;
use App\Models\PreventivoItem;
use App\Models\RegolaVetro;
use App\Models\Vetro;
use Database\Seeders\CatalogoSeeder;
use Database\Seeders\ConfiguratoreSeeder;
use Database\Seeders\NuoveOpzioniSeeder;
use Database\Seeders\ProfiliExtraSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ConfiguratorePrezzoTest extends TestCase
{
use RefreshDatabase;
private const BASE = 500;
protected function setUp(): void
{
parent::setUp();
Storage::fake('public');
$this->seed([CatalogoSeeder::class, ConfiguratoreSeeder::class, ProfiliExtraSeeder::class, NuoveOpzioniSeeder::class]);
}
/** Listino 1000-1400 x 1200-1400 mm: la cella 1200 x 1400 vale BASE. */
private function conListino(string $codice): Modello
{
$modello = Modello::where('codice', $codice)->firstOrFail();
foreach ([1000, 1200, 1400] as $l) {
foreach ([1200, 1400] as $h) {
ListinoPrezzo::create(['modello_id' => $modello->id, 'larghezza' => $l, 'altezza' => $h, 'prezzo' => $l === 1200 && $h === 1400 ? self::BASE : 100]);
}
}
return $modello;
}
private function prezzo(string $codice, array $scelte)
{
return $this->postJson("/api/sistemi/pvc/modelli/{$codice}/prezzo", $scelte);
}
public function test_il_seeder_crea_il_catalogo_pvc_con_le_immagini(): void
{
$this->assertSame(48, Finitura::where('tipo', 'profilo')->count());
$this->assertSame(4, Finitura::where('tipo', 'maniglia')->count());
$this->assertSame(14, Vetro::count());
$this->assertSame(19, RegolaVetro::count());
$this->assertSame('V3', Vetro::where('predefinito', true)->value('codice'));
$finestra = Modello::where('codice', 'finestra_2_ante')->firstOrFail();
$this->assertSame('F2', $finestra->regolaVetro->codice);
$this->assertSame('FINESTRA 2 ANTE PVC - SALAMANDER BE 82', $finestra->titolo);
$this->assertTrue($finestra->haModulo('finitura_esterna') && $finestra->haModulo('apertura'));
$this->assertNull(Modello::where('codice', 'cassonetto_80')->value('regola_vetro_id'));
// Icone di apertura proprie del modello; un file per record.
$apertura = $finestra->opzioni()->where('gruppo', 'apertura')->pluck('icona');
$this->assertCount(2, $apertura);
$apertura->each(fn ($p) => Storage::disk('public')->assertExists($p));
Storage::disk('public')->assertExists(Finitura::where('codice', '2')->value('texture'));
$this->assertCount(4, Vetro::whereNotNull('texture_bump')->pluck('texture_bump')->unique());
}
public function test_il_seeder_non_sovrascrive_il_catalogo_esistente(): void
{
Vetro::where('codice', 'V1')->update(['maggiorazione_mq' => 99]);
$this->seed(ConfiguratoreSeeder::class);
$this->assertSame(14, Vetro::count());
$this->assertEquals(99, Vetro::where('codice', 'V1')->value('maggiorazione_mq'));
}
public function test_prezzo_base_con_scelte_predefinite(): void
{
$this->conListino('finestra_2_ante');
// 1150 x 1300 -> prima misura a listino >= : 1200 x 1400. Bianco, vetro standard (0 €/m²), cerniere no.
$this->prezzo('finestra_2_ante', ['larghezza' => 1150, 'altezza' => 1300])
->assertOk()
->assertJsonPath('prezzo', self::BASE)
->assertJsonPath('dettaglio.misura_listino', ['larghezza' => 1200, 'altezza' => 1400])
->assertJsonPath('configurazione.tipo_finitura', 'Bianco liscio')
->assertJsonPath('configurazione.vetro', 'Standard trasparente')
->assertJsonPath('scelte.opzioni.apertura', 'dx');
}
public function test_supplementi_tipo_finitura_e_opzioni(): void
{
$this->conListino('finestra_2_ante');
$misura = ['larghezza' => 1200, 'altezza' => 1400];
// Stessa finitura non predefinita dentro e fuori: uniforme 27%.
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '2', 'finitura_esterna' => '2'])
->assertJsonPath('prezzo', 635)
->assertJsonPath('configurazione.tipo_finitura', 'Rivestimento uniforme');
// Finiture diverse: bicolore 30%, più cerniere a scomparsa (+100 € sulla finestra 2 ante).
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '1', 'finitura_esterna' => '2', 'opzioni' => ['cerniere_a_scomparsa' => 'si']])
->assertJsonPath('prezzo', 750)
->assertJsonPath('configurazione.cerniere_a_scomparsa', 'SI');
}
public function test_variazione_vetro_con_regola_standard(): void
{
$this->conListino('finestra_2_ante');
// F2, 1200 x 1400: 2 lastre (1200 - 312) / 2 = 444 x (1400 - 189) = 1211 -> 0,537684 m² ciascuna.
// Acustico base 80 €/m²: 2 x 0,537684 x 80 = 86,02944.
$this->prezzo('finestra_2_ante', ['larghezza' => 1200, 'altezza' => 1400, 'vetro' => 'V1'])
->assertOk()
->assertJsonPath('prezzo', 586.03)
->assertJsonPath('dettaglio.vetro.regola', 'F2')
->assertJsonPath('dettaglio.vetro.lastre.0.larghezza', 444)
->assertJsonPath('dettaglio.vetro.lastre.0.mq', 0.538)
->assertJsonPath('dettaglio.vetro.mq_fatturabili', 1.075);
// Senza vetro: -90 €/m², minimo 0 -> 500 - 96,78312.
$this->prezzo('finestra_2_ante', ['larghezza' => 1200, 'altezza' => 1400, 'vetro' => 'V7'])
->assertJsonPath('prezzo', 403.22);
}
public function test_il_minimo_fatturabile_vale_per_singola_lastra(): void
{
$modello = Modello::where('codice', 'finestra_1_anta')->firstOrFail();
ListinoPrezzo::create(['modello_id' => $modello->id, 'larghezza' => 600, 'altezza' => 600, 'prezzo' => 200]);
// F1, 600 x 600: 411 x 411 = 0,168921 m² < 0,5 -> si fattura 0,5 x 40 €/m² (satinato).
$this->prezzo('finestra_1_anta', ['larghezza' => 600, 'altezza' => 600, 'vetro' => 'V2'])
->assertJsonPath('prezzo', 220)
->assertJsonPath('dettaglio.vetro.lastre.0.mq_fatturabili', 0.5);
}
public function test_traslante_anta_piu_fisso_calcola_le_due_lastre_separatamente(): void
{
$modello = Modello::where('codice', 'traslante_scorrevole_1_anta_f')->firstOrFail();
$modello->update(['attivo' => true, 'cartella' => 'x', 'file_modello' => 'x.glb', 'moduli' => [...$modello->moduli, 'listino', 'vetro']]);
ListinoPrezzo::create(['modello_id' => $modello->id, 'larghezza' => 2000, 'altezza' => 2200, 'prezzo' => 1000]);
// Fisso (2000/2 - 81) x (2200 - 105) = 919 x 2095; anta (2000/2 - 181) x (2200 - 205) = 819 x 1995.
// (1,925305 + 1,633905) x 80 €/m² = 284,7368.
$this->prezzo('traslante_scorrevole_1_anta_f', ['larghezza' => 2000, 'altezza' => 2200, 'vetro' => 'V1'])
->assertOk()
->assertJsonPath('prezzo', 1284.74)
->assertJsonPath('dettaglio.vetro.lastre.0.descrizione', 'Vetro fisso')
->assertJsonPath('dettaglio.vetro.lastre.1.larghezza', 819);
}
public function test_misura_fuori_listino_o_scelta_inesistente_restituiscono_422(): void
{
$this->conListino('finestra_2_ante');
$this->prezzo('finestra_2_ante', ['larghezza' => 900, 'altezza' => 1400])->assertUnprocessable()->assertJsonValidationErrors('larghezza');
$this->prezzo('finestra_2_ante', ['larghezza' => 1500, 'altezza' => 1400])->assertUnprocessable();
$this->prezzo('finestra_2_ante', ['larghezza' => 1200, 'altezza' => 1400, 'vetro' => 'V99'])->assertJsonValidationErrors('vetro');
$this->prezzo('finestra_2_ante', ['larghezza' => 1200, 'altezza' => 1400, 'opzioni' => ['apertura' => 'su']])->assertJsonValidationErrors('opzioni.apertura');
}
public function test_le_opzioni_del_modello_sostituiscono_il_gruppo_del_sistema(): void
{
$modello = $this->conListino('finestra_2_ante');
Opzione::create([
'sistema_id' => $modello->sistema_id, 'modello_id' => $modello->id, 'gruppo' => 'tipo_finitura',
'valore' => 'uniforme', 'etichetta' => 'Uniforme speciale', 'supplemento_tipo' => 'percentuale', 'supplemento' => 40,
]);
$misura = ['larghezza' => 1200, 'altezza' => 1400];
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '2', 'finitura_esterna' => '2'])->assertJsonPath('prezzo', 700);
// Il gruppo personalizzato non ha "bicolore": nessun supplemento.
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '1', 'finitura_esterna' => '2'])->assertJsonPath('prezzo', self::BASE);
}
public function test_configurazione_espone_menu_e_immagini_senza_prezzi(): void
{
$this->conListino('finestra_2_ante');
$json = $this->getJson('/api/sistemi/pvc/modelli/finestra_2_ante/configurazione')
->assertOk()
->assertJsonCount(48, 'finiture')
->assertJsonCount(14, 'vetri')
->assertJsonCount(6, 'misure')
->assertJsonPath('form.radios.apertura.1.scale_x', -1)
->assertJsonPath('form.select.cerniere_a_scomparsa.1.func', 'nascondiCerniere')
->assertJsonPath('form.finiture.1.nome', 'finitura_esterna')
->json();
$this->assertStringContainsString('/api/media/configuratore/pvc/opzioni/apertura/finestra_2_ante/', $json['form']['radios']['apertura'][0]['icon']);
$this->assertStringContainsString('/api/media/configuratore/pvc/finiture/texture/2/2_A.jpg', $json['finiture'][1]['texture_map']);
$this->assertArrayNotHasKey('maggiorazione_mq', $json['vetri'][0]);
$this->assertArrayNotHasKey('supplemento', $json['form']['select']['cerniere_a_scomparsa'][1]);
// Modello senza finitura esterna né vetro (cassonetto).
$this->conListino('cassonetto_80');
$this->getJson('/api/sistemi/pvc/modelli/cassonetto_80/configurazione')
->assertJsonCount(1, 'form.finiture')
->assertJsonCount(0, 'vetri')
->assertJsonPath('form.radios', []);
}
public function test_media_serve_le_immagini_con_cors_e_blocca_altri_percorsi(): void
{
$percorso = Vetro::where('codice', 'V2')->value('texture_bump');
$this->get("/api/media/{$percorso}", ['Origin' => 'https://www.kreiosinfissi.it'])
->assertOk()
->assertHeader('Access-Control-Allow-Origin');
Storage::disk('public')->put('preventivi/segreto.png', 'x');
$this->get('/api/media/preventivi/segreto.png')->assertNotFound();
$this->get('/api/media/configuratore/../preventivi/segreto.png')->assertNotFound();
$this->get('/api/media/configuratore/pvc/inesistente.jpg')->assertNotFound();
}
public function test_il_carrello_ricalcola_il_prezzo_e_ignora_quello_del_client(): void
{
$this->conListino('finestra_2_ante');
$response = $this->postJson('/api/carrello/aggiungi', [
'sistema' => 'pvc',
'type' => 'finestra_2_ante',
'qty' => 2,
'prezzo' => 1,
'scelte' => ['larghezza' => 1200, 'altezza' => 1400, 'finitura_interna' => '2', 'finitura_esterna' => '2', 'vetro' => 'V1'],
'payload' => ['vetro' => 'Vetro inventato', 'altezza_maniglia' => '1050 mm'],
])->assertCreated();
$item = PreventivoItem::firstOrFail();
// 500 x 1,27 + 86,02944
$this->assertEquals(721.03, $item->prezzo_unitario);
$this->assertEquals(1442.06, $item->prezzo_totale);
$this->assertSame('Acustico base', $item->payload['vetro']);
$this->assertSame('1050 mm', $item->payload['altezza_maniglia']);
$this->assertSame('FINESTRA 2 ANTE PVC - SALAMANDER BE 82', $item->nome_modello);
$response->assertJsonPath('carrello.items.0.prezzo_unitario', 721.03);
// Senza scelte non si può aggiungere: il prezzo non arriva più dal client.
$this->postJson('/api/carrello/aggiungi', ['sistema' => 'pvc', 'type' => 'finestra_2_ante', 'prezzo' => 1])
->assertJsonValidationErrors('scelte');
}
public function test_lato_delle_finiture_sul_sistema(): void
{
$this->conListino('finestra_2_ante');
Finitura::where('tipo', 'profilo')->where('codice', '2')->update(['lato' => 'interno']);
Finitura::where('tipo', 'profilo')->where('codice', '3')->update(['lato' => 'esterno']);
$json = $this->getJson('/api/sistemi/pvc/modelli/finestra_2_ante/configurazione')->assertOk()->json();
$interne = array_column($json['finiture_interne'], 'id');
$esterne = array_column($json['finiture_esterne'], 'id');
$this->assertContains('2', $interne);
$this->assertNotContains('2', $esterne);
$this->assertContains('3', $esterne);
$this->assertNotContains('3', $interne);
$this->assertSame('data_finiture_esterne', $json['form']['finiture'][1]['data']);
$misura = ['larghezza' => 1200, 'altezza' => 1400];
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '2', 'finitura_esterna' => '3'])->assertOk();
$this->prezzo('finestra_2_ante', $misura + ['finitura_esterna' => '2'])->assertJsonValidationErrors('finitura_esterna');
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '3'])->assertJsonValidationErrors('finitura_interna');
}
public function test_attivazione_e_lato_delle_finiture_per_modello(): void
{
$modello = $this->conListino('finestra_2_ante');
$this->conListino('finestra_1_anta');
$finitura = fn (string $codice) => Finitura::where('tipo', 'profilo')->where('codice', $codice)->value('id');
Finitura::whereKey($finitura('5'))->update(['attivo' => false]);
DB::table('finiture_modelli')->insert([
// Attiva nel sistema, disattivata nel modello.
['modello_id' => $modello->id, 'finitura_id' => $finitura('4'), 'lato' => 'nessuno'],
// Disattiva nel sistema, attivata nel modello solo sull'esterno.
['modello_id' => $modello->id, 'finitura_id' => $finitura('5'), 'lato' => 'esterno'],
]);
$json = $this->getJson('/api/sistemi/pvc/modelli/finestra_2_ante/configurazione')->json();
$this->assertNotContains('4', array_column($json['finiture_interne'], 'id'));
$this->assertNotContains('4', array_column($json['finiture_esterne'], 'id'));
$this->assertContains('5', array_column($json['finiture_esterne'], 'id'));
$this->assertNotContains('5', array_column($json['finiture_interne'], 'id'));
$misura = ['larghezza' => 1200, 'altezza' => 1400];
$this->prezzo('finestra_2_ante', $misura + ['finitura_esterna' => '4'])->assertJsonValidationErrors('finitura_esterna');
$this->prezzo('finestra_2_ante', $misura + ['finitura_esterna' => '5'])->assertOk()->assertJsonPath('configurazione.tipo_finitura', 'Bicolore');
// Gli altri modelli seguono il sistema.
$altro = $this->getJson('/api/sistemi/pvc/modelli/finestra_1_anta/configurazione')->json();
$this->assertContains('4', array_column($altro['finiture_interne'], 'id'));
$this->assertNotContains('5', array_column($altro['finiture_esterne'], 'id'));
}
public function test_profili_extra_percentuale_metro_lineare_e_cadauno(): void
{
$this->conListino('finestra_2_ante');
$misura = ['larghezza' => 1200, 'altezza' => 1400];
// Nessun profilo: nessun supplemento.
$this->prezzo('finestra_2_ante', $misura)->assertJsonPath('prezzo', self::BASE)->assertJsonPath('scelte.opzioni.profili_extra', []);
// Coprifili scatolati +3% = 15; guida 34: (1400 + 100) x 2 = 3 ml x 15 = 45; HO9320: 85 cad.;
// NP8300: perimetro (1200 + 1400) x 2 = 5,2 ml x 25 = 130.
$this->prezzo('finestra_2_ante', $misura + ['opzioni' => ['profili_extra' => ['NP8300', 'coprifili_scatolati', '50811', '9320']]])
->assertOk()
->assertJsonPath('prezzo', 775)
->assertJsonPath('configurazione.profili_extra', 'COPRIFILI SCATOLATI, 50811_GUIDA 34, HO9320_T/M, NP8300_ALL.30')
->assertJsonPath('dettaglio.supplementi.1.metri_lineari', 3);
// I profili extra si calcolano sul serramento con le finiture: uniforme 500 x 1,27 = 635,
// coprifili +3% di 635 = 19,05; la cerniera (+100) viene dopo e non entra nel 3%.
$this->prezzo('finestra_2_ante', $misura + ['finitura_interna' => '2', 'finitura_esterna' => '2', 'opzioni' => ['profili_extra' => ['coprifili_scatolati'], 'cerniere_a_scomparsa' => 'si']])
->assertJsonPath('prezzo', 754.05)
->assertJsonPath('dettaglio.subtotali.finitura', 635)
->assertJsonPath('dettaglio.subtotali.profili', 654.05);
}
public function test_profili_extra_alternativi_e_per_modello(): void
{
$this->conListino('finestra_2_ante');
$this->conListino('celino');
$misura = ['larghezza' => 1200, 'altezza' => 1400];
// Piatti e scatolati sono alternativi; il frontale è solo del cielino.
$this->prezzo('finestra_2_ante', $misura + ['opzioni' => ['profili_extra' => ['coprifili_piatti', 'coprifili_scatolati']]])
->assertJsonValidationErrors('opzioni.profili_extra');
$this->prezzo('finestra_2_ante', $misura + ['opzioni' => ['profili_extra' => ['406200']]])
->assertJsonValidationErrors('opzioni.profili_extra');
// Cielino: frontale 4062000 solo sulla larghezza, 1,2 ml x 145 = 174.
$this->prezzo('celino', $misura + ['opzioni' => ['profili_extra' => ['406200']]])
->assertOk()
->assertJsonPath('prezzo', 674);
$this->prezzo('celino', $misura + ['opzioni' => ['profili_extra' => ['9320']]])
->assertJsonValidationErrors('opzioni.profili_extra');
$finestra = $this->getJson('/api/sistemi/pvc/modelli/finestra_2_ante/configurazione')
->assertJsonCount(8, 'form.multiple.profili_extra')
->assertJsonPath('form.multiple.profili_extra.0.alternativa', 'coprifili')
->json();
$this->assertStringContainsString('/api/media/configuratore/pvc/opzioni/sistema/profili_extra/coprifili_piatti.jpg', $finestra['form']['multiple']['profili_extra'][0]['icon']);
$this->getJson('/api/sistemi/pvc/modelli/celino/configurazione')
->assertJsonCount(1, 'form.multiple.profili_extra')
->assertJsonPath('form.multiple.profili_extra.0.valore', '406200');
$this->conListino('scuretto_base');
$this->getJson('/api/sistemi/pvc/modelli/scuretto_base/configurazione')->assertJsonPath('form.multiple', []);
}
public function test_il_seeder_dei_profili_extra_gira_una_volta(): void
{
Opzione::where('gruppo', 'profili_extra')->whereNull('modello_id')->where('valore', '9320')->update(['supplemento' => 90]);
$this->seed(ProfiliExtraSeeder::class);
$this->assertSame(9, Opzione::where('gruppo', 'profili_extra')->count());
$this->assertEquals(90, Opzione::where('gruppo', 'profili_extra')->where('valore', '9320')->value('supplemento'));
$this->assertFalse(Modello::where('codice', 'cassonetto_80')->firstOrFail()->haModulo('profili_extra'));
$this->assertTrue(Modello::where('codice', 'celino')->firstOrFail()->haModulo('profili_extra'));
}
public function test_cerniere_soglia_e_motore_per_modello(): void
{
$misura = ['larghezza' => 1200, 'altezza' => 1400];
foreach (['finestra_1_anta', 'finestra_3_ante', 'portafinestra_2_ante', 'finestra_vasistas', 'porta_1_anta'] as $codice) {
$this->conListino($codice);
}
// Cerniere a scomparsa: 50 € di sistema, 150 € sulla finestra 3 ante.
$this->prezzo('finestra_1_anta', $misura + ['opzioni' => ['cerniere_a_scomparsa' => 'si']])->assertJsonPath('prezzo', 550);
$this->prezzo('finestra_3_ante', $misura + ['opzioni' => ['cerniere_a_scomparsa' => 'si']])->assertJsonPath('prezzo', 650);
// Portafinestra 2 ante: cerniere 100 + soglia ribassata 105.
$this->prezzo('portafinestra_2_ante', $misura + ['opzioni' => ['cerniere_a_scomparsa' => 'si', 'soglia_ribassata' => 'si']])
->assertJsonPath('prezzo', 705)
->assertJsonPath('configurazione.soglia_ribassata', 'SI');
// Wasistas: motore 150 €, nasconde la maniglia nel 3D.
$this->prezzo('finestra_vasistas', $misura + ['opzioni' => ['motore' => 'si']])
->assertJsonPath('prezzo', 650)
->assertJsonPath('configurazione.motore', 'SI');
$this->getJson('/api/sistemi/pvc/modelli/finestra_vasistas/configurazione')
->assertJsonPath('form.select.motore.1.func', 'nascondiCategoria')
->assertJsonPath('form.select.motore.1.categoria', 'maniglia')
->assertJsonPath('form.gruppi.motore.sezione', 'accessori')
->assertJsonPath('form.gruppi.motore.etichetta', 'Motore');
// Porta: niente cerniere a scomparsa né soglia (foglio "Nomi": NO).
$porta = $this->getJson('/api/sistemi/pvc/modelli/porta_1_anta/configurazione')->json('form.select');
$this->assertSame([], $porta);
$this->prezzo('porta_1_anta', $misura + ['opzioni' => ['soglia_ribassata' => 'si']])->assertJsonPath('prezzo', self::BASE);
}
public function test_un_gruppo_creato_dal_backoffice_compare_nel_configuratore(): void
{
$modello = $this->conListino('finestra_2_ante');
\App\Models\GruppoOpzioni::create(['codice' => 'zanzariera', 'etichetta' => 'Zanzariera', 'ui' => 'select', 'sezione' => 'accessori', 'modulo' => 'zanzariera', 'ordine' => 9]);
foreach ([['no', 'NO', 0], ['si', 'SI', 80]] as [$valore, $etichetta, $prezzo]) {
Opzione::create(['sistema_id' => $modello->sistema_id, 'gruppo' => 'zanzariera', 'valore' => $valore, 'etichetta' => $etichetta, 'supplemento_tipo' => 'fisso', 'supplemento' => $prezzo]);
}
$misura = ['larghezza' => 1200, 'altezza' => 1400, 'opzioni' => ['zanzariera' => 'si']];
// Senza il modulo nel modello il gruppo non c'è.
$this->prezzo('finestra_2_ante', $misura)->assertJsonPath('prezzo', self::BASE);
$modello->update(['moduli' => [...$modello->moduli, 'zanzariera']]);
$this->assertArrayHasKey('zanzariera', Modello::moduliDisponibili());
$this->prezzo('finestra_2_ante', $misura)->assertJsonPath('prezzo', 580);
$this->getJson('/api/sistemi/pvc/modelli/finestra_2_ante/configurazione')->assertJsonPath('form.gruppi.zanzariera.etichetta', 'Zanzariera');
}
public function test_il_seeder_delle_nuove_opzioni_gira_una_volta(): void
{
Opzione::where('gruppo', 'motore')->where('valore', 'si')->update(['supplemento' => 170]);
$this->seed(NuoveOpzioniSeeder::class);
$this->assertEquals(170, Opzione::where('gruppo', 'motore')->where('valore', 'si')->value('supplemento'));
$this->assertSame(2, Opzione::where('gruppo', 'motore')->count());
$vasistas = Modello::where('codice', 'finestra_vasistas')->firstOrFail();
$this->assertTrue($vasistas->haModulo('motore') && $vasistas->haModulo('cerniere'));
$this->assertFalse(Modello::where('codice', 'finestra_anta_fissa')->firstOrFail()->haModulo('cerniere'));
}
}