feat(ticket): add entry reservation handling to ticket refund logic and tests
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Domains\Commerce\Catalog\Models\Variant;
|
|||||||
use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
use App\Domains\Commerce\Purchase\Models\PurchaseItem;
|
||||||
use App\Domains\Core\Auth\Models\User;
|
use App\Domains\Core\Auth\Models\User;
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
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\Event\Models\Event;
|
||||||
use App\Domains\Ticketing\Ticket\Services\ResolvedTicketValidity;
|
use App\Domains\Ticketing\Ticket\Services\ResolvedTicketValidity;
|
||||||
use App\Domains\Ticketing\Ticket\Services\ResolvedValidityGroup;
|
use App\Domains\Ticketing\Ticket\Services\ResolvedValidityGroup;
|
||||||
@@ -192,7 +193,9 @@ class Ticket extends Model
|
|||||||
|
|
||||||
public function can_refund(): bool
|
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
|
public function canRefund(): bool
|
||||||
@@ -235,6 +238,12 @@ class Ticket extends Model
|
|||||||
return $this->hasOne(TicketRefund::class);
|
return $this->hasOne(TicketRefund::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return HasOne<EntryReservation, $this> */
|
||||||
|
public function entryReservation(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(EntryReservation::class);
|
||||||
|
}
|
||||||
|
|
||||||
/** @return BelongsTo<CatalogItem, $this> */
|
/** @return BelongsTo<CatalogItem, $this> */
|
||||||
public function sourceCatalogItem(): BelongsTo
|
public function sourceCatalogItem(): BelongsTo
|
||||||
{
|
{
|
||||||
@@ -247,6 +256,15 @@ class Ticket extends Model
|
|||||||
return $this->belongsTo(Variant::class, 'source_variant_id')->withTrashed();
|
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
|
public function isValid(): bool
|
||||||
{
|
{
|
||||||
if ($this->hasTerminalStatus() || $this->used_at !== null) {
|
if ($this->hasTerminalStatus() || $this->used_at !== null) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class AdminAppTicketService
|
|||||||
'scannerUser',
|
'scannerUser',
|
||||||
'sourceCatalogItem.category',
|
'sourceCatalogItem.category',
|
||||||
'sourcePurchaseItem.purchase',
|
'sourcePurchaseItem.purchase',
|
||||||
|
'entryReservation',
|
||||||
'refund.createdBy',
|
'refund.createdBy',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ use App\Domains\Core\Authorization\Enums\RoleCode;
|
|||||||
use App\Domains\Core\Menu\Models\Menu;
|
use App\Domains\Core\Menu\Models\Menu;
|
||||||
use App\Domains\Core\Tenant\Models\AdminWebsiteType;
|
use App\Domains\Core\Tenant\Models\AdminWebsiteType;
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
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\Ticket;
|
||||||
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
use App\Domains\Ticketing\Ticket\Models\TicketRefund;
|
||||||
use App\Shared\Attachable\Enums\AttachmentType;
|
use App\Shared\Attachable\Enums\AttachmentType;
|
||||||
@@ -222,6 +224,54 @@ class AdminAppTicketControllerTest extends TestCase
|
|||||||
$this->assertSame(1, $purchaseItem->ticketRefunds()->count());
|
$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
|
public function test_it_partially_refunds_a_ticket_using_the_tenant_percentage(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createTenant('ticket-partial-refund');
|
$tenant = $this->createTenant('ticket-partial-refund');
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Ticket;
|
namespace Tests\Unit\Ticket;
|
||||||
|
|
||||||
use App\Domains\Core\Auth\Models\User;
|
|
||||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||||
|
use App\Domains\Core\Auth\Models\User;
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
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\Enums\ValidityTimeType;
|
||||||
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
use App\Domains\Ticketing\Ticket\Models\Ticket;
|
||||||
use App\Domains\Ticketing\Ticket\Models\ValidityTime;
|
use App\Domains\Ticketing\Ticket\Models\ValidityTime;
|
||||||
@@ -54,6 +55,7 @@ class TicketTest extends TestCase
|
|||||||
$this->assertInstanceOf(User::class, $ticket->scannerUser()->getRelated());
|
$this->assertInstanceOf(User::class, $ticket->scannerUser()->getRelated());
|
||||||
$this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated());
|
$this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated());
|
||||||
$this->assertInstanceOf(Variant::class, $ticket->sourceVariant()->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
|
public function test_unused_ticket_without_validity_time_is_valid(): void
|
||||||
@@ -83,6 +85,21 @@ class TicketTest extends TestCase
|
|||||||
$this->assertFalse($active->can_refund());
|
$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
|
public function test_fixed_window_controls_ticket_validity(): void
|
||||||
{
|
{
|
||||||
Carbon::setTestNow('2026-07-21 10:00:00');
|
Carbon::setTestNow('2026-07-21 10:00:00');
|
||||||
|
|||||||
Reference in New Issue
Block a user