diff --git a/app/Domains/Ticketing/Ticket/Models/Ticket.php b/app/Domains/Ticketing/Ticket/Models/Ticket.php index 0236eb70..12560bc3 100644 --- a/app/Domains/Ticketing/Ticket/Models/Ticket.php +++ b/app/Domains/Ticketing/Ticket/Models/Ticket.php @@ -7,6 +7,7 @@ use App\Domains\Commerce\Catalog\Models\Variant; use App\Domains\Commerce\Purchase\Models\PurchaseItem; use App\Domains\Core\Auth\Models\User; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Desfile\Models\EntryReservation; use App\Domains\Ticketing\Event\Models\Event; use App\Domains\Ticketing\Ticket\Services\ResolvedTicketValidity; use App\Domains\Ticketing\Ticket\Services\ResolvedValidityGroup; @@ -192,7 +193,9 @@ class Ticket extends Model public function can_refund(): bool { - return $this->is_active() && $this->allow_refund(); + return $this->is_active() + && $this->allow_refund() + && ! $this->hasEntryReservation(); } public function canRefund(): bool @@ -235,6 +238,12 @@ class Ticket extends Model return $this->hasOne(TicketRefund::class); } + /** @return HasOne */ + public function entryReservation(): HasOne + { + return $this->hasOne(EntryReservation::class); + } + /** @return BelongsTo */ public function sourceCatalogItem(): BelongsTo { @@ -247,6 +256,15 @@ class Ticket extends Model return $this->belongsTo(Variant::class, 'source_variant_id')->withTrashed(); } + private function hasEntryReservation(): bool + { + if ($this->relationLoaded('entryReservation')) { + return $this->getRelation('entryReservation') instanceof EntryReservation; + } + + return $this->exists && $this->entryReservation()->exists(); + } + public function isValid(): bool { if ($this->hasTerminalStatus() || $this->used_at !== null) { diff --git a/app/Domains/Ticketing/Ticket/Services/AdminAppTicketService.php b/app/Domains/Ticketing/Ticket/Services/AdminAppTicketService.php index f2c9a038..81dbfa0f 100644 --- a/app/Domains/Ticketing/Ticket/Services/AdminAppTicketService.php +++ b/app/Domains/Ticketing/Ticket/Services/AdminAppTicketService.php @@ -27,6 +27,7 @@ class AdminAppTicketService 'scannerUser', 'sourceCatalogItem.category', 'sourcePurchaseItem.purchase', + 'entryReservation', 'refund.createdBy', ]; diff --git a/tests/Feature/Ticket/AdminAppTicketControllerTest.php b/tests/Feature/Ticket/AdminAppTicketControllerTest.php index 012560c5..7e00c4b5 100644 --- a/tests/Feature/Ticket/AdminAppTicketControllerTest.php +++ b/tests/Feature/Ticket/AdminAppTicketControllerTest.php @@ -13,6 +13,8 @@ use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Core\Menu\Models\Menu; use App\Domains\Core\Tenant\Models\AdminWebsiteType; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Desfile\Enums\EntryReservationPaymentType; +use App\Domains\Ticketing\Desfile\Models\EntryReservation; use App\Domains\Ticketing\Ticket\Models\Ticket; use App\Domains\Ticketing\Ticket\Models\TicketRefund; use App\Shared\Attachable\Enums\AttachmentType; @@ -222,6 +224,54 @@ class AdminAppTicketControllerTest extends TestCase $this->assertSame(1, $purchaseItem->ticketRefunds()->count()); } + public function test_it_does_not_refund_a_reserved_desfile_entry(): void + { + $tenant = $this->createTenant('desfile_pura_tendencia'); + $tenant->update([ + 'allow_ticket_refund' => true, + 'allow_ticket_total_refund' => true, + ]); + $admin = $this->createAdminAppUser($tenant); + $this->grantTicketsMenu($tenant); + Sanctum::actingAs($admin); + [$ticket, $purchaseItem] = $this->createRefundableTicket($tenant, $admin, '100.00'); + $inventory = $ticket->sourceCatalogItem->inventory; + $variant = Variant::query()->create([ + 'catalog_item_id' => $ticket->source_catalog_item_id, + 'inventory_id' => $inventory->id, + 'precio' => '100.00', + ]); + $ticket->update(['source_variant_id' => $variant->id]); + EntryReservation::query()->create([ + 'variant_id' => $variant->id, + 'ticket_id' => $ticket->id, + 'inventory_id' => $inventory->id, + 'fecha_reserva' => now(), + 'importe' => '100.00', + 'tipo_pago' => EntryReservationPaymentType::Other, + ]); + + $this->getJson('/api/v1/adminapp/tenant/tickets') + ->assertOk() + ->assertJsonPath('data.0.id', $ticket->id) + ->assertJsonPath('data.0.allow_refund', true) + ->assertJsonPath('data.0.can_refund', false); + + $this->getJson("/api/v1/adminapp/tenant/tickets/{$ticket->id}/refund") + ->assertUnprocessable() + ->assertJsonValidationErrors('refund'); + + $this->postJson("/api/v1/adminapp/tenant/tickets/{$ticket->id}/refund", [ + 'refund_type' => TicketRefund::TYPE_TOTAL, + ]) + ->assertUnprocessable() + ->assertJsonValidationErrors('refund'); + + $this->assertNull($ticket->fresh()->refunded_at); + $this->assertSame(0, $purchaseItem->ticketRefunds()->count()); + $this->assertSame(0, $inventory->fresh()->refunded_units); + } + public function test_it_partially_refunds_a_ticket_using_the_tenant_percentage(): void { $tenant = $this->createTenant('ticket-partial-refund'); diff --git a/tests/Unit/Ticket/TicketTest.php b/tests/Unit/Ticket/TicketTest.php index 0bce1fed..e0f93480 100644 --- a/tests/Unit/Ticket/TicketTest.php +++ b/tests/Unit/Ticket/TicketTest.php @@ -2,10 +2,11 @@ namespace Tests\Unit\Ticket; -use App\Domains\Core\Auth\Models\User; use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Domains\Commerce\Catalog\Models\Variant; +use App\Domains\Core\Auth\Models\User; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Desfile\Models\EntryReservation; use App\Domains\Ticketing\Ticket\Enums\ValidityTimeType; use App\Domains\Ticketing\Ticket\Models\Ticket; use App\Domains\Ticketing\Ticket\Models\ValidityTime; @@ -54,6 +55,7 @@ class TicketTest extends TestCase $this->assertInstanceOf(User::class, $ticket->scannerUser()->getRelated()); $this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated()); $this->assertInstanceOf(Variant::class, $ticket->sourceVariant()->getRelated()); + $this->assertInstanceOf(EntryReservation::class, $ticket->entryReservation()->getRelated()); } public function test_unused_ticket_without_validity_time_is_valid(): void @@ -83,6 +85,21 @@ class TicketTest extends TestCase $this->assertFalse($active->can_refund()); } + public function test_a_ticket_with_an_entry_reservation_cannot_be_refunded(): void + { + $tenant = new Tenant([ + 'allow_ticket_refund' => true, + 'allow_ticket_total_refund' => true, + ]); + $ticket = (new Ticket) + ->setRelation('tenant', $tenant) + ->setRelation('entryReservation', new EntryReservation); + + $this->assertTrue($ticket->is_active()); + $this->assertTrue($ticket->allow_refund()); + $this->assertFalse($ticket->can_refund()); + } + public function test_fixed_window_controls_ticket_validity(): void { Carbon::setTestNow('2026-07-21 10:00:00');