Add new product images and implement SeedMutualSmepCatalogTest for catalog migration
- Added multiple new product images for smart TVs, audio devices, and accessories in WEBP format. - Created a new test class SeedMutualSmepCatalogTest to validate the catalog migration process. - Implemented setup and teardown methods to manage database schema and data during tests. - Verified the integrity of catalog items, inventories, and associated attributes after migration.
This commit is contained in:
308
database/scripts/build_mutual_smep_catalog_assets.php
Normal file
308
database/scripts/build_mutual_smep_catalog_assets.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$sourceRoot = 'C:\\Users\\ncoronel\\Documents\\catalogo_smep_limpio';
|
||||
$projectRoot = dirname(__DIR__, 2);
|
||||
$assetRoot = $projectRoot.'/public/images/tennants/mutual_smep/catalog';
|
||||
$manifestPath = $projectRoot.'/database/data/mutual_smep_catalog_v1.json';
|
||||
$defaultStock = 10;
|
||||
|
||||
if (! is_dir($sourceRoot)) {
|
||||
throw new RuntimeException("Source catalog not found: {$sourceRoot}");
|
||||
}
|
||||
|
||||
/** @return array{0: string, 1: string} */
|
||||
function catalogDestination(string $source, string $name): array
|
||||
{
|
||||
return match ($source) {
|
||||
'acolchados-frazadas-y-edredon' => preg_match('/^(Frazada|Manta)/iu', $name)
|
||||
? ['Dormitorio y Blanco', 'Frazadas y Mantas']
|
||||
: ['Dormitorio y Blanco', 'Acolchados y Edredones'],
|
||||
'acolchados-y-sabanas-infantil' => ['Dormitorio y Blanco', 'Infantil y Cuna'],
|
||||
'base-sommier-1-pl-0-80x1-90', 'base-sommier-2-pl-1-40x1-90' => ['Dormitorio y Blanco', 'Bases y Sommiers'],
|
||||
'colchon-1-plaza-0-80x1-90', 'colchon-2-plazas-1-40x1-90' => str_starts_with(mb_strtolower($name), 'soporte')
|
||||
? ['Tecnología', 'Soportes para TV']
|
||||
: ['Dormitorio y Blanco', 'Colchones'],
|
||||
'aires-acondicionados' => ['Climatización', 'Aires Acondicionados'],
|
||||
'calefaccion-con-salida-al-ext' => ['Climatización', 'Calefactores a Gas con Salida'],
|
||||
'calefactor-sin-salida-al-ext' => ['Climatización', 'Calefactores a Gas sin Salida'],
|
||||
'calefaccion-electrica' => ['Climatización', 'Calefacción Eléctrica'],
|
||||
'calefones-y-termotanques' => ['Electrodomésticos', 'Calefones y Termotanques'],
|
||||
'heladeras' => ['Electrodomésticos', 'Heladeras'],
|
||||
'lavarropas-automaticos' => ['Electrodomésticos', 'Lavarropas y Secarropas'],
|
||||
'cocinas' => ['Electrodomésticos', 'Cocinas'],
|
||||
'purificador-de-aire' => ['Electrodomésticos', 'Purificadores y Extractores de Cocina'],
|
||||
'microondas-y-hornos-electricos' => str_starts_with(mb_strtolower($name), 'horno')
|
||||
? ['Electrodomésticos', 'Hornos Eléctricos']
|
||||
: ['Electrodomésticos', 'Microondas'],
|
||||
'electrodomesticos-pequenos' => smallApplianceCategory($name),
|
||||
'celulares' => ['Tecnología', 'Celulares'],
|
||||
'notebooks-y-tablets' => ['Tecnología', 'Notebooks'],
|
||||
'smart-tv' => str_starts_with(mb_strtolower($name), 'soporte')
|
||||
? ['Tecnología', 'Soportes para TV']
|
||||
: ['Tecnología', 'Smart TV'],
|
||||
'tecnologia' => preg_match('/^(Mouse|Teclado)/iu', $name)
|
||||
? ['Tecnología', 'Accesorios de Computación']
|
||||
: ['Tecnología', 'Audio'],
|
||||
'bicicletas' => preg_match('/\bR16\b/iu', $name)
|
||||
? ['Bicicletas y Aire Libre', 'Bicicletas Infantiles']
|
||||
: ['Bicicletas y Aire Libre', 'Bicicletas para Adultos'],
|
||||
'piletas' => ['Bicicletas y Aire Libre', 'Piletas'],
|
||||
'bazar' => ['Bazar', 'Termos'],
|
||||
default => throw new RuntimeException("Unmapped source category: {$source}"),
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array{0: string, 1: string} */
|
||||
function smallApplianceCategory(string $name): array
|
||||
{
|
||||
return match (true) {
|
||||
preg_match('/^(Cafetera|Pava eléctrica|Tostadora)/iu', $name) === 1 => ['Electrodomésticos', 'Café y Desayuno'],
|
||||
preg_match('/^(Freidora|Waflera|Pochoclera)/iu', $name) === 1 => ['Electrodomésticos', 'Freidoras y Cocción'],
|
||||
preg_match('/^(Secador|Cortacabello|Cortabarba)/iu', $name) === 1 => ['Electrodomésticos', 'Cuidado Personal'],
|
||||
preg_match('/^(Lustraspiradora|Plancha)/iu', $name) === 1 => ['Electrodomésticos', 'Limpieza y Planchado'],
|
||||
default => ['Electrodomésticos', 'Preparación de Alimentos'],
|
||||
};
|
||||
}
|
||||
|
||||
function productBrand(string $name): ?string
|
||||
{
|
||||
if (preg_match('/\bJC\b/u', $name)) {
|
||||
return 'Jean Cartier';
|
||||
}
|
||||
|
||||
if (mb_stripos($name, 'Liliana') !== false) {
|
||||
return 'Liliana';
|
||||
}
|
||||
|
||||
foreach ([
|
||||
'Rosario Central', 'Jean Cartier', 'King Koil', 'Fire Bird', 'Xtrike Me',
|
||||
'Whitenblack', 'Electrolux', 'Kavanagh', 'Pelopincho', 'Suavegom',
|
||||
'Motorola', 'Samsung', 'Xiaomi', 'Moulinex', 'Peabody', 'Philips',
|
||||
'Florencia', 'Kohinoor', 'Inducol', 'Longvie', 'Stanley', 'Noblex',
|
||||
'Moonki', 'Lenovo', 'Piero', 'Eskabe', 'Oster', 'Drean', 'Gafa', 'Atma',
|
||||
'Spar', 'Stark', 'Venzo', 'Havit', 'Acer', 'ASUS', 'MSI', 'Philco',
|
||||
'Gama', 'GIGO', 'Ross', 'Nakan', 'TCL', 'BGH', 'JBL', 'TST', 'GBS', 'BLU',
|
||||
] as $brand) {
|
||||
if (mb_stripos($name, $brand) !== false) {
|
||||
return $brand;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return array{key: string, name: string, values: array<string, string>}|null */
|
||||
function variantGroup(string $name): ?array
|
||||
{
|
||||
$measure = bedMeasureValue($name);
|
||||
$groups = [
|
||||
'/(?:Kavanagh.*[Ss][ií]mil plumón reversible|[Ss][ií]mil plumón reversible.*Kavanagh)/iu' => ['acolchado-kavanagh-simil-plumon-reversible', 'Acolchado Kavanagh Símil Plumón Reversible'],
|
||||
'/Edredón.*Lisboa/iu' => ['edredon-lisboa-jean-cartier', 'Edredón Lisboa Jean Cartier'],
|
||||
'/Sense Dúo Bitono con corderito/iu' => ['acolchado-sense-duo-bitono-corderito', 'Acolchado Sense Dúo Bitono con Corderito'],
|
||||
'/Frazada polar.*Kavanagh Premium Soft/iu' => ['frazada-kavanagh-premium-soft', 'Frazada Kavanagh Premium Soft'],
|
||||
'/Kit Edredón.*Alaska.*corderito/iu' => ['kit-edredon-alaska-corderito', 'Kit Edredón Alaska con Corderito'],
|
||||
'/Colch[oó]n Inducol Constanza/iu' => ['colchon-inducol-constanza', 'Colchón Inducol Constanza'],
|
||||
];
|
||||
|
||||
foreach ($groups as $pattern => [$key, $itemName]) {
|
||||
if ($measure !== null && preg_match($pattern, $name)) {
|
||||
return [
|
||||
'key' => $key,
|
||||
'name' => $itemName,
|
||||
'values' => ['medida_cama' => $measure],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('/Motorola G15\s+4\/(256|512)\s*GB/iu', $name, $match)) {
|
||||
return [
|
||||
'key' => 'motorola-g15',
|
||||
'name' => 'Motorola G15',
|
||||
'values' => [
|
||||
'memoria_ram' => '4 GB',
|
||||
'almacenamiento' => "{$match[1]} GB",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function bedMeasureValue(string $name): ?string
|
||||
{
|
||||
return match (true) {
|
||||
preg_match('/\bcuna\b/iu', $name) === 1 => 'Cuna',
|
||||
preg_match('/\bKing\b/iu', $name) === 1 => 'King',
|
||||
preg_match('/\bQueen\b/iu', $name) === 1 => 'Queen',
|
||||
preg_match('/2\s*1\/2\s*pl/iu', $name) === 1 => '2 1/2 Plazas',
|
||||
preg_match('/1\s*1\/2\s*pl/iu', $name) === 1 => '1 1/2 Plazas',
|
||||
preg_match('/1[,.]40\s*x?\s*1[,.]90|140x190|140x24/iu', $name) === 1 => '2 Plazas',
|
||||
preg_match('/0[,.](80|90)\s*x?\s*1[,.]90|080x|090x|0,8x/iu', $name) === 1 => '1 Plaza',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
function compressCatalogImage(string $source, string $destination): void
|
||||
{
|
||||
$sourceImage = @imagecreatefromjpeg($source);
|
||||
if ($sourceImage === false) {
|
||||
throw new RuntimeException("Unable to read JPEG image: {$source}");
|
||||
}
|
||||
|
||||
$width = imagesx($sourceImage);
|
||||
$height = imagesy($sourceImage);
|
||||
$scale = min(1, 1200 / max($width, $height));
|
||||
$targetWidth = max(1, (int) round($width * $scale));
|
||||
$targetHeight = max(1, (int) round($height * $scale));
|
||||
$targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
|
||||
|
||||
if ($targetImage === false) {
|
||||
throw new RuntimeException("Unable to allocate image: {$source}");
|
||||
}
|
||||
|
||||
imagefill($targetImage, 0, 0, imagecolorallocate($targetImage, 255, 255, 255));
|
||||
imagecopyresampled(
|
||||
$targetImage,
|
||||
$sourceImage,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$targetWidth,
|
||||
$targetHeight,
|
||||
$width,
|
||||
$height,
|
||||
);
|
||||
|
||||
$destinationDirectory = dirname($destination);
|
||||
if (! is_dir($destinationDirectory) && ! mkdir($destinationDirectory, 0777, true) && ! is_dir($destinationDirectory)) {
|
||||
throw new RuntimeException("Unable to create directory: {$destinationDirectory}");
|
||||
}
|
||||
|
||||
if (! imagewebp($targetImage, $destination, 82)) {
|
||||
throw new RuntimeException("Unable to write WebP image: {$destination}");
|
||||
}
|
||||
}
|
||||
|
||||
$rawProducts = [];
|
||||
$jsonFiles = glob($sourceRoot.'\\*\\*.json') ?: [];
|
||||
sort($jsonFiles);
|
||||
|
||||
foreach ($jsonFiles as $jsonFile) {
|
||||
$data = json_decode((string) file_get_contents($jsonFile), true, flags: JSON_THROW_ON_ERROR);
|
||||
$sourceCategory = (string) $data['categoria']['slug'];
|
||||
|
||||
foreach ($data['productos'] as $product) {
|
||||
[$parentCategory, $category] = catalogDestination($sourceCategory, $product['nombre']);
|
||||
$images = [];
|
||||
|
||||
foreach ($product['imagenes'] as $relativeImage) {
|
||||
$sourceImage = dirname($jsonFile).'/'.str_replace('/', DIRECTORY_SEPARATOR, $relativeImage);
|
||||
$basename = pathinfo($relativeImage, PATHINFO_FILENAME).'.webp';
|
||||
$publicRelativePath = "images/tennants/mutual_smep/catalog/{$sourceCategory}/{$basename}";
|
||||
$destination = $projectRoot.'/public/'.$publicRelativePath;
|
||||
compressCatalogImage($sourceImage, $destination);
|
||||
$images[] = $publicRelativePath;
|
||||
}
|
||||
|
||||
$rawProducts[] = [
|
||||
'source_slug' => $product['slug'],
|
||||
'name' => $product['nombre'],
|
||||
'description' => $product['descripcion'] ?? null,
|
||||
'price' => (float) $product['precio']['importe'],
|
||||
'brand' => productBrand($product['nombre']),
|
||||
'parent_category' => $parentCategory,
|
||||
'category' => $category,
|
||||
'images' => $images,
|
||||
'variant_group' => variantGroup($product['nombre']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($rawProducts as $product) {
|
||||
$group = $product['variant_group'];
|
||||
|
||||
if ($group === null) {
|
||||
$items[$product['source_slug']] = [
|
||||
'slug' => $product['source_slug'],
|
||||
'name' => $product['name'],
|
||||
'description' => $product['description'],
|
||||
'price' => $product['price'],
|
||||
'stock' => $defaultStock,
|
||||
'brand' => $product['brand'],
|
||||
'parent_category' => $product['parent_category'],
|
||||
'category' => $product['category'],
|
||||
'images' => $product['images'],
|
||||
'attribute_codes' => [],
|
||||
'variants' => [],
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $group['key'];
|
||||
if (! isset($items[$key])) {
|
||||
$items[$key] = [
|
||||
'slug' => $key,
|
||||
'name' => $group['name'],
|
||||
'description' => $product['description'],
|
||||
'price' => $product['price'],
|
||||
'stock' => null,
|
||||
'brand' => $product['brand'],
|
||||
'parent_category' => $product['parent_category'],
|
||||
'category' => $product['category'],
|
||||
'images' => [],
|
||||
'attribute_codes' => [],
|
||||
'variants' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$items[$key]['price'] = min($items[$key]['price'], $product['price']);
|
||||
$items[$key]['description'] ??= $product['description'];
|
||||
$items[$key]['brand'] ??= $product['brand'];
|
||||
$items[$key]['attribute_codes'] = array_values(array_unique([
|
||||
...$items[$key]['attribute_codes'],
|
||||
...array_keys($group['values']),
|
||||
]));
|
||||
$items[$key]['variants'][] = [
|
||||
'source_slug' => $product['source_slug'],
|
||||
'description' => $product['description'],
|
||||
'price' => $product['price'],
|
||||
'stock' => $defaultStock,
|
||||
'values' => $group['values'],
|
||||
'images' => $product['images'],
|
||||
];
|
||||
}
|
||||
|
||||
ksort($items);
|
||||
|
||||
$manifest = [
|
||||
'version' => 1,
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'default_stock' => $defaultStock,
|
||||
'source_product_count' => count($rawProducts),
|
||||
'catalog_item_count' => count($items),
|
||||
'image_count' => array_sum(array_map(fn (array $product): int => count($product['images']), $rawProducts)),
|
||||
'items' => array_values($items),
|
||||
];
|
||||
|
||||
$manifestDirectory = dirname($manifestPath);
|
||||
if (! is_dir($manifestDirectory) && ! mkdir($manifestDirectory, 0777, true) && ! is_dir($manifestDirectory)) {
|
||||
throw new RuntimeException("Unable to create directory: {$manifestDirectory}");
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$manifestPath,
|
||||
json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR).PHP_EOL,
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'manifest' => $manifestPath,
|
||||
'source_products' => count($rawProducts),
|
||||
'catalog_items' => count($items),
|
||||
'images' => $manifest['image_count'],
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
|
||||
Reference in New Issue
Block a user