From 979ed428a236452613720b98e74a5e6e70c2d344 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 24 Jul 2026 10:08:04 -0300 Subject: [PATCH] feat(menu): add label field to menu model and update related functionality --- .../Menu/Controllers/MenuController.php | 2 + app/Domains/Menu/Models/Menu.php | 1 + .../Tenant/Resources/TenantResource.php | 1 + ...4_000000_create_account_menu_hierarchy.php | 128 +++++++++++++++--- ...07_24_000100_add_label_to_menues_table.php | 57 ++++++++ database/seeders/MenuSeeder.php | 32 ++++- .../Feature/Menu/TenantMenuControllerTest.php | 3 + tests/Feature/Seeders/MenuSeederTest.php | 17 ++- .../Tenant/BootstrapTenantControllerTest.php | 49 ++++--- 9 files changed, 235 insertions(+), 55 deletions(-) create mode 100644 database/migrations/2026_07_24_000100_add_label_to_menues_table.php diff --git a/app/Domains/Menu/Controllers/MenuController.php b/app/Domains/Menu/Controllers/MenuController.php index 1947792..2c9135e 100644 --- a/app/Domains/Menu/Controllers/MenuController.php +++ b/app/Domains/Menu/Controllers/MenuController.php @@ -21,6 +21,7 @@ class MenuController extends Controller { $validated = $request->validate([ 'code' => 'required|string|unique:menues,code', + 'label' => 'required|string|max:255', 'parent_menu_code' => [ 'nullable', 'string', @@ -49,6 +50,7 @@ class MenuController extends Controller { $validated = $request->validate([ 'code' => 'sometimes|required|string|unique:menues,code,'.$menu->id, + 'label' => 'sometimes|required|string|max:255', 'parent_menu_code' => [ 'nullable', 'string', diff --git a/app/Domains/Menu/Models/Menu.php b/app/Domains/Menu/Models/Menu.php index 8728779..de3eaee 100644 --- a/app/Domains/Menu/Models/Menu.php +++ b/app/Domains/Menu/Models/Menu.php @@ -26,6 +26,7 @@ class Menu extends Model protected $fillable = [ 'code', + 'label', 'parent_menu_code', 'content_type', 'static_content_schema', diff --git a/app/Domains/Tenant/Resources/TenantResource.php b/app/Domains/Tenant/Resources/TenantResource.php index 241b658..c972511 100644 --- a/app/Domains/Tenant/Resources/TenantResource.php +++ b/app/Domains/Tenant/Resources/TenantResource.php @@ -79,6 +79,7 @@ class TenantResource extends JsonResource $formatted = [ 'id' => $menu->id, 'code' => $menu->code, + 'label' => $menu->label, 'parent_menu_code' => $menu->parent_menu_code, 'content_type' => $menu->content_type, 'route' => $menu->route, diff --git a/database/migrations/2026_07_24_000000_create_account_menu_hierarchy.php b/database/migrations/2026_07_24_000000_create_account_menu_hierarchy.php index c14be42..565ec28 100644 --- a/database/migrations/2026_07_24_000000_create_account_menu_hierarchy.php +++ b/database/migrations/2026_07_24_000000_create_account_menu_hierarchy.php @@ -22,24 +22,67 @@ return new class extends Migration ] ); - $routes = [ - 'profile' => '/mi-cuenta/datos-personales', - 'purchases' => '/mi-cuenta/compras', - 'tickets' => '/mi-cuenta/tickets', + $menus = [ + 'profile' => [ + 'code' => 'account.profile', + 'route' => '/mi-cuenta/datos-personales', + ], + 'purchases' => [ + 'code' => 'account.purchases', + 'route' => '/mi-cuenta/compras', + ], + 'tickets' => [ + 'code' => 'account.tickets', + 'route' => '/mi-cuenta/tickets', + ], ]; - foreach ($routes as $code => $route) { - DB::table('menues') - ->where('code', $code) - ->update([ + foreach ($menus as $legacyCode => $menuData) { + $legacyMenu = DB::table('menues')->where('code', $legacyCode)->first(); + + DB::table('menues')->updateOrInsert( + ['code' => $menuData['code']], + [ 'parent_menu_code' => 'account', - 'route' => $route, + 'content_type' => $legacyMenu?->content_type ?? Menu::CONTENT_TYPE_DYNAMIC, + 'static_content_schema' => $legacyMenu?->static_content_schema, + 'route' => $menuData['route'], + 'created_at' => $legacyMenu?->created_at ?? $now, 'updated_at' => $now, - ]); + ] + ); + + if (! $legacyMenu) { + continue; + } + + $assignments = DB::table('tenants_menues') + ->where('menu_code', $legacyCode) + ->get(); + + foreach ($assignments as $assignment) { + $alreadyAssigned = DB::table('tenants_menues') + ->where('tenant_code', $assignment->tenant_code) + ->where('menu_code', $menuData['code']) + ->exists(); + + if (! $alreadyAssigned) { + DB::table('tenants_menues')->insert([ + 'tenant_code' => $assignment->tenant_code, + 'menu_code' => $menuData['code'], + 'static_content' => $assignment->static_content, + 'created_at' => $assignment->created_at, + 'updated_at' => $now, + ]); + } + } + + DB::table('tenants_menues')->where('menu_code', $legacyCode)->delete(); + DB::table('menues')->where('code', $legacyCode)->delete(); } $tenantCodes = DB::table('tenants_menues') - ->whereIn('menu_code', array_keys($routes)) + ->whereIn('menu_code', array_column($menus, 'code')) ->distinct() ->pluck('tenant_code'); @@ -66,20 +109,63 @@ return new class extends Migration public function down(): void { $now = now(); - $routes = [ - 'profile' => '/profile', - 'purchases' => '/purchases', - 'tickets' => '/tickets', + $menus = [ + 'account.profile' => [ + 'code' => 'profile', + 'route' => '/profile', + ], + 'account.purchases' => [ + 'code' => 'purchases', + 'route' => '/purchases', + ], + 'account.tickets' => [ + 'code' => 'tickets', + 'route' => '/tickets', + ], ]; - foreach ($routes as $code => $route) { - DB::table('menues') - ->where('code', $code) - ->update([ + foreach ($menus as $accountCode => $menuData) { + $accountMenu = DB::table('menues')->where('code', $accountCode)->first(); + + DB::table('menues')->updateOrInsert( + ['code' => $menuData['code']], + [ 'parent_menu_code' => null, - 'route' => $route, + 'content_type' => $accountMenu?->content_type ?? Menu::CONTENT_TYPE_DYNAMIC, + 'static_content_schema' => $accountMenu?->static_content_schema, + 'route' => $menuData['route'], + 'created_at' => $accountMenu?->created_at ?? $now, 'updated_at' => $now, - ]); + ] + ); + + if (! $accountMenu) { + continue; + } + + $assignments = DB::table('tenants_menues') + ->where('menu_code', $accountCode) + ->get(); + + foreach ($assignments as $assignment) { + $alreadyAssigned = DB::table('tenants_menues') + ->where('tenant_code', $assignment->tenant_code) + ->where('menu_code', $menuData['code']) + ->exists(); + + if (! $alreadyAssigned) { + DB::table('tenants_menues')->insert([ + 'tenant_code' => $assignment->tenant_code, + 'menu_code' => $menuData['code'], + 'static_content' => $assignment->static_content, + 'created_at' => $assignment->created_at, + 'updated_at' => $now, + ]); + } + } + + DB::table('tenants_menues')->where('menu_code', $accountCode)->delete(); + DB::table('menues')->where('code', $accountCode)->delete(); } DB::table('tenants_menues')->where('menu_code', 'account')->delete(); diff --git a/database/migrations/2026_07_24_000100_add_label_to_menues_table.php b/database/migrations/2026_07_24_000100_add_label_to_menues_table.php new file mode 100644 index 0000000..acd9e4a --- /dev/null +++ b/database/migrations/2026_07_24_000100_add_label_to_menues_table.php @@ -0,0 +1,57 @@ +string('label')->nullable()->after('code'); + }); + + $labels = [ + 'index' => 'Inicio', + 'product.detail' => 'Detalle de producto', + 'checkout' => 'Finalizar compra', + 'account' => 'Mi cuenta', + 'account.profile' => 'Datos personales', + 'account.purchases' => 'Mis compras', + 'account.tickets' => 'Mis tickets', + 'help' => 'Ayuda', + 'help.faq' => 'Preguntas frecuentes', + 'help.contact' => 'Contacto', + 'help.payment-methods' => 'Medios de pago', + 'help.shipping' => 'Envíos', + 'help.terms-and-conditions' => 'Términos y condiciones', + ]; + + DB::table('menues') + ->select(['id', 'code']) + ->orderBy('id') + ->eachById(function ($menus) use ($labels): void { + foreach ($menus as $menu) { + $fallback = Str::headline(Str::afterLast($menu->code, '.')); + + DB::table('menues') + ->where('id', $menu->id) + ->update(['label' => $labels[$menu->code] ?? $fallback]); + } + }); + + Schema::table('menues', function (Blueprint $table) { + $table->string('label')->nullable(false)->change(); + }); + } + + public function down(): void + { + Schema::table('menues', function (Blueprint $table) { + $table->dropColumn('label'); + }); + } +}; diff --git a/database/seeders/MenuSeeder.php b/database/seeders/MenuSeeder.php index 9baf3c9..362d784 100644 --- a/database/seeders/MenuSeeder.php +++ b/database/seeders/MenuSeeder.php @@ -14,36 +14,46 @@ class MenuSeeder extends Seeder public function run(): void { $menus = [ - ['code' => 'index', 'route' => '/'], - ['code' => 'product.detail', 'route' => '/product/:id'], - ['code' => 'checkout', 'route' => '/checkout'], + ['code' => 'index', 'label' => 'Inicio', 'route' => '/'], + [ + 'code' => 'product.detail', + 'label' => 'Detalle de producto', + 'route' => '/product/:id', + ], + ['code' => 'checkout', 'label' => 'Finalizar compra', 'route' => '/checkout'], [ 'code' => 'account', + 'label' => 'Mi cuenta', 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/mi-cuenta', ], [ - 'code' => 'profile', + 'code' => 'account.profile', + 'label' => 'Datos personales', 'parent_menu_code' => 'account', 'route' => '/mi-cuenta/datos-personales', ], [ - 'code' => 'purchases', + 'code' => 'account.purchases', + 'label' => 'Mis compras', 'parent_menu_code' => 'account', 'route' => '/mi-cuenta/compras', ], [ - 'code' => 'tickets', + 'code' => 'account.tickets', + 'label' => 'Mis tickets', 'parent_menu_code' => 'account', 'route' => '/mi-cuenta/tickets', ], [ 'code' => 'help', + 'label' => 'Ayuda', 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/ayuda', ], [ 'code' => 'help.faq', + 'label' => 'Preguntas frecuentes', 'parent_menu_code' => 'help', 'content_type' => Menu::CONTENT_TYPE_STATIC, 'static_content_schema' => [ @@ -55,6 +65,7 @@ class MenuSeeder extends Seeder ], [ 'code' => 'help.contact', + 'label' => 'Contacto', 'parent_menu_code' => 'help', 'content_type' => Menu::CONTENT_TYPE_STATIC, 'static_content_schema' => [ @@ -77,18 +88,21 @@ class MenuSeeder extends Seeder ], [ 'code' => 'help.payment-methods', + 'label' => 'Medios de pago', 'parent_menu_code' => 'help', 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/ayuda/medios-de-pago', ], [ 'code' => 'help.shipping', + 'label' => 'Envíos', 'parent_menu_code' => 'help', 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/ayuda/envios', ], [ 'code' => 'help.terms-and-conditions', + 'label' => 'Términos y condiciones', 'parent_menu_code' => 'help', 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/ayuda/terminos-y-condiciones', @@ -105,6 +119,10 @@ class MenuSeeder extends Seeder Menu::updateOrCreate(['code' => $menuData['code']], $menuData); } + Menu::query() + ->whereIn('code', ['profile', 'purchases', 'tickets']) + ->delete(); + $tenants = Tenant::all(); $allMenus = Menu::pluck('code')->toArray(); @@ -167,7 +185,7 @@ class MenuSeeder extends Seeder if ($tenant->codigo === 'sonder') { // Sonder NO tiene tickets - $menuCodes = array_diff($menuCodes, ['tickets']); + $menuCodes = array_diff($menuCodes, ['account.tickets']); } else { // Los demás NO tienen product.detail $menuCodes = array_diff($menuCodes, ['product.detail']); diff --git a/tests/Feature/Menu/TenantMenuControllerTest.php b/tests/Feature/Menu/TenantMenuControllerTest.php index 52f3667..a5e811b 100644 --- a/tests/Feature/Menu/TenantMenuControllerTest.php +++ b/tests/Feature/Menu/TenantMenuControllerTest.php @@ -19,6 +19,7 @@ class TenantMenuControllerTest extends TestCase $tenant = $this->createTenant(); $menu = Menu::query()->create([ 'code' => 'about', + 'label' => 'Nosotros', 'content_type' => Menu::CONTENT_TYPE_STATIC, 'static_content_schema' => [ 'title' => 'required|string|max:20', @@ -51,6 +52,7 @@ class TenantMenuControllerTest extends TestCase $tenant = $this->createTenant(); $menu = Menu::query()->create([ 'code' => 'about', + 'label' => 'Nosotros', 'content_type' => Menu::CONTENT_TYPE_STATIC, 'static_content_schema' => [ 'title' => 'required|string|max:20', @@ -85,6 +87,7 @@ class TenantMenuControllerTest extends TestCase $tenant = $this->createTenant(); $menu = Menu::query()->create([ 'code' => 'catalog', + 'label' => 'Catálogo', 'route' => '/catalog', ]); diff --git a/tests/Feature/Seeders/MenuSeederTest.php b/tests/Feature/Seeders/MenuSeederTest.php index 61b7e97..25479fa 100644 --- a/tests/Feature/Seeders/MenuSeederTest.php +++ b/tests/Feature/Seeders/MenuSeederTest.php @@ -26,21 +26,25 @@ class MenuSeederTest extends TestCase ->firstOrFail(); $this->assertSame(Menu::CONTENT_TYPE_DYNAMIC, $account->content_type); + $this->assertSame('Mi cuenta', $account->label); $this->assertSame('/mi-cuenta', $account->route); $this->assertSame([ - 'profile', - 'purchases', - 'tickets', + 'account.profile', + 'account.purchases', + 'account.tickets', ], $account->children->pluck('code')->sort()->values()->all()); $this->assertSame([ - 'profile' => '/mi-cuenta/datos-personales', - 'purchases' => '/mi-cuenta/compras', - 'tickets' => '/mi-cuenta/tickets', + 'account.profile' => '/mi-cuenta/datos-personales', + 'account.purchases' => '/mi-cuenta/compras', + 'account.tickets' => '/mi-cuenta/tickets', ], $account->children->pluck('route', 'code')->sortKeys()->all()); $this->assertTrue( $tenant->menues()->where('menues.code', 'account')->exists() ); + $this->assertFalse( + Menu::query()->whereIn('code', ['profile', 'purchases', 'tickets'])->exists() + ); } public function test_it_seeds_the_help_menu_hierarchy_and_content_schemas(): void @@ -53,6 +57,7 @@ class MenuSeederTest extends TestCase ->firstOrFail(); $this->assertSame(Menu::CONTENT_TYPE_DYNAMIC, $help->content_type); + $this->assertSame('Ayuda', $help->label); $this->assertSame([ 'help.contact', 'help.faq', diff --git a/tests/Feature/Tenant/BootstrapTenantControllerTest.php b/tests/Feature/Tenant/BootstrapTenantControllerTest.php index c37e8fa..b6ec956 100644 --- a/tests/Feature/Tenant/BootstrapTenantControllerTest.php +++ b/tests/Feature/Tenant/BootstrapTenantControllerTest.php @@ -2,6 +2,8 @@ namespace Tests\Feature\Tenant; +use App\Domains\Attachable\Enums\AttachmentType; +use App\Domains\Attachable\Models\Attachment; use App\Domains\Menu\Models\Menu; use App\Domains\Tenant\Models\Tenant; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -19,18 +21,18 @@ class BootstrapTenantControllerTest extends TestCase $hdrKey = (string) Str::uuid(); $ftrKey = (string) Str::uuid(); - $headerAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $headerAttachment = Attachment::create([ 'key' => $hdrKey, - 'path' => 'tenants/' . $hdrKey . '.png', + 'path' => 'tenants/'.$hdrKey.'.png', 'filename' => 'logo_header.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); - $footerAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $footerAttachment = Attachment::create([ 'key' => $ftrKey, - 'path' => 'tenants/' . $ftrKey . '.png', + 'path' => 'tenants/'.$ftrKey.'.png', 'filename' => 'logo_footer.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); @@ -105,11 +107,13 @@ class BootstrapTenantControllerTest extends TestCase $tenant = $this->createTenant(); $parent = Menu::query()->create([ 'code' => 'help', + 'label' => 'Ayuda', 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/ayuda', ]); $staticChild = Menu::query()->create([ 'code' => 'help.faq', + 'label' => 'Preguntas frecuentes', 'parent_menu_code' => $parent->code, 'content_type' => Menu::CONTENT_TYPE_STATIC, 'static_content_schema' => [ @@ -120,6 +124,7 @@ class BootstrapTenantControllerTest extends TestCase ]); $dynamicChild = Menu::query()->create([ 'code' => 'help.shipping', + 'label' => 'Envíos', 'parent_menu_code' => $parent->code, 'content_type' => Menu::CONTENT_TYPE_DYNAMIC, 'route' => '/ayuda/envios', @@ -144,8 +149,10 @@ class BootstrapTenantControllerTest extends TestCase ->assertJsonCount(1, 'data.menues') ->assertJsonCount(2, 'data.menues.0.submenues') ->assertJsonPath('data.menues.0.code', 'help') + ->assertJsonPath('data.menues.0.label', 'Ayuda') ->assertJsonPath('data.menues.0.parent_menu_code', null) ->assertJsonPath('data.menues.0.submenues.0.code', 'help.faq') + ->assertJsonPath('data.menues.0.submenues.0.label', 'Preguntas frecuentes') ->assertJsonPath('data.menues.0.submenues.0.parent_menu_code', 'help') ->assertJsonPath('data.menues.0.submenues.1.code', 'help.shipping'); @@ -259,18 +266,18 @@ class BootstrapTenantControllerTest extends TestCase $hdrUuid = (string) Str::uuid(); $ftrUuid = (string) Str::uuid(); - $hdrAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $hdrAttachment = Attachment::create([ 'key' => $hdrUuid, - 'path' => 'tenants/' . $hdrUuid . '.png', + 'path' => 'tenants/'.$hdrUuid.'.png', 'filename' => 'hdr.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); - $ftrAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $ftrAttachment = Attachment::create([ 'key' => $ftrUuid, - 'path' => 'tenants/' . $ftrUuid . '.png', + 'path' => 'tenants/'.$ftrUuid.'.png', 'filename' => 'ftr.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); @@ -497,18 +504,18 @@ class BootstrapTenantControllerTest extends TestCase $hdrKey = (string) Str::uuid(); $ftrKey = (string) Str::uuid(); - $headerAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $headerAttachment = Attachment::create([ 'key' => $hdrKey, - 'path' => 'tenants/' . $hdrKey . '.png', + 'path' => 'tenants/'.$hdrKey.'.png', 'filename' => 'logo_header.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); - $footerAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $footerAttachment = Attachment::create([ 'key' => $ftrKey, - 'path' => 'tenants/' . $ftrKey . '.png', + 'path' => 'tenants/'.$ftrKey.'.png', 'filename' => 'logo_footer.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); @@ -534,11 +541,11 @@ class BootstrapTenantControllerTest extends TestCase $base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; $ftrUuid = (string) Str::uuid(); - $footerAttachment = \App\Domains\Attachable\Models\Attachment::create([ + $footerAttachment = Attachment::create([ 'key' => $ftrUuid, - 'path' => 'tenants/' . $ftrUuid . '.png', + 'path' => 'tenants/'.$ftrUuid.'.png', 'filename' => 'logo_footer.png', - 'type' => \App\Domains\Attachable\Enums\AttachmentType::Image, + 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]);