From a6506db3187c0b86dd2898806f5072d2b547e3f6 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:20:44 -0500 Subject: [PATCH 1/8] Add test to cover OEM scenario --- playwright-tests/framework/TestPages.ts | 3 + .../pages/OrderConfirmationPage.ts | 2 +- playwright-tests/pages/PaymentMethodPage.ts | 17 ++++ .../pages/ProblemGlassQuestionsPage.ts | 43 +++++++++ playwright-tests/tests/0000__M.test.ts | 13 ++- .../tests/InsuranceOEMAllState.ts | 88 +++++++++++++++++++ 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 playwright-tests/pages/ProblemGlassQuestionsPage.ts create mode 100644 playwright-tests/tests/InsuranceOEMAllState.ts diff --git a/playwright-tests/framework/TestPages.ts b/playwright-tests/framework/TestPages.ts index e873a1c7b..b0baf2db1 100644 --- a/playwright-tests/framework/TestPages.ts +++ b/playwright-tests/framework/TestPages.ts @@ -24,6 +24,7 @@ import { CCPolicyInfoPage } from "../pages/CCPolicyInfoPage" import { DuplicateCheckPage } from "../pages/DuplicateCheckPage" import { PolicyVehiclesPage } from "../pages/PolicyVehiclesPage" import { PolicyInfoSubmittedPage } from "../pages/PolicyInfoSubmittedPage" +import { ProblemGlassQuestionsPage } from "../pages/ProblemGlassQuestionsPage" import RecalibrationInfoPage from "../pages/RecalibrationInfoPage" import { CoverageStatementPage } from "../pages/CoverageStatementPage" import { VerifyDetailsPage } from "../pages/VerifyDetailsPage" @@ -48,6 +49,7 @@ export interface ITestPages { paymentMethodPage: PaymentMethodPage, policyInfoSubmittedPage: PolicyInfoSubmittedPage, policyVehiclesPage: PolicyVehiclesPage, + problemGlassQuestionsPage: ProblemGlassQuestionsPage, recalibrationInfoPage: RecalibrationInfoPage, schedulePage: SchedulePage, mobileDetailsPage: MobileDetailsPage, @@ -83,6 +85,7 @@ export const createTestPages: TestPagesFactory = (page: Page) => { paymentMethodPage: new PaymentMethodPage(page), policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page), policyVehiclesPage: new PolicyVehiclesPage(page), + problemGlassQuestionsPage: new ProblemGlassQuestionsPage(page), recalibrationInfoPage: new RecalibrationInfoPage(page), schedulePage: new SchedulePage(page), mobileDetailsPage: new MobileDetailsPage(page), diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 02951bdd2..6bf10926c 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -47,7 +47,7 @@ export class OrderConfirmationPage extends BasePage { async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, isCashInsuranceFlow, - isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified } = testData; + isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified, isOEM } = testData; await this.serviceText.waitFor({ state: "visible" }); Soft.expect((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase())); diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 4ea1a8b82..b4b00a151 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -496,6 +496,22 @@ export class PaymentMethodPage extends BasePage { let updatedAppointmentDate = parsedAppointmentDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); return updatedAppointmentDate; } + + async validateOEMPart( isOEM: boolean): Promise { + if (isOEM == true) { + // Get Vuex state from localStorage + const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); + + // Validate the presence of an OEM part + if (vuexState.order?.lineItems?.glassParts?.length > 0) { + const firstGlassPartNumber = vuexState.order.lineItems.glassParts[0].partNumber; + + await expect(firstGlassPartNumber.includes("OEM")).toBe(true); + } else { + throw new Error("No glass parts found in the order"); + } + } + } @step("PaymentMethodPage >> Select Payment Method: ") async handlePaymentMethodPage(testData: Partial) { @@ -504,6 +520,7 @@ export class PaymentMethodPage extends BasePage { await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage); await this.validatePaymentDetailsPage(testData); await this.ValidateAfterPayBreakOutSection(); + await this.validateOEMPart(testData.isOEM!); // Verify VAPS wipers on backend for standard and premium packages if (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium) { diff --git a/playwright-tests/pages/ProblemGlassQuestionsPage.ts b/playwright-tests/pages/ProblemGlassQuestionsPage.ts new file mode 100644 index 000000000..08124eef0 --- /dev/null +++ b/playwright-tests/pages/ProblemGlassQuestionsPage.ts @@ -0,0 +1,43 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { IClaimDetails, ICustomerDetails } from 'safelite-playwright-core'; +import { InsuranceBasePage } from './InsuranceBasePage'; +import { ITestData } from 'framework/TestData'; +import { step } from 'framework/localTypes/Step'; + +export class ProblemGlassQuestionsPage extends InsuranceBasePage { + readonly provideVinManuallyButton: Locator; + readonly provideLicensePlateButton: Locator; + readonly provideHomeAddressButton: Locator; + readonly continueOnVinPopUpButton: Locator; + readonly dontShareVinButton: Locator; + + readonly yestoGlassQuestionButton: Locator; + readonly notoGlassQuestionButton: Locator; + readonly continueButton: Locator; + + + constructor(page: Page) { + super(page); + + this.provideVinManuallyButton = page.locator('label[for="VINManually"]'); + this.provideLicensePlateButton = page.locator('label[for="licensePlate"]'); + this.provideHomeAddressButton = page.locator('label[for="HomeAddress"]'); + this.continueOnVinPopUpButton = page.locator('button:has-text("Continue")'); + this.dontShareVinButton = page.getByRole('button', { name: "I would rather not share my VIN" }); + + this.yestoGlassQuestionButton = page.locator('label:has-text("Yes")'); + this.notoGlassQuestionButton = page.locator('label:has-text("No")'); + this.continueButton = page.locator('button[type="submit"].btn-success.nav-continue'); + + } + + + @step("ProblemGlassQuestionsPage >> Click continue button and select yes or no for problem glass question") + + async handleproblemGlassQuestionsPage(testData: Partial){ + + await this.dontShareVinButton.click(); + await this.yestoGlassQuestionButton.click(); + await this.continueButton.click(); + } +} \ No newline at end of file diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index c8788b04a..4209daad6 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -28,6 +28,7 @@ import insuranceITAC21stCenturyTests from "./InsuranceITAC21stCentury"; import insuranceGeicoTests from "./InsuranceGeico"; import insuranceITACOptimizedPriceValidationAllStateTests from "./InsuranceITACOptimizedPriceValidationAllState"; import insuranceNoCompProgressiveTests from "./InsuranceNoCompProgressive"; +import insuranceOEMAllstateTests from "./InsuranceOEMAllState"; import cashRepairInShopAfterPayTests from "./CashRepairInShopAfterPay"; import cashRepairInShopPayPalTests from "./CashRepairInShopPayPal"; import cashReplaceMultiSlidingGlassDropoffTests from "./CashReplaceMultiSlidingGlassDropoff"; @@ -82,6 +83,7 @@ const allStandardTests = [ {name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests}, {name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests}, // {name: "InsuranceBigTruckVerified", tests: insuranceBigTruckVerifiedTests}, + {name: "InsuranceOEMAllstate", tests: insuranceOEMAllstateTests}, {name: "InsuranceUnverified", tests: insuranceUnverifiedTests}, // {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} @@ -323,7 +325,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } export async function handleInsuranceFlow(testCase: TestCase) { - const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, flow } = testCase.testData; + const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, isOEM, flow } = testCase.testData; // Check if the insurance policy has endorsements const hasEndorsements = endorsements && endorsements.length > 0; @@ -332,6 +334,15 @@ export async function handleInsuranceFlow(testCase: TestCase) { let insuranceCompanyPage = testCase.pages.insuranceCompanyPage; await insuranceCompanyPage.handleInsuranceCompanyPage(testCase.testData); + // Handle OEM scenario + if (isOEM) { + // User should be on heritage problem glass questions page after selecting Allstate on insurance company page + if (testCase.testData.isOEM === true) { + let problemGlassQuestionsPage = testCase.pages.problemGlassQuestionsPage; + await problemGlassQuestionsPage.handleproblemGlassQuestionsPage(testCase.testData); + } + } + // Handle ccPolicyInfoPage let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData); diff --git a/playwright-tests/tests/InsuranceOEMAllState.ts b/playwright-tests/tests/InsuranceOEMAllState.ts new file mode 100644 index 000000000..c5fc0dc14 --- /dev/null +++ b/playwright-tests/tests/InsuranceOEMAllState.ts @@ -0,0 +1,88 @@ +//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("InsuranceOEMAllstate"); + +// Now get the test data with the seeded faker +const insuranceOEMAllstateData: Partial = { + ...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, + isOEM: true, + + // Override customer details with specific name and California location + customerDetails: { + ...getDefaultTestData().customerDetails!, + firstName: 'Jane', + lastName: 'OEMTest', + address: { + ...getDefaultTestData().customerDetails!.address, + city: 'Richmond', + state: 'Virginia', + postalCode: '23219' + } + }, + + + // Insurance claim details + claimDetails: { + client: 'Allstate', + policyNumber: 'MockOEMTest', + policyDeductible: "Unverified", + policyZip: '43085', + 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: '2024', + make: 'Jeep', + model: 'Wrangler', + 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: '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 insuranceOEMAllstateTests: ITestCase[] = []; + +const tc = { + name: `InsuranceOEMAllstate`, + tags: ['@E2E','@InsuranceOEMAllstate', '@test_report', '@Insurance'], + testData: insuranceOEMAllstateData +}; +insuranceOEMAllstateTests.push(tc); + +export default insuranceOEMAllstateTests; \ No newline at end of file From 05643bdcf342228971fb400d221a75eb79b6b877 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:25:18 -0500 Subject: [PATCH 2/8] Fix step name for clarity --- playwright-tests/pages/ProblemGlassQuestionsPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/ProblemGlassQuestionsPage.ts b/playwright-tests/pages/ProblemGlassQuestionsPage.ts index 08124eef0..e875ed262 100644 --- a/playwright-tests/pages/ProblemGlassQuestionsPage.ts +++ b/playwright-tests/pages/ProblemGlassQuestionsPage.ts @@ -32,7 +32,7 @@ export class ProblemGlassQuestionsPage extends InsuranceBasePage { } - @step("ProblemGlassQuestionsPage >> Click continue button and select yes or no for problem glass question") + @step("ProblemGlassQuestionsPage >> Handle VIN popup and answer glass question") async handleproblemGlassQuestionsPage(testData: Partial){ From 5090d2f2313c74a90f26323da9d0e19877eb6b24 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 17 Nov 2025 15:23:14 -0500 Subject: [PATCH 3/8] Update locator for Afterpay banner validation --- playwright-tests/pages/ServicePackagesPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 63a60a6e6..ba95f67cd 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -44,7 +44,7 @@ export class ServicePackagesPage extends BasePage { this.promoCodeTextbox = this.page.getByLabel('Enter a promo code'); this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' }); this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); - this.afterPayBanner = this.page.locator('#afterpay-banner'); + this.afterPayBanner = this.page.locator('div.afterpay-modal-banner'); this.glassOnlypackagePrice = this.page.locator('fieldset:has(legend#ServicePackageQuestion) label').filter({ hasText: 'Glass service only' }).locator('.pricing-info'); } From d9ef3f65b5900e45c4f56fbc0218b3598f15f292 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 17 Nov 2025 16:18:00 -0500 Subject: [PATCH 4/8] Rename for clarification --- playwright-tests/framework/TestPages.ts | 6 +++--- ...tionsPage.ts => HeritageProblemGlassQuestionsPage.ts} | 9 ++++----- playwright-tests/pages/OrderConfirmationPage.ts | 2 +- playwright-tests/tests/0000__M.test.ts | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) rename playwright-tests/pages/{ProblemGlassQuestionsPage.ts => HeritageProblemGlassQuestionsPage.ts} (81%) diff --git a/playwright-tests/framework/TestPages.ts b/playwright-tests/framework/TestPages.ts index b0baf2db1..c2cc04b35 100644 --- a/playwright-tests/framework/TestPages.ts +++ b/playwright-tests/framework/TestPages.ts @@ -24,7 +24,7 @@ import { CCPolicyInfoPage } from "../pages/CCPolicyInfoPage" import { DuplicateCheckPage } from "../pages/DuplicateCheckPage" import { PolicyVehiclesPage } from "../pages/PolicyVehiclesPage" import { PolicyInfoSubmittedPage } from "../pages/PolicyInfoSubmittedPage" -import { ProblemGlassQuestionsPage } from "../pages/ProblemGlassQuestionsPage" +import { HeritageProblemGlassQuestionsPage } from "../pages/HeritageProblemGlassQuestionsPage" import RecalibrationInfoPage from "../pages/RecalibrationInfoPage" import { CoverageStatementPage } from "../pages/CoverageStatementPage" import { VerifyDetailsPage } from "../pages/VerifyDetailsPage" @@ -49,7 +49,7 @@ export interface ITestPages { paymentMethodPage: PaymentMethodPage, policyInfoSubmittedPage: PolicyInfoSubmittedPage, policyVehiclesPage: PolicyVehiclesPage, - problemGlassQuestionsPage: ProblemGlassQuestionsPage, + heritageProblemGlassQuestionsPage: HeritageProblemGlassQuestionsPage, recalibrationInfoPage: RecalibrationInfoPage, schedulePage: SchedulePage, mobileDetailsPage: MobileDetailsPage, @@ -85,7 +85,7 @@ export const createTestPages: TestPagesFactory = (page: Page) => { paymentMethodPage: new PaymentMethodPage(page), policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page), policyVehiclesPage: new PolicyVehiclesPage(page), - problemGlassQuestionsPage: new ProblemGlassQuestionsPage(page), + heritageProblemGlassQuestionsPage: new HeritageProblemGlassQuestionsPage(page), recalibrationInfoPage: new RecalibrationInfoPage(page), schedulePage: new SchedulePage(page), mobileDetailsPage: new MobileDetailsPage(page), diff --git a/playwright-tests/pages/ProblemGlassQuestionsPage.ts b/playwright-tests/pages/HeritageProblemGlassQuestionsPage.ts similarity index 81% rename from playwright-tests/pages/ProblemGlassQuestionsPage.ts rename to playwright-tests/pages/HeritageProblemGlassQuestionsPage.ts index e875ed262..097fd5200 100644 --- a/playwright-tests/pages/ProblemGlassQuestionsPage.ts +++ b/playwright-tests/pages/HeritageProblemGlassQuestionsPage.ts @@ -1,10 +1,9 @@ import { expect, type Locator, type Page } from '@playwright/test'; -import { IClaimDetails, ICustomerDetails } from 'safelite-playwright-core'; import { InsuranceBasePage } from './InsuranceBasePage'; import { ITestData } from 'framework/TestData'; import { step } from 'framework/localTypes/Step'; -export class ProblemGlassQuestionsPage extends InsuranceBasePage { +export class HeritageProblemGlassQuestionsPage extends InsuranceBasePage { readonly provideVinManuallyButton: Locator; readonly provideLicensePlateButton: Locator; readonly provideHomeAddressButton: Locator; @@ -32,9 +31,9 @@ export class ProblemGlassQuestionsPage extends InsuranceBasePage { } - @step("ProblemGlassQuestionsPage >> Handle VIN popup and answer glass question") - - async handleproblemGlassQuestionsPage(testData: Partial){ + @step("HeritageProblemGlassQuestionsPage >> Handle VIN popup and answer glass question on heritage") + // Handles OEM scenario with vehicle that has only one glass question + async handleHeritageProblemGlassQuestionsPage(testData: Partial){ await this.dontShareVinButton.click(); await this.yestoGlassQuestionButton.click(); diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 6bf10926c..a65c80fd2 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -47,7 +47,7 @@ export class OrderConfirmationPage extends BasePage { async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, isCashInsuranceFlow, - isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified, isOEM } = testData; + isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified} = testData; await this.serviceText.waitFor({ state: "visible" }); Soft.expect((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase())); diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 4209daad6..4f0bbe6bf 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -338,8 +338,8 @@ export async function handleInsuranceFlow(testCase: TestCase) { if (isOEM) { // User should be on heritage problem glass questions page after selecting Allstate on insurance company page if (testCase.testData.isOEM === true) { - let problemGlassQuestionsPage = testCase.pages.problemGlassQuestionsPage; - await problemGlassQuestionsPage.handleproblemGlassQuestionsPage(testCase.testData); + let HeritageProblemGlassQuestionsPage = testCase.pages.heritageProblemGlassQuestionsPage; + await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); } } From 9f1e230ea23d82499b068637a170582495958f4b Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Tue, 18 Nov 2025 13:35:15 -0500 Subject: [PATCH 5/8] Re-add CASH-1620 to develop. --- src/constants/cart-item-categories.js | 2 +- src/constants/cart-item-types.js | 2 +- src/constants/part-type-strings.js | 1 + src/constants/quote-page-discounts.js | 80 +++++++++++++ .../service-package-radio-for-afterpay.vue | 4 +- .../service-package-radio.vue | 9 +- src/fmg-components/cart/cart.vue | 79 ++++++------- src/layouts/quote/quote.spec.js | 2 +- src/layouts/quote/quote.vue | 104 ++++++++++------- .../service-package-question.spec.js | 53 ++++----- .../service-package-question.vue | 110 ++++++++---------- .../service-package-radio.vue | 7 +- src/mixins/base-mixin.js | 4 +- 13 files changed, 260 insertions(+), 197 deletions(-) create mode 100644 src/constants/quote-page-discounts.js diff --git a/src/constants/cart-item-categories.js b/src/constants/cart-item-categories.js index 6bbb960b9..5c8faafcf 100644 --- a/src/constants/cart-item-categories.js +++ b/src/constants/cart-item-categories.js @@ -3,7 +3,7 @@ const cartItemCategories = { GLASS_PARTS: "glassParts", SUPPORTING_ITEMS: "supportingItems", PROMOS: "promos", - SERVICE_PACKAGE_DISCOUNT: "servicePackageDiscount", + QUOTE_PAGE_DISCOUNT: "quotePageDiscount", }; export { cartItemCategories }; diff --git a/src/constants/cart-item-types.js b/src/constants/cart-item-types.js index 0bb418d43..8f70aeaed 100644 --- a/src/constants/cart-item-types.js +++ b/src/constants/cart-item-types.js @@ -10,7 +10,7 @@ const cartItemTypes = { PROMOS: "PROMOS", EARLY_BIRD: "EARLY BIRD", SUPPLIES_REPAIR: "SUPPLIES-REPAIR", - SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", + QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT", DONATION: "DONATION", }; diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 741132e28..17b96d2fb 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -10,6 +10,7 @@ const partTypeStrings = { REPAIR_FEE: "REPAIR FEE", EARLY_BIRD: "EARLY BIRD", SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", + QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT", DONATION: "DONATION", }; diff --git a/src/constants/quote-page-discounts.js b/src/constants/quote-page-discounts.js new file mode 100644 index 000000000..2dd32d915 --- /dev/null +++ b/src/constants/quote-page-discounts.js @@ -0,0 +1,80 @@ +import { packageNames } from "./package-names"; +import { partTypeStrings } from "./part-type-strings"; + +export const quotePageDiscountTable = [ + // Repair + { + name: "Repair Glass Only", + experimentCode: "Show_GlassOnly_Repair_Discount", + partNumber: "GL RPR CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "GLASS RPR GEOTARGET DISC", + packageLevel: packageNames.TIER_ONE, + }, + { + name: "Repair Standard", + experimentCode: "Show_Standard_Repair_Discount", + partNumber: "STD RPR CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "STD RPR GEOTARGET DISC", + packageLevel: packageNames.TIER_TWO, + }, + { + name: "Repair Premium", + experimentCode: "Show_Premium_Repair_Discount", + partNumber: "PRM RPR CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "PREM RPR GEOTARGET DISC", + packageLevel: packageNames.TIER_THREE, + }, + // Replace - No recal + { + name: "Replace Glass Only", + experimentCode: "Show_GlassOnly_Replace_Discount", + partNumber: "GL RPL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "GLASS RPL GEOTARGET DISC", + packageLevel: packageNames.TIER_ONE, + }, + { + name: "Replace Standard", + experimentCode: "Show_Standard_Replace_Discount", + partNumber: "STD RPL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "STD RPL GEOTARGET DISC", + packageLevel: packageNames.TIER_TWO, + }, + { + name: "Replace Premium", + experimentCode: "Show_Premium_Replace_Discount", + partNumber: "PRM RPL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "PREM RPL GEOTARGET DISC", + packageLevel: packageNames.TIER_THREE, + }, + // Replace - with recal + { + name: "Recal Glass Only", + experimentCode: "Show_GlassOnly_Recal_Discount", + partNumber: "GL RCL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "GLASS RCL GEOTARGET DISC", + packageLevel: packageNames.TIER_ONE, + }, + { + name: "Recal Standard", + experimentCode: "Show_Standard_Recal_Discount", + partNumber: "STD RCL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "STD RCL GEOTARGET DISC", + packageLevel: packageNames.TIER_TWO, + }, + { + name: "Recal Premium", + experimentCode: "Show_Premium_Recal_Discount", + partNumber: "PRM RCL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "PREM RCL GEOTARGET DISC", + packageLevel: packageNames.TIER_THREE, + }, +]; diff --git a/src/experiment-components/service-package-radio-for-afterpay.vue b/src/experiment-components/service-package-radio-for-afterpay.vue index 2e746e4f2..7f7e91084 100644 --- a/src/experiment-components/service-package-radio-for-afterpay.vue +++ b/src/experiment-components/service-package-radio-for-afterpay.vue @@ -145,9 +145,7 @@ export default { return "$" + decimalPrice.toFixed(2); }, hasPackageDiscount() { - return ( - this.additionalButtonData.servicePackageDiscount && this.additionalButtonData.Text - ); + return this.additionalButtonData.hasDiscount && this.additionalButtonData.Text; }, }, }; diff --git a/src/experiment-components/service-package-radio.vue b/src/experiment-components/service-package-radio.vue index ee7e12712..a116fecd8 100644 --- a/src/experiment-components/service-package-radio.vue +++ b/src/experiment-components/service-package-radio.vue @@ -4,11 +4,11 @@ class="package-label pricing-by-day-pkg-lbl" :class="[ this.buttonLabelSubCopy ? 'has-subheader' : '', - !this.additionalButtonData.servicePackageDiscount ? 'adjust-top' : '', + !this.additionalButtonData.hasDiscount ? 'adjust-top' : '', ]" for="testradio">
-
+

@@ -84,10 +84,7 @@

diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index d1bd2a939..e890e53cc 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -190,6 +190,7 @@ import { cartItemCategories } from "@/constants/cart-item-categories"; import { cartItemTypes } from "@/constants/cart-item-types"; import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance"; import { experimentSettings } from "@/constants/experiments"; +import { quotePageDiscountTable } from "@/constants/quote-page-discounts"; export default { name: "cart", @@ -256,11 +257,11 @@ export default { return subTotal; }, - getServicePackageDiscount() { - const servicePackageDiscountLineItems = this.servicePackageDiscountCartItem; + getQuotePageDiscount() { + const quotePageDiscountLineItems = this.quotePageDiscountCartItem; let subTotal = 0; - subTotal += servicePackageDiscountLineItems?.subTotal ?? 0; + subTotal += quotePageDiscountLineItems?.subTotal ?? 0; return subTotal; }, getVapsCartItemsForSelectedPackage(packageName) { @@ -303,7 +304,8 @@ export default { getLineItemAmount(amount, useVerifyingText, category) { if (useVerifyingText) return this.verifyingCoverageText; - return category == "promos" || category == "servicePackageDiscount" + return category == cartItemCategories.PROMOS || + category == cartItemCategories.QUOTE_PAGE_DISCOUNT ? "(" + this.currencyFormatter.format(amount * -1) + ")" : this.currencyFormatter.format(amount); }, @@ -316,16 +318,21 @@ export default { if (category == cartItemCategories.VAPS || category == cartItemCategories.PROMOS) { this.saveVaps(this.lineItems); // Check if service package discount should be removed after vaps change - if ( - this.servicePackageDiscountCartItem && - this.discountPackageNames != this.packageLevel - ) { - this.lineItems.supportingItems = this.lineItems.supportingItems.filter( - (lineItemsToKeep) => - lineItemsToKeep.cartItemType != - this.servicePackageDiscountCartItem.cartItemType + if (this.quotePageDiscountCartItem) { + const existingLineItem = this.quotePageDiscountCartItem.lineItems.at(0); + const discountInfo = quotePageDiscountTable.find( + (info) => info.partNumber === existingLineItem?.partNumber ); - shouldSaveSupportingItems = true; + const packageLevelForDiscount = discountInfo?.packageLevel; + + if (this.packageLevel !== packageLevelForDiscount) { + this.lineItems.supportingItems = this.lineItems.supportingItems.filter( + (lineItemsToKeep) => + lineItemsToKeep.cartItemType != + this.quotePageDiscountCartItem.cartItemType + ); + shouldSaveSupportingItems = true; + } } } if (shouldSaveSupportingItems) { @@ -483,8 +490,8 @@ export default { cartItems.push(this.mobileFeeCartItem); } - if (this.servicePackageDiscountCartItem) { - cartItems.push(this.servicePackageDiscountCartItem); + if (this.quotePageDiscountCartItem) { + cartItems.push(this.quotePageDiscountCartItem); } if (this.premiumAppointmentDiscountCartItem) { @@ -500,12 +507,6 @@ export default { return cartItems; }, }, - discountPackageNames() { - const discountServicePackage = experimentMixin.methods.getSettingValue( - experimentSettings.PROMO_ON_PACKAGE - ); - return getDiscountedPackageName(discountServicePackage); - }, servicePackageTitleWidget() { const servicePackageNames = this.getCmsContent( this.servicePackageOptionsCmsName, @@ -550,8 +551,8 @@ export default { } packagePrice += this.getVapsPrice(this.packageLevel); - if (this.servicePackageDiscountCartItem) { - packagePrice -= this.getServicePackageDiscount(); + if (this.quotePageDiscountCartItem) { + packagePrice -= this.getQuotePageDiscount(); } return packagePrice; @@ -928,25 +929,25 @@ export default { } return cartItem; }, - servicePackageDiscountCartItemName() { + quotePageDiscountCartItemName() { return this.getCmsContent("ServicePackageDiscountTextWidget", "Text"); }, - servicePackageDiscountCartItem() { + quotePageDiscountCartItem() { let cartItem = null; - const servicePackageDiscountLineItem = this.supportingItems.find( - (lineItem) => lineItem.partType == partTypeStrings.SERVICE_PACKAGE_DISCOUNT + const quotePageDiscountLineItem = this.supportingItems.find( + (lineItem) => lineItem.partType == partTypeStrings.QUOTE_PAGE_DISCOUNT ); - if (servicePackageDiscountLineItem) { + if (quotePageDiscountLineItem) { cartItem = { name: - this.servicePackageDiscountCartItemName + + this.quotePageDiscountCartItemName + Math.abs( - baseMixin.methods.getTotalLineItemPrice(servicePackageDiscountLineItem) + baseMixin.methods.getTotalLineItemPrice(quotePageDiscountLineItem) ), - category: cartItemCategories.SERVICE_PACKAGE_DISCOUNT, - cartItemType: cartItemTypes.SERVICE_PACKAGE_DISCOUNT, + category: cartItemCategories.QUOTE_PAGE_DISCOUNT, + cartItemType: cartItemTypes.QUOTE_PAGE_DISCOUNT, isDisplayed: true, isRemovable: false, subTotal: 0, @@ -955,15 +956,15 @@ export default { isCoveredByInsurance: false, }; - servicePackageDiscountLineItem.cartItemType = cartItem.cartItemType; - cartItem.lineItems.push(servicePackageDiscountLineItem); + quotePageDiscountLineItem.cartItemType = cartItem.cartItemType; + cartItem.lineItems.push(quotePageDiscountLineItem); cartItem.subTotal += - (servicePackageDiscountLineItem.kitPrice ?? 0) + - (servicePackageDiscountLineItem.laborAmount ?? 0) + - (servicePackageDiscountLineItem.sellingPrice ?? 0); + (quotePageDiscountLineItem.kitPrice ?? 0) + + (quotePageDiscountLineItem.laborAmount ?? 0) + + (quotePageDiscountLineItem.sellingPrice ?? 0); - cartItem.salesTax += servicePackageDiscountLineItem.salesTax ?? 0; + cartItem.salesTax += quotePageDiscountLineItem.salesTax ?? 0; } return cartItem; @@ -972,7 +973,7 @@ export default { let cartItem = null; const otherSupportingItems = this.supportingItems.filter((item) => { - return item.partType != partTypeStrings.SERVICE_PACKAGE_DISCOUNT; + return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT; }); // Don't include fees they are part of the package total let otherSupportingItemsLineItems = diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index aea32211e..5d9f3bacb 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -82,7 +82,7 @@ jest.mock("@/mixins/base-mixin", () => ({ filterOutFees(items) { return null; }, - filterOutServicePackageDiscountPart(items) { + filterOutQuotePageDiscountPart(items) { return null; }, isFormValid(form) { diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 7f9ea7efc..17326ec8e 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -31,10 +31,11 @@ :isRecalibrationOnOrder="isRecalibrationOnOrder" :shouldHideRecalibration="shouldHideRecalibration" @vapsItemsSelected="vapsItemsSelectedAction" - @servicePackageDiscountSelected="servicePackageDiscountSelectedAction" + @quotePageDiscountSelected="quotePageDiscountSelectedAction" @currentNumberOfPackages="currentNumberOfPackagesAction" :servicePackage="servicePackage" :activePromos="lineItems.promos" + :availableQuotePageDiscounts="quoteDiscountPartsInfo" v-on="{ 'buttonEvent.openModal': openModalAction }" validationRules="option-required" isRequired /> @@ -174,6 +175,7 @@ import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stas import { storeMutations } from "@/constants/store-mutations"; import { debugLog } from "@/helpers/debug-log-helper"; import { savePageData } from "@/router/methods/helpers/save-page-data"; +import { quotePageDiscountTable } from "../../constants/quote-page-discounts"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); @@ -184,6 +186,14 @@ const ACCEPTED_QUOTE_TYPES = { INSURANCE: "insurance", }; +function getAvailableQuotePageDiscounts() { + const activeDiscounts = quotePageDiscountTable?.filter((discountEntry) => + experimentMixin?.methods?.hasSettingEqualTo(discountEntry?.experimentCode, "true") + ); + + return activeDiscounts ?? []; +} + export default { name: "quote", async beforeRouteEnter(to, from, next) { @@ -300,29 +310,22 @@ export default { const lineItems = deepClone(store.getters.order.lineItems); lineItems.vaps = lineItems.vaps ?? []; const nullSafeGlassParts = lineItems.glassParts ?? []; - const servicePackageDiscountSettingValue = experimentMixin.methods.getSettingValue( - experimentSettings.SERVICE_PACKAGE_DISCOUNT - ); - const isServicePackageDiscount = servicePackageDiscountSettingValue === "True"; - //Service package cash discount api call when experiment is active - var servicePackageDiscountPart = []; - if (isServicePackageDiscount) { - const servicePackageDiscountPartResponse = - await baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.GET_SERVICE_PACKAGE_DISCOUNT_PART, - null, - "quote" - ); - if (servicePackageDiscountPartResponse.data) { - servicePackageDiscountPart.push(servicePackageDiscountPartResponse.data); - } - } + + const quotePageDiscountPartsInfo = getAvailableQuotePageDiscounts(); + + const quotePageDiscountParts = quotePageDiscountPartsInfo.map((partInfo) => ({ + partNumber: partInfo.partNumber, + partType: partInfo.partType, + description: partInfo.description, + isInsurable: null, + })); + const availableLineItems = [ resultMap.rainRepel, ...resultMap.supportingItems, ...resultMap.wipers, ...nullSafeGlassParts, - ...servicePackageDiscountPart, + ...quotePageDiscountParts, ]; // When calling pricing from the quote page, always use the cash parent account but save the existing one in case it's unverified insurance navigating backwards @@ -349,7 +352,7 @@ export default { false ); - // This will remove any servicePackageDiscount item from lineItems.supportingItems + // This will remove any discount item from lineItems.supportingItems baseMixin.methods.dispatchStoreAction( storeActions.SAVE_SUPPORTING_ITEMS, resultMap.supportingItems, @@ -451,10 +454,9 @@ export default { ) : baseMixin.methods.filterOutFees(availableLineItems); - const lineItemsNoDiscount = - baseMixin.methods.filterOutServicePackageDiscountPart( - lineItemsForCalculatingPrice - ); + const lineItemsNoDiscount = baseMixin.methods.filterOutQuotePageDiscountPart( + lineItemsForCalculatingPrice + ); if (isInsuranceFromQueryString != null) { if (isInsuranceFromQueryString.toLowerCase() === "true") { @@ -578,17 +580,8 @@ export default { isRecalibrationOnOrder() { return store.getters.isRecalibrationOnOrder; }, - isServicePackageDiscountOnOrder() { - return containsLineItemWithPartType( - partTypeStrings.SERVICE_PACKAGE_DISCOUNT, - this.availableLineItems - ); - }, - servicePackageDiscountParts() { - return findLineItemsWithPartType( - partTypeStrings.SERVICE_PACKAGE_DISCOUNT, - this.availableLineItems - ); + isQuotePageDiscountOnOrder() { + return this.quotePageDiscountPartsInfo?.length > 0; }, shouldHideRecalibration() { return ( @@ -617,6 +610,23 @@ export default { thresholdAmount && (thresholdAmount = parseInt(thresholdAmount)); return thresholdAmount; }, + quoteDiscountPartsInfo() { + const defs = getAvailableQuotePageDiscounts(); + const withPrices = defs + .map((def) => { + const match = this.availableLineItems?.find?.( + (pricedItem) => pricedItem.partNumber === def.partNumber + ); + + return { + packageLevel: def.packageLevel, + item: match, + }; + }) + .filter((result) => result.item); + + return withPrices; + }, }, methods: { getColCount() { @@ -660,7 +670,7 @@ export default { vapsItemsSelectedAction(vapsItemsSelected) { this.lineItems.vaps = vapsItemsSelected; }, - servicePackageDiscountSelectedAction(supportingItemsSelected) { + quotePageDiscountSelectedAction(supportingItemsSelected) { this.lineItems.supportingItems = supportingItemsSelected; }, backButtonAction() { @@ -722,10 +732,10 @@ export default { false ); } - if (this.isInsuranceSelected && this.isServicePackageDiscountOnOrder) { + if (this.isInsuranceSelected && this.isQuotePageDiscountOnOrder) { let supportingItems = this.lineItems.supportingItems; supportingItems = supportingItems.filter((item) => { - return item.partType != this.servicePackageDiscountParts[0].partType; + return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT; }); this.dispatchStoreAction( this.storeActions.SAVE_SUPPORTING_ITEMS, @@ -811,19 +821,23 @@ export default { lineItemsToPrice = baseMixin.methods.filterOutRecalibration(lineItemsToPrice); } - if (this.isServicePackageDiscountOnOrder) { + if (this.isQuotePageDiscountOnOrder) { lineItemsToPrice = lineItemsToPrice?.filter((item) => { - return ( - item.partType != this.servicePackageDiscountParts[0].partType - ); + return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT; }); } let price = 0; if ( - tier.additionalButtonData?.servicePackageDiscount && - this.isServicePackageDiscountOnOrder + tier.additionalButtonData?.hasDiscount && + this.isQuotePageDiscountOnOrder ) { - lineItemsToPrice.push(...this.servicePackageDiscountParts); + const parts = this.quoteDiscountPartsInfo; + const matchPart = parts.find( + (discountInfo) => discountInfo.packageLevel === tier + ); + if (matchPart) { + lineItemsToPrice.push(matchPart); + } } price += baseMixin.methods.getTierOnePackagePrice( baseMixin.methods.filterOutFees(lineItemsToPrice) diff --git a/src/layouts/quote/service-package-question/service-package-question.spec.js b/src/layouts/quote/service-package-question/service-package-question.spec.js index 523c0edce..a44dc463c 100644 --- a/src/layouts/quote/service-package-question/service-package-question.spec.js +++ b/src/layouts/quote/service-package-question/service-package-question.spec.js @@ -14,6 +14,7 @@ import { experimentSettings } from "@/constants/experiments"; import baseMixin from "@/mixins/base-mixin.js"; import { partTypeStrings } from "@/constants/part-type-strings"; import { nextTick } from "vue"; +import { packageNames } from "../../../constants/package-names"; jest.mock("@/store", () => ({ commit: jest.fn(), @@ -73,9 +74,9 @@ describe("service-package-question.vue", () => { price: 35.5, }, { - partNumber: "DISC CASHSAVE70", + partNumber: "PRM RCL CSH PKG", description: null, - partType: "SERVICE PACKAGE DISCOUNT", + partType: "QUOTE PAGE DISCOUNT", price: -70, }, ], @@ -105,13 +106,27 @@ describe("service-package-question.vue", () => { description: null, kitPrice: 0, laborAmount: 0, - partNumber: "DISC CASHSAVE70", - partType: "SERVICE PACKAGE DISCOUNT", + partNumber: "PRM RCL CSH PKG", + partType: "QUOTE PAGE DISCOUNT", salesTax: null, sellingPrice: -70, }, ], }, + availableQuotePageDiscounts: [ + { + packageLevel: packageNames.TIER_THREE, + item: { + description: null, + kitPrice: 0, + laborAmount: 0, + partNumber: "PRM RCL CSH PKG", + partType: "QUOTE PAGE DISCOUNT", + salesTax: null, + sellingPrice: -70, + }, + }, + ], activePromos: [], }; const packageNameKey = "05_01_CSR_Quote_Standard_Repair"; @@ -136,41 +151,17 @@ describe("service-package-question.vue", () => { }); it("should return price when there is discount", () => { const wrapper = setupMocks({}); - const partType = partTypeStrings.SERVICE_PACKAGE_DISCOUNT; - wrapper.vm.isServicePackageDiscountOnOrder = containsLineItemWithPartType( - partType, - mockProps.lineItems.supportingItems - ); - const servicePackageDiscountLineItem = findLineItemsWithPartType( - partType, - mockProps.lineItems.supportingItems - ); - wrapper.vm.getServicePackageDiscountPrice(); - const price = baseMixin.methods.getTotalLineItemPrice(servicePackageDiscountLineItem[0]); + const price = wrapper.vm.getDiscountPrice(packageNames.TIER_THREE); expect(Math.abs(price)).toEqual(70); }); - it("isServicePackageDiscountOnOrder returns true if it contains service package discount lineItems", () => { + it("isDiscountOnOrder returns true if it contains quote package discount lineItems", () => { // Arrange const wrapper = setupMocks({}); // Assert - expect(wrapper.vm.isServicePackageDiscountOnOrder).toBe(true); - }); - it("servicePackageDiscountParts should returns service package discount lineItems", () => { - // Arrange - const wrapper = setupMocks({}); - - // Assert - expect(wrapper.vm.servicePackageDiscountParts).toEqual([ - { - partNumber: "DISC CASHSAVE70", - description: null, - partType: "SERVICE PACKAGE DISCOUNT", - price: -70, - }, - ]); + expect(wrapper.vm.isDiscountOnOrder).toBe(true); }); it("should have the correct insurance pricing text when insurance is selected", () => { // Arrange diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue index d1a99da08..e45147295 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -60,6 +60,7 @@ export default { servicePackage: null, isRecalibrationOnOrder: Boolean, shouldHideRecalibration: Boolean, + availableQuotePageDiscounts: null, }, data() { return { @@ -85,27 +86,18 @@ export default { selectedPackageName(newValue) { const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue); this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage); - const servicePackageDiscountPartInSelectedPackage = - this.getServicePackageDiscountPartForSelectedPackage(newValue); + const discountPartInSelectedPackage = this.getDiscountPartForSelectedPackage(newValue); let supportingItems = this.supportingLineItems; - if ( - containsLineItemWithPartType( - partTypeStrings.SERVICE_PACKAGE_DISCOUNT, - supportingItems - ) - ) { - supportingItems = supportingItems.filter( - (item) => item.partType !== partTypeStrings.SERVICE_PACKAGE_DISCOUNT - ); - } - if ( - servicePackageDiscountPartInSelectedPackage?.length > 0 && - supportingItems != null - ) { - supportingItems.push(servicePackageDiscountPartInSelectedPackage[0]); + + supportingItems = supportingItems.filter( + (item) => item.partType !== partTypeStrings.QUOTE_PAGE_DISCOUNT + ); + + if (discountPartInSelectedPackage) { + supportingItems.push(discountPartInSelectedPackage); } - this.$emit("servicePackageDiscountSelected", supportingItems); + this.$emit("quotePageDiscountSelected", supportingItems); }, servicePackage(newValue) { if (newValue !== null) { @@ -118,7 +110,7 @@ export default { return this.availableLineItems ?? []; }, supportingLineItems() { - return this.$store.getters.lineItems?.supportingItems; + return this.$store.getters.lineItems?.supportingItems ?? []; }, servicePackageAnswers() { const cmsWidgetName = this.isInsuranceSelected @@ -149,18 +141,15 @@ export default { ? this.getDiscountedPackagePriceString(answer.Name, false) : this.getDiscountedPackagePriceString( answer.Name, - this.isServicePackageDiscountOnOrder && - this.discountPackageNames == answer.Name + this.hasDiscountForPackage(answer.Name) ), buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName), additionalButtonData: { strikeThroughPrice: this.getPackagePriceString(answer.Name), - servicePackageDiscount: - this.isServicePackageDiscountOnOrder && - this.discountPackageNames == answer.Name, + hasDiscount: this.hasDiscountForPackage(answer.Name), Text: this.isInsuranceSelected ? false - : "Special: Save $" + this.getServicePackageDiscountPrice(), + : "Special: Save $" + this.getDiscountPrice(answer.Name), isInsuranceSelected: this.isInsuranceSelected, }, })); @@ -168,17 +157,8 @@ export default { this.$emit("currentNumberOfPackages", modifiedAnswers.length); return modifiedAnswers; }, - isServicePackageDiscountOnOrder() { - return containsLineItemWithPartType( - partTypeStrings.SERVICE_PACKAGE_DISCOUNT, - this.nullSafeAvailableLineItems - ); - }, - discountPackageNames() { - const discountServicePackage = experimentMixin.methods.getSettingValue( - experimentSettings.PROMO_ON_PACKAGE - ); - return getDiscountedPackageName(discountServicePackage); + isDiscountOnOrder() { + return this.availableQuotePageDiscounts?.length > 0; }, frontWipersApplicableForTierTwo() { return shouldFrontWipersBeAvailable( @@ -226,12 +206,6 @@ export default { isRepair() { return this.$store.getters.order.damage.isRepair; }, - servicePackageDiscountParts() { - return findLineItemsWithPartType( - partTypeStrings.SERVICE_PACKAGE_DISCOUNT, - this.nullSafeAvailableLineItems - ); - }, showRecalInServicePackages() { return this.isRecalibrationOnOrder && !this.shouldHideRecalibration; }, @@ -280,16 +254,16 @@ export default { const formattedPriceFloat = parseFloat( this.getPackagePrice(packageName, { discountedPrice: false, - servicePackageDiscount: false, + quotePageDiscount: false, }) ).toFixed(2); return "$" + formattedPriceFloat; }, - getDiscountedPackagePriceString(packageName, servicePackageDiscount) { + getDiscountedPackagePriceString(packageName, quotePageDiscount) { const formattedPriceFloat = parseFloat( this.getPackagePrice(packageName, { discountedPrice: true, - servicePackageDiscount, + quotePageDiscount, }) ).toFixed(2); @@ -298,7 +272,7 @@ export default { } return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat; }, - getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) { + getPackagePrice(packageName, { discountedPrice = false, quotePageDiscount = false }) { let lineItemsToPrice = [...this.nullSafeAvailableLineItems]; if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) { @@ -306,17 +280,20 @@ export default { } //remove service package discount part - if (this.isServicePackageDiscountOnOrder) { + if (this.isDiscountOnOrder) { lineItemsToPrice = lineItemsToPrice.filter((item) => { - return item.partType != this.servicePackageDiscountParts[0].partType; + return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT; }); } if (discountedPrice) { lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos)); } - if (servicePackageDiscount) { - lineItemsToPrice.push(...this.servicePackageDiscountParts); + if (quotePageDiscount) { + const part = this.getDiscountPartForSelectedPackage(packageName); + if (part) { + lineItemsToPrice.push(part); + } } let priceFloat = this.isInsuranceSelected ? 0 @@ -328,14 +305,16 @@ export default { return priceFloat; }, - getServicePackageDiscountPrice() { - if (this.isServicePackageDiscountOnOrder) { - let price = 0; - price += baseMixin.methods.getTotalLineItemPrice( - this.servicePackageDiscountParts[0] - ); + getDiscountPrice(packageName) { + const part = this.getDiscountPartForSelectedPackage(packageName); + + if (part) { + const price = baseMixin.methods.getTotalLineItemPrice(part); + return Math.abs(price); } + + return null; }, getVapsPrice(packageName, applyPromoDiscounts = false) { const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName); @@ -398,13 +377,18 @@ export default { return vapsLineItemsForSelectedPackage; }, - getServicePackageDiscountPartForSelectedPackage(packageName) { - const selectedPackage = this.servicePackageAnswers.filter( - (item) => item.value === packageName - ); - if (selectedPackage?.[0]?.additionalButtonData?.servicePackageDiscount) { - return this.servicePackageDiscountParts; - } else return []; + getDiscountPartForSelectedPackage(packageName) { + if (this.availableQuotePageDiscounts?.length > 0) { + const match = this.availableQuotePageDiscounts.find( + (discountInfo) => discountInfo.packageLevel === packageName + ); + return match?.item; + } + + return null; + }, + hasDiscountForPackage(packageName) { + return !!this.getDiscountPartForSelectedPackage(packageName); }, combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) { const combinedLineItemArray = [...lineItemsTwo]; diff --git a/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.vue index 441a4f5cb..8bb7e921a 100644 --- a/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.vue @@ -5,7 +5,7 @@ :class="[this.buttonLabelSubCopy ? 'has-subheader' : '']" for="testradio">
-
+

@@ -77,10 +77,7 @@

diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index 5204178de..813a3189a 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -129,9 +129,9 @@ export default { }); return lineItemsArray; }, - filterOutServicePackageDiscountPart(lineItems) { + filterOutQuotePageDiscountPart(lineItems) { const filteredLineItems = lineItems?.filter((item) => { - return !item?.partType?.includes(partTypeStrings.SERVICE_PACKAGE_DISCOUNT); + return !item?.partType?.includes(partTypeStrings.QUOTE_PAGE_DISCOUNT); }); return filteredLineItems; }, From 9812c874df821eb9a431f34b01c292086ffbc0ec Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:49:45 -0500 Subject: [PATCH 6/8] Move method to base page for reusability --- playwright-tests/pages/BasePage.ts | 16 ++++++++++++++++ playwright-tests/pages/PaymentMethodPage.ts | 16 ---------------- playwright-tests/tests/0000__M.test.ts | 4 +--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 6596986df..1562677a2 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -169,4 +169,20 @@ export class BasePage { Soft.expect(actualProgressPercentage).toBe(progressPercentage); console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`); } + + async validateOEMPart( isOEM: boolean): Promise { + if (isOEM!) { + // Get Vuex state from localStorage + const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); + + // Validate the presence of an OEM part + if (vuexState.order?.lineItems?.glassParts?.length > 0) { + const firstGlassPartNumber = vuexState.order.lineItems.glassParts[0].partNumber; + + await expect(firstGlassPartNumber.includes("OEM")).toBe(true); + } else { + throw new Error("No glass parts found in the order"); + } + } + } } \ No newline at end of file diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index b4b00a151..cbf6d01c8 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -496,22 +496,6 @@ export class PaymentMethodPage extends BasePage { let updatedAppointmentDate = parsedAppointmentDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); return updatedAppointmentDate; } - - async validateOEMPart( isOEM: boolean): Promise { - if (isOEM == true) { - // Get Vuex state from localStorage - const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); - - // Validate the presence of an OEM part - if (vuexState.order?.lineItems?.glassParts?.length > 0) { - const firstGlassPartNumber = vuexState.order.lineItems.glassParts[0].partNumber; - - await expect(firstGlassPartNumber.includes("OEM")).toBe(true); - } else { - throw new Error("No glass parts found in the order"); - } - } - } @step("PaymentMethodPage >> Select Payment Method: ") async handlePaymentMethodPage(testData: Partial) { diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 4f0bbe6bf..d2d85f4b9 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -335,12 +335,10 @@ export async function handleInsuranceFlow(testCase: TestCase) { await insuranceCompanyPage.handleInsuranceCompanyPage(testCase.testData); // Handle OEM scenario - if (isOEM) { + if (isOEM === true) { // User should be on heritage problem glass questions page after selecting Allstate on insurance company page - if (testCase.testData.isOEM === true) { let HeritageProblemGlassQuestionsPage = testCase.pages.heritageProblemGlassQuestionsPage; await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); - } } // Handle ccPolicyInfoPage From a154a93b36cde71ecf3f2b3e2f0d855431d5e9af Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:33:56 -0500 Subject: [PATCH 7/8] minor changes related to OEM scenario --- playwright-tests/framework/TestData.ts | 7 ++++--- playwright-tests/pages/PaymentMethodPage.ts | 7 +++++-- playwright-tests/tests/0000__M.test.ts | 4 ++-- playwright-tests/tests/InsuranceOEMAllState.ts | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/playwright-tests/framework/TestData.ts b/playwright-tests/framework/TestData.ts index 9ed648426..1af123c41 100644 --- a/playwright-tests/framework/TestData.ts +++ b/playwright-tests/framework/TestData.ts @@ -3,7 +3,8 @@ import { PaymentMethod } from './localTypes/Enums' export interface ITestData extends base { // Put any project-specific data here. Anything useful to other Safelite projects should be submitted as a pull request to safelite-playwright-core. - paymentMethod: PaymentMethod - isOptedInForTextMessages: boolean - totalAmount?: number + paymentMethod: PaymentMethod, + isOptedInForTextMessages: boolean, + totalAmount?: number, + isForcedOEM?: boolean } \ No newline at end of file diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index cbf6d01c8..015a088ae 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -499,12 +499,15 @@ export class PaymentMethodPage extends BasePage { @step("PaymentMethodPage >> Select Payment Method: ") async handlePaymentMethodPage(testData: Partial) { - const { servicePackage, isRecalVehicle, paymentDetails } = testData; + const { servicePackage, isRecalVehicle, paymentDetails, isForcedOEM } = testData; await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage); await this.validatePaymentDetailsPage(testData); await this.ValidateAfterPayBreakOutSection(); - await this.validateOEMPart(testData.isOEM!); + + if (isForcedOEM){ + await this.validateOEMPart(isForcedOEM); + } // Verify VAPS wipers on backend for standard and premium packages if (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium) { diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index d2d85f4b9..cd8270a8a 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -325,7 +325,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } export async function handleInsuranceFlow(testCase: TestCase) { - const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, isOEM, flow } = testCase.testData; + const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, isForcedOEM, flow } = testCase.testData; // Check if the insurance policy has endorsements const hasEndorsements = endorsements && endorsements.length > 0; @@ -335,7 +335,7 @@ export async function handleInsuranceFlow(testCase: TestCase) { await insuranceCompanyPage.handleInsuranceCompanyPage(testCase.testData); // Handle OEM scenario - if (isOEM === true) { + if (isForcedOEM) { // User should be on heritage problem glass questions page after selecting Allstate on insurance company page let HeritageProblemGlassQuestionsPage = testCase.pages.heritageProblemGlassQuestionsPage; await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); diff --git a/playwright-tests/tests/InsuranceOEMAllState.ts b/playwright-tests/tests/InsuranceOEMAllState.ts index c5fc0dc14..c55939a4c 100644 --- a/playwright-tests/tests/InsuranceOEMAllState.ts +++ b/playwright-tests/tests/InsuranceOEMAllState.ts @@ -20,7 +20,7 @@ const insuranceOEMAllstateData: Partial = { isPolicyFound: false, isPolicyUnverified: true, isRecalVehicle: true, - isOEM: true, + isForcedOEM: true, // Override customer details with specific name and California location customerDetails: { From 37facdfecd992c9e87b51fe9fe20f77368f90f42 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Thu, 20 Nov 2025 11:46:54 -0500 Subject: [PATCH 8/8] CASH-1925 CASH-1925 add new endpoints for work order submit --- src/constants/endpoints.js | 8 ++++++++ src/store/index.js | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 17a8f9a08..c1771b3f5 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -124,6 +124,14 @@ const endpoints = { url: "/order/api/v1/order/save-session/fmg", method: "POST", }, + SubmitOrder: { + url: "/order/api/v1/order/submit-order", + method: "POST", + }, + SubmitOrderForPia: { + url: "/order/api/v1/order/submit-order-for-pia", + method: "POST", + }, LoadSession: { url: "/order/api/v1/order/load-session", method: "POST", diff --git a/src/store/index.js b/src/store/index.js index 6bccc5969..ca1d76f6f 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2363,9 +2363,17 @@ export const actions = { //affiliate cookie save only when submitAfterSave const affiliateCookies = submitAfterSave ? applicationUser.affiliateCookies : null; + let endPoint = endpoints.SaveSession.url; + if (createUnscheduledStatusWorkOrderForPIA) { + endPoint = endpoints.SubmitOrderForPia.url; + } + if (submitAfterSave) { + endPoint = endpoints.SubmitOrder.url; + } + return globalMethods.callHttpClient({ method: endpoints.SaveSession.method, - endpoint: endpoints.SaveSession.url, + endpoint: endPoint, payload: { submitAfterSave: submitAfterSave, userAgent: navigator.userAgent,