*/ private const CATEGORY_PRESENTATIONS = [ 'entradas' => [ 'label' => null, 'product' => self::PRODUCT, 'type' => null, 'order' => 1, ], 'alojamientos' => [ 'label' => 'Camping', 'product' => 'tipo_alojamiento', 'type' => null, 'order' => 2, ], 'camping' => [ 'label' => null, 'product' => 'tipo_alojamiento', 'type' => null, 'order' => 2, ], 'comidas' => [ 'label' => 'Comida', 'product' => 'event_date', 'type' => 'horario', 'order' => 3, ], 'comida' => [ 'label' => null, 'product' => 'event_date', 'type' => 'horario', 'order' => 3, ], 'merchandising' => [ 'label' => null, 'product' => self::PRODUCT, 'type' => 'color', 'order' => 4, ], ]; /** * @return array{ * statuses: list, * categories: list * }> * }> * } */ public function get(Tenant $tenant): array { $items = CatalogItem::query() ->where('tenant_code', $tenant->codigo) ->where('has_tickets', true) ->whereHas('category') ->with($this->relations()) ->orderBy('group_order') ->orderBy('nombre') ->get(); return $this->build($items); } /** * Return active catalog options plus soft-deleted sources still referenced by * tickets, so historical tickets never become impossible to filter. * * @return array{ * statuses: list, * categories: list * }> * }> * } */ public function getForFilters(Tenant $tenant): array { $historicalVariantIds = Ticket::query() ->where('tenant_code', $tenant->codigo) ->whereNotNull('source_variant_id') ->distinct() ->pluck('source_variant_id') ->map(fn ($id): int => (int) $id) ->all(); $historicalCatalogItemIds = Ticket::query() ->where('tenant_code', $tenant->codigo) ->whereNotNull('source_catalog_item_id') ->distinct() ->pluck('source_catalog_item_id') ->map(fn ($id): int => (int) $id) ->merge( Variant::withTrashed() ->whereKey($historicalVariantIds) ->pluck('catalog_item_id') ->map(fn ($id): int => (int) $id), ) ->unique() ->values() ->all(); $items = CatalogItem::withTrashed() ->where('tenant_code', $tenant->codigo) ->whereHas('category') ->where(function ($query) use ($historicalCatalogItemIds): void { $query ->where(function ($activeQuery): void { $activeQuery ->whereNull('catalog_items.deleted_at') ->where('has_tickets', true); }) ->orWhereIn('catalog_items.id', $historicalCatalogItemIds); }) ->with([ 'category', 'itemAttributes.attribute.options', 'variants' => fn ($query) => $query ->withTrashed() ->where(function ($variantQuery) use ($historicalVariantIds): void { $variantQuery ->whereNull('variantes.deleted_at') ->orWhereIn('variantes.id', $historicalVariantIds); }), 'variants.definitions.itemAttribute.attribute.options', 'variants.eventDates', 'variants.eventDate', ]) ->orderBy('group_order') ->orderBy('nombre') ->get(); return $this->build($items); } /** * @param Collection $items * @return array{ * statuses: list, * categories: list * }> * }> * } */ private function build(Collection $items): array { $categories = []; foreach ($items as $item) { $sourceCategory = trim((string) $item->category?->nombre); $categoryValue = mb_strtolower($sourceCategory); $presentation = self::CATEGORY_PRESENTATIONS[$categoryValue] ?? [ 'label' => null, 'product' => self::PRODUCT, 'type' => null, 'order' => PHP_INT_MAX, ]; $categories[$categoryValue] ??= [ 'value' => $categoryValue, 'label' => $presentation['label'] ?? $sourceCategory, 'order' => $presentation['order'], 'products' => [], ]; foreach ($this->products($item, $presentation['product'], $presentation['type']) as $product) { $productValue = $product['value']; $existingProduct = $categories[$categoryValue]['products'][$productValue] ?? [ 'value' => $productValue, 'label' => $product['label'], 'types' => [], ]; foreach ($product['types'] as $type) { $existingProduct['types'][$type['value']] = $type; } $categories[$categoryValue]['products'][$productValue] = $existingProduct; } } uasort($categories, fn (array $left, array $right): int => $left['order'] <=> $right['order'] ?: $left['label'] <=> $right['label']); return [ 'statuses' => [ ['value' => Ticket::STATUS_ACTIVE, 'label' => 'Activo'], ['value' => Ticket::STATUS_USED, 'label' => 'Usado'], ['value' => Ticket::STATUS_EXPIRED, 'label' => 'Vencido'], ], 'categories' => array_values(array_map( fn (array $category): array => [ 'value' => $category['value'], 'label' => $category['label'], 'products' => array_values(array_map( fn (array $product): array => [ 'value' => $product['value'], 'label' => $product['label'], 'types' => array_values($product['types']), ], $category['products'], )), ], $categories, )), ]; } /** @return list */ private function relations(): array { return [ 'category', 'itemAttributes.attribute.options', 'variants.definitions.itemAttribute.attribute.options', 'variants.eventDates', 'variants.eventDate', ]; } /** * @return list * }> */ private function products(CatalogItem $item, string $productCode, ?string $typeCode): array { if ($productCode === self::PRODUCT) { return [[ 'value' => $item->slug, 'label' => $item->nombre, 'types' => $this->types($item, $typeCode), ]]; } $products = []; foreach ($item->variants as $variant) { foreach ($this->variantOptions($variant, $productCode) as $productOption) { $productValue = $productOption['value']; $products[$productValue] ??= [ 'value' => $productValue, 'label' => $this->optionLabel($productOption['label'], $productCode), 'types' => [], ]; foreach ($this->variantOptions($variant, $typeCode) as $typeOption) { $products[$productValue]['types'][$typeOption['value']] = $typeOption; } } } return array_values(array_map( fn (array $product): array => [ 'value' => $product['value'], 'label' => $product['label'], 'types' => array_values($product['types']), ], $products, )); } /** @return list */ private function types(CatalogItem $item, ?string $typeCode): array { $types = []; foreach ($item->variants as $variant) { foreach ($this->variantOptions($variant, $typeCode) as $typeOption) { $types[$typeOption['value']] = $typeOption; } } return array_values($types); } /** @return list */ private function variantOptions(Variant $variant, ?string $attributeCode): array { if ($attributeCode === null) { return []; } $selection = $variant->selectionOptions($variant->catalogItem->itemAttributes) ->get($attributeCode); if ($selection === null) { return []; } return array_is_list($selection) ? $selection : [$selection]; } private function optionLabel(string $label, string $attributeCode): string { if ($attributeCode !== 'event_date') { return $label; } [$day, $month] = array_pad(explode('/', $label), 2, null); return $day !== null && $month !== null ? "{$day}/{$month}" : $label; } }