Merge branch 'tenant/smep' into homo
This commit is contained in:
2691
database/data/mutual_smep_catalog_v1.json
Normal file
2691
database/data/mutual_smep_catalog_v1.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const CLIENT_CODE = 'mutual_smep';
|
||||
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
private const EXCLUDED_MENU_CODES = [
|
||||
'account.tickets',
|
||||
'adminapp.tickets',
|
||||
'adminapp.fiesta-futbol-infantil.entradas',
|
||||
'adminapp.fiesta-futbol-infantil.alojamientos',
|
||||
'adminapp.fiesta-futbol-infantil.merchandising',
|
||||
'adminapp.fiesta-futbol-infantil.comida',
|
||||
'adminapp.desfile.entradas',
|
||||
'event.index',
|
||||
'event.category',
|
||||
'event.detail',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private array $storedPaths = [];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (DB::table('tenants')->where('codigo', self::TENANT_CODE)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
! DB::table('admin_website_types')->where('codigo', 'shopit')->exists()
|
||||
|| ! DB::table('storefront_website_types')->where('codigo', 'shopit')->exists()
|
||||
) {
|
||||
// Fresh installations provision website-type reference data separately.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function (): void {
|
||||
$clientId = $this->clientId();
|
||||
$headerLogoId = $this->storeImage(
|
||||
'images/tennants/mutual_smep/mutual_smep_header.png',
|
||||
'mutual_smep_header.png',
|
||||
);
|
||||
$footerLogoId = $this->storeImage(
|
||||
'images/tennants/mutual_smep/mutual_smep_footer.png',
|
||||
'mutual_smep_footer.png',
|
||||
);
|
||||
$faviconId = $this->storeImage(
|
||||
'images/tennants/mutual_smep/mutual_smep_favicon.png',
|
||||
'mutual_smep_favicon.png',
|
||||
);
|
||||
$now = now();
|
||||
|
||||
DB::table('tenants')->insert([
|
||||
'client_id' => $clientId,
|
||||
'codigo' => self::TENANT_CODE,
|
||||
'nombre' => 'Mutual SMEP',
|
||||
'timezone' => 'America/Argentina/Buenos_Aires',
|
||||
'dominio' => 'mutual-smep.localhost',
|
||||
'base_path' => '/',
|
||||
'site_title' => 'Tienda Mutual SMEP',
|
||||
'address' => 'San Lorenzo 1543, Rosario, Santa Fe',
|
||||
'phone' => '+54 9 341 247-4530',
|
||||
'primary_color' => '#4A7FF5',
|
||||
'secondary_color' => '#0051A4',
|
||||
'danger_color' => '#FF8888',
|
||||
'success_color' => '#198754',
|
||||
'header_bg_color' => '#FFFFFF',
|
||||
'footer_bg_color' => '#0051A4',
|
||||
'header_logo_id' => $headerLogoId,
|
||||
'footer_logo_id' => $footerLogoId,
|
||||
'favicon_id' => $faviconId,
|
||||
'admin_website_type_code' => 'shopit',
|
||||
'storefront_website_type_code' => 'shopit',
|
||||
'display_categories' => true,
|
||||
'display_seach_bar' => true,
|
||||
'display_cart' => true,
|
||||
'cart_editing_policy' => 'quantity_and_remove',
|
||||
'checkout_editing_policy' => 'disabled',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
$this->assignSocialMedia($now);
|
||||
$this->assignMenus($now);
|
||||
});
|
||||
} catch (Throwable $throwable) {
|
||||
Storage::disk('s3')->delete($this->storedPaths);
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$tenant = DB::table('tenants')
|
||||
->where('codigo', self::TENANT_CODE)
|
||||
->first(['id', 'client_id', 'header_logo_id', 'footer_logo_id', 'favicon_id']);
|
||||
|
||||
if ($tenant === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attachmentIds = collect([
|
||||
$tenant->header_logo_id,
|
||||
$tenant->footer_logo_id,
|
||||
$tenant->favicon_id,
|
||||
])->filter()->unique()->values();
|
||||
$storedPaths = DB::table('attachments')
|
||||
->whereIn('id', $attachmentIds)
|
||||
->pluck('path')
|
||||
->all();
|
||||
|
||||
DB::transaction(function () use ($tenant, $attachmentIds): void {
|
||||
DB::table('tenant_social_media')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->delete();
|
||||
DB::table('tenants_menues')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->delete();
|
||||
DB::table('tenants')
|
||||
->where('id', $tenant->id)
|
||||
->delete();
|
||||
DB::table('attachments')
|
||||
->whereIn('id', $attachmentIds)
|
||||
->delete();
|
||||
|
||||
$clientHasTenants = DB::table('tenants')
|
||||
->where('client_id', $tenant->client_id)
|
||||
->exists();
|
||||
|
||||
if (! $clientHasTenants) {
|
||||
DB::table('clients')
|
||||
->where('id', $tenant->client_id)
|
||||
->where('code', self::CLIENT_CODE)
|
||||
->delete();
|
||||
}
|
||||
});
|
||||
|
||||
Storage::disk('s3')->delete($storedPaths);
|
||||
}
|
||||
|
||||
private function clientId(): int
|
||||
{
|
||||
$clientId = DB::table('clients')->where('code', self::CLIENT_CODE)->value('id');
|
||||
|
||||
if ($clientId !== null) {
|
||||
DB::table('clients')->where('id', $clientId)->update([
|
||||
'name' => 'Mutual SMEP',
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return (int) $clientId;
|
||||
}
|
||||
|
||||
return (int) DB::table('clients')->insertGetId([
|
||||
'code' => self::CLIENT_CODE,
|
||||
'name' => 'Mutual SMEP',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function assignSocialMedia(DateTimeInterface $now): void
|
||||
{
|
||||
$socialMedia = [
|
||||
[
|
||||
'code' => 'instagram',
|
||||
'icon' => 'fa-brands fa-instagram',
|
||||
'name' => 'Instagram',
|
||||
'url' => 'https://www.instagram.com/mutualsmep/',
|
||||
'orden' => 0,
|
||||
],
|
||||
[
|
||||
'code' => 'facebook',
|
||||
'icon' => 'fa-brands fa-facebook',
|
||||
'name' => 'Facebook',
|
||||
'url' => 'https://www.facebook.com/smeprosario',
|
||||
'orden' => 1,
|
||||
],
|
||||
[
|
||||
'code' => 'whatsapp',
|
||||
'icon' => 'fa-brands fa-whatsapp',
|
||||
'name' => 'WhatsApp',
|
||||
'url' => 'https://wa.me/5493412474530',
|
||||
'orden' => 2,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($socialMedia as $network) {
|
||||
DB::table('social_media')->insertOrIgnore([
|
||||
'code' => $network['code'],
|
||||
'icon' => $network['icon'],
|
||||
'name' => $network['name'],
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
DB::table('tenant_social_media')->insertOrIgnore([
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'social_media_code' => $network['code'],
|
||||
'url' => $network['url'],
|
||||
'orden' => $network['orden'],
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function assignMenus(DateTimeInterface $now): void
|
||||
{
|
||||
DB::table('menues')
|
||||
->whereNotIn('code', self::EXCLUDED_MENU_CODES)
|
||||
->pluck('code')
|
||||
->each(function (string $menuCode) use ($now): void {
|
||||
$staticContent = match ($menuCode) {
|
||||
'help.faq' => $this->frequentlyAskedQuestions(),
|
||||
'help.contact' => $this->contactContent(),
|
||||
default => null,
|
||||
};
|
||||
|
||||
DB::table('tenants_menues')->updateOrInsert(
|
||||
[
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'menu_code' => $menuCode,
|
||||
],
|
||||
[
|
||||
'static_content' => $staticContent === null
|
||||
? null
|
||||
: json_encode($staticContent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/** @return list<array{pregunta: string, respuesta: string, is_active: bool}> */
|
||||
private function frequentlyAskedQuestions(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'pregunta' => '¿Hay algún límite de compra?',
|
||||
'respuesta' => 'La cantidad disponible depende del stock de cada producto.',
|
||||
'is_active' => true,
|
||||
],
|
||||
[
|
||||
'pregunta' => '¿Cuáles son los medios de pago disponibles?',
|
||||
'respuesta' => 'Podés consultar y seleccionar los medios de pago habilitados al finalizar tu compra.',
|
||||
'is_active' => false,
|
||||
],
|
||||
[
|
||||
'pregunta' => '¿Cómo puedo recibir asesoramiento antes de comprar?',
|
||||
'respuesta' => 'Podés comunicarte con Mutual SMEP por WhatsApp al +54 9 341 247-4530.',
|
||||
'is_active' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function contactContent(): array
|
||||
{
|
||||
return [
|
||||
'whatsapp' => [
|
||||
'whatsapp_url' => 'https://wa.me/5493412474530',
|
||||
'whatsapp_label' => 'Chateá con Mutual SMEP',
|
||||
],
|
||||
'phone' => '+54 9 341 247-4530',
|
||||
'locations' => [
|
||||
'rosario' => [
|
||||
'label' => 'Rosario',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'Mutual SMEP',
|
||||
'address' => 'San Lorenzo 1543, Rosario, Santa Fe',
|
||||
'coordinates' => [-32.9431184, -60.6437991],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function storeImage(string $relativePath, string $filename): int
|
||||
{
|
||||
$sourcePath = public_path($relativePath);
|
||||
|
||||
if (! is_file($sourcePath)) {
|
||||
throw new RuntimeException("Image not found at path: {$sourcePath}");
|
||||
}
|
||||
|
||||
$contents = file_get_contents($sourcePath);
|
||||
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException("Could not read image at path: {$sourcePath}");
|
||||
}
|
||||
|
||||
$key = (string) Str::uuid();
|
||||
$storedPath = 'tenants/'.self::TENANT_CODE."/{$key}.png";
|
||||
|
||||
if (! Storage::disk('s3')->put($storedPath, $contents)) {
|
||||
throw new RuntimeException("Could not store image at path: {$storedPath}");
|
||||
}
|
||||
|
||||
$this->storedPaths[] = $storedPath;
|
||||
|
||||
return DB::table('attachments')->insertGetId([
|
||||
'key' => $key,
|
||||
'path' => $storedPath,
|
||||
'filename' => $filename,
|
||||
'type' => 'image',
|
||||
'mime_type' => 'image/png',
|
||||
'extension' => 'png',
|
||||
'size' => strlen($contents),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
/** @var array<string, list<string>> */
|
||||
private const CATEGORIES = [
|
||||
'Dormitorio y Blanco' => [
|
||||
'Colchones',
|
||||
'Bases y Sommiers',
|
||||
'Acolchados y Edredones',
|
||||
'Frazadas y Mantas',
|
||||
'Infantil y Cuna',
|
||||
],
|
||||
'Electrodomésticos' => [
|
||||
'Heladeras',
|
||||
'Lavarropas y Secarropas',
|
||||
'Cocinas',
|
||||
'Microondas',
|
||||
'Hornos Eléctricos',
|
||||
'Calefones y Termotanques',
|
||||
'Purificadores y Extractores de Cocina',
|
||||
'Preparación de Alimentos',
|
||||
'Café y Desayuno',
|
||||
'Freidoras y Cocción',
|
||||
'Cuidado Personal',
|
||||
'Limpieza y Planchado',
|
||||
],
|
||||
'Climatización' => [
|
||||
'Aires Acondicionados',
|
||||
'Calefactores a Gas con Salida',
|
||||
'Calefactores a Gas sin Salida',
|
||||
'Calefacción Eléctrica',
|
||||
],
|
||||
'Tecnología' => [
|
||||
'Celulares',
|
||||
'Notebooks',
|
||||
'Smart TV',
|
||||
'Audio',
|
||||
'Accesorios de Computación',
|
||||
'Soportes para TV',
|
||||
],
|
||||
'Bicicletas y Aire Libre' => [
|
||||
'Bicicletas Infantiles',
|
||||
'Bicicletas para Adultos',
|
||||
'Piletas',
|
||||
],
|
||||
'Bazar' => [
|
||||
'Termos',
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (! DB::table('tenants')->where('codigo', self::TENANT_CODE)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function (): void {
|
||||
foreach (self::CATEGORIES as $parentName => $subcategoryNames) {
|
||||
$parentId = $this->upsertCategory($parentName, null);
|
||||
|
||||
foreach ($subcategoryNames as $subcategoryName) {
|
||||
$this->upsertCategory($subcategoryName, $parentId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::transaction(function (): void {
|
||||
foreach (self::CATEGORIES as $parentName => $subcategoryNames) {
|
||||
$parentId = DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('nombre', $parentName)
|
||||
->whereNull('categoria_id')
|
||||
->value('id');
|
||||
|
||||
if ($parentId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subcategoryIds = DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('categoria_id', $parentId)
|
||||
->whereIn('nombre', $subcategoryNames)
|
||||
->pluck('id');
|
||||
|
||||
foreach ($subcategoryIds as $subcategoryId) {
|
||||
$hasChildren = DB::table('categorias')
|
||||
->where('categoria_id', $subcategoryId)
|
||||
->exists();
|
||||
|
||||
if (! $hasChildren) {
|
||||
DB::table('categorias')->where('id', $subcategoryId)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$hasChildren = DB::table('categorias')
|
||||
->where('categoria_id', $parentId)
|
||||
->exists();
|
||||
|
||||
if (! $hasChildren) {
|
||||
DB::table('categorias')->where('id', $parentId)->delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function upsertCategory(string $name, ?int $parentId): int
|
||||
{
|
||||
$categoryId = DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('nombre', $name)
|
||||
->value('id');
|
||||
|
||||
if ($categoryId !== null) {
|
||||
DB::table('categorias')->where('id', $categoryId)->update([
|
||||
'categoria_id' => $parentId,
|
||||
'is_enabled' => true,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return (int) $categoryId;
|
||||
}
|
||||
|
||||
return (int) DB::table('categorias')->insertGetId([
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'categoria_id' => $parentId,
|
||||
'nombre' => $name,
|
||||
'is_enabled' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
/** @var list<array<string, mixed>> */
|
||||
private const ATTRIBUTES = [
|
||||
[
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
'metadata_schema' => [
|
||||
'hex' => ['type' => 'string'],
|
||||
],
|
||||
'options' => [
|
||||
['value' => 'Negro', 'label' => 'Negro', 'metadata' => ['hex' => '#000000']],
|
||||
['value' => 'Blanco', 'label' => 'Blanco', 'metadata' => ['hex' => '#FFFFFF']],
|
||||
['value' => 'Gris', 'label' => 'Gris', 'metadata' => ['hex' => '#808080']],
|
||||
['value' => 'Plata', 'label' => 'Plata', 'metadata' => ['hex' => '#C0C0C0']],
|
||||
['value' => 'Azul', 'label' => 'Azul', 'metadata' => ['hex' => '#0000FF']],
|
||||
['value' => 'Rojo', 'label' => 'Rojo', 'metadata' => ['hex' => '#DC3545']],
|
||||
['value' => 'Rosa', 'label' => 'Rosa', 'metadata' => ['hex' => '#FFC0CB']],
|
||||
['value' => 'Multicolor', 'label' => 'Multicolor'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'medida_cama',
|
||||
'nombre' => 'Medida',
|
||||
'options' => [
|
||||
['value' => 'Cuna', 'label' => 'Cuna'],
|
||||
['value' => '1 Plaza', 'label' => '1 Plaza'],
|
||||
['value' => '1 1/2 Plazas', 'label' => '1 1/2 Plazas'],
|
||||
['value' => '2 Plazas', 'label' => '2 Plazas'],
|
||||
['value' => '2 1/2 Plazas', 'label' => '2 1/2 Plazas'],
|
||||
['value' => 'Queen', 'label' => 'Queen'],
|
||||
['value' => 'King', 'label' => 'King'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'almacenamiento',
|
||||
'nombre' => 'Almacenamiento',
|
||||
'options' => [
|
||||
['value' => '128 GB', 'label' => '128 GB'],
|
||||
['value' => '256 GB', 'label' => '256 GB'],
|
||||
['value' => '512 GB', 'label' => '512 GB'],
|
||||
['value' => '1 TB', 'label' => '1 TB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'memoria_ram',
|
||||
'nombre' => 'Memoria RAM',
|
||||
'options' => [
|
||||
['value' => '4 GB', 'label' => '4 GB'],
|
||||
['value' => '8 GB', 'label' => '8 GB'],
|
||||
['value' => '16 GB', 'label' => '16 GB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'rodado',
|
||||
'nombre' => 'Rodado',
|
||||
'options' => [
|
||||
['value' => '16', 'label' => '16'],
|
||||
['value' => '26', 'label' => '26'],
|
||||
['value' => '29', 'label' => '29'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (! DB::table('tenants')->where('codigo', self::TENANT_CODE)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function (): void {
|
||||
foreach (self::ATTRIBUTES as $definition) {
|
||||
$attributeId = $this->upsertAttribute($definition);
|
||||
|
||||
DB::table('attribute_options')->where('attribute_id', $attributeId)->delete();
|
||||
|
||||
foreach ($definition['options'] as $index => $option) {
|
||||
DB::table('attribute_options')->insert([
|
||||
'attribute_id' => $attributeId,
|
||||
'value' => $option['value'],
|
||||
'label' => $option['label'],
|
||||
'sort_order' => $index + 1,
|
||||
'metadata' => isset($option['metadata'])
|
||||
? json_encode($option['metadata'], JSON_THROW_ON_ERROR)
|
||||
: null,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('attribute')
|
||||
->where('tenant_codigo', self::TENANT_CODE)
|
||||
->whereIn('codigo', array_column(self::ATTRIBUTES, 'codigo'))
|
||||
->delete();
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $definition */
|
||||
private function upsertAttribute(array $definition): int
|
||||
{
|
||||
$attributeId = DB::table('attribute')
|
||||
->where('tenant_codigo', self::TENANT_CODE)
|
||||
->where('codigo', $definition['codigo'])
|
||||
->value('id');
|
||||
$values = [
|
||||
'nombre' => $definition['nombre'],
|
||||
'is_required' => false,
|
||||
'metadata_schema' => isset($definition['metadata_schema'])
|
||||
? json_encode($definition['metadata_schema'], JSON_THROW_ON_ERROR)
|
||||
: null,
|
||||
'type' => 'select',
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if ($attributeId !== null) {
|
||||
DB::table('attribute')->where('id', $attributeId)->update($values);
|
||||
|
||||
return (int) $attributeId;
|
||||
}
|
||||
|
||||
return (int) DB::table('attribute')->insertGetId([
|
||||
'tenant_codigo' => self::TENANT_CODE,
|
||||
'codigo' => $definition['codigo'],
|
||||
...$values,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
private const MANIFEST = 'data/mutual_smep_catalog_v1.json';
|
||||
|
||||
private const STORAGE_PREFIX = 'tenants/mutual_smep/catalog-v2/';
|
||||
|
||||
private const LEGACY_STORAGE_PREFIX = 'tenants/mutual_smep/catalog-v1/';
|
||||
|
||||
private const BRAND_MARKER = 'Provisioned by Mutual SMEP catalog migration v1.';
|
||||
|
||||
/** @var list<array{code: string, category: string}> */
|
||||
private const FEATURED_GROUPS = [
|
||||
['code' => 'smep-dormitorio-y-blanco', 'category' => 'Dormitorio y Blanco'],
|
||||
['code' => 'smep-electrodomesticos', 'category' => 'Electrodomésticos'],
|
||||
['code' => 'smep-climatizacion', 'category' => 'Climatización'],
|
||||
['code' => 'smep-tecnologia', 'category' => 'Tecnología'],
|
||||
['code' => 'smep-bicicletas-y-aire-libre', 'category' => 'Bicicletas y Aire Libre'],
|
||||
['code' => 'smep-bazar', 'category' => 'Bazar'],
|
||||
];
|
||||
|
||||
private const HIGHLIGHTED_GROUP_CODE = 'productos-destacados-smep';
|
||||
|
||||
private const CATEGORY_FEATURED_ITEM_LIMIT = 6;
|
||||
|
||||
/** @var list<string> */
|
||||
private const HIGHLIGHTED_PRODUCT_SLUGS = [
|
||||
'acolchado-kavanagh-simil-plumon-reversible',
|
||||
'acolchado-sense-duo-bitono-corderito',
|
||||
'colchon-inducol-constanza',
|
||||
'edredon-lisboa-jean-cartier',
|
||||
'frazada-kavanagh-premium-soft',
|
||||
'kit-edredon-alaska-corderito',
|
||||
'motorola-g15',
|
||||
'smart-tv-noblex-50-dr50-x8500-google-tv',
|
||||
'heladera-gafa-hgf-368afp-330-lt-color-plata',
|
||||
'cafetera-express-atma-ceat-5418p-1-litro',
|
||||
'split-3300-w-tcl-taca-3300fcsa-frio-calor',
|
||||
'bicicleta-venzo-loki-r29-fd-21-v-shimano',
|
||||
'termo-stanley-1-l-adventure-go-to-con-tapon',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private array $storedPaths = [];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (! DB::table('tenants')->where('codigo', self::TENANT_CODE)->exists()) {
|
||||
throw new RuntimeException("Tenant '".self::TENANT_CODE."' not found.");
|
||||
}
|
||||
|
||||
$manifest = $this->manifest();
|
||||
$slugs = array_column($manifest['items'], 'slug');
|
||||
$existingSlug = DB::table('catalog_items')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->whereIn('slug', $slugs)
|
||||
->value('slug');
|
||||
|
||||
if ($existingSlug !== null) {
|
||||
throw new RuntimeException("Catalog item '{$existingSlug}' already exists for Mutual SMEP.");
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($manifest): void {
|
||||
$brandIds = [];
|
||||
$categoryIds = [];
|
||||
$attributeIds = [];
|
||||
|
||||
foreach ($manifest['items'] as $order => $item) {
|
||||
$brandId = $this->brandId($item['brand'], $brandIds);
|
||||
$categoryId = $this->categoryId(
|
||||
$item['parent_category'],
|
||||
$item['category'],
|
||||
$categoryIds,
|
||||
);
|
||||
$hasVariants = $item['variants'] !== [];
|
||||
$inventoryId = $hasVariants
|
||||
? null
|
||||
: $this->createInventory((int) $item['stock']);
|
||||
$catalogItemId = DB::table('catalog_items')->insertGetId([
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'category_id' => $categoryId,
|
||||
'brand_id' => $brandId,
|
||||
'inventory_id' => $inventoryId,
|
||||
'type' => 'standard',
|
||||
'slug' => $item['slug'],
|
||||
'nombre' => $item['name'],
|
||||
'group_order' => $order + 1,
|
||||
'descripcion' => $item['description'],
|
||||
'precio' => $item['price'],
|
||||
'inventory_policy' => 'tracked',
|
||||
'inventory_subject' => 'product',
|
||||
'has_tickets' => false,
|
||||
]);
|
||||
|
||||
$this->attachImages($catalogItemId, null, $item['images']);
|
||||
|
||||
if (! $hasVariants) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemAttributeIds = [];
|
||||
foreach ($item['attribute_codes'] as $attributeOrder => $attributeCode) {
|
||||
$attributeId = $this->attributeId($attributeCode, $attributeIds);
|
||||
$itemAttributeIds[$attributeCode] = DB::table('item_attributes')->insertGetId([
|
||||
'catalog_item_id' => $catalogItemId,
|
||||
'attribute_id' => $attributeId,
|
||||
'allow_multi_select' => false,
|
||||
'sort_order' => $attributeOrder + 1,
|
||||
'show_in_selector' => true,
|
||||
'ticket_label' => null,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($item['variants'] as $variant) {
|
||||
$variantId = DB::table('variantes')->insertGetId([
|
||||
'catalog_item_id' => $catalogItemId,
|
||||
'inventory_id' => $this->createInventory((int) $variant['stock']),
|
||||
'descripcion' => $variant['description'],
|
||||
'precio' => $variant['price'],
|
||||
]);
|
||||
|
||||
foreach ($variant['values'] as $attributeCode => $value) {
|
||||
$itemAttributeId = $itemAttributeIds[$attributeCode] ?? null;
|
||||
|
||||
if ($itemAttributeId === null) {
|
||||
throw new RuntimeException(
|
||||
"Attribute '{$attributeCode}' is not assigned to '{$item['slug']}'."
|
||||
);
|
||||
}
|
||||
|
||||
DB::table('variant_values')->insert([
|
||||
'variant_id' => $variantId,
|
||||
'item_attribute_id' => $itemAttributeId,
|
||||
'value' => $value,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->attachImages($catalogItemId, $variantId, $variant['images']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->createFeaturedGroups();
|
||||
});
|
||||
} catch (Throwable $throwable) {
|
||||
Storage::disk('s3')->delete($this->storedPaths);
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$manifest = $this->manifest();
|
||||
$catalogItemIds = DB::table('catalog_items')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->whereIn('slug', array_column($manifest['items'], 'slug'))
|
||||
->pluck('id');
|
||||
$variantInventoryIds = DB::table('variantes')
|
||||
->whereIn('catalog_item_id', $catalogItemIds)
|
||||
->pluck('inventory_id');
|
||||
$itemInventoryIds = DB::table('catalog_items')
|
||||
->whereIn('id', $catalogItemIds)
|
||||
->whereNotNull('inventory_id')
|
||||
->pluck('inventory_id');
|
||||
$attachmentIds = DB::table('catalog_items_attachments')
|
||||
->whereIn('catalog_item_id', $catalogItemIds)
|
||||
->pluck('attachment_id')
|
||||
->unique()
|
||||
->values();
|
||||
$storedPaths = DB::table('attachments')
|
||||
->whereIn('id', $attachmentIds)
|
||||
->where(fn ($query) => $query
|
||||
->where('path', 'like', self::STORAGE_PREFIX.'%')
|
||||
->orWhere('path', 'like', self::LEGACY_STORAGE_PREFIX.'%'))
|
||||
->pluck('path')
|
||||
->all();
|
||||
|
||||
DB::transaction(function () use (
|
||||
$catalogItemIds,
|
||||
$variantInventoryIds,
|
||||
$itemInventoryIds,
|
||||
$attachmentIds,
|
||||
): void {
|
||||
DB::table('featured_groups')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->whereIn('code', [
|
||||
self::HIGHLIGHTED_GROUP_CODE,
|
||||
...array_column(self::FEATURED_GROUPS, 'code'),
|
||||
])
|
||||
->delete();
|
||||
DB::table('catalog_items')->whereIn('id', $catalogItemIds)->delete();
|
||||
DB::table('inventories')
|
||||
->whereIn('id', $variantInventoryIds->merge($itemInventoryIds)->unique())
|
||||
->delete();
|
||||
DB::table('attachments')->whereIn('id', $attachmentIds)->delete();
|
||||
DB::table('brands')
|
||||
->where('tenant_codigo', self::TENANT_CODE)
|
||||
->where('descripcion', self::BRAND_MARKER)
|
||||
->whereNotExists(fn ($query) => $query
|
||||
->selectRaw('1')
|
||||
->from('catalog_items')
|
||||
->whereColumn('catalog_items.brand_id', 'brands.id'))
|
||||
->delete();
|
||||
});
|
||||
|
||||
Storage::disk('s3')->delete($storedPaths);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function manifest(): array
|
||||
{
|
||||
$path = database_path(self::MANIFEST);
|
||||
|
||||
if (! is_file($path)) {
|
||||
throw new RuntimeException("Mutual SMEP catalog manifest not found: {$path}");
|
||||
}
|
||||
|
||||
$manifest = json_decode(
|
||||
(string) file_get_contents($path),
|
||||
true,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
if (
|
||||
($manifest['version'] ?? null) !== 1
|
||||
|| ($manifest['tenant_code'] ?? null) !== self::TENANT_CODE
|
||||
|| count($manifest['items'] ?? []) !== 142
|
||||
|| ($manifest['image_count'] ?? null) !== 511
|
||||
) {
|
||||
throw new RuntimeException('Mutual SMEP catalog manifest is invalid.');
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/** @param array<string, int> $cache */
|
||||
private function brandId(?string $name, array &$cache): ?int
|
||||
{
|
||||
if ($name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($cache[$name])) {
|
||||
return $cache[$name];
|
||||
}
|
||||
|
||||
$brandId = DB::table('brands')
|
||||
->where('tenant_codigo', self::TENANT_CODE)
|
||||
->where('nombre', $name)
|
||||
->value('id');
|
||||
|
||||
if ($brandId === null) {
|
||||
$brandId = DB::table('brands')->insertGetId([
|
||||
'tenant_codigo' => self::TENANT_CODE,
|
||||
'nombre' => $name,
|
||||
'descripcion' => self::BRAND_MARKER,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $cache[$name] = (int) $brandId;
|
||||
}
|
||||
|
||||
/** @param array<string, int> $cache */
|
||||
private function categoryId(string $parentName, string $name, array &$cache): int
|
||||
{
|
||||
$key = "{$parentName}|{$name}";
|
||||
|
||||
if (isset($cache[$key])) {
|
||||
return $cache[$key];
|
||||
}
|
||||
|
||||
$parentId = DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('nombre', $parentName)
|
||||
->whereNull('categoria_id')
|
||||
->value('id');
|
||||
$categoryId = $parentId === null
|
||||
? null
|
||||
: DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('categoria_id', $parentId)
|
||||
->where('nombre', $name)
|
||||
->value('id');
|
||||
|
||||
if ($categoryId === null) {
|
||||
throw new RuntimeException("Category '{$parentName} > {$name}' not found for Mutual SMEP.");
|
||||
}
|
||||
|
||||
return $cache[$key] = (int) $categoryId;
|
||||
}
|
||||
|
||||
/** @param array<string, int> $cache */
|
||||
private function attributeId(string $code, array &$cache): int
|
||||
{
|
||||
if (isset($cache[$code])) {
|
||||
return $cache[$code];
|
||||
}
|
||||
|
||||
$attributeId = DB::table('attribute')
|
||||
->where('tenant_codigo', self::TENANT_CODE)
|
||||
->where('codigo', $code)
|
||||
->value('id');
|
||||
|
||||
if ($attributeId === null) {
|
||||
throw new RuntimeException("Attribute '{$code}' not found for Mutual SMEP.");
|
||||
}
|
||||
|
||||
return $cache[$code] = (int) $attributeId;
|
||||
}
|
||||
|
||||
private function createInventory(int $stock): int
|
||||
{
|
||||
return (int) DB::table('inventories')->insertGetId([
|
||||
'sold_units' => 0,
|
||||
'refunded_units' => 0,
|
||||
'reserved_stock' => 0,
|
||||
'real_stock' => $stock,
|
||||
]);
|
||||
}
|
||||
|
||||
private function createFeaturedGroups(): void
|
||||
{
|
||||
$this->createHighlightedGroup();
|
||||
|
||||
foreach (self::FEATURED_GROUPS as $order => $group) {
|
||||
$categoryId = DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->whereNull('categoria_id')
|
||||
->where('nombre', $group['category'])
|
||||
->value('id');
|
||||
|
||||
if ($categoryId === null) {
|
||||
throw new RuntimeException("Parent category '{$group['category']}' not found for Mutual SMEP.");
|
||||
}
|
||||
|
||||
$featuredGroupId = DB::table('featured_groups')->insertGetId([
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'code' => $group['code'],
|
||||
'source_type' => 'manual',
|
||||
'category_id' => $categoryId,
|
||||
'product_layout' => 'column_with_image',
|
||||
'group_layout' => 'carousel',
|
||||
'group_name' => $group['category'],
|
||||
'group_order' => $order + 2,
|
||||
]);
|
||||
|
||||
$catalogItemIds = DB::table('catalog_items')
|
||||
->join('categorias', 'categorias.id', '=', 'catalog_items.category_id')
|
||||
->where('catalog_items.tenant_code', self::TENANT_CODE)
|
||||
->where('categorias.tenant_code', self::TENANT_CODE)
|
||||
->where('categorias.categoria_id', $categoryId)
|
||||
->orderBy('catalog_items.group_order')
|
||||
->orderBy('catalog_items.id')
|
||||
->limit(self::CATEGORY_FEATURED_ITEM_LIMIT)
|
||||
->pluck('catalog_items.id');
|
||||
|
||||
foreach ($catalogItemIds as $itemOrder => $catalogItemId) {
|
||||
DB::table('featured_items')->insert([
|
||||
'featured_group_id' => $featuredGroupId,
|
||||
'catalog_item_id' => $catalogItemId,
|
||||
'order' => $itemOrder + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createHighlightedGroup(): void
|
||||
{
|
||||
$featuredGroupId = DB::table('featured_groups')->insertGetId([
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'code' => self::HIGHLIGHTED_GROUP_CODE,
|
||||
'source_type' => 'manual',
|
||||
'category_id' => null,
|
||||
'product_layout' => 'column_with_image',
|
||||
'group_layout' => 'carousel',
|
||||
'group_name' => 'Productos destacados',
|
||||
'group_order' => 1,
|
||||
]);
|
||||
|
||||
$catalogItemIds = DB::table('catalog_items')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->whereIn('slug', self::HIGHLIGHTED_PRODUCT_SLUGS)
|
||||
->pluck('id', 'slug');
|
||||
|
||||
foreach (self::HIGHLIGHTED_PRODUCT_SLUGS as $order => $slug) {
|
||||
$catalogItemId = $catalogItemIds->get($slug);
|
||||
|
||||
if ($catalogItemId === null) {
|
||||
throw new RuntimeException("Highlighted product '{$slug}' not found for Mutual SMEP.");
|
||||
}
|
||||
|
||||
DB::table('featured_items')->insert([
|
||||
'featured_group_id' => $featuredGroupId,
|
||||
'catalog_item_id' => $catalogItemId,
|
||||
'order' => $order + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<string> $images */
|
||||
private function attachImages(int $catalogItemId, ?int $variantId, array $images): void
|
||||
{
|
||||
foreach ($images as $order => $relativePath) {
|
||||
$sourcePath = public_path($relativePath);
|
||||
|
||||
if (! is_file($sourcePath)) {
|
||||
throw new RuntimeException("Catalog image not found: {$sourcePath}");
|
||||
}
|
||||
|
||||
$contents = file_get_contents($sourcePath);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException("Catalog image could not be read: {$sourcePath}");
|
||||
}
|
||||
|
||||
$catalogRelativePath = Str::after(
|
||||
str_replace('\\', '/', $relativePath),
|
||||
'images/tennants/mutual_smep/catalog/',
|
||||
);
|
||||
$storedPath = self::STORAGE_PREFIX.$catalogRelativePath;
|
||||
|
||||
if (! Storage::disk('s3')->put($storedPath, $contents)) {
|
||||
throw new RuntimeException("Catalog image could not be stored: {$storedPath}");
|
||||
}
|
||||
|
||||
$this->storedPaths[] = $storedPath;
|
||||
$attachmentId = DB::table('attachments')->insertGetId([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => $storedPath,
|
||||
'filename' => basename($relativePath),
|
||||
'type' => 'image',
|
||||
'mime_type' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
'size' => strlen($contents),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('catalog_items_attachments')->insert([
|
||||
'catalog_item_id' => $catalogItemId,
|
||||
'variant_id' => $variantId,
|
||||
'attachment_id' => $attachmentId,
|
||||
'orden' => $order,
|
||||
'is_enabled' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
private const EXTRA_CODE = 'carousel';
|
||||
|
||||
private const IMAGE_DIRECTORY = 'images/tennants/mutual_smep/carousel';
|
||||
|
||||
private const STORAGE_PREFIX = 'tenants/mutual_smep/extras/carousel-v1/';
|
||||
|
||||
/** @var list<string> */
|
||||
private const IMAGES = [
|
||||
'01-tecnologia.webp',
|
||||
'02-hogar.webp',
|
||||
'03-vida-activa.webp',
|
||||
'04-descanso.webp',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private array $storedPaths = [];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$tenant = DB::table('tenants')
|
||||
->where('codigo', self::TENANT_CODE)
|
||||
->first(['codigo', 'storefront_website_type_code']);
|
||||
|
||||
if ($tenant === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$extraId = DB::table('storefront_website_type_extras')
|
||||
->where('storefront_website_type_code', $tenant->storefront_website_type_code)
|
||||
->where('codigo', self::EXTRA_CODE)
|
||||
->value('id');
|
||||
|
||||
if ($extraId === null || DB::table('websites_extras')->where([
|
||||
'website_code' => self::TENANT_CODE,
|
||||
'website_type_extra_id' => $extraId,
|
||||
])->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($extraId): void {
|
||||
$attachmentIds = [];
|
||||
|
||||
foreach (self::IMAGES as $filename) {
|
||||
$attachmentIds[] = $this->storeImage($filename);
|
||||
}
|
||||
|
||||
DB::table('websites_extras')->insert([
|
||||
'website_code' => self::TENANT_CODE,
|
||||
'website_type_extra_id' => $extraId,
|
||||
'config' => json_encode($attachmentIds, JSON_THROW_ON_ERROR),
|
||||
'is_enabled' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
});
|
||||
} catch (Throwable $throwable) {
|
||||
Storage::disk('s3')->delete($this->storedPaths);
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$attachments = DB::table('attachments')
|
||||
->where('path', 'like', self::STORAGE_PREFIX.'%')
|
||||
->get(['id', 'path']);
|
||||
|
||||
if ($attachments->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attachmentIds = $attachments->pluck('id')->map(fn ($id): int => (int) $id)->all();
|
||||
$extraId = DB::table('storefront_website_type_extras')
|
||||
->where('storefront_website_type_code', 'shopit')
|
||||
->where('codigo', self::EXTRA_CODE)
|
||||
->value('id');
|
||||
|
||||
DB::transaction(function () use ($attachmentIds, $extraId): void {
|
||||
if ($extraId !== null) {
|
||||
DB::table('websites_extras')
|
||||
->where('website_code', self::TENANT_CODE)
|
||||
->where('website_type_extra_id', $extraId)
|
||||
->where('config', json_encode($attachmentIds, JSON_THROW_ON_ERROR))
|
||||
->delete();
|
||||
}
|
||||
|
||||
DB::table('attachments')->whereIn('id', $attachmentIds)->delete();
|
||||
});
|
||||
|
||||
Storage::disk('s3')->delete($attachments->pluck('path')->all());
|
||||
}
|
||||
|
||||
private function storeImage(string $filename): int
|
||||
{
|
||||
$sourcePath = public_path(self::IMAGE_DIRECTORY."/{$filename}");
|
||||
|
||||
if (! is_file($sourcePath)) {
|
||||
throw new RuntimeException("Carousel image not found: {$sourcePath}");
|
||||
}
|
||||
|
||||
$contents = file_get_contents($sourcePath);
|
||||
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException("Carousel image could not be read: {$sourcePath}");
|
||||
}
|
||||
|
||||
$storedPath = self::STORAGE_PREFIX.$filename;
|
||||
|
||||
if (! Storage::disk('s3')->put($storedPath, $contents)) {
|
||||
throw new RuntimeException("Carousel image could not be stored: {$storedPath}");
|
||||
}
|
||||
|
||||
$this->storedPaths[] = $storedPath;
|
||||
|
||||
return (int) DB::table('attachments')->insertGetId([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => $storedPath,
|
||||
'filename' => $filename,
|
||||
'type' => 'image',
|
||||
'mime_type' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
'size' => strlen($contents),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
private const ITEM_LIMIT = 6;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private const GROUPS = [
|
||||
'smep-dormitorio-y-blanco' => 'Dormitorio y Blanco',
|
||||
'smep-electrodomesticos' => 'Electrodomésticos',
|
||||
'smep-climatizacion' => 'Climatización',
|
||||
'smep-tecnologia' => 'Tecnología',
|
||||
'smep-bicicletas-y-aire-libre' => 'Bicicletas y Aire Libre',
|
||||
'smep-bazar' => 'Bazar',
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
foreach (array_keys(self::GROUPS) as $code) {
|
||||
$groupId = DB::table('featured_groups')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('code', $code)
|
||||
->value('id');
|
||||
|
||||
if ($groupId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keptItemIds = DB::table('featured_items')
|
||||
->where('featured_group_id', $groupId)
|
||||
->orderBy('order')
|
||||
->orderBy('id')
|
||||
->limit(self::ITEM_LIMIT)
|
||||
->pluck('id');
|
||||
|
||||
DB::table('featured_items')
|
||||
->where('featured_group_id', $groupId)
|
||||
->when(
|
||||
$keptItemIds->isNotEmpty(),
|
||||
fn ($query) => $query->whereNotIn('id', $keptItemIds),
|
||||
)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach (self::GROUPS as $code => $parentCategoryName) {
|
||||
$groupId = DB::table('featured_groups')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('code', $code)
|
||||
->value('id');
|
||||
$parentCategoryId = DB::table('categorias')
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->whereNull('categoria_id')
|
||||
->where('nombre', $parentCategoryName)
|
||||
->value('id');
|
||||
|
||||
if ($groupId === null || $parentCategoryId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$catalogItemIds = DB::table('catalog_items')
|
||||
->join('categorias', 'categorias.id', '=', 'catalog_items.category_id')
|
||||
->where('catalog_items.tenant_code', self::TENANT_CODE)
|
||||
->where('categorias.tenant_code', self::TENANT_CODE)
|
||||
->where('categorias.categoria_id', $parentCategoryId)
|
||||
->orderBy('catalog_items.group_order')
|
||||
->orderBy('catalog_items.id')
|
||||
->pluck('catalog_items.id');
|
||||
|
||||
DB::table('featured_items')->where('featured_group_id', $groupId)->delete();
|
||||
|
||||
foreach ($catalogItemIds as $order => $catalogItemId) {
|
||||
DB::table('featured_items')->insert([
|
||||
'featured_group_id' => $groupId,
|
||||
'catalog_item_id' => $catalogItemId,
|
||||
'order' => $order + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
private const LEGACY_PREFIX = 'tenants/mutual_smep/catalog-v1/';
|
||||
|
||||
private const OPTIMIZED_PREFIX = 'tenants/mutual_smep/catalog-v2/';
|
||||
|
||||
private const LOCAL_PREFIX = 'images/tennants/mutual_smep/catalog/';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$attachments = $this->attachments(self::LEGACY_PREFIX);
|
||||
$uploadedPaths = [];
|
||||
|
||||
try {
|
||||
foreach ($attachments as $attachment) {
|
||||
$relativePath = Str::after($attachment->path, self::LEGACY_PREFIX);
|
||||
$localPath = public_path(self::LOCAL_PREFIX.$relativePath);
|
||||
|
||||
if (! is_file($localPath)) {
|
||||
throw new RuntimeException("Optimized catalog image not found: {$localPath}");
|
||||
}
|
||||
|
||||
$contents = file_get_contents($localPath);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException("Optimized catalog image could not be read: {$localPath}");
|
||||
}
|
||||
|
||||
$optimizedPath = self::OPTIMIZED_PREFIX.$relativePath;
|
||||
if (! Storage::disk('s3')->put($optimizedPath, $contents)) {
|
||||
throw new RuntimeException("Optimized catalog image could not be stored: {$optimizedPath}");
|
||||
}
|
||||
|
||||
$uploadedPaths[] = $optimizedPath;
|
||||
$attachment->optimized_path = $optimizedPath;
|
||||
$attachment->optimized_size = strlen($contents);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($attachments): void {
|
||||
foreach ($attachments as $attachment) {
|
||||
DB::table('attachments')->where('id', $attachment->id)->update([
|
||||
'path' => $attachment->optimized_path,
|
||||
'size' => $attachment->optimized_size,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
} catch (Throwable $throwable) {
|
||||
Storage::disk('s3')->delete($uploadedPaths);
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$attachments = $this->attachments(self::OPTIMIZED_PREFIX)
|
||||
->filter(function (object $attachment): bool {
|
||||
$relativePath = Str::after($attachment->path, self::OPTIMIZED_PREFIX);
|
||||
$legacyPath = self::LEGACY_PREFIX.$relativePath;
|
||||
|
||||
if (! Storage::disk('s3')->exists($legacyPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$attachment->legacy_path = $legacyPath;
|
||||
$attachment->legacy_size = Storage::disk('s3')->size($legacyPath);
|
||||
|
||||
return true;
|
||||
})
|
||||
->values();
|
||||
|
||||
DB::transaction(function () use ($attachments): void {
|
||||
foreach ($attachments as $attachment) {
|
||||
DB::table('attachments')->where('id', $attachment->id)->update([
|
||||
'path' => $attachment->legacy_path,
|
||||
'size' => $attachment->legacy_size,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
Storage::disk('s3')->delete($attachments->pluck('path')->all());
|
||||
}
|
||||
|
||||
private function attachments(string $prefix): Collection
|
||||
{
|
||||
return DB::table('attachments')
|
||||
->join(
|
||||
'catalog_items_attachments',
|
||||
'catalog_items_attachments.attachment_id',
|
||||
'=',
|
||||
'attachments.id',
|
||||
)
|
||||
->join('catalog_items', 'catalog_items.id', '=', 'catalog_items_attachments.catalog_item_id')
|
||||
->where('catalog_items.tenant_code', self::TENANT_CODE)
|
||||
->where('attachments.path', 'like', $prefix.'%')
|
||||
->select('attachments.id', 'attachments.path', 'attachments.size')
|
||||
->distinct()
|
||||
->orderBy('attachments.id')
|
||||
->get();
|
||||
}
|
||||
};
|
||||
311
database/scripts/build_mutual_smep_catalog_assets.php
Normal file
311
database/scripts/build_mutual_smep_catalog_assets.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
const CATALOG_IMAGE_MAX_DIMENSION = 960;
|
||||
const CATALOG_IMAGE_WEBP_QUALITY = 76;
|
||||
|
||||
$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, CATALOG_IMAGE_MAX_DIMENSION / 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, CATALOG_IMAGE_WEBP_QUALITY)) {
|
||||
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;
|
||||
@@ -3,10 +3,10 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Models\Attribute;
|
||||
use App\Shared\Enums\FieldType;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticketing\Ticket\Enums\ValidityTimeType;
|
||||
use App\Domains\Ticketing\Ticket\Models\ValidityTime;
|
||||
use App\Shared\Enums\FieldType;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class AttributeSeeder extends Seeder
|
||||
@@ -25,6 +25,10 @@ class AttributeSeeder extends Seeder
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tenant->codigo === 'mutual_smep') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
|
||||
@@ -29,7 +29,9 @@ class DatabaseSeeder extends Seeder
|
||||
TenantSeeder::class,
|
||||
DesfilePuraTendenciaSeeder::class,
|
||||
AttributeSeeder::class,
|
||||
MutualSmepAttributeSeeder::class,
|
||||
CategorySeeder::class,
|
||||
MutualSmepCategorySeeder::class,
|
||||
BrandSeeder::class,
|
||||
ProductCatalogFromImagesSeeder::class,
|
||||
FiestaFutbolInfantilProductSeeder::class,
|
||||
|
||||
@@ -296,6 +296,7 @@ class MenuSeeder extends Seeder
|
||||
$helpTenantCodes = [
|
||||
'sonder',
|
||||
'fiesta_futbol_infantil',
|
||||
'mutual_smep',
|
||||
];
|
||||
$ticketAdminMenuCodes = [
|
||||
'adminapp.tickets',
|
||||
@@ -365,6 +366,42 @@ class MenuSeeder extends Seeder
|
||||
],
|
||||
],
|
||||
];
|
||||
$mutualSmepFrequentlyAskedQuestions = [
|
||||
[
|
||||
'pregunta' => '¿Hay algún límite de compra?',
|
||||
'respuesta' => 'La cantidad disponible depende del stock de cada producto.',
|
||||
'is_active' => true,
|
||||
],
|
||||
[
|
||||
'pregunta' => '¿Cuáles son los medios de pago disponibles?',
|
||||
'respuesta' => 'Podés consultar y seleccionar los medios de pago habilitados al finalizar tu compra.',
|
||||
'is_active' => false,
|
||||
],
|
||||
[
|
||||
'pregunta' => '¿Cómo puedo recibir asesoramiento antes de comprar?',
|
||||
'respuesta' => 'Podés comunicarte con Mutual SMEP por WhatsApp al +54 9 341 247-4530.',
|
||||
'is_active' => false,
|
||||
],
|
||||
];
|
||||
$mutualSmepContactContent = [
|
||||
'whatsapp' => [
|
||||
'whatsapp_url' => 'https://wa.me/5493412474530',
|
||||
'whatsapp_label' => 'Chateá con Mutual SMEP',
|
||||
],
|
||||
'phone' => '+54 9 341 247-4530',
|
||||
'locations' => [
|
||||
'rosario' => [
|
||||
'label' => 'Rosario',
|
||||
'addresses' => [
|
||||
[
|
||||
'label' => 'Mutual SMEP',
|
||||
'address' => 'San Lorenzo 1543, Rosario, Santa Fe',
|
||||
'coordinates' => [-32.9431184, -60.6437991],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
$menuCodes = $allMenus;
|
||||
@@ -406,10 +443,14 @@ class MenuSeeder extends Seeder
|
||||
|
||||
if (in_array($tenant->codigo, $helpTenantCodes, true)) {
|
||||
$tenant->menues()->updateExistingPivot('help.faq', [
|
||||
'static_content' => $frequentlyAskedQuestions,
|
||||
'static_content' => $tenant->codigo === 'mutual_smep'
|
||||
? $mutualSmepFrequentlyAskedQuestions
|
||||
: $frequentlyAskedQuestions,
|
||||
]);
|
||||
$tenant->menues()->updateExistingPivot('help.contact', [
|
||||
'static_content' => $contactContent,
|
||||
'static_content' => $tenant->codigo === 'mutual_smep'
|
||||
? $mutualSmepContactContent
|
||||
: $contactContent,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
122
database/seeders/MutualSmepAttributeSeeder.php
Normal file
122
database/seeders/MutualSmepAttributeSeeder.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Models\Attribute;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Shared\Enums\FieldType;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MutualSmepAttributeSeeder extends Seeder
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
/** @var list<array<string, mixed>> */
|
||||
private const ATTRIBUTES = [
|
||||
[
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
'metadata_schema' => [
|
||||
'hex' => ['type' => 'string'],
|
||||
],
|
||||
'options' => [
|
||||
['value' => 'Negro', 'label' => 'Negro', 'metadata' => ['hex' => '#000000']],
|
||||
['value' => 'Blanco', 'label' => 'Blanco', 'metadata' => ['hex' => '#FFFFFF']],
|
||||
['value' => 'Gris', 'label' => 'Gris', 'metadata' => ['hex' => '#808080']],
|
||||
['value' => 'Plata', 'label' => 'Plata', 'metadata' => ['hex' => '#C0C0C0']],
|
||||
['value' => 'Azul', 'label' => 'Azul', 'metadata' => ['hex' => '#0000FF']],
|
||||
['value' => 'Rojo', 'label' => 'Rojo', 'metadata' => ['hex' => '#DC3545']],
|
||||
['value' => 'Rosa', 'label' => 'Rosa', 'metadata' => ['hex' => '#FFC0CB']],
|
||||
['value' => 'Multicolor', 'label' => 'Multicolor'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'medida_cama',
|
||||
'nombre' => 'Medida',
|
||||
'options' => [
|
||||
['value' => 'Cuna', 'label' => 'Cuna'],
|
||||
['value' => '1 Plaza', 'label' => '1 Plaza'],
|
||||
['value' => '1 1/2 Plazas', 'label' => '1 1/2 Plazas'],
|
||||
['value' => '2 Plazas', 'label' => '2 Plazas'],
|
||||
['value' => '2 1/2 Plazas', 'label' => '2 1/2 Plazas'],
|
||||
['value' => 'Queen', 'label' => 'Queen'],
|
||||
['value' => 'King', 'label' => 'King'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'almacenamiento',
|
||||
'nombre' => 'Almacenamiento',
|
||||
'options' => [
|
||||
['value' => '128 GB', 'label' => '128 GB'],
|
||||
['value' => '256 GB', 'label' => '256 GB'],
|
||||
['value' => '512 GB', 'label' => '512 GB'],
|
||||
['value' => '1 TB', 'label' => '1 TB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'memoria_ram',
|
||||
'nombre' => 'Memoria RAM',
|
||||
'options' => [
|
||||
['value' => '4 GB', 'label' => '4 GB'],
|
||||
['value' => '8 GB', 'label' => '8 GB'],
|
||||
['value' => '16 GB', 'label' => '16 GB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'codigo' => 'rodado',
|
||||
'nombre' => 'Rodado',
|
||||
'options' => [
|
||||
['value' => '16', 'label' => '16'],
|
||||
['value' => '26', 'label' => '26'],
|
||||
['value' => '29', 'label' => '29'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
if (! Tenant::query()->where('codigo', self::TENANT_CODE)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function (): void {
|
||||
foreach (self::ATTRIBUTES as $definition) {
|
||||
$options = $definition['options'];
|
||||
unset($definition['options']);
|
||||
|
||||
$attribute = Attribute::query()->updateOrCreate(
|
||||
[
|
||||
'tenant_codigo' => self::TENANT_CODE,
|
||||
'codigo' => $definition['codigo'],
|
||||
],
|
||||
[
|
||||
...$definition,
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => false,
|
||||
'metadata_schema' => $definition['metadata_schema'] ?? null,
|
||||
],
|
||||
);
|
||||
|
||||
$attribute->options()->delete();
|
||||
$attribute->options()->createMany(
|
||||
collect($options)
|
||||
->values()
|
||||
->map(fn (array $option, int $index): array => [
|
||||
...$option,
|
||||
'sort_order' => $index + 1,
|
||||
])
|
||||
->all()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Attribute::query()
|
||||
->where('tenant_codigo', self::TENANT_CODE)
|
||||
->whereIn('codigo', array_column(self::ATTRIBUTES, 'codigo'))
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
123
database/seeders/MutualSmepCategorySeeder.php
Normal file
123
database/seeders/MutualSmepCategorySeeder.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Models\Category;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MutualSmepCategorySeeder extends Seeder
|
||||
{
|
||||
private const TENANT_CODE = 'mutual_smep';
|
||||
|
||||
/** @var array<string, list<string>> */
|
||||
private const CATEGORIES = [
|
||||
'Dormitorio y Blanco' => [
|
||||
'Colchones',
|
||||
'Bases y Sommiers',
|
||||
'Acolchados y Edredones',
|
||||
'Frazadas y Mantas',
|
||||
'Infantil y Cuna',
|
||||
],
|
||||
'Electrodomésticos' => [
|
||||
'Heladeras',
|
||||
'Lavarropas y Secarropas',
|
||||
'Cocinas',
|
||||
'Microondas',
|
||||
'Hornos Eléctricos',
|
||||
'Calefones y Termotanques',
|
||||
'Purificadores y Extractores de Cocina',
|
||||
'Preparación de Alimentos',
|
||||
'Café y Desayuno',
|
||||
'Freidoras y Cocción',
|
||||
'Cuidado Personal',
|
||||
'Limpieza y Planchado',
|
||||
],
|
||||
'Climatización' => [
|
||||
'Aires Acondicionados',
|
||||
'Calefactores a Gas con Salida',
|
||||
'Calefactores a Gas sin Salida',
|
||||
'Calefacción Eléctrica',
|
||||
],
|
||||
'Tecnología' => [
|
||||
'Celulares',
|
||||
'Notebooks',
|
||||
'Smart TV',
|
||||
'Audio',
|
||||
'Accesorios de Computación',
|
||||
'Soportes para TV',
|
||||
],
|
||||
'Bicicletas y Aire Libre' => [
|
||||
'Bicicletas Infantiles',
|
||||
'Bicicletas para Adultos',
|
||||
'Piletas',
|
||||
],
|
||||
'Bazar' => [
|
||||
'Termos',
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
if (! Tenant::query()->where('codigo', self::TENANT_CODE)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::transaction(function (): void {
|
||||
foreach (self::CATEGORIES as $parentName => $subcategoryNames) {
|
||||
$parent = Category::query()->updateOrCreate(
|
||||
[
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'nombre' => $parentName,
|
||||
],
|
||||
[
|
||||
'categoria_id' => null,
|
||||
'is_enabled' => true,
|
||||
],
|
||||
);
|
||||
|
||||
foreach ($subcategoryNames as $subcategoryName) {
|
||||
Category::query()->updateOrCreate(
|
||||
[
|
||||
'tenant_code' => self::TENANT_CODE,
|
||||
'nombre' => $subcategoryName,
|
||||
],
|
||||
[
|
||||
'categoria_id' => $parent->id,
|
||||
'is_enabled' => true,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::transaction(function (): void {
|
||||
foreach (self::CATEGORIES as $parentName => $subcategoryNames) {
|
||||
$parent = Category::query()
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('nombre', $parentName)
|
||||
->whereNull('categoria_id')
|
||||
->first();
|
||||
|
||||
if (! $parent instanceof Category) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Category::query()
|
||||
->where('tenant_code', self::TENANT_CODE)
|
||||
->where('categoria_id', $parent->id)
|
||||
->whereIn('nombre', $subcategoryNames)
|
||||
->whereDoesntHave('subCategories')
|
||||
->delete();
|
||||
|
||||
if (! $parent->subCategories()->exists()) {
|
||||
$parent->delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user