CSR-1520 | First integration of promo logic
Implementation of validate and revalidate endpoints in the store Validate promo on quote via query string promo Revalidate promos on quote page load Remove promo query string on quote and payment-method forward navigation Add in Id logic for all line items Send relevant pricing data to service package question to be used later for UX changes
This commit is contained in:
parent
68803c6d12
commit
291e13330d
11 changed files with 419 additions and 54 deletions
|
|
@ -27,7 +27,7 @@ module.exports = {
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 78,
|
statements: 75,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,14 @@ const endpoints = {
|
||||||
url: "/price/api/v1/price/order-items",
|
url: "/price/api/v1/price/order-items",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
ValidatePromo: {
|
||||||
|
url: "/price/api/v1/price/order-promo",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
RevalidatePromos: {
|
||||||
|
url: "/price/api/v1/price/revalidated-order-promos",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
LogExperimentExposureIfAssigned: {
|
LogExperimentExposureIfAssigned: {
|
||||||
url: "/experiments/api/v1/experiments/log-exposure",
|
url: "/experiments/api/v1/experiments/log-exposure",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ const storeActions = {
|
||||||
CLEAR_VIN: "clearVin",
|
CLEAR_VIN: "clearVin",
|
||||||
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
|
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
|
||||||
GET_SIGNATURE: "getSignature",
|
GET_SIGNATURE: "getSignature",
|
||||||
|
VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA: "validateOrderPromoAndSaveServerData",
|
||||||
|
REVALIDATE_ORDER_PROMOS_AND_UPDATE_STORE: "revalidateOrderPromosAndUpdateStore",
|
||||||
|
|
||||||
// DEPENDENCY MUTATIONS
|
// DEPENDENCY MUTATIONS
|
||||||
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
|
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
|
||||||
|
|
@ -83,6 +85,7 @@ const storeActions = {
|
||||||
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
||||||
"saveSupportingItemsSuppressingStateResetting",
|
"saveSupportingItemsSuppressingStateResetting",
|
||||||
SAVE_VAPS: "saveVaps",
|
SAVE_VAPS: "saveVaps",
|
||||||
|
SAVE_PROMOS: "savePromos",
|
||||||
SAVE_CUSTOMER_DETAILS: "saveCustomerDetails",
|
SAVE_CUSTOMER_DETAILS: "saveCustomerDetails",
|
||||||
SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes",
|
SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes",
|
||||||
SAVE_WORK_ORDER_FLAG: "saveWorkOrderFlag",
|
SAVE_WORK_ORDER_FLAG: "saveWorkOrderFlag",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ const storeMutations = {
|
||||||
UPDATE_VAPS: "updateVaps",
|
UPDATE_VAPS: "updateVaps",
|
||||||
UPDATE_SUPPORTING_ITEMS: "updateSupportingItems",
|
UPDATE_SUPPORTING_ITEMS: "updateSupportingItems",
|
||||||
UPDATE_LINE_ITEMS_SERVER_DATA: "updateLineItemsServerData",
|
UPDATE_LINE_ITEMS_SERVER_DATA: "updateLineItemsServerData",
|
||||||
|
UPDATE_INACTIVE_PROMOS: "updateInactivePromos",
|
||||||
|
UPDATE_PROMOS: "updatePromos",
|
||||||
|
|
||||||
UPDATE_REGISTRATION: "updateRegistration",
|
UPDATE_REGISTRATION: "updateRegistration",
|
||||||
|
|
||||||
|
|
|
||||||
101
src/helpers/promotions-helper.js
Normal file
101
src/helpers/promotions-helper.js
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import store from "@/store";
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
||||||
|
export const promoPartNumberStrings = {
|
||||||
|
WIPER_DISCOUNT_PART_NUMBER: "WIPER DISCOUNT",
|
||||||
|
RAIN_DEFENSE_DISCOUNT_PART_NUMBER: "DISC RAIN DEFEN",
|
||||||
|
GLASS_DISCOUNT_PART_NUMBER: "DISCOUNT",
|
||||||
|
GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"];
|
||||||
|
|
||||||
|
export function getAddableVapsFromAvailableLineItems(availableLineItems) {
|
||||||
|
const addableVaps = [];
|
||||||
|
addableVaps.push(...findLineItemsWithPartType(partTypeStrings.FRONT_WIPER, availableLineItems));
|
||||||
|
addableVaps.push(
|
||||||
|
...findLineItemsWithPartType(partTypeStrings.RAIN_DEFENSE, availableLineItems)
|
||||||
|
);
|
||||||
|
return addableVaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeVapsPromosFromPromoArray(promoArray) {
|
||||||
|
if (!promoArray) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const filteredPromoArray = promoArray.filter((promo) => {
|
||||||
|
return (
|
||||||
|
promo.partNumber != promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER &&
|
||||||
|
promo.partNumber != promoPartNumberStrings.RAIN_DEFENSE_DISCOUNT_PART_NUMBER &&
|
||||||
|
promo.partNumber != promoPartNumberStrings.GLASS_CLEANER_DISCOUNT_PART_NUMBER
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return filteredPromoArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function revalidatePromosAndValidateNewPromo(
|
||||||
|
newPromo,
|
||||||
|
pricedLineItems,
|
||||||
|
pageNameToLog
|
||||||
|
) {
|
||||||
|
const hasActivePromos = store.getters.order.lineItems.promos;
|
||||||
|
const hasHasInactivePromos = store.getters.order.lineItems.inactivePromos;
|
||||||
|
const hasNewPromo = !!newPromo;
|
||||||
|
const addableVaps = getAddableVapsFromAvailableLineItems(pricedLineItems);
|
||||||
|
|
||||||
|
let validatePromoResult = null;
|
||||||
|
let revalidatePromoResult = null;
|
||||||
|
|
||||||
|
if (hasActivePromos || hasHasInactivePromos) {
|
||||||
|
revalidatePromoResult = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.REVALIDATE_ORDER_PROMOS_AND_UPDATE_STORE,
|
||||||
|
null,
|
||||||
|
pageNameToLog,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (hasNewPromo) {
|
||||||
|
validatePromoResult = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
|
||||||
|
{
|
||||||
|
promoCode: newPromo,
|
||||||
|
addableVaps: addableVaps,
|
||||||
|
},
|
||||||
|
pageNameToLog,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { validatePromoResult, revalidatePromoResult };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
|
||||||
|
const matchingPromos = [];
|
||||||
|
promos.forEach((promo) => {
|
||||||
|
let allIdsMatch = true;
|
||||||
|
promo.discountedLineItemIds.forEach((id) => {
|
||||||
|
if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) {
|
||||||
|
allIdsMatch = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (allIdsMatch) {
|
||||||
|
matchingPromos.push(promo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return matchingPromos;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldStripPromoQueryString(fmgPageQueryValue) {
|
||||||
|
return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// private methods
|
||||||
|
function findLineItemsWithPartType(typeToFind, itemsToSearch) {
|
||||||
|
const partTypeMatches = itemsToSearch?.filter(
|
||||||
|
(lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()
|
||||||
|
);
|
||||||
|
return partTypeMatches;
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,12 @@ jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
|
||||||
navigateToHeritageFunnel: jest.fn(),
|
navigateToHeritageFunnel: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/helpers/promotions-helper", () => ({
|
||||||
|
revalidatePromosAndValidateNewPromo: jest.fn(() => {
|
||||||
|
return { validatePromoResponse: null, revalidatePromoResponse: null };
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
"@/store",
|
"@/store",
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -43,11 +49,20 @@ jest.mock("@/mixins/base-mixin", () => ({
|
||||||
getTierOnePackagePrice() {
|
getTierOnePackagePrice() {
|
||||||
return mockTierOnePrice;
|
return mockTierOnePrice;
|
||||||
},
|
},
|
||||||
filterOutFees() {
|
filterOutFees(items) {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
filterOutFees: jest.fn().mockImplementation(() => {
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let mockTierOnePrice = 501;
|
let mockTierOnePrice = 501;
|
||||||
|
|
||||||
const mockPriceOrderStoreAction = storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
|
const mockPriceOrderStoreAction = storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
|
||||||
|
|
@ -93,6 +108,16 @@ afterEach(() => {
|
||||||
describe("quote.vue", () => {
|
describe("quote.vue", () => {
|
||||||
test("IsInsurance false should navigateWithSaving", async () => {
|
test("IsInsurance false should navigateWithSaving", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
|
store.getters.payment = {
|
||||||
|
insuranceCoverage: {},
|
||||||
|
isInsurance: false,
|
||||||
|
};
|
||||||
|
store.getters.order = {
|
||||||
|
lineItems: [],
|
||||||
|
payment: {
|
||||||
|
parentAccountNumber: 167132,
|
||||||
|
},
|
||||||
|
};
|
||||||
const { wrapper } = setupMocks({
|
const { wrapper } = setupMocks({
|
||||||
customMountOptions: {
|
customMountOptions: {
|
||||||
router: {
|
router: {
|
||||||
|
|
@ -102,11 +127,6 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
store.getters.payment = {
|
|
||||||
insuranceCoverage: {},
|
|
||||||
isInsurance: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper.vm.dispatchStoreAction = jest.fn(() => {
|
wrapper.vm.dispatchStoreAction = jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
data: [],
|
data: [],
|
||||||
|
|
@ -124,20 +144,28 @@ describe("quote.vue", () => {
|
||||||
|
|
||||||
test("IsInsurance true should navigateToHeritageFunnel", async () => {
|
test("IsInsurance true should navigateToHeritageFunnel", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
|
store.getters = {
|
||||||
|
payment: {
|
||||||
|
insuranceCoverage: {},
|
||||||
|
isInsurance: true,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
lineItems: [],
|
||||||
|
payment: {
|
||||||
|
parentAccountNumber: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
const { wrapper } = setupMocks({
|
const { wrapper } = setupMocks({
|
||||||
customMountOptions: {
|
customMountOptions: {
|
||||||
router: {
|
router: {
|
||||||
navigateWithSaving: jest.fn(),
|
navigateWithSaving: jest.fn(),
|
||||||
},
|
},
|
||||||
route: { quote },
|
route: { quote },
|
||||||
|
mixins: [mockMixin],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
store.getters.payment = {
|
|
||||||
insuranceCoverage: {},
|
|
||||||
isInsurance: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper.vm.dispatchStoreAction = jest.fn(() => {
|
wrapper.vm.dispatchStoreAction = jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
data: [],
|
data: [],
|
||||||
|
|
@ -154,9 +182,9 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should pass arePagePrerequisitesValid with a repair order", () => {
|
test("should pass arePagePrerequisitesValid with a repair order", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
|
lineItems: [],
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
zipCode: "12345",
|
zipCode: "12345",
|
||||||
zipCodeCtu: "value",
|
zipCodeCtu: "value",
|
||||||
|
|
@ -172,6 +200,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
|
@ -179,7 +208,6 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should pass arePagePrerequisitesValid with a replace order", () => {
|
test("should pass arePagePrerequisitesValid with a replace order", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
|
|
@ -200,6 +228,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
|
@ -207,9 +236,9 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should fail arePagePrerequisitesValid without glass parts or flagged as repair", () => {
|
test("should fail arePagePrerequisitesValid without glass parts or flagged as repair", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
|
lineItems: [],
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
zipCode: "12345",
|
zipCode: "12345",
|
||||||
zipCodeCtu: "value",
|
zipCodeCtu: "value",
|
||||||
|
|
@ -220,6 +249,7 @@ describe("quote.vue", () => {
|
||||||
referralNumber: "1234567",
|
referralNumber: "1234567",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
|
@ -227,8 +257,6 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should have non-null values for necessary data members after 'beforeRouteEnter'", async () => {
|
test("should have non-null values for necessary data members after 'beforeRouteEnter'", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
|
|
@ -236,7 +264,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
//mock this to avoid needing to populate this.$route in an unrelated test
|
//mock this to avoid needing to populate this.$route in an unrelated test
|
||||||
wrapper.vm.getDefaultIsInsuranceSelectedValue = jest.fn();
|
wrapper.vm.getDefaultIsInsuranceSelectedValue = jest.fn();
|
||||||
|
|
||||||
|
|
@ -257,8 +285,6 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should default to insurance if query param 'isInsurance' is true", async () => {
|
test("should default to insurance if query param 'isInsurance' is true", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
|
|
@ -272,6 +298,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
wrapper.vm.$route = { query: { isInsurance: "true" } };
|
wrapper.vm.$route = { query: { isInsurance: "true" } };
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
|
|
@ -287,8 +314,6 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should default to cash if query param 'isInsurance' is false", async () => {
|
test("should default to cash if query param 'isInsurance' is false", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
|
|
@ -302,6 +327,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
wrapper.vm.$route = { query: { isInsurance: "false" } };
|
wrapper.vm.$route = { query: { isInsurance: "false" } };
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
|
|
@ -317,7 +343,6 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should default to insurance if insurance selection is saved to store", async () => {
|
test("should default to insurance if insurance selection is saved to store", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
|
|
@ -332,6 +357,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
// Ensure that query param isn't overriding selection
|
// Ensure that query param isn't overriding selection
|
||||||
wrapper.vm.$route = { query: null };
|
wrapper.vm.$route = { query: null };
|
||||||
|
|
||||||
|
|
@ -348,8 +374,6 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("should default to cash if cash selection is saved to store", async () => {
|
test("should default to cash if cash selection is saved to store", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
|
|
@ -363,6 +387,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
// Ensure that query param isn't overriding selection
|
// Ensure that query param isn't overriding selection
|
||||||
wrapper.vm.$route = { query: null };
|
wrapper.vm.$route = { query: null };
|
||||||
|
|
||||||
|
|
@ -380,8 +405,6 @@ describe("quote.vue", () => {
|
||||||
test("should default to cash if total economy package price is under $500", async () => {
|
test("should default to cash if total economy package price is under $500", async () => {
|
||||||
// Also needs no query parameter or previous selection in store to be present
|
// Also needs no query parameter or previous selection in store to be present
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
|
|
@ -396,6 +419,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
mockTierOnePrice = 200;
|
mockTierOnePrice = 200;
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
// Ensure that query param isn't overriding selection
|
// Ensure that query param isn't overriding selection
|
||||||
wrapper.vm.$route = { query: null };
|
wrapper.vm.$route = { query: null };
|
||||||
|
|
||||||
|
|
@ -413,7 +437,6 @@ describe("quote.vue", () => {
|
||||||
test("should default to insurance if total economy package price is over $500", async () => {
|
test("should default to insurance if total economy package price is over $500", async () => {
|
||||||
// Also needs no query parameter or previous selection in store to be present
|
// Also needs no query parameter or previous selection in store to be present
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
|
|
@ -429,6 +452,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
mockTierOnePrice = 505;
|
mockTierOnePrice = 505;
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
// Ensure that query param isn't overriding selection
|
// Ensure that query param isn't overriding selection
|
||||||
wrapper.vm.$route = { query: null };
|
wrapper.vm.$route = { query: null };
|
||||||
|
|
||||||
|
|
@ -445,7 +469,7 @@ describe("quote.vue", () => {
|
||||||
});
|
});
|
||||||
test("Should default to insurence if user Service zip is from certain States", async () => {
|
test("Should default to insurence if user Service zip is from certain States", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
store.getters = {
|
store.getters = {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
|
|
@ -459,6 +483,7 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
wrapper.vm.$route = { query: null };
|
wrapper.vm.$route = { query: null };
|
||||||
//Act
|
//Act
|
||||||
await quote.beforeRouteEnter.call(
|
await quote.beforeRouteEnter.call(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
:availableLineItems="availableLineItems"
|
:availableLineItems="availableLineItems"
|
||||||
:isInsuranceSelected="isInsuranceSelected"
|
:isInsuranceSelected="isInsuranceSelected"
|
||||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||||
|
:activePromos="allActivePromos"
|
||||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||||
validationRules="option-required"
|
validationRules="option-required"
|
||||||
isRequired />
|
isRequired />
|
||||||
|
|
@ -79,6 +80,9 @@ import { Form, defineRule } from "vee-validate";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
import { applicationConfig } from "@/constants/application-config";
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
|
import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
|
||||||
|
import { revalidatePromosAndValidateNewPromo } from "@/helpers/promotions-helper";
|
||||||
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
|
import { setTransitionHooks } from "vue";
|
||||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -122,14 +126,12 @@ export default {
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
const clonedGlassParts = store.getters.order.lineItems.glassParts
|
const nullSafeGlassParts = store.getters.order.lineItems.glassParts ?? [];
|
||||||
? JSON.parse(JSON.stringify(store.getters.order.lineItems.glassParts))
|
|
||||||
: [];
|
|
||||||
const availableLineItems = [
|
const availableLineItems = [
|
||||||
resultMap.rainDefense,
|
resultMap.rainDefense,
|
||||||
...resultMap.supportingItems,
|
...resultMap.supportingItems,
|
||||||
...resultMap.wipers,
|
...resultMap.wipers,
|
||||||
...clonedGlassParts,
|
...nullSafeGlassParts,
|
||||||
];
|
];
|
||||||
|
|
||||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
|
@ -146,13 +148,32 @@ export default {
|
||||||
resultMap.supportingItems,
|
resultMap.supportingItems,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Promo logic
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
const lowerCaseParams = new URLSearchParams();
|
||||||
|
for (const [name, value] of urlParams) {
|
||||||
|
lowerCaseParams.append(name.toLowerCase(), value);
|
||||||
|
}
|
||||||
|
const promoCodeFromQueryString = lowerCaseParams.get(queryStrings.PROMO);
|
||||||
|
|
||||||
|
const { validatePromoResponse, revalidatePromoResponse } =
|
||||||
|
await revalidatePromosAndValidateNewPromo(
|
||||||
|
promoCodeFromQueryString,
|
||||||
|
pricingResults,
|
||||||
|
"quote"
|
||||||
|
);
|
||||||
|
// End of promo logic
|
||||||
|
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
vm.pricedGlassParts = clonedGlassParts;
|
vm.pricedGlassParts = nullSafeGlassParts;
|
||||||
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?.data.orderPromos;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -162,8 +183,18 @@ export default {
|
||||||
availableLineItems: null,
|
availableLineItems: null,
|
||||||
supportingItems: null,
|
supportingItems: null,
|
||||||
pricedGlassParts: null,
|
pricedGlassParts: null,
|
||||||
|
newValidatedPromos: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
allActivePromos() {
|
||||||
|
const allActivePromos = [];
|
||||||
|
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: {
|
||||||
openModalAction(modalName) {
|
openModalAction(modalName) {
|
||||||
this.$refs[modalName].openModal();
|
this.$refs[modalName].openModal();
|
||||||
|
|
@ -239,6 +270,7 @@ 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_PROMOS, this.newValidatedPromos, false);
|
||||||
|
|
||||||
const payment = this.$store.getters.payment;
|
const payment = this.$store.getters.payment;
|
||||||
if (payment.isInsurance) {
|
if (payment.isInsurance) {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ import {
|
||||||
containsLineItemWithPartType,
|
containsLineItemWithPartType,
|
||||||
findLineItemsWithPartType,
|
findLineItemsWithPartType,
|
||||||
} from "@/helpers/service-package-helper";
|
} from "@/helpers/service-package-helper";
|
||||||
|
import {
|
||||||
|
getPromosThatMatchLineItemsOnOrder,
|
||||||
|
removeVapsPromosFromPromoArray,
|
||||||
|
} from "@/helpers/promotions-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "servicePackageQuestion",
|
name: "servicePackageQuestion",
|
||||||
|
|
@ -40,6 +44,7 @@ export default {
|
||||||
isRequired: Boolean,
|
isRequired: Boolean,
|
||||||
isInsuranceSelected: Boolean,
|
isInsuranceSelected: Boolean,
|
||||||
availableLineItems: null,
|
availableLineItems: null,
|
||||||
|
activePromos: null,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -86,8 +91,11 @@ export default {
|
||||||
buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName),
|
buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName),
|
||||||
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName),
|
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName),
|
||||||
buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName),
|
buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName),
|
||||||
buttonAuxillaryCopy: this.getPackagePriceString(answer.Name),
|
buttonAuxillaryCopy: this.getDiscountedPackagePriceString(answer.Name),
|
||||||
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
|
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
|
||||||
|
additionalButtonData: {
|
||||||
|
strikeThroughPrice: this.getPackagePriceString(answer.Name),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
return modifiedAnswers;
|
return modifiedAnswers;
|
||||||
},
|
},
|
||||||
|
|
@ -161,21 +169,33 @@ export default {
|
||||||
return this.processIfStatements(footerText, "custom", this.getCustomValueFromString);
|
return this.processIfStatements(footerText, "custom", this.getCustomValueFromString);
|
||||||
},
|
},
|
||||||
getPackagePriceString(packageName) {
|
getPackagePriceString(packageName) {
|
||||||
const formattedPriceFloat = parseFloat(this.getPackagePrice(packageName)).toFixed(2);
|
const formattedPriceFloat = parseFloat(
|
||||||
|
this.getPackagePrice(packageName, { discountedPrice: false })
|
||||||
|
).toFixed(2);
|
||||||
|
return "$" + formattedPriceFloat;
|
||||||
|
},
|
||||||
|
getDiscountedPackagePriceString(packageName) {
|
||||||
|
const formattedPriceFloat = parseFloat(
|
||||||
|
this.getPackagePrice(packageName, { discountedPrice: true })
|
||||||
|
).toFixed(2);
|
||||||
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
||||||
},
|
},
|
||||||
getPackagePrice(packageName) {
|
getPackagePrice(packageName, { discountedPrice = false }) {
|
||||||
|
const lineItemsToPrice = [...this.nullSafeAvailableLineItems];
|
||||||
|
if (discountedPrice) {
|
||||||
|
lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos));
|
||||||
|
}
|
||||||
let priceFloat = this.isInsuranceSelected
|
let priceFloat = this.isInsuranceSelected
|
||||||
? 0
|
? 0
|
||||||
: baseMixin.methods.getTierOnePackagePrice(
|
: baseMixin.methods.getTierOnePackagePrice(
|
||||||
baseMixin.methods.filterOutFees(this.nullSafeAvailableLineItems)
|
baseMixin.methods.filterOutFees(lineItemsToPrice)
|
||||||
);
|
);
|
||||||
|
|
||||||
priceFloat += this.getVapsPrice(packageName);
|
priceFloat += this.getVapsPrice(packageName, discountedPrice);
|
||||||
|
|
||||||
return priceFloat;
|
return priceFloat;
|
||||||
},
|
},
|
||||||
getVapsPrice(packageName) {
|
getVapsPrice(packageName, applyPromoDiscounts = false) {
|
||||||
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
|
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
|
||||||
|
|
||||||
let price = 0;
|
let price = 0;
|
||||||
|
|
@ -184,6 +204,16 @@ export default {
|
||||||
price += baseMixin.methods.getTotalLineItemPrice(item);
|
price += baseMixin.methods.getTotalLineItemPrice(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (applyPromoDiscounts && this.activePromos) {
|
||||||
|
const relevantPromos = getPromosThatMatchLineItemsOnOrder(
|
||||||
|
this.activePromos,
|
||||||
|
vapsItems
|
||||||
|
);
|
||||||
|
relevantPromos.forEach((promo) => {
|
||||||
|
price += baseMixin.methods.getTotalLineItemPrice(promo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return price;
|
return price;
|
||||||
},
|
},
|
||||||
selectDefaultPackage() {
|
selectDefaultPackage() {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import store from "@/store";
|
||||||
import analyticsMixin from "@/mixins/analytics-mixin";
|
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
import { experimentTriggers } from "../constants/experiments";
|
import { experimentTriggers } from "../constants/experiments";
|
||||||
import { applicationConfig } from "../constants/application-config";
|
import { applicationConfig } from "../constants/application-config";
|
||||||
|
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
|
@ -283,7 +284,7 @@ async function navigate(
|
||||||
queryStringsObject[queryStrings.ZIP_CODE] = zip;
|
queryStringsObject[queryStrings.ZIP_CODE] = zip;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promo) {
|
if (promo && !shouldStripPromoQueryString(currentRoute.query.fmgPage)) {
|
||||||
queryStringsObject[queryStrings.PROMO] = promo;
|
queryStringsObject[queryStrings.PROMO] = promo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ const getDefaultState = () => {
|
||||||
supportingItems: null,
|
supportingItems: null,
|
||||||
vaps: null,
|
vaps: null,
|
||||||
serverData: null,
|
serverData: null,
|
||||||
|
promos: null,
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
isInsurance: null,
|
isInsurance: null,
|
||||||
|
|
@ -94,6 +95,7 @@ const getDefaultState = () => {
|
||||||
parentAccountNumber: 0,
|
parentAccountNumber: 0,
|
||||||
payNowType: "cc",
|
payNowType: "cc",
|
||||||
piaErrorCode: null,
|
piaErrorCode: null,
|
||||||
|
inactivePromos: null,
|
||||||
},
|
},
|
||||||
schedule: {
|
schedule: {
|
||||||
date: null,
|
date: null,
|
||||||
|
|
@ -193,6 +195,12 @@ export const mutations = {
|
||||||
updateLineItemsServerData(state, serverData) {
|
updateLineItemsServerData(state, serverData) {
|
||||||
state.order.lineItems.serverData = serverData;
|
state.order.lineItems.serverData = serverData;
|
||||||
},
|
},
|
||||||
|
updateInactivePromos(state, inactivePromos) {
|
||||||
|
state.order.payment.inactivePromos = inactivePromos;
|
||||||
|
},
|
||||||
|
updatePromos(state, promos) {
|
||||||
|
state.order.lineItems.promos = promos;
|
||||||
|
},
|
||||||
updatePageData(state, pageData) {
|
updatePageData(state, pageData) {
|
||||||
state.applicationUser.pageData[pageData.page] = pageData.data;
|
state.applicationUser.pageData[pageData.page] = pageData.data;
|
||||||
},
|
},
|
||||||
|
|
@ -1227,10 +1235,10 @@ export const actions = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
getWipers(context, { pageNameToLog }) {
|
async getWipers(context, { pageNameToLog }) {
|
||||||
const carId = context.getters.vehicle.carId;
|
const carId = context.getters.vehicle.carId;
|
||||||
const serviceZipCode = context.getters.order.serviceLocation.zipCode;
|
const serviceZipCode = context.getters.order.serviceLocation.zipCode;
|
||||||
return globalMethods
|
const response = await globalMethods
|
||||||
.callHttpClient({
|
.callHttpClient({
|
||||||
method: endpoints.GetWipers.method,
|
method: endpoints.GetWipers.method,
|
||||||
endpoint: `${endpoints.GetWipers.url}/${carId}/${serviceZipCode}`,
|
endpoint: `${endpoints.GetWipers.url}/${carId}/${serviceZipCode}`,
|
||||||
|
|
@ -1241,15 +1249,20 @@ export const actions = {
|
||||||
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
|
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
syncLineItemIds(response.data, context.getters.order.lineItems.vaps);
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
getRainDefense(context, { pageNameToLog }) {
|
async getRainDefense(context, { pageNameToLog }) {
|
||||||
return globalMethods.callHttpClient({
|
const response = await globalMethods.callHttpClient({
|
||||||
method: endpoints.GetRainDefense.method,
|
method: endpoints.GetRainDefense.method,
|
||||||
endpoint: endpoints.GetRainDefense.url,
|
endpoint: endpoints.GetRainDefense.url,
|
||||||
logApiCall: true,
|
logApiCall: true,
|
||||||
pageNameToLog: pageNameToLog,
|
pageNameToLog: pageNameToLog,
|
||||||
});
|
});
|
||||||
|
syncLineItemIds([response.data], context.getters.order.lineItems.vaps);
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
getMobileFeePart(context, { pageNameToLog }) {
|
getMobileFeePart(context, { pageNameToLog }) {
|
||||||
|
|
@ -1574,6 +1587,7 @@ export const actions = {
|
||||||
supportingItems: lineItems.supportingItems,
|
supportingItems: lineItems.supportingItems,
|
||||||
vaps: lineItems.vaps,
|
vaps: lineItems.vaps,
|
||||||
serverData: lineItems.serverData,
|
serverData: lineItems.serverData,
|
||||||
|
promos: lineItems.promos,
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
InsuranceCoverage: {
|
InsuranceCoverage: {
|
||||||
|
|
@ -1581,6 +1595,7 @@ export const actions = {
|
||||||
},
|
},
|
||||||
isInsurance: order.payment.isInsurance,
|
isInsurance: order.payment.isInsurance,
|
||||||
parentAccountNumber: order.payment.parentAccountNumber,
|
parentAccountNumber: order.payment.parentAccountNumber,
|
||||||
|
inactivePromos: order.payment.inactivePromos,
|
||||||
},
|
},
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
streetAddress: order.serviceLocation.address,
|
streetAddress: order.serviceLocation.address,
|
||||||
|
|
@ -1995,17 +2010,21 @@ export const actions = {
|
||||||
if (!deepEqual(supportingItems, context.state.order.lineItems.supportingItems)) {
|
if (!deepEqual(supportingItems, context.state.order.lineItems.supportingItems)) {
|
||||||
context.dispatch(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
||||||
}
|
}
|
||||||
|
addGuidToLineItemsIfNotAlreadyThere(supportingItems);
|
||||||
context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItems);
|
context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItems);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveSupportingItemsSuppressingStateResetting(context, supportingItems) {
|
saveSupportingItemsSuppressingStateResetting(context, supportingItems) {
|
||||||
|
addGuidToLineItemsIfNotAlreadyThere(supportingItems);
|
||||||
context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItems);
|
context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItems);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveVaps(context, vaps) {
|
saveVaps(context, vaps) {
|
||||||
|
addGuidToLineItemsIfNotAlreadyThere(vaps);
|
||||||
context.commit(storeMutations.UPDATE_VAPS, vaps);
|
context.commit(storeMutations.UPDATE_VAPS, vaps);
|
||||||
},
|
},
|
||||||
|
savePromos(context, promos) {
|
||||||
|
context.commit(storeMutations.UPDATE_PROMOS, promos);
|
||||||
|
},
|
||||||
|
|
||||||
// Price order actions
|
// Price order actions
|
||||||
async priceOrderItemsAndSaveServerData(
|
async priceOrderItemsAndSaveServerData(
|
||||||
|
|
@ -2061,6 +2080,95 @@ export const actions = {
|
||||||
return availableLineItems;
|
return availableLineItems;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async validateOrderPromoAndSaveServerData(
|
||||||
|
context,
|
||||||
|
{ payload: { promoCode, addableVaps }, pageNameToLog }
|
||||||
|
) {
|
||||||
|
const order = context.getters.order;
|
||||||
|
addGuidToLineItemsIfNotAlreadyThere(addableVaps);
|
||||||
|
const requestObject = {
|
||||||
|
promoCode: promoCode,
|
||||||
|
order: {
|
||||||
|
appointmentType: order.serviceLocation.appointmentType,
|
||||||
|
carId: order.vehicle.carId,
|
||||||
|
correlationId: order.referralCorrelationId,
|
||||||
|
eon: order.eon,
|
||||||
|
isRepair: order.damage.isRepair,
|
||||||
|
glassToReplace: renameGlassToReplaceAttributes(order.damage.glassToReplace),
|
||||||
|
lineItemsOnOrder: getArrayOfAllLineItems(order.lineItems),
|
||||||
|
addableVaps: addableVaps,
|
||||||
|
parentAccountNumber: order.payment.parentAccountNumber,
|
||||||
|
referralSequenceNumber: order.referralSequenceNumber,
|
||||||
|
serviceState: order.serviceLocation.state,
|
||||||
|
serverData: order.lineItems.serverData,
|
||||||
|
vehicleYear: "" + order.vehicle.year,
|
||||||
|
zipCodeOrProviderCtu:
|
||||||
|
order.serviceLocation.appointmentType === "Inshop"
|
||||||
|
? order.serviceLocation.provider.address.zipCodeCtu
|
||||||
|
: order.serviceLocation.zipCodeCtu,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateResponse = await globalMethods.callHttpClient({
|
||||||
|
method: endpoints.ValidatePromo.method,
|
||||||
|
endpoint: endpoints.ValidatePromo.url,
|
||||||
|
payload: requestObject,
|
||||||
|
logApiCall: true,
|
||||||
|
pageNameToLog: pageNameToLog,
|
||||||
|
});
|
||||||
|
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA,
|
||||||
|
validateResponse.data.lineItemsServerData
|
||||||
|
);
|
||||||
|
|
||||||
|
return validateResponse;
|
||||||
|
},
|
||||||
|
async revalidateOrderPromosAndUpdateStore(context, { pageNameToLog }) {
|
||||||
|
const order = context.getters.order;
|
||||||
|
|
||||||
|
let requestObject = {
|
||||||
|
inactivePromos: order.payment.inactivePromos,
|
||||||
|
order: {
|
||||||
|
appointmentType: order.serviceLocation.appointmentType,
|
||||||
|
carId: order.vehicle.carId,
|
||||||
|
correlationId: order.referralCorrelationId,
|
||||||
|
eon: order.eon,
|
||||||
|
isRepair: order.damage.isRepair,
|
||||||
|
glassToReplace: renameGlassToReplaceAttributes(order.damage.glassToReplace),
|
||||||
|
lineItemsOnOrder: getArrayOfAllLineItems(order.lineItems),
|
||||||
|
parentAccountNumber: order.payment.parentAccountNumber,
|
||||||
|
referralSequenceNumber: order.referralSequenceNumber,
|
||||||
|
serviceState: order.serviceLocation.state,
|
||||||
|
serverData: order.lineItems.serverData,
|
||||||
|
vehicleYear: "" + order.vehicle.year,
|
||||||
|
zipCodeOrProviderCtu:
|
||||||
|
order.serviceLocation.appointmentType === "Inshop"
|
||||||
|
? order.serviceLocation.provider.address.zipCodeCtu
|
||||||
|
: order.serviceLocation.zipCodeCtu,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const revalidateResponse = await globalMethods.callHttpClient({
|
||||||
|
method: endpoints.RevalidatePromos.method,
|
||||||
|
endpoint: endpoints.RevalidatePromos.url,
|
||||||
|
payload: requestObject,
|
||||||
|
logApiCall: true,
|
||||||
|
pageNameToLog: pageNameToLog,
|
||||||
|
});
|
||||||
|
|
||||||
|
const revalidationErrorPromoCodes = revalidateResponse.data.errors.map((x) => x.promoCode);
|
||||||
|
|
||||||
|
context.commit(storeMutations.UPDATE_PROMOS, revalidateResponse.data.promoLineItems);
|
||||||
|
context.commit(storeMutations.UPDATE_INACTIVE_PROMOS, revalidationErrorPromoCodes);
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA,
|
||||||
|
revalidateResponse.data.lineItemsServerData
|
||||||
|
);
|
||||||
|
|
||||||
|
return revalidateResponse;
|
||||||
|
},
|
||||||
|
|
||||||
// Misc order actions
|
// Misc order actions
|
||||||
saveSchedule(context, scheduleInfo) {
|
saveSchedule(context, scheduleInfo) {
|
||||||
context.commit(storeMutations.UPDATE_SCHEDULE, scheduleInfo);
|
context.commit(storeMutations.UPDATE_SCHEDULE, scheduleInfo);
|
||||||
|
|
@ -2125,7 +2233,7 @@ export const actions = {
|
||||||
if (!deepEqual(parts, context.state.order.lineItems.glassParts)) {
|
if (!deepEqual(parts, context.state.order.lineItems.glassParts)) {
|
||||||
context.dispatch(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
||||||
}
|
}
|
||||||
|
addGuidToLineItemsIfNotAlreadyThere(parts);
|
||||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, parts);
|
context.commit(storeMutations.UPDATE_GLASS_PARTS, parts);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -2281,6 +2389,19 @@ function addPricesToLineItems(lineItems, pricingLineItems) {
|
||||||
return lineItems;
|
return lineItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getArrayOfAllLineItems(lineItems) {
|
||||||
|
let consolidatedLineItemsArray = [];
|
||||||
|
if (lineItems.glassParts != null)
|
||||||
|
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.glassParts];
|
||||||
|
if (lineItems.supportingItems != null)
|
||||||
|
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.supportingItems];
|
||||||
|
if (lineItems.vaps != null)
|
||||||
|
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.vaps];
|
||||||
|
if (lineItems.promos != null)
|
||||||
|
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.promos];
|
||||||
|
return consolidatedLineItemsArray;
|
||||||
|
}
|
||||||
|
|
||||||
function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
|
function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
|
||||||
let flattenedArray = [];
|
let flattenedArray = [];
|
||||||
lineItems?.forEach((lineItem) => {
|
lineItems?.forEach((lineItem) => {
|
||||||
|
|
@ -2315,6 +2436,37 @@ function convertGlassPieceToBackEndCompatibleFormat(glassPieces) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renameGlassToReplaceAttributes(glassToReplace) {
|
||||||
|
let newGlassToReplace = [];
|
||||||
|
if (glassToReplace) {
|
||||||
|
glassToReplace.forEach((item) => {
|
||||||
|
newGlassToReplace.push({ location: item.glassLocation, name: item.glassName });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return newGlassToReplace;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addGuidToLineItemsIfNotAlreadyThere(lineItems) {
|
||||||
|
lineItems.forEach((lineItem) => {
|
||||||
|
if (!lineItem.id) {
|
||||||
|
lineItem.id = crypto.randomUUID();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLineItemIds(lineItemsWithoutIds, lineItemsWithIds) {
|
||||||
|
if (!lineItemsWithIds || !lineItemsWithoutIds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lineItemsWithoutIds.forEach((noId) => {
|
||||||
|
lineItemsWithIds.forEach((withId) => {
|
||||||
|
if (noId.partNumber === withId.partNumber && noId.partType === withId.partType) {
|
||||||
|
noId.id = withId.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function providersEqual(providerA, providerB) {
|
function providersEqual(providerA, providerB) {
|
||||||
return (
|
return (
|
||||||
providerA.providerNumber === providerB.providerNumber &&
|
providerA.providerNumber === providerB.providerNumber &&
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||||
// Mock global method
|
// Mock global method
|
||||||
globalMethods.callHttpClient = jest.fn();
|
globalMethods.callHttpClient = jest.fn();
|
||||||
|
|
||||||
|
global.crypto = { randomUUID: jest.fn() };
|
||||||
|
|
||||||
describe("Mutations", () => {
|
describe("Mutations", () => {
|
||||||
it("Updates vehicle year in state", () => {
|
it("Updates vehicle year in state", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -1378,6 +1380,8 @@ describe("Actions", () => {
|
||||||
state: state,
|
state: state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
context.state.order.lineItems = [];
|
||||||
|
|
||||||
const commit = jest.fn();
|
const commit = jest.fn();
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
|
|
||||||
|
|
@ -1385,10 +1389,10 @@ describe("Actions", () => {
|
||||||
context.dispatch = dispatch;
|
context.dispatch = dispatch;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
actions.saveGlassParts(context, { glassParts: {} });
|
actions.saveGlassParts(context, []);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(commit).toBeCalledWith(storeMutations.UPDATE_GLASS_PARTS, { glassParts: {} });
|
expect(commit).toBeCalledWith(storeMutations.UPDATE_GLASS_PARTS, []);
|
||||||
expect(dispatch).toBeCalledWith(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
expect(dispatch).toBeCalledWith(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1417,7 +1421,7 @@ describe("Actions", () => {
|
||||||
state: {
|
state: {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
supportingItems: ["TestValue1", "TestValue2"],
|
supportingItems: [{ partNum: "TestValue1" }, { partNum: "TestValue2" }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1430,7 +1434,11 @@ describe("Actions", () => {
|
||||||
context.dispatch = dispatch;
|
context.dispatch = dispatch;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
actions.saveSupportingItems(context, ["TestValue3", "TestValue4", "TestValue5"]);
|
actions.saveSupportingItems(context, [
|
||||||
|
{ partNum: "TestValue3" },
|
||||||
|
{ partNum: "TestValue4" },
|
||||||
|
{ partNum: "TestValue5" },
|
||||||
|
]);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(dispatch).toBeCalledWith(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
expect(dispatch).toBeCalledWith(storeActions.RESET_SERVICE_LOCATION_STATE_AND_DEPENDENCIES);
|
||||||
|
|
@ -1442,7 +1450,7 @@ describe("Actions", () => {
|
||||||
state: {
|
state: {
|
||||||
order: {
|
order: {
|
||||||
lineItems: {
|
lineItems: {
|
||||||
supportingItems: ["TestValue1", "TestValue2"],
|
supportingItems: [{ partNum: "TestValue1" }, { partNum: "TestValue2" }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1455,7 +1463,10 @@ describe("Actions", () => {
|
||||||
context.dispatch = dispatch;
|
context.dispatch = dispatch;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
actions.saveSupportingItems(context, ["TestValue1", "TestValue2"]);
|
actions.saveSupportingItems(context, [
|
||||||
|
{ partNum: "TestValue1" },
|
||||||
|
{ partNum: "TestValue2" },
|
||||||
|
]);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(dispatch).not.toBeCalledWith(
|
expect(dispatch).not.toBeCalledWith(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue