Merge remote-tracking branch 'origin/release/end-to-end-insurance' into rlsmerge/endToEnd-to-develop
This commit is contained in:
commit
6d1deb8b75
40 changed files with 1367 additions and 355 deletions
|
|
@ -11,6 +11,7 @@ const applicationConfig = {
|
|||
PAGE_QUERYSTRING: "fmgPage",
|
||||
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
|
||||
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||
CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER: "87291",
|
||||
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
|
||||
PIA_RESPONSE_URL:
|
||||
location.protocol +
|
||||
|
|
|
|||
18
src/constants/coverage-status.js
Normal file
18
src/constants/coverage-status.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const coverageStatus = {
|
||||
PENDING: "Pending",
|
||||
NOCOMP: "NoComp",
|
||||
VERIFIED: "Verified",
|
||||
};
|
||||
|
||||
export function coverageStatusValue(intCoverageStatus) {
|
||||
switch (intCoverageStatus) {
|
||||
case 0:
|
||||
return coverageStatus.PENDING;
|
||||
case 1:
|
||||
return coverageStatus.NOCOMP;
|
||||
case 2:
|
||||
return coverageStatus.VERIFIED;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ const experimentSettings = {
|
|||
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
|
||||
PIA_EXPERIENCE: "PIA Experience",
|
||||
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
|
||||
IS_EMAIL_OPTIONAL: "isEmailOptional",
|
||||
};
|
||||
|
||||
const experimentTriggers = {
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ const storeActions = {
|
|||
SAVE_PAYMENT_TYPE: "savePaymentType",
|
||||
SAVE_PAYMENT_METHOD_CHOICE: "savePaymentMethodChoice",
|
||||
SAVE_PARENT_ACCOUNT_NUMBER: "saveParentAccountNumber",
|
||||
SAVE_BILL_TO_ACCOUNT_NUMBER: "saveBillToAccountNumber",
|
||||
SAVE_SUPPORTING_ITEMS: "saveSupportingItems",
|
||||
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
||||
"saveSupportingItemsSuppressingStateResetting",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const storeMutations = {
|
|||
UPDATE_REFERRAL_DATE: "updateReferralDate",
|
||||
UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId",
|
||||
UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber",
|
||||
UPDATE_BILL_TO_ACCT_NUMBER: "updateBillToAcctNumber",
|
||||
UPDATE_EON: "updateEON",
|
||||
UPDATE_IS_INSURANCE: "updateIsInsurance",
|
||||
UPDATE_SAVED_SESSION_ID: "updateSavedSessionId",
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ export default {
|
|||
hideInput: Boolean,
|
||||
centerErrorMessage: Boolean,
|
||||
keyDownHandler: Function,
|
||||
addOptionalText: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
isRequired: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const uuid = uuidv4();
|
||||
|
|
@ -202,7 +207,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
return this.addOptionalText
|
||||
? this.getCmsContent(this.cmsWidgetName, "QuestionText") + " (optional)"
|
||||
: this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
},
|
||||
value: {
|
||||
get: function () {
|
||||
|
|
|
|||
|
|
@ -13,78 +13,107 @@
|
|||
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="price-table">
|
||||
<div class="service-type">
|
||||
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
|
||||
<span>
|
||||
{{ getFormattedAmount("", packagePrice) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="cart-panel">
|
||||
<div class="price-table">
|
||||
<div class="service-type">
|
||||
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
|
||||
<span>
|
||||
{{ getFormattedAmount("", packagePrice) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="packagePriceWithoutDiscount > packagePrice"
|
||||
class="struck-out-price">
|
||||
{{ getFormattedAmount("", packagePriceWithoutDiscount) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Packaged Cart Items -->
|
||||
<div v-for="(cartItem, i) in packageCartItems" :key="i" class="packaged-cart-item">
|
||||
<span>{{ cartItem.name }}</span>
|
||||
<textLink
|
||||
v-if="allowItemRemoval"
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
:text="removeLinkText"
|
||||
href="javascript:void(0)"
|
||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)">
|
||||
<template v-slot:after-text>
|
||||
<span class="sr-only">{{ cartItem.name }}</span>
|
||||
</template>
|
||||
</textLink>
|
||||
</div>
|
||||
<!-- Packaged Cart Items -->
|
||||
<div
|
||||
v-for="(cartItem, i) in packageCartItems"
|
||||
:key="i"
|
||||
class="packaged-cart-item">
|
||||
<span
|
||||
><span v-if="cartItem.category === 'promos'"><</span
|
||||
>{{ cartItem.name
|
||||
}}<span v-if="cartItem.category === 'promos'">></span></span
|
||||
>
|
||||
<textLink
|
||||
v-if="allowItemRemoval"
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
:text="removeLinkText"
|
||||
href="javascript:void(0)"
|
||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)">
|
||||
<template v-slot:after-text>
|
||||
<span class="sr-only">{{ cartItem.name }}</span>
|
||||
</template>
|
||||
</textLink>
|
||||
</div>
|
||||
|
||||
<!-- Nonpackaged Cart Items -->
|
||||
<div
|
||||
v-for="(cartItem, i) in nonpackageCartItems"
|
||||
:key="i"
|
||||
class="non-packaged-cart-item"
|
||||
:class="[
|
||||
i % 2 == 0 ? 'even' : 'odd',
|
||||
cartItem.isRemovable ? 'removable-cart-item' : '',
|
||||
]">
|
||||
<span v-if="cartItem != recycleFeeCartItem">{{ cartItem.name }}</span>
|
||||
<textLink
|
||||
v-if="allowItemRemoval && cartItem.isRemovable"
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
:text="removeLinkText"
|
||||
href="javascript:void(0)"
|
||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
||||
<textLink
|
||||
v-else-if="cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName"
|
||||
linkType="text"
|
||||
:text="recycleFeeCartItem.name"
|
||||
href="javascript:void(0)"
|
||||
@click-event="openModal(recyclingModalCmsWidgetName)" />
|
||||
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||
{{ recycleFeeCartItem.name }}
|
||||
</span>
|
||||
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
|
||||
</div>
|
||||
<!-- Nonpackaged Cart Items -->
|
||||
<div
|
||||
v-for="(cartItem, i) in nonpackageCartItems"
|
||||
:key="i"
|
||||
class="non-packaged-cart-item"
|
||||
:class="[
|
||||
i % 2 == 0 ? 'even' : 'odd',
|
||||
cartItem.isRemovable ? 'removable-cart-item' : '',
|
||||
]">
|
||||
<span v-if="cartItem != recycleFeeCartItem">{{ cartItem.name }}</span>
|
||||
<textLink
|
||||
v-if="allowItemRemoval && cartItem.isRemovable"
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
:text="removeLinkText"
|
||||
href="javascript:void(0)"
|
||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
||||
<textLink
|
||||
v-else-if="
|
||||
cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName
|
||||
"
|
||||
linkType="text"
|
||||
:text="recycleFeeCartItem.name"
|
||||
href="javascript:void(0)"
|
||||
@click-event="openModal(recyclingModalCmsWidgetName)" />
|
||||
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||
{{ recycleFeeCartItem.name }}
|
||||
</span>
|
||||
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Sub total, sales tax, total columns -->
|
||||
<div class="sub-total">
|
||||
<span>{{ subtotalText }}</span
|
||||
><span>{{ getFormattedAmount("", subTotal) }}</span>
|
||||
</div>
|
||||
<div class="sales-tax">
|
||||
<span>{{ salesTaxText }}</span
|
||||
><span>{{ getFormattedAmount("", salesTax) }}</span>
|
||||
</div>
|
||||
<div v-if="showAsPaid" class="amount-paid">
|
||||
<span>{{ amountPaidText }}</span
|
||||
><span>{{ getFormattedAmount("", amountPaid) }}</span>
|
||||
</div>
|
||||
<div class="amount-due">
|
||||
<span>{{ amountDueText }}</span
|
||||
><span>{{ getFormattedAmount("", amountDue) }}</span>
|
||||
<!-- Sub total, sales tax, total columns -->
|
||||
<div class="sub-total">
|
||||
<span>{{ subtotalText }}</span
|
||||
><span>{{ getFormattedAmount("", subTotal) }}</span>
|
||||
</div>
|
||||
<div class="sales-tax">
|
||||
<span>{{ salesTaxText }}</span
|
||||
><span>{{ getFormattedAmount("", salesTax) }}</span>
|
||||
</div>
|
||||
<div v-if="showAsPaid" class="amount-paid">
|
||||
<span>{{ amountPaidText }}</span
|
||||
><span>{{ getFormattedAmount("", amountPaid) }}</span>
|
||||
</div>
|
||||
<div class="amount-due">
|
||||
<span>{{ amountDueText }}</span
|
||||
><span>{{ getFormattedAmount("", amountDue) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<promoModalQuestion
|
||||
ref="promoModalQuestion"
|
||||
class="small mt-4"
|
||||
v-model="lineItems"
|
||||
@addedPromo="handleAddedPromo"
|
||||
:availableVaps="availableVaps"
|
||||
modalWidgetName="PromoModalWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="my-2 lh-1 applied-promo"
|
||||
v-for="(promoCode, i) in this.getPromoCodeList()"
|
||||
:key="i">
|
||||
<span class="caption">Promo code <{{ promoCode }}> applied </span>
|
||||
</div>
|
||||
<contentGroupModal ref="RecycleModal" cmsWidgetName="RecycleModal">
|
||||
<textBlock cmsWidgetName="RecycleTextBlock" />
|
||||
</contentGroupModal>
|
||||
|
|
@ -96,6 +125,7 @@
|
|||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
||||
|
||||
// Mixins
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
|
@ -149,6 +179,17 @@ export default {
|
|||
|
||||
return subTotal;
|
||||
},
|
||||
getPromoDiscounts() {
|
||||
const promoItems = this.promoCartItems;
|
||||
|
||||
let subTotal = 0;
|
||||
|
||||
promoItems?.forEach((item) => {
|
||||
subTotal += item.subTotal;
|
||||
});
|
||||
|
||||
return subTotal;
|
||||
},
|
||||
getVapsCartItemsForSelectedPackage(packageName) {
|
||||
const packageContentTypes = getPackageContents(
|
||||
this.glassToReplace,
|
||||
|
|
@ -167,6 +208,12 @@ export default {
|
|||
});
|
||||
});
|
||||
|
||||
if (this.promoCartItems) {
|
||||
this.promoCartItems.forEach((promoCartItem) => {
|
||||
vapsCartItemsForSelectedPackage.push(promoCartItem);
|
||||
});
|
||||
}
|
||||
|
||||
return vapsCartItemsForSelectedPackage;
|
||||
},
|
||||
getCmsContentForVapsType(vapsType) {
|
||||
|
|
@ -192,6 +239,16 @@ export default {
|
|||
|
||||
this.$emit("update:modelValue", this.lineItems);
|
||||
},
|
||||
getPromoCodeList() {
|
||||
if (this.$refs["promoModalQuestion"]) {
|
||||
return this.$refs["promoModalQuestion"].getPromoCodeList();
|
||||
}
|
||||
return [];
|
||||
},
|
||||
handleAddedPromo(lineItems) {
|
||||
// NOTE: these lineItems are passed from promo-modal-question
|
||||
this.$emit("update:modelValue", lineItems);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
screenReaderTotalAmountDueText() {
|
||||
|
|
@ -313,6 +370,12 @@ export default {
|
|||
|
||||
return packagePrice;
|
||||
},
|
||||
packagePriceWithoutDiscount() {
|
||||
let fullPrice = this.packagePrice;
|
||||
let discount = this.getPromoDiscounts();
|
||||
fullPrice += Math.abs(discount);
|
||||
return fullPrice;
|
||||
},
|
||||
isRepair() {
|
||||
if (!this.damage) {
|
||||
return;
|
||||
|
|
@ -540,7 +603,6 @@ export default {
|
|||
|
||||
return cartItem;
|
||||
},
|
||||
|
||||
suppliesRepairCartItemName() {
|
||||
return this.getCmsContent("SuppliesRepairTextWidget", "Text");
|
||||
},
|
||||
|
|
@ -578,7 +640,6 @@ export default {
|
|||
|
||||
return cartItem;
|
||||
},
|
||||
|
||||
mobileFeeCartItemName() {
|
||||
return this.getCmsContent("MobileServiceTextWidget", "Text");
|
||||
},
|
||||
|
|
@ -734,7 +795,6 @@ export default {
|
|||
|
||||
return promoCartItems;
|
||||
},
|
||||
|
||||
removeLinkText() {
|
||||
return this.getCmsContent("RemoveCartItemTextWidget", "Text");
|
||||
},
|
||||
|
|
@ -795,6 +855,7 @@ export default {
|
|||
textBlock,
|
||||
textLink,
|
||||
contentGroupModal,
|
||||
promoModalQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -824,12 +885,14 @@ export default {
|
|||
.amount-due {
|
||||
color: $green;
|
||||
}
|
||||
.price-table {
|
||||
.cart-panel {
|
||||
max-height: 0;
|
||||
transition: all 350ms ease-in;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
color: $gray-600;
|
||||
}
|
||||
.price-table {
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -846,6 +909,10 @@ export default {
|
|||
background-color: $gray-100;
|
||||
align-items: center;
|
||||
}
|
||||
.struck-out-price {
|
||||
text-decoration: line-through;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.packaged-cart-item {
|
||||
background-color: $gray-100;
|
||||
padding-left: 2.5rem;
|
||||
|
|
@ -934,17 +1001,26 @@ export default {
|
|||
&.expanded:after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&.expanded + .price-table {
|
||||
&.expanded + .cart-panel {
|
||||
max-height: 650px;
|
||||
transition: all 150ms ease-in;
|
||||
overflow: hidden;
|
||||
padding-top: 1rem;
|
||||
padding: 1rem 0 0.5rem;
|
||||
visibility: visible;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.applied-promo {
|
||||
span {
|
||||
color: $green-700;
|
||||
font-weight: 500;
|
||||
padding: 2px 8px;
|
||||
background-color: $green-100;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.payment-method-question {
|
||||
.question-text {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,400 @@
|
|||
import { mount, shallowMount } from "@vue/test-utils";
|
||||
import promoModalQuestion from "./promo-modal-question";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import {
|
||||
promoErrorCodes,
|
||||
getPromoCodesFromPromoObjectsWithoutDuplicates,
|
||||
getPromoCodeWithoutBundleIdentifier,
|
||||
} from "@/helpers/promotions-helper";
|
||||
import { cartItemCategories } from "@/constants/cart-item-categories";
|
||||
|
||||
let mockReturnsForStoreActions = {};
|
||||
|
||||
jest.mock("@/mixins/base-mixin", () => ({
|
||||
...jest.requireActual("@/mixins/base-mixin"),
|
||||
methods: {
|
||||
dispatchStoreActionWithLogging: jest.fn(
|
||||
(action, { promoCode, addableVaps }, pageNameToLog, someBool) => {
|
||||
return mockReturnsForStoreActions[action];
|
||||
}
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
// reset store action returns
|
||||
mockReturnsForStoreActions = {};
|
||||
});
|
||||
|
||||
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return widgetName[cmsFieldName];
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/digital-components/modal/modal", () => ({
|
||||
methods: {
|
||||
closeModal: jest.fn(),
|
||||
resetButtonStyle: jest.fn(),
|
||||
resetForm: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const modalWidgetName = "modalWidgetName";
|
||||
|
||||
const mockModalCmsContent = {
|
||||
FooterText: "Sample modal footer text here.",
|
||||
};
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
if (widgetName === modalWidgetName) {
|
||||
return mockModalCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
describe("promo-modal-question.vue", () => {
|
||||
it("Should reset all alerts on onModalClosed", async () => {
|
||||
// Arrange
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.onModalClosed();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayInvalidPromoAlert).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 () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
await wrapper.vm.onModalClosed();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("update:modelValue")).toEqual([[lineItems]]);
|
||||
});
|
||||
it("focuses on the input element when focusOnPromoInput is called", async () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
const focusMock = jest.fn();
|
||||
const inputMock = { focus: focusMock };
|
||||
|
||||
// Mock document.getElementById to return the inputMock
|
||||
jest.spyOn(document, "getElementById").mockReturnValue(inputMock);
|
||||
|
||||
//Act
|
||||
|
||||
wrapper.vm.focusOnPromoInput();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(focusMock).toHaveBeenCalled();
|
||||
});
|
||||
it("returns a validate response on applying promo", async () => {
|
||||
// Arrange
|
||||
const newPromo = "testPromo";
|
||||
const pageNameToLog = "testPage";
|
||||
const validateResponse = { orderPromos: [] };
|
||||
const addableVaps = [];
|
||||
mockReturnsForStoreActions[storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA] =
|
||||
validateResponse;
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
// Act
|
||||
|
||||
wrapper.vm.getPromoCodeData();
|
||||
|
||||
const promoValidationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
newPromo,
|
||||
lineItems,
|
||||
addableVaps,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(promoValidationResponse).toEqual(validateResponse);
|
||||
});
|
||||
test("If promocode is valid return taxed lineItems", async () => {
|
||||
//Arrange
|
||||
const taxedlineItems = {};
|
||||
|
||||
mockReturnsForStoreActions[storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA] =
|
||||
taxedlineItems;
|
||||
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
let promoCode = "1wiper0";
|
||||
const pricedLineItemsToTax = [];
|
||||
|
||||
pricedLineItemsToTax.push(promoCode);
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
|
||||
attachTo: document.body,
|
||||
});
|
||||
wrapper.vm.addPromoCode();
|
||||
|
||||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
providerNumber: 2,
|
||||
appointmentType: "IN_SHOP",
|
||||
serviceLocationCity: "city",
|
||||
serviceLocationState: "state",
|
||||
serviceLocationZipCode: "12345",
|
||||
pricedLineItems: pricedLineItemsToTax,
|
||||
},
|
||||
|
||||
"payment-method",
|
||||
|
||||
false
|
||||
);
|
||||
|
||||
// Assert
|
||||
|
||||
expect(taxedLineItems).toEqual(taxedlineItems);
|
||||
});
|
||||
test("Get promoCode list will return the applied promos", async () => {
|
||||
// Arrange
|
||||
const promos = [
|
||||
{ promoCode: "duplicatePromo" },
|
||||
{ promoCode: "duplicatePromo" },
|
||||
{ promoCode: "bundlePromo/400" },
|
||||
{ promoCode: "bundlePromo/401" },
|
||||
];
|
||||
const appliedPromos = ["duplicatePromo", "bundlePromo"];
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.getPromoCodeList();
|
||||
const promoCodeToDisplay = getPromoCodesFromPromoObjectsWithoutDuplicates(promos);
|
||||
|
||||
// Assert
|
||||
expect(promoCodeToDisplay).toEqual(appliedPromos);
|
||||
});
|
||||
test("On clicking remove the promo gets removed from the lineItems", async () => {
|
||||
//Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [{ promoCode: "duplicatePromo" }, { promoCode: "duplicatePromo" }],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
const promo = ["bundlePromo"];
|
||||
//Act
|
||||
wrapper.vm.removeItem(promo);
|
||||
const existingPromo = (lineItems[cartItemCategories.PROMOS] = lineItems[
|
||||
cartItemCategories.PROMOS
|
||||
].filter(
|
||||
(lineItemsToKeep) =>
|
||||
getPromoCodeWithoutBundleIdentifier(lineItemsToKeep.promoCode) != promo
|
||||
));
|
||||
|
||||
//Assert
|
||||
expect(existingPromo).toEqual(lineItems.promos);
|
||||
});
|
||||
test("Getting stacking alert on stack promo error code", async () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
const additionalInfo = ["testAdditionalInfo"];
|
||||
|
||||
const stackingErrorCode = promoErrorCodes.PROMO_STACKING_NOT_ALLOWED;
|
||||
|
||||
// Act
|
||||
wrapper.vm.getErrorMessage(stackingErrorCode, additionalInfo);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayStackingPromoAlert).toBe(true);
|
||||
});
|
||||
test("Getting invalid promo alert on invalid promoCode", async () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
const additionalInfo = ["testAdditionalInfo"];
|
||||
|
||||
const invalidErrorCode = promoErrorCodes.INVALID_PROMO_ON_ORDER;
|
||||
|
||||
// Act
|
||||
wrapper.vm.getErrorMessage(invalidErrorCode, additionalInfo);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayInvalidPromoAlert).toBe(true);
|
||||
});
|
||||
test("Getting invalid promo alert on invalid promoCode", async () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
const additionalInfo = ["APPOINTMENT_TYPE"];
|
||||
|
||||
const invalidErrorCode = promoErrorCodes.INVALID_PROMO_ON_ORDER;
|
||||
|
||||
// Act
|
||||
wrapper.vm.getErrorMessage(invalidErrorCode, additionalInfo);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayInShopPromoAlert).toBe(true);
|
||||
});
|
||||
test("Get conflicting promoCodes on applying more than one promo", async () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
const additionalInfo = ["1wiper0", "Glass30"];
|
||||
const conflictingCodes = ["1wiper0", "Glass30"];
|
||||
|
||||
//Act
|
||||
wrapper.vm.getConflictingPromoCode(additionalInfo);
|
||||
|
||||
//Assert
|
||||
expect(conflictingCodes).toEqual(additionalInfo);
|
||||
});
|
||||
});
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import promoQuestion from "@/layouts/payment-method/promo-modal-question/promo-question/promo-question";
|
||||
import promoQuestion from "@/fmg-components/promo-modal-question/promo-question/promo-question";
|
||||
import modal from "@/digital-components/modal/modal";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import { storeActions } from "@/constants/store-actions.js";
|
||||
|
|
@ -100,6 +100,12 @@ export default {
|
|||
modelValue: Object,
|
||||
modalWidgetName: String,
|
||||
availableVaps: Object,
|
||||
pageName: String,
|
||||
taxPromos: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
promoLinkText() {
|
||||
|
|
@ -253,44 +259,52 @@ export default {
|
|||
this.promoCode,
|
||||
this.lineItems,
|
||||
this.availableVaps,
|
||||
"payment-method"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
if (promoCodeData.isValid) {
|
||||
const pricedLineItemsToTax = [];
|
||||
pricedLineItemsToTax.push(...promoCodeData.promoCode);
|
||||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
providerNumber:
|
||||
store.getters.order.serviceLocation.provider.providerNumber,
|
||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||
serviceLocationState: store.getters.order.serviceLocation.state,
|
||||
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
||||
pricedLineItems: pricedLineItemsToTax,
|
||||
},
|
||||
"payment-method",
|
||||
false
|
||||
);
|
||||
if (this.taxPromos) {
|
||||
const pricedLineItemsToTax = [];
|
||||
pricedLineItemsToTax.push(...promoCodeData.promoCode);
|
||||
const taxedLineItems =
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
providerNumber:
|
||||
store.getters.order.serviceLocation.provider.providerNumber,
|
||||
appointmentType:
|
||||
store.getters.order.serviceLocation.appointmentType,
|
||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||
serviceLocationState: store.getters.order.serviceLocation.state,
|
||||
serviceLocationZipCode:
|
||||
store.getters.order.serviceLocation.zipCode,
|
||||
pricedLineItems: pricedLineItemsToTax,
|
||||
},
|
||||
"payment-method",
|
||||
false
|
||||
);
|
||||
|
||||
// Match all line items to the line items as they are in the store
|
||||
// and rebuild the original structure.
|
||||
this.lineItems = mapTaxedLineItemsToStoreFormat(taxedLineItems, this.lineItems);
|
||||
const taxedVaps = mapTaxedLineItemsToStoreFormat(
|
||||
taxedLineItems,
|
||||
this.availableVaps
|
||||
);
|
||||
// Match all line items to the line items as they are in the store
|
||||
// and rebuild the original structure.
|
||||
this.lineItems = mapTaxedLineItemsToStoreFormat(
|
||||
taxedLineItems,
|
||||
this.lineItems
|
||||
);
|
||||
const taxedVaps = mapTaxedLineItemsToStoreFormat(
|
||||
taxedLineItems,
|
||||
this.availableVaps
|
||||
);
|
||||
|
||||
const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos(
|
||||
promoCodeData.promoCode,
|
||||
taxedVaps,
|
||||
this.lineItems
|
||||
);
|
||||
const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos(
|
||||
promoCodeData.promoCode,
|
||||
taxedVaps,
|
||||
this.lineItems
|
||||
);
|
||||
|
||||
this.lineItems.vaps?.push(...getVaps);
|
||||
}
|
||||
this.lineItems?.promos.push(...promoCodeData.promoCode);
|
||||
this.lineItems.vaps?.push(...getVaps);
|
||||
this.$emit("added-promo", this.lineItems);
|
||||
this.closeModal();
|
||||
} else {
|
||||
this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo);
|
||||
|
|
@ -18,7 +18,7 @@ export function updateOrCreateFunnelCookie() {
|
|||
ReferralNumber: store.getters.order.referralNumber,
|
||||
ReferralDate: store.getters.order.referralDate,
|
||||
ReferralCorrelationId: store.getters.order.referralCorrelationId,
|
||||
ReferralParentAccountNumber: store.getters.order.accountNumber,
|
||||
ReferralParentAccountNumber: store.getters.order.payment.parentAccountNumber,
|
||||
HasDelayedClaimRegistration: wasClaimRegistrationDelayed,
|
||||
SuppressConceptFunnel: shouldSuppressConceptFunnel,
|
||||
SavedSessionId: store.getters.applicationUser.savedSessionId,
|
||||
|
|
|
|||
|
|
@ -139,11 +139,7 @@ async function getLatestPageForRedirection() {
|
|||
} else if (!estimateComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_DAMAGE;
|
||||
} else {
|
||||
if (scheduleComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.SCHEDULE;
|
||||
} else if (serviceLocationComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.SERVICE_LOCATION;
|
||||
} else if (quoteComponent.methods.arePagePrerequisitesValid()) {
|
||||
if (quoteComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.QUOTE;
|
||||
} else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.CAPABILITY_QUESTIONS;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { storeMutations } from "@/constants/store-mutations";
|
|||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||
import store from "@/store";
|
||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
jest.mock("@/helpers/damage-helper", () => ({
|
||||
isGlassAvailableForCarId: jest.fn().mockImplementation(() => true),
|
||||
|
|
@ -641,6 +642,17 @@ describe("address-lookup.vue", () => {
|
|||
});
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getSettingValue: jest.fn((settingName) => {
|
||||
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
function setupMocks({
|
||||
isZipValid = true,
|
||||
isZipServiceable = true,
|
||||
|
|
@ -709,6 +721,7 @@ function setupMocks({
|
|||
},
|
||||
},
|
||||
},
|
||||
mixins: [mockMixin],
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@
|
|||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
<customerQuestions
|
||||
ref="customerQuestions"
|
||||
v-model="customerQuestions"
|
||||
:validationRules="EmailValidationRules"
|
||||
:isEmailOptional="IsEmailOptional" />
|
||||
|
||||
<alert
|
||||
ref="alertVinNotFound"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
v-model="customerModel.emailAddress"
|
||||
ref="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
:isRequired="!isEmailOptional"
|
||||
:validationRules="validationRules"
|
||||
:addOptionalText="isEmailOptional" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-0">
|
||||
|
|
@ -79,6 +81,7 @@ export default {
|
|||
}),
|
||||
},
|
||||
validationRules: String,
|
||||
isEmailOptional: Boolean,
|
||||
},
|
||||
computed: {
|
||||
customerModel: {
|
||||
|
|
|
|||
|
|
@ -164,7 +164,6 @@ export default {
|
|||
if (
|
||||
store.getters.order.vehicle.carId &&
|
||||
store.getters.order.serviceLocation.zipCode &&
|
||||
store.getters.order.customer.emailAddress &&
|
||||
store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES)
|
||||
) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ export default {
|
|||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.lineItems = lineItemsFromSubmittedOrder;
|
||||
vm.vaps = availableVaps;
|
||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
||||
});
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { settleAllPromises } from "@/helpers/layout-helper.js";
|
|||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import baseMixin from "../../mixins/base-mixin";
|
||||
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
|
|
@ -273,12 +274,24 @@ function setupMocks({
|
|||
Answers: cmsAnswers,
|
||||
FunnelFooterWidget: FunnelFooterWidget,
|
||||
};
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getSettingValue: jest.fn((settingName) => {
|
||||
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}),
|
||||
},
|
||||
};
|
||||
const apiPromise = Promise.resolve({ cmsContent });
|
||||
settleAllPromises.mockImplementation(() => apiPromise);
|
||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [baseMixin] });
|
||||
const mountOptions = getMountOptions({
|
||||
...mountOptionsMockData,
|
||||
mixins: [baseMixin, mockMixin],
|
||||
});
|
||||
mountOptions["attachTo"] = document.body;
|
||||
|
||||
const wrapper = shallowMount(estimate, mountOptions);
|
||||
|
|
|
|||
|
|
@ -52,9 +52,10 @@
|
|||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="emailAddress"
|
||||
isRequired
|
||||
:isRequired="!IsEmailOptional"
|
||||
disableAutoFill
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
:validationRules="EmailValidationRules"
|
||||
:addOptionalText="IsEmailOptional" />
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { nextTick } from "vue";
|
|||
import { storeActions } from "@/constants/store-actions";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import store from "@/store";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
jest.mock("@/assets/img/loader.gif", () => "loader.gif");
|
||||
jest.mock("@/assets/img/windshield.png", () => "windshield.png");
|
||||
|
|
@ -692,11 +693,20 @@ function setupMocks({
|
|||
};
|
||||
|
||||
const apiPromise = Promise.resolve(apiResponses);
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getSettingValue: jest.fn((settingName) => {
|
||||
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}),
|
||||
},
|
||||
};
|
||||
settleAllPromises.mockImplementation(() => apiPromise);
|
||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||
const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [mockMixin] });
|
||||
mountOptions["attachTo"] = document.body; // append wrapper to document.body to test DOM methods
|
||||
|
||||
const wrapper = shallowMount(licensePlateLookup, mountOptions);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@
|
|||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="email"
|
||||
customInputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
:isRequired="!IsEmailOptional"
|
||||
:validationRules="EmailValidationRules"
|
||||
:addOptionalText="IsEmailOptional" />
|
||||
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,6 @@
|
|||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
recyclingModalCmsWidgetName="RecycleModal" />
|
||||
|
||||
<promoModalQuestion
|
||||
class="small mt-4"
|
||||
v-model="lineItems"
|
||||
:availableVaps="availableVaps"
|
||||
modalWidgetName="PromoModalWidget" />
|
||||
|
||||
<hr class="my-5" />
|
||||
|
||||
<div>
|
||||
|
|
@ -82,7 +76,6 @@ import paymentMethodQuestion from "@/layouts/payment-method/payment-method-quest
|
|||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import cart from "@/fmg-components/cart/cart";
|
||||
import reviewDropdown from "@/layouts/payment-method/review-dropdown/review-dropdown";
|
||||
import promoModalQuestion from "@/layouts/payment-method/promo-modal-question/promo-modal-question";
|
||||
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
||||
|
|
@ -244,7 +237,7 @@ export default {
|
|||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
||||
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||
|
|
@ -314,9 +307,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Line Items
|
||||
const packageReqs = !!store.getters.order.lineItems.supportingItems;
|
||||
|
||||
// Service Location
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
const mobileReqs = !!(
|
||||
|
|
@ -361,9 +351,7 @@ export default {
|
|||
customer.emailAddress
|
||||
);
|
||||
|
||||
return (
|
||||
packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs
|
||||
);
|
||||
return serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs;
|
||||
},
|
||||
getPaymentMethodFromStore() {
|
||||
const piaType = store.getters.order.payment.piaType;
|
||||
|
|
@ -405,7 +393,7 @@ export default {
|
|||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
||||
providerNumber:
|
||||
this.$store.getters.order.serviceLocation.provider.providerNumber,
|
||||
appointmentType: this.$store.getters.order.serviceLocation.appointmentType,
|
||||
|
|
@ -596,7 +584,6 @@ export default {
|
|||
cart,
|
||||
alert,
|
||||
paymentMethodQuestion,
|
||||
promoModalQuestion,
|
||||
reviewDropdown,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
import { mount, shallowMount } from "@vue/test-utils";
|
||||
import promoModalQuestion from "./promo-modal-question";
|
||||
|
||||
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return widgetName[cmsFieldName];
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/digital-components/modal/modal", () => ({
|
||||
methods: {
|
||||
closeModal: jest.fn(),
|
||||
resetButtonStyle: jest.fn(),
|
||||
resetForm: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const modalWidgetName = "modalWidgetName";
|
||||
|
||||
const mockModalCmsContent = {
|
||||
FooterText: "Sample modal footer text here.",
|
||||
};
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
if (widgetName === modalWidgetName) {
|
||||
return mockModalCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
describe("promo-modal-question.vue", () => {
|
||||
it("Should reset all alerts on onModalClosed", async () => {
|
||||
// Arrange
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.onModalClosed();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayInvalidPromoAlert).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 () => {
|
||||
// Arrange
|
||||
const lineItems = {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
vaps: [],
|
||||
promos: [],
|
||||
};
|
||||
|
||||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
await wrapper.vm.onModalClosed();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("update:modelValue")).toEqual([[lineItems]]);
|
||||
});
|
||||
});
|
||||
|
|
@ -356,7 +356,7 @@ export default {
|
|||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
billToAccountNumber: "87291",
|
||||
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
||||
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||
|
|
@ -440,9 +440,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Line Items
|
||||
const packageReqs = !!store.getters.order.lineItems.supportingItems;
|
||||
|
||||
// Service Location
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
const mobileReqs = !!(
|
||||
|
|
@ -492,7 +489,6 @@ export default {
|
|||
(store.getters.order.payment.isPia || !!store.getters.order.payment.piaType);
|
||||
|
||||
return (
|
||||
packageReqs &&
|
||||
serviceLocationReqs &&
|
||||
isInsuranceSet &&
|
||||
scheduleReqs &&
|
||||
|
|
|
|||
|
|
@ -133,8 +133,6 @@ describe("quote.vue", () => {
|
|||
};
|
||||
});
|
||||
|
||||
wrapper.vm.pricedGlassParts = [];
|
||||
|
||||
//Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
|
|
@ -250,8 +248,8 @@ describe("quote.vue", () => {
|
|||
);
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.pricedGlassParts !== null).toBe(true);
|
||||
expect(wrapper.vm.supportingItems !== null).toBe(true);
|
||||
expect(wrapper.vm.lineItems !== null).toBe(true);
|
||||
expect(wrapper.vm.availableVaps !== null).toBe(true);
|
||||
expect(wrapper.vm.availableLineItems !== null).toBe(true);
|
||||
// This should have its own test
|
||||
//expect(vm.isInsuranceSelected !== null).toBe(true);
|
||||
|
|
@ -510,6 +508,31 @@ describe("quote.vue", () => {
|
|||
//Assert
|
||||
expect(wrapper.vm.isInsuranceSelected).toBe(true);
|
||||
});
|
||||
test("On forward button action save promos", async () => {
|
||||
//Arrange
|
||||
store.getters.payment = {
|
||||
insuranceCoverage: {},
|
||||
isInsurance: false,
|
||||
};
|
||||
store.getters.order = {
|
||||
lineItems: [],
|
||||
payment: {
|
||||
parentAccountNumber: 167132,
|
||||
},
|
||||
};
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
router: {
|
||||
navigateWithSaving: jest.fn(),
|
||||
},
|
||||
route: { quote },
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
||||
expect(wrapper.vm.lineItems.promos !== null).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({ customMountOptions }) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,14 @@
|
|||
cmsWidgetName="AfterpayModalWidget"
|
||||
:lineItems="availableLineItems" />
|
||||
|
||||
<promoModalQuestion
|
||||
class="small mt-4"
|
||||
v-model="lineItems"
|
||||
:availableVaps="availableVaps"
|
||||
pageName="quote"
|
||||
:taxPromos="false"
|
||||
modalWidgetName="PromoModalWidget" />
|
||||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="left"
|
||||
|
|
@ -89,9 +97,11 @@ import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
|
|||
import {
|
||||
revalidatePromosAndValidateQueryStringPromo,
|
||||
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
||||
createPromoSuccessAlert,
|
||||
} from "@/helpers/promotions-helper";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
|
|
@ -139,7 +149,8 @@ export default {
|
|||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
const nullSafeGlassParts = store.getters.order.lineItems.glassParts ?? [];
|
||||
const lineItems = store.getters.order.lineItems;
|
||||
const nullSafeGlassParts = lineItems.glassParts ?? [];
|
||||
const availableLineItems = [
|
||||
resultMap.rainDefense,
|
||||
...resultMap.supportingItems,
|
||||
|
|
@ -164,6 +175,7 @@ export default {
|
|||
|
||||
// Promo logic
|
||||
// Populate the previous state of promos for toast message usage in "next()"
|
||||
const availableVaps = [...resultMap.wipers, resultMap.rainDefense];
|
||||
const oldActivePromos = store.getters.lineItems.promos?.slice(0);
|
||||
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
|
||||
|
||||
|
|
@ -175,13 +187,16 @@ export default {
|
|||
pricingResults,
|
||||
"quote"
|
||||
);
|
||||
lineItems.glassParts = nullSafeGlassParts;
|
||||
lineItems.promos = lineItems.promos ?? [];
|
||||
lineItems.vaps = lineItems.vaps ?? [];
|
||||
// End of promo logic
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.pricedGlassParts = nullSafeGlassParts;
|
||||
vm.supportingItems = resultMap.supportingItems;
|
||||
vm.availableVaps = availableVaps;
|
||||
vm.lineItems = lineItems;
|
||||
vm.availableLineItems = pricingResults;
|
||||
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
|
||||
|
||||
|
|
@ -208,15 +223,17 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isInsuranceSelected: null,
|
||||
selectedVaps: null,
|
||||
availableLineItems: null,
|
||||
supportingItems: null,
|
||||
pricedGlassParts: null,
|
||||
lineItems: [],
|
||||
availableVaps: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allActivePromos() {
|
||||
return this.$store.getters.order.lineItems.promos ?? [];
|
||||
return this.lineItems.promos ?? [];
|
||||
},
|
||||
lineItemsCloneForWatcher() {
|
||||
return Object.assign({}, this.lineItems);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -258,7 +275,7 @@ export default {
|
|||
}
|
||||
},
|
||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
this.selectedVaps = vapsItemsSelected;
|
||||
this.lineItems.vaps = vapsItemsSelected;
|
||||
},
|
||||
backButtonAction() {
|
||||
vehicleQuestionsMixin.methods.navigateBack(this);
|
||||
|
|
@ -276,6 +293,11 @@ export default {
|
|||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
||||
false
|
||||
);
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER,
|
||||
applicationConfig.CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -285,15 +307,23 @@ export default {
|
|||
this.supportingItems = this.filterOutFees(this.supportingItems);
|
||||
}
|
||||
|
||||
if (this.pricedGlassParts.length > 0) {
|
||||
if (this.lineItems.glassParts?.length > 0) {
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
||||
this.pricedGlassParts,
|
||||
this.lineItems.glassParts,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.lineItems.vaps, false);
|
||||
this.dispatchStoreAction(
|
||||
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
|
||||
{
|
||||
activePromos: this.lineItems.promos,
|
||||
inactivePromos: this.$store.getters.order.payment.inactivePromos,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
const payment = this.$store.getters.payment;
|
||||
if (payment.isInsurance) {
|
||||
|
|
@ -309,6 +339,32 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
lineItemsCloneForWatcher: {
|
||||
handler(newValue, oldValue) {
|
||||
if (
|
||||
!oldValue ||
|
||||
oldValue.length == 0 ||
|
||||
!oldValue.vaps ||
|
||||
!newValue ||
|
||||
newValue.length == 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (oldValue.promos.length < newValue.promos.length) {
|
||||
const oldPromoCodes = oldValue.promos.map(
|
||||
(promoObject) => promoObject.promoCode
|
||||
);
|
||||
const newlyActivatedPromoCodes = newValue.promos.filter(
|
||||
(newPromo) => !oldPromoCodes.includes(newPromo.promoCode)
|
||||
);
|
||||
const alert = createPromoSuccessAlert(newlyActivatedPromoCodes[0].promoCode);
|
||||
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
navbar,
|
||||
|
|
@ -321,6 +377,7 @@ export default {
|
|||
contentGroupModal,
|
||||
loadingModal,
|
||||
afterpayModalBanner,
|
||||
promoModalQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -256,6 +256,57 @@ describe("service-package-question.vue", () => {
|
|||
// Assert
|
||||
expect(wrapper.vm.selectedPackageName).toBe("TierThree");
|
||||
});
|
||||
it("should select default package if promos are added", async () => {
|
||||
//Arrange
|
||||
mockProps.activePromos != null;
|
||||
const wrapper = setupMocks({
|
||||
mountOptionsMockData: {
|
||||
store: {
|
||||
getters: {
|
||||
order: {
|
||||
damage: {
|
||||
isRepair: false,
|
||||
glassToReplace: [{ glassLocation: "Windshield" }],
|
||||
},
|
||||
},
|
||||
lineItems: {
|
||||
vaps: [],
|
||||
},
|
||||
hasAnyNonWindshieldGlassParts: false,
|
||||
payment: {
|
||||
isInsurance: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
wrapper.setProps({
|
||||
activePromos: [
|
||||
{
|
||||
discountedLineItemIds: [
|
||||
{
|
||||
0: "428ec73c-38e4-4e14-8703-a987b0391898",
|
||||
1: "79db94d7-163c-4408-a1ec-f83e58c11992",
|
||||
},
|
||||
],
|
||||
partType: "PROMO_DISCOUNT",
|
||||
promoCode: "1WIPER0",
|
||||
partNumber: "WIPER DISCOUNT",
|
||||
laborAmount: 0,
|
||||
sellingPrice: -10,
|
||||
kitPrice: 0,
|
||||
salesTax: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const selectDefaultPackageMock = jest.spyOn(wrapper.vm, "selectDefaultPackage");
|
||||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(selectDefaultPackageMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe("service-package-question.vue, matching business rules for package display", () => {
|
||||
// mock scenarios in figma:
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ export default {
|
|||
this.selectDefaultPackage();
|
||||
}
|
||||
},
|
||||
activePromos() {
|
||||
this.selectDefaultPackage();
|
||||
},
|
||||
selectedPackageName(newValue) {
|
||||
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
||||
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
||||
|
|
|
|||
|
|
@ -314,14 +314,13 @@ export default {
|
|||
serviceLocation.provider.providerNumber);
|
||||
|
||||
const paymentInfo = store.getters.payment.isInsurance !== null;
|
||||
const supportingItems = store.getters.lineItems.supportingItems !== null;
|
||||
|
||||
const damageInfo =
|
||||
store.getters.order.damage.isRepair ||
|
||||
(store.getters.order.lineItems?.glassParts != null &&
|
||||
store.getters.order.lineItems.glassParts.length > 0);
|
||||
|
||||
return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo;
|
||||
return serviceLocationPreReqs && paymentInfo && damageInfo;
|
||||
},
|
||||
async getAvailableDatesMethod(startDate, endDate) {
|
||||
const newShopTimeSlots = await getAvailableDates(
|
||||
|
|
@ -348,9 +347,14 @@ export default {
|
|||
},
|
||||
getSelectedTimeSlotInfo() {
|
||||
const supportingItems = this.getSupportingItems();
|
||||
const isPremiumAppointment =
|
||||
!!supportingItems.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE)
|
||||
.length > 0;
|
||||
|
||||
var isPremiumAppointment = false;
|
||||
if (supportingItems) {
|
||||
isPremiumAppointment =
|
||||
!!supportingItems.filter(
|
||||
(lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE
|
||||
).length > 0;
|
||||
}
|
||||
|
||||
const selectedTimeSlotInfo = {
|
||||
timeSlot: store.getters.order.schedule,
|
||||
|
|
@ -461,6 +465,10 @@ export default {
|
|||
false
|
||||
);
|
||||
} else {
|
||||
if (!supportingItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if it's not a mobile and/or premium early bird, then make sure we remove any that may have been added
|
||||
const removePremiumFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
|
|||
false
|
||||
);
|
||||
|
||||
if (
|
||||
mobileFeePart.data === null ||
|
||||
mobileFeePart.data === undefined ||
|
||||
mobileFeePart.data === ""
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the Mobile Fee Part Price
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ import {
|
|||
getServiceabilityDetails,
|
||||
getShopProviderData,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
|
||||
|
|
@ -348,11 +349,19 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
return (
|
||||
store.getters.lineItems.supportingItems !== null &&
|
||||
store.getters.order.serviceLocation.zipCode !== null &&
|
||||
store.getters.payment.isInsurance !== null
|
||||
);
|
||||
// insurance drops the recycle fee on replace orders so it won't be in supportingItems
|
||||
if (store.getters.payment.isInsurance && !store.getters.order.damage.isRepair) {
|
||||
return (
|
||||
store.getters.order.serviceLocation.zipCode !== null &&
|
||||
store.getters.payment.isInsurance !== null
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
store.getters.lineItems.supportingItems !== null &&
|
||||
store.getters.order.serviceLocation.zipCode !== null &&
|
||||
store.getters.payment.isInsurance !== null
|
||||
);
|
||||
}
|
||||
},
|
||||
setData(zipCodeData, serviceabilityDetails, mobileFeePart, shopProviderData) {
|
||||
if (zipCodeData) {
|
||||
|
|
@ -438,7 +447,14 @@ export default {
|
|||
this.recalibrationInformationModal.openModal();
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
if (store.getters.order.payment.isInsurance) {
|
||||
navigateToHeritageFunnel({ shouldSaveSession: false });
|
||||
} else {
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_BACK,
|
||||
this.$route
|
||||
);
|
||||
}
|
||||
},
|
||||
updateAndSaveSupportingItems() {
|
||||
const supportingItems = store.getters.lineItems.supportingItems;
|
||||
|
|
@ -463,7 +479,7 @@ export default {
|
|||
);
|
||||
} else {
|
||||
// if it's not a mobile, then make sure we remove any that may have been added
|
||||
const removeMobileFeeIndex = supportingItems.findIndex(
|
||||
const removeMobileFeeIndex = supportingItems?.findIndex(
|
||||
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import vinLookup from "./vin-lookup.vue";
|
|||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
import store from "@/store";
|
||||
|
||||
|
|
@ -378,5 +379,11 @@ function mockOutStubFunctions(wrapper) {
|
|||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn(() => "placeholder CMS content"),
|
||||
getSettingValue: jest.fn((settingName) => {
|
||||
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -53,8 +53,9 @@
|
|||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
isRequired
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
:isRequired="!IsEmailOptional"
|
||||
:validationRules="EmailValidationRules"
|
||||
:addOptionalText="IsEmailOptional" />
|
||||
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
|
|
|
|||
|
|
@ -120,77 +120,182 @@ export default {
|
|||
await this.logPageView(analyticsPageEvents.ENTRY);
|
||||
},
|
||||
|
||||
pushSubmittedOrderToDataLayer() {
|
||||
// check if submitted order exists; exit if not.
|
||||
const hasSubmittedOrder = store.getters.hasSubmittedOrder;
|
||||
pushOrderToDataLayer() {
|
||||
// helper check for if an object is defined (but maybe falsey)
|
||||
const isDefined = (x) => x !== null && x !== undefined;
|
||||
|
||||
if (!hasSubmittedOrder) {
|
||||
return;
|
||||
// Get correct order object
|
||||
const hasSubmittedOrder = store.getters.hasSubmittedOrder;
|
||||
const order = hasSubmittedOrder ? store.getters.submittedOrder : store.getters.order;
|
||||
|
||||
// Begin assembling payload for data layer
|
||||
const payload = {};
|
||||
|
||||
// Service Zip
|
||||
if (
|
||||
order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
isDefined(order.serviceLocation.zipCode)
|
||||
) {
|
||||
payload.serviceZipCode = order.serviceLocation.zipCode;
|
||||
} else if (
|
||||
isDefined(order.serviceLocation.appointmentType) &&
|
||||
order.serviceLocation.appointmentType !== AppointmentTypeStrings.MOBILE &&
|
||||
isDefined(order.serviceLocation.provider.address.zipCode)
|
||||
) {
|
||||
payload.serviceZipCode = order.serviceLocation.provider.address.zipCode;
|
||||
} else {
|
||||
payload.serviceZipCode = "";
|
||||
}
|
||||
|
||||
const order = store.getters.submittedOrder;
|
||||
// Damage Type
|
||||
if (isDefined(order.damage.isRepair)) {
|
||||
payload.damageType = order.damage.isRepair ? "repair" : "replace";
|
||||
} else {
|
||||
payload.damageType = "";
|
||||
}
|
||||
|
||||
// assemble data for payload
|
||||
// // reduce promocode array
|
||||
// Account Type
|
||||
if (isDefined(order.payment.isInsurance)) {
|
||||
payload.accountType = order.payment.isInsurance ? "insurance" : "cash";
|
||||
} else {
|
||||
payload.accountType = "";
|
||||
}
|
||||
|
||||
// Promo Codes
|
||||
const promos = order.lineItems.promos ?? [];
|
||||
const promoCodes = promos.map((promo) => promo.promoCode);
|
||||
const promoString =
|
||||
promoCodes.length === 0 ? "" : promoCodes.reduce((prev, next) => `${prev},${next}`);
|
||||
if (promos.length === 0) {
|
||||
payload.promoCodes = "";
|
||||
} else {
|
||||
const promoCodes = promos.map((promo) => promo.promoCode);
|
||||
const promoString = promoCodes.reduce((prev, next) => `${prev},${next}`);
|
||||
payload.promoCodes = promoString;
|
||||
}
|
||||
|
||||
// // reduce glass array
|
||||
const glassToReplace = order.damage.glassToReplace ?? [];
|
||||
const glassToReplaceNames = glassToReplace.map(
|
||||
(glassPiece) => `${glassPiece.glassLocation}/${glassPiece.glassName}`
|
||||
);
|
||||
const glassString =
|
||||
glassToReplaceNames.length === 0
|
||||
? ""
|
||||
: glassToReplaceNames.reduce((prev, next) => `${prev},${next}`);
|
||||
// Vehicle info
|
||||
if (isDefined(order.vehicle.year)) {
|
||||
// Ensure cast to string.
|
||||
payload.vehicleYear = `${order.vehicle.year}`;
|
||||
} else {
|
||||
payload.vehicleYear = "";
|
||||
}
|
||||
|
||||
// // calculate subtotal
|
||||
const lineItems = order.lineItems;
|
||||
if (isDefined(order.vehicle.make)) {
|
||||
payload.vehicleMake = order.vehicle.make;
|
||||
} else {
|
||||
payload.vehicleMake = "";
|
||||
}
|
||||
|
||||
if (isDefined(order.vehicle.model)) {
|
||||
payload.vehicleModel = order.vehicle.model;
|
||||
} else {
|
||||
payload.vehicleModel = "";
|
||||
}
|
||||
|
||||
if (isDefined(order.vehicle.style)) {
|
||||
payload.vehicleStyle = order.vehicle.style;
|
||||
} else {
|
||||
payload.vehicleStyle = "";
|
||||
}
|
||||
|
||||
// Glass pieces
|
||||
const glass = order.damage.glassToReplace ?? [];
|
||||
if (glass.length === 0) {
|
||||
payload.glassToReplace = "";
|
||||
} else {
|
||||
const glassNames = glass.map((g) => `${g.glassLocation}/${g.glassName}`);
|
||||
const glassString = glassNames.reduce((prev, next) => `${prev},${next}`);
|
||||
|
||||
payload.glassToReplace = glassString;
|
||||
}
|
||||
|
||||
// Work Order Id
|
||||
if (order.workOrderId) {
|
||||
const parsedId = parseInt(order.workOrderId);
|
||||
if (!isNaN(parsedId)) {
|
||||
payload.workOrderId = parsedId;
|
||||
} else {
|
||||
payload.workOrderId = "";
|
||||
}
|
||||
} else {
|
||||
payload.workOrderId = "";
|
||||
}
|
||||
|
||||
// Provider Ctu
|
||||
if (isDefined(order.serviceLocation.zipCodeCtu)) {
|
||||
const parsedCtu = parseInt(order.serviceLocation.zipCodeCtu);
|
||||
if (!isNaN(parsedCtu)) {
|
||||
payload.providerCtu = parseInt(order.serviceLocation.zipCodeCtu);
|
||||
} else {
|
||||
payload.providerCtu = "";
|
||||
}
|
||||
} else {
|
||||
payload.providerCtu = "";
|
||||
}
|
||||
|
||||
// Work Order Number
|
||||
if (order.workOrderNumber) {
|
||||
payload.orderNumber = order.workOrderNumber;
|
||||
} else {
|
||||
payload.orderNumber = "";
|
||||
}
|
||||
|
||||
// Pricing
|
||||
// Only fire for completed orders?
|
||||
|
||||
const lineItems = order.lineItems ?? {};
|
||||
const combinedLineItems = [
|
||||
...(lineItems.glassParts ?? []),
|
||||
...(lineItems.supportingItems ?? []),
|
||||
...(lineItems.vaps ?? []),
|
||||
...(lineItems.promos ?? []),
|
||||
];
|
||||
const subtotal = baseMixin.methods
|
||||
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
|
||||
.toFixed(2);
|
||||
|
||||
// // get correct zip code
|
||||
const providerZip = order.serviceLocation.provider.address.zipCode;
|
||||
const serviceZip =
|
||||
order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|
||||
? order.serviceLocation.zipCode
|
||||
: providerZip;
|
||||
const isPricingAvailable =
|
||||
combinedLineItems.length > 0 &&
|
||||
combinedLineItems.every(
|
||||
(lineItem) =>
|
||||
isDefined(lineItem.kitPrice) &&
|
||||
isDefined(lineItem.laborAmount) &&
|
||||
isDefined(lineItem.sellingPrice)
|
||||
);
|
||||
const isTaxAvailable =
|
||||
isPricingAvailable &&
|
||||
combinedLineItems.every((lineItem) => isDefined(lineItem.salesTax));
|
||||
|
||||
// // calculate total
|
||||
const total = baseMixin.methods
|
||||
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, true)
|
||||
.toFixed(2);
|
||||
if (isPricingAvailable) {
|
||||
const subtotal = baseMixin.methods
|
||||
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
|
||||
.toFixed(2);
|
||||
|
||||
const payload = {
|
||||
serviceZipCode: serviceZip,
|
||||
damageType: order.damage.isRepair ? "repair" : "replace",
|
||||
accountType: order.payment.isInsurance ? "insurance" : "cash",
|
||||
promoCodes: promoString,
|
||||
vehicleYear: `${order.vehicle.year}`,
|
||||
vehicleMake: order.vehicle.make,
|
||||
vehicleModel: order.vehicle.model,
|
||||
vehicleStyle: order.vehicle.style,
|
||||
glassToReplace: glassString,
|
||||
workOrderId: parseInt(order.workOrderId),
|
||||
providerCtu: parseInt(order.serviceLocation.zipCodeCtu),
|
||||
orderNumber: order.workOrderNumber,
|
||||
priceTotal: parseFloat(total),
|
||||
priceSubTotal: parseFloat(subtotal),
|
||||
isRecalibrationOnOrder: store.getters.isRecalibrationOnSubmittedOrder,
|
||||
appointmentType: order.serviceLocation.appointmentType,
|
||||
};
|
||||
payload.priceSubTotal = parseFloat(subtotal);
|
||||
} else {
|
||||
payload.priceSubTotal = "";
|
||||
}
|
||||
|
||||
if (isTaxAvailable) {
|
||||
const total = baseMixin.methods
|
||||
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, true)
|
||||
.toFixed(2);
|
||||
|
||||
payload.priceTotal = parseFloat(total);
|
||||
} else {
|
||||
payload.priceTotal = "";
|
||||
}
|
||||
|
||||
// Recalibration
|
||||
if (hasSubmittedOrder) {
|
||||
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedOrder;
|
||||
} else {
|
||||
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder;
|
||||
}
|
||||
|
||||
// Appointment Type
|
||||
if (isDefined(order.serviceLocation.appointmentType)) {
|
||||
payload.appointmentType = order.serviceLocation.appointmentType;
|
||||
} else {
|
||||
payload.appointmentType = "";
|
||||
}
|
||||
|
||||
// push to data layer.
|
||||
pushToDataLayerIfDefined(payload);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -38,27 +38,38 @@ const parts = {
|
|||
requiresRecalibration: true,
|
||||
salesTax: 63.86,
|
||||
sellingPrice: 791.46,
|
||||
kitPrice: 0,
|
||||
laborAmount: 0,
|
||||
},
|
||||
frontWipers: {
|
||||
name: "front wipers",
|
||||
partNumber: "SBB16",
|
||||
description: "SAFELITE BEAM BLADE 16",
|
||||
partType: "FRONT WIPER",
|
||||
price: 32.64,
|
||||
sellingPrice: 32.64,
|
||||
kitPrice: 0,
|
||||
laborAmount: 0,
|
||||
salesTax: 1,
|
||||
},
|
||||
rearWipers: {
|
||||
name: "rear wipers",
|
||||
partNumber: "SBBR12A",
|
||||
description: "SAFELITE REAR BLADE 12A",
|
||||
partType: "REAR WIPER",
|
||||
price: 24.48,
|
||||
sellingPrice: 24.48,
|
||||
kitPrice: 0,
|
||||
laborAmount: 0,
|
||||
salesTax: 2,
|
||||
},
|
||||
rainDefense: {
|
||||
name: "rain defense",
|
||||
partNumber: "RAIN DEFENSE",
|
||||
description: null,
|
||||
partType: "RAIN DEFENSE",
|
||||
price: 35.5,
|
||||
sellingPrice: 35.5,
|
||||
kitPrice: 0,
|
||||
laborAmount: 0,
|
||||
salesTax: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -324,10 +335,10 @@ describe("analyticsMixin.js", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
describe("pushSubmittedOrderToDataLayer", () => {
|
||||
describe("pushOrderToDataLayer", () => {
|
||||
beforeEach(() => {
|
||||
store.getters.hasSubmittedOrder = true;
|
||||
store.getters.submittedOrder = {
|
||||
store.getters.hasSubmittedOrder = false;
|
||||
store.getters.order = {
|
||||
vehicle: {
|
||||
year: "2020",
|
||||
make: "acura",
|
||||
|
|
@ -342,8 +353,8 @@ describe("analyticsMixin.js", () => {
|
|||
address2: "add2",
|
||||
city: "city",
|
||||
state: "state",
|
||||
zipCode: "zip",
|
||||
zipCodeCtu: "zipCtu",
|
||||
zipCode: "11111",
|
||||
zipCodeCtu: "11110",
|
||||
appointmentType: "IN_SHOP",
|
||||
isVehicleProtected: true,
|
||||
provider: {
|
||||
|
|
@ -352,8 +363,8 @@ describe("analyticsMixin.js", () => {
|
|||
streetAddress: "add3",
|
||||
city: "city2",
|
||||
state: "state2",
|
||||
zipCode: "zip2",
|
||||
zipCodeCtu: "zipCtu2",
|
||||
zipCode: "22222",
|
||||
zipCodeCtu: "22220",
|
||||
},
|
||||
},
|
||||
techNotes: "",
|
||||
|
|
@ -368,13 +379,28 @@ describe("analyticsMixin.js", () => {
|
|||
damage: {
|
||||
isRepair: false,
|
||||
numberOfChips: null,
|
||||
glassToReplace: [{ glassName: "single", location: "windshield" }],
|
||||
glassToReplace: [{ glassName: "single", glassLocation: "windshield" }],
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [parts.windshield],
|
||||
supportingItems: [],
|
||||
vaps: [parts.frontWipers],
|
||||
promos: [{ promoCode: "promoTEST" }, { promoCode: "promoTEST2" }],
|
||||
promos: [
|
||||
{
|
||||
promoCode: "promoTEST",
|
||||
kitPrice: 0,
|
||||
sellingPrice: 0,
|
||||
laborAmount: 0,
|
||||
salesTax: 0,
|
||||
},
|
||||
{
|
||||
promoCode: "promoTEST2",
|
||||
kitPrice: 0,
|
||||
sellingPrice: 0,
|
||||
laborAmount: 0,
|
||||
salesTax: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
payment: {
|
||||
isInsurance: false,
|
||||
|
|
@ -397,6 +423,8 @@ describe("analyticsMixin.js", () => {
|
|||
workOrderNumber: "01820-111111",
|
||||
workOrderId: "222222222222",
|
||||
};
|
||||
store.getters.isRecalibrationOnOrder = true;
|
||||
store.getters.isRecalibrationOnSubmittedOrder = false;
|
||||
});
|
||||
|
||||
test("Pushes to data layer if nominal", () => {
|
||||
|
|
@ -408,28 +436,203 @@ describe("analyticsMixin.js", () => {
|
|||
};
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
||||
analyticsMixin.methods.pushOrderToDataLayer();
|
||||
|
||||
// Assert
|
||||
expect(mockDataLayerFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Does not push to data layer if no submitted order available.", () => {
|
||||
test("Pushes populated data to data layer", () => {
|
||||
// Arrange
|
||||
store.getters.hasSubmittedOrder = false;
|
||||
store.getters.submittedOrder = undefined;
|
||||
window.dataLayer = [];
|
||||
|
||||
const mockDataLayerFn = jest.fn();
|
||||
// Act
|
||||
analyticsMixin.methods.pushOrderToDataLayer();
|
||||
|
||||
window.dataLayer = {
|
||||
push: mockDataLayerFn,
|
||||
const result = window.dataLayer[0];
|
||||
|
||||
// Assert
|
||||
console.log(result);
|
||||
const allFieldsPopulated = Object.keys(result).every(
|
||||
(key) => result[key] === false || !!result[key]
|
||||
);
|
||||
expect(allFieldsPopulated).toBe(true);
|
||||
});
|
||||
|
||||
test("Pushes correct data when submitted order is present", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
store.getters.hasSubmittedOrder = true;
|
||||
store.getters.submittedOrder = store.getters.order;
|
||||
store.getters.order = {
|
||||
vehicle: {
|
||||
year: null,
|
||||
make: null,
|
||||
model: null,
|
||||
style: null,
|
||||
carId: null,
|
||||
category: null,
|
||||
vin: null,
|
||||
},
|
||||
serviceLocation: {
|
||||
address: null,
|
||||
address2: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
appointmentType: null,
|
||||
isVehicleProtected: null,
|
||||
provider: {
|
||||
providerNumber: null,
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
},
|
||||
},
|
||||
techNotes: null,
|
||||
},
|
||||
customer: {
|
||||
firstName: null,
|
||||
lastName: null,
|
||||
emailAddress: null,
|
||||
phoneNumber: null,
|
||||
isSmsOptIn: null,
|
||||
},
|
||||
damage: {
|
||||
isRepair: null,
|
||||
numberOfChips: null,
|
||||
glassToReplace: null,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: null,
|
||||
supportingItems: null,
|
||||
vaps: null,
|
||||
promos: null,
|
||||
},
|
||||
payment: {
|
||||
isInsurance: null,
|
||||
insuranceCoverage: {
|
||||
isVerified: null,
|
||||
coverageStatus: null,
|
||||
coverageVerificationType: null,
|
||||
},
|
||||
isPia: null,
|
||||
piaType: null,
|
||||
inactivePromos: null,
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMinMinutes: null,
|
||||
jobMaxMinutes: null,
|
||||
},
|
||||
workOrderNumber: null,
|
||||
workOrderId: null,
|
||||
};
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
||||
analyticsMixin.methods.pushOrderToDataLayer();
|
||||
|
||||
const result = window.dataLayer[0];
|
||||
|
||||
// Assert
|
||||
expect(mockDataLayerFn).not.toHaveBeenCalled();
|
||||
console.log(result);
|
||||
const allFieldsPopulated = Object.keys(result).every(
|
||||
(key) => result[key] === false || !!result[key]
|
||||
);
|
||||
expect(allFieldsPopulated).toBe(true);
|
||||
});
|
||||
|
||||
test("Pushes default values when data is missing", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
store.getters.order = {
|
||||
vehicle: {
|
||||
year: null,
|
||||
make: null,
|
||||
model: null,
|
||||
style: null,
|
||||
carId: null,
|
||||
category: null,
|
||||
vin: null,
|
||||
},
|
||||
serviceLocation: {
|
||||
address: null,
|
||||
address2: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
appointmentType: null,
|
||||
isVehicleProtected: null,
|
||||
provider: {
|
||||
providerNumber: null,
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
},
|
||||
},
|
||||
techNotes: null,
|
||||
},
|
||||
customer: {
|
||||
firstName: null,
|
||||
lastName: null,
|
||||
emailAddress: null,
|
||||
phoneNumber: null,
|
||||
isSmsOptIn: null,
|
||||
},
|
||||
damage: {
|
||||
isRepair: null,
|
||||
numberOfChips: null,
|
||||
glassToReplace: null,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: null,
|
||||
supportingItems: null,
|
||||
vaps: null,
|
||||
promos: null,
|
||||
},
|
||||
payment: {
|
||||
isInsurance: null,
|
||||
insuranceCoverage: {
|
||||
isVerified: null,
|
||||
coverageStatus: null,
|
||||
coverageVerificationType: null,
|
||||
},
|
||||
isPia: null,
|
||||
piaType: null,
|
||||
inactivePromos: null,
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMinMinutes: null,
|
||||
jobMaxMinutes: null,
|
||||
},
|
||||
workOrderNumber: null,
|
||||
workOrderId: null,
|
||||
};
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushOrderToDataLayer();
|
||||
|
||||
const result = window.dataLayer[0];
|
||||
|
||||
// Assert
|
||||
console.log(result);
|
||||
const allFieldsPopulatedOrDefault = Object.keys(result).every(
|
||||
(key) => result[key] === false || !!result[key] || result[key] === ""
|
||||
);
|
||||
expect(allFieldsPopulatedOrDefault).toBe(true);
|
||||
});
|
||||
|
||||
test("Glass and Promo strings correctly formatted", () => {
|
||||
|
|
@ -437,7 +640,7 @@ describe("analyticsMixin.js", () => {
|
|||
window.dataLayer = [];
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
||||
analyticsMixin.methods.pushOrderToDataLayer();
|
||||
|
||||
// Assert
|
||||
const glassString = window.dataLayer[0].glassToReplace;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,20 @@ import { storeActions } from "@/constants/store-actions.js";
|
|||
import store from "@/store";
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
IsEmailOptional() {
|
||||
const emailOptional = this.getSettingValue(experimentSettings.IS_EMAIL_OPTIONAL);
|
||||
return emailOptional === "true";
|
||||
},
|
||||
EmailValidationRules() {
|
||||
return this.IsEmailOptional
|
||||
? "email-address-format"
|
||||
: "email-address-required|email-address-format";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async navigateForwardWithSingleCarMatch() {
|
||||
const pageName = this.$options?.name;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ const routes = [
|
|||
}
|
||||
|
||||
if (!arePagePrerequisitesValid(component)) {
|
||||
console.log("Page prereq error: " + component.default.name);
|
||||
GoToFunnelStartOn404(next);
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +111,7 @@ const routes = [
|
|||
}
|
||||
|
||||
if (!isExistingFmgPageName(to.query.fmgPage)) {
|
||||
console.log("Not an existing fmg page name:" + to.query.fmgPage);
|
||||
GoToFunnelStartOn404(next);
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +133,9 @@ const routes = [
|
|||
.components.default();
|
||||
|
||||
if (!arePagePrerequisitesValid(nextComponent)) {
|
||||
console.log(
|
||||
"Page Prereqs not valid for next component: " + nextComponent.default.name
|
||||
);
|
||||
GoToFunnelStartOn404(next);
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +225,9 @@ router.afterEach(async (to, from) => {
|
|||
|
||||
// Push experiments to Data Layer
|
||||
analyticsMixin.methods.pushExperimentsToDataLayer();
|
||||
|
||||
// Push current order status to Data Layer
|
||||
analyticsMixin.methods.pushOrderToDataLayer();
|
||||
});
|
||||
|
||||
router.navigateWithoutSaving = (
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from "@/helpers/promotions-helper";
|
||||
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
||||
import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations";
|
||||
import { coverageStatusValue } from "@/constants/coverage-status";
|
||||
// Export State
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
|
|
@ -123,6 +124,7 @@ const getDefaultState = () => {
|
|||
currentDeductible: 0,
|
||||
policyNumber: null,
|
||||
dateOfLoss: null,
|
||||
lossCause: null,
|
||||
isItac: false,
|
||||
additionalAuthFlag: null,
|
||||
},
|
||||
|
|
@ -248,6 +250,9 @@ export const mutations = {
|
|||
updateParentAcctNumber(state, parentAcctNumber) {
|
||||
state.order.payment.parentAccountNumber = parentAcctNumber;
|
||||
},
|
||||
updateBillToAcctNumber(state, billToAcctNumber) {
|
||||
state.order.payment.billToAccountNumber = billToAcctNumber;
|
||||
},
|
||||
updateEON(state, eon) {
|
||||
state.order.eon = eon;
|
||||
},
|
||||
|
|
@ -523,6 +528,8 @@ export const mutations = {
|
|||
|
||||
state.order.payment.parentAccountNumber =
|
||||
sessionInformation.order.payment.parentAccountNumber;
|
||||
state.order.payment.billToAccountNumber =
|
||||
sessionInformation.order.payment.billToAccountNumber;
|
||||
state.order.payment.inactivePromos = sessionInformation.order.payment.inactivePromos;
|
||||
|
||||
state.order.serviceLocation.address =
|
||||
|
|
@ -557,8 +564,9 @@ export const mutations = {
|
|||
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
|
||||
state.order.payment.insuranceCoverage.isVerified =
|
||||
sessionInformation?.order.payment.insuranceCoverage.isVerified;
|
||||
state.order.payment.insuranceCoverage.coverageStatus =
|
||||
sessionInformation?.order.payment.insuranceCoverage.coverageStatus;
|
||||
state.order.payment.insuranceCoverage.coverageStatus = coverageStatusValue(
|
||||
sessionInformation?.order.payment.insuranceCoverage.coverageStatus
|
||||
);
|
||||
state.order.payment.insuranceCoverage.coverageVerificationType =
|
||||
sessionInformation?.order.payment.insuranceCoverage.coverageVerificationType;
|
||||
|
||||
|
|
@ -573,12 +581,24 @@ export const mutations = {
|
|||
state.applicationUser.pageData = sessionInformation.applicationUser.pageData;
|
||||
state.applicationUser.lastPage = sessionInformation.applicationUser.lastPage;
|
||||
|
||||
if (sessionInformation.applicationUser.savedSessionId) {
|
||||
state.applicationUser.savedSessionId =
|
||||
sessionInformation.applicationUser.savedSessionId;
|
||||
}
|
||||
|
||||
state.order.schedule.date = sessionInformation.order.schedule?.date;
|
||||
state.order.schedule.startTime = sessionInformation.order.schedule?.startTime;
|
||||
state.order.schedule.endTime = sessionInformation.order.schedule?.endTime;
|
||||
state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode;
|
||||
state.order.schedule.jobMaxMinutes = sessionInformation.order.schedule?.jobMaxMinutes;
|
||||
state.order.schedule.jobMinMinutes = sessionInformation.order.schedule?.jobMinMinutes;
|
||||
|
||||
state.order.policy.currentDeductible = sessionInformation.order.policy?.currentDeductible;
|
||||
state.order.policy.additionalAuthFlag = sessionInformation.order.policy?.additionalAuthFlag;
|
||||
state.order.policy.isItac = sessionInformation.order.policy?.isItac;
|
||||
state.order.policy.policyNumber = sessionInformation.order.policy?.policyNumber;
|
||||
state.order.policy.dateOfLoss = sessionInformation.order.policy?.lossDate;
|
||||
state.order.policy.lossCause = sessionInformation.order.policy?.lossCause;
|
||||
},
|
||||
updateExperiments(state, experiments) {
|
||||
state.applicationUser.experiments = experiments;
|
||||
|
|
@ -1343,7 +1363,7 @@ export const actions = {
|
|||
getMobileFeePart(context, { pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
||||
const billToAccountNumber = 87291; // TODO: MAKE THIS REAL
|
||||
const billToAccountNumber = context.getters.payment.billToAccountNumber;
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetMobileFeePart.method,
|
||||
|
|
@ -1354,15 +1374,19 @@ export const actions = {
|
|||
},
|
||||
|
||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems.map(
|
||||
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems?.map(
|
||||
(lineItem) => ({
|
||||
partNumber: lineItem.partNumber,
|
||||
})
|
||||
);
|
||||
const lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
||||
lineItemsWithOnlyPartNumbers,
|
||||
"lineItems"
|
||||
);
|
||||
|
||||
var lineItems = null;
|
||||
if (lineItemsWithOnlyPartNumbers) {
|
||||
lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
||||
lineItemsWithOnlyPartNumbers,
|
||||
"lineItems"
|
||||
);
|
||||
}
|
||||
|
||||
const vehicle = context.getters.vehicle;
|
||||
const carId = vehicle.carId;
|
||||
|
|
@ -1374,9 +1398,18 @@ export const actions = {
|
|||
"glassPieces"
|
||||
);
|
||||
|
||||
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}`;
|
||||
if (lineItems) {
|
||||
endPoint += `&${lineItems}`;
|
||||
}
|
||||
|
||||
if (glassPieces) {
|
||||
endPoint += `&${glassPieces}`;
|
||||
}
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetServiceabilityDetails.method,
|
||||
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&${lineItems}&${glassPieces}`,
|
||||
endpoint: endPoint,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
|
@ -1453,7 +1486,7 @@ export const actions = {
|
|||
) {
|
||||
const order = context.state.order;
|
||||
const vehicle = context.state.order.vehicle;
|
||||
|
||||
const payment = context.state.order.payment;
|
||||
let lineItems = [
|
||||
...(order.lineItems.supportingItems ?? []),
|
||||
...(order.lineItems.vaps ?? []),
|
||||
|
|
@ -1475,15 +1508,16 @@ export const actions = {
|
|||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
applicationName: applicationConfig.APPLICATION_NAME,
|
||||
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
||||
parentAccountNumber: payment.parentAccountNumber,
|
||||
carId: vehicle.carId,
|
||||
lineItems: lineItems,
|
||||
glassPieces: glassPieces,
|
||||
eon: order.eon,
|
||||
billToAccountNumber: context.getters.payment.billToAccountNumber,
|
||||
coverage: {
|
||||
status: "",
|
||||
deductible: 0,
|
||||
additionalAuthFlag: "",
|
||||
status: payment.insuranceCoverage.coverageStatus,
|
||||
deductible: order.policy.currentDeductible,
|
||||
additionalAuthFlag: order.policy.additionalAuthFlag,
|
||||
},
|
||||
partSelection: {
|
||||
hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
|
||||
|
|
@ -1521,6 +1555,7 @@ export const actions = {
|
|||
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
|
||||
const order = context.state.order;
|
||||
const vehicle = context.state.order.vehicle;
|
||||
const payment = context.state.order.payment;
|
||||
let lineItems = [
|
||||
...(order.lineItems.supportingItems ?? []),
|
||||
...(order.lineItems.vaps ?? []),
|
||||
|
|
@ -1539,15 +1574,16 @@ export const actions = {
|
|||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
applicationName: applicationConfig.APPLICATION_NAME,
|
||||
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
||||
parentAccountNumber: payment.parentAccountNumber,
|
||||
carId: vehicle.carId,
|
||||
lineItems: lineItems,
|
||||
glassPieces: glassPieces,
|
||||
eon: order.eon,
|
||||
billToAccountNumber: context.getters.payment.billToAccountNumber,
|
||||
coverage: {
|
||||
status: "",
|
||||
deductible: 0,
|
||||
additionalAuthFlag: "",
|
||||
status: payment.insuranceCoverage.coverageStatus,
|
||||
deductible: order.policy.currentDeductible,
|
||||
additionalAuthFlag: order.policy.additionalAuthFlag,
|
||||
},
|
||||
partSelection: {
|
||||
hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
|
||||
|
|
@ -1566,6 +1602,7 @@ export const actions = {
|
|||
},
|
||||
zipCode: order.serviceLocation.zipCode,
|
||||
};
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetMobileTimeSlots.method,
|
||||
endpoint: endpoints.GetMobileTimeSlots.url,
|
||||
|
|
@ -2117,6 +2154,9 @@ export const actions = {
|
|||
saveParentAccountNumber(context, parentAccountNumber) {
|
||||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
||||
},
|
||||
saveBillToAccountNumber(context, billToAccountNumber) {
|
||||
context.commit(storeMutations.UPDATE_BILL_TO_ACCT_NUMBER, billToAccountNumber);
|
||||
},
|
||||
|
||||
saveSupportingItems(context, supportingItems) {
|
||||
if (!deepEqual(supportingItems, context.state.order.lineItems.supportingItems)) {
|
||||
|
|
@ -2210,6 +2250,7 @@ export const actions = {
|
|||
|
||||
const vehicle = context.getters.order.vehicle;
|
||||
|
||||
// TODO: Insurance pricing...`ParentAccountNumber=${context.getters.order.payment.parentAccountNumber}`
|
||||
let queryString =
|
||||
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
|
||||
`&CTU=${ctuToUse}` +
|
||||
|
|
@ -2320,6 +2361,9 @@ export const actions = {
|
|||
) {
|
||||
const order = context.getters.order;
|
||||
lineItemsToUse = lineItemsToUse ?? order.lineItems;
|
||||
addGuidToLineItemsIfNotAlreadyThere(lineItemsToUse.vaps);
|
||||
syncLineItemIds(addableVaps, lineItemsToUse.vaps);
|
||||
// addableVaps still won't have IDs if they weren't already in lineItemsToUse
|
||||
addGuidToLineItemsIfNotAlreadyThere(addableVaps);
|
||||
const requestObject = {
|
||||
promoCode: promoCode,
|
||||
|
|
@ -2838,12 +2882,18 @@ function syncLineItemIds(lineItemsWithoutIds, lineItemsWithIds) {
|
|||
if (!lineItemsWithIds || !lineItemsWithoutIds) {
|
||||
return;
|
||||
}
|
||||
var clonedLineItemsWithIds = deepClone(lineItemsWithIds);
|
||||
lineItemsWithoutIds.forEach((noId) => {
|
||||
lineItemsWithIds.forEach((withId) => {
|
||||
var matchedLineItemIndex = -1;
|
||||
clonedLineItemsWithIds.forEach((withId, index) => {
|
||||
if (noId.partNumber === withId.partNumber && noId.partType === withId.partType) {
|
||||
matchedLineItemIndex = index;
|
||||
noId.id = withId.id;
|
||||
}
|
||||
});
|
||||
if (matchedLineItemIndex > -1) {
|
||||
clonedLineItemsWithIds.splice(matchedLineItemIndex, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2933,7 +2933,7 @@ describe("Actions", () => {
|
|||
// Arrange
|
||||
const context = state;
|
||||
const promoCode = "testPromo";
|
||||
const lineItemsToUse = { vaps: [1], promos: [2] };
|
||||
const lineItemsToUse = { vaps: [{ partNumber: 1 }], promos: [2] };
|
||||
const addableVaps = [{ partNumber: "addableVap" }];
|
||||
|
||||
context["getters"] = {
|
||||
|
|
@ -3013,7 +3013,7 @@ describe("Actions", () => {
|
|||
},
|
||||
referralSequenceNumber: "test",
|
||||
lineItems: {
|
||||
vaps: [1],
|
||||
vaps: [{ partNumber: 1 }],
|
||||
promos: [2],
|
||||
serverData: "test",
|
||||
},
|
||||
|
|
@ -3026,7 +3026,7 @@ describe("Actions", () => {
|
|||
|
||||
crypto.randomUUID = jest.fn(() => "GUID");
|
||||
|
||||
const expectedLineItemsOnOrder = [1, 2];
|
||||
const expectedLineItemsOnOrder = [{ partNumber: 1, id: "GUID" }, 2];
|
||||
|
||||
// Act
|
||||
actions.validateOrderPromoAndSaveServerData(context, {
|
||||
|
|
@ -3073,7 +3073,7 @@ describe("Actions", () => {
|
|||
},
|
||||
referralSequenceNumber: "test",
|
||||
lineItems: {
|
||||
vaps: [1],
|
||||
vaps: [{ partNumber: 1 }],
|
||||
promos: [2],
|
||||
serverData: "test",
|
||||
},
|
||||
|
|
@ -3131,7 +3131,7 @@ describe("Actions", () => {
|
|||
},
|
||||
referralSequenceNumber: "test",
|
||||
lineItems: {
|
||||
vaps: [1],
|
||||
vaps: [{ partNumber: 1 }],
|
||||
promos: [2],
|
||||
serverData: "test",
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue