Add unit tests for Orders navigation count refresh and fallback customer caching, update POS mobile accessibility labels:
- Introduced `orders-draft-count-refresh.spec.js` for testing `Orders` navigation hooks on customer changes and order deletions. - Enhanced `use-draft-transaction-customer.spec.js` with tests for clearing cached fallback customers. - Updated POS mobile components with accessibility labels and localization for image viewer and customer suggestion buttons. - Added support for `vehicleCustomerSuggestionsGet` handling in e2e mocks and tests.
This commit is contained in:
+5
-10
@@ -1,11 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref } from 'vue';
|
||||
import VerifiedCustomer from "@/components/viewport/elements/icons/VerifiedCustomer.vue";
|
||||
import BookedCustomer from "@/components/viewport/elements/icons/BookedCustomer.vue";
|
||||
import UnknownCustomer from "@/components/viewport/elements/icons/UnknownCustomer.vue";
|
||||
import KnownCustomer from "@/components/viewport/elements/icons/KnownCustomer.vue";
|
||||
import CardPaymentCustomer from "@/components/viewport/elements/icons/CardPaymentCustomer.vue";
|
||||
import { VehicleStatusKey, determineStatusKey, VehicleStatusComponents, statusKeyToComponent } from '../objects/PosVehicleStatus.vue';
|
||||
import { defineProps, type PropType } from 'vue';
|
||||
import { VehicleStatusKey, statusKeyToComponent } from '../objects/PosVehicleStatus.vue';
|
||||
|
||||
const getBackgroundColor = (status: VehicleStatusKey) => {
|
||||
switch (status) {
|
||||
@@ -27,8 +22,8 @@ const getBackgroundColor = (status: VehicleStatusKey) => {
|
||||
|
||||
const props = defineProps({
|
||||
status: {
|
||||
type: String as () => VehicleStatusKey,
|
||||
required: true
|
||||
type: String as PropType<VehicleStatusKey>,
|
||||
default: 'unknown'
|
||||
},
|
||||
registrationNumber: {
|
||||
type: String,
|
||||
@@ -152,4 +147,4 @@ const props = defineProps({
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
+1
@@ -335,6 +335,7 @@ const step2 = async () => {
|
||||
|
||||
popups.select("complete_booking", {
|
||||
props: {
|
||||
safetySeal: getResolvedMobileSafetySeal(),
|
||||
onCompleteWithCertificate: onCompleteBookingWithSafetySeal,
|
||||
onCompleteWithoutCertificate: onCompleteBookingWithSafetySeal,
|
||||
},
|
||||
|
||||
+11
-2
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineEmits, ref } from "vue";
|
||||
import { metadata, popups } from "../objects/PosDepartmentStepMobileFlow.vue";
|
||||
import { getResolvedMobileSafetySeal } from "../objects/mobileOrderCompletion.js";
|
||||
import SessionUser from "@/components/session/token/SessionUser.vue";
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
@@ -8,7 +9,15 @@ const emit = defineEmits<{
|
||||
|
||||
const popupProps = computed(() => popups.get()?.props || {});
|
||||
const isSubmitting = ref(false);
|
||||
const safetySealNumber = ref("");
|
||||
const resolveExistingSafetySeal = () => {
|
||||
const popupSafetySeal = popupProps.value?.safetySeal;
|
||||
if (popupSafetySeal !== null && popupSafetySeal !== undefined) {
|
||||
return String(popupSafetySeal).trim();
|
||||
}
|
||||
|
||||
return getResolvedMobileSafetySeal();
|
||||
};
|
||||
const safetySealNumber = ref(resolveExistingSafetySeal());
|
||||
|
||||
const closePopup = () => {
|
||||
emit('close');
|
||||
@@ -64,7 +73,7 @@ const completeWithCertificate = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedSafetySeal = String(safetySealNumber.value ?? "").trim();
|
||||
const normalizedSafetySeal = String(safetySealNumber.value ?? "").trim() || resolveExistingSafetySeal();
|
||||
const callback = popupProps.value?.onCompleteWithCertificate || popupProps.value?.onComplete;
|
||||
if (typeof callback === "function") {
|
||||
await runPopupCompletionCallback(callback, normalizedSafetySeal);
|
||||
|
||||
+23
-4
@@ -36,6 +36,15 @@ const toPositiveInteger = (value: unknown) => {
|
||||
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
|
||||
};
|
||||
|
||||
const resolveSelectedCustomerId = (value: unknown) => {
|
||||
if (value && typeof value === "object") {
|
||||
const candidate = value as Record<string, unknown>;
|
||||
return toPositiveInteger(candidate.customerId ?? candidate.customerNumber ?? candidate.id ?? null);
|
||||
}
|
||||
|
||||
return toPositiveInteger(value);
|
||||
};
|
||||
|
||||
const getCurrentCustomerNumber = () => toPositiveInteger(customer_id.value);
|
||||
|
||||
const getResolvedQuickAction = () => {
|
||||
@@ -116,14 +125,24 @@ const focusCustomerSearchInput = async () => {
|
||||
};
|
||||
|
||||
const onClick = async (result: Partial<PosSearchResult>) => {
|
||||
const selectedCustomerId = resolveSelectedCustomerId(result?.customerId);
|
||||
if (!selectedCustomerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeQuickAction.value = CUSTOMER_PICKER_MODE_INVOICE;
|
||||
await searchAndSelectCustomer(result.customerId);
|
||||
metadata.setCustomerId(result.customerId);
|
||||
await searchAndSelectCustomer(selectedCustomerId);
|
||||
metadata.setCustomerId(selectedCustomerId);
|
||||
emit("close");
|
||||
};
|
||||
|
||||
const onClickSuggestion = async (customerId: number) => {
|
||||
await onClick({ customerId });
|
||||
const onClickSuggestion = async (payload: unknown) => {
|
||||
const selectedCustomerId = resolveSelectedCustomerId(payload);
|
||||
if (!selectedCustomerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await onClick({ customerId: selectedCustomerId });
|
||||
};
|
||||
|
||||
const onSelectCustomerInvoice = async () => {
|
||||
|
||||
+36
-8
@@ -13,6 +13,7 @@ import {
|
||||
doesTransactionContainWashCertificateProduct,
|
||||
WASH_CERTIFICATE_PRODUCT_ID,
|
||||
} from "@/components/displays/department/pos/utils/washCertificate.js";
|
||||
import { determineStatusKey, type VehicleStatusKey } from './PosVehicleStatus.vue';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
/** Views */
|
||||
@@ -433,21 +434,48 @@ const setActiveVehicleIndex = (index: number): void => {
|
||||
activeVehicleIndex.value = index;
|
||||
}
|
||||
|
||||
const normalizeVehicleStatus = (vehicle: PosVehicle): VehicleStatusKey => {
|
||||
if (typeof vehicle.status === 'string' && vehicle.status.length > 0) {
|
||||
return vehicle.status as VehicleStatusKey;
|
||||
}
|
||||
|
||||
return determineStatusKey({
|
||||
hasBooking: Array.isArray(vehicle.booking_matches) ? vehicle.booking_matches.length > 0 : !!vehicle.booking_id,
|
||||
customer_id: vehicle.customer_id,
|
||||
customer_name: vehicle.customer_name,
|
||||
barred: vehicle.barred,
|
||||
wash_subscription: vehicle.wash_subscription,
|
||||
});
|
||||
}
|
||||
|
||||
const normalizeVehicleSelection = (vehicle: PosVehicle | null): PosVehicle | null => {
|
||||
if (!vehicle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...vehicle,
|
||||
reg: String(vehicle.reg ?? ''),
|
||||
status: normalizeVehicleStatus(vehicle),
|
||||
};
|
||||
}
|
||||
|
||||
// Function to select a vehicle
|
||||
const selectVehicle = (index: number, vehicle: PosVehicle | null) => {
|
||||
const normalizedVehicle = normalizeVehicleSelection(vehicle);
|
||||
switch (index) {
|
||||
case 1:
|
||||
vehicle_1.value = vehicle;
|
||||
vehicle_1.value = normalizedVehicle;
|
||||
// If the vehicle has a reference, and it is currently not set, set it
|
||||
if (vehicle?.reference && !metadata.getReference()) {
|
||||
metadata.setReference(vehicle.reference);
|
||||
if (normalizedVehicle?.reference && !metadata.getReference()) {
|
||||
metadata.setReference(normalizedVehicle.reference);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
vehicle_2.value = vehicle;
|
||||
vehicle_2.value = normalizedVehicle;
|
||||
break;
|
||||
case 3:
|
||||
vehicle_3.value = vehicle;
|
||||
vehicle_3.value = normalizedVehicle;
|
||||
break;
|
||||
default:
|
||||
console.warn(`Invalid vehicle index: ${index}. Please use 1, 2, or 3.`);
|
||||
@@ -1659,9 +1687,9 @@ const retrievePos = () => {
|
||||
|
||||
// Restore vehicles data
|
||||
if (parsedData.vehicles) {
|
||||
vehicles.vehicle_1.value = parsedData.vehicles.vehicle_1 ?? null;
|
||||
vehicles.vehicle_2.value = parsedData.vehicles.vehicle_2 ?? null;
|
||||
vehicles.vehicle_3.value = parsedData.vehicles.vehicle_3 ?? null;
|
||||
vehicles.vehicle_1.value = normalizeVehicleSelection(parsedData.vehicles.vehicle_1 ?? null);
|
||||
vehicles.vehicle_2.value = normalizeVehicleSelection(parsedData.vehicles.vehicle_2 ?? null);
|
||||
vehicles.vehicle_3.value = normalizeVehicleSelection(parsedData.vehicles.vehicle_3 ?? null);
|
||||
vehicles.activeVehicleIndex.value = parsedData.vehicles.activeVehicleIndex ?? 1;
|
||||
}
|
||||
|
||||
|
||||
+32
-12
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {defineEmits, ref, watch, computed} from "vue";
|
||||
import {metadata, popups, actionButtons} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
|
||||
import SessionUser from "@/components/session/token/SessionUser.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { popups } from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
|
||||
// Define the close event to emit when the component is closed
|
||||
const emit = defineEmits(["close"]);
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = popups.get()?.props; // props: {base64String: attachment.base64String, filename: attachment.filename}
|
||||
const imageZoomDefault = ref<number>(1);
|
||||
@@ -134,36 +135,55 @@ const handleMouseWheel = (e: WheelEvent) => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<button class="button is-fullwidth is-white" :disabled="imageZoom >= imageZoomMax" @pointerdown="imageZoomIn" @pointerup="clearInterval" @pointerleave="clearInterval">
|
||||
<button
|
||||
class="button is-fullwidth is-white"
|
||||
data-testid="pos-mobile-image-viewer-zoom-in"
|
||||
:disabled="imageZoom >= imageZoomMax"
|
||||
@pointerdown="imageZoomIn"
|
||||
@pointerup="clearInterval"
|
||||
@pointerleave="clearInterval"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</span>
|
||||
<small>Zoom</small>
|
||||
<small>{{ t("global.zoom") }}</small>
|
||||
</button>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<button class="button is-fullwidth is-white" :disabled="imageZoom <= imageZoomMin" @pointerdown="imageZoomOut" @pointerup="clearInterval" @pointerleave="clearInterval">
|
||||
<button
|
||||
class="button is-fullwidth is-white"
|
||||
data-testid="pos-mobile-image-viewer-zoom-out"
|
||||
:disabled="imageZoom <= imageZoomMin"
|
||||
@pointerdown="imageZoomOut"
|
||||
@pointerup="clearInterval"
|
||||
@pointerleave="clearInterval"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</span>
|
||||
<small>Zoom ud</small>
|
||||
<small>{{ t("global.zoom_out") }}</small>
|
||||
</button>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<button class="button is-fullwidth is-white" @click="imageZoomReset" :disabled="imageZoom === imageZoomDefault">
|
||||
<button
|
||||
class="button is-fullwidth is-white"
|
||||
data-testid="pos-mobile-image-viewer-reset"
|
||||
@click="imageZoomReset"
|
||||
:disabled="imageZoom === imageZoomDefault"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-redo"></i>
|
||||
</span>
|
||||
<small>Nulstil</small>
|
||||
<small>{{ t("global.reset") }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Close button -->
|
||||
<button class="button is-fullwidth is-white mt-2" @click="close">
|
||||
<button class="button is-fullwidth is-white mt-2" data-testid="pos-mobile-image-viewer-close" @click="close">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-times"></i>
|
||||
</span>
|
||||
<small>Luk</small>
|
||||
<small>{{ t("global.close") }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -199,4 +219,4 @@ const handleMouseWheel = (e: WheelEvent) => {
|
||||
width: 95vw;
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -165,7 +165,7 @@ if (props.reg_1) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field" :class="{ 'is-hidden': isCustomerSelected() }">
|
||||
<div class="field" :class="{ 'is-hidden': isCustomerSelected() }" data-testid="pos-customer-suggestions">
|
||||
<div class="control">
|
||||
<template v-if="customer_suggestions.length > 0">
|
||||
<div class="columns is-multiline is-mobile">
|
||||
@@ -189,6 +189,7 @@ if (props.reg_1) {
|
||||
<button
|
||||
class="button is-light is-fullwidth"
|
||||
@click="onClick(customer)"
|
||||
:data-testid="`pos-customer-suggestion-select-${customer.customer_number}`"
|
||||
:class="{
|
||||
'is-loading': isSettingCustomerTo(customer.customer_number),
|
||||
'is-danger': customer.barred,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { authenticatedRequest } from "@/components/session/authenticatedRequest.
|
||||
import { editOrderItem, getOrderItems } from "@/components/shop/OrdersItems.vue";
|
||||
import {createApp} from "vue";
|
||||
import i18n from '@/i18n';
|
||||
import { dispatchNavigationCountRefresh } from "@/components/models/navigation/items/navigationCountEvents.js";
|
||||
|
||||
const t = (key) => i18n.global.t(key);
|
||||
const mountOrderCustomerAssignmentModal = ({
|
||||
@@ -150,6 +151,10 @@ const normalizePositiveInteger = (value) => {
|
||||
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
|
||||
};
|
||||
|
||||
const refreshDraftNavigationCount = () => {
|
||||
dispatchNavigationCountRefresh();
|
||||
};
|
||||
|
||||
const getFinalProductPriceForCustomer = async (productId, departmentId, customerId) => {
|
||||
const normalizedProductId = normalizePositiveInteger(productId);
|
||||
const normalizedDepartmentId = normalizePositiveInteger(departmentId);
|
||||
@@ -455,7 +460,7 @@ const assignDraftOrderCustomer = async ({
|
||||
};
|
||||
},
|
||||
add: async (customer_id, cashier_id, department_id, reference, reg_1, reg_2, reg_3, notes, invoice_collection_id) => {
|
||||
return ObjectsGlobal.add.object(
|
||||
const response = await ObjectsGlobal.add.object(
|
||||
Orders.meta.endpoint,
|
||||
{
|
||||
customer_id: parseInt(customer_id),
|
||||
@@ -469,15 +474,19 @@ const assignDraftOrderCustomer = async ({
|
||||
invoice_collection_id: parseInt(invoice_collection_id),
|
||||
}
|
||||
);
|
||||
refreshDraftNavigationCount();
|
||||
return response;
|
||||
},
|
||||
set: {
|
||||
customer_id: async (id, customer_id) => {
|
||||
return ObjectsGlobal.set.column(
|
||||
const response = await ObjectsGlobal.set.column(
|
||||
Orders.meta.endpoint,
|
||||
id,
|
||||
"customer_id",
|
||||
parseInt(customer_id)
|
||||
)
|
||||
);
|
||||
refreshDraftNavigationCount();
|
||||
return response;
|
||||
},
|
||||
cashier_id: async (id, cashier_id) => {
|
||||
return ObjectsGlobal.set.column(
|
||||
@@ -629,7 +638,9 @@ const assignDraftOrderCustomer = async ({
|
||||
},
|
||||
delete: {
|
||||
single: async (id) => {
|
||||
return ObjectsGlobal.delete.object(Orders.meta.endpoint, id);
|
||||
const response = await ObjectsGlobal.delete.object(Orders.meta.endpoint, id);
|
||||
refreshDraftNavigationCount();
|
||||
return response;
|
||||
},
|
||||
},
|
||||
functions: {
|
||||
|
||||
@@ -16,6 +16,16 @@ const resolveConfiguredDraftTransactionCustomerNumber = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const setDraftTransactionCustomerNumber = (customerNumber) => {
|
||||
const normalizedCustomerNumber = toPositiveInteger(customerNumber);
|
||||
|
||||
SessionUser.runtimeConfig.economic.transactionDraftCustomerNumber.value = normalizedCustomerNumber;
|
||||
fallbackDraftTransactionCustomerNumber.value = normalizedCustomerNumber;
|
||||
draftTransactionCustomerConfigRequest = null;
|
||||
|
||||
return normalizedCustomerNumber;
|
||||
};
|
||||
|
||||
export const ensureDraftTransactionCustomerLoaded = async () => {
|
||||
if (resolveConfiguredDraftTransactionCustomerNumber() !== null) {
|
||||
return resolveConfiguredDraftTransactionCustomerNumber();
|
||||
|
||||
@@ -348,6 +348,7 @@
|
||||
"edit": "Rediger",
|
||||
"enter_safety_seal_number": "Indtast sikkerhedsforseglingsnummer",
|
||||
"generate_wash_certificate": "Generer vaskecertifikat",
|
||||
"wash_certificate": "Vaskecertifikat",
|
||||
"invoice_email": "Faktura-e-mail",
|
||||
"license_plate": "Nummerplade",
|
||||
"license_plates": "Nummerplader",
|
||||
@@ -1960,6 +1961,8 @@
|
||||
"required": "Påkrævet",
|
||||
"requires_action": "Kræver handling",
|
||||
"reset": "Nulstil",
|
||||
"zoom": "Zoom",
|
||||
"zoom_out": "Zoom ud",
|
||||
"rows": "Rækker",
|
||||
"save": "Gem",
|
||||
"scanning": "Scanner",
|
||||
|
||||
@@ -348,6 +348,7 @@
|
||||
"edit": "Bearbeiten",
|
||||
"enter_safety_seal_number": "Sicherheitsplomben-Nummer eingeben",
|
||||
"generate_wash_certificate": "Waschzertifikat erstellen",
|
||||
"wash_certificate": "Waschzertifikat",
|
||||
"invoice_email": "Rechnungs-E-Mail",
|
||||
"license_plate": "Kennzeichen",
|
||||
"license_plates": "Kennzeichen",
|
||||
@@ -1926,6 +1927,8 @@
|
||||
"required": "Erforderlich",
|
||||
"requires_action": "Aktion erforderlich",
|
||||
"reset": "Zurücksetzen",
|
||||
"zoom": "Zoom",
|
||||
"zoom_out": "Herauszoomen",
|
||||
"rows": "Zeilen",
|
||||
"save": "Speichern",
|
||||
"scanning": "Scannen",
|
||||
|
||||
@@ -348,6 +348,7 @@
|
||||
"edit": "Edit",
|
||||
"enter_safety_seal_number": "Enter safety seal number",
|
||||
"generate_wash_certificate": "Generate wash certificate",
|
||||
"wash_certificate": "Wash certificate",
|
||||
"invoice_email": "Invoice email",
|
||||
"license_plate": "License plate",
|
||||
"license_plates": "License plates",
|
||||
@@ -1960,6 +1961,8 @@
|
||||
"required": "Required",
|
||||
"requires_action": "Requires action",
|
||||
"reset": "Reset",
|
||||
"zoom": "Zoom",
|
||||
"zoom_out": "Zoom out",
|
||||
"rows": "Rows",
|
||||
"save": "Save",
|
||||
"scanning": "Scanning",
|
||||
|
||||
@@ -348,6 +348,7 @@
|
||||
"edit": "Redigere",
|
||||
"enter_safety_seal_number": "Skriv inn sikkerhetsforseglingsnummeret",
|
||||
"generate_wash_certificate": "Generer vaskesertifikat",
|
||||
"wash_certificate": "Vaskesertifikat",
|
||||
"invoice_email": "Faktura e-post",
|
||||
"license_plate": "Nummerskilt",
|
||||
"license_plates": "Nummerskilt",
|
||||
@@ -1926,6 +1927,8 @@
|
||||
"required": "Påkrevd",
|
||||
"requires_action": "Krever handling",
|
||||
"reset": "Nullstill",
|
||||
"zoom": "Zoom",
|
||||
"zoom_out": "Zoom ut",
|
||||
"rows": "Rader",
|
||||
"save": "Lagre",
|
||||
"scanning": "Skanning",
|
||||
|
||||
@@ -348,6 +348,7 @@
|
||||
"edit": "Edit",
|
||||
"enter_safety_seal_number": "Ange säkerhetsfärseglingsnummer",
|
||||
"generate_wash_certificate": "Generera tvättcertifikat",
|
||||
"wash_certificate": "Tvättcertifikat",
|
||||
"invoice_email": "Faktura-e-post",
|
||||
"license_plate": "License plate",
|
||||
"license_plates": "License plates",
|
||||
@@ -1926,6 +1927,8 @@
|
||||
"required": "Obligatorisk",
|
||||
"requires_action": "Kräver åtgärd",
|
||||
"reset": "Återställ",
|
||||
"zoom": "Zoom",
|
||||
"zoom_out": "Zooma ut",
|
||||
"rows": "Rows",
|
||||
"save": "Spara",
|
||||
"scanning": "Scanning",
|
||||
|
||||
@@ -13,6 +13,7 @@ import ConfigurationSelect from "@/components/displays/superuser/configuration/C
|
||||
import Swal from "sweetalert2";
|
||||
import ConfigurationInputNumber from "@/components/displays/superuser/configuration/ConfigurationInputNumber.vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { setDraftTransactionCustomerNumber } from "@/composables/useDraftTransactionCustomer.js";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -88,6 +89,21 @@ const parseNullableConfigNumber = (variable) => {
|
||||
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
|
||||
};
|
||||
|
||||
const saveDraftTransactionCustomerNumber = async (customerNumber) => {
|
||||
const normalizedCustomerNumber = Number.isInteger(Number.parseInt(String(customerNumber ?? ''), 10))
|
||||
&& Number.parseInt(String(customerNumber ?? ''), 10) > 0
|
||||
? Number.parseInt(String(customerNumber ?? ''), 10)
|
||||
: null;
|
||||
|
||||
const response = await SessionUser.superUser.modules.economic.config.transactionDraftCustomerNumber.set(
|
||||
normalizedCustomerNumber
|
||||
);
|
||||
|
||||
setDraftTransactionCustomerNumber(normalizedCustomerNumber);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const load = async () => {
|
||||
await getLayouts();
|
||||
await getModuleConfig();
|
||||
@@ -206,7 +222,7 @@ load();
|
||||
:title="$t('configuration.economic.transaction_draft_customer_number')"
|
||||
:description="$t('configuration.economic.transaction_draft_customer_number_desc')"
|
||||
:value="parseNullableConfigNumber('transactionDraftCustomerNumber')"
|
||||
:on-save="SessionUser.superUser.modules.economic.config.transactionDraftCustomerNumber.set"
|
||||
:on-save="saveDraftTransactionCustomerNumber"
|
||||
:min="0"
|
||||
/>
|
||||
</ConfigurationCategory>
|
||||
|
||||
@@ -116,6 +116,12 @@ function buildTodayTimestamp(time = "08:00:00.000Z") {
|
||||
return `${todayIsoDate}T${time}`;
|
||||
}
|
||||
|
||||
async function forceLocale(page, locale = "en") {
|
||||
await page.addInitScript((value) => {
|
||||
window.localStorage.setItem("locale", value);
|
||||
}, locale);
|
||||
}
|
||||
|
||||
function createMultiBookingFixture({ reg, vehicle = {}, bookings = [] }) {
|
||||
const baseFixture = createMobilePosFixture();
|
||||
return createMobilePosFixture({
|
||||
@@ -436,7 +442,7 @@ test("mobile customer popup exposes the draft quick action and selects the confi
|
||||
seedState: {
|
||||
customerId: null,
|
||||
includePrimaryItem: false,
|
||||
reg: "AB12345",
|
||||
reg: "FREE123",
|
||||
reference: "MOBILE-DRAFT",
|
||||
},
|
||||
route: {
|
||||
@@ -469,6 +475,94 @@ test("mobile customer popup exposes the draft quick action and selects the confi
|
||||
await expect(page.getByTestId("pos-mobile-customer-name")).toContainText(MOBILE_DRAFT_TRANSACTION_CUSTOMER_NAME);
|
||||
});
|
||||
|
||||
test("mobile customer popup applies a previous-customer suggestion", async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name !== "chromium-mobile", "Mobile POS order suite is scoped to chromium-mobile.");
|
||||
|
||||
const suggestedCustomerId = 55667788;
|
||||
const suggestedCustomerName = "Suggestion Logistics";
|
||||
const secondarySuggestedCustomerId = 66778899;
|
||||
const fixture = createMobilePosFixture({
|
||||
customersByNumber: {
|
||||
[suggestedCustomerId]: {
|
||||
id: suggestedCustomerId,
|
||||
customerNumber: suggestedCustomerId,
|
||||
name: suggestedCustomerName,
|
||||
address: "Suggestion Street 8",
|
||||
zip: "2630",
|
||||
city: "Taastrup",
|
||||
mobilePhone: "55667788",
|
||||
email: "suggestion@example.com",
|
||||
corporateIdentificationNumber: "55667788",
|
||||
barred: false,
|
||||
economic_customer: suggestedCustomerId,
|
||||
},
|
||||
[secondarySuggestedCustomerId]: {
|
||||
id: secondarySuggestedCustomerId,
|
||||
customerNumber: secondarySuggestedCustomerId,
|
||||
name: "Fallback Suggestion",
|
||||
address: "Fallback Street 9",
|
||||
zip: "2630",
|
||||
city: "Taastrup",
|
||||
mobilePhone: "66778899",
|
||||
email: "fallback@example.com",
|
||||
corporateIdentificationNumber: "66778899",
|
||||
barred: false,
|
||||
economic_customer: secondarySuggestedCustomerId,
|
||||
},
|
||||
},
|
||||
vehicleCustomerSuggestionsByReg: {
|
||||
FREE123: [
|
||||
{
|
||||
id: 901,
|
||||
customer_number: suggestedCustomerId,
|
||||
customer_name: suggestedCustomerName,
|
||||
barred: false,
|
||||
},
|
||||
{
|
||||
id: 902,
|
||||
customer_number: secondarySuggestedCustomerId,
|
||||
customer_name: "Fallback Suggestion",
|
||||
barred: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "pos-mobile-previous-customer-suggestion-token",
|
||||
seedState: {
|
||||
customerId: null,
|
||||
includePrimaryItem: false,
|
||||
reg: "FREE123",
|
||||
reference: "MOBILE-SUGGESTION",
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await openCustomerPopupFromStep1(page);
|
||||
await expect(page.getByTestId("pos-customer-suggestions")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId(`pos-customer-suggestion-select-${suggestedCustomerId}`)).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await page.getByTestId(`pos-customer-suggestion-select-${suggestedCustomerId}`).click();
|
||||
await expect(page.getByTestId("pos-mobile-customer-popup")).toBeHidden({ timeout: 10_000 });
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const snapshot = await getStoredPosSnapshot(page);
|
||||
return snapshot?.metadata?.customerId ?? null;
|
||||
})
|
||||
.toBe(suggestedCustomerId);
|
||||
|
||||
await waitForMobileNextStepCooldown(page);
|
||||
await page.getByTestId("pos-mobile-next-step").click();
|
||||
await waitForMobileStepTwoReady(page);
|
||||
await expect(page.getByTestId("pos-mobile-customer-name")).toContainText(suggestedCustomerName);
|
||||
});
|
||||
|
||||
test("mobile customer popup defaults to customer invoice mode and keeps customer search visible", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
@@ -998,6 +1092,74 @@ test.describe("POS mobile order flow", () => {
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("stored step 1 vehicles without status normalize to unknown without Vue prop warnings", async ({ page }) => {
|
||||
const fixture = createMobilePosFixture();
|
||||
const consoleProblems = [];
|
||||
const pageErrors = [];
|
||||
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error" || message.type() === "warning" || message.text().includes('prop "status"')) {
|
||||
consoleProblems.push(`${message.type()}: ${message.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.stack || error.message);
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "stored-step1-missing-status-token",
|
||||
seedState: false,
|
||||
route: {
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
const storedValue = window.localStorage.getItem("pos");
|
||||
const snapshot = storedValue ? JSON.parse(storedValue) : {};
|
||||
snapshot.vehicles = snapshot.vehicles || {};
|
||||
snapshot.vehicles.vehicle_1 = {
|
||||
reg: "EC2123",
|
||||
customer_id: null,
|
||||
type: null,
|
||||
booking_id: null,
|
||||
booking_matches: [],
|
||||
wash_subscription: false,
|
||||
barred: false,
|
||||
reference: "",
|
||||
last_order_id: null,
|
||||
};
|
||||
snapshot.vehicles.vehicle_2 = null;
|
||||
snapshot.vehicles.vehicle_3 = null;
|
||||
snapshot.vehicles.activeVehicleIndex = 1;
|
||||
window.localStorage.setItem("pos", JSON.stringify(snapshot));
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
await expect(page.getByTestId("pos-mobile-step-1-shell")).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const snapshot = await getStoredPosSnapshot(page);
|
||||
return {
|
||||
reg: snapshot?.vehicles?.vehicle_1?.reg ?? null,
|
||||
status: snapshot?.vehicles?.vehicle_1?.status ?? null,
|
||||
};
|
||||
},
|
||||
{ timeout: 10_000 }
|
||||
)
|
||||
.toEqual({
|
||||
reg: "EC2123",
|
||||
status: "unknown",
|
||||
});
|
||||
|
||||
const combinedProblems = [...consoleProblems, ...pageErrors].join("\n");
|
||||
expect(combinedProblems).not.toContain('Invalid prop: type check failed for prop "status"');
|
||||
expect(combinedProblems).not.toContain('Expected String with value "undefined"');
|
||||
});
|
||||
|
||||
test("matched vehicle manual input seeds the step 2 reference and primary product defaults", async ({ page }) => {
|
||||
const fixture = createMobilePosFixture();
|
||||
await createOrderFromStep1(page, fixture, {
|
||||
@@ -2292,6 +2454,8 @@ test.describe("POS mobile order flow", () => {
|
||||
},
|
||||
});
|
||||
|
||||
await forceLocale(page, "en");
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "mobile-action-metadata-token",
|
||||
seedState: {
|
||||
@@ -2317,7 +2481,7 @@ test.describe("POS mobile order flow", () => {
|
||||
await expect(clearAllButton).toHaveAttribute("data-action-key", "pos-mobile-clear-all");
|
||||
await expect(completeButton).toHaveAttribute("data-copy-key", "complete");
|
||||
await expect(clearAllButton).toHaveAttribute("data-copy-key", "clear_all");
|
||||
await expect(completeButton).toContainText(/Afslut|Fuldf/);
|
||||
await expect(completeButton).toContainText(/Complete|Afslut|Fuldf/);
|
||||
await expectCleanActionText(completeButton);
|
||||
await expectCleanActionText(clearAllButton);
|
||||
|
||||
@@ -2329,21 +2493,63 @@ test.describe("POS mobile order flow", () => {
|
||||
const attachmentsToggle = page.getByTestId("pos-mobile-attachments-toggle");
|
||||
await expect(attachmentsToggle).toBeVisible({ timeout: 10_000 });
|
||||
await expect(attachmentsToggle).toHaveAttribute("data-action-key", "pos-mobile-attachments-toggle");
|
||||
await expect(attachmentsToggle).toContainText("Attachments");
|
||||
await expectCleanActionText(attachmentsToggle);
|
||||
|
||||
await attachmentsToggle.click();
|
||||
|
||||
for (const actionKey of [
|
||||
"pos-mobile-attachment-view-take-picture",
|
||||
"pos-mobile-attachment-view-close",
|
||||
"pos-mobile-attachments-upload-file",
|
||||
"pos-mobile-attachments-wash-certificate",
|
||||
]) {
|
||||
const expectedLabelsByActionKey = {
|
||||
"pos-mobile-attachment-view-take-picture": "Take picture",
|
||||
"pos-mobile-attachment-view-close": "Close",
|
||||
"pos-mobile-attachments-upload-file": "Upload",
|
||||
"pos-mobile-attachments-wash-certificate": "Wash certificate",
|
||||
};
|
||||
|
||||
for (const [actionKey, label] of Object.entries(expectedLabelsByActionKey)) {
|
||||
const action = getByActionKey(page, actionKey);
|
||||
await expect(action).toBeVisible({ timeout: 10_000 });
|
||||
await expect(action).toHaveAttribute("data-action-key", actionKey);
|
||||
await expect(action).toContainText(label);
|
||||
await expectCleanActionText(action);
|
||||
}
|
||||
|
||||
await expect(page.locator("body")).not.toContainText("admin.pos.wash_certificate");
|
||||
});
|
||||
|
||||
test("mobile image viewer uses localized controls", async ({ page }) => {
|
||||
const imageAttachment = {
|
||||
filename: "damage.svg",
|
||||
base64String:
|
||||
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiI+PHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSJyZWQiLz48L3N2Zz4=",
|
||||
};
|
||||
|
||||
await forceLocale(page, "en");
|
||||
|
||||
await setupMobilePosPage(page, createMobilePosFixture(), {
|
||||
token: "mobile-image-viewer-locale-token",
|
||||
seedState: {
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
reg: "AB12345",
|
||||
includePrimaryItem: false,
|
||||
attachmentsBase64: [imageAttachment],
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-step-1")).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByTestId("pos-mobile-attachments-toggle").click();
|
||||
|
||||
const thumbnail = page.getByAltText("damage.svg");
|
||||
await expect(thumbnail).toBeVisible({ timeout: 10_000 });
|
||||
await thumbnail.click();
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-zoom-in")).toContainText("Zoom", { timeout: 10_000 });
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-zoom-out")).toContainText("Zoom out");
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-reset")).toContainText("Reset");
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-close")).toContainText("Close");
|
||||
});
|
||||
|
||||
test("step 2 sync is idempotent when the order already matches the local transaction", async ({ page }) => {
|
||||
@@ -2649,6 +2855,63 @@ test.describe("POS mobile order flow", () => {
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("booking completion popup reuses the step 2 safety seal without asking for it again", async ({ page }) => {
|
||||
const orderId = 9412;
|
||||
const fixture = createMobilePosFixture({
|
||||
ordersById: {
|
||||
[orderId]: buildRegularOrder(orderId, {
|
||||
reg_1: "SEAL321",
|
||||
reference: "",
|
||||
}),
|
||||
},
|
||||
orderItemsByOrderId: {
|
||||
[orderId]: [],
|
||||
},
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "mobile-booking-prefilled-seal-token",
|
||||
seedState: {
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
reg: "SEAL321",
|
||||
reference: "",
|
||||
includePrimaryItem: false,
|
||||
vehicleType: null,
|
||||
bookingId: 8103,
|
||||
vehicleStatus: "booked",
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 2,
|
||||
orderId,
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-step-2")).toBeVisible({ timeout: 10_000 });
|
||||
await waitForBookingHydration(page, {
|
||||
primaryId: 53,
|
||||
addonProductIds: [41],
|
||||
});
|
||||
|
||||
const safetySealInput = page.getByTestId("pos-mobile-safety-seal-step-2-input");
|
||||
await expect(safetySealInput).toBeVisible({ timeout: 10_000 });
|
||||
await safetySealInput.fill("5150");
|
||||
|
||||
await page.getByTestId("pos-mobile-next-step").click();
|
||||
|
||||
const popupSafetySealInput = page.getByTestId("pos-mobile-booking-safety-seal-input");
|
||||
await expect(page.getByTestId("pos-mobile-popup")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(popupSafetySealInput).toHaveValue("5150");
|
||||
|
||||
await page.getByTestId("pos-mobile-booking-complete-with-certificate").click();
|
||||
|
||||
await expect.poll(() => fixture.requestCounters.bookingComplete, { timeout: 10_000 }).toBe(1);
|
||||
await expect.poll(() => fixture.bookingsById[8103]?.status ?? "").toBe("completed");
|
||||
await expect.poll(() => String(fixture.bookingsById[8103]?.safety_seal ?? "")).toBe("5150");
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("booking completion failure keeps the mobile booking popup open for retry", async ({ page }) => {
|
||||
const orderId = 9410;
|
||||
const fixture = createMobilePosFixture({
|
||||
|
||||
@@ -265,6 +265,7 @@ function createRequestCounters(overrides = {}) {
|
||||
vehiclesSearchGet: 0,
|
||||
vehiclesGet: 0,
|
||||
unknownVehiclesGet: 0,
|
||||
vehicleCustomerSuggestionsGet: 0,
|
||||
usersCustomerGet: 0,
|
||||
customersGet: 0,
|
||||
customerNotesGet: 0,
|
||||
@@ -664,6 +665,7 @@ function buildDefaultFixture() {
|
||||
8103: safetySealBooking,
|
||||
},
|
||||
cvrSearchResponses: defaultCvrSearchResponses,
|
||||
vehicleCustomerSuggestionsByReg: {},
|
||||
customerRegistrationResponse: {
|
||||
status: 200,
|
||||
data: {
|
||||
@@ -696,6 +698,7 @@ function normalizeFixture(fixture) {
|
||||
fixture.customersByNumber = mergeObjectMaps({}, fixture.customersByNumber);
|
||||
fixture.customerAttributesByNumber = mergeObjectMaps({}, fixture.customerAttributesByNumber);
|
||||
fixture.customerNotesByNumber = mergeObjectMaps({}, fixture.customerNotesByNumber);
|
||||
fixture.vehicleCustomerSuggestionsByReg = mergeObjectMaps({}, fixture.vehicleCustomerSuggestionsByReg);
|
||||
fixture.ordersById = mergeObjectMaps({}, fixture.ordersById);
|
||||
fixture.orderItemsByOrderId = mergeObjectMaps({}, fixture.orderItemsByOrderId);
|
||||
fixture.attachmentsByOrderId = mergeObjectMaps({}, fixture.attachmentsByOrderId);
|
||||
@@ -743,6 +746,10 @@ export function createMobilePosFixture(overrides = {}) {
|
||||
customersByNumber: mergeObjectMaps(base.customersByNumber, overrides.customersByNumber),
|
||||
customerAttributesByNumber: mergeObjectMaps(base.customerAttributesByNumber, overrides.customerAttributesByNumber),
|
||||
customerNotesByNumber: mergeObjectMaps(base.customerNotesByNumber, overrides.customerNotesByNumber),
|
||||
vehicleCustomerSuggestionsByReg: mergeObjectMaps(
|
||||
base.vehicleCustomerSuggestionsByReg,
|
||||
overrides.vehicleCustomerSuggestionsByReg
|
||||
),
|
||||
ordersById: mergeObjectMaps(base.ordersById, overrides.ordersById),
|
||||
orderItemsByOrderId: mergeObjectMaps(base.orderItemsByOrderId, overrides.orderItemsByOrderId),
|
||||
attachmentsByOrderId: mergeObjectMaps(base.attachmentsByOrderId, overrides.attachmentsByOrderId),
|
||||
@@ -1469,6 +1476,14 @@ export async function mockMobilePosApi(page, fixture) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/department/vehicle/customer-suggestions") && method === "GET") {
|
||||
recordCounter(fixture, "vehicleCustomerSuggestionsGet");
|
||||
const reg = normalizeRegistrationValue(parsedUrl.searchParams.get("reg_1"));
|
||||
const suggestions = fixture.vehicleCustomerSuggestionsByReg[reg] || [];
|
||||
await route.fulfill(json({ success: true, data: clone(suggestions) }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/cvr/search") && method === "GET") {
|
||||
recordCounter(fixture, "cvrSearchGet");
|
||||
const query = String(parsedUrl.searchParams.get("query") || "").trim();
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const addObjectMock = vi.hoisted(() => vi.fn(async () => ({ data: { data: { id: 91 } } })));
|
||||
const setColumnMock = vi.hoisted(() => vi.fn(async () => ({ data: { success: true } })));
|
||||
const deleteObjectMock = vi.hoisted(() => vi.fn(async () => ({ data: { success: true } })));
|
||||
const dispatchNavigationCountRefreshMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("sweetalert2", () => ({
|
||||
default: {
|
||||
fire: vi.fn(() => Promise.resolve()),
|
||||
close: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/i18n", () => ({
|
||||
default: {
|
||||
global: {
|
||||
t: (key) => key,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/models/navigation/items/navigationCountEvents.js", () => ({
|
||||
dispatchNavigationCountRefresh: dispatchNavigationCountRefreshMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue", () => ({
|
||||
ObjectsGlobal: {
|
||||
add: {
|
||||
object: addObjectMock,
|
||||
},
|
||||
set: {
|
||||
column: setColumnMock,
|
||||
},
|
||||
delete: {
|
||||
object: deleteObjectMock,
|
||||
},
|
||||
showDeleteConfirmationModal: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
SessionUser: {
|
||||
objects: {
|
||||
orders: {
|
||||
functions: {
|
||||
get_customer_id: vi.fn(),
|
||||
},
|
||||
set: {
|
||||
customer_id: vi.fn(),
|
||||
invoice_collection_id: vi.fn(),
|
||||
},
|
||||
},
|
||||
collectedOrderInvoices: {
|
||||
functions: {
|
||||
showInvoiceCollectionPickerForm: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
request: vi.fn(() => Promise.resolve({ data: { data: {} } })),
|
||||
functions: {
|
||||
parseErrorMessage: vi.fn(() => "error"),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/authenticatedRequest.vue", () => ({
|
||||
authenticatedRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shop/OrdersItems.vue", () => ({
|
||||
editOrderItem: vi.fn(),
|
||||
getOrderItems: vi.fn(async () => ({ data: { data: [] } })),
|
||||
}));
|
||||
|
||||
import { Orders } from "@/components/session/token/SessionUser/Objects/Orders.vue";
|
||||
|
||||
describe("Orders draft count refresh hooks", () => {
|
||||
beforeEach(() => {
|
||||
addObjectMock.mockClear();
|
||||
setColumnMock.mockClear();
|
||||
deleteObjectMock.mockClear();
|
||||
dispatchNavigationCountRefreshMock.mockClear();
|
||||
});
|
||||
|
||||
it("refreshes navigation counts after changing an order customer", async () => {
|
||||
await Orders.set.customer_id(45, 6001);
|
||||
|
||||
expect(setColumnMock).toHaveBeenCalledWith("/orders", 45, "customer_id", 6001);
|
||||
expect(dispatchNavigationCountRefreshMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("refreshes navigation counts after deleting an order", async () => {
|
||||
await Orders.delete.single(45);
|
||||
|
||||
expect(deleteObjectMock).toHaveBeenCalledWith("/orders", 45);
|
||||
expect(dispatchNavigationCountRefreshMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -66,4 +66,27 @@ describe("useDraftTransactionCustomer", () => {
|
||||
await expect(module.ensureDraftTransactionCustomerLoaded()).resolves.toBe(778899);
|
||||
expect(module.getDraftTransactionCustomerNumber()).toBe(778899);
|
||||
});
|
||||
|
||||
it("can clear a previously cached fallback customer number", async () => {
|
||||
sessionState.canAccessSuperUser.mockReturnValue(true);
|
||||
sessionState.getTransactionDraftCustomerNumberConfig.mockResolvedValue({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
variable: "transactionDraftCustomerNumber",
|
||||
value: 778899,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const module = await import("@/composables/useDraftTransactionCustomer.js");
|
||||
|
||||
await expect(module.ensureDraftTransactionCustomerLoaded()).resolves.toBe(778899);
|
||||
expect(module.getDraftTransactionCustomerNumber()).toBe(778899);
|
||||
|
||||
expect(module.setDraftTransactionCustomerNumber(null)).toBeNull();
|
||||
expect(module.getDraftTransactionCustomerNumber()).toBeNull();
|
||||
expect(sessionState.runtimeConfigValue.value).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user