From e7c8807a67b0b5f0d7e6fbf8a347b24c8a9b0cbf Mon Sep 17 00:00:00 2001 From: ncoronel Date: Mon, 10 Aug 2026 12:27:00 -0300 Subject: [PATCH] feat(variant): refactor selection values to expose options with labels; update related resources and tests for consistency --- .../Cart/Resources/CartItemResource.php | 25 +-------- app/Domains/Cart/Services/CartService.php | 4 +- app/Domains/Catalog/Models/Variant.php | 51 +++++++++++++++++++ .../Resources/CatalogFeaturedItemResource.php | 2 +- .../Resources/CatalogItemDetailResource.php | 6 +-- .../Catalog/Resources/CatalogItemResource.php | 2 +- .../Resources/CatalogSearchItemResource.php | 2 +- .../Catalog/Services/CatalogService.php | 8 +-- .../Catalog/Services/FeaturedGroupService.php | 2 +- .../CatalogItemDetailControllerTest.php | 17 +++++-- tests/Unit/Catalog/CatalogModelsTest.php | 37 ++++++++++++++ 11 files changed, 113 insertions(+), 43 deletions(-) diff --git a/app/Domains/Cart/Resources/CartItemResource.php b/app/Domains/Cart/Resources/CartItemResource.php index 6256601..fff4977 100644 --- a/app/Domains/Cart/Resources/CartItemResource.php +++ b/app/Domains/Cart/Resources/CartItemResource.php @@ -7,7 +7,6 @@ use App\Domains\Catalog\Enums\InventoryPolicy; use App\Domains\Catalog\Models\Variant; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Support\Collection; /** * @mixin CartItem @@ -46,35 +45,13 @@ class CartItemResource extends JsonResource 'stock_tecnico' => $this->catalogItem->inventory_policy === InventoryPolicy::Unlimited ? null : $variant->inventory->availableStock(), - 'values' => $this->variantValues($variant), + 'values' => $variant->selectionOptions(), ]) ->values(), ], ]; } - /** @return Collection> */ - private function variantValues(Variant $variant): Collection - { - $values = $variant->selectionValues(); - $eventDates = $variant->selectedEventDates(); - - if ($eventDates->isNotEmpty()) { - $labels = $eventDates - ->map(fn ($eventDate): string => $eventDate->date->format('d/m/Y').' · ' - .substr($eventDate->time_start, 0, 5).' a ' - .substr($eventDate->time_end, 0, 5)) - ->values(); - - $values->put( - 'event_date', - $labels->count() === 1 ? $labels->first() : $labels->all(), - ); - } - - return $values; - } - protected function formatMoney(float|int|string|null $amount): string { return number_format((float) ($amount ?? 0), 2, '.', ''); diff --git a/app/Domains/Cart/Services/CartService.php b/app/Domains/Cart/Services/CartService.php index 1142944..cacad7f 100644 --- a/app/Domains/Cart/Services/CartService.php +++ b/app/Domains/Cart/Services/CartService.php @@ -108,12 +108,12 @@ class CartService 'items.catalogItem.attachments', 'items.catalogItem.inventory', 'items.catalogItem.variants.inventory', - 'items.catalogItem.variants.definitions.itemAttribute.attribute', + 'items.catalogItem.variants.definitions.itemAttribute.attribute.options', 'items.catalogItem.variants.eventDates', 'items.catalogItem.variants.eventDate', 'items.variant.attachments', 'items.variant.inventory', - 'items.variant.definitions.itemAttribute.attribute', + 'items.variant.definitions.itemAttribute.attribute.options', 'items.variant.eventDates', 'items.variant.eventDate', ]); diff --git a/app/Domains/Catalog/Models/Variant.php b/app/Domains/Catalog/Models/Variant.php index 388d87b..2728512 100644 --- a/app/Domains/Catalog/Models/Variant.php +++ b/app/Domains/Catalog/Models/Variant.php @@ -149,6 +149,57 @@ class Variant extends Model return $values; } + /** + * @return Collection> + */ + public function selectionOptions(): Collection + { + $options = $this->definitions + ->groupBy('item_attribute_id') + ->mapWithKeys(function (Collection $definitions): array { + $itemAttribute = $definitions->first()?->itemAttribute; + $attribute = $itemAttribute?->attribute; + $attributeCode = $attribute?->codigo; + + if ($attributeCode === null) { + return []; + } + + $values = $definitions + ->pluck('value') + ->values() + ->map(function (string $value) use ($attribute): array { + $attributeOption = $attribute->options->firstWhere('value', $value); + + return [ + 'value' => $value, + 'label' => $attributeOption?->label ?? $value, + ]; + }); + + return [ + $attributeCode => $itemAttribute->allow_multi_select + ? $values->all() + : $values->first(), + ]; + }); + + $eventDateOptions = $this->selectedEventDates() + ->map(fn (EventDate $eventDate): array => [ + 'value' => (string) $eventDate->id, + 'label' => $eventDate->date->format('d/m/Y'), + ]) + ->values(); + + if ($eventDateOptions->count() === 1) { + $options->put('event_date', $eventDateOptions->first()); + } elseif ($eventDateOptions->isNotEmpty()) { + $options->put('event_date', $eventDateOptions->all()); + } + + return $options; + } + /** @return Collection */ public function selectedEventDates(): Collection { diff --git a/app/Domains/Catalog/Resources/CatalogFeaturedItemResource.php b/app/Domains/Catalog/Resources/CatalogFeaturedItemResource.php index 42da6e7..bbea333 100644 --- a/app/Domains/Catalog/Resources/CatalogFeaturedItemResource.php +++ b/app/Domains/Catalog/Resources/CatalogFeaturedItemResource.php @@ -46,7 +46,7 @@ class CatalogFeaturedItemResource extends JsonResource 'stock_tecnico' => $catalogItem->inventory_policy === InventoryPolicy::Unlimited ? null : $variant->inventory->availableStock(), - 'values' => $variant->selectionValues(), + 'values' => $variant->selectionOptions(), ]) ->values(), ]; diff --git a/app/Domains/Catalog/Resources/CatalogItemDetailResource.php b/app/Domains/Catalog/Resources/CatalogItemDetailResource.php index 8736767..c1cfd75 100644 --- a/app/Domains/Catalog/Resources/CatalogItemDetailResource.php +++ b/app/Domains/Catalog/Resources/CatalogItemDetailResource.php @@ -104,9 +104,7 @@ class CatalogItemDetailResource extends JsonResource ->map(fn ($eventDate, int $index): array => [ 'id' => $eventDate->id, 'value' => (string) $eventDate->id, - 'label' => $eventDate->date->format('d/m/Y').' · ' - .substr($eventDate->time_start, 0, 5).' a ' - .substr($eventDate->time_end, 0, 5), + 'label' => $eventDate->date->format('d/m/Y'), 'sort_order' => $index, 'validity_time_id' => null, 'validity_time' => null, @@ -156,7 +154,7 @@ class CatalogItemDetailResource extends JsonResource /** @return array */ private function variantData(Variant $variant): array { - $values = $variant->selectionValues(); + $values = $variant->selectionOptions(); $eventDates = $variant->selectedEventDates(); return [ diff --git a/app/Domains/Catalog/Resources/CatalogItemResource.php b/app/Domains/Catalog/Resources/CatalogItemResource.php index 1de5671..f10705d 100644 --- a/app/Domains/Catalog/Resources/CatalogItemResource.php +++ b/app/Domains/Catalog/Resources/CatalogItemResource.php @@ -45,7 +45,7 @@ class CatalogItemResource extends JsonResource 'descripcion' => $variant->getDescription(), 'precio' => number_format($variant->getPrice(), 2, '.', ''), 'real_stock' => $variant->inventory?->real_stock, - 'values' => $variant->selectionValues(), + 'values' => $variant->selectionOptions(), 'images' => $variant->attachments ->map(fn ($attachment) => $attachment->getTemporaryUrl(1440)) ->values(), diff --git a/app/Domains/Catalog/Resources/CatalogSearchItemResource.php b/app/Domains/Catalog/Resources/CatalogSearchItemResource.php index 1a80d4c..e575d0e 100644 --- a/app/Domains/Catalog/Resources/CatalogSearchItemResource.php +++ b/app/Domains/Catalog/Resources/CatalogSearchItemResource.php @@ -42,7 +42,7 @@ class CatalogSearchItemResource extends JsonResource 'stock_tecnico' => $this->inventory_policy === InventoryPolicy::Unlimited ? null : $variant->inventory?->availableStock(), - 'values' => $variant->selectionValues(), + 'values' => $variant->selectionOptions(), ]) ->values(), ]; diff --git a/app/Domains/Catalog/Services/CatalogService.php b/app/Domains/Catalog/Services/CatalogService.php index d6919cf..f619d73 100644 --- a/app/Domains/Catalog/Services/CatalogService.php +++ b/app/Domains/Catalog/Services/CatalogService.php @@ -153,7 +153,7 @@ class CatalogService 'variants.attachments', 'variants.eventDate', 'variants.eventDates', - 'variants.definitions.itemAttribute.attribute', + 'variants.definitions.itemAttribute.attribute.options', 'bundleComponents.catalogItem', 'bundleComponents.variant.catalogItem', ]); @@ -176,7 +176,7 @@ class CatalogService 'variants.eventDate', 'variants.eventDates', 'variants.definitions' => fn ($query) => $query->orderBy('id'), - 'variants.definitions.itemAttribute.attribute', + 'variants.definitions.itemAttribute.attribute.options', 'bundleComponents.catalogItem.inventory', 'bundleComponents.variant.inventory', 'bundleComponents.variant.definitions.itemAttribute.attribute', @@ -231,7 +231,7 @@ class CatalogService 'variants.attachments', 'variants.eventDate', 'variants.eventDates', - 'variants.definitions.itemAttribute.attribute', + 'variants.definitions.itemAttribute.attribute.options', 'bundleComponents.catalogItem', 'bundleComponents.variant.catalogItem', ]) @@ -265,7 +265,7 @@ class CatalogService 'variants.attachments', 'variants.eventDate', 'variants.eventDates', - 'variants.definitions.itemAttribute.attribute', + 'variants.definitions.itemAttribute.attribute.options', 'bundleComponents.catalogItem', 'bundleComponents.variant.catalogItem', ]) diff --git a/app/Domains/Catalog/Services/FeaturedGroupService.php b/app/Domains/Catalog/Services/FeaturedGroupService.php index 6d11d84..200c6d8 100644 --- a/app/Domains/Catalog/Services/FeaturedGroupService.php +++ b/app/Domains/Catalog/Services/FeaturedGroupService.php @@ -45,7 +45,7 @@ class FeaturedGroupService 'variants.attachments', 'variants.eventDate', 'variants.eventDates', - 'variants.definitions.itemAttribute.attribute', + 'variants.definitions.itemAttribute.attribute.options', 'bundleComponents.catalogItem', 'bundleComponents.variant.catalogItem', ]); diff --git a/tests/Feature/Catalog/CatalogItemDetailControllerTest.php b/tests/Feature/Catalog/CatalogItemDetailControllerTest.php index b28fdc3..96d2039 100644 --- a/tests/Feature/Catalog/CatalogItemDetailControllerTest.php +++ b/tests/Feature/Catalog/CatalogItemDetailControllerTest.php @@ -117,17 +117,20 @@ class CatalogItemDetailControllerTest extends TestCase ->assertOk() ->assertJsonPath('data.variants.0.id', $firstVariant->id) ->assertJsonPath('data.variants.0.stock_tecnico', 4) - ->assertJsonPath('data.variants.0.values.size', 'S') + ->assertJsonPath('data.variants.0.values.size.value', 'S') + ->assertJsonPath('data.variants.0.values.size.label', 'Small') ->assertJsonPath('data.variants.1.id', $secondVariant->id) ->assertJsonPath('data.variants.1.stock_tecnico', 7) - ->assertJsonPath('data.variants.1.values.size', 'M') + ->assertJsonPath('data.variants.1.values.size.value', 'M') + ->assertJsonPath('data.variants.1.values.size.label', 'Medium') ->assertJsonPath('data.attributes.0.codigo', 'size') ->assertJsonPath('data.attributes.0.options.0.value', 'S') ->assertJsonPath('data.attributes.0.options.1.value', 'M') ->assertJsonCount(2, 'data.attributes.0.options') ->assertJsonPath('data.selected_variant.id', $secondVariant->id) ->assertJsonPath('data.selected_variant.stock_tecnico', 7) - ->assertJsonPath('data.selected_variant.values.size', 'M') + ->assertJsonPath('data.selected_variant.values.size.value', 'M') + ->assertJsonPath('data.selected_variant.values.size.label', 'Medium') ->assertJsonCount(1, 'data.selected_variant.images'); $response ->assertJsonMissingPath('data.stock_tecnico') @@ -207,10 +210,14 @@ class CatalogItemDetailControllerTest extends TestCase ->assertJsonCount(2, 'data.attributes.0.options') ->assertJsonPath('data.attributes.0.options.0.id', $eventDate->id) ->assertJsonPath('data.attributes.0.options.0.value', (string) $eventDate->id) - ->assertJsonPath('data.attributes.0.options.0.label', '09/10/2026 · 09:00 a 18:00') + ->assertJsonPath('data.attributes.0.options.0.label', '09/10/2026') ->assertJsonPath('data.attributes.0.options.1.id', $unusedEventDate->id) ->assertJsonPath('data.attributes.0.options.1.value', (string) $unusedEventDate->id) - ->assertJsonPath('data.variants.0.values.event_date', (string) $eventDate->id); + ->assertJsonPath('data.variants.0.values.event_date.value', (string) $eventDate->id) + ->assertJsonPath( + 'data.variants.0.values.event_date.label', + '09/10/2026', + ); $this->assertDatabaseCount('attribute_options', 0); } diff --git a/tests/Unit/Catalog/CatalogModelsTest.php b/tests/Unit/Catalog/CatalogModelsTest.php index bf78f9c..19f87bf 100644 --- a/tests/Unit/Catalog/CatalogModelsTest.php +++ b/tests/Unit/Catalog/CatalogModelsTest.php @@ -186,6 +186,43 @@ class CatalogModelsTest extends TestCase $this->assertSame('2026-10-09', $variant->eventDate->date->format('Y-m-d')); } + public function test_variant_exposes_selection_values_with_api_labels(): void + { + $attribute = new Attribute; + $attribute->codigo = 'size'; + $attribute->setRelation('options', new EloquentCollection([ + new AttributeOption(['value' => 'M', 'label' => 'Medium']), + ])); + + $itemAttribute = new ItemAttribute; + $itemAttribute->allow_multi_select = false; + $itemAttribute->setRelation('attribute', $attribute); + + $definition = new VariantDefinition(['value' => 'M']); + $definition->item_attribute_id = 1; + $definition->setRelation('itemAttribute', $itemAttribute); + + $eventDate = new EventDate([ + 'date' => '2026-10-09', + 'time_start' => '09:00:00', + 'time_end' => '18:00:00', + ]); + $eventDate->id = 20; + + $variant = new Variant; + $variant->setRelation('definitions', new EloquentCollection([$definition])); + $variant->setRelation('eventDates', new EloquentCollection([$eventDate])); + + $this->assertSame( + ['value' => 'M', 'label' => 'Medium'], + $variant->selectionOptions()->get('size'), + ); + $this->assertSame( + ['value' => '20', 'label' => '09/10/2026'], + $variant->selectionOptions()->get('event_date'), + ); + } + public function test_inventory_maps_stock_without_a_polymorphic_owner(): void { $inventory = $this->trackedInventory(realStock: 10, reservedStock: 3);