2792 lines
88 KiB
Vue
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>
|