diff --git a/app/Domains/Commerce/Catalog/Models/Inventory.php b/app/Domains/Commerce/Catalog/Models/Inventory.php index 047a611..d18af51 100644 --- a/app/Domains/Commerce/Catalog/Models/Inventory.php +++ b/app/Domains/Commerce/Catalog/Models/Inventory.php @@ -73,6 +73,16 @@ class Inventory extends Model $this->save(); } + public function releaseEntry(int $amount): void + { + if ($amount < 1 || $this->entry_reserved_stock < $amount) { + throw new \InvalidArgumentException('La cantidad de entradas reservadas no es vĂ¡lida.'); + } + + $this->entry_reserved_stock -= $amount; + $this->save(); + } + public function reserve(int $amount, bool $tracksInventory): void { if ($amount < 0) { diff --git a/app/Domains/Ticketing/Desfile/Controllers/EntryReservationController.php b/app/Domains/Ticketing/Desfile/Controllers/EntryReservationController.php index c98709e..f9e1cc2 100644 --- a/app/Domains/Ticketing/Desfile/Controllers/EntryReservationController.php +++ b/app/Domains/Ticketing/Desfile/Controllers/EntryReservationController.php @@ -72,4 +72,14 @@ class EntryReservationController extends Controller $request->user(), $request->validated('idempotency_key'), $request->validated('rows'), )); } + + public function destroy( + IndexEntryReservationsRequest $request, + int $reservation, + EntryReservationService $service, + ): Response { + $service->cancel($request->user(), $reservation); + + return response()->noContent(); + } } diff --git a/app/Domains/Ticketing/Desfile/Services/EntryReservationService.php b/app/Domains/Ticketing/Desfile/Services/EntryReservationService.php index 21f4db9..e8cb948 100644 --- a/app/Domains/Ticketing/Desfile/Services/EntryReservationService.php +++ b/app/Domains/Ticketing/Desfile/Services/EntryReservationService.php @@ -71,6 +71,41 @@ class EntryReservationService return $reservation->ticket; } + public function cancel(User $user, int $reservationId): void + { + abort_unless($user->tenant_codigo === 'desfile_pura_tendencia', 403); + + DB::transaction(function () use ($user, $reservationId): void { + $reservation = EntryReservation::query() + ->whereKey($reservationId) + ->whereHas('variant.catalogItem', fn (Builder $query): Builder => $query + ->where('tenant_code', $user->tenant_codigo) + ->where('slug', 'entrada')) + ->lockForUpdate() + ->firstOrFail(); + + if ($reservation->ticket_id !== null) { + $ticket = Ticket::query()->lockForUpdate()->findOrFail($reservation->ticket_id); + + if (! $ticket->can_cancel()) { + throw ValidationException::withMessages([ + 'status' => 'El ticket debe estar activo para poder cancelar la reserva.', + ]); + } + + $ticket->markAsCancelled(); + $ticket->save(); + } + + if ($reservation->inventory_id !== null) { + $inventory = Inventory::query()->lockForUpdate()->findOrFail($reservation->inventory_id); + $inventory->releaseEntry(1); + } + + $reservation->delete(); + }, 3); + } + /** @param array{tipo_pago?: string|null} $filters */ private function reservationsQuery(User $user, array $filters = []): Builder { diff --git a/app/Domains/Ticketing/Desfile/routes/api.php b/app/Domains/Ticketing/Desfile/routes/api.php index 8141a95..f588ecd 100644 --- a/app/Domains/Ticketing/Desfile/routes/api.php +++ b/app/Domains/Ticketing/Desfile/routes/api.php @@ -20,6 +20,10 @@ Route::get('v1/adminapp/tenant/desfile/entry-reservations/{reservation}/ticket/p Route::post('v1/adminapp/tenant/desfile/entry-reservations', [EntryReservationController::class, 'store']) ->middleware(['auth:sanctum', 'adminapp.tenant', 'tenant.menu:adminapp.desfile.reservas']) ->name('adminapp.desfile.entry-reservations.store'); +Route::delete('v1/adminapp/tenant/desfile/entry-reservations/{reservation}', [EntryReservationController::class, 'destroy']) + ->whereNumber('reservation') + ->middleware(['auth:sanctum', 'adminapp.tenant', 'tenant.menu:adminapp.desfile.reservas']) + ->name('adminapp.desfile.entry-reservations.destroy'); Route::prefix('v1/adminapp/tenant/desfile') ->middleware(['auth:sanctum', 'adminapp.tenant', 'tenant.menu:adminapp.desfile.entradas'])