Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c26da10677 | ||
|
|
22f8b5f203 | ||
|
|
987acc5b0f | ||
|
|
cf1509e34f | ||
|
|
ce2e671768 | ||
|
|
cf2add29dd |
@@ -7,7 +7,7 @@ export const fallbackChangePatterns = [
|
||||
/^vite\.config\.js$/u,
|
||||
/^playwright(?:\..+)?\.config\.(?:js|ts)$/u,
|
||||
/^playwright\.global-(?:setup|teardown)\.mjs$/u,
|
||||
/^scripts\/run-playwright/u,
|
||||
/^scripts\/run-playwright-(?:pr|ci-parallel|batched-chromium)\.mjs$/u,
|
||||
/^tests\/e2e\/(?:support|fixtures)\//u,
|
||||
];
|
||||
|
||||
@@ -24,6 +24,15 @@ export const sourceMappings = [
|
||||
specs: ["tests/e2e/navigation.smoke.spec.js"],
|
||||
projects: chromiumProjects,
|
||||
},
|
||||
{
|
||||
name: "superuser-roles-permissions",
|
||||
patterns: [
|
||||
/^src\/views\/dashboards\/superUserDashboard\/roles\/SuperUserRolesPermissions\.vue$/u,
|
||||
/^src\/views\/dashboards\/superUserDashboard\/roles\/rolePermissionCatalog\.js$/u,
|
||||
],
|
||||
specs: ["tests/e2e/superuser-roles-permissions.spec.ts"],
|
||||
projects: chromiumProjects,
|
||||
},
|
||||
{
|
||||
name: "booking",
|
||||
patterns: [/bookings?/iu, /time-bookings/iu, /^src\/views\/guest\/book\//u],
|
||||
@@ -52,6 +61,15 @@ export const sourceMappings = [
|
||||
specs: ["tests/e2e/edge-gateways.smoke.spec.js", "tests/e2e/edge-gateways.routes.spec.js"],
|
||||
projects: chromiumProjects,
|
||||
},
|
||||
{
|
||||
name: "admin-department-notifications",
|
||||
patterns: [
|
||||
/^src\/components\/displays\/department\/notifications\//u,
|
||||
/^src\/components\/displays\/pagination\/models\/DepartmentPos\/NotificationsPhonePagination\.vue$/u,
|
||||
],
|
||||
specs: ["tests/e2e/admin-department-notifications.spec.ts"],
|
||||
projects: chromiumProjects,
|
||||
},
|
||||
{
|
||||
name: "invoicing",
|
||||
patterns: [/invoic/iu, /economic[-/]?queue/iu, /collected-order/iu],
|
||||
|
||||
@@ -57,6 +57,7 @@ export const ownedFilesByRole = {
|
||||
admin: [
|
||||
"admin-bookings-mobile.spec.ts",
|
||||
"admin-daily-report.spec.ts",
|
||||
"admin-department-notifications.spec.ts",
|
||||
"admin-department-visibility.spec.ts",
|
||||
"admin-overview-mobile.spec.ts",
|
||||
"admin-overview-night-washes.spec.ts",
|
||||
@@ -105,6 +106,7 @@ export const ownedFilesByRole = {
|
||||
"superuser-departments-archive.spec.ts",
|
||||
"superuser-drafts.spec.ts",
|
||||
"superuser-products-layout.spec.ts",
|
||||
"superuser-roles-permissions.spec.ts",
|
||||
"superuser-system-status.smoke.spec.js",
|
||||
"superuser-users.spec.ts",
|
||||
"superuser-vehicles.smoke.spec.js",
|
||||
|
||||
+72
-16
@@ -4,12 +4,15 @@ import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
|
||||
import { loadList } from "@/components/pagination/paginatedList.vue";
|
||||
const props = defineProps({
|
||||
objects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
loadList: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { ref } from 'vue';
|
||||
@@ -22,6 +25,52 @@ const su_object = SessionUser.objects.department_notification_sms;
|
||||
const hasUpdatePermission = () => {
|
||||
return (SessionUser.hasPermission('department_notification_sms_update') || SessionUser.canAccessSuperUser());
|
||||
};
|
||||
|
||||
const enabledSavingIds = ref(new Set());
|
||||
|
||||
const isEnabled = (value) => {
|
||||
return value === true || value === 1 || value === "1" || value === "true";
|
||||
};
|
||||
|
||||
const isEnabledSaving = (id) => {
|
||||
return enabledSavingIds.value.has(Number(id));
|
||||
};
|
||||
|
||||
const setEnabledSaving = (id, saving) => {
|
||||
const nextIds = new Set(enabledSavingIds.value);
|
||||
const normalizedId = Number(id);
|
||||
if (saving) {
|
||||
nextIds.add(normalizedId);
|
||||
} else {
|
||||
nextIds.delete(normalizedId);
|
||||
}
|
||||
enabledSavingIds.value = nextIds;
|
||||
};
|
||||
|
||||
const toggleEnabled = async (obj, event) => {
|
||||
const target = event?.target;
|
||||
const previousValue = isEnabled(obj.enabled);
|
||||
|
||||
if (!target || !hasUpdatePermission() || isEnabledSaving(obj.id)) {
|
||||
if (target) {
|
||||
target.checked = previousValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const nextValue = Boolean(target.checked);
|
||||
setEnabledSaving(obj.id, true);
|
||||
|
||||
try {
|
||||
await su_object.set.enabled(obj.id, nextValue);
|
||||
await props.loadList();
|
||||
} catch (error) {
|
||||
target.checked = previousValue;
|
||||
console.error('Unable to update department notification SMS active state', error);
|
||||
} finally {
|
||||
setEnabledSaving(obj.id, false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,7 +91,7 @@ const hasUpdatePermission = () => {
|
||||
<!-- Label -->
|
||||
<EditableTableColumn
|
||||
:object="obj"
|
||||
:loadList="loadList"
|
||||
:loadList="props.loadList"
|
||||
:editFunction="su_object.showEditObjectFieldForm"
|
||||
column="label"
|
||||
:permission-check-function="hasUpdatePermission"
|
||||
@@ -50,7 +99,7 @@ const hasUpdatePermission = () => {
|
||||
<!-- Phone country code -->
|
||||
<EditableTableColumn
|
||||
:object="obj"
|
||||
:loadList="loadList"
|
||||
:loadList="props.loadList"
|
||||
:editFunction="su_object.showEditObjectFieldForm"
|
||||
column="phone_country_code"
|
||||
:permission-check-function="hasUpdatePermission"
|
||||
@@ -58,22 +107,27 @@ const hasUpdatePermission = () => {
|
||||
<!-- Phone -->
|
||||
<EditableTableColumn
|
||||
:object="obj"
|
||||
:loadList="loadList"
|
||||
:loadList="props.loadList"
|
||||
:editFunction="su_object.showEditObjectFieldForm"
|
||||
column="phone"
|
||||
:permission-check-function="hasUpdatePermission"
|
||||
/>
|
||||
<!-- Enabled -->
|
||||
<EditableTableColumn
|
||||
:object="obj"
|
||||
:loadList="loadList"
|
||||
:editFunction="su_object.showEditObjectFieldForm"
|
||||
column="enabled"
|
||||
:parse-function="(value) => {
|
||||
return value ? t('common.yes') : t('common.no');
|
||||
}"
|
||||
:permission-check-function="hasUpdatePermission"
|
||||
/>
|
||||
<td>
|
||||
<input
|
||||
:id="`department-notification-sms-enabled-${obj.id}`"
|
||||
class="switch is-rounded is-small"
|
||||
type="checkbox"
|
||||
:checked="isEnabled(obj.enabled)"
|
||||
:disabled="!hasUpdatePermission() || isEnabledSaving(obj.id)"
|
||||
:data-testid="`department-notification-sms-enabled-toggle-${obj.id}`"
|
||||
:aria-label="`${su_object.columns.enabled.label}: ${obj.label}`"
|
||||
@change="toggleEnabled(obj, $event)"
|
||||
/>
|
||||
<label :for="`department-notification-sms-enabled-${obj.id}`">
|
||||
{{ isEnabled(obj.enabled) ? t('common.yes') : t('common.no') }}
|
||||
</label>
|
||||
</td>
|
||||
<!-- Actions -->
|
||||
<td>
|
||||
<ActionSettingsWheelButton class="is-float-right">
|
||||
@@ -81,9 +135,10 @@ const hasUpdatePermission = () => {
|
||||
<RequiresPermission :permission="'department_notification_sms_delete'">
|
||||
<template #default>
|
||||
<ActionSettingsWheelItem
|
||||
@click="su_object.functions.showDeleteObjectForm(obj.id, loadList)"
|
||||
@click="su_object.functions.showDeleteObjectForm(obj.id, props.loadList)"
|
||||
:icon="SessionUser.objects.global.icons.delete"
|
||||
:label="t('global.delete')"
|
||||
:test-id="`department-notification-sms-delete-${obj.id}`"
|
||||
template="danger"
|
||||
/>
|
||||
</template>
|
||||
@@ -91,6 +146,7 @@ const hasUpdatePermission = () => {
|
||||
<ActionSettingsWheelItem
|
||||
:icon="SessionUser.objects.global.icons.delete"
|
||||
:label="t('global.delete')"
|
||||
:test-id="`department-notification-sms-delete-${obj.id}`"
|
||||
template="danger"
|
||||
disabled
|
||||
/>
|
||||
@@ -111,4 +167,4 @@ const hasUpdatePermission = () => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ if (router.currentRoute.value.params.departmentId) {
|
||||
<span>{{ t('common.add') }} {{ SessionUser.objects.department_notification_sms.meta.labels.single }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<DepartmentNotificationsPhoneTable :objects="list" />
|
||||
<DepartmentNotificationsPhoneTable :objects="list" :load-list="loadList" />
|
||||
</TableLabeledPagination>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ import UserOtherVaskeabonnement
|
||||
import Swal from "sweetalert2";
|
||||
import OrderItemsTable from "@/components/displays/department/pos/order/orderItemsTable.vue";
|
||||
import OrderContentTable from "@/components/displays/superuser/tables/OrderContentTable.vue";
|
||||
import {
|
||||
buildMultiMonthInvoiceContext,
|
||||
MULTI_MONTH_INVOICE_ACTION,
|
||||
promptMultiMonthInvoiceWarning,
|
||||
} from "@/services/invoiceMonthSplitWarning.js";
|
||||
|
||||
const props = defineProps({
|
||||
orders: {
|
||||
@@ -126,32 +131,34 @@ const isAnyOrderSelected = () => {
|
||||
return selectedInvoiceCollections.value.length > 0;
|
||||
}
|
||||
|
||||
const getSelectedOrders = () => {
|
||||
return props.orders.filter((order) => selectedInvoiceCollections.value.includes(order.invoice_collection_id));
|
||||
}
|
||||
|
||||
/** Invoice collections */
|
||||
const onInvoiceCollections = async () => {
|
||||
// Check if any orders are selected
|
||||
if (selectedInvoiceCollections.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
/** Create the invoice */
|
||||
const onCreateInvoiceDraft = async () => {
|
||||
// Create the invoice
|
||||
console.log('Create invoice');
|
||||
await SessionUser.objects.collectedOrderInvoices.functions.economic.invoice(parseInt(props.collectedOrderInvoice.id)).then((response) => {
|
||||
console.log('Invoice created successfully', response);
|
||||
Swal.fire({
|
||||
title: 'Fakturaen er oprettet',
|
||||
text: 'Fakturaen er oprettet i E-conomic',
|
||||
icon: 'success',
|
||||
showConfirmButton: false,
|
||||
timer: 2000
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.log('Error creating invoice', error);
|
||||
}
|
||||
)
|
||||
const selectedOrders = getSelectedOrders();
|
||||
const invoiceWarningContext = buildMultiMonthInvoiceContext(selectedOrders, {
|
||||
getDate: (order) => order?.created_at ?? order?.date,
|
||||
getInvoiceCollectionId: (order) => order?.invoice_collection_id,
|
||||
});
|
||||
const invoiceWarningAction = await promptMultiMonthInvoiceWarning({
|
||||
context: invoiceWarningContext,
|
||||
splitByMonth: SessionUser.objects.collectedOrderInvoices.functions.split_by_month,
|
||||
parseErrorMessage: SessionUser.functions.parseErrorMessage,
|
||||
});
|
||||
if (invoiceWarningAction === MULTI_MONTH_INVOICE_ACTION.SPLIT) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
if (invoiceWarningAction === MULTI_MONTH_INVOICE_ACTION.CANCEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < selectedInvoiceCollections.value.length; i++) {
|
||||
const selectedInvoiceCollectionId = selectedInvoiceCollections.value[i];
|
||||
// Check if the invoice collection is already booked
|
||||
@@ -210,6 +217,7 @@ const isOrderContentVisible = (order) => {
|
||||
class="button is-small"
|
||||
@click="onInvoiceCollections()"
|
||||
:disabled="!isAnyOrderSelected()"
|
||||
data-testid="invoice-order-table-invoice-button"
|
||||
>
|
||||
{{ $t('global.invoice_now') }}
|
||||
</button>
|
||||
@@ -274,4 +282,4 @@ const isOrderContentVisible = (order) => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -444,10 +444,14 @@ export const CollectedOrderInvoices = {
|
||||
});
|
||||
},
|
||||
split_by_month: async (dateFrom, dateTo, options = {}) => {
|
||||
const invoiceCollectionIds = Array.isArray(options.invoiceCollectionIds)
|
||||
? options.invoiceCollectionIds
|
||||
: options.invoice_collection_ids;
|
||||
return authenticatedRequest('/collected-invoices/split-by-month', 'POST', {
|
||||
dateFrom,
|
||||
dateTo,
|
||||
...(options.preview !== undefined ? { preview: !!options.preview } : {}),
|
||||
...(Array.isArray(invoiceCollectionIds) ? { invoice_collection_ids: invoiceCollectionIds } : {}),
|
||||
}).then((response) => {
|
||||
console.log(response);
|
||||
return response;
|
||||
|
||||
@@ -620,6 +620,13 @@ export const ObjectsGlobal = {
|
||||
function getLockedValue(column, defaultValue = '') {
|
||||
return lockedValues[column] !== undefined ? lockedValues[column] : '';
|
||||
}
|
||||
function getCreationDefaultValue(column, defaultValue = '') {
|
||||
return columns[column].creation.default !== undefined ? columns[column].creation.default : defaultValue;
|
||||
}
|
||||
function isBooleanDefault(column, value) {
|
||||
const defaultValue = getCreationDefaultValue(column, true);
|
||||
return String(defaultValue) === String(value);
|
||||
}
|
||||
// Add the form fields
|
||||
for (const column in columns) {
|
||||
if (columns[column].creation.required) {
|
||||
@@ -647,8 +654,8 @@ export const ObjectsGlobal = {
|
||||
<!-- Select -->
|
||||
<div class="select is-fullwidth">
|
||||
<select id="${column}" class="has-background-light has-text-black">
|
||||
<option value="true">Ja</option>
|
||||
<option value="false">Nej</option>
|
||||
<option value="true" ${isBooleanDefault(column, true) ? 'selected' : ''}>Ja</option>
|
||||
<option value="false" ${isBooleanDefault(column, false) ? 'selected' : ''}>Nej</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -97,6 +97,7 @@ export const Vehicles = {
|
||||
sortable: true,
|
||||
creation: {
|
||||
required: true,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
customer_id: {
|
||||
@@ -117,7 +118,7 @@ export const Vehicles = {
|
||||
},
|
||||
};
|
||||
},
|
||||
add: async (type, reg, wash_subscription, customer_id, reference = null) => {
|
||||
add: async (type, reg, wash_subscription = false, customer_id, reference = null) => {
|
||||
return ObjectsGlobal.add.object(
|
||||
Vehicles.meta.endpoint,
|
||||
{
|
||||
|
||||
@@ -22,7 +22,12 @@ export const getUsersVehicles = async () => {
|
||||
}
|
||||
|
||||
export const addVehicleUser = async (reg, type, notes) => {
|
||||
return await authenticatedRequest("/user/vehicles", "POST", {reg, type, notes})
|
||||
return await authenticatedRequest("/vehicles", "POST", {
|
||||
reg,
|
||||
type: parseInt(type),
|
||||
wash_subscription: false,
|
||||
...(notes ? { notes } : {}),
|
||||
})
|
||||
.then(
|
||||
(response) => {
|
||||
vehicles.value.push(response.data.data);
|
||||
@@ -35,4 +40,4 @@ export const addVehicleUser = async (reg, type, notes) => {
|
||||
);
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1239,6 +1239,130 @@
|
||||
"load_employees": "@:{'templates.generated.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'templates.generated.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'templates.generated.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'templates.generated.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'templates.generated.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'templates.generated.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'templates.generated.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'templates.generated.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'templates.generated.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -3904,6 +4028,15 @@
|
||||
"preview_title": "@:{'words.generated.forhandsvis'} @:{'words.generated.manedsopdeling'}",
|
||||
"success_text": "@:{'words.generated.behandlede'} {processed} @:{'words.generated.fakturasamlinger'}. Opdelte {changed} @:{'words.generated.og'} sprang {skipped} @:{'words.generated.over'}.",
|
||||
"success_title": "@.capitalize:{'words.generated.manedsopdeling'} @:{'words.generated.fuldført'}"
|
||||
},
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Fakturer sammen",
|
||||
"split_by_month": "Opdel efter måned",
|
||||
"split_error_title": "Månedsopdeling mislykkedes",
|
||||
"split_success_text": "Behandlede {processed} fakturasamlinger. Opdelte {changed} og sprang {skipped} over.",
|
||||
"split_success_title": "Månedsopdeling fuldført",
|
||||
"text": "Du er ved at fakturere ordrer fra flere måneder sammen ({months}). Skal de i stedet opdeles efter måned?",
|
||||
"title": "Ordrer fra flere måneder"
|
||||
}
|
||||
},
|
||||
"invoicing": {
|
||||
@@ -4722,6 +4855,97 @@
|
||||
"subtitle": "@.capitalize:{'words.generated.administreret'} @:{'words.replication.services.database'}-, @:{'words.replication.services.redis'}- @:{'words.generated.og'} @:{'words.replication.services.minio'}-@:{'words.generated.replikering'}"
|
||||
},
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Adgangsomrade",
|
||||
"actions": {
|
||||
"clear_all": "Ryd gruppe",
|
||||
"reload": "Genindlaes",
|
||||
"retry": "Prov igen",
|
||||
"select_all": "Vaelg gruppe"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktiveret",
|
||||
"permission": "Tilladelse",
|
||||
"route": "Rute",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Giver adgang til {name}.",
|
||||
"empty": "Der er ingen tilladelser tilgaengelige.",
|
||||
"empty_group": "Denne gruppe har ingen tilladelser.",
|
||||
"errors": {
|
||||
"load": "Rolletilladelser kunne ikke indlaeses.",
|
||||
"update": "En eller flere tilladelser kunne ikke opdateres. Rollens aktuelle tilladelser blev genindlaest."
|
||||
},
|
||||
"filtered_summary": "{count} matcher",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Sidetype- og afdelingsadgang.",
|
||||
"label": "Adgang"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Tilladelser til bookinger, tidsbookinger og planlaegning.",
|
||||
"label": "Bookinger"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Tilladelser til kunder, brugere, medarbejdere, roller og login.",
|
||||
"label": "Kunder og brugere"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Afdelingsdrift, baner, relaer, notifikationer, rapporter og mal.",
|
||||
"label": "Afdelinger og drift"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Tilladelser til fakturaer, faktureringsperioder, opkraevning og e-conomic.",
|
||||
"label": "Fakturering og e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Tilladelser til ordrer, POS, betalinger og ordrelinjer.",
|
||||
"label": "Ordrer og POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Tilladelser, der ikke matcher en anden gruppe.",
|
||||
"label": "Andet"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Tilladelser til produkter, priser, rabatter og valuta.",
|
||||
"label": "Produkter og priser"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Tilladelser til selvbetjening, vask, scannere, gateways og edge-udstyr.",
|
||||
"label": "Selvbetjening og edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, konfiguration, release, replikering og integrationer.",
|
||||
"label": "System og konfiguration"
|
||||
}
|
||||
},
|
||||
"no_description": "Der er ingen beskrivelse.",
|
||||
"no_search_results": "Ingen tilladelser matcher sogningen.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Giver adgang til administrationssider.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Giver adgang til superbrugersider.",
|
||||
"label": "Superbruger"
|
||||
},
|
||||
"user": {
|
||||
"description": "Giver adgang til brugersider.",
|
||||
"label": "Bruger"
|
||||
}
|
||||
},
|
||||
"search_label": "Sog i tilladelser",
|
||||
"search_placeholder": "Sog pa label, nogle, beskrivelse, rute eller metode",
|
||||
"selected_summary": "{selected} af {total} valgt",
|
||||
"status": {
|
||||
"disabled": "Deaktiveret",
|
||||
"enabled": "Aktiveret"
|
||||
},
|
||||
"subtitle": "Rolletilladelser",
|
||||
"title": "Tilladelser for rolle {id}",
|
||||
"title_with_name": "Tilladelser for {name}"
|
||||
},
|
||||
"subtitle": "@.capitalize:{'words.generated.brugerroller'}"
|
||||
},
|
||||
"self_wash": {
|
||||
@@ -5844,6 +6068,130 @@
|
||||
"load_employees": "@.capitalize:{'words.generated.medarbejdere'} @:{'words.generated.kunne'} @:{'words.generated.ikke'} @:{'words.generated.indlæses'}.",
|
||||
"save_employee": "@.capitalize:{'words.generated.medarbejderen'} @:{'words.generated.kunne'} @:{'words.generated.ikke'} @:{'words.generated.gemmes'}.",
|
||||
"deactivate_employee": "@.capitalize:{'words.generated.medarbejderen'} @:{'words.generated.kunne'} @:{'words.generated.ikke'} deaktiveres."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rolletilladelser",
|
||||
"tooltip": "Vis rolletilladelser",
|
||||
"intro": "Roller samler de præcise funktioner, der tildeles en medarbejder, når rollen gemmes.",
|
||||
"scope": "Alle funktioner er begrænset til de afdelinger, der vælges for medarbejderen.",
|
||||
"selected": "Valgt",
|
||||
"close": "Luk",
|
||||
"unavailable": "Tilladelsesdetaljer er ikke tilgængelige for denne rolle, før rollekataloget er opdateret.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Ordrer",
|
||||
"bookings": "Bookinger",
|
||||
"time_bookings": "Tidsbookinger",
|
||||
"reports": "Rapporter",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Øvrigt"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Log ind",
|
||||
"description": "Giver medarbejderen adgang til at logge ind i Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Se egne tilladelser",
|
||||
"description": "Viser medarbejderen, hvilke adgangsrettigheder kontoen har."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Se ordrer",
|
||||
"description": "Kan åbne ordrelister og ordredetaljer for tildelte afdelinger."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Opret ordrer",
|
||||
"description": "Kan oprette nye ordrer for tildelte afdelinger."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Rediger ordrer",
|
||||
"description": "Kan ændre ordredetaljer for tildelte afdelinger."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Slet ordrer",
|
||||
"description": "Kan slette ordrer i tildelte afdelinger."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Se ordrelinjer",
|
||||
"description": "Kan se produkter, ydelser og priser på ordrer."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Opret ordrelinjer",
|
||||
"description": "Kan tilføje produkter og ydelser til ordrer."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Rediger ordrelinjer",
|
||||
"description": "Kan ændre produkter, antal og priser på ordrer."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Slet ordrelinjer",
|
||||
"description": "Kan fjerne produkter og ydelser fra ordrer."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Opkræv ordrer",
|
||||
"description": "Kan tage betaling eller opkræve en betalingsreservation for en ordre."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Se afdelingsbookinger",
|
||||
"description": "Kan se bookinger for tildelte afdelinger."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Se egne bookinger",
|
||||
"description": "Kan se bookingantal og bookinger knyttet til medarbejderkontoen."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Rediger bookinger",
|
||||
"description": "Kan ændre tilladte bookingdetaljer."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Opret bookinger",
|
||||
"description": "Kan oprette nye bookinger for tildelte afdelinger."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Afslut bookinger",
|
||||
"description": "Kan markere bookinger som afsluttet."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Gensend bookingbekræftelser",
|
||||
"description": "Kan gensende bookingbekræftelser og afslutningsmails."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Se tidsbookinger",
|
||||
"description": "Kan gennemse tidsbookingposter for tildelte afdelinger."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Opret tidsbookinger",
|
||||
"description": "Kan oprette nye tidsbookingposter."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Rediger tidsbookinger",
|
||||
"description": "Kan redigere tidsbookingposter, når backend-ruten understøtter det."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Se ordrestatistik",
|
||||
"description": "Kan åbne statistikvisninger for ordrer i tildelte afdelinger."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Se bookingstatistik",
|
||||
"description": "Kan åbne statistikvisninger for bookinger i tildelte afdelinger."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Åbn begrænset backoffice",
|
||||
"description": "Kan tilgå det begrænsede backofficeområde."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Administrer afdelingspriser",
|
||||
"description": "Kan redigere eksplicitte priser for tildelte afdelinger."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Administrer medarbejderadgang",
|
||||
"description": "Kan oprette, redigere og deaktivere begrænsede backofficemedarbejdere."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Ekstra funktion",
|
||||
"description": "Denne funktion kommer fra rollekataloget, men er endnu ikke beskrevet i grænsefladen."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1350,6 +1350,130 @@
|
||||
"load_employees": "@:{'templates.generated.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'templates.generated.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'templates.generated.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'templates.generated.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'templates.generated.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'templates.generated.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'templates.generated.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'templates.generated.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'templates.generated.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -4015,6 +4139,15 @@
|
||||
"preview_title": "Preview @:{'words.generated.monthly'} @:{'words.generated.split'}",
|
||||
"success_text": "@:{'words.generated.processed'} {processed} @:{'words.generated.invoice'} @:{'words.generated.collections'}. @.capitalize:{'words.generated.split'} {changed}, @:{'words.generated.skipped'} {skipped}.",
|
||||
"success_title": "@.capitalize:{'words.generated.monthly'} @:{'words.generated.split'} @:{'words.generated.completed'}"
|
||||
},
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Zusammen abrechnen",
|
||||
"split_by_month": "Nach Monat aufteilen",
|
||||
"split_error_title": "Monatsaufteilung fehlgeschlagen",
|
||||
"split_success_text": "{processed} Rechnungssammlungen verarbeitet. {changed} aufgeteilt, {skipped} übersprungen.",
|
||||
"split_success_title": "Monatsaufteilung abgeschlossen",
|
||||
"text": "Sie sind dabei, Aufträge aus mehreren Monaten gemeinsam abzurechnen ({months}). Sollen sie stattdessen nach Monat aufgeteilt werden?",
|
||||
"title": "Aufträge aus mehreren Monaten"
|
||||
}
|
||||
},
|
||||
"invoicing": {
|
||||
@@ -4833,6 +4966,97 @@
|
||||
"subtitle": "@.capitalize:{'words.generated.managed'} @:{'words.generated.database'} @:{'words.generated.and'} @:{'words.replication.services.redis'} @:{'words.generated.replication'}"
|
||||
},
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Zugriffsbereich",
|
||||
"actions": {
|
||||
"clear_all": "Gruppe leeren",
|
||||
"reload": "Neu laden",
|
||||
"retry": "Erneut versuchen",
|
||||
"select_all": "Gruppe auswaehlen"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktiviert",
|
||||
"permission": "Berechtigung",
|
||||
"route": "Route",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Erlaubt Zugriff auf {name}.",
|
||||
"empty": "Es sind keine Berechtigungen verfuegbar.",
|
||||
"empty_group": "Diese Gruppe hat keine Berechtigungen.",
|
||||
"errors": {
|
||||
"load": "Rollenberechtigungen konnten nicht geladen werden.",
|
||||
"update": "Eine oder mehrere Berechtigungen konnten nicht aktualisiert werden. Die aktuellen Rollenberechtigungen wurden neu geladen."
|
||||
},
|
||||
"filtered_summary": "{count} Treffer",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Seitentyp- und Abteilungszugriff.",
|
||||
"label": "Zugriff"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Berechtigungen fuer Buchungen, Zeitbuchungen und Planung.",
|
||||
"label": "Buchungen"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Berechtigungen fuer Kunden, Benutzer, Mitarbeiter, Rollen und Anmeldung.",
|
||||
"label": "Kunden und Benutzer"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Abteilungsbetrieb, Spuren, Relais, Benachrichtigungen, Berichte und Ziele.",
|
||||
"label": "Abteilungen und Betrieb"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Berechtigungen fuer Rechnungen, Abrechnungsperioden, Inkasso und e-conomic.",
|
||||
"label": "Rechnungen und e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Berechtigungen fuer Auftraege, POS, Zahlungen und Auftragszeilen.",
|
||||
"label": "Auftraege und POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Berechtigungen, die keiner anderen Gruppe entsprechen.",
|
||||
"label": "Sonstige"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Berechtigungen fuer Produkte, Preise, Rabatte und Waehrungen.",
|
||||
"label": "Produkte und Preise"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Berechtigungen fuer Self-Service, Waschen, Scanner, Gateways und Edge-Hardware.",
|
||||
"label": "Self-Service und Edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, Konfiguration, Release, Replikation und Integrationen.",
|
||||
"label": "System und Konfiguration"
|
||||
}
|
||||
},
|
||||
"no_description": "Keine Beschreibung verfuegbar.",
|
||||
"no_search_results": "Keine Berechtigungen entsprechen der Suche.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Erlaubt Zugriff auf Admin-Dashboardseiten.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Erlaubt Zugriff auf Superuser-Dashboardseiten.",
|
||||
"label": "Superuser"
|
||||
},
|
||||
"user": {
|
||||
"description": "Erlaubt Zugriff auf Benutzer-Dashboardseiten.",
|
||||
"label": "Benutzer"
|
||||
}
|
||||
},
|
||||
"search_label": "Berechtigungen suchen",
|
||||
"search_placeholder": "Nach Label, Schluessel, Beschreibung, Route oder Methode suchen",
|
||||
"selected_summary": "{selected} von {total} ausgewaehlt",
|
||||
"status": {
|
||||
"disabled": "Deaktiviert",
|
||||
"enabled": "Aktiviert"
|
||||
},
|
||||
"subtitle": "Rollenberechtigungen",
|
||||
"title": "Berechtigungen fuer Rolle {id}",
|
||||
"title_with_name": "Berechtigungen fuer {name}"
|
||||
},
|
||||
"subtitle": "Benutzerrollen"
|
||||
},
|
||||
"self_wash": {
|
||||
@@ -5955,6 +6179,130 @@
|
||||
"load_employees": "@:{'words.generated.mitarbeiter'} @:{'words.generated.konnten'} @:{'words.generated.nicht'} @:{'words.generated.geladen'} @:{'words.generated.werden'}.",
|
||||
"save_employee": "@:{'words.generated.mitarbeiter'} @:{'words.generated.konnte'} @:{'words.generated.nicht'} @:{'words.generated.gespeichert'} @:{'words.generated.werden'}.",
|
||||
"deactivate_employee": "@:{'words.generated.mitarbeiter'} @:{'words.generated.konnte'} @:{'words.generated.nicht'} @:{'words.generated.deaktiviert'} @:{'words.generated.werden'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rollenberechtigungen",
|
||||
"tooltip": "Rollenberechtigungen anzeigen",
|
||||
"intro": "Rollen bündeln die genauen Funktionen, die einem Mitarbeiter beim Speichern der Rolle zugewiesen werden.",
|
||||
"scope": "Jede Funktion ist auf die Abteilungen begrenzt, die für den Mitarbeiter ausgewählt sind.",
|
||||
"selected": "Ausgewählt",
|
||||
"close": "Schließen",
|
||||
"unavailable": "Berechtigungsdetails sind für diese Rolle erst verfügbar, wenn der Rollenkatalog aktualisiert wurde.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Aufträge",
|
||||
"bookings": "Buchungen",
|
||||
"time_bookings": "Zeitbuchungen",
|
||||
"reports": "Berichte",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Sonstiges"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Anmelden",
|
||||
"description": "Erlaubt dem Mitarbeiter, sich bei Pleno anzumelden."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Eigene Berechtigungen anzeigen",
|
||||
"description": "Zeigt dem Mitarbeiter, welche Zugriffsrechte das Konto hat."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Aufträge anzeigen",
|
||||
"description": "Kann Auftragslisten und Auftragsdetails für zugewiesene Abteilungen öffnen."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Aufträge erstellen",
|
||||
"description": "Kann neue Aufträge für zugewiesene Abteilungen erstellen."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Aufträge bearbeiten",
|
||||
"description": "Kann Auftragsdetails für zugewiesene Abteilungen ändern."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Aufträge löschen",
|
||||
"description": "Kann Aufträge in zugewiesenen Abteilungen löschen."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Auftragspositionen anzeigen",
|
||||
"description": "Kann Produkte, Leistungen und Preise auf Aufträgen sehen."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Auftragspositionen erstellen",
|
||||
"description": "Kann Produkte und Leistungen zu Aufträgen hinzufügen."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Auftragspositionen bearbeiten",
|
||||
"description": "Kann Produkte, Mengen und Preise auf Aufträgen ändern."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Auftragspositionen löschen",
|
||||
"description": "Kann Produkte und Leistungen aus Aufträgen entfernen."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Aufträge abrechnen",
|
||||
"description": "Kann Zahlungen annehmen oder eine Zahlungsreservierung für einen Auftrag belasten."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Abteilungsbuchungen anzeigen",
|
||||
"description": "Kann Buchungen für zugewiesene Abteilungen sehen."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Eigene Buchungen anzeigen",
|
||||
"description": "Kann Buchungszahlen und Buchungen sehen, die mit dem Mitarbeiterkonto verbunden sind."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Buchungen bearbeiten",
|
||||
"description": "Kann erlaubte Buchungsdetails ändern."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Buchungen erstellen",
|
||||
"description": "Kann neue Buchungen für zugewiesene Abteilungen erstellen."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Buchungen abschließen",
|
||||
"description": "Kann Buchungen als abgeschlossen markieren."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Buchungsbestätigungen erneut senden",
|
||||
"description": "Kann Buchungsbestätigungen und Abschluss-E-Mails erneut senden."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Zeitbuchungen anzeigen",
|
||||
"description": "Kann Zeitbuchungseinträge für zugewiesene Abteilungen prüfen."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Zeitbuchungen erstellen",
|
||||
"description": "Kann neue Zeitbuchungseinträge hinzufügen."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Zeitbuchungen bearbeiten",
|
||||
"description": "Kann Zeitbuchungseinträge bearbeiten, wenn die Backend-Route dies unterstützt."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Auftragsstatistik anzeigen",
|
||||
"description": "Kann Auftragsstatistiken für zugewiesene Abteilungen öffnen."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Buchungsstatistik anzeigen",
|
||||
"description": "Kann Buchungsstatistiken für zugewiesene Abteilungen öffnen."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Begrenztes Backoffice öffnen",
|
||||
"description": "Kann den begrenzten Backofficebereich öffnen."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Abteilungspreise verwalten",
|
||||
"description": "Kann explizite Preise für zugewiesene Abteilungen bearbeiten."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Mitarbeiterzugang verwalten",
|
||||
"description": "Kann begrenzte Backoffice-Mitarbeiter erstellen, bearbeiten und deaktivieren."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Zusätzliche Funktion",
|
||||
"description": "Diese Funktion kommt aus dem Rollenkatalog, ist aber in der Oberfläche noch nicht beschrieben."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1071,6 +1071,130 @@
|
||||
"load_employees": "@:{'templates.generated.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'templates.generated.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'templates.generated.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'templates.generated.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'templates.generated.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'templates.generated.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'templates.generated.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'templates.generated.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'templates.generated.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -3736,6 +3860,15 @@
|
||||
"preview_title": "@.capitalize:{'words.generated.preview'} @:{'words.generated.monthly'} @:{'words.generated.split'}",
|
||||
"success_text": "@:{'words.generated.processed'} {processed} @:{'words.generated.invoice'} @:{'words.generated.collections'}. @.capitalize:{'words.generated.split'} {changed}, @:{'words.generated.skipped'} {skipped}.",
|
||||
"success_title": "@.capitalize:{'words.generated.monthly'} @:{'words.generated.split'} @:{'words.generated.completed'}"
|
||||
},
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Invoice together",
|
||||
"split_by_month": "Split by month",
|
||||
"split_error_title": "Monthly split failed",
|
||||
"split_success_text": "Processed {processed} invoice collections. Split {changed}, skipped {skipped}.",
|
||||
"split_success_title": "Monthly split completed",
|
||||
"text": "You are about to invoice orders from multiple months together ({months}). Should they be split by month instead?",
|
||||
"title": "Orders from multiple months"
|
||||
}
|
||||
},
|
||||
"invoicing": {
|
||||
@@ -4554,6 +4687,97 @@
|
||||
"subtitle": "@.capitalize:{'words.generated.managed'} @:{'words.replication.services.database'}, @:{'words.replication.services.redis'}, @:{'words.generated.and'} @:{'words.replication.services.minio'} @:{'words.generated.replication'}"
|
||||
},
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Access scope",
|
||||
"actions": {
|
||||
"clear_all": "Clear group",
|
||||
"reload": "Reload",
|
||||
"retry": "Try again",
|
||||
"select_all": "Select group"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Enabled",
|
||||
"permission": "Permission",
|
||||
"route": "Route",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Allows access to {name}.",
|
||||
"empty": "No permissions are available.",
|
||||
"empty_group": "This group has no permissions.",
|
||||
"errors": {
|
||||
"load": "Could not load role permissions.",
|
||||
"update": "Could not update one or more permissions. The current role permissions were reloaded."
|
||||
},
|
||||
"filtered_summary": "{count} matching",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Page type and department access permissions.",
|
||||
"label": "Access"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Booking, time booking, and planning permissions.",
|
||||
"label": "Bookings"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Customer, user, employee, role, and authentication permissions.",
|
||||
"label": "Customers and users"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Department operations, lanes, relays, notifications, reports, and goals.",
|
||||
"label": "Departments and operations"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Invoice, billing period, collection, and e-conomic permissions.",
|
||||
"label": "Invoicing and e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Order, POS, payment, and order line permissions.",
|
||||
"label": "Orders and POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Permissions that do not match another group.",
|
||||
"label": "Other"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Product, price, discount, and currency permissions.",
|
||||
"label": "Products and pricing"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Self-serve, wash, scanner, gateway, and edge hardware permissions.",
|
||||
"label": "Self-serve and edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, configuration, release, replication, and integration permissions.",
|
||||
"label": "System and configuration"
|
||||
}
|
||||
},
|
||||
"no_description": "No description is available.",
|
||||
"no_search_results": "No permissions match the current search.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Allows access to admin dashboard pages.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Allows access to superuser dashboard pages.",
|
||||
"label": "Superuser"
|
||||
},
|
||||
"user": {
|
||||
"description": "Allows access to user dashboard pages.",
|
||||
"label": "User"
|
||||
}
|
||||
},
|
||||
"search_label": "Search permissions",
|
||||
"search_placeholder": "Search by label, key, description, route, or method",
|
||||
"selected_summary": "{selected} of {total} selected",
|
||||
"status": {
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled"
|
||||
},
|
||||
"subtitle": "Role permissions",
|
||||
"title": "Role {id} permissions",
|
||||
"title_with_name": "{name} permissions"
|
||||
},
|
||||
"subtitle": "@.capitalize:{'words.generated.user'} @:{'words.generated.roles'}"
|
||||
},
|
||||
"self_wash": {
|
||||
@@ -5676,6 +5900,130 @@
|
||||
"load_employees": "@.capitalize:{'words.generated.could'} @:{'words.generated.not'} @:{'words.generated.load'} @:{'words.generated.employees'}.",
|
||||
"save_employee": "@.capitalize:{'words.generated.could'} @:{'words.generated.not'} @:{'words.generated.save'} @:{'words.generated.employee'}.",
|
||||
"deactivate_employee": "@.capitalize:{'words.generated.could'} @:{'words.generated.not'} @:{'words.generated.deactivate'} @:{'words.generated.employee'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Role permissions",
|
||||
"tooltip": "View role permissions",
|
||||
"intro": "Roles combine the exact capabilities assigned to an employee when the role is saved.",
|
||||
"scope": "Every capability is limited to the departments selected for the employee.",
|
||||
"selected": "Selected",
|
||||
"close": "Close",
|
||||
"unavailable": "Permission details are unavailable for this role until the role catalog has been updated.",
|
||||
"groups": {
|
||||
"account": "Account",
|
||||
"orders": "Orders",
|
||||
"bookings": "Bookings",
|
||||
"time_bookings": "Time bookings",
|
||||
"reports": "Reports",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Other"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Sign in",
|
||||
"description": "Allows the employee to sign in to Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "View own permissions",
|
||||
"description": "Shows the employee which access rights their account has."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "View orders",
|
||||
"description": "Can open order lists and order details for assigned departments."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Create orders",
|
||||
"description": "Can create new orders for assigned departments."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Edit orders",
|
||||
"description": "Can update order details for assigned departments."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Delete orders",
|
||||
"description": "Can delete orders in assigned departments."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "View order lines",
|
||||
"description": "Can see products, services, and prices on orders."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Create order lines",
|
||||
"description": "Can add products and services to orders."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Edit order lines",
|
||||
"description": "Can change products, quantities, and prices on orders."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Delete order lines",
|
||||
"description": "Can remove products and services from orders."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Charge orders",
|
||||
"description": "Can take payment or charge a payment intent for an order."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "View department bookings",
|
||||
"description": "Can see bookings for assigned departments."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "View own bookings",
|
||||
"description": "Can see booking counts and bookings tied to the employee account."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Edit bookings",
|
||||
"description": "Can change permitted booking details."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Create bookings",
|
||||
"description": "Can create new bookings for assigned departments."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Complete bookings",
|
||||
"description": "Can mark bookings as completed."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Resend booking confirmations",
|
||||
"description": "Can resend booking confirmation and completion emails."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "View time booking entries",
|
||||
"description": "Can inspect time-booking entries for assigned departments."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Create time booking entries",
|
||||
"description": "Can add new time-booking entries."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Edit time booking entries",
|
||||
"description": "Can edit time-booking entries when the backend route supports it."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "View order statistics",
|
||||
"description": "Can open order statistics views for assigned departments."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "View booking statistics",
|
||||
"description": "Can open booking statistics views for assigned departments."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Open limited backoffice",
|
||||
"description": "Can access the limited backoffice area."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Manage department prices",
|
||||
"description": "Can edit explicit prices for assigned departments."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Manage employee access",
|
||||
"description": "Can create, edit, and deactivate limited backoffice employees."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Additional capability",
|
||||
"description": "This capability is provided by the role catalog but is not yet described in the interface."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3149,6 +3149,15 @@
|
||||
"preview_title": "@:{'templates.generated.compat.invoicing_period.monthly_split.preview_title'}",
|
||||
"success_text": "@:{'templates.generated.compat.invoicing_period.monthly_split.success_text'}",
|
||||
"success_title": "@:{'templates.generated.compat.invoicing_period.monthly_split.success_title'}"
|
||||
},
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.invoice_together'}",
|
||||
"split_by_month": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.split_by_month'}",
|
||||
"split_error_title": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.split_error_title'}",
|
||||
"split_success_text": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.split_success_text'}",
|
||||
"split_success_title": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.split_success_title'}",
|
||||
"text": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.text'}",
|
||||
"title": "@:{'templates.generated.compat.invoicing_period.multi_month_invoice_warning.title'}"
|
||||
}
|
||||
},
|
||||
"invoicing": {
|
||||
@@ -4255,6 +4264,97 @@
|
||||
"title": "@:replication.labels.replication"
|
||||
},
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "@:{'templates.generated.compat.roles.permissions.access_scope'}",
|
||||
"actions": {
|
||||
"clear_all": "@:{'templates.generated.compat.roles.permissions.actions.clear_all'}",
|
||||
"reload": "@:{'templates.generated.compat.roles.permissions.actions.reload'}",
|
||||
"retry": "@:{'templates.generated.compat.roles.permissions.actions.retry'}",
|
||||
"select_all": "@:{'templates.generated.compat.roles.permissions.actions.select_all'}"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "@:{'templates.generated.compat.roles.permissions.columns.enabled'}",
|
||||
"permission": "@:{'templates.generated.compat.roles.permissions.columns.permission'}",
|
||||
"route": "@:{'templates.generated.compat.roles.permissions.columns.route'}",
|
||||
"status": "@:{'templates.generated.compat.roles.permissions.columns.status'}"
|
||||
},
|
||||
"department_access_description": "@:{'templates.generated.compat.roles.permissions.department_access_description'}",
|
||||
"empty": "@:{'templates.generated.compat.roles.permissions.empty'}",
|
||||
"empty_group": "@:{'templates.generated.compat.roles.permissions.empty_group'}",
|
||||
"errors": {
|
||||
"load": "@:{'templates.generated.compat.roles.permissions.errors.load'}",
|
||||
"update": "@:{'templates.generated.compat.roles.permissions.errors.update'}"
|
||||
},
|
||||
"filtered_summary": "@:{'templates.generated.compat.roles.permissions.filtered_summary'}",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.access.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.access.label'}"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.bookings.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.bookings.label'}"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.customers_users.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.customers_users.label'}"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.departments_operations.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.departments_operations.label'}"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.invoicing_economic.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.invoicing_economic.label'}"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.orders_pos.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.orders_pos.label'}"
|
||||
},
|
||||
"other": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.other.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.other.label'}"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.products_pricing.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.products_pricing.label'}"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.selfserve_edge.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.selfserve_edge.label'}"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.groups.system_config.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.groups.system_config.label'}"
|
||||
}
|
||||
},
|
||||
"no_description": "@:{'templates.generated.compat.roles.permissions.no_description'}",
|
||||
"no_search_results": "@:{'templates.generated.compat.roles.permissions.no_search_results'}",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.page_access.admin.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.page_access.admin.label'}"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.page_access.superuser.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.page_access.superuser.label'}"
|
||||
},
|
||||
"user": {
|
||||
"description": "@:{'templates.generated.compat.roles.permissions.page_access.user.description'}",
|
||||
"label": "@:{'templates.generated.compat.roles.permissions.page_access.user.label'}"
|
||||
}
|
||||
},
|
||||
"search_label": "@:{'templates.generated.compat.roles.permissions.search_label'}",
|
||||
"search_placeholder": "@:{'templates.generated.compat.roles.permissions.search_placeholder'}",
|
||||
"selected_summary": "@:{'templates.generated.compat.roles.permissions.selected_summary'}",
|
||||
"status": {
|
||||
"disabled": "@:{'templates.generated.compat.roles.permissions.status.disabled'}",
|
||||
"enabled": "@:{'templates.generated.compat.roles.permissions.status.enabled'}"
|
||||
},
|
||||
"subtitle": "@:{'templates.generated.compat.roles.permissions.subtitle'}",
|
||||
"title": "@:{'templates.generated.compat.roles.permissions.title'}",
|
||||
"title_with_name": "@:{'templates.generated.compat.roles.permissions.title_with_name'}"
|
||||
},
|
||||
"subtitle": "@:{'templates.generated.compat.roles.subtitle'}",
|
||||
"title": "@:{'templates.generated.compat.common.roles'}"
|
||||
},
|
||||
|
||||
@@ -1353,6 +1353,130 @@
|
||||
"load_employees": "@:{'templates.generated.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'templates.generated.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'templates.generated.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'templates.generated.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'templates.generated.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'templates.generated.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'templates.generated.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'templates.generated.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'templates.generated.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -4018,6 +4142,15 @@
|
||||
"preview_title": "Preview @:{'words.generated.monthly'} @:{'words.generated.split'}",
|
||||
"success_text": "@:{'words.generated.processed'} {processed} @:{'words.generated.invoice'} @:{'words.generated.collections'}. @.capitalize:{'words.generated.split'} {changed}, @:{'words.generated.skipped'} {skipped}.",
|
||||
"success_title": "@.capitalize:{'words.generated.monthly'} @:{'words.generated.split'} @:{'words.generated.completed'}"
|
||||
},
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Fakturer samlet",
|
||||
"split_by_month": "Del opp etter måned",
|
||||
"split_error_title": "Månedsdeling mislyktes",
|
||||
"split_success_text": "Behandlet {processed} fakturasamlinger. Delte opp {changed}, hoppet over {skipped}.",
|
||||
"split_success_title": "Månedsdeling fullført",
|
||||
"text": "Du er i ferd med å fakturere ordrer fra flere måneder samlet ({months}). Skal de i stedet deles opp etter måned?",
|
||||
"title": "Ordrer fra flere måneder"
|
||||
}
|
||||
},
|
||||
"invoicing": {
|
||||
@@ -4836,6 +4969,97 @@
|
||||
"subtitle": "@.capitalize:{'words.generated.managed'} @:{'words.replication.services.database'} @:{'words.generated.and'} @:{'words.replication.services.redis'} @:{'words.generated.replication'}"
|
||||
},
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Tilgangsomrade",
|
||||
"actions": {
|
||||
"clear_all": "Tomm gruppe",
|
||||
"reload": "Last inn pa nytt",
|
||||
"retry": "Prov igjen",
|
||||
"select_all": "Velg gruppe"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktivert",
|
||||
"permission": "Tillatelse",
|
||||
"route": "Rute",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Gir tilgang til {name}.",
|
||||
"empty": "Ingen tillatelser er tilgjengelige.",
|
||||
"empty_group": "Denne gruppen har ingen tillatelser.",
|
||||
"errors": {
|
||||
"load": "Kunne ikke laste rolletillatelser.",
|
||||
"update": "En eller flere tillatelser kunne ikke oppdateres. De gjeldende rolletillatelsene ble lastet inn pa nytt."
|
||||
},
|
||||
"filtered_summary": "{count} treff",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Sidetype- og avdelingstilgang.",
|
||||
"label": "Tilgang"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Tillatelser for bookinger, tidsbookinger og planlegging.",
|
||||
"label": "Bookinger"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Tillatelser for kunder, brukere, ansatte, roller og innlogging.",
|
||||
"label": "Kunder og brukere"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Avdelingsdrift, baner, releer, varsler, rapporter og mal.",
|
||||
"label": "Avdelinger og drift"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Tillatelser for fakturaer, faktureringsperioder, innkreving og e-conomic.",
|
||||
"label": "Fakturering og e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Tillatelser for ordrer, POS, betalinger og ordrelinjer.",
|
||||
"label": "Ordrer og POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Tillatelser som ikke matcher en annen gruppe.",
|
||||
"label": "Annet"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Tillatelser for produkter, priser, rabatter og valuta.",
|
||||
"label": "Produkter og priser"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Tillatelser for selvbetjening, vask, skannere, gatewayer og edge-utstyr.",
|
||||
"label": "Selvbetjening og edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, konfigurasjon, release, replikering og integrasjoner.",
|
||||
"label": "System og konfigurasjon"
|
||||
}
|
||||
},
|
||||
"no_description": "Ingen beskrivelse er tilgjengelig.",
|
||||
"no_search_results": "Ingen tillatelser matcher soket.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Gir tilgang til administrasjonssider.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Gir tilgang til superbrukersider.",
|
||||
"label": "Superbruker"
|
||||
},
|
||||
"user": {
|
||||
"description": "Gir tilgang til brukersider.",
|
||||
"label": "Bruker"
|
||||
}
|
||||
},
|
||||
"search_label": "Sok i tillatelser",
|
||||
"search_placeholder": "Sok pa label, nokkel, beskrivelse, rute eller metode",
|
||||
"selected_summary": "{selected} av {total} valgt",
|
||||
"status": {
|
||||
"disabled": "Deaktivert",
|
||||
"enabled": "Aktivert"
|
||||
},
|
||||
"subtitle": "Rolletillatelser",
|
||||
"title": "Tillatelser for rolle {id}",
|
||||
"title_with_name": "Tillatelser for {name}"
|
||||
},
|
||||
"subtitle": "@.capitalize:{'words.generated.user'} roles"
|
||||
},
|
||||
"self_wash": {
|
||||
@@ -5958,6 +6182,130 @@
|
||||
"load_employees": "@.capitalize:{'words.generated.kunne'} @:{'words.generated.ikke'} @:{'words.generated.laste'} @:{'words.generated.medarbeidere'}.",
|
||||
"save_employee": "@.capitalize:{'words.generated.kunne'} @:{'words.generated.ikke'} @:{'words.generated.lagre'} @:{'words.generated.medarbeideren'}.",
|
||||
"deactivate_employee": "@.capitalize:{'words.generated.kunne'} @:{'words.generated.ikke'} deaktivere @:{'words.generated.medarbeideren'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rolletillatelser",
|
||||
"tooltip": "Vis rolletillatelser",
|
||||
"intro": "Roller samler de nøyaktige funksjonene som tildeles en ansatt når rollen lagres.",
|
||||
"scope": "Alle funksjoner er begrenset til avdelingene som velges for den ansatte.",
|
||||
"selected": "Valgt",
|
||||
"close": "Lukk",
|
||||
"unavailable": "Tillatelsesdetaljer er ikke tilgjengelige for denne rollen før rollekatalogen er oppdatert.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Ordrer",
|
||||
"bookings": "Bookinger",
|
||||
"time_bookings": "Tidsbookinger",
|
||||
"reports": "Rapporter",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Annet"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Logg inn",
|
||||
"description": "Lar den ansatte logge inn i Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Se egne tillatelser",
|
||||
"description": "Viser den ansatte hvilke tilgangsrettigheter kontoen har."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Se ordrer",
|
||||
"description": "Kan åpne ordrelister og ordredetaljer for tildelte avdelinger."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Opprett ordrer",
|
||||
"description": "Kan opprette nye ordrer for tildelte avdelinger."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Rediger ordrer",
|
||||
"description": "Kan endre ordredetaljer for tildelte avdelinger."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Slett ordrer",
|
||||
"description": "Kan slette ordrer i tildelte avdelinger."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Se ordrelinjer",
|
||||
"description": "Kan se produkter, tjenester og priser på ordrer."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Opprett ordrelinjer",
|
||||
"description": "Kan legge produkter og tjenester til ordrer."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Rediger ordrelinjer",
|
||||
"description": "Kan endre produkter, antall og priser på ordrer."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Slett ordrelinjer",
|
||||
"description": "Kan fjerne produkter og tjenester fra ordrer."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Belast ordrer",
|
||||
"description": "Kan ta betaling eller belaste en betalingsreservasjon for en ordre."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Se avdelingsbookinger",
|
||||
"description": "Kan se bookinger for tildelte avdelinger."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Se egne bookinger",
|
||||
"description": "Kan se bookingantall og bookinger knyttet til ansattkontoen."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Rediger bookinger",
|
||||
"description": "Kan endre tillatte bookingdetaljer."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Opprett bookinger",
|
||||
"description": "Kan opprette nye bookinger for tildelte avdelinger."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Fullfør bookinger",
|
||||
"description": "Kan markere bookinger som fullført."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Send bookingbekreftelser på nytt",
|
||||
"description": "Kan sende bookingbekreftelser og fullførings-e-poster på nytt."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Se tidsbookinger",
|
||||
"description": "Kan gå gjennom tidsbookingoppføringer for tildelte avdelinger."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Opprett tidsbookinger",
|
||||
"description": "Kan legge til nye tidsbookingoppføringer."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Rediger tidsbookinger",
|
||||
"description": "Kan redigere tidsbookingoppføringer når backend-ruten støtter det."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Se ordrestatistikk",
|
||||
"description": "Kan åpne ordrestatistikk for tildelte avdelinger."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Se bookingstatistikk",
|
||||
"description": "Kan åpne bookingstatistikk for tildelte avdelinger."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Åpne begrenset backoffice",
|
||||
"description": "Kan åpne det begrensede backofficeområdet."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Administrer avdelingspriser",
|
||||
"description": "Kan redigere eksplisitte priser for tildelte avdelinger."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Administrer ansattilgang",
|
||||
"description": "Kan opprette, redigere og deaktivere begrensede backofficeansatte."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Ekstra funksjon",
|
||||
"description": "Denne funksjonen kommer fra rollekatalogen, men er ennå ikke beskrevet i grensesnittet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1403,6 +1403,130 @@
|
||||
"load_employees": "@:{'templates.generated.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'templates.generated.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'templates.generated.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'templates.generated.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'templates.generated.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'templates.generated.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'templates.generated.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'templates.generated.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'templates.generated.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'templates.generated.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'templates.generated.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -4068,6 +4192,15 @@
|
||||
"preview_title": "Preview @:{'words.generated.monthly'} @:{'words.generated.split'}",
|
||||
"success_text": "@:{'words.generated.processed'} {processed} @:{'words.generated.invoice'} @:{'words.generated.collections'}. @.capitalize:{'words.generated.split'} {changed}, @:{'words.generated.skipped'} {skipped}.",
|
||||
"success_title": "@.capitalize:{'words.generated.monthly'} @:{'words.generated.split'} @:{'words.generated.completed'}"
|
||||
},
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Fakturera tillsammans",
|
||||
"split_by_month": "Dela upp per månad",
|
||||
"split_error_title": "Månadsuppdelning misslyckades",
|
||||
"split_success_text": "Bearbetade {processed} fakturasamlingar. Delade upp {changed}, hoppade över {skipped}.",
|
||||
"split_success_title": "Månadsuppdelning klar",
|
||||
"text": "Du håller på att fakturera ordrar från flera månader tillsammans ({months}). Ska de delas upp per månad i stället?",
|
||||
"title": "Ordrar från flera månader"
|
||||
}
|
||||
},
|
||||
"invoicing": {
|
||||
@@ -4886,6 +5019,97 @@
|
||||
"subtitle": "@.capitalize:{'words.generated.managed'} @:{'words.generated.database'} @:{'words.generated.and'} @:{'words.replication.services.redis'} @:{'words.generated.replication'}"
|
||||
},
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Atkomstomrade",
|
||||
"actions": {
|
||||
"clear_all": "Rensa grupp",
|
||||
"reload": "Ladda om",
|
||||
"retry": "Forsok igen",
|
||||
"select_all": "Valj grupp"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktiverad",
|
||||
"permission": "Behorighet",
|
||||
"route": "Rutt",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Ger atkomst till {name}.",
|
||||
"empty": "Inga behorigheter ar tillgangliga.",
|
||||
"empty_group": "Den har gruppen har inga behorigheter.",
|
||||
"errors": {
|
||||
"load": "Kunde inte lasa in rollbehorigheter.",
|
||||
"update": "En eller flera behorigheter kunde inte uppdateras. Rollens aktuella behorigheter laddades om."
|
||||
},
|
||||
"filtered_summary": "{count} traffar",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Sidtyp- och avdelningsatkomst.",
|
||||
"label": "Atkomst"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Behorigheter for bokningar, tidsbokningar och planering.",
|
||||
"label": "Bokningar"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Behorigheter for kunder, anvandare, medarbetare, roller och inloggning.",
|
||||
"label": "Kunder och anvandare"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Avdelningsdrift, banor, relan, aviseringar, rapporter och mal.",
|
||||
"label": "Avdelningar och drift"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Behorigheter for fakturor, faktureringsperioder, inkasso och e-conomic.",
|
||||
"label": "Fakturering och e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Behorigheter for ordrar, POS, betalningar och orderrader.",
|
||||
"label": "Ordrar och POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Behorigheter som inte matchar nagon annan grupp.",
|
||||
"label": "Ovrigt"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Behorigheter for produkter, priser, rabatter och valuta.",
|
||||
"label": "Produkter och priser"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Behorigheter for sjalvbetjaning, tvatt, skannrar, gateways och edge-utrustning.",
|
||||
"label": "Sjalvbetjaning och edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, konfiguration, release, replikering och integrationer.",
|
||||
"label": "System och konfiguration"
|
||||
}
|
||||
},
|
||||
"no_description": "Ingen beskrivning ar tillganglig.",
|
||||
"no_search_results": "Inga behorigheter matchar aktuell sokning.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Ger atkomst till administrationssidor.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Ger atkomst till superuser-sidor.",
|
||||
"label": "Superuser"
|
||||
},
|
||||
"user": {
|
||||
"description": "Ger atkomst till anvandarsidor.",
|
||||
"label": "Anvandare"
|
||||
}
|
||||
},
|
||||
"search_label": "Sok behorigheter",
|
||||
"search_placeholder": "Sok pa label, nyckel, beskrivning, rutt eller metod",
|
||||
"selected_summary": "{selected} av {total} valda",
|
||||
"status": {
|
||||
"disabled": "Inaktiverad",
|
||||
"enabled": "Aktiverad"
|
||||
},
|
||||
"subtitle": "Rollbehorigheter",
|
||||
"title": "Behorigheter for roll {id}",
|
||||
"title_with_name": "Behorigheter for {name}"
|
||||
},
|
||||
"subtitle": "@.capitalize:{'words.generated.user'} @:{'words.generated.roles'}"
|
||||
},
|
||||
"self_wash": {
|
||||
@@ -6008,6 +6232,130 @@
|
||||
"load_employees": "@.capitalize:{'words.generated.kunde'} @:{'words.generated.inte'} @:{'words.generated.ladda'} @:{'words.generated.medarbetare'}.",
|
||||
"save_employee": "@.capitalize:{'words.generated.kunde'} @:{'words.generated.inte'} @:{'words.generated.spara'} @:{'words.generated.medarbetaren'}.",
|
||||
"deactivate_employee": "@.capitalize:{'words.generated.kunde'} @:{'words.generated.inte'} @:{'words.generated.inaktivera'} @:{'words.generated.medarbetaren'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rollbehörigheter",
|
||||
"tooltip": "Visa rollbehörigheter",
|
||||
"intro": "Roller samlar de exakta funktioner som tilldelas en medarbetare när rollen sparas.",
|
||||
"scope": "Alla funktioner är begränsade till de avdelningar som väljs för medarbetaren.",
|
||||
"selected": "Vald",
|
||||
"close": "Stäng",
|
||||
"unavailable": "Behörighetsdetaljer är inte tillgängliga för den här rollen förrän rollkatalogen har uppdaterats.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Order",
|
||||
"bookings": "Bokningar",
|
||||
"time_bookings": "Tidsbokningar",
|
||||
"reports": "Rapporter",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Övrigt"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Logga in",
|
||||
"description": "Ger medarbetaren möjlighet att logga in i Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Se egna behörigheter",
|
||||
"description": "Visar medarbetaren vilka åtkomsträttigheter kontot har."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Se order",
|
||||
"description": "Kan öppna orderlistor och orderdetaljer för tilldelade avdelningar."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Skapa order",
|
||||
"description": "Kan skapa nya order för tilldelade avdelningar."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Redigera order",
|
||||
"description": "Kan ändra orderdetaljer för tilldelade avdelningar."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Ta bort order",
|
||||
"description": "Kan ta bort order i tilldelade avdelningar."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Se orderrader",
|
||||
"description": "Kan se produkter, tjänster och priser på order."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Skapa orderrader",
|
||||
"description": "Kan lägga till produkter och tjänster på order."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Redigera orderrader",
|
||||
"description": "Kan ändra produkter, antal och priser på order."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Ta bort orderrader",
|
||||
"description": "Kan ta bort produkter och tjänster från order."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Debitera order",
|
||||
"description": "Kan ta betalt eller debitera en betalningsreservation för en order."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Se avdelningsbokningar",
|
||||
"description": "Kan se bokningar för tilldelade avdelningar."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Se egna bokningar",
|
||||
"description": "Kan se bokningsantal och bokningar kopplade till medarbetarkontot."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Redigera bokningar",
|
||||
"description": "Kan ändra tillåtna bokningsdetaljer."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Skapa bokningar",
|
||||
"description": "Kan skapa nya bokningar för tilldelade avdelningar."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Slutför bokningar",
|
||||
"description": "Kan markera bokningar som slutförda."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Skicka bokningsbekräftelser igen",
|
||||
"description": "Kan skicka bokningsbekräftelser och slutförandemejl igen."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Se tidsbokningar",
|
||||
"description": "Kan granska tidsbokningsposter för tilldelade avdelningar."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Skapa tidsbokningar",
|
||||
"description": "Kan lägga till nya tidsbokningsposter."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Redigera tidsbokningar",
|
||||
"description": "Kan redigera tidsbokningsposter när backend-rutten stöder det."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Se orderstatistik",
|
||||
"description": "Kan öppna orderstatistik för tilldelade avdelningar."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Se bokningsstatistik",
|
||||
"description": "Kan öppna bokningsstatistik för tilldelade avdelningar."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Öppna begränsat backoffice",
|
||||
"description": "Kan öppna det begränsade backofficeområdet."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Hantera avdelningspriser",
|
||||
"description": "Kan redigera uttryckliga priser för tilldelade avdelningar."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Hantera medarbetaråtkomst",
|
||||
"description": "Kan skapa, redigera och inaktivera begränsade backofficemedarbetare."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Extra funktion",
|
||||
"description": "Den här funktionen kommer från rollkatalogen men är ännu inte beskriven i gränssnittet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Fakturer sammen",
|
||||
"split_by_month": "Opdel efter måned",
|
||||
"split_error_title": "Månedsopdeling mislykkedes",
|
||||
"split_success_text": "Behandlede {processed} fakturasamlinger. Opdelte {changed} og sprang {skipped} over.",
|
||||
"split_success_title": "Månedsopdeling fuldført",
|
||||
"text": "Du er ved at fakturere ordrer fra flere måneder sammen ({months}). Skal de i stedet opdeles efter måned?",
|
||||
"title": "Ordrer fra flere måneder"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"compat": {
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Adgangsomrade",
|
||||
"actions": {
|
||||
"clear_all": "Ryd gruppe",
|
||||
"reload": "Genindlaes",
|
||||
"retry": "Prov igen",
|
||||
"select_all": "Vaelg gruppe"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktiveret",
|
||||
"permission": "Tilladelse",
|
||||
"route": "Rute",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Giver adgang til {name}.",
|
||||
"empty": "Der er ingen tilladelser tilgaengelige.",
|
||||
"empty_group": "Denne gruppe har ingen tilladelser.",
|
||||
"errors": {
|
||||
"load": "Rolletilladelser kunne ikke indlaeses.",
|
||||
"update": "En eller flere tilladelser kunne ikke opdateres. Rollens aktuelle tilladelser blev genindlaest."
|
||||
},
|
||||
"filtered_summary": "{count} matcher",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Sidetype- og afdelingsadgang.",
|
||||
"label": "Adgang"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Tilladelser til bookinger, tidsbookinger og planlaegning.",
|
||||
"label": "Bookinger"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Tilladelser til kunder, brugere, medarbejdere, roller og login.",
|
||||
"label": "Kunder og brugere"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Afdelingsdrift, baner, relaer, notifikationer, rapporter og mal.",
|
||||
"label": "Afdelinger og drift"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Tilladelser til fakturaer, faktureringsperioder, opkraevning og e-conomic.",
|
||||
"label": "Fakturering og e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Tilladelser til ordrer, POS, betalinger og ordrelinjer.",
|
||||
"label": "Ordrer og POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Tilladelser, der ikke matcher en anden gruppe.",
|
||||
"label": "Andet"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Tilladelser til produkter, priser, rabatter og valuta.",
|
||||
"label": "Produkter og priser"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Tilladelser til selvbetjening, vask, scannere, gateways og edge-udstyr.",
|
||||
"label": "Selvbetjening og edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, konfiguration, release, replikering og integrationer.",
|
||||
"label": "System og konfiguration"
|
||||
}
|
||||
},
|
||||
"no_description": "Der er ingen beskrivelse.",
|
||||
"no_search_results": "Ingen tilladelser matcher sogningen.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Giver adgang til administrationssider.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Giver adgang til superbrugersider.",
|
||||
"label": "Superbruger"
|
||||
},
|
||||
"user": {
|
||||
"description": "Giver adgang til brugersider.",
|
||||
"label": "Bruger"
|
||||
}
|
||||
},
|
||||
"search_label": "Sog i tilladelser",
|
||||
"search_placeholder": "Sog pa label, nogle, beskrivelse, rute eller metode",
|
||||
"selected_summary": "{selected} af {total} valgt",
|
||||
"status": {
|
||||
"disabled": "Deaktiveret",
|
||||
"enabled": "Aktiveret"
|
||||
},
|
||||
"subtitle": "Rolletilladelser",
|
||||
"title": "Tilladelser for rolle {id}",
|
||||
"title_with_name": "Tilladelser for {name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,130 @@
|
||||
"load_employees": "@:{'phrases.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'phrases.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'phrases.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'phrases.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'phrases.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'phrases.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'phrases.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'phrases.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'phrases.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -204,6 +328,130 @@
|
||||
"load_employees": "@.capitalize:{'terms.glossary.medarbejdere'} @:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} @:{'terms.glossary.indlæses'}.",
|
||||
"save_employee": "@.capitalize:{'terms.glossary.medarbejderen'} @:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} @:{'terms.glossary.gemmes'}.",
|
||||
"deactivate_employee": "@.capitalize:{'terms.glossary.medarbejderen'} @:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} deaktiveres."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rolletilladelser",
|
||||
"tooltip": "Vis rolletilladelser",
|
||||
"intro": "Roller samler de præcise funktioner, der tildeles en medarbejder, når rollen gemmes.",
|
||||
"scope": "Alle funktioner er begrænset til de afdelinger, der vælges for medarbejderen.",
|
||||
"selected": "Valgt",
|
||||
"close": "Luk",
|
||||
"unavailable": "Tilladelsesdetaljer er ikke tilgængelige for denne rolle, før rollekataloget er opdateret.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Ordrer",
|
||||
"bookings": "Bookinger",
|
||||
"time_bookings": "Tidsbookinger",
|
||||
"reports": "Rapporter",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Øvrigt"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Log ind",
|
||||
"description": "Giver medarbejderen adgang til at logge ind i Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Se egne tilladelser",
|
||||
"description": "Viser medarbejderen, hvilke adgangsrettigheder kontoen har."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Se ordrer",
|
||||
"description": "Kan åbne ordrelister og ordredetaljer for tildelte afdelinger."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Opret ordrer",
|
||||
"description": "Kan oprette nye ordrer for tildelte afdelinger."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Rediger ordrer",
|
||||
"description": "Kan ændre ordredetaljer for tildelte afdelinger."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Slet ordrer",
|
||||
"description": "Kan slette ordrer i tildelte afdelinger."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Se ordrelinjer",
|
||||
"description": "Kan se produkter, ydelser og priser på ordrer."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Opret ordrelinjer",
|
||||
"description": "Kan tilføje produkter og ydelser til ordrer."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Rediger ordrelinjer",
|
||||
"description": "Kan ændre produkter, antal og priser på ordrer."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Slet ordrelinjer",
|
||||
"description": "Kan fjerne produkter og ydelser fra ordrer."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Opkræv ordrer",
|
||||
"description": "Kan tage betaling eller opkræve en betalingsreservation for en ordre."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Se afdelingsbookinger",
|
||||
"description": "Kan se bookinger for tildelte afdelinger."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Se egne bookinger",
|
||||
"description": "Kan se bookingantal og bookinger knyttet til medarbejderkontoen."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Rediger bookinger",
|
||||
"description": "Kan ændre tilladte bookingdetaljer."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Opret bookinger",
|
||||
"description": "Kan oprette nye bookinger for tildelte afdelinger."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Afslut bookinger",
|
||||
"description": "Kan markere bookinger som afsluttet."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Gensend bookingbekræftelser",
|
||||
"description": "Kan gensende bookingbekræftelser og afslutningsmails."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Se tidsbookinger",
|
||||
"description": "Kan gennemse tidsbookingposter for tildelte afdelinger."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Opret tidsbookinger",
|
||||
"description": "Kan oprette nye tidsbookingposter."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Rediger tidsbookinger",
|
||||
"description": "Kan redigere tidsbookingposter, når backend-ruten understøtter det."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Se ordrestatistik",
|
||||
"description": "Kan åbne statistikvisninger for ordrer i tildelte afdelinger."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Se bookingstatistik",
|
||||
"description": "Kan åbne statistikvisninger for bookinger i tildelte afdelinger."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Åbn begrænset backoffice",
|
||||
"description": "Kan tilgå det begrænsede backofficeområde."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Administrer afdelingspriser",
|
||||
"description": "Kan redigere eksplicitte priser for tildelte afdelinger."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Administrer medarbejderadgang",
|
||||
"description": "Kan oprette, redigere og deaktivere begrænsede backofficemedarbejdere."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Ekstra funktion",
|
||||
"description": "Denne funktion kommer fra rollekataloget, men er endnu ikke beskrevet i grænsefladen."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Zusammen abrechnen",
|
||||
"split_by_month": "Nach Monat aufteilen",
|
||||
"split_error_title": "Monatsaufteilung fehlgeschlagen",
|
||||
"split_success_text": "{processed} Rechnungssammlungen verarbeitet. {changed} aufgeteilt, {skipped} übersprungen.",
|
||||
"split_success_title": "Monatsaufteilung abgeschlossen",
|
||||
"text": "Sie sind dabei, Aufträge aus mehreren Monaten gemeinsam abzurechnen ({months}). Sollen sie stattdessen nach Monat aufgeteilt werden?",
|
||||
"title": "Aufträge aus mehreren Monaten"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"compat": {
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Zugriffsbereich",
|
||||
"actions": {
|
||||
"clear_all": "Gruppe leeren",
|
||||
"reload": "Neu laden",
|
||||
"retry": "Erneut versuchen",
|
||||
"select_all": "Gruppe auswaehlen"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktiviert",
|
||||
"permission": "Berechtigung",
|
||||
"route": "Route",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Erlaubt Zugriff auf {name}.",
|
||||
"empty": "Es sind keine Berechtigungen verfuegbar.",
|
||||
"empty_group": "Diese Gruppe hat keine Berechtigungen.",
|
||||
"errors": {
|
||||
"load": "Rollenberechtigungen konnten nicht geladen werden.",
|
||||
"update": "Eine oder mehrere Berechtigungen konnten nicht aktualisiert werden. Die aktuellen Rollenberechtigungen wurden neu geladen."
|
||||
},
|
||||
"filtered_summary": "{count} Treffer",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Seitentyp- und Abteilungszugriff.",
|
||||
"label": "Zugriff"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Berechtigungen fuer Buchungen, Zeitbuchungen und Planung.",
|
||||
"label": "Buchungen"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Berechtigungen fuer Kunden, Benutzer, Mitarbeiter, Rollen und Anmeldung.",
|
||||
"label": "Kunden und Benutzer"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Abteilungsbetrieb, Spuren, Relais, Benachrichtigungen, Berichte und Ziele.",
|
||||
"label": "Abteilungen und Betrieb"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Berechtigungen fuer Rechnungen, Abrechnungsperioden, Inkasso und e-conomic.",
|
||||
"label": "Rechnungen und e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Berechtigungen fuer Auftraege, POS, Zahlungen und Auftragszeilen.",
|
||||
"label": "Auftraege und POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Berechtigungen, die keiner anderen Gruppe entsprechen.",
|
||||
"label": "Sonstige"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Berechtigungen fuer Produkte, Preise, Rabatte und Waehrungen.",
|
||||
"label": "Produkte und Preise"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Berechtigungen fuer Self-Service, Waschen, Scanner, Gateways und Edge-Hardware.",
|
||||
"label": "Self-Service und Edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, Konfiguration, Release, Replikation und Integrationen.",
|
||||
"label": "System und Konfiguration"
|
||||
}
|
||||
},
|
||||
"no_description": "Keine Beschreibung verfuegbar.",
|
||||
"no_search_results": "Keine Berechtigungen entsprechen der Suche.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Erlaubt Zugriff auf Admin-Dashboardseiten.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Erlaubt Zugriff auf Superuser-Dashboardseiten.",
|
||||
"label": "Superuser"
|
||||
},
|
||||
"user": {
|
||||
"description": "Erlaubt Zugriff auf Benutzer-Dashboardseiten.",
|
||||
"label": "Benutzer"
|
||||
}
|
||||
},
|
||||
"search_label": "Berechtigungen suchen",
|
||||
"search_placeholder": "Nach Label, Schluessel, Beschreibung, Route oder Methode suchen",
|
||||
"selected_summary": "{selected} von {total} ausgewaehlt",
|
||||
"status": {
|
||||
"disabled": "Deaktiviert",
|
||||
"enabled": "Aktiviert"
|
||||
},
|
||||
"subtitle": "Rollenberechtigungen",
|
||||
"title": "Berechtigungen fuer Rolle {id}",
|
||||
"title_with_name": "Berechtigungen fuer {name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,130 @@
|
||||
"load_employees": "@:{'phrases.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'phrases.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'phrases.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'phrases.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'phrases.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'phrases.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'phrases.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'phrases.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'phrases.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -204,6 +328,130 @@
|
||||
"load_employees": "@:{'terms.glossary.mitarbeiter'} @:{'terms.glossary.konnten'} @:{'terms.glossary.nicht'} @:{'terms.glossary.geladen'} @:{'terms.glossary.werden'}.",
|
||||
"save_employee": "@:{'terms.glossary.mitarbeiter'} @:{'terms.glossary.konnte'} @:{'terms.glossary.nicht'} @:{'terms.glossary.gespeichert'} @:{'terms.glossary.werden'}.",
|
||||
"deactivate_employee": "@:{'terms.glossary.mitarbeiter'} @:{'terms.glossary.konnte'} @:{'terms.glossary.nicht'} @:{'terms.glossary.deaktiviert'} @:{'terms.glossary.werden'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rollenberechtigungen",
|
||||
"tooltip": "Rollenberechtigungen anzeigen",
|
||||
"intro": "Rollen bündeln die genauen Funktionen, die einem Mitarbeiter beim Speichern der Rolle zugewiesen werden.",
|
||||
"scope": "Jede Funktion ist auf die Abteilungen begrenzt, die für den Mitarbeiter ausgewählt sind.",
|
||||
"selected": "Ausgewählt",
|
||||
"close": "Schließen",
|
||||
"unavailable": "Berechtigungsdetails sind für diese Rolle erst verfügbar, wenn der Rollenkatalog aktualisiert wurde.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Aufträge",
|
||||
"bookings": "Buchungen",
|
||||
"time_bookings": "Zeitbuchungen",
|
||||
"reports": "Berichte",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Sonstiges"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Anmelden",
|
||||
"description": "Erlaubt dem Mitarbeiter, sich bei Pleno anzumelden."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Eigene Berechtigungen anzeigen",
|
||||
"description": "Zeigt dem Mitarbeiter, welche Zugriffsrechte das Konto hat."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Aufträge anzeigen",
|
||||
"description": "Kann Auftragslisten und Auftragsdetails für zugewiesene Abteilungen öffnen."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Aufträge erstellen",
|
||||
"description": "Kann neue Aufträge für zugewiesene Abteilungen erstellen."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Aufträge bearbeiten",
|
||||
"description": "Kann Auftragsdetails für zugewiesene Abteilungen ändern."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Aufträge löschen",
|
||||
"description": "Kann Aufträge in zugewiesenen Abteilungen löschen."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Auftragspositionen anzeigen",
|
||||
"description": "Kann Produkte, Leistungen und Preise auf Aufträgen sehen."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Auftragspositionen erstellen",
|
||||
"description": "Kann Produkte und Leistungen zu Aufträgen hinzufügen."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Auftragspositionen bearbeiten",
|
||||
"description": "Kann Produkte, Mengen und Preise auf Aufträgen ändern."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Auftragspositionen löschen",
|
||||
"description": "Kann Produkte und Leistungen aus Aufträgen entfernen."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Aufträge abrechnen",
|
||||
"description": "Kann Zahlungen annehmen oder eine Zahlungsreservierung für einen Auftrag belasten."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Abteilungsbuchungen anzeigen",
|
||||
"description": "Kann Buchungen für zugewiesene Abteilungen sehen."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Eigene Buchungen anzeigen",
|
||||
"description": "Kann Buchungszahlen und Buchungen sehen, die mit dem Mitarbeiterkonto verbunden sind."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Buchungen bearbeiten",
|
||||
"description": "Kann erlaubte Buchungsdetails ändern."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Buchungen erstellen",
|
||||
"description": "Kann neue Buchungen für zugewiesene Abteilungen erstellen."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Buchungen abschließen",
|
||||
"description": "Kann Buchungen als abgeschlossen markieren."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Buchungsbestätigungen erneut senden",
|
||||
"description": "Kann Buchungsbestätigungen und Abschluss-E-Mails erneut senden."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Zeitbuchungen anzeigen",
|
||||
"description": "Kann Zeitbuchungseinträge für zugewiesene Abteilungen prüfen."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Zeitbuchungen erstellen",
|
||||
"description": "Kann neue Zeitbuchungseinträge hinzufügen."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Zeitbuchungen bearbeiten",
|
||||
"description": "Kann Zeitbuchungseinträge bearbeiten, wenn die Backend-Route dies unterstützt."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Auftragsstatistik anzeigen",
|
||||
"description": "Kann Auftragsstatistiken für zugewiesene Abteilungen öffnen."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Buchungsstatistik anzeigen",
|
||||
"description": "Kann Buchungsstatistiken für zugewiesene Abteilungen öffnen."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Begrenztes Backoffice öffnen",
|
||||
"description": "Kann den begrenzten Backofficebereich öffnen."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Abteilungspreise verwalten",
|
||||
"description": "Kann explizite Preise für zugewiesene Abteilungen bearbeiten."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Mitarbeiterzugang verwalten",
|
||||
"description": "Kann begrenzte Backoffice-Mitarbeiter erstellen, bearbeiten und deaktivieren."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Zusätzliche Funktion",
|
||||
"description": "Diese Funktion kommt aus dem Rollenkatalog, ist aber in der Oberfläche noch nicht beschrieben."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Invoice together",
|
||||
"split_by_month": "Split by month",
|
||||
"split_error_title": "Monthly split failed",
|
||||
"split_success_text": "Processed {processed} invoice collections. Split {changed}, skipped {skipped}.",
|
||||
"split_success_title": "Monthly split completed",
|
||||
"text": "You are about to invoice orders from multiple months together ({months}). Should they be split by month instead?",
|
||||
"title": "Orders from multiple months"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"compat": {
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Access scope",
|
||||
"actions": {
|
||||
"clear_all": "Clear group",
|
||||
"reload": "Reload",
|
||||
"retry": "Try again",
|
||||
"select_all": "Select group"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Enabled",
|
||||
"permission": "Permission",
|
||||
"route": "Route",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Allows access to {name}.",
|
||||
"empty": "No permissions are available.",
|
||||
"empty_group": "This group has no permissions.",
|
||||
"errors": {
|
||||
"load": "Could not load role permissions.",
|
||||
"update": "Could not update one or more permissions. The current role permissions were reloaded."
|
||||
},
|
||||
"filtered_summary": "{count} matching",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Page type and department access permissions.",
|
||||
"label": "Access"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Booking, time booking, and planning permissions.",
|
||||
"label": "Bookings"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Customer, user, employee, role, and authentication permissions.",
|
||||
"label": "Customers and users"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Department operations, lanes, relays, notifications, reports, and goals.",
|
||||
"label": "Departments and operations"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Invoice, billing period, collection, and e-conomic permissions.",
|
||||
"label": "Invoicing and e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Order, POS, payment, and order line permissions.",
|
||||
"label": "Orders and POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Permissions that do not match another group.",
|
||||
"label": "Other"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Product, price, discount, and currency permissions.",
|
||||
"label": "Products and pricing"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Self-serve, wash, scanner, gateway, and edge hardware permissions.",
|
||||
"label": "Self-serve and edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, configuration, release, replication, and integration permissions.",
|
||||
"label": "System and configuration"
|
||||
}
|
||||
},
|
||||
"no_description": "No description is available.",
|
||||
"no_search_results": "No permissions match the current search.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Allows access to admin dashboard pages.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Allows access to superuser dashboard pages.",
|
||||
"label": "Superuser"
|
||||
},
|
||||
"user": {
|
||||
"description": "Allows access to user dashboard pages.",
|
||||
"label": "User"
|
||||
}
|
||||
},
|
||||
"search_label": "Search permissions",
|
||||
"search_placeholder": "Search by label, key, description, route, or method",
|
||||
"selected_summary": "{selected} of {total} selected",
|
||||
"status": {
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled"
|
||||
},
|
||||
"subtitle": "Role permissions",
|
||||
"title": "Role {id} permissions",
|
||||
"title_with_name": "{name} permissions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,130 @@
|
||||
"load_employees": "@:{'phrases.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'phrases.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'phrases.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'phrases.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'phrases.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'phrases.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'phrases.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'phrases.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'phrases.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -204,6 +328,130 @@
|
||||
"load_employees": "@.capitalize:{'terms.glossary.could'} @:{'terms.glossary.not'} @:{'terms.glossary.load'} @:{'terms.glossary.employees'}.",
|
||||
"save_employee": "@.capitalize:{'terms.glossary.could'} @:{'terms.glossary.not'} @:{'terms.glossary.save'} @:{'terms.glossary.employee'}.",
|
||||
"deactivate_employee": "@.capitalize:{'terms.glossary.could'} @:{'terms.glossary.not'} @:{'terms.glossary.deactivate'} @:{'terms.glossary.employee'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Role permissions",
|
||||
"tooltip": "View role permissions",
|
||||
"intro": "Roles combine the exact capabilities assigned to an employee when the role is saved.",
|
||||
"scope": "Every capability is limited to the departments selected for the employee.",
|
||||
"selected": "Selected",
|
||||
"close": "Close",
|
||||
"unavailable": "Permission details are unavailable for this role until the role catalog has been updated.",
|
||||
"groups": {
|
||||
"account": "Account",
|
||||
"orders": "Orders",
|
||||
"bookings": "Bookings",
|
||||
"time_bookings": "Time bookings",
|
||||
"reports": "Reports",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Other"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Sign in",
|
||||
"description": "Allows the employee to sign in to Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "View own permissions",
|
||||
"description": "Shows the employee which access rights their account has."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "View orders",
|
||||
"description": "Can open order lists and order details for assigned departments."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Create orders",
|
||||
"description": "Can create new orders for assigned departments."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Edit orders",
|
||||
"description": "Can update order details for assigned departments."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Delete orders",
|
||||
"description": "Can delete orders in assigned departments."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "View order lines",
|
||||
"description": "Can see products, services, and prices on orders."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Create order lines",
|
||||
"description": "Can add products and services to orders."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Edit order lines",
|
||||
"description": "Can change products, quantities, and prices on orders."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Delete order lines",
|
||||
"description": "Can remove products and services from orders."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Charge orders",
|
||||
"description": "Can take payment or charge a payment intent for an order."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "View department bookings",
|
||||
"description": "Can see bookings for assigned departments."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "View own bookings",
|
||||
"description": "Can see booking counts and bookings tied to the employee account."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Edit bookings",
|
||||
"description": "Can change permitted booking details."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Create bookings",
|
||||
"description": "Can create new bookings for assigned departments."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Complete bookings",
|
||||
"description": "Can mark bookings as completed."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Resend booking confirmations",
|
||||
"description": "Can resend booking confirmation and completion emails."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "View time booking entries",
|
||||
"description": "Can inspect time-booking entries for assigned departments."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Create time booking entries",
|
||||
"description": "Can add new time-booking entries."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Edit time booking entries",
|
||||
"description": "Can edit time-booking entries when the backend route supports it."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "View order statistics",
|
||||
"description": "Can open order statistics views for assigned departments."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "View booking statistics",
|
||||
"description": "Can open booking statistics views for assigned departments."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Open limited backoffice",
|
||||
"description": "Can access the limited backoffice area."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Manage department prices",
|
||||
"description": "Can edit explicit prices for assigned departments."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Manage employee access",
|
||||
"description": "Can create, edit, and deactivate limited backoffice employees."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Additional capability",
|
||||
"description": "This capability is provided by the role catalog but is not yet described in the interface."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"invoicing_period": {
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.invoice_together'}",
|
||||
"split_by_month": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.split_by_month'}",
|
||||
"split_error_title": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.split_error_title'}",
|
||||
"split_success_text": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.split_success_text'}",
|
||||
"split_success_title": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.split_success_title'}",
|
||||
"text": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.text'}",
|
||||
"title": "@:{'phrases.compat.invoicing_period.multi_month_invoice_warning.title'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,96 @@
|
||||
{
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "@:{'phrases.compat.roles.permissions.access_scope'}",
|
||||
"actions": {
|
||||
"clear_all": "@:{'phrases.compat.roles.permissions.actions.clear_all'}",
|
||||
"reload": "@:{'phrases.compat.roles.permissions.actions.reload'}",
|
||||
"retry": "@:{'phrases.compat.roles.permissions.actions.retry'}",
|
||||
"select_all": "@:{'phrases.compat.roles.permissions.actions.select_all'}"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "@:{'phrases.compat.roles.permissions.columns.enabled'}",
|
||||
"permission": "@:{'phrases.compat.roles.permissions.columns.permission'}",
|
||||
"route": "@:{'phrases.compat.roles.permissions.columns.route'}",
|
||||
"status": "@:{'phrases.compat.roles.permissions.columns.status'}"
|
||||
},
|
||||
"department_access_description": "@:{'phrases.compat.roles.permissions.department_access_description'}",
|
||||
"empty": "@:{'phrases.compat.roles.permissions.empty'}",
|
||||
"empty_group": "@:{'phrases.compat.roles.permissions.empty_group'}",
|
||||
"errors": {
|
||||
"load": "@:{'phrases.compat.roles.permissions.errors.load'}",
|
||||
"update": "@:{'phrases.compat.roles.permissions.errors.update'}"
|
||||
},
|
||||
"filtered_summary": "@:{'phrases.compat.roles.permissions.filtered_summary'}",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.access.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.access.label'}"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.bookings.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.bookings.label'}"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.customers_users.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.customers_users.label'}"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.departments_operations.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.departments_operations.label'}"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.invoicing_economic.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.invoicing_economic.label'}"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.orders_pos.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.orders_pos.label'}"
|
||||
},
|
||||
"other": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.other.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.other.label'}"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.products_pricing.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.products_pricing.label'}"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.selfserve_edge.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.selfserve_edge.label'}"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.groups.system_config.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.groups.system_config.label'}"
|
||||
}
|
||||
},
|
||||
"no_description": "@:{'phrases.compat.roles.permissions.no_description'}",
|
||||
"no_search_results": "@:{'phrases.compat.roles.permissions.no_search_results'}",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.page_access.admin.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.page_access.admin.label'}"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.page_access.superuser.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.page_access.superuser.label'}"
|
||||
},
|
||||
"user": {
|
||||
"description": "@:{'phrases.compat.roles.permissions.page_access.user.description'}",
|
||||
"label": "@:{'phrases.compat.roles.permissions.page_access.user.label'}"
|
||||
}
|
||||
},
|
||||
"search_label": "@:{'phrases.compat.roles.permissions.search_label'}",
|
||||
"search_placeholder": "@:{'phrases.compat.roles.permissions.search_placeholder'}",
|
||||
"selected_summary": "@:{'phrases.compat.roles.permissions.selected_summary'}",
|
||||
"status": {
|
||||
"disabled": "@:{'phrases.compat.roles.permissions.status.disabled'}",
|
||||
"enabled": "@:{'phrases.compat.roles.permissions.status.enabled'}"
|
||||
},
|
||||
"subtitle": "@:{'phrases.compat.roles.permissions.subtitle'}",
|
||||
"title": "@:{'phrases.compat.roles.permissions.title'}",
|
||||
"title_with_name": "@:{'phrases.compat.roles.permissions.title_with_name'}"
|
||||
},
|
||||
"subtitle": "@:{'phrases.compat.roles.subtitle'}",
|
||||
"title": "@:{'phrases.compat.common.roles'}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Fakturer samlet",
|
||||
"split_by_month": "Del opp etter måned",
|
||||
"split_error_title": "Månedsdeling mislyktes",
|
||||
"split_success_text": "Behandlet {processed} fakturasamlinger. Delte opp {changed}, hoppet over {skipped}.",
|
||||
"split_success_title": "Månedsdeling fullført",
|
||||
"text": "Du er i ferd med å fakturere ordrer fra flere måneder samlet ({months}). Skal de i stedet deles opp etter måned?",
|
||||
"title": "Ordrer fra flere måneder"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"compat": {
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Tilgangsomrade",
|
||||
"actions": {
|
||||
"clear_all": "Tomm gruppe",
|
||||
"reload": "Last inn pa nytt",
|
||||
"retry": "Prov igjen",
|
||||
"select_all": "Velg gruppe"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktivert",
|
||||
"permission": "Tillatelse",
|
||||
"route": "Rute",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Gir tilgang til {name}.",
|
||||
"empty": "Ingen tillatelser er tilgjengelige.",
|
||||
"empty_group": "Denne gruppen har ingen tillatelser.",
|
||||
"errors": {
|
||||
"load": "Kunne ikke laste rolletillatelser.",
|
||||
"update": "En eller flere tillatelser kunne ikke oppdateres. De gjeldende rolletillatelsene ble lastet inn pa nytt."
|
||||
},
|
||||
"filtered_summary": "{count} treff",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Sidetype- og avdelingstilgang.",
|
||||
"label": "Tilgang"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Tillatelser for bookinger, tidsbookinger og planlegging.",
|
||||
"label": "Bookinger"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Tillatelser for kunder, brukere, ansatte, roller og innlogging.",
|
||||
"label": "Kunder og brukere"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Avdelingsdrift, baner, releer, varsler, rapporter og mal.",
|
||||
"label": "Avdelinger og drift"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Tillatelser for fakturaer, faktureringsperioder, innkreving og e-conomic.",
|
||||
"label": "Fakturering og e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Tillatelser for ordrer, POS, betalinger og ordrelinjer.",
|
||||
"label": "Ordrer og POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Tillatelser som ikke matcher en annen gruppe.",
|
||||
"label": "Annet"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Tillatelser for produkter, priser, rabatter og valuta.",
|
||||
"label": "Produkter og priser"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Tillatelser for selvbetjening, vask, skannere, gatewayer og edge-utstyr.",
|
||||
"label": "Selvbetjening og edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, konfigurasjon, release, replikering og integrasjoner.",
|
||||
"label": "System og konfigurasjon"
|
||||
}
|
||||
},
|
||||
"no_description": "Ingen beskrivelse er tilgjengelig.",
|
||||
"no_search_results": "Ingen tillatelser matcher soket.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Gir tilgang til administrasjonssider.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Gir tilgang til superbrukersider.",
|
||||
"label": "Superbruker"
|
||||
},
|
||||
"user": {
|
||||
"description": "Gir tilgang til brukersider.",
|
||||
"label": "Bruker"
|
||||
}
|
||||
},
|
||||
"search_label": "Sok i tillatelser",
|
||||
"search_placeholder": "Sok pa label, nokkel, beskrivelse, rute eller metode",
|
||||
"selected_summary": "{selected} av {total} valgt",
|
||||
"status": {
|
||||
"disabled": "Deaktivert",
|
||||
"enabled": "Aktivert"
|
||||
},
|
||||
"subtitle": "Rolletillatelser",
|
||||
"title": "Tillatelser for rolle {id}",
|
||||
"title_with_name": "Tillatelser for {name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,130 @@
|
||||
"load_employees": "@:{'phrases.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'phrases.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'phrases.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'phrases.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'phrases.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'phrases.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'phrases.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'phrases.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'phrases.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -204,6 +328,130 @@
|
||||
"load_employees": "@.capitalize:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} @:{'terms.glossary.laste'} @:{'terms.glossary.medarbeidere'}.",
|
||||
"save_employee": "@.capitalize:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} @:{'terms.glossary.lagre'} @:{'terms.glossary.medarbeideren'}.",
|
||||
"deactivate_employee": "@.capitalize:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} deaktivere @:{'terms.glossary.medarbeideren'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rolletillatelser",
|
||||
"tooltip": "Vis rolletillatelser",
|
||||
"intro": "Roller samler de nøyaktige funksjonene som tildeles en ansatt når rollen lagres.",
|
||||
"scope": "Alle funksjoner er begrenset til avdelingene som velges for den ansatte.",
|
||||
"selected": "Valgt",
|
||||
"close": "Lukk",
|
||||
"unavailable": "Tillatelsesdetaljer er ikke tilgjengelige for denne rollen før rollekatalogen er oppdatert.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Ordrer",
|
||||
"bookings": "Bookinger",
|
||||
"time_bookings": "Tidsbookinger",
|
||||
"reports": "Rapporter",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Annet"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Logg inn",
|
||||
"description": "Lar den ansatte logge inn i Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Se egne tillatelser",
|
||||
"description": "Viser den ansatte hvilke tilgangsrettigheter kontoen har."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Se ordrer",
|
||||
"description": "Kan åpne ordrelister og ordredetaljer for tildelte avdelinger."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Opprett ordrer",
|
||||
"description": "Kan opprette nye ordrer for tildelte avdelinger."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Rediger ordrer",
|
||||
"description": "Kan endre ordredetaljer for tildelte avdelinger."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Slett ordrer",
|
||||
"description": "Kan slette ordrer i tildelte avdelinger."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Se ordrelinjer",
|
||||
"description": "Kan se produkter, tjenester og priser på ordrer."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Opprett ordrelinjer",
|
||||
"description": "Kan legge produkter og tjenester til ordrer."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Rediger ordrelinjer",
|
||||
"description": "Kan endre produkter, antall og priser på ordrer."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Slett ordrelinjer",
|
||||
"description": "Kan fjerne produkter og tjenester fra ordrer."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Belast ordrer",
|
||||
"description": "Kan ta betaling eller belaste en betalingsreservasjon for en ordre."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Se avdelingsbookinger",
|
||||
"description": "Kan se bookinger for tildelte avdelinger."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Se egne bookinger",
|
||||
"description": "Kan se bookingantall og bookinger knyttet til ansattkontoen."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Rediger bookinger",
|
||||
"description": "Kan endre tillatte bookingdetaljer."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Opprett bookinger",
|
||||
"description": "Kan opprette nye bookinger for tildelte avdelinger."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Fullfør bookinger",
|
||||
"description": "Kan markere bookinger som fullført."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Send bookingbekreftelser på nytt",
|
||||
"description": "Kan sende bookingbekreftelser og fullførings-e-poster på nytt."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Se tidsbookinger",
|
||||
"description": "Kan gå gjennom tidsbookingoppføringer for tildelte avdelinger."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Opprett tidsbookinger",
|
||||
"description": "Kan legge til nye tidsbookingoppføringer."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Rediger tidsbookinger",
|
||||
"description": "Kan redigere tidsbookingoppføringer når backend-ruten støtter det."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Se ordrestatistikk",
|
||||
"description": "Kan åpne ordrestatistikk for tildelte avdelinger."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Se bookingstatistikk",
|
||||
"description": "Kan åpne bookingstatistikk for tildelte avdelinger."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Åpne begrenset backoffice",
|
||||
"description": "Kan åpne det begrensede backofficeområdet."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Administrer avdelingspriser",
|
||||
"description": "Kan redigere eksplisitte priser for tildelte avdelinger."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Administrer ansattilgang",
|
||||
"description": "Kan opprette, redigere og deaktivere begrensede backofficeansatte."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Ekstra funksjon",
|
||||
"description": "Denne funksjonen kommer fra rollekatalogen, men er ennå ikke beskrevet i grensesnittet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compat": {
|
||||
"invoicing_period": {
|
||||
"multi_month_invoice_warning": {
|
||||
"invoice_together": "Fakturera tillsammans",
|
||||
"split_by_month": "Dela upp per månad",
|
||||
"split_error_title": "Månadsuppdelning misslyckades",
|
||||
"split_success_text": "Bearbetade {processed} fakturasamlingar. Delade upp {changed}, hoppade över {skipped}.",
|
||||
"split_success_title": "Månadsuppdelning klar",
|
||||
"text": "Du håller på att fakturera ordrar från flera månader tillsammans ({months}). Ska de delas upp per månad i stället?",
|
||||
"title": "Ordrar från flera månader"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"compat": {
|
||||
"roles": {
|
||||
"permissions": {
|
||||
"access_scope": "Atkomstomrade",
|
||||
"actions": {
|
||||
"clear_all": "Rensa grupp",
|
||||
"reload": "Ladda om",
|
||||
"retry": "Forsok igen",
|
||||
"select_all": "Valj grupp"
|
||||
},
|
||||
"columns": {
|
||||
"enabled": "Aktiverad",
|
||||
"permission": "Behorighet",
|
||||
"route": "Rutt",
|
||||
"status": "Status"
|
||||
},
|
||||
"department_access_description": "Ger atkomst till {name}.",
|
||||
"empty": "Inga behorigheter ar tillgangliga.",
|
||||
"empty_group": "Den har gruppen har inga behorigheter.",
|
||||
"errors": {
|
||||
"load": "Kunde inte lasa in rollbehorigheter.",
|
||||
"update": "En eller flera behorigheter kunde inte uppdateras. Rollens aktuella behorigheter laddades om."
|
||||
},
|
||||
"filtered_summary": "{count} traffar",
|
||||
"groups": {
|
||||
"access": {
|
||||
"description": "Sidtyp- och avdelningsatkomst.",
|
||||
"label": "Atkomst"
|
||||
},
|
||||
"bookings": {
|
||||
"description": "Behorigheter for bokningar, tidsbokningar och planering.",
|
||||
"label": "Bokningar"
|
||||
},
|
||||
"customers_users": {
|
||||
"description": "Behorigheter for kunder, anvandare, medarbetare, roller och inloggning.",
|
||||
"label": "Kunder och anvandare"
|
||||
},
|
||||
"departments_operations": {
|
||||
"description": "Avdelningsdrift, banor, relan, aviseringar, rapporter och mal.",
|
||||
"label": "Avdelningar och drift"
|
||||
},
|
||||
"invoicing_economic": {
|
||||
"description": "Behorigheter for fakturor, faktureringsperioder, inkasso och e-conomic.",
|
||||
"label": "Fakturering och e-conomic"
|
||||
},
|
||||
"orders_pos": {
|
||||
"description": "Behorigheter for ordrar, POS, betalningar och orderrader.",
|
||||
"label": "Ordrar och POS"
|
||||
},
|
||||
"other": {
|
||||
"description": "Behorigheter som inte matchar nagon annan grupp.",
|
||||
"label": "Ovrigt"
|
||||
},
|
||||
"products_pricing": {
|
||||
"description": "Behorigheter for produkter, priser, rabatter och valuta.",
|
||||
"label": "Produkter och priser"
|
||||
},
|
||||
"selfserve_edge": {
|
||||
"description": "Behorigheter for sjalvbetjaning, tvatt, skannrar, gateways och edge-utrustning.",
|
||||
"label": "Sjalvbetjaning och edge"
|
||||
},
|
||||
"system_config": {
|
||||
"description": "System, konfiguration, release, replikering och integrationer.",
|
||||
"label": "System och konfiguration"
|
||||
}
|
||||
},
|
||||
"no_description": "Ingen beskrivning ar tillganglig.",
|
||||
"no_search_results": "Inga behorigheter matchar aktuell sokning.",
|
||||
"page_access": {
|
||||
"admin": {
|
||||
"description": "Ger atkomst till administrationssidor.",
|
||||
"label": "Admin"
|
||||
},
|
||||
"superuser": {
|
||||
"description": "Ger atkomst till superuser-sidor.",
|
||||
"label": "Superuser"
|
||||
},
|
||||
"user": {
|
||||
"description": "Ger atkomst till anvandarsidor.",
|
||||
"label": "Anvandare"
|
||||
}
|
||||
},
|
||||
"search_label": "Sok behorigheter",
|
||||
"search_placeholder": "Sok pa label, nyckel, beskrivning, rutt eller metod",
|
||||
"selected_summary": "{selected} av {total} valda",
|
||||
"status": {
|
||||
"disabled": "Inaktiverad",
|
||||
"enabled": "Aktiverad"
|
||||
},
|
||||
"subtitle": "Rollbehorigheter",
|
||||
"title": "Behorigheter for roll {id}",
|
||||
"title_with_name": "Behorigheter for {name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,130 @@
|
||||
"load_employees": "@:{'phrases.compat.limited_backoffice.errors.load_employees'}",
|
||||
"save_employee": "@:{'phrases.compat.limited_backoffice.errors.save_employee'}",
|
||||
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}"
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "@:{'phrases.compat.limited_backoffice.role_permissions.title'}",
|
||||
"tooltip": "@:{'phrases.compat.limited_backoffice.role_permissions.tooltip'}",
|
||||
"intro": "@:{'phrases.compat.limited_backoffice.role_permissions.intro'}",
|
||||
"scope": "@:{'phrases.compat.limited_backoffice.role_permissions.scope'}",
|
||||
"selected": "@:{'phrases.compat.limited_backoffice.role_permissions.selected'}",
|
||||
"close": "@:{'phrases.compat.limited_backoffice.role_permissions.close'}",
|
||||
"unavailable": "@:{'phrases.compat.limited_backoffice.role_permissions.unavailable'}",
|
||||
"groups": {
|
||||
"account": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.account'}",
|
||||
"orders": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.orders'}",
|
||||
"bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.bookings'}",
|
||||
"time_bookings": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.time_bookings'}",
|
||||
"reports": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.reports'}",
|
||||
"limited_backoffice": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.limited_backoffice'}",
|
||||
"unknown": "@:{'phrases.compat.limited_backoffice.role_permissions.groups.unknown'}"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.sign_in.description'}"
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_permissions.description'}"
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_orders.description'}"
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_orders.description'}"
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_orders.description'}"
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.delete_orders.description'}"
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_items.description'}"
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_order_items.description'}"
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_order_lines.description'}"
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.remove_order_lines.description'}"
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.charge_orders.description'}"
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_department_bookings.description'}"
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_own_bookings.description'}"
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.update_bookings.description'}"
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_bookings.description'}"
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description'}"
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description'}"
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description'}"
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description'}"
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description'}"
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_order_statistics.description'}"
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description'}"
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description'}"
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_department_prices.description'}"
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.manage_employee_access.description'}"
|
||||
},
|
||||
"unknown": {
|
||||
"label": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.label'}",
|
||||
"description": "@:{'phrases.compat.limited_backoffice.role_permissions.capabilities.unknown.description'}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
@@ -204,6 +328,130 @@
|
||||
"load_employees": "@.capitalize:{'terms.glossary.kunde'} @:{'terms.glossary.inte'} @:{'terms.glossary.ladda'} @:{'terms.glossary.medarbetare'}.",
|
||||
"save_employee": "@.capitalize:{'terms.glossary.kunde'} @:{'terms.glossary.inte'} @:{'terms.glossary.spara'} @:{'terms.glossary.medarbetaren'}.",
|
||||
"deactivate_employee": "@.capitalize:{'terms.glossary.kunde'} @:{'terms.glossary.inte'} @:{'terms.glossary.inaktivera'} @:{'terms.glossary.medarbetaren'}."
|
||||
},
|
||||
"role_permissions": {
|
||||
"title": "Rollbehörigheter",
|
||||
"tooltip": "Visa rollbehörigheter",
|
||||
"intro": "Roller samlar de exakta funktioner som tilldelas en medarbetare när rollen sparas.",
|
||||
"scope": "Alla funktioner är begränsade till de avdelningar som väljs för medarbetaren.",
|
||||
"selected": "Vald",
|
||||
"close": "Stäng",
|
||||
"unavailable": "Behörighetsdetaljer är inte tillgängliga för den här rollen förrän rollkatalogen har uppdaterats.",
|
||||
"groups": {
|
||||
"account": "Konto",
|
||||
"orders": "Order",
|
||||
"bookings": "Bokningar",
|
||||
"time_bookings": "Tidsbokningar",
|
||||
"reports": "Rapporter",
|
||||
"limited_backoffice": "Backoffice",
|
||||
"unknown": "Övrigt"
|
||||
},
|
||||
"capabilities": {
|
||||
"sign_in": {
|
||||
"label": "Logga in",
|
||||
"description": "Ger medarbetaren möjlighet att logga in i Pleno."
|
||||
},
|
||||
"view_own_permissions": {
|
||||
"label": "Se egna behörigheter",
|
||||
"description": "Visar medarbetaren vilka åtkomsträttigheter kontot har."
|
||||
},
|
||||
"view_orders": {
|
||||
"label": "Se order",
|
||||
"description": "Kan öppna orderlistor och orderdetaljer för tilldelade avdelningar."
|
||||
},
|
||||
"create_orders": {
|
||||
"label": "Skapa order",
|
||||
"description": "Kan skapa nya order för tilldelade avdelningar."
|
||||
},
|
||||
"edit_orders": {
|
||||
"label": "Redigera order",
|
||||
"description": "Kan ändra orderdetaljer för tilldelade avdelningar."
|
||||
},
|
||||
"delete_orders": {
|
||||
"label": "Ta bort order",
|
||||
"description": "Kan ta bort order i tilldelade avdelningar."
|
||||
},
|
||||
"view_order_items": {
|
||||
"label": "Se orderrader",
|
||||
"description": "Kan se produkter, tjänster och priser på order."
|
||||
},
|
||||
"create_order_items": {
|
||||
"label": "Skapa orderrader",
|
||||
"description": "Kan lägga till produkter och tjänster på order."
|
||||
},
|
||||
"update_order_lines": {
|
||||
"label": "Redigera orderrader",
|
||||
"description": "Kan ändra produkter, antal och priser på order."
|
||||
},
|
||||
"remove_order_lines": {
|
||||
"label": "Ta bort orderrader",
|
||||
"description": "Kan ta bort produkter och tjänster från order."
|
||||
},
|
||||
"charge_orders": {
|
||||
"label": "Debitera order",
|
||||
"description": "Kan ta betalt eller debitera en betalningsreservation för en order."
|
||||
},
|
||||
"view_department_bookings": {
|
||||
"label": "Se avdelningsbokningar",
|
||||
"description": "Kan se bokningar för tilldelade avdelningar."
|
||||
},
|
||||
"view_own_bookings": {
|
||||
"label": "Se egna bokningar",
|
||||
"description": "Kan se bokningsantal och bokningar kopplade till medarbetarkontot."
|
||||
},
|
||||
"update_bookings": {
|
||||
"label": "Redigera bokningar",
|
||||
"description": "Kan ändra tillåtna bokningsdetaljer."
|
||||
},
|
||||
"create_bookings": {
|
||||
"label": "Skapa bokningar",
|
||||
"description": "Kan skapa nya bokningar för tilldelade avdelningar."
|
||||
},
|
||||
"mark_bookings_complete": {
|
||||
"label": "Slutför bokningar",
|
||||
"description": "Kan markera bokningar som slutförda."
|
||||
},
|
||||
"send_booking_confirmations": {
|
||||
"label": "Skicka bokningsbekräftelser igen",
|
||||
"description": "Kan skicka bokningsbekräftelser och slutförandemejl igen."
|
||||
},
|
||||
"view_time_booking_entries": {
|
||||
"label": "Se tidsbokningar",
|
||||
"description": "Kan granska tidsbokningsposter för tilldelade avdelningar."
|
||||
},
|
||||
"create_time_booking_entries": {
|
||||
"label": "Skapa tidsbokningar",
|
||||
"description": "Kan lägga till nya tidsbokningsposter."
|
||||
},
|
||||
"edit_time_booking_entries": {
|
||||
"label": "Redigera tidsbokningar",
|
||||
"description": "Kan redigera tidsbokningsposter när backend-rutten stöder det."
|
||||
},
|
||||
"view_order_statistics": {
|
||||
"label": "Se orderstatistik",
|
||||
"description": "Kan öppna orderstatistik för tilldelade avdelningar."
|
||||
},
|
||||
"view_booking_statistics": {
|
||||
"label": "Se bokningsstatistik",
|
||||
"description": "Kan öppna bokningsstatistik för tilldelade avdelningar."
|
||||
},
|
||||
"open_limited_backoffice": {
|
||||
"label": "Öppna begränsat backoffice",
|
||||
"description": "Kan öppna det begränsade backofficeområdet."
|
||||
},
|
||||
"manage_department_prices": {
|
||||
"label": "Hantera avdelningspriser",
|
||||
"description": "Kan redigera uttryckliga priser för tilldelade avdelningar."
|
||||
},
|
||||
"manage_employee_access": {
|
||||
"label": "Hantera medarbetaråtkomst",
|
||||
"description": "Kan skapa, redigera och inaktivera begränsade backofficemedarbetare."
|
||||
},
|
||||
"unknown": {
|
||||
"label": "Extra funktion",
|
||||
"description": "Den här funktionen kommer från rollkatalogen men är ännu inte beskriven i gränssnittet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import Swal from "sweetalert2";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
export const MULTI_MONTH_INVOICE_ACTION = {
|
||||
CONTINUE: "continue",
|
||||
CANCEL: "cancel",
|
||||
SPLIT: "split",
|
||||
};
|
||||
|
||||
const toPositiveInteger = (value) => {
|
||||
const parsed = Number.parseInt(String(value ?? ""), 10);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
||||
};
|
||||
|
||||
const getDateKey = (value) => {
|
||||
const rawValue = String(value ?? "").trim();
|
||||
const directMatch = rawValue.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||||
if (directMatch) {
|
||||
return `${directMatch[1]}-${directMatch[2]}-${directMatch[3]}`;
|
||||
}
|
||||
|
||||
const parsedDate = new Date(rawValue);
|
||||
if (Number.isNaN(parsedDate.getTime())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const year = parsedDate.getFullYear();
|
||||
const month = String(parsedDate.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(parsedDate.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
export const getMonthKey = (value) => {
|
||||
const dateKey = getDateKey(value);
|
||||
return dateKey ? dateKey.slice(0, 7) : null;
|
||||
};
|
||||
|
||||
const translate = (key, params = {}) => i18n.global.t(key, params);
|
||||
|
||||
const getSplitResponsePayload = (response = {}) => response?.data?.data ?? response?.data ?? {};
|
||||
|
||||
export const buildMultiMonthInvoiceContext = (
|
||||
items = [],
|
||||
{
|
||||
getDate = (item) => item?.created_at ?? item?.date,
|
||||
getInvoiceCollectionId = (item) => item?.invoice_collection_id,
|
||||
} = {}
|
||||
) => {
|
||||
const months = new Set();
|
||||
const dateKeys = [];
|
||||
const invoiceCollectionIds = new Set();
|
||||
|
||||
(Array.isArray(items) ? items : []).forEach((item) => {
|
||||
const dateValue = getDate(item);
|
||||
const dateKey = getDateKey(dateValue);
|
||||
if (dateKey) {
|
||||
dateKeys.push(dateKey);
|
||||
months.add(dateKey.slice(0, 7));
|
||||
}
|
||||
|
||||
const invoiceCollectionId = toPositiveInteger(getInvoiceCollectionId(item));
|
||||
if (invoiceCollectionId) {
|
||||
invoiceCollectionIds.add(invoiceCollectionId);
|
||||
}
|
||||
});
|
||||
|
||||
dateKeys.sort();
|
||||
|
||||
return {
|
||||
months: Array.from(months).sort(),
|
||||
dateFrom: dateKeys[0] ?? null,
|
||||
dateTo: dateKeys[dateKeys.length - 1] ?? null,
|
||||
invoiceCollectionIds: Array.from(invoiceCollectionIds).sort((left, right) => left - right),
|
||||
};
|
||||
};
|
||||
|
||||
export const shouldWarnAboutMultiMonthInvoice = (context = {}) => (
|
||||
Array.isArray(context.months) &&
|
||||
context.months.length > 1 &&
|
||||
Array.isArray(context.invoiceCollectionIds) &&
|
||||
context.invoiceCollectionIds.length > 0 &&
|
||||
Boolean(context.dateFrom) &&
|
||||
Boolean(context.dateTo)
|
||||
);
|
||||
|
||||
export const promptMultiMonthInvoiceWarning = async ({
|
||||
context,
|
||||
splitByMonth,
|
||||
parseErrorMessage = (error) => error?.message ?? String(error),
|
||||
} = {}) => {
|
||||
if (!shouldWarnAboutMultiMonthInvoice(context)) {
|
||||
return MULTI_MONTH_INVOICE_ACTION.CONTINUE;
|
||||
}
|
||||
|
||||
const months = context.months.join(", ");
|
||||
const confirmation = await Swal.fire({
|
||||
icon: "warning",
|
||||
title: translate("invoicing_period.multi_month_invoice_warning.title"),
|
||||
text: translate("invoicing_period.multi_month_invoice_warning.text", { months }),
|
||||
showCancelButton: true,
|
||||
showDenyButton: true,
|
||||
confirmButtonText: translate("invoicing_period.multi_month_invoice_warning.split_by_month"),
|
||||
denyButtonText: translate("invoicing_period.multi_month_invoice_warning.invoice_together"),
|
||||
cancelButtonText: translate("common.cancel"),
|
||||
});
|
||||
|
||||
if (confirmation.isDenied) {
|
||||
return MULTI_MONTH_INVOICE_ACTION.CONTINUE;
|
||||
}
|
||||
|
||||
if (!confirmation.isConfirmed) {
|
||||
return MULTI_MONTH_INVOICE_ACTION.CANCEL;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await splitByMonth(context.dateFrom, context.dateTo, {
|
||||
invoiceCollectionIds: context.invoiceCollectionIds,
|
||||
preview: false,
|
||||
});
|
||||
const result = getSplitResponsePayload(response);
|
||||
await Swal.fire({
|
||||
icon: "success",
|
||||
title: translate("invoicing_period.multi_month_invoice_warning.split_success_title"),
|
||||
text: translate("invoicing_period.multi_month_invoice_warning.split_success_text", {
|
||||
processed: result.processed_count ?? 0,
|
||||
changed: result.changed_count ?? 0,
|
||||
skipped: result.skipped_count ?? 0,
|
||||
}),
|
||||
});
|
||||
return MULTI_MONTH_INVOICE_ACTION.SPLIT;
|
||||
} catch (error) {
|
||||
await Swal.fire({
|
||||
icon: "error",
|
||||
title: translate("invoicing_period.multi_month_invoice_warning.split_error_title"),
|
||||
text: parseErrorMessage(error),
|
||||
});
|
||||
return MULTI_MONTH_INVOICE_ACTION.CANCEL;
|
||||
}
|
||||
};
|
||||
@@ -28,6 +28,7 @@ const errorMessage = ref("");
|
||||
const successMessage = ref("");
|
||||
const editingEmployee = ref(null);
|
||||
const formAttempted = ref(false);
|
||||
const showRolePermissions = ref(false);
|
||||
const form = ref({
|
||||
display_name: "",
|
||||
email: "",
|
||||
@@ -59,9 +60,155 @@ const roleMessages = computed(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const rolePermissionGroupMessages = computed(() => ({
|
||||
account: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.account"),
|
||||
},
|
||||
orders: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.orders"),
|
||||
},
|
||||
bookings: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.bookings"),
|
||||
},
|
||||
time_bookings: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.time_bookings"),
|
||||
},
|
||||
reports: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.reports"),
|
||||
},
|
||||
limited_backoffice: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.limited_backoffice"),
|
||||
},
|
||||
unknown: {
|
||||
label: t("templates.limited_backoffice.role_permissions.groups.unknown"),
|
||||
},
|
||||
}));
|
||||
|
||||
const roleCapabilityMessages = computed(() => ({
|
||||
sign_in: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.sign_in.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.sign_in.description"),
|
||||
},
|
||||
view_own_permissions: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_own_permissions.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_own_permissions.description"),
|
||||
},
|
||||
view_orders: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_orders.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_orders.description"),
|
||||
},
|
||||
create_orders: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.create_orders.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.create_orders.description"),
|
||||
},
|
||||
edit_orders: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.edit_orders.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.edit_orders.description"),
|
||||
},
|
||||
delete_orders: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.delete_orders.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.delete_orders.description"),
|
||||
},
|
||||
view_order_items: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_order_items.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_order_items.description"),
|
||||
},
|
||||
create_order_items: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.create_order_items.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.create_order_items.description"),
|
||||
},
|
||||
update_order_lines: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.update_order_lines.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.update_order_lines.description"),
|
||||
},
|
||||
remove_order_lines: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.remove_order_lines.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.remove_order_lines.description"),
|
||||
},
|
||||
charge_orders: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.charge_orders.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.charge_orders.description"),
|
||||
},
|
||||
view_department_bookings: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_department_bookings.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_department_bookings.description"),
|
||||
},
|
||||
view_own_bookings: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_own_bookings.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_own_bookings.description"),
|
||||
},
|
||||
update_bookings: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.update_bookings.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.update_bookings.description"),
|
||||
},
|
||||
create_bookings: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.create_bookings.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.create_bookings.description"),
|
||||
},
|
||||
mark_bookings_complete: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.mark_bookings_complete.description"),
|
||||
},
|
||||
send_booking_confirmations: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.send_booking_confirmations.description"),
|
||||
},
|
||||
view_time_booking_entries: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_time_booking_entries.description"),
|
||||
},
|
||||
create_time_booking_entries: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.create_time_booking_entries.description"),
|
||||
},
|
||||
edit_time_booking_entries: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.edit_time_booking_entries.description"),
|
||||
},
|
||||
view_order_statistics: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_order_statistics.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_order_statistics.description"),
|
||||
},
|
||||
view_booking_statistics: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.view_booking_statistics.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.view_booking_statistics.description"),
|
||||
},
|
||||
open_limited_backoffice: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.open_limited_backoffice.description"),
|
||||
},
|
||||
manage_department_prices: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.manage_department_prices.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.manage_department_prices.description"),
|
||||
},
|
||||
manage_employee_access: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.manage_employee_access.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.manage_employee_access.description"),
|
||||
},
|
||||
unknown: {
|
||||
label: t("templates.limited_backoffice.role_permissions.capabilities.unknown.label"),
|
||||
description: t("templates.limited_backoffice.role_permissions.capabilities.unknown.description"),
|
||||
},
|
||||
}));
|
||||
|
||||
const roleLabel = (role) => roleMessages.value[role?.key]?.label || role?.label || "";
|
||||
const roleDescription = (role) => roleMessages.value[role?.key]?.description || role?.description || "";
|
||||
const roleByKey = (key) => roles.value.find((role) => role.key === key) || { key };
|
||||
const rolePermissionGroupLabel = (groupKey) =>
|
||||
rolePermissionGroupMessages.value[groupKey]?.label || rolePermissionGroupMessages.value.unknown.label;
|
||||
const roleCapabilityLabel = (capabilityKey) =>
|
||||
roleCapabilityMessages.value[capabilityKey]?.label || roleCapabilityMessages.value.unknown.label;
|
||||
const roleCapabilityDescription = (capabilityKey) =>
|
||||
roleCapabilityMessages.value[capabilityKey]?.description || roleCapabilityMessages.value.unknown.description;
|
||||
const rolePermissionGroups = (role) =>
|
||||
(Array.isArray(role?.permission_groups) ? role.permission_groups : [])
|
||||
.map((group) => ({
|
||||
key: String(group?.key || ""),
|
||||
capabilities: (Array.isArray(group?.capabilities) ? group.capabilities : [])
|
||||
.map((capability) => String(capability || ""))
|
||||
.filter(Boolean),
|
||||
}))
|
||||
.filter((group) => group.key && group.capabilities.length > 0);
|
||||
|
||||
const visibleRoles = computed(() => roles.value.filter((role) => ROLE_KEYS.includes(role.key)));
|
||||
const isEditing = computed(() => Boolean(editingEmployee.value));
|
||||
@@ -301,9 +448,29 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="limited-employee-role">
|
||||
{{ t("templates.limited_backoffice.employees.role") }}
|
||||
</label>
|
||||
<div class="limited-employee-role-label-row">
|
||||
<label class="label mb-0" for="limited-employee-role">
|
||||
{{ t("templates.limited_backoffice.employees.role") }}
|
||||
</label>
|
||||
<b-tooltip
|
||||
:label="t('templates.limited_backoffice.role_permissions.tooltip')"
|
||||
position="is-left"
|
||||
type="is-dark"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-white is-small limited-role-help-button"
|
||||
:aria-label="t('templates.limited_backoffice.role_permissions.tooltip')"
|
||||
:title="t('templates.limited_backoffice.role_permissions.tooltip')"
|
||||
data-testid="limited-employee-role-help"
|
||||
@click="showRolePermissions = true"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-question-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select id="limited-employee-role" v-model="form.role_key" data-testid="limited-employee-role">
|
||||
@@ -464,6 +631,94 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-modal
|
||||
v-model="showRolePermissions"
|
||||
:width="880"
|
||||
:can-cancel="['escape', 'x', 'outside']"
|
||||
trap-focus
|
||||
aria-role="dialog"
|
||||
aria-modal
|
||||
>
|
||||
<div class="modal-card limited-role-permissions-modal" data-testid="limited-role-permissions-modal">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">{{ t("templates.limited_backoffice.role_permissions.title") }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="delete"
|
||||
:aria-label="t('templates.limited_backoffice.role_permissions.close')"
|
||||
data-testid="limited-role-permissions-close"
|
||||
@click="showRolePermissions = false"
|
||||
></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<p class="limited-role-permissions-modal__intro" data-testid="limited-role-permissions-intro">
|
||||
{{ t("templates.limited_backoffice.role_permissions.intro") }}
|
||||
</p>
|
||||
<p class="limited-role-permissions-modal__scope">
|
||||
{{ t("templates.limited_backoffice.role_permissions.scope") }}
|
||||
</p>
|
||||
|
||||
<div class="limited-role-permissions-list">
|
||||
<section
|
||||
v-for="role in visibleRoles"
|
||||
:key="role.key"
|
||||
class="limited-role-permissions-role"
|
||||
:class="{ 'limited-role-permissions-role--selected': role.key === form.role_key }"
|
||||
:data-testid="`limited-role-permissions-role-${role.key}`"
|
||||
>
|
||||
<div class="limited-role-permissions-role__header">
|
||||
<div>
|
||||
<h3 class="title is-6 mb-1">{{ roleLabel(role) }}</h3>
|
||||
<p class="is-size-7 has-text-grey">{{ roleDescription(role) }}</p>
|
||||
</div>
|
||||
<span v-if="role.key === form.role_key" class="tag is-info is-light">
|
||||
{{ t("templates.limited_backoffice.role_permissions.selected") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="rolePermissionGroups(role).length === 0"
|
||||
class="notification is-warning is-light limited-role-permissions-role__empty"
|
||||
>
|
||||
{{ t("templates.limited_backoffice.role_permissions.unavailable") }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="group in rolePermissionGroups(role)"
|
||||
:key="`${role.key}-${group.key}`"
|
||||
class="limited-role-permissions-group"
|
||||
:data-testid="`limited-role-permissions-group-${role.key}-${group.key}`"
|
||||
>
|
||||
<h4 class="limited-role-permissions-group__title">
|
||||
{{ rolePermissionGroupLabel(group.key) }}
|
||||
</h4>
|
||||
<ul class="limited-role-permissions-capabilities">
|
||||
<li
|
||||
v-for="capability in group.capabilities"
|
||||
:key="`${role.key}-${group.key}-${capability}`"
|
||||
class="limited-role-permissions-capability"
|
||||
>
|
||||
<strong>{{ roleCapabilityLabel(capability) }}</strong>
|
||||
<span>{{ roleCapabilityDescription(capability) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot is-justify-content-flex-end">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-dark"
|
||||
data-testid="limited-role-permissions-close-footer"
|
||||
@click="showRolePermissions = false"
|
||||
>
|
||||
{{ t("templates.limited_backoffice.role_permissions.close") }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
</LimitedBackofficeLayout>
|
||||
</RestrictedPageWrapper>
|
||||
</template>
|
||||
@@ -484,6 +739,28 @@ onMounted(() => {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.limited-employee-role-label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.limited-role-help-button {
|
||||
flex: 0 0 auto;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: #485fc7;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.limited-role-help-button:focus,
|
||||
.limited-role-help-button:hover {
|
||||
color: #363636;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.limited-employees-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -509,6 +786,78 @@ onMounted(() => {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.limited-role-permissions-modal {
|
||||
width: 100%;
|
||||
max-width: 880px;
|
||||
}
|
||||
|
||||
.limited-role-permissions-modal__intro {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.limited-role-permissions-modal__scope {
|
||||
margin-bottom: 1rem;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
.limited-role-permissions-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.limited-role-permissions-role {
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.limited-role-permissions-role--selected {
|
||||
border-color: #485fc7;
|
||||
box-shadow: inset 3px 0 0 #485fc7;
|
||||
}
|
||||
|
||||
.limited-role-permissions-role__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.limited-role-permissions-role__empty {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.limited-role-permissions-group + .limited-role-permissions-group {
|
||||
margin-top: 0.85rem;
|
||||
}
|
||||
|
||||
.limited-role-permissions-group__title {
|
||||
margin-bottom: 0.4rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: #363636;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.limited-role-permissions-capabilities {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.limited-role-permissions-capability {
|
||||
display: grid;
|
||||
gap: 0.1rem;
|
||||
padding-left: 0.75rem;
|
||||
border-left: 2px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.limited-role-permissions-capability span {
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.limited-employees-header {
|
||||
display: block;
|
||||
@@ -518,4 +867,14 @@ onMounted(() => {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.limited-role-permissions-role__header {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.limited-role-permissions-role__header .tag {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+36
-4
@@ -22,6 +22,11 @@ import InvoicingBillingPeriodFilters from "@/views/dashboards/superUserDashboard
|
||||
import InvoicingBillingPeriodCustomerAttributes from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/InvoicingBillingPeriodCustomerAttributes.vue";
|
||||
import InvoicingPeriodFlagList from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue";
|
||||
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
|
||||
import {
|
||||
buildMultiMonthInvoiceContext,
|
||||
MULTI_MONTH_INVOICE_ACTION,
|
||||
promptMultiMonthInvoiceWarning,
|
||||
} from "@/services/invoiceMonthSplitWarning.js";
|
||||
import {
|
||||
buildPossibleDuplicateGroups,
|
||||
formatDuplicateDateLabel,
|
||||
@@ -330,6 +335,13 @@ const parsePositiveInteger = (value: any) => {
|
||||
|
||||
const getCustomerNumber = (customer: any) => parsePositiveInteger(customer?.customer_number);
|
||||
|
||||
const markCustomerPeriodRefreshLoading = (customer: any) => {
|
||||
const customerNumber = getCustomerNumber(customer);
|
||||
if (customerNumber) {
|
||||
invoiceQueue.markPeriodRefreshLoading?.([customerNumber], []);
|
||||
}
|
||||
};
|
||||
|
||||
const queueInvoiceCollections = (invoiceCollectionIds: number[], customer: any = null) => {
|
||||
const uniqueInvoiceCollectionIds = Array.from(
|
||||
new Set(
|
||||
@@ -351,12 +363,10 @@ const queueInvoiceCollections = (invoiceCollectionIds: number[], customer: any =
|
||||
|
||||
const onClickInvoiceNow = async (customer: any, transactionIds: number[]) => {
|
||||
const customerNumber = getCustomerNumber(customer);
|
||||
if (customerNumber) {
|
||||
invoiceQueue.markPeriodRefreshLoading?.([customerNumber], []);
|
||||
}
|
||||
|
||||
try {
|
||||
if (transactionIds.length === 0) {
|
||||
markCustomerPeriodRefreshLoading(customer);
|
||||
const month = dates.variables.start.value.getMonth() + 1;
|
||||
const year = dates.variables.start.value.getFullYear();
|
||||
|
||||
@@ -392,10 +402,32 @@ const onClickInvoiceNow = async (customer: any, transactionIds: number[]) => {
|
||||
await fetchMissingInvoiceCollections(customer, transactionIds);
|
||||
const invoiceCollectionIds = getInvoiceCollectionIdsForTransactionIds(customer, transactionIds);
|
||||
if (invoiceCollectionIds.length === 0) {
|
||||
invoiceQueue.finishPeriodRefresh?.([customerNumber], []);
|
||||
return;
|
||||
}
|
||||
|
||||
const invoiceWarningContext = buildMultiMonthInvoiceContext(
|
||||
transactionIds
|
||||
.map((transactionId) => getTransactionById(customer, transactionId))
|
||||
.filter((transaction: any) => transaction !== null),
|
||||
{
|
||||
getDate: (transaction: any) => transaction?.date ?? transaction?.created_at,
|
||||
getInvoiceCollectionId: (transaction: any) => getTransactionInvoiceCollectionId(transaction),
|
||||
}
|
||||
);
|
||||
const invoiceWarningAction = await promptMultiMonthInvoiceWarning({
|
||||
context: invoiceWarningContext,
|
||||
splitByMonth: SessionUser.objects.collectedOrderInvoices.functions.split_by_month,
|
||||
parseErrorMessage: SessionUser.functions.parseErrorMessage,
|
||||
});
|
||||
if (invoiceWarningAction === MULTI_MONTH_INVOICE_ACTION.SPLIT) {
|
||||
reloadPeriodPage();
|
||||
return;
|
||||
}
|
||||
if (invoiceWarningAction === MULTI_MONTH_INVOICE_ACTION.CANCEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
markCustomerPeriodRefreshLoading(customer);
|
||||
queueInvoiceCollections(invoiceCollectionIds, customer);
|
||||
} catch (error: any) {
|
||||
invoiceQueue.finishPeriodRefresh?.([customerNumber], []);
|
||||
|
||||
@@ -1,286 +1,572 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import PageTitle from "@/components/global/PageTitle.vue";
|
||||
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import {useRouter} from 'vue-router'
|
||||
import {ref, watch} from "vue";
|
||||
import {loadList, setEndpoint, setOrder} from "@/components/pagination/paginatedList.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { ObjectsGlobal } from "@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue";
|
||||
import RolesSubPageWrapper from "@/views/dashboards/superUserDashboard/roles/RolesSubPageWrapper.vue";
|
||||
import {
|
||||
filterPermissionRecords,
|
||||
getPermissionDelta,
|
||||
groupPermissionRecords,
|
||||
normalizePermissionCatalog,
|
||||
pageAccessPermissionKeys,
|
||||
permissionGroupDefinitions,
|
||||
sanitizePermissionDomId,
|
||||
} from "@/views/dashboards/superUserDashboard/roles/rolePermissionCatalog.js";
|
||||
|
||||
// Lists
|
||||
const permissions = ref([]);
|
||||
const permissionsList = ref([]);
|
||||
const rolePermissions = ref([]);
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const role = ref(null);
|
||||
const departments = ref([]);
|
||||
const permissionRecords = ref([]);
|
||||
const selectedPermissions = ref(new Set());
|
||||
const busyPermissions = ref(new Set());
|
||||
const busyGroups = ref(new Set());
|
||||
const isLoading = ref(true);
|
||||
const loadError = ref("");
|
||||
const mutationError = ref("");
|
||||
const search = ref("");
|
||||
const activeGroup = ref("access");
|
||||
|
||||
// Loaded state
|
||||
const isLoaded = ref(false);
|
||||
// Individually loaded states
|
||||
const isDepartmentsLoaded = ref(false);
|
||||
const isPermissionsLoaded = ref(false);
|
||||
const isRolePermissionsLoaded = ref(false);
|
||||
const isPermissionsListLoaded = ref(false);
|
||||
const roleId = computed(() => Number.parseInt(String(route.params.roleId || ""), 10));
|
||||
const roleTitle = computed(() =>
|
||||
role.value?.name
|
||||
? t("roles.permissions.title_with_name", { name: role.value.name })
|
||||
: t("roles.permissions.title", { id: Number.isFinite(roleId.value) ? roleId.value : "" })
|
||||
);
|
||||
|
||||
watch(() => departments.value, () => {
|
||||
console.log('Departments loaded');
|
||||
isDepartmentsLoaded.value = true;
|
||||
});
|
||||
const buildPermissionOverrides = (departmentList) => {
|
||||
const overrides = {};
|
||||
|
||||
watch(() => permissionsList.value, () => {
|
||||
console.log('Permissions list loaded');
|
||||
isPermissionsListLoaded.value = true;
|
||||
});
|
||||
for (const permission of pageAccessPermissionKeys) {
|
||||
overrides[permission] = {
|
||||
label: t(`roles.permissions.page_access.${permission}.label`),
|
||||
description: t(`roles.permissions.page_access.${permission}.description`),
|
||||
groupKey: "access",
|
||||
isVirtual: true,
|
||||
};
|
||||
}
|
||||
|
||||
watch(() => permissions.value, () => {
|
||||
console.log('Permissions loaded');
|
||||
isPermissionsLoaded.value = true;
|
||||
});
|
||||
|
||||
watch(() => rolePermissions.value, () => {
|
||||
console.log('Role permissions loaded');
|
||||
isRolePermissionsLoaded.value = true;
|
||||
});
|
||||
|
||||
// Get the department from the route
|
||||
const router = useRouter()
|
||||
const roleId = ref(parseInt(router.currentRoute.value.params.roleId));
|
||||
|
||||
// Set the endpoint
|
||||
setEndpoint(SessionUser.objects.department_categories.meta.endpoint + `?id=${roleId.value}`);
|
||||
// Set the order
|
||||
setOrder("category_id", "asc");
|
||||
// Load the list
|
||||
loadList();
|
||||
|
||||
const loadPermissionsList = async () => {
|
||||
const response = await SessionUser.objects.permissions.get.all().then((response) => {
|
||||
const tmpPermissionsList = [];
|
||||
const tmpPermissions = [];
|
||||
for (const i in response) {
|
||||
const endpoint = i;
|
||||
const methods = response[i];
|
||||
for (const method in methods) {
|
||||
const permissions = methods[method];
|
||||
for (const permission in permissions) {
|
||||
tmpPermissionsList.push(permission);
|
||||
tmpPermissions[endpoint] = tmpPermissions[endpoint] || {}; // Create the endpoint if it doesn't exist
|
||||
tmpPermissions[endpoint][method] = tmpPermissions[endpoint][method] || {}; // Create the method if it doesn't exist
|
||||
tmpPermissions[endpoint][method][permission] = permissions[permission];
|
||||
}
|
||||
}
|
||||
for (const department of departmentList) {
|
||||
const departmentId = Number.parseInt(String(department?.id ?? ""), 10);
|
||||
if (!Number.isFinite(departmentId)) {
|
||||
continue;
|
||||
}
|
||||
permissionsList.value = tmpPermissionsList;
|
||||
permissions.value = tmpPermissions;
|
||||
});
|
||||
}
|
||||
// Get all available permissions
|
||||
loadPermissionsList();
|
||||
const loadRolePermissions = async () => {
|
||||
await SessionUser.objects.roles.get.single(roleId.value).then((response) => {
|
||||
rolePermissions.value = response.permissions;
|
||||
});
|
||||
}
|
||||
// Get the permissions for the role
|
||||
loadRolePermissions();
|
||||
|
||||
const convertRouteToHumanReadable = (route) => {
|
||||
let arrow = " -> ";
|
||||
// Replace / with space
|
||||
let humanReadable = route.replace(/\//g, arrow);
|
||||
// Replace _ with space
|
||||
humanReadable = humanReadable.replace(/_/g, " ");
|
||||
// Capitalize the first letter
|
||||
humanReadable = humanReadable.charAt(0).toUpperCase() + humanReadable.slice(1);
|
||||
// If the route ends with an arrow, remove it
|
||||
if (humanReadable.endsWith(arrow)) {
|
||||
humanReadable = humanReadable.slice(0, -arrow.length);
|
||||
const name = String(department?.name || departmentId);
|
||||
overrides[`department_access_${departmentId}`] = {
|
||||
label: name,
|
||||
description: t("roles.permissions.department_access_description", { name }),
|
||||
groupKey: "access",
|
||||
isVirtual: true,
|
||||
departmentId,
|
||||
};
|
||||
}
|
||||
// If the route starts with an arrow, remove it
|
||||
if (humanReadable.startsWith(arrow)) {
|
||||
humanReadable = humanReadable.slice(arrow.length);
|
||||
}
|
||||
return humanReadable;
|
||||
}
|
||||
|
||||
const endpointMethodColors = {
|
||||
GET: "is-success",
|
||||
POST: "is-info",
|
||||
PUT: "is-warning",
|
||||
PATCH: "is-warning",
|
||||
DELETE: "is-danger",
|
||||
OPTIONS: "is-light",
|
||||
}
|
||||
return overrides;
|
||||
};
|
||||
|
||||
const hasPermission = (permission) => {
|
||||
return rolePermissions.value.includes(permission);
|
||||
}
|
||||
|
||||
const togglePermission = (permission) => {
|
||||
if (hasPermission(permission)) {
|
||||
SessionUser.objects.roles.functions.permissions.remove(roleId.value, permission).then((response) => {
|
||||
// Remove the permission
|
||||
rolePermissions.value = rolePermissions.value.filter((value) => value !== permission);
|
||||
});
|
||||
const replaceSetValue = (targetRef, value, shouldAdd) => {
|
||||
const next = new Set(targetRef.value);
|
||||
if (shouldAdd) {
|
||||
next.add(value);
|
||||
} else {
|
||||
SessionUser.objects.roles.functions.permissions.add(roleId.value, permission).then((response) => {
|
||||
// Add the permission
|
||||
rolePermissions.value.push(permission);
|
||||
});
|
||||
next.delete(value);
|
||||
}
|
||||
}
|
||||
targetRef.value = next;
|
||||
};
|
||||
|
||||
const setPermissionBusy = (permission, isBusy) => {
|
||||
replaceSetValue(busyPermissions, permission, isBusy);
|
||||
};
|
||||
|
||||
const loadDepartments = async () => {
|
||||
const response = await SessionUser.objects.departments.get.all().then((response) => {
|
||||
departments.value = response;
|
||||
const setGroupBusy = (groupKey, isBusy) => {
|
||||
replaceSetValue(busyGroups, groupKey, isBusy);
|
||||
};
|
||||
|
||||
const hasPermission = (permission) => selectedPermissions.value.has(permission);
|
||||
const isPermissionBusy = (permission) => busyPermissions.value.has(permission);
|
||||
const isGroupBusy = (groupKey) => busyGroups.value.has(groupKey);
|
||||
const permissionDomId = (permission) => `role-permission-${sanitizePermissionDomId(permission)}`;
|
||||
const permissionTestId = (permission) => `role-permission-row-${sanitizePermissionDomId(permission)}`;
|
||||
|
||||
const allRecordsByGroup = computed(() => groupPermissionRecords(permissionRecords.value));
|
||||
const filteredPermissionRecords = computed(() => filterPermissionRecords(permissionRecords.value, search.value));
|
||||
const filteredRecordsByGroup = computed(() => groupPermissionRecords(filteredPermissionRecords.value));
|
||||
|
||||
const groupTabs = computed(() =>
|
||||
permissionGroupDefinitions
|
||||
.map((group) => {
|
||||
const allRecords = allRecordsByGroup.value.get(group.key) || [];
|
||||
const filteredRecords = filteredRecordsByGroup.value.get(group.key) || [];
|
||||
|
||||
return {
|
||||
...group,
|
||||
label: t(`roles.permissions.groups.${group.key}.label`),
|
||||
description: t(`roles.permissions.groups.${group.key}.description`),
|
||||
totalCount: allRecords.length,
|
||||
filteredCount: filteredRecords.length,
|
||||
};
|
||||
})
|
||||
.filter((group) => group.totalCount > 0)
|
||||
);
|
||||
|
||||
const totalCount = computed(() => permissionRecords.value.length);
|
||||
const filteredCount = computed(() => filteredPermissionRecords.value.length);
|
||||
const selectedCount = computed(() => selectedPermissions.value.size);
|
||||
|
||||
const getAllGroupRecords = (groupKey) => allRecordsByGroup.value.get(groupKey) || [];
|
||||
const getFilteredGroupRecords = (groupKey) => filteredRecordsByGroup.value.get(groupKey) || [];
|
||||
|
||||
const isGroupFullySelected = (groupKey) => {
|
||||
const records = getAllGroupRecords(groupKey);
|
||||
return records.length > 0 && records.every((record) => selectedPermissions.value.has(record.permission));
|
||||
};
|
||||
|
||||
const isGroupFullyCleared = (groupKey) => {
|
||||
const records = getAllGroupRecords(groupKey);
|
||||
return records.length > 0 && records.every((record) => !selectedPermissions.value.has(record.permission));
|
||||
};
|
||||
|
||||
watch(
|
||||
groupTabs,
|
||||
(tabs) => {
|
||||
if (tabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tabs.some((tab) => tab.key === activeGroup.value)) {
|
||||
activeGroup.value = tabs[0].key;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const loadRolePermissions = async () => {
|
||||
ObjectsGlobal.clearCache();
|
||||
const selectedRole = await SessionUser.objects.roles.get.single(roleId.value);
|
||||
role.value = selectedRole || null;
|
||||
selectedPermissions.value = new Set(Array.isArray(selectedRole?.permissions) ? selectedRole.permissions : []);
|
||||
};
|
||||
|
||||
const loadPageData = async () => {
|
||||
isLoading.value = true;
|
||||
loadError.value = "";
|
||||
mutationError.value = "";
|
||||
|
||||
try {
|
||||
if (!Number.isFinite(roleId.value)) {
|
||||
throw new Error("Invalid role id");
|
||||
}
|
||||
|
||||
ObjectsGlobal.clearCache();
|
||||
const [permissionCatalog, selectedRole, departmentList] = await Promise.all([
|
||||
SessionUser.objects.permissions.get.all(),
|
||||
SessionUser.objects.roles.get.single(roleId.value),
|
||||
SessionUser.objects.departments.get.all(),
|
||||
]);
|
||||
const safeDepartments = Array.isArray(departmentList) ? departmentList : [];
|
||||
|
||||
role.value = selectedRole || null;
|
||||
departments.value = safeDepartments;
|
||||
selectedPermissions.value = new Set(Array.isArray(selectedRole?.permissions) ? selectedRole.permissions : []);
|
||||
permissionRecords.value = normalizePermissionCatalog(permissionCatalog || {}, {
|
||||
departments: safeDepartments,
|
||||
permissionOverrides: buildPermissionOverrides(safeDepartments),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load role permissions page", error);
|
||||
loadError.value = t("roles.permissions.errors.load");
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const mutatePermissions = async ({ add = [], remove = [], groupKey = null }) => {
|
||||
const additions = add.filter((permission) => !selectedPermissions.value.has(permission) && !isPermissionBusy(permission));
|
||||
const removals = remove.filter((permission) => selectedPermissions.value.has(permission) && !isPermissionBusy(permission));
|
||||
const affectedPermissions = [...additions, ...removals];
|
||||
|
||||
if (affectedPermissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutationError.value = "";
|
||||
|
||||
if (groupKey) {
|
||||
setGroupBusy(groupKey, true);
|
||||
}
|
||||
for (const permission of affectedPermissions) {
|
||||
setPermissionBusy(permission, true);
|
||||
}
|
||||
|
||||
const optimisticPermissions = new Set(selectedPermissions.value);
|
||||
for (const permission of additions) {
|
||||
optimisticPermissions.add(permission);
|
||||
}
|
||||
for (const permission of removals) {
|
||||
optimisticPermissions.delete(permission);
|
||||
}
|
||||
selectedPermissions.value = optimisticPermissions;
|
||||
|
||||
try {
|
||||
const operations = [
|
||||
...additions.map((permission) =>
|
||||
SessionUser.objects.roles.functions.permissions.add(roleId.value, permission).then(() => ({
|
||||
permission,
|
||||
action: "add",
|
||||
}))
|
||||
),
|
||||
...removals.map((permission) =>
|
||||
SessionUser.objects.roles.functions.permissions.remove(roleId.value, permission).then(() => ({
|
||||
permission,
|
||||
action: "remove",
|
||||
}))
|
||||
),
|
||||
];
|
||||
const results = await Promise.allSettled(operations);
|
||||
const failedResult = results.find((result) => result.status === "rejected");
|
||||
|
||||
if (failedResult) {
|
||||
throw failedResult.reason;
|
||||
}
|
||||
|
||||
ObjectsGlobal.clearCache();
|
||||
const nextPermissions = new Set(selectedPermissions.value);
|
||||
for (const permission of additions) {
|
||||
nextPermissions.add(permission);
|
||||
}
|
||||
for (const permission of removals) {
|
||||
nextPermissions.delete(permission);
|
||||
}
|
||||
selectedPermissions.value = nextPermissions;
|
||||
} catch (error) {
|
||||
console.error("Failed to update role permissions", error);
|
||||
mutationError.value = t("roles.permissions.errors.update");
|
||||
await loadRolePermissions().catch((reloadError) => {
|
||||
console.error("Failed to reload role permissions after mutation error", reloadError);
|
||||
});
|
||||
} finally {
|
||||
for (const permission of affectedPermissions) {
|
||||
setPermissionBusy(permission, false);
|
||||
}
|
||||
if (groupKey) {
|
||||
setGroupBusy(groupKey, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const togglePermission = async (record) => {
|
||||
if (!record?.permission || isPermissionBusy(record.permission)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldAdd = !selectedPermissions.value.has(record.permission);
|
||||
await mutatePermissions({
|
||||
add: shouldAdd ? [record.permission] : [],
|
||||
remove: shouldAdd ? [] : [record.permission],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadDepartments();
|
||||
const applyGroupSelection = async (groupKey, shouldSelect) => {
|
||||
if (isGroupBusy(groupKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
watch(() => isDepartmentsLoaded.value && isPermissionsListLoaded.value && isPermissionsLoaded.value && isRolePermissionsLoaded.value, () => {
|
||||
console.log('Loaded:', 'Departments:', departments.value.length, 'Permissions:', permissionsList.value.length, 'Role permissions:', rolePermissions.value.length);
|
||||
isLoaded.value = true;
|
||||
});
|
||||
const delta = getPermissionDelta(getAllGroupRecords(groupKey), selectedPermissions.value, shouldSelect);
|
||||
await mutatePermissions({
|
||||
...delta,
|
||||
groupKey,
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(loadPageData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessSuperUser()">
|
||||
<RolesSubPageWrapper>
|
||||
<template #title>
|
||||
<PageTitle :title="`Role ${roleId}`" subtitle="Role permissions">
|
||||
<PageTitle :title="roleTitle" :subtitle="t('roles.permissions.subtitle')">
|
||||
<template #buttons>
|
||||
<button class="button is-dark" type="button" :disabled="isLoading" @click="loadPageData">
|
||||
<span class="icon">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</span>
|
||||
<span>{{ t("roles.permissions.actions.reload") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
</PageTitle>
|
||||
</template>
|
||||
<!-- Department access permissions -->
|
||||
<div class="box" v-if="isLoaded">
|
||||
<p class="is-pulled-right">
|
||||
<span class="tag is-dark is-large">
|
||||
Department access
|
||||
</span>
|
||||
</p>
|
||||
<p class="mt-2"> Department dependent permissions applies to:</p>
|
||||
<div class="columns is-multiline">
|
||||
<div v-for="department in departments" :key="department.id" class="column is-3">
|
||||
<span class="tag is-dark">
|
||||
{{ department.name }}
|
||||
</span>
|
||||
<!-- Enable switch -->
|
||||
<div class="field">
|
||||
<input
|
||||
:id="'enable_department_' + department.id + '_access'"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
@change="togglePermission('department_access_' + department.id)"
|
||||
:checked="hasPermission('department_access_' + department.id)"
|
||||
/>
|
||||
<label :for="'enable_department_' + department.id + '_access'"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="role-permissions-page" data-testid="role-permissions-page">
|
||||
<div v-if="loadError" class="notification is-danger is-light" data-testid="role-permissions-load-error">
|
||||
<p>{{ loadError }}</p>
|
||||
<button class="button is-danger is-light mt-3" type="button" @click="loadPageData">
|
||||
{{ t("roles.permissions.actions.retry") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page type permissions -->
|
||||
<div class="box">
|
||||
<p class="is-pulled-right">
|
||||
<span class="tag is-dark is-large">
|
||||
Page type access
|
||||
</span>
|
||||
</p>
|
||||
<p class="mt-2"> Page type dependent permissions applies to:</p>
|
||||
<div class="columns is-multiline">
|
||||
<!-- User -->
|
||||
<div class="column is-3">
|
||||
<span class="tag is-dark">
|
||||
User
|
||||
</span>
|
||||
<!-- Enable switch -->
|
||||
<div class="field">
|
||||
<input
|
||||
id="enable_user"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
@change="togglePermission('user')"
|
||||
:checked="hasPermission('user')"
|
||||
/>
|
||||
<label for="enable_user"></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Admin -->
|
||||
<div class="column is-3">
|
||||
<span class="tag is-dark">
|
||||
Admin
|
||||
</span>
|
||||
<!-- Enable switch -->
|
||||
<div class="field">
|
||||
<input
|
||||
id="enable_admin"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
@change="togglePermission('admin')"
|
||||
:checked="hasPermission('admin')"
|
||||
/>
|
||||
<label for="enable_admin"></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Super user -->
|
||||
<div class="column is-3">
|
||||
<span class="tag is-dark">
|
||||
Super user
|
||||
</span>
|
||||
<!-- Enable switch -->
|
||||
<div class="field">
|
||||
<input
|
||||
id="enable_superuser"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
@change="togglePermission('superuser')"
|
||||
:checked="hasPermission('superuser')"
|
||||
/>
|
||||
<label for="enable_superuser"></label>
|
||||
</div>
|
||||
|
||||
<div v-else-if="isLoading" class="role-permissions-loading" data-testid="role-permissions-loading">
|
||||
<progress class="progress is-small is-dark" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Permissions -->
|
||||
<div v-for="([endpoint, permission]) of Object.entries(permissions).sort((a, b) => a[0].localeCompare(b[0]))" class="box" v-if="isLoaded">
|
||||
<p class="is-pulled-right">
|
||||
<span class="tag is-dark is-large">
|
||||
{{ endpoint }}
|
||||
</span>
|
||||
</p>
|
||||
<!-- Permissions -->
|
||||
<div class="mt-2" v-for="(permission, method) in permission" :key="method">
|
||||
<span class="tag mt-3" :class="endpointMethodColors[method]">
|
||||
<strong class="has-text-black">{{ method }}</strong>
|
||||
</span>
|
||||
<strong> {{ endpoint}}</strong> {{ convertRouteToHumanReadable(endpoint) }}
|
||||
<!--{{ permission }} -->
|
||||
<p class="mt-1"> Permissions </p>
|
||||
<div v-for="(permission, permission_node) in permission" :key="permission_node">
|
||||
<span class="tag is-dark">
|
||||
{{ permission_node }}
|
||||
</span>
|
||||
{{ permission }}
|
||||
<!-- Enable switch -->
|
||||
<div class="field">
|
||||
<input
|
||||
:id="'enable_' + permission_node"
|
||||
type="checkbox"
|
||||
class="switch is-rounded"
|
||||
:checked="hasPermission(permission_node)"
|
||||
@change="togglePermission(permission_node)"
|
||||
/>
|
||||
<label :for="'enable_' + permission_node"></label>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="mutationError" class="notification is-danger is-light" data-testid="role-permission-error">
|
||||
{{ mutationError }}
|
||||
</div>
|
||||
|
||||
<div class="role-permissions-toolbar">
|
||||
<div class="field role-permissions-search">
|
||||
<label class="label" for="role-permissions-search">
|
||||
{{ t("roles.permissions.search_label") }}
|
||||
</label>
|
||||
<div class="control has-icons-left">
|
||||
<input
|
||||
id="role-permissions-search"
|
||||
v-model="search"
|
||||
class="input"
|
||||
type="search"
|
||||
:placeholder="t('roles.permissions.search_placeholder')"
|
||||
data-testid="role-permissions-search"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="role-permissions-summary" aria-live="polite">
|
||||
<span class="tag is-dark is-light" data-testid="role-permissions-selected-count">
|
||||
{{ t("roles.permissions.selected_summary", { selected: selectedCount, total: totalCount }) }}
|
||||
</span>
|
||||
<span v-if="search" class="tag is-info is-light" data-testid="role-permissions-filtered-count">
|
||||
{{ t("roles.permissions.filtered_summary", { count: filteredCount }) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<pre>{{ permission }}</pre> -->
|
||||
</div>
|
||||
|
||||
<b-tabs
|
||||
v-if="groupTabs.length"
|
||||
v-model="activeGroup"
|
||||
expanded
|
||||
type="is-boxed"
|
||||
data-testid="role-permissions-tabs"
|
||||
>
|
||||
<b-tab-item
|
||||
v-for="group in groupTabs"
|
||||
:key="group.key"
|
||||
:value="group.key"
|
||||
:label="`${group.label} (${group.filteredCount})`"
|
||||
:icon="group.icon"
|
||||
icon-pack="fas"
|
||||
>
|
||||
<section class="role-permission-group" :data-testid="`role-permissions-group-${group.key}`">
|
||||
<div class="role-permission-group-header">
|
||||
<div>
|
||||
<h2 class="title is-5">{{ group.label }}</h2>
|
||||
<p class="role-permission-group-description">{{ group.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="buttons role-permission-group-actions">
|
||||
<button
|
||||
class="button is-dark is-small"
|
||||
type="button"
|
||||
:disabled="isGroupBusy(group.key) || isGroupFullySelected(group.key)"
|
||||
:data-testid="`role-permissions-select-all-${group.key}`"
|
||||
@click="applyGroupSelection(group.key, true)"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-check-square"></i>
|
||||
</span>
|
||||
<span>{{ t("roles.permissions.actions.select_all") }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="button is-light is-small"
|
||||
type="button"
|
||||
:disabled="isGroupBusy(group.key) || isGroupFullyCleared(group.key)"
|
||||
:data-testid="`role-permissions-clear-all-${group.key}`"
|
||||
@click="applyGroupSelection(group.key, false)"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-minus-square"></i>
|
||||
</span>
|
||||
<span>{{ t("roles.permissions.actions.clear_all") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="getFilteredGroupRecords(group.key).length === 0"
|
||||
class="notification is-light"
|
||||
:data-testid="`role-permissions-empty-${group.key}`"
|
||||
>
|
||||
{{ search ? t("roles.permissions.no_search_results") : t("roles.permissions.empty_group") }}
|
||||
</div>
|
||||
|
||||
<div v-else class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable role-permissions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t("roles.permissions.columns.enabled") }}</th>
|
||||
<th>{{ t("roles.permissions.columns.permission") }}</th>
|
||||
<th>{{ t("roles.permissions.columns.route") }}</th>
|
||||
<th>{{ t("roles.permissions.columns.status") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="record in getFilteredGroupRecords(group.key)"
|
||||
:key="record.permission"
|
||||
:class="{ 'role-permission-selected': hasPermission(record.permission) }"
|
||||
:data-testid="permissionTestId(record.permission)"
|
||||
>
|
||||
<td class="role-permission-toggle-cell">
|
||||
<div class="field">
|
||||
<input
|
||||
:id="permissionDomId(record.permission)"
|
||||
class="switch is-rounded"
|
||||
type="checkbox"
|
||||
:checked="hasPermission(record.permission)"
|
||||
:disabled="isPermissionBusy(record.permission)"
|
||||
:data-testid="`role-permission-toggle-${sanitizePermissionDomId(record.permission)}`"
|
||||
@change="togglePermission(record)"
|
||||
/>
|
||||
<label :for="permissionDomId(record.permission)"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ record.label }}</strong>
|
||||
<p class="role-permission-description">
|
||||
{{ record.description || t("roles.permissions.no_description") }}
|
||||
</p>
|
||||
<code>{{ record.permission }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<div class="role-permission-route">
|
||||
<span>{{ record.endpointLabel || t("roles.permissions.access_scope") }}</span>
|
||||
<span v-if="record.method" class="tag is-small" :class="record.methodColor">
|
||||
{{ record.method }}
|
||||
</span>
|
||||
</div>
|
||||
<code v-if="record.endpoint">{{ record.endpoint }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="tag"
|
||||
:class="hasPermission(record.permission) ? 'is-success is-light' : 'is-light'"
|
||||
>
|
||||
{{
|
||||
hasPermission(record.permission)
|
||||
? t("roles.permissions.status.enabled")
|
||||
: t("roles.permissions.status.disabled")
|
||||
}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</b-tab-item>
|
||||
</b-tabs>
|
||||
|
||||
<div v-else class="notification is-light" data-testid="role-permissions-empty">
|
||||
{{ t("roles.permissions.empty") }}
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</RolesSubPageWrapper>
|
||||
</RestrictedPageWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.role-permissions-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
.role-permissions-loading {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.role-permissions-toolbar {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.role-permissions-search {
|
||||
flex: 1 1 28rem;
|
||||
max-width: 42rem;
|
||||
}
|
||||
|
||||
.role-permissions-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.role-permission-group {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.role-permission-group-header {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.role-permission-group-description,
|
||||
.role-permission-description {
|
||||
color: #5f6672;
|
||||
}
|
||||
|
||||
.role-permission-group-actions {
|
||||
flex: 0 0 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.role-permissions-table th:first-child,
|
||||
.role-permission-toggle-cell {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.role-permission-route {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.role-permission-selected {
|
||||
background: #f2fbf5;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.role-permissions-toolbar,
|
||||
.role-permission-group-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.role-permissions-search {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.role-permissions-summary,
|
||||
.role-permission-group-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
export const endpointMethodColors = {
|
||||
GET: "is-success",
|
||||
POST: "is-info",
|
||||
PUT: "is-warning",
|
||||
PATCH: "is-warning",
|
||||
DELETE: "is-danger",
|
||||
OPTIONS: "is-light",
|
||||
};
|
||||
|
||||
export const pageAccessPermissionKeys = ["user", "admin", "superuser"];
|
||||
|
||||
export const permissionGroupDefinitions = [
|
||||
{
|
||||
key: "access",
|
||||
icon: "unlock",
|
||||
patterns: [/^(user|admin|superuser)$/u, /^department_access_\d+$/u],
|
||||
},
|
||||
{
|
||||
key: "orders_pos",
|
||||
icon: "cash-register",
|
||||
patterns: [/order/iu, /pos/iu, /stripe/iu, /payment/iu, /xlvask/iu],
|
||||
},
|
||||
{
|
||||
key: "bookings",
|
||||
icon: "calendar-alt",
|
||||
patterns: [/booking/iu, /time[-_]?booking/iu],
|
||||
},
|
||||
{
|
||||
key: "customers_users",
|
||||
icon: "users",
|
||||
patterns: [/customer/iu, /user/iu, /subuser/iu, /employee/iu, /auth/iu, /role/iu, /permission/iu],
|
||||
},
|
||||
{
|
||||
key: "departments_operations",
|
||||
icon: "building",
|
||||
patterns: [/department/iu, /lane/iu, /relay/iu, /daily[-_]?report/iu, /complaint/iu, /goal/iu, /notification/iu],
|
||||
},
|
||||
{
|
||||
key: "products_pricing",
|
||||
icon: "tags",
|
||||
patterns: [/product/iu, /price/iu, /pricing/iu, /discount/iu, /currency/iu],
|
||||
},
|
||||
{
|
||||
key: "invoicing_economic",
|
||||
icon: "file-invoice",
|
||||
patterns: [/invoice/iu, /economic/iu, /billing/iu, /collection/iu],
|
||||
},
|
||||
{
|
||||
key: "selfserve_edge",
|
||||
icon: "network-wired",
|
||||
patterns: [/self[-_]?serve/iu, /selfserve/iu, /edge/iu, /gateway/iu, /scanner/iu, /wash/iu, /shelly/iu],
|
||||
},
|
||||
{
|
||||
key: "system_config",
|
||||
icon: "cogs",
|
||||
patterns: [/system/iu, /config/iu, /configuration/iu, /replication/iu, /release/iu, /coolify/iu, /module/iu, /backup/iu, /workfeed/iu, /error[-_]?report/iu],
|
||||
},
|
||||
{
|
||||
key: "other",
|
||||
icon: "key",
|
||||
patterns: [],
|
||||
},
|
||||
];
|
||||
|
||||
const permissionGroupByKey = new Map(permissionGroupDefinitions.map((group) => [group.key, group]));
|
||||
const permissionGroupMatchPriority = [
|
||||
"access",
|
||||
"orders_pos",
|
||||
"bookings",
|
||||
"departments_operations",
|
||||
"products_pricing",
|
||||
"invoicing_economic",
|
||||
"selfserve_edge",
|
||||
"system_config",
|
||||
"customers_users",
|
||||
];
|
||||
const permissionGroupsForMatching = permissionGroupDefinitions
|
||||
.filter((group) => group.key !== "other")
|
||||
.sort(
|
||||
(left, right) =>
|
||||
permissionGroupMatchPriority.indexOf(left.key) - permissionGroupMatchPriority.indexOf(right.key)
|
||||
);
|
||||
|
||||
const normalizeText = (value) =>
|
||||
String(value ?? "")
|
||||
.toLowerCase()
|
||||
.normalize("NFKD")
|
||||
.replace(/[\u0300-\u036f]/gu, "")
|
||||
.trim();
|
||||
|
||||
export const sanitizePermissionDomId = (value) =>
|
||||
String(value ?? "")
|
||||
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.toLowerCase() || "permission";
|
||||
|
||||
const splitHumanWords = (value) =>
|
||||
String(value ?? "")
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
||||
.replace(/[_./:-]+/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
export const humanizePermissionKey = (permission) => {
|
||||
const words = splitHumanWords(permission);
|
||||
if (!words) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return words.charAt(0).toUpperCase() + words.slice(1);
|
||||
};
|
||||
|
||||
export const humanizeEndpoint = (endpoint) => {
|
||||
const words = splitHumanWords(String(endpoint ?? "").replace(/^\/+|\/+$/g, ""));
|
||||
if (!words) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return words.charAt(0).toUpperCase() + words.slice(1);
|
||||
};
|
||||
|
||||
export const getPermissionGroupKey = ({ permission = "", endpoint = "", description = "" } = {}) => {
|
||||
const haystack = `${permission} ${endpoint} ${description}`;
|
||||
const match = permissionGroupsForMatching.find((group) =>
|
||||
group.patterns.some((pattern) => pattern.test(haystack))
|
||||
);
|
||||
|
||||
return match?.key || "other";
|
||||
};
|
||||
|
||||
const buildSearchText = (record) =>
|
||||
normalizeText(
|
||||
[
|
||||
record.permission,
|
||||
record.label,
|
||||
record.description,
|
||||
record.endpoint,
|
||||
record.endpointLabel,
|
||||
record.method,
|
||||
record.groupKey,
|
||||
].join(" ")
|
||||
);
|
||||
|
||||
const createPermissionRecord = ({
|
||||
permission,
|
||||
description = "",
|
||||
endpoint = "",
|
||||
method = "",
|
||||
permissionOverrides = {},
|
||||
}) => {
|
||||
const key = String(permission || "").trim();
|
||||
const override = permissionOverrides[key] || {};
|
||||
const record = {
|
||||
id: key,
|
||||
permission: key,
|
||||
label: override.label || humanizePermissionKey(key),
|
||||
description: override.description || String(description || ""),
|
||||
endpoint: String(endpoint || ""),
|
||||
endpointLabel: humanizeEndpoint(endpoint),
|
||||
method: String(method || ""),
|
||||
methodColor: endpointMethodColors[String(method || "").toUpperCase()] || "is-light",
|
||||
groupKey: override.groupKey || getPermissionGroupKey({ permission: key, endpoint, description }),
|
||||
isVirtual: Boolean(override.isVirtual),
|
||||
departmentId: override.departmentId || null,
|
||||
};
|
||||
|
||||
record.searchText = buildSearchText(record);
|
||||
return record;
|
||||
};
|
||||
|
||||
const createAccessPermissionRecords = ({ departments = [], permissionOverrides = {} }) => {
|
||||
const pageRecords = pageAccessPermissionKeys.map((permission) =>
|
||||
createPermissionRecord({
|
||||
permission,
|
||||
permissionOverrides: {
|
||||
...permissionOverrides,
|
||||
[permission]: {
|
||||
...(permissionOverrides[permission] || {}),
|
||||
groupKey: "access",
|
||||
isVirtual: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const departmentRecords = departments.map((department) => {
|
||||
const departmentId = Number.parseInt(String(department?.id ?? ""), 10);
|
||||
const permission = `department_access_${departmentId}`;
|
||||
const override = permissionOverrides[permission] || {};
|
||||
|
||||
return createPermissionRecord({
|
||||
permission,
|
||||
permissionOverrides: {
|
||||
...permissionOverrides,
|
||||
[permission]: {
|
||||
label: override.label || String(department?.name || permission),
|
||||
description: override.description || "",
|
||||
groupKey: "access",
|
||||
isVirtual: true,
|
||||
departmentId,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return [...pageRecords, ...departmentRecords].filter((record) => record.permission && record.permission !== "department_access_NaN");
|
||||
};
|
||||
|
||||
export const normalizePermissionCatalog = (catalog = {}, options = {}) => {
|
||||
const permissionOverrides = options.permissionOverrides || {};
|
||||
const recordsByPermission = new Map();
|
||||
|
||||
for (const record of createAccessPermissionRecords({
|
||||
departments: options.departments || [],
|
||||
permissionOverrides,
|
||||
})) {
|
||||
recordsByPermission.set(record.permission, record);
|
||||
}
|
||||
|
||||
for (const [endpoint, methods] of Object.entries(catalog || {})) {
|
||||
if (!methods || typeof methods !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [method, permissions] of Object.entries(methods)) {
|
||||
if (!permissions || typeof permissions !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [permission, description] of Object.entries(permissions)) {
|
||||
const key = String(permission || "").trim();
|
||||
if (!key || recordsByPermission.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recordsByPermission.set(
|
||||
key,
|
||||
createPermissionRecord({
|
||||
permission: key,
|
||||
description,
|
||||
endpoint,
|
||||
method,
|
||||
permissionOverrides,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...recordsByPermission.values()].sort((left, right) => {
|
||||
const groupCompare =
|
||||
permissionGroupDefinitions.findIndex((group) => group.key === left.groupKey) -
|
||||
permissionGroupDefinitions.findIndex((group) => group.key === right.groupKey);
|
||||
if (groupCompare !== 0) {
|
||||
return groupCompare;
|
||||
}
|
||||
|
||||
return left.label.localeCompare(right.label, undefined, { sensitivity: "base" });
|
||||
});
|
||||
};
|
||||
|
||||
export const filterPermissionRecords = (records = [], search = "") => {
|
||||
const normalizedSearch = normalizeText(search);
|
||||
if (!normalizedSearch) {
|
||||
return [...records];
|
||||
}
|
||||
|
||||
return records.filter((record) => record.searchText.includes(normalizedSearch));
|
||||
};
|
||||
|
||||
export const groupPermissionRecords = (records = []) => {
|
||||
const groups = new Map(permissionGroupDefinitions.map((group) => [group.key, []]));
|
||||
|
||||
for (const record of records) {
|
||||
const groupKey = permissionGroupByKey.has(record.groupKey) ? record.groupKey : "other";
|
||||
groups.get(groupKey).push(record);
|
||||
}
|
||||
|
||||
return groups;
|
||||
};
|
||||
|
||||
export const getPermissionDelta = (records = [], selectedPermissions = [], shouldSelect = true) => {
|
||||
const selectedSet = selectedPermissions instanceof Set ? selectedPermissions : new Set(selectedPermissions);
|
||||
const uniquePermissions = [...new Set(records.map((record) => record.permission).filter(Boolean))];
|
||||
|
||||
if (shouldSelect) {
|
||||
return {
|
||||
add: uniquePermissions.filter((permission) => !selectedSet.has(permission)),
|
||||
remove: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
add: [],
|
||||
remove: uniquePermissions.filter((permission) => selectedSet.has(permission)),
|
||||
};
|
||||
};
|
||||
@@ -1340,6 +1340,10 @@ const clearRecentCompletedRefreshInterval = () => {
|
||||
};
|
||||
|
||||
const scheduleRecentCompletedWashRefresh = (attempt = 0) => {
|
||||
if (isMyWashStartUnmounted.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearRecentCompletedRefreshTimeout();
|
||||
|
||||
if (refreshRecentlyCompletedWashState() || attempt >= 20 || isMyWashStartUnmounted.value) {
|
||||
@@ -1347,6 +1351,10 @@ const scheduleRecentCompletedWashRefresh = (attempt = 0) => {
|
||||
}
|
||||
|
||||
recentCompletedRefreshTimeout.value = window.setTimeout(() => {
|
||||
if (isMyWashStartUnmounted.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleRecentCompletedWashRefresh(attempt + 1);
|
||||
}, 250);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import { expect, Page, test } from "@playwright/test";
|
||||
import { apiPathPattern, mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
|
||||
type NotificationSmsRow = {
|
||||
id: number;
|
||||
department: number;
|
||||
label: string;
|
||||
phone_country_code: number;
|
||||
phone: number;
|
||||
enabled: boolean;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
const json = (body: unknown, status = 200) => ({
|
||||
status,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const setupNotificationSmsApi = async (page: Page) => {
|
||||
let listRequestCount = 0;
|
||||
const mutations: Array<{ method: string; body?: Record<string, unknown>; id?: number }> = [];
|
||||
let rows: NotificationSmsRow[] = [
|
||||
{
|
||||
id: 501,
|
||||
department: 1,
|
||||
label: "Dispatch line",
|
||||
phone_country_code: 45,
|
||||
phone: 12345678,
|
||||
enabled: true,
|
||||
created_at: "2026-07-06 12:00:00",
|
||||
},
|
||||
];
|
||||
|
||||
await page.route(apiPathPattern("/department/notification/sms"), async (route) => {
|
||||
const request = route.request();
|
||||
const method = request.method();
|
||||
|
||||
if (method === "GET") {
|
||||
listRequestCount += 1;
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: rows,
|
||||
meta: {
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
total: rows.length,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "PUT") {
|
||||
const body = request.postDataJSON() as Record<string, unknown>;
|
||||
mutations.push({ method, body });
|
||||
rows = rows.map((row) =>
|
||||
Number(row.id) === Number(body.id)
|
||||
? {
|
||||
...row,
|
||||
enabled: body.enabled === true,
|
||||
}
|
||||
: row
|
||||
);
|
||||
|
||||
await route.fulfill(json({ data: { message: "Department notification SMS updated" } }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "DELETE") {
|
||||
const id = Number(new URL(request.url()).searchParams.get("id") || 0);
|
||||
mutations.push({ method, id });
|
||||
rows = rows.filter((row) => Number(row.id) !== id);
|
||||
|
||||
await route.fulfill(json({ data: { message: "Department notification SMS deleted" } }));
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill(json({ message: "Unsupported notification SMS method" }, 405));
|
||||
});
|
||||
|
||||
return {
|
||||
mutations,
|
||||
get listRequestCount() {
|
||||
return listRequestCount;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
test("@smoke department notification SMS active toggle and delete refresh the table", async ({ page }) => {
|
||||
const notificationErrors: Array<{ status: number; url: string }> = [];
|
||||
page.on("response", (response) => {
|
||||
if (response.url().includes("/department/notification/sms") && response.status() >= 400) {
|
||||
notificationErrors.push({
|
||||
status: response.status(),
|
||||
url: response.url(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await seedAuthenticatedState(page, "admin-department-notifications-token");
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: [
|
||||
"admin",
|
||||
"department_access_1",
|
||||
"department_notification_sms_get",
|
||||
"department_notification_sms_update",
|
||||
"department_notification_sms_delete",
|
||||
"department_notification_sms_add",
|
||||
],
|
||||
});
|
||||
const notificationSmsApi = await setupNotificationSmsApi(page);
|
||||
|
||||
await page.goto("/admin/1/modules/notifications", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const row = page.locator("tr", { hasText: "Dispatch line" });
|
||||
await expect(row).toBeVisible();
|
||||
const enabledToggle = page.getByTestId("department-notification-sms-enabled-toggle-501");
|
||||
await expect(enabledToggle).toBeChecked();
|
||||
|
||||
const listRequestsBeforeToggle = notificationSmsApi.listRequestCount;
|
||||
const updateRequestPromise = page.waitForRequest(
|
||||
(request) => request.method() === "PUT" && request.url().includes("/department/notification/sms")
|
||||
);
|
||||
await enabledToggle.uncheck({ force: true });
|
||||
|
||||
const updateRequest = await updateRequestPromise;
|
||||
expect(updateRequest.postDataJSON()).toMatchObject({
|
||||
id: 501,
|
||||
enabled: false,
|
||||
});
|
||||
await expect.poll(() => notificationSmsApi.listRequestCount).toBeGreaterThan(listRequestsBeforeToggle);
|
||||
await expect(enabledToggle).not.toBeChecked();
|
||||
|
||||
const listRequestsBeforeDelete = notificationSmsApi.listRequestCount;
|
||||
await row.locator('button[aria-haspopup="true"]').click();
|
||||
const deleteRequestPromise = page.waitForRequest(
|
||||
(request) => request.method() === "DELETE" && request.url().includes("/department/notification/sms")
|
||||
);
|
||||
await page.getByTestId("department-notification-sms-delete-501").click();
|
||||
await expect(page.locator(".swal2-popup")).toBeVisible();
|
||||
await page.locator(".swal2-confirm").click();
|
||||
|
||||
const deleteRequest = await deleteRequestPromise;
|
||||
expect(new URL(deleteRequest.url()).searchParams.get("id")).toBe("501");
|
||||
await expect.poll(() => notificationSmsApi.listRequestCount).toBeGreaterThan(listRequestsBeforeDelete);
|
||||
await expect(page.locator("tr", { hasText: "Dispatch line" })).toHaveCount(0);
|
||||
expect(notificationErrors).toEqual([]);
|
||||
expect(notificationSmsApi.mutations).toEqual([
|
||||
{
|
||||
method: "PUT",
|
||||
body: {
|
||||
id: 501,
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "DELETE",
|
||||
id: 501,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -1933,6 +1933,111 @@ test.describe("Invoicing period tab", () => {
|
||||
await expect(page).toHaveURL(/activeTab=period/);
|
||||
});
|
||||
|
||||
test("@smoke period view warns and splits selected multi-month invoice collections", async ({ page }) => {
|
||||
const splitRequests = [];
|
||||
const economicInvoiceRequests = [];
|
||||
await openPeriodView(page, {
|
||||
payloadFactory: () => ({
|
||||
types: {
|
||||
all: [
|
||||
{
|
||||
id: 31,
|
||||
customer_number: 4301,
|
||||
customer_name: "Multi Month Fleet",
|
||||
requires_action: true,
|
||||
transactions: [
|
||||
{
|
||||
id: 8801,
|
||||
date: "2026-03-28T10:00:00.000Z",
|
||||
amount: 120,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 88001,
|
||||
},
|
||||
{
|
||||
id: 8802,
|
||||
date: "2026-04-02T10:00:00.000Z",
|
||||
amount: 180,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 88001,
|
||||
},
|
||||
],
|
||||
queue: {
|
||||
has_active_job: false,
|
||||
statuses: [],
|
||||
invoice_collection_ids: [],
|
||||
is_action_blocked: false,
|
||||
},
|
||||
meta: {},
|
||||
},
|
||||
],
|
||||
invoice_per_order: [],
|
||||
fixed_pricing: [],
|
||||
tank_cleaning: [],
|
||||
special_arrangements: [],
|
||||
vehicle_subscriptions: [],
|
||||
possible_duplicates: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await page.route("**/collected-invoices/split-by-month**", async (route) => {
|
||||
if (
|
||||
route.request().method() !== "POST" ||
|
||||
!matchesApiPath(route.request().url(), "/collected-invoices/split-by-month")
|
||||
) {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = JSON.parse(route.request().postData() || "{}");
|
||||
splitRequests.push(payload);
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: {
|
||||
preview: false,
|
||||
processed_count: 1,
|
||||
changed_count: 1,
|
||||
skipped_count: 0,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route("**/collected-invoices/economic**", async (route) => {
|
||||
if (
|
||||
route.request().method() === "POST" &&
|
||||
matchesApiPath(route.request().url(), "/collected-invoices/economic")
|
||||
) {
|
||||
economicInvoiceRequests.push(JSON.parse(route.request().postData() || "{}"));
|
||||
await route.fulfill(json({ data: {} }));
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fallback();
|
||||
});
|
||||
|
||||
await page.getByTestId("invoicing-period-view-selector-all").click();
|
||||
await expect(page.getByTestId("invoicing-period-customer-4301")).toBeVisible();
|
||||
await page.getByTestId("invoicing-period-customer-invoice-4301").click();
|
||||
|
||||
await expect(
|
||||
page.getByRole("heading", { name: /Orders from multiple months|Ordrer fra flere måneder/i })
|
||||
).toBeVisible();
|
||||
await page.getByRole("button", { name: /Split by month|Opdel efter måned/i }).click();
|
||||
|
||||
await expect.poll(() => splitRequests.length).toBe(1);
|
||||
expect(splitRequests[0]).toEqual({
|
||||
dateFrom: "2026-03-28",
|
||||
dateTo: "2026-04-02",
|
||||
invoice_collection_ids: [88001],
|
||||
preview: false,
|
||||
});
|
||||
expect(economicInvoiceRequests).toEqual([]);
|
||||
await expect(page.getByText(/Monthly split completed|Månedsopdeling fuldført/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test("@smoke period view invoices multiple customers with page refresh and independent loading", async ({ page }) => {
|
||||
const token = "superuser-period-parallel-token";
|
||||
const periodRequests = [];
|
||||
|
||||
@@ -82,12 +82,128 @@ const pricePayload = {
|
||||
],
|
||||
};
|
||||
|
||||
const rolePermissionGroups = {
|
||||
viewer: [{ key: "account", capabilities: ["sign_in", "view_own_permissions"] }],
|
||||
cashier: [
|
||||
{ key: "account", capabilities: ["sign_in", "view_own_permissions"] },
|
||||
{
|
||||
key: "orders",
|
||||
capabilities: [
|
||||
"view_orders",
|
||||
"create_orders",
|
||||
"edit_orders",
|
||||
"view_order_items",
|
||||
"create_order_items",
|
||||
"update_order_lines",
|
||||
"remove_order_lines",
|
||||
"charge_orders",
|
||||
],
|
||||
},
|
||||
],
|
||||
booking_coordinator: [
|
||||
{ key: "account", capabilities: ["sign_in", "view_own_permissions"] },
|
||||
{ key: "orders", capabilities: ["view_orders"] },
|
||||
{
|
||||
key: "bookings",
|
||||
capabilities: [
|
||||
"view_department_bookings",
|
||||
"view_own_bookings",
|
||||
"update_bookings",
|
||||
"create_bookings",
|
||||
"mark_bookings_complete",
|
||||
"send_booking_confirmations",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "time_bookings",
|
||||
capabilities: ["view_time_booking_entries", "create_time_booking_entries", "edit_time_booking_entries"],
|
||||
},
|
||||
],
|
||||
operations_lead: [
|
||||
{ key: "account", capabilities: ["sign_in", "view_own_permissions"] },
|
||||
{
|
||||
key: "orders",
|
||||
capabilities: [
|
||||
"view_orders",
|
||||
"create_orders",
|
||||
"edit_orders",
|
||||
"delete_orders",
|
||||
"view_order_items",
|
||||
"create_order_items",
|
||||
"update_order_lines",
|
||||
"remove_order_lines",
|
||||
"charge_orders",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "bookings",
|
||||
capabilities: [
|
||||
"view_department_bookings",
|
||||
"view_own_bookings",
|
||||
"update_bookings",
|
||||
"create_bookings",
|
||||
"mark_bookings_complete",
|
||||
"send_booking_confirmations",
|
||||
],
|
||||
},
|
||||
{ key: "reports", capabilities: ["view_order_statistics", "view_booking_statistics"] },
|
||||
],
|
||||
department_admin: [
|
||||
{ key: "account", capabilities: ["sign_in", "view_own_permissions"] },
|
||||
{
|
||||
key: "orders",
|
||||
capabilities: [
|
||||
"view_orders",
|
||||
"create_orders",
|
||||
"edit_orders",
|
||||
"delete_orders",
|
||||
"view_order_items",
|
||||
"create_order_items",
|
||||
"update_order_lines",
|
||||
"remove_order_lines",
|
||||
"charge_orders",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "bookings",
|
||||
capabilities: [
|
||||
"view_department_bookings",
|
||||
"view_own_bookings",
|
||||
"update_bookings",
|
||||
"create_bookings",
|
||||
"mark_bookings_complete",
|
||||
"send_booking_confirmations",
|
||||
],
|
||||
},
|
||||
{ key: "reports", capabilities: ["view_order_statistics", "view_booking_statistics"] },
|
||||
{
|
||||
key: "limited_backoffice",
|
||||
capabilities: ["open_limited_backoffice", "manage_department_prices", "manage_employee_access"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const rolesPayload = [
|
||||
{ key: "viewer", label: "Viewer", description: "Can view." },
|
||||
{ key: "cashier", label: "Cashier", description: "Can sell." },
|
||||
{ key: "booking_coordinator", label: "Booking coordinator", description: "Can coordinate." },
|
||||
{ key: "operations_lead", label: "Operations lead", description: "Can coordinate operations." },
|
||||
{ key: "department_admin", label: "Department admin", description: "Can administer." },
|
||||
{ key: "viewer", label: "Viewer", description: "Can view.", permission_groups: rolePermissionGroups.viewer },
|
||||
{ key: "cashier", label: "Cashier", description: "Can sell.", permission_groups: rolePermissionGroups.cashier },
|
||||
{
|
||||
key: "booking_coordinator",
|
||||
label: "Booking coordinator",
|
||||
description: "Can coordinate.",
|
||||
permission_groups: rolePermissionGroups.booking_coordinator,
|
||||
},
|
||||
{
|
||||
key: "operations_lead",
|
||||
label: "Operations lead",
|
||||
description: "Can coordinate operations.",
|
||||
permission_groups: rolePermissionGroups.operations_lead,
|
||||
},
|
||||
{
|
||||
key: "department_admin",
|
||||
label: "Department admin",
|
||||
description: "Can administer.",
|
||||
permission_groups: rolePermissionGroups.department_admin,
|
||||
},
|
||||
{ key: "superuser", label: "Superuser", description: "Must not render." },
|
||||
];
|
||||
|
||||
@@ -271,6 +387,55 @@ test.describe("Limited backoffice", () => {
|
||||
expect(api.forbiddenCalls).toEqual([]);
|
||||
});
|
||||
|
||||
test("shows grouped human-readable role permission help without raw permission keys", async ({ page }, testInfo) => {
|
||||
await seedLimitedBackofficeSession(page);
|
||||
await mockLimitedBackofficeApi(page);
|
||||
|
||||
await page.goto("/backoffice/employees");
|
||||
|
||||
const helpButton = page.getByTestId("limited-employee-role-help");
|
||||
await expect(helpButton).toBeVisible();
|
||||
await expect(helpButton).toHaveAttribute("title", "View role permissions");
|
||||
|
||||
if (isDesktopProject(testInfo)) {
|
||||
await helpButton.hover();
|
||||
await expect(page.locator(".tooltip-content").filter({ hasText: "View role permissions" })).toBeVisible();
|
||||
}
|
||||
|
||||
await helpButton.click();
|
||||
|
||||
const modal = page.getByTestId("limited-role-permissions-modal");
|
||||
await expect(modal).toBeVisible();
|
||||
await expect(modal).toContainText("Role permissions");
|
||||
await expect(modal).toContainText("Every capability is limited to the departments selected for the employee.");
|
||||
await expect(page.getByTestId("limited-role-permissions-role-cashier")).toContainText("Cashier");
|
||||
await expect(page.getByTestId("limited-role-permissions-role-cashier")).toContainText("Orders");
|
||||
await expect(page.getByTestId("limited-role-permissions-role-cashier")).toContainText("View orders");
|
||||
await expect(page.getByTestId("limited-role-permissions-role-cashier")).toContainText("Create order lines");
|
||||
await expect(page.getByTestId("limited-role-permissions-role-viewer")).toContainText("Selected");
|
||||
|
||||
for (const rawPermission of [
|
||||
"list_orders",
|
||||
"add_order",
|
||||
"edit_order_items",
|
||||
"delete_order_items",
|
||||
"department_timebookings_entries_get",
|
||||
"limited_backoffice_employees_manage",
|
||||
"department_access_1",
|
||||
"raw_permissions",
|
||||
]) {
|
||||
await expect(page.locator("body")).not.toContainText(rawPermission);
|
||||
}
|
||||
|
||||
await page.getByTestId("limited-role-permissions-close-footer").click();
|
||||
await expect(page.getByTestId("limited-role-permissions-modal")).toHaveCount(0);
|
||||
|
||||
await helpButton.click();
|
||||
await expect(page.getByTestId("limited-role-permissions-modal")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByTestId("limited-role-permissions-modal")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("does not render data for a department outside the manager scope", async ({ page }, testInfo) => {
|
||||
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await page.goto("/user/wash");
|
||||
|
||||
await expect(page.getByRole("link", { name: "Start vask" })).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByRole("link", { name: "Start vask" })).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByText("Roskilde").first()).toBeVisible();
|
||||
|
||||
const orderedDepartmentNames = await page
|
||||
@@ -268,7 +268,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: "domcontentloaded" });
|
||||
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId("self-serve-inline-progress")).toContainText("ZZ00000", { timeout: 20_000 });
|
||||
|
||||
await advanceGuidedWashToLastStep(page);
|
||||
await expect(page.getByTestId("self-serve-guided-next")).toBeHidden();
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import { expect, test, type Page, type Route } from "@playwright/test";
|
||||
|
||||
import { apiPathPattern, mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
|
||||
// This route is sensitive to stale cached Vue modules when the PWA service worker is active in mobile runs.
|
||||
test.use({ serviceWorkers: "block" });
|
||||
|
||||
const json = (body: unknown, status = 200) => ({
|
||||
status,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const permissionCatalog = {
|
||||
"/orders": {
|
||||
GET: {
|
||||
list_orders: "List orders",
|
||||
},
|
||||
POST: {
|
||||
add_order: "Create orders",
|
||||
},
|
||||
},
|
||||
"/admin/bookings": {
|
||||
POST: {
|
||||
create_bookings: "Create bookings",
|
||||
},
|
||||
},
|
||||
"/superuser/system/replication": {
|
||||
GET: {
|
||||
superuser_replication_view: "View replication status",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const departments = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Copenhagen",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Odense",
|
||||
},
|
||||
];
|
||||
|
||||
type PermissionCall = {
|
||||
action: "add" | "remove";
|
||||
permission: string;
|
||||
};
|
||||
|
||||
const openGroupTab = async (page: Page, label: RegExp) => {
|
||||
await page.locator(".tabs").getByText(label).click();
|
||||
};
|
||||
|
||||
const clickPermissionSwitch = async (page: Page, permission: string) => {
|
||||
await page.locator(`label[for="role-permission-${permission}"]`).click();
|
||||
};
|
||||
|
||||
const installRolePermissionRoutes = async (
|
||||
page: Page,
|
||||
options: {
|
||||
failAction?: "add" | "remove";
|
||||
failPermission?: string;
|
||||
} = {}
|
||||
) => {
|
||||
let rolePermissions = ["user", "superuser", "department_access_1", "list_orders"];
|
||||
const calls: PermissionCall[] = [];
|
||||
|
||||
const rolePayload = () => ({
|
||||
id: 2,
|
||||
name: "Operations manager",
|
||||
description: "Can manage operations.",
|
||||
created_at: "2026-07-06T08:00:00.000Z",
|
||||
permissions: [...rolePermissions],
|
||||
});
|
||||
|
||||
await page.route(apiPathPattern("/permissions"), async (route: Route) => {
|
||||
await route.fulfill(json({ data: permissionCatalog }));
|
||||
});
|
||||
|
||||
await page.route(apiPathPattern("/departments"), async (route: Route) => {
|
||||
await route.fulfill(json({ data: departments }));
|
||||
});
|
||||
|
||||
await page.route(apiPathPattern("/roles"), async (route: Route) => {
|
||||
await route.fulfill(json({ data: rolePayload() }));
|
||||
});
|
||||
|
||||
await page.route(apiPathPattern("/roles/permissions"), async (route: Route) => {
|
||||
const request = route.request();
|
||||
const method = request.method();
|
||||
const url = new URL(request.url());
|
||||
const body = method === "POST" ? request.postDataJSON() : {};
|
||||
const permission =
|
||||
method === "POST" ? String(body.permission_id || "") : String(url.searchParams.get("permission_id") || "");
|
||||
const action = method === "POST" ? "add" : "remove";
|
||||
|
||||
calls.push({ action, permission });
|
||||
|
||||
if (options.failAction === action && options.failPermission === permission) {
|
||||
await route.fulfill(json({ data: { message: "Mutation failed" } }, 500));
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "add" && !rolePermissions.includes(permission)) {
|
||||
rolePermissions = [...rolePermissions, permission];
|
||||
}
|
||||
if (action === "remove") {
|
||||
rolePermissions = rolePermissions.filter((value) => value !== permission);
|
||||
}
|
||||
|
||||
await route.fulfill(json({ data: rolePayload() }));
|
||||
});
|
||||
|
||||
return {
|
||||
calls,
|
||||
getRolePermissions: () => [...rolePermissions],
|
||||
};
|
||||
};
|
||||
|
||||
const bootRolePermissionsPage = async (page: Page, routeOptions = {}) => {
|
||||
await page.addInitScript(() => {
|
||||
window.localStorage.setItem("locale", "en");
|
||||
});
|
||||
await seedAuthenticatedState(page, "superuser-role-permissions-token");
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: [
|
||||
"superuser",
|
||||
"user",
|
||||
"permissions_list",
|
||||
"list_roles",
|
||||
"add_role_permission",
|
||||
"delete_role_permission",
|
||||
],
|
||||
sessionData: {
|
||||
group_id: 1,
|
||||
},
|
||||
});
|
||||
const harness = await installRolePermissionRoutes(page, routeOptions);
|
||||
|
||||
await page.goto("/superuser/roles/2", { waitUntil: "domcontentloaded" });
|
||||
await expect(page.getByTestId("role-permissions-page")).toBeVisible();
|
||||
await expect(page.getByTestId("role-permissions-loading")).toHaveCount(0);
|
||||
|
||||
return harness;
|
||||
};
|
||||
|
||||
test.describe("Superuser role permissions", () => {
|
||||
test("@smoke @pr loads grouped permissions, searches, and toggles without a blank page", async ({ page }) => {
|
||||
const { calls } = await bootRolePermissionsPage(page);
|
||||
|
||||
await expect(page.getByText("Operations manager permissions")).toBeVisible();
|
||||
await expect(page.getByTestId("role-permissions-selected-count")).toContainText("4 of 9 selected");
|
||||
await expect(page.getByRole("heading", { name: "Access" })).toBeVisible();
|
||||
|
||||
await openGroupTab(page, /Orders and POS/);
|
||||
await expect(page.getByTestId("role-permission-row-list_orders")).toContainText("List orders");
|
||||
await clickPermissionSwitch(page, "list_orders");
|
||||
|
||||
await expect.poll(() => calls).toContainEqual({ action: "remove", permission: "list_orders" });
|
||||
await expect(page.getByTestId("role-permissions-page")).toBeVisible();
|
||||
await expect(page.locator("body")).not.toContainText(/white screen|TypeError|Unhandled/i);
|
||||
|
||||
await page.getByTestId("role-permissions-search").fill("booking");
|
||||
await openGroupTab(page, /Bookings/);
|
||||
await expect(page.getByTestId("role-permission-row-create_bookings")).toContainText("Create bookings");
|
||||
await openGroupTab(page, /Orders and POS/);
|
||||
await expect(page.getByTestId("role-permissions-empty-orders_pos")).toBeVisible();
|
||||
});
|
||||
|
||||
test("selects and clears all permissions in a group without no-op requests", async ({ page }) => {
|
||||
const { calls, getRolePermissions } = await bootRolePermissionsPage(page);
|
||||
|
||||
await openGroupTab(page, /Orders and POS/);
|
||||
await page.getByTestId("role-permissions-clear-all-orders_pos").click();
|
||||
await expect.poll(() => calls).toEqual([{ action: "remove", permission: "list_orders" }]);
|
||||
expect(getRolePermissions()).not.toContain("list_orders");
|
||||
expect(getRolePermissions()).not.toContain("add_order");
|
||||
|
||||
await page.getByTestId("role-permissions-select-all-orders_pos").click();
|
||||
await expect
|
||||
.poll(() => calls)
|
||||
.toEqual([
|
||||
{ action: "remove", permission: "list_orders" },
|
||||
{ action: "add", permission: "add_order" },
|
||||
{ action: "add", permission: "list_orders" },
|
||||
]);
|
||||
expect(getRolePermissions()).toEqual(expect.arrayContaining(["list_orders", "add_order"]));
|
||||
});
|
||||
|
||||
test("keeps the page rendered and reloads role permissions when a mutation fails", async ({ page }) => {
|
||||
const { calls, getRolePermissions } = await bootRolePermissionsPage(page, {
|
||||
failAction: "add",
|
||||
failPermission: "add_order",
|
||||
});
|
||||
|
||||
await openGroupTab(page, /Orders and POS/);
|
||||
await clickPermissionSwitch(page, "add_order");
|
||||
|
||||
await expect.poll(() => calls).toContainEqual({ action: "add", permission: "add_order" });
|
||||
await expect(page.getByTestId("role-permission-error")).toBeVisible();
|
||||
await expect(page.getByTestId("role-permissions-page")).toBeVisible();
|
||||
await expect(page.getByTestId("role-permission-toggle-add_order")).not.toBeChecked();
|
||||
expect(getRolePermissions()).not.toContain("add_order");
|
||||
});
|
||||
});
|
||||
@@ -42,6 +42,7 @@ test("[PAGES][User][/user/vehicles] opens the add-vehicle dialog", async ({ page
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.getByRole("heading", { name: /opret/i })).toBeVisible();
|
||||
await expect(dialog.locator("label").filter({ hasText: /^Type$/ })).toBeVisible();
|
||||
await expect(dialog.locator("select#wash_subscription")).toHaveValue("false");
|
||||
await expect(dialog.getByRole("button", { name: /gem/i })).toBeVisible();
|
||||
await expect(dialog.getByRole("button", { name: /annuller/i })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,12 @@ async function primeSuperuserSession(page) {
|
||||
await primeMockSession(page, { token });
|
||||
}
|
||||
|
||||
async function runDiagnostic(page, testId, inputValue = "") {
|
||||
async function expectDiagnosticResult(page, expectedText) {
|
||||
const result = page.locator(".swal2-popup:visible pre").first();
|
||||
await expect(result).toContainText(expectedText);
|
||||
}
|
||||
|
||||
async function runDiagnostic(page, testId, { inputValue = "", expectedText } = {}) {
|
||||
await page.locator(`[data-testid="${testId}"]:visible`).first().click();
|
||||
await expect(page.locator(".swal2-popup")).toBeVisible();
|
||||
const input = page.locator(".swal2-input");
|
||||
@@ -14,10 +19,10 @@ async function runDiagnostic(page, testId, inputValue = "") {
|
||||
await input.fill(inputValue);
|
||||
}
|
||||
await page.locator(".swal2-confirm").click();
|
||||
await expect(page.locator(".swal2-popup pre")).toBeVisible();
|
||||
await expectDiagnosticResult(page, expectedText);
|
||||
}
|
||||
|
||||
async function runShiftDiagnostic(page, { startFrom, startTo, employeeID = "", released = "" }) {
|
||||
async function runShiftDiagnostic(page, { startFrom, startTo, employeeID = "", released = "", expectedText }) {
|
||||
await page.locator('[data-testid="workfeed-diagnostics-list-shifts"]:visible').first().click();
|
||||
await expect(page.locator(".swal2-popup")).toBeVisible();
|
||||
await page.locator("#workfeed-start-from").fill(startFrom);
|
||||
@@ -29,11 +34,13 @@ async function runShiftDiagnostic(page, { startFrom, startTo, employeeID = "", r
|
||||
await page.locator("#workfeed-released").selectOption(released);
|
||||
}
|
||||
await page.locator(".swal2-confirm").click();
|
||||
await expect(page.locator(".swal2-popup pre")).toBeVisible();
|
||||
await expectDiagnosticResult(page, expectedText);
|
||||
}
|
||||
|
||||
async function closeResultModal(page) {
|
||||
await page.locator(".swal2-confirm").click();
|
||||
const confirmButton = page.locator(".swal2-popup:visible .swal2-confirm").first();
|
||||
await expect(confirmButton).toBeVisible();
|
||||
await confirmButton.evaluate((button) => button.click());
|
||||
await expect(page.locator(".swal2-popup")).toBeHidden();
|
||||
}
|
||||
|
||||
@@ -67,12 +74,10 @@ test.describe("Workfeed configuration smoke", () => {
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-get-employee"]:visible').first()).toBeVisible();
|
||||
await expect(page.locator('[data-testid="workfeed-diagnostics-get-shift"]:visible').first()).toBeVisible();
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-departments");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("North Facility");
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-departments", { expectedText: "North Facility" });
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-employees");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Anne Nielsen");
|
||||
await runDiagnostic(page, "workfeed-diagnostics-list-employees", { expectedText: "Anne Nielsen" });
|
||||
await closeResultModal(page);
|
||||
|
||||
await runShiftDiagnostic(page, {
|
||||
@@ -80,16 +85,20 @@ test.describe("Workfeed configuration smoke", () => {
|
||||
startTo: "2026-03-25T00:00",
|
||||
employeeID: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
released: "true",
|
||||
expectedText: "Morning Shift",
|
||||
});
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Morning Shift");
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-get-employee", "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Anne Nielsen");
|
||||
await runDiagnostic(page, "workfeed-diagnostics-get-employee", {
|
||||
inputValue: "emp_01J5P2F2A0CQKBBX4P8M3Y7R5P",
|
||||
expectedText: "Anne Nielsen",
|
||||
});
|
||||
await closeResultModal(page);
|
||||
|
||||
await runDiagnostic(page, "workfeed-diagnostics-get-shift", "shf_01J5P3X9D35R9C6BDZ1S0R4V6Q");
|
||||
await expect(page.locator(".swal2-popup pre")).toContainText("Morning Shift");
|
||||
await runDiagnostic(page, "workfeed-diagnostics-get-shift", {
|
||||
inputValue: "shf_01J5P3X9D35R9C6BDZ1S0R4V6Q",
|
||||
expectedText: "Morning Shift",
|
||||
});
|
||||
await closeResultModal(page);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// @vitest-environment jsdom
|
||||
import { flushPromises, mount } from "@vue/test-utils";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
setEnabled: vi.fn(),
|
||||
showDeleteObjectForm: vi.fn(),
|
||||
showEditObjectFieldForm: vi.fn(),
|
||||
hasPermission: vi.fn(),
|
||||
canAccessSuperUser: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("vue-i18n", () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) =>
|
||||
({
|
||||
"common.yes": "Yes",
|
||||
"common.no": "No",
|
||||
"global.delete": "Delete",
|
||||
"tables.showing": "Showing",
|
||||
}[key] ?? key),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
SessionUser: {
|
||||
hasPermission: mocks.hasPermission,
|
||||
canAccessSuperUser: mocks.canAccessSuperUser,
|
||||
objects: {
|
||||
global: {
|
||||
icons: {
|
||||
delete: "fas fa-trash",
|
||||
},
|
||||
},
|
||||
department_notification_sms: {
|
||||
meta: {
|
||||
labels: {
|
||||
multiple: "notification phone numbers",
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: { label: "ID" },
|
||||
label: { label: "Label" },
|
||||
phone_country_code: { label: "Country code" },
|
||||
phone: { label: "Phone" },
|
||||
enabled: { label: "Enabled" },
|
||||
},
|
||||
set: {
|
||||
enabled: mocks.setEnabled,
|
||||
},
|
||||
showEditObjectFieldForm: mocks.showEditObjectFieldForm,
|
||||
functions: {
|
||||
showDeleteObjectForm: mocks.showDeleteObjectForm,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/buttons/EditableTableColumn.vue", () => ({
|
||||
default: {
|
||||
name: "EditableTableColumn",
|
||||
props: {
|
||||
object: Object,
|
||||
column: String,
|
||||
loadList: Function,
|
||||
},
|
||||
template: `<td :data-testid="\`editable-\${column}-\${object.id}\`">{{ object[column] }}</td>`,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/buttons/ActionSettingsWheelButton.vue", () => ({
|
||||
default: {
|
||||
name: "ActionSettingsWheelButton",
|
||||
template: `<div><slot name="actions" /></div>`,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/buttons/ActionSettingsWheelItem.vue", () => ({
|
||||
default: {
|
||||
name: "ActionSettingsWheelItem",
|
||||
props: {
|
||||
testId: String,
|
||||
label: String,
|
||||
disabled: Boolean,
|
||||
},
|
||||
emits: ["click"],
|
||||
template: `<button type="button" :data-testid="testId" :disabled="disabled" @click="$emit('click')">{{ label }}</button>`,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/permissionbased/RequiresPermission.vue", () => ({
|
||||
default: {
|
||||
name: "RequiresPermission",
|
||||
props: {
|
||||
permission: String,
|
||||
},
|
||||
template: `<slot />`,
|
||||
},
|
||||
}));
|
||||
|
||||
import DepartmentNotificationsPhoneTable from "@/components/displays/department/notifications/departmentNotificationsPhoneTable.vue";
|
||||
|
||||
const mountTable = (props = {}) =>
|
||||
mount(DepartmentNotificationsPhoneTable, {
|
||||
props: {
|
||||
objects: [
|
||||
{
|
||||
id: 10,
|
||||
label: "Dispatch line",
|
||||
phone_country_code: 45,
|
||||
phone: 12345678,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
loadList: vi.fn().mockResolvedValue({}),
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (key) =>
|
||||
({
|
||||
"tables.showing": "Showing",
|
||||
}[key] ?? key),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("DepartmentNotificationsPhoneTable", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
mocks.setEnabled.mockReset();
|
||||
mocks.showDeleteObjectForm.mockReset();
|
||||
mocks.showEditObjectFieldForm.mockReset();
|
||||
mocks.hasPermission.mockReset();
|
||||
mocks.canAccessSuperUser.mockReset();
|
||||
|
||||
mocks.setEnabled.mockResolvedValue({});
|
||||
mocks.hasPermission.mockReturnValue(true);
|
||||
mocks.canAccessSuperUser.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("updates enabled state and reloads the provided paginated list", async () => {
|
||||
const loadList = vi.fn().mockResolvedValue("reloaded");
|
||||
const wrapper = mountTable({ loadList });
|
||||
|
||||
await wrapper.get('[data-testid="department-notification-sms-enabled-toggle-10"]').setValue(false);
|
||||
await flushPromises();
|
||||
|
||||
expect(mocks.setEnabled).toHaveBeenCalledWith(10, false);
|
||||
expect(loadList).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("rolls back the switch and skips reload when the enabled update fails", async () => {
|
||||
const error = new Error("Not Found");
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
mocks.setEnabled.mockRejectedValueOnce(error);
|
||||
const loadList = vi.fn().mockResolvedValue("reloaded");
|
||||
const wrapper = mountTable({ loadList });
|
||||
const toggle = wrapper.get('[data-testid="department-notification-sms-enabled-toggle-10"]');
|
||||
|
||||
await toggle.setValue(false);
|
||||
await flushPromises();
|
||||
|
||||
expect(toggle.element.checked).toBe(true);
|
||||
expect(loadList).not.toHaveBeenCalled();
|
||||
expect(errorSpy).toHaveBeenCalledWith("Unable to update department notification SMS active state", error);
|
||||
});
|
||||
|
||||
it("passes the provided paginated list reload callback to delete", async () => {
|
||||
const loadList = vi.fn().mockResolvedValue("reloaded");
|
||||
const wrapper = mountTable({ loadList });
|
||||
|
||||
await wrapper.get('[data-testid="department-notification-sms-delete-10"]').trigger("click");
|
||||
|
||||
expect(mocks.showDeleteObjectForm).toHaveBeenCalledWith(10, loadList);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("sweetalert2", () => ({
|
||||
default: {
|
||||
fire: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import Swal from "sweetalert2";
|
||||
import {
|
||||
buildMultiMonthInvoiceContext,
|
||||
MULTI_MONTH_INVOICE_ACTION,
|
||||
promptMultiMonthInvoiceWarning,
|
||||
shouldWarnAboutMultiMonthInvoice,
|
||||
} from "@/services/invoiceMonthSplitWarning.js";
|
||||
|
||||
describe("invoice month split warning", () => {
|
||||
beforeEach(() => {
|
||||
Swal.fire.mockReset();
|
||||
});
|
||||
|
||||
it("builds a scoped multi-month context without timezone shifting date strings", () => {
|
||||
const context = buildMultiMonthInvoiceContext([
|
||||
{ id: 1, created_at: "2026-03-31 23:30:00", invoice_collection_id: 501 },
|
||||
{ id: 2, created_at: "2026-04-01T00:30:00.000Z", invoice_collection_id: "501" },
|
||||
{ id: 3, created_at: "2026-04-03 09:00:00", invoice_collection_id: 502 },
|
||||
{ id: 4, created_at: "invalid", invoice_collection_id: null },
|
||||
]);
|
||||
|
||||
expect(context).toEqual({
|
||||
months: ["2026-03", "2026-04"],
|
||||
dateFrom: "2026-03-31",
|
||||
dateTo: "2026-04-03",
|
||||
invoiceCollectionIds: [501, 502],
|
||||
});
|
||||
expect(shouldWarnAboutMultiMonthInvoice(context)).toBe(true);
|
||||
});
|
||||
|
||||
it("continues without a modal for single-month selections", async () => {
|
||||
const action = await promptMultiMonthInvoiceWarning({
|
||||
context: buildMultiMonthInvoiceContext([
|
||||
{ created_at: "2026-04-01 10:00:00", invoice_collection_id: 501 },
|
||||
{ created_at: "2026-04-02 10:00:00", invoice_collection_id: 501 },
|
||||
]),
|
||||
splitByMonth: vi.fn(),
|
||||
});
|
||||
|
||||
expect(action).toBe(MULTI_MONTH_INVOICE_ACTION.CONTINUE);
|
||||
expect(Swal.fire).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("splits selected invoice collections when the warning is confirmed", async () => {
|
||||
const splitByMonth = vi.fn().mockResolvedValue({
|
||||
data: {
|
||||
data: {
|
||||
processed_count: 2,
|
||||
changed_count: 1,
|
||||
skipped_count: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
Swal.fire.mockResolvedValueOnce({ isConfirmed: true }).mockResolvedValueOnce({ isConfirmed: true });
|
||||
|
||||
const action = await promptMultiMonthInvoiceWarning({
|
||||
context: buildMultiMonthInvoiceContext([
|
||||
{ created_at: "2026-03-20 10:00:00", invoice_collection_id: 7001 },
|
||||
{ created_at: "2026-04-02 10:00:00", invoice_collection_id: 7001 },
|
||||
]),
|
||||
splitByMonth,
|
||||
});
|
||||
|
||||
expect(action).toBe(MULTI_MONTH_INVOICE_ACTION.SPLIT);
|
||||
expect(splitByMonth).toHaveBeenCalledWith("2026-03-20", "2026-04-02", {
|
||||
invoiceCollectionIds: [7001],
|
||||
preview: false,
|
||||
});
|
||||
expect(Swal.fire).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
icon: "warning",
|
||||
showDenyButton: true,
|
||||
})
|
||||
);
|
||||
expect(Swal.fire).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
icon: "success",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("continues invoicing together when the warning deny button is selected", async () => {
|
||||
const splitByMonth = vi.fn();
|
||||
Swal.fire.mockResolvedValueOnce({ isDenied: true });
|
||||
|
||||
const action = await promptMultiMonthInvoiceWarning({
|
||||
context: buildMultiMonthInvoiceContext([
|
||||
{ created_at: "2026-03-20 10:00:00", invoice_collection_id: 7001 },
|
||||
{ created_at: "2026-04-02 10:00:00", invoice_collection_id: 7001 },
|
||||
]),
|
||||
splitByMonth,
|
||||
});
|
||||
|
||||
expect(action).toBe(MULTI_MONTH_INVOICE_ACTION.CONTINUE);
|
||||
expect(splitByMonth).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("cancels invoicing when the warning is dismissed", async () => {
|
||||
const splitByMonth = vi.fn();
|
||||
Swal.fire.mockResolvedValueOnce({ isDismissed: true });
|
||||
|
||||
const action = await promptMultiMonthInvoiceWarning({
|
||||
context: buildMultiMonthInvoiceContext([
|
||||
{ created_at: "2026-03-20 10:00:00", invoice_collection_id: 7001 },
|
||||
{ created_at: "2026-04-02 10:00:00", invoice_collection_id: 7001 },
|
||||
]),
|
||||
splitByMonth,
|
||||
});
|
||||
|
||||
expect(action).toBe(MULTI_MONTH_INVOICE_ACTION.CANCEL);
|
||||
expect(splitByMonth).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,170 @@
|
||||
// @vitest-environment jsdom
|
||||
import { flushPromises, mount } from "@vue/test-utils";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("vue-i18n", async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({
|
||||
t: (key) => key,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("sweetalert2", () => ({
|
||||
default: {
|
||||
fire: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
SessionUser: {
|
||||
objects: {
|
||||
global: {
|
||||
language: {
|
||||
hide_content: "Hide",
|
||||
show_content: "Show",
|
||||
status: "Status",
|
||||
completed: "Completed",
|
||||
not_completed: "Not completed",
|
||||
},
|
||||
},
|
||||
orders: {
|
||||
columns: {
|
||||
id: { label: "ID", visible: true },
|
||||
created_at: { label: "Created", visible: true },
|
||||
},
|
||||
},
|
||||
collectedOrderInvoices: {
|
||||
functions: {
|
||||
split_by_month: vi.fn(),
|
||||
economic: {
|
||||
invoice: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
vehicles: {
|
||||
columns: {
|
||||
wash_subscription: {
|
||||
label: "Subscription",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
functions: {
|
||||
currency: {
|
||||
toLocal: (value) => String(value),
|
||||
},
|
||||
parseErrorMessage: (error) => error?.message ?? String(error),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/views/dashboards/superUserDashboard/user/displays/other/UserOtherSpecialArrangement.vue", () => ({
|
||||
default: { template: "<div />" },
|
||||
}));
|
||||
|
||||
vi.mock("@/views/dashboards/superUserDashboard/user/displays/other/UserOtherVaskeabonnement.vue", () => ({
|
||||
default: { template: "<div />" },
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/department/pos/order/orderItemsTable.vue", () => ({
|
||||
default: { template: "<div />" },
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/superuser/tables/OrderContentTable.vue", () => ({
|
||||
default: { template: "<div />" },
|
||||
}));
|
||||
|
||||
import Swal from "sweetalert2";
|
||||
import InvoiceOrderTable from "@/components/displays/superuser/tables/InvoiceOrderTable.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
|
||||
const mountTable = (orders) =>
|
||||
mount(InvoiceOrderTable, {
|
||||
props: {
|
||||
orders,
|
||||
options: {},
|
||||
columns: {},
|
||||
user_id: 42,
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (key) =>
|
||||
({
|
||||
"global.invoice_now": "Invoice now",
|
||||
"global.unselect": "Unselect",
|
||||
"common.all": "All",
|
||||
"common.select": "Select",
|
||||
}[key] ?? key),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("InvoiceOrderTable multi-month warning", () => {
|
||||
beforeEach(() => {
|
||||
Swal.fire.mockReset();
|
||||
SessionUser.objects.collectedOrderInvoices.functions.split_by_month.mockReset();
|
||||
SessionUser.objects.collectedOrderInvoices.functions.economic.invoice.mockReset();
|
||||
SessionUser.objects.collectedOrderInvoices.functions.economic.invoice.mockResolvedValue({});
|
||||
});
|
||||
|
||||
it("warns for selected invoice collections containing orders from multiple months before invoicing together", async () => {
|
||||
Swal.fire.mockResolvedValueOnce({ isDenied: true }).mockReturnValueOnce(new Promise(() => {}));
|
||||
const wrapper = mountTable([
|
||||
{
|
||||
id: 1,
|
||||
invoice_collection_id: 9001,
|
||||
created_at: "2026-03-15 10:00:00",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
invoice_collection_id: 9001,
|
||||
created_at: "2026-04-02 10:00:00",
|
||||
},
|
||||
]);
|
||||
|
||||
await wrapper.find("tbody input[type='checkbox']").trigger("click");
|
||||
await wrapper.get("[data-testid='invoice-order-table-invoice-button']").trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(Swal.fire).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
icon: "warning",
|
||||
showDenyButton: true,
|
||||
})
|
||||
);
|
||||
expect(SessionUser.objects.collectedOrderInvoices.functions.split_by_month).not.toHaveBeenCalled();
|
||||
expect(SessionUser.objects.collectedOrderInvoices.functions.economic.invoice).toHaveBeenCalledWith(9001, 42);
|
||||
});
|
||||
|
||||
it("does not warn for selected invoice collections containing only one month", async () => {
|
||||
Swal.fire.mockReturnValueOnce(new Promise(() => {}));
|
||||
const wrapper = mountTable([
|
||||
{
|
||||
id: 1,
|
||||
invoice_collection_id: 9002,
|
||||
created_at: "2026-04-01 10:00:00",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
invoice_collection_id: 9002,
|
||||
created_at: "2026-04-02 10:00:00",
|
||||
},
|
||||
]);
|
||||
|
||||
await wrapper.find("tbody input[type='checkbox']").trigger("click");
|
||||
await wrapper.get("[data-testid='invoice-order-table-invoice-button']").trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(Swal.fire).toHaveBeenCalledTimes(1);
|
||||
expect(Swal.fire).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: "Fakturaer oprettet",
|
||||
})
|
||||
);
|
||||
expect(SessionUser.objects.collectedOrderInvoices.functions.economic.invoice).toHaveBeenCalledWith(9002, 42);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
import { computed, nextTick } from "vue";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { flushPromises, mount } from "@vue/test-utils";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const {
|
||||
@@ -34,6 +34,12 @@ vi.mock("@/services/economicTransferQueue.js", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("sweetalert2", () => ({
|
||||
default: {
|
||||
fire: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
SessionUser: {
|
||||
objects: {
|
||||
@@ -62,6 +68,7 @@ vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
createVehicleSubscriptionInvoice: vi.fn(),
|
||||
add_fixed_pricing: vi.fn(),
|
||||
add_vehicle_subscriptions: vi.fn(),
|
||||
split_by_month: vi.fn(),
|
||||
},
|
||||
},
|
||||
vehicles: {
|
||||
@@ -76,6 +83,7 @@ vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
currency: {
|
||||
toLocal: (value) => String(value),
|
||||
},
|
||||
parseErrorMessage: (error) => error?.message ?? String(error),
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -148,6 +156,7 @@ vi.mock(
|
||||
import InvoicingBillingPeriodViewAll from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { invoiceQueue } from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportInvoiceQueue.vue";
|
||||
import Swal from "sweetalert2";
|
||||
import {
|
||||
periodPaging,
|
||||
resetPeriodPagingState,
|
||||
@@ -222,11 +231,23 @@ describe("Invoicing period queue state", () => {
|
||||
loadingCustomerNumbersRef.value = [];
|
||||
SessionUser.objects.orders.get.multiple.mockReset();
|
||||
SessionUser.objects.orders.get.multiple.mockResolvedValue([]);
|
||||
SessionUser.objects.collectedOrderInvoices.functions.split_by_month.mockReset();
|
||||
SessionUser.objects.collectedOrderInvoices.functions.split_by_month.mockResolvedValue({
|
||||
data: {
|
||||
data: {
|
||||
processed_count: 1,
|
||||
changed_count: 1,
|
||||
skipped_count: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
invoiceQueue.addInvoiceCollectionsToQueue.mockReset();
|
||||
invoiceQueue.processInvoiceCollectionQueue.mockReset();
|
||||
invoiceQueue.markPeriodRefreshLoading.mockClear();
|
||||
invoiceQueue.finishPeriodRefresh.mockClear();
|
||||
invoiceQueue.isPeriodCustomerRefreshLoading.mockClear();
|
||||
Swal.fire.mockReset();
|
||||
Swal.fire.mockResolvedValue({ isDenied: true });
|
||||
resetPeriodPagingState();
|
||||
sharedVariablesRef.value = {
|
||||
types: {
|
||||
@@ -644,6 +665,168 @@ describe("Invoicing period queue state", () => {
|
||||
expect(windowOpenSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("splits instead of queueing when multi-month invoicing warning is confirmed", async () => {
|
||||
sharedVariablesRef.value = {
|
||||
types: {
|
||||
all: [
|
||||
{
|
||||
id: 8,
|
||||
customer_number: 1008,
|
||||
customer_name: "Multi Month Customer",
|
||||
requires_action: true,
|
||||
queue: {
|
||||
has_active_job: false,
|
||||
statuses: [],
|
||||
invoice_collection_ids: [],
|
||||
is_action_blocked: false,
|
||||
},
|
||||
transactions: [
|
||||
{
|
||||
id: 8101,
|
||||
amount: 75,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 8100,
|
||||
date: "2026-03-28T10:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 8102,
|
||||
amount: 125,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 8100,
|
||||
date: "2026-04-02T10:00:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Swal.fire.mockResolvedValueOnce({ isConfirmed: true }).mockResolvedValueOnce({ isConfirmed: true });
|
||||
|
||||
const wrapper = mountView();
|
||||
await nextTick();
|
||||
|
||||
await wrapper.get("[data-testid='invoicing-period-customer-invoice-1008']").trigger("click");
|
||||
await flushPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(SessionUser.objects.collectedOrderInvoices.functions.split_by_month).toHaveBeenCalledWith(
|
||||
"2026-03-28",
|
||||
"2026-04-02",
|
||||
{
|
||||
invoiceCollectionIds: [8100],
|
||||
preview: false,
|
||||
}
|
||||
);
|
||||
expect(invoiceQueue.addInvoiceCollectionsToQueue).not.toHaveBeenCalled();
|
||||
expect(invoiceQueue.processInvoiceCollectionQueue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues queueing together when multi-month invoicing warning is denied", async () => {
|
||||
sharedVariablesRef.value = {
|
||||
types: {
|
||||
all: [
|
||||
{
|
||||
id: 9,
|
||||
customer_number: 1009,
|
||||
customer_name: "Invoice Together Customer",
|
||||
requires_action: true,
|
||||
queue: {
|
||||
has_active_job: false,
|
||||
statuses: [],
|
||||
invoice_collection_ids: [],
|
||||
is_action_blocked: false,
|
||||
},
|
||||
transactions: [
|
||||
{
|
||||
id: 8201,
|
||||
amount: 75,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 8200,
|
||||
date: "2026-03-28T10:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 8202,
|
||||
amount: 125,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 8200,
|
||||
date: "2026-04-02T10:00:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Swal.fire.mockResolvedValueOnce({ isDenied: true });
|
||||
|
||||
const wrapper = mountView();
|
||||
await nextTick();
|
||||
|
||||
await wrapper.get("[data-testid='invoicing-period-customer-invoice-1009']").trigger("click");
|
||||
await flushPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(SessionUser.objects.collectedOrderInvoices.functions.split_by_month).not.toHaveBeenCalled();
|
||||
expect(invoiceQueue.addInvoiceCollectionsToQueue).toHaveBeenCalledWith([8200], {
|
||||
customerNumber: 1009,
|
||||
});
|
||||
expect(invoiceQueue.processInvoiceCollectionQueue).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not queue when multi-month invoicing warning is dismissed", async () => {
|
||||
sharedVariablesRef.value = {
|
||||
types: {
|
||||
all: [
|
||||
{
|
||||
id: 10,
|
||||
customer_number: 1010,
|
||||
customer_name: "Cancel Multi Month Customer",
|
||||
requires_action: true,
|
||||
queue: {
|
||||
has_active_job: false,
|
||||
statuses: [],
|
||||
invoice_collection_ids: [],
|
||||
is_action_blocked: false,
|
||||
},
|
||||
transactions: [
|
||||
{
|
||||
id: 8301,
|
||||
amount: 75,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 8300,
|
||||
date: "2026-03-28T10:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: 8302,
|
||||
amount: 125,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
invoice_collection_id: 8300,
|
||||
date: "2026-04-02T10:00:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Swal.fire.mockResolvedValueOnce({ isDismissed: true });
|
||||
|
||||
const wrapper = mountView();
|
||||
await nextTick();
|
||||
|
||||
await wrapper.get("[data-testid='invoicing-period-customer-invoice-1010']").trigger("click");
|
||||
await flushPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(SessionUser.objects.collectedOrderInvoices.functions.split_by_month).not.toHaveBeenCalled();
|
||||
expect(invoiceQueue.addInvoiceCollectionsToQueue).not.toHaveBeenCalled();
|
||||
expect(invoiceQueue.processInvoiceCollectionQueue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies invoice-now loading only to the affected customer", async () => {
|
||||
sharedVariablesRef.value = {
|
||||
types: {
|
||||
|
||||
@@ -63,6 +63,26 @@ describe("ObjectsGlobal select editor escaping", () => {
|
||||
});
|
||||
|
||||
describe("ObjectsGlobal create form locked values", () => {
|
||||
it("renders boolean create fields with an explicit false default selected", async () => {
|
||||
const html = await ObjectsGlobal.generateCreateObjectForm(
|
||||
{
|
||||
wash_subscription: {
|
||||
label: "Wash subscription",
|
||||
type: "boolean",
|
||||
creation: {
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
document.body.innerHTML = html;
|
||||
|
||||
expect(document.querySelector("#wash_subscription")?.value).toBe("false");
|
||||
});
|
||||
|
||||
it("submits required locked values without reading them back from the DOM", async () => {
|
||||
const add = vi.fn().mockResolvedValue({});
|
||||
const object = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { sourceMappings } from "../../scripts/playwright-pr-mapping.mjs";
|
||||
import { fallbackChangePatterns, sourceMappings } from "../../scripts/playwright-pr-mapping.mjs";
|
||||
|
||||
const specsFor = (file) =>
|
||||
sourceMappings
|
||||
@@ -7,9 +7,35 @@ const specsFor = (file) =>
|
||||
.flatMap((mapping) => mapping.specs);
|
||||
|
||||
describe("Playwright PR mapping", () => {
|
||||
const triggersFallback = (file) => fallbackChangePatterns.some((pattern) => pattern.test(file));
|
||||
|
||||
it("maps user vehicle table changes to the user vehicles E2E coverage", () => {
|
||||
expect(specsFor("src/components/displays/user/vehicles/vehiclesTable.vue")).toContain(
|
||||
"tests/e2e/userVehicles.spec.ts"
|
||||
);
|
||||
});
|
||||
|
||||
it("maps department notification table changes to the admin notification E2E coverage", () => {
|
||||
expect(
|
||||
specsFor("src/components/displays/department/notifications/departmentNotificationsPhoneTable.vue")
|
||||
).toContain("tests/e2e/admin-department-notifications.spec.ts");
|
||||
expect(
|
||||
specsFor("src/components/displays/pagination/models/DepartmentPos/NotificationsPhonePagination.vue")
|
||||
).toContain("tests/e2e/admin-department-notifications.spec.ts");
|
||||
});
|
||||
|
||||
it("maps superuser role permission page changes to role permissions E2E coverage", () => {
|
||||
expect(specsFor("src/views/dashboards/superUserDashboard/roles/SuperUserRolesPermissions.vue")).toContain(
|
||||
"tests/e2e/superuser-roles-permissions.spec.ts"
|
||||
);
|
||||
expect(specsFor("src/views/dashboards/superUserDashboard/roles/rolePermissionCatalog.js")).toContain(
|
||||
"tests/e2e/superuser-roles-permissions.spec.ts"
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps full-slice ownership metadata out of broad PR smoke fallback", () => {
|
||||
expect(triggersFallback("scripts/run-playwright-pr.mjs")).toBe(true);
|
||||
expect(triggersFallback("scripts/run-playwright-ci-parallel.mjs")).toBe(true);
|
||||
expect(triggersFallback("scripts/run-playwright-full-slice.mjs")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
filterPermissionRecords,
|
||||
getPermissionDelta,
|
||||
groupPermissionRecords,
|
||||
humanizeEndpoint,
|
||||
humanizePermissionKey,
|
||||
normalizePermissionCatalog,
|
||||
sanitizePermissionDomId,
|
||||
} from "../../src/views/dashboards/superUserDashboard/roles/rolePermissionCatalog.js";
|
||||
|
||||
const catalog = {
|
||||
"/orders": {
|
||||
GET: {
|
||||
list_orders: "List orders",
|
||||
},
|
||||
POST: {
|
||||
add_order: "Add an order",
|
||||
},
|
||||
},
|
||||
"/admin/bookings": {
|
||||
POST: {
|
||||
create_bookings: "Create bookings",
|
||||
},
|
||||
},
|
||||
"/superuser/system/replication": {
|
||||
GET: {
|
||||
superuser_replication_view: "View replication status",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const departments = [
|
||||
{
|
||||
id: 7,
|
||||
name: "Copenhagen",
|
||||
},
|
||||
];
|
||||
|
||||
const permissionOverrides = {
|
||||
user: {
|
||||
label: "User",
|
||||
description: "Allows access to user dashboard pages.",
|
||||
groupKey: "access",
|
||||
},
|
||||
admin: {
|
||||
label: "Admin",
|
||||
description: "Allows access to admin dashboard pages.",
|
||||
groupKey: "access",
|
||||
},
|
||||
superuser: {
|
||||
label: "Superuser",
|
||||
description: "Allows access to superuser dashboard pages.",
|
||||
groupKey: "access",
|
||||
},
|
||||
department_access_7: {
|
||||
label: "Copenhagen",
|
||||
description: "Allows access to Copenhagen.",
|
||||
groupKey: "access",
|
||||
departmentId: 7,
|
||||
},
|
||||
};
|
||||
|
||||
describe("role permission catalog helpers", () => {
|
||||
it("normalizes route permissions and virtual access permissions", () => {
|
||||
const records = normalizePermissionCatalog(catalog, {
|
||||
departments,
|
||||
permissionOverrides,
|
||||
});
|
||||
|
||||
expect(records.map((record) => record.permission)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"user",
|
||||
"admin",
|
||||
"superuser",
|
||||
"department_access_7",
|
||||
"list_orders",
|
||||
"add_order",
|
||||
"create_bookings",
|
||||
"superuser_replication_view",
|
||||
])
|
||||
);
|
||||
expect(records.find((record) => record.permission === "department_access_7")).toMatchObject({
|
||||
groupKey: "access",
|
||||
label: "Copenhagen",
|
||||
description: "Allows access to Copenhagen.",
|
||||
isVirtual: true,
|
||||
});
|
||||
expect(records.find((record) => record.permission === "list_orders")).toMatchObject({
|
||||
groupKey: "orders_pos",
|
||||
label: "List orders",
|
||||
description: "List orders",
|
||||
endpoint: "/orders",
|
||||
endpointLabel: "Orders",
|
||||
method: "GET",
|
||||
});
|
||||
expect(records.find((record) => record.permission === "create_bookings")).toMatchObject({
|
||||
groupKey: "bookings",
|
||||
});
|
||||
expect(records.find((record) => record.permission === "superuser_replication_view")).toMatchObject({
|
||||
groupKey: "system_config",
|
||||
});
|
||||
});
|
||||
|
||||
it("searches labels, descriptions, keys, endpoints, and methods", () => {
|
||||
const records = normalizePermissionCatalog(catalog, {
|
||||
departments,
|
||||
permissionOverrides,
|
||||
});
|
||||
|
||||
expect(filterPermissionRecords(records, "copenhagen").map((record) => record.permission)).toEqual([
|
||||
"department_access_7",
|
||||
]);
|
||||
expect(filterPermissionRecords(records, "POST").map((record) => record.permission)).toEqual(
|
||||
expect.arrayContaining(["add_order", "create_bookings"])
|
||||
);
|
||||
expect(filterPermissionRecords(records, "replication status").map((record) => record.permission)).toEqual([
|
||||
"superuser_replication_view",
|
||||
]);
|
||||
});
|
||||
|
||||
it("groups records by stable group key", () => {
|
||||
const grouped = groupPermissionRecords(
|
||||
normalizePermissionCatalog(catalog, {
|
||||
departments,
|
||||
permissionOverrides,
|
||||
})
|
||||
);
|
||||
|
||||
expect(grouped.get("access").map((record) => record.permission)).toEqual(
|
||||
expect.arrayContaining(["user", "admin", "superuser", "department_access_7"])
|
||||
);
|
||||
expect(grouped.get("orders_pos").map((record) => record.permission)).toEqual(
|
||||
expect.arrayContaining(["list_orders", "add_order"])
|
||||
);
|
||||
expect(grouped.get("bookings").map((record) => record.permission)).toEqual(["create_bookings"]);
|
||||
});
|
||||
|
||||
it("calculates bulk add and remove deltas without no-op requests", () => {
|
||||
const records = normalizePermissionCatalog(catalog, {
|
||||
departments,
|
||||
permissionOverrides,
|
||||
}).filter((record) => ["list_orders", "add_order"].includes(record.permission));
|
||||
|
||||
expect(getPermissionDelta(records, new Set(["list_orders"]), true)).toEqual({
|
||||
add: ["add_order"],
|
||||
remove: [],
|
||||
});
|
||||
expect(getPermissionDelta(records, new Set(["list_orders"]), false)).toEqual({
|
||||
add: [],
|
||||
remove: ["list_orders"],
|
||||
});
|
||||
});
|
||||
|
||||
it("humanizes and sanitizes permission display values", () => {
|
||||
expect(humanizePermissionKey("superuser_replication_view")).toBe("Superuser replication view");
|
||||
expect(humanizeEndpoint("/superuser/system/replication")).toBe("Superuser system replication");
|
||||
expect(sanitizePermissionDomId("department/access 7")).toBe("department-access-7");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/components/session/authenticatedRequest.vue", () => ({
|
||||
authenticatedRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/request/HandleGlobalError.vue", () => ({
|
||||
parseError: vi.fn(),
|
||||
}));
|
||||
|
||||
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
|
||||
import { addVehicleUser, vehicles } from "@/components/session/user/UserVehicleRequest.vue";
|
||||
|
||||
describe("UserVehicleRequest", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vehicles.value = [];
|
||||
});
|
||||
|
||||
it("creates customer vehicles through the shared endpoint with wash subscriptions disabled", async () => {
|
||||
authenticatedRequest.mockResolvedValue({
|
||||
data: {
|
||||
data: {
|
||||
id: 123,
|
||||
reg: "AB12345",
|
||||
wash_subscription: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await addVehicleUser("AB12345", "53", "Driver note");
|
||||
|
||||
expect(authenticatedRequest).toHaveBeenCalledWith("/vehicles", "POST", {
|
||||
reg: "AB12345",
|
||||
type: 53,
|
||||
wash_subscription: false,
|
||||
notes: "Driver note",
|
||||
});
|
||||
expect(vehicles.value).toEqual([
|
||||
{
|
||||
id: 123,
|
||||
reg: "AB12345",
|
||||
wash_subscription: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user