Compare commits

...
Author SHA1 Message Date
Jeppe Bundgaard 3a78e523b2 Focus EAN PR Playwright mapping 2026-07-06 18:11:07 +02:00
Jeppe Bundgaard 7f68471b85 Add EAN to economic customer registration 2026-07-06 18:10:42 +02:00
Jeppe Bundgaard 6c1c1def78 Add limited backoffice employee QR login links 2026-07-06 17:32:58 +02:00
Jeppe B bb375eb149 Merge pull request #144 from copenhagentruckwash/codex/fix-invoicing-period-selfwash
Fix invoicing period Selvvask counts
2026-07-06 17:08:06 +02:00
Jeppe B 7e5e6ce680 Enforce only tankcleaning POS product rules (#151) 2026-07-06 17:07:03 +02:00
Jeppe Bundgaard 1032f8b4f1 Show limited backoffice employees in backoffice 2026-07-06 16:59:46 +02:00
Jeppe B 744a56c8b2 Merge PR #157: Cleanup superuser dashboard tabs
Cleanup superuser dashboard tabs
2026-07-06 16:59:27 +02:00
Jeppe Bundgaard e58372a190 Merge remote-tracking branch 'origin/master' into codex/pr157-merge-resolve-692380
# Conflicts:
#	scripts/playwright-pr-mapping.mjs
#	src/i18n/generated/da-v2.json
#	src/i18n/generated/de-v2.json
#	src/i18n/generated/en-v2.json
#	src/i18n/generated/no-v2.json
#	src/i18n/generated/sv-v2.json
#	src/i18n/source/da/phrases/index.json
#	src/i18n/source/de/phrases/index.json
#	src/i18n/source/en/phrases/index.json
#	src/i18n/source/no/phrases/index.json
#	src/i18n/source/sv/phrases/index.json
#	src/views/dashboards/userDashboard/wash/MyWashStart.vue
#	tests/unit/playwright-pr-mapping.spec.js
2026-07-06 16:59:00 +02:00
Jeppe Bundgaard 19d44b8666 Add limited backoffice user ID translations 2026-07-06 16:53:41 +02:00
Jeppe Bundgaard 92a52d6194 Gate subuser customer navigation and actions 2026-07-06 16:48:24 +02:00
Jeppe BandJeppe Bundgaard db1c0e25c0 Move limited backoffice shortcut into header (#140)
* Move limited backoffice shortcut into header

* Use shared Playwright port locks in CI

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 16:47:33 +02:00
Jeppe Bundgaard 04860a7304 Deduplicate limited backoffice price products 2026-07-06 16:34:10 +02:00
Jeppe B d8d67301ed Add autosave for limited backoffice prices (#143) 2026-07-06 16:26:46 +02:00
Jeppe Bundgaard fa1e915b9c Merge origin/master into Selvvask period fix 2026-07-06 16:07:05 +02:00
Jeppe BandJeppe Bundgaard da86b69b2c Warn before invoicing multiple months together (#155)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 16:02:10 +02:00
Jeppe B 44e3167278 Build superuser department overview page (#156)
Build the superuser department overview page and align PR changed-E2E selection with the PR head ref.
2026-07-06 16:01:26 +02:00
Jeppe B 5cc22f14bc Fix date-only selection handling
Use local date-only parsing and formatting across date inputs and filters so clicked dates persist exactly.\n\nVerified with local format, lint, i18n, build, full unit tests, PR E2E, focused date E2E, broad smoke reruns, and GitHub PR checks.
2026-07-06 15:52:19 +02:00
Jeppe B 8519af5cbb Merge pull request #141 from copenhagentruckwash/codex/custom-pricing-only-departments
Add custom-only department pricing toggle
2026-07-06 15:31:41 +02:00
Jeppe BandJeppe Bundgaard 3fc4be8335 Update limited backoffice employee form (#146)
* Update limited backoffice employee form

* Map limited backoffice changes to focused E2E

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 15:31:16 +02:00
Jeppe Bundgaard 5a5cc0bed3 Guard MyWashStart timers after teardown 2026-07-06 15:02:57 +02:00
Jeppe Bundgaard d84e9e3406 Map superuser dashboard changes to E2E 2026-07-06 14:56:18 +02:00
Jeppe Bundgaard c41954993c Fix PR E2E changed diff head 2026-07-06 14:54:41 +02:00
Jeppe Bundgaard 36c1f10de8 Reduce PR E2E runner pressure 2026-07-06 14:47:03 +02:00
Jeppe Bundgaard 4378dad2b9 Stabilize PR E2E runner checks 2026-07-06 14:39:06 +02:00
Jeppe BandJeppe Bundgaard 278298ecd2 Submit error reports without screenshots (#153)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 14:28:24 +02:00
Jeppe Bundgaard b8959cb7bb Cleanup superuser dashboard tabs 2026-07-06 14:07:36 +02:00
Jeppe BandJeppe Bundgaard dec90bbbbf Block restricted mobile additional items (#148)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 14:07:04 +02:00
Jeppe BandJeppe Bundgaard a4b9d2f04d Add superuser vehicle creation flow (#152)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 13:59:40 +02:00
Jeppe Bundgaard ee41366c08 Bound changed PR E2E selection 2026-07-06 13:57:06 +02:00
Jeppe Bundgaard e22f78a449 Stabilize unit timer cleanup 2026-07-06 13:06:41 +02:00
Jeppe Bundgaard 0e9292d6d9 Merge remote-tracking branch 'origin/master' into codex/custom-pricing-only-departments
# Conflicts:
#	scripts/playwright-pr-mapping.mjs
2026-07-06 12:48:54 +02:00
Jeppe Bundgaard 69250ada66 Stabilize self-serve restored lane smoke test 2026-07-06 12:44:26 +02:00
Jeppe BandJeppe Bundgaard 22f8b5f203 Improve superuser role permissions page (#149)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 12:43:03 +02:00
Jeppe B 987acc5b0f Add role permission help modal (#142)
Adds the Rolle help icon and grouped permissions modal for limited backoffice employees, plus i18n and Playwright coverage.
2026-07-06 12:25:01 +02:00
Jeppe Bundgaard ce844137a0 Harden POS order detail E2E wait 2026-07-06 12:18:20 +02:00
Jeppe Bundgaard 7f3a8c07e2 Stabilize edge gateway installer smoke tests 2026-07-06 11:47:58 +02:00
Jeppe Bundgaard bd14d58f08 Stabilize self-serve wash smoke assertion 2026-07-06 11:03:22 +02:00
Jeppe Bundgaard b8494cd1d9 Merge remote-tracking branch 'origin/master' into codex/custom-pricing-only-departments
# Conflicts:
#	tests/unit/playwright-pr-mapping.spec.js
2026-07-06 10:36:57 +02:00
Jeppe Bundgaard 1a1c3db8e5 Fix invoicing period selfwash counts 2026-07-06 10:33:48 +02:00
Jeppe Bundgaard 49772c1334 Trigger frontend PR checks 2026-07-06 10:30:43 +02:00
Jeppe Bundgaard 02ddb99000 Add custom-only department pricing toggle 2026-07-06 10:21:06 +02:00
Jeppe BandJeppe Bundgaard cf1509e34f Fix POS step metadata persistence (#138)
* Fix POS step metadata persistence

* Preserve registration save payload formatting

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 10:20:13 +02:00
Jeppe BandJeppe Bundgaard ce2e671768 Fix department notification SMS table refresh
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 09:57:30 +02:00
Jeppe BandJeppe Bundgaard cf2add29dd Default vehicle subscriptions to false (#136)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 09:21:31 +02:00
215 changed files with 14162 additions and 1407 deletions
+20 -4
View File
@@ -102,7 +102,7 @@ jobs:
needs: build-and-unit
name: E2E-pr-${{ matrix.suite }}-${{ matrix.project }}
runs-on: [self-hosted, Linux, X64, pleno, frontend, docker]
timeout-minutes: 30
timeout-minutes: 45
strategy:
fail-fast: false
max-parallel: 4
@@ -112,6 +112,8 @@ jobs:
env:
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-pr-${{ matrix.suite }}-${{ matrix.project }}
PLAYWRIGHT_REPORTER_MODE: line-html
PLAYWRIGHT_WORKERS: 1
PLAYWRIGHT_VIDEO_MODE: on-first-retry
steps:
- name: Repair self-hosted workspace permissions
shell: bash
@@ -140,20 +142,29 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
HEAD_SHA: ${{ github.sha }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
zero_sha="0000000000000000000000000000000000000000"
if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_BASE_SHA" ]]; then
base_ref="$PR_BASE_SHA"
head_ref="$PR_HEAD_SHA"
elif [[ -z "$PUSH_BEFORE_SHA" || "$PUSH_BEFORE_SHA" == "$zero_sha" ]]; then
git fetch --no-tags --prune origin "$DEFAULT_BRANCH"
base_ref="origin/$DEFAULT_BRANCH"
head_ref="$HEAD_SHA"
else
base_ref="$PUSH_BEFORE_SHA"
head_ref="$HEAD_SHA"
fi
if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_HEAD_SHA" ]]; then
head_ref="$PR_HEAD_SHA"
else
head_ref="$HEAD_SHA"
fi
echo "base=$base_ref" >> "$GITHUB_OUTPUT"
echo "head=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "head=$head_ref" >> "$GITHUB_OUTPUT"
- name: Setup Node.js
uses: actions/setup-node@v5
@@ -181,8 +192,9 @@ jobs:
*) echo "Unsupported Playwright PR project: $MATRIX_PROJECT" >&2; exit 1 ;;
esac
port_seed=$((20000 + (RUN_ID % 20000) + suite_offset + project_offset))
lock_root="${RUNNER_TEMP:-/tmp}/pleno-playwright-port-locks"
lock_root="${PLAYWRIGHT_PORT_LOCK_ROOT:-/tmp/pleno-playwright-port-locks}"
mkdir -p "$lock_root"
chmod 1777 "$lock_root" 2>/dev/null || true
find "$lock_root" -mindepth 1 -maxdepth 1 -type d -mmin +360 -exec rmdir {} \; 2>/dev/null || true
playwright_port_lock=""
playwright_dev_port=""
@@ -223,7 +235,10 @@ jobs:
--env CI="${CI:-}" \
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
--env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
--env PLAYWRIGHT_WORKERS="${PLAYWRIGHT_WORKERS:-1}" \
--env MATRIX_SUITE="$MATRIX_SUITE" \
--env MATRIX_PROJECT="$MATRIX_PROJECT" \
--env DIFF_BASE_REF="$DIFF_BASE_REF" \
@@ -357,8 +372,9 @@ jobs:
*) echo "Unsupported Playwright device: $MATRIX_DEVICE" >&2; exit 1 ;;
esac
port_seed=$((20000 + (RUN_ID % 20000) + role_offset + browser_offset + device_offset))
lock_root="${RUNNER_TEMP:-/tmp}/pleno-playwright-port-locks"
lock_root="${PLAYWRIGHT_PORT_LOCK_ROOT:-/tmp/pleno-playwright-port-locks}"
mkdir -p "$lock_root"
chmod 1777 "$lock_root" 2>/dev/null || true
find "$lock_root" -mindepth 1 -maxdepth 1 -type d -mmin +360 -exec rmdir {} \; 2>/dev/null || true
playwright_port_lock=""
playwright_dev_port=""
+12
View File
@@ -2415,6 +2415,12 @@ paths:
type: string
description: Contact person name
example: "Mikkel"
ean:
type: string
description: Optional EAN used for e-invoicing in e-conomic
maxLength: 13
pattern: '^[0-9]{1,13}$'
example: "5790001234567"
g_recaptcha_response:
type: string
description: reCAPTCHA verification token
@@ -8189,6 +8195,12 @@ paths:
email: {type: string}
phone: {type: integer}
name: {type: string}
ean:
type: string
description: Optional EAN used for e-invoicing in e-conomic
maxLength: 13
pattern: '^[0-9]{1,13}$'
example: "5790001234567"
responses:
'200':
description: Success
+87 -4
View File
@@ -7,14 +7,18 @@ 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-(?:ci-parallel|batched-chromium)\.mjs$/u,
/^tests\/e2e\/(?:support|fixtures)\//u,
];
export const sourceMappings = [
{
name: "auth",
patterns: [/^src\/(?:views|components|middleware)\/.*auth/iu, /^src\/views\/auth\//u, /^src\/components\/session\//u],
patterns: [
/^src\/(?:views|components|middleware)\/.*auth/iu,
/^src\/views\/auth\//u,
/^src\/components\/session\/(?!token\/SessionUser\/Objects\/)/u,
],
specs: ["tests/e2e/auth.smoke.spec.js", "tests/e2e/userAuth.spec.ts"],
projects: chromiumProjects,
},
@@ -24,6 +28,49 @@ export const sourceMappings = [
specs: ["tests/e2e/navigation.smoke.spec.js"],
projects: chromiumProjects,
},
{
name: "backoffice",
patterns: [/^src\/views\/backoffice\//u],
specs: ["tests/e2e/limited-backoffice.spec.ts"],
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: "superuser-dashboard",
patterns: [
/^src\/views\/dashboards\/superUserDashboard\/SuperUserDashboard(?:Navigation)?\.vue$/u,
/^src\/components\/displays\/superuser\/system\//u,
],
specs: ["tests/e2e/superuser-system-status.smoke.spec.js"],
projects: chromiumProjects,
},
{
name: "superuser-department-overview",
patterns: [
/^src\/services\/superuserDepartmentOverview\.js$/u,
/^src\/views\/dashboards\/superUserDashboard\/department\/Department\.vue$/u,
/^src\/views\/dashboards\/superUserDashboard\/department\/SuperUserDashboardDepartmentNavigation\.vue$/u,
],
specs: ["tests/e2e/superuser-department-overview.spec.js"],
projects: chromiumProjects,
},
{
name: "header-navigation",
patterns: [
/^src\/components\/viewport\/page\/headers\//u,
/^src\/components\/models\/navigation\/items\/NavigationMenuItemsGlobal\.vue$/u,
],
specs: ["tests/e2e/navigation.smoke.spec.js", "tests/e2e/limited-backoffice.spec.ts"],
projects: chromiumProjects,
},
{
name: "booking",
patterns: [/bookings?/iu, /time-bookings/iu, /^src\/views\/guest\/book\//u],
@@ -42,8 +89,20 @@ export const sourceMappings = [
},
{
name: "pos",
patterns: [/\/pos[/-]/iu, /POS/iu, /^src\/assets\/pos\.css$/u],
specs: ["tests/e2e/pos-flow.spec.js", "tests/e2e/pos-mobile-order-flow.spec.js", "tests/e2e/admin-pos-orders.spec.ts"],
patterns: [
/\/pos[/-]/iu,
/POS/iu,
/^src\/assets\/pos\.css$/u,
/^src\/components\/displays\/boxes\/ProductBox\.vue$/u,
/^src\/features\/customer\/customerProductRules\.js$/u,
/^src\/services\/economicCustomerIdentifiers\.js$/u,
],
specs: [
"tests/e2e/pos-flow.spec.js",
"tests/e2e/pos-mobile-order-flow.spec.js",
"tests/e2e/admin-pos-orders.spec.ts",
"tests/e2e/pos-customer-rules.spec.js",
],
projects: chromiumProjects,
},
{
@@ -52,6 +111,21 @@ 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: "limited-backoffice",
patterns: [/^src\/views\/backoffice\/LimitedBackoffice/u],
specs: ["tests/e2e/limited-backoffice.spec.ts"],
projects: chromiumProjects,
},
{
name: "invoicing",
patterns: [/invoic/iu, /economic[-/]?queue/iu, /collected-order/iu],
@@ -74,6 +148,15 @@ export const sourceMappings = [
specs: ["tests/e2e/superuser-system-status.smoke.spec.js"],
projects: chromiumProjects,
},
{
name: "superuser-department-pricing",
patterns: [
/^src\/views\/dashboards\/superUserDashboard\/department\/(?:DepartmentPricing|SuperUserSelectedDepartmentObject)\.vue$/u,
/^src\/components\/session\/token\/SessionUser\/Objects\/Departments\.vue$/u,
],
specs: ["tests/e2e/superuser-department-pricing-custom-only.spec.ts"],
projects: ["chromium-desktop"],
},
{
name: "self-serve",
patterns: [/self[-/]?serve/iu, /selfserve/iu, /wash\/MyWash/iu],
+4
View File
@@ -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",
@@ -100,11 +101,14 @@ export const ownedFilesByRole = {
"superuser-customer-complaints.spec.ts",
"superuser-customers-mass-import.spec.ts",
"superuser-department-branding.spec.js",
"superuser-department-overview.spec.js",
"superuser-department-gates.spec.ts",
"superuser-department-lanes.spec.ts",
"superuser-department-pricing-custom-only.spec.ts",
"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",
+24 -4
View File
@@ -261,6 +261,8 @@ function selectChangedTests(changedFiles) {
specProjects: new Map(),
mappedFiles: [],
unmappedFiles: [],
directSpecFiles: [],
skippedDirectSpecFiles: [],
fallback: false,
};
@@ -270,8 +272,7 @@ function selectChangedTests(changedFiles) {
const file = normalizePath(rawFile);
if (isE2eSpec(file)) {
addSpec(selection, file, selectedProjects);
selection.mappedFiles.push(file);
selection.directSpecFiles.push(file);
continue;
}
@@ -295,13 +296,26 @@ function selectChangedTests(changedFiles) {
selection.mappedFiles.push(file);
for (const mapping of matches) {
const projects = mapping.projects.filter((project) => selectedProjects.includes(project));
const mappedProjects = mapping.projects.length > 0 ? mapping.projects : selectedProjects;
const projects = mappedProjects.filter((project) => selectedProjects.includes(project));
if (projects.length === 0) {
continue;
}
for (const spec of mapping.specs) {
addSpec(selection, spec, projects.length > 0 ? projects : selectedProjects);
addSpec(selection, spec, projects);
}
}
}
if (selection.specProjects.size === 0 && !selection.fallback) {
for (const file of selection.directSpecFiles) {
addSpec(selection, file, selectedProjects);
selection.mappedFiles.push(file);
}
} else {
selection.skippedDirectSpecFiles.push(...selection.directSpecFiles);
}
return selection;
}
@@ -370,6 +384,12 @@ async function runChangedSelection(selection) {
return 0;
}
if (selection.skippedDirectSpecFiles.length > 0) {
console.log(
`[playwright-pr] Source mappings selected changed-area specs; direct E2E file edits are covered by mapped/core gates: ${selection.skippedDirectSpecFiles.join(", ")}`
);
}
for (const [index, group] of groups.entries()) {
for (const project of group.projects) {
const code = await runPlaywright({
+11 -45
View File
@@ -6,6 +6,7 @@ import Swal from "sweetalert2";
import CustomerProductDiscountDisplay
from "@/components/displays/department/pos/displays/CustomerProductDiscountDisplay.vue";
import { useI18n } from 'vue-i18n';
import { isProductRestrictedForCustomer } from "@/features/customer/customerProductRules.js";
const { t } = useI18n();
const expandIcon = ref(null);
@@ -83,23 +84,6 @@ const hasRestrictAdditionalServices = () => {
return customerAttributesSafe.value.some((attribute) => attribute.attribute === 'restrictAdditionalServices');
};
const hasRestrictSpotFree = () => {
return customerAttributesSafe.value.some((attribute) => attribute.attribute === 'restrictSpotFree');
};
const hasRestrictInteriorCleaning = () => {
return customerAttributesSafe.value.some((attribute) => attribute.attribute === 'restrictInteriorCleaning');
};
const hasRestrictTankCleaning = () => {
return customerAttributesSafe.value.some((attribute) => attribute.attribute === 'restrictTankCleaning');
};
const hasOnlyTankCleaning = () => {
return customerAttributesSafe.value.some((attribute) => attribute.attribute === 'onlyTankCleaning');
};
/**
* On before add product addon
* This is used to check if adding a product has any requirements.
@@ -162,37 +146,19 @@ const doesAddonHaveNoteRequirement = (addon) => {
};
const isAddonRestricted = (addon) => {
const addonName = getAddonName(addon).toLowerCase();
// Check if the addon has "Spot Free" in the name
if (hasRestrictSpotFree() && addonName.includes('spot free')) {
return true;
}
// Check if the addon has "Indvendig vask" in the name
if (hasRestrictInteriorCleaning() && addonName.includes('indvendig vask')) {
return true;
}
const addonProduct = getAddonProduct(addon);
return isProductRestrictedForCustomer({
...addonProduct,
name: getAddonName(addon) || addonProduct.name,
category: addonProduct.category ?? addon?.category,
}, customerAttributesSafe.value);
};
const isProductRestricted = () => {
const productName = String(props.name || '').toLowerCase();
// Check if the product has "Spot Free" in the name
if (hasRestrictSpotFree() && productName.includes('spot free')) {
return true;
}
// Check if the product has "Indvendig vask" in the name
if (hasRestrictInteriorCleaning() && productName.includes('indvendig vask')) {
return true;
}
// Check if the product category is 5 (Tank Cleaning)
if (props.product?.category === 5) {
if (hasRestrictTankCleaning()) {
return true;
}
if (!hasOnlyTankCleaning()) {
return true;
}
}
return isProductRestrictedForCustomer({
...props.product,
name: props.name || props.product?.name,
}, customerAttributesSafe.value);
};
const orderByOrderPriority = (addons) => {
@@ -5,6 +5,7 @@ import { useWindowSize } from "@vueuse/core";
import { BMessage } from "buefy";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { formatLocalDateOnly, parseLocalDateOnly } from "@/services/dateOnly.js";
type DateRange = {
startDate: Date,
@@ -115,23 +116,11 @@ const availableYears = computed(() => (
));
const formatDateInputValue = (date: Date) => {
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
return "";
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
return formatLocalDateOnly(date);
};
const parseDateInputValue = (value: string) => {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
if (!match) {
return new Date(value);
}
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
return parseLocalDateOnly(value);
};
const isSameDateInputValue = (left: Date, right: Date) => (
@@ -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,9 +1,12 @@
<script setup>
import { computed, nextTick, ref, watch } from "vue";
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
import { isBlankPosMetadataValue } from "@/components/shop/POSDepartmentProcess.vue";
import {
isBlankPosMetadataValue,
registerPosStepSaveBarrier,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
const props = defineProps({
order_id: {
@@ -123,7 +126,7 @@ const referenceField = createCustomerWishField({
key: "reference",
label: computed(() => t("common.reference")),
source: () => props.reference,
saveValue: (value) => SessionUser.objects.orders.set.reference(props.order_id, value),
saveValue: (value) => saveOrderMetadataField("reference", value, props.order_id),
testIdBase: "pos-order-customer-wishes-reference",
isRequired: () => props.referenceRequired,
warningStateWhenEmpty: "danger",
@@ -134,7 +137,7 @@ const poField = createCustomerWishField({
key: "po",
label: computed(() => t("admin.pos.po_number")),
source: () => props.po,
saveValue: (value) => SessionUser.objects.orders.set.po(props.order_id, value),
saveValue: (value) => saveOrderMetadataField("po", value, props.order_id),
testIdBase: "pos-order-customer-wishes-po",
isRequired: () => props.poRequired,
warningStateWhenEmpty: "warning",
@@ -145,7 +148,7 @@ const safetySealField = createCustomerWishField({
key: "safety_seal",
label: computed(() => t("admin.pos.safety_seal")),
source: () => props.safetySeal,
saveValue: (value) => SessionUser.objects.orders.set.safety_seal(props.order_id, value),
saveValue: (value) => saveOrderMetadataField("safety_seal", value, props.order_id),
testIdBase: "pos-order-customer-wishes-safety-seal",
isRequired: () => false,
warningStateWhenEmpty: null,
@@ -161,6 +164,15 @@ const safetySealField = createCustomerWishField({
const fields = computed(() => {
return props.showSafetySeal ? [referenceField, poField, safetySealField] : [referenceField, poField];
});
const flushCustomerWishFields = async () => {
await Promise.all(fields.value.map((field) => field.autosave.flushAndWait()));
};
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(flushCustomerWishFields);
onBeforeUnmount(() => {
unregisterStepSaveBarrier();
});
</script>
<template>
@@ -1,8 +1,11 @@
<script setup>
import { computed, nextTick, ref, watch } from 'vue';
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
import {
registerPosStepSaveBarrier,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
const props = defineProps({
order_id: {
@@ -30,8 +33,7 @@ const isEditing = ref(false);
const autosave = useOrderMetadataAutosave({
source: () => props.note,
saveValue: async (value) => {
await SessionUser.objects.orders.set.notes(props.order_id, value);
return value;
return saveOrderMetadataField("notes", value, props.order_id);
},
onSaved: async (savedValue, requestedValue) => {
if (typeof props.onSaved === "function") {
@@ -68,6 +70,12 @@ const lostFocus = async () => {
isEditing.value = false;
await autosave.flush();
};
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(() => autosave.flushAndWait());
onBeforeUnmount(() => {
unregisterStepSaveBarrier();
});
</script>
<template>
@@ -1,8 +1,11 @@
<script setup>
import { computed, nextTick, ref, watch } from "vue";
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
import {
registerPosStepSaveBarrier,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
const props = defineProps({
index: {
@@ -36,8 +39,7 @@ autosave = useOrderMetadataAutosave({
source: () => props.modelValue,
normalizeValue: normalizeRegistrationDraft,
saveValue: async (value) => {
await SessionUser.objects.orders.set[fieldKey.value](props.order_id, value);
return value;
return saveOrderMetadataField(fieldKey.value, value, props.order_id);
},
onSaved: async () => {
await props.loadOrder();
@@ -74,6 +76,12 @@ const closeEditor = async () => {
isEditing.value = false;
await autosave.flush();
};
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(() => autosave.flushAndWait());
onBeforeUnmount(() => {
unregisterStepSaveBarrier();
});
</script>
<template>
@@ -817,6 +817,10 @@ const isDraftAssignmentModalOpen = computed(() => {
});
const canEditTransaction = (order) => {
if (props.isCustomerView) {
return SessionUser.canAccessCustomerFeature("orders", "edit");
}
return SessionUser.canAccessAdmin() || SessionUser.canAccessDepartment(order.department_id);
};
@@ -1259,7 +1263,7 @@ const formatCashierName = (order) => {
:hover-text="order.reference"
:parse-function="(value) => truncateOrderField(value, 10)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => true"
:permission-check-function="() => canEditTransaction(order)"
/>
<EditableTableColumn
:object="order"
@@ -1278,7 +1282,7 @@ const formatCashierName = (order) => {
:hover-text="order.po"
:parse-function="(value) => truncateOrderField(value, 10)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => canSeePoField() || SessionUser.canAccessAdmin()"
:permission-check-function="() => canEditTransaction(order)"
/>
<td>{{ order.created_at }}</td>
<td>{{ SessionUser.functions.currency.toLocal(order.total_net_amount) }}</td>
@@ -1774,7 +1778,7 @@ const formatCashierName = (order) => {
:hover-text="order.reference"
:parse-function="(value) => truncateOrderField(value, 20)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => true"
:permission-check-function="() => canEditTransaction(order)"
/>
</small>
</div>
@@ -1818,7 +1822,7 @@ const formatCashierName = (order) => {
:hover-text="order.po"
:parse-function="(value) => truncateOrderField(value, 20)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => canSeePoField() || SessionUser.canAccessAdmin()"
:permission-check-function="() => canEditTransaction(order)"
/>
</small>
</div>
@@ -1863,7 +1867,7 @@ const formatCashierName = (order) => {
:hover-text="order.reg_1"
:parse-function="(value) => truncateOrderField(value, 20)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => true"
:permission-check-function="() => canEditTransaction(order)"
/>
</small>
</div>
@@ -1895,7 +1899,7 @@ const formatCashierName = (order) => {
:hover-text="order.reg_2"
:parse-function="(value) => truncateOrderField(value, 20)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => true"
:permission-check-function="() => canEditTransaction(order)"
/>
</small>
</div>
@@ -1927,7 +1931,7 @@ const formatCashierName = (order) => {
:hover-text="order.reg_3"
:parse-function="(value) => truncateOrderField(value, 20)"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permission-check-function="() => true"
:permission-check-function="() => canEditTransaction(order)"
/>
</small>
</div>
@@ -30,6 +30,7 @@ import PosLastScannedLicensePlatesV2 from "@/components/displays/department/pos/
import PosDesktopOrderBookingSelectorModal from "@/components/displays/department/pos/steps/elements/PosDesktopOrderBookingSelectorModal.vue";
import PosDesktopCustomerConflictModal from "@/components/displays/department/pos/steps/elements/PosDesktopCustomerConflictModal.vue";
import PosDesktopDuplicateWarning from "@/components/displays/department/pos/steps/elements/PosDesktopDuplicateWarning.vue";
import { todayLocalDateOnly } from "@/services/dateOnly.js";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { parsePosRouteSearch } from "@/views/dashboards/departmentDashboard/modules/Pos/posRouteState.js";
import {
@@ -377,8 +378,8 @@ const fetchDuplicateOrdersForContext = async (context) => {
try {
const response = await SessionUser.request(SessionUser.objects.orders.meta.endpoint, "GET", {
filters: `reg_1:${normalizedContext.reg1},department_id:${department_id.value},created_at-date_from:${
new Date().toISOString().split("T")[0]
},created_at-date_to:${new Date().toISOString().split("T")[0]}`,
todayLocalDateOnly()
},created_at-date_to:${todayLocalDateOnly()}`,
limit: 5,
});
@@ -4,7 +4,7 @@ import {
setBackgroundColor,
backgroundColors,
} from "@/components/viewport/page/headers/ViewportHeaderSettings.vue";
import { onMounted, ref, watch, computed } from "vue";
import { onBeforeUnmount, onMounted, ref, watch, computed } from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import PosDepartmentStepMobile2Product from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Product.vue";
import PosDepartmentStepMobile2LastOrder from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2LastOrder.vue";
@@ -43,6 +43,8 @@ import {
customer_name,
isAddonRestricted,
canBuyAdditionalServices,
registerPosStepSaveBarrier,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
import { createOrderItem, getOrderItems, removeOrderItem } from "@/components/shop/OrdersItems.vue";
import { PosProduct } from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
@@ -196,8 +198,7 @@ const applyPendingBookingFromSelection = async () => {
// Set PO number on the order if provided by the booking
if (booking.po && typeof booking.po === "string" && booking.po.trim() !== "") {
try {
await SessionUser.objects.orders.set.po(order_id.value, booking.po);
order_po.value = booking.po.trim();
order_po.value = await saveOrderMetadataField("po", booking.po, order_id.value);
} catch (e) {
console.error("Failed to set PO on order from booking", e);
}
@@ -217,9 +218,9 @@ const applyPendingBookingFromSelection = async () => {
const bookingReference = String(fullBookingData.reference ?? fullBookingData.reference_number ?? "").trim();
if (bookingReference !== "") {
try {
await SessionUser.objects.orders.set.reference(order_id.value, bookingReference);
persistedReference.value = bookingReference;
metadata.setReference(bookingReference);
const savedReference = await saveOrderMetadataField("reference", bookingReference, order_id.value);
persistedReference.value = savedReference;
metadata.setReference(savedReference);
hydrateReferenceFromSources();
} catch (setRefError) {
console.error("Failed to set reference on order from booking:", setRefError);
@@ -228,10 +229,10 @@ const applyPendingBookingFromSelection = async () => {
const bookingNotes = String(fullBookingData.notes ?? fullBookingData.note ?? "").trim();
if (bookingNotes !== "") {
try {
await SessionUser.objects.orders.set.notes(order_id.value, bookingNotes);
order_notes.value = bookingNotes;
notes.value = bookingNotes;
metadata.setNotes(bookingNotes);
const savedNotes = await saveOrderMetadataField("notes", bookingNotes, order_id.value);
order_notes.value = savedNotes;
notes.value = savedNotes;
metadata.setNotes(savedNotes);
} catch (setNotesError) {
console.error("Failed to set notes on order from booking:", setNotesError);
}
@@ -243,13 +244,11 @@ const applyPendingBookingFromSelection = async () => {
const currentReg2 = normalizeRegistrationValue(reg_2.value);
if (bookingReg1 && bookingReg1 !== currentReg1) {
reg_1.value = bookingReg1;
await SessionUser.objects.orders.set.reg_1(order_id.value, bookingReg1);
reg_1.value = await saveOrderMetadataField("reg_1", bookingReg1, order_id.value);
}
if (bookingReg2 !== currentReg2) {
reg_2.value = bookingReg2;
await SessionUser.objects.orders.set.reg_2(order_id.value, bookingReg2);
reg_2.value = await saveOrderMetadataField("reg_2", bookingReg2, order_id.value);
}
const bookingMatches = Array.isArray(vehicles.vehicle_1.value?.booking_matches)
@@ -538,8 +537,7 @@ const notesAutosave = useOrderMetadataAutosave({
return value;
}
await SessionUser.objects.orders.set.notes(normalizedOrderId, value);
return value;
return saveOrderMetadataField("notes", value, normalizedOrderId);
},
onSaved: async (value) => {
order_notes.value = value;
@@ -555,8 +553,7 @@ const referenceAutosave = useOrderMetadataAutosave({
return value;
}
await SessionUser.objects.orders.set.reference(normalizedOrderId, value);
return value;
return saveOrderMetadataField("reference", value, normalizedOrderId);
},
onSaved: async (value) => {
persistedReference.value = value;
@@ -572,8 +569,7 @@ const safetySealAutosave = useOrderMetadataAutosave({
return value;
}
await SessionUser.objects.orders.set.safety_seal(normalizedOrderId, value);
return value;
return saveOrderMetadataField("safety_seal", value, normalizedOrderId);
},
onSaved: async (value) => {
order_safety_seal.value = value;
@@ -585,6 +581,18 @@ const stepTwoNotesInput = notesAutosave.draft;
const stepTwoReferenceInput = referenceAutosave.draft;
const stepTwoSafetySealInput = safetySealAutosave.draft;
const showSafetySealField = computed(() => transactionItems.containsWashCertificate());
const flushStepTwoMetadataFields = async () => {
await Promise.all([
notesAutosave.flushAndWait(),
referenceAutosave.flushAndWait(),
safetySealAutosave.flushAndWait(),
]);
};
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(flushStepTwoMetadataFields);
onBeforeUnmount(() => {
unregisterStepSaveBarrier();
});
const resolveReferenceFromSources = () => {
const orderReference = normalizeReferenceValue(persistedReference.value);
@@ -22,7 +22,7 @@ import PosDepartmentStep2MobileVehicleSelection
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import PosDepartmentStepMobile2FloatingCart
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2FloatingCart.vue";
import { isAddonRestricted, canBuyAdditionalServices } from "@/components/shop/POSDepartmentProcess.vue";
import { isAddonRestricted, canBuyAdditionalServices, isProductRestricted } from "@/components/shop/POSDepartmentProcess.vue";
const props = defineProps({
label: {
@@ -46,6 +46,7 @@ const props = defineProps({
},
});
const canSelectAdditionalItems = computed(() => canBuyAdditionalServices());
const checked = ref(props.defaultChecked);
// Function to generate a summary from the last order
function generateSummary(order: PosOrder): string {
@@ -72,6 +73,12 @@ const displaySubtitle = computed(() => {
});
// Emit event on toggle
function onToggle(isOpen: boolean) {
if (isOpen && !canSelectAdditionalItems.value) {
checked.value = false;
pos.views.additionalItemSelection.value = false;
return;
}
checked.value = isOpen;
// Open the additional item selection view if toggled open
if (isOpen) {
@@ -118,17 +125,45 @@ const getAvailableAdditionalItems = () => {
}));
}
const availableAdditionalItems = ref<Addon[]>(getAvailableAdditionalItems());
const isAdditionalItemRestricted = (product: PosProduct) => {
if (!canSelectAdditionalItems.value) {
return true;
}
if (isProductRestricted(product)) {
return true;
}
return isAddonRestricted(convertProductToAddon(product));
}
watch(canSelectAdditionalItems, (canSelect) => {
if (!canSelect) {
checked.value = false;
pos.views.additionalItemSelection.value = false;
}
});
// Computed property to filter out restricted additional items based on customer attributes
const filteredAdditionalItems = computed(() => {
// If additional services are restricted, return empty array
if (!canBuyAdditionalServices()) {
if (!canSelectAdditionalItems.value) {
return [];
}
// Filter out individually restricted addons
return availableAdditionalItems.value.filter((addon: Addon) => !isAddonRestricted(addon));
return availableAdditionalItems.value.filter((addon: Addon) => {
if (isAddonRestricted(addon)) {
return false;
}
if (addon.product && isProductRestricted(addon.product)) {
return false;
}
return true;
});
});
const onClickAddOtherProduct = () => {
if (!canSelectAdditionalItems.value) {
pos.views.additionalItemSelection.value = false;
return;
}
pos.views.additionalItemSelection.value = !pos.views.additionalItemSelection.value;
}
@@ -159,7 +194,9 @@ watch(() => pos.transactionItems.additionalItems.value, (newVal) => {
}, { deep: true });
const onClickAddProduct = async (product: PosProduct) => {
// If the product requires note, open note input.
if (isAdditionalItemRestricted(product)) {
return;
}
pos.transactionItems.addAdditionalItem(product);
// If the view is fullscreen, close it after adding
//if (pos.views.additionalItemSelection.value) {
@@ -171,7 +208,7 @@ const onClickAddProduct = async (product: PosProduct) => {
<template>
<div data-testid="pos-mobile-additional-items">
<!-- Minimal view, when not set as fullscreen view -->
<WhiteBoxCard :toggleable="pos.transactionItems.additionalItems.value && pos.transactionItems.additionalItems.value.length == 0"
<WhiteBoxCard :toggleable="canSelectAdditionalItems && pos.transactionItems.additionalItems.value && pos.transactionItems.additionalItems.value.length == 0"
:defaultOpen="pos.transactionItems.additionalItems.value && pos.transactionItems.additionalItems.value.length > 0"
@toggle="onToggle"
:forceState="(pos.transactionItems.additionalItems.value && pos.transactionItems.additionalItems.value.length > 0) ? true : (pos.views.additionalItemSelection.value)"
@@ -201,7 +238,7 @@ const onClickAddProduct = async (product: PosProduct) => {
</div>
</template>
<!-- Footer -->
<template #footer>
<template #footer v-if="canSelectAdditionalItems">
<!-- Select other product button -->
<a class="card-footer-item" data-testid="pos-mobile-additional-items-open" @click="onClickAddOtherProduct">
<span class="icon">
@@ -212,7 +249,7 @@ const onClickAddProduct = async (product: PosProduct) => {
</template>
</WhiteBoxCard>
<!-- Fullscreen view, when selecting other products -->
<template v-else>
<template v-else-if="canSelectAdditionalItems">
<div data-testid="pos-mobile-additional-items-selection">
<!-- Categories of products -->
<PosDepartmentStep2MobileVehicleSelection :onAddProduct="onClickAddProduct" :onSearchClick="() => console.warn('AdditionalItem Search Clicked')"/><!-- :asAddons="true" :addons="availableAdditionalItems" @update:addons="availableAdditionalItems = $event"/>-->
@@ -20,6 +20,7 @@ import {
hasAttribute,
uploadAttachment,
restoreStoredPosOrderId,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
import * as POSDepartmentProcess from "@/components/shop/POSDepartmentProcess.vue";
import { errors } from "@/components/request/HandleGlobalError.vue";
@@ -96,17 +97,30 @@ const props = defineProps({
},
});
const updateReference = () => {
if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.reference(order_id.value, reference.value);
const getActiveOrderId = () => {
const parsedOrderId = Number.parseInt(String(order_id.value ?? ""), 10);
return Number.isInteger(parsedOrderId) && parsedOrderId > 0 ? parsedOrderId : null;
};
const updateNotes = () => {
if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.notes(order_id.value, order_notes.value);
const updateReference = async () => {
const activeOrderId = getActiveOrderId();
if (activeOrderId) {
await saveOrderMetadataField("reference", reference.value, activeOrderId);
}
};
const updateSafetySeal = () => {
if (!!order_id.value && order_id.value > 0)
SessionUser.objects.orders.set.safety_seal(order_id.value, order_safety_seal.value);
const updateNotes = async () => {
const activeOrderId = getActiveOrderId();
if (activeOrderId) {
await saveOrderMetadataField("notes", order_notes.value, activeOrderId);
}
};
const updateSafetySeal = async () => {
const activeOrderId = getActiveOrderId();
if (activeOrderId) {
await saveOrderMetadataField("safety_seal", order_safety_seal.value, activeOrderId);
}
};
// Helpers: extracted for clarity and reuse
@@ -196,9 +210,9 @@ const step1 = async () => {
// Reset the vehicle selection
views.vehicleSelection.value = false;
// Merge references and notes
mergeReferences();
mergeNotes();
mergeSafetySeal();
await mergeReferences();
await mergeNotes();
await mergeSafetySeal();
// We only need to check if the customer requires reference, if the field is empty.
if (!isNonEmptyString(reference.value)) {
const isValid = await validateReferenceRequirements();
@@ -214,37 +228,39 @@ const mergeReferences = () => {
if (metadata.getReference() && metadata.getReference().length >= 1) {
// If there is no reference, set it to the registration number of vehicle 1
reference.value = metadata.getReference();
updateReference();
return updateReference();
} else if (reference.value && reference.value.length >= 1) {
// If there is a reference in the field, set it to the metadata
metadata.setReference(reference.value);
updateReference();
return updateReference();
} else if (vehicles.vehicle_1.value?.reference && vehicles.vehicle_1.value?.reference.length >= 1) {
// If there is no reference, set it to the registration number of vehicle 1
reference.value = vehicles.vehicle_1.value?.reference;
metadata.setReference(reference.value);
updateReference();
return updateReference();
}
return Promise.resolve();
};
const mergeNotes = () => {
if (metadata.getNotes() && metadata.getNotes().length >= 1) {
order_notes.value = metadata.getNotes();
updateNotes();
return updateNotes();
} else if (order_notes.value && order_notes.value.length >= 1) {
metadata.setNotes(order_notes.value);
updateNotes();
return updateNotes();
}
return Promise.resolve();
};
const mergeSafetySeal = () => {
const resolvedSafetySeal = getResolvedMobileSafetySeal();
order_safety_seal.value = resolvedSafetySeal;
syncMobileSafetySealState(resolvedSafetySeal);
updateSafetySeal();
return updateSafetySeal();
};
const finalizeStep1 = async () => {
mergeReferences();
mergeNotes();
mergeSafetySeal();
await mergeReferences();
await mergeNotes();
await mergeSafetySeal();
// Upload the attachments if there are any (And the order id has been created)
if (attachments.getBase64().length > 0 && order_id.value > 0) {
const uploadResults = await Promise.all(
@@ -62,6 +62,7 @@ watch(
form.invoiceEmail,
form.contactEmail,
form.contactPhone,
form.ean,
canCreateCustomer.value,
searchResult.value,
errorMessage.value,
@@ -124,6 +125,24 @@ watch(
/>
</div>
</div>
<div class="field">
<label class="label"
><small>{{ $t("admin.pos.ean") }}</small></label
>
<div class="control">
<input
v-model="form.ean"
class="input"
type="text"
inputmode="numeric"
maxlength="13"
:placeholder="$t('admin.pos.ean')"
:disabled="searchResult === null || submitting"
data-testid="pos-mobile-add-customer-ean"
@input="handleAutofillFieldInput('ean')"
/>
</div>
</div>
<div class="field">
<label class="label"
><small>{{ $t("admin.pos.invoice_email") }}</small></label
@@ -3,6 +3,7 @@ export const POS_ADD_CUSTOMER_AUTOFILL_FIELDS = [
"invoiceEmail",
"contactEmail",
"contactPhone",
"ean",
];
export function normalizePosAddCustomerValue(value) {
@@ -20,6 +21,7 @@ export function createPosAddCustomerFields(overrides = {}) {
invoiceEmail: "",
contactEmail: "",
contactPhone: "",
ean: "",
...overrides,
};
}
@@ -30,6 +32,7 @@ export function createPosAddCustomerManualOverrides(overrides = {}) {
invoiceEmail: false,
contactEmail: false,
contactPhone: false,
ean: false,
...overrides,
};
}
@@ -93,6 +96,7 @@ export function buildPosAddCustomerAutofillValues(searchResult) {
invoiceEmail: normalizePosAddCustomerValue(searchResult?.email),
contactEmail: normalizePosAddCustomerValue(searchResult?.email),
contactPhone: normalizePosAddCustomerValue(searchResult?.phone),
ean: normalizePosAddCustomerValue(searchResult?.ean),
};
}
@@ -5,6 +5,7 @@ import {
order_safety_seal,
reference,
clearStoredPosOrderId,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
import { metadata } from "./PosDepartmentStepMobileFlow.vue";
@@ -52,9 +53,9 @@ export const persistCurrentMobileOrderMetadata = async () => {
order_notes.value = normalizedNotes;
await Promise.all([
SessionUser.objects.orders.set.reference(normalizedOrderId, normalizedReference),
SessionUser.objects.orders.set.notes(normalizedOrderId, normalizedNotes),
SessionUser.objects.orders.set.safety_seal(normalizedOrderId, normalizedSafetySeal),
saveOrderMetadataField("reference", normalizedReference, normalizedOrderId),
saveOrderMetadataField("notes", normalizedNotes, normalizedOrderId),
saveOrderMetadataField("safety_seal", normalizedSafetySeal, normalizedOrderId),
]);
return normalizedOrderId;
@@ -1,6 +1,13 @@
<script setup lang="ts">
import { ref, onBeforeUnmount, onMounted, watch } from "vue";
import { order_id, reg_1, reg_2, reg_3, getOrderDetails } from "@/components/shop/POSDepartmentProcess.vue";
import {
order_id,
reg_1,
reg_2,
reg_3,
registerPosStepSaveBarrier,
saveOrderMetadataField,
} from "@/components/shop/POSDepartmentProcess.vue";
import PosDepartmentStepMobile1RegistrationNumberInputField from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumberInputField.vue";
import BarcodeScanner from "@/components/viewport/elements/icons/BarcodeScanner.vue";
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
@@ -103,9 +110,7 @@ const createRegistrationAutosave = (vehicleIndex: number) =>
return value;
}
await SessionUser.objects.orders.set[`reg_${vehicleIndex}`](normalizedOrderId, value);
const refreshedOrder = await getOrderDetails(normalizedOrderId);
return refreshedOrder?.[`reg_${vehicleIndex}`] ?? value;
return saveOrderMetadataField(`reg_${vehicleIndex}`, value, normalizedOrderId);
},
onSaved: async (value) => {
setPersistedRegistrationValue(vehicleIndex, value);
@@ -194,6 +199,11 @@ const flushAllRegistrations = async () => {
await Promise.all([1, 2, 3].map((vehicleIndex) => registrationAutosaves[vehicleIndex].flush()));
};
const waitForAllRegistrationSaves = async () => {
await Promise.all([1, 2, 3].map((vehicleIndex) => registrationAutosaves[vehicleIndex].flushAndWait()));
};
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(waitForAllRegistrationSaves);
// Function to set a value if it is not null
const createVehicleSelectionFromSearchResult = (value: PosSearchResult) => ({
...value,
@@ -266,11 +276,12 @@ onMounted(() => {
});
onBeforeUnmount(() => {
unregisterStepSaveBarrier();
void flushAllRegistrations();
});
const closeManualInput = async () => {
await flushAllRegistrations();
await waitForAllRegistrationSaves();
emit("close");
};
</script>
@@ -12,6 +12,7 @@ import PosDepartmentStepMobileButtonNextStep from "@/components/displays/departm
import SessionUser from "@/components/session/token/SessionUser.vue";
import { PosOrder } from "@/components/displays/department/pos/steps/mobile/objects/PosOrder.vue";
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
import { todayLocalDateOnly } from "@/services/dateOnly.js";
// Define the close event to emit when the component is closed
const emit = defineEmits(["close"]);
/** Display variables */
@@ -126,7 +127,7 @@ const liveTransactions = ref(null);
const isLoading = ref(true);
const syncListTransactionHistory = async () => {
let dateToday = new Date().toISOString().split("T")[0]; // Get today's date in YYYY-MM-DD format
let dateToday = todayLocalDateOnly(); // Get today's date in YYYY-MM-DD format
isLoading.value = true;
// Fetch the list of orders created today for the current department
SessionUser.objects.orders.get
@@ -22,7 +22,7 @@ const props = defineProps({
import {computed, ref, watch} from 'vue';
import { departments, getDepartments, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {orderBy, orderDirection, setOrder, loadList} from "@/components/pagination/paginatedList.vue";
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
import Swal from "sweetalert2";
import ActionSettingsWheelItemLabel from "@/components/displays/buttons/ActionSettingsWheelItemLabel.vue";
@@ -38,6 +38,8 @@ import {
setCachedXlvaskUsageAmount
} from "@/components/displays/department/pos/sync/xlvaskUsageAmountCache.js";
const { orderBy, orderDirection, setOrder, loadList } = usePaginatedListInstance();
const redirectDepartmentOrderPage = (orderId, departmentId) => {
// Send the user to the order page (In a new tab)
// `/admin/${departmentId}/modules/pos/orders/${orderId}`
@@ -0,0 +1 @@
export const SELFWASH_PERIOD_ALL_LIMIT = 10000;
@@ -1,6 +1,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { useI18n } from 'vue-i18n';
import { formatLocalDateOnly } from '@/services/dateOnly.js';
type startEndDate = {
start: Date; // The start date of the range
end: Date; // The end date of the range
@@ -27,7 +28,7 @@ const datePresetFunctions = {
month: {
// Get the first day of the month based on the provided date first day at 00:00:01
firstDayOfMonth: (date: Date): Date => {
return new Date(date.getFullYear(), date.getMonth(), 2, 0, 0, 1, 0); // Set to the first day of the month at 00:00:01
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0); // Set to the first day of the month at 00:00:00
},
// Get the last day of the month based on the provided date last day at 23:59:59
lastDayOfMonth: (date: Date): Date => {
@@ -116,7 +117,7 @@ export const datePresets = <datePreset[]>[
*/
const convertToISO = (date: Date): string => {
return date.toISOString().split('T')[0]; // Convert to YYYY-MM-DD format
return formatLocalDateOnly(date); // Convert to YYYY-MM-DD format
};
export const dateFunctions = {
@@ -150,4 +151,4 @@ export default defineComponent({
},
});
</script>
<template></template>
<template></template>
@@ -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>
@@ -21,9 +21,32 @@ const props = defineProps({
type: Number, default: 0
}
})
import {computed, ref, watch } from "vue";
import {computed, provide, ref, watch } from "vue";
import { useRouter } from "vue-router";
import {
PaginatedListKey,
usePaginatedList,
} from "@/components/pagination/paginatedList.vue";
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
import XlvaskUsageOrdersTable from "@/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue";
import PaginationDisplayTemplateDates
from "@/components/displays/pagination/templates/PaginationDisplayTemplateDates.vue";
import ShowErrorField from "@/components/global/ShowErrorField.vue";
import { useI18n } from 'vue-i18n'
import { BSwitch } from "buefy";
import { isUsageOrderAttachedToOrder } from "@/components/displays/department/pos/sync/xlvaskUsageFilters.js";
import { SELFWASH_PERIOD_ALL_LIMIT } from "@/components/displays/department/pos/sync/xlvaskUsagePeriodConstants.js";
import { formatLocalDateOnly, parseLocalDateOnly } from "@/services/dateOnly.js";
const { t } = useI18n()
const router = useRouter();
const paginatedList = usePaginatedList();
provide(PaginatedListKey, paginatedList);
const {
isLoading,
list,
loadList,
@@ -38,22 +61,8 @@ import {
setOrder,
hideSearchField,
setHideSearchField,
} from "@/components/pagination/paginatedList.vue";
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
} = paginatedList;
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
import XlvaskUsageOrdersTable from "@/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue";
import PaginationDisplayTemplateDates
from "@/components/displays/pagination/templates/PaginationDisplayTemplateDates.vue";
import ShowErrorField from "@/components/global/ShowErrorField.vue";
import { useI18n } from 'vue-i18n'
import { BSwitch } from "buefy";
import { isUsageOrderAttachedToOrder } from "@/components/displays/department/pos/sync/xlvaskUsageFilters.js";
const { t } = useI18n()
const router = useRouter();
const SELFWASH_PERIOD_ALL_LIMIT = 10000;
setEndpoint("/modules/xlvask/services/usage/orders", false);
if (props.loadAllAtOnce) {
setMetaItemsPerPage(SELFWASH_PERIOD_ALL_LIMIT, false);
@@ -63,7 +72,7 @@ const parseInitialDate = (value) => {
return new Date();
}
const parsed = new Date(`${value}T00:00:00`);
const parsed = parseLocalDateOnly(value);
return Number.isNaN(parsed.getTime()) ? new Date() : parsed;
};
@@ -129,7 +138,7 @@ const dateFrom = ref(parseInitialDate(props.initialDateFrom));
const dateTo = ref(parseInitialDate(props.initialDateTo));
const parsedDate = (date) => {
return new Date(date);
return parseLocalDateOnly(date);
};
const reloadScheduled = ref(false);
@@ -148,14 +157,14 @@ const actions = {
from: {
select: (date) => {
dateFrom.value = parsedDate(date);
setFilter("StartTime-date_from", date.toISOString().split("T")[0], false);
setFilter("StartTime-date_from", formatLocalDateOnly(date), false);
scheduleReload();
}
},
to: {
select: (date) => {
dateTo.value = parsedDate(date);
setFilter("StartTime-date_to", date.toISOString().split("T")[0], false);
setFilter("StartTime-date_to", formatLocalDateOnly(date), false);
scheduleReload();
}
},
@@ -109,8 +109,8 @@ import OrdersTable from "@/components/displays/department/pos/orders/ordersTable
import {SessionUser} from "@/components/session/token/SessionUser.vue";
import PaginationDisplayFilters from "@/components/displays/pagination/PaginationDisplayFilters.vue";
import DatePeriodSelector from "@/components/displays/buttons/DatePeriodSelector.vue";
import {now} from "@vueuse/core";
import { useI18n } from 'vue-i18n'
import { formatLocalDateOnly, parseLocalDateOnly, todayLocalDateOnly } from "@/services/dateOnly.js";
const { t } = useI18n()
const router = useRouter();
@@ -176,8 +176,8 @@ if (props.autoLoad) {
const date_from = ref(null);
const date_to = ref(null);
const onDateRangeSelected = (newSelectionStartDate, newSelectionToDate) => {
const formattedStartDate = newSelectionStartDate.toISOString().split("T")[0];
const formattedEndDate = newSelectionToDate.toISOString().split("T")[0];
const formattedStartDate = formatLocalDateOnly(newSelectionStartDate);
const formattedEndDate = formatLocalDateOnly(newSelectionToDate);
date_from.value = formattedStartDate;
date_to.value = formattedEndDate;
setFilter("created_at-date_from", formattedStartDate, true);
@@ -361,7 +361,7 @@ const doesEndpointMatch = (matcher) => {
<!-- Shortcuts for date filters -->
<DatePeriodSelector :on-selection-change="onDateRangeSelected"
:visibility="{ showDailySelector: false, showWeeklySelector: false, showMultipleMonthWarning: false, showUpdateButton: false, showMonthSelector: false, showStartDate: false, showEndDate: false, showSelectionValidity: false, showYearSelector: false, showToLabel: false }"
v-bind:selection="{ startDate: date_from ? new Date(date_from) : new Date(now()), endDate: date_to ? new Date(date_to) : new Date(now()) }"/>
v-bind:selection="{ startDate: parseLocalDateOnly(date_from || todayLocalDateOnly()), endDate: parseLocalDateOnly(date_to || todayLocalDateOnly()) }"/>
</div>
</template>
<template #default>
@@ -10,11 +10,21 @@ const props = defineProps(["setCustomerFilter", "hideSearch", "autoLoad"]);
const paginatedList = usePaginatedList();
provide(PaginatedListKey, paginatedList);
const { list, loadList, setEndpoint, setFilter, setOrder, hideSearchField, setHideSearchField } = paginatedList;
const {
list,
loadList,
setAdditionalQueryParameters,
setEndpoint,
setFilter,
setOrder,
hideSearchField,
setHideSearchField,
} = paginatedList;
const { t } = useI18n();
setEndpoint("/users", false);
setFilter("customer_number", 0, false);
setAdditionalQueryParameters({ include_limited_backoffice_employees: "true" });
setOrder("created_at", "desc");
// Hide the search field, if the hideSearch prop is set
@@ -24,13 +24,16 @@ import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue"
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
import BookingsTable from "@/components/displays/user/bookings/bookingsTable.vue";
import {departments} from "@/components/pagination/departmentTabs.vue";
import {ref, watch} from "vue";
import {computed, ref, watch} from "vue";
import { Colors } from "@/ThemeConfig.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n'
import { todayLocalDateOnly } from "@/services/dateOnly.js";
const { t } = useI18n()
const router = useRouter();
const isUserRoute = computed(() => router.currentRoute.value.path.startsWith("/user"));
const canCreateBooking = computed(() => SessionUser.canAccessCustomerFeature("bookings", "add"));
// Set the selected status to all
const selectedStatus = ref("*");
@@ -52,7 +55,7 @@ watch(() => getFilter("status"), (value) => {
// If the route starts with /user, set the endpoint to /user/bookings
if (router.currentRoute.value.path.startsWith("/user")) {
setEndpoint("/user/bookings", false);
setFilter("date", new Date().toISOString().split("T")[0], false);
setFilter("date", todayLocalDateOnly(), false);
setOrder("created_at", "desc");
}
// If the route starts with /superuser, set the endpoint to /bookings
@@ -81,7 +84,7 @@ loadList();
const showingToday = ref(true);
watch(() => getFilter("date"), (value) => {
showingToday.value = value === new Date().toISOString().split("T")[0];
showingToday.value = value === todayLocalDateOnly();
});
// If the screen is mobile, set the is small variable to true
@@ -147,17 +150,17 @@ const showNewOrderBookingsPortal = () => {
</div>
<div class="column is-auto-fill my-3" v-if="!isSmall"/>
<!-- Only show the bookings for today (Switch, if the route is /user) -->
<div class="column is-narrow my-3" v-if="router.currentRoute.value.path.startsWith('/user')" :class="{ 'has-text-right': !isSmall }">
<div class="column is-narrow my-3" v-if="isUserRoute" :class="{ 'has-text-right': !isSmall }">
<label class="label">{{ isSmall ? t('common.today') : t('pagination.show_only_today') }}</label>
<div class="control">
<div class="field">
<input id="today" type="checkbox" class="switch is-rounded" @change="(event) => { setFilter('date', event.target.checked ? new Date().toISOString().split('T')[0] : '*') }" checked="checked" :class="{ 'is-link': showingToday }" />
<input id="today" type="checkbox" class="switch is-rounded" @change="(event) => { setFilter('date', event.target.checked ? todayLocalDateOnly() : '*') }" checked="checked" :class="{ 'is-link': showingToday }" />
<label for="today"></label>
</div>
</div>
</div>
<!-- Create a new booking, if the route is /user -->
<div class="column is-narrow my-3" v-if="router.currentRoute.value.path.startsWith('/user')" :class="{ 'has-text-right': !isSmall }">
<div class="column is-narrow my-3" v-if="isUserRoute && canCreateBooking" :class="{ 'has-text-right': !isSmall }">
<button
class="button is-link"
@click="SessionUser.functions.redirectTo.external('/user/bookings/new', true)"
@@ -188,4 +191,4 @@ const showNewOrderBookingsPortal = () => {
<style scoped>
</style>
</style>
@@ -8,6 +8,7 @@ import OrderBookingsTable from "@/views/dashboards/userDashboard/bookings/displa
import { departments, getDepartments } from "@/components/pagination/departmentTabs.vue";
import { Colors } from "@/ThemeConfig.vue";
import { useI18n } from "vue-i18n";
import { endOfLocalDate, startOfLocalDate, todayLocalDateOnly } from "@/services/dateOnly.js";
const { t } = useI18n();
/**
@@ -16,6 +17,7 @@ const { t } = useI18n();
const router = useRouter();
const isUserRoute = computed(() => router.currentRoute.value.path.startsWith("/user"));
const isAdminRoute = computed(() => router.currentRoute.value.path.startsWith("/admin"));
const canCreateBooking = computed(() => SessionUser.canAccessCustomerFeature("bookings", "add"));
/**
* Props
*/
@@ -52,11 +54,9 @@ const onOnlyTodayFilterChange = (event, autoLoadList = true) => {
setFilter("datetime-date_from", null, false);
setFilter("datetime-date_to", null, false);
} else {
const startOfDay = new Date().setHours(0, 0, 0, 0);
const endOfDay = new Date().setHours(23, 59, 59, 999);
//setFilter('datetime', null, false);
setFilter("datetime-date_from", new Date(startOfDay).toISOString(), false);
setFilter("datetime-date_to", new Date(endOfDay).toISOString(), false);
setFilter("datetime-date_from", startOfLocalDate(val).toISOString(), false);
setFilter("datetime-date_to", endOfLocalDate(val).toISOString(), false);
}
if (autoLoadList) {
loadList();
@@ -95,7 +95,7 @@ onMounted(() => {
departmentFilter.value = value;
} else if (key === "only_today" && value === true) {
setFilterKey = false; // Since the only_today filter is handled separately
onOnlyTodayFilterChange({ target: { value: new Date().toISOString().split("T")[0] } }, false);
onOnlyTodayFilterChange({ target: { value: todayLocalDateOnly() } }, false);
}
if (setFilterKey) {
setFilter(key, value, false);
@@ -170,7 +170,7 @@ onMounted(() => {
<div class="select">
<select :value="onlyTodayFilter" @change="onOnlyTodayFilterChange">
<option value="*">{{ t("common.all") }}</option>
<option :value="new Date().toISOString().split('T')[0]">{{ t("common.yes") }}</option>
<option :value="todayLocalDateOnly()">{{ t("common.yes") }}</option>
</select>
</div>
</div>
@@ -193,7 +193,7 @@ onMounted(() => {
@change="
(event) => {
onOnlyTodayFilterChange({
target: { value: event.target.checked ? new Date().toISOString().split('T')[0] : '*' },
target: { value: event.target.checked ? todayLocalDateOnly() : '*' },
});
}
"
@@ -203,7 +203,7 @@ onMounted(() => {
<label for="today"></label>
</div>
</div>
<div class="order-bookings-pagination__new-booking-action">
<div class="order-bookings-pagination__new-booking-action" v-if="canCreateBooking">
<label class="label is-small order-bookings-pagination__desktop-spacer">&nbsp;</label>
<button
class="button is-link button-same-width"
@@ -226,7 +226,7 @@ onMounted(() => {
@change="
(event) => {
onOnlyTodayFilterChange(
{ target: { value: event.target.checked ? new Date().toISOString().split('T')[0] : '*' } },
{ target: { value: event.target.checked ? todayLocalDateOnly() : '*' } },
false
);
onOrderIdFilterChange({ target: { value: event.target.checked ? 'is null' : '*' } });
@@ -8,6 +8,7 @@ import {
import {SessionUser} from "@/components/session/token/SessionUser.vue";
import { useRouter } from "vue-router";
import VehiclesTable from "@/components/displays/user/vehicles/vehiclesTable.vue";
import { computed } from "vue";
import PaginationDisplayFilters from "@/components/displays/pagination/PaginationDisplayFilters.vue";
import {Colors} from "@/ThemeConfig.vue";
@@ -20,6 +21,8 @@ const { t } = useI18n();
* Router
*/
const router = useRouter();
const isUserRoute = computed(() => router.currentRoute.value.path.startsWith("/user"));
const canAddVehicle = computed(() => SessionUser.canAccessCustomerFeature("vehicles", "add"));
const props = defineProps({
customer_id: {
@@ -52,9 +55,13 @@ loadList();
:columnLabels="{ type: SessionUser.objects.vehicles.columns.type.label }"
/>
</div>
<div v-if="$slots.actions" class="vehicles-pagination__add-action">
<label class="label is-small">&nbsp;</label>
<slot name="actions" />
</div>
<!-- Create a new vehicle, if the route is /user -->
<div class="vehicles-pagination__add-action"
v-if="router.currentRoute.value.path.startsWith('/user')">
v-if="isUserRoute && canAddVehicle">
<label class="label is-small">&nbsp;</label>
<button
class="button is-link button-same-width vehicles-pagination__add-button"
@@ -1,6 +1,7 @@
<script setup lang="ts">
import PaginationDisplayItemColumn from "@/components/displays/pagination/PaginationDisplayItemColumn.vue";
import { ref, computed } from "vue";
import { computed } from "vue";
import { formatLocalDateOnly, parseLocalDateOnly } from "@/services/dateOnly.js";
const props = defineProps({
label: {
type: String,
@@ -19,7 +20,7 @@ defineExpose({
});
const formattedDate = computed(() => {
return date.value.toISOString().split('T')[0];
return formatLocalDateOnly(date.value);
});
// This component is used to display a date input in a pagination display item column.
@@ -36,8 +37,7 @@ const formattedDate = computed(() => {
class="input"
:value="formattedDate"
@input="(e) => {
const newDate = new Date(e.target.value);
console.log('Selected date:', newDate);
const newDate = parseLocalDateOnly(e.target.value);
emit('update:date', newDate);
}"
/>
@@ -47,4 +47,4 @@ const formattedDate = computed(() => {
<style scoped>
</style>
</style>
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { SessionUser } from '@/components/session/token/SessionUser.vue';
@@ -10,7 +9,7 @@ import PaginationDisplayTemplateButton
from "@/components/displays/pagination/templates/PaginationDisplayTemplateButton.vue";
import { datePresets , dateFunctions} from '@/components/displays/pagination/PaginationDisplayDates.vue';
const props = defineProps({
defineProps({
startDate: {
type: Date,
required: true,
@@ -46,6 +45,7 @@ function updateStartDate(date: Date) {
// Update the end date model when the date is changed
function updateEndDate(date: Date) {
endDateModel.value = date;
emits('update:endDate', date);
}
</script>
@@ -79,4 +79,4 @@ function updateEndDate(date: Date) {
<style scoped>
</style>
</style>
@@ -1,5 +1,5 @@
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { RouterLink } from "vue-router";
import { useI18n } from "vue-i18n";
import {
@@ -16,6 +16,7 @@ import {
const { t, te, locale } = useI18n();
const MAX_GATEWAY_CARDS = 8;
const DEFAULT_DASHBOARD_TAB = "infrastructure";
const gatewayStatusPriority = Object.freeze({
OFFLINE: 0,
DEGRADED: 1,
@@ -77,7 +78,14 @@ const gatewayFleetUsage = ref(createEmptyGatewayFleetUsage());
const gatewaySectionSuppressed = ref(false);
const gatewayDepartments = ref({});
const gatewayDepartmentsLoaded = ref(false);
const activeDashboardTab = ref(DEFAULT_DASHBOARD_TAB);
const showGatewaySection = computed(() => canViewGateways.value && !gatewaySectionSuppressed.value);
const dashboardTabKeys = computed(() => [
DEFAULT_DASHBOARD_TAB,
...(showGatewaySection.value ? ["gateways"] : []),
"modules",
"sessions",
]);
const gatewaySummaryCards = computed(() => [
{
id: "total",
@@ -187,6 +195,16 @@ const isStale = computed(() => {
return nowTick.value - lastLoadedAt.value.getTime() > (refreshAfterSeconds.value * 2000);
});
watch(
() => [activeDashboardTab.value, dashboardTabKeys.value.join("|")],
() => {
if (!dashboardTabKeys.value.includes(activeDashboardTab.value)) {
activeDashboardTab.value = DEFAULT_DASHBOARD_TAB;
}
},
{ immediate: true }
);
const loadStatus = async ({ force = false } = {}) => {
const [snapshotValue] = await Promise.all([
getSuperuserSystemStatus({ force }),
@@ -707,203 +725,250 @@ function modulePath(key) {
</ul>
</div>
<section class="system-status-section">
<div class="section-heading">
<h3>{{ $t("system_status.sections.infrastructure") }}</h3>
<RouterLink class="section-link" to="/superuser/system/replication">
{{ $t("system_status.actions.open_replication") }}
</RouterLink>
</div>
<div class="status-card-grid">
<article
v-for="card in statusCards"
:key="card.id"
class="status-card"
:class="statusClass(card.status)"
:data-testid="`status-card-${card.id}`"
>
<div class="status-card__top">
<p class="status-card__title">{{ card.title }}</p>
<span class="status-pill" :class="statusClass(card.status)">{{ statusLabel(card.status) }}</span>
</div>
<strong class="status-card__primary">{{ card.primary }}</strong>
<p class="status-card__secondary">{{ card.secondary }}</p>
<small class="status-card__detail">{{ card.detail }}</small>
<small
v-if="card.replicationLabel"
class="status-card__detail status-card__replication"
:data-testid="`status-card-${card.id}-replication`"
>
{{ card.replicationLabel }}
</small>
</article>
</div>
</section>
<section
v-if="showGatewaySection"
class="system-status-section"
data-testid="system-status-gateways"
<b-tabs
v-model="activeDashboardTab"
expanded
type="is-boxed"
class="system-status-tabs"
data-testid="system-status-dashboard-tabs"
>
<div class="section-heading">
<div>
<h3>{{ $t("system_status.sections.gateways") }}</h3>
<p class="section-subtitle">{{ $t("system_status.gateways.description") }}</p>
</div>
<RouterLink class="section-link" to="/superuser/selfserve/edge-agents">
{{ $t("system_status.gateways.actions.open_fleet") }}
</RouterLink>
</div>
<b-tab-item value="infrastructure">
<template #header>
<b-icon pack="fas" icon="server" />
<span data-testid="system-status-tab-infrastructure">
{{ $t("system_status.sections.infrastructure") }}
</span>
</template>
<div class="gateway-summary-grid">
<article
v-for="card in gatewaySummaryCards"
:key="card.id"
class="summary-card gateway-summary-card"
:class="card.toneClass"
:data-testid="`gateway-summary-card-${card.id}`"
<section class="system-status-section" data-testid="system-status-panel-infrastructure">
<div class="section-heading">
<h3>{{ $t("system_status.sections.infrastructure") }}</h3>
<RouterLink class="section-link" to="/superuser/system/replication">
{{ $t("system_status.actions.open_replication") }}
</RouterLink>
</div>
<div class="status-card-grid">
<article
v-for="card in statusCards"
:key="card.id"
class="status-card"
:class="statusClass(card.status)"
:data-testid="`status-card-${card.id}`"
>
<div class="status-card__top">
<p class="status-card__title">{{ card.title }}</p>
<span class="status-pill" :class="statusClass(card.status)">{{ statusLabel(card.status) }}</span>
</div>
<strong class="status-card__primary">{{ card.primary }}</strong>
<p class="status-card__secondary">{{ card.secondary }}</p>
<small class="status-card__detail">{{ card.detail }}</small>
<small
v-if="card.replicationLabel"
class="status-card__detail status-card__replication"
:data-testid="`status-card-${card.id}-replication`"
>
{{ card.replicationLabel }}
</small>
</article>
</div>
</section>
</b-tab-item>
<b-tab-item v-if="showGatewaySection" value="gateways">
<template #header>
<b-icon pack="fas" icon="network-wired" />
<span data-testid="system-status-tab-gateways">
{{ $t("system_status.sections.gateways") }}
</span>
</template>
<section
class="system-status-section"
data-testid="system-status-gateways"
>
<p class="summary-label">{{ card.title }}</p>
<strong>{{ card.value }}</strong>
</article>
</div>
<div
v-if="gatewayError"
class="notification is-warning is-light gateway-notification"
data-testid="gateway-section-error"
>
{{ $t("system_status.gateways.error") }}
</div>
<div
v-if="gatewayLoading && !gatewayCards.length"
class="notification is-light gateway-notification"
>
{{ $t("system_status.gateways.loading") }}
</div>
<div v-if="gatewayCards.length" class="gateway-grid">
<article
v-for="gateway in gatewayCards"
:key="gateway.id"
class="gateway-card"
:class="gateway.toneClass"
:data-testid="`gateway-card-${gateway.id}`"
>
<div class="gateway-card__top">
<div class="section-heading">
<div>
<strong class="gateway-card__title">{{ gateway.displayLabel }}</strong>
<p class="gateway-card__subtitle">{{ gateway.departmentName }}</p>
<h3>{{ $t("system_status.sections.gateways") }}</h3>
<p class="section-subtitle">{{ $t("system_status.gateways.description") }}</p>
</div>
<span class="status-pill" :class="gateway.toneClass">{{ gateway.statusLabel }}</span>
<RouterLink class="section-link" to="/superuser/selfserve/edge-agents">
{{ $t("system_status.gateways.actions.open_fleet") }}
</RouterLink>
</div>
<div class="gateway-card__meta">
<span>
{{ $t("system_status.gateways.labels.discovery") }}: {{ gateway.discoveryLabel }}
</span>
<span>
{{ $t("system_status.gateways.labels.last_heartbeat") }}: {{ gateway.lastHeartbeatLabel }}
</span>
<span v-if="gateway.activeOperationLabel">
{{ $t("system_status.gateways.labels.active_operation") }}: {{ gateway.activeOperationLabel }}
</span>
<div class="gateway-summary-grid">
<article
v-for="card in gatewaySummaryCards"
:key="card.id"
class="summary-card gateway-summary-card"
:class="card.toneClass"
:data-testid="`gateway-summary-card-${card.id}`"
>
<p class="summary-label">{{ card.title }}</p>
<strong>{{ card.value }}</strong>
</article>
</div>
<p v-if="gateway.message" class="gateway-card__message">
{{ gateway.message }}
</p>
<RouterLink :to="gateway.link" class="module-card__link">
{{ $t("system_status.gateways.actions.open_gateway") }}
</RouterLink>
</article>
</div>
<div
v-else-if="!gatewayLoading && !gatewayError"
class="gateway-empty"
data-testid="gateway-empty-state"
>
{{ $t("system_status.gateways.empty") }}
</div>
</section>
<section class="system-status-section">
<div class="section-heading">
<h3>{{ $t("system_status.sections.modules") }}</h3>
</div>
<div class="module-grid">
<article
v-for="module in modules"
:key="module.key"
class="module-card"
:class="statusClass(module.status)"
:data-testid="`module-card-${module.key}`"
>
<div class="module-card__top">
<p class="module-card__title">{{ moduleLabel(module.key) }}</p>
<span class="status-pill" :class="statusClass(module.status)">{{ statusLabel(module.status) }}</span>
<div
v-if="gatewayError"
class="notification is-warning is-light gateway-notification"
data-testid="gateway-section-error"
>
{{ $t("system_status.gateways.error") }}
</div>
<p class="module-card__reason" :data-testid="`module-reason-${module.key}`">
{{ moduleReasonText(module) }}
</p>
<div class="module-card__meta">
<span>{{ $t("system_status.labels.enabled") }}: {{ module.enabled ? $t("system_status.status.yes") : $t("system_status.status.no") }}</span>
<span>{{ $t("system_status.labels.configured") }}: {{ module.configured ? $t("system_status.status.yes") : $t("system_status.status.no") }}</span>
<span>{{ $t("system_status.labels.checked_at") }}: {{ formatDate(module.checked_at) }}</span>
<div
v-if="gatewayLoading && !gatewayCards.length"
class="notification is-light gateway-notification"
>
{{ $t("system_status.gateways.loading") }}
</div>
<RouterLink v-if="modulePath(module.key)" :to="modulePath(module.key)" class="module-card__link">
{{ $t("system_status.actions.open_config") }}
</RouterLink>
</article>
</div>
</section>
<section class="system-status-section">
<div class="section-heading">
<h3>{{ $t("system_status.sections.sessions") }}</h3>
<p>{{ $t("system_status.labels.activity_window", { minutes: sessions.active_window_minutes }) }}</p>
</div>
<div v-if="gatewayCards.length" class="gateway-grid">
<article
v-for="gateway in gatewayCards"
:key="gateway.id"
class="gateway-card"
:class="gateway.toneClass"
:data-testid="`gateway-card-${gateway.id}`"
>
<div class="gateway-card__top">
<div>
<strong class="gateway-card__title">{{ gateway.displayLabel }}</strong>
<p class="gateway-card__subtitle">{{ gateway.departmentName }}</p>
</div>
<span class="status-pill" :class="gateway.toneClass">{{ gateway.statusLabel }}</span>
</div>
<div class="table-container sessions-table">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>{{ $t("system_status.table.active") }}</th>
<th>{{ $t("system_status.table.name") }}</th>
<th>{{ $t("system_status.table.type") }}</th>
<th>{{ $t("system_status.table.device") }}</th>
<th>{{ $t("system_status.table.route") }}</th>
<th>{{ $t("system_status.table.first_seen") }}</th>
<th>{{ $t("system_status.table.last_seen") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="session in sessions.recent_sessions" :key="`${session.session_kind}-${session.principal_id}-${session.last_seen_at}`">
<td>
<span class="status-pill" :class="session.active ? 'is-ok' : 'is-down'">
{{ session.active ? $t("system_status.status.active") : $t("system_status.status.inactive") }}
<div class="gateway-card__meta">
<span>
{{ $t("system_status.gateways.labels.discovery") }}: {{ gateway.discoveryLabel }}
</span>
</td>
<td>
<strong>{{ sessionDisplayName(session) }}</strong>
<div class="session-context">{{ sessionContextLabel(session) }}</div>
</td>
<td>{{ formatSessionKind(session.session_kind) }}</td>
<td>{{ formatDeviceType(session.device_type) }}</td>
<td class="session-route">{{ session.last_route || "--" }}</td>
<td>{{ formatDate(session.first_seen_at) }}</td>
<td>{{ formatDate(session.last_seen_at) }}</td>
</tr>
<tr v-if="!sessions.recent_sessions?.length">
<td colspan="7">{{ $t("system_status.states.no_sessions") }}</td>
</tr>
</tbody>
</table>
</div>
</section>
<span>
{{ $t("system_status.gateways.labels.last_heartbeat") }}: {{ gateway.lastHeartbeatLabel }}
</span>
<span v-if="gateway.activeOperationLabel">
{{ $t("system_status.gateways.labels.active_operation") }}: {{ gateway.activeOperationLabel }}
</span>
</div>
<p v-if="gateway.message" class="gateway-card__message">
{{ gateway.message }}
</p>
<RouterLink :to="gateway.link" class="module-card__link">
{{ $t("system_status.gateways.actions.open_gateway") }}
</RouterLink>
</article>
</div>
<div
v-else-if="!gatewayLoading && !gatewayError"
class="gateway-empty"
data-testid="gateway-empty-state"
>
{{ $t("system_status.gateways.empty") }}
</div>
</section>
</b-tab-item>
<b-tab-item value="modules">
<template #header>
<b-icon pack="fas" icon="puzzle-piece" />
<span data-testid="system-status-tab-modules">
{{ $t("system_status.sections.modules") }}
</span>
</template>
<section class="system-status-section" data-testid="system-status-panel-modules">
<div class="section-heading">
<h3>{{ $t("system_status.sections.modules") }}</h3>
</div>
<div class="module-grid">
<article
v-for="module in modules"
:key="module.key"
class="module-card"
:class="statusClass(module.status)"
:data-testid="`module-card-${module.key}`"
>
<div class="module-card__top">
<p class="module-card__title">{{ moduleLabel(module.key) }}</p>
<span class="status-pill" :class="statusClass(module.status)">{{ statusLabel(module.status) }}</span>
</div>
<p class="module-card__reason" :data-testid="`module-reason-${module.key}`">
{{ moduleReasonText(module) }}
</p>
<div class="module-card__meta">
<span>{{ $t("system_status.labels.enabled") }}: {{ module.enabled ? $t("system_status.status.yes") : $t("system_status.status.no") }}</span>
<span>{{ $t("system_status.labels.configured") }}: {{ module.configured ? $t("system_status.status.yes") : $t("system_status.status.no") }}</span>
<span>{{ $t("system_status.labels.checked_at") }}: {{ formatDate(module.checked_at) }}</span>
</div>
<RouterLink v-if="modulePath(module.key)" :to="modulePath(module.key)" class="module-card__link">
{{ $t("system_status.actions.open_config") }}
</RouterLink>
</article>
</div>
</section>
</b-tab-item>
<b-tab-item value="sessions">
<template #header>
<b-icon pack="fas" icon="users" />
<span data-testid="system-status-tab-sessions">
{{ $t("system_status.sections.sessions") }}
</span>
</template>
<section class="system-status-section" data-testid="system-status-panel-sessions">
<div class="section-heading">
<h3>{{ $t("system_status.sections.sessions") }}</h3>
<p>{{ $t("system_status.labels.activity_window", { minutes: sessions.active_window_minutes }) }}</p>
</div>
<div class="table-container sessions-table">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>{{ $t("system_status.table.active") }}</th>
<th>{{ $t("system_status.table.name") }}</th>
<th>{{ $t("system_status.table.type") }}</th>
<th>{{ $t("system_status.table.device") }}</th>
<th>{{ $t("system_status.table.route") }}</th>
<th>{{ $t("system_status.table.first_seen") }}</th>
<th>{{ $t("system_status.table.last_seen") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="session in sessions.recent_sessions" :key="`${session.session_kind}-${session.principal_id}-${session.last_seen_at}`">
<td>
<span class="status-pill" :class="session.active ? 'is-ok' : 'is-down'">
{{ session.active ? $t("system_status.status.active") : $t("system_status.status.inactive") }}
</span>
</td>
<td>
<strong>{{ sessionDisplayName(session) }}</strong>
<div class="session-context">{{ sessionContextLabel(session) }}</div>
</td>
<td>{{ formatSessionKind(session.session_kind) }}</td>
<td>{{ formatDeviceType(session.device_type) }}</td>
<td class="session-route">{{ session.last_route || "--" }}</td>
<td>{{ formatDate(session.first_seen_at) }}</td>
<td>{{ formatDate(session.last_seen_at) }}</td>
</tr>
<tr v-if="!sessions.recent_sessions?.length">
<td colspan="7">{{ $t("system_status.states.no_sessions") }}</td>
</tr>
</tbody>
</table>
</div>
</section>
</b-tab-item>
</b-tabs>
</div>
</template>
@@ -924,10 +989,10 @@ function modulePath(key) {
.module-card,
.gateway-card {
border: 1px solid #d7dde7;
border-radius: 18px;
border-radius: 8px;
padding: 1rem 1.1rem;
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.06);
background: #ffffff;
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.05);
}
.summary-card--overall {
@@ -991,6 +1056,23 @@ function modulePath(key) {
color: #0f4c81;
}
.system-status-tabs {
min-width: 0;
}
.system-status-tabs :deep(.tab-content) {
padding: 1rem 0 0;
}
.system-status-tabs :deep(.tabs ul) {
align-items: stretch;
}
.system-status-tabs :deep(.tabs a) {
min-height: 2.75rem;
gap: 0.35rem;
}
.status-card__top,
.module-card__top {
display: flex;
@@ -1107,7 +1189,7 @@ function modulePath(key) {
.gateway-empty {
border: 1px dashed #cbd5e1;
border-radius: 18px;
border-radius: 8px;
padding: 1rem 1.1rem;
color: #475569;
background: rgba(248, 250, 252, 0.8);
@@ -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>
@@ -11,7 +11,9 @@ defineProps({
});
const editUser = (user) => {
showEditUserForm(user.id, user.customer_number, user.display_name, user.group_id);
showEditUserForm(user.id, user.customer_number, user.display_name, user.group_id, {
limitedBackofficeManaged: Boolean(user.limited_backoffice_managed),
});
};
</script>
@@ -13,6 +13,7 @@ import { showDownloadWashCertificate } from "@/components/shop/DownloadWashCerti
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import Swal from 'sweetalert2';
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import { todayLocalDateOnly, yesterdayLocalDateOnly } from "@/services/dateOnly.js";
const redirectBookingObjectPage = (objectId) => {
// Send the user to the object page
window.location.href = `/user/bookings/${objectId}`;
@@ -237,7 +238,8 @@ window.addEventListener('resize', () => {
* To organize the bookings, we want to show the bookings that are created today first, then yesterday, then all other days
* @type {string}
*/
const currentDate = new Date().toISOString().split("T")[0];
const currentDate = todayLocalDateOnly();
const previousDate = yesterdayLocalDateOnly();
/**
* Sort the bookings by date
@@ -362,7 +364,7 @@ const canUserEditObject = (object) => {
</span>
<span class="has-text-grey">
<!-- Human readable date (Today, Yesterday, etc.) -->
{{ object.date === currentDate ? $t('tables.bookings.today') : object.date === new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split("T")[0] ? $t('tables.bookings.yesterday') : object.date }}
{{ object.date === currentDate ? $t('tables.bookings.today') : object.date === previousDate ? $t('tables.bookings.yesterday') : object.date }}
<!-- Number of bookings on the date -->
({{ $t('tables.bookings.bookings_count', { count: objects.filter((booking) => booking.date === object.date).length }) }})
</span>
@@ -45,6 +45,11 @@ const reload = () => {
}
};
const canViewVehicle = () => SessionUser.canAccessCustomerFeature("vehicles", "list");
const canEditVehicle = () => SessionUser.canAccessCustomerFeature("vehicles", "edit");
const canDeleteVehicle = () => SessionUser.canAccessCustomerFeature("vehicles", "delete");
const canShowVehicleRowActions = () => canViewVehicle() || canDeleteVehicle();
const redirectUserVehiclePage = (vehicleId) => {
// Send the user to the vehicle page
window.location.href = `/user/vehicles/${vehicleId}`;
@@ -146,6 +151,7 @@ const getProductOptionsLabel = (vehicle) => {
:object="object"
:loadList="reload"
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
:permission-check-function="canEditVehicle"
column="reg"
/>
<!-- Type -->
@@ -153,6 +159,7 @@ const getProductOptionsLabel = (vehicle) => {
:object="object"
:loadList="reload"
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
:permission-check-function="canEditVehicle"
column="type"
:parse-function="
(value) => {
@@ -165,6 +172,7 @@ const getProductOptionsLabel = (vehicle) => {
:object="object"
:loadList="reload"
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
:permission-check-function="canEditVehicle"
column="wash_subscription"
:parse-function="
(value) => {
@@ -174,7 +182,7 @@ const getProductOptionsLabel = (vehicle) => {
/>
<!-- Product Options, if the wash subscription is set to true -->
<td v-if="!props.compact">
<template v-if="object.wash_subscription">
<template v-if="object.wash_subscription && canEditVehicle()">
<!-- Enabled subscription -->
<ActionSettingsWheelButton
:label="getProductOptionsLabel(object)"
@@ -218,13 +226,14 @@ const getProductOptionsLabel = (vehicle) => {
:object="object"
:loadList="reload"
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
:permission-check-function="canEditVehicle"
column="reference"
/>
<!-- Actions -->
<td>
<td v-if="!props.compact">
<!-- Actions stay grouped behind the wheel menu. -->
<ActionSettingsWheelButton
v-if="!props.compact"
v-if="canShowVehicleRowActions()"
:user_id="object.user_id"
:reg_1="object.reg"
:displayActionsDirectly="false"
@@ -232,12 +241,14 @@ const getProductOptionsLabel = (vehicle) => {
<template #actions>
<!-- View (Redirect to the vehicle page) -->
<ActionSettingsWheelItem
v-if="canViewVehicle()"
:label="$t('global.manage') + ' ' + $t('objects.vehicles.single')"
icon="fas fa-eye"
:click-action="() => redirectUserVehiclePage(object.id)"
/>
<!-- Delete -->
<ActionSettingsWheelItem
v-if="canDeleteVehicle()"
:label="$t('global.delete') + ' ' + $t('objects.vehicles.single')"
icon="fas fa-trash"
:template="'danger'"
@@ -4,11 +4,13 @@ import axios from "axios";
import { API_URL } from "@/config.js";
import { getError, parseError, removeError } from "@/components/request/HandleGlobalError.vue";
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
import { economicEanPayloadValue, normalizeEconomicEan } from "@/services/economicCustomerIdentifiers.js";
const customerFormData = reactive({
cvr: null,
contactEmail: null,
contactPhone: null
contactPhone: null,
ean: null
});
const deliverSuccess = ref(false);
@@ -17,17 +19,24 @@ let deliverTimeoutId;
const submitCustomer = async () => {
removeError("authRegisterCvr");
try {
await axios.post(API_URL + "/auth/register/cvr", {
const payload = {
cvr: customerFormData.cvr,
invoiceEmail: customerFormData.contactEmail,
contactEmail: customerFormData.contactEmail,
contactPhone: customerFormData.contactPhone,
companyPhone: customerFormData.contactPhone
});
};
const ean = economicEanPayloadValue(customerFormData.ean);
if (ean !== undefined) {
payload.ean = ean;
}
await axios.post(API_URL + "/auth/register/cvr", payload);
customerFormData.contactPhone = null;
customerFormData.contactEmail = null;
customerFormData.cvr = null;
customerFormData.ean = null;
deliverSuccess.value = true;
deliverTimeoutId = setTimeout(() => {
@@ -59,6 +68,20 @@ onBeforeUnmount(() => {
>
</div>
<div class="form-field">
<label for="customer_ean">{{ $t("customer_creation.customer.ean_label") }}</label>
<input
id="customer_ean"
:value="customerFormData.ean"
type="text"
inputmode="numeric"
maxlength="13"
autocomplete="off"
placeholder="5790001234567"
@input="customerFormData.ean = normalizeEconomicEan($event.target.value)"
>
</div>
<div class="form-field">
<label for="email">{{ $t("customer_creation.customer.email_label") }}</label>
<input id="email" v-model="customerFormData.contactEmail" type="email" name="email" autocomplete="email">
@@ -5,6 +5,7 @@ import {
getProductsCategory,
loadOrderItems,
productCategoryAllowed,
isProductRestricted,
setProductsCategory,
getUserProductDiscount,
showFakeCreateOrderItem,
@@ -13,7 +14,8 @@ import {
getUserGlobalDiscount,
getUserDiscounts,
customer_id,
customer_attributes
customer_attributes,
loadCustomerAttributes
} from "@/components/shop/POSDepartmentProcess.vue";
import {getProductCategory, getProducts} from "@/components/shop/Products.vue";
import {createOrderItem} from "@/components/shop/OrdersItems.vue";
@@ -151,6 +153,10 @@ const fetchProductsForCategory = async ({
products.value = [];
try {
if (customerNumber) {
await loadCustomerAttributes(customerNumber);
}
const response = normalizedCategory === null
? await getProducts(departmentId, props.finalPrice, customerNumber)
: await getProductCategory(normalizedCategory, departmentId, props.finalPrice, customerNumber);
@@ -593,7 +599,7 @@ const getRecommendedProductPrice = (productId) => {
const isRecommendedProductCategoryAllowed = (productId) => {
const product = getProductById(productId);
return !!product && productCategoryAllowed(product.category);
return !!product && !isProductRestricted(product);
};
const addRecommendedProductToOrder = async (productId) => {
@@ -8,9 +8,9 @@ const props = defineProps(['label', 'isText']);
const label = computed(() => props.label || t('admin.pos.buttons.go_back'));
// Go to previous step
const goToPreviousStep = () => {
const goToPreviousStep = async () => {
// Get the department id
previousStep();
await previousStep();
}
const emptyIfText = (string) => props.isText ? '' : string;
@@ -32,4 +32,4 @@ const emptyIfText = (string) => props.isText ? '' : string;
<style scoped>
</style>
</style>
@@ -405,6 +405,24 @@ watch(activeQuickAction, (nextMode, previousMode) => {
/>
</div>
</div>
<div class="field">
<label class="label"
><small>{{ $t("admin.pos.ean") }}</small></label
>
<div class="control">
<input
v-model="addCustomerForm.ean"
class="input"
type="text"
inputmode="numeric"
maxlength="13"
:placeholder="$t('admin.pos.ean')"
:disabled="addCustomerSearchResult === null || isAddCustomerSubmitting"
data-testid="pos-desktop-add-customer-ean"
@input="handleAddCustomerFieldInput('ean')"
/>
</div>
</div>
<div class="field">
<label class="label"
><small>{{ $t("admin.pos.invoice_email") }}</small></label
@@ -0,0 +1,241 @@
<script setup>
import { computed, nextTick, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import CustomerSearchSelect from "@/components/search/economic/CustomerSearchSelect.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const emit = defineEmits(["close", "created"]);
const { t } = useI18n();
const selectedCustomer = ref(null);
const registrationNumber = ref("");
const vehicleType = ref("");
const washSubscription = ref(false);
const reference = ref("");
const vehicleTypeOptions = ref([]);
const isLoadingVehicleTypes = ref(false);
const isSubmitting = ref(false);
const errorMessage = ref("");
const vehicleTypeError = ref("");
const getCustomerNumber = (customer) => {
const parsedValue = Number.parseInt(
String(customer?.customerNumber ?? customer?.customer_number ?? customer?.customer_id ?? customer?.id ?? ""),
10
);
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
};
const selectedCustomerNumber = computed(() => getCustomerNumber(selectedCustomer.value));
const normalizedVehicleType = computed(() => {
if (vehicleType.value === "") {
return null;
}
const parsedValue = Number.parseInt(String(vehicleType.value), 10);
return Number.isInteger(parsedValue) && parsedValue >= 0 ? parsedValue : null;
});
const normalizedRegistrationNumber = computed(() => {
return registrationNumber.value.trim().toUpperCase();
});
const canSubmit = computed(() => {
return (
!isSubmitting.value &&
!isLoadingVehicleTypes.value &&
selectedCustomerNumber.value !== null &&
normalizedRegistrationNumber.value.length > 0 &&
normalizedVehicleType.value !== null
);
});
const parseErrorMessage = (error) => {
return SessionUser.functions.parseErrorMessage(error) || t("vehicles.add_modal.error");
};
const loadVehicleTypes = async () => {
isLoadingVehicleTypes.value = true;
vehicleTypeError.value = "";
try {
vehicleTypeOptions.value = await SessionUser.objects.vehicles.columns.type.options();
} catch (error) {
vehicleTypeError.value = parseErrorMessage(error) || t("vehicles.add_modal.type_load_error");
} finally {
isLoadingVehicleTypes.value = false;
}
};
const closeModal = () => {
if (!isSubmitting.value) {
emit("close");
}
};
const submitVehicle = async () => {
if (!canSubmit.value) {
errorMessage.value = t("vehicles.add_modal.validation_error");
return;
}
isSubmitting.value = true;
errorMessage.value = "";
try {
const response = await SessionUser.objects.vehicles.add(
normalizedVehicleType.value,
normalizedRegistrationNumber.value,
washSubscription.value,
selectedCustomerNumber.value,
reference.value.trim() || null
);
emit("created", response);
} catch (error) {
errorMessage.value = parseErrorMessage(error);
} finally {
isSubmitting.value = false;
}
};
onMounted(async () => {
await loadVehicleTypes();
await nextTick();
document.getElementById("superuser-add-vehicle-customer-search")?.focus();
});
</script>
<template>
<div class="modal is-active" data-testid="superuser-add-vehicle-modal">
<div class="modal-background" @click="closeModal"></div>
<div class="modal-card superuser-add-vehicle-modal">
<header class="modal-card-head">
<p class="modal-card-title">{{ t("vehicles.add_modal.title") }}</p>
<button
class="delete"
type="button"
:aria-label="t('common.close')"
data-testid="superuser-add-vehicle-close"
@click="closeModal"
></button>
</header>
<section class="modal-card-body">
<div v-if="errorMessage" class="notification is-danger is-light" data-testid="superuser-add-vehicle-error">
{{ errorMessage }}
</div>
<CustomerSearchSelect
v-model="selectedCustomer"
input-id="superuser-add-vehicle-customer-search"
test-id-prefix="superuser-add-vehicle-customer"
:disabled="isSubmitting"
:placeholder="t('vehicles.add_modal.customer_placeholder')"
/>
<div class="field">
<label class="label" for="superuser-add-vehicle-registration">{{ t("vehicles.form.license_plate") }}</label>
<div class="control">
<input
id="superuser-add-vehicle-registration"
v-model="registrationNumber"
class="input"
type="text"
autocomplete="off"
:placeholder="t('vehicles.add_modal.registration_placeholder')"
:disabled="isSubmitting"
data-testid="superuser-add-vehicle-registration"
/>
</div>
</div>
<div class="field">
<label class="label" for="superuser-add-vehicle-type">{{ t("vehicles.form.type") }}</label>
<div class="control" :class="{ 'is-loading': isLoadingVehicleTypes }">
<div class="select is-fullwidth">
<select
id="superuser-add-vehicle-type"
v-model="vehicleType"
:disabled="isSubmitting || isLoadingVehicleTypes || vehicleTypeOptions.length === 0"
data-testid="superuser-add-vehicle-type"
>
<option disabled value="">{{ t("vehicles.add_modal.type_placeholder") }}</option>
<option v-for="option in vehicleTypeOptions" :key="option.id" :value="option.id">
{{ option.name }}
</option>
</select>
</div>
</div>
<p v-if="vehicleTypeError" class="help is-danger" data-testid="superuser-add-vehicle-type-error">
{{ vehicleTypeError }}
</p>
</div>
<div class="field">
<label class="checkbox">
<input
v-model="washSubscription"
type="checkbox"
:disabled="isSubmitting"
data-testid="superuser-add-vehicle-wash-subscription"
/>
{{ t("objects.vehicles.columns.wash_subscription") }}
</label>
</div>
<div class="field">
<label class="label" for="superuser-add-vehicle-reference">{{ t("common.reference") }}</label>
<div class="control">
<input
id="superuser-add-vehicle-reference"
v-model="reference"
class="input"
type="text"
autocomplete="off"
:placeholder="t('vehicles.add_modal.reference_placeholder')"
:disabled="isSubmitting"
data-testid="superuser-add-vehicle-reference"
/>
</div>
</div>
</section>
<footer class="modal-card-foot is-justify-content-flex-end">
<button
class="button"
type="button"
:disabled="isSubmitting"
data-testid="superuser-add-vehicle-cancel"
@click="closeModal"
>
{{ t("common.cancel") }}
</button>
<button
class="button is-link"
type="button"
:class="{ 'is-loading': isSubmitting }"
:disabled="!canSubmit"
data-testid="superuser-add-vehicle-submit"
@click="submitVehicle"
>
{{ t("vehicles.add_modal.submit") }}
</button>
</footer>
</div>
</div>
</template>
<style scoped>
.superuser-add-vehicle-modal {
max-width: min(44rem, calc(100vw - 2rem));
width: 100%;
}
.modal-card-body {
overflow: visible;
}
</style>
@@ -5,7 +5,9 @@ import Swal from "sweetalert2";
//
export const showEditUserForm = async (id, customer_number, display_name, role) => {
export const showEditUserForm = async (id, customer_number, display_name, role, options = {}) => {
const lockedRoleFields = options.limitedBackofficeManaged ? 'disabled' : '';
await Swal.fire({
title: 'Edit User',
html: `
@@ -13,7 +15,7 @@ export const showEditUserForm = async (id, customer_number, display_name, role)
<div class="field">
<label class="label">Customer Number</label>
<div class="control">
<input class="input" type="text" id="customer_number" value="${customer_number}" />
<input class="input" type="text" id="customer_number" value="${customer_number}" ${lockedRoleFields} />
</div>
</div>
<div class="field">
@@ -25,7 +27,7 @@ export const showEditUserForm = async (id, customer_number, display_name, role)
<div class="field">
<label class="label">Role</label>
<div class="control">
<input class="input" type="text" id="role" value="${role}" />
<input class="input" type="text" id="role" value="${role}" ${lockedRoleFields} />
</div>
</div>
<div class="field">
@@ -50,4 +52,4 @@ export const showEditUserForm = async (id, customer_number, display_name, role)
});
}
</script>
</script>
@@ -19,17 +19,17 @@ const SENSITIVE_QUERY_KEYS = /authorization|password|passwd|secret|token|api[_-]
const FALLBACK_LABELS = {
"error_report.button": "Report error",
"error_report.title": "Report error",
"error_report.subtitle": "Send the current screen and recent error details to support.",
"error_report.subtitle": "Send recent error details to support. A screenshot is attached when available.",
"error_report.before_error": "What were you doing before the error occurred?",
"error_report.expected": "What did you expect would happen?",
"error_report.actual": "What actually happened?",
"error_report.before_error_placeholder": "Describe the action you were taking, for example opening orders or selecting a customer.",
"error_report.expected_placeholder": "Describe the result you expected to see.",
"error_report.actual_placeholder": "Describe what you saw instead, including any error text.",
"error_report.consent": "I accept that the current app screen, recent request errors, Vue errors, browser details, and my answers are collected for troubleshooting.",
"error_report.consent": "I accept that recent request errors, Vue errors, browser details, my answers, and an app screenshot when available are collected for troubleshooting.",
"error_report.submit": "Submit report",
"error_report.submitted": "Error report submitted.",
"error_report.capture_failed": "The screen capture failed. Please try again.",
"error_report.capture_failed": "The screen capture failed. The report will be sent without a screenshot.",
"error_report.submit_failed": "The error report could not be submitted.",
"error_report.required": "All fields and data collection acceptance are required.",
};
@@ -277,15 +277,18 @@ const submit = async () => {
isSubmitting.value = true;
try {
let screenshot = "";
let screenshot = null;
try {
screenshot = await captureScreenshot();
} catch {
captureError.value = tr("error_report.capture_failed");
return;
}
const context = buildContext();
context.screenshot_attachment = {
status: screenshot ? "stored" : "capture_failed",
attached: Boolean(screenshot),
};
await submitErrorReport({
before_error: form.before_error.trim(),
expected: form.expected.trim(),
@@ -340,7 +343,7 @@ const submit = async () => {
<div v-if="submitted" class="notification is-success is-light" data-testid="error-report-submitted">
{{ tr("error_report.submitted") }}
</div>
<div v-if="captureError" class="notification is-danger is-light">
<div v-if="captureError" class="notification is-warning is-light" data-testid="error-report-capture-warning">
{{ captureError }}
</div>
<div v-if="submitError" class="notification is-danger is-light">
+4 -3
View File
@@ -4,6 +4,7 @@ import { ref, watch, onMounted } from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useRoute } from "vue-router";
import { isAccessibleVisibleNamedDepartment, sortByDepartmentPriorityOrder } from "@/services/departmentVisibility.js";
import { todayLocalDateOnly } from "@/services/dateOnly.js";
// Get the department ID from the URL
const route = useRoute();
const department_id = route.params.departmentId || SessionUser.functions.getDepartmentIdFromUrl() || null;
@@ -73,9 +74,9 @@ const emphasis = ref({
"department:" +
department_id +
",status:pending,date-date_from:" +
new Date().toISOString().split("T")[0] +
todayLocalDateOnly() +
",date-date_to:" +
new Date().toISOString().split("T")[0],
todayLocalDateOnly(),
limit: 1, // Limit to 1 booking, we only need to know if there are any bookings or not
page: 1,
})
@@ -97,7 +98,7 @@ const emphasis = ref({
* Check if the department has a daily report for today
*/
SessionUser.request(SessionUser.objects.department_daily_reports.meta.endpoint + "/get", "GET", {
date: new Date().toISOString().split("T")[0],
date: todayLocalDateOnly(),
id: department_id, // This refers to the department ID
})
.then((response) => {
@@ -75,19 +75,11 @@ const items = computed<NavigationItemProps[]>(() => [
* ]
* }
*/
{
label: t('templates.limited_backoffice.title'),
to: `/backoffice`,
type: 'link',
permissions: ['limited_backoffice_access'],
hidden: hasPermission('superuser'),
},
{
label: t('common.settings'),
to: `/user/profile`,
type: 'link',
permissions: ['user'],
hidden: !window.location.pathname.startsWith('/user')
hidden: !window.location.pathname.startsWith('/user') || !SessionUser.authenticated.value
},
{
label: t('common.logout'),
@@ -51,7 +51,7 @@ const items = computed<NavigationItemProps[]>(() => [
to: `/user`,
type: 'link',
// Dashboard visible to regular users and all subusers (subusers always have at least one grant)
permissions: ['user', 'BOOKINGS_LIST', 'ORDERS_LIST', 'VEHICLES_LIST', 'SELFSERVE_LIST', 'SUBUSERS_LIST'],
permissions: ['user', 'BOOKINGS_LIST', 'BOOKINGS_ADD', 'ORDERS_LIST', 'VEHICLES_LIST', 'VEHICLES_ADD', 'SELFSERVE_LIST', 'SUBUSERS_LIST'],
},
{
label: t('common.vehicles'),
@@ -0,0 +1,288 @@
<script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { isSearching, searchCustomer, searchCustomerResults } from "@/components/search/economic/customerSearch.vue";
const props = defineProps({
modelValue: {
type: Object,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
inputId: {
type: String,
default: "customer-search-select-input",
},
testIdPrefix: {
type: String,
default: "customer-search-select",
},
placeholder: {
type: String,
default: "",
},
});
const emit = defineEmits(["update:modelValue", "selected", "cleared"]);
const { t } = useI18n();
const searchQuery = ref("");
const showResults = ref(false);
const selectedResultIndex = ref(-1);
const getCustomerNumber = (customer) => {
const parsedValue = Number.parseInt(
String(customer?.customerNumber ?? customer?.customer_number ?? customer?.customer_id ?? customer?.id ?? ""),
10
);
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
};
const getCustomerName = (customer) => {
return String(customer?.name ?? customer?.customer_name ?? customer?.customerName ?? "").trim();
};
const getCustomerCity = (customer) => {
return String(customer?.city ?? customer?.address_city ?? "").trim();
};
const formatCustomer = (customer) => {
if (!customer) {
return "";
}
const name = getCustomerName(customer);
const customerNumber = getCustomerNumber(customer);
return [name, customerNumber ? `#${customerNumber}` : null].filter(Boolean).join(" - ");
};
const selectedCustomerNumber = computed(() => getCustomerNumber(props.modelValue));
const selectedCustomerName = computed(() => getCustomerName(props.modelValue));
const selectedCustomerCity = computed(() => getCustomerCity(props.modelValue));
const hasResults = computed(() => searchCustomerResults.value.length > 0);
const placeholderText = computed(() => props.placeholder || t("vehicles.add_modal.customer_placeholder"));
const resetSearchResults = () => {
searchCustomer(null);
selectedResultIndex.value = -1;
};
const setSelectedCustomer = (customer) => {
emit("update:modelValue", customer);
emit("selected", customer);
searchQuery.value = formatCustomer(customer);
showResults.value = false;
selectedResultIndex.value = -1;
};
const clearSelectedCustomer = async () => {
emit("update:modelValue", null);
emit("cleared");
searchQuery.value = "";
showResults.value = false;
resetSearchResults();
await nextTick();
document.getElementById(props.inputId)?.focus();
};
const handleSearchInput = () => {
if (props.modelValue) {
emit("update:modelValue", null);
}
const query = searchQuery.value.trim();
selectedResultIndex.value = -1;
if (!query) {
showResults.value = false;
resetSearchResults();
return;
}
showResults.value = true;
searchCustomer(query);
};
const handleFocus = () => {
if (searchQuery.value.trim() && hasResults.value) {
showResults.value = true;
}
};
const handleBlur = () => {
window.setTimeout(() => {
showResults.value = false;
selectedResultIndex.value = -1;
}, 150);
};
const handleKeydown = (event) => {
if (!showResults.value || !hasResults.value) {
return;
}
if (event.key === "ArrowDown") {
event.preventDefault();
selectedResultIndex.value = Math.min(selectedResultIndex.value + 1, searchCustomerResults.value.length - 1);
} else if (event.key === "ArrowUp") {
event.preventDefault();
selectedResultIndex.value = Math.max(selectedResultIndex.value - 1, 0);
} else if (event.key === "Enter") {
event.preventDefault();
const selectedCustomer = searchCustomerResults.value[selectedResultIndex.value] || searchCustomerResults.value[0];
if (selectedCustomer) {
setSelectedCustomer(selectedCustomer);
}
} else if (event.key === "Escape") {
showResults.value = false;
selectedResultIndex.value = -1;
}
};
watch(
() => props.modelValue,
(customer) => {
if (customer) {
searchQuery.value = formatCustomer(customer);
} else if (!document.activeElement || document.activeElement.id !== props.inputId) {
searchQuery.value = "";
}
},
{ immediate: true }
);
onBeforeUnmount(() => {
resetSearchResults();
});
</script>
<template>
<div class="customer-search-select">
<div class="field">
<label class="label" :for="inputId">{{ t("vehicles.add_modal.customer_label") }}</label>
<div class="control has-icons-left" :class="{ 'is-loading': isSearching }">
<input
:id="inputId"
v-model="searchQuery"
class="input"
type="text"
autocomplete="off"
:placeholder="placeholderText"
:disabled="disabled"
:aria-expanded="showResults && hasResults"
:data-testid="`${testIdPrefix}-input`"
@input="handleSearchInput"
@focus="handleFocus"
@blur="handleBlur"
@keydown="handleKeydown"
/>
<span class="icon is-left">
<i class="fas fa-search"></i>
</span>
</div>
</div>
<div
v-if="showResults && hasResults"
class="dropdown is-active customer-search-select__dropdown"
:data-testid="`${testIdPrefix}-results`"
>
<div class="dropdown-menu customer-search-select__menu" role="listbox">
<div class="dropdown-content">
<button
v-for="(customer, index) in searchCustomerResults"
:key="getCustomerNumber(customer) || index"
type="button"
class="dropdown-item customer-search-select__option"
:class="{ 'is-active': selectedResultIndex === index }"
:data-testid="`${testIdPrefix}-option-${index}`"
@mousedown.prevent="setSelectedCustomer(customer)"
>
<span class="customer-search-select__option-main">{{ getCustomerName(customer) }}</span>
<span class="customer-search-select__option-meta">
#{{ getCustomerNumber(customer) }}
<template v-if="getCustomerCity(customer)"> · {{ getCustomerCity(customer) }}</template>
</span>
</button>
</div>
</div>
</div>
<div v-if="modelValue" class="customer-search-select__selected" :data-testid="`${testIdPrefix}-selected`">
<div>
<p class="has-text-weight-semibold">{{ t("vehicles.add_modal.selected_customer") }}</p>
<p>{{ selectedCustomerName }}</p>
<p class="is-size-7 has-text-grey">
#{{ selectedCustomerNumber }}
<span v-if="selectedCustomerCity"> · {{ selectedCustomerCity }}</span>
</p>
</div>
<button
type="button"
class="button is-small is-light"
:disabled="disabled"
:data-testid="`${testIdPrefix}-clear`"
@click="clearSelectedCustomer"
>
{{ t("common.clear") }}
</button>
</div>
</div>
</template>
<style scoped>
.customer-search-select {
position: relative;
}
.customer-search-select__dropdown,
.customer-search-select__menu {
width: 100%;
}
.customer-search-select__dropdown {
left: 0;
position: absolute;
right: 0;
top: 4.75rem;
z-index: 40;
}
.customer-search-select__option {
align-items: flex-start;
background: transparent;
border: 0;
display: flex;
flex-direction: column;
text-align: left;
white-space: normal;
width: 100%;
}
.customer-search-select__option-main {
font-weight: 600;
}
.customer-search-select__option-meta {
color: #6b7280;
font-size: 0.8rem;
}
.customer-search-select__selected {
align-items: flex-start;
background: #f5f8fc;
border: 1px solid #d8e2ef;
border-radius: 6px;
display: flex;
gap: 1rem;
justify-content: space-between;
margin-bottom: 1rem;
padding: 0.85rem 1rem;
}
</style>
@@ -89,6 +89,66 @@ const SUBUSER_MANAGEMENT_PERMISSIONS = {
},
};
const CUSTOMER_FEATURE_PERMISSIONS = {
vehicles: {
list: {
customer: ["user", "list_own_vehicles"],
subuser: ["VEHICLES_LIST"],
},
add: {
customer: ["user", "add_vehicle"],
subuser: ["VEHICLES_ADD"],
},
edit: {
customer: ["edit_vehicle", "add_vehicle"],
subuser: ["VEHICLES_EDIT"],
},
delete: {
customer: ["delete_vehicle"],
subuser: ["VEHICLES_DELETE"],
},
},
bookings: {
list: {
customer: ["user", "list_own_bookings"],
subuser: ["BOOKINGS_LIST"],
},
add: {
customer: ["user"],
subuser: ["BOOKINGS_ADD"],
},
edit: {
customer: ["edit_own_bookings"],
subuser: ["BOOKINGS_EDIT"],
},
delete: {
customer: ["delete_own_bookings"],
subuser: ["BOOKINGS_DELETE"],
},
},
orders: {
list: {
customer: ["user", "list_own_orders"],
subuser: ["ORDERS_LIST"],
},
edit: {
customer: ["edit_own_orders"],
subuser: ["ORDERS_EDIT"],
},
},
selfserve: {
list: {
customer: ["user", "list_own_department_selfserve_vehicle_conditions"],
subuser: ["SELFSERVE_LIST"],
},
add: {
customer: ["add_own_department_selfserve_vehicle_conditions"],
subuser: ["SELFSERVE_ADD"],
},
},
subusers: SUBUSER_MANAGEMENT_PERMISSIONS,
};
const getStoredSessionSnapshot = () => {
if (typeof window === "undefined") {
return null;
@@ -858,6 +918,13 @@ export const SessionUser = {
}
return SessionUser.permissions.value.includes(permission) || SessionUser.permissions.value.includes("superuser");
},
hasAnyPermission: (permissions = []) => {
const permissionList = Array.isArray(permissions) ? permissions : [permissions].filter(Boolean);
if (permissionList.length === 0) {
return true;
}
return permissionList.some((permission) => SessionUser.hasPermission(permission));
},
/** Can access shortcuts */
canAccessAdmin: () => {
return SessionUser.hasPermission("admin") || SessionUser.hasPermission("superuser");
@@ -877,6 +944,15 @@ export const SessionUser = {
? SessionUser.hasPermission(permissions.subuser)
: SessionUser.hasPermission(permissions.customer);
},
canAccessCustomerFeature: (feature, action = "list") => {
const permissions = CUSTOMER_FEATURE_PERMISSIONS[feature]?.[action];
if (!permissions) {
return false;
}
const permissionList = SessionUser.isSubuser.value ? permissions.subuser : permissions.customer;
return SessionUser.hasAnyPermission(permissionList);
},
canAccessDepartment: (id = 0) => {
return SessionUser.hasPermission("department_access_" + parseInt(id)) || SessionUser.canAccessSuperUser();
},
@@ -5,6 +5,7 @@ import { ObjectsGlobal } from "@/components/session/token/SessionUser/Objects/Ob
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {ref} from "vue";
import i18n from '@/i18n';
import { todayLocalDateOnly } from "@/services/dateOnly.js";
const t = (key) => i18n.global.t(key);
@@ -241,7 +242,7 @@ export const Bookings = {
'/bookings',
'GET',
{
filters: `department:${department},status:pending,date-date_from:${new Date().toISOString().split('T')[0]},date-date_to:${new Date().toISOString().split('T')[0]}`,
filters: `department:${department},status:pending,date-date_from:${todayLocalDateOnly()},date-date_to:${todayLocalDateOnly()}`,
limit: 100,
page: 1
}
@@ -289,4 +290,4 @@ export const Bookings = {
);
}
};
</script>
</script>
@@ -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;
@@ -5,6 +5,7 @@ import { ObjectsGlobal } from "@/components/session/token/SessionUser/Objects/Ob
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {ref} from "vue";
import i18n from '@/i18n';
import { todayLocalDateOnly } from "@/services/dateOnly.js";
const t = (key) => i18n.global.t(key);
@@ -80,7 +81,7 @@ const t = (key) => i18n.global.t(key);
water_usage_morning: parseInt(water_usage_morning),
water_usage: parseInt(water_usage),
notes: notes ? notes : 'Daily report',
date: date ? date : new Date().toISOString().split('T')[0]
date: date ? date : todayLocalDateOnly()
}
);
},
@@ -1,6 +1,7 @@
<script>
import { ObjectsGlobal } from "@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { formatLocalDateOnly, todayLocalDateOnly } from "@/services/dateOnly.js";
/**
* The DepartmentTimeBookingsEntries object
@@ -230,10 +231,10 @@ export const DepartmentTimeBookingsEntries = {
if (!dateFrom) {
const lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7);
dateFrom = lastWeek.toISOString().split('T')[0]; // Format as YYYY-MM-DD
dateFrom = formatLocalDateOnly(lastWeek); // Format as YYYY-MM-DD
}
if (!dateTo) {
dateTo = new Date().toISOString().split('T')[0]; // Format as YYYY-MM-DD
dateTo = todayLocalDateOnly(); // Format as YYYY-MM-DD
}
console.log("Fetching public available times for department:", departmentId, "from", dateFrom, "to", dateTo);
// Make the request to fetch public available times for the department
@@ -279,4 +280,4 @@ export const DepartmentTimeBookingsEntries = {
);
}
};
</script>
</script>
@@ -108,6 +108,14 @@ export const Departments = {
required: false,
},
},
custom_pricing_only: {
label: t("objects.departments.columns.custom_pricing_only"),
type: "boolean",
sortable: true,
creation: {
required: false,
},
},
longitude: {
label: t("objects.departments.columns.longitude"),
type: "number",
@@ -164,6 +172,14 @@ export const Departments = {
archived: async (id, archived) => {
return ObjectsGlobal.set.column(Departments.meta.endpoint, id, "archived", ObjectsGlobal.parse.boolean(archived));
},
custom_pricing_only: async (id, custom_pricing_only) => {
return ObjectsGlobal.set.column(
Departments.meta.endpoint,
id,
"custom_pricing_only",
ObjectsGlobal.parse.boolean(custom_pricing_only)
);
},
longitude: async (id, longitude) => {
return ObjectsGlobal.set.column(Departments.meta.endpoint, id, "longitude", parseFloat(longitude));
},
@@ -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>
+210 -110
View File
@@ -10,6 +10,10 @@ import { getAttributes } from "@/components/shop/CustomerAttributes.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import { doesOrderContainWashCertificateProduct } from "@/components/displays/department/pos/utils/washCertificate.js";
import {
isProductCategoryRestrictedForCustomer,
isProductRestrictedForCustomer,
} from "@/features/customer/customerProductRules.js";
import Swal from "sweetalert2";
/**
@@ -52,6 +56,42 @@ export const nextStepDelay = ref(0); // 2 seconds delay
export const isCreatingOrder = ref(false);
export const desktopStep1PreflightHandler = ref(null);
let createOrderRequest = null;
const posStepSaveBarriers = new Set();
let latestOrderDetailsRequestId = 0;
let latestOrderMetadataMutationSequence = 0;
let activeOrderMetadataMutationCount = 0;
export const registerPosStepSaveBarrier = (handler) => {
if (typeof handler !== "function") {
return () => {};
}
posStepSaveBarriers.add(handler);
return () => {
posStepSaveBarriers.delete(handler);
};
};
export const unregisterPosStepSaveBarrier = (handler) => {
posStepSaveBarriers.delete(handler);
};
export const flushPosStepSaveBarriers = async () => {
const barriers = Array.from(posStepSaveBarriers);
if (barriers.length === 0) {
return true;
}
const results = await Promise.allSettled(barriers.map((handler) => handler()));
const failedResult = results.find((result) => result.status === "rejected");
if (failedResult) {
parseError(failedResult.reason, "stepError");
return false;
}
return true;
};
export const setDesktopStep1PreflightHandler = (handler = null) => {
desktopStep1PreflightHandler.value = typeof handler === "function" ? handler : null;
@@ -109,10 +149,16 @@ export const nextStep = async (options = { isMobile: false, orderCreation: true
}
// Set the delay to 2 seconds
setNextStepDelay(2);
if (!(await flushPosStepSaveBarriers())) {
nextStepDelay.value = 0;
return;
}
// Clear the errors
clearErrors();
// If the current step is 1, create the order
if (step.value === 1) {
await loadCustomerAttributes(customer_id.value);
if (normalizedOptions.orderCreation === false) {
step.value++;
return;
@@ -188,8 +234,22 @@ export const nextStep = async (options = { isMobile: false, orderCreation: true
step.value++;
};
export const previousStep = () => {
step.value--;
export const previousStep = async () => {
if (nextStepDelay.value > 0) {
return;
}
if (!(await flushPosStepSaveBarriers())) {
return;
}
step.value = Math.max(1, step.value - 1);
if (order_id.value) {
pushPosRouteState(`id=${order_id.value}&customer_id=${customer_id.value}&step=${step.value}`);
} else {
pushPosRouteState(`step=${step.value}`);
}
};
export const getCurrentStep = () => {
@@ -784,6 +844,129 @@ watch([reg_1, reg_2, reg_3], () => {
reg_3.value = normalizeRegistrationValue(reg_3.value);
});
const applyOrderMetadataFieldToPosState = (field, value) => {
if (field === "reference") {
reference.value = normalizeOrderMetadataString(value);
return reference.value;
}
if (field === "notes") {
order_notes.value = normalizeOrderMetadataString(value);
return order_notes.value;
}
if (field === "po") {
order_po.value = normalizeOrderMetadataString(value);
return order_po.value;
}
if (field === "safety_seal") {
order_safety_seal.value = normalizeOrderMetadataString(value);
return order_safety_seal.value;
}
if (field === "reg_1") {
reg_1.value = normalizeRegistrationValue(value);
return reg_1.value;
}
if (field === "reg_2") {
reg_2.value = normalizeRegistrationValue(value);
return reg_2.value;
}
if (field === "reg_3") {
reg_3.value = normalizeRegistrationValue(value);
return reg_3.value;
}
return normalizeOrderMetadataString(value);
};
export const applyOrderDetailsToPosState = (orderData, includes = {}) => {
if (!orderData || typeof orderData !== "object") {
return null;
}
const includedCustomer = includes?.customer ?? null;
const normalizedOrderCustomerNumber = resolveCustomerNumber(orderData?.customer_id);
const selectedCustomerBeforeHydration = getSelectedCustomerNumber();
department_id.value = orderData.department_id;
applyOrderMetadataFieldToPosState("reference", orderData.reference);
applyOrderMetadataFieldToPosState("notes", orderData.notes);
applyOrderMetadataFieldToPosState("po", orderData.po);
applyOrderMetadataFieldToPosState("safety_seal", orderData.safety_seal);
applyOrderMetadataFieldToPosState("reg_1", orderData.reg_1);
applyOrderMetadataFieldToPosState("reg_2", orderData.reg_2);
applyOrderMetadataFieldToPosState("reg_3", orderData.reg_3);
invoiceCollectionId.value = orderData.invoice_collection_id || null;
completed_at.value = orderData.completed_at || null;
if (includedCustomer && typeof includedCustomer === "object") {
const customerSource =
includedCustomer.economic_customer && typeof includedCustomer.economic_customer === "object"
? includedCustomer.economic_customer
: includedCustomer;
selectCustomer(
normalizeCustomerRecord(customerSource, {
...includedCustomer,
customerNumber: normalizedOrderCustomerNumber ?? resolveCustomerNumber(includedCustomer),
name: includedCustomer.name ?? includedCustomer.customer_name ?? orderData.customer_name ?? customer_name.value,
customer_name:
includedCustomer.customer_name ?? includedCustomer.name ?? orderData.customer_name ?? customer_name.value,
})
);
} else if (normalizedOrderCustomerNumber) {
customer_id.value = normalizedOrderCustomerNumber;
if (
selectedCustomerBeforeHydration !== normalizedOrderCustomerNumber ||
customer_name.value === "" ||
!customer_data.value
) {
void searchAndSelectCustomer(normalizedOrderCustomerNumber, { forceRefresh: true });
}
} else {
clearSelectedCustomerState();
}
return orderData;
};
export const saveOrderMetadataField = async (field, value, targetOrderId = order_id.value) => {
const normalizedOrderId = toPositiveInteger(targetOrderId);
const setter = SessionUser.objects.orders.set?.[field];
if (!normalizedOrderId || typeof setter !== "function") {
throw new Error(`Unable to save order field: ${field}`);
}
const mutationSequence = latestOrderMetadataMutationSequence + 1;
latestOrderMetadataMutationSequence = mutationSequence;
activeOrderMetadataMutationCount += 1;
const isRegistrationField = field === "reg_1" || field === "reg_2" || field === "reg_3";
const requestValue = isRegistrationField
? String(value ?? "").toUpperCase()
: normalizeOrderMetadataString(value);
const normalizedValue = isRegistrationField ? normalizeRegistrationValue(value) : requestValue;
try {
const response = await setter(normalizedOrderId, requestValue);
const updatedOrder = response?.data?.data;
if (updatedOrder && toPositiveInteger(updatedOrder.id) === normalizedOrderId) {
if (mutationSequence === latestOrderMetadataMutationSequence && activeOrderMetadataMutationCount === 1) {
applyOrderDetailsToPosState(updatedOrder, response?.data?.includes ?? {});
}
return applyOrderMetadataFieldToPosState(field, updatedOrder[field] ?? normalizedValue);
}
return applyOrderMetadataFieldToPosState(field, normalizedValue);
} finally {
activeOrderMetadataMutationCount = Math.max(0, activeOrderMetadataMutationCount - 1);
}
};
/** Get the order details by order id */
export const getOrderDetails = async (id = null) => {
const token = localStorage.getItem("token");
@@ -791,6 +974,9 @@ export const getOrderDetails = async (id = null) => {
if (!token || !targetOrderId) {
return null;
}
const requestId = ++latestOrderDetailsRequestId;
const metadataMutationSequenceAtRequest = latestOrderMetadataMutationSequence;
const hadActiveMetadataMutationAtRequest = activeOrderMetadataMutationCount > 0;
return axios
.get(API_URL + "/order?id=" + targetOrderId, {
headers: {
@@ -799,54 +985,21 @@ export const getOrderDetails = async (id = null) => {
})
.then((response) => {
const order_data = response.data.data;
const includedCustomer = response?.data?.includes?.customer ?? null;
const normalizedOrderCustomerNumber = resolveCustomerNumber(order_data?.customer_id);
const selectedCustomerBeforeHydration = getSelectedCustomerNumber();
// Set the order details
department_id.value = order_data.department_id;
reference.value = order_data.reference || "";
order_notes.value = order_data.notes || "";
order_po.value = order_data.po || "";
order_safety_seal.value = normalizeOrderMetadataString(order_data.safety_seal);
reg_1.value = order_data.reg_1 || "";
reg_2.value = order_data.reg_2 || "";
reg_3.value = order_data.reg_3 || "";
invoiceCollectionId.value = order_data.invoice_collection_id || null;
completed_at.value = order_data.completed_at || null;
if (!order_data) {
return null;
}
if (includedCustomer && typeof includedCustomer === "object") {
const customerSource =
includedCustomer.economic_customer && typeof includedCustomer.economic_customer === "object"
? includedCustomer.economic_customer
: includedCustomer;
const normalizedResponseOrderId = toPositiveInteger(order_data.id ?? targetOrderId);
const normalizedCurrentOrderId = toPositiveInteger(order_id.value);
const isStaleForDifferentOrder =
requestId !== latestOrderDetailsRequestId &&
normalizedCurrentOrderId &&
normalizedResponseOrderId &&
normalizedCurrentOrderId !== normalizedResponseOrderId;
const isStaleBeforeMetadataMutation = metadataMutationSequenceAtRequest !== latestOrderMetadataMutationSequence;
selectCustomer(
normalizeCustomerRecord(customerSource, {
...includedCustomer,
customerNumber: normalizedOrderCustomerNumber ?? resolveCustomerNumber(includedCustomer),
name:
includedCustomer.name ??
includedCustomer.customer_name ??
order_data.customer_name ??
customer_name.value,
customer_name:
includedCustomer.customer_name ??
includedCustomer.name ??
order_data.customer_name ??
customer_name.value,
})
);
} else if (normalizedOrderCustomerNumber) {
customer_id.value = normalizedOrderCustomerNumber;
if (
selectedCustomerBeforeHydration !== normalizedOrderCustomerNumber ||
customer_name.value === "" ||
!customer_data.value
) {
void searchAndSelectCustomer(normalizedOrderCustomerNumber, { forceRefresh: true });
}
} else {
clearSelectedCustomerState();
if (!isStaleForDifferentOrder && !isStaleBeforeMetadataMutation && !hadActiveMetadataMutationAtRequest) {
applyOrderDetailsToPosState(order_data, response?.data?.includes ?? {});
}
// Return the order data
return order_data;
@@ -1338,7 +1491,7 @@ const copyLastWashReferenceToEmptyCurrentOrder = async (sourceReference) => {
const normalizedOrderId = toPositiveInteger(order_id.value);
if (normalizedOrderId) {
await SessionUser.objects.orders.set.reference(normalizedOrderId, normalizedSourceReference);
await saveOrderMetadataField("reference", normalizedSourceReference, normalizedOrderId);
}
};
@@ -1466,24 +1619,7 @@ export const customerUsesPONumbers = () => {
/** Check if the product category is allowed */
export const productCategoryAllowed = (category) => {
// Check if the product category is the addons category
if (category === "addons") {
// Check if the customer has the attribute restrictAdditionalServices
return !hasAttribute("restrictAdditionalServices");
}
if (category === "spot_free") {
// Check if the customer has the attribute restrictSpotFree
return !hasAttribute("restrictSpotFree");
}
if (category === "interior_cleaning") {
// Check if the customer has the attribute restrictInteriorCleaning
return !hasAttribute("restrictInteriorCleaning");
}
if (category === "tank_cleaning") {
// Check if the customer has the attribute restrictTankCleaning
return !hasAttribute("restrictTankCleaning");
}
return true;
return !isProductCategoryRestrictedForCustomer(category, customer_attributes.value);
};
/** Check if the customer should be billed per order, or per billing period */
@@ -1529,43 +1665,12 @@ export const hasOnlyTankCleaning = () => {
/** Check if a product is restricted based on customer attributes */
export const isProductRestricted = (product) => {
if (!product) return false;
const productName = (product.name || "").toLowerCase();
// Check if the product has "Spot Free" in the name
if (!canBuySpotFree() && productName.includes("spot free")) {
return true;
}
// Check if the product has "Indvendig vask" in the name
if (!canBuyInteriorCleaning() && productName.includes("indvendig vask")) {
return true;
}
// Check if the product category is 5 (Tank Cleaning)
if (product.category === 5) {
if (!canBuyTankCleaning()) {
return true;
}
if (!hasOnlyTankCleaning()) {
return true;
}
}
return false;
return isProductRestrictedForCustomer(product, customer_attributes.value);
};
/** Check if an addon is restricted based on customer attributes */
export const isAddonRestricted = (addon) => {
if (!addon) return false;
const addonName = (addon.name || "").toLowerCase();
// Check if the addon has "Spot Free" in the name
if (!canBuySpotFree() && addonName.includes("spot free")) {
return true;
}
// Check if the addon has "Indvendig vask" in the name
if (!canBuyInteriorCleaning() && addonName.includes("indvendig vask")) {
return true;
}
return false;
return isProductRestrictedForCustomer(addon, customer_attributes.value);
};
export function loadOrderDetails(onAfterSuccess = null, options = {}) {
@@ -2208,32 +2313,27 @@ export const hydrateSelectedOrderBookingForDesktop = async () => {
const bookingReference = getOrderBookingReferenceValue(booking);
if (bookingReference !== "" && bookingReference !== reference.value) {
await SessionUser.objects.orders.set.reference(normalizedOrderId, bookingReference);
reference.value = bookingReference;
reference.value = await saveOrderMetadataField("reference", bookingReference, normalizedOrderId);
}
const bookingReg1 = getOrderBookingReg1Value(booking);
if (bookingReg1 !== "" && bookingReg1 !== reg_1.value) {
await SessionUser.objects.orders.set.reg_1(normalizedOrderId, bookingReg1);
reg_1.value = bookingReg1;
reg_1.value = await saveOrderMetadataField("reg_1", bookingReg1, normalizedOrderId);
}
const bookingReg2 = getOrderBookingReg2Value(booking);
if (bookingReg2 !== reg_2.value) {
await SessionUser.objects.orders.set.reg_2(normalizedOrderId, bookingReg2);
reg_2.value = bookingReg2;
reg_2.value = await saveOrderMetadataField("reg_2", bookingReg2, normalizedOrderId);
}
const bookingNotes = getOrderBookingNotesValue(booking);
if (bookingNotes !== "" && bookingNotes !== order_notes.value) {
await SessionUser.objects.orders.set.notes(normalizedOrderId, bookingNotes);
order_notes.value = bookingNotes;
order_notes.value = await saveOrderMetadataField("notes", bookingNotes, normalizedOrderId);
}
const bookingPo = String(booking?.po ?? "").trim();
if (bookingPo !== "" && isBlankPosMetadataValue(order_po.value)) {
await SessionUser.objects.orders.set.po(normalizedOrderId, bookingPo);
order_po.value = bookingPo;
order_po.value = await saveOrderMetadataField("po", bookingPo, normalizedOrderId);
}
const currentOrderItems = await loadOrderItems();
@@ -6,9 +6,8 @@ import CollectedInvoiceQueueMonitor from "@/components/viewport/page/headers/men
import SubuserGrantSelector from "@/components/session/subuser/SubuserGrantSelector.vue";
import { IS_DEV } from '@/config.js';
import SessionUser from "@/components/session/token/SessionUser.vue";
import { useRoute } from "vue-router";
import { computed } from "vue";
const route = useRoute();
import HeaderAccessShortcuts from "@/components/viewport/page/headers/HeaderAccessShortcuts.vue";
const isAuthenticatedSessionReady = computed(() => SessionUser.isInitiated() && SessionUser.authenticated.value);
</script>
@@ -41,20 +40,7 @@ const isAuthenticatedSessionReady = computed(() => SessionUser.isInitiated() &&
<!-- User account name and avatar (Desktop) -->
<div class="navbar-item mr-1">
<div class="buttons">
<!-- Back-Office button -->
<router-link class="button is-white" to="/superuser" v-if="SessionUser.canAccessSuperUser() && !route.path.includes('/superuser')">
<span class="icon">
<i class="fas fa-tools" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
</span>
<span>{{ $t('header.backoffice') }}</span>
</router-link>
<!-- Department button -->
<router-link class="button is-white" to="/admin" v-if="SessionUser.canAccessAdmin() && !route.path.includes('/admin')">
<span class="icon">
<i class="fas fa-building" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
</span>
<span>{{ $t('common.departments') }}</span>
</router-link>
<HeaderAccessShortcuts />
<!-- Return to own account if impersonating -->
<template v-if="SessionUser.hasSuperUserToken()">
<router-link class="button is-white" to="/auth/return">
@@ -63,11 +63,7 @@ const navigationItems = computed<NavigationMenuItem[]>(() => {
});
const hasPermission = (item: NavigationMenuItem) => {
if (!item.permissions?.length) {
return true;
}
return item.permissions.some((permission) => SessionUser.hasPermission(permission));
return SessionUser.hasAnyPermission(item.permissions ?? []);
};
const isItemVisible = (item: NavigationMenuItem) => !item.hidden && hasPermission(item);
@@ -0,0 +1,99 @@
<script setup>
import { computed } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { IS_DEV } from "@/config.js";
import SessionUser from "@/components/session/token/SessionUser.vue";
import {
getAdminDepartmentIdFromRoute,
getAdminDepartmentRoute,
getLimitedBackofficeDepartmentIdFromRoute,
getLimitedBackofficeDepartmentRoute,
} from "@/components/viewport/page/headers/headerShortcuts.js";
const route = useRoute();
const { t } = useI18n({ useScope: "global" });
const adminDepartmentId = computed(() => getAdminDepartmentIdFromRoute(route));
const backofficeDepartmentId = computed(() => getLimitedBackofficeDepartmentIdFromRoute(route));
const canAccessDepartment = (departmentId) =>
Number.isInteger(departmentId) && departmentId > 0 && SessionUser.canAccessDepartment(departmentId);
const canShowSuperuserBackoffice = computed(
() => SessionUser.canAccessSuperUser() && !route.path.includes("/superuser")
);
const canShowLimitedBackoffice = computed(() => {
const departmentId = adminDepartmentId.value;
return (
canAccessDepartment(departmentId) &&
SessionUser.hasPermission("limited_backoffice_access") &&
!SessionUser.canAccessSuperUser()
);
});
const limitedBackofficeTarget = computed(() => getLimitedBackofficeDepartmentRoute(adminDepartmentId.value));
const adminShortcutTarget = computed(() =>
backofficeDepartmentId.value ? getAdminDepartmentRoute(backofficeDepartmentId.value) : "/admin"
);
const canShowAdminShortcut = computed(() => {
if (!SessionUser.canAccessAdmin() || route.path.includes("/admin")) {
return false;
}
const departmentId = backofficeDepartmentId.value;
return departmentId === null || canAccessDepartment(departmentId);
});
</script>
<template>
<router-link
v-if="canShowSuperuserBackoffice"
class="button is-white"
to="/superuser"
data-testid="superuser-backoffice-header-button"
>
<span class="icon">
<i class="fas fa-tools" :class="{ 'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV }"></i>
</span>
<span>{{ t("header.backoffice") }}</span>
</router-link>
<router-link
v-if="canShowLimitedBackoffice"
class="button is-white"
:to="limitedBackofficeTarget"
data-testid="limited-backoffice-header-button"
>
<span class="icon">
<i class="fas fa-tools" :class="{ 'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV }"></i>
</span>
<span>{{ t("templates.limited_backoffice.title") }}</span>
</router-link>
<router-link
v-if="canShowAdminShortcut"
class="button is-white"
:to="adminShortcutTarget"
data-testid="admin-header-button"
>
<span class="icon">
<i class="fas fa-building" :class="{ 'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV }"></i>
</span>
<span>{{ t("common.departments") }}</span>
</router-link>
</template>
<style scoped>
.transform-color-black {
filter: invert(100%) grayscale(100%) brightness(0) contrast(100%);
}
.transform-color-red {
filter: invert(27%) sepia(96%) saturate(7493%) hue-rotate(357deg) brightness(103%) contrast(101%);
}
</style>
@@ -5,9 +5,8 @@ import NavigationMenuGlobalSearch from "@/components/viewport/page/headers/menu/
import CollectedInvoiceQueueMonitor from "@/components/viewport/page/headers/menu/CollectedInvoiceQueueMonitor.vue";
import { IS_DEV } from '@/config.js';
import SessionUser from "@/components/session/token/SessionUser.vue";
import { useRoute } from "vue-router";
import { computed } from "vue";
const route = useRoute();
import HeaderAccessShortcuts from "@/components/viewport/page/headers/HeaderAccessShortcuts.vue";
const isAuthenticatedSessionReady = computed(() => SessionUser.isInitiated() && SessionUser.authenticated.value);
</script>
@@ -41,20 +40,7 @@ const isAuthenticatedSessionReady = computed(() => SessionUser.isInitiated() &&
<!-- User account name and avatar (Desktop) -->
<div class="navbar-item mr-1">
<div class="buttons">
<!-- Back-Office button -->
<router-link class="button is-white" to="/superuser" v-if="SessionUser.canAccessSuperUser() && !route.path.includes('/superuser')">
<span class="icon">
<i class="fas fa-tools" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
</span>
<span>{{ $t('header.backoffice') }}</span>
</router-link>
<!-- Department button -->
<router-link class="button is-white" to="/admin" v-if="SessionUser.canAccessAdmin() && !route.path.includes('/admin')">
<span class="icon">
<i class="fas fa-building" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
</span>
<span>{{ $t('common.departments') }}</span>
</router-link>
<HeaderAccessShortcuts />
<!-- Return to own account if impersonating -->
<template v-if="SessionUser.hasSuperUserToken()">
<router-link class="button is-white" to="/auth/return">
@@ -0,0 +1,31 @@
const parsePositiveInteger = (value) => {
const parsedValue = Number.parseInt(String(value ?? ""), 10);
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
};
const getPath = (route) => String(route?.path ?? "");
export const getAdminDepartmentIdFromRoute = (route) => {
const path = getPath(route);
const fromPath = parsePositiveInteger(path.match(/^\/admin\/(\d+)(?:\/|$)/)?.[1]);
if (fromPath !== null) {
return fromPath;
}
return path.startsWith("/admin") ? parsePositiveInteger(route?.params?.departmentId) : null;
};
export const getLimitedBackofficeDepartmentIdFromRoute = (route) => {
const path = getPath(route);
const fromPath = parsePositiveInteger(path.match(/^\/backoffice\/departments\/(\d+)(?:\/|$)/)?.[1]);
if (fromPath !== null) {
return fromPath;
}
return path.startsWith("/backoffice/departments") ? parsePositiveInteger(route?.params?.departmentId) : null;
};
export const getLimitedBackofficeDepartmentRoute = (departmentId) =>
`/backoffice/departments/${encodeURIComponent(String(departmentId))}/prices`;
export const getAdminDepartmentRoute = (departmentId) => `/admin/${encodeURIComponent(String(departmentId))}`;
@@ -43,23 +43,24 @@ const redirect = async (to: string) => {
}
};
// Permissions and visibility logic
const hasPermission = (permission: string) => {
return SessionUser.hasPermission(permission);
}
const hasDepartmentIdInPath = (path: string) => {
const departmentRegex = /\/admin\/\d+/;
return departmentRegex.test(path);
}
const hasItemPermissions = (item: any) => {
return SessionUser.hasAnyPermission(item?.permissions ?? []);
}
const hasPermissions = computed(() => {
if (!props.item.permissions || props.item.permissions.length === 0) {
return true; // No permissions required, item is visible
}
return props.item.permissions.some(hasPermission);
return hasItemPermissions(props.item);
});
const isVisible = () => {
if (props.item.hidden) {
return false;
}
switch (props.item.type) {
case 'category':
// Check if any child item is visible
@@ -96,10 +97,14 @@ const hasChildren = computed(() => {
});
const isChildVisible = (child: any) => {
if (child.type === 'department') {
return hasPermissions.value && hasDepartmentIdInPath(router.currentRoute.value.path);
if (child.hidden) {
return false;
}
return hasPermissions.value;
if (child.type === 'department') {
return hasItemPermissions(child) && hasDepartmentIdInPath(router.currentRoute.value.path);
}
return hasItemPermissions(child);
}
const hasVisibleChildren = computed(() => {
+76 -32
View File
@@ -23,10 +23,12 @@ export const useOrderMetadataAutosave = ({
const draft = ref(normalizeValue(resolveSourceValue(source)));
const lastSavedValue = ref(normalizeValue(resolveSourceValue(source)));
const isSaving = ref(false);
const lastError = ref(null);
const isDirty = computed(() => normalizeValue(draft.value) !== normalizeValue(lastSavedValue.value));
let timerId = null;
let saveAgainAfterCurrentRequest = false;
let activeSavePromise = null;
let nextSequence = 0;
let lastAppliedSequence = 0;
@@ -66,45 +68,57 @@ export const useOrderMetadataAutosave = ({
const sequence = ++nextSequence;
let saveCompleted = false;
isSaving.value = true;
lastError.value = null;
try {
const result = await saveValue(requestedValue);
const savedValue = normalizeValue(result ?? requestedValue);
const run = (async () => {
try {
const result = await saveValue(requestedValue);
const savedValue = normalizeValue(result ?? requestedValue);
if (sequence >= lastAppliedSequence) {
lastAppliedSequence = sequence;
lastSavedValue.value = savedValue;
if (sequence >= lastAppliedSequence) {
lastAppliedSequence = sequence;
lastSavedValue.value = savedValue;
if (normalizeValue(draft.value) === requestedValue) {
draft.value = savedValue;
if (normalizeValue(draft.value) === requestedValue) {
draft.value = savedValue;
}
}
if (typeof onSaved === "function") {
await onSaved(savedValue, requestedValue);
}
saveCompleted = true;
return true;
} catch (error) {
lastError.value = error;
if (typeof onError === "function") {
onError(error);
} else {
console.error(error);
}
return false;
} finally {
isSaving.value = false;
const shouldSaveAgain = saveAgainAfterCurrentRequest;
saveAgainAfterCurrentRequest = false;
if (saveCompleted && (shouldSaveAgain || isDirty.value) && isDirty.value) {
void persist();
}
}
})();
if (typeof onSaved === "function") {
await onSaved(savedValue, requestedValue);
const trackedSavePromise = run.finally(() => {
if (activeSavePromise === trackedSavePromise) {
activeSavePromise = null;
}
saveCompleted = true;
return true;
} catch (error) {
if (typeof onError === "function") {
onError(error);
} else {
console.error(error);
}
return false;
} finally {
isSaving.value = false;
const shouldSaveAgain = saveAgainAfterCurrentRequest;
saveAgainAfterCurrentRequest = false;
if (saveCompleted && (shouldSaveAgain || isDirty.value) && isDirty.value) {
void persist();
}
}
});
activeSavePromise = trackedSavePromise;
return trackedSavePromise;
};
const scheduleSave = () => {
@@ -123,6 +137,34 @@ export const useOrderMetadataAutosave = ({
return persist();
};
const flushAndWait = async () => {
clearPendingSave();
while (isSaving.value && activeSavePromise) {
await activeSavePromise;
}
if (!isDirty.value) {
return false;
}
const didSave = await persist();
if (!didSave && isDirty.value) {
throw lastError.value || new Error("Unable to save order metadata");
}
while (isSaving.value && activeSavePromise) {
await activeSavePromise;
}
if (isDirty.value) {
return flushAndWait();
}
return didSave;
};
onBeforeUnmount(() => {
clearPendingSave();
@@ -139,10 +181,12 @@ export const useOrderMetadataAutosave = ({
return {
draft,
lastSavedValue,
lastError,
isDirty,
isSaving,
scheduleSave,
flush,
flushAndWait,
clearPendingSave,
syncFromSource,
};
+9 -2
View File
@@ -2,6 +2,7 @@ import { computed, onBeforeUnmount, reactive, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { searchAndSelectCustomer } from "@/components/shop/POSDepartmentProcess.vue";
import SessionUser from "@/components/session/token/SessionUser.vue";
import { economicEanPayloadValue } from "@/services/economicCustomerIdentifiers.js";
import {
applyPosAddCustomerSearchResult,
createPosAddCustomerFields,
@@ -152,14 +153,20 @@ export function usePosAddCustomerForm(options = {}) {
errorMessage.value = null;
try {
await SessionUser.request("/auth/register/cvr", "POST", {
const payload = {
cvr: form.cvr,
companyPhone: Number.parseInt(form.companyPhone, 10),
invoiceEmail: form.invoiceEmail,
contactEmail: form.contactEmail,
contactPhone: Number.parseInt(form.contactPhone, 10),
searchResult: searchResult.value,
});
};
const ean = economicEanPayloadValue(form.ean);
if (ean !== undefined) {
payload.ean = ean;
}
await SessionUser.request("/auth/register/cvr", "POST", payload);
const selectedCustomer = await searchAndSelectCustomer(form.companyPhone);
const createdCustomerNumber = Number.parseInt(form.companyPhone, 10);
@@ -0,0 +1,95 @@
const TANK_CLEANING_TERMS = ["tank cleaning", "tankcleaning", "tankrens"];
const normalizeAttributeName = (attribute) => {
if (typeof attribute === "string") {
return attribute;
}
return String(attribute?.attribute ?? "");
};
export const hasCustomerAttribute = (attributes, attributeName) => {
if (!Array.isArray(attributes)) {
return false;
}
return attributes.some((attribute) => normalizeAttributeName(attribute) === attributeName);
};
const normalizedText = (value) => String(value ?? "").trim().toLowerCase();
const textContainsAny = (value, terms) => {
const text = normalizedText(value);
return terms.some((term) => term !== "" && text.includes(term));
};
export const isTankCleaningCategory = (category) => {
if (Number(category) === 5) {
return true;
}
return textContainsAny(category, ["tank_cleaning", ...TANK_CLEANING_TERMS]);
};
export const isTankCleaningProduct = (product) => {
if (!product) {
return false;
}
if (isTankCleaningCategory(product.category ?? product.product_category)) {
return true;
}
return textContainsAny(
[
product.name,
product.product_name,
product.category_name,
product.categoryName,
].join(" "),
TANK_CLEANING_TERMS
);
};
export const isProductCategoryRestrictedForCustomer = (category, attributes = []) => {
if (category === "addons" && hasCustomerAttribute(attributes, "restrictAdditionalServices")) {
return true;
}
if (category === "spot_free" && hasCustomerAttribute(attributes, "restrictSpotFree")) {
return true;
}
if (category === "interior_cleaning" && hasCustomerAttribute(attributes, "restrictInteriorCleaning")) {
return true;
}
if (isTankCleaningCategory(category) && hasCustomerAttribute(attributes, "restrictTankCleaning")) {
return true;
}
if (hasCustomerAttribute(attributes, "onlyTankCleaning") && !isTankCleaningCategory(category)) {
return true;
}
return false;
};
export const isProductRestrictedForCustomer = (product, attributes = []) => {
if (!product) {
return false;
}
const productName = normalizedText(product.name ?? product.product_name);
if (hasCustomerAttribute(attributes, "restrictSpotFree") && productName.includes("spot free")) {
return true;
}
if (hasCustomerAttribute(attributes, "restrictInteriorCleaning") && productName.includes("indvendig vask")) {
return true;
}
if (isTankCleaningProduct(product) && hasCustomerAttribute(attributes, "restrictTankCleaning")) {
return true;
}
if (hasCustomerAttribute(attributes, "onlyTankCleaning") && !isTankCleaningProduct(product)) {
return true;
}
return false;
};
+417 -7
View File
@@ -1176,6 +1176,9 @@
"title": "@:{'templates.generated.compat.limited_backoffice.prices.title'}",
"save": "@:{'templates.generated.compat.limited_backoffice.prices.save'}",
"saved": "@:{'templates.generated.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'templates.generated.compat.limited_backoffice.prices.product'}",
"price": "@:{'templates.generated.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'templates.generated.compat.limited_backoffice.prices.price_required'}",
@@ -1190,6 +1193,15 @@
"edit_title": "@:{'templates.generated.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'templates.generated.compat.limited_backoffice.employees.name'}",
"email": "@:{'templates.generated.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'templates.generated.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'templates.generated.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'templates.generated.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'templates.generated.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_update_help'}",
@@ -1208,7 +1220,13 @@
"active": "@:{'templates.generated.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'templates.generated.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'templates.generated.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'templates.generated.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'templates.generated.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -1238,7 +1256,132 @@
"save_prices": "@:{'templates.generated.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'templates.generated.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -1568,6 +1711,7 @@
"company_phone": "Virksomhedstelefon",
"contact_email": "@.capitalize:{'words.generated.kontakt'}-@:{'words.generated.e'}-@:{'words.generated.mail'}",
"contact_phone": "Kontakttelefon",
"ean": "EAN",
"created_by": "@.capitalize:{'words.generated.oprettet'} @:{'words.generated.af'}",
"customer_conflict": {
"help_text": "@.capitalize:{'words.replication.article.host_mention'} @:{'words.generated.indtastede'} @:{'words.generated.nummerplade'} @:{'words.generated.tilhører'} @:{'words.replication.host_definite_suffix'} @:{'words.generated.anden'} @:{'words.generated.gemt'} @:{'words.generated.kunde'}. @.capitalize:{'words.generated.vælg'} @:{'words.generated.om'} @:{'words.replication.article.host_mention'} @:{'words.generated.valgte'} @:{'words.generated.kunde'} @:{'words.generated.skal'} @:{'words.generated.beholdes'}, @:{'words.generated.eller'} @:{'words.generated.om'} @:{'words.generated.der'} @:{'words.generated.skal'} @:{'words.generated.skiftes'} @:{'words.generated.til'} @:{'words.generated.køretøjets'} @:{'words.generated.kunde'}.",
@@ -3116,6 +3260,7 @@
"benefit_portal": "@:{'words.generated.kundeportal'}",
"benefit_portal_desc": "@:{'words.generated.benyt'} kundeportalen @:{'words.generated.til'} @:{'words.generated.at'} @:{'words.generated.tilføje'} @:{'words.generated.køretøjer'}, @:{'words.generated.booke'} @:{'words.generated.vaske'}, downloade @:{'words.generated.fakturaer'}, @:{'words.generated.vaskecertifikater'} @:{'words.generated.og'} @:{'words.generated.information'} @:{'words.generated.for'} @:{'words.generated.historiske'} @:{'words.generated.vaske'}",
"benefit_wash_desc": "@:{'words.generated.benyt'} @:{'words.generated.vores'} @:{'words.generated.nationale'} @:{'words.generated.netværk'} @:{'words.generated.af'} @:{'words.generated.vaskehaller'} @:{'words.generated.til'} @:{'words.generated.konkurrencedygtige'} @:{'words.generated.priser'}",
"ean_label": "@:{'templates.generated.compat.admin.pos.ean'}",
"email_label": "@:{'words.generated.firma'} @:{'words.generated.email'}",
"intro": "@.capitalize:{'words.generated.som'} @:{'words.generated.kunde'} @:{'words.generated.hos'} @:{'words.generated.truck'} @:{'words.generated.wash'} @:{'words.generated.far'} @:{'words.generated.du'} @:{'words.generated.adgang'} @:{'words.generated.til'}:",
"phone_label": "@:{'words.generated.firma'} @:{'words.generated.telefon'}",
@@ -3331,6 +3476,13 @@
"opening_hours": {
"closed": "Stengt"
},
"pricing": {
"custom_pricing_disabled": "Fallback aktiv",
"custom_pricing_enabled": "Kun egne priser",
"custom_pricing_missing_price": "Manglende afdelingspriser bliver 999999.",
"custom_pricing_only": "Ingen fallback-priser",
"effective_department_price": "Effektiv afdelingspris"
},
"search_placeholder": "@.capitalize:{'words.generated.søg'} @:{'words.generated.efter'} @:{'words.generated.afdelingsnavn'}",
"select_department": "@.capitalize:{'words.generated.vælg'} @:{'words.replication.host_definite_suffix'} @:{'words.generated.afdeling'}",
"subtitle": "@.capitalize:{'words.generated.administrer'} @:{'words.generated.afdelinger'}",
@@ -3354,17 +3506,17 @@
"error_report": {
"button": "Rapporter fejl",
"title": "Rapporter fejl",
"subtitle": "Send det aktuelle skærmbillede og de seneste fejldetaljer til support.",
"subtitle": "Send de seneste fejldetaljer til support. Et skærmbillede vedhæftes, når det er muligt.",
"before_error": "Hvad lavede du, før fejlen opstod?",
"expected": "Hvad forventede du, der ville ske?",
"actual": "Hvad skete der faktisk?",
"before_error_placeholder": "Beskriv handlingen, du var i gang med, for eksempel at åbne ordrer eller vælge en kunde.",
"expected_placeholder": "Beskriv det resultat, du forventede at se.",
"actual_placeholder": "Beskriv, hvad du så i stedet, inklusive eventuel fejltekst.",
"consent": "Jeg accepterer, at det aktuelle app-skærmbillede, de seneste request-fejl, Vue-fejl, browseroplysninger og mine svar indsamles til fejlfinding.",
"consent": "Jeg accepterer, at de seneste request-fejl, Vue-fejl, browseroplysninger, mine svar og et app-skærmbillede, når det er muligt, indsamles til fejlfinding.",
"submit": "Send rapport",
"submitted": "Fejlrapporten er sendt.",
"capture_failed": "Skærmbilledet kunne ikke oprettes. Prøv igen.",
"capture_failed": "Skærmbilledet kunne ikke oprettes. Rapporten sendes uden skærmbillede.",
"submit_failed": "Fejlrapporten kunne ikke sendes.",
"required": "Alle felter og accept af dataindsamling er påkrævet.",
"page_subtitle": "Rapporterede skærme, request-fejl og Vue-fejl",
@@ -3904,6 +4056,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": {
@@ -4161,6 +4322,7 @@
"departments": {
"columns": {
"archived": "Arkiveret",
"custom_pricing_only": "Ingen fallback-priser",
"dimension": "Dimension",
"economic_department": "@.upper:{'words.generated.e'}-@:{'words.generated.conomic'} @:{'words.generated.afdeling'}",
"latitude": "@:{'templates.generated.compat.departments.form.latitude'}",
@@ -4722,6 +4884,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": {
@@ -5717,6 +5970,20 @@
},
"vehicles": {
"add": "@:{'words.generated.tilføj'} @:{'words.generated.køretøj'}",
"add_modal": {
"customer_label": "Kunde",
"customer_placeholder": "Søg efter kundenavn eller kundenummer",
"error": "Køretøjet kunne ikke tilføjes.",
"no_customer_results": "Ingen kunder fundet",
"reference_placeholder": "Valgfri reference",
"registration_placeholder": "Registreringsnummer",
"selected_customer": "Valgt kunde",
"submit": "Tilføj køretøj",
"title": "Tilføj køretøj",
"type_load_error": "Køretøjstyper kunne ikke indlæses.",
"type_placeholder": "Vælg køretøjstype",
"validation_error": "Vælg kunde, registreringsnummer og køretøjstype."
},
"brand": "@:{'templates.generated.compat.admin.pos.make'}",
"color": "Farve",
"delete_vehicle": "@:{'templates.generated.compat.vehicles.delete'}",
@@ -5781,6 +6048,9 @@
"title": "@.capitalize:{'words.generated.afdelingspriser'}",
"save": "@:{'words.generated.gem'} @:{'words.generated.priser'}",
"saved": "Priserne @:{'words.generated.er'} @:{'words.generated.gemt'}.",
"autosave_saving": "Gemmer priser...",
"autosave_pending": "Prisændringer afventer.",
"autosave_saved": "Alle prisændringer er gemt.",
"product": "Produkt",
"price": "Afdelingspris",
"price_required": "@.capitalize:{'words.generated.alle'} @:{'words.generated.produkter'} @:{'words.generated.skal'} have @:{'words.replication.host_definite_suffix'} @:{'words.generated.ikke'}-negativ numerisk pris.",
@@ -5795,6 +6065,15 @@
"edit_title": "@:{'words.generated.rediger'} @:{'words.generated.medarbejder'}",
"name": "Navn",
"email": "Email",
"user_id": "Bruger-id",
"phone": "Telefon",
"phone_validation": "Telefonnummeret skal være 4-15 cifre.",
"country_codes": {
"denmark": "Danmark",
"sweden": "Sverige",
"norway": "Norge",
"finland": "Finland"
},
"password": "@.capitalize:{'words.generated.adgangskode'}",
"password_create_help": "@.capitalize:{'words.generated.pakrævet'} @:{'words.generated.for'} nye @:{'words.generated.medarbejdere'}. Brug @:{'words.generated.mindst'} 8 tegn.",
"password_update_help": "Lad feltet være tomt @:{'words.generated.for'} at beholde den nuværende @:{'words.generated.adgangskode'}.",
@@ -5813,7 +6092,13 @@
"active": "Aktiv",
"inactive": "Inaktiv",
"edit": "@:{'words.generated.rediger'}",
"deactivate": "Deaktiver"
"deactivate": "Deaktiver",
"login_link": "QR-login",
"login_link_title": "Medarbejderloginlink",
"login_link_qr_alt": "QR-kode-loginlink",
"login_link_url": "Loginlink",
"copy_login_link": "Kopiér link",
"login_link_copied": "Linket er kopieret."
},
"roles": {
"viewer": {
@@ -5843,7 +6128,132 @@
"save_prices": "@.capitalize:{'words.generated.afdelingspriser'} @:{'words.generated.kunne'} @:{'words.generated.ikke'} @:{'words.generated.gemmes'}.",
"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."
"deactivate_employee": "@.capitalize:{'words.generated.medarbejderen'} @:{'words.generated.kunne'} @:{'words.generated.ikke'} deaktiveres.",
"generate_employee_login_link": "Medarbejderens loginlink kunne ikke oprettes."
},
"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."
}
}
}
}
}
+395 -7
View File
@@ -1287,6 +1287,9 @@
"title": "@:{'templates.generated.compat.limited_backoffice.prices.title'}",
"save": "@:{'templates.generated.compat.limited_backoffice.prices.save'}",
"saved": "@:{'templates.generated.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'templates.generated.compat.limited_backoffice.prices.product'}",
"price": "@:{'templates.generated.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'templates.generated.compat.limited_backoffice.prices.price_required'}",
@@ -1301,6 +1304,15 @@
"edit_title": "@:{'templates.generated.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'templates.generated.compat.limited_backoffice.employees.name'}",
"email": "@:{'templates.generated.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'templates.generated.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'templates.generated.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'templates.generated.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'templates.generated.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_update_help'}",
@@ -1319,7 +1331,13 @@
"active": "@:{'templates.generated.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'templates.generated.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'templates.generated.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'templates.generated.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'templates.generated.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -1349,7 +1367,132 @@
"save_prices": "@:{'templates.generated.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'templates.generated.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -1679,6 +1822,7 @@
"company_phone": "Firmentelefon",
"contact_email": "@:{'words.generated.kontakt'}-@.upper:{'words.generated.e'}-@:{'words.generated.mail'}",
"contact_phone": "Kontakttelefonnummer",
"ean": "EAN",
"created_by": "@.capitalize:{'words.generated.erstellt'} @:{'words.generated.von'}",
"customer_conflict": {
"help_text": "@.capitalize:{'words.generated.dieses'} @:{'words.generated.kennzeichen'} @:{'words.generated.gehoert'} @:{'words.generated.zu'} @:{'words.generated.einem'} @:{'words.generated.anderen'} @:{'words.generated.gespeicherten'} @:{'words.generated.kunden'}. @:{'words.generated.waehlen'} @.capitalize:{'words.generated.sie'}, @:{'words.generated.ob'} @:{'words.generated.der'} @:{'words.generated.ausgewaehlte'} @.capitalize:{'words.generated.kunde'} @:{'words.generated.beibehalten'} @:{'words.generated.oder'} @:{'words.generated.zum'} @:{'words.generated.fahrzeugkunden'} @:{'words.generated.gewechselt'} @:{'words.generated.werden'} @:{'words.generated.soll'}.",
@@ -3227,6 +3371,7 @@
"benefit_portal": "@:{'words.generated.kundenportal'}",
"benefit_portal_desc": "@.capitalize:{'words.generated.nutzen'} @.capitalize:{'words.generated.sie'} @:{'words.generated.das'} @:{'words.generated.kundenportal'}, @:{'words.generated.um'} @:{'words.generated.fahrzeuge'} hinzuzuf?@:{'words.generated.gen'}, @.upper:{'words.generated.w'}?@:{'words.generated.schen'} @:{'words.generated.zu'} @:{'words.generated.buchen'}, @:{'words.generated.rechnungen'} @:{'words.generated.und'} @:{'words.generated.waschzertifikate'} herunterzuladen @:{'words.generated.sowie'} @:{'words.generated.informationen'} @:{'words.generated.zu'} @:{'words.generated.fr'}?@:{'words.generated.heren'} @.upper:{'words.generated.w'}?@:{'words.generated.schen'} einzusehen",
"benefit_wash_desc": "@.capitalize:{'words.generated.nutzen'} @.capitalize:{'words.generated.sie'} @:{'words.generated.unser'} @:{'words.generated.nationales'} @:{'words.generated.netz'} @:{'words.generated.von'} @:{'words.generated.waschhallen'} @:{'words.generated.zu'} @:{'words.generated.wettbewerbsf'}?@:{'words.generated.higen'} @:{'words.generated.preisen'}",
"ean_label": "@:{'templates.generated.compat.admin.pos.ean'}",
"email_label": "Firmen-@.upper:{'words.generated.e'}-@:{'words.generated.mail'}",
"intro": "@.capitalize:{'words.generated.als'} @.capitalize:{'words.generated.kunde'} @:{'words.generated.bei'} @:{'words.generated.truck'} @.capitalize:{'words.generated.wash'} @:{'words.generated.erhalten'} @.capitalize:{'words.generated.sie'} @:{'words.generated.zugang'} @:{'words.generated.zu'}:",
"phone_label": "@:{'templates.generated.compat.admin.pos.company_phone'}",
@@ -3465,17 +3610,17 @@
"error_report": {
"button": "Fehler melden",
"title": "Fehler melden",
"subtitle": "Sende den aktuellen Bildschirm und die letzten Fehlerdetails an den Support.",
"subtitle": "Sende die letzten Fehlerdetails an den Support. Ein Screenshot wird angehängt, wenn möglich.",
"before_error": "Was haben Sie getan, bevor der Fehler auftrat?",
"expected": "Was hatten Sie erwartet?",
"actual": "Was ist tatsächlich passiert?",
"before_error_placeholder": "Beschreiben Sie die Aktion, zum Beispiel Bestellungen öffnen oder einen Kunden auswählen.",
"expected_placeholder": "Beschreiben Sie das erwartete Ergebnis.",
"actual_placeholder": "Beschreiben Sie, was stattdessen zu sehen war, inklusive Fehlermeldung.",
"consent": "Ich akzeptiere, dass der aktuelle App-Bildschirm, letzte Request-Fehler, Vue-Fehler, Browserdetails und meine Antworten zur Fehlersuche erfasst werden.",
"consent": "Ich akzeptiere, dass letzte Request-Fehler, Vue-Fehler, Browserdetails, meine Antworten und ein App-Screenshot, wenn möglich, zur Fehlersuche erfasst werden.",
"submit": "Bericht senden",
"submitted": "Fehlerbericht gesendet.",
"capture_failed": "Der Screenshot konnte nicht erstellt werden. Bitte versuchen Sie es erneut.",
"capture_failed": "Der Screenshot konnte nicht erstellt werden. Der Bericht wird ohne Screenshot gesendet.",
"submit_failed": "Der Fehlerbericht konnte nicht gesendet werden.",
"required": "Alle Felder und die Zustimmung zur Datenerfassung sind erforderlich.",
"page_subtitle": "Gemeldete Bildschirme, Request-Fehler und Vue-Fehler",
@@ -4015,6 +4160,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 +4987,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": {
@@ -5892,6 +6137,9 @@
"title": "@.capitalize:{'words.generated.abteilungspreise'}",
"save": "@:{'words.generated.preise'} @:{'words.generated.speichern'}",
"saved": "@:{'words.generated.preise'} @:{'words.generated.gespeichert'}.",
"autosave_saving": "Preise werden gespeichert...",
"autosave_pending": "Preisaenderungen ausstehend.",
"autosave_saved": "Alle Preisaenderungen gespeichert.",
"product": "@.capitalize:{'words.generated.produkt'}",
"price": "Abteilungspreis",
"price_required": "Jedes @.capitalize:{'words.generated.produkt'} @:{'words.generated.muss'} einen @:{'words.generated.nicht'}-negativen numerischen Preis @:{'words.generated.haben'}.",
@@ -5906,6 +6154,15 @@
"edit_title": "@:{'words.generated.mitarbeiter'} @:{'words.generated.bearbeiten'}",
"name": "Name",
"email": "E-Mail",
"user_id": "Benutzer-ID",
"phone": "Telefon",
"phone_validation": "Die Telefonnummer muss 4-15 Ziffern enthalten.",
"country_codes": {
"denmark": "Dänemark",
"sweden": "Schweden",
"norway": "Norwegen",
"finland": "Finnland"
},
"password": "@:{'words.generated.passwort'}",
"password_create_help": "@.capitalize:{'words.generated.fur'} neue @:{'words.generated.mitarbeiter'} @:{'words.generated.erforderlich'}. Verwenden @.capitalize:{'words.generated.sie'} @:{'words.generated.mindestens'} 8 Zeichen.",
"password_update_help": "Leer lassen, um das vorhandene @:{'words.generated.passwort'} beizubehalten.",
@@ -5924,7 +6181,13 @@
"active": "Aktiv",
"inactive": "Inaktiv",
"edit": "@.capitalize:{'words.generated.bearbeiten'}",
"deactivate": "Deaktivieren"
"deactivate": "Deaktivieren",
"login_link": "QR-Login",
"login_link_title": "Mitarbeiter-Loginlink",
"login_link_qr_alt": "QR-Code-Loginlink",
"login_link_url": "Loginlink",
"copy_login_link": "Link kopieren",
"login_link_copied": "Link kopiert."
},
"roles": {
"viewer": {
@@ -5954,7 +6217,132 @@
"save_prices": "@.capitalize:{'words.generated.abteilungspreise'} @:{'words.generated.konnten'} @:{'words.generated.nicht'} @:{'words.generated.gespeichert'} @:{'words.generated.werden'}.",
"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'}."
"deactivate_employee": "@:{'words.generated.mitarbeiter'} @:{'words.generated.konnte'} @:{'words.generated.nicht'} @:{'words.generated.deaktiviert'} @:{'words.generated.werden'}.",
"generate_employee_login_link": "Mitarbeiter-Loginlink konnte nicht erstellt 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."
}
}
}
}
}
+417 -7
View File
@@ -1008,6 +1008,9 @@
"title": "@:{'templates.generated.compat.limited_backoffice.prices.title'}",
"save": "@:{'templates.generated.compat.limited_backoffice.prices.save'}",
"saved": "@:{'templates.generated.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'templates.generated.compat.limited_backoffice.prices.product'}",
"price": "@:{'templates.generated.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'templates.generated.compat.limited_backoffice.prices.price_required'}",
@@ -1022,6 +1025,15 @@
"edit_title": "@:{'templates.generated.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'templates.generated.compat.limited_backoffice.employees.name'}",
"email": "@:{'templates.generated.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'templates.generated.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'templates.generated.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'templates.generated.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'templates.generated.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_update_help'}",
@@ -1040,7 +1052,13 @@
"active": "@:{'templates.generated.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'templates.generated.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'templates.generated.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'templates.generated.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'templates.generated.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -1070,7 +1088,132 @@
"save_prices": "@:{'templates.generated.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'templates.generated.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -1400,6 +1543,7 @@
"company_phone": "@.capitalize:{'words.generated.company'} @:{'words.generated.phone'}",
"contact_email": "@.capitalize:{'words.generated.contact'} @:{'words.generated.email'}",
"contact_phone": "@.capitalize:{'words.generated.contact'} @:{'words.generated.phone'}",
"ean": "EAN",
"created_by": "@.capitalize:{'words.generated.created'} @:{'words.generated.by'}",
"customer_conflict": {
"help_text": "@.capitalize:{'words.generated.this'} @:{'words.generated.typed'} @:{'words.generated.plate'} @:{'words.generated.belongs'} @:{'words.generated.to'} @:{'words.generated.another'} @:{'words.generated.saved'} @:{'words.generated.customer'}. @.capitalize:{'words.generated.choose'} @:{'words.generated.whether'} @:{'words.generated.to'} @:{'words.generated.keep'} @:{'words.replication.article.host_mention'} @:{'words.generated.selected'} @:{'words.generated.customer'} @:{'words.generated.or'} @:{'words.generated.switch'} @:{'words.generated.to'} @:{'words.replication.article.host_mention'} @:{'words.generated.vehicle'} @:{'words.generated.customer'}.",
@@ -2948,6 +3092,7 @@
"benefit_portal": "@.capitalize:{'words.generated.customer'} @.capitalize:{'words.generated.portal'}",
"benefit_portal_desc": "@.capitalize:{'words.generated.use'} @:{'words.replication.article.host_mention'} @:{'words.generated.customer'} @:{'words.generated.portal'} @:{'words.generated.to'} @:{'words.generated.add'} @:{'words.generated.vehicles'}, @:{'words.generated.book'} @:{'words.generated.washes'}, @:{'words.generated.download'} @:{'words.generated.invoices'}, @:{'words.generated.wash'} @:{'words.generated.certificates'} @:{'words.generated.and'} @:{'words.generated.information'} @:{'words.generated.for'} @:{'words.generated.historical'} @:{'words.generated.washes'}",
"benefit_wash_desc": "@.capitalize:{'words.generated.use'} @:{'words.generated.our'} @:{'words.generated.national'} @:{'words.generated.network'} @:{'words.generated.of'} @:{'words.generated.wash'} @:{'words.generated.halls'} @:{'words.generated.at'} @:{'words.generated.competitive'} @:{'words.generated.prices'}",
"ean_label": "@:{'templates.generated.compat.admin.pos.ean'}",
"email_label": "@.capitalize:{'words.generated.company'} @:{'words.generated.email'}",
"intro": "@.capitalize:{'words.generated.as'} @:{'words.generated.a'} @:{'words.generated.customer'} @:{'words.generated.at'} @.capitalize:{'words.generated.truck'} @.capitalize:{'words.generated.wash'} @:{'words.generated.you'} @:{'words.generated.get'} @:{'words.generated.access'} @:{'words.generated.to'}:",
"phone_label": "@:{'templates.generated.compat.admin.pos.company_phone'}",
@@ -3163,6 +3308,13 @@
"opening_hours": {
"closed": "@:{'templates.generated.compat.global.closed'}"
},
"pricing": {
"custom_pricing_disabled": "Fallback enabled",
"custom_pricing_enabled": "Custom only",
"custom_pricing_missing_price": "Missing department prices resolve to 999999.",
"custom_pricing_only": "No fallback pricing",
"effective_department_price": "Effective department price"
},
"search_placeholder": "@.capitalize:{'words.generated.search'} @:{'words.generated.by'} @:{'words.generated.department'} @:{'words.generated.name'}",
"select_department": "@.capitalize:{'words.generated.select'} @:{'words.generated.a'} @:{'words.generated.department'}",
"subtitle": "@.capitalize:{'words.generated.manage'} @:{'words.generated.departments'}",
@@ -3186,17 +3338,17 @@
"error_report": {
"button": "Report error",
"title": "Report error",
"subtitle": "Send the current screen and recent error details to support.",
"subtitle": "Send recent error details to support. A screenshot is attached when available.",
"before_error": "What were you doing before the error occurred?",
"expected": "What did you expect would happen?",
"actual": "What actually happened?",
"before_error_placeholder": "Describe the action you were taking, for example opening orders or selecting a customer.",
"expected_placeholder": "Describe the result you expected to see.",
"actual_placeholder": "Describe what you saw instead, including any error text.",
"consent": "I accept that the current app screen, recent request errors, Vue errors, browser details, and my answers are collected for troubleshooting.",
"consent": "I accept that recent request errors, Vue errors, browser details, my answers, and an app screenshot when available are collected for troubleshooting.",
"submit": "Submit report",
"submitted": "Error report submitted.",
"capture_failed": "The screen capture failed. Please try again.",
"capture_failed": "The screen capture failed. The report will be sent without a screenshot.",
"submit_failed": "The error report could not be submitted.",
"required": "All fields and data collection acceptance are required.",
"page_subtitle": "Reported screens, request failures, and Vue errors",
@@ -3736,6 +3888,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": {
@@ -3993,6 +4154,7 @@
"departments": {
"columns": {
"archived": "@:{'words.generated.archived'}",
"custom_pricing_only": "No fallback pricing",
"dimension": "Dimension",
"economic_department": "@.upper:{'words.generated.e'}-@:{'words.generated.conomic'} @.capitalize:{'words.generated.department'}",
"latitude": "@:{'templates.generated.compat.departments.form.latitude'}",
@@ -4554,6 +4716,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": {
@@ -5549,6 +5802,20 @@
},
"vehicles": {
"add": "@.capitalize:{'words.generated.add'} @:{'words.generated.vehicle'}",
"add_modal": {
"customer_label": "Customer",
"customer_placeholder": "Search by customer name or number",
"error": "Unable to add vehicle.",
"no_customer_results": "No customers found",
"reference_placeholder": "Optional reference",
"registration_placeholder": "Registration number",
"selected_customer": "Selected customer",
"submit": "Add vehicle",
"title": "Add vehicle",
"type_load_error": "Unable to load vehicle types.",
"type_placeholder": "Select vehicle type",
"validation_error": "Select a customer, registration number, and vehicle type."
},
"brand": "Brand",
"color": "Color",
"delete_vehicle": "@:{'templates.generated.compat.vehicles.delete'}",
@@ -5613,6 +5880,9 @@
"title": "@.capitalize:{'words.generated.department'} @:{'words.generated.prices'}",
"save": "@.capitalize:{'words.generated.save'} @:{'words.generated.prices'}",
"saved": "@.capitalize:{'words.generated.prices'} @:{'words.generated.saved'}.",
"autosave_saving": "Saving prices...",
"autosave_pending": "Price changes pending.",
"autosave_saved": "All price changes saved.",
"product": "@.capitalize:{'words.generated.product'}",
"price": "@.capitalize:{'words.generated.department'} @:{'words.generated.price'}",
"price_required": "Every @:{'words.generated.product'} @:{'words.generated.must'} @:{'words.generated.have'} @:{'words.generated.a'} non-negative numeric @:{'words.generated.price'}.",
@@ -5627,6 +5897,15 @@
"edit_title": "@.capitalize:{'words.generated.edit'} @:{'words.generated.employee'}",
"name": "Name",
"email": "Email",
"user_id": "User ID",
"phone": "Phone",
"phone_validation": "Phone number must be 4-15 digits.",
"country_codes": {
"denmark": "Denmark",
"sweden": "Sweden",
"norway": "Norway",
"finland": "Finland"
},
"password": "@.capitalize:{'words.generated.password'}",
"password_create_help": "@.capitalize:{'words.generated.required'} @:{'words.generated.for'} new @:{'words.generated.employees'}. Use @:{'words.generated.at'} @:{'words.generated.least'} 8 characters.",
"password_update_help": "Leave blank @:{'words.generated.to'} keep @:{'words.replication.article.host_mention'} existing @:{'words.generated.password'}.",
@@ -5645,7 +5924,13 @@
"active": "Active",
"inactive": "@.capitalize:{'words.generated.inactive'}",
"edit": "@.capitalize:{'words.generated.edit'}",
"deactivate": "@.capitalize:{'words.generated.deactivate'}"
"deactivate": "@.capitalize:{'words.generated.deactivate'}",
"login_link": "QR login",
"login_link_title": "Employee login link",
"login_link_qr_alt": "QR code login link",
"login_link_url": "Login link",
"copy_login_link": "Copy link",
"login_link_copied": "Link copied."
},
"roles": {
"viewer": {
@@ -5675,7 +5960,132 @@
"save_prices": "@.capitalize:{'words.generated.could'} @:{'words.generated.not'} @:{'words.generated.save'} @:{'words.generated.department'} @:{'words.generated.prices'}.",
"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'}."
"deactivate_employee": "@.capitalize:{'words.generated.could'} @:{'words.generated.not'} @:{'words.generated.deactivate'} @:{'words.generated.employee'}.",
"generate_employee_login_link": "Could not create employee login link."
},
"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."
}
}
}
}
}
+200
View File
@@ -385,6 +385,7 @@
"delete": "@:{'templates.generated.compat.global.delete'}",
"details": "@:{'templates.generated.compat.admin.pos.details'}",
"download": "@:{'templates.generated.compat.guest.pwa.title'}",
"ean": "@:{'templates.generated.compat.admin.pos.ean'}",
"drafts_assignment": {
"button": "@:{'templates.generated.compat.admin.pos.drafts_assignment.button'}",
"create_invoice_collection": "@:{'templates.generated.compat.admin.pos.drafts_assignment.create_invoice_collection'}",
@@ -2061,6 +2062,7 @@
"benefit_wash": "@:{'templates.generated.compat.tables.bookings.wash'}",
"benefit_wash_desc": "@:{'templates.generated.compat.tables.bookings.wash_desc'}",
"cvr_label": "@:{'templates.generated.compat.customers.cvr'}",
"ean_label": "@:{'templates.generated.compat.customer_creation.customer.ean_label'}",
"email_label": "@:{'templates.generated.compat.customer_creation.customer.email_label'}",
"intro": "@:{'templates.generated.compat.customer_creation.customer.intro'}",
"phone_label": "@:{'templates.generated.compat.customer_creation.customer.phone_label'}",
@@ -2420,6 +2422,13 @@
"title": "@:{'templates.generated.compat.departments.tab.opening_hours'}"
},
"phone": "@:common.phone",
"pricing": {
"custom_pricing_disabled": "@:{'templates.generated.compat.departments.pricing.custom_pricing_disabled'}",
"custom_pricing_enabled": "@:{'templates.generated.compat.departments.pricing.custom_pricing_enabled'}",
"custom_pricing_missing_price": "@:{'templates.generated.compat.departments.pricing.custom_pricing_missing_price'}",
"custom_pricing_only": "@:{'templates.generated.compat.departments.pricing.custom_pricing_only'}",
"effective_department_price": "@:{'templates.generated.compat.departments.pricing.effective_department_price'}"
},
"products": {
"new_product": "@:products.new_product"
},
@@ -3149,6 +3158,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": {
@@ -3486,6 +3504,7 @@
"departments": {
"columns": {
"archived": "@:{'templates.generated.compat.objects.departments.columns.archived'}",
"custom_pricing_only": "@:{'templates.generated.compat.objects.departments.columns.custom_pricing_only'}",
"dimension": "@:{'templates.generated.compat.objects.departments.columns.dimension'}",
"economic_department": "@:{'templates.generated.compat.objects.departments.columns.economic_department'}",
"latitude": "@:{'templates.generated.compat.objects.departments.columns.latitude'}",
@@ -4255,6 +4274,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'}"
},
@@ -4468,6 +4578,82 @@
"subtitle": "@:{'templates.generated.compat.superuser_dashboard.departments.subtitle'}",
"title": "@:common.departments"
},
"department_navigation": {
"overview": "Overview",
"modules": "Modules",
"branding": "Profile & Branding",
"gateways": "Gateways",
"stripe": "Stripe",
"pricing": "Pricing",
"categories": "Categories"
},
"department_overview": {
"title": "Department overview",
"subtitle": "Operational overview for the selected department",
"loading": "Loading department overview",
"date_from": "From",
"date_to": "To",
"range_label": "{from} to {to}",
"empty_value": "-",
"out_of": "of {total}",
"presets": {
"today": "Today",
"last_seven_days": "Last 7 days"
},
"metrics": {
"bookings": "Bookings",
"complaints": "Complaints",
"night_washes": "Night washes",
"overtime": "Overtime",
"products_sold": "Products sold",
"revenue": "Revenue",
"transactions": "Transactions",
"washes": "Washes",
"water_usage": "Water"
},
"units": {
"hours": "h",
"liters": "L"
},
"products": {
"title": "Product mix",
"subtitle": "Tracked wash products in the selected period",
"empty": "No product activity for the selected period"
},
"profile": {
"title": "Department profile",
"no_description": "No department description",
"department_id": "Department ID",
"economic_department_id": "Economic department",
"branding": "Branding",
"created_at": "Created",
"updated_at": "Updated"
},
"hardware": {
"title": "Hardware readiness",
"subtitle": "Gateway-backed lane and relay state",
"gateways": "Gateways online",
"lanes": "Lanes",
"gates": "Gates",
"relays": "Relays",
"scanners": "Scanners",
"issues": "Issues"
},
"quick_links": {
"title": "Department tools",
"subtitle": "Open the focused setup areas for this department",
"modules": "Modules",
"branding": "Branding",
"gateways": "Gateways",
"stripe": "Stripe",
"pricing": "Pricing",
"categories": "Categories"
},
"errors": {
"invalid_department": "A valid department is required",
"load": "Unable to load the department overview"
}
},
"employees": {
"edit_employee_title": "@:employees.edit_employee",
"new_employee_subtitle": "@:{'templates.generated.compat.superuser_dashboard.employees.new_employee_subtitle'}",
@@ -5743,6 +5929,20 @@
"vehicles": {
"actions": "@:common.actions",
"add": "@:{'templates.generated.compat.vehicles.add'}",
"add_modal": {
"customer_label": "@:{'templates.generated.compat.vehicles.add_modal.customer_label'}",
"customer_placeholder": "@:{'templates.generated.compat.vehicles.add_modal.customer_placeholder'}",
"error": "@:{'templates.generated.compat.vehicles.add_modal.error'}",
"no_customer_results": "@:{'templates.generated.compat.vehicles.add_modal.no_customer_results'}",
"reference_placeholder": "@:{'templates.generated.compat.vehicles.add_modal.reference_placeholder'}",
"registration_placeholder": "@:{'templates.generated.compat.vehicles.add_modal.registration_placeholder'}",
"selected_customer": "@:{'templates.generated.compat.vehicles.add_modal.selected_customer'}",
"submit": "@:{'templates.generated.compat.vehicles.add_modal.submit'}",
"title": "@:{'templates.generated.compat.vehicles.add_modal.title'}",
"type_load_error": "@:{'templates.generated.compat.vehicles.add_modal.type_load_error'}",
"type_placeholder": "@:{'templates.generated.compat.vehicles.add_modal.type_placeholder'}",
"validation_error": "@:{'templates.generated.compat.vehicles.add_modal.validation_error'}"
},
"brand": "@:{'templates.generated.compat.vehicles.brand'}",
"color": "@:{'templates.generated.compat.vehicles.color'}",
"created_at": "@:{'templates.generated.compat.global.generated'}",
+395 -7
View File
@@ -1290,6 +1290,9 @@
"title": "@:{'templates.generated.compat.limited_backoffice.prices.title'}",
"save": "@:{'templates.generated.compat.limited_backoffice.prices.save'}",
"saved": "@:{'templates.generated.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'templates.generated.compat.limited_backoffice.prices.product'}",
"price": "@:{'templates.generated.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'templates.generated.compat.limited_backoffice.prices.price_required'}",
@@ -1304,6 +1307,15 @@
"edit_title": "@:{'templates.generated.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'templates.generated.compat.limited_backoffice.employees.name'}",
"email": "@:{'templates.generated.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'templates.generated.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'templates.generated.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'templates.generated.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'templates.generated.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_update_help'}",
@@ -1322,7 +1334,13 @@
"active": "@:{'templates.generated.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'templates.generated.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'templates.generated.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'templates.generated.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'templates.generated.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -1352,7 +1370,132 @@
"save_prices": "@:{'templates.generated.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'templates.generated.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -1682,6 +1825,7 @@
"company_phone": "Firmatelefon",
"contact_email": "@.capitalize:{'words.generated.kontakt'} @:{'words.generated.e'}-@:{'words.generated.post'}",
"contact_phone": "@.capitalize:{'words.generated.kontakt'} @:{'words.generated.telefon'}",
"ean": "EAN",
"created_by": "Laget @:{'words.generated.av'}",
"customer_conflict": {
"help_text": "@.capitalize:{'words.generated.det'} @:{'words.generated.innskrevne'} @:{'words.generated.skiltet'} @:{'words.generated.tilhoerer'} @:{'words.replication.host_definite_suffix'} @:{'words.generated.annen'} @:{'words.generated.lagret'} @:{'words.generated.kunde'}. @.capitalize:{'words.generated.velg'} @:{'words.generated.om'} @:{'words.replication.article.host_mention'} @:{'words.generated.valgte'} @:{'words.generated.kunden'} @:{'words.generated.skal'} @:{'words.generated.beholdes'} @:{'words.generated.eller'} @:{'words.generated.om'} @:{'words.generated.det'} @:{'words.generated.skal'} @:{'words.generated.byttes'} @:{'words.generated.til'} @:{'words.generated.kjoretoykunden'}.",
@@ -3230,6 +3374,7 @@
"benefit_portal": "@:{'words.generated.kundeportal'}",
"benefit_portal_desc": "@.capitalize:{'words.generated.bruk'} kundeportalen @:{'words.generated.til'} @:{'words.generated.a'} @:{'words.generated.legge'} @:{'words.generated.til'} @:{'words.generated.kjøretøy'}, bokvaske, @:{'words.generated.laste'} @:{'words.generated.ned'} @:{'words.generated.fakturaer'}, @:{'words.generated.vaskesertifikater'} @:{'words.generated.og'} @:{'words.generated.informasjon'} @:{'words.generated.for'} @:{'words.generated.historiske'} @:{'words.generated.vask'}.",
"benefit_wash_desc": "@.capitalize:{'words.generated.bruk'} @:{'words.generated.vart'} @:{'words.generated.nasjonale'} @:{'words.generated.nettverk'} @:{'words.generated.av'} @:{'words.generated.vaskehaller'} @:{'words.generated.til'} @:{'words.generated.konkurransedyktige'} @:{'words.generated.priser'}",
"ean_label": "@:{'templates.generated.compat.admin.pos.ean'}",
"email_label": "Firmaets @:{'words.generated.e'}-@:{'words.generated.post'}",
"intro": "@.capitalize:{'words.generated.som'} @:{'words.generated.kunde'} @:{'words.generated.hos'} @:{'words.generated.truck'} @.capitalize:{'words.generated.wash'} @:{'words.generated.far'} @:{'words.generated.du'} @:{'words.generated.tilgang'} @:{'words.generated.til'}:",
"phone_label": "@:{'templates.generated.compat.admin.pos.company_phone'}",
@@ -3468,17 +3613,17 @@
"error_report": {
"button": "Rapporter feil",
"title": "Rapporter feil",
"subtitle": "Send gjeldende skjermbilde og de siste feildetaljene til support.",
"subtitle": "Send de siste feildetaljene til support. Et skjermbilde legges ved når det er mulig.",
"before_error": "Hva gjorde du før feilen oppstod?",
"expected": "Hva forventet du at skulle skje?",
"actual": "Hva skjedde faktisk?",
"before_error_placeholder": "Beskriv handlingen du utførte, for eksempel å åpne ordre eller velge en kunde.",
"expected_placeholder": "Beskriv resultatet du forventet å se.",
"actual_placeholder": "Beskriv hva du så i stedet, inkludert eventuell feiltekst.",
"consent": "Jeg godtar at gjeldende app-skjermbilde, siste request-feil, Vue-feil, nettleserdetaljer og svarene mine samles inn for feilsøking.",
"consent": "Jeg godtar at siste request-feil, Vue-feil, nettleserdetaljer, svarene mine og et app-skjermbilde når det er mulig, samles inn for feilsøking.",
"submit": "Send rapport",
"submitted": "Feilrapport sendt.",
"capture_failed": "Skjermbildet kunne ikke tas. Prøv igjen.",
"capture_failed": "Skjermbildet kunne ikke tas. Rapporten sendes uten skjermbilde.",
"submit_failed": "Feilrapporten kunne ikke sendes.",
"required": "Alle felter og godkjenning av datainnsamling er påkrevd.",
"page_subtitle": "Rapporterte skjermer, request-feil og Vue-feil",
@@ -4018,6 +4163,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 +4990,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": {
@@ -5895,6 +6140,9 @@
"title": "@.capitalize:{'words.generated.avdelingspriser'}",
"save": "@.capitalize:{'words.generated.lagre'} @:{'words.generated.priser'}",
"saved": "Prisene @:{'words.generated.er'} @:{'words.generated.lagret'}.",
"autosave_saving": "Lagrer priser...",
"autosave_pending": "Prisendringer venter.",
"autosave_saved": "Alle prisendringer er lagret.",
"product": "Produkt",
"price": "Avdelingspris",
"price_required": "@.capitalize:{'words.generated.alle'} @:{'words.generated.produkter'} @:{'words.generated.ma'} ha @:{'words.replication.host_definite_suffix'} @:{'words.generated.ikke'}-negativ numerisk pris.",
@@ -5909,6 +6157,15 @@
"edit_title": "@:{'words.generated.rediger'} @:{'words.generated.medarbeider'}",
"name": "Navn",
"email": "E-post",
"user_id": "Bruker-ID",
"phone": "Telefon",
"phone_validation": "Telefonnummeret må være 4-15 sifre.",
"country_codes": {
"denmark": "Danmark",
"sweden": "Sverige",
"norway": "Norge",
"finland": "Finland"
},
"password": "@.capitalize:{'words.generated.passord'}",
"password_create_help": "@.capitalize:{'words.generated.kreves'} @:{'words.generated.for'} nye @:{'words.generated.medarbeidere'}. Bruk @:{'words.generated.minst'} 8 tegn.",
"password_update_help": "La stå tomt @:{'words.generated.for'} å beholde nåværende @:{'words.generated.passord'}.",
@@ -5927,7 +6184,13 @@
"active": "Aktiv",
"inactive": "Inaktiv",
"edit": "@:{'words.generated.rediger'}",
"deactivate": "Deaktiver"
"deactivate": "Deaktiver",
"login_link": "QR-innlogging",
"login_link_title": "Innloggingslenke for medarbeider",
"login_link_qr_alt": "QR-kode for innloggingslenke",
"login_link_url": "Innloggingslenke",
"copy_login_link": "Kopier lenke",
"login_link_copied": "Lenken er kopiert."
},
"roles": {
"viewer": {
@@ -5957,7 +6220,132 @@
"save_prices": "@.capitalize:{'words.generated.kunne'} @:{'words.generated.ikke'} @:{'words.generated.lagre'} @:{'words.generated.avdelingspriser'}.",
"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'}."
"deactivate_employee": "@.capitalize:{'words.generated.kunne'} @:{'words.generated.ikke'} deaktivere @:{'words.generated.medarbeideren'}.",
"generate_employee_login_link": "Kunne ikke opprette innloggingslenke for 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."
}
}
}
}
}
+395 -7
View File
@@ -1340,6 +1340,9 @@
"title": "@:{'templates.generated.compat.limited_backoffice.prices.title'}",
"save": "@:{'templates.generated.compat.limited_backoffice.prices.save'}",
"saved": "@:{'templates.generated.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'templates.generated.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'templates.generated.compat.limited_backoffice.prices.product'}",
"price": "@:{'templates.generated.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'templates.generated.compat.limited_backoffice.prices.price_required'}",
@@ -1354,6 +1357,15 @@
"edit_title": "@:{'templates.generated.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'templates.generated.compat.limited_backoffice.employees.name'}",
"email": "@:{'templates.generated.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'templates.generated.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'templates.generated.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'templates.generated.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'templates.generated.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'templates.generated.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'templates.generated.compat.limited_backoffice.employees.password_update_help'}",
@@ -1372,7 +1384,13 @@
"active": "@:{'templates.generated.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'templates.generated.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'templates.generated.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'templates.generated.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'templates.generated.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'templates.generated.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'templates.generated.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -1402,7 +1420,132 @@
"save_prices": "@:{'templates.generated.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'templates.generated.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'templates.generated.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -1732,6 +1875,7 @@
"company_phone": "Färetagstelefon",
"contact_email": "@.capitalize:{'words.generated.kontakt'}-@:{'words.generated.e'}-@:{'words.generated.post'}",
"contact_phone": "Kontakttelefon",
"ean": "EAN",
"created_by": "@.capitalize:{'words.generated.skapad'} @:{'words.generated.av'}",
"customer_conflict": {
"help_text": "@.capitalize:{'words.generated.det'} @:{'words.generated.angivna'} @:{'words.generated.registreringsnumret'} @:{'words.generated.tillhor'} @:{'words.replication.host_definite_suffix'} @:{'words.generated.annan'} @:{'words.generated.sparad'} @:{'words.generated.kund'}. @:{'words.generated.valj_2'} @:{'words.generated.om'} @:{'words.replication.article.host_mention'} @:{'words.generated.valda'} @:{'words.generated.kunden'} @:{'words.generated.ska'} @:{'words.generated.behallas'} @:{'words.generated.eller'} @:{'words.generated.om'} @:{'words.generated.bytet'} @:{'words.generated.ska'} @:{'words.generated.goras'} @:{'words.generated.till'} @:{'words.generated.fordonskunden'}.",
@@ -3280,6 +3424,7 @@
"benefit_portal": "@:{'words.generated.kundportal'}",
"benefit_portal_desc": "@.capitalize:{'words.generated.anvand'} kundportalen @:{'words.generated.for'} @:{'words.generated.att'} @:{'words.generated.lagga'} @:{'words.generated.till'} @:{'words.generated.fordon'}, @:{'words.generated.boka'} @:{'words.generated.tvattar'}, @:{'words.generated.ladda'} @:{'words.generated.ner'} @:{'words.generated.fakturor'}, @:{'words.generated.tvattcertifikat'} @:{'words.generated.och'} @:{'words.generated.information'} @:{'words.generated.om'} @:{'words.generated.historiska'} @:{'words.generated.tvattar'}",
"benefit_wash_desc": "@.capitalize:{'words.generated.anvand'} @:{'words.generated.vart'} @:{'words.generated.nationella'} @:{'words.generated.natverk'} @:{'words.generated.av'} @:{'words.generated.tvatthallar'} @:{'words.generated.till'} @:{'words.generated.konkurrenskraftiga'} @:{'words.generated.priser'}",
"ean_label": "@:{'templates.generated.compat.admin.pos.ean'}",
"email_label": "Företagets @:{'words.generated.e'}-@:{'words.generated.post'}",
"intro": "@.capitalize:{'words.generated.som'} @:{'words.generated.kund'} @:{'words.generated.hos'} @:{'words.generated.truck'} @.capitalize:{'words.generated.wash'} @:{'words.generated.far'} @:{'words.generated.du'} @:{'words.generated.tillgang'} @:{'words.generated.till'}:",
"phone_label": "@:{'templates.generated.compat.admin.pos.company_phone'}",
@@ -3518,17 +3663,17 @@
"error_report": {
"button": "Rapportera fel",
"title": "Rapportera fel",
"subtitle": "Skicka aktuell skärm och de senaste feldetaljerna till support.",
"subtitle": "Skicka de senaste feldetaljerna till support. En skärmbild bifogas när det är möjligt.",
"before_error": "Vad gjorde du innan felet uppstod?",
"expected": "Vad förväntade du dig skulle hända?",
"actual": "Vad hände faktiskt?",
"before_error_placeholder": "Beskriv åtgärden du gjorde, till exempel att öppna ordrar eller välja en kund.",
"expected_placeholder": "Beskriv resultatet du förväntade dig att se.",
"actual_placeholder": "Beskriv vad du såg i stället, inklusive eventuell feltext.",
"consent": "Jag accepterar att aktuell app-skärm, senaste request-fel, Vue-fel, webbläsardetaljer och mina svar samlas in för felsökning.",
"consent": "Jag accepterar att senaste request-fel, Vue-fel, webbläsardetaljer, mina svar och en app-skärmbild när det är möjligt samlas in för felsökning.",
"submit": "Skicka rapport",
"submitted": "Felrapport skickad.",
"capture_failed": "Skärmbilden kunde inte tas. Försök igen.",
"capture_failed": "Skärmbilden kunde inte tas. Rapporten skickas utan skärmbild.",
"submit_failed": "Felrapporten kunde inte skickas.",
"required": "Alla fält och godkännande av datainsamling krävs.",
"page_subtitle": "Rapporterade skärmar, request-fel och Vue-fel",
@@ -4068,6 +4213,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 +5040,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": {
@@ -5945,6 +6190,9 @@
"title": "@.capitalize:{'words.generated.avdelningspriser'}",
"save": "@.capitalize:{'words.generated.spara'} @:{'words.generated.priser'}",
"saved": "Priserna @:{'words.generated.har'} @:{'words.generated.sparats'}.",
"autosave_saving": "Sparar priser...",
"autosave_pending": "Prisandringar vantar.",
"autosave_saved": "Alla prisandringar har sparats.",
"product": "Produkt",
"price": "Avdelningspris",
"price_required": "@.capitalize:{'words.generated.alla'} @:{'words.generated.produkter'} @:{'words.generated.maste'} ha ett icke-negativt numeriskt pris.",
@@ -5959,6 +6207,15 @@
"edit_title": "@:{'words.generated.redigera'} @:{'words.generated.medarbetare'}",
"name": "Namn",
"email": "E-post",
"user_id": "Användar-ID",
"phone": "Telefon",
"phone_validation": "Telefonnumret måste vara 4-15 siffror.",
"country_codes": {
"denmark": "Danmark",
"sweden": "Sverige",
"norway": "Norge",
"finland": "Finland"
},
"password": "@.capitalize:{'words.generated.losenord'}",
"password_create_help": "@.capitalize:{'words.generated.kravs'} @:{'words.generated.for'} nya @:{'words.generated.medarbetare'}. Använd @:{'words.generated.minst'} 8 tecken.",
"password_update_help": "Lämna tomt @:{'words.generated.for'} att behålla nuvarande @:{'words.generated.losenord'}.",
@@ -5977,7 +6234,13 @@
"active": "Aktiv",
"inactive": "Inaktiv",
"edit": "@:{'words.generated.redigera'}",
"deactivate": "@.capitalize:{'words.generated.inaktivera'}"
"deactivate": "@.capitalize:{'words.generated.inaktivera'}",
"login_link": "QR-inloggning",
"login_link_title": "Inloggningslänk för medarbetare",
"login_link_qr_alt": "QR-kod för inloggningslänk",
"login_link_url": "Inloggningslänk",
"copy_login_link": "Kopiera länk",
"login_link_copied": "Länken har kopierats."
},
"roles": {
"viewer": {
@@ -6007,7 +6270,132 @@
"save_prices": "@.capitalize:{'words.generated.kunde'} @:{'words.generated.inte'} @:{'words.generated.spara'} @:{'words.generated.avdelningspriser'}.",
"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'}."
"deactivate_employee": "@.capitalize:{'words.generated.kunde'} @:{'words.generated.inte'} @:{'words.generated.inaktivera'} @:{'words.generated.medarbetaren'}.",
"generate_employee_login_link": "Kunde inte skapa inloggningslänk för 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."
}
}
}
}
}
@@ -221,6 +221,7 @@
"company_phone": "Virksomhedstelefon",
"contact_email": "@.capitalize:{'terms.glossary.kontakt'}-@:{'terms.glossary.e'}-@:{'terms.glossary.mail'}",
"contact_phone": "Kontakttelefon",
"ean": "EAN",
"created_by": "@.capitalize:{'terms.glossary.oprettet'} @:{'terms.glossary.af'}",
"customer_conflict": {
"help_text": "@.capitalize:{'terms.replication.article.host_mention'} @:{'terms.glossary.indtastede'} @:{'terms.glossary.nummerplade'} @:{'terms.glossary.tilhører'} @:{'terms.replication.host_definite_suffix'} @:{'terms.glossary.anden'} @:{'terms.glossary.gemt'} @:{'terms.glossary.kunde'}. @.capitalize:{'terms.glossary.vælg'} @:{'terms.glossary.om'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.valgte'} @:{'terms.glossary.kunde'} @:{'terms.glossary.skal'} @:{'terms.glossary.beholdes'}, @:{'terms.glossary.eller'} @:{'terms.glossary.om'} @:{'terms.glossary.der'} @:{'terms.glossary.skal'} @:{'terms.glossary.skiftes'} @:{'terms.glossary.til'} @:{'terms.glossary.køretøjets'} @:{'terms.glossary.kunde'}.",
@@ -7,6 +7,7 @@
"benefit_portal": "@:{'terms.glossary.kundeportal'}",
"benefit_portal_desc": "@:{'terms.glossary.benyt'} kundeportalen @:{'terms.glossary.til'} @:{'terms.glossary.at'} @:{'terms.glossary.tilføje'} @:{'terms.glossary.køretøjer'}, @:{'terms.glossary.booke'} @:{'terms.glossary.vaske'}, downloade @:{'terms.glossary.fakturaer'}, @:{'terms.glossary.vaskecertifikater'} @:{'terms.glossary.og'} @:{'terms.glossary.information'} @:{'terms.glossary.for'} @:{'terms.glossary.historiske'} @:{'terms.glossary.vaske'}",
"benefit_wash_desc": "@:{'terms.glossary.benyt'} @:{'terms.glossary.vores'} @:{'terms.glossary.nationale'} @:{'terms.glossary.netværk'} @:{'terms.glossary.af'} @:{'terms.glossary.vaskehaller'} @:{'terms.glossary.til'} @:{'terms.glossary.konkurrencedygtige'} @:{'terms.glossary.priser'}",
"ean_label": "@:{'phrases.compat.admin.pos.ean'}",
"email_label": "@:{'terms.glossary.firma'} @:{'terms.glossary.email'}",
"intro": "@.capitalize:{'terms.glossary.som'} @:{'terms.glossary.kunde'} @:{'terms.glossary.hos'} @:{'terms.glossary.truck'} @:{'terms.glossary.wash'} @:{'terms.glossary.far'} @:{'terms.glossary.du'} @:{'terms.glossary.adgang'} @:{'terms.glossary.til'}:",
"phone_label": "@:{'terms.glossary.firma'} @:{'terms.glossary.telefon'}",
@@ -21,6 +21,13 @@
"opening_hours": {
"closed": "Stengt"
},
"pricing": {
"custom_pricing_disabled": "Fallback aktiv",
"custom_pricing_enabled": "Kun egne priser",
"custom_pricing_missing_price": "Manglende afdelingspriser bliver 999999.",
"custom_pricing_only": "Ingen fallback-priser",
"effective_department_price": "Effektiv afdelingspris"
},
"search_placeholder": "@.capitalize:{'terms.glossary.søg'} @:{'terms.glossary.efter'} @:{'terms.glossary.afdelingsnavn'}",
"select_department": "@.capitalize:{'terms.glossary.vælg'} @:{'terms.replication.host_definite_suffix'} @:{'terms.glossary.afdeling'}",
"subtitle": "@.capitalize:{'terms.glossary.administrer'} @:{'terms.glossary.afdelinger'}",
@@ -3,17 +3,17 @@
"error_report": {
"button": "Rapporter fejl",
"title": "Rapporter fejl",
"subtitle": "Send det aktuelle skærmbillede og de seneste fejldetaljer til support.",
"subtitle": "Send de seneste fejldetaljer til support. Et skærmbillede vedhæftes, når det er muligt.",
"before_error": "Hvad lavede du, før fejlen opstod?",
"expected": "Hvad forventede du, der ville ske?",
"actual": "Hvad skete der faktisk?",
"before_error_placeholder": "Beskriv handlingen, du var i gang med, for eksempel at åbne ordrer eller vælge en kunde.",
"expected_placeholder": "Beskriv det resultat, du forventede at se.",
"actual_placeholder": "Beskriv, hvad du så i stedet, inklusive eventuel fejltekst.",
"consent": "Jeg accepterer, at det aktuelle app-skærmbillede, de seneste request-fejl, Vue-fejl, browseroplysninger og mine svar indsamles til fejlfinding.",
"consent": "Jeg accepterer, at de seneste request-fejl, Vue-fejl, browseroplysninger, mine svar og et app-skærmbillede, når det er muligt, indsamles til fejlfinding.",
"submit": "Send rapport",
"submitted": "Fejlrapporten er sendt.",
"capture_failed": "Skærmbilledet kunne ikke oprettes. Prøv igen.",
"capture_failed": "Skærmbilledet kunne ikke oprettes. Rapporten sendes uden skærmbillede.",
"submit_failed": "Fejlrapporten kunne ikke sendes.",
"required": "Alle felter og accept af dataindsamling er påkrævet.",
"page_subtitle": "Rapporterede skærme, request-fejl og Vue-fejl",
@@ -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"
}
}
}
}
@@ -140,6 +140,7 @@
"departments": {
"columns": {
"archived": "Arkiveret",
"custom_pricing_only": "Ingen fallback-priser",
"dimension": "Dimension",
"economic_department": "@.upper:{'terms.glossary.e'}-@:{'terms.glossary.conomic'} @:{'terms.glossary.afdeling'}",
"latitude": "@:{'phrases.compat.departments.form.latitude'}",
@@ -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}"
}
}
}
}
@@ -2,6 +2,20 @@
"compat": {
"vehicles": {
"add": "@:{'terms.glossary.tilføj'} @:{'terms.glossary.køretøj'}",
"add_modal": {
"customer_label": "Kunde",
"customer_placeholder": "Søg efter kundenavn eller kundenummer",
"error": "Køretøjet kunne ikke tilføjes.",
"no_customer_results": "Ingen kunder fundet",
"reference_placeholder": "Valgfri reference",
"registration_placeholder": "Registreringsnummer",
"selected_customer": "Valgt kunde",
"submit": "Tilføj køretøj",
"title": "Tilføj køretøj",
"type_load_error": "Køretøjstyper kunne ikke indlæses.",
"type_placeholder": "Vælg køretøjstype",
"validation_error": "Vælg kunde, registreringsnummer og køretøjstype."
},
"brand": "@:{'phrases.compat.admin.pos.make'}",
"color": "Farve",
"delete_vehicle": "@:{'phrases.compat.vehicles.delete'}",
+297 -11
View File
@@ -28,13 +28,16 @@
"title": "@:{'phrases.compat.limited_backoffice.forbidden.title'}",
"department": "@:{'phrases.compat.limited_backoffice.forbidden.department'}"
},
"prices": {
"title": "@:{'phrases.compat.limited_backoffice.prices.title'}",
"save": "@:{'phrases.compat.limited_backoffice.prices.save'}",
"saved": "@:{'phrases.compat.limited_backoffice.prices.saved'}",
"product": "@:{'phrases.compat.limited_backoffice.prices.product'}",
"price": "@:{'phrases.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'phrases.compat.limited_backoffice.prices.price_required'}",
"prices": {
"title": "@:{'phrases.compat.limited_backoffice.prices.title'}",
"save": "@:{'phrases.compat.limited_backoffice.prices.save'}",
"saved": "@:{'phrases.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'phrases.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'phrases.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'phrases.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'phrases.compat.limited_backoffice.prices.product'}",
"price": "@:{'phrases.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'phrases.compat.limited_backoffice.prices.price_required'}",
"setup_required_title": "@:{'phrases.compat.limited_backoffice.prices.setup_required_title'}",
"setup_required_message": "@:{'phrases.compat.limited_backoffice.prices.setup_required_message'}",
"no_products": "@:{'phrases.compat.limited_backoffice.prices.no_products'}"
@@ -46,6 +49,15 @@
"edit_title": "@:{'phrases.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'phrases.compat.limited_backoffice.employees.name'}",
"email": "@:{'phrases.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'phrases.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'phrases.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'phrases.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'phrases.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'phrases.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'phrases.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'phrases.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'phrases.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'phrases.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'phrases.compat.limited_backoffice.employees.password_update_help'}",
@@ -64,7 +76,13 @@
"active": "@:{'phrases.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'phrases.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'phrases.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'phrases.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'phrases.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'phrases.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'phrases.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'phrases.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'phrases.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'phrases.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'phrases.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -94,7 +112,132 @@
"save_prices": "@:{'phrases.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'phrases.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -141,6 +284,9 @@
"title": "@.capitalize:{'terms.glossary.afdelingspriser'}",
"save": "@:{'terms.glossary.gem'} @:{'terms.glossary.priser'}",
"saved": "Priserne @:{'terms.glossary.er'} @:{'terms.glossary.gemt'}.",
"autosave_saving": "Gemmer priser...",
"autosave_pending": "Prisændringer afventer.",
"autosave_saved": "Alle prisændringer er gemt.",
"product": "Produkt",
"price": "Afdelingspris",
"price_required": "@.capitalize:{'terms.glossary.alle'} @:{'terms.glossary.produkter'} @:{'terms.glossary.skal'} have @:{'terms.replication.host_definite_suffix'} @:{'terms.glossary.ikke'}-negativ numerisk pris.",
@@ -155,6 +301,15 @@
"edit_title": "@:{'terms.glossary.rediger'} @:{'terms.glossary.medarbejder'}",
"name": "Navn",
"email": "Email",
"user_id": "Bruger-id",
"phone": "Telefon",
"phone_validation": "Telefonnummeret skal være 4-15 cifre.",
"country_codes": {
"denmark": "Danmark",
"sweden": "Sverige",
"norway": "Norge",
"finland": "Finland"
},
"password": "@.capitalize:{'terms.glossary.adgangskode'}",
"password_create_help": "@.capitalize:{'terms.glossary.pakrævet'} @:{'terms.glossary.for'} nye @:{'terms.glossary.medarbejdere'}. Brug @:{'terms.glossary.mindst'} 8 tegn.",
"password_update_help": "Lad feltet være tomt @:{'terms.glossary.for'} at beholde den nuværende @:{'terms.glossary.adgangskode'}.",
@@ -173,7 +328,13 @@
"active": "Aktiv",
"inactive": "Inaktiv",
"edit": "@:{'terms.glossary.rediger'}",
"deactivate": "Deaktiver"
"deactivate": "Deaktiver",
"login_link": "QR-login",
"login_link_title": "Medarbejderloginlink",
"login_link_qr_alt": "QR-kode-loginlink",
"login_link_url": "Loginlink",
"copy_login_link": "Kopiér link",
"login_link_copied": "Linket er kopieret."
},
"roles": {
"viewer": {
@@ -203,7 +364,132 @@
"save_prices": "@.capitalize:{'terms.glossary.afdelingspriser'} @:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} @:{'terms.glossary.gemmes'}.",
"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."
"deactivate_employee": "@.capitalize:{'terms.glossary.medarbejderen'} @:{'terms.glossary.kunne'} @:{'terms.glossary.ikke'} deaktiveres.",
"generate_employee_login_link": "Medarbejderens loginlink kunne ikke oprettes."
},
"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."
}
}
}
}
}
@@ -221,6 +221,7 @@
"company_phone": "Firmentelefon",
"contact_email": "@:{'terms.glossary.kontakt'}-@.upper:{'terms.glossary.e'}-@:{'terms.glossary.mail'}",
"contact_phone": "Kontakttelefonnummer",
"ean": "EAN",
"created_by": "@.capitalize:{'terms.glossary.erstellt'} @:{'terms.glossary.von'}",
"customer_conflict": {
"help_text": "@.capitalize:{'terms.glossary.dieses'} @:{'terms.glossary.kennzeichen'} @:{'terms.glossary.gehoert'} @:{'terms.glossary.zu'} @:{'terms.glossary.einem'} @:{'terms.glossary.anderen'} @:{'terms.glossary.gespeicherten'} @:{'terms.glossary.kunden'}. @:{'terms.glossary.waehlen'} @.capitalize:{'terms.glossary.sie'}, @:{'terms.glossary.ob'} @:{'terms.glossary.der'} @:{'terms.glossary.ausgewaehlte'} @.capitalize:{'terms.glossary.kunde'} @:{'terms.glossary.beibehalten'} @:{'terms.glossary.oder'} @:{'terms.glossary.zum'} @:{'terms.glossary.fahrzeugkunden'} @:{'terms.glossary.gewechselt'} @:{'terms.glossary.werden'} @:{'terms.glossary.soll'}.",
@@ -7,6 +7,7 @@
"benefit_portal": "@:{'terms.glossary.kundenportal'}",
"benefit_portal_desc": "@.capitalize:{'terms.glossary.nutzen'} @.capitalize:{'terms.glossary.sie'} @:{'terms.glossary.das'} @:{'terms.glossary.kundenportal'}, @:{'terms.glossary.um'} @:{'terms.glossary.fahrzeuge'} hinzuzuf?@:{'terms.glossary.gen'}, @.upper:{'terms.glossary.w'}?@:{'terms.glossary.schen'} @:{'terms.glossary.zu'} @:{'terms.glossary.buchen'}, @:{'terms.glossary.rechnungen'} @:{'terms.glossary.und'} @:{'terms.glossary.waschzertifikate'} herunterzuladen @:{'terms.glossary.sowie'} @:{'terms.glossary.informationen'} @:{'terms.glossary.zu'} @:{'terms.glossary.fr'}?@:{'terms.glossary.heren'} @.upper:{'terms.glossary.w'}?@:{'terms.glossary.schen'} einzusehen",
"benefit_wash_desc": "@.capitalize:{'terms.glossary.nutzen'} @.capitalize:{'terms.glossary.sie'} @:{'terms.glossary.unser'} @:{'terms.glossary.nationales'} @:{'terms.glossary.netz'} @:{'terms.glossary.von'} @:{'terms.glossary.waschhallen'} @:{'terms.glossary.zu'} @:{'terms.glossary.wettbewerbsf'}?@:{'terms.glossary.higen'} @:{'terms.glossary.preisen'}",
"ean_label": "@:{'phrases.compat.admin.pos.ean'}",
"email_label": "Firmen-@.upper:{'terms.glossary.e'}-@:{'terms.glossary.mail'}",
"intro": "@.capitalize:{'terms.glossary.als'} @.capitalize:{'terms.glossary.kunde'} @:{'terms.glossary.bei'} @:{'terms.glossary.truck'} @.capitalize:{'terms.glossary.wash'} @:{'terms.glossary.erhalten'} @.capitalize:{'terms.glossary.sie'} @:{'terms.glossary.zugang'} @:{'terms.glossary.zu'}:",
"phone_label": "@:{'phrases.compat.admin.pos.company_phone'}",
@@ -3,17 +3,17 @@
"error_report": {
"button": "Fehler melden",
"title": "Fehler melden",
"subtitle": "Sende den aktuellen Bildschirm und die letzten Fehlerdetails an den Support.",
"subtitle": "Sende die letzten Fehlerdetails an den Support. Ein Screenshot wird angehängt, wenn möglich.",
"before_error": "Was haben Sie getan, bevor der Fehler auftrat?",
"expected": "Was hatten Sie erwartet?",
"actual": "Was ist tatsächlich passiert?",
"before_error_placeholder": "Beschreiben Sie die Aktion, zum Beispiel Bestellungen öffnen oder einen Kunden auswählen.",
"expected_placeholder": "Beschreiben Sie das erwartete Ergebnis.",
"actual_placeholder": "Beschreiben Sie, was stattdessen zu sehen war, inklusive Fehlermeldung.",
"consent": "Ich akzeptiere, dass der aktuelle App-Bildschirm, letzte Request-Fehler, Vue-Fehler, Browserdetails und meine Antworten zur Fehlersuche erfasst werden.",
"consent": "Ich akzeptiere, dass letzte Request-Fehler, Vue-Fehler, Browserdetails, meine Antworten und ein App-Screenshot, wenn möglich, zur Fehlersuche erfasst werden.",
"submit": "Bericht senden",
"submitted": "Fehlerbericht gesendet.",
"capture_failed": "Der Screenshot konnte nicht erstellt werden. Bitte versuchen Sie es erneut.",
"capture_failed": "Der Screenshot konnte nicht erstellt werden. Der Bericht wird ohne Screenshot gesendet.",
"submit_failed": "Der Fehlerbericht konnte nicht gesendet werden.",
"required": "Alle Felder und die Zustimmung zur Datenerfassung sind erforderlich.",
"page_subtitle": "Gemeldete Bildschirme, Request-Fehler und Vue-Fehler",
@@ -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}"
}
}
}
}
+297 -11
View File
@@ -28,13 +28,16 @@
"title": "@:{'phrases.compat.limited_backoffice.forbidden.title'}",
"department": "@:{'phrases.compat.limited_backoffice.forbidden.department'}"
},
"prices": {
"title": "@:{'phrases.compat.limited_backoffice.prices.title'}",
"save": "@:{'phrases.compat.limited_backoffice.prices.save'}",
"saved": "@:{'phrases.compat.limited_backoffice.prices.saved'}",
"product": "@:{'phrases.compat.limited_backoffice.prices.product'}",
"price": "@:{'phrases.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'phrases.compat.limited_backoffice.prices.price_required'}",
"prices": {
"title": "@:{'phrases.compat.limited_backoffice.prices.title'}",
"save": "@:{'phrases.compat.limited_backoffice.prices.save'}",
"saved": "@:{'phrases.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'phrases.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'phrases.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'phrases.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'phrases.compat.limited_backoffice.prices.product'}",
"price": "@:{'phrases.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'phrases.compat.limited_backoffice.prices.price_required'}",
"setup_required_title": "@:{'phrases.compat.limited_backoffice.prices.setup_required_title'}",
"setup_required_message": "@:{'phrases.compat.limited_backoffice.prices.setup_required_message'}",
"no_products": "@:{'phrases.compat.limited_backoffice.prices.no_products'}"
@@ -46,6 +49,15 @@
"edit_title": "@:{'phrases.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'phrases.compat.limited_backoffice.employees.name'}",
"email": "@:{'phrases.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'phrases.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'phrases.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'phrases.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'phrases.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'phrases.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'phrases.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'phrases.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'phrases.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'phrases.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'phrases.compat.limited_backoffice.employees.password_update_help'}",
@@ -64,7 +76,13 @@
"active": "@:{'phrases.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'phrases.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'phrases.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'phrases.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'phrases.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'phrases.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'phrases.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'phrases.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'phrases.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'phrases.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'phrases.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -94,7 +112,132 @@
"save_prices": "@:{'phrases.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'phrases.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -141,6 +284,9 @@
"title": "@.capitalize:{'terms.glossary.abteilungspreise'}",
"save": "@:{'terms.glossary.preise'} @:{'terms.glossary.speichern'}",
"saved": "@:{'terms.glossary.preise'} @:{'terms.glossary.gespeichert'}.",
"autosave_saving": "Preise werden gespeichert...",
"autosave_pending": "Preisaenderungen ausstehend.",
"autosave_saved": "Alle Preisaenderungen gespeichert.",
"product": "@.capitalize:{'terms.glossary.produkt'}",
"price": "Abteilungspreis",
"price_required": "Jedes @.capitalize:{'terms.glossary.produkt'} @:{'terms.glossary.muss'} einen @:{'terms.glossary.nicht'}-negativen numerischen Preis @:{'terms.glossary.haben'}.",
@@ -155,6 +301,15 @@
"edit_title": "@:{'terms.glossary.mitarbeiter'} @:{'terms.glossary.bearbeiten'}",
"name": "Name",
"email": "E-Mail",
"user_id": "Benutzer-ID",
"phone": "Telefon",
"phone_validation": "Die Telefonnummer muss 4-15 Ziffern enthalten.",
"country_codes": {
"denmark": "Dänemark",
"sweden": "Schweden",
"norway": "Norwegen",
"finland": "Finnland"
},
"password": "@:{'terms.glossary.passwort'}",
"password_create_help": "@.capitalize:{'terms.glossary.fur'} neue @:{'terms.glossary.mitarbeiter'} @:{'terms.glossary.erforderlich'}. Verwenden @.capitalize:{'terms.glossary.sie'} @:{'terms.glossary.mindestens'} 8 Zeichen.",
"password_update_help": "Leer lassen, um das vorhandene @:{'terms.glossary.passwort'} beizubehalten.",
@@ -173,7 +328,13 @@
"active": "Aktiv",
"inactive": "Inaktiv",
"edit": "@.capitalize:{'terms.glossary.bearbeiten'}",
"deactivate": "Deaktivieren"
"deactivate": "Deaktivieren",
"login_link": "QR-Login",
"login_link_title": "Mitarbeiter-Loginlink",
"login_link_qr_alt": "QR-Code-Loginlink",
"login_link_url": "Loginlink",
"copy_login_link": "Link kopieren",
"login_link_copied": "Link kopiert."
},
"roles": {
"viewer": {
@@ -203,7 +364,132 @@
"save_prices": "@.capitalize:{'terms.glossary.abteilungspreise'} @:{'terms.glossary.konnten'} @:{'terms.glossary.nicht'} @:{'terms.glossary.gespeichert'} @:{'terms.glossary.werden'}.",
"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'}."
"deactivate_employee": "@:{'terms.glossary.mitarbeiter'} @:{'terms.glossary.konnte'} @:{'terms.glossary.nicht'} @:{'terms.glossary.deaktiviert'} @:{'terms.glossary.werden'}.",
"generate_employee_login_link": "Mitarbeiter-Loginlink konnte nicht erstellt 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."
}
}
}
}
}
@@ -221,6 +221,7 @@
"company_phone": "@.capitalize:{'terms.glossary.company'} @:{'terms.glossary.phone'}",
"contact_email": "@.capitalize:{'terms.glossary.contact'} @:{'terms.glossary.email'}",
"contact_phone": "@.capitalize:{'terms.glossary.contact'} @:{'terms.glossary.phone'}",
"ean": "EAN",
"created_by": "@.capitalize:{'terms.glossary.created'} @:{'terms.glossary.by'}",
"customer_conflict": {
"help_text": "@.capitalize:{'terms.glossary.this'} @:{'terms.glossary.typed'} @:{'terms.glossary.plate'} @:{'terms.glossary.belongs'} @:{'terms.glossary.to'} @:{'terms.glossary.another'} @:{'terms.glossary.saved'} @:{'terms.glossary.customer'}. @.capitalize:{'terms.glossary.choose'} @:{'terms.glossary.whether'} @:{'terms.glossary.to'} @:{'terms.glossary.keep'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.selected'} @:{'terms.glossary.customer'} @:{'terms.glossary.or'} @:{'terms.glossary.switch'} @:{'terms.glossary.to'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.vehicle'} @:{'terms.glossary.customer'}.",
@@ -7,6 +7,7 @@
"benefit_portal": "@.capitalize:{'terms.glossary.customer'} @.capitalize:{'terms.glossary.portal'}",
"benefit_portal_desc": "@.capitalize:{'terms.glossary.use'} @:{'terms.replication.article.host_mention'} @:{'terms.glossary.customer'} @:{'terms.glossary.portal'} @:{'terms.glossary.to'} @:{'terms.glossary.add'} @:{'terms.glossary.vehicles'}, @:{'terms.glossary.book'} @:{'terms.glossary.washes'}, @:{'terms.glossary.download'} @:{'terms.glossary.invoices'}, @:{'terms.glossary.wash'} @:{'terms.glossary.certificates'} @:{'terms.glossary.and'} @:{'terms.glossary.information'} @:{'terms.glossary.for'} @:{'terms.glossary.historical'} @:{'terms.glossary.washes'}",
"benefit_wash_desc": "@.capitalize:{'terms.glossary.use'} @:{'terms.glossary.our'} @:{'terms.glossary.national'} @:{'terms.glossary.network'} @:{'terms.glossary.of'} @:{'terms.glossary.wash'} @:{'terms.glossary.halls'} @:{'terms.glossary.at'} @:{'terms.glossary.competitive'} @:{'terms.glossary.prices'}",
"ean_label": "@:{'phrases.compat.admin.pos.ean'}",
"email_label": "@.capitalize:{'terms.glossary.company'} @:{'terms.glossary.email'}",
"intro": "@.capitalize:{'terms.glossary.as'} @:{'terms.glossary.a'} @:{'terms.glossary.customer'} @:{'terms.glossary.at'} @.capitalize:{'terms.glossary.truck'} @.capitalize:{'terms.glossary.wash'} @:{'terms.glossary.you'} @:{'terms.glossary.get'} @:{'terms.glossary.access'} @:{'terms.glossary.to'}:",
"phone_label": "@:{'phrases.compat.admin.pos.company_phone'}",
@@ -21,6 +21,13 @@
"opening_hours": {
"closed": "@:{'phrases.compat.global.closed'}"
},
"pricing": {
"custom_pricing_disabled": "Fallback enabled",
"custom_pricing_enabled": "Custom only",
"custom_pricing_missing_price": "Missing department prices resolve to 999999.",
"custom_pricing_only": "No fallback pricing",
"effective_department_price": "Effective department price"
},
"search_placeholder": "@.capitalize:{'terms.glossary.search'} @:{'terms.glossary.by'} @:{'terms.glossary.department'} @:{'terms.glossary.name'}",
"select_department": "@.capitalize:{'terms.glossary.select'} @:{'terms.glossary.a'} @:{'terms.glossary.department'}",
"subtitle": "@.capitalize:{'terms.glossary.manage'} @:{'terms.glossary.departments'}",
@@ -3,17 +3,17 @@
"error_report": {
"button": "Report error",
"title": "Report error",
"subtitle": "Send the current screen and recent error details to support.",
"subtitle": "Send recent error details to support. A screenshot is attached when available.",
"before_error": "What were you doing before the error occurred?",
"expected": "What did you expect would happen?",
"actual": "What actually happened?",
"before_error_placeholder": "Describe the action you were taking, for example opening orders or selecting a customer.",
"expected_placeholder": "Describe the result you expected to see.",
"actual_placeholder": "Describe what you saw instead, including any error text.",
"consent": "I accept that the current app screen, recent request errors, Vue errors, browser details, and my answers are collected for troubleshooting.",
"consent": "I accept that recent request errors, Vue errors, browser details, my answers, and an app screenshot when available are collected for troubleshooting.",
"submit": "Submit report",
"submitted": "Error report submitted.",
"capture_failed": "The screen capture failed. Please try again.",
"capture_failed": "The screen capture failed. The report will be sent without a screenshot.",
"submit_failed": "The error report could not be submitted.",
"required": "All fields and data collection acceptance are required.",
"page_subtitle": "Reported screens, request failures, and Vue errors",
@@ -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"
}
}
}
}
@@ -140,6 +140,7 @@
"departments": {
"columns": {
"archived": "@:{'terms.glossary.archived'}",
"custom_pricing_only": "No fallback pricing",
"dimension": "Dimension",
"economic_department": "@.upper:{'terms.glossary.e'}-@:{'terms.glossary.conomic'} @.capitalize:{'terms.glossary.department'}",
"latitude": "@:{'phrases.compat.departments.form.latitude'}",
@@ -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"
}
}
}
}
@@ -2,6 +2,20 @@
"compat": {
"vehicles": {
"add": "@.capitalize:{'terms.glossary.add'} @:{'terms.glossary.vehicle'}",
"add_modal": {
"customer_label": "Customer",
"customer_placeholder": "Search by customer name or number",
"error": "Unable to add vehicle.",
"no_customer_results": "No customers found",
"reference_placeholder": "Optional reference",
"registration_placeholder": "Registration number",
"selected_customer": "Selected customer",
"submit": "Add vehicle",
"title": "Add vehicle",
"type_load_error": "Unable to load vehicle types.",
"type_placeholder": "Select vehicle type",
"validation_error": "Select a customer, registration number, and vehicle type."
},
"brand": "Brand",
"color": "Color",
"delete_vehicle": "@:{'phrases.compat.vehicles.delete'}",
+297 -11
View File
@@ -28,13 +28,16 @@
"title": "@:{'phrases.compat.limited_backoffice.forbidden.title'}",
"department": "@:{'phrases.compat.limited_backoffice.forbidden.department'}"
},
"prices": {
"title": "@:{'phrases.compat.limited_backoffice.prices.title'}",
"save": "@:{'phrases.compat.limited_backoffice.prices.save'}",
"saved": "@:{'phrases.compat.limited_backoffice.prices.saved'}",
"product": "@:{'phrases.compat.limited_backoffice.prices.product'}",
"price": "@:{'phrases.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'phrases.compat.limited_backoffice.prices.price_required'}",
"prices": {
"title": "@:{'phrases.compat.limited_backoffice.prices.title'}",
"save": "@:{'phrases.compat.limited_backoffice.prices.save'}",
"saved": "@:{'phrases.compat.limited_backoffice.prices.saved'}",
"autosave_saving": "@:{'phrases.compat.limited_backoffice.prices.autosave_saving'}",
"autosave_pending": "@:{'phrases.compat.limited_backoffice.prices.autosave_pending'}",
"autosave_saved": "@:{'phrases.compat.limited_backoffice.prices.autosave_saved'}",
"product": "@:{'phrases.compat.limited_backoffice.prices.product'}",
"price": "@:{'phrases.compat.limited_backoffice.prices.price'}",
"price_required": "@:{'phrases.compat.limited_backoffice.prices.price_required'}",
"setup_required_title": "@:{'phrases.compat.limited_backoffice.prices.setup_required_title'}",
"setup_required_message": "@:{'phrases.compat.limited_backoffice.prices.setup_required_message'}",
"no_products": "@:{'phrases.compat.limited_backoffice.prices.no_products'}"
@@ -46,6 +49,15 @@
"edit_title": "@:{'phrases.compat.limited_backoffice.employees.edit_title'}",
"name": "@:{'phrases.compat.limited_backoffice.employees.name'}",
"email": "@:{'phrases.compat.limited_backoffice.employees.email'}",
"user_id": "@:{'phrases.compat.limited_backoffice.employees.user_id'}",
"phone": "@:{'phrases.compat.limited_backoffice.employees.phone'}",
"phone_validation": "@:{'phrases.compat.limited_backoffice.employees.phone_validation'}",
"country_codes": {
"denmark": "@:{'phrases.compat.limited_backoffice.employees.country_codes.denmark'}",
"sweden": "@:{'phrases.compat.limited_backoffice.employees.country_codes.sweden'}",
"norway": "@:{'phrases.compat.limited_backoffice.employees.country_codes.norway'}",
"finland": "@:{'phrases.compat.limited_backoffice.employees.country_codes.finland'}"
},
"password": "@:{'phrases.compat.limited_backoffice.employees.password'}",
"password_create_help": "@:{'phrases.compat.limited_backoffice.employees.password_create_help'}",
"password_update_help": "@:{'phrases.compat.limited_backoffice.employees.password_update_help'}",
@@ -64,7 +76,13 @@
"active": "@:{'phrases.compat.limited_backoffice.employees.active'}",
"inactive": "@:{'phrases.compat.limited_backoffice.employees.inactive'}",
"edit": "@:{'phrases.compat.limited_backoffice.employees.edit'}",
"deactivate": "@:{'phrases.compat.limited_backoffice.employees.deactivate'}"
"deactivate": "@:{'phrases.compat.limited_backoffice.employees.deactivate'}",
"login_link": "@:{'phrases.compat.limited_backoffice.employees.login_link'}",
"login_link_title": "@:{'phrases.compat.limited_backoffice.employees.login_link_title'}",
"login_link_qr_alt": "@:{'phrases.compat.limited_backoffice.employees.login_link_qr_alt'}",
"login_link_url": "@:{'phrases.compat.limited_backoffice.employees.login_link_url'}",
"copy_login_link": "@:{'phrases.compat.limited_backoffice.employees.copy_login_link'}",
"login_link_copied": "@:{'phrases.compat.limited_backoffice.employees.login_link_copied'}"
},
"roles": {
"viewer": {
@@ -94,7 +112,132 @@
"save_prices": "@:{'phrases.compat.limited_backoffice.errors.save_prices'}",
"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'}"
"deactivate_employee": "@:{'phrases.compat.limited_backoffice.errors.deactivate_employee'}",
"generate_employee_login_link": "@:{'phrases.compat.limited_backoffice.errors.generate_employee_login_link'}"
},
"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": {
@@ -141,6 +284,9 @@
"title": "@.capitalize:{'terms.glossary.department'} @:{'terms.glossary.prices'}",
"save": "@.capitalize:{'terms.glossary.save'} @:{'terms.glossary.prices'}",
"saved": "@.capitalize:{'terms.glossary.prices'} @:{'terms.glossary.saved'}.",
"autosave_saving": "Saving prices...",
"autosave_pending": "Price changes pending.",
"autosave_saved": "All price changes saved.",
"product": "@.capitalize:{'terms.glossary.product'}",
"price": "@.capitalize:{'terms.glossary.department'} @:{'terms.glossary.price'}",
"price_required": "Every @:{'terms.glossary.product'} @:{'terms.glossary.must'} @:{'terms.glossary.have'} @:{'terms.glossary.a'} non-negative numeric @:{'terms.glossary.price'}.",
@@ -155,6 +301,15 @@
"edit_title": "@.capitalize:{'terms.glossary.edit'} @:{'terms.glossary.employee'}",
"name": "Name",
"email": "Email",
"user_id": "User ID",
"phone": "Phone",
"phone_validation": "Phone number must be 4-15 digits.",
"country_codes": {
"denmark": "Denmark",
"sweden": "Sweden",
"norway": "Norway",
"finland": "Finland"
},
"password": "@.capitalize:{'terms.glossary.password'}",
"password_create_help": "@.capitalize:{'terms.glossary.required'} @:{'terms.glossary.for'} new @:{'terms.glossary.employees'}. Use @:{'terms.glossary.at'} @:{'terms.glossary.least'} 8 characters.",
"password_update_help": "Leave blank @:{'terms.glossary.to'} keep @:{'terms.replication.article.host_mention'} existing @:{'terms.glossary.password'}.",
@@ -173,7 +328,13 @@
"active": "Active",
"inactive": "@.capitalize:{'terms.glossary.inactive'}",
"edit": "@.capitalize:{'terms.glossary.edit'}",
"deactivate": "@.capitalize:{'terms.glossary.deactivate'}"
"deactivate": "@.capitalize:{'terms.glossary.deactivate'}",
"login_link": "QR login",
"login_link_title": "Employee login link",
"login_link_qr_alt": "QR code login link",
"login_link_url": "Login link",
"copy_login_link": "Copy link",
"login_link_copied": "Link copied."
},
"roles": {
"viewer": {
@@ -203,7 +364,132 @@
"save_prices": "@.capitalize:{'terms.glossary.could'} @:{'terms.glossary.not'} @:{'terms.glossary.save'} @:{'terms.glossary.department'} @:{'terms.glossary.prices'}.",
"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'}."
"deactivate_employee": "@.capitalize:{'terms.glossary.could'} @:{'terms.glossary.not'} @:{'terms.glossary.deactivate'} @:{'terms.glossary.employee'}.",
"generate_employee_login_link": "Could not create employee login link."
},
"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."
}
}
}
}
}
@@ -293,6 +293,7 @@
"delete": "@:{'phrases.compat.global.delete'}",
"details": "@:{'phrases.compat.admin.pos.details'}",
"download": "@:{'phrases.compat.guest.pwa.title'}",
"ean": "@:{'phrases.compat.admin.pos.ean'}",
"drafts_assignment": {
"button": "@:{'phrases.compat.admin.pos.drafts_assignment.button'}",
"create_invoice_collection": "@:{'phrases.compat.admin.pos.drafts_assignment.create_invoice_collection'}",
@@ -8,6 +8,7 @@
"benefit_wash": "@:{'phrases.compat.tables.bookings.wash'}",
"benefit_wash_desc": "@:{'phrases.compat.tables.bookings.wash_desc'}",
"cvr_label": "@:{'phrases.compat.customers.cvr'}",
"ean_label": "@:{'phrases.compat.customer_creation.customer.ean_label'}",
"email_label": "@:{'phrases.compat.customer_creation.customer.email_label'}",
"intro": "@:{'phrases.compat.customer_creation.customer.intro'}",
"phone_label": "@:{'phrases.compat.customer_creation.customer.phone_label'}",

Some files were not shown because too many files have changed in this diff Show More