feat: implement product seeding logic from image catalog and add supporting service methods and requests
This commit is contained in:
@@ -31,6 +31,7 @@ class StoreProductRequest extends FormRequest
|
|||||||
'nombre' => ['required', 'string', 'max:255'],
|
'nombre' => ['required', 'string', 'max:255'],
|
||||||
'descripcion' => ['nullable', 'string'],
|
'descripcion' => ['nullable', 'string'],
|
||||||
'precio' => ['required', 'numeric', 'min:0'],
|
'precio' => ['required', 'numeric', 'min:0'],
|
||||||
|
'stock' => ['sometimes', 'integer', 'min:0'],
|
||||||
'attribute_ids' => ['sometimes', 'array'],
|
'attribute_ids' => ['sometimes', 'array'],
|
||||||
'attribute_ids.*' => [
|
'attribute_ids.*' => [
|
||||||
'required',
|
'required',
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ class ProductService
|
|||||||
return DB::transaction(function () use ($tenant, $data) {
|
return DB::transaction(function () use ($tenant, $data) {
|
||||||
$attributeIds = $data['attribute_ids'] ?? [];
|
$attributeIds = $data['attribute_ids'] ?? [];
|
||||||
$images = $data['images'] ?? [];
|
$images = $data['images'] ?? [];
|
||||||
unset($data['attribute_ids'], $data['images']);
|
$stock = $data['stock'] ?? 0;
|
||||||
|
unset($data['attribute_ids'], $data['images'], $data['stock']);
|
||||||
|
|
||||||
/** @var Product $product */
|
/** @var Product $product */
|
||||||
$product = Product::query()->create([
|
$product = Product::query()->create([
|
||||||
@@ -40,6 +41,12 @@ class ProductService
|
|||||||
$this->syncProductImages($product, $images);
|
$this->syncProductImages($product, $images);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create default variant with stock
|
||||||
|
$this->createVariant($product, [
|
||||||
|
'stock' => $stock,
|
||||||
|
'definitions' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
return $product->load(['attributes.options', 'attachments', 'brand', 'category']);
|
return $product->load(['attributes.options', 'attachments', 'brand', 'category']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -109,6 +116,17 @@ class ProductService
|
|||||||
$images = $data['images'] ?? [];
|
$images = $data['images'] ?? [];
|
||||||
unset($data['images']);
|
unset($data['images']);
|
||||||
|
|
||||||
|
// If the variant being created has definitions, it is a real variant.
|
||||||
|
// Remove any default variants (those without definitions).
|
||||||
|
$hasDefinitions = ! empty($data['definitions']);
|
||||||
|
if ($hasDefinitions) {
|
||||||
|
$defaultVariants = $product->variants()->whereDoesntHave('definitions')->get();
|
||||||
|
foreach ($defaultVariants as $defaultVariant) {
|
||||||
|
$this->deleteVariantAttachments($defaultVariant);
|
||||||
|
$product->deleteVariant($defaultVariant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$variant = $product->createVariant($data);
|
$variant = $product->createVariant($data);
|
||||||
|
|
||||||
if (! empty($images)) {
|
if (! empty($images)) {
|
||||||
@@ -153,6 +171,14 @@ class ProductService
|
|||||||
$product = $variant->product;
|
$product = $variant->product;
|
||||||
$this->deleteVariantAttachments($variant);
|
$this->deleteVariantAttachments($variant);
|
||||||
$product->deleteVariant($variant);
|
$product->deleteVariant($variant);
|
||||||
|
|
||||||
|
// Re-create a default variant with stock 0 if it has no variants left
|
||||||
|
if ($product->variants()->count() === 0) {
|
||||||
|
$product->createVariant([
|
||||||
|
'stock' => 0,
|
||||||
|
'definitions' => [],
|
||||||
|
]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,52 @@ class AttributeSeeder extends Seeder
|
|||||||
$tenants = Tenant::all();
|
$tenants = Tenant::all();
|
||||||
|
|
||||||
foreach ($tenants as $tenant) {
|
foreach ($tenants as $tenant) {
|
||||||
|
// Seed Color attribute
|
||||||
|
$existingColor = Attribute::query()
|
||||||
|
->where('tenant_codigo', $tenant->codigo)
|
||||||
|
->where('codigo', 'color')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingColor) {
|
||||||
|
Product::deleteAttribute($existingColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Product::createAttribute($tenant, [
|
||||||
|
'codigo' => 'color',
|
||||||
|
'nombre' => 'Color',
|
||||||
|
'type' => FieldType::Select->value,
|
||||||
|
'is_required' => true,
|
||||||
|
'metadata_schema' => [
|
||||||
|
'hex' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
[
|
||||||
|
'value' => 'Negro',
|
||||||
|
'label' => 'Negro',
|
||||||
|
'sort_order' => 1,
|
||||||
|
'metadata' => ['hex' => '#000000'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => 'Gris',
|
||||||
|
'label' => 'Gris',
|
||||||
|
'sort_order' => 2,
|
||||||
|
'metadata' => ['hex' => '#808080'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => 'Blanco',
|
||||||
|
'label' => 'Blanco',
|
||||||
|
'sort_order' => 3,
|
||||||
|
'metadata' => ['hex' => '#FFFFFF'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => 'Azul',
|
||||||
|
'label' => 'Azul',
|
||||||
|
'sort_order' => 4,
|
||||||
|
'metadata' => ['hex' => '#0000FF'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
// Seed Talle (Size - Text options) attribute
|
// Seed Talle (Size - Text options) attribute
|
||||||
$existingTalle = Attribute::query()
|
$existingTalle = Attribute::query()
|
||||||
->where('tenant_codigo', $tenant->codigo)
|
->where('tenant_codigo', $tenant->codigo)
|
||||||
@@ -64,51 +110,6 @@ class AttributeSeeder extends Seeder
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Seed Color attribute
|
|
||||||
$existingColor = Attribute::query()
|
|
||||||
->where('tenant_codigo', $tenant->codigo)
|
|
||||||
->where('codigo', 'color')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($existingColor) {
|
|
||||||
Product::deleteAttribute($existingColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
Product::createAttribute($tenant, [
|
|
||||||
'codigo' => 'color',
|
|
||||||
'nombre' => 'Color',
|
|
||||||
'type' => FieldType::Select->value,
|
|
||||||
'is_required' => true,
|
|
||||||
'metadata_schema' => [
|
|
||||||
'hex' => ['type' => 'string'],
|
|
||||||
],
|
|
||||||
'options' => [
|
|
||||||
[
|
|
||||||
'value' => 'Negro',
|
|
||||||
'label' => 'Negro',
|
|
||||||
'sort_order' => 1,
|
|
||||||
'metadata' => ['hex' => '#000000'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'value' => 'Gris',
|
|
||||||
'label' => 'Gris',
|
|
||||||
'sort_order' => 2,
|
|
||||||
'metadata' => ['hex' => '#808080'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'value' => 'Blanco',
|
|
||||||
'label' => 'Blanco',
|
|
||||||
'sort_order' => 3,
|
|
||||||
'metadata' => ['hex' => '#FFFFFF'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'value' => 'Azul',
|
|
||||||
'label' => 'Azul',
|
|
||||||
'sort_order' => 4,
|
|
||||||
'metadata' => ['hex' => '#0000FF'],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class CategorySeeder extends Seeder
|
|||||||
'Remeras',
|
'Remeras',
|
||||||
'Pantalones',
|
'Pantalones',
|
||||||
'Zapatillas',
|
'Zapatillas',
|
||||||
'Buzos'
|
'Buzos',
|
||||||
|
'Accesorios'
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($categories as $nombre) {
|
foreach ($categories as $nombre) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class ProductCatalogFromImagesSeeder extends Seeder
|
|||||||
],
|
],
|
||||||
'zapatillas_lecoq' => ['price' => 109999, 'stock' => 6],
|
'zapatillas_lecoq' => ['price' => 109999, 'stock' => 6],
|
||||||
'zapatillas_topper' => ['price' => 104999, 'stock' => 7],
|
'zapatillas_topper' => ['price' => 104999, 'stock' => 7],
|
||||||
|
'pelota_mundial' => ['price' => 45000, 'stock' => 15],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,6 +221,15 @@ class ProductCatalogFromImagesSeeder extends Seeder
|
|||||||
$variantGroup['files']
|
$variantGroup['files']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($attributeIds->isEmpty()) {
|
||||||
|
$this->productService->createVariant($product, [
|
||||||
|
'stock' => $variantGroup['stock'],
|
||||||
|
'definitions' => [],
|
||||||
|
'images' => $images,
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($sizes as $size) {
|
foreach ($sizes as $size) {
|
||||||
$definitions = [];
|
$definitions = [];
|
||||||
|
|
||||||
@@ -301,6 +311,19 @@ class ProductCatalogFromImagesSeeder extends Seeder
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($groupKey === 'pelota_mundial') {
|
||||||
|
return [
|
||||||
|
'type' => 'pelota',
|
||||||
|
'category_name' => 'Accesorios',
|
||||||
|
'brand_name' => 'Adidas',
|
||||||
|
'color' => null,
|
||||||
|
'attribute_codes' => [],
|
||||||
|
'product_slug' => $groupKey,
|
||||||
|
'product_name' => 'Pelota Mundial 2026',
|
||||||
|
'description' => 'Pelota oficial del mundial 2026 sin variantes.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$segments = explode('_', $groupKey);
|
$segments = explode('_', $groupKey);
|
||||||
$type = array_shift($segments);
|
$type = array_shift($segments);
|
||||||
|
|
||||||
|
|||||||
BIN
storage/public/catalog/pelota_mundial.webp
Normal file
BIN
storage/public/catalog/pelota_mundial.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
storage/public/catalog/pelota_mundial_1.jpg
Normal file
BIN
storage/public/catalog/pelota_mundial_1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
Reference in New Issue
Block a user