*/ public function generate( CatalogItem $catalogItem, User $user, int $quantity = 1, ?int $sourceVariantId = null, ?int $sourcePurchaseId = null, ): Collection { if ($quantity < 1) { throw TicketGenerationException::invalidQuantity(); } return DB::transaction(function () use ($catalogItem, $user, $quantity, $sourceVariantId, $sourcePurchaseId): Collection { $targets = $this->resolveTargets( $catalogItem, $quantity, $sourceVariantId, ); return $targets->map(function (array $target) use ( $sourcePurchaseId, $user, ): Ticket { $item = $target['catalog_item']; $variant = $target['variant']; $eventDate = $target['event_date']; $validityGroups = $this->buildTicketValidityGroups( $item, $variant, $eventDate, $this->resolveValidityTime($item, $variant), ); $ticket = Ticket::query()->create([ 'tenant_code' => $item->tenant_code, 'ticket' => (string) Str::uuid(), 'name' => $this->ticketName($item, $variant, $eventDate), 'description' => (string) ($item->descripcion ?? ''), 'source_purchase_id' => $sourcePurchaseId, 'source_catalog_item_id' => $item->getKey(), 'source_variant_id' => $variant?->getKey(), 'used_at' => null, 'user_id' => $user->getKey(), ]); $groups = $validityGroups->map(function (Collection $validityTimes) use ($ticket): TicketValidityGroup { $group = $ticket->validityGroups()->create(); $group->validityTimes()->attach( $validityTimes ->map(fn (ValidityTime $validityTime): int => $validityTime->getKey()) ->all() ); $group->setRelation( 'validityTimes', new EloquentCollection($validityTimes->all()), ); return $group; }); $ticket->setRelation('validityGroups', new EloquentCollection($groups->all())); return $ticket; }); }); } /** * @return Collection */ private function resolveTargets( CatalogItem $catalogItem, int $quantity, ?int $sourceVariantId, ): Collection { if (! $catalogItem->isBundle()) { $variant = $this->resolveVariant($catalogItem, $sourceVariantId); $this->validateTarget($catalogItem, $variant); return $this->targetsForVariant($catalogItem, $variant, $quantity); } $catalogItem->loadMissing([ 'bundleComponents.catalogItem', 'bundleComponents.variant.catalogItem', ]); if ($catalogItem->bundleComponents->isEmpty()) { throw TicketGenerationException::emptyBundle($catalogItem); } return $catalogItem->bundleComponents ->flatMap(function ($component) use ($quantity): Collection { $componentItem = $component->catalogItem; $variant = $component->variant; $this->validateTarget($componentItem, $variant); return $this->targetsForVariant( $componentItem, $variant, $quantity * $component->quantity, ); }) ->values(); } /** * @return Collection */ private function targetsForVariant( CatalogItem $catalogItem, ?Variant $variant, int $quantity, ): Collection { if ($variant === null) { return Collection::times($quantity, fn (): array => [ 'catalog_item' => $catalogItem, 'variant' => null, 'event_date' => null, ]); } $variant->loadMissing(['eventDates.validityTime', 'eventDate.validityTime']); $selectedEventDates = $variant->selectedEventDates(); if ($catalogItem->ticket_generation_policy === TicketGenerationPolicy::OnePerUnit) { $eventDate = $selectedEventDates->count() === 1 ? $selectedEventDates->first() : null; return Collection::times($quantity, fn (): array => [ 'catalog_item' => $catalogItem, 'variant' => $variant, 'event_date' => $eventDate, ]); } $eventDates = $selectedEventDates->isEmpty() ? collect([null]) : $selectedEventDates; return Collection::times($quantity) ->flatMap(fn () => $eventDates->map(fn (?EventDate $eventDate): array => [ 'catalog_item' => $catalogItem, 'variant' => $variant, 'event_date' => $eventDate, ])) ->values(); } private function resolveVariant( CatalogItem $catalogItem, ?int $sourceVariantId, ): ?Variant { if ($sourceVariantId === null) { return null; } $variant = $catalogItem->variants() ->whereKey($sourceVariantId) ->first(); if ($variant === null) { throw TicketGenerationException::variantNotFound( $catalogItem, $sourceVariantId, ); } $variant->setRelation('catalogItem', $catalogItem); return $variant; } private function validateTarget( CatalogItem $catalogItem, ?Variant $variant, ): void { if (! $catalogItem->has_tickets) { throw TicketGenerationException::ticketsDisabled($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(); } private function ticketName( CatalogItem $catalogItem, ?Variant $variant, ?EventDate $eventDate, ): string { if ($variant === null) { return $catalogItem->nombre; } $options = $variant->selectionOptions(); if ($eventDate !== null) { $options->put('event_date', [ 'value' => (string) $eventDate->getKey(), 'label' => $eventDate->date->format('d/m/Y'), ]); } $properties = $options ->flatMap(function (array $option): array { if (array_is_list($option)) { return collect($option) ->pluck('label') ->filter(fn ($label): bool => is_string($label) && $label !== '') ->all(); } $label = $option['label'] ?? null; return is_string($label) && $label !== '' ? [$label] : []; }) ->values(); if ($properties->isEmpty()) { return $catalogItem->nombre; } return $catalogItem->nombre.' ('.$properties->implode(', ').')'; } /** @return Collection> */ private function buildTicketValidityGroups( CatalogItem $catalogItem, ?Variant $variant, ?EventDate $eventDate, ?ValidityTime $validityTime, ): Collection { $eventDates = collect([$eventDate]); if ( $catalogItem->ticket_generation_policy === TicketGenerationPolicy::OnePerUnit && $variant !== null ) { $variant->loadMissing(['eventDates.validityTime', 'eventDate.validityTime']); $selectedEventDates = $variant->selectedEventDates(); if ($selectedEventDates->isNotEmpty()) { $eventDates = $selectedEventDates; } } return $eventDates ->map(function (?EventDate $date) use ($validityTime): Collection { $date?->loadMissing('validityTime'); return collect([$date?->validityTime, $validityTime]) ->filter() ->unique(fn (ValidityTime $time): int => $time->getKey()) ->values(); }) ->filter(fn (Collection $group): bool => $group->isNotEmpty()) ->values(); } }