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 requires to be on the order to satisfy // Items that are already in 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 []; } } // 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 // 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; }