diff --git a/src/components/displays/superuser/tables/rolesTable.vue b/src/components/displays/superuser/tables/rolesTable.vue index 237a4394..53adc3e2 100644 --- a/src/components/displays/superuser/tables/rolesTable.vue +++ b/src/components/displays/superuser/tables/rolesTable.vue @@ -1,21 +1,136 @@ @@ -85,4 +240,4 @@ const redirect = (path) => { \ No newline at end of file + diff --git a/src/components/session/token/SessionUser/Objects/Roles.vue b/src/components/session/token/SessionUser/Objects/Roles.vue index 6e42dedf..916421f2 100644 --- a/src/components/session/token/SessionUser/Objects/Roles.vue +++ b/src/components/session/token/SessionUser/Objects/Roles.vue @@ -1,5 +1,4 @@ \ No newline at end of file + diff --git a/src/i18n/generated/da-v2.json b/src/i18n/generated/da-v2.json index 93ba2d6d..8ee5383d 100644 --- a/src/i18n/generated/da-v2.json +++ b/src/i18n/generated/da-v2.json @@ -5048,6 +5048,22 @@ "title": "Tilladelser for rolle {id}", "title_with_name": "Tilladelser for {name}" }, + "templates": { + "apply": "Anvend {template}-skabelon", + "confirm_button": "Anvend skabelon", + "confirm_text": "Tilfoej {count} manglende tilladelser fra {template} til {role}?", + "confirm_title": "Anvend tilladelsesskabelon?", + "error_text": "En eller flere tilladelser fra {template} kunne ikke tilfoejes til {role}. Rollelisten blev genindlaest.", + "error_title": "Kunne ikke anvende skabelon", + "load_error": "Kunne ikke indlaese skabeloner. Proev igen", + "loading": "Indlaeser skabeloner...", + "section": "Begraenset backoffice-skabeloner", + "success_text": "Tilfoejede {count} tilladelser fra {template} til {role}.", + "success_title": "Skabelon anvendt", + "unavailable": "Ingen skabeloner er tilgaengelige.", + "up_to_date_text": "{role} har allerede alle tilladelser i {template}.", + "up_to_date_title": "Rollen er opdateret" + }, "subtitle": "@.capitalize:{'words.generated.brugerroller'}" }, "self_wash": { diff --git a/src/i18n/generated/de-v2.json b/src/i18n/generated/de-v2.json index 24561469..2c477fa9 100644 --- a/src/i18n/generated/de-v2.json +++ b/src/i18n/generated/de-v2.json @@ -5159,6 +5159,22 @@ "title": "Berechtigungen fuer Rolle {id}", "title_with_name": "Berechtigungen fuer {name}" }, + "templates": { + "apply": "{template}-Vorlage anwenden", + "confirm_button": "Vorlage anwenden", + "confirm_text": "{count} fehlende Berechtigungen aus {template} zu {role} hinzufuegen?", + "confirm_title": "Berechtigungsvorlage anwenden?", + "error_text": "Eine oder mehrere Berechtigungen aus {template} konnten {role} nicht hinzugefuegt werden. Die Rollenliste wurde neu geladen.", + "error_title": "Vorlage konnte nicht angewendet werden", + "load_error": "Vorlagen konnten nicht geladen werden. Erneut versuchen", + "loading": "Vorlagen werden geladen...", + "section": "Limited-Backoffice-Vorlagen", + "success_text": "{count} Berechtigungen aus {template} zu {role} hinzugefuegt.", + "success_title": "Vorlage angewendet", + "unavailable": "Keine Vorlagen verfuegbar.", + "up_to_date_text": "{role} hat bereits alle Berechtigungen in {template}.", + "up_to_date_title": "Rolle ist aktuell" + }, "subtitle": "Benutzerrollen" }, "self_wash": { diff --git a/src/i18n/generated/en-v2.json b/src/i18n/generated/en-v2.json index 120928db..e0b6d072 100644 --- a/src/i18n/generated/en-v2.json +++ b/src/i18n/generated/en-v2.json @@ -4880,6 +4880,22 @@ "title": "Role {id} permissions", "title_with_name": "{name} permissions" }, + "templates": { + "apply": "Apply {template} template", + "confirm_button": "Apply template", + "confirm_text": "Add {count} missing permissions from {template} to {role}?", + "confirm_title": "Apply permission template?", + "error_text": "One or more permissions from {template} could not be added to {role}. The role list was reloaded.", + "error_title": "Could not apply template", + "load_error": "Could not load templates. Try again", + "loading": "Loading templates...", + "section": "Limited backoffice templates", + "success_text": "Added {count} permissions from {template} to {role}.", + "success_title": "Template applied", + "unavailable": "No templates are available.", + "up_to_date_text": "{role} already has every permission in {template}.", + "up_to_date_title": "Role is up to date" + }, "subtitle": "@.capitalize:{'words.generated.user'} @:{'words.generated.roles'}" }, "self_wash": { diff --git a/src/i18n/generated/global-v2.json b/src/i18n/generated/global-v2.json index bec0cdf2..a4565474 100644 --- a/src/i18n/generated/global-v2.json +++ b/src/i18n/generated/global-v2.json @@ -4373,6 +4373,22 @@ "title": "@:{'templates.generated.compat.roles.permissions.title'}", "title_with_name": "@:{'templates.generated.compat.roles.permissions.title_with_name'}" }, + "templates": { + "apply": "@:{'templates.generated.compat.roles.templates.apply'}", + "confirm_button": "@:{'templates.generated.compat.roles.templates.confirm_button'}", + "confirm_text": "@:{'templates.generated.compat.roles.templates.confirm_text'}", + "confirm_title": "@:{'templates.generated.compat.roles.templates.confirm_title'}", + "error_text": "@:{'templates.generated.compat.roles.templates.error_text'}", + "error_title": "@:{'templates.generated.compat.roles.templates.error_title'}", + "load_error": "@:{'templates.generated.compat.roles.templates.load_error'}", + "loading": "@:{'templates.generated.compat.roles.templates.loading'}", + "section": "@:{'templates.generated.compat.roles.templates.section'}", + "success_text": "@:{'templates.generated.compat.roles.templates.success_text'}", + "success_title": "@:{'templates.generated.compat.roles.templates.success_title'}", + "unavailable": "@:{'templates.generated.compat.roles.templates.unavailable'}", + "up_to_date_text": "@:{'templates.generated.compat.roles.templates.up_to_date_text'}", + "up_to_date_title": "@:{'templates.generated.compat.roles.templates.up_to_date_title'}" + }, "subtitle": "@:{'templates.generated.compat.roles.subtitle'}", "title": "@:{'templates.generated.compat.common.roles'}" }, diff --git a/src/i18n/generated/no-v2.json b/src/i18n/generated/no-v2.json index 0cf1e09a..18593fb8 100644 --- a/src/i18n/generated/no-v2.json +++ b/src/i18n/generated/no-v2.json @@ -5162,6 +5162,22 @@ "title": "Tillatelser for rolle {id}", "title_with_name": "Tillatelser for {name}" }, + "templates": { + "apply": "Bruk {template}-mal", + "confirm_button": "Bruk mal", + "confirm_text": "Legg til {count} manglende tillatelser fra {template} til {role}?", + "confirm_title": "Bruke tillatelsesmal?", + "error_text": "En eller flere tillatelser fra {template} kunne ikke legges til i {role}. Rollelisten ble lastet inn pa nytt.", + "error_title": "Kunne ikke bruke mal", + "load_error": "Kunne ikke laste maler. Prov igjen", + "loading": "Laster maler...", + "section": "Begrenset backoffice-maler", + "success_text": "La til {count} tillatelser fra {template} i {role}.", + "success_title": "Mal brukt", + "unavailable": "Ingen maler er tilgjengelige.", + "up_to_date_text": "{role} har allerede alle tillatelser i {template}.", + "up_to_date_title": "Rollen er oppdatert" + }, "subtitle": "@.capitalize:{'words.generated.user'} roles" }, "self_wash": { diff --git a/src/i18n/generated/sv-v2.json b/src/i18n/generated/sv-v2.json index f037f4a9..61435a34 100644 --- a/src/i18n/generated/sv-v2.json +++ b/src/i18n/generated/sv-v2.json @@ -5212,6 +5212,22 @@ "title": "Behorigheter for roll {id}", "title_with_name": "Behorigheter for {name}" }, + "templates": { + "apply": "Anvand mallen {template}", + "confirm_button": "Anvand mall", + "confirm_text": "Lagg till {count} saknade behorigheter fran {template} till {role}?", + "confirm_title": "Anvanda behorighetsmall?", + "error_text": "En eller flera behorigheter fran {template} kunde inte laggas till i {role}. Rolllistan laddades om.", + "error_title": "Kunde inte anvanda mall", + "load_error": "Kunde inte ladda mallar. Forsok igen", + "loading": "Laddar mallar...", + "section": "Begransade backoffice-mallar", + "success_text": "Lade till {count} behorigheter fran {template} i {role}.", + "success_title": "Mall anvand", + "unavailable": "Inga mallar ar tillgangliga.", + "up_to_date_text": "{role} har redan alla behorigheter i {template}.", + "up_to_date_title": "Rollen ar uppdaterad" + }, "subtitle": "@.capitalize:{'words.generated.user'} @:{'words.generated.roles'}" }, "self_wash": { diff --git a/src/i18n/source/da/phrases/compat/roles/permissions.json b/src/i18n/source/da/phrases/compat/roles/permissions.json index 70bd9c03..b0edf540 100644 --- a/src/i18n/source/da/phrases/compat/roles/permissions.json +++ b/src/i18n/source/da/phrases/compat/roles/permissions.json @@ -91,6 +91,22 @@ "subtitle": "Rolletilladelser", "title": "Tilladelser for rolle {id}", "title_with_name": "Tilladelser for {name}" + }, + "templates": { + "apply": "Anvend {template}-skabelon", + "confirm_button": "Anvend skabelon", + "confirm_text": "Tilfoej {count} manglende tilladelser fra {template} til {role}?", + "confirm_title": "Anvend tilladelsesskabelon?", + "error_text": "En eller flere tilladelser fra {template} kunne ikke tilfoejes til {role}. Rollelisten blev genindlaest.", + "error_title": "Kunne ikke anvende skabelon", + "load_error": "Kunne ikke indlaese skabeloner. Proev igen", + "loading": "Indlaeser skabeloner...", + "section": "Begraenset backoffice-skabeloner", + "success_text": "Tilfoejede {count} tilladelser fra {template} til {role}.", + "success_title": "Skabelon anvendt", + "unavailable": "Ingen skabeloner er tilgaengelige.", + "up_to_date_text": "{role} har allerede alle tilladelser i {template}.", + "up_to_date_title": "Rollen er opdateret" } } } diff --git a/src/i18n/source/de/phrases/compat/roles/permissions.json b/src/i18n/source/de/phrases/compat/roles/permissions.json index ce6a00e6..7640ad81 100644 --- a/src/i18n/source/de/phrases/compat/roles/permissions.json +++ b/src/i18n/source/de/phrases/compat/roles/permissions.json @@ -91,6 +91,22 @@ "subtitle": "Rollenberechtigungen", "title": "Berechtigungen fuer Rolle {id}", "title_with_name": "Berechtigungen fuer {name}" + }, + "templates": { + "apply": "{template}-Vorlage anwenden", + "confirm_button": "Vorlage anwenden", + "confirm_text": "{count} fehlende Berechtigungen aus {template} zu {role} hinzufuegen?", + "confirm_title": "Berechtigungsvorlage anwenden?", + "error_text": "Eine oder mehrere Berechtigungen aus {template} konnten {role} nicht hinzugefuegt werden. Die Rollenliste wurde neu geladen.", + "error_title": "Vorlage konnte nicht angewendet werden", + "load_error": "Vorlagen konnten nicht geladen werden. Erneut versuchen", + "loading": "Vorlagen werden geladen...", + "section": "Limited-Backoffice-Vorlagen", + "success_text": "{count} Berechtigungen aus {template} zu {role} hinzugefuegt.", + "success_title": "Vorlage angewendet", + "unavailable": "Keine Vorlagen verfuegbar.", + "up_to_date_text": "{role} hat bereits alle Berechtigungen in {template}.", + "up_to_date_title": "Rolle ist aktuell" } } } diff --git a/src/i18n/source/en/phrases/compat/roles/permissions.json b/src/i18n/source/en/phrases/compat/roles/permissions.json index 68707f08..bad7fa5a 100644 --- a/src/i18n/source/en/phrases/compat/roles/permissions.json +++ b/src/i18n/source/en/phrases/compat/roles/permissions.json @@ -91,6 +91,22 @@ "subtitle": "Role permissions", "title": "Role {id} permissions", "title_with_name": "{name} permissions" + }, + "templates": { + "apply": "Apply {template} template", + "confirm_button": "Apply template", + "confirm_text": "Add {count} missing permissions from {template} to {role}?", + "confirm_title": "Apply permission template?", + "error_text": "One or more permissions from {template} could not be added to {role}. The role list was reloaded.", + "error_title": "Could not apply template", + "load_error": "Could not load templates. Try again", + "loading": "Loading templates...", + "section": "Limited backoffice templates", + "success_text": "Added {count} permissions from {template} to {role}.", + "success_title": "Template applied", + "unavailable": "No templates are available.", + "up_to_date_text": "{role} already has every permission in {template}.", + "up_to_date_title": "Role is up to date" } } } diff --git a/src/i18n/source/global/shared/roles/index.json b/src/i18n/source/global/shared/roles/index.json index 2c0cf1bb..020bd5a6 100644 --- a/src/i18n/source/global/shared/roles/index.json +++ b/src/i18n/source/global/shared/roles/index.json @@ -91,6 +91,22 @@ "title": "@:{'phrases.compat.roles.permissions.title'}", "title_with_name": "@:{'phrases.compat.roles.permissions.title_with_name'}" }, + "templates": { + "apply": "@:{'phrases.compat.roles.templates.apply'}", + "confirm_button": "@:{'phrases.compat.roles.templates.confirm_button'}", + "confirm_text": "@:{'phrases.compat.roles.templates.confirm_text'}", + "confirm_title": "@:{'phrases.compat.roles.templates.confirm_title'}", + "error_text": "@:{'phrases.compat.roles.templates.error_text'}", + "error_title": "@:{'phrases.compat.roles.templates.error_title'}", + "load_error": "@:{'phrases.compat.roles.templates.load_error'}", + "loading": "@:{'phrases.compat.roles.templates.loading'}", + "section": "@:{'phrases.compat.roles.templates.section'}", + "success_text": "@:{'phrases.compat.roles.templates.success_text'}", + "success_title": "@:{'phrases.compat.roles.templates.success_title'}", + "unavailable": "@:{'phrases.compat.roles.templates.unavailable'}", + "up_to_date_text": "@:{'phrases.compat.roles.templates.up_to_date_text'}", + "up_to_date_title": "@:{'phrases.compat.roles.templates.up_to_date_title'}" + }, "subtitle": "@:{'phrases.compat.roles.subtitle'}", "title": "@:{'phrases.compat.common.roles'}" } diff --git a/src/i18n/source/no/phrases/compat/roles/permissions.json b/src/i18n/source/no/phrases/compat/roles/permissions.json index e9dca406..9df5fed2 100644 --- a/src/i18n/source/no/phrases/compat/roles/permissions.json +++ b/src/i18n/source/no/phrases/compat/roles/permissions.json @@ -91,6 +91,22 @@ "subtitle": "Rolletillatelser", "title": "Tillatelser for rolle {id}", "title_with_name": "Tillatelser for {name}" + }, + "templates": { + "apply": "Bruk {template}-mal", + "confirm_button": "Bruk mal", + "confirm_text": "Legg til {count} manglende tillatelser fra {template} til {role}?", + "confirm_title": "Bruke tillatelsesmal?", + "error_text": "En eller flere tillatelser fra {template} kunne ikke legges til i {role}. Rollelisten ble lastet inn pa nytt.", + "error_title": "Kunne ikke bruke mal", + "load_error": "Kunne ikke laste maler. Prov igjen", + "loading": "Laster maler...", + "section": "Begrenset backoffice-maler", + "success_text": "La til {count} tillatelser fra {template} i {role}.", + "success_title": "Mal brukt", + "unavailable": "Ingen maler er tilgjengelige.", + "up_to_date_text": "{role} har allerede alle tillatelser i {template}.", + "up_to_date_title": "Rollen er oppdatert" } } } diff --git a/src/i18n/source/sv/phrases/compat/roles/permissions.json b/src/i18n/source/sv/phrases/compat/roles/permissions.json index f8a9b0c2..4e99ce2d 100644 --- a/src/i18n/source/sv/phrases/compat/roles/permissions.json +++ b/src/i18n/source/sv/phrases/compat/roles/permissions.json @@ -91,6 +91,22 @@ "subtitle": "Rollbehorigheter", "title": "Behorigheter for roll {id}", "title_with_name": "Behorigheter for {name}" + }, + "templates": { + "apply": "Anvand mallen {template}", + "confirm_button": "Anvand mall", + "confirm_text": "Lagg till {count} saknade behorigheter fran {template} till {role}?", + "confirm_title": "Anvanda behorighetsmall?", + "error_text": "En eller flera behorigheter fran {template} kunde inte laggas till i {role}. Rolllistan laddades om.", + "error_title": "Kunde inte anvanda mall", + "load_error": "Kunde inte ladda mallar. Forsok igen", + "loading": "Laddar mallar...", + "section": "Begransade backoffice-mallar", + "success_text": "Lade till {count} behorigheter fran {template} i {role}.", + "success_title": "Mall anvand", + "unavailable": "Inga mallar ar tillgangliga.", + "up_to_date_text": "{role} har redan alla behorigheter i {template}.", + "up_to_date_title": "Rollen ar uppdaterad" } } } diff --git a/tests/e2e/superuser-roles-permissions.spec.ts b/tests/e2e/superuser-roles-permissions.spec.ts index ea327277..376d5b79 100644 --- a/tests/e2e/superuser-roles-permissions.spec.ts +++ b/tests/e2e/superuser-roles-permissions.spec.ts @@ -43,11 +43,46 @@ const departments = [ }, ]; +const roleTemplatePermissions = [ + "user", + "permissions_list_own", + "list_departments", + "list_orders", + "fetch_order", + "statistics_orders_new", +]; + +const limitedBackofficePermissionTemplates = [ + { + key: "viewer", + label: "Deactivated", + description: "Keeps the employee registered without order, booking, or management permissions.", + permissions: ["user", "permissions_list_own"], + }, + { + key: "cashier", + label: "Cashier", + description: "Can work with POS orders for assigned departments.", + permissions: roleTemplatePermissions, + }, +]; + type PermissionCall = { action: "add" | "remove"; permission: string; }; +const roleListEnvelope = (rows: Array>) => ({ + data: rows, + meta: { + pagination: { + page: 1, + per_page: 100, + total: rows.length, + }, + }, +}); + const openGroupTab = async (page: Page, label: RegExp) => { await page.locator(".tabs").getByText(label).click(); }; @@ -147,6 +182,107 @@ const bootRolePermissionsPage = async (page: Page, routeOptions = {}) => { }; test.describe("Superuser role permissions", () => { + test("applies limited backoffice templates from the role action wheel in add-only mode", async ({ page }) => { + const role = { + id: 2, + name: "Operations manager", + description: "Can manage operations.", + created_at: "2026-07-06T08:00:00.000Z", + permissions: ["user", "list_orders"], + }; + const calls: PermissionCall[] = []; + + await page.addInitScript(() => { + window.localStorage.setItem("locale", "en"); + }); + await seedAuthenticatedState(page, "superuser-roles-template-token"); + await mockApi(page, { + authenticated: true, + permissions: ["superuser", "user", "list_roles", "add_role_permission"], + sessionData: { + group_id: 1, + }, + }); + + await page.route(apiPathPattern("/roles/limited-backoffice-permission-templates"), async (route: Route) => { + await route.fulfill(json({ data: limitedBackofficePermissionTemplates })); + }); + + await page.route(apiPathPattern("/roles"), async (route: Route) => { + await route.fulfill(json(roleListEnvelope([role]))); + }); + + await page.route(apiPathPattern("/roles/permissions"), async (route: Route) => { + const body = route.request().postDataJSON(); + const permission = String(body.permission_id || ""); + calls.push({ action: "add", permission }); + if (!role.permissions.includes(permission)) { + role.permissions = [...role.permissions, permission]; + } + await route.fulfill(json({ data: role })); + }); + + await page.goto("/superuser/roles", { waitUntil: "domcontentloaded" }); + await expect(page.getByRole("row", { name: /Operations manager/ })).toBeVisible(); + + const row = page.getByRole("row", { name: /Operations manager/ }); + await row.locator(".action-settings-wheel-trigger").click(); + await expect(page.getByTestId("role-limited-backoffice-template-actions-2")).toBeVisible(); + await page.getByTestId("role-template-2-cashier").click(); + + await expect(page.getByText("Apply permission template?")).toBeVisible(); + await page.getByRole("button", { name: "Apply template" }).click(); + + await expect + .poll(() => calls.map((call) => call.permission)) + .toEqual(["permissions_list_own", "list_departments", "fetch_order", "statistics_orders_new"]); + await expect(page.getByText("Template applied")).toBeVisible(); + }); + + test("disables limited backoffice template actions when a role is already up to date", async ({ page }) => { + const role = { + id: 3, + name: "Cashier role", + description: "Current cashier permissions.", + created_at: "2026-07-06T08:00:00.000Z", + permissions: [...roleTemplatePermissions], + }; + const calls: PermissionCall[] = []; + + await page.addInitScript(() => { + window.localStorage.setItem("locale", "en"); + }); + await seedAuthenticatedState(page, "superuser-roles-template-up-to-date-token"); + await mockApi(page, { + authenticated: true, + permissions: ["superuser", "user", "list_roles", "add_role_permission"], + sessionData: { + group_id: 1, + }, + }); + + await page.route(apiPathPattern("/roles/limited-backoffice-permission-templates"), async (route: Route) => { + await route.fulfill(json({ data: limitedBackofficePermissionTemplates })); + }); + + await page.route(apiPathPattern("/roles"), async (route: Route) => { + await route.fulfill(json(roleListEnvelope([role]))); + }); + + await page.route(apiPathPattern("/roles/permissions"), async (route: Route) => { + calls.push({ action: "add", permission: String(route.request().postDataJSON().permission_id || "") }); + await route.fulfill(json({ data: role })); + }); + + await page.goto("/superuser/roles", { waitUntil: "domcontentloaded" }); + const row = page.getByRole("row", { name: /Cashier role/ }); + await expect(row).toBeVisible(); + await row.locator(".action-settings-wheel-trigger").click(); + + await expect(page.getByTestId("role-template-3-cashier")).toBeDisabled(); + expect(calls).toEqual([]); + }); + test("@smoke @pr loads grouped permissions, searches, and toggles without a blank page", async ({ page }) => { const { calls } = await bootRolePermissionsPage(page);