Fix self-serve settings state and contract (#229)
Ensure department-scoped self-serve settings load and save safely across route transitions, document the API contract, and cover stale in-flight state.
This commit is contained in:
@@ -3534,6 +3534,19 @@ paths:
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
auto_deactivation:
|
||||
type: object
|
||||
required: [at, timezone, label]
|
||||
properties:
|
||||
at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
timezone:
|
||||
type: string
|
||||
example: Europe/Copenhagen
|
||||
label:
|
||||
type: string
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
put:
|
||||
@@ -3566,6 +3579,21 @@ paths:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
auto_deactivation:
|
||||
type: object
|
||||
required: [at, timezone, label]
|
||||
properties:
|
||||
at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
timezone:
|
||||
type: string
|
||||
example: Europe/Copenhagen
|
||||
label:
|
||||
type: string
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
|
||||
@@ -43,17 +43,40 @@ const extractEnabledValue = (response) => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getDepartmentSelfServeEnabled = async (departmentId) => {
|
||||
const extractSelfServeStatus = (response, fallbackEnabled = false) => {
|
||||
const payload = response?.data?.data ?? response?.data ?? response;
|
||||
const enabled = Object.prototype.hasOwnProperty.call(payload || {}, "enabled")
|
||||
? normalizeEnabledValue(payload.enabled)
|
||||
: normalizeEnabledValue(fallbackEnabled);
|
||||
const autoDeactivation = payload?.auto_deactivation || payload?.autoDeactivation || null;
|
||||
|
||||
return {
|
||||
enabled,
|
||||
hasAutoDeactivation: autoDeactivation !== null,
|
||||
autoDeactivation: {
|
||||
at: autoDeactivation?.at || null,
|
||||
timezone: autoDeactivation?.timezone || "Europe/Copenhagen",
|
||||
label: autoDeactivation?.label || (autoDeactivation?.at ? String(autoDeactivation.at) : "NEVER"),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getDepartmentSelfServeStatus = async (departmentId) => {
|
||||
const normalizedDepartmentId = normalizeDepartmentId(departmentId);
|
||||
const response = await authenticatedRequest(
|
||||
`/departments/self-serve/enabled?id=${normalizedDepartmentId}`,
|
||||
"GET"
|
||||
);
|
||||
|
||||
return normalizeEnabledValue(extractEnabledValue(response));
|
||||
return extractSelfServeStatus(response);
|
||||
};
|
||||
|
||||
export const setDepartmentSelfServeEnabled = async (departmentId, enabled) => {
|
||||
export const getDepartmentSelfServeEnabled = async (departmentId) => {
|
||||
const status = await getDepartmentSelfServeStatus(departmentId);
|
||||
return status.enabled;
|
||||
};
|
||||
|
||||
export const setDepartmentSelfServeStatus = async (departmentId, enabled) => {
|
||||
const normalizedDepartmentId = normalizeDepartmentId(departmentId);
|
||||
const normalizedEnabled = normalizeEnabledValue(enabled);
|
||||
|
||||
@@ -63,12 +86,20 @@ export const setDepartmentSelfServeEnabled = async (departmentId, enabled) => {
|
||||
);
|
||||
|
||||
const responseEnabled = extractEnabledValue(response);
|
||||
return responseEnabled === undefined
|
||||
? normalizedEnabled
|
||||
: normalizeEnabledValue(responseEnabled);
|
||||
return extractSelfServeStatus(
|
||||
response,
|
||||
responseEnabled === undefined ? normalizedEnabled : normalizeEnabledValue(responseEnabled)
|
||||
);
|
||||
};
|
||||
|
||||
export const setDepartmentSelfServeEnabled = async (departmentId, enabled) => {
|
||||
const status = await setDepartmentSelfServeStatus(departmentId, enabled);
|
||||
return status.enabled;
|
||||
};
|
||||
|
||||
export default {
|
||||
getDepartmentSelfServeEnabled,
|
||||
getDepartmentSelfServeStatus,
|
||||
setDepartmentSelfServeEnabled,
|
||||
setDepartmentSelfServeStatus,
|
||||
};
|
||||
|
||||
@@ -965,6 +965,12 @@ export const router = createRouter({
|
||||
component: DepartmentModulesSetup,
|
||||
meta: { middleware: superUserMiddleware }
|
||||
},
|
||||
{
|
||||
name: 'departmentsopeninghours',
|
||||
path: '/superuser/departments/:departmentId/opening-hours',
|
||||
component: DepartmentTimeBookingsOpeningHours,
|
||||
meta: { middleware: superUserMiddleware }
|
||||
},
|
||||
{
|
||||
name: 'departmentsgateways',
|
||||
path: '/superuser/departments/:departmentId/gateways',
|
||||
|
||||
+102
-13
@@ -2,10 +2,27 @@
|
||||
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import DepartmentDashboardPageWrapper from "@/views/dashboards/departmentDashboard/DepartmentDashboardPageWrapper.vue";
|
||||
import DepartmentSubPageWrapper from "@/views/dashboards/superUserDashboard/department/DepartmentSubPageWrapper.vue";
|
||||
import { setDepartment as setSelectedDepartment } from "@/views/dashboards/superUserDashboard/department/SuperUserSelectedDepartmentObject.vue";
|
||||
import NotFoundFallBackPageWrapper from "@/components/page/wrappers/NotFoundFallBackPageWrapper.vue";
|
||||
import {ref} from 'vue';
|
||||
import PageTitle from "@/components/global/PageTitle.vue";
|
||||
import {computed, ref, watch} from 'vue';
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
const opening_hours = ref(null);
|
||||
const route = useRoute();
|
||||
const departmentId = computed(() => route.params.departmentId || SessionUser.functions.getDepartmentIdFromUrl());
|
||||
const isSuperuserDepartmentRoute = computed(() => String(route.path || "").startsWith("/superuser/departments/"));
|
||||
const pageTitle = computed(() => SessionUser.objects.global.language.opening_hours);
|
||||
const pageSubtitle = "Afdelingens åbningstider";
|
||||
const hasPagePermission = computed(() => (
|
||||
isSuperuserDepartmentRoute.value
|
||||
? SessionUser.canAccessSuperUser()
|
||||
: SessionUser.canAccessAdmin()
|
||||
));
|
||||
const hasDepartmentAccess = computed(() => (
|
||||
Boolean(departmentId.value) && SessionUser.canAccessDepartment(departmentId.value)
|
||||
));
|
||||
|
||||
const isOpeningHoursLoaded = () => { return opening_hours.value !== null; }
|
||||
|
||||
@@ -54,30 +71,50 @@ const getControlClass = () => {
|
||||
return '';
|
||||
}
|
||||
|
||||
const getOpeningHours = (departmentId) => {
|
||||
let openingHoursRequest = 0;
|
||||
const getOpeningHours = (selectedDepartmentId) => {
|
||||
const requestId = ++openingHoursRequest;
|
||||
opening_hours.value = null;
|
||||
// Fetch the opening hours for the department
|
||||
SessionUser.request(
|
||||
return SessionUser.request(
|
||||
SessionUser.objects.department_time_bookings_opening_hours.meta.endpoint,
|
||||
'GET',
|
||||
{
|
||||
department: departmentId
|
||||
department: selectedDepartmentId
|
||||
}
|
||||
).then((response) => {
|
||||
if (requestId !== openingHoursRequest) {
|
||||
return;
|
||||
}
|
||||
console.log('response', response.data.data);
|
||||
// Check if there's any object in the response
|
||||
opening_hours.value = response.data.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (requestId === openingHoursRequest) {
|
||||
console.error('Error fetching opening hours:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const departmentId = SessionUser.functions.getDepartmentIdFromUrl();
|
||||
if (departmentId) {
|
||||
getOpeningHours(departmentId);
|
||||
} else {
|
||||
watch(
|
||||
() => [departmentId.value, isSuperuserDepartmentRoute.value],
|
||||
([selectedDepartmentId, isSuperuserRoute], [previousDepartmentId] = []) => {
|
||||
if (selectedDepartmentId) {
|
||||
if (isSuperuserRoute) {
|
||||
setSelectedDepartment(selectedDepartmentId);
|
||||
}
|
||||
if (selectedDepartmentId !== previousDepartmentId) {
|
||||
getOpeningHours(selectedDepartmentId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
openingHoursRequest++;
|
||||
opening_hours.value = null;
|
||||
console.error('Department ID not found in URL');
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const getOpeningHoursValue = (name) => {
|
||||
if (opening_hours.value === null) {
|
||||
@@ -88,12 +125,64 @@ const getOpeningHoursValue = (name) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessAdmin()">
|
||||
<RestrictedPageWrapper :hasPermission="hasPagePermission">
|
||||
<DepartmentSubPageWrapper v-if="isSuperuserDepartmentRoute">
|
||||
<template #title>
|
||||
<PageTitle :title="pageTitle" :subtitle="pageSubtitle" />
|
||||
</template>
|
||||
<NotFoundFallBackPageWrapper :exists="hasDepartmentAccess" :error="$t('admin.errors.select_department')">
|
||||
<table class="table is-striped is-bordered is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{SessionUser.objects.global.language.weekday}}</th>
|
||||
<th>{{SessionUser.objects.global.language.opening_hour}}</th>
|
||||
<th>{{SessionUser.objects.global.language.closing_hour}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(weekday, index) in weekdays" :key="index">
|
||||
<td>{{ weekday.label }}</td>
|
||||
<td>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded" :class="getControlClass(weekday, 'start')">
|
||||
<input type="time" class="input is-fullwidth" @change="onChangeTime(weekday.name + '_start', $event.target.value)" v-if="isOpeningHoursLoaded()" :value="getOpeningHoursValue(weekday.name + '_start')" :id="weekday.name + '_start'" />
|
||||
<input type="time" class="input is-skeleton is-fullwidth" v-else disabled/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-danger" @click="onChangeTime(weekday.name + '_start', null)" v-bind:disabled="!getOpeningHoursValue(weekday.name + '_start')">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded" :class="getControlClass(weekday, 'end')">
|
||||
<input type="time" class="input is-fullwidth" @change="onChangeTime(weekday.name + '_end', $event.target.value)" v-if="isOpeningHoursLoaded()" :value="getOpeningHoursValue(weekday.name + '_end')" :id="weekday.name + '_end'" />
|
||||
<input type="time" class="input is-skeleton is-fullwidth" v-else disabled/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-danger" @click="onChangeTime(weekday.name + '_end', null)" v-bind:disabled="!getOpeningHoursValue(weekday.name + '_end')">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</NotFoundFallBackPageWrapper>
|
||||
</DepartmentSubPageWrapper>
|
||||
<DepartmentDashboardPageWrapper
|
||||
:title="SessionUser.objects.global.language.opening_hours"
|
||||
subtitle="Afdelingens åbningstider"
|
||||
v-else
|
||||
:title="pageTitle"
|
||||
:subtitle="pageSubtitle"
|
||||
>
|
||||
<NotFoundFallBackPageWrapper :exists="SessionUser.functions.getDepartmentIdFromUrl() && SessionUser.canAccessDepartment(SessionUser.functions.getDepartmentIdFromUrl())" :error="$t('admin.errors.select_department')">
|
||||
<NotFoundFallBackPageWrapper :exists="hasDepartmentAccess" :error="$t('admin.errors.select_department')">
|
||||
<table class="table is-striped is-bordered is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
+6
@@ -26,6 +26,12 @@ const tabs = computed(() =>
|
||||
to: `${departmentPath.value}/modules`,
|
||||
active: (path) => path.startsWith(`${departmentPath.value}/modules`),
|
||||
},
|
||||
{
|
||||
key: "opening-hours",
|
||||
label: "Opening hours",
|
||||
to: `${departmentPath.value}/opening-hours`,
|
||||
active: (path) => path.startsWith(`${departmentPath.value}/opening-hours`),
|
||||
},
|
||||
{
|
||||
key: "branding",
|
||||
label: t("superuser_dashboard.department_navigation.branding"),
|
||||
|
||||
+114
-18
@@ -5,7 +5,7 @@ import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import DepartmentSubPageWrapper from "@/views/dashboards/superUserDashboard/department/DepartmentSubPageWrapper.vue";
|
||||
import { departmentAdvanced, setDepartment, departmentId } from "@/views/dashboards/superUserDashboard/department/SuperUserSelectedDepartmentObject.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ConfigurationSelect from "@/components/displays/superuser/configuration/ConfigurationSelect.vue";
|
||||
import SuperuserOverviewPanel from "@/components/displays/superuser/overview/SuperuserOverviewPanel.vue";
|
||||
@@ -14,14 +14,13 @@ import SuperUserDashboardDepartmentModulesNavigation
|
||||
import ConfigurationSwitch from "@/components/displays/superuser/configuration/ConfigurationSwitch.vue";
|
||||
import ConfigurationInput from "@/components/displays/superuser/configuration/ConfigurationInput.vue";
|
||||
import {
|
||||
getDepartmentSelfServeEnabled,
|
||||
setDepartmentSelfServeEnabled,
|
||||
getDepartmentSelfServeStatus,
|
||||
setDepartmentSelfServeStatus,
|
||||
} from "@/composables/departmentSelfServeEnabled.js";
|
||||
|
||||
// Get the department from the route
|
||||
const router = useRouter();
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
setDepartment(router.currentRoute.value.params.departmentId);
|
||||
const departmentTitle = computed(() => departmentAdvanced.value.name || t("superuser_dashboard.department_navigation.modules"));
|
||||
const pageSubtitle = computed(() => t("superuser_dashboard.department_pages.modules.subtitle"));
|
||||
|
||||
@@ -30,9 +29,13 @@ const workfeedDepartmentOptions = ref([{ value: "", label: "No Workfeed departme
|
||||
const isLoadingWorkfeedDepartments = ref(false);
|
||||
const workfeedDepartmentOptionsError = ref("");
|
||||
const selfServeEnabled = ref(false);
|
||||
const selfServeAutoDeactivation = ref({ at: null, timezone: "Europe/Copenhagen", label: "NEVER" });
|
||||
const isLoadingSelfServeEnabled = ref(false);
|
||||
const isSavingSelfServeEnabled = ref(false);
|
||||
const selfServeEnabledError = ref("");
|
||||
let departmentVariablesRequestSequence = 0;
|
||||
let selfServeStatusRequestSequence = 0;
|
||||
let selfServeSaveRequestSequence = 0;
|
||||
const departmentVariableDescriptions = computed(() => ({
|
||||
bookingsystem_enabled: t("superuser_dashboard.department_pages.modules.variables.bookingsystem_enabled"),
|
||||
bookingsystem_time_based_enabled: t("superuser_dashboard.department_pages.modules.variables.bookingsystem_time_based_enabled"),
|
||||
@@ -47,20 +50,44 @@ const departmentVariableRows = computed(() => departmentVariables.value.map((var
|
||||
value: variable.value,
|
||||
})));
|
||||
|
||||
const getDepartmentVariables = async () => {
|
||||
await SessionUser.request(
|
||||
const selfServeAutoDeactivationText = computed(() => {
|
||||
if (!selfServeEnabled.value) {
|
||||
return "Automatic deactivation: not scheduled while self-serve is disabled.";
|
||||
}
|
||||
|
||||
const label = String(selfServeAutoDeactivation.value?.label || "").trim();
|
||||
return `Automatic deactivation: ${label || "NEVER"}`;
|
||||
});
|
||||
|
||||
const getDepartmentVariables = async (
|
||||
requestedDepartmentId = String(departmentId.value)
|
||||
) => {
|
||||
const requestSequence = ++departmentVariablesRequestSequence;
|
||||
try {
|
||||
const response = await SessionUser.request(
|
||||
SessionUser.objects.department_variables.meta.endpoint,
|
||||
"GET",
|
||||
{
|
||||
filters: "department_id:" + departmentId.value,
|
||||
filters: "department_id:" + requestedDepartmentId,
|
||||
}
|
||||
);
|
||||
if (
|
||||
requestSequence !== departmentVariablesRequestSequence
|
||||
|| String(departmentId.value) !== requestedDepartmentId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
departmentVariables.value = response.data.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (
|
||||
requestSequence === departmentVariablesRequestSequence
|
||||
&& String(departmentId.value) === requestedDepartmentId
|
||||
) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getVariableValue = (variable) => {
|
||||
@@ -153,40 +180,107 @@ const setWorkfeedDepartmentId = async (selectedDepartmentId) => {
|
||||
});
|
||||
};
|
||||
|
||||
const loadSelfServeEnabled = async () => {
|
||||
const loadSelfServeEnabled = async ({
|
||||
preserveEnabledOnError = false,
|
||||
requestedDepartmentId = String(departmentId.value),
|
||||
} = {}) => {
|
||||
const requestSequence = ++selfServeStatusRequestSequence;
|
||||
isLoadingSelfServeEnabled.value = true;
|
||||
selfServeEnabledError.value = "";
|
||||
try {
|
||||
selfServeEnabled.value = await getDepartmentSelfServeEnabled(departmentId.value);
|
||||
const status = await getDepartmentSelfServeStatus(requestedDepartmentId);
|
||||
if (
|
||||
requestSequence !== selfServeStatusRequestSequence
|
||||
|| String(departmentId.value) !== requestedDepartmentId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
selfServeEnabled.value = status.enabled;
|
||||
selfServeAutoDeactivation.value = status.autoDeactivation;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (
|
||||
requestSequence === selfServeStatusRequestSequence
|
||||
&& String(departmentId.value) === requestedDepartmentId
|
||||
) {
|
||||
selfServeEnabledError.value = "Unable to load self-serve status.";
|
||||
if (!preserveEnabledOnError) {
|
||||
selfServeEnabled.value = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
if (requestSequence === selfServeStatusRequestSequence) {
|
||||
isLoadingSelfServeEnabled.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateSelfServeEnabled = async (enabled) => {
|
||||
const requestedDepartmentId = String(departmentId.value);
|
||||
const requestSequence = ++selfServeSaveRequestSequence;
|
||||
const previousValue = selfServeEnabled.value;
|
||||
selfServeEnabled.value = Boolean(enabled);
|
||||
isSavingSelfServeEnabled.value = true;
|
||||
selfServeEnabledError.value = "";
|
||||
|
||||
try {
|
||||
selfServeEnabled.value = await setDepartmentSelfServeEnabled(departmentId.value, enabled);
|
||||
const status = await setDepartmentSelfServeStatus(requestedDepartmentId, enabled);
|
||||
if (
|
||||
requestSequence !== selfServeSaveRequestSequence
|
||||
|| String(departmentId.value) !== requestedDepartmentId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
selfServeEnabled.value = status.enabled;
|
||||
if (status.hasAutoDeactivation) {
|
||||
selfServeAutoDeactivation.value = status.autoDeactivation;
|
||||
} else {
|
||||
await loadSelfServeEnabled({
|
||||
preserveEnabledOnError: true,
|
||||
requestedDepartmentId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (
|
||||
requestSequence === selfServeSaveRequestSequence
|
||||
&& String(departmentId.value) === requestedDepartmentId
|
||||
) {
|
||||
selfServeEnabled.value = previousValue;
|
||||
selfServeEnabledError.value = "Unable to update self-serve status.";
|
||||
}
|
||||
} finally {
|
||||
if (
|
||||
requestSequence === selfServeSaveRequestSequence
|
||||
&& String(departmentId.value) === requestedDepartmentId
|
||||
) {
|
||||
isSavingSelfServeEnabled.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getDepartmentVariables();
|
||||
getWorkfeedDepartmentOptions();
|
||||
loadSelfServeEnabled();
|
||||
watch(
|
||||
() => router.currentRoute.value.params.departmentId,
|
||||
(nextDepartmentId) => {
|
||||
selfServeSaveRequestSequence++;
|
||||
isSavingSelfServeEnabled.value = false;
|
||||
setDepartment(nextDepartmentId);
|
||||
departmentVariables.value = [];
|
||||
selfServeEnabled.value = false;
|
||||
selfServeAutoDeactivation.value = {
|
||||
at: null,
|
||||
timezone: "Europe/Copenhagen",
|
||||
label: "NEVER",
|
||||
};
|
||||
selfServeEnabledError.value = "";
|
||||
getDepartmentVariables(String(nextDepartmentId));
|
||||
loadSelfServeEnabled();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -287,13 +381,15 @@ loadSelfServeEnabled();
|
||||
:subtitle="$t('superuser_dashboard.department_pages.modules.subtitle')"
|
||||
>
|
||||
<template #default>
|
||||
<div :title="selfServeAutoDeactivationText">
|
||||
<ConfigurationSwitch
|
||||
title="Selvvask"
|
||||
description="Aktiver selvvask i denne afdeling."
|
||||
:description="selfServeAutoDeactivationText"
|
||||
:value="selfServeEnabled"
|
||||
:disabled="isLoadingSelfServeEnabled || isSavingSelfServeEnabled"
|
||||
:on-switch="updateSelfServeEnabled"
|
||||
></ConfigurationSwitch>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="selfServeEnabledError">
|
||||
{{ selfServeEnabledError }}
|
||||
</p>
|
||||
|
||||
@@ -84,6 +84,7 @@ import { __pushMock, __routeRef } from "vue-router";
|
||||
const flushMicrotasks = async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
const createDeferred = () => {
|
||||
|
||||
@@ -9,14 +9,47 @@ const source = readFileSync(
|
||||
|
||||
describe("DepartmentModulesSetup self-serve management regression", () => {
|
||||
it("loads and updates department self-serve status through shared helpers", () => {
|
||||
expect(source).toContain("getDepartmentSelfServeEnabled");
|
||||
expect(source).toContain("setDepartmentSelfServeEnabled");
|
||||
expect(source).toContain("getDepartmentSelfServeStatus");
|
||||
expect(source).toContain("setDepartmentSelfServeStatus");
|
||||
expect(source).toContain("loadSelfServeEnabled()");
|
||||
expect(source).toContain("updateSelfServeEnabled");
|
||||
});
|
||||
|
||||
it("renders a dedicated self-serve module switch in setup UI", () => {
|
||||
expect(source).toContain('title="Selvvask"');
|
||||
expect(source).toContain("Aktiver selvvask i denne afdeling.");
|
||||
expect(source).toContain(':title="selfServeAutoDeactivationText"');
|
||||
expect(source).toContain(':description="selfServeAutoDeactivationText"');
|
||||
expect(source).toContain("NEVER");
|
||||
});
|
||||
|
||||
it("refetches scheduling metadata for legacy message-only update responses", () => {
|
||||
expect(source).toContain("if (status.hasAutoDeactivation)");
|
||||
expect(source).toContain("preserveEnabledOnError: true");
|
||||
expect(source).toContain("requestedDepartmentId");
|
||||
expect(source).toContain("if (!preserveEnabledOnError)");
|
||||
});
|
||||
|
||||
it("reloads self-serve status safely when the route department changes", () => {
|
||||
expect(source).toContain("watch(");
|
||||
expect(source).toContain("router.currentRoute.value.params.departmentId");
|
||||
expect(source).toContain("selfServeStatusRequestSequence");
|
||||
expect(source).toContain("requestSequence !== selfServeStatusRequestSequence");
|
||||
expect(source).toContain("{ immediate: true }");
|
||||
});
|
||||
|
||||
it("invalidates an in-flight save when the route department changes", () => {
|
||||
expect(source).toContain("selfServeSaveRequestSequence");
|
||||
expect(source).toContain("const requestSequence = ++selfServeSaveRequestSequence");
|
||||
expect(source).toContain("requestSequence !== selfServeSaveRequestSequence");
|
||||
expect(source).toContain("selfServeSaveRequestSequence++");
|
||||
expect(source).toContain("isSavingSelfServeEnabled.value = false");
|
||||
});
|
||||
|
||||
it("ignores department-variable responses from a previously selected route department", () => {
|
||||
expect(source).toContain("departmentVariablesRequestSequence");
|
||||
expect(source).toContain("const requestSequence = ++departmentVariablesRequestSequence");
|
||||
expect(source).toContain("requestSequence !== departmentVariablesRequestSequence");
|
||||
expect(source).toContain("String(departmentId.value) !== requestedDepartmentId");
|
||||
expect(source).toContain("getDepartmentVariables(String(nextDepartmentId))");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const source = readFileSync(
|
||||
join(
|
||||
process.cwd(),
|
||||
"src/views/dashboards/departmentDashboard/modules/time-bookings/DepartmentTimeBookingsOpeningHours.vue"
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
describe("Department opening-hours route synchronization", () => {
|
||||
it("clears and reloads opening hours when the selected department changes", () => {
|
||||
expect(source).toContain("departmentId.value, isSuperuserDepartmentRoute.value");
|
||||
expect(source).toContain("{ immediate: true }");
|
||||
expect(source).toContain("opening_hours.value = null");
|
||||
expect(source).toContain("department: selectedDepartmentId");
|
||||
});
|
||||
|
||||
it("ignores a slower response from the previously selected department", () => {
|
||||
expect(source).toContain("const requestId = ++openingHoursRequest");
|
||||
expect(source).toContain("requestId !== openingHoursRequest");
|
||||
});
|
||||
|
||||
it("initializes the selected superuser department for direct and reloaded routes", () => {
|
||||
expect(source).toContain("setDepartment as setSelectedDepartment");
|
||||
expect(source).toContain("if (isSuperuserRoute)");
|
||||
expect(source).toContain("setSelectedDepartment(selectedDepartmentId)");
|
||||
});
|
||||
|
||||
it("initializes superuser state when only the route context changes", () => {
|
||||
expect(source).toContain("isSuperuserDepartmentRoute.value");
|
||||
expect(source).toContain("[selectedDepartmentId, isSuperuserRoute]");
|
||||
expect(source).toContain("selectedDepartmentId !== previousDepartmentId");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user