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.
This commit is contained in:
@@ -4,6 +4,7 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Mail\PreventivoCliente;
|
||||
use App\Models\Cliente;
|
||||
use App\Models\Impostazione;
|
||||
use App\Models\Preventivo;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
@@ -53,6 +54,7 @@ class ConfermaPreventivoTest extends TestCase
|
||||
'citta' => 'Milano',
|
||||
'provincia' => 'MI',
|
||||
'note' => null,
|
||||
'trasporto' => 'campania_entro_150km',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -120,6 +122,176 @@ class ConfermaPreventivoTest extends TestCase
|
||||
$this->assertSame('mario.rossi@example.com', $preventivo->cliente->email);
|
||||
}
|
||||
|
||||
/** Carrello da 1000 € e cliente loggato; ritorna [preventivo, header Authorization]. */
|
||||
private function carrelloDaMilleConCliente(array $attributiCliente = []): array
|
||||
{
|
||||
Mockery::mock('alias:App\Support\PreventivoPdf')
|
||||
->shouldReceive('genera')
|
||||
->andReturn('preventivi/preventivo-1.pdf');
|
||||
Mail::fake();
|
||||
|
||||
$cliente = Cliente::create($attributiCliente + ['username' => 'user1234', 'password' => 'Segreta123!']);
|
||||
$token = $cliente->createToken('configuratore', ['*'])->plainTextToken;
|
||||
|
||||
$preventivo = $this->creaCarrelloBozza();
|
||||
$preventivo->items()->first()->update(['prezzo_unitario' => 1000, 'prezzo_totale' => 1000]);
|
||||
$preventivo->ricalcolaTotale();
|
||||
|
||||
return [$preventivo, ['Authorization' => "Bearer {$token}"]];
|
||||
}
|
||||
|
||||
public function test_regola_base_sconti_a_cascata(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente();
|
||||
|
||||
$this->getJson('/api/auth/me', $auth)
|
||||
->assertJsonPath('sconti.max_1', 50)
|
||||
->assertJsonPath('sconti.max_2', 10);
|
||||
|
||||
// Schema: 1000 -50% = 500, -10% extra sul già scontato = 450.
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 50, 'sconto_2' => 10], $auth)
|
||||
->assertOk();
|
||||
|
||||
$preventivo->refresh();
|
||||
$this->assertEquals(1000, $preventivo->subtotale);
|
||||
$this->assertEquals(450 + 100, $preventivo->totale);
|
||||
$this->assertSame(['sconto_1' => 500.0, 'sconto_2' => 50.0, 'imponibile' => 450.0], $preventivo->importiSconto());
|
||||
}
|
||||
|
||||
public function test_sconto_oltre_il_massimo_e_rifiutato(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente();
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 60], $auth)
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('sconto_1');
|
||||
|
||||
$this->assertSame('bozza', $preventivo->fresh()->stato);
|
||||
}
|
||||
|
||||
public function test_massimi_personalizzati_per_cliente(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente(['sconto_max_1' => 30, 'sconto_max_2' => 0]);
|
||||
|
||||
$this->getJson('/api/auth/me', $auth)
|
||||
->assertJsonPath('sconti.max_1', 30)
|
||||
->assertJsonPath('sconti.max_2', 0);
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 30, 'sconto_2' => 5], $auth)
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('sconto_2');
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 30], $auth)
|
||||
->assertOk();
|
||||
$this->assertEquals(700 + 100, $preventivo->fresh()->totale);
|
||||
}
|
||||
|
||||
public function test_regola_base_modificata_dal_backoffice(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente();
|
||||
Impostazione::imposta('sconti.max_1', 20);
|
||||
Impostazione::imposta('sconti.max_2', 5);
|
||||
|
||||
$this->getJson('/api/auth/me', $auth)
|
||||
->assertJsonPath('sconti.max_1', 20)
|
||||
->assertJsonPath('sconti.max_2', 5);
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 25], $auth)
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('sconto_1');
|
||||
}
|
||||
|
||||
public function test_sconti_disabilitati_dall_amministratore(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente(['sconti_abilitati' => false]);
|
||||
|
||||
$this->getJson('/api/auth/me', $auth)->assertJsonPath('sconti.max_1', 0);
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 10], $auth)
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('sconto_1');
|
||||
}
|
||||
|
||||
public function test_guest_non_puo_applicare_sconti(): void
|
||||
{
|
||||
Mockery::mock('alias:App\Support\PreventivoPdf')
|
||||
->shouldReceive('genera')
|
||||
->andReturn('preventivi/preventivo-1.pdf');
|
||||
Mail::fake();
|
||||
|
||||
$preventivo = $this->creaCarrelloBozza();
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma() + ['sconto_1' => 10])
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('sconto_1');
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $this->datiConferma())->assertOk();
|
||||
$this->assertEquals(500 + 100, $preventivo->fresh()->totale); // + trasporto entro 150 km, 1 pz
|
||||
}
|
||||
|
||||
public function test_trasporto_cadauno_dopo_gli_sconti(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente();
|
||||
// 10 finestre: il contributo si paga 10 volte e non entra negli sconti.
|
||||
$preventivo->items()->first()->update(['quantita' => 10]);
|
||||
|
||||
$dati = ['trasporto' => 'campania_oltre_150km', 'sconto_1' => 50, 'sconto_2' => 10] + $this->datiConferma();
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $dati, $auth)->assertOk();
|
||||
|
||||
$preventivo->refresh();
|
||||
$this->assertEquals(1500, $preventivo->trasporto_importo);
|
||||
$this->assertEquals(450 + 1500, $preventivo->totale);
|
||||
$this->assertFalse($preventivo->trasportoDaQuantificare());
|
||||
}
|
||||
|
||||
public function test_oltre_20_pezzi_trasporto_da_quantificare_senza_scelta(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente();
|
||||
$preventivo->items()->first()->update(['quantita' => 21]);
|
||||
|
||||
// Nessuna scelta richiesta (e quella inviata è ignorata): il trasporto è da quantificare.
|
||||
$dati = $this->datiConferma();
|
||||
unset($dati['trasporto']);
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", $dati, $auth)->assertOk();
|
||||
|
||||
$preventivo->refresh()->load('items', 'cliente');
|
||||
$this->assertSame(Preventivo::TRASPORTO_OLTRE_MAX, $preventivo->trasporto);
|
||||
$this->assertNull($preventivo->trasporto_importo);
|
||||
$this->assertEquals(1000, $preventivo->totale);
|
||||
|
||||
$html = view('pdf.preventivo', ['preventivo' => $preventivo])->render();
|
||||
$this->assertStringContainsString('Contributo per il trasporto da quantificare', $html);
|
||||
$this->assertStringContainsString('supera i 20 pezzi', $html);
|
||||
}
|
||||
|
||||
public function test_trasporto_altro_da_quantificare(): void
|
||||
{
|
||||
[$preventivo, $auth] = $this->carrelloDaMilleConCliente();
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", ['trasporto' => 'altro'] + $this->datiConferma(), $auth)->assertOk();
|
||||
|
||||
$preventivo->refresh();
|
||||
$this->assertNull($preventivo->trasporto_importo);
|
||||
$this->assertTrue($preventivo->trasportoDaQuantificare());
|
||||
$this->assertEquals(1000, $preventivo->totale);
|
||||
|
||||
$html = view('pdf.preventivo', ['preventivo' => $preventivo->load('items', 'cliente')])->render();
|
||||
$this->assertStringContainsString(config('preventivi.trasporto.nota_da_quantificare'), $html);
|
||||
}
|
||||
|
||||
public function test_trasporto_obbligatorio_e_valido(): void
|
||||
{
|
||||
$preventivo = $this->creaCarrelloBozza();
|
||||
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", ['trasporto' => null] + $this->datiConferma())
|
||||
->assertStatus(422)->assertJsonValidationErrors('trasporto');
|
||||
$this->postJson("/api/carrello/{$preventivo->token}/conferma", ['trasporto' => 'luna'] + $this->datiConferma())
|
||||
->assertStatus(422)->assertJsonValidationErrors('trasporto');
|
||||
|
||||
$this->getJson('/api/config')->assertJsonPath('trasporto.0.valore', 'campania_entro_150km')
|
||||
->assertJsonPath('trasporto.2.importo', null);
|
||||
}
|
||||
|
||||
public function test_conferma_con_token_non_valido_tratta_l_utente_come_guest(): void
|
||||
{
|
||||
Mockery::mock('alias:App\Support\PreventivoPdf')
|
||||
|
||||
@@ -315,6 +315,13 @@ class ConfiguratorePrezzoTest extends TestCase
|
||||
->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
|
||||
|
||||
Reference in New Issue
Block a user