refactor(catalog): Complete catalog refactor to simplify its data model and its querying.

source commits: refactor/catalog
This commit is contained in:
2026-07-21 09:23:19 -03:00
parent d3182fbd13
commit 29a2ca19a3
116 changed files with 5141 additions and 5217 deletions

View File

@@ -3,15 +3,16 @@
namespace Database\Seeders;
use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Catalog\Enums\ProductLayout;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\Brand;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Category;
use App\Domains\Catalog\Models\Product;
use App\Domains\Catalog\Services\ProductService;
use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Seeder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use RuntimeException;
@@ -65,7 +66,7 @@ class ProductCatalogFromImagesSeeder extends Seeder
'istockphoto-1675347112-2048x2048.jpg',
];
public function __construct(private readonly ProductService $productService) {}
public function __construct(private readonly CatalogService $catalogService) {}
/**
* Run the database seeds.
@@ -140,14 +141,14 @@ class ProductCatalogFromImagesSeeder extends Seeder
->unique()
->values();
$existingProducts = Product::query()
->where('tenant_codigo', $tenant->codigo)
$existingProducts = CatalogItem::query()
->where('tenant_code', $tenant->codigo)
->whereIn('slug', $productSlugsToDelete)
->with(['attachments', 'variants.attachments'])
->with('variants:id,catalog_item_id,inventory_id')
->get();
foreach ($existingProducts as $existingProduct) {
$this->productService->delete($existingProduct);
$this->catalogService->delete($existingProduct);
}
foreach ($productsToSeed as $catalogProduct) {
@@ -158,6 +159,34 @@ class ProductCatalogFromImagesSeeder extends Seeder
$catalogProduct['variant_groups'],
);
}
$this->seedFeaturedProducts($tenant);
}
private function seedFeaturedProducts(Tenant $tenant): void
{
FeaturedGroup::query()->where('tenant_code', $tenant->codigo)->delete();
$group = FeaturedGroup::query()->create([
'tenant_code' => $tenant->codigo,
'product_layout' => ProductLayout::ColumnWithImage,
'group_name' => 'Productos',
'group_order' => 0,
]);
$items = CatalogItem::query()
->where('tenant_code', $tenant->codigo)
->orderBy('id')
->get('id');
$group->featuredItems()->createMany(
$items->values()->map(
fn (CatalogItem $item, int $order): array => [
'catalog_item_id' => $item->id,
'order' => $order,
]
)->all()
);
}
/**
@@ -189,33 +218,21 @@ class ProductCatalogFromImagesSeeder extends Seeder
throw new RuntimeException("Category '{$metadata['category_name']}' not found.");
}
$attributeIds = Attribute::query()
$attributeCodes = Attribute::query()
->where('tenant_codigo', $tenant->codigo)
->whereIn('codigo', $metadata['attribute_codes'])
->pluck('id', 'codigo');
->pluck('codigo');
if ($attributeIds->count() !== count($metadata['attribute_codes'])) {
if ($attributeCodes->count() !== count($metadata['attribute_codes'])) {
throw new RuntimeException("Missing attributes for product '{$metadata['product_slug']}'.");
}
$product = $this->productService->create($tenant, [
'categoria_id' => $category->id,
'brand_id' => $brand?->id,
'slug' => $metadata['product_slug'],
'nombre' => $metadata['product_name'],
'descripcion' => $metadata['description'],
'precio' => $pricing['price'],
'inventory_policy' => InventoryPolicy::Tracked->value,
'attribute_ids' => array_values($attributeIds->all()),
]);
$productAttributeIds = DB::table('products_attributes')
->where('product_id', $product->id)
->pluck('id', 'attribute_id');
$sizes = $metadata['type'] === 'zapatillas'
? self::SHOE_SIZES
: self::CLOTHING_SIZES;
$variants = [];
$itemImages = [];
$directStock = 0;
foreach ($variantGroups as $variantGroup) {
$images = array_map(
@@ -223,47 +240,29 @@ class ProductCatalogFromImagesSeeder extends Seeder
$variantGroup['files']
);
if ($attributeIds->isEmpty()) {
$this->productService->createVariant($product, [
'stock' => $variantGroup['stock'],
'inventory_policy' => InventoryPolicy::Tracked->value,
'definitions' => [],
'images' => $images,
]);
if ($attributeCodes->isEmpty()) {
$directStock = $variantGroup['stock'];
$itemImages = [...$itemImages, ...$images];
continue;
}
foreach ($sizes as $size) {
$definitions = [];
$values = [];
if (isset($attributeIds['color']) && $variantGroup['metadata']['color'] !== null) {
$colorProductAttributeId = $productAttributeIds[$attributeIds['color']] ?? null;
if ($colorProductAttributeId === null) {
throw new RuntimeException("Missing product attribute for color on product '{$metadata['product_slug']}'.");
}
$definitions[] = [
'products_attribute_id' => $colorProductAttributeId,
'value' => $variantGroup['metadata']['color'],
];
if ($attributeCodes->contains('color') && $variantGroup['metadata']['color'] !== null) {
$values['color'] = $variantGroup['metadata']['color'];
}
$sizeAttributeCode = $metadata['type'] === 'zapatillas'
? 'talle_numerico'
: 'talle';
$sizeProductAttributeId = $productAttributeIds[$attributeIds[$sizeAttributeCode]] ?? null;
if ($sizeProductAttributeId === null) {
if (! $attributeCodes->contains($sizeAttributeCode)) {
throw new RuntimeException("Missing product attribute for size on product '{$metadata['product_slug']}'.");
}
$definitions[] = [
'products_attribute_id' => $sizeProductAttributeId,
'value' => $size,
];
$values[$sizeAttributeCode] = $size;
// Override stock for specific variants as requested by the user
$stock = $variantGroup['stock'];
@@ -280,14 +279,30 @@ class ProductCatalogFromImagesSeeder extends Seeder
$stock = 0;
}
$this->productService->createVariant($product, [
'stock' => $stock,
'inventory_policy' => InventoryPolicy::Tracked->value,
'definitions' => $definitions,
$variants[] = [
'real_stock' => $stock,
'values' => $values,
'images' => $images,
]);
];
}
}
$this->catalogService->create([
'tenant_code' => $tenant->codigo,
'category_id' => $category->id,
'brand_id' => $brand?->id,
'slug' => $metadata['product_slug'],
'nombre' => $metadata['product_name'],
'descripcion' => $metadata['description'],
'precio' => $pricing['price'],
'inventory_policy' => InventoryPolicy::Tracked->value,
...($attributeCodes->isEmpty()
? ['real_stock' => $directStock, 'images' => $itemImages]
: [
'attribute_codes' => array_values($metadata['attribute_codes']),
'variants' => $variants,
]),
]);
}
/**