Add limited backoffice role template actions

This commit is contained in:
Jeppe Bundgaard
2026-07-07 03:23:32 +02:00
parent 96ccc01d08
commit 7180bbd6d4
15 changed files with 490 additions and 4 deletions
@@ -1,21 +1,136 @@
<script setup>
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import { useI18n } from 'vue-i18n';
import Swal from "sweetalert2";
const { t } = useI18n();
defineProps(['objects']);
import { ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { loadList } = usePaginatedListInstance();
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
import ActionSettingsWheelItemLabel from "@/components/displays/buttons/ActionSettingsWheelItemLabel.vue";
const redirect = (path) => {
window.location = path;
}
const limitedBackofficeTemplates = ref([]);
const templatesLoading = ref(false);
const templatesLoadError = ref(false);
const applyingTemplateKey = ref("");
const canApplyLimitedBackofficeTemplates = computed(() => SessionUser.hasPermission("add_role_permission"));
const loadLimitedBackofficeTemplates = async () => {
if (!canApplyLimitedBackofficeTemplates.value || templatesLoading.value) {
return;
}
templatesLoading.value = true;
templatesLoadError.value = false;
try {
const templates = await SessionUser.objects.roles.functions.getLimitedBackofficePermissionTemplates();
limitedBackofficeTemplates.value = Array.isArray(templates) ? templates : [];
} catch (error) {
console.error("Failed to load limited backoffice permission templates", error);
templatesLoadError.value = true;
} finally {
templatesLoading.value = false;
}
};
const templateTranslation = (template, field) => {
const key = `templates.limited_backoffice.roles.${template?.key}.${field}`;
const translated = t(key);
return translated === key ? template?.[field] || template?.key || "" : translated;
};
const templateLabel = (template) => templateTranslation(template, "label");
const rolePermissions = (role) => (Array.isArray(role?.permissions) ? role.permissions : []);
const templatePermissions = (template) => (Array.isArray(template?.permissions) ? template.permissions : []);
const missingTemplatePermissions = (role, template) => {
const currentPermissions = new Set(rolePermissions(role));
return templatePermissions(template).filter((permission) => permission && !currentPermissions.has(permission));
};
const roleTemplateActionKey = (role, template) => `${role?.id}:${template?.key}`;
const isApplyingTemplate = (role, template) => applyingTemplateKey.value === roleTemplateActionKey(role, template);
const isTemplateActionDisabled = (role, template) =>
templatesLoading.value || Boolean(applyingTemplateKey.value) || missingTemplatePermissions(role, template).length === 0;
const applyLimitedBackofficeTemplate = async (role, template) => {
const missingPermissions = missingTemplatePermissions(role, template);
const label = templateLabel(template);
if (missingPermissions.length === 0) {
await Swal.fire({
icon: "success",
title: t("roles.templates.up_to_date_title"),
text: t("roles.templates.up_to_date_text", { role: role.name, template: label }),
});
return;
}
const confirmation = await Swal.fire({
icon: "question",
title: t("roles.templates.confirm_title"),
text: t("roles.templates.confirm_text", {
count: missingPermissions.length,
role: role.name,
template: label,
}),
showCancelButton: true,
confirmButtonText: t("roles.templates.confirm_button"),
cancelButtonText: t("common.cancel"),
});
if (!confirmation.isConfirmed) {
return;
}
applyingTemplateKey.value = roleTemplateActionKey(role, template);
try {
const results = await Promise.allSettled(
missingPermissions.map((permission) =>
SessionUser.objects.roles.functions.permissions.add(role.id, permission)
)
);
const failed = results.filter((result) => result.status === "rejected");
if (failed.length > 0) {
throw failed[0].reason;
}
await Swal.fire({
icon: "success",
title: t("roles.templates.success_title"),
text: t("roles.templates.success_text", {
count: missingPermissions.length,
role: role.name,
template: label,
}),
});
} catch (error) {
console.error("Failed to apply limited backoffice permission template", error);
await Swal.fire({
icon: "error",
title: t("roles.templates.error_title"),
text: t("roles.templates.error_text", { role: role.name, template: label }),
});
} finally {
applyingTemplateKey.value = "";
await loadList();
}
};
onMounted(loadLimitedBackofficeTemplates);
</script>
<template>
@@ -66,6 +181,46 @@ const redirect = (path) => {
icon="fas fa-clone"
:click-action="() => SessionUser.objects.roles.functions.cloneObject(object.id).then(() => loadList())"
></ActionSettingsWheelItem>
<div
v-if="canApplyLimitedBackofficeTemplates"
class="role-limited-backoffice-template-actions"
:data-testid="`role-limited-backoffice-template-actions-${object.id}`"
>
<ActionSettingsWheelItemLabel
:label="$t('roles.templates.section')"
icon="fas fa-id-badge"
></ActionSettingsWheelItemLabel>
<ActionSettingsWheelItem
v-if="templatesLoading"
:label="$t('roles.templates.loading')"
icon="fas fa-spinner"
disabled
></ActionSettingsWheelItem>
<ActionSettingsWheelItem
v-else-if="templatesLoadError"
:label="$t('roles.templates.load_error')"
icon="fas fa-sync-alt"
:click-action="loadLimitedBackofficeTemplates"
></ActionSettingsWheelItem>
<ActionSettingsWheelItem
v-else-if="limitedBackofficeTemplates.length === 0"
:label="$t('roles.templates.unavailable')"
icon="fas fa-ban"
disabled
></ActionSettingsWheelItem>
<template v-else>
<ActionSettingsWheelItem
v-for="template in limitedBackofficeTemplates"
:key="template.key"
:label="$t('roles.templates.apply', { template: templateLabel(template) })"
:icon="isApplyingTemplate(object, template) ? 'fas fa-spinner' : 'fas fa-id-badge'"
:disabled="isTemplateActionDisabled(object, template)"
:test-id="`role-template-${object.id}-${template.key}`"
:click-action="() => applyLimitedBackofficeTemplate(object, template)"
></ActionSettingsWheelItem>
</template>
</div>
</template>
</ActionSettingsWheelButton>
</div>
@@ -85,4 +240,4 @@ const redirect = (path) => {
<style scoped>
</style>
</style>
@@ -1,5 +1,4 @@
<script>
import Swal from "sweetalert2";
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import { ObjectsGlobal } from "@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
@@ -123,6 +122,10 @@ export const Roles = {
id: id
});
},
getLimitedBackofficePermissionTemplates: async () => {
const response = await authenticatedRequest("/roles/limited-backoffice-permission-templates", "GET");
return response?.data?.data ?? response?.data ?? [];
},
permissions: {
add: async (roleId, permissionId) => {
return authenticatedRequest("/roles/permissions", "POST", {
@@ -161,4 +164,4 @@ export const Roles = {
);
}
};
</script>
</script>
+16
View File
@@ -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": {
+16
View File
@@ -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": {
+16
View File
@@ -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": {
+16
View File
@@ -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'}"
},
+16
View File
@@ -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": {
+16
View File
@@ -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": {
@@ -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"
}
}
}
@@ -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"
}
}
}
@@ -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"
}
}
}
@@ -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'}"
}
@@ -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"
}
}
}
@@ -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"
}
}
}
@@ -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<Record<string, unknown>>) => ({
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);