diff --git a/assets/js/layout/AppMenu.vue b/assets/js/layout/AppMenu.vue index 215d037..04ef828 100644 --- a/assets/js/layout/AppMenu.vue +++ b/assets/js/layout/AppMenu.vue @@ -79,6 +79,44 @@ const adminMenu = { // Dynamisches Menü (wird geladen) const model = ref([...coreMenu]); +// Hilfsfunktion: Konvertiert ein Menü-Item und alle Sub-Items +const convertMenuItem = (item) => { + // Erstelle Permission-Prüfung + let visibleFn; + + if (item.permission) { + // Explizite Permission aus Plugin (z.B. "billing.view") + visibleFn = () => authStore.hasPermission(item.permission); + } else if (item.module) { + // Fallback: Modulname + ".view" (z.B. "billing" -> "billing.view") + visibleFn = () => authStore.hasPermission(`${item.module}.view`); + } else if (item.source) { + // Fallback: Plugin-Identifier + ".view" (z.B. "billing" -> "billing.view") + visibleFn = () => authStore.hasPermission(`${item.source}.view`); + } + + const converted = { + label: item.label, + icon: item.icon ? `pi pi-fw ${item.icon}` : 'pi pi-fw pi-circle', + to: item.to, + ...(visibleFn && { visible: visibleFn }) + }; + + // Rekursiv Sub-Items verarbeiten + if (item.items && Array.isArray(item.items)) { + converted.items = item.items + .filter(subItem => !subItem.separator) // Separators filtern + .map(subItem => convertMenuItem(subItem)); + } + + // Separators beibehalten + if (item.separator) { + return { separator: true }; + } + + return converted; +}; + // Plugin-Menüs laden const loadPluginMenus = async () => { try { @@ -91,29 +129,7 @@ const loadPluginMenus = async () => { // Konvertiere zu PrimeVue Menü-Format const menuGroup = { label: groupLabel, - items: items.map(item => { - // Erstelle Permission-Prüfung - let visibleFn; - - if (item.permission) { - // Explizite Permission aus Plugin - visibleFn = () => authStore.hasPermission(item.permission); - } else if (item.module) { - // Fallback: Leite von Modulname ab (z.B. 'billing') - visibleFn = () => permissionStore.canView(item.module); - } else if (item.source) { - // Fallback: Nutze Plugin-Identifier als Modulname - visibleFn = () => permissionStore.canView(item.source); - } - - return { - label: item.label, - icon: item.icon ? `pi pi-fw ${item.icon}` : 'pi pi-fw pi-circle', - to: item.to, - ...(item.items && { items: item.items }), - ...(visibleFn && { visible: visibleFn }) - }; - }) + items: items.map(item => convertMenuItem(item)) }; model.value.push(menuGroup); diff --git a/composer.lock b/composer.lock index 24d0d26..7b86407 100644 --- a/composer.lock +++ b/composer.lock @@ -2052,12 +2052,12 @@ "source": { "type": "git", "url": "https://git.osdata-home.de/mycrm/mycrm-billing-module", - "reference": "a70043acd597e4ff5064a51e639a5f28227e5cd4" + "reference": "8c2544569381333f45ae013b6d0a4ac61d4eb0e0" }, "require": { "doctrine/orm": "^3.0", "php": ">=8.2", - "symfony/framework-bundle": "^7.1" + "symfony/framework-bundle": "^7.3" }, "suggest": { "api-platform/symfony": "Required for API Platform integration (>=4.0)" @@ -2066,7 +2066,7 @@ "type": "symfony-bundle", "extra": { "symfony": { - "require": "7.1.*" + "require": "7.3.*" } }, "autoload": { @@ -2078,7 +2078,7 @@ "proprietary" ], "description": "Ausgangsrechnungsverwaltung für myCRM", - "time": "2025-12-22T08:27:54+00:00" + "time": "2025-12-28T09:14:04+00:00" }, { "name": "nelmio/cors-bundle", diff --git a/docs/PLUGIN_MENUS_AND_PERMISSIONS.md b/docs/PLUGIN_MENUS_AND_PERMISSIONS.md index 3a2e177..a8747d4 100644 --- a/docs/PLUGIN_MENUS_AND_PERMISSIONS.md +++ b/docs/PLUGIN_MENUS_AND_PERMISSIONS.md @@ -22,21 +22,22 @@ public function getMenuItems(): array { return [ [ - 'label' => 'Rechnungen', // Menü-Titel + 'label' => 'Rechnungen', // Menü-Titel (Container) 'icon' => 'pi-file-pdf', // PrimeIcons Icon (ohne pi pi-fw prefix) 'group' => 'Finanzen', // Gruppierung im Menü + // WICHTIG: Container-Items brauchen KEINE permission Property 'items' => [ [ 'label' => 'Alle Rechnungen', 'icon' => 'pi-list', 'to' => '/billing/invoices', // Vue Router path - 'permission' => 'billing.view' // Optional: Permission-Check + 'permission' => 'billing.view' // WICHTIG: Jedes Sub-Item braucht permission ], [ 'label' => 'Neue Rechnung', 'icon' => 'pi-plus', 'to' => '/billing/invoices/create', - 'permission' => 'billing.create' + 'permission' => 'billing.create' // Explizite Permission ], [ 'separator' => true // Optional: Trennlinie @@ -45,7 +46,7 @@ public function getMenuItems(): array 'label' => 'Einstellungen', 'icon' => 'pi-cog', 'to' => '/billing/settings', - 'permission' => 'billing.manage' + 'permission' => 'billing.manage' // Explizite Permission ] ] ] @@ -75,11 +76,14 @@ public function getMenuItems(): array ```php public function getPermissionModules(): array { - return ['billing', 'invoicing']; + // Best Practice: Ein einziges Modul pro Plugin + return ['billing']; } ``` -Das System erstellt automatisch folgende Permissions: +**WICHTIG:** Verwende nur **ein** Permission-Modul pro Plugin, nicht mehrere! Dies vereinfacht die Administration massiv. + +Das System erstellt automatisch folgende Permissions für das Modul `billing`: - `billing.view` - `billing.create` - `billing.edit` @@ -276,7 +280,8 @@ class BillingModulePlugin implements ModulePluginInterface public function getPermissionModules(): array { - return ['billing', 'invoicing', 'payments']; + // Best Practice: Ein einziges Modul für das gesamte Plugin + return ['billing']; } public function getMenuItems(): array @@ -286,12 +291,13 @@ class BillingModulePlugin implements ModulePluginInterface 'label' => 'Rechnungen', 'icon' => 'pi-file-pdf', 'group' => 'Finanzen', + // Wichtig: Container-Item hat KEINE permission Property 'items' => [ [ 'label' => 'Dashboard', 'icon' => 'pi-chart-line', 'to' => '/billing/dashboard', - 'permission' => 'billing.view' + 'permission' => 'billing.view' // Explizite Permission ], [ 'label' => 'Alle Rechnungen', @@ -312,7 +318,7 @@ class BillingModulePlugin implements ModulePluginInterface 'label' => 'Zahlungen', 'icon' => 'pi-money-bill', 'to' => '/billing/payments', - 'permission' => 'payments.view' + 'permission' => 'billing.view' // Einheitliches Modul! ], [ 'label' => 'Einstellungen', @@ -438,6 +444,78 @@ tail -f var/log/dev.log | grep -E "(Plugin|Menu|Permission)" - **Keine Core-Gruppen überschreiben** - "Home", "CRM", "Administration" sind reserviert - **Keine tiefen Verschachtelungen** - Max. 2 Ebenen (Gruppe → Items) - **Keine externen URLs ohne Warnung** - User erwarten interne Navigation +- **Nicht mehrere Permission-Module für ein Plugin** - Erschwert die Administration unnötig + +### 🎯 WICHTIG: Permission-Format und Sub-Items (Stand: 2025-12-28) + +**Ein Permission-Modul pro Plugin:** +```php +// ✅ RICHTIG: Ein einziges Modul +public function getPermissionModules(): array +{ + return ['billing']; +} + +// ❌ FALSCH: Unnötig aufgeteilt +public function getPermissionModules(): array +{ + return ['billing', 'invoices', 'payments']; +} +``` + +**Explizite Permissions für ALLE Sub-Items:** +```php +// ✅ RICHTIG: Jedes anklickbare Item hat permission +public function getMenuItems(): array +{ + return [ + [ + 'label' => 'Rechnungen', // Container - KEINE Permission nötig + 'icon' => 'pi-file-pdf', + 'group' => 'Finanzen', + 'items' => [ + [ + 'label' => 'Alle Rechnungen', + 'to' => '/billing/invoices', + 'permission' => 'billing.view' // ✅ Explizite Permission + ], + [ + 'label' => 'Neue Rechnung', + 'to' => '/billing/invoices/create', + 'permission' => 'billing.create' // ✅ Explizite Permission + ] + ] + ] + ]; +} + +// ❌ FALSCH: Sub-Items ohne Permission +public function getMenuItems(): array +{ + return [ + [ + 'label' => 'Rechnungen', + 'permission' => 'billing.view', // ❌ Container braucht keine Permission + 'items' => [ + [ + 'label' => 'Alle Rechnungen', + 'to' => '/billing/invoices' + // ❌ FEHLT: permission Property! + ] + ] + ] + ]; +} +``` + +**Warum ist das wichtig?** + +Das Frontend (`AppMenu.vue`) verwendet eine **rekursive `convertMenuItem()` Funktion**, die: +1. Alle Sub-Items durchläuft und deren `permission` Properties in `visible` Funktionen umwandelt +2. Items mit `source` Property automatisch zu `${source}.view` Permission konvertiert +3. Nur Items mit erfüllter Permission anzeigt + +**Container-Items** (mit `items` Array) brauchen **KEINE eigene Permission**, da sie nur dann angezeigt werden, wenn mindestens ein Sub-Item sichtbar ist. --- diff --git a/symfony.lock b/symfony.lock index ef832bb..5296740 100644 --- a/symfony.lock +++ b/symfony.lock @@ -74,7 +74,7 @@ ] }, "mycrm/billing-module": { - "version": "v1.0.0" + "version": "dev-main" }, "nelmio/cors-bundle": { "version": "2.6",