From 4bb4f526e47579b704e7ef439961d8566daad8da Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 11 Sep 2026 16:22:46 -0300 Subject: [PATCH] feat(event): support ticket refund configuration in event endpoints --- .../Event/Requests/UpdateEventRequest.php | 45 ++++++++++++++ app/Domains/Event/Resources/EventResource.php | 4 ++ app/Domains/Event/Services/EventService.php | 6 ++ .../Event/AdminAppEventControllerTest.php | 62 +++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/app/Domains/Event/Requests/UpdateEventRequest.php b/app/Domains/Event/Requests/UpdateEventRequest.php index adf383d..6867b03 100644 --- a/app/Domains/Event/Requests/UpdateEventRequest.php +++ b/app/Domains/Event/Requests/UpdateEventRequest.php @@ -33,6 +33,16 @@ class UpdateEventRequest extends FormRequest 'contact.whatsapp_url' => ['nullable', 'url', 'max:2048'], 'contact.instagram_url' => ['nullable', 'url', 'max:2048'], 'contact.facebook_url' => ['nullable', 'url', 'max:2048'], + 'allow_ticket_refund' => ['sometimes', 'boolean'], + 'allow_ticket_total_refund' => ['sometimes', 'boolean'], + 'allow_ticket_partial_refund' => ['sometimes', 'boolean'], + 'ticket_partial_refund_percentage' => [ + 'sometimes', + 'numeric', + 'decimal:0,2', + 'min:0', + 'max:99.99', + ], ]; } @@ -49,6 +59,41 @@ class UpdateEventRequest extends FormRequest 'The social media field is required.' ); } + + if (! array_key_exists('allow_ticket_refund', $input)) { + return; + } + + foreach ([ + 'allow_ticket_total_refund', + 'allow_ticket_partial_refund', + 'ticket_partial_refund_percentage', + ] as $field) { + if (! array_key_exists($field, $input)) { + $validator->errors()->add($field, 'El campo es obligatorio.'); + } + } + + $totalEnabled = $this->boolean('allow_ticket_total_refund'); + $partialEnabled = $this->boolean('allow_ticket_partial_refund'); + + $refundEnabled = $this->boolean('allow_ticket_refund'); + + if ($refundEnabled && ! $totalEnabled && ! $partialEnabled) { + $validator->errors()->add( + 'allow_ticket_refund', + 'Seleccioná al menos un tipo de reembolso.' + ); + } + + if ($refundEnabled + && $partialEnabled + && (float) ($input['ticket_partial_refund_percentage'] ?? 0) <= 0) { + $validator->errors()->add( + 'ticket_partial_refund_percentage', + 'Ingresá un porcentaje mayor que cero para el reembolso parcial.' + ); + } }, ]; } diff --git a/app/Domains/Event/Resources/EventResource.php b/app/Domains/Event/Resources/EventResource.php index feed495..207cf42 100644 --- a/app/Domains/Event/Resources/EventResource.php +++ b/app/Domains/Event/Resources/EventResource.php @@ -18,6 +18,10 @@ class EventResource extends JsonResource 'id' => $this->id, 'title' => $this->event_title, 'location' => $this->event_location, + 'allow_ticket_refund' => $this->allow_ticket_refund, + 'allow_ticket_total_refund' => $this->allow_ticket_total_refund, + 'allow_ticket_partial_refund' => $this->allow_ticket_partial_refund, + 'ticket_partial_refund_percentage' => $this->ticket_partial_refund_percentage, 'dates' => EventDateResource::collection($this->eventDates), 'social_media' => $this->socialMedia->map(fn ($item): array => [ 'code' => $item->code, diff --git a/app/Domains/Event/Services/EventService.php b/app/Domains/Event/Services/EventService.php index 7561e59..c3b19c8 100644 --- a/app/Domains/Event/Services/EventService.php +++ b/app/Domains/Event/Services/EventService.php @@ -34,6 +34,12 @@ class EventService $tenant->update([ 'event_title' => $data['title'], 'event_location' => $data['location'], + ...array_intersect_key($data, array_flip([ + 'allow_ticket_refund', + 'allow_ticket_total_refund', + 'allow_ticket_partial_refund', + 'ticket_partial_refund_percentage', + ])), ]); if (array_key_exists('social_media', $data)) { diff --git a/tests/Feature/Event/AdminAppEventControllerTest.php b/tests/Feature/Event/AdminAppEventControllerTest.php index 5cfe5b0..ebf2591 100644 --- a/tests/Feature/Event/AdminAppEventControllerTest.php +++ b/tests/Feature/Event/AdminAppEventControllerTest.php @@ -51,6 +51,10 @@ class AdminAppEventControllerTest extends TestCase ->assertOk() ->assertJsonPath('data.title', 'Festival Acme') ->assertJsonPath('data.location', 'Predio Ferial, Rosario') + ->assertJsonPath('data.allow_ticket_refund', true) + ->assertJsonPath('data.allow_ticket_total_refund', true) + ->assertJsonPath('data.allow_ticket_partial_refund', true) + ->assertJsonPath('data.ticket_partial_refund_percentage', '25.50') ->assertJsonCount(0, 'data.dates') ->assertJsonPath('data.contact.whatsapp_url', 'https://wa.me/5493415550101') ->assertJsonPath('data.contact.instagram_url', 'https://instagram.com/acme') @@ -62,6 +66,10 @@ class AdminAppEventControllerTest extends TestCase 'event_title' => 'Festival Acme', 'event_location' => 'Predio Ferial, Rosario', 'event_date_text' => null, + 'allow_ticket_refund' => true, + 'allow_ticket_total_refund' => true, + 'allow_ticket_partial_refund' => true, + 'ticket_partial_refund_percentage' => 25.50, ]); $this->assertDatabaseCount('event_dates', 0); $this->assertDatabaseHas('tenant_social_media', [ @@ -71,6 +79,56 @@ class AdminAppEventControllerTest extends TestCase ]); } + public function test_disabling_refunds_preserves_the_configured_types_and_percentage(): void + { + $tenant = $this->createTenant('acme'); + $tenant->update([ + 'allow_ticket_refund' => true, + 'allow_ticket_total_refund' => true, + 'allow_ticket_partial_refund' => true, + 'ticket_partial_refund_percentage' => 35.50, + ]); + Sanctum::actingAs($this->createAdminAppUser($tenant)); + + $payload = $this->eventPayload(); + $payload['allow_ticket_refund'] = false; + $payload['ticket_partial_refund_percentage'] = 35.50; + + $this->putJson('/api/v1/adminapp/tenant/event', $payload) + ->assertOk() + ->assertJsonPath('data.allow_ticket_refund', false) + ->assertJsonPath('data.allow_ticket_total_refund', true) + ->assertJsonPath('data.allow_ticket_partial_refund', true) + ->assertJsonPath('data.ticket_partial_refund_percentage', '35.50'); + + $tenant->refresh(); + $this->assertFalse($tenant->allow_refund()); + $this->assertTrue($tenant->allow_ticket_total_refund); + $this->assertTrue($tenant->allow_ticket_partial_refund); + $this->assertSame('35.50', $tenant->ticket_partial_refund_percentage); + } + + public function test_enabled_refunds_require_a_type_and_a_valid_partial_percentage(): void + { + $tenant = $this->createTenant('acme'); + Sanctum::actingAs($this->createAdminAppUser($tenant)); + + $payload = $this->eventPayload(); + $payload['allow_ticket_total_refund'] = false; + $payload['allow_ticket_partial_refund'] = false; + + $this->putJson('/api/v1/adminapp/tenant/event', $payload) + ->assertUnprocessable() + ->assertJsonValidationErrors('allow_ticket_refund'); + + $payload['allow_ticket_partial_refund'] = true; + $payload['ticket_partial_refund_percentage'] = 0; + + $this->putJson('/api/v1/adminapp/tenant/event', $payload) + ->assertUnprocessable() + ->assertJsonValidationErrors('ticket_partial_refund_percentage'); + } + public function test_an_adminapp_user_can_read_only_its_tenant_active_event(): void { $tenant = $this->createTenant('acme'); @@ -375,6 +433,10 @@ class AdminAppEventControllerTest extends TestCase return [ 'title' => 'Festival Acme', 'location' => 'Predio Ferial, Rosario', + 'allow_ticket_refund' => true, + 'allow_ticket_total_refund' => true, + 'allow_ticket_partial_refund' => true, + 'ticket_partial_refund_percentage' => 25.50, 'contact' => [ 'whatsapp_url' => 'https://wa.me/5493415550101', 'instagram_url' => 'https://instagram.com/acme',