Files
pleno-vue/src/components/shop/POSDepartmentProcess.vue
T

2792 lines
88 KiB
Vue

<script>
import { computed, ref, watch } from "vue";
import axios from "axios";
import { API_URL } from "@/config.js";
import { getScansDepartmentPagination } from "@/components/numberplatescanners/Scans.vue";
import { parseError, clearErrors } from "@/components/request/HandleGlobalError.vue";
import { getNotes, createNote, deleteNote } from "@/components/shop/CustomerNotes.vue";
import { createOrderItem, getOrderItems, removeOrderItem } from "@/components/shop/OrdersItems.vue";
import { getAttributes } from "@/components/shop/CustomerAttributes.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import { doesOrderContainWashCertificateProduct } from "@/components/displays/department/pos/utils/washCertificate.js";
import {
getCustomerProductRestriction,
getProductCategoryRestrictionForCustomer,
hasValidCustomerProductRestrictionContract,
isProductCategoryRestrictedForCustomer,
} from "@/features/customer/customerProductRules.js";
import Swal from "sweetalert2";
/**
* Display toggleable items
*/
export const hidePricesCatalog = ref(null);
export const hideDiscountsCatalog = ref(null);
// Setters for the hide prices and discounts
export const setHidePricesCatalog = (value) => {
hidePricesCatalog.value = value;
localStorage.setItem("hidePricesCatalog", "" + value);
};
export const setHideDiscountsCatalog = (value) => {
hideDiscountsCatalog.value = value;
localStorage.setItem("hideDiscountsCatalog", "" + value);
};
// Getters
export const getHidePricesCatalog = () => {
// If the value is not set, get it from the local storage
if (hidePricesCatalog.value === null) {
hidePricesCatalog.value = localStorage.getItem("hidePricesCatalog") === "true";
}
return hidePricesCatalog.value;
};
export const getHideDiscountsCatalog = () => {
// If the value is not set, get it from the local storage
if (hideDiscountsCatalog.value === null) {
hideDiscountsCatalog.value = localStorage.getItem("hideDiscountsCatalog") === "true";
}
return hideDiscountsCatalog.value;
};
/** Define steps for the POS Department Process */
export const step = ref(1);
export const nextStepDelay = ref(0); // 2 seconds delay
export const isCreatingOrder = ref(false);
export const desktopStep1PreflightHandler = ref(null);
let createOrderRequest = null;
const posStepSaveBarriers = new Set();
let latestOrderDetailsRequestId = 0;
let latestOrderMetadataMutationSequence = 0;
let activeOrderMetadataMutationCount = 0;
export const registerPosStepSaveBarrier = (handler) => {
if (typeof handler !== "function") {
return () => {};
}
posStepSaveBarriers.add(handler);
return () => {
posStepSaveBarriers.delete(handler);
};
};
export const unregisterPosStepSaveBarrier = (handler) => {
posStepSaveBarriers.delete(handler);
};
export const flushPosStepSaveBarriers = async () => {
const barriers = Array.from(posStepSaveBarriers);
if (barriers.length === 0) {
return true;
}
const results = await Promise.allSettled(barriers.map((handler) => handler()));
const failedResult = results.find((result) => result.status === "rejected");
if (failedResult) {
parseError(failedResult.reason, "stepError");
return false;
}
return true;
};
export const setDesktopStep1PreflightHandler = (handler = null) => {
desktopStep1PreflightHandler.value = typeof handler === "function" ? handler : null;
};
export const clearDesktopStep1PreflightHandler = (handler = null) => {
if (handler === null || desktopStep1PreflightHandler.value === handler) {
desktopStep1PreflightHandler.value = null;
}
};
/** Define the next step delay function */
export const setNextStepDelay = (delay) => {
nextStepDelay.value = delay;
setTimeout(() => {
nextStepDelay.value = 0;
}, delay * 1000);
};
/** Define the step functions */
export const nextStep = async (options = { isMobile: false, orderCreation: true }) => {
const normalizedOptions = {
isMobile: false,
orderCreation: true,
...options,
};
// Check if the delay has passed since the last step (To prevent double clicks)
if (nextStepDelay.value > 0) {
return;
}
if (
normalizedOptions.isMobile === false &&
step.value === 1 &&
normalizedOptions.orderCreation !== false &&
typeof desktopStep1PreflightHandler.value === "function"
) {
const canProceedFromPreflight = await desktopStep1PreflightHandler.value({ reason: "next" });
if (!canProceedFromPreflight) {
return;
}
}
if (
normalizedOptions.isMobile === false &&
step.value === 1 &&
normalizedOptions.orderCreation !== false &&
reg_1.value
) {
if (!hasLoadedPendingBookings.value || isLoadingPendingBookings.value) {
await loadPendingBookings();
}
if (doesVehiclePlateRequireBookingSelection(reg_1.value)) {
return;
}
}
// Set the delay to 2 seconds
setNextStepDelay(2);
if (!(await flushPosStepSaveBarriers())) {
nextStepDelay.value = 0;
return;
}
// Clear the errors
clearErrors();
// If the current step is 1, create the order
if (step.value === 1) {
await loadCustomerAttributes(customer_id.value);
if (normalizedOptions.orderCreation === false) {
step.value++;
return;
}
const didCreateOrder = await createOrder(normalizedOptions);
if (didCreateOrder && normalizedOptions.isMobile === false) {
await hydrateSelectedOrderBookingForDesktop();
}
if (didCreateOrder && step.value === 1) {
step.value++;
}
return;
}
if (step.value === 2) {
// If the current step is 2, set the product category to addons
setProductsCategory(4);
// Set the step to 3 in the query parameters
pushPosRouteState(`id=${order_id.value}&customer_id=${customer_id.value}&step=3`);
}
if (step.value === 3) {
if (normalizedOptions.isMobile === false && doesOrderContainMaterial()) {
step.value = 4;
pushPosRouteState(`id=${order_id.value}&customer_id=${customer_id.value}&step=4`);
return;
}
try {
if (normalizedOptions.isMobile === false) {
const didLinkSelectedBooking = await finalizeSelectedOrderBookingForDesktop();
if (!didLinkSelectedBooking) {
return;
}
}
await SessionUser.objects.orders.functions.mark_as_completed(order_id.value);
await Swal.fire({
title: "Fuldført",
text: "Transaktionen er fuldført, omdirigerer automatisk...",
icon: "success",
showConfirmButton: false,
timer: 2000,
});
window.location.href = "/admin/" + department_id.value + "/modules/pos";
} catch (error) {
parseError(error, "stepError");
}
return;
}
if (step.value === 4) {
try {
if (normalizedOptions.isMobile === false) {
const didLinkSelectedBooking = await finalizeSelectedOrderBookingForDesktop();
if (!didLinkSelectedBooking) {
return;
}
}
await SessionUser.objects.orders.functions.mark_as_completed(order_id.value);
await Swal.fire({
title: "Fuldført",
text: "Transaktionen er fuldført, omdirigerer automatisk...",
icon: "success",
showConfirmButton: false,
timer: 2000,
});
window.location.href = "/admin/" + department_id.value + "/modules/pos";
} catch (error) {
parseError(error, "stepError");
}
return;
}
step.value++;
};
export const previousStep = async () => {
if (nextStepDelay.value > 0) {
return;
}
if (!(await flushPosStepSaveBarriers())) {
return;
}
step.value = Math.max(1, step.value - 1);
if (order_id.value) {
pushPosRouteState(`id=${order_id.value}&customer_id=${customer_id.value}&step=${step.value}`);
} else {
pushPosRouteState(`step=${step.value}`);
}
};
export const getCurrentStep = () => {
return step.value;
};
export const setStep = (newStep) => {
step.value = newStep;
};
/** Define the current order dynamic variables */
export const customer_id = ref("");
export const customer_name = ref("");
export const customer_data = ref([]); // The full customer data object
export const department_id = ref("");
export const reference = ref("");
export const notes = ref([]); // The customer notes
export const order_notes = ref(""); // The order notes
export const order_po = ref(""); // The PO number
export const order_safety_seal = ref(""); // Optional safety seal metadata
export const reg_1 = ref(""); // Only uppercase letters and numbers without spaces
export const reg_2 = ref(""); // Only uppercase letters and numbers without spaces
export const reg_3 = ref(""); // Only uppercase letters and numbers without spaces
export const order_id = ref(null);
export const order_items = ref([]);
export const customer_attributes = ref([]);
export const customer_attributes_status = ref("idle");
export const customer_attributes_error = ref(null);
export const user_discounts = ref([]);
const has_user_discounts_loaded = ref(false);
export const scan_data = ref([]);
export const invoiceCollectionId = ref(null);
export const completed_at = ref(null);
let latestCustomerNotesRequestId = 0;
let activeCustomerNotesCustomerNumber = null;
let latestCustomerAttributesRequestId = 0;
const toPositiveInteger = (value) => {
const parsed = Number.parseInt(String(value), 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
};
const getRouteDepartmentId = () => {
return getWindowPathDepartmentId();
};
const getSessionUrlDepartmentId = () => {
try {
return toPositiveInteger(SessionUser.functions?.getDepartmentIdFromUrl?.());
} catch (error) {
return null;
}
};
const getWindowPathDepartmentId = () => {
try {
if (typeof window === "undefined") {
return null;
}
const match = (window.location?.pathname || "").match(/^\/admin\/(\d+)(?:\/|$)/);
return toPositiveInteger(match?.[1]);
} catch (error) {
return null;
}
};
const getSelectedDepartmentId = (candidateDepartmentId = null) => {
const candidates = [
candidateDepartmentId,
department_id.value,
getRouteDepartmentId(),
getSessionUrlDepartmentId(),
getWindowPathDepartmentId(),
];
for (const candidate of candidates) {
const normalizedDepartmentId = toPositiveInteger(candidate);
if (normalizedDepartmentId) {
return normalizedDepartmentId;
}
}
return null;
};
const reportMissingDepartmentForOrderCreation = () => {
parseError(
{
response: {
data: {
data: {
message: "Afdeling kunne ikke bestemmes for ordren",
},
message: "Afdeling kunne ikke bestemmes for ordren",
},
},
},
"stepError"
);
};
const getCurrentOrderDepartmentId = async (targetOrderId) => {
const normalizedOrderId = toPositiveInteger(targetOrderId);
if (!normalizedOrderId) {
return null;
}
if (typeof SessionUser.objects.orders.functions.get_department_id === "function") {
return toPositiveInteger(await SessionUser.objects.orders.functions.get_department_id(normalizedOrderId));
}
if (typeof SessionUser.objects.orders.get.single === "function") {
const order = await SessionUser.objects.orders.get.single(normalizedOrderId);
return toPositiveInteger(order?.department_id);
}
return null;
};
export const ensureCurrentOrderDepartment = async (targetDepartmentId = null) => {
const normalizedOrderId = toPositiveInteger(order_id.value);
const normalizedDepartmentId = getSelectedDepartmentId(targetDepartmentId);
if (!normalizedOrderId || !normalizedDepartmentId) {
return true;
}
try {
const currentDepartmentId = await getCurrentOrderDepartmentId(normalizedOrderId);
if (currentDepartmentId === normalizedDepartmentId) {
department_id.value = normalizedDepartmentId;
return true;
}
parseError(
{
response: {
data: {
data: {
message: "Ordren tilhører ikke den valgte afdeling",
},
message: "Ordren tilhører ikke den valgte afdeling",
},
},
},
"stepError"
);
return false;
} catch (error) {
parseError(error, "stepError");
return false;
}
};
const MATERIAL_PRODUCT_IDS = new Set([53, 54]);
const normalizeOrderMetadataString = (value) => {
return value === null || value === undefined ? "" : String(value);
};
const getNullableOrderMetadataValue = (value) => {
const normalizedValue = normalizeOrderMetadataString(value).trim();
return normalizedValue === "" ? null : normalizedValue;
};
const resolveCustomerNumber = (customerOrNumber) => {
if (customerOrNumber && typeof customerOrNumber === "object") {
return toPositiveInteger(
customerOrNumber.customerNumber ?? customerOrNumber.customer_number ?? customerOrNumber.id
);
}
return toPositiveInteger(customerOrNumber);
};
const getSelectedCustomerNumber = () => {
return resolveCustomerNumber(customer_id.value);
};
const normalizePhoneDisplayValue = (value) => {
if (value === null || value === undefined) {
return "";
}
if (typeof value === "string" || typeof value === "number") {
const normalizedValue = String(value).trim();
return normalizedValue === "" ? "" : normalizedValue;
}
if (Array.isArray(value) || typeof value !== "object") {
return "";
}
const numberCandidate =
value.number ?? value.phoneNumber ?? value.phone_number ?? value.mobilePhone ?? value.mobile_phone ?? null;
if (numberCandidate === null || numberCandidate === undefined) {
return "";
}
return normalizePhoneDisplayValue(numberCandidate);
};
const resolveNormalizedPhoneNumber = (...candidates) => {
for (const candidate of candidates) {
const normalizedValue = normalizePhoneDisplayValue(candidate);
if (normalizedValue !== "") {
return normalizedValue;
}
}
return "";
};
const normalizeCustomerRecord = (customer, fallback = {}) => {
if (!customer || typeof customer !== "object" || Array.isArray(customer)) {
const fallbackCustomerNumber = resolveCustomerNumber(
fallback.customerNumber ?? fallback.customer_number ?? customer
);
return {
id: fallback.id ?? fallbackCustomerNumber ?? null,
customerNumber: fallbackCustomerNumber,
name: fallback.name ?? fallback.customer_name ?? fallback.customerName ?? "",
address: fallback.address ?? "",
zip: fallback.zip ?? fallback.zip_code ?? fallback.postalCode ?? "",
city: fallback.city ?? "",
mobilePhone: resolveNormalizedPhoneNumber(
fallback.mobilePhone,
fallback.mobile_phone,
fallback.phone?.number,
fallback.phone,
fallback.phoneNumber,
fallback.phone_number
),
email: fallback.email ?? "",
corporateIdentificationNumber:
fallback.corporateIdentificationNumber ?? fallback.corporate_identification_number ?? fallback.cvr ?? "",
economic_customer: fallback.economic_customer ?? fallbackCustomerNumber,
barred: Boolean(fallback.barred),
};
}
const resolvedCustomerNumber = resolveCustomerNumber(
customer.customerNumber ??
customer.customer_number ??
customer.economic_customer ??
customer.economicCustomer ??
fallback.customerNumber ??
fallback.customer_number
);
return {
...customer,
id: customer.id ?? resolvedCustomerNumber ?? null,
customerNumber: resolvedCustomerNumber,
name:
customer.name ?? customer.customer_name ?? customer.customerName ?? fallback.name ?? fallback.customer_name ?? "",
address: customer.address ?? fallback.address ?? "",
zip: customer.zip ?? customer.zip_code ?? customer.postalCode ?? fallback.zip ?? fallback.zip_code ?? "",
city: customer.city ?? fallback.city ?? "",
mobilePhone: resolveNormalizedPhoneNumber(
customer.mobilePhone,
customer.mobile_phone,
customer.phone?.number,
customer.phone,
customer.phoneNumber,
customer.phone_number,
fallback.mobilePhone,
fallback.mobile_phone,
fallback.phone?.number,
fallback.phone,
fallback.phoneNumber,
fallback.phone_number
),
email: customer.email ?? fallback.email ?? "",
corporateIdentificationNumber:
customer.corporateIdentificationNumber ??
customer.corporate_identification_number ??
customer.cvr ??
fallback.corporateIdentificationNumber ??
fallback.corporate_identification_number ??
fallback.cvr ??
"",
economic_customer: customer.economic_customer ?? customer.economicCustomer ?? resolvedCustomerNumber,
barred: Boolean(customer.barred ?? fallback.barred),
};
};
const clearSelectedCustomerState = (options = {}) => {
const normalizedOptions = {
clearOrderNotes: false,
...options,
};
customer_id.value = "";
customer_name.value = "";
customer_data.value = [];
customer_attributes.value = [];
customer_attributes_status.value = "idle";
customer_attributes_error.value = null;
latestCustomerAttributesRequestId += 1;
notes.value = [];
user_discounts.value = [];
has_user_discounts_loaded.value = false;
activeCustomerNotesCustomerNumber = null;
latestCustomerNotesRequestId += 1;
if (normalizedOptions.clearOrderNotes) {
order_notes.value = "";
}
};
export const getStoredPosOrderId = () => {
const storedOrderId = toPositiveInteger(localStorage.getItem("pos_order_id"));
if (!storedOrderId) {
localStorage.removeItem("pos_order_id");
return null;
}
return storedOrderId;
};
export const clearStoredPosOrderId = () => {
localStorage.removeItem("pos_order_id");
};
const hasActivePosOrderContext = () => {
return Boolean(
toPositiveInteger(order_id.value) ||
getSelectedCustomerNumber() ||
String(customer_name.value ?? "").trim() ||
(Array.isArray(order_items.value) && order_items.value.length > 0) ||
String(reference.value ?? "").trim() ||
String(order_notes.value ?? "").trim() ||
String(order_po.value ?? "").trim() ||
String(order_safety_seal.value ?? "").trim() ||
String(reg_1.value ?? "").trim() ||
String(reg_2.value ?? "").trim() ||
String(reg_3.value ?? "").trim() ||
invoiceCollectionId.value ||
completed_at.value ||
getStoredPosOrderId()
);
};
export const clearActivePosOrderContext = (options = {}) => {
const normalizedOptions = {
clearStep: false,
clearStoredOrderId: true,
...options,
};
const hadActiveContext = hasActivePosOrderContext();
clearErrors();
if (normalizedOptions.clearStep) {
step.value = 1;
}
order_id.value = null;
clearSelectedCustomerState({ clearOrderNotes: true });
order_items.value = [];
user_discounts.value = [];
has_user_discounts_loaded.value = false;
scans.value = [];
scan_data.value = [];
invoiceCollectionId.value = null;
completed_at.value = null;
reference.value = "";
order_notes.value = "";
order_po.value = "";
order_safety_seal.value = "";
reg_1.value = "";
reg_2.value = "";
reg_3.value = "";
isCreatingOrder.value = false;
createOrderRequest = null;
clearSelectedOrderBookingSelection();
if (normalizedOptions.clearStoredOrderId) {
clearStoredPosOrderId();
}
return hadActiveContext;
};
const SELECTED_ORDER_BOOKING_ID_STORAGE_KEY = "pos_selected_order_booking_id";
const SELECTED_ORDER_BOOKING_PLATE_STORAGE_KEY = "pos_selected_order_booking_plate";
const SELECTED_ORDER_BOOKING_SKIPPED_PLATE_STORAGE_KEY = "pos_selected_order_booking_skipped_plate";
export const selectedOrderBookingId = ref(null);
export const selectedOrderBookingPlate = ref("");
export const selectedOrderBookingSkippedPlate = ref("");
const normalizeVehiclePlateForBookingSelection = (value) =>
String(value ?? "")
.replace(/\s/g, "")
.toUpperCase();
const getCurrentBookingSelectionDepartmentId = () => {
return toPositiveInteger(department_id.value);
};
const doesOrderBookingMatchCurrentDepartment = (booking = null) => {
const currentDepartmentId = getCurrentBookingSelectionDepartmentId();
if (!currentDepartmentId) {
return true;
}
const bookingDepartmentId = toPositiveInteger(booking?.department ?? booking?.department_id);
if (!bookingDepartmentId) {
return false;
}
return bookingDepartmentId === currentDepartmentId;
};
const restoreSelectedOrderBookingState = () => {
if (typeof localStorage === "undefined") {
return;
}
selectedOrderBookingId.value = toPositiveInteger(localStorage.getItem(SELECTED_ORDER_BOOKING_ID_STORAGE_KEY));
selectedOrderBookingPlate.value = normalizeVehiclePlateForBookingSelection(
localStorage.getItem(SELECTED_ORDER_BOOKING_PLATE_STORAGE_KEY)
);
selectedOrderBookingSkippedPlate.value = normalizeVehiclePlateForBookingSelection(
localStorage.getItem(SELECTED_ORDER_BOOKING_SKIPPED_PLATE_STORAGE_KEY)
);
if (selectedOrderBookingId.value) {
localStorage.setItem(SELECTED_ORDER_BOOKING_ID_STORAGE_KEY, String(selectedOrderBookingId.value));
} else {
localStorage.removeItem(SELECTED_ORDER_BOOKING_ID_STORAGE_KEY);
}
if (selectedOrderBookingPlate.value) {
localStorage.setItem(SELECTED_ORDER_BOOKING_PLATE_STORAGE_KEY, selectedOrderBookingPlate.value);
} else {
localStorage.removeItem(SELECTED_ORDER_BOOKING_PLATE_STORAGE_KEY);
}
if (selectedOrderBookingSkippedPlate.value) {
localStorage.setItem(SELECTED_ORDER_BOOKING_SKIPPED_PLATE_STORAGE_KEY, selectedOrderBookingSkippedPlate.value);
} else {
localStorage.removeItem(SELECTED_ORDER_BOOKING_SKIPPED_PLATE_STORAGE_KEY);
}
};
const persistSelectedOrderBookingState = () => {
if (typeof localStorage === "undefined") {
return;
}
if (selectedOrderBookingId.value) {
localStorage.setItem(SELECTED_ORDER_BOOKING_ID_STORAGE_KEY, String(selectedOrderBookingId.value));
} else {
localStorage.removeItem(SELECTED_ORDER_BOOKING_ID_STORAGE_KEY);
}
if (selectedOrderBookingPlate.value) {
localStorage.setItem(SELECTED_ORDER_BOOKING_PLATE_STORAGE_KEY, selectedOrderBookingPlate.value);
} else {
localStorage.removeItem(SELECTED_ORDER_BOOKING_PLATE_STORAGE_KEY);
}
if (selectedOrderBookingSkippedPlate.value) {
localStorage.setItem(SELECTED_ORDER_BOOKING_SKIPPED_PLATE_STORAGE_KEY, selectedOrderBookingSkippedPlate.value);
} else {
localStorage.removeItem(SELECTED_ORDER_BOOKING_SKIPPED_PLATE_STORAGE_KEY);
}
};
export const clearSelectedOrderBookingSelection = (options = {}) => {
const normalizedOptions = {
clearSkippedPlate: true,
...options,
};
selectedOrderBookingId.value = null;
selectedOrderBookingPlate.value = "";
if (normalizedOptions.clearSkippedPlate) {
selectedOrderBookingSkippedPlate.value = "";
}
persistSelectedOrderBookingState();
};
export const setSelectedOrderBookingSelection = (booking, plate = null) => {
const normalizedBookingId = toPositiveInteger(booking?.id ?? booking);
const normalizedPlate = normalizeVehiclePlateForBookingSelection(
plate ?? booking?.reg_1 ?? booking?.reg_2 ?? booking?.plate ?? ""
);
selectedOrderBookingId.value = normalizedBookingId;
selectedOrderBookingPlate.value = normalizedPlate;
selectedOrderBookingSkippedPlate.value = "";
persistSelectedOrderBookingState();
};
export const skipSelectedOrderBookingSelection = (plate) => {
clearSelectedOrderBookingSelection({ clearSkippedPlate: false });
selectedOrderBookingSkippedPlate.value = normalizeVehiclePlateForBookingSelection(plate);
persistSelectedOrderBookingState();
};
export const isSelectedOrderBookingSkippedForPlate = (plate) => {
const normalizedPlate = normalizeVehiclePlateForBookingSelection(plate);
return normalizedPlate !== "" && normalizedPlate === selectedOrderBookingSkippedPlate.value;
};
restoreSelectedOrderBookingState();
export const restoreStoredPosOrderId = async (
options = {
validateOrder: true,
customerId: null,
departmentId: null,
allowCompleted: false,
syncDepartment: false,
}
) => {
const storedOrderId = getStoredPosOrderId();
if (!storedOrderId) {
return null;
}
if (options.validateOrder === false) {
order_id.value = storedOrderId;
return storedOrderId;
}
try {
const storedOrder = await SessionUser.objects.orders.get.single(storedOrderId);
const resolvedOrderId = toPositiveInteger(storedOrder?.id);
if (resolvedOrderId !== storedOrderId) {
throw new Error("Stored order was not found");
}
const selectedCustomerId = toPositiveInteger(options.customerId);
if (selectedCustomerId && toPositiveInteger(storedOrder?.customer_id) !== selectedCustomerId) {
throw new Error("Stored order customer does not match current customer");
}
if (!options.allowCompleted && storedOrder?.completed_at) {
throw new Error("Stored order is already completed");
}
const selectedDepartmentId = toPositiveInteger(options.departmentId);
if (selectedDepartmentId && toPositiveInteger(storedOrder?.department_id) !== selectedDepartmentId) {
if (options.syncDepartment === true) {
order_id.value = storedOrderId;
const isCurrentDepartment = await ensureCurrentOrderDepartment(selectedDepartmentId);
if (!isCurrentDepartment) {
throw new Error("Stored order department does not match current department");
}
} else {
throw new Error("Stored order department does not match current department");
}
}
order_id.value = storedOrderId;
return storedOrderId;
} catch (error) {
if (toPositiveInteger(order_id.value) === storedOrderId) {
order_id.value = null;
}
clearStoredPosOrderId();
return null;
}
};
export const reset_all_values = () => {
// Reset all values to their initial state
clearActivePosOrderContext({ clearStep: true });
// Clear the query parameters
pushPosRouteState("step=1");
};
/** Whenever the reg_1, reg_2 or reg_3 changes, remove any spaces and make the string uppercase */
const normalizeRegistrationValue = (value) =>
String(value ?? "")
.replace(/\s/g, "")
.toUpperCase();
export const isBlankPosMetadataValue = (value) => String(value ?? "").trim().length === 0;
const getPosRoutePath = () => {
const normalizedDepartmentId = Number.parseInt(String(department_id.value || ""), 10);
if (Number.isInteger(normalizedDepartmentId) && normalizedDepartmentId > 0) {
return `/admin/${normalizedDepartmentId}/modules/pos`;
}
return window.location.pathname && window.location.pathname !== "/" ? window.location.pathname : "/admin/modules/pos";
};
export const pushPosRouteState = (queryString) => {
const normalizedQueryString = String(queryString || "").replace(/^\?/, "");
window.history.pushState({}, "", `${getPosRoutePath()}?${normalizedQueryString}`);
};
watch([reg_1, reg_2, reg_3], () => {
reg_1.value = normalizeRegistrationValue(reg_1.value);
reg_2.value = normalizeRegistrationValue(reg_2.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 */
export const getOrderDetails = async (id = null) => {
const token = localStorage.getItem("token");
const targetOrderId = id ?? order_id.value;
if (!token || !targetOrderId) {
return null;
}
const requestId = ++latestOrderDetailsRequestId;
const metadataMutationSequenceAtRequest = latestOrderMetadataMutationSequence;
const hadActiveMetadataMutationAtRequest = activeOrderMetadataMutationCount > 0;
return axios
.get(API_URL + "/order?id=" + targetOrderId, {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((response) => {
const order_data = response.data.data;
if (!order_data) {
return null;
}
const normalizedResponseOrderId = toPositiveInteger(order_data.id ?? targetOrderId);
const normalizedCurrentOrderId = toPositiveInteger(order_id.value);
const isStaleForDifferentOrder =
requestId !== latestOrderDetailsRequestId &&
normalizedCurrentOrderId &&
normalizedResponseOrderId &&
normalizedCurrentOrderId !== normalizedResponseOrderId;
const isStaleBeforeMetadataMutation = metadataMutationSequenceAtRequest !== latestOrderMetadataMutationSequence;
if (!isStaleForDifferentOrder && !isStaleBeforeMetadataMutation && !hadActiveMetadataMutationAtRequest) {
applyOrderDetailsToPosState(order_data, response?.data?.includes ?? {});
}
// Return the order data
return order_data;
})
.catch((error) => {
parseError(error, "stepError");
return null;
});
};
export const getOrderReg1 = async (orderId) => {
const order = await getOrderDetails(orderId);
if (!order) {
return "";
}
return order.reg_1;
};
/** Define the current order functions */
export const createOrder = async (options = { isMobile: false }) => {
if (order_id.value) {
return ensureCurrentOrderDepartment(options.departmentId ?? getDepartment());
}
if (isCreatingOrder.value && createOrderRequest) {
return createOrderRequest;
}
const token = localStorage.getItem("token");
if (!token) {
return Promise.resolve(false);
}
const normalizedBookingId = toPositiveInteger(options.bookingId ?? selectedOrderBookingId.value);
const selectedDepartmentId = getSelectedDepartmentId(options.departmentId);
if (!selectedDepartmentId) {
reportMissingDepartmentForOrderCreation();
return false;
}
isCreatingOrder.value = true;
createOrderRequest = axios
.post(
API_URL + "/orders",
{
customer_id: customer_id.value,
department_id: selectedDepartmentId,
reference: reference.value,
notes: order_notes.value,
po: order_po.value,
safety_seal: getNullableOrderMetadataValue(order_safety_seal.value),
reg_1: reg_1.value,
reg_2: reg_2.value,
reg_3: reg_3.value,
...(normalizedBookingId ? { booking_id: normalizedBookingId } : {}),
is_handheld: options.isMobile,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
)
.then((response) => {
// Check if the response is successful
if (!response.data.success) {
parseError(response, "stepError");
return false;
}
const createdOrder = response.data.data || {};
order_id.value = parseInt(createdOrder.id);
if (!isBlankPosMetadataValue(createdOrder.po)) {
order_po.value = String(createdOrder.po);
}
if (selectedDepartmentId) {
department_id.value = selectedDepartmentId;
}
// Set the order id in the local storage (To be make F5 safe)
localStorage.setItem("pos_order_id", order_id.value);
// Set the query parameters
pushPosRouteState(`id=${order_id.value}&customer_id=${customer_id.value}&step=2`);
return true;
})
.catch((error) => {
parseError(error, "stepError");
return false;
})
.finally(() => {
isCreatingOrder.value = false;
createOrderRequest = null;
});
return createOrderRequest;
};
export const deleteOrder = (options = {}) => {
const token = localStorage.getItem("token");
if (!token) {
return null;
}
return SessionUser.objects.orders.delete
.single(order_id.value, options)
.then((response) => {
if (response?.data?.success === false) {
parseError(response, "stepError");
return false;
}
order_id.value = null;
return response;
})
.catch((error) => {
parseError(error, "stepError");
return false;
});
};
export const getOrderId = () => {
return order_id.value;
};
/** Define the department license plate scans dynamic variables */
export const scans = ref([]);
export const scans_page = ref(1);
export const scans_limit = ref(10);
/** Define the department license plate scans functions */
export const getScans = (forceClearCache = false) => {
if (forceClearCache) {
scans.value = [];
}
// If the scans are already loaded, return them
if (scans.value.length > 0) {
return scans.value;
}
// Get the scans from the API
getScansDepartmentPagination(department_id.value, scans_page.value, scans_limit.value)
.then((response) => {
scans.value = response.data.data;
})
.catch((error) => {
console.error(error);
});
return scans.value;
};
/** Define the department setters */
export const setDepartment = (id = null) => {
const nextDepartmentId = getSelectedDepartmentId(id);
const previousDepartmentKey = String(department_id.value ?? "");
department_id.value = nextDepartmentId ?? "";
if (previousDepartmentKey !== String(nextDepartmentId ?? "")) {
syncPendingBookingsDepartmentState();
void ensureCurrentOrderDepartment(nextDepartmentId);
}
};
/** Define the department getters */
export const getDepartment = () => {
return department_id.value;
};
/** Define the product category dynamic variables */
export const products_category = ref(null);
/** Define the product category functions */
export const setProductsCategory = (category) => {
if (products_category.value === category) {
return;
}
products_category.value = category;
};
export const getProductsCategory = () => {
return products_category.value;
};
export const getUserGlobalDiscount = () => {
// Check if the user discounts are loaded
if (user_discounts.value === undefined) {
getUserDiscounts();
}
// Find the specific discount for the category
let discount = 0;
try {
discount = parseInt(user_discounts.value.find((discount) => discount.id === "999999").percentage) || null;
} catch (e) {}
// If there is no discount, return 0
if (!discount) {
return 0;
}
// Return the discount
return discount;
};
/** Check if the customer is barred */
export const isCustomerBarred = (customer) => {
// Check if the customer.barred field exists, otherwise return false
return customer.barred ? customer.barred : false;
};
/** get the customer email (if present) */
export const getCustomerEmail = () => {
// Check if the customer.email field exists, otherwise return empty string
return customer_data.value.email ? customer_data.value.email : "";
};
/** Select a customer */
export const selectCustomer = (customer, options = {}) => {
const normalizedOptions = {
forceRefresh: false,
...options,
};
// If the customer is null, clear the customer data
if (!customer) {
clearSelectedCustomerState();
return;
}
// Check if the customer is barred
if (isCustomerBarred(customer)) {
/**
Swal.fire({
title: 'Kunden er spærret',
text: 'Denne kunde er spærret og kan ikke bruges',
icon: 'error',
confirmButtonText: 'Ok'
}); */
// Select the 999 customer
searchAndSelectCustomer(999, normalizedOptions);
return;
}
const normalizedCustomer = normalizeCustomerRecord(customer);
const nextCustomerNumber = resolveCustomerNumber(normalizedCustomer);
if (!nextCustomerNumber) {
clearSelectedCustomerState();
return;
}
const currentCustomerNumber = getSelectedCustomerNumber();
const isSameCustomer = currentCustomerNumber === nextCustomerNumber;
if (!isSameCustomer) {
clearSelectedCustomerState();
}
customer_id.value = nextCustomerNumber;
customer_name.value = normalizedCustomer.name || "";
customer_data.value = normalizedCustomer;
console.log(normalizedCustomer);
if (isSameCustomer && !normalizedOptions.forceRefresh) {
return;
}
// Get the customer's notes
fetchCustomerNotes(nextCustomerNumber);
// Get the customer's attributes
loadCustomerAttributes(nextCustomerNumber);
};
/** Is customer selected */
export const isCustomerSelected = () => {
return customer_name.value !== "";
};
export const doesOrderContainMaterial = () => {
return order_items.value.some((item) => MATERIAL_PRODUCT_IDS.has(Number(item?.product?.id ?? item?.product_id)));
};
export const doesOrderContainWashCertificate = () => {
return doesOrderContainWashCertificateProduct(order_items.value);
};
/** Fetch the customer's notes */
export const fetchCustomerNotes = (customerNumber = customer_id.value) => {
const normalizedCustomerNumber = resolveCustomerNumber(customerNumber);
if (!normalizedCustomerNumber) {
activeCustomerNotesCustomerNumber = null;
latestCustomerNotesRequestId += 1;
notes.value = [];
return Promise.resolve(notes.value);
}
const requestId = latestCustomerNotesRequestId + 1;
latestCustomerNotesRequestId = requestId;
activeCustomerNotesCustomerNumber = normalizedCustomerNumber;
return getNotes(normalizedCustomerNumber)
.then((response) => {
const isLatestRequest = requestId === latestCustomerNotesRequestId;
const isStillSelectedCustomer = getSelectedCustomerNumber() === normalizedCustomerNumber;
const isActiveCustomer = activeCustomerNotesCustomerNumber === normalizedCustomerNumber;
if (!isLatestRequest || !isStillSelectedCustomer || !isActiveCustomer) {
return notes.value;
}
notes.value = Array.isArray(response?.data?.data) ? response.data.data : [];
return notes.value;
})
.catch((error) => {
console.error(error);
return notes.value;
});
};
/** Create a customer note */
export const addCustomerNote = (note) => {
const selectedCustomerNumber = getSelectedCustomerNumber();
createNote(selectedCustomerNumber, note)
.then((response) => {
fetchCustomerNotes(selectedCustomerNumber);
})
.catch((error) => {
console.error(error);
});
};
/** Delete a customer note */
export const removeCustomerNote = (id) => {
const selectedCustomerNumber = getSelectedCustomerNumber();
deleteNote(id)
.then((response) => {
fetchCustomerNotes(selectedCustomerNumber);
})
.catch((error) => {
console.error(error);
});
};
/** Show confirmation delete note dialog */
export const showDeleteNoteDialog = (noteId) => {
Swal.fire({
title: "Are you sure?",
text: `You are about to delete the note: ${noteId}`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Yes, delete it!",
cancelButtonText: "No, keep it",
}).then((result) => {
if (result.isConfirmed) {
removeCustomerNote(noteId);
}
});
};
/** Select a scan */
export const selectScan = (scan) => {
// Check which registration number is empty
//if (!Object.keys(reg_1.value).length > 0 || !reg_1.value) {
// Set the registration number 1
//reg_1.value = scan.plate;
//return;
//}
// Set the registration number 1
reg_1.value = scan.plate;
};
const cached_customer_names = [];
const activeCustomerSelectionRequests = new Map();
/** Get the customer's name */
export const getCustomerName = async (customerNumber) => {
if (!customerNumber) {
return "";
}
// Check if the customer name is already cached
if (cached_customer_names[customerNumber]) {
return cached_customer_names[customerNumber];
}
// Get the customer data
await authenticatedRequest(`/users/customer?customer_number=${customerNumber}`, "GET")
.then((response) => {
console.log(response);
// Cache the customer name
cached_customer_names[customerNumber] = response.data.data.customer_name;
return cached_customer_names[customerNumber];
})
.catch((error) => {
// If the customer is not found, return an empty string
cached_customer_names[customerNumber] = "";
console.error(error);
});
return "";
};
/** Search, then select customer by customer number */
export const searchAndSelectCustomer = async (customerNumber, options = {}) => {
const normalizedOptions = {
forceRefresh: false,
...options,
};
const normalizedCustomerNumber = resolveCustomerNumber(customerNumber);
if (!normalizedCustomerNumber) {
return null;
}
const selectedCustomerNumber = getSelectedCustomerNumber();
const hasSelectedCustomerData =
customer_data.value &&
typeof customer_data.value === "object" &&
!Array.isArray(customer_data.value) &&
Object.keys(customer_data.value).length > 0;
if (
!normalizedOptions.forceRefresh &&
selectedCustomerNumber === normalizedCustomerNumber &&
hasSelectedCustomerData
) {
return customer_data.value;
}
if (!normalizedOptions.forceRefresh && activeCustomerSelectionRequests.has(normalizedCustomerNumber)) {
return await activeCustomerSelectionRequests.get(normalizedCustomerNumber);
}
// Get the customer data
const request = authenticatedRequest(`/users/customer?customer_number=${normalizedCustomerNumber}`, "GET")
.then((response) => {
console.log(response);
const responseData = response?.data?.data ?? {};
const rawEconomicCustomer = responseData.economic_customer ?? null;
const normalizedCustomer = normalizeCustomerRecord(rawEconomicCustomer, {
...responseData,
customerNumber: resolveCustomerNumber(rawEconomicCustomer) ?? normalizedCustomerNumber,
});
selectCustomer(normalizedCustomer, normalizedOptions);
return normalizedCustomer;
})
.catch((error) => {
console.error(error);
return null;
})
.finally(() => {
if (activeCustomerSelectionRequests.get(normalizedCustomerNumber) === request) {
activeCustomerSelectionRequests.delete(normalizedCustomerNumber);
}
});
if (!normalizedOptions.forceRefresh) {
activeCustomerSelectionRequests.set(normalizedCustomerNumber, request);
}
return await request;
};
/** Get order items */
export const loadOrderItems = async () => {
const normalizedOrderId = Number(order_id.value);
if (!Number.isInteger(normalizedOrderId) || normalizedOrderId <= 0) {
order_items.value = [];
return order_items.value;
}
const response = await getOrderItems(normalizedOrderId);
if (!response || !response.data || !Array.isArray(response.data.data)) {
order_items.value = [];
return order_items.value;
}
order_items.value = response.data.data;
return order_items.value;
};
const isStandaloneOrderItem = (item) => item?.related_item_id === null || item?.related_item_id === undefined;
const getOrderItemProductId = (item) => {
return toPositiveInteger(item?.product?.id ?? item?.product_id);
};
const getOrderItemQuantity = (item) => {
const parsedQuantity = Number.parseInt(String(item?.quantity ?? 1), 10);
return Number.isInteger(parsedQuantity) && parsedQuantity > 0 ? parsedQuantity : null;
};
const getOrderItemNotes = (item) => {
const notesValue = String(item?.notes ?? item?.product?.notes ?? "").trim();
return notesValue === "" ? null : notesValue;
};
const copyLastWashReferenceToEmptyCurrentOrder = async (sourceReference) => {
const normalizedSourceReference = String(sourceReference ?? "").trim();
if (!normalizedSourceReference || !isBlankPosMetadataValue(reference.value)) {
return;
}
reference.value = normalizedSourceReference;
const normalizedOrderId = toPositiveInteger(order_id.value);
if (normalizedOrderId) {
await saveOrderMetadataField("reference", normalizedSourceReference, normalizedOrderId);
}
};
const createCopiedOrderItem = (targetOrderId, sourceItem, relatedItemId = null) => {
const productId = getOrderItemProductId(sourceItem);
const quantity = getOrderItemQuantity(sourceItem);
if (!productId || !quantity) {
return Promise.resolve(null);
}
return createOrderItem(
targetOrderId,
productId,
quantity,
relatedItemId,
getOrderItemNotes(sourceItem)
);
};
export const copyLastWashItemsToCurrentOrder = async (sourceItems = [], options = {}) => {
const normalizedSourceItems = Array.isArray(sourceItems) ? sourceItems.filter(Boolean) : [];
const standaloneItems = normalizedSourceItems.filter(isStandaloneOrderItem);
const primarySourceItem =
standaloneItems.find((item) => getOrderItemProductId(item) && getOrderItemQuantity(item)) ||
normalizedSourceItems.find((item) => getOrderItemProductId(item) && getOrderItemQuantity(item));
if (!primarySourceItem) {
return false;
}
await copyLastWashReferenceToEmptyCurrentOrder(options.sourceReference ?? options.sourceOrder?.reference);
const didEnsureOrder = await createOrder({
...options,
isMobile: false,
});
if (!didEnsureOrder) {
return false;
}
const normalizedOrderId = toPositiveInteger(order_id.value);
if (!normalizedOrderId) {
return false;
}
const currentItems = await loadOrderItems();
await Promise.all((currentItems || []).map((item) => removeOrderItem(item.id)).filter(Boolean));
order_items.value = [];
const createdPrimaryResponse = await createCopiedOrderItem(normalizedOrderId, primarySourceItem, null);
const createdPrimaryItemId = toPositiveInteger(createdPrimaryResponse?.data?.data?.id);
if (!createdPrimaryItemId) {
await loadOrderItems();
return false;
}
const primarySourceItemId = toPositiveInteger(primarySourceItem?.id);
const addonItems = primarySourceItemId
? normalizedSourceItems.filter((item) => toPositiveInteger(item?.related_item_id) === primarySourceItemId)
: [];
const addonItemIds = new Set(addonItems.map((item) => toPositiveInteger(item?.id)).filter(Boolean));
const additionalItems = standaloneItems.filter((item) => {
const sourceItemId = toPositiveInteger(item?.id);
if (sourceItemId && sourceItemId === primarySourceItemId) {
return false;
}
if (sourceItemId && addonItemIds.has(sourceItemId)) {
return false;
}
return item !== primarySourceItem && getOrderItemProductId(item) && getOrderItemQuantity(item);
});
await Promise.all(
addonItems.map((item) => createCopiedOrderItem(normalizedOrderId, item, createdPrimaryItemId)).filter(Boolean)
);
await Promise.all(additionalItems.map((item) => createCopiedOrderItem(normalizedOrderId, item, null)).filter(Boolean));
await loadOrderItems();
return true;
};
/** Get customer attributes */
export const loadCustomerAttributes = async (customerNumber = customer_id.value) => {
const normalizedCustomerNumber = resolveCustomerNumber(customerNumber);
if (!normalizedCustomerNumber) {
customer_attributes.value = [];
customer_attributes_status.value = "idle";
customer_attributes_error.value = null;
return customer_attributes.value;
}
const requestId = ++latestCustomerAttributesRequestId;
customer_attributes.value = [];
customer_attributes_status.value = "loading";
customer_attributes_error.value = null;
try {
const response = await getAttributes(normalizedCustomerNumber);
if (
requestId !== latestCustomerAttributesRequestId ||
getSelectedCustomerNumber() !== normalizedCustomerNumber
) {
return customer_attributes.value;
}
if (
!response?.data?.success
|| !Array.isArray(response?.data?.data)
|| !hasValidCustomerProductRestrictionContract(response.data.data)
) {
throw new Error("Customer attributes response was invalid");
}
customer_attributes.value = response.data.data;
customer_attributes_status.value = "ready";
return customer_attributes.value;
} catch (error) {
if (
requestId === latestCustomerAttributesRequestId &&
getSelectedCustomerNumber() === normalizedCustomerNumber
) {
customer_attributes.value = [];
customer_attributes_status.value = "error";
customer_attributes_error.value = error;
}
return customer_attributes.value;
}
};
export const retryCustomerAttributes = () => loadCustomerAttributes(customer_id.value);
const getCustomerAttributeReadinessRestriction = () => {
if (!getSelectedCustomerNumber() || customer_attributes_status.value === "ready") {
return null;
}
const isLoadFailure = customer_attributes_status.value === "error";
return {
restricted: true,
rule: null,
rules: [],
collections: [],
messageKey: isLoadFailure ? "pos.restrictions.load_failed" : "pos.restrictions.loading",
reason: isLoadFailure ? "customer-attributes-error" : "customer-attributes-loading",
};
};
/** Check if an attribute is set in the customer attributes */
export const hasAttribute = (attrib) => {
// Check if the customer has been loaded
if (!customer_attributes.value) {
return false;
}
return customer_attributes.value.some((attribute) => attribute.attribute === attrib);
};
export const customerRequiresReferenceNumber = () => {
return hasAttribute("requiresReferenceNumber");
};
export const customerUsesPONumbers = () => {
return hasAttribute("usePONumbers");
};
/** Check if the product category is allowed */
export const getProductCategoryRestriction = (category, options = {}) => {
return getProductCategoryRestrictionForCustomer(category, customer_attributes.value, options);
};
export const productCategoryAllowed = (category, options = {}) => {
return !isProductCategoryRestrictedForCustomer(category, customer_attributes.value, options);
};
/** Check if the customer should be billed per order, or per billing period */
export const invoiceAllOrdersIndividually = () => {
// Check if the customer has the attribute invoiceAllOrdersIndividually
return hasAttribute("invoiceAllOrdersIndividually");
};
/** Check if the customer should be billed using Stripe */
export const invoiceUsingStripe = () => {
// Check if the customer has the attribute invoiceWithStripe
return hasAttribute("invoiceWithStripe");
};
/** Check if the customer is allowed to buy spot free products */
export const canBuySpotFree = () => {
// Check if the customer has the attribute restrictSpotFree
return !hasAttribute("restrictSpotFree");
};
/** Check if the customer is allowed to buy interior cleaning products */
export const canBuyInteriorCleaning = () => {
// Check if the customer has the attribute restrictInteriorCleaning
return !hasAttribute("restrictInteriorCleaning");
};
/** Check if the customer is allowed to buy tank cleaning products */
export const canBuyTankCleaning = () => {
// Check if the customer has the attribute restrictTankCleaning
return !hasAttribute("restrictTankCleaning");
};
/** Check if the customer is allowed to buy additional services */
export const canBuyAdditionalServices = () => {
// Check if the customer has the attribute restrictAdditionalServices
return !hasAttribute("restrictAdditionalServices");
};
/** Check if the customer has the onlyTankCleaning attribute */
export const hasOnlyTankCleaning = () => {
return hasAttribute("onlyTankCleaning");
};
/** Check if a product is restricted based on customer attributes */
export const getProductRestriction = (product, options = {}) => {
const readinessRestriction = getCustomerAttributeReadinessRestriction();
if (readinessRestriction) {
return readinessRestriction;
}
return getCustomerProductRestriction(product, customer_attributes.value, options);
};
export const isProductRestricted = (product, options = {}) => {
return getProductRestriction(product, options).restricted;
};
const getAddonProductForRestriction = (addon) => {
const addonProduct = addon?.product || addon || {};
return {
...addonProduct,
id: addon?.option_id ?? addonProduct.id ?? addon?.product_id ?? addon?.id,
name: addon?.name || addonProduct.name,
category: addonProduct.category ?? addon?.category,
category_name: addonProduct.category_name ?? addon?.category_name,
};
};
/** Check if an addon is restricted based on customer attributes */
export const getAddonRestriction = (addon, options = {}) => {
const readinessRestriction = getCustomerAttributeReadinessRestriction();
if (readinessRestriction) {
return readinessRestriction;
}
return getCustomerProductRestriction(getAddonProductForRestriction(addon), customer_attributes.value, {
includeNumericAddonCategory: true,
isRelatedAddon: true,
...options,
});
};
export const isAddonRestricted = (addon, options = {}) => {
return getAddonRestriction(addon, options).restricted;
};
export function loadOrderDetails(onAfterSuccess = null, options = {}) {
return getOrderDetails().then(async (response) => {
console.log(response);
const selectedDepartmentId = getSelectedDepartmentId(options.departmentId ?? null);
if (
response &&
options.syncDepartmentWithSelection === true &&
selectedDepartmentId &&
toPositiveInteger(response.department_id) !== selectedDepartmentId
) {
await ensureCurrentOrderDepartment(selectedDepartmentId);
}
if (response && onAfterSuccess) {
onAfterSuccess(response);
}
return response;
});
}
/** Set the order id */
export const setOrderId = (id, options = {}) => {
const selectedDepartmentId = getSelectedDepartmentId(options.departmentId ?? null);
order_id.value = id;
// Load the order details
loadOrderDetails(null, {
departmentId: selectedDepartmentId,
syncDepartmentWithSelection: options.syncDepartmentWithSelection !== false,
});
if (options.loadItems !== false) {
// Load the order items
loadOrderItems();
}
};
/** Delete everything button */
export const deleteEverything = async () => {
// If the order is created, delete it
if (order_id.value) {
const deleted = await SessionUser.objects.orders.functions.deleteWithConfirmation(order_id.value).catch((error) => {
parseError(error, "stepError");
return false;
});
if (!deleted) {
return;
}
}
// Clear the cache
clearCache();
location.reload();
};
/** Delete everything button dialog */
export const showDeleteEverythingDialog = () => {
Swal.fire({
title: "Er du sikker?",
text: "Du er ved at slette alt i denne ordre. Dette kan ikke fortrydes.",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ja, slet alt",
cancelButtonText: "Nej, fortryd",
allowEscapeKey: false,
allowOutsideClick: false,
showLoaderOnConfirm: true,
}).then((result) => {
if (result.isConfirmed) {
deleteEverything().then(() => {
Swal.fire("Slettet", "Ordren er slettet", "success");
});
}
});
};
/** Clear cache */
export const clearCache = () => {
// Reset the query parameters
pushPosRouteState("step=1");
// Clear the customer data
clearCustomerSelection();
// Clear the order data
order_id.value = null;
order_items.value = [];
// Clear the registration numbers
reg_1.value = "";
reg_2.value = "";
reg_3.value = "";
order_po.value = "";
order_safety_seal.value = "";
clearSelectedOrderBookingSelection();
// Clear the scans
scans.value = [];
};
export const clearCustomerSelection = () => {
clearSelectedCustomerState({ clearOrderNotes: true });
};
/** Show delete order dialog */
export const showDeleteOrderDialog = () => {
SessionUser.objects.orders.functions.showDeleteConfirmationModal(order_id.value, () => {
clearCache();
location.reload();
});
};
/** Get users discounts */
export const getUserDiscounts = async () => {
const token = localStorage.getItem("token");
if (!token) {
return null;
}
return axios
.get(API_URL + "/superuser/user/discounts", {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
customer_number: customer_id.value,
},
})
.then((response) => {
user_discounts.value = response.data.data;
//console.log(user_discounts.value);
return user_discounts.value;
})
.catch((error) => {
console.error(error);
});
};
const ensureUserDiscountsLoaded = () => {
if (!has_user_discounts_loaded.value) {
has_user_discounts_loaded.value = true;
getUserDiscounts().then(() => {});
}
};
const isDirectCustomerProductDiscount = (discount, productId) => {
return discount?.product_or_category_id == productId && Number(discount?.is_category) === 0;
};
const parseFixedPriceValue = (value) => {
if (value === null || value === undefined || value === '') {
return null;
}
const parsed = Number.parseInt(String(value), 10);
return Number.isFinite(parsed) ? parsed : null;
};
/** Get user product discount */
export const getUserProductDiscount = (product, allowCategory = null, onlyCategory = null) => {
// Check if the user discounts are loaded
ensureUserDiscountsLoaded();
// Get the product's id
const productId = product.id;
// Get the product's category
const category = getProductsCategory(productId);
// Find the specific discount for the product
let discount = 0;
let allowCategoryDiscount = null;
let categoryDiscount = 0;
let globalDiscount = 0;
let productDiscount = 0;
let allowProductDiscount = null;
let appliedDiscount = 0;
// Check if we should allow the category discount
if (allowCategory || allowCategory === null) {
// If the allowCategory is null, check if the category discount is allowed by the product
if (allowCategory === null) {
allowCategoryDiscount = parseInt(product.apply_category_discount) === 1;
} else if (allowCategory) {
allowCategoryDiscount = true;
}
//console.log('Allow category discount', allowCategoryDiscount);
//console.log(product);
}
// Check if we should allow the product discount
if (allowCategory === null) {
allowProductDiscount = true;
} else {
allowProductDiscount = allowCategory;
}
// Check if we should only allow the category discount
if (onlyCategory) {
allowProductDiscount = false;
//console.log('Only category discount');
}
// Get the global discount
try {
globalDiscount = parseInt(user_discounts.value.find((discount) => discount.id == "999999").percentage) || null;
} catch (e) {}
//console.log('Global discount', globalDiscount);
try {
productDiscount =
parseInt(
user_discounts.value.find(
(discount) => discount.product_or_category_id == productId && discount.is_category == "0"
).percentage
) || null;
} catch (e) {}
//console.log('Product discount', productDiscount);
try {
categoryDiscount =
parseInt(
user_discounts.value.find(
(discount) => discount.product_or_category_id == category && discount.is_category == "1"
).percentage
) || null;
} catch (e) {}
//console.log('Category discount', categoryDiscount);
// If we should allow the product discount, apply it
if (allowProductDiscount && !onlyCategory && productDiscount) {
appliedDiscount = productDiscount;
}
// If the category has a discount, apply it (If the category discount is allowed, and has a higher percentage than the product discount)
if (categoryDiscount && allowCategoryDiscount && categoryDiscount > appliedDiscount) {
appliedDiscount = categoryDiscount;
}
// If the global discount is higher than the applied discount, apply it
if (globalDiscount && globalDiscount > appliedDiscount && allowCategoryDiscount) {
appliedDiscount = globalDiscount;
}
// If there's a global discount, and category discounts are allowed, apply the global discount (If it's higher than the applied discount)
if (globalDiscount && globalDiscount > appliedDiscount && allowCategoryDiscount) {
appliedDiscount = globalDiscount;
}
// If there is no discount, return 0
if (!appliedDiscount) {
return 0;
}
// Return the applied discount
//console.log('Applied discount', appliedDiscount);
return appliedDiscount;
};
export const getUserProductFixedPrice = (product) => {
ensureUserDiscountsLoaded();
const productId = product?.id;
if (productId === null || productId === undefined) {
return null;
}
const fixedPriceDiscount = (Array.isArray(user_discounts.value) ? user_discounts.value : []).find((discount) => (
isDirectCustomerProductDiscount(discount, productId) && parseFixedPriceValue(discount.fixed_price) !== null
));
return fixedPriceDiscount ? parseFixedPriceValue(fixedPriceDiscount.fixed_price) : null;
};
export const getUserProductPrice = (product) => {
const fixedPrice = getUserProductFixedPrice(product);
if (fixedPrice !== null) {
return fixedPrice;
}
const price = Number(product?.price ?? 0);
const discount = Number(getUserProductDiscount(product) || 0);
if (!Number.isFinite(price)) {
return 0;
}
return Number((price - (price * (discount / 100))).toFixed(0));
};
const isPendingOrderItemProduct = (product) => {
return product && typeof product === "object" && String(product.name ?? product.product_name ?? "").trim() !== "";
};
export const clearPendingOrderItems = () => {
order_items.value = order_items.value.filter((item) => item?._pending !== true);
};
export const showPendingCreateOrderItem = (product, quantity, price, relatedItemId = null, notes = "") => {
if (!isPendingOrderItemProduct(product)) {
console.warn("Skipping pending order item without product details", product);
return null;
}
const pendingItem = {
id: `pending-${Date.now()}-${Math.floor(Math.random() * 1000)}`,
_pending: true,
product: product,
product_id: product.id,
related_item_id: relatedItemId,
quantity: quantity,
price: price,
notes: notes || "",
reference: "",
};
order_items.value.push(pendingItem);
return pendingItem;
};
export const showFakeCreateOrderItem = (product, quantity, price, relatedItemId = null, notes = "") => {
return showPendingCreateOrderItem(product, quantity, price, relatedItemId, notes);
};
/** Vehicle search functionality */
export const vehicles_matching = ref([]);
export const isSearching = ref(false);
export const latest_search_id = ref(0);
export const pendingBookings = ref([]);
export const hasLoadedPendingBookings = ref(false);
export const isLoadingPendingBookings = ref(false);
let pendingBookingsRequest = null;
let pendingBookingsRequestDepartmentKey = null;
const pendingPlateBookingRequests = new Map();
const loadedPendingBookingPlates = new Set();
let pendingBookingsDepartmentKey = null;
function getPendingBookingsDepartmentKey() {
return String(department_id.value ?? "");
}
function resetPendingBookingsState(departmentKey = getPendingBookingsDepartmentKey()) {
pendingBookingsDepartmentKey = departmentKey;
pendingBookings.value = [];
hasLoadedPendingBookings.value = false;
isLoadingPendingBookings.value = false;
pendingBookingsRequest = null;
pendingBookingsRequestDepartmentKey = null;
pendingPlateBookingRequests.clear();
loadedPendingBookingPlates.clear();
}
function syncPendingBookingsDepartmentState() {
const currentDepartmentKey = getPendingBookingsDepartmentKey();
if (pendingBookingsDepartmentKey !== currentDepartmentKey) {
resetPendingBookingsState(currentDepartmentKey);
}
return currentDepartmentKey;
}
export const is_latest_search = (search_id) => {
// Check if the search ID is the latest
return search_id === latest_search_id.value;
};
// Get the department booking list
export const loadPendingBookings = () => {
const currentDepartmentKey = syncPendingBookingsDepartmentState();
if (pendingBookingsRequest) {
return pendingBookingsRequest;
}
const requestDepartmentId = department_id.value;
const requestDepartmentKey = currentDepartmentKey;
isLoadingPendingBookings.value = true;
pendingBookingsRequestDepartmentKey = requestDepartmentKey;
pendingBookingsRequest = SessionUser.request(SessionUser.objects.order_bookings.meta.endpoint, "GET", {
filters: "department:" + requestDepartmentId + ",order_id:is null",
page: 1,
limit: 100,
})
.then((response) => {
if (pendingBookingsDepartmentKey !== requestDepartmentKey) {
return pendingBookings.value;
}
pendingBookings.value = Array.isArray(response?.data?.data) ? response.data.data : [];
hasLoadedPendingBookings.value = true;
return pendingBookings.value;
})
.catch((error) => {
console.error("Error:", error);
if (pendingBookingsDepartmentKey === requestDepartmentKey) {
hasLoadedPendingBookings.value = true;
}
return pendingBookings.value;
})
.finally(() => {
if (pendingBookingsRequestDepartmentKey === requestDepartmentKey) {
isLoadingPendingBookings.value = false;
pendingBookingsRequest = null;
pendingBookingsRequestDepartmentKey = null;
}
});
return pendingBookingsRequest;
};
const getOrderBookingSortDateValue = (booking) => {
return booking?.datetime ?? booking?.created_at ?? booking?.date ?? null;
};
const hasOrderBookingSortDateValue = (booking) => {
return Boolean(getOrderBookingSortDateValue(booking));
};
const getOrderBookingSortTimestamp = (booking) => {
const dateValue = getOrderBookingSortDateValue(booking);
const parsedValue = dateValue ? Date.parse(dateValue) : Number.NaN;
if (!Number.isNaN(parsedValue)) {
return parsedValue;
}
const fallbackId = Number.parseInt(String(booking?.id ?? 0), 10);
return Number.isFinite(fallbackId) ? fallbackId : Number.MAX_SAFE_INTEGER;
};
const sortPendingOrderBookings = (bookings = []) => {
return [...bookings].sort((left, right) => {
const leftHasDate = hasOrderBookingSortDateValue(left);
const rightHasDate = hasOrderBookingSortDateValue(right);
if (leftHasDate !== rightHasDate) {
return rightHasDate ? 1 : -1;
}
const timeDifference = getOrderBookingSortTimestamp(left) - getOrderBookingSortTimestamp(right);
if (timeDifference !== 0) {
return timeDifference;
}
return Number.parseInt(String(left?.id ?? 0), 10) - Number.parseInt(String(right?.id ?? 0), 10);
});
};
const getOrderBookingReferenceValue = (booking) => {
return String(booking?.reference ?? booking?.reference_number ?? "").trim();
};
const getOrderBookingNotesValue = (booking) => {
return String(booking?.notes ?? booking?.note ?? "").trim();
};
const getOrderBookingReg1Value = (booking) => {
return normalizeRegistrationValue(booking?.reg_1 ?? booking?.regNr ?? "");
};
const getOrderBookingReg2Value = (booking) => {
return normalizeRegistrationValue(booking?.reg_2 ?? booking?.regNrTrailer ?? "");
};
const getOrderBookingCustomerNumber = (booking) => {
return resolveCustomerNumber(booking?.customer_number ?? booking?.customer_id ?? booking?.customerNumber);
};
const dedupeOrderBookings = (bookings = []) => {
const bookingsByKey = new Map();
bookings.filter(Boolean).forEach((booking) => {
const normalizedBookingId = toPositiveInteger(booking?.id);
const bookingKey =
normalizedBookingId ??
[
normalizeVehiclePlateForBookingSelection(booking?.reg_1),
normalizeVehiclePlateForBookingSelection(booking?.reg_2),
booking?.datetime,
booking?.reference,
booking?.reference_number,
]
.filter(Boolean)
.join("|");
if (!bookingKey || bookingsByKey.has(bookingKey)) {
return;
}
bookingsByKey.set(bookingKey, booking);
});
return Array.from(bookingsByKey.values());
};
const mergePendingBookings = (bookings = []) => {
const mergedBookings = dedupeOrderBookings([...(pendingBookings.value || []), ...(bookings || [])]);
pendingBookings.value = sortPendingOrderBookings(mergedBookings);
return pendingBookings.value;
};
const resolveVehiclePlateBookingMatches = (vehiclePlate, bookingMatches = null) => {
if (Array.isArray(bookingMatches)) {
return sortPendingOrderBookings(
dedupeOrderBookings(bookingMatches.filter((booking) => doesOrderBookingMatchCurrentDepartment(booking)))
);
}
return getVehiclePlateBookings(vehiclePlate);
};
export const getVehiclePlateBookings = (vehiclePlate) => {
const normalizedVehiclePlate = normalizeVehiclePlateForBookingSelection(vehiclePlate);
if (!normalizedVehiclePlate) {
return [];
}
const seenBookingIds = new Set();
const matchingBookings = pendingBookings.value.filter((booking) => {
const isMatch =
normalizeVehiclePlateForBookingSelection(booking?.reg_1) === normalizedVehiclePlate ||
normalizeVehiclePlateForBookingSelection(booking?.reg_2) === normalizedVehiclePlate;
if (!isMatch || !doesOrderBookingMatchCurrentDepartment(booking)) {
return false;
}
const normalizedBookingId = toPositiveInteger(booking?.id);
if (!normalizedBookingId) {
return true;
}
if (seenBookingIds.has(normalizedBookingId)) {
return false;
}
seenBookingIds.add(normalizedBookingId);
return true;
});
return sortPendingOrderBookings(matchingBookings);
};
export const ensureVehiclePlateBookingsLoaded = async (vehiclePlate, _options = {}) => {
const normalizedVehiclePlate = normalizeVehiclePlateForBookingSelection(vehiclePlate);
const currentDepartmentKey = syncPendingBookingsDepartmentState();
if (!normalizedVehiclePlate || !department_id.value) {
return [];
}
if (loadedPendingBookingPlates.has(normalizedVehiclePlate)) {
return getVehiclePlateBookings(normalizedVehiclePlate);
}
if (pendingPlateBookingRequests.has(normalizedVehiclePlate)) {
return pendingPlateBookingRequests.get(normalizedVehiclePlate);
}
const requestDepartmentId = department_id.value;
const requestDepartmentKey = currentDepartmentKey;
const fetchBookingsForField = async (field) => {
const filters = [`department:${requestDepartmentId}`, `${field}:${normalizedVehiclePlate}`, "order_id:is null"].join(
","
);
const response = await SessionUser.request(SessionUser.objects.order_bookings.meta.endpoint, "GET", {
filters,
page: 1,
limit: 250,
});
return Array.isArray(response?.data?.data) ? response.data.data : [];
};
const requestPromise = Promise.all([
fetchBookingsForField("reg_1"),
fetchBookingsForField("reg_2"),
])
.then(([reg1Bookings, reg2Bookings]) => {
if (pendingBookingsDepartmentKey !== requestDepartmentKey) {
return getVehiclePlateBookings(normalizedVehiclePlate);
}
const fetchedBookings = sortPendingOrderBookings(dedupeOrderBookings([...reg1Bookings, ...reg2Bookings]));
mergePendingBookings(fetchedBookings);
loadedPendingBookingPlates.add(normalizedVehiclePlate);
return getVehiclePlateBookings(normalizedVehiclePlate);
})
.catch((error) => {
console.error("Unable to load pending bookings for plate:", normalizedVehiclePlate, error);
if (pendingBookingsDepartmentKey === requestDepartmentKey) {
loadedPendingBookingPlates.add(normalizedVehiclePlate);
}
return getVehiclePlateBookings(normalizedVehiclePlate);
})
.finally(() => {
if (pendingPlateBookingRequests.get(normalizedVehiclePlate) === requestPromise) {
pendingPlateBookingRequests.delete(normalizedVehiclePlate);
}
});
pendingPlateBookingRequests.set(normalizedVehiclePlate, requestPromise);
return requestPromise;
};
export const doesVehiclePlateHaveBooking = (vehiclePlate) => {
return getVehiclePlateBookings(vehiclePlate).length > 0;
};
export const getPreferredVehiclePlateBooking = (vehiclePlate) => {
return getVehiclePlateBookings(vehiclePlate)[0] || null;
};
export const getVehiclePlateBooking = (vehiclePlate) => {
return getPreferredVehiclePlateBooking(vehiclePlate);
};
export const getSelectedVehiclePlateBooking = (vehiclePlate, bookingMatches = null) => {
const normalizedVehiclePlate = normalizeVehiclePlateForBookingSelection(vehiclePlate);
const normalizedBookingId = toPositiveInteger(selectedOrderBookingId.value);
if (!normalizedVehiclePlate || !normalizedBookingId) {
return null;
}
if (normalizeVehiclePlateForBookingSelection(selectedOrderBookingPlate.value) !== normalizedVehiclePlate) {
return null;
}
const matchingBookings = resolveVehiclePlateBookingMatches(normalizedVehiclePlate, bookingMatches);
return (
matchingBookings.find((booking) => toPositiveInteger(booking?.id) === normalizedBookingId) || null
);
};
export const doesVehiclePlateRequireBookingSelection = (vehiclePlate, bookingMatches = null) => {
const normalizedVehiclePlate = normalizeVehiclePlateForBookingSelection(vehiclePlate);
if (!normalizedVehiclePlate) {
return false;
}
const matchingBookings = resolveVehiclePlateBookingMatches(normalizedVehiclePlate, bookingMatches);
if (matchingBookings.length <= 1) {
return false;
}
if (isSelectedOrderBookingSkippedForPlate(normalizedVehiclePlate)) {
return false;
}
return !getSelectedVehiclePlateBooking(normalizedVehiclePlate, matchingBookings);
};
const getSelectedPendingOrderBooking = () => {
const normalizedBookingId = toPositiveInteger(selectedOrderBookingId.value);
if (!normalizedBookingId) {
return null;
}
return (
pendingBookings.value.find((booking) => toPositiveInteger(booking?.id) === normalizedBookingId) || null
);
};
const fetchOrderBookingProductWithPricing = async (productId) => {
const normalizedProductId = toPositiveInteger(productId);
if (!normalizedProductId) {
return null;
}
return await SessionUser.objects.products.get.single(normalizedProductId, {
department_id: department_id.value,
customer_id: getSelectedCustomerNumber(),
category_id: null,
final_price: true,
});
};
const getFirstAvailableWashProduct = async () => {
try {
const products = await SessionUser.objects.products.get.all({
department_id: department_id.value,
is_wash: true,
limit: 1,
});
if (!Array.isArray(products) || products.length === 0) {
return null;
}
return await fetchOrderBookingProductWithPricing(products[0].id);
} catch (error) {
console.error("Unable to fetch fallback wash product:", error);
return null;
}
};
export const hydrateSelectedOrderBookingForDesktop = async () => {
const normalizedOrderId = toPositiveInteger(order_id.value);
const normalizedBookingId = toPositiveInteger(selectedOrderBookingId.value);
if (!normalizedOrderId || !normalizedBookingId) {
return true;
}
try {
let booking = getSelectedPendingOrderBooking();
if (!booking || !Array.isArray(booking?.items)) {
booking = await SessionUser.objects.order_bookings.get.single(normalizedBookingId, {
department_id: department_id.value,
});
}
if (!booking) {
return false;
}
const bookingCustomerNumber = getOrderBookingCustomerNumber(booking);
if (bookingCustomerNumber && bookingCustomerNumber !== getSelectedCustomerNumber()) {
await searchAndSelectCustomer(bookingCustomerNumber);
}
const bookingReference = getOrderBookingReferenceValue(booking);
if (bookingReference !== "" && bookingReference !== reference.value) {
reference.value = await saveOrderMetadataField("reference", bookingReference, normalizedOrderId);
}
const bookingReg1 = getOrderBookingReg1Value(booking);
if (bookingReg1 !== "" && bookingReg1 !== reg_1.value) {
reg_1.value = await saveOrderMetadataField("reg_1", bookingReg1, normalizedOrderId);
}
const bookingReg2 = getOrderBookingReg2Value(booking);
if (bookingReg2 !== reg_2.value) {
reg_2.value = await saveOrderMetadataField("reg_2", bookingReg2, normalizedOrderId);
}
const bookingNotes = getOrderBookingNotesValue(booking);
if (bookingNotes !== "" && bookingNotes !== order_notes.value) {
order_notes.value = await saveOrderMetadataField("notes", bookingNotes, normalizedOrderId);
}
const bookingPo = String(booking?.po ?? "").trim();
if (bookingPo !== "" && isBlankPosMetadataValue(order_po.value)) {
order_po.value = await saveOrderMetadataField("po", bookingPo, normalizedOrderId);
}
const currentOrderItems = await loadOrderItems();
if (Array.isArray(currentOrderItems) && currentOrderItems.length > 0) {
return true;
}
const bookingItems = Array.isArray(booking?.items) ? booking.items : [];
if (bookingItems.length === 0) {
return true;
}
const primaryRawItem = bookingItems[0];
const primaryRawProductId = toPositiveInteger(primaryRawItem?.id);
if (!primaryRawProductId) {
return true;
}
let primaryProduct = await fetchOrderBookingProductWithPricing(primaryRawProductId);
if (!primaryProduct) {
return true;
}
if (!primaryProduct.is_wash) {
const fallbackWashProduct = await getFirstAvailableWashProduct();
if (fallbackWashProduct) {
primaryProduct = fallbackWashProduct;
}
}
const forcedPrimaryPrice = primaryProduct.price ?? null;
const primaryItemResponse = await createOrderItem(
normalizedOrderId,
primaryProduct.id,
1,
null,
null,
forcedPrimaryPrice
);
const relatedPrimaryItemId = toPositiveInteger(primaryItemResponse?.data?.data?.id);
const secondaryBookingItems = bookingItems.slice(1);
for (const bookingItem of secondaryBookingItems) {
const secondaryProductId = toPositiveInteger(bookingItem?.id);
if (!secondaryProductId) {
continue;
}
const secondaryProduct = await fetchOrderBookingProductWithPricing(secondaryProductId);
await createOrderItem(
normalizedOrderId,
secondaryProductId,
Math.max(1, Number.parseInt(String(bookingItem?.quantity ?? 1), 10) || 1),
relatedPrimaryItemId,
String(bookingItem?.notes ?? "").trim() || null,
secondaryProduct?.price ?? null
);
}
await loadOrderItems();
return true;
} catch (error) {
console.error("Unable to hydrate selected desktop order booking:", error);
parseError(error, "stepError");
return false;
}
};
const finalizeSelectedOrderBookingForDesktop = async () => {
const normalizedOrderId = toPositiveInteger(order_id.value);
const normalizedBookingId = toPositiveInteger(selectedOrderBookingId.value);
if (!normalizedOrderId || !normalizedBookingId) {
return true;
}
try {
await SessionUser.objects.orders.set.booking_id(normalizedOrderId, normalizedBookingId);
await SessionUser.objects.order_bookings.set.order_id(normalizedBookingId, normalizedOrderId);
pendingBookings.value = pendingBookings.value.filter(
(booking) => toPositiveInteger(booking?.id) !== normalizedBookingId
);
return true;
} catch (error) {
console.error("Unable to complete selected desktop order booking:", error);
parseError(error, "stepError");
return false;
}
};
const get_unregistered_vehicle_object = (reg) => {
// Generate an id for the unregistered vehicle, this is only used for the dropdown
let id_prefix = 999999999999;
let id = id_prefix + Math.floor(Math.random() * 1000000);
return {
id: id,
user_id: null,
customer_id: null,
customer_name: "",
type: null,
barred: false,
reg: reg,
wash_subscription: false,
addons: {
enabled: 0,
available: 0,
list: [],
},
is_known: true,
};
};
const getUnregisteredVehicleObjects = async (inputValue, search_id) => {
// Check if the search ID is the latest
if (!is_latest_search(search_id)) {
console.log("Search ID is not the latest. Ignoring this search.");
return [];
}
return await SessionUser.request("/department/vehicles/unknown-customer", "GET", {
search: inputValue,
page: 1,
limit: 10,
})
.then((response) => {
console.log("Unregistered vehicles:", response.data.data);
if (!is_latest_search(search_id)) {
return [];
}
let result = response?.data?.data;
let unregistered_vehicles = [];
if (result && result.length > 0) {
// If there are results, parse them into unregistered_vehicles
for (let i = 0; i < result.length; i++) {
// Check if the unregistered vehicle already exists in the vehicles_matching array
let existingVehicle = vehicles_matching.value.find((vehicle) => vehicle.reg === result[i].reg_1);
if (existingVehicle) {
// If it exists, skip adding it again
continue;
}
unregistered_vehicles.push(get_unregistered_vehicle_object(result[i].reg_1));
}
console.warn(unregistered_vehicles, search_id, "Parsed unregistered vehicles for search ID");
return unregistered_vehicles;
} else {
return [];
// Do nothing
}
})
.catch((error) => {
console.error("Error:", error, search_id);
return [];
})
.finally(() => {
console.log("Finish search", search_id);
if (is_latest_search(search_id)) {
// Set the isSearching flag to false
isSearching.value = false;
}
});
};
// Function to register a new search and return the search ID
// This is used to ensure that only the latest search is processed
export const register_new_search = () => {
let search_id = Date.now();
// Set the latest search ID to the current search ID
latest_search_id.value = search_id;
return search_id;
};
export const reg_1_status = ref("unknown");
export const reg_2_status = ref("unknown");
export const reg_3_status = ref("unknown");
// Function to search for vehicles
export const searchVehicle = async (inputValue, search_id) => {
// Check if the search ID is the latest
if (!is_latest_search(search_id)) {
console.log("Search ID is not the latest. Ignoring this search.");
return;
}
// Set the isSearching flag to true
isSearching.value = true;
// Perform the search
SessionUser.request(SessionUser.objects.vehicles.meta.endpoint + "/search", "GET", {
search: inputValue,
...(department_id.value ? { department: department_id.value } : {}),
page: 1,
limit: 10,
}).then((response) => {
// Check if the registration number is already in the vehicles_matching array
if (!is_latest_search(search_id)) {
return;
}
let result = response?.data?.data;
if (result && result.length > 0) {
// If there are results, parse them into vehicles_matching
if (!is_latest_search(search_id)) {
return;
}
vehicles_matching.value = result;
}
});
};
export const attachments = ref([]);
export const fetchAttachments = async (orderId) => {
if (!orderId) {
return [];
}
return await SessionUser.objects.orders.functions
.fetchAttachments(order_id.value)
.then((response) => {
attachments.value = response;
return attachments.value;
})
.catch((error) => {
console.error("Error fetching attachments:", error);
return [];
});
};
export const deleteAttachment = async (attachmentId) => {
if (!attachmentId) {
return false;
}
return await SessionUser.objects.orders.functions
.removeAttachment(order_id.value, attachmentId)
.then((response) => {
// Remove the attachment from the attachments array
attachments.value = attachments.value.filter((attachment) => attachment.id !== attachmentId);
return true;
})
.catch((error) => {
console.error("Error deleting attachment:", error);
return false;
});
};
export const uploadAttachment = async (file = { filename: "", base64String: "" }) => {
if (!file || !file.filename || !file.base64String) {
return false;
}
return await SessionUser.objects.orders.functions
.uploadAttachment(order_id.value, file.filename, file.base64String)
.then((response) => {
// Add the attachment to the attachments
attachments.value.push(response);
return true;
})
.catch((error) => {
console.error("Error uploading attachment:", error);
return false;
});
};
export const downloadAttachment = (attachmentId, orderId = null) => {
if (!attachmentId) {
return false;
}
return SessionUser.objects.orders.functions
.downloadAttachment(orderId ? orderId : order_id.value, attachmentId)
.then((response) => {
// Create a link element
const link = document.createElement("a");
link.href = response;
link.download = response.filename;
link.style.display = "none";
link.target = "_blank";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return true;
})
.catch((error) => {
console.error("Error downloading attachment:", error);
return false;
});
};
export const getAttachmentPreviewLink = (attachmentId) => {
if (!attachmentId) {
return null;
}
return SessionUser.objects.orders.functions
.downloadAttachment(order_id.value, attachmentId)
.then((response) => {
if (!response || typeof response !== "string") {
return null;
}
try {
const parsedUrl = new URL(response);
parsedUrl.searchParams.set("preview", "true");
return parsedUrl.toString();
} catch {
const separator = response.includes("?") ? "&" : "?";
return `${response}${separator}preview=true`;
}
})
.catch((error) => {
console.error("Error getting attachment preview link:", error);
return null;
});
};
</script>