feat: implement FeaturedVariant and CatalogController for managing featured variants and catalog display

This commit is contained in:
ncoronel 2026-07-14 14:55:48 -03:00
parent 299a462283
commit c2efec8906
17 changed files with 200 additions and 61 deletions

View File

@ -0,0 +1,23 @@
<?php
namespace App\Domains\Catalog\Controllers;
use App\Domains\Catalog\Resources\CatalogFeaturedGroupResource;
use App\Domains\Product\Models\FeaturedGroup;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Routing\Controller;
class CatalogController extends Controller
{
public function index(string $tenant): AnonymousResourceCollection
{
$featuredGroups = FeaturedGroup::where('tenant_codigo', $tenant)
->with(['featuredVariants' => function ($query) {
$query->orderBy('order');
}, 'featuredVariants.variant.product', 'featuredVariants.variant.attachments', 'featuredVariants.variant.product.attachments'])
->orderBy('group_order')
->get();
return CatalogFeaturedGroupResource::collection($featuredGroups);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Domains\Catalog\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Domains\Product\Models\FeaturedVariant;
class CatalogFeaturedGroupResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->group_name,
'layout' => $this->product_layout,
'group_order' => $this->group_order,
'items' => $this->whenLoaded('featuredVariants', function () use ($request) {
return $this->featuredVariants->map(function (FeaturedVariant $featuredVariant) use ($request) {
$variant = $featuredVariant->variant;
$variantResource = (new ProductVariantResource($variant))->toArray($request);
if ($this->product_layout === 'row' || $this->product_layout === 'column_with_cart' || $this->product_layout === 'vertical_with_cart') {
// Devuelve el Product Variant Resource completo como pidio el usuario
// "Que todo devuelva el product variant resource"
return $variantResource;
}
// Para column_with_image o vertical_with_image
if ($this->product_layout === 'column_with_image' || $this->product_layout === 'vertical_with_image') {
if (isset($variantResource['images']) && count($variantResource['images']) > 0) {
$variantResource['images'] = [$variantResource['images'][0]];
}
return $variantResource;
}
return $variantResource;
});
}),
];
}
}

View File

@ -1,6 +1,7 @@
<?php
use App\Domains\Catalog\Controllers\BrandController;
use App\Domains\Catalog\Controllers\CatalogController;
use App\Domains\Catalog\Controllers\CategoryController;
use App\Domains\Catalog\Controllers\ProductController;
use App\Domains\Catalog\Controllers\AttributeController;
@ -8,6 +9,7 @@ use App\Domains\Catalog\Controllers\ProductVariantController;
use Illuminate\Support\Facades\Route;
Route::prefix('tenants/{tenant:codigo}')->group(function (): void {
Route::get('catalog', [CatalogController::class, 'index']);
Route::apiResource('marcas', BrandController::class)->parameters(['marcas' => 'marca']);
Route::apiResource('categorias', CategoryController::class)->parameters(['categorias' => 'categoria']);
Route::apiResource('productos', ProductController::class);

View File

@ -32,7 +32,7 @@ class FeaturedGroupController extends Controller
public function show(string $tenant, FeaturedGroup $featuredGroup): FeaturedGroupResource
{
abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
return new FeaturedGroupResource($featuredGroup->load('featuredProducts'));
return new FeaturedGroupResource($featuredGroup->load('featuredVariants'));
}
public function update(UpdateFeaturedGroupRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedGroupResource

View File

@ -1,42 +0,0 @@
<?php
namespace App\Domains\Product\Controllers;
use App\Domains\Product\Models\FeaturedGroup;
use App\Domains\Product\Models\FeaturedProduct;
use App\Domains\Product\Requests\StoreFeaturedProductRequest;
use App\Domains\Product\Requests\UpdateFeaturedProductRequest;
use App\Domains\Product\Resources\FeaturedProductResource;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Routing\Controller;
use Illuminate\Http\JsonResponse;
class FeaturedProductController extends Controller
{
public function index(string $tenant, FeaturedGroup $featuredGroup): AnonymousResourceCollection
{
abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
return FeaturedProductResource::collection($featuredGroup->featuredProducts);
}
public function store(StoreFeaturedProductRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedProductResource
{
abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
$product = $featuredGroup->featuredProducts()->create($request->validated());
return new FeaturedProductResource($product);
}
public function update(UpdateFeaturedProductRequest $request, string $tenant, FeaturedGroup $featuredGroup, FeaturedProduct $featuredProduct): FeaturedProductResource
{
abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredProduct->featured_group_id !== $featuredGroup->id, 404);
$featuredProduct->update($request->validated());
return new FeaturedProductResource($featuredProduct);
}
public function destroy(string $tenant, FeaturedGroup $featuredGroup, FeaturedProduct $featuredProduct): JsonResponse
{
abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredProduct->featured_group_id !== $featuredGroup->id, 404);
$featuredProduct->delete();
return response()->json(null, 204);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Domains\Product\Controllers;
use App\Domains\Product\Models\FeaturedGroup;
use App\Domains\Product\Models\FeaturedVariant;
use App\Domains\Product\Requests\StoreFeaturedVariantRequest;
use App\Domains\Product\Requests\UpdateFeaturedVariantRequest;
use App\Domains\Product\Resources\FeaturedVariantResource;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Routing\Controller;
use Illuminate\Http\JsonResponse;
class FeaturedVariantController extends Controller
{
public function index(string $tenant, FeaturedGroup $featuredGroup): AnonymousResourceCollection
{
abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
return FeaturedVariantResource::collection($featuredGroup->featuredVariants);
}
public function store(StoreFeaturedVariantRequest $request, string $tenant, FeaturedGroup $featuredGroup): FeaturedVariantResource
{
abort_if($featuredGroup->tenant_codigo !== $tenant, 404);
$variant = $featuredGroup->featuredVariants()->create($request->validated());
return new FeaturedVariantResource($variant);
}
public function update(UpdateFeaturedVariantRequest $request, string $tenant, FeaturedGroup $featuredGroup, FeaturedVariant $featuredVariant): FeaturedVariantResource
{
abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredVariant->featured_group_id !== $featuredGroup->id, 404);
$featuredVariant->update($request->validated());
return new FeaturedVariantResource($featuredVariant);
}
public function destroy(string $tenant, FeaturedGroup $featuredGroup, FeaturedVariant $featuredVariant): JsonResponse
{
abort_if($featuredGroup->tenant_codigo !== $tenant || $featuredVariant->featured_group_id !== $featuredGroup->id, 404);
$featuredVariant->delete();
return response()->json(null, 204);
}
}

View File

@ -11,10 +11,11 @@ class FeaturedGroup extends Model
'tenant_codigo',
'group_name',
'product_layout',
'group_order',
];
public function featuredProducts(): HasMany
public function featuredVariants(): HasMany
{
return $this->hasMany(FeaturedProduct::class);
return $this->hasMany(FeaturedVariant::class);
}
}

View File

@ -2,14 +2,17 @@
namespace App\Domains\Product\Models;
use App\Domains\Catalog\Models\ProductVariant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class FeaturedProduct extends Model
class FeaturedVariant extends Model
{
protected $table = 'featured_variants';
protected $fillable = [
'featured_group_id',
'product_id',
'product_variant_id',
'order',
];
@ -18,8 +21,8 @@ class FeaturedProduct extends Model
return $this->belongsTo(FeaturedGroup::class);
}
public function product(): BelongsTo
public function variant(): BelongsTo
{
return $this->belongsTo(Product::class);
return $this->belongsTo(ProductVariant::class, 'product_variant_id');
}
}

View File

@ -15,7 +15,8 @@ class StoreFeaturedGroupRequest extends FormRequest
{
return [
'group_name' => ['required', 'string', 'max:255'],
'product_layout' => ['required', 'string', 'in:row,column_with_image,column_with_cart'],
'product_layout' => ['required', 'string', 'in:row,column_with_image,column_with_cart,vertical_with_image,vertical_with_cart'],
'group_order' => ['nullable', 'integer'],
];
}
}

View File

@ -4,7 +4,7 @@ namespace App\Domains\Product\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreFeaturedProductRequest extends FormRequest
class StoreFeaturedVariantRequest extends FormRequest
{
public function authorize(): bool
{
@ -14,7 +14,7 @@ class StoreFeaturedProductRequest extends FormRequest
public function rules(): array
{
return [
'product_id' => ['required', 'integer'],
'product_variant_id' => ['required', 'integer'],
'order' => ['nullable', 'integer'],
];
}

View File

@ -15,7 +15,8 @@ class UpdateFeaturedGroupRequest extends FormRequest
{
return [
'group_name' => ['sometimes', 'string', 'max:255'],
'product_layout' => ['sometimes', 'string', 'in:row,column_with_image,column_with_cart'],
'product_layout' => ['sometimes', 'string', 'in:row,column_with_image,column_with_cart,vertical_with_image,vertical_with_cart'],
'group_order' => ['nullable', 'integer'],
];
}
}

View File

@ -4,7 +4,7 @@ namespace App\Domains\Product\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateFeaturedProductRequest extends FormRequest
class UpdateFeaturedVariantRequest extends FormRequest
{
public function authorize(): bool
{

View File

@ -14,7 +14,8 @@ class FeaturedGroupResource extends JsonResource
'tenant_codigo' => $this->tenant_codigo,
'group_name' => $this->group_name,
'product_layout' => $this->product_layout,
'featured_products' => FeaturedProductResource::collection($this->whenLoaded('featuredProducts')),
'group_order' => $this->group_order,
'featured_variants' => FeaturedVariantResource::collection($this->whenLoaded('featuredVariants')),
];
}
}

View File

@ -5,16 +5,15 @@ namespace App\Domains\Product\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class FeaturedProductResource extends JsonResource
class FeaturedVariantResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'featured_group_id' => $this->featured_group_id,
'product_id' => $this->product_id,
'product_variant_id' => $this->product_variant_id,
'order' => $this->order,
// You can add a nested ProductResource here if needed and when loaded
];
}
}

View File

@ -1,15 +1,15 @@
<?php
use App\Domains\Product\Controllers\FeaturedGroupController;
use App\Domains\Product\Controllers\FeaturedProductController;
use App\Domains\Product\Controllers\FeaturedVariantController;
use Illuminate\Support\Facades\Route;
Route::prefix('tenants/{tenant:codigo}')->group(function (): void {
Route::apiResource('featured-groups', FeaturedGroupController::class);
Route::apiResource('featured-groups.products', FeaturedProductController::class)
Route::apiResource('featured-groups.variants', FeaturedVariantController::class)
->only(['index', 'store', 'update', 'destroy'])
->parameters([
'featured-groups' => 'featuredGroup',
'products' => 'featuredProduct',
'variants' => 'featuredVariant',
]);
});

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('featured_groups', function (Blueprint $table) {
$table->integer('group_order')->default(0)->after('product_layout');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('featured_groups', function (Blueprint $table) {
$table->dropColumn('group_order');
});
}
};

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (Schema::hasTable('featured_products')) {
Schema::rename('featured_products', 'featured_variants');
}
if (Schema::hasColumn('featured_variants', 'product_id')) {
Schema::table('featured_variants', function (Blueprint $table) {
$table->renameColumn('product_id', 'product_variant_id');
});
}
Schema::table('featured_variants', function (Blueprint $table) {
$table->foreign('product_variant_id')->references('id')->on('productos_variantes')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('featured_variants', function (Blueprint $table) {
//
});
}
};