Merge pull request #1754 from Safelite/feature/CSR-1969
Feature/csr 1969
This commit is contained in:
commit
a558d97685
4 changed files with 387 additions and 77 deletions
|
|
@ -135,7 +135,6 @@ export default {
|
|||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.lineItems = lineItemsFromSubmittedOrder;
|
||||
vm.vaps = availableVaps;
|
||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
||||
});
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -220,6 +220,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 = (
|
||||
|
|
|
|||
Loading…
Reference in a new issue