Created a shared "removeCurrentlyActivePromoCodesFromInactivePromos" method and used it in multiple places to fix bug
314 lines
11 KiB
JavaScript
314 lines
11 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_DEFENSE_DISCOUNT_PART_NUMBER: "DISC RAIN DEFEN",
|
|
GLASS_DISCOUNT_PART_NUMBER: "DISCOUNT",
|
|
GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN",
|
|
};
|
|
|
|
export const addableVapsPromoPartNumbers = [
|
|
promoPartNumberStrings.RAIN_DEFENSE_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,
|
|
];
|
|
|
|
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.length) {
|
|
return [];
|
|
}
|
|
|
|
return promoList.filter((promo) => addableVapsPromoPartNumbers.includes(promo.partNumber));
|
|
}
|
|
|
|
export function getLineItemsThatMatchPromos(promos, availableLineItems) {
|
|
const matchingLineItems = [];
|
|
promos.forEach((promo) => {
|
|
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_DEFENSE, 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_DEFENSE_DISCOUNT_PART_NUMBER &&
|
|
promo.partNumber != promoPartNumberStrings.GLASS_CLEANER_DISCOUNT_PART_NUMBER
|
|
);
|
|
});
|
|
return filteredPromoArray;
|
|
}
|
|
|
|
export async function revalidatePromosAndValidateQueryStringPromo(
|
|
newPromo,
|
|
pricedLineItems,
|
|
pageNameToLog
|
|
) {
|
|
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,
|
|
{},
|
|
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 (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) {
|
|
// Save any valid (applied or not) query string promoCode to inactivePromos
|
|
// in order to not lose the query string promoCode if back button is pressed
|
|
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 = []
|
|
) {
|
|
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 inactivePromoCodesFromResponse = getPromoCodesFromPromoObjectsWithoutDuplicates(
|
|
promoResponse.errors
|
|
);
|
|
const newlyInactivatedPromos = inactivePromoCodesFromResponse.filter(
|
|
(errorPromoCode) => !oldInactivePromos.includes(errorPromoCode)
|
|
);
|
|
|
|
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 {
|
|
alerts.push(
|
|
createPromoErrorAlert(
|
|
promoResponse.promoCode,
|
|
promoResponse.errorCode,
|
|
promoResponse.additionalInfo
|
|
)
|
|
);
|
|
}
|
|
}
|
|
return alerts;
|
|
}
|
|
|
|
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
|
|
const matchingPromos = [];
|
|
promos.forEach((promo) => {
|
|
let allIdsMatch = true;
|
|
promo.discountedLineItemIds.forEach((id) => {
|
|
if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) {
|
|
allIdsMatch = false;
|
|
return;
|
|
}
|
|
});
|
|
if (allIdsMatch) {
|
|
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);
|
|
}
|
|
|
|
// private methods
|
|
function findLineItemsWithPartType(typeToFind, itemsToSearch) {
|
|
const partTypeMatches = itemsToSearch?.filter(
|
|
(lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()
|
|
);
|
|
return partTypeMatches;
|
|
}
|
|
|
|
// 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
|
|
function getPromoCodesFromPromoObjectsWithoutDuplicates(promos) {
|
|
const promoCodes = [];
|
|
promos.forEach((promo) => {
|
|
const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
|
|
if (!promoCodes.includes(promoCodeToDisplay)) {
|
|
promoCodes.push(promoCodeToDisplay);
|
|
}
|
|
});
|
|
|
|
return promoCodes;
|
|
}
|
|
|
|
function createPromoSuccessAlert(promoCode) {
|
|
return {
|
|
messageHeadline: "Promo applied!",
|
|
messageCopy: `Promo "${promoCode.toUpperCase()}" was successfully applied to your cart.`,
|
|
type: "alert-success",
|
|
isDismissible: true,
|
|
shouldAutoFade: true,
|
|
};
|
|
}
|
|
|
|
function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
|
|
const alert = {
|
|
messageHeadline: "Promo error",
|
|
type: "alert-danger",
|
|
shouldAutoFade: false,
|
|
isDismissible: true,
|
|
};
|
|
if (stackingPromoErrorCodes.includes(errorCode)) {
|
|
alert.messageCopy = `Sorry, promo code "${promoCode.toUpperCase()}" cannot be combined with "${additionalInfo[0].toUpperCase()}"`;
|
|
} else {
|
|
alert.messageCopy = `Sorry, promo code "${promoCode.toUpperCase()}" is not valid`;
|
|
}
|
|
return alert;
|
|
}
|