diff --git a/app/Domains/Catalog/Controllers/ProductController.php b/app/Domains/Catalog/Controllers/ProductController.php index 0eaf2e3..1fc0d3d 100644 --- a/app/Domains/Catalog/Controllers/ProductController.php +++ b/app/Domains/Catalog/Controllers/ProductController.php @@ -29,16 +29,12 @@ class ProductController extends Controller return ProductResource::make($product)->response()->setStatusCode(201); } - public function show(Tenant $tenant, Product $producto): ProductResource + public function show(Tenant $tenant, Product $producto, ProductService $productService): ProductResource { $producto = $this->resolveScopedProduct($tenant, $producto); + $producto = $productService->getProductDetail($tenant, $producto); - return ProductResource::make($producto->load([ - 'attachments', - 'brand', - 'category', - 'attributes.options', - ])); + return ProductResource::make($producto); } public function update(UpdateProductRequest $request, Tenant $tenant, Product $producto, ProductService $productService): ProductResource diff --git a/app/Domains/Catalog/Models/Product.php b/app/Domains/Catalog/Models/Product.php index ec84324..117dc05 100644 --- a/app/Domains/Catalog/Models/Product.php +++ b/app/Domains/Catalog/Models/Product.php @@ -27,7 +27,8 @@ class Product extends Model use HasFactory; protected $table = 'productos'; - + + protected ?ProductVariant $defaultVariant = null; protected function casts(): array { return [ @@ -274,4 +275,16 @@ class Product extends Model $attribute->options()->delete(); $attribute->delete(); } + + + + public function setDefaultVariant(ProductVariant $variant): void + { + $this->defaultVariant = $variant; + } + + public function getDefaultVariant(): ?ProductVariant + { + return $this->defaultVariant; + } } diff --git a/app/Domains/Catalog/Resources/ProductResource.php b/app/Domains/Catalog/Resources/ProductResource.php index 313a9ac..c9b8a46 100644 --- a/app/Domains/Catalog/Resources/ProductResource.php +++ b/app/Domains/Catalog/Resources/ProductResource.php @@ -32,6 +32,22 @@ class ProductResource extends JsonResource ->values() ), 'attributes' => AttributeResource::collection($this->whenLoaded('attributes')), + 'variants' => ProductVariantResource::collection($this->whenLoaded('variants')), + 'default_variant' => $this->when( + $this->getDefaultVariant() !== null, + function () { + $defaultVariant = $this->getDefaultVariant(); + return [ + 'variant_id' => $defaultVariant->id, + 'images' => $defaultVariant->attachments->isNotEmpty() + ? $defaultVariant->attachments->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))->values() + : $this->attachments->map(fn ($attachment) => $attachment->getTemporaryUrl(1440))->values(), + 'attributes' => $defaultVariant->definitions->mapWithKeys(function ($definition) { + return [$definition->attribute?->codigo => $definition->value]; + })->toArray(), + ]; + } + ), ]; } } diff --git a/app/Domains/Catalog/Services/ProductService.php b/app/Domains/Catalog/Services/ProductService.php index 02c2d85..d35b07a 100644 --- a/app/Domains/Catalog/Services/ProductService.php +++ b/app/Domains/Catalog/Services/ProductService.php @@ -277,4 +277,27 @@ class ProductService return $products; } + + public function getProductDetail(Tenant $tenant, Product $product): Product + { + $product->load([ + 'attachments', + 'brand', + 'category', + 'attributes.options', + ]); + + $defaultVariant = $product->variants() + ->with([ + 'attachments', + 'definitions.attribute.options', + ]) + ->first(); + + if ($defaultVariant) { + $product->setDefaultVariant($defaultVariant); + } + + return $product; + } } diff --git a/tests/Feature/Catalog/ProductControllerTest.php b/tests/Feature/Catalog/ProductControllerTest.php index 0573476..41f4728 100644 --- a/tests/Feature/Catalog/ProductControllerTest.php +++ b/tests/Feature/Catalog/ProductControllerTest.php @@ -389,6 +389,75 @@ class ProductControllerTest extends TestCase ]); } + public function test_it_returns_product_detail_with_variants_and_default_variant(): void + { + // 1. Create a product + $product = Product::create([ + 'tenant_codigo' => $this->tenant->codigo, + 'categoria_id' => 1, + 'slug' => 'test-product-show', + 'nombre' => 'Test Product Show', + 'precio' => 100.00, + ]); + + // Associate attributes to product + $product->attributes()->sync([$this->sizeAttr->id, $this->colorAttr->id]); + + // 2. Create variant + $variant = $product->createVariant([ + 'slug' => 'test-product-show-s-azul', + 'nombre' => 'Test Product Show S Azul', + 'stock' => 10, + 'precio' => 105.00, + 'definitions' => [ + [ + 'attribute_id' => $this->sizeAttr->id, + 'value' => 'S', + ], + [ + 'attribute_id' => $this->colorAttr->id, + 'value' => 'Azul', + ], + ], + ]); + + // Attach an image to the variant + $attachment = \App\Domains\Attachable\Models\Attachment::create([ + 'key' => (string) Str::uuid(), + 'path' => 'attachments/variant-img.png', + 'filename' => 'variant-img.png', + 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'mime_type' => 'image/png', + ]); + $variant->attachments()->attach($attachment->id); + + // 3. Request product show detail + $response = $this->getJson("/api/tenants/{$this->tenant->codigo}/productos/{$product->id}"); + + $response->assertOk(); + + // 4. Assert default_variant structure and data + $response->assertJsonStructure([ + 'data' => [ + 'id', + 'nombre', + 'default_variant' => [ + 'variant_id', + 'images', + 'attributes' => [ + 'talle', + 'color', + ], + ], + ], + ]); + + $response->assertJsonPath('data.default_variant.variant_id', $variant->id); + $response->assertJsonPath('data.default_variant.attributes.talle', 'S'); + $response->assertJsonPath('data.default_variant.attributes.color', 'Azul'); + $response->assertJsonCount(1, 'data.default_variant.images'); + } + protected function createTenant(string $codigo, string $nombre, string $dominio): Tenant { $hdrKey = (string) Str::uuid();