Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6463a3d211 | ||
|
|
e792f5820d |
@@ -1,9 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, ref, watch } from "vue";
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
||||||
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
|
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({
|
const props = defineProps({
|
||||||
order_id: {
|
order_id: {
|
||||||
@@ -123,7 +126,7 @@ const referenceField = createCustomerWishField({
|
|||||||
key: "reference",
|
key: "reference",
|
||||||
label: computed(() => t("common.reference")),
|
label: computed(() => t("common.reference")),
|
||||||
source: () => props.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",
|
testIdBase: "pos-order-customer-wishes-reference",
|
||||||
isRequired: () => props.referenceRequired,
|
isRequired: () => props.referenceRequired,
|
||||||
warningStateWhenEmpty: "danger",
|
warningStateWhenEmpty: "danger",
|
||||||
@@ -134,7 +137,7 @@ const poField = createCustomerWishField({
|
|||||||
key: "po",
|
key: "po",
|
||||||
label: computed(() => t("admin.pos.po_number")),
|
label: computed(() => t("admin.pos.po_number")),
|
||||||
source: () => props.po,
|
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",
|
testIdBase: "pos-order-customer-wishes-po",
|
||||||
isRequired: () => props.poRequired,
|
isRequired: () => props.poRequired,
|
||||||
warningStateWhenEmpty: "warning",
|
warningStateWhenEmpty: "warning",
|
||||||
@@ -145,7 +148,7 @@ const safetySealField = createCustomerWishField({
|
|||||||
key: "safety_seal",
|
key: "safety_seal",
|
||||||
label: computed(() => t("admin.pos.safety_seal")),
|
label: computed(() => t("admin.pos.safety_seal")),
|
||||||
source: () => props.safetySeal,
|
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",
|
testIdBase: "pos-order-customer-wishes-safety-seal",
|
||||||
isRequired: () => false,
|
isRequired: () => false,
|
||||||
warningStateWhenEmpty: null,
|
warningStateWhenEmpty: null,
|
||||||
@@ -161,6 +164,15 @@ const safetySealField = createCustomerWishField({
|
|||||||
const fields = computed(() => {
|
const fields = computed(() => {
|
||||||
return props.showSafetySeal ? [referenceField, poField, safetySealField] : [referenceField, poField];
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, ref, watch } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
||||||
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
|
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
|
||||||
|
import {
|
||||||
|
registerPosStepSaveBarrier,
|
||||||
|
saveOrderMetadataField,
|
||||||
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
order_id: {
|
order_id: {
|
||||||
@@ -30,8 +33,7 @@ const isEditing = ref(false);
|
|||||||
const autosave = useOrderMetadataAutosave({
|
const autosave = useOrderMetadataAutosave({
|
||||||
source: () => props.note,
|
source: () => props.note,
|
||||||
saveValue: async (value) => {
|
saveValue: async (value) => {
|
||||||
await SessionUser.objects.orders.set.notes(props.order_id, value);
|
return saveOrderMetadataField("notes", value, props.order_id);
|
||||||
return value;
|
|
||||||
},
|
},
|
||||||
onSaved: async (savedValue, requestedValue) => {
|
onSaved: async (savedValue, requestedValue) => {
|
||||||
if (typeof props.onSaved === "function") {
|
if (typeof props.onSaved === "function") {
|
||||||
@@ -68,6 +70,12 @@ const lostFocus = async () => {
|
|||||||
isEditing.value = false;
|
isEditing.value = false;
|
||||||
await autosave.flush();
|
await autosave.flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(() => autosave.flushAndWait());
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
unregisterStepSaveBarrier();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, ref, watch } from "vue";
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
||||||
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
|
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
|
||||||
|
import {
|
||||||
|
registerPosStepSaveBarrier,
|
||||||
|
saveOrderMetadataField,
|
||||||
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
index: {
|
index: {
|
||||||
@@ -36,8 +39,7 @@ autosave = useOrderMetadataAutosave({
|
|||||||
source: () => props.modelValue,
|
source: () => props.modelValue,
|
||||||
normalizeValue: normalizeRegistrationDraft,
|
normalizeValue: normalizeRegistrationDraft,
|
||||||
saveValue: async (value) => {
|
saveValue: async (value) => {
|
||||||
await SessionUser.objects.orders.set[fieldKey.value](props.order_id, value);
|
return saveOrderMetadataField(fieldKey.value, value, props.order_id);
|
||||||
return value;
|
|
||||||
},
|
},
|
||||||
onSaved: async () => {
|
onSaved: async () => {
|
||||||
await props.loadOrder();
|
await props.loadOrder();
|
||||||
@@ -74,6 +76,12 @@ const closeEditor = async () => {
|
|||||||
isEditing.value = false;
|
isEditing.value = false;
|
||||||
await autosave.flush();
|
await autosave.flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unregisterStepSaveBarrier = registerPosStepSaveBarrier(() => autosave.flushAndWait());
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
unregisterStepSaveBarrier();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
setBackgroundColor,
|
setBackgroundColor,
|
||||||
backgroundColors,
|
backgroundColors,
|
||||||
} from "@/components/viewport/page/headers/ViewportHeaderSettings.vue";
|
} 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 { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||||
import PosDepartmentStepMobile2Product from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Product.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";
|
import PosDepartmentStepMobile2LastOrder from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2LastOrder.vue";
|
||||||
@@ -43,6 +43,8 @@ import {
|
|||||||
customer_name,
|
customer_name,
|
||||||
isAddonRestricted,
|
isAddonRestricted,
|
||||||
canBuyAdditionalServices,
|
canBuyAdditionalServices,
|
||||||
|
registerPosStepSaveBarrier,
|
||||||
|
saveOrderMetadataField,
|
||||||
} from "@/components/shop/POSDepartmentProcess.vue";
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
import { createOrderItem, getOrderItems, removeOrderItem } from "@/components/shop/OrdersItems.vue";
|
import { createOrderItem, getOrderItems, removeOrderItem } from "@/components/shop/OrdersItems.vue";
|
||||||
import { PosProduct } from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.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
|
// Set PO number on the order if provided by the booking
|
||||||
if (booking.po && typeof booking.po === "string" && booking.po.trim() !== "") {
|
if (booking.po && typeof booking.po === "string" && booking.po.trim() !== "") {
|
||||||
try {
|
try {
|
||||||
await SessionUser.objects.orders.set.po(order_id.value, booking.po);
|
order_po.value = await saveOrderMetadataField("po", booking.po, order_id.value);
|
||||||
order_po.value = booking.po.trim();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to set PO on order from booking", 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();
|
const bookingReference = String(fullBookingData.reference ?? fullBookingData.reference_number ?? "").trim();
|
||||||
if (bookingReference !== "") {
|
if (bookingReference !== "") {
|
||||||
try {
|
try {
|
||||||
await SessionUser.objects.orders.set.reference(order_id.value, bookingReference);
|
const savedReference = await saveOrderMetadataField("reference", bookingReference, order_id.value);
|
||||||
persistedReference.value = bookingReference;
|
persistedReference.value = savedReference;
|
||||||
metadata.setReference(bookingReference);
|
metadata.setReference(savedReference);
|
||||||
hydrateReferenceFromSources();
|
hydrateReferenceFromSources();
|
||||||
} catch (setRefError) {
|
} catch (setRefError) {
|
||||||
console.error("Failed to set reference on order from booking:", 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();
|
const bookingNotes = String(fullBookingData.notes ?? fullBookingData.note ?? "").trim();
|
||||||
if (bookingNotes !== "") {
|
if (bookingNotes !== "") {
|
||||||
try {
|
try {
|
||||||
await SessionUser.objects.orders.set.notes(order_id.value, bookingNotes);
|
const savedNotes = await saveOrderMetadataField("notes", bookingNotes, order_id.value);
|
||||||
order_notes.value = bookingNotes;
|
order_notes.value = savedNotes;
|
||||||
notes.value = bookingNotes;
|
notes.value = savedNotes;
|
||||||
metadata.setNotes(bookingNotes);
|
metadata.setNotes(savedNotes);
|
||||||
} catch (setNotesError) {
|
} catch (setNotesError) {
|
||||||
console.error("Failed to set notes on order from booking:", 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);
|
const currentReg2 = normalizeRegistrationValue(reg_2.value);
|
||||||
|
|
||||||
if (bookingReg1 && bookingReg1 !== currentReg1) {
|
if (bookingReg1 && bookingReg1 !== currentReg1) {
|
||||||
reg_1.value = bookingReg1;
|
reg_1.value = await saveOrderMetadataField("reg_1", bookingReg1, order_id.value);
|
||||||
await SessionUser.objects.orders.set.reg_1(order_id.value, bookingReg1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookingReg2 !== currentReg2) {
|
if (bookingReg2 !== currentReg2) {
|
||||||
reg_2.value = bookingReg2;
|
reg_2.value = await saveOrderMetadataField("reg_2", bookingReg2, order_id.value);
|
||||||
await SessionUser.objects.orders.set.reg_2(order_id.value, bookingReg2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingMatches = Array.isArray(vehicles.vehicle_1.value?.booking_matches)
|
const bookingMatches = Array.isArray(vehicles.vehicle_1.value?.booking_matches)
|
||||||
@@ -538,8 +537,7 @@ const notesAutosave = useOrderMetadataAutosave({
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SessionUser.objects.orders.set.notes(normalizedOrderId, value);
|
return saveOrderMetadataField("notes", value, normalizedOrderId);
|
||||||
return value;
|
|
||||||
},
|
},
|
||||||
onSaved: async (value) => {
|
onSaved: async (value) => {
|
||||||
order_notes.value = value;
|
order_notes.value = value;
|
||||||
@@ -555,8 +553,7 @@ const referenceAutosave = useOrderMetadataAutosave({
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SessionUser.objects.orders.set.reference(normalizedOrderId, value);
|
return saveOrderMetadataField("reference", value, normalizedOrderId);
|
||||||
return value;
|
|
||||||
},
|
},
|
||||||
onSaved: async (value) => {
|
onSaved: async (value) => {
|
||||||
persistedReference.value = value;
|
persistedReference.value = value;
|
||||||
@@ -572,8 +569,7 @@ const safetySealAutosave = useOrderMetadataAutosave({
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SessionUser.objects.orders.set.safety_seal(normalizedOrderId, value);
|
return saveOrderMetadataField("safety_seal", value, normalizedOrderId);
|
||||||
return value;
|
|
||||||
},
|
},
|
||||||
onSaved: async (value) => {
|
onSaved: async (value) => {
|
||||||
order_safety_seal.value = value;
|
order_safety_seal.value = value;
|
||||||
@@ -585,6 +581,18 @@ const stepTwoNotesInput = notesAutosave.draft;
|
|||||||
const stepTwoReferenceInput = referenceAutosave.draft;
|
const stepTwoReferenceInput = referenceAutosave.draft;
|
||||||
const stepTwoSafetySealInput = safetySealAutosave.draft;
|
const stepTwoSafetySealInput = safetySealAutosave.draft;
|
||||||
const showSafetySealField = computed(() => transactionItems.containsWashCertificate());
|
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 resolveReferenceFromSources = () => {
|
||||||
const orderReference = normalizeReferenceValue(persistedReference.value);
|
const orderReference = normalizeReferenceValue(persistedReference.value);
|
||||||
|
|||||||
+35
-19
@@ -20,6 +20,7 @@ import {
|
|||||||
hasAttribute,
|
hasAttribute,
|
||||||
uploadAttachment,
|
uploadAttachment,
|
||||||
restoreStoredPosOrderId,
|
restoreStoredPosOrderId,
|
||||||
|
saveOrderMetadataField,
|
||||||
} from "@/components/shop/POSDepartmentProcess.vue";
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
import * as POSDepartmentProcess from "@/components/shop/POSDepartmentProcess.vue";
|
import * as POSDepartmentProcess from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
import { errors } from "@/components/request/HandleGlobalError.vue";
|
import { errors } from "@/components/request/HandleGlobalError.vue";
|
||||||
@@ -96,17 +97,30 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateReference = () => {
|
const getActiveOrderId = () => {
|
||||||
if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.reference(order_id.value, reference.value);
|
const parsedOrderId = Number.parseInt(String(order_id.value ?? ""), 10);
|
||||||
|
return Number.isInteger(parsedOrderId) && parsedOrderId > 0 ? parsedOrderId : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNotes = () => {
|
const updateReference = async () => {
|
||||||
if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.notes(order_id.value, order_notes.value);
|
const activeOrderId = getActiveOrderId();
|
||||||
|
if (activeOrderId) {
|
||||||
|
await saveOrderMetadataField("reference", reference.value, activeOrderId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSafetySeal = () => {
|
const updateNotes = async () => {
|
||||||
if (!!order_id.value && order_id.value > 0)
|
const activeOrderId = getActiveOrderId();
|
||||||
SessionUser.objects.orders.set.safety_seal(order_id.value, order_safety_seal.value);
|
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
|
// Helpers: extracted for clarity and reuse
|
||||||
@@ -196,9 +210,9 @@ const step1 = async () => {
|
|||||||
// Reset the vehicle selection
|
// Reset the vehicle selection
|
||||||
views.vehicleSelection.value = false;
|
views.vehicleSelection.value = false;
|
||||||
// Merge references and notes
|
// Merge references and notes
|
||||||
mergeReferences();
|
await mergeReferences();
|
||||||
mergeNotes();
|
await mergeNotes();
|
||||||
mergeSafetySeal();
|
await mergeSafetySeal();
|
||||||
// We only need to check if the customer requires reference, if the field is empty.
|
// We only need to check if the customer requires reference, if the field is empty.
|
||||||
if (!isNonEmptyString(reference.value)) {
|
if (!isNonEmptyString(reference.value)) {
|
||||||
const isValid = await validateReferenceRequirements();
|
const isValid = await validateReferenceRequirements();
|
||||||
@@ -214,37 +228,39 @@ const mergeReferences = () => {
|
|||||||
if (metadata.getReference() && metadata.getReference().length >= 1) {
|
if (metadata.getReference() && metadata.getReference().length >= 1) {
|
||||||
// If there is no reference, set it to the registration number of vehicle 1
|
// If there is no reference, set it to the registration number of vehicle 1
|
||||||
reference.value = metadata.getReference();
|
reference.value = metadata.getReference();
|
||||||
updateReference();
|
return updateReference();
|
||||||
} else if (reference.value && reference.value.length >= 1) {
|
} else if (reference.value && reference.value.length >= 1) {
|
||||||
// If there is a reference in the field, set it to the metadata
|
// If there is a reference in the field, set it to the metadata
|
||||||
metadata.setReference(reference.value);
|
metadata.setReference(reference.value);
|
||||||
updateReference();
|
return updateReference();
|
||||||
} else if (vehicles.vehicle_1.value?.reference && vehicles.vehicle_1.value?.reference.length >= 1) {
|
} 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
|
// If there is no reference, set it to the registration number of vehicle 1
|
||||||
reference.value = vehicles.vehicle_1.value?.reference;
|
reference.value = vehicles.vehicle_1.value?.reference;
|
||||||
metadata.setReference(reference.value);
|
metadata.setReference(reference.value);
|
||||||
updateReference();
|
return updateReference();
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
const mergeNotes = () => {
|
const mergeNotes = () => {
|
||||||
if (metadata.getNotes() && metadata.getNotes().length >= 1) {
|
if (metadata.getNotes() && metadata.getNotes().length >= 1) {
|
||||||
order_notes.value = metadata.getNotes();
|
order_notes.value = metadata.getNotes();
|
||||||
updateNotes();
|
return updateNotes();
|
||||||
} else if (order_notes.value && order_notes.value.length >= 1) {
|
} else if (order_notes.value && order_notes.value.length >= 1) {
|
||||||
metadata.setNotes(order_notes.value);
|
metadata.setNotes(order_notes.value);
|
||||||
updateNotes();
|
return updateNotes();
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
const mergeSafetySeal = () => {
|
const mergeSafetySeal = () => {
|
||||||
const resolvedSafetySeal = getResolvedMobileSafetySeal();
|
const resolvedSafetySeal = getResolvedMobileSafetySeal();
|
||||||
order_safety_seal.value = resolvedSafetySeal;
|
order_safety_seal.value = resolvedSafetySeal;
|
||||||
syncMobileSafetySealState(resolvedSafetySeal);
|
syncMobileSafetySealState(resolvedSafetySeal);
|
||||||
updateSafetySeal();
|
return updateSafetySeal();
|
||||||
};
|
};
|
||||||
const finalizeStep1 = async () => {
|
const finalizeStep1 = async () => {
|
||||||
mergeReferences();
|
await mergeReferences();
|
||||||
mergeNotes();
|
await mergeNotes();
|
||||||
mergeSafetySeal();
|
await mergeSafetySeal();
|
||||||
// Upload the attachments if there are any (And the order id has been created)
|
// Upload the attachments if there are any (And the order id has been created)
|
||||||
if (attachments.getBase64().length > 0 && order_id.value > 0) {
|
if (attachments.getBase64().length > 0 && order_id.value > 0) {
|
||||||
const uploadResults = await Promise.all(
|
const uploadResults = await Promise.all(
|
||||||
|
|||||||
+4
-3
@@ -5,6 +5,7 @@ import {
|
|||||||
order_safety_seal,
|
order_safety_seal,
|
||||||
reference,
|
reference,
|
||||||
clearStoredPosOrderId,
|
clearStoredPosOrderId,
|
||||||
|
saveOrderMetadataField,
|
||||||
} from "@/components/shop/POSDepartmentProcess.vue";
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
import { metadata } from "./PosDepartmentStepMobileFlow.vue";
|
import { metadata } from "./PosDepartmentStepMobileFlow.vue";
|
||||||
|
|
||||||
@@ -52,9 +53,9 @@ export const persistCurrentMobileOrderMetadata = async () => {
|
|||||||
order_notes.value = normalizedNotes;
|
order_notes.value = normalizedNotes;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
SessionUser.objects.orders.set.reference(normalizedOrderId, normalizedReference),
|
saveOrderMetadataField("reference", normalizedReference, normalizedOrderId),
|
||||||
SessionUser.objects.orders.set.notes(normalizedOrderId, normalizedNotes),
|
saveOrderMetadataField("notes", normalizedNotes, normalizedOrderId),
|
||||||
SessionUser.objects.orders.set.safety_seal(normalizedOrderId, normalizedSafetySeal),
|
saveOrderMetadataField("safety_seal", normalizedSafetySeal, normalizedOrderId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return normalizedOrderId;
|
return normalizedOrderId;
|
||||||
|
|||||||
+16
-5
@@ -1,6 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onBeforeUnmount, onMounted, watch } from "vue";
|
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 PosDepartmentStepMobile1RegistrationNumberInputField from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumberInputField.vue";
|
||||||
import BarcodeScanner from "@/components/viewport/elements/icons/BarcodeScanner.vue";
|
import BarcodeScanner from "@/components/viewport/elements/icons/BarcodeScanner.vue";
|
||||||
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
|
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
|
||||||
@@ -103,9 +110,7 @@ const createRegistrationAutosave = (vehicleIndex: number) =>
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SessionUser.objects.orders.set[`reg_${vehicleIndex}`](normalizedOrderId, value);
|
return saveOrderMetadataField(`reg_${vehicleIndex}`, value, normalizedOrderId);
|
||||||
const refreshedOrder = await getOrderDetails(normalizedOrderId);
|
|
||||||
return refreshedOrder?.[`reg_${vehicleIndex}`] ?? value;
|
|
||||||
},
|
},
|
||||||
onSaved: async (value) => {
|
onSaved: async (value) => {
|
||||||
setPersistedRegistrationValue(vehicleIndex, value);
|
setPersistedRegistrationValue(vehicleIndex, value);
|
||||||
@@ -194,6 +199,11 @@ const flushAllRegistrations = async () => {
|
|||||||
await Promise.all([1, 2, 3].map((vehicleIndex) => registrationAutosaves[vehicleIndex].flush()));
|
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
|
// Function to set a value if it is not null
|
||||||
const createVehicleSelectionFromSearchResult = (value: PosSearchResult) => ({
|
const createVehicleSelectionFromSearchResult = (value: PosSearchResult) => ({
|
||||||
...value,
|
...value,
|
||||||
@@ -266,11 +276,12 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
unregisterStepSaveBarrier();
|
||||||
void flushAllRegistrations();
|
void flushAllRegistrations();
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeManualInput = async () => {
|
const closeManualInput = async () => {
|
||||||
await flushAllRegistrations();
|
await waitForAllRegistrationSaves();
|
||||||
emit("close");
|
emit("close");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ const props = defineProps(['label', 'isText']);
|
|||||||
const label = computed(() => props.label || t('admin.pos.buttons.go_back'));
|
const label = computed(() => props.label || t('admin.pos.buttons.go_back'));
|
||||||
|
|
||||||
// Go to previous step
|
// Go to previous step
|
||||||
const goToPreviousStep = () => {
|
const goToPreviousStep = async () => {
|
||||||
// Get the department id
|
// Get the department id
|
||||||
previousStep();
|
await previousStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyIfText = (string) => props.isText ? '' : string;
|
const emptyIfText = (string) => props.isText ? '' : string;
|
||||||
@@ -32,4 +32,4 @@ const emptyIfText = (string) => props.isText ? '' : string;
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,42 @@ export const nextStepDelay = ref(0); // 2 seconds delay
|
|||||||
export const isCreatingOrder = ref(false);
|
export const isCreatingOrder = ref(false);
|
||||||
export const desktopStep1PreflightHandler = ref(null);
|
export const desktopStep1PreflightHandler = ref(null);
|
||||||
let createOrderRequest = 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) => {
|
export const setDesktopStep1PreflightHandler = (handler = null) => {
|
||||||
desktopStep1PreflightHandler.value = typeof handler === "function" ? handler : null;
|
desktopStep1PreflightHandler.value = typeof handler === "function" ? handler : null;
|
||||||
@@ -109,6 +145,10 @@ export const nextStep = async (options = { isMobile: false, orderCreation: true
|
|||||||
}
|
}
|
||||||
// Set the delay to 2 seconds
|
// Set the delay to 2 seconds
|
||||||
setNextStepDelay(2);
|
setNextStepDelay(2);
|
||||||
|
if (!(await flushPosStepSaveBarriers())) {
|
||||||
|
nextStepDelay.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Clear the errors
|
// Clear the errors
|
||||||
clearErrors();
|
clearErrors();
|
||||||
// If the current step is 1, create the order
|
// If the current step is 1, create the order
|
||||||
@@ -188,8 +228,22 @@ export const nextStep = async (options = { isMobile: false, orderCreation: true
|
|||||||
step.value++;
|
step.value++;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const previousStep = () => {
|
export const previousStep = async () => {
|
||||||
step.value--;
|
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 = () => {
|
export const getCurrentStep = () => {
|
||||||
@@ -784,6 +838,129 @@ watch([reg_1, reg_2, reg_3], () => {
|
|||||||
reg_3.value = normalizeRegistrationValue(reg_3.value);
|
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 */
|
/** Get the order details by order id */
|
||||||
export const getOrderDetails = async (id = null) => {
|
export const getOrderDetails = async (id = null) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
@@ -791,6 +968,9 @@ export const getOrderDetails = async (id = null) => {
|
|||||||
if (!token || !targetOrderId) {
|
if (!token || !targetOrderId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const requestId = ++latestOrderDetailsRequestId;
|
||||||
|
const metadataMutationSequenceAtRequest = latestOrderMetadataMutationSequence;
|
||||||
|
const hadActiveMetadataMutationAtRequest = activeOrderMetadataMutationCount > 0;
|
||||||
return axios
|
return axios
|
||||||
.get(API_URL + "/order?id=" + targetOrderId, {
|
.get(API_URL + "/order?id=" + targetOrderId, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -799,54 +979,21 @@ export const getOrderDetails = async (id = null) => {
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const order_data = response.data.data;
|
const order_data = response.data.data;
|
||||||
const includedCustomer = response?.data?.includes?.customer ?? null;
|
if (!order_data) {
|
||||||
const normalizedOrderCustomerNumber = resolveCustomerNumber(order_data?.customer_id);
|
return null;
|
||||||
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 (includedCustomer && typeof includedCustomer === "object") {
|
const normalizedResponseOrderId = toPositiveInteger(order_data.id ?? targetOrderId);
|
||||||
const customerSource =
|
const normalizedCurrentOrderId = toPositiveInteger(order_id.value);
|
||||||
includedCustomer.economic_customer && typeof includedCustomer.economic_customer === "object"
|
const isStaleForDifferentOrder =
|
||||||
? includedCustomer.economic_customer
|
requestId !== latestOrderDetailsRequestId &&
|
||||||
: includedCustomer;
|
normalizedCurrentOrderId &&
|
||||||
|
normalizedResponseOrderId &&
|
||||||
|
normalizedCurrentOrderId !== normalizedResponseOrderId;
|
||||||
|
const isStaleBeforeMetadataMutation = metadataMutationSequenceAtRequest !== latestOrderMetadataMutationSequence;
|
||||||
|
|
||||||
selectCustomer(
|
if (!isStaleForDifferentOrder && !isStaleBeforeMetadataMutation && !hadActiveMetadataMutationAtRequest) {
|
||||||
normalizeCustomerRecord(customerSource, {
|
applyOrderDetailsToPosState(order_data, response?.data?.includes ?? {});
|
||||||
...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();
|
|
||||||
}
|
}
|
||||||
// Return the order data
|
// Return the order data
|
||||||
return order_data;
|
return order_data;
|
||||||
@@ -1338,7 +1485,7 @@ const copyLastWashReferenceToEmptyCurrentOrder = async (sourceReference) => {
|
|||||||
|
|
||||||
const normalizedOrderId = toPositiveInteger(order_id.value);
|
const normalizedOrderId = toPositiveInteger(order_id.value);
|
||||||
if (normalizedOrderId) {
|
if (normalizedOrderId) {
|
||||||
await SessionUser.objects.orders.set.reference(normalizedOrderId, normalizedSourceReference);
|
await saveOrderMetadataField("reference", normalizedSourceReference, normalizedOrderId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2208,32 +2355,27 @@ export const hydrateSelectedOrderBookingForDesktop = async () => {
|
|||||||
|
|
||||||
const bookingReference = getOrderBookingReferenceValue(booking);
|
const bookingReference = getOrderBookingReferenceValue(booking);
|
||||||
if (bookingReference !== "" && bookingReference !== reference.value) {
|
if (bookingReference !== "" && bookingReference !== reference.value) {
|
||||||
await SessionUser.objects.orders.set.reference(normalizedOrderId, bookingReference);
|
reference.value = await saveOrderMetadataField("reference", bookingReference, normalizedOrderId);
|
||||||
reference.value = bookingReference;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingReg1 = getOrderBookingReg1Value(booking);
|
const bookingReg1 = getOrderBookingReg1Value(booking);
|
||||||
if (bookingReg1 !== "" && bookingReg1 !== reg_1.value) {
|
if (bookingReg1 !== "" && bookingReg1 !== reg_1.value) {
|
||||||
await SessionUser.objects.orders.set.reg_1(normalizedOrderId, bookingReg1);
|
reg_1.value = await saveOrderMetadataField("reg_1", bookingReg1, normalizedOrderId);
|
||||||
reg_1.value = bookingReg1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingReg2 = getOrderBookingReg2Value(booking);
|
const bookingReg2 = getOrderBookingReg2Value(booking);
|
||||||
if (bookingReg2 !== reg_2.value) {
|
if (bookingReg2 !== reg_2.value) {
|
||||||
await SessionUser.objects.orders.set.reg_2(normalizedOrderId, bookingReg2);
|
reg_2.value = await saveOrderMetadataField("reg_2", bookingReg2, normalizedOrderId);
|
||||||
reg_2.value = bookingReg2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingNotes = getOrderBookingNotesValue(booking);
|
const bookingNotes = getOrderBookingNotesValue(booking);
|
||||||
if (bookingNotes !== "" && bookingNotes !== order_notes.value) {
|
if (bookingNotes !== "" && bookingNotes !== order_notes.value) {
|
||||||
await SessionUser.objects.orders.set.notes(normalizedOrderId, bookingNotes);
|
order_notes.value = await saveOrderMetadataField("notes", bookingNotes, normalizedOrderId);
|
||||||
order_notes.value = bookingNotes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookingPo = String(booking?.po ?? "").trim();
|
const bookingPo = String(booking?.po ?? "").trim();
|
||||||
if (bookingPo !== "" && isBlankPosMetadataValue(order_po.value)) {
|
if (bookingPo !== "" && isBlankPosMetadataValue(order_po.value)) {
|
||||||
await SessionUser.objects.orders.set.po(normalizedOrderId, bookingPo);
|
order_po.value = await saveOrderMetadataField("po", bookingPo, normalizedOrderId);
|
||||||
order_po.value = bookingPo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentOrderItems = await loadOrderItems();
|
const currentOrderItems = await loadOrderItems();
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ export const useOrderMetadataAutosave = ({
|
|||||||
const draft = ref(normalizeValue(resolveSourceValue(source)));
|
const draft = ref(normalizeValue(resolveSourceValue(source)));
|
||||||
const lastSavedValue = ref(normalizeValue(resolveSourceValue(source)));
|
const lastSavedValue = ref(normalizeValue(resolveSourceValue(source)));
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
|
const lastError = ref(null);
|
||||||
const isDirty = computed(() => normalizeValue(draft.value) !== normalizeValue(lastSavedValue.value));
|
const isDirty = computed(() => normalizeValue(draft.value) !== normalizeValue(lastSavedValue.value));
|
||||||
|
|
||||||
let timerId = null;
|
let timerId = null;
|
||||||
let saveAgainAfterCurrentRequest = false;
|
let saveAgainAfterCurrentRequest = false;
|
||||||
|
let activeSavePromise = null;
|
||||||
let nextSequence = 0;
|
let nextSequence = 0;
|
||||||
let lastAppliedSequence = 0;
|
let lastAppliedSequence = 0;
|
||||||
|
|
||||||
@@ -66,45 +68,57 @@ export const useOrderMetadataAutosave = ({
|
|||||||
const sequence = ++nextSequence;
|
const sequence = ++nextSequence;
|
||||||
let saveCompleted = false;
|
let saveCompleted = false;
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
|
lastError.value = null;
|
||||||
|
|
||||||
try {
|
const run = (async () => {
|
||||||
const result = await saveValue(requestedValue);
|
try {
|
||||||
const savedValue = normalizeValue(result ?? requestedValue);
|
const result = await saveValue(requestedValue);
|
||||||
|
const savedValue = normalizeValue(result ?? requestedValue);
|
||||||
|
|
||||||
if (sequence >= lastAppliedSequence) {
|
if (sequence >= lastAppliedSequence) {
|
||||||
lastAppliedSequence = sequence;
|
lastAppliedSequence = sequence;
|
||||||
lastSavedValue.value = savedValue;
|
lastSavedValue.value = savedValue;
|
||||||
|
|
||||||
if (normalizeValue(draft.value) === requestedValue) {
|
if (normalizeValue(draft.value) === requestedValue) {
|
||||||
draft.value = savedValue;
|
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") {
|
const trackedSavePromise = run.finally(() => {
|
||||||
await onSaved(savedValue, requestedValue);
|
if (activeSavePromise === trackedSavePromise) {
|
||||||
|
activeSavePromise = null;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
saveCompleted = true;
|
activeSavePromise = trackedSavePromise;
|
||||||
|
return trackedSavePromise;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduleSave = () => {
|
const scheduleSave = () => {
|
||||||
@@ -123,6 +137,34 @@ export const useOrderMetadataAutosave = ({
|
|||||||
return persist();
|
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(() => {
|
onBeforeUnmount(() => {
|
||||||
clearPendingSave();
|
clearPendingSave();
|
||||||
|
|
||||||
@@ -139,10 +181,12 @@ export const useOrderMetadataAutosave = ({
|
|||||||
return {
|
return {
|
||||||
draft,
|
draft,
|
||||||
lastSavedValue,
|
lastSavedValue,
|
||||||
|
lastError,
|
||||||
isDirty,
|
isDirty,
|
||||||
isSaving,
|
isSaving,
|
||||||
scheduleSave,
|
scheduleSave,
|
||||||
flush,
|
flush,
|
||||||
|
flushAndWait,
|
||||||
clearPendingSave,
|
clearPendingSave,
|
||||||
syncFromSource,
|
syncFromSource,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ function createCardOrderFixture(overrides = {}) {
|
|||||||
customer_id: CARD_CUSTOMER_ID,
|
customer_id: CARD_CUSTOMER_ID,
|
||||||
department_id: DEPARTMENT_ID,
|
department_id: DEPARTMENT_ID,
|
||||||
reference: "CARD-REF-54518",
|
reference: "CARD-REF-54518",
|
||||||
|
po: "",
|
||||||
|
safety_seal: "",
|
||||||
notes: "Paid at terminal",
|
notes: "Paid at terminal",
|
||||||
reg_1: "ZZ99999",
|
reg_1: "ZZ99999",
|
||||||
reg_2: "",
|
reg_2: "",
|
||||||
@@ -236,6 +238,61 @@ test.describe("POS desktop card payments", () => {
|
|||||||
await expect(stepThree.getByTestId("pos-stripe-create-intent")).toBeVisible({ timeout: 10_000 });
|
await expect(stepThree.getByTestId("pos-stripe-create-intent")).toBeVisible({ timeout: 10_000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("step 2 customer wishes stay visible after moving to card payment step 3", async ({ page }) => {
|
||||||
|
const stepTwoReference = "CARD-STEP-2-REF";
|
||||||
|
const stepTwoPo = "PO-CARD-STEP-2";
|
||||||
|
const fixture = createCardOrderFixture({
|
||||||
|
ordersById: {
|
||||||
|
[ORDER_ID]: {
|
||||||
|
...createCardOrderFixture().ordersById[ORDER_ID],
|
||||||
|
reference: "",
|
||||||
|
po: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await mockApi(page, {
|
||||||
|
authenticated: true,
|
||||||
|
permissions: DESKTOP_POS_PERMISSIONS,
|
||||||
|
edgeGateways: false,
|
||||||
|
pos: fixture,
|
||||||
|
});
|
||||||
|
await primeSession(page, `desktop-card-step2-${Date.now()}`);
|
||||||
|
await page.goto(`/admin/${DEPARTMENT_ID}/modules/pos?id=${ORDER_ID}&customer_id=${CARD_CUSTOMER_ID}&step=2`);
|
||||||
|
|
||||||
|
const stepTwo = page.getByTestId("pos-step-2");
|
||||||
|
await expect(stepTwo).toBeVisible({ timeout: 10_000 });
|
||||||
|
await expect(stepTwo.getByTestId("pos-order-metadata-grid")).toBeVisible({ timeout: 10_000 });
|
||||||
|
|
||||||
|
await stepTwo.getByTestId("pos-order-customer-wishes-reference").click();
|
||||||
|
await stepTwo.getByTestId("pos-order-customer-wishes-reference-input").fill(stepTwoReference);
|
||||||
|
await stepTwo.getByTestId("pos-order-customer-wishes-po").click();
|
||||||
|
await stepTwo.getByTestId("pos-order-customer-wishes-po-input").fill(stepTwoPo);
|
||||||
|
await stepTwo.getByTestId("pos-next-step").click();
|
||||||
|
|
||||||
|
const stepThree = page.getByTestId("pos-step-3");
|
||||||
|
await expect(stepThree).toBeVisible({ timeout: 10_000 });
|
||||||
|
await expect(stepThree.getByTestId("pos-order-customer-wishes-reference")).toContainText(stepTwoReference, {
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
await expect(stepThree.getByTestId("pos-order-customer-wishes-po")).toContainText(stepTwoPo, {
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
await expect.poll(() => fixture.ordersById[ORDER_ID].reference, { timeout: 10_000 }).toBe(stepTwoReference);
|
||||||
|
await expect.poll(() => fixture.ordersById[ORDER_ID].po, { timeout: 10_000 }).toBe(stepTwoPo);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
const reloadedStepThree = page.getByTestId("pos-step-3");
|
||||||
|
await expect(reloadedStepThree).toBeVisible({ timeout: 10_000 });
|
||||||
|
await expect(reloadedStepThree.getByTestId("pos-order-customer-wishes-reference")).toContainText(stepTwoReference, {
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
await expect(reloadedStepThree.getByTestId("pos-order-customer-wishes-po")).toContainText(stepTwoPo, {
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("idle state renders grouped terminal statuses and only keeps ready terminals selectable", async ({ page }) => {
|
test("idle state renders grouped terminal statuses and only keeps ready terminals selectable", async ({ page }) => {
|
||||||
const fixture = createCardOrderFixture({
|
const fixture = createCardOrderFixture({
|
||||||
readers: [
|
readers: [
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|||||||
reference: vi.fn(),
|
reference: vi.fn(),
|
||||||
reg_1: vi.fn(),
|
reg_1: vi.fn(),
|
||||||
reg_2: vi.fn(),
|
reg_2: vi.fn(),
|
||||||
|
reg_3: vi.fn(),
|
||||||
|
safety_seal: vi.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
order_bookings: {
|
order_bookings: {
|
||||||
@@ -152,6 +154,8 @@ import {
|
|||||||
getVehiclePlateBookings,
|
getVehiclePlateBookings,
|
||||||
invoiceCollectionId,
|
invoiceCollectionId,
|
||||||
loadOrderItems,
|
loadOrderItems,
|
||||||
|
nextStep,
|
||||||
|
nextStepDelay,
|
||||||
order_id,
|
order_id,
|
||||||
order_items,
|
order_items,
|
||||||
hydrateSelectedOrderBookingForDesktop,
|
hydrateSelectedOrderBookingForDesktop,
|
||||||
@@ -163,7 +167,10 @@ import {
|
|||||||
reg_2,
|
reg_2,
|
||||||
reg_3,
|
reg_3,
|
||||||
pendingBookings,
|
pendingBookings,
|
||||||
|
previousStep,
|
||||||
|
registerPosStepSaveBarrier,
|
||||||
restoreStoredPosOrderId,
|
restoreStoredPosOrderId,
|
||||||
|
saveOrderMetadataField,
|
||||||
searchAndSelectCustomer,
|
searchAndSelectCustomer,
|
||||||
setOrderId,
|
setOrderId,
|
||||||
scan_data,
|
scan_data,
|
||||||
@@ -172,6 +179,122 @@ import {
|
|||||||
step,
|
step,
|
||||||
} from "@/components/shop/POSDepartmentProcess.vue";
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
||||||
|
|
||||||
|
describe("POSDepartmentProcess order metadata persistence", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
window.history.replaceState({}, "", "/admin/12/modules/pos?step=2");
|
||||||
|
clearActivePosOrderContext({ clearStep: true });
|
||||||
|
localStorage.setItem("token", "test-token");
|
||||||
|
nextStepDelay.value = 0;
|
||||||
|
order_id.value = 54518;
|
||||||
|
customer_id.value = 999;
|
||||||
|
customer_name.value = "Card Terminal Customer";
|
||||||
|
customer_data.value = {};
|
||||||
|
department_id.value = 12;
|
||||||
|
step.value = 2;
|
||||||
|
Object.values(SessionUser.objects.orders.set).forEach((setter) => setter.mockReset?.());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies saved metadata responses to shared POS state", async () => {
|
||||||
|
SessionUser.objects.orders.set.reference.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
id: 54518,
|
||||||
|
customer_id: 999,
|
||||||
|
department_id: 12,
|
||||||
|
reference: "STEP-2-REF",
|
||||||
|
notes: "Visible note",
|
||||||
|
po: "PO-STEP-2",
|
||||||
|
safety_seal: "SEAL-1",
|
||||||
|
reg_1: "aa 12345",
|
||||||
|
reg_2: "",
|
||||||
|
reg_3: "",
|
||||||
|
invoice_collection_id: 200,
|
||||||
|
completed_at: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(saveOrderMetadataField("reference", "STEP-2-REF", 54518)).resolves.toBe("STEP-2-REF");
|
||||||
|
|
||||||
|
expect(SessionUser.objects.orders.set.reference).toHaveBeenCalledWith(54518, "STEP-2-REF");
|
||||||
|
expect(reference.value).toBe("STEP-2-REF");
|
||||||
|
expect(order_notes.value).toBe("Visible note");
|
||||||
|
expect(order_po.value).toBe("PO-STEP-2");
|
||||||
|
expect(order_safety_seal.value).toBe("SEAL-1");
|
||||||
|
expect(reg_1.value).toBe("AA12345");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves registration input formatting for the save request and normalizes shared state", async () => {
|
||||||
|
SessionUser.objects.orders.set.reg_1.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
id: 54518,
|
||||||
|
reg_1: "CD1234",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(saveOrderMetadataField("reg_1", "cd-12 34", 54518)).resolves.toBe("CD1234");
|
||||||
|
|
||||||
|
expect(SessionUser.objects.orders.set.reg_1).toHaveBeenCalledWith(54518, "CD-12 34");
|
||||||
|
expect(reg_1.value).toBe("CD1234");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("waits for registered field saves before moving from step 2 to step 3", async () => {
|
||||||
|
let resolveBarrier;
|
||||||
|
const barrier = vi.fn(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
resolveBarrier = () => {
|
||||||
|
reference.value = "BARRIER-SAVED";
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const unregister = registerPosStepSaveBarrier(barrier);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const moveToNextStep = nextStep({ isMobile: false, orderCreation: false });
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(barrier).toHaveBeenCalledTimes(1);
|
||||||
|
expect(window.location.search).toBe("?step=2");
|
||||||
|
|
||||||
|
resolveBarrier();
|
||||||
|
await moveToNextStep;
|
||||||
|
|
||||||
|
expect(reference.value).toBe("BARRIER-SAVED");
|
||||||
|
expect(window.location.search).toContain("step=3");
|
||||||
|
expect(window.location.search).toContain("id=54518");
|
||||||
|
expect(window.location.search).toContain("customer_id=999");
|
||||||
|
} finally {
|
||||||
|
unregister();
|
||||||
|
nextStepDelay.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("waits for registered field saves before moving backward", async () => {
|
||||||
|
step.value = 3;
|
||||||
|
window.history.replaceState({}, "", "/admin/12/modules/pos?id=54518&customer_id=999&step=3");
|
||||||
|
const barrier = vi.fn(async () => {
|
||||||
|
order_po.value = "PO-BACK-SAVED";
|
||||||
|
});
|
||||||
|
const unregister = registerPosStepSaveBarrier(barrier);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await previousStep();
|
||||||
|
|
||||||
|
expect(barrier).toHaveBeenCalledTimes(1);
|
||||||
|
expect(order_po.value).toBe("PO-BACK-SAVED");
|
||||||
|
expect(step.value).toBe(2);
|
||||||
|
expect(window.location.search).toContain("step=2");
|
||||||
|
} finally {
|
||||||
|
unregister();
|
||||||
|
nextStepDelay.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("POSDepartmentProcess.ensureVehiclePlateBookingsLoaded", () => {
|
describe("POSDepartmentProcess.ensureVehiclePlateBookingsLoaded", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
department_id.value = 531;
|
department_id.value = 531;
|
||||||
|
|||||||
@@ -85,4 +85,59 @@ describe("useOrderMetadataAutosave", () => {
|
|||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("flushAndWait waits for an in-flight save and persists the queued latest draft", async () => {
|
||||||
|
let resolveFirstSave;
|
||||||
|
const saveValue = vi.fn((value) => {
|
||||||
|
if (value === "first") {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveFirstSave = () => resolve(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(value);
|
||||||
|
});
|
||||||
|
const { autosave, wrapper } = mountAutosave({ saveValue });
|
||||||
|
|
||||||
|
autosave.draft.value = "first";
|
||||||
|
const firstFlush = autosave.flush();
|
||||||
|
|
||||||
|
autosave.draft.value = "second";
|
||||||
|
const waitForSaves = autosave.flushAndWait();
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(saveValue).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveValue).toHaveBeenNthCalledWith(1, "first");
|
||||||
|
|
||||||
|
resolveFirstSave();
|
||||||
|
await expect(firstFlush).resolves.toBe(true);
|
||||||
|
await expect(waitForSaves).resolves.toBe(false);
|
||||||
|
|
||||||
|
expect(saveValue).toHaveBeenCalledTimes(2);
|
||||||
|
expect(saveValue).toHaveBeenNthCalledWith(2, "second");
|
||||||
|
expect(autosave.lastSavedValue.value).toBe("second");
|
||||||
|
expect(autosave.isDirty.value).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("flushAndWait rejects when the dirty draft cannot be saved", async () => {
|
||||||
|
const saveError = new Error("server rejected metadata");
|
||||||
|
const saveValue = vi.fn(async () => {
|
||||||
|
throw saveError;
|
||||||
|
});
|
||||||
|
const { autosave, onError, wrapper } = mountAutosave({ saveValue });
|
||||||
|
|
||||||
|
autosave.draft.value = "rejected";
|
||||||
|
|
||||||
|
await expect(autosave.flushAndWait()).rejects.toThrow("server rejected metadata");
|
||||||
|
|
||||||
|
expect(saveValue).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onError).toHaveBeenCalledWith(saveError);
|
||||||
|
expect(autosave.lastError.value).toBe(saveError);
|
||||||
|
expect(autosave.isDirty.value).toBe(true);
|
||||||
|
|
||||||
|
autosave.syncFromSource("rejected");
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user