From 35384b48abc1d3dfff58345df33a19a5c0d65a94 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Wed, 1 Jul 2026 08:55:27 -0300 Subject: [PATCH] feat: add filtering for product detail attribute options based on available variant values --- .../Catalog/Services/ProductService.php | 34 +++++++++++++++++++ .../Feature/Catalog/ProductControllerTest.php | 21 ++++++++++++ 2 files changed, 55 insertions(+) diff --git a/app/Domains/Catalog/Services/ProductService.php b/app/Domains/Catalog/Services/ProductService.php index 933d700..0a3a096 100644 --- a/app/Domains/Catalog/Services/ProductService.php +++ b/app/Domains/Catalog/Services/ProductService.php @@ -293,6 +293,8 @@ class ProductService 'variants.definitions.productAttribute.attribute.options', ]); + $this->filterProductDetailAttributeOptions($product); + $selectedVariant = $variantId !== null ? $product->variants->firstWhere('id', $variantId) : $product->variants->first(fn (ProductVariant $variant) => $variant->stock > 0); @@ -313,4 +315,36 @@ class ProductService return $product; } + + protected function filterProductDetailAttributeOptions(Product $product): void + { + $availableValuesByAttributeId = []; + + foreach ($product->variants as $variant) { + foreach ($variant->definitions as $definition) { + $attributeId = $definition->productAttribute?->attribute_id; + + if ($attributeId === null || $definition->value === null) { + continue; + } + + $availableValuesByAttributeId[$attributeId][$definition->value] = true; + } + } + + foreach ($product->attributes as $attribute) { + if (! $attribute->relationLoaded('options')) { + continue; + } + + $availableValues = $availableValuesByAttributeId[$attribute->id] ?? []; + + $attribute->setRelation( + 'options', + $attribute->options + ->filter(fn ($option): bool => array_key_exists($option->value, $availableValues)) + ->values() + ); + } + } } diff --git a/tests/Feature/Catalog/ProductControllerTest.php b/tests/Feature/Catalog/ProductControllerTest.php index 460a68a..af47cb9 100644 --- a/tests/Feature/Catalog/ProductControllerTest.php +++ b/tests/Feature/Catalog/ProductControllerTest.php @@ -405,6 +405,10 @@ class ProductControllerTest extends TestCase 'precio' => 100.00, ]); $productAttributes = $this->syncVariantAttributes($product); + $this->colorAttr->options()->create([ + 'value' => 'Verde', + 'label' => 'Verde', + ]); $variant = $product->createVariant([ 'stock' => 0, @@ -479,6 +483,23 @@ class ProductControllerTest extends TestCase $response->assertJsonPath('data.variant.definitions.color', 'Rojo'); $response->assertJsonCount(1, 'data.variant.images'); $this->assertStringContainsString($variantAttachment->path, $response->json('data.variant.images.0')); + + $colorAttribute = collect($response->json('data.attributes'))->firstWhere('codigo', 'color'); + $this->assertNotNull($colorAttribute); + $this->assertEqualsCanonicalizing( + ['Azul', 'Rojo'], + collect($colorAttribute['options'])->pluck('value')->all() + ); + + $attributesResponse = $this->getJson("/api/tenants/{$this->tenant->codigo}/attributes"); + $attributesResponse->assertOk(); + + $masterColorAttribute = collect($attributesResponse->json('data'))->firstWhere('codigo', 'color'); + $this->assertNotNull($masterColorAttribute); + $this->assertEqualsCanonicalizing( + ['Azul', 'Rojo', 'Verde'], + collect($masterColorAttribute['options'])->pluck('value')->all() + ); } public function test_it_returns_requested_variant_in_product_detail(): void