Updates with 631 changes

Improves the handling of cash to insurance flows, including adding new test cases and adjusting logic for policy verification and payment methods.
Also, updates UI elements to ensure correct display of deductible information and handles split windshield scenarios.
The `safelite-playwright-core` dependency is updated to version 1.0.22.
This commit is contained in:
maguire-arman 2025-06-23 14:17:34 -04:00
commit 8da9b21539
25 changed files with 783 additions and 208 deletions

View file

@ -23,7 +23,7 @@
"luxon": "^3.6.1",
"ortoni-report": "^3.0.2",
"playwright-jira-reporter": "^1.0.4",
"safelite-playwright-core": "^1.0.21",
"safelite-playwright-core": "^1.0.22",
"typescript": "^5.8.3"
}
},
@ -3278,9 +3278,9 @@
"peer": true
},
"node_modules/safelite-playwright-core": {
"version": "1.0.21",
"resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/safelite-playwright-core/-/safelite-playwright-core-1.0.21.tgz",
"integrity": "sha1-qvCxkMa7MWLpUNHHNklAD3osv8U=",
"version": "1.0.22",
"resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/safelite-playwright-core/-/safelite-playwright-core-1.0.22.tgz",
"integrity": "sha1-KIwwcw2+Uy3s9xV/9zhCyWfH/7Y=",
"dev": true,
"license": "ISC",
"dependencies": {

View file

@ -31,7 +31,7 @@
"luxon": "^3.6.1",
"ortoni-report": "^3.0.2",
"playwright-jira-reporter": "^1.0.4",
"safelite-playwright-core": "^1.0.21",
"safelite-playwright-core": "^1.0.22",
"typescript": "^5.8.3"
},
"private": "true"

View file

@ -9,7 +9,8 @@ export class CoverageStatementPage extends InsuranceBasePage {
readonly scheduleOnlineButton: Locator;
readonly cancelMyClaimButton: Locator;
readonly deductibleAmount: Locator;
readonly verfiyingCoverageText: Locator;
readonly verifyingCoverageText: Locator;
readonly noCompText: Locator;
readonly continueButton: Locator; // For ITAC/NoComp
url = process.env['BASE_URL']! + '/FixMyGlass/CoverageStatement.aspx';
@ -19,7 +20,8 @@ export class CoverageStatementPage extends InsuranceBasePage {
this.scheduleOnlineButton = this.page.getByText('Continue to schedule online');
this.cancelMyClaimButton = this.page.getByText('Cancel my claim');
this.deductibleAmount = this.page.getByRole('heading', { name: '$' }).locator('span');
this.verfiyingCoverageText = this.page.getByRole('heading', { name: 'Were verifying your coverage' });
this.verifyingCoverageText = this.page.getByRole('heading', { name: 'We\'re verifying your coverage' });
this.noCompText = this.page.getByRole('heading', { name: 'Your policy doesn\'t cover this service' });
this.continueButton = page.getByRole('button', { name: 'Continue' });
// this.validateURL(this.url);
}
@ -46,16 +48,20 @@ export class CoverageStatementPage extends InsuranceBasePage {
"span.deductible-text-black[data-bind='text: deductibleFormatted']"
);
const unverifiedDeductibleElement = this.verifyingCoverageText;
// Check if the locator is visible before running the expectation
if (await deductibleElement.isVisible()) {
// Check if the page contains the properly formatted deductible amount
await expect(deductibleElement).toContainText(`$${expectedDeductibleRegex}`);
}
}
async validateUnverifiedText(){
await expect(this.verfiyingCoverageText).toBeEnabled();
if (await unverifiedDeductibleElement.isVisible() || await this.noCompText.isVisible()) {
// Click on continue button if unverified header is visible
await this.continueButton.click();
}
}
@step("CoverageStatementPage >> Next page: ")
async handleCoverageStatementPage(testData: Partial<ITestData>) {

View file

@ -35,7 +35,7 @@ export class OrderConfirmationPage extends BasePage {
this.apptDateText = this.page.locator('[class="scheduleText"]');
this.amountDueText = this.page.getByLabel('expand cart');
this.viewCartButton = this.page.locator('#cart-dropdown-head');
this.deductibleText = this.page.locator('#deductible-value');
this.deductibleText = this.page.locator('.deductible');
this.subtotalText = this.page.locator('.sub-total');
this.finalAmountDue = this.page.locator('div.amount-due');
this.cartServicePackageText = this.cartServicePackageText = this.page.locator('.cart-panel');
@ -45,8 +45,8 @@ export class OrderConfirmationPage extends BasePage {
async validateOrderConfirmationPage(testData: Partial<ITestData>) {
// Destructure data we use
const { vehicleDetails, customerDetails, servicePackage,
paymentDetails, paymentMethod } = testData;
const { vehicleDetails, customerDetails, servicePackage, promoCode, isCashInsuranceFlow,
isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified } = testData;
await this.serviceText.waitFor({ state: "visible" });
expect.soft((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase()));
@ -63,7 +63,7 @@ export class OrderConfirmationPage extends BasePage {
const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', ''));
// General Validations
expect.soft(emailTextValue).toContain(customerDetails!.email);
expect.soft(emailTextValue?.toLowerCase()).toContain(customerDetails!.email);
// Service package validations
await expect.soft(this.cartServicePackageText).toContainText(`${servicePackage}`)
@ -89,43 +89,25 @@ export class OrderConfirmationPage extends BasePage {
const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', ''));
expect.soft(subtotalAmt).toBeGreaterThan(0);
// expect.soft(deductibleAmt).toEqual(0);
if (paymentDetails!.paymentType === PaymentType.PayAtService && (servicePackageAmt > 0)) {
if ((paymentDetails!.paymentType === PaymentType.PayAtService || isCashInsuranceFlow) && (servicePackageAmt > 0)) {
// Verify amount due > 0
expect.soft(amountDueAmt).toBeGreaterThan(0);
expect.soft(finalAmountDueAmt).toBeGreaterThan(0);
if (isPolicyUnverified && PaymentType.PayWithInsurance){
expect.soft(finalAmountDueAmt).toContain('Verifying coverage')
}
} else {
// Verify amount due 0
expect.soft(amountDueAmt).toEqual(0);
expect.soft(finalAmountDueAmt).toEqual(0);
}
} else {
// Price validations for insurance users
if (servicePackage === ServicePackage.GlassOnly) {
expect.soft(servicePackageAmt).toEqual(0);
} else {
expect.soft(servicePackageAmt).toBeGreaterThan(0);
}
// Check for either "Verifying coverage" or "0.00" in price fields
expect.soft(
amountDueValue?.includes('Verifying coverage') ||
amountDueValue?.includes('0.00')
).toBeTruthy();
expect.soft(
subtotalTextValue?.includes('Verifying coverage') ||
subtotalTextValue?.includes('0.00')
).toBeTruthy();
expect.soft(
finalAmountDueValue?.includes('Verifying coverage') ||
finalAmountDueValue?.includes('0.00')
).toBeTruthy();
}
}
}
async getFormattedAppointmentDate(appointmentDate: string) {

View file

@ -358,10 +358,12 @@ l
}
let isCaliforniaState = localStorage.order.serviceLocation.state as string == 'CA' ? true : false;
let hasRecalPart: boolean = false;
let canSafeliteRecalibrate: boolean = false;
if (!isRepair) {
hasRecalPart = localStorage.order.lineItems.glassParts.find((item: any) => item.requiresRecalibration === true) ? true : false;
canSafeliteRecalibrate = localStorage.order.lineItems.glassParts.find((item: any) => item.canSafeliteRecalibrate === true) ? true : false;
}
let recalRequired = !isRepair && (isInsurance || isCaliforniaState) && hasRecalPart
let recalRequired = !isRepair && (isInsurance || isCaliforniaState) && hasRecalPart && canSafeliteRecalibrate
let stringForRepair: string[] = ["Expert windshield repair", "Exclusive resin sealant", "Nationwide lifetime guarantee"];
let stringIfRecal = isRepair
? ""

View file

@ -157,10 +157,20 @@ export class ServicePackagesPage extends BasePage {
// Validate the presence of specific parts in the glassParts array
const glassParts = vuexState.order?.lineItems?.glassParts;
const WINDSHIELD_TYPES = [
  "SINGLE WINDSHIELD",
  "DRIVER SPLIT WINDSHIELD",
  "PASSENGER SPLIT WINDSHIELD"
];
if (glassParts?.length > 0) {
for (const partType of vehicleDamage) {
const hasPartType = glassParts.some(glassPart => glassPart.partType === partType);
// Normalize part type to "WINDSHIELD" if it matches any of the defined types (Split Windshield types)
  const normalizedPartType = WINDSHIELD_TYPES.includes(partType) ? "WINDSHIELD" : partType;
const hasPartType = glassParts.some(glassPart => glassPart.partType === normalizedPartType);
await expect(hasPartType, `Expected part type: ${partType}`).toBe(true);
}
} else {

View file

@ -27,9 +27,13 @@ export class VehicleDamagePage extends BasePage {
readonly rearStationaryBttn: Locator;
readonly rearSlidingGlassBttn: Locator;
readonly editVehicleLink: Locator;
readonly singleSplitWindshieldChkBox: Locator;
readonly driverSplitWindshieldChkBox: Locator;
readonly passengerSplitWindshieldChkBox: Locator;
readonly noReplacementAvailableAlert: Locator;
readonly bothReplaceRepairAlert: Locator;
readonly splitWindshieldAlert: Locator;
url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle-damage';
constructor(page: Page) {
@ -51,8 +55,12 @@ export class VehicleDamagePage extends BasePage {
this.passengerVentGlassChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Vent glass"]');
this.passengerBackDoorChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Back door"]');
this.rearWindowChkBox = this.page.locator('[buttonlabel="Rear window"]');
this.singleSplitWindshieldChkBox = this.page.locator('[aria-labelledby="WindshieldReplaceOptions"]').locator('[buttonlabel="Single windshield"]');
this.driverSplitWindshieldChkBox = this.page.locator('[aria-labelledby="WindshieldReplaceOptions"]').locator('[buttonlabel="Split windshield, driver side"]');
this.passengerSplitWindshieldChkBox = this.page.locator('[aria-labelledby="WindshieldReplaceOptions"]').locator('[buttonlabel="Split windshield, passenger side"]');
this.noReplacementAvailableAlert = this.page.locator('.alert-danger.widget-name-NoReplacementAvailableError');
this.bothReplaceRepairAlert = this.page.locator('div.alert-danger.widget-name-HasReplacementConflict');
this.splitWindshieldAlert = this.page.locator('.alert-danger.widget-name-SplitSingleConflict');
this.rearStationaryBttn = this.page.locator('label').filter({ hasText: 'Stationary' });
this.rearSlidingGlassBttn = this.page.locator('label').filter({ hasText: 'Glass with slider' });
this.editVehicleLink = this.page.getByRole('link', { name: 'Edit vehicle' });
@ -77,6 +85,21 @@ export class VehicleDamagePage extends BasePage {
await this.windshieldChkBox.check();
await this.selectCrack();
break;
case VehicleDamage.SingleSplitWindshield:
await this.windshieldChkBox.check();
await this.selectCrack();
await this.singleSplitWindshieldChkBox.check();
break;
case VehicleDamage.DriverSplitWindshield:
await this.windshieldChkBox.check();
await this.selectCrack();
await this.driverSplitWindshieldChkBox.check();
break;
case VehicleDamage.PassengerSplitWindshield:
await this.windshieldChkBox.check();
await this.selectCrack();
await this.passengerSplitWindshieldChkBox.check();
break;
case VehicleDamage.DriverFrontDoor:
await this.sideDoorButton.check();
await this.driverSideButton.check();
@ -165,10 +188,17 @@ export class VehicleDamagePage extends BasePage {
expect(alertMessage).toContain("Service not availableWe're sorry, but we currently offer only repair service for your vehicle type. Need help with next steps? Call us at800-394-0288.")
}
async checkForSplitWindshieldAlertMessage(): Promise<void> {
await expect(this.splitWindshieldAlert).toBeVisible();
const alertMessage = await this.splitWindshieldAlert.textContent();
console.log(`Alert encountered: ${alertMessage}`);
expect(alertMessage).toContain("Single-piece or split?Please select just one windshield option: single-piece or split.")
}
@step('VehicleDamagePage >> Select Damage')
async handleVehicleDamagePage(testData: Partial<ITestData>): Promise<void> {
const {vehicleDamage} = testData;
const {isRepairReplace, isRepairOnly} = testData.alertFlags || {};
const {isRepairReplace, isRepairOnly, isSplitWindshield} = testData.alertFlags || {};
await this.validateProgressBar(ProgressBarPercentages.VehicleDamagePage);
await this.selectDamage(vehicleDamage!);
@ -181,6 +211,10 @@ export class VehicleDamagePage extends BasePage {
await this.checkForRepairOnlyAlertMessage();
throw new TestSuccessAlert('Both assertions are met successfully.');
}
if (isSplitWindshield) {
await this.checkForSplitWindshieldAlertMessage();
throw new TestSuccessAlert('Both assertions are met successfully.');
}
await this.nextPage();
}
}

View file

@ -12,6 +12,7 @@ export class VehicleSelectionPage extends BasePage {
readonly makeDropdown: Locator;
readonly modelDropdown: Locator;
readonly styleDropdown: Locator;
readonly zipCodeTextBox: Locator;
readonly discontinuedServiceAlert: Locator;
url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle';
@ -23,6 +24,7 @@ export class VehicleSelectionPage extends BasePage {
this.makeDropdown = this.page.locator('#makeQuestionField');
this.modelDropdown = this.page.locator('#modelQuestionField');
this.styleDropdown = this.page.locator('#styleQuestionField');
this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' });
this.discontinuedServiceAlert = this.page.locator('.alert-danger.widget-name-AlertNoServiceWidget');
// this.validateURL(this.url);
}
@ -49,13 +51,18 @@ export class VehicleSelectionPage extends BasePage {
@step("VehicleSelectionPage >> Select Vehicle: ")
async handleVehicleSelectionPage(testData: Partial<ITestData>) {
await this.validateProgressBar(ProgressBarPercentages.VehicleSelectionPage);
const { vehicleDetails } = testData;
const { isHeavyTruckVehicle, isSplitWindshield } = testData.alertFlags || {};
const { vehicleDetails, isHeavyTruck, customerDetails } = testData;
const { isHeavyTruckVehicleAlert, isSplitWindshield } = testData.alertFlags || {};
await this.selectVehicle(vehicleDetails!);
if (isHeavyTruck) {
await this.zipCodeTextBox.fill(customerDetails?.address.postalCode!);
}
// Handle alert conditions for vehicle selection
if (isHeavyTruckVehicle || isSplitWindshield) {
if (isHeavyTruckVehicleAlert) {
await this.continueButton.click();
await this.checkForAlertMessages();
throw new TestSuccessAlert('Both assertions are met successfully.');
}

View file

@ -27,12 +27,17 @@ import insuranceAcuityPaypalTests from "./InsuranceAcuityPaypal";
import insuranceITAC21stCenturyTests from "./InsuranceITAC21stCentury";
import insuranceGeicoTests from "./InsuranceGeico";
import insuranceITACOptimizedPriceValidationAllStateTests from "./InsuranceITACOptimizedPriceValidationAllState";
import insuranceNoCompProgressiveTests from "./InsuranceNoCompProgressive";
import cashRepairInShopAfterPayTests from "./CashRepairInShopAfterPay";
import cashRepairInShopPayPalTests from "./CashRepairInShopPayPal";
import cashReplaceMultiSlidingGlassDropoffTests from "./CashReplaceMultiSlidingGlassDropoff";
import cashReplaceMultiGlassMobileTests from "./CashReplaceMultiGlassMobile";
import { getTestObject, TestCase, prepareTest, RuleEngine, TestInfo } from 'framework/Typedefs';
import { createTestPages } from "framework/TestPages";
import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp";
import insuranceBigTruckVerifiedTests from "./InsuranceBigTruckVerified";
import insuranceUnverifiedTests from "./InsuranceUnverified";
import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield";
const test = getTestObject();
@ -69,8 +74,14 @@ const allStandardTests = [
{name: "CashReplaceVinMobile", tests: cashReplaceVinMobileTests},
{name: "CashReplaceWiperDropoff", tests: cashReplaceWiperDropoffTests},
{name: "CashReplaceWiperPromoInshop", tests: cashReplaceWiperPromoInShopTests},
{name: "CashReplaceSwitchToInsuranceProgressiveNoComp", tests: cashReplaceSwitchToInsuranceProgressiveNoCompTests},
// TODO: Uncomment when QA is ready to run heavy truck tests
// {name: "CashReplaceSplitWindshield", tests: CashReplaceSplitWindshieldTests},
{name: "InsuranceAcuityPaypal", tests: insuranceAcuityPaypalTests},
{name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests},
{name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests},
// {name: "InsuranceBigTruckVerified", tests: insuranceBigTruckVerifiedTests},
{name: "InsuranceUnverified", tests: insuranceUnverifiedTests},
// {name: "InsuranceGeico", tests: insuranceGeicoTests},
// {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests}
@ -78,9 +89,10 @@ const allStandardTests = [
// Alert validation scenarios
const allAlertTests = [
{ name: "Alert Scenario 1: Heavy Truck", tests: heavyTruckTests },
//TODO: Uncomment when QA is ready to run heavy truck tests
// { name: "Alert Scenario 1: Heavy Truck", tests: heavyTruckTests },
{ name: "Alert Scenario 2: Repair and Replace", tests: repairAndReplaceTests },
{ name: "Alert Scenario 3: Split Windshield", tests: splitWindshieldTests },
// { name: "Alert Scenario 3: Split Windshield", tests: splitWindshieldTests },
{ name: "Alert Scenario 4: Repair Only", tests: repairOnlyTests },
{ name: "Alert Scenario 5: Unserviceable Zip", tests: unserviceableZipTests },
{ name: "Alert Scenario 6: Invalid Zip", tests: invalidZipTests },
@ -166,10 +178,14 @@ async function runWorkflow(page: Page, testCase: TestCase) {
} = testCase.testData;
// Check if the vehicle damage includes a windshield crack
const hasWindshieldCrack = vehicleDamage!.some(damage =>
damage === VehicleDamage.WindshieldCrack
const hasWindshieldCrack = vehicleDamage!.some(damage =>
damage === VehicleDamage.WindshieldCrack ||
damage === VehicleDamage.DriverSplitWindshield ||
damage === VehicleDamage.PassengerSplitWindshield ||
damage === VehicleDamage.SingleSplitWindshield
);
//============================= TEST WORKFLOW STEPS =============================
// Use Environment Variable to decide whether or not we want to skip content site aka home page
@ -273,6 +289,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
let serviceLocationPage = testCase.pages.serviceLocationPage;
await serviceLocationPage.handleServiceLocationPage(testCase.testData);
// Schedule appointment
let schedulePage = testCase.pages.schedulePage;
await schedulePage.handleSchedulePage(testCase.testData);
@ -300,7 +317,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
}
export async function handleInsuranceFlow(testCase: TestCase) {
const { flow, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData;
const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, flow } = testCase.testData;
// Check if the insurance policy has endorsements
const hasEndorsements = endorsements && endorsements.length > 0;
@ -344,7 +361,7 @@ export async function handleInsuranceFlow(testCase: TestCase) {
let policyInfoSubmittedPage = testCase.pages.policyInfoSubmittedPage;
await policyInfoSubmittedPage.handlePolicyInfoSubmittedPage();
if(isRecalNotification)
if(isRecalVehicle)
{
let recalibrationInfoPage = testCase.pages.recalibrationInfoPage;
await recalibrationInfoPage.handleRecalibrationInfoPage();
@ -353,4 +370,22 @@ export async function handleInsuranceFlow(testCase: TestCase) {
//handle coveraage statement page
let coverageStatementPage = testCase.pages.coverageStatementPage;
await coverageStatementPage.handleCoverageStatementPage(testCase.testData);
// Handle scenario where user selected Cash to Insurance and needs to go back through the flow
// right now we are choosing pay at appointment as payment method for this scenario
if (isCashInsuranceFlow) {
let serviceLocationPage = testCase.pages.serviceLocationPage;
await serviceLocationPage.nextPage();
//schedule
let schedulePage = testCase.pages.schedulePage;
await schedulePage.nextPage();
//customer dertails
let contactDetailsPage = testCase.pages.contactDetailsPage;
await contactDetailsPage.nextPage();
//paymentmethods
let paymentMethodPage = testCase.pages.paymentMethodPage;
await paymentMethodPage.nextPage();
}
}

View file

@ -0,0 +1,74 @@
//Imports here
import { ITestData } from 'framework/TestData'
import { PartQuestionType, PaymentType, VehicleDamage } from 'safelite-playwright-core';
import { ITestCase } from '../framework/Typedefs'
import { VehicleLookupType } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Set the seed before generating any data
setFakerSeedFromTestName("CashReplaceSplitWindshield.ts");
// Now get the test data with the seeded faker
const CashReplaceSplitWindshieldData: Partial<ITestData> = {
...getDefaultTestData(), // Get default data with current seed
isHeavyTruck: true, // Flag for big truck
customerDetails: {
...getDefaultTestData().customerDetails!,
address: {
...getDefaultTestData().customerDetails!.address,
postalCode: '55414'
}
},
// Override vehicle details
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2006',
make: 'Navistar',
model: '5000 I',
style: '2 door conventional cab',
vehicleLookupType: VehicleLookupType.Zip
},
vehiclePartQuestions: [
{
partQuestionType: PartQuestionType.DriverWindshield,
isOnPage: true,
optionToSelect: 'Green Tint',
secondaryQuestionOptionToSelect: 'driver side, encap, asymmetrically strengthen'
},
{
partQuestionType: PartQuestionType.PassengerWindshield,
isOnPage: true,
optionToSelect: 'Green Tint',
secondaryQuestionOptionToSelect: 'passenger side, encap, asymmetrically strengthen'
},
],
// Override default vehicle damage (Split Windshield)
vehicleDamage: [VehicleDamage.DriverSplitWindshield, VehicleDamage.PassengerSplitWindshield],
// Override appointment details
appointmentDetails: {
...getDefaultTestData().appointmentDetails!,
shopAddress: "504 Malcolm Ave Se, Minneapolis, MN 55414"
},
// Override payment details
paymentDetails: {
paymentType: PaymentType.PayAtService
}
}
const CashReplaceSplitWindshieldTests: ITestCase[] = [];
const tc = {
name: `CashReplaceSplitWindshield`,
tags: ['@E2E','@CashReplaceSplitWindshield', '@test_report', '@CASH'],
testData: CashReplaceSplitWindshieldData
};
CashReplaceSplitWindshieldTests.push(tc);
export default CashReplaceSplitWindshieldTests;

View file

@ -0,0 +1,81 @@
//Imports here
import { ITestData } from 'framework/TestData'
import { ServiceLocation } from "safelite-playwright-core";
import { DamageType, PartQuestionType, PaymentType } from 'safelite-playwright-core'
import { ITestCase } from '../framework/Typedefs'
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Set the seed for consistent data generation
setFakerSeedFromTestName("CashReplaceSwitchToInsuranceProgressiveNoComp");
// Define insurance test data for Progressive
const cashReplaceSwitchToInsuranceProgressiveNoCompData: Partial<ITestData> = {
...getDefaultTestData(),
isCashInsuranceFlow: true,
// Override customer details based on provided ZIP code
customerDetails: {
...getDefaultTestData().customerDetails!,
address: {
...getDefaultTestData().customerDetails!.address,
postalCode: "70458",
},
},
// Vehicle details for a 2012 Chrysler 300
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: "2012",
make: "Chrysler",
model: "300",
style: "4 door sedan"
},
partQuestions: [
{
partQuestionType: PartQuestionType.GeneralQuestion1,
isOnPage: true,
optionToSelect: 'Yes'
},
],
// Override payment details
paymentDetails: {
paymentType: PaymentType.PayWithInsurance, // Ensures insurance payment method is selected
},
// Insurance claim details
claimDetails: {
client: "Progressive",
policyNumber: "Mock495646B",
policyDeductible: 0,
damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" }),
damageCause: DamageType.Rock,
},
isPolicyFound: true,
isUseVehicleOnPolicy: true,
isPolicyDriver: true,
// Appointment details for in-shop service
appointmentDetails: {
serviceLocation: ServiceLocation.InShop,
shopAddress: "8985 Yellow Brick Rd, Rosedale, MD 21237",
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
},
};
// Create and register the test case
const cashReplaceSwitchToInsuranceProgressiveNoCompTests: ITestCase[] = [];
const tc = {
name: `CashReplaceSwitchToInsuranceProgressiveNoComp`,
tags: ["@E2E", "@CashReplaceSwitchToInsuranceProgressiveNoComp", "@test_report", "@CASH"],
testData: cashReplaceSwitchToInsuranceProgressiveNoCompData,
};
cashReplaceSwitchToInsuranceProgressiveNoCompTests.push(tc);
export default cashReplaceSwitchToInsuranceProgressiveNoCompTests;

View file

@ -0,0 +1,82 @@
//Imports here
import { ITestData } from 'framework/TestData'
import { ServiceLocation, DamageType, PaymentType, PartQuestionType } from 'safelite-playwright-core';
import { ITestCase } from '../framework/Typedefs'
import { PaymentMethod } from "framework/localTypes/Enums";
import { VehicleLookupType } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Set the seed based on test name for consistent but unique data
setFakerSeedFromTestName("InsuranceBigTruckVerified");
// Now get the test data with the seeded faker
const insuranceBigTruckVerifiedData: Partial<ITestData> = {
...getDefaultTestData(), // Get default data with current seed
// Key feature: Insurance flow with GEICO
paymentMethod: PaymentMethod.Insurance,
// Insurance claim flags
isDuplicateClaim: true,
isPolicyFound: true,
isUseVehicleOnPolicy: true,
isHeavyTruck: true,
// Override customer details with specific name and California location
customerDetails: {
...getDefaultTestData().customerDetails!,
firstName: 'Big',
lastName: 'Truck',
address: {
...getDefaultTestData().customerDetails!.address,
city: 'Ontario',
state: 'California',
postalCode: '43085'
}
},
// Insurance claim details
claimDetails: {
client: 'USAA',
policyNumber: 'Mock900040BigTruck',
policyDeductible: 2000.00,
policyZip: '55414',
damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}),
damageCause: DamageType.Rock
},
// Hyundai vehicle details with VIN lookup
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2025',
make: 'Peterbilt',
model: '579',
style: 'conventional cab',
vin: '1XPBDP9X6SD693446',
vehicleLookupType: VehicleLookupType.Vin,
},
// No need to override vehicleDamage as it already defaults to WindshieldCrack
// Override for in-shop appointment
appointmentDetails: {
serviceLocation: ServiceLocation.InShop,
shopAddress: '5719 Brandt Pike, Dayton, OH 45424',
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
},
// Override payment details (empty because we skip payment method page in insurance flow)
paymentDetails: {}
}
const insuranceBigTruckVerifiedTests: ITestCase[] = [];
const tc = {
name: `InsuranceBigTruckVerified`,
tags: ['@E2E','@InsuranceBigTruckVerified', '@test_report', '@Insurance'],
testData: insuranceBigTruckVerifiedData
};
insuranceBigTruckVerifiedTests.push(tc);
export default insuranceBigTruckVerifiedTests;

View file

@ -20,7 +20,7 @@ const insuranceITAC21stCenturyData: Partial<ITestData> = {
isDuplicateClaim: true,
flow: Flow.Managed,
isUseVehicleOnPolicy: true,
isRecalNotification: true, // Special flag for recalibration notification
isRecalVehicle: true, // Special flag for recalibration notification
// Override customer details for California location
customerDetails: {

View file

@ -0,0 +1,90 @@
//Imports here
import { ITestData } from 'framework/TestData'
import { ServiceLocation, DamageType, PaymentType, PartQuestionType } from 'safelite-playwright-core';
import { ITestCase } from '../framework/Typedefs'
import { PaymentMethod } from "framework/localTypes/Enums";
import { VehicleLookupType } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Set the seed based on test name for consistent but unique data
setFakerSeedFromTestName("InsuranceNoCompProgressive");
// Now get the test data with the seeded faker
const insuranceNoCompProgressiveData: Partial<ITestData> = {
...getDefaultTestData(), // Get default data with current seed
// Key feature: Insurance flow with GEICO
paymentMethod: PaymentMethod.Insurance,
// Insurance claim flags
isDuplicateClaim: true,
isPolicyFound: true,
isUseVehicleOnPolicy: true,
isHeavyTruck: false,
isPolicyDriver: true,
isRecalVehicle: true,
// Override customer details with specific name and California location
customerDetails: {
...getDefaultTestData().customerDetails!,
firstName: 'SHERI',
lastName: 'SCHATZ',
address: {
...getDefaultTestData().customerDetails!.address,
city: 'Slidell',
state: 'Louisiana',
postalCode: '70458'
}
},
// Insurance claim details
claimDetails: {
client: 'Progressive',
policyNumber: 'Mock495646B',
policyDeductible: 1722.20,
policyZip: '70458',
damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}),
damageCause: DamageType.Rock
},
// Hyundai vehicle details with VIN lookup
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2015',
make: 'Acura',
model: 'MDX',
style: '4 door utility',
vehicleLookupType: VehicleLookupType.Zip,
},
// Part questions related to recalibration
partQuestions: [
{
partQuestionType: PartQuestionType.GeneralQuestion1,
isOnPage: true,
optionToSelect: 'Yes'
},
],
// Override for in-shop appointment
appointmentDetails: {
serviceLocation: ServiceLocation.InShop,
shopAddress: '56705 Garrett Road, Slidell, LA 70458',
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
},
// Override payment details (empty because we skip payment method page in insurance flow)
paymentDetails: {}
}
const insuranceNoCompProgressiveTests: ITestCase[] = [];
const tc = {
name: `InsuranceNoCompProgressive`,
tags: ['@E2E','@InsuranceNoCompProgressive', '@test_report', '@Insurance'],
testData: insuranceNoCompProgressiveData
};
insuranceNoCompProgressiveTests.push(tc);
export default insuranceNoCompProgressiveTests;

View file

@ -0,0 +1,87 @@
//Imports here
import { ITestData } from 'framework/TestData'
import { ServiceLocation, DamageType, PaymentType, PartQuestionType } from 'safelite-playwright-core';
import { ITestCase } from '../framework/Typedefs'
import { PaymentMethod } from "framework/localTypes/Enums";
import { VehicleLookupType } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Set the seed based on test name for consistent but unique data
setFakerSeedFromTestName("InsuranceUnverified");
// Now get the test data with the seeded faker
const insuranceUnverifiedData: Partial<ITestData> = {
...getDefaultTestData(), // Get default data with current seed
// Key feature: Insurance flow with GEICO
paymentMethod: PaymentMethod.Insurance,
// Insurance claim flags
isPolicyFound: false,
isPolicyUnverified: true,
isRecalVehicle: true,
// Override customer details with specific name and California location
customerDetails: {
...getDefaultTestData().customerDetails!,
firstName: 'Jane',
lastName: 'Unverified',
address: {
...getDefaultTestData().customerDetails!.address,
city: 'Richmond',
state: 'Virginia',
postalCode: '23219'
}
},
// Insurance claim details
claimDetails: {
client: '21st Century',
policyNumber: 'UnverifiedMock',
policyDeductible: "Unverified",
policyZip: '43123',
damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}),
damageCause: DamageType.Rock
},
// Hyundai vehicle details with VIN lookup
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2014',
make: 'Honda',
model: 'Accord',
style: '4 door sedan',
vehicleLookupType: VehicleLookupType.Zip,
},
// Part questions related to recalibration
partQuestions: [
{
partQuestionType: PartQuestionType.GeneralQuestion1,
isOnPage: true,
optionToSelect: 'Yes'
},
],
// Override for in-shop appointment
appointmentDetails: {
serviceLocation: ServiceLocation.InShop,
shopAddress: '5719 Brandt Pike, Dayton, OH 45424',
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
},
// Override payment details (empty because we skip payment method page in insurance flow)
paymentDetails: {}
}
const insuranceUnverifiedTests: ITestCase[] = [];
const tc = {
name: `InsuranceUnverified`,
tags: ['@E2E','@InsuranceUnverified', '@test_report', '@Insurance'],
testData: insuranceUnverifiedData
};
insuranceUnverifiedTests.push(tc);
export default insuranceUnverifiedTests;

View file

@ -4,11 +4,23 @@ import { VehicleDamage, ServiceLocation } from 'safelite-playwright-core';
import { ITestCase } from '../../framework/Typedefs';
import { getNextWeekday } from 'safelite-playwright-core';
import { defaultTestData } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Heavy Truck (alert) Test Data
const heavyTruckData: Partial<ITestData> = {
...defaultTestData, // Start with all defaults
isHeavyTruck: true, // Flag for heavy truck vehicle
customerDetails: {
...getDefaultTestData().customerDetails!,
address: {
...getDefaultTestData().customerDetails!.address,
postalCode: '21237'
}
},
// Override specific fields with test-specific data
vehicleDetails: {
...defaultTestData.vehicleDetails!,
@ -18,7 +30,7 @@ const heavyTruckData: Partial<ITestData> = {
style: 'conventional cab'
},
alertFlags: {
isHeavyTruckVehicle: true
isHeavyTruckVehicleAlert: true
},
vehicleDamage: [
VehicleDamage.WindshieldOneChip,

View file

@ -1,8 +1,9 @@
// Imports here
import { ITestData } from 'framework/TestData';
import { ITestCase } from '../../framework/Typedefs'
import { ITestCase, TestCase } from '../../framework/Typedefs'
import { VehicleDamage } from 'safelite-playwright-core';
import { defaultTestData } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
// Windshield selected, when vehicle has split windshield (alert) Test Data
const splitWindshieldData: Partial<ITestData> = {
@ -10,11 +11,21 @@ const splitWindshieldData: Partial<ITestData> = {
// Override specific fields with test-specific data
vehicleDamage: [
VehicleDamage.WindshieldOneChip
VehicleDamage.SingleSplitWindshield,
VehicleDamage.DriverSplitWindshield,
VehicleDamage.PassengerSplitWindshield
],
alertFlags: {
isSplitWindshield: true
}
},
customerDetails: {
...getDefaultTestData().customerDetails!,
address: {
...getDefaultTestData().customerDetails!.address,
postalCode: '55414'
}
},
isHeavyTruck: true, // Flag for heavy truck vehicle
};
const vehiclesToTest = [
@ -35,20 +46,21 @@ const vehiclesToTest = [
make: 'Kenworth',
model: 'T600',
style: 'conventional cab'
}
},
];
const splitWindshieldTests: ITestCase[] = [];
// Generate test cases for each vehicle
vehiclesToTest.forEach((vehicle, index) => {
const testData = {
...splitWindshieldData,
vehicleDetails: {
...defaultTestData.vehicleDetails!,
...vehicle
}
};
    const testData = {
        ...splitWindshieldData,
        vehicleDetails: {
            ...defaultTestData.vehicleDetails!,
            ...vehicle
        },
    }
const tc = {
name: `alert0003_${vehicle.make.toLowerCase()}_${vehicle.model.toLowerCase()} Windshield selected, when vehicle has split windshield - ${vehicle.make} ${vehicle.model} ${vehicle.year}`,

View file

@ -94,8 +94,16 @@ export default {
FirstName: transfer.data.first_name,
LastName: transfer.data.last_name,
Email: transfer.data.email,
Subject: transfer.data.chat_summary, // for now passing in chat_summery in Subject. But this will be changed in future.
Subject: transfer.data.chat_summary,
};
window.embedded_svc.settings.extraPrechatFormDetails = [
{
label: "Scarlett AI Summary",
value: transfer.data.chat_summary,
transcriptFields: ["Scarlett_AI_Summary__c"],
displayToAgent: true,
},
];
if (
window.embedded_svc.liveAgentAPI &&
typeof window.embedded_svc.liveAgentAPI.startChat === "function"

View file

@ -117,6 +117,10 @@
<span>{{ amountPaidText }}</span>
<span>{{ getLineItemAmount(amountPaid) }}</span>
</div>
<div v-if="donationCartItem" class="donation-amount">
<span>{{ donationCartItemName }}</span>
<span>{{ getLineItemAmount(donationCartItem.subTotal) }}</span>
</div>
<div class="amount-due">
<span>{{ amountDueText }}</span>
<span>{{ getLineItemAmount(amountDue, showCoverageAsPending) }}</span>
@ -472,10 +476,6 @@ export default {
});
}
if (this.donationCartItem) {
cartItems.push(this.donationCartItem);
}
return cartItems;
},
},
@ -1309,12 +1309,15 @@ export default {
.sub-total,
.sales-tax,
.amount-due,
.amount-paid {
font-weight: 500;
.amount-paid,
.donation-amount {
font-family:
UrbanistSemibold, AvertaSemibold; //Okay to remove AvertaSemibold after 2025.06.19 merge/release
color: $black;
}
.sub-total {
border-top: 1px solid $green;
background-color: $green-100;
}
.service-type,
.deductible {

View file

@ -62,7 +62,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import baseMixin from "@/mixins/base-mixin.js";
import { queryStrings } from "@/constants/query-strings";
import { nextTick } from "vue";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash";
// Define Validation Rules
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -100,7 +100,7 @@ export default {
}
const zip =
consumeQueryFromStash(queryStrings.ZIP_CODE) ??
peekQueryFromStash(queryStrings.ZIP_CODE) ??
store.getters.order.serviceLocation.zipCode;
var vinByAddressPromise;

View file

@ -1,4 +1,5 @@
import { reactive } from "vue";
import { debugLog } from "@/helpers/debug-log-helper";
const queryStash = reactive({
queries: [],
@ -27,10 +28,18 @@ export function stashAllQueries(toRoute) {
}
function getStashedQuery(key) {
debugLog(`Fetching querystring with key:`, key);
const match = queryStash.queries.find(
(entry) => entry?.key?.toLowerCase() === key?.toLowerCase()
);
if (match) {
debugLog(`Found result:`, match.value);
debugLog(`Already consumed:`, match.used);
} else {
debugLog(`Found no result`);
}
return match;
}

View file

@ -0,0 +1,14 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { paymentMethods } from "@/constants/payment-method-constants";
import { debugLog } from "@/helpers/debug-log-helper";
export async function paymentBeforeEnter(to, from) {
const piaType = store.getters.order?.payment?.piaType;
debugLog(`Entering payment with type =`, piaType);
if (piaType === paymentMethods.PAYPAL) {
debugLog(`Changing to payment type =`, paymentMethods.CREDIT_CARD);
await store.dispatch(storeActions.SAVE_PAYMENT_METHOD_CHOICE, paymentMethods.CREDIT_CARD);
}
}

View file

@ -11,6 +11,7 @@ import { autoRouteBeforeEnter } from "@/router/methods/route-logic/auto-route";
import { errorBeforeEnter } from "@/router/methods/route-logic/error";
import { restartBeforeEnter } from "@/router/methods/route-logic/restart";
import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method";
import { paymentBeforeEnter } from "@/router/methods/route-logic/payment";
export const routes = [
// Non-virtual pages.
@ -32,7 +33,7 @@ export const routes = [
createRoute(routeData.SCHEDULE),
createRoute(routeData.CUSTOMER_DETAILS),
createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter),
createRoute(routeData.PAYMENT),
createRoute(routeData.PAYMENT, paymentBeforeEnter),
createRoute(routeData.PAYMENT_PIA_RETURN),
createRoute(routeData.CONFIRMATION),
createRoute(routeData.RETURN_USER),

View file

@ -1072,33 +1072,6 @@ export const getters = {
},
};
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
}
function getTimeSlotsAdditionalEventData(
provisionalTriggers,
zipCode,
firstAvailableAppointmentDateString,
shopAppointmentType
) {
var numberOfDays = null;
if (firstAvailableAppointmentDateString)
numberOfDays = getDateDifferenceInDays(
new Date().toISOString().split("T")[0],
firstAvailableAppointmentDateString
);
if (shopAppointmentType)
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
else
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
}
// Export Actions
export const actions = {
// Vehicle API Actions
@ -2059,20 +2032,31 @@ export const actions = {
},
};
return globalMethods.callHttpClient({
let hasCalled = timeSlotCallFlags.shop;
if (!hasCalled) {
timeSlotCallFlags.shop = true;
}
const options = {
method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.url,
payload: payload,
logApiCall: true,
pageNameToLog: pageNameToLog,
additionalSuccessEventDataHandler: (response) =>
};
// Only set handler if this is the very first call in this session
if (!hasCalled) {
options.additionalSuccessEventDataHandler = (response) =>
getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers,
order.serviceLocation.zipCode,
response.data.days?.[0]?.date,
shopAppointmentType
),
});
);
}
return globalMethods.callHttpClient(options);
},
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
@ -2116,19 +2100,30 @@ export const actions = {
zipCode: order.serviceLocation.zipCode,
};
return globalMethods.callHttpClient({
let hasCalled = timeSlotCallFlags.mobile;
if (!hasCalled) {
timeSlotCallFlags.mobile = true;
}
const options = {
method: endpoints.GetMobileTimeSlots.method,
endpoint: endpoints.GetMobileTimeSlots.url,
payload: payload,
logApiCall: true,
pageNameToLog: pageNameToLog,
additionalSuccessEventDataHandler: (response) =>
};
// Only set handler if this is the very first call in this session
if (!hasCalled) {
options.additionalSuccessEventDataHandler = (response) =>
getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers,
order.serviceLocation.zipCode,
response.data.days?.[0]?.date
),
});
);
}
return globalMethods.callHttpClient(options);
},
getMobilePremiumFee(context, { pageNameToLog }) {
@ -3457,100 +3452,6 @@ export default createStore({
actions,
});
// Private Functions
function getHasRecalibrationPart(state) {
return containsRecalParts(state.order.lineItems);
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
if (!arrayOfObjects) return null;
return arrayOfObjects.sort((a, b) => {
if (a[propertyName] < b[propertyName]) return -1;
else if (a[propertyName] > b[propertyName]) return 1;
else return 0;
});
}
function convertGlassPieceNamingForApi(glassArray) {
if (!glassArray || glassArray.length === 0) return [];
// check if array already converted. (likely when a session has been saved previously and then reloaded)
if (glassArray[0].location !== undefined) {
return glassArray;
}
const converted = [];
glassArray.forEach((glass) => {
converted.push({
location: glass.glassLocation,
name: glass.glassName,
});
});
return converted;
}
function convertResultsForApi(resultsArray) {
if (!resultsArray) return [];
const converted = [];
resultsArray.forEach((answer) => {
converted.push({
location: answer.glassLocation,
name: answer.glassName,
result: answer.result,
});
});
return converted;
}
function convertGlassPieceNamingFromApi(glassArray) {
glassArray.forEach((glass) => {
glass.glassLocation = glass.glassPiece.location;
glass.glassName = glass.glassPiece.name;
delete glass.glassPiece;
return glass;
});
return glassArray;
}
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
lineItem.salesTax = pricedLineItem.salesTax;
});
return lineItems;
}
function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) {
pricedLineItems.forEach((pricedLineItem) => {
const lineItemIndex = taxingLineItems.findIndex(
(taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber
);
if (pricedLineItem.childParts) {
addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems);
}
const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0];
pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0;
});
return pricedLineItems;
}
export function mapTaxedLineItemsToStoreFormat(availableLineItems, storeLineItems) {
// clone the lineItems array because what we're passing in is referencing the store directly
const lineItems = deepClone(storeLineItems);
@ -3658,6 +3559,127 @@ export function getArrayOfAllLineItemsAndChildParts(lineItems) {
return consolidatedLineItemsArray;
}
// Private Functions
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
}
function getTimeSlotsAdditionalEventData(
provisionalTriggers,
zipCode,
firstAvailableAppointmentDateString,
shopAppointmentType
) {
var numberOfDays = null;
if (firstAvailableAppointmentDateString)
numberOfDays = getDateDifferenceInDays(
new Date().toISOString().split("T")[0],
firstAvailableAppointmentDateString
);
if (shopAppointmentType)
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
else
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(
","
)}`;
}
function getHasRecalibrationPart(state) {
return containsRecalParts(state.order.lineItems);
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
if (!arrayOfObjects) return null;
return arrayOfObjects.sort((a, b) => {
if (a[propertyName] < b[propertyName]) return -1;
else if (a[propertyName] > b[propertyName]) return 1;
else return 0;
});
}
function convertGlassPieceNamingForApi(glassArray) {
if (!glassArray || glassArray.length === 0) return [];
// check if array already converted. (likely when a session has been saved previously and then reloaded)
if (glassArray[0].location !== undefined) {
return glassArray;
}
const converted = [];
glassArray.forEach((glass) => {
converted.push({
location: glass.glassLocation,
name: glass.glassName,
});
});
return converted;
}
function convertResultsForApi(resultsArray) {
if (!resultsArray) return [];
const converted = [];
resultsArray.forEach((answer) => {
converted.push({
location: answer.glassLocation,
name: answer.glassName,
result: answer.result,
});
});
return converted;
}
function convertGlassPieceNamingFromApi(glassArray) {
glassArray.forEach((glass) => {
glass.glassLocation = glass.glassPiece.location;
glass.glassName = glass.glassPiece.name;
delete glass.glassPiece;
return glass;
});
return glassArray;
}
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
lineItem.salesTax = pricedLineItem.salesTax;
});
return lineItems;
}
function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) {
pricedLineItems.forEach((pricedLineItem) => {
const lineItemIndex = taxingLineItems.findIndex(
(taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber
);
if (pricedLineItem.childParts) {
addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems);
}
const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0];
pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0;
});
return pricedLineItems;
}
function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) {
let flattenedArray = [];
lineItems?.forEach((lineItem) => {
@ -3966,3 +3988,8 @@ function getExternalParameterDefaultState() {
function saveExternalParameterState(externalParameterState) {
window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState));
}
const timeSlotCallFlags = {
shop: false,
mobile: false,
};

View file

@ -121,8 +121,8 @@ $body-color: $gray-600;
//Fonts
$font-family-sans-serif: AvertaRegular, Arial, Helvetica, sans-serif;
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
$font-family-monospace:
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
// stylelint-enable value-keyword-case
$font-family-base: $font-family-sans-serif;
$font-family-code: $font-family-monospace;
@ -170,8 +170,7 @@ $spacers: (
/* 16px */ 5: $spacer * 1.5,
/* 24px */ 6: $spacer * 2,
/* 32px */ 7: $spacer * 2.5,
/* 40px */ 8: $spacer * 3,
/* 48px */
/* 40px */ 8: $spacer * 3 /* 48px */,
);
//Enable negative spacing (does NOT work on padding)