Merge pull request #1754 from Safelite/feature/CSR-1969

Feature/csr 1969
This commit is contained in:
chloeherdsafelite 2024-02-15 12:26:03 -05:00 committed by GitHub
commit a558d97685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 387 additions and 77 deletions

View file

@ -135,7 +135,6 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
vm.lineItems = lineItemsFromSubmittedOrder;
vm.vaps = availableVaps;
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
});
},
data() {

View file

@ -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);
},

View file

@ -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;

View file

@ -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 = (