$selectedValues * @param list $excludedVariantIds * @return array */ public function options( CatalogItem $catalogItem, array $selectedValues, array $excludedVariantIds = [], ?int $includedVariantId = null, ): array { $catalogItem->load([ 'itemAttributes.attribute', 'variants' => fn ($query) => $query->orderBy('id'), 'variants.inventory', 'variants.eventDate', 'variants.eventDates', 'variants.definitions' => fn ($query) => $query->orderBy('id'), 'variants.definitions.itemAttribute.attribute.options', ]); $excluded = collect($excludedVariantIds)->map(fn ($id): int => (int) $id); $variants = $catalogItem->visibleVariants($includedVariantId) ->reject(fn (Variant $variant): bool => $variant->id !== $includedVariantId && $excluded->contains($variant->id)) ->values(); $normalizedSelections = collect($selectedValues) ->mapWithKeys(fn ($value, string $key): array => [$key => $this->normalizeValue($value)]) ->filter(fn ($value): bool => $value !== null && $value !== '' && $value !== []) ->all(); $matchingVariants = $variants ->filter(fn (Variant $variant): bool => $this->matches($variant, $normalizedSelections)) ->values(); $attributeKeys = $variants ->flatMap(fn (Variant $variant): array => $variant ->selectorOptions($catalogItem->itemAttributes) ->keys() ->all()) ->unique() ->values(); $isComplete = $attributeKeys->isNotEmpty() && $attributeKeys->every(fn (string $key): bool => array_key_exists($key, $normalizedSelections)); $resolvedVariant = $isComplete && $matchingVariants->count() === 1 ? $matchingVariants->first() : null; return [ 'variants' => $variants ->map(fn (Variant $variant): array => $this->variantData($catalogItem, $variant)) ->values(), 'options' => $this->availableOptions($catalogItem, $matchingVariants), 'selected_values' => $normalizedSelections, 'resolved_variant_id' => $resolvedVariant?->id, 'valid' => $matchingVariants->isNotEmpty(), ]; } /** @param array $selectedValues */ private function matches(Variant $variant, array $selectedValues): bool { $variantValues = $variant->selectionValues(); foreach ($selectedValues as $key => $selectedValue) { if (! $variantValues->has($key) || $this->valueKey($variantValues->get($key)) !== $this->valueKey($selectedValue)) { return false; } } return true; } /** * @param Collection $variants * @return array> */ private function availableOptions(CatalogItem $catalogItem, Collection $variants): array { $options = []; $seen = []; foreach ($variants as $variant) { foreach ($variant->selectorOptions($catalogItem->itemAttributes) as $key => $option) { $optionKey = $this->valueKey($option); if (isset($seen[$key][$optionKey])) { continue; } $seen[$key][$optionKey] = true; $options[$key][] = $option; } } return $options; } /** @return array */ private function variantData(CatalogItem $catalogItem, Variant $variant): array { return [ 'id' => $variant->id, 'descripcion' => $variant->getDescription(), 'precio' => number_format($variant->getPrice(), 2, '.', ''), 'stock_tecnico' => $catalogItem->inventory_policy === InventoryPolicy::Unlimited ? null : $variant->inventory?->availableStock(), 'values' => $variant->selectorOptions($catalogItem->itemAttributes), ]; } private function normalizeValue(mixed $value): mixed { if (is_array($value) && array_key_exists('value', $value)) { return (string) $value['value']; } if (is_array($value)) { return array_map(fn ($item) => $this->normalizeValue($item), $value); } return is_scalar($value) ? (string) $value : null; } private function valueKey(mixed $value): string { $normalized = $this->normalizeValue($value); if (is_array($normalized)) { sort($normalized); } return json_encode($normalized, JSON_THROW_ON_ERROR); } }