feat(ticket): implement validity time management for tickets and catalog items

- Added ValidityTime model and migration to manage ticket validity periods.
- Updated TicketGeneratorService to resolve and assign validity times to tickets.
- Refactored ticket generation logic to remove legacy date fields and use validity time.
- Introduced timezone support for tenants to handle service dates correctly.
- Updated migrations to remove deprecated columns and add foreign keys for validity times.
- Modified seeders and tests to accommodate new validity time structure.
- Enhanced tests to validate ticket generation and validity time behavior.
This commit is contained in:
2026-08-06 15:52:19 -03:00
parent 7f01adab73
commit 448ffb4102
30 changed files with 556 additions and 381 deletions

View File

@@ -5,8 +5,10 @@ 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;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@@ -39,7 +41,8 @@ class TicketGeneratorService
$user,
): Ticket {
$item = $target['catalog_item'];
$selectedItem = $target['variant'] ?? $item;
$variant = $target['variant'];
$validityTime = $this->resolveValidityTime($item, $variant);
return Ticket::query()->create([
'tenant_code' => $item->tenant_code,
@@ -48,9 +51,11 @@ class TicketGeneratorService
'description' => (string) ($item->descripcion ?? ''),
'source_purchase_id' => $sourcePurchaseId,
'source_catalog_item_id' => $item->getKey(),
'source_variant_id' => $target['variant']?->getKey(),
'starts_at' => $selectedItem->getMinimumUseDate(),
'expires_at' => $selectedItem->getMaximumUseDate(),
'source_variant_id' => $variant?->getKey(),
'validity_time_id' => $validityTime?->getKey(),
'service_date' => $validityTime?->type === ValidityTimeType::ServiceDateWindow
? ($variant?->eventDate?->date ?? now($item->tenant->timezone)->toDateString())
: null,
'used_at' => null,
'user_id' => $user->getKey(),
]);
@@ -134,12 +139,37 @@ class TicketGeneratorService
throw TicketGenerationException::ticketsDisabled($catalogItem);
}
// TODO: Reactivar esta validación cuando los pagos con productos vencidos
// deban rechazarse nuevamente. Se deja deshabilitada temporalmente.
// $selectedItem = $variant ?? $catalogItem;
//
// if ($selectedItem->getMaximumUseDate()?->lessThanOrEqualTo(now())) {
// throw TicketGenerationException::maximumUseDateReached($catalogItem);
// }
}
private function resolveValidityTime(
CatalogItem $catalogItem,
?Variant $variant,
): ?ValidityTime {
if ($catalogItem->validity_time_id !== null) {
return $catalogItem->validityTime;
}
if ($variant === null) {
return null;
}
$variant->loadMissing('definitions.itemAttribute.attribute.options.validityTime');
$validityTimes = $variant->definitions
->map(function ($definition): ?ValidityTime {
$option = $definition->itemAttribute?->attribute?->options
->firstWhere('value', $definition->value);
return $option?->validityTime;
})
->filter()
->unique(fn (ValidityTime $validityTime): int => $validityTime->getKey())
->values();
if ($validityTimes->count() > 1) {
throw TicketGenerationException::ambiguousValidityTime($catalogItem);
}
return $validityTimes->first();
}
}