refactor(ticket): implement fixed validity time generation for event date and time windows in TicketGeneratorService

This commit is contained in:
2026-08-07 10:43:05 -03:00
parent 717ee5d194
commit 91af233941
22 changed files with 665 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Domains\Ticket\Services;
use App\Domains\Auth\Models\User;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Exceptions\TicketGenerationException;
use App\Domains\Ticket\Models\Ticket;
use App\Domains\Ticket\Models\ValidityTime;
@@ -34,14 +35,21 @@ class TicketGeneratorService
$quantity,
$sourceVariantId,
);
$fixedValidityTimes = [];
return $targets->map(function (array $target) use (
&$fixedValidityTimes,
$sourcePurchaseId,
$user,
): Ticket {
$item = $target['catalog_item'];
$variant = $target['variant'];
$validityTime = $this->resolveValidityTime($item, $variant);
$validityTime = $this->materializeEventDateValidityTime(
$variant,
$validityTime,
$fixedValidityTimes,
);
return Ticket::query()->create([
'tenant_code' => $item->tenant_code,
@@ -168,4 +176,56 @@ class TicketGeneratorService
return $validityTimes->first();
}
/**
* Convert a recurring time window into the fixed date and time purchased by
* the customer. Equal tickets generated together share the same snapshot.
*
* @param array<string, ValidityTime> $fixedValidityTimes
*/
private function materializeEventDateValidityTime(
?Variant $variant,
?ValidityTime $validityTime,
array &$fixedValidityTimes,
): ?ValidityTime {
if (
$variant === null
|| $validityTime === null
|| $validityTime->type !== ValidityTimeType::TimeWindow
) {
return $validityTime;
}
$variant->loadMissing('eventDate');
$eventDate = $variant->eventDate;
if ($eventDate === null) {
return $validityTime;
}
$cacheKey = $eventDate->getKey().':'.$validityTime->getKey();
if (isset($fixedValidityTimes[$cacheKey])) {
return $fixedValidityTimes[$cacheKey];
}
$startsAt = $validityTime->startsAt($eventDate->date);
$expiresAt = $validityTime->expiresAt($eventDate->date);
if (
$startsAt !== null
&& $expiresAt !== null
&& $expiresAt->lessThanOrEqualTo($startsAt)
) {
$expiresAt = $expiresAt->addDay();
}
return $fixedValidityTimes[$cacheKey] = ValidityTime::query()->create([
'type' => ValidityTimeType::FixedWindow,
'start_time' => null,
'end_time' => null,
'fixed_starts_at' => $startsAt,
'fixed_expires_at' => $expiresAt,
]);
}
}

View File

@@ -0,0 +1,33 @@
# Dominio Ticket
## Propósito
Genera, valida, consulta y exporta entradas asociadas a compras pagadas de productos o variantes ticketables.
## Modelo
- `Ticket`: pertenece a tenant y usuario, conserva referencias a compra, producto, variante, validez y usuario escáner.
- `ValidityTime`: define ventanas absolutas o relativas de vigencia para productos, opciones y tickets.
- `ValidityTimeType`: enum de estrategias de vigencia.
El modelo calcula si un ticket está vigente, vencido o usado, y resuelve sus fechas efectivas de inicio y fin.
## Flujo de generación
1. `Purchase` emite `PurchasePaid` al confirmarse el pago.
2. `GenerateTicketsForPaidPurchase` atiende el evento.
3. `TicketGeneratorService` crea los tickets requeridos según ítems, cantidades y vigencia.
4. El flujo puede emitir disponibilidad para que `Notification` informe al comprador.
## Endpoints
Bajo `/tenants/{tenant:codigo}`, protegidos por `auth:sanctum`:
- `GET /tickets`.
- `POST /tickets/pdf`.
`TicketPdfService` genera la descarga y `TicketResource`/`ValidityTimeResource` definen las respuestas.
## Dependencias y reglas
Depende de `Purchase`, `Catalog`, `Tenant` y `Auth`. La generación debe ser idempotente ante reintentos del evento. `TicketNotAvailableException` y `TicketGenerationException` separan indisponibilidad de errores de generación.