Merge branch 'develop' into feature/CSR-1823-tax-revalidated-promos
This commit is contained in:
commit
db0a007390
14 changed files with 240 additions and 84 deletions
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 [];
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -249,5 +249,8 @@ export default {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.two-list-card-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue