Merge branch 'develop' into feature/CSR-1823-tax-revalidated-promos

This commit is contained in:
Leah Schumann 2023-11-29 06:51:33 -05:00
commit db0a007390
14 changed files with 240 additions and 84 deletions

View file

@ -19,6 +19,7 @@ const queryStrings = {
AUTH_CODE: "auth_code", AUTH_CODE: "auth_code",
TRANSACTION_ID: "transaction_id", TRANSACTION_ID: "transaction_id",
TRANS_REFERENCE_NUMBER: "auth_trans_ref_no", TRANS_REFERENCE_NUMBER: "auth_trans_ref_no",
DISPLAY_PIA_ALERT: "displayPiaAlert",
}; };
export { queryStrings }; export { queryStrings };

View file

@ -35,7 +35,7 @@ export default {
isRequired: Boolean, isRequired: Boolean,
maxLength: { maxLength: {
type: Number, type: Number,
default: 250, default: 150,
}, },
modelValue: String, modelValue: String,
}, },
@ -103,7 +103,7 @@ export default {
} }
} }
p { p {
color: $gray-500; color: $gray-600;
&.urgent-countdown { &.urgent-countdown {
color: $red; color: $red;
} }

View file

@ -5,7 +5,10 @@
class="row vin-toggle flex align-items-center pt-4" class="row vin-toggle flex align-items-center pt-4"
:class="[isExpanded ? 'expanded' : '']" :class="[isExpanded ? 'expanded' : '']"
@click="toggleIsExpanded()"> @click="toggleIsExpanded()">
<a href="#!" class="col d-flex justify-content-between py-0"> <a
aria-label="expand cart"
href="#!"
class="col d-flex justify-content-between py-0">
<span class="label">{{ amountDueText }}</span> <span class="label">{{ amountDueText }}</span>
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span> <span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span>
</a> </a>
@ -13,7 +16,8 @@
<div class="price-table"> <div class="price-table">
<div class="service-type"> <div class="service-type">
<textBlock :cmsWidgetName="servicePackageTitleWidget" /> <textBlock :cmsWidgetName="servicePackageTitleWidget" />
<span> <span tabindex="0">
<span class="sr-only">{{ screenReaderTotalAmountDueText }}</span>
{{ getFormattedAmount("", packagePrice) }} {{ getFormattedAmount("", packagePrice) }}
</span> </span>
</div> </div>
@ -27,7 +31,11 @@
linkType="text" linkType="text"
:text="removeLinkText" :text="removeLinkText"
href="#!" href="#!"
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" /> @click-event="removeItem(cartItem.cartItemType, cartItem.category)">
<template v-slot:after-text>
<span class="sr-only">{{ cartItem.name }}</span>
</template>
</textLink>
</div> </div>
<!-- Nonpackaged Cart Items --> <!-- Nonpackaged Cart Items -->
@ -56,25 +64,40 @@
<span v-else-if="cartItem == recycleFeeCartItem"> <span v-else-if="cartItem == recycleFeeCartItem">
{{ recycleFeeCartItem.name }} {{ recycleFeeCartItem.name }}
</span> </span>
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span> <span tabindex="0"
><span class="sr-only">{{ screenReaderRecycleFeeText }}</span
>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span
>
</div> </div>
<!-- Sub total, sales tax, total columns --> <!-- Sub total, sales tax, total columns -->
<div class="sub-total"> <div class="sub-total">
<span>{{ subtotalText }}</span <span>{{ subtotalText }}</span
><span>{{ getFormattedAmount("", subTotal) }}</span> ><span tabindex="0"
><span class="sr-only">{{ screenReaderSubTotalText }}</span
>{{ getFormattedAmount("", subTotal) }}</span
>
</div> </div>
<div class="sales-tax"> <div class="sales-tax">
<span>{{ salesTaxText }}</span <span>{{ salesTaxText }}</span
><span>{{ getFormattedAmount("", salesTax) }}</span> ><span tabindex="0"
><span class="sr-only">{{ screenReaderSalesTaxText }}</span
>{{ getFormattedAmount("", salesTax) }}</span
>
</div> </div>
<div v-if="showAsPaid" class="amount-paid"> <div v-if="showAsPaid" class="amount-paid">
<span>{{ amountPaidText }}</span <span>{{ amountPaidText }}</span
><span>{{ getFormattedAmount("", amountPaid) }}</span> ><span tabindex="0"
><span class="sr-only">{{ screenReaderAmountPaidText }}</span
>{{ getFormattedAmount("", amountPaid) }}</span
>
</div> </div>
<div class="amount-due"> <div class="amount-due">
<span>{{ amountDueText }}</span <span>{{ amountDueText }}</span
><span>{{ getFormattedAmount("", amountDue) }}</span> ><span tabindex="0"
><span class="sr-only">{{ screenReaderTotalAmountDueText }}</span
>{{ getFormattedAmount("", amountDue) }}</span
>
</div> </div>
</div> </div>
</div> </div>
@ -187,6 +210,24 @@ export default {
}, },
}, },
computed: { computed: {
screenReaderTotalAmountDueText() {
return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text");
},
screenReaderRemoveVapsText() {
return this.getCmsContent("ScreenReaderRemoveVapsWidget", "Text");
},
screenReaderRecycleFeeText() {
return this.getCmsContent("ScreenReaderRecycleFeeWidget", "Text");
},
screenReaderSubTotalText() {
return this.getCmsContent("ScreenReaderSubTotalWidget", "Text");
},
screenReaderAmountPaidText() {
return this.getCmsContent("ScreenReaderAmountPaidWidget", "Text");
},
screenReaderSalesTaxText() {
return this.getCmsContent("ScreenReaderSalesTaxWidget", "Text");
},
availableLineItems() { availableLineItems() {
if (!this.lineItems || this.lineItems.length < 1) { if (!this.lineItems || this.lineItems.length < 1) {
return []; return [];

View file

@ -132,8 +132,19 @@ export async function revalidatePromosAndValidateQueryStringPromo(
pageNameToLog, pageNameToLog,
false false
); );
if (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) { if (validatePromoResponse.errorCode == null) {
// Save any valid (applied or not) query string promoCode to inactivePromos // 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 // in order to not lose the query string promoCode if back button is pressed
const inactivePromoCodes = store.getters.payment.inactivePromos ?? []; const inactivePromoCodes = store.getters.payment.inactivePromos ?? [];
if (!inactivePromoCodes.includes(newPromo)) { if (!inactivePromoCodes.includes(newPromo)) {
@ -202,16 +213,33 @@ export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
} }
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) { export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
const matchingPromoCodes = [];
const matchingPromos = []; const matchingPromos = [];
const consolidatedPromosWithIds = {};
// Combine bundle promos to make sure each part of the bundle is satisfied
promos.forEach((promo) => { 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; let allIdsMatch = true;
promo.discountedLineItemIds.forEach((id) => { consolidatedPromosWithIds[promoCode].forEach((id) => {
if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) { if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) {
allIdsMatch = false; allIdsMatch = false;
return; return;
} }
}); });
if (allIdsMatch) { if (allIdsMatch) {
matchingPromoCodes.push(promoCode);
}
});
promos.forEach((promo) => {
const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
if (matchingPromoCodes.includes(cleanPromoCode)) {
matchingPromos.push(promo); matchingPromos.push(promo);
} }
}); });

View file

@ -48,7 +48,7 @@
class="mb-4" class="mb-4"
v-model="techNotes" v-model="techNotes"
cmsWidgetName="TextAreaContentWidget" cmsWidgetName="TextAreaContentWidget"
maxLength="250" /> maxLength="150" />
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" /> <textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />

View file

@ -62,6 +62,7 @@ import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { processIfStatements } from "@/helpers/cms-content-helper"; import { processIfStatements } from "@/helpers/cms-content-helper";
import { deepClone } from "@/helpers/object-helper";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("checkbox-required", required(errorMessages.OPTION_REQUIRED)); defineRule("checkbox-required", required(errorMessages.OPTION_REQUIRED));
@ -198,6 +199,12 @@ export default {
return null; return null;
} }
}, },
getAnswerFromCms(name, answers) {
const answerIndex = answers.findIndex((answer) => {
return answer.Name === name;
});
return answers[answerIndex];
},
}, },
computed: { computed: {
currentCartItems() { currentCartItems() {
@ -280,21 +287,17 @@ export default {
} }
return result; return result;
}, },
wipersModalDataFromCms() {
return this.getAnswerFromCms("WipersModal", this.answersCmsData);
},
rainDefenseModalDataFromCms() {
return this.getAnswerFromCms("RainDefenseModal", this.answersCmsData);
},
buttonsToDisplay() { buttonsToDisplay() {
const buttons = []; const buttons = [];
if (!this.answersCmsData) return buttons; if (!this.answersCmsData) return buttons;
const getAnswer = (name, answers) => { const wipersModalData = deepClone(this.wipersModalDataFromCms);
const answerIndex = answers.findIndex((answer) => {
return answer.Name === name;
});
return answers[answerIndex];
};
const modalData = getAnswer("WipersModal", this.answersCmsData);
// remove custom var for now
modalData.Text = modalData.Text?.replaceAll("{custom:WIPERTYPE}", "");
if (this.wipersOffered === wipersOfferedStrings.BOTH) { if (this.wipersOffered === wipersOfferedStrings.BOTH) {
// NO REAR WIPERS or FRONT WIPERS IN CART; SHOW COMBO BUTTON // NO REAR WIPERS or FRONT WIPERS IN CART; SHOW COMBO BUTTON
@ -303,27 +306,50 @@ export default {
this.wipersModal?.totalRearPrice, this.wipersModal?.totalRearPrice,
]; ];
prices.sort((a, b) => a - b); prices.sort((a, b) => a - b);
modalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1]; wipersModalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1];
buttons.push(modalData); wipersModalData.Text = wipersModalData.Text?.replaceAll("{custom:WIPERTYPE}", "");
buttons.push(wipersModalData);
} }
if (this.wipersOffered === wipersOfferedStrings.FRONT) { if (this.wipersOffered === wipersOfferedStrings.FRONT) {
// NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON // NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalFrontPrice; wipersModalData.SubText = "+$" + this.wipersModal?.totalFrontPrice;
buttons.push(modalData); if (this.rearWipersInCart) {
wipersModalData.Text = wipersModalData.Text?.replaceAll(
"{custom:WIPERTYPE}",
wipersOfferedStrings.FRONT.toLowerCase() + " "
);
} else {
wipersModalData.Text = wipersModalData.Text?.replaceAll(
"{custom:WIPERTYPE}",
""
);
}
buttons.push(wipersModalData);
} }
if (this.wipersOffered === wipersOfferedStrings.REAR) { if (this.wipersOffered === wipersOfferedStrings.REAR) {
// NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON // NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalRearPrice; wipersModalData.SubText = "+$" + this.wipersModal?.totalRearPrice;
buttons.push(modalData); if (this.frontWipersInCart) {
wipersModalData.Text = wipersModalData.Text?.replaceAll(
"{custom:WIPERTYPE}",
wipersOfferedStrings.REAR.toLowerCase() + " "
);
} else {
wipersModalData.Text = wipersModalData.Text?.replaceAll(
"{custom:WIPERTYPE}",
""
);
}
buttons.push(wipersModalData);
} }
if (!this.rainDefenseInCart) { if (!this.rainDefenseInCart) {
// NO RAIN DEFENSE IN CART; SHOW RAIN DEFENSE BUTTON // NO RAIN DEFENSE IN CART; SHOW RAIN DEFENSE BUTTON
const modalData = getAnswer("RainDefenseModal", this.answersCmsData); const rainDefenseModalData = deepClone(this.rainDefenseModalDataFromCms);
modalData.SubText = "+$" + this.rainDefenseModal?.listPrice; rainDefenseModalData.SubText = "+$" + this.rainDefenseModal?.listPrice;
buttons.push(modalData); buttons.push(rainDefenseModalData);
} }
return buttons; return buttons;
@ -417,6 +443,7 @@ export default {
p.price { p.price {
text-align: center; text-align: center;
color: $green; color: $green;
font-weight: 500;
} }
p:last-child { p:last-child {
margin-bottom: 0; margin-bottom: 0;
@ -438,6 +465,7 @@ export default {
.additional-button-data { .additional-button-data {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500;
line-height: 1.75; line-height: 1.75;
padding-left: 0.5rem; padding-left: 0.5rem;
color: $green; color: $green;

View file

@ -103,6 +103,7 @@ import {
getNewlyInactivatedPromos, getNewlyInactivatedPromos,
} from "@/helpers/promotions-helper"; } from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { routerParams } from "@/router/router-constants/router-params";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
@ -440,7 +441,7 @@ export default {
} catch (error) { } catch (error) {
console.log("error: response from pia submit work order:" + error.message); console.log("error: response from pia submit work order:" + error.message);
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
this.$route.params[this.routerParams.DISPLAY_PIA_ALERT] = true; this.$route.params[routerParams.DISPLAY_PIA_ALERT] = true;
return; return;
} }
} }
@ -488,7 +489,10 @@ export default {
return this.paymentMethodInternalModel; return this.paymentMethodInternalModel;
}, },
shouldDisplayPiaAlert() { shouldDisplayPiaAlert() {
return this.$route.params[this.routerParams.DISPLAY_PIA_ALERT]; return (
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] ||
this.$route.params[routerParams.DISPLAY_PIA_ALERT]
);
}, },
// Necessary to make the watcher of lineItems work // Necessary to make the watcher of lineItems work
// JavaScript does not keep a record of the old value, only a reference to it's location in the memory. // JavaScript does not keep a record of the old value, only a reference to it's location in the memory.

View file

@ -11,6 +11,7 @@ jest.mock("@/digital-components/modal/modal", () => ({
methods: { methods: {
closeModal: jest.fn(), closeModal: jest.fn(),
resetButtonStyle: jest.fn(), resetButtonStyle: jest.fn(),
resetForm: jest.fn(),
}, },
})); }));
@ -49,7 +50,9 @@ describe("promo-modal-question.vue", () => {
// Assert // Assert
expect(wrapper.vm.displayInvalidPromoAlert).toBe(false); expect(wrapper.vm.displayInvalidPromoAlert).toBe(false);
expect(wrapper.vm.displayInvalidOnOrderPromoAlert).toBe(false); expect(wrapper.vm.displayStackingPromoAlert).toBe(false);
expect(wrapper.vm.displayInShopPromoAlert).toBe(false);
expect(wrapper.vm.displaySimilarPromoAlert).toBe(false);
}); });
it("Should emit update:modelValue on Modal closed", async () => { it("Should emit update:modelValue on Modal closed", async () => {
// Arrange // Arrange

View file

@ -22,19 +22,34 @@
<alert <alert
ref="alertInvalidPromo" ref="alertInvalidPromo"
v-if="displayInvalidPromoAlert" v-if="displayInvalidPromoAlert"
v-html="InvalidPromoText" v-html="errorMessage"
class="my-4 alert" class="my-4 alert"
cmsWidgetName="AlertInvalidPromoWidget" cmsWidgetName="AlertInvalidPromoWidget"
alertClass="alert-danger" alertClass="alert-danger"
v-bind:isDismissible="true" /> v-bind:isDismissible="false" />
<alert <alert
ref="alertInvalidPromoOnOrder" ref="AlertSimilarPromo"
v-if="displayInvalidOnOrderPromoAlert" v-if="displaySimilarPromoAlert"
v-html="InvalidPromoOnOrderText"
class="my-4 alert" class="my-4 alert"
cmsWidgetName="AlertInvalidPromoOnOrderWidget" cmsWidgetName="AlertSimilarPromoWidget"
alertClass="alert-danger" alertClass="alert-danger"
v-bind:isDismissible="true" /> v-bind:isDismissible="false" />
<alert
ref="alertInvalidStackingPromo"
v-if="displayStackingPromoAlert"
v-html="errorMessage"
class="my-4 alert"
cmsWidgetName="AlertStackingPromoWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
<alert
ref="AlertInShopPromo"
v-if="displayInShopPromoAlert"
v-html="errorMessage"
class="my-4 alert"
cmsWidgetName="AlertInshopPromoWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
<div v-for="(promoCode, i) in this.getPromoCodeList()" :key="i"> <div v-for="(promoCode, i) in this.getPromoCodeList()" :key="i">
<span class="applied-promo">Promo code "{{ promoCode }}" applied </span> <span class="applied-promo">Promo code "{{ promoCode }}" applied </span>
<textLink <textLink
@ -71,8 +86,11 @@ export default {
return { return {
promoCode: "", promoCode: "",
promoTextInputId: "", promoTextInputId: "",
errorMessage: "",
displayInvalidPromoAlert: false, displayInvalidPromoAlert: false,
displayInvalidOnOrderPromoAlert: false, displaySimilarPromoAlert: false,
displayStackingPromoAlert: false,
displayInShopPromoAlert: false,
lineItems: deepClone(this.modelValue), lineItems: deepClone(this.modelValue),
}; };
}, },
@ -97,20 +115,14 @@ export default {
modal() { modal() {
return this.$refs[this.modalName]; return this.$refs[this.modalName];
}, },
PromoOnOrderText() { StackPromoText() {
return this.getCmsContent("AlertInvalidPromoOnOrderWidget", "HeadlineText"); return this.getCmsContent("AlertStackingPromoWidget", "HeadlineText");
},
InvalidPromoOnOrderText() {
return this.PromoOnOrderText?.replaceAll(
"{custom:NEWPROMOCODE}",
this.promoCode.toUpperCase()
).replaceAll("{custom:OLDPROMOCODE}", this.getPromoCode()?.toUpperCase());
}, },
PromoText() { PromoText() {
return this.getCmsContent("AlertInvalidPromoWidget", "HeadlineText"); return this.getCmsContent("AlertInvalidPromoWidget", "HeadlineText");
}, },
InvalidPromoText() { InShopPromoText() {
return this.PromoText?.replaceAll("{custom:PROMOCODE}", this.promoCode.toUpperCase()); return this.getCmsContent("AlertInshopPromoWidget", "HeadlineText");
}, },
removeLinkText() { removeLinkText() {
return this.getCmsContent("RemoveCartItemTextWidget", "Text"); return this.getCmsContent("RemoveCartItemTextWidget", "Text");
@ -118,8 +130,11 @@ export default {
}, },
methods: { methods: {
resetAlerts() { resetAlerts() {
this.displayInvalidOnOrderPromoAlert = false; this.displayStackingPromoAlert = false;
this.displayInvalidPromoAlert = false; this.displayInvalidPromoAlert = false;
this.displaySimilarPromoAlert = false;
this.displayInShopPromoAlert = false;
this.errorMessage = "";
}, },
resetsOnPromoInput() { resetsOnPromoInput() {
this.resetAlerts(); this.resetAlerts();
@ -130,8 +145,16 @@ export default {
); );
return promosToDisplay; return promosToDisplay;
}, },
getPromoCode() { getConflictingPromoCode(additionalInfo) {
return this.lineItems.promos?.[0]?.promoCode; var conflictingCodes = additionalInfo?.[0];
if (additionalInfo?.length > 1) {
if (additionalInfo?.length > 2) conflictingCodes += ",";
for (let i = 1; i < additionalInfo?.length - 1; i++) {
conflictingCodes += ` "${additionalInfo[i]},`;
}
conflictingCodes += `" and "${additionalInfo?.slice(-1)[0]}`;
}
return conflictingCodes;
}, },
onInputIdAssigned(inputId) { onInputIdAssigned(inputId) {
this.promoTextInputId = inputId; this.promoTextInputId = inputId;
@ -146,6 +169,7 @@ export default {
this.resetAlerts(); this.resetAlerts();
this.$emit("update:modelValue", this.lineItems); this.$emit("update:modelValue", this.lineItems);
this.promoCode = ""; this.promoCode = "";
this.modal.resetForm();
}, },
focusOnPromoInput() { focusOnPromoInput() {
const input = document.getElementById(this.promoTextInputId); const input = document.getElementById(this.promoTextInputId);
@ -171,6 +195,7 @@ export default {
isValid: !("errorCode" in promoValidationResponse), isValid: !("errorCode" in promoValidationResponse),
promoCode: promoValidationResponse?.orderPromos, promoCode: promoValidationResponse?.orderPromos,
errorCode: promoValidationResponse?.errorCode, errorCode: promoValidationResponse?.errorCode,
additionalInfo: promoValidationResponse?.additionalInfo,
}; };
}, },
removeItem(promo) { removeItem(promo) {
@ -181,6 +206,43 @@ export default {
getPromoCodeWithoutBundleIdentifier(lineItemsToKeep.promoCode) != promo getPromoCodeWithoutBundleIdentifier(lineItemsToKeep.promoCode) != promo
); );
}, },
getErrorMessage(error, additionalInfo) {
switch (error) {
case promoErrorCodes.PROMO_STACKING_NOT_ALLOWED:
case promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER:
return additionalInfo.length === 1 &&
additionalInfo[0] === this.promoCode.toUpperCase()
? (this.displaySimilarPromoAlert = true)
: ((this.errorMessage = this.StackPromoText?.replace(
/\{custom:(NEW|OLD)PROMOCODE\}/g,
(match, group) =>
group === "NEW"
? this.promoCode.toUpperCase()
: this.getConflictingPromoCode(additionalInfo)
)),
(this.displayStackingPromoAlert = true));
case promoErrorCodes.INVALID_PROMO_ON_ORDER:
if (additionalInfo.some((x) => x.toUpperCase() === "APPOINTMENT_TYPE")) {
this.errorMessage = this.InShopPromoText?.replaceAll(
"{custom:INSHOPPROMOCODE}",
this.promoCode.toUpperCase()
);
return (this.displayInShopPromoAlert = true);
} else {
this.errorMessage = this.PromoText?.replaceAll(
"{custom:PROMOCODE}",
this.promoCode.toUpperCase()
);
return (this.displayInvalidPromoAlert = true);
}
default:
this.errorMessage = this.PromoText?.replaceAll(
"{custom:PROMOCODE}",
this.promoCode.toUpperCase()
);
return (this.displayInvalidPromoAlert = true);
}
},
async addPromoCode() { async addPromoCode() {
if (this.promoCode) { if (this.promoCode) {
this.resetAlerts(); this.resetAlerts();
@ -227,11 +289,7 @@ export default {
this.lineItems.vaps?.push(...getVaps); this.lineItems.vaps?.push(...getVaps);
this.closeModal(); this.closeModal();
} else { } else {
if (promoCodeData.errorCode == promoErrorCodes.PROMO_STACKING_NOT_ALLOWED) { this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo);
this.displayInvalidOnOrderPromoAlert = true;
} else {
this.displayInvalidPromoAlert = true;
}
this.focusOnPromoInput(); this.focusOnPromoInput();
this.resetModalButtonStyle(); this.resetModalButtonStyle();
} }
@ -241,7 +299,9 @@ export default {
watch: { watch: {
promoCode() { promoCode() {
this.displayInvalidPromoAlert = false; this.displayInvalidPromoAlert = false;
this.displayInvalidOnOrderPromoAlert = false; this.displayStackingPromoAlert = false;
this.displaySimilarPromoAlert = false;
this.displayInShopPromoAlert = false;
}, },
modelValue: { modelValue: {
handler(newValue) { handler(newValue) {

View file

@ -24,12 +24,9 @@ export default {
if (piaError) { if (piaError) {
console.log("Error during payment: " + piaError); console.log("Error during payment: " + piaError);
this.$router.navigateWithoutSaving( this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route, {
this.navigationScenarios.PIA_ERROR, [queryStrings.DISPLAY_PIA_ALERT]: true,
this.$route, });
{},
{ [routerParams.DISPLAY_PIA_ALERT]: true }
);
} else { } else {
switch (store.getters.order.payment.piaType) { switch (store.getters.order.payment.piaType) {
case paymentMethods.CREDIT_CARD: case paymentMethods.CREDIT_CARD:

View file

@ -350,8 +350,8 @@ export default {
const taxedVaps = mapTaxedLineItemsToStoreFormat(taxedLineItems, availableVaps); const taxedVaps = mapTaxedLineItemsToStoreFormat(taxedLineItems, availableVaps);
// Add items that were not in the store yet but added via query string promo validation // Add items that were not in the store yet but added via query string promo validation
lineItems.promos = lineItems.promos ?? []; //newValidatedPromos contains both validated and revalidated promos.
lineItems.promos.push(...newValidatedPromos); lineItems.promos = Array.from(newValidatedPromos);
const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos( const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos(
newValidatedPromos, newValidatedPromos,
taxedVaps, taxedVaps,

View file

@ -178,7 +178,6 @@ export default {
vm.supportingItems = resultMap.supportingItems; vm.supportingItems = resultMap.supportingItems;
vm.availableLineItems = pricingResults; vm.availableLineItems = pricingResults;
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems); vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
vm.newValidatedPromos = validatePromoResponse?.orderPromos;
if (revalidatePromoResponse) { if (revalidatePromoResponse) {
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse( const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
@ -207,16 +206,11 @@ export default {
availableLineItems: null, availableLineItems: null,
supportingItems: null, supportingItems: null,
pricedGlassParts: null, pricedGlassParts: null,
newValidatedPromos: null,
}; };
}, },
computed: { computed: {
allActivePromos() { allActivePromos() {
const allActivePromos = []; return this.$store.getters.order.lineItems.promos ?? [];
if (this.newValidatedPromos) allActivePromos.push(...this.newValidatedPromos);
if (this.$store.getters.order.lineItems.promos)
allActivePromos.push(...this.$store.getters.order.lineItems.promos);
return allActivePromos;
}, },
}, },
methods: { methods: {
@ -294,11 +288,6 @@ export default {
} }
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false); this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
this.dispatchStoreAction(
this.storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{ activePromos: this.allActivePromos },
false
);
const payment = this.$store.getters.payment; const payment = this.$store.getters.payment;
if (payment.isInsurance) { if (payment.isInsurance) {

View file

@ -249,5 +249,8 @@ export default {
height: auto; height: auto;
} }
} }
.two-list-card-width {
width: 100%;
}
} }
</style> </style>

View file

@ -2130,6 +2130,8 @@ export const actions = {
}, },
// Manage promo saving to ensure a promoCode never ends up in both active and inactive // Manage promo saving to ensure a promoCode never ends up in both active and inactive
saveActiveAndOrInactivePromos(context, { activePromos = null, inactivePromos = null }) { saveActiveAndOrInactivePromos(context, { activePromos = null, inactivePromos = null }) {
activePromos = activePromos?.slice(0);
inactivePromos = inactivePromos?.slice(0);
let activePromosToSave; let activePromosToSave;
let inactivePromosToSave; let inactivePromosToSave;
if (!activePromos && !inactivePromos) { if (!activePromos && !inactivePromos) {