CASH-1426 use correct part number for rain repel discount. rain defen is the actual part number on the part file.
386 lines
14 KiB
JavaScript
386 lines
14 KiB
JavaScript
import store from "@/store";
|
|
import { storeActions } from "@/constants/store-actions";
|
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
|
import { deepClone } from "@/helpers/object-helper";
|
|
import baseMixin from "@/mixins/base-mixin.js";
|
|
|
|
export const promoPartNumberStrings = {
|
|
WIPER_DISCOUNT_PART_NUMBER: "WIPER DISCOUNT",
|
|
RAIN_REPEL_DISCOUNT_PART_NUMBER: "DISC RAIN DEFEN",
|
|
GLASS_DISCOUNT_PART_NUMBER: "DISCOUNT",
|
|
GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN",
|
|
};
|
|
|
|
export const addableVapsPromoPartNumbers = [
|
|
promoPartNumberStrings.RAIN_REPEL_DISCOUNT_PART_NUMBER,
|
|
promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER,
|
|
];
|
|
|
|
export const promoErrorCodes = {
|
|
UNKNOWN: "Unknown",
|
|
PROMO_NOT_YET_IN_USE: "PromoNotYetInUse",
|
|
PROMO_USAGE_COUNT_EXCEEDED: "PromoUsageCountExceeded",
|
|
PROMO_EXPIRED: "PromoExpired",
|
|
PROMO_DOES_NOT_EXIST: "PromoDoesNotExist",
|
|
PROMO_STACKING_NOT_ALLOWED: "PromoStackingNotAllowed",
|
|
SIMILAR_PROMO_ALREADY_ON_ORDER: "SimilarPromoAlreadyOnOrder",
|
|
INVALID_PROMO_ON_ORDER: "InvalidPromoOnOrder",
|
|
PROMO_VALIDATION_ERROR: "PromoValidationError",
|
|
NO_SUITABLE_ITEM_FOR_PROMO: "NoSuitableItemForPromo",
|
|
};
|
|
|
|
const stackingPromoErrorCodes = [
|
|
promoErrorCodes.PROMO_STACKING_NOT_ALLOWED,
|
|
promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER,
|
|
];
|
|
|
|
export const excludeFromInactivePromoErrorCodes = [
|
|
promoErrorCodes.UNKNOWN,
|
|
promoErrorCodes.PROMO_NOT_YET_IN_USE,
|
|
promoErrorCodes.PROMO_USAGE_COUNT_EXCEEDED,
|
|
promoErrorCodes.PROMO_EXPIRED,
|
|
promoErrorCodes.PROMO_DOES_NOT_EXIST,
|
|
];
|
|
|
|
export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"];
|
|
|
|
export function getPromosWithAddableVaps(promoList) {
|
|
if (promoList == null || !promoList.length) {
|
|
return [];
|
|
}
|
|
|
|
return promoList.filter((promo) => addableVapsPromoPartNumbers.includes(promo.partNumber));
|
|
}
|
|
|
|
export function getLineItemsThatMatchPromos(promos, availableLineItems) {
|
|
if (promos == null || availableLineItems == null) {
|
|
return [];
|
|
}
|
|
const matchingLineItems = [];
|
|
promos.forEach((promo) => {
|
|
if (promo.discountedLineItemIds == null) {
|
|
return;
|
|
}
|
|
promo.discountedLineItemIds.forEach((discountedLineItemId) => {
|
|
matchingLineItems.push(
|
|
...availableLineItems.filter((lineItem) => lineItem.id === discountedLineItemId)
|
|
);
|
|
});
|
|
});
|
|
return matchingLineItems;
|
|
}
|
|
|
|
// Note: Addable VAPs for the Promo Validation endpoint can also be on the order already
|
|
// There is no need to only send un-added VAPs in this array.
|
|
export function getAddableVapsFromAvailableLineItems(availableLineItems) {
|
|
const addableVaps = [];
|
|
addableVaps.push(...findLineItemsWithPartType(partTypeStrings.FRONT_WIPER, availableLineItems));
|
|
addableVaps.push(...findLineItemsWithPartType(partTypeStrings.RAIN_REPEL, availableLineItems));
|
|
return addableVaps;
|
|
}
|
|
|
|
export function removeVapsPromosFromPromoArray(promoArray) {
|
|
if (!promoArray) {
|
|
return [];
|
|
}
|
|
const filteredPromoArray = promoArray.filter((promo) => {
|
|
return (
|
|
promo.partNumber != promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER &&
|
|
promo.partNumber != promoPartNumberStrings.RAIN_REPEL_DISCOUNT_PART_NUMBER &&
|
|
promo.partNumber != promoPartNumberStrings.GLASS_CLEANER_DISCOUNT_PART_NUMBER
|
|
);
|
|
});
|
|
return filteredPromoArray;
|
|
}
|
|
|
|
export async function revalidatePromosAndValidateQueryStringPromo(
|
|
newPromo,
|
|
pricedLineItems,
|
|
pageNameToLog,
|
|
useDefaultCashParentAccount = false
|
|
) {
|
|
const hasActivePromos = store.getters.order.lineItems.promos;
|
|
const hasInactivePromos = store.getters.payment.inactivePromos;
|
|
const hasNewPromo = !!newPromo;
|
|
const addableVaps = getAddableVapsFromAvailableLineItems(pricedLineItems);
|
|
|
|
let validatePromoResponse = null;
|
|
let revalidatePromoResponse = null;
|
|
|
|
if (
|
|
(hasActivePromos && hasActivePromos.length) ||
|
|
(hasInactivePromos && hasInactivePromos.length)
|
|
) {
|
|
revalidatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA,
|
|
{ useDefaultCashParentAccount },
|
|
pageNameToLog,
|
|
false
|
|
);
|
|
|
|
const revalidationErrorPromoCodes = revalidatePromoResponse.errors.map((x) => x.promoCode);
|
|
baseMixin.methods.dispatchStoreAction(
|
|
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
|
|
{
|
|
activePromos: revalidatePromoResponse.promoLineItems,
|
|
inactivePromos: revalidationErrorPromoCodes,
|
|
},
|
|
false
|
|
);
|
|
}
|
|
|
|
if (hasNewPromo) {
|
|
validatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
|
|
{
|
|
promoCode: newPromo,
|
|
addableVaps: addableVaps,
|
|
},
|
|
pageNameToLog,
|
|
false
|
|
);
|
|
|
|
if (validatePromoResponse.errorCode == null) {
|
|
// Save promo to store
|
|
const activePromos = store.getters.order.lineItems.promos ?? [];
|
|
activePromos.push(...validatePromoResponse.orderPromos);
|
|
baseMixin.methods.dispatchStoreAction(
|
|
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
|
|
{
|
|
activePromos: activePromos,
|
|
},
|
|
false
|
|
);
|
|
} else if (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) {
|
|
// Save any valid query string promoCode (not applied) to inactivePromos
|
|
// in order to not lose the query string promoCode if back button is pressed
|
|
const activePromoCodes =
|
|
store.getters.order.lineItems.promos?.map((promoObject) => {
|
|
return promoObject.promoCode.toUpperCase();
|
|
}) ?? [];
|
|
const isErrorPromoAlreadyOnOrder = activePromoCodes.includes(
|
|
validatePromoResponse.promoCode.toUpperCase()
|
|
);
|
|
// Do not save an inactivePromo if it is already active on the order
|
|
if (!isErrorPromoAlreadyOnOrder) {
|
|
const inactivePromoCodes = store.getters.payment.inactivePromos ?? [];
|
|
if (!inactivePromoCodes.includes(newPromo)) {
|
|
inactivePromoCodes.push(newPromo);
|
|
baseMixin.methods.dispatchStoreAction(
|
|
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
|
|
{
|
|
inactivePromos: inactivePromoCodes,
|
|
},
|
|
false
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return { validatePromoResponse, revalidatePromoResponse };
|
|
}
|
|
|
|
export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
|
|
promoResponse,
|
|
oldActivePromos = [],
|
|
oldInactivePromos = []
|
|
) {
|
|
if (promoResponse == null) {
|
|
return [];
|
|
}
|
|
const alerts = [];
|
|
// Revalidate always has an "errors" array
|
|
if (promoResponse.errors) {
|
|
const oldPromoCodes = oldActivePromos.map((promo) =>
|
|
getPromoCodeWithoutBundleIdentifier(promo.promoCode)
|
|
);
|
|
const activePromoCodesFromResponse = getPromoCodesFromPromoObjectsWithoutDuplicates(
|
|
promoResponse.promoLineItems
|
|
);
|
|
const newlyActivatedPromoCodes = activePromoCodesFromResponse.filter(
|
|
(newPromoCode) => !oldPromoCodes.includes(newPromoCode)
|
|
);
|
|
|
|
newlyActivatedPromoCodes.forEach((newlyActivatedPromoCode) => {
|
|
alerts.push(createPromoSuccessAlert(newlyActivatedPromoCode));
|
|
});
|
|
|
|
const newlyInactivatedPromos = getNewlyInactivatedPromos(
|
|
oldInactivePromos,
|
|
promoResponse.errors
|
|
);
|
|
newlyInactivatedPromos.forEach((newlyInactivatedPromo) => {
|
|
alerts.push(createPromoErrorAlert(newlyInactivatedPromo));
|
|
});
|
|
}
|
|
// validate response
|
|
else {
|
|
if (promoResponse.orderPromos) {
|
|
const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(
|
|
promoResponse.orderPromos[0].promoCode
|
|
);
|
|
alerts.push(createPromoSuccessAlert(promoCodeToDisplay));
|
|
} else {
|
|
if (promoResponse.promoCode) {
|
|
alerts.push(
|
|
createPromoErrorAlert(
|
|
promoResponse.promoCode,
|
|
promoResponse.errorCode,
|
|
promoResponse.additionalInfo
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return alerts;
|
|
}
|
|
|
|
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
|
|
if (promos == null || lineItemsOnOrder == null) {
|
|
return [];
|
|
}
|
|
const matchingPromoCodes = [];
|
|
const matchingPromos = [];
|
|
const consolidatedPromosWithIds = {};
|
|
// Combine bundle promos to make sure each part of the bundle is satisfied
|
|
promos.forEach((promo) => {
|
|
const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
|
|
if (consolidatedPromosWithIds[cleanPromoCode]) {
|
|
consolidatedPromosWithIds[cleanPromoCode].push(...promo.discountedLineItemIds);
|
|
} else {
|
|
consolidatedPromosWithIds[cleanPromoCode] = promo.discountedLineItemIds.slice(0);
|
|
}
|
|
});
|
|
Object.keys(consolidatedPromosWithIds).forEach((promoCode) => {
|
|
let allIdsMatch = true;
|
|
consolidatedPromosWithIds[promoCode].forEach((id) => {
|
|
if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) {
|
|
allIdsMatch = false;
|
|
return;
|
|
}
|
|
});
|
|
if (allIdsMatch) {
|
|
matchingPromoCodes.push(promoCode);
|
|
}
|
|
});
|
|
promos.forEach((promo) => {
|
|
const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
|
|
if (matchingPromoCodes.includes(cleanPromoCode)) {
|
|
matchingPromos.push(promo);
|
|
}
|
|
});
|
|
return matchingPromos;
|
|
}
|
|
|
|
// Returns the vaps that the provided <promos> requires to be on the order to satisfy
|
|
// Items that are already in <lineItemsOnOrder> will not be returned
|
|
export function getVapsThatNeedToBeAddedToSatisfyPromos(
|
|
promos,
|
|
availableLineItems,
|
|
lineItemsOnOrder
|
|
) {
|
|
const clonedVaps = deepClone(lineItemsOnOrder?.vaps ?? []);
|
|
const promosWithAddableVaps = getPromosWithAddableVaps(promos);
|
|
if (promosWithAddableVaps.length) {
|
|
const matchingLineItems = getLineItemsThatMatchPromos(
|
|
promosWithAddableVaps,
|
|
availableLineItems
|
|
);
|
|
// Check if matching items are already in the order
|
|
const idsOfVapsAlreadyInOrder = clonedVaps.map((lineItem) => lineItem.id);
|
|
const vapsToAddToCart = matchingLineItems.filter(
|
|
(lineItem) => !idsOfVapsAlreadyInOrder.includes(lineItem.id)
|
|
);
|
|
return vapsToAddToCart;
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function removeCurrentlyActivePromoCodesFromInactivePromos(
|
|
activePromoObjects,
|
|
inactivePromos
|
|
) {
|
|
activePromoObjects = activePromoObjects ?? [];
|
|
inactivePromos = inactivePromos ?? [];
|
|
const activePromoCodes = activePromoObjects.map((promoObject) =>
|
|
getPromoCodeWithoutBundleIdentifier(promoObject.promoCode).toUpperCase()
|
|
);
|
|
return inactivePromos.filter((inactivePromo) => {
|
|
return !activePromoCodes.includes(inactivePromo.toUpperCase());
|
|
});
|
|
}
|
|
|
|
// bundle promos look like: "bundlePromo/###"" for each promo, this returns "bundlePromo" only
|
|
export function getPromoCodeWithoutBundleIdentifier(promoCode) {
|
|
return promoCode.split("/")[0];
|
|
}
|
|
|
|
export function shouldStripPromoQueryString(fmgPageQueryValue) {
|
|
return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue);
|
|
}
|
|
|
|
export function getNewlyInactivatedPromos(oldInactivePromos, newInactivePromos) {
|
|
const inactivePromoCodesFromResponse =
|
|
getPromoCodesFromPromoObjectsWithoutDuplicates(newInactivePromos);
|
|
return inactivePromoCodesFromResponse.filter(
|
|
(errorPromoCode) => !oldInactivePromos?.includes(errorPromoCode)
|
|
);
|
|
}
|
|
|
|
export function createPromoSuccessAlert(promoCode) {
|
|
return {
|
|
messageHeadline: "Promo code applied!",
|
|
messageCopy: `Promo code ${getPromoCodeWithoutBundleIdentifier(
|
|
promoCode.toUpperCase()
|
|
)} was successfully applied to your cart.`,
|
|
type: "alert-success",
|
|
isDismissible: true,
|
|
shouldAutoFade: true,
|
|
};
|
|
}
|
|
|
|
export function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
|
|
const alert = {
|
|
messageHeadline: "Promo code error",
|
|
type: "alert-danger",
|
|
shouldAutoFade: false,
|
|
isDismissible: true,
|
|
};
|
|
if (stackingPromoErrorCodes.includes(errorCode)) {
|
|
alert.messageCopy = `Sorry, promo code ${getPromoCodeWithoutBundleIdentifier(
|
|
promoCode.toUpperCase()
|
|
)} cannot be combined with ${additionalInfo[0].toUpperCase()}`;
|
|
} else {
|
|
alert.messageCopy = `Sorry, promo code ${getPromoCodeWithoutBundleIdentifier(
|
|
promoCode.toUpperCase()
|
|
)} is not valid`;
|
|
}
|
|
return alert;
|
|
}
|
|
|
|
// returns a list of promo codes (string) only without duplicates or bundle identifiers
|
|
// <promos> parameter must be an array of objects with a 'promoCode' field
|
|
export function getPromoCodesFromPromoObjectsWithoutDuplicates(promos) {
|
|
const promoCodes = [];
|
|
if (!promos) {
|
|
return promoCodes;
|
|
}
|
|
promos.forEach((promo) => {
|
|
const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
|
|
if (!promoCodes.includes(promoCodeToDisplay)) {
|
|
promoCodes.push(promoCodeToDisplay);
|
|
}
|
|
});
|
|
|
|
return promoCodes;
|
|
}
|
|
|
|
// private methods
|
|
function findLineItemsWithPartType(typeToFind, itemsToSearch) {
|
|
const partTypeMatches =
|
|
itemsToSearch?.filter(
|
|
(lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()
|
|
) ?? [];
|
|
return partTypeMatches;
|
|
}
|