From 127e7b19b52805eb5fc407697a4eb218402551ca Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 26 Feb 2025 13:43:14 -0500 Subject: [PATCH 01/26] CASH-152: fixes to avoid null checks on service package question and quote modals --- src/digital-components/modal/modal.vue | 2 +- .../save-progress-modal-question.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue index 72ed56035..74cfc0964 100644 --- a/src/digital-components/modal/modal.vue +++ b/src/digital-components/modal/modal.vue @@ -135,7 +135,7 @@ export default { }, closeModal() { const modal = Modal.getInstance(document.getElementById(this.modalId)); - modal.hide(); + modal?.hide(); this.$emit("isModalOpened", false); }, }, diff --git a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue index ebaa5a8be..76ee7c6a2 100644 --- a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue +++ b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue @@ -56,7 +56,7 @@ export default { }, unmounted() { // remove any modal effects before leaving page (e.g. user hits browser back button) - this.modal.closeModal(); + this.modal?.closeModal(); }, computed: { buttonText() { From 24b473fffd07b87bb35f5806290a63207c469a6a Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 26 Feb 2025 13:44:06 -0500 Subject: [PATCH 02/26] CASH-152: add price placeholder and boolean for cash vs insurance --- src/experiment-components/date-picker-for-pricing-by-day.vue | 4 ++++ src/layouts/schedule/schedule.vue | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/experiment-components/date-picker-for-pricing-by-day.vue b/src/experiment-components/date-picker-for-pricing-by-day.vue index df4c9b140..0ed0a3b79 100644 --- a/src/experiment-components/date-picker-for-pricing-by-day.vue +++ b/src/experiment-components/date-picker-for-pricing-by-day.vue @@ -72,6 +72,9 @@ :id="`${month.monthLabel}-${date.dateNum.toString()}`" /> @@ -156,6 +159,7 @@ export default { type: String, default: "", }, + showPricingByDay: Boolean, }, setup(props) { const uuid = uuidv4(); diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 925683073..c766d95ad 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -27,6 +27,7 @@ class="text-link-small" :customSelectableDatesCallback="getAvailableDatesMethod" validationRules="date-required" + :showPricingByDay="showPricingByDay" @date-clicked="openInshopTimeSlotsModal" /> Date: Thu, 6 Mar 2025 16:10:28 -0500 Subject: [PATCH 03/26] CASH-152: Refactor - move cart- and payment- related helpers out of base mixin --- src/fmg-components/cart/cart.vue | 18 +- src/helpers/cart-and-payment-helper.js | 56 ++++++ src/helpers/cart-and-payment-helper.spec.js | 66 +++++++ .../payment-method/payment-method.spec.js | 4 + src/layouts/payment-method/payment-method.vue | 8 +- .../payment-pia-return/payment-pia-return.vue | 10 +- src/layouts/payment/payment.spec.js | 6 +- src/layouts/payment/payment.vue | 10 +- src/mixins/base-mixin.js | 57 ------ src/mixins/base-mixin.spec.js | 173 ------------------ 10 files changed, 166 insertions(+), 242 deletions(-) create mode 100644 src/helpers/cart-and-payment-helper.js create mode 100644 src/helpers/cart-and-payment-helper.spec.js diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index c4a8e4900..0fce4c4ad 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -173,6 +173,12 @@ import { } from "@/helpers/service-package-helper"; import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper"; import { storeActions } from "@/constants/store-actions"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; // Constants import { partTypeStrings } from "@/constants/part-type-strings"; @@ -1069,8 +1075,8 @@ export default { subTotal() { if (!this.lineItems || this.lineItems.length < 1) return; return this.isInsurance || !this.shouldHideRecalibration - ? baseMixin.methods.getSubTotal(this.lineItems) // calculate with recal (if on order) - : baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal); // calculate without recal + ? getSubTotal(this.lineItems) // calculate with recal (if on order) + : getSubTotal(this.lineItemsWithoutRecal); // calculate without recal }, salesTax() { if (!this.lineItems || this.lineItems.length < 1) return; @@ -1085,15 +1091,15 @@ export default { } return this.isInsurance || !this.shouldHideRecalibration - ? baseMixin.methods.getSalesTax(this.lineItems) // calculate with recal (if on order) - : baseMixin.methods.getSalesTax(this.lineItemsWithoutRecal); // calculated without recal + ? getSalesTax(this.lineItems) // calculate with recal (if on order) + : getSalesTax(this.lineItemsWithoutRecal); // calculated without recal }, amountDue() { if (!this.lineItems || this.lineItems.length < 1) return; if (this.showAsPaid) return 0; return this.isInsurance || !this.shouldHideRecalibration - ? baseMixin.methods.getAmountDue(this.lineItems) // calculate with recal (if on order) - : baseMixin.methods.getAmountDue(this.lineItemsWithoutRecal); // calculated without recal + ? getAmountDue(this.lineItems) // calculate with recal (if on order) + : getAmountDue(this.lineItemsWithoutRecal); // calculated without recal }, amountPaid() { if (!this.showAsPaid) { diff --git a/src/helpers/cart-and-payment-helper.js b/src/helpers/cart-and-payment-helper.js new file mode 100644 index 000000000..d419af8a2 --- /dev/null +++ b/src/helpers/cart-and-payment-helper.js @@ -0,0 +1,56 @@ +import store from "@/store"; +import baseMixin from "@/mixins/base-mixin.js"; + +export function getDisplayAmountDue(lineItems) { + // lineItems s/b an OBJECT here (not flattened) FROM PAYMENT + return getAmountDue(lineItems).toLocaleString("en-US", { + style: "currency", + currency: "USD", + }); +} + +export function getAmountDue(lineItems, includeTax = true) { + // lineItems s/b an OBJECT here (not flattened) FROM CART, PAYMENT PAGES + var amountDue = 0; + if (lineItems?.glassParts) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItems.glassParts, + includeTax + ); + } + + if (lineItems?.supportingItems) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItems.supportingItems, + includeTax + ); + } + + const order = baseMixin?.methods?.hasSubmittedOrder() ? baseMixin?.methods?.getSubmittedOrder() : store.getters.order; + if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) { + amountDue = order.policy.currentDeductible; + } + + if (lineItems?.vaps) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(lineItems.vaps, includeTax); + } + + if (lineItems?.promos) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(lineItems.promos, includeTax); + } + + return ((amountDue * 100) / 100).toFixed(2); +} + +export function getSubTotal(lineItems) { + // this is amountDue without sales tax + // lineItems s/b an OBJECT here (not flattened) USED IN CART + // can be an ARRAY (flattened) FROM QUOTE + return getAmountDue(lineItems, false); +} + +export function getSalesTax(lineItems) { + // this is amountDue minus subTotal + // lineItems s/b an OBJECT here (not flattened) USED IN CART + return (((getAmountDue(lineItems) - getSubTotal(lineItems)) * 100) / 100).toFixed(2); +} diff --git a/src/helpers/cart-and-payment-helper.spec.js b/src/helpers/cart-and-payment-helper.spec.js new file mode 100644 index 000000000..aeebe0431 --- /dev/null +++ b/src/helpers/cart-and-payment-helper.spec.js @@ -0,0 +1,66 @@ +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; + +const lineItems = { + glassParts: [], + supportingItems: [], + vaps: [ + { + cartItemType: "FRONT WIPERS", + description: 'WIPER BLADE STANDARD 18"', + kitPrice: 0, + laborAmount: 0, + partNumber: "WB18", + partType: "FRONT WIPER", + salesTax: 2.0, + sellingPrice: 30.0, + }, + { + cartItemType: "FRONT WIPERS", + description: 'WIPER BLADE STANDARD 26"', + kitPrice: 0, + laborAmount: 0, + partNumber: "WB26", + partType: "FRONT WIPER", + salesTax: 1.0, + sellingPrice: 20.0, + }, + ], + promos: [], +}; + +describe('cart-and-payment-helper', () => { + + describe('getDisplayAmountDue', () => { + it('should return the correct display amount due', () => { + const result = getDisplayAmountDue(lineItems); + expect(result).toBe('53.00'); + }); + }); + + describe('getAmountDue', () => { + it('should return the correct amount due', () => { + const result = getAmountDue(lineItems, true); + expect(result).toBe('53.00'); + }); + }); + + describe('getSubTotal', () => { + it('should return the correct subtotal', () => { + const result = getSubTotal(lineItems); + expect(result).toBe('50.00'); + }); + }); + + describe('getSalesTax', () => { + it('should return the correct sales tax', () => { + const result = getSalesTax(lineItems); + expect(result).toBe('3.00'); + }); + }); +}); + diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js index bf1157f2c..543f4dc78 100644 --- a/src/layouts/payment-method/payment-method.spec.js +++ b/src/layouts/payment-method/payment-method.spec.js @@ -16,6 +16,10 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ submitWorkOrder: jest.fn(), })); +jest.mock("@/helpers/cart-and-payment-helper.js", () => ({ + getAmountDue: jest.fn(), +})); + let piaDisabledFlag = false; describe("payment-method.vue", () => { diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index add9339c1..0b1e46134 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -157,6 +157,12 @@ import { coverageStatus } from "@/constants/insurance"; import { containsLineItemWithPartType } from "@/helpers/service-package-helper"; import { containsRecalParts } from "@/helpers/recal-helper"; import { getBoolFromString } from "@/helpers/boolean-helper"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); @@ -765,7 +771,7 @@ export default { ); }, totalAmountDue() { - return baseMixin.methods.getAmountDue(this.lineItems); + return getAmountDue(this.lineItems); }, isPiaEnabled() { const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE); diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue index a7979f8aa..e581efc5f 100644 --- a/src/layouts/payment-pia-return/payment-pia-return.vue +++ b/src/layouts/payment-pia-return/payment-pia-return.vue @@ -10,11 +10,17 @@ import { queryStrings } from "@/constants/query-strings"; import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { storeActions } from "@/constants/store-actions"; import store from "@/store"; -import baseMixin from "@/mixins/base-mixin.js"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import { Form } from "vee-validate"; import { paymentMethods } from "@/constants/payment-method-constants"; import { routerParams } from "@/router/router-constants/router-params"; +import baseMixin from "@/mixins/base-mixin.js"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; export default { name: "payment-pia-return", @@ -159,7 +165,7 @@ export default { } }, getAmountDue() { - return baseMixin.methods.getAmountDue(store.getters.order.lineItems); + return getAmountDue(store.getters.order.lineItems); }, async saveAndSubmitWorkOrder() { // Final work order submit after returning from PIA. diff --git a/src/layouts/payment/payment.spec.js b/src/layouts/payment/payment.spec.js index 7bc14f229..351ce0f4b 100644 --- a/src/layouts/payment/payment.spec.js +++ b/src/layouts/payment/payment.spec.js @@ -5,10 +5,14 @@ import payment from "@/layouts/payment/payment"; import { shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper"; import store from "@/store"; -import baseMixin from "@/mixins/base-mixin"; import { storeActions } from "@/constants/store-actions"; import { paymentMethods } from "@/constants/payment-method-constants"; +jest.mock("@/helpers/cart-and-payment-helper.js", () => ({ + getAmountDue: jest.fn(), + getDisplayAmountDue: jest.fn(), +})); + // Constants const parts = { windshield: { diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue index ecd2489c8..c11c9033b 100644 --- a/src/layouts/payment/payment.vue +++ b/src/layouts/payment/payment.vue @@ -234,6 +234,12 @@ import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer. import { routerParams } from "@/router/router-constants/router-params"; import { coverageStatus } from "@/constants/insurance"; import { getBoolFromString } from "@/helpers/boolean-helper.js"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; export default { name: "payment", @@ -656,10 +662,10 @@ export default { this.piaLineItems = lineItems.join("||"); }, getAmountDue() { - return baseMixin.methods.getAmountDue(store.getters.order.lineItems); + return getAmountDue(store.getters.order.lineItems); }, getDisplayAmountDue() { - return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems); + return getDisplayAmountDue(store.getters.order.lineItems); }, async paymentFailedPayLater() { this.$refs.loadingModal.isModalVisible = true; diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index 1983933ea..e76ebc79b 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -144,64 +144,7 @@ export default { return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice; } }, - getDisplayAmountDue(lineItems) { - return this.getAmountDue(lineItems).toLocaleString("en-US", { - style: "currency", - currency: "USD", - }); - }, - getAmountDue(lineItems, includeTax = true) { - var amountDue = 0; - if (lineItems?.glassParts) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.glassParts, - includeTax - ); - } - if (lineItems?.supportingItems) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.supportingItems, - includeTax - ); - } - - const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order; - if ( - store.getters.coverageIsVerified && - !order.policy.isNoComp && - !order.policy.isItac - ) { - amountDue = order.policy.currentDeductible; - } - - if (lineItems?.vaps) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.vaps, - includeTax - ); - } - - if (lineItems?.promos) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.promos, - includeTax - ); - } - - return ((amountDue * 100) / 100).toFixed(2); - }, - getSubTotal(lineItems) { - // this is amountDue without sales tax - return this.getAmountDue(lineItems, false); - }, - getSalesTax(lineItems) { - // this is amountDue minus subTotal - return ( - ((this.getAmountDue(lineItems) - this.getSubTotal(lineItems)) * 100) / - 100 - ).toFixed(2); - }, scrollToPageTop() { const container = document.getElementsByClassName("page-container-grouped-styles")[0]; container.scrollTo({ top: 0, left: 0, behavior: "smooth" }); diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index a3493910e..b230506c8 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -145,179 +145,6 @@ describe("baseMixin.js", () => { expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled(); }); - test("getAmountDue for verified deductible should be deductible plus any lineitems", () => { - const mixIn = getMixInInstance({}); - const lineItems = { - glassParts: [], - supportingItems: [], - vaps: [ - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 18"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB18", - partType: "FRONT WIPER", - salesTax: 2.0, - sellingPrice: 30.0, - }, - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 26"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB26", - partType: "FRONT WIPER", - salesTax: 1.0, - sellingPrice: 20.0, - }, - ], - promos: [], - }; - - const payment = { insuranceCoverage: { isVerified: false } }; - const policy = { isNoComp: false, isItac: false, currentDeductible: 250 }; - - store.getters = { - payment: payment, - policy: policy, - order: { - payment: payment, - policy: policy, - }, - hasSubmittedOrder: false, - }; - - var amtDue = mixIn.methods.getAmountDue(lineItems); - - expect(amtDue).toEqual("53.00"); - }); - - test("getAmountDue should function with a submitted order", () => { - const mixIn = getMixInInstance({}); - const lineItems = { - glassParts: [], - supportingItems: [], - vaps: [ - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 18"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB18", - partType: "FRONT WIPER", - salesTax: 2.0, - sellingPrice: 30.0, - }, - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 26"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB26", - partType: "FRONT WIPER", - salesTax: 1.0, - sellingPrice: 20.0, - }, - ], - promos: [], - }; - - const payment = { insuranceCoverage: { isVerified: false } }; - const policy = { isNoComp: false, isItac: false, currentDeductible: 250 }; - - mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(true); - mixIn.methods.getSubmittedOrder = jest.fn().mockReturnValue({ - payment: payment, - policy: policy, - }); - - store.getters = { - payment: {}, - policy: {}, - order: {}, - }; - - var amtDue = mixIn.methods.getAmountDue(lineItems); - - expect(amtDue).toEqual("53.00"); - }); - - test("getAmountDue for cash should sum lineitems", () => { - const mixIn = getMixInInstance({}); - const lineItems = { - glassParts: [ - { - partNumber: "FW06143GTYN", - description: - "solar, heads-up display, third visor frit, soundproofing, rain/light sensor, lane departure warning system", - color: "Green Tint", - partType: "WINDSHIELD", - canSafeliteRecalibrate: true, - requiresRecalibration: true, - requiresCapabilityQuestions: false, - recalibrationType: "STATIC", - childParts: [ - { - partNumber: "RS 101 PAD", - kitPrice: 0, - laborAmount: 0, - salesTax: 5.0, - sellingPrice: 5.0, - }, - ], - id: "73e80976-79be-455a-b4e7-f9b058b61749", - kitPrice: 0, - laborAmount: 60, - salesTax: 20.0, - sellingPrice: 1000.0, - }, - ], - supportingItems: [], - vaps: [ - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 18"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB18", - partType: "FRONT WIPER", - salesTax: 2.0, - sellingPrice: 30.0, - }, - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 26"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB26", - partType: "FRONT WIPER", - salesTax: 1.0, - sellingPrice: 20.0, - }, - ], - promos: [], - }; - - const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } }; - const policy = { isNoComp: false, isItac: false, currentDeductible: 0 }; - - mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(false); - - store.getters = { - payment: payment, - policy: policy, - order: { - payment: payment, - policy: policy, - }, - hasSubmittedOrder: false, - }; - - var amtDue = mixIn.methods.getAmountDue(lineItems); - - expect(amtDue).toEqual("1143.00"); - }); }); function getMixInInstance({ isDispatchSuccess = true }) { From 46555cbaf9fdb0404a7a5b819e466ce5ad7dbcad Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Thu, 6 Mar 2025 16:27:09 -0500 Subject: [PATCH 04/26] CASH-152: Refactor - move cart- and payment- related helpers out of base mixin --- src/fmg-components/cart/cart.vue | 18 +- src/helpers/cart-and-payment-helper.js | 56 ++++++ src/helpers/cart-and-payment-helper.spec.js | 66 +++++++ .../payment-method/payment-method.spec.js | 4 + src/layouts/payment-method/payment-method.vue | 8 +- .../payment-pia-return/payment-pia-return.vue | 10 +- src/layouts/payment/payment.spec.js | 6 +- src/layouts/payment/payment.vue | 10 +- src/mixins/base-mixin.js | 57 ------ src/mixins/base-mixin.spec.js | 173 ------------------ 10 files changed, 166 insertions(+), 242 deletions(-) create mode 100644 src/helpers/cart-and-payment-helper.js create mode 100644 src/helpers/cart-and-payment-helper.spec.js diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index c4a8e4900..0fce4c4ad 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -173,6 +173,12 @@ import { } from "@/helpers/service-package-helper"; import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper"; import { storeActions } from "@/constants/store-actions"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; // Constants import { partTypeStrings } from "@/constants/part-type-strings"; @@ -1069,8 +1075,8 @@ export default { subTotal() { if (!this.lineItems || this.lineItems.length < 1) return; return this.isInsurance || !this.shouldHideRecalibration - ? baseMixin.methods.getSubTotal(this.lineItems) // calculate with recal (if on order) - : baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal); // calculate without recal + ? getSubTotal(this.lineItems) // calculate with recal (if on order) + : getSubTotal(this.lineItemsWithoutRecal); // calculate without recal }, salesTax() { if (!this.lineItems || this.lineItems.length < 1) return; @@ -1085,15 +1091,15 @@ export default { } return this.isInsurance || !this.shouldHideRecalibration - ? baseMixin.methods.getSalesTax(this.lineItems) // calculate with recal (if on order) - : baseMixin.methods.getSalesTax(this.lineItemsWithoutRecal); // calculated without recal + ? getSalesTax(this.lineItems) // calculate with recal (if on order) + : getSalesTax(this.lineItemsWithoutRecal); // calculated without recal }, amountDue() { if (!this.lineItems || this.lineItems.length < 1) return; if (this.showAsPaid) return 0; return this.isInsurance || !this.shouldHideRecalibration - ? baseMixin.methods.getAmountDue(this.lineItems) // calculate with recal (if on order) - : baseMixin.methods.getAmountDue(this.lineItemsWithoutRecal); // calculated without recal + ? getAmountDue(this.lineItems) // calculate with recal (if on order) + : getAmountDue(this.lineItemsWithoutRecal); // calculated without recal }, amountPaid() { if (!this.showAsPaid) { diff --git a/src/helpers/cart-and-payment-helper.js b/src/helpers/cart-and-payment-helper.js new file mode 100644 index 000000000..d419af8a2 --- /dev/null +++ b/src/helpers/cart-and-payment-helper.js @@ -0,0 +1,56 @@ +import store from "@/store"; +import baseMixin from "@/mixins/base-mixin.js"; + +export function getDisplayAmountDue(lineItems) { + // lineItems s/b an OBJECT here (not flattened) FROM PAYMENT + return getAmountDue(lineItems).toLocaleString("en-US", { + style: "currency", + currency: "USD", + }); +} + +export function getAmountDue(lineItems, includeTax = true) { + // lineItems s/b an OBJECT here (not flattened) FROM CART, PAYMENT PAGES + var amountDue = 0; + if (lineItems?.glassParts) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItems.glassParts, + includeTax + ); + } + + if (lineItems?.supportingItems) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItems.supportingItems, + includeTax + ); + } + + const order = baseMixin?.methods?.hasSubmittedOrder() ? baseMixin?.methods?.getSubmittedOrder() : store.getters.order; + if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) { + amountDue = order.policy.currentDeductible; + } + + if (lineItems?.vaps) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(lineItems.vaps, includeTax); + } + + if (lineItems?.promos) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(lineItems.promos, includeTax); + } + + return ((amountDue * 100) / 100).toFixed(2); +} + +export function getSubTotal(lineItems) { + // this is amountDue without sales tax + // lineItems s/b an OBJECT here (not flattened) USED IN CART + // can be an ARRAY (flattened) FROM QUOTE + return getAmountDue(lineItems, false); +} + +export function getSalesTax(lineItems) { + // this is amountDue minus subTotal + // lineItems s/b an OBJECT here (not flattened) USED IN CART + return (((getAmountDue(lineItems) - getSubTotal(lineItems)) * 100) / 100).toFixed(2); +} diff --git a/src/helpers/cart-and-payment-helper.spec.js b/src/helpers/cart-and-payment-helper.spec.js new file mode 100644 index 000000000..aeebe0431 --- /dev/null +++ b/src/helpers/cart-and-payment-helper.spec.js @@ -0,0 +1,66 @@ +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; + +const lineItems = { + glassParts: [], + supportingItems: [], + vaps: [ + { + cartItemType: "FRONT WIPERS", + description: 'WIPER BLADE STANDARD 18"', + kitPrice: 0, + laborAmount: 0, + partNumber: "WB18", + partType: "FRONT WIPER", + salesTax: 2.0, + sellingPrice: 30.0, + }, + { + cartItemType: "FRONT WIPERS", + description: 'WIPER BLADE STANDARD 26"', + kitPrice: 0, + laborAmount: 0, + partNumber: "WB26", + partType: "FRONT WIPER", + salesTax: 1.0, + sellingPrice: 20.0, + }, + ], + promos: [], +}; + +describe('cart-and-payment-helper', () => { + + describe('getDisplayAmountDue', () => { + it('should return the correct display amount due', () => { + const result = getDisplayAmountDue(lineItems); + expect(result).toBe('53.00'); + }); + }); + + describe('getAmountDue', () => { + it('should return the correct amount due', () => { + const result = getAmountDue(lineItems, true); + expect(result).toBe('53.00'); + }); + }); + + describe('getSubTotal', () => { + it('should return the correct subtotal', () => { + const result = getSubTotal(lineItems); + expect(result).toBe('50.00'); + }); + }); + + describe('getSalesTax', () => { + it('should return the correct sales tax', () => { + const result = getSalesTax(lineItems); + expect(result).toBe('3.00'); + }); + }); +}); + diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js index bf1157f2c..543f4dc78 100644 --- a/src/layouts/payment-method/payment-method.spec.js +++ b/src/layouts/payment-method/payment-method.spec.js @@ -16,6 +16,10 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ submitWorkOrder: jest.fn(), })); +jest.mock("@/helpers/cart-and-payment-helper.js", () => ({ + getAmountDue: jest.fn(), +})); + let piaDisabledFlag = false; describe("payment-method.vue", () => { diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index add9339c1..0b1e46134 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -157,6 +157,12 @@ import { coverageStatus } from "@/constants/insurance"; import { containsLineItemWithPartType } from "@/helpers/service-package-helper"; import { containsRecalParts } from "@/helpers/recal-helper"; import { getBoolFromString } from "@/helpers/boolean-helper"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); @@ -765,7 +771,7 @@ export default { ); }, totalAmountDue() { - return baseMixin.methods.getAmountDue(this.lineItems); + return getAmountDue(this.lineItems); }, isPiaEnabled() { const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE); diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue index a7979f8aa..e581efc5f 100644 --- a/src/layouts/payment-pia-return/payment-pia-return.vue +++ b/src/layouts/payment-pia-return/payment-pia-return.vue @@ -10,11 +10,17 @@ import { queryStrings } from "@/constants/query-strings"; import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { storeActions } from "@/constants/store-actions"; import store from "@/store"; -import baseMixin from "@/mixins/base-mixin.js"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import { Form } from "vee-validate"; import { paymentMethods } from "@/constants/payment-method-constants"; import { routerParams } from "@/router/router-constants/router-params"; +import baseMixin from "@/mixins/base-mixin.js"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; export default { name: "payment-pia-return", @@ -159,7 +165,7 @@ export default { } }, getAmountDue() { - return baseMixin.methods.getAmountDue(store.getters.order.lineItems); + return getAmountDue(store.getters.order.lineItems); }, async saveAndSubmitWorkOrder() { // Final work order submit after returning from PIA. diff --git a/src/layouts/payment/payment.spec.js b/src/layouts/payment/payment.spec.js index 7bc14f229..351ce0f4b 100644 --- a/src/layouts/payment/payment.spec.js +++ b/src/layouts/payment/payment.spec.js @@ -5,10 +5,14 @@ import payment from "@/layouts/payment/payment"; import { shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper"; import store from "@/store"; -import baseMixin from "@/mixins/base-mixin"; import { storeActions } from "@/constants/store-actions"; import { paymentMethods } from "@/constants/payment-method-constants"; +jest.mock("@/helpers/cart-and-payment-helper.js", () => ({ + getAmountDue: jest.fn(), + getDisplayAmountDue: jest.fn(), +})); + // Constants const parts = { windshield: { diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue index ecd2489c8..c11c9033b 100644 --- a/src/layouts/payment/payment.vue +++ b/src/layouts/payment/payment.vue @@ -234,6 +234,12 @@ import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer. import { routerParams } from "@/router/router-constants/router-params"; import { coverageStatus } from "@/constants/insurance"; import { getBoolFromString } from "@/helpers/boolean-helper.js"; +import { + getDisplayAmountDue, + getAmountDue, + getSubTotal, + getSalesTax, +} from "@/helpers/cart-and-payment-helper.js"; export default { name: "payment", @@ -656,10 +662,10 @@ export default { this.piaLineItems = lineItems.join("||"); }, getAmountDue() { - return baseMixin.methods.getAmountDue(store.getters.order.lineItems); + return getAmountDue(store.getters.order.lineItems); }, getDisplayAmountDue() { - return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems); + return getDisplayAmountDue(store.getters.order.lineItems); }, async paymentFailedPayLater() { this.$refs.loadingModal.isModalVisible = true; diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index 1983933ea..e76ebc79b 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -144,64 +144,7 @@ export default { return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice; } }, - getDisplayAmountDue(lineItems) { - return this.getAmountDue(lineItems).toLocaleString("en-US", { - style: "currency", - currency: "USD", - }); - }, - getAmountDue(lineItems, includeTax = true) { - var amountDue = 0; - if (lineItems?.glassParts) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.glassParts, - includeTax - ); - } - if (lineItems?.supportingItems) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.supportingItems, - includeTax - ); - } - - const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order; - if ( - store.getters.coverageIsVerified && - !order.policy.isNoComp && - !order.policy.isItac - ) { - amountDue = order.policy.currentDeductible; - } - - if (lineItems?.vaps) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.vaps, - includeTax - ); - } - - if (lineItems?.promos) { - amountDue += this.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.promos, - includeTax - ); - } - - return ((amountDue * 100) / 100).toFixed(2); - }, - getSubTotal(lineItems) { - // this is amountDue without sales tax - return this.getAmountDue(lineItems, false); - }, - getSalesTax(lineItems) { - // this is amountDue minus subTotal - return ( - ((this.getAmountDue(lineItems) - this.getSubTotal(lineItems)) * 100) / - 100 - ).toFixed(2); - }, scrollToPageTop() { const container = document.getElementsByClassName("page-container-grouped-styles")[0]; container.scrollTo({ top: 0, left: 0, behavior: "smooth" }); diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index a3493910e..b230506c8 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -145,179 +145,6 @@ describe("baseMixin.js", () => { expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled(); }); - test("getAmountDue for verified deductible should be deductible plus any lineitems", () => { - const mixIn = getMixInInstance({}); - const lineItems = { - glassParts: [], - supportingItems: [], - vaps: [ - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 18"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB18", - partType: "FRONT WIPER", - salesTax: 2.0, - sellingPrice: 30.0, - }, - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 26"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB26", - partType: "FRONT WIPER", - salesTax: 1.0, - sellingPrice: 20.0, - }, - ], - promos: [], - }; - - const payment = { insuranceCoverage: { isVerified: false } }; - const policy = { isNoComp: false, isItac: false, currentDeductible: 250 }; - - store.getters = { - payment: payment, - policy: policy, - order: { - payment: payment, - policy: policy, - }, - hasSubmittedOrder: false, - }; - - var amtDue = mixIn.methods.getAmountDue(lineItems); - - expect(amtDue).toEqual("53.00"); - }); - - test("getAmountDue should function with a submitted order", () => { - const mixIn = getMixInInstance({}); - const lineItems = { - glassParts: [], - supportingItems: [], - vaps: [ - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 18"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB18", - partType: "FRONT WIPER", - salesTax: 2.0, - sellingPrice: 30.0, - }, - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 26"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB26", - partType: "FRONT WIPER", - salesTax: 1.0, - sellingPrice: 20.0, - }, - ], - promos: [], - }; - - const payment = { insuranceCoverage: { isVerified: false } }; - const policy = { isNoComp: false, isItac: false, currentDeductible: 250 }; - - mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(true); - mixIn.methods.getSubmittedOrder = jest.fn().mockReturnValue({ - payment: payment, - policy: policy, - }); - - store.getters = { - payment: {}, - policy: {}, - order: {}, - }; - - var amtDue = mixIn.methods.getAmountDue(lineItems); - - expect(amtDue).toEqual("53.00"); - }); - - test("getAmountDue for cash should sum lineitems", () => { - const mixIn = getMixInInstance({}); - const lineItems = { - glassParts: [ - { - partNumber: "FW06143GTYN", - description: - "solar, heads-up display, third visor frit, soundproofing, rain/light sensor, lane departure warning system", - color: "Green Tint", - partType: "WINDSHIELD", - canSafeliteRecalibrate: true, - requiresRecalibration: true, - requiresCapabilityQuestions: false, - recalibrationType: "STATIC", - childParts: [ - { - partNumber: "RS 101 PAD", - kitPrice: 0, - laborAmount: 0, - salesTax: 5.0, - sellingPrice: 5.0, - }, - ], - id: "73e80976-79be-455a-b4e7-f9b058b61749", - kitPrice: 0, - laborAmount: 60, - salesTax: 20.0, - sellingPrice: 1000.0, - }, - ], - supportingItems: [], - vaps: [ - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 18"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB18", - partType: "FRONT WIPER", - salesTax: 2.0, - sellingPrice: 30.0, - }, - { - cartItemType: "FRONT WIPERS", - description: 'WIPER BLADE STANDARD 26"', - kitPrice: 0, - laborAmount: 0, - partNumber: "WB26", - partType: "FRONT WIPER", - salesTax: 1.0, - sellingPrice: 20.0, - }, - ], - promos: [], - }; - - const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } }; - const policy = { isNoComp: false, isItac: false, currentDeductible: 0 }; - - mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(false); - - store.getters = { - payment: payment, - policy: policy, - order: { - payment: payment, - policy: policy, - }, - hasSubmittedOrder: false, - }; - - var amtDue = mixIn.methods.getAmountDue(lineItems); - - expect(amtDue).toEqual("1143.00"); - }); }); function getMixInInstance({ isDispatchSuccess = true }) { From e4fee3c8abe880c2e81c95207215f5829f2fa9ef Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Tue, 11 Mar 2025 15:15:45 -0400 Subject: [PATCH 05/26] CASH-152: refactoring changes --- src/fmg-components/cart/cart.vue | 2 +- ...nd-payment-helper.js => pricing-helper.js} | 14 ++++++++-- ...-helper.spec.js => pricing-helper.spec.js} | 28 +++++++++---------- src/layouts/payment-method/payment-method.vue | 2 +- .../payment-pia-return/payment-pia-return.vue | 2 +- src/layouts/payment/payment.vue | 2 +- 6 files changed, 28 insertions(+), 22 deletions(-) rename src/helpers/{cart-and-payment-helper.js => pricing-helper.js} (85%) rename src/helpers/{cart-and-payment-helper.spec.js => pricing-helper.spec.js} (65%) diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 7a6386eb5..b6525fa67 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -178,7 +178,7 @@ import { getAmountDue, getSubTotal, getSalesTax, -} from "@/helpers/cart-and-payment-helper.js"; +} from "@/helpers/pricing-helper.js"; // Constants import { partTypeStrings } from "@/constants/part-type-strings"; diff --git a/src/helpers/cart-and-payment-helper.js b/src/helpers/pricing-helper.js similarity index 85% rename from src/helpers/cart-and-payment-helper.js rename to src/helpers/pricing-helper.js index d419af8a2..a386ed231 100644 --- a/src/helpers/cart-and-payment-helper.js +++ b/src/helpers/pricing-helper.js @@ -26,17 +26,25 @@ export function getAmountDue(lineItems, includeTax = true) { ); } - const order = baseMixin?.methods?.hasSubmittedOrder() ? baseMixin?.methods?.getSubmittedOrder() : store.getters.order; + const order = baseMixin?.methods?.hasSubmittedOrder() + ? baseMixin?.methods?.getSubmittedOrder() + : store.getters.order; if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) { amountDue = order.policy.currentDeductible; } if (lineItems?.vaps) { - amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(lineItems.vaps, includeTax); + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItems.vaps, + includeTax + ); } if (lineItems?.promos) { - amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(lineItems.promos, includeTax); + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItems.promos, + includeTax + ); } return ((amountDue * 100) / 100).toFixed(2); diff --git a/src/helpers/cart-and-payment-helper.spec.js b/src/helpers/pricing-helper.spec.js similarity index 65% rename from src/helpers/cart-and-payment-helper.spec.js rename to src/helpers/pricing-helper.spec.js index aeebe0431..6076343f5 100644 --- a/src/helpers/cart-and-payment-helper.spec.js +++ b/src/helpers/pricing-helper.spec.js @@ -33,34 +33,32 @@ const lineItems = { promos: [], }; -describe('cart-and-payment-helper', () => { - - describe('getDisplayAmountDue', () => { - it('should return the correct display amount due', () => { +describe("cart-and-payment-helper", () => { + describe("getDisplayAmountDue", () => { + it("should return the correct display amount due", () => { const result = getDisplayAmountDue(lineItems); - expect(result).toBe('53.00'); + expect(result).toBe("53.00"); }); }); - describe('getAmountDue', () => { - it('should return the correct amount due', () => { + describe("getAmountDue", () => { + it("should return the correct amount due", () => { const result = getAmountDue(lineItems, true); - expect(result).toBe('53.00'); + expect(result).toBe("53.00"); }); }); - describe('getSubTotal', () => { - it('should return the correct subtotal', () => { + describe("getSubTotal", () => { + it("should return the correct subtotal", () => { const result = getSubTotal(lineItems); - expect(result).toBe('50.00'); + expect(result).toBe("50.00"); }); }); - describe('getSalesTax', () => { - it('should return the correct sales tax', () => { + describe("getSalesTax", () => { + it("should return the correct sales tax", () => { const result = getSalesTax(lineItems); - expect(result).toBe('3.00'); + expect(result).toBe("3.00"); }); }); }); - diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 1d1e34b27..33e070a93 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -163,7 +163,7 @@ import { getAmountDue, getSubTotal, getSalesTax, -} from "@/helpers/cart-and-payment-helper.js"; +} from "@/helpers/pricing-helper.js"; defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue index cd68ada0f..b9f19f64a 100644 --- a/src/layouts/payment-pia-return/payment-pia-return.vue +++ b/src/layouts/payment-pia-return/payment-pia-return.vue @@ -20,7 +20,7 @@ import { getAmountDue, getSubTotal, getSalesTax, -} from "@/helpers/cart-and-payment-helper.js"; +} from "@/helpers/pricing-helper.js"; // iframeResizer IS loaded into the page and necessary for the package to // to auto scale the iFrame this page is loaded in // Do not remove despite showing as "unused" CASH-309 diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue index c11c9033b..abf06927b 100644 --- a/src/layouts/payment/payment.vue +++ b/src/layouts/payment/payment.vue @@ -239,7 +239,7 @@ import { getAmountDue, getSubTotal, getSalesTax, -} from "@/helpers/cart-and-payment-helper.js"; +} from "@/helpers/pricing-helper.js"; export default { name: "payment", From a98a45c76970d2333c34a4c87df6f0d09bbba60e Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 12 Mar 2025 10:16:56 -0400 Subject: [PATCH 06/26] CASH-152: refactor/improve pricing-helper --- src/helpers/pricing-helper.js | 58 ++++++++++++++++------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index a386ed231..7508ff0ca 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -1,48 +1,47 @@ import store from "@/store"; import baseMixin from "@/mixins/base-mixin.js"; -export function getDisplayAmountDue(lineItems) { - // lineItems s/b an OBJECT here (not flattened) FROM PAYMENT - return getAmountDue(lineItems).toLocaleString("en-US", { +export function getDisplayAmountDue(lineItemsObject, includeTax = true) { + return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", { style: "currency", currency: "USD", }); } -export function getAmountDue(lineItems, includeTax = true) { - // lineItems s/b an OBJECT here (not flattened) FROM CART, PAYMENT PAGES - var amountDue = 0; - if (lineItems?.glassParts) { - amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.glassParts, - includeTax - ); - } - - if (lineItems?.supportingItems) { - amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.supportingItems, - includeTax - ); - } - +export function getAmountDue(lineItemsObject, includeTax = true) { + let amountDue = 0; const order = baseMixin?.methods?.hasSubmittedOrder() ? baseMixin?.methods?.getSubmittedOrder() : store.getters.order; + + if (lineItemsObject?.glassParts) { + amountDue += baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts( + lineItemsObject.glassParts, + includeTax + ); + } + + if (lineItemsObject?.supportingItems) { + amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( + lineItemsObject.supportingItems, + includeTax + ); + } + if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) { amountDue = order.policy.currentDeductible; } - if (lineItems?.vaps) { + if (lineItemsObject?.vaps) { amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.vaps, + lineItemsObject.vaps, includeTax ); } - if (lineItems?.promos) { + if (lineItemsObject?.promos) { amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( - lineItems.promos, + lineItemsObject.promos, includeTax ); } @@ -50,15 +49,12 @@ export function getAmountDue(lineItems, includeTax = true) { return ((amountDue * 100) / 100).toFixed(2); } -export function getSubTotal(lineItems) { +export function getSubTotal(lineItemsObject) { // this is amountDue without sales tax - // lineItems s/b an OBJECT here (not flattened) USED IN CART - // can be an ARRAY (flattened) FROM QUOTE - return getAmountDue(lineItems, false); + return getAmountDue(lineItemsObject, false); } -export function getSalesTax(lineItems) { +export function getSalesTax(lineItemsObject) { // this is amountDue minus subTotal - // lineItems s/b an OBJECT here (not flattened) USED IN CART - return (((getAmountDue(lineItems) - getSubTotal(lineItems)) * 100) / 100).toFixed(2); + return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(2); } From d74c50c53ef75e84d0679825baa6692d000d14b2 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 12 Mar 2025 10:21:04 -0400 Subject: [PATCH 07/26] CASH-152: refactor/improve recal-helper --- src/helpers/recal-helper.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/helpers/recal-helper.js b/src/helpers/recal-helper.js index d43da5d34..2794bbcf6 100644 --- a/src/helpers/recal-helper.js +++ b/src/helpers/recal-helper.js @@ -35,10 +35,9 @@ export function containsRecalParts(lineItems) { } } -export function getItemsWithoutRecalParts(lineItems) { - const copy = deepClone(lineItems); - - const firstLevelFiltered = copy.filter((li) => !isRecalPart(li)); +export function getItemsWithoutRecalParts(lineItemsArray) { + if (!lineItemsArray || !Array.isArray(lineItemsArray)) return null; + const firstLevelFiltered = deepClone(lineItemsArray).filter((li) => !isRecalPart(li)); const childrenFiltered = firstLevelFiltered.map((li) => { if (li.childParts && li.childParts.length > 0) { From 58f2efcedb76f814e8cffbbbdd27396d5389cb27 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 12 Mar 2025 10:28:15 -0400 Subject: [PATCH 08/26] CASH-152: refactor/improve service-package-helper --- src/fmg-components/cart/cart.vue | 4 ++-- src/helpers/service-package-helper.js | 2 +- .../service-package-question/service-package-question.vue | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index b6525fa67..f569c11d4 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -169,7 +169,7 @@ import { deepClone } from "@/helpers/object-helper"; import { getHighestFullySatisfiedTier, getPackageContents, - getDiscountPackageName, + getDiscountedPackageName, } from "@/helpers/service-package-helper"; import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper"; import { storeActions } from "@/constants/store-actions"; @@ -473,7 +473,7 @@ export default { const discountServicePackage = experimentMixin.methods.getSettingValue( experimentSettings.PROMO_ON_PACKAGE ); - return getDiscountPackageName(discountServicePackage); + return getDiscountedPackageName(discountServicePackage); }, servicePackageTitleWidget() { const servicePackageNames = this.getCmsContent( diff --git a/src/helpers/service-package-helper.js b/src/helpers/service-package-helper.js index 6ff937564..42ef05c8b 100644 --- a/src/helpers/service-package-helper.js +++ b/src/helpers/service-package-helper.js @@ -239,7 +239,7 @@ export function getPackageNameByType(packageType) { return package_names[packageType] || null; } -export function getDiscountPackageName(discountPackage) { +export function getDiscountedPackageName(discountPackage) { const package_names = { ECON: packageNames.TIER_ONE, STANDARD: packageNames.TIER_TWO, 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 ea40b2780..830a296d2 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -30,7 +30,7 @@ import { containsLineItemWithPartType, findLineItemsWithPartType, getPackageNameByType, - getDiscountPackageName, + getDiscountedPackageName, } from "@/helpers/service-package-helper"; import { getPromosThatMatchLineItemsOnOrder, From e75db991fe67b9ed28226d38079988c15b061ff8 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 12 Mar 2025 10:28:52 -0400 Subject: [PATCH 09/26] CASH-152: name update for quote for clarity --- src/layouts/quote/quote.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index d32054108..2e35a1d46 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -484,7 +484,7 @@ export default { }; }, mounted() { - this.attachCustomEvents(); + this.attachCustomEventsForAnalytics(); }, computed: { lineItemsCloneForWatcher() { @@ -670,7 +670,7 @@ export default { ); } }, - attachCustomEvents() { + attachCustomEventsForAnalytics() { this.prependActionToMethod(this, this.forwardButtonAction, () => { if (!this.isInsuranceSelected && this.lineItems.vaps?.length == 0) { const tierOnePrice = From 51f0225e1999dc09c75d242db1f6dd51215c6801 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 12 Mar 2025 11:11:37 -0400 Subject: [PATCH 10/26] CASH-152: refactor/improve some base-mixin helpers --- src/mixins/base-mixin.js | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index e76ebc79b..78bde1e1d 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -110,6 +110,22 @@ export default { }); return filteredLineItems; }, + filterOutCertainPartTypesOrNumbers(lineItemsArray, {partTypesToRemove = [], partNumbersToRemove = []}) { + // lineItems s/b an ARRAY here + if (!Array.isArray(lineItemsArray)) return; + + partTypesToRemove.forEach((partType) => { + lineItemsArray = lineItemsArray.filter((item) => { + return !item?.partType?.includes(partType); + }); + }); + partNumbersToRemove.forEach((partNumber) => { + lineItemsArray = lineItemsArray.filter((item) => { + return !item?.partNumber?.includes(partNumber); + }); + }); + return lineItemsArray; + }, filterOutServicePackageDiscountPart(lineItems) { const filteredLineItems = lineItems?.filter((item) => { return !item?.partType?.includes(partTypeStrings.SERVICE_PACKAGE_DISCOUNT); @@ -133,15 +149,20 @@ export default { return totalPrice; }, getTotalLineItemPrice(lineItem, includeTax) { + const kitPrice = lineItem.kitPrice ?? 0; + const laborAmount = lineItem.laborAmount ?? 0; + const sellingPrice = lineItem.sellingPrice ?? 0; + const salesTax = lineItem.salesTax ?? 0; + if (includeTax) { return ( - lineItem.kitPrice + - lineItem.laborAmount + - lineItem.sellingPrice + - lineItem.salesTax + kitPrice + + laborAmount + + sellingPrice + + salesTax ); } else { - return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice; + return kitPrice + laborAmount + sellingPrice; } }, From 2bd4d42f572cdb0affa1351f525060f4f7d066e7 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 12 Mar 2025 11:27:09 -0400 Subject: [PATCH 11/26] CASH-152: get lowest selected package price on Schedule --- src/layouts/schedule/schedule.vue | 35 ++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index c766d95ad..37aeb98dd 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -110,6 +110,10 @@ import { required } from "@/helpers/validation-rules"; import store from "@/store"; import experimentMixin from "@/mixins/experiment-mixin.js"; import { experimentSettings } from "@/constants/experiments"; +import { getDisplayAmountDue } from "@/helpers/pricing-helper.js"; +import { getItemsWithoutRecalParts } from "@/helpers/recal-helper"; +import { partNumberStrings } from "@/constants/part-number-strings"; +import { deepClone } from "@/helpers/object-helper"; // DEFINE VALIDATION RULES defineRule("date-required", required(errorMessages.DATE_REQUIRED)); @@ -333,7 +337,36 @@ export default { ); }, showPricingByDay() { - return !this.$store.getters.payment.isInsurance; + return !this.$store.getters.payment.isInsurance && this.isPricingByDayExperiment; + }, + lineItemsToBePriced() { + const lineItems = deepClone(store.getters.order.lineItems); + + const glassParts = (this.isRecalibrationOnOrder && this.shouldHideRecalibration) ? + getItemsWithoutRecalParts(lineItems.glassParts) : + lineItems.glassParts ?? []; + + const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(lineItems.supportingItems, {partNumbersToRemove: [partNumberStrings.RECYCLE_FEE]}); + + return { + glassParts: glassParts, + supportingItems: supportingItemsWithoutFees, + vaps: lineItems.vaps ?? [], + promos: lineItems.promos ?? [], + }; + }, + lowestPrice() { + return getDisplayAmountDue(this.lineItemsToBePriced, false); // passing false here so IncludeTax is false + }, + isRecalibrationOnOrder() { + return store.getters.isRecalibrationOnOrder; + }, + shouldHideRecalibration() { + return ( + experimentMixin.methods + .getSettingValue(experimentSettings.RECAL_PRICE_REMOVE) + ?.toLowerCase() === "true" && this.isRecalibrationOnOrder + ); }, }, methods: { From c3538ce36e3ed9c2c3cd7e8cab7bb63a37ab7365 Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Fri, 14 Mar 2025 16:13:21 -0400 Subject: [PATCH 12/26] CASH-185 CASH-185 added success message to display when user clicks checkbox --- .../time-slot-modal-question.vue | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue index 40c3926ba..20b9322cb 100644 --- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue +++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue @@ -39,6 +39,13 @@ v-model="waitListRequested" @click="waitListChecked" /> +
+ + +
{ + this.scrollToSuccessMessage(); + }); + } + }, modelValue: { handler(newValue) { this.handleChange(newValue); @@ -583,10 +609,40 @@ export default { margin-bottom: -8px; .ui-checkbox { padding-left: 0px; + input{ + border-radius: 4px; + box-shadow: 0px 1px 4px 0px #00000033; + } } .form-check-input { margin-left: 0px; } } + .waitlist-success { + background-color: #ECF5E9; + display: flex; + align-items: flex-start; + border: 1px solid #0C7E47; + border-radius: 5px; + font-size: $h5-font-size; + padding: 12px 16px; + margin-bottom: -8px; + height: 78px; + width:326px; + .success-text{ + margin-left:8px; + strong{ + + font-weight: 600; + letter-spacing: 0%; + } + } + .success-image{ + margin-top: 4.5px; + width: 16px; + height: 16px; + color:#0C7E47 + } + } } From b15bf1be6f0ee2e0036010f83170e9ab90f9b790 Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Fri, 14 Mar 2025 16:30:09 -0400 Subject: [PATCH 13/26] CASH-185 CASH-185 formatting --- .../time-slot-modal-question.vue | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue index 20b9322cb..c6d08069c 100644 --- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue +++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue @@ -39,11 +39,11 @@ v-model="waitListRequested" @click="waitListChecked" />
-
- +
+
Date: Mon, 17 Mar 2025 10:07:29 -0400 Subject: [PATCH 14/26] CASH-152: massive commit to implement pricing by day on schedule page --- src/constants/part-number-strings.js | 2 + src/constants/schedule-constants.js | 10 +- src/constants/store-actions.js | 1 + .../date-picker/mixins/constants.js | 4 +- .../date-picker-for-pricing-by-day.vue | 46 ++-- src/helpers/pricing-helper.js | 31 +++ src/helpers/pricing-helper.spec.js | 4 +- src/helpers/service-package-helper.js | 90 ++++++++ .../payment-method/payment-method.spec.js | 2 +- src/layouts/payment/payment.spec.js | 2 +- src/layouts/quote/quote.vue | 1 + src/layouts/schedule/schedule.spec.js | 4 + src/layouts/schedule/schedule.vue | 215 ++++++++++++------ src/mixins/base-mixin.spec.js | 1 - src/store/index.js | 12 + 15 files changed, 327 insertions(+), 98 deletions(-) diff --git a/src/constants/part-number-strings.js b/src/constants/part-number-strings.js index d21ab83ac..d373ba016 100644 --- a/src/constants/part-number-strings.js +++ b/src/constants/part-number-strings.js @@ -3,6 +3,8 @@ const partNumberStrings = { MOBILE_STATIC_RECAL_FEE: "RECAL MOBILE", MOBILE_DUAL_RECAL_FEE: "RECAL MOBILEDUAL", DONATION: "DONATION", + // Fees + RECYCLE_FEE: "RECYCLE FEE", }; export { partNumberStrings }; diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js index 5bb784efb..b7eaa4882 100644 --- a/src/constants/schedule-constants.js +++ b/src/constants/schedule-constants.js @@ -8,9 +8,17 @@ const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM"; const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; +const PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE = "DISC CASHSAVE20"; + const RouteCodeFlags = { ALL_DAY_DROP_OFF: "ALL DAY DROP OFF", OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF", }; -export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE, RouteCodeFlags }; +export { + AppointmentTypeStrings, + PREMIUM_TIME_SLOT_ID_FLAG, + PREMIUM_FEE_PART_TYPE, + PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE, + RouteCodeFlags, +}; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 6c6ba62b7..af68c8997 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -30,6 +30,7 @@ const storeActions = { GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOBILE_FEE_PART: "getMobileFeePart", + GET_PRICING_BY_DAY_UPCHARGE_PART: "getPricingByDayUpchargePart", GET_SERVICE_PACKAGE_DISCOUNT_PART: "getServicePackageDiscountPart", GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails", GET_SHOP_TIME_SLOTS: "getShopTimeSlots", diff --git a/src/digital-components/date-picker/mixins/constants.js b/src/digital-components/date-picker/mixins/constants.js index f37e312f5..f9c9f6c9f 100644 --- a/src/digital-components/date-picker/mixins/constants.js +++ b/src/digital-components/date-picker/mixins/constants.js @@ -23,4 +23,6 @@ const MONTHS_OF_YEAR = [ const DAYS_OF_WEEK = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; -export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK }; +const PREMIUM_DAY_INDEXES = [1, 5, 6]; // assign Monday, Friday, Saturday to be premium days + +export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK, PREMIUM_DAY_INDEXES }; diff --git a/src/experiment-components/date-picker-for-pricing-by-day.vue b/src/experiment-components/date-picker-for-pricing-by-day.vue index 0ed0a3b79..7d63499ef 100644 --- a/src/experiment-components/date-picker-for-pricing-by-day.vue +++ b/src/experiment-components/date-picker-for-pricing-by-day.vue @@ -66,15 +66,15 @@ type="radio" name="day-of-month" v-model="selectedDate" - @click="fireDateSelectedEvent" - @keypress.enter="fireDateSelectedEvent" + @click="fireDateSelectedEvent($event, date)" + @keypress.enter="fireDateSelectedEvent($event, date)" :value="date.inputValue" :id="`${month.monthLabel}-${date.dateNum.toString()}`" />
@@ -106,6 +106,7 @@ import { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, + PREMIUM_DAY_INDEXES, } from "@/digital-components/date-picker/mixins/constants"; import { selectableDaysOptions, @@ -160,6 +161,8 @@ export default { default: "", }, showPricingByDay: Boolean, + baseDayPrice: Number, + pricingByDayUpcharge: Number, }, setup(props) { const uuid = uuidv4(); @@ -224,17 +227,12 @@ export default { initializeComponent(initialData) { this.setCalendarData(initialData); }, - fireDateSelectedEvent(event) { + fireDateSelectedEvent(event, date) { // Ignore if arrow key selected radioButton if (event.screenX === 0 && event.screenY === 0) { return; } - this.$emit("date-clicked"); - }, - fireDateSelectedEvent2(date) { - console.log("date: ", date); - this.selectedDate = date; - this.$emit("date-clicked"); + this.$emit("date-clicked", date); }, getWeekStartDate(dateString) { const date = convertDateStringToDate(dateString); @@ -433,6 +431,8 @@ export default { hideSomeDaysForInitialView: hideSomeDaysForInitialView, hideSecondMonth: hideSecondMonth, preSelectedDate: config.preSelectedDate, + baseDayPrice: config.baseDayPrice, + pricingByDayUpcharge: config.pricingByDayUpcharge, }; return initialData; }); @@ -457,6 +457,8 @@ export default { initialViewEndDate: config.initialViewEndDate, hideSecondMonth: hideSecondMonth, preSelectedDate: config.preSelectedDate, + baseDayPrice: config.baseDayPrice, + pricingByDayUpcharge: config.pricingByDayUpcharge, }; if (direction === "future") { // first 0, then 1 @@ -590,6 +592,17 @@ export default { ("0" + monthNum).slice(-2) + "-" + ("0" + i).slice(-2); + const dayIndex = convertDateStringToDate(dateString).getDay(); + const isSelectable = + this.selectableDatesData.findIndex((date) => date.date === dateString) > -1 + ? true + : false; + + const isPricingByDayUpchargeDay = PREMIUM_DAY_INDEXES.includes(dayIndex); + let displayPrice = isPricingByDayUpchargeDay + ? options.baseDayPrice + options.pricingByDayUpcharge + : options.baseDayPrice; + const priceString = "$" + displayPrice; if (offset === 0 && i === this.todayDateNum) { dayClasses += " current-day"; @@ -600,7 +613,7 @@ export default { if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") { dayClasses += " unavailable-day"; } - if (convertDateStringToDate(dateString).getDay() === 0) { + if (dayIndex === 0) { dayClasses += " sunday"; } if ( @@ -615,10 +628,9 @@ export default { dateNum: i, dayClasses: dayClasses, inputValue: dateString, - isSelectable: - this.selectableDatesData.findIndex((date) => date.date === dateString) > -1 - ? true - : false, + priceString: priceString, + isSelectable: isSelectable, + isPricingByDayUpchargeDay: isPricingByDayUpchargeDay, }; dates.push(dateObject); } diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index 7508ff0ca..b9262f95d 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -1,4 +1,5 @@ import store from "@/store"; +import { storeActions } from "@/constants/store-actions"; import baseMixin from "@/mixins/base-mixin.js"; export function getDisplayAmountDue(lineItemsObject, includeTax = true) { @@ -58,3 +59,33 @@ export function getSalesTax(lineItemsObject) { // this is amountDue minus subTotal return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(2); } + +export async function getPriceUpchargeByDayPart(pageNameToLog) { + // Get the Pricing By Day Part + const basePriceByDayPart = await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.GET_PRICING_BY_DAY_UPCHARGE_PART, + null, + pageNameToLog, + false + ); + + if ( + basePriceByDayPart?.data === null || + basePriceByDayPart?.data === undefined || + basePriceByDayPart?.data === "" + ) { + return null; + } + + // Get the Pricing By Day Base Part price + const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, + { + availableLineItems: [basePriceByDayPart?.data], + }, + pageNameToLog, + false + ); + + return pricingResults[0]; +} diff --git a/src/helpers/pricing-helper.spec.js b/src/helpers/pricing-helper.spec.js index 6076343f5..8a49bb5ae 100644 --- a/src/helpers/pricing-helper.spec.js +++ b/src/helpers/pricing-helper.spec.js @@ -3,7 +3,7 @@ import { getAmountDue, getSubTotal, getSalesTax, -} from "@/helpers/cart-and-payment-helper.js"; +} from "@/helpers/pricing-helper.js"; const lineItems = { glassParts: [], @@ -33,7 +33,7 @@ const lineItems = { promos: [], }; -describe("cart-and-payment-helper", () => { +describe("pricing-helper", () => { describe("getDisplayAmountDue", () => { it("should return the correct display amount due", () => { const result = getDisplayAmountDue(lineItems); diff --git a/src/helpers/service-package-helper.js b/src/helpers/service-package-helper.js index 42ef05c8b..b6144efc2 100644 --- a/src/helpers/service-package-helper.js +++ b/src/helpers/service-package-helper.js @@ -1,6 +1,12 @@ import { partTypeStrings } from "@/constants/part-type-strings"; import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected"; import { packageNames } from "@/constants/package-names"; +import baseMixin from "@/mixins/base-mixin.js"; +import { getItemsWithoutRecalParts } from "@/helpers/recal-helper"; +import { + getPromosThatMatchLineItemsOnOrder, + removeVapsPromosFromPromoArray, +} from "@/helpers/promotions-helper"; export function containsLineItemWithPartType(typeToFind, itemsToSearch) { const partTypeMatches = findLineItemsWithPartType(typeToFind, itemsToSearch); @@ -248,6 +254,90 @@ export function getDiscountedPackageName(discountPackage) { return package_names[discountPackage] || null; } +// export function getDiscountedPackagePriceString2(packageName, servicePackageDiscount) { +// const formattedPriceFloat = parseFloat( +// this.getPackagePrice(packageName, { +// discountedPrice: true, +// servicePackageDiscount, +// }) +// ).toFixed(2); + +// return "$" + formattedPriceFloat; +// } + +// export function getPackagePrice( +// packageName, +// { discountedPrice = false, servicePackageDiscount = false } +// ) { +// let lineItemsToPrice = [...this.shallowLineItems]; + +// if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) { +// lineItemsToPrice = getItemsWithoutRecalParts(lineItemsToPrice); +// } + +// //remove service package discount part +// if (this.isServicePackageDiscountOnOrder) { +// lineItemsToPrice = lineItemsToPrice.filter((item) => { +// return item.partType != this.servicePackageDiscountParts[0].partType; +// }); +// } + +// if (discountedPrice) { +// lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos)); +// } + +// if (servicePackageDiscount) { +// lineItemsToPrice.push(...this.servicePackageDiscountParts); +// } +// let priceFloat = baseMixin.methods.getTierOnePackagePrice( +// baseMixin.methods.filterOutFees(lineItemsToPrice) +// ); + +// priceFloat += this.getVapsPrice(packageName, discountedPrice); + +// // console.log("SPHelper getPackagePrice()... priceFloat: ", priceFloat) +// // console.log(" ") + +// return priceFloat; +// } + +// export function getVapsPrice(packageName, applyPromoDiscounts = false) { +// const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName); +// let price = 0; + +// vapsItems.forEach((item) => { +// price += baseMixin.methods.getTotalLineItemPrice(item); +// }); + +// if (applyPromoDiscounts && this.activePromos) { +// const relevantPromos = getPromosThatMatchLineItemsOnOrder(this.lineItems.promos, vapsItems); +// relevantPromos.forEach((promo) => { +// price += baseMixin.methods.getTotalLineItemPrice(promo); +// }); +// } + +// return price; +// } + +// export function getVapsLineItemsForSelectedPackage(packageName) { +// const packageContentTypes = getPackageContents( +// this.glassToReplace, +// this.shallowLineItems, +// this.isRepair, +// packageName +// ); + +// let vapsLineItemsForSelectedPackage = []; + +// packageContentTypes.forEach((vapType) => { +// vapsLineItemsForSelectedPackage.push( +// ...findLineItemsWithPartType(vapType, this.shallowLineItems) +// ); +// }); + +// return vapsLineItemsForSelectedPackage; +// } + function maxTier(tierA, tierB) { if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) { return packageNames.TIER_THREE; diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js index 543f4dc78..013916af0 100644 --- a/src/layouts/payment-method/payment-method.spec.js +++ b/src/layouts/payment-method/payment-method.spec.js @@ -16,7 +16,7 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ submitWorkOrder: jest.fn(), })); -jest.mock("@/helpers/cart-and-payment-helper.js", () => ({ +jest.mock("@/helpers/pricing-helper.js", () => ({ getAmountDue: jest.fn(), })); diff --git a/src/layouts/payment/payment.spec.js b/src/layouts/payment/payment.spec.js index 351ce0f4b..05ad6dbc9 100644 --- a/src/layouts/payment/payment.spec.js +++ b/src/layouts/payment/payment.spec.js @@ -8,7 +8,7 @@ import store from "@/store"; import { storeActions } from "@/constants/store-actions"; import { paymentMethods } from "@/constants/payment-method-constants"; -jest.mock("@/helpers/cart-and-payment-helper.js", () => ({ +jest.mock("@/helpers/pricing-helper.js", () => ({ getAmountDue: jest.fn(), getDisplayAmountDue: jest.fn(), })); diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 2e35a1d46..615662013 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -266,6 +266,7 @@ export default { false ); + // This will remove any servicePackageDiscount item from lineItems.supportingItems baseMixin.methods.dispatchStoreAction( storeActions.SAVE_SUPPORTING_ITEMS, resultMap.supportingItems, diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js index 48e6e8afa..95ad63a40 100644 --- a/src/layouts/schedule/schedule.spec.js +++ b/src/layouts/schedule/schedule.spec.js @@ -130,6 +130,10 @@ jest.mock("@/mixins/base-mixin.js", () => ({ ]); } }), + filterOutCertainPartTypesOrNumbers: jest.fn(), + hasSubmittedOrder: jest.fn(), + getTotalPriceOfAllLineItemsAndChildParts: jest.fn(), + getTotalLineItemPrice: jest.fn(), }, })); diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 37aeb98dd..d1d754b5c 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -19,18 +19,20 @@ + @date-clicked="handleDateClicked" /> item.partType == PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE + ); + if (pricingByDayUpchargeFeeIndex > -1) { + includePricingByDaySurcharge = true; + } + } + + // Load page with date already selected? let preSelectedDate = await store.getters.order.schedule.date; if (!preSelectedDate || preSelectedDate.startTime === null) { preSelectedDate = null; } - const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({ - // setup config options for date-picker - selectableDatesSetting: "custom", - initialViewRowsToShow: 5, - customSelectableDatesCallback: getAvailableDates, - preSelectedDate: preSelectedDate, - }); - // TODO - ONCE A DATEPICKER VERSION IS FINALIZED, - // MAKE SURE THE ABOVE METHOD loadInitialData POINTS TO THE RIGHT FILE + // Set up promises + const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); + + const alertReasonsPromise = locationAlerts.methods.loadInitialData( + store.getters.order.serviceLocation.zipCodeCtu, + store.getters.order.serviceLocation.provider?.address?.zipCodeCtu + ); + + // While Pricing By Day Experiment is ongoing, there are two different datePickers, but only one will load + let datePickerInitialDataPromise; + if (isPricingByDayExperiment) { + datePickerInitialDataPromise = await datePickerForPricingByDay.methods.loadInitialData({ + // setup config options for date-picker + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: getAvailableDates, + preSelectedDate: preSelectedDate, + baseDayPrice: baseDayPrice, + pricingByDayUpcharge: pricingByDayUpcharge, + }); + } else { + datePickerInitialDataPromise = await datePicker.methods.loadInitialData({ + // setup config options for date-picker + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: getAvailableDates, + preSelectedDate: preSelectedDate, + }); + } const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_MOBILE_PREMIUM_FEE, null, "schedule" ); - const premiumFeeWithPricePromise = premiumFeePromise.then((result) => { if (result.data) { return baseMixin.methods.dispatchStoreActionWithLogging( @@ -269,11 +345,6 @@ export default { } }); - const alertReasonsPromise = locationAlerts.methods.loadInitialData( - store.getters.order.serviceLocation.zipCodeCtu, - store.getters.order.serviceLocation.provider?.address?.zipCodeCtu - ); - // Settle promises and get results const promiseResultMap = [ { @@ -300,6 +371,7 @@ export default { next((vm) => { vm.setCmsContent(resultMap.cmsContent); vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData); + vm.$refs.datePickerForPricingByDay.initializeComponent(resultMap.datePickerInitialData); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse; vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice @@ -307,6 +379,12 @@ export default { : null; vm.updateFooterButtonText(vm.selectedTimeSlotInfo); vm.setDisplayWaitList(); + vm.pricingByDayUpchargeLineItem = pricingByDayUpchargeLineItem; + vm.includePricingByDaySurcharge = includePricingByDaySurcharge; + vm.isPricingByDayExperiment = isPricingByDayExperiment; + vm.baseDayPrice = baseDayPrice; + vm.pricingByDayUpcharge = pricingByDayUpcharge; + vm.showPricingByDay = showPricingByDay; }); }, computed: { @@ -329,45 +407,6 @@ export default { (selectableDate) => selectableDate.date === this.selectedDate ); }, - isPricingByDayExperiment() { - return ( - experimentMixin.methods - .getSettingValue(experimentSettings.PRICING_BY_DAY) - ?.toLowerCase() === "true" - ); - }, - showPricingByDay() { - return !this.$store.getters.payment.isInsurance && this.isPricingByDayExperiment; - }, - lineItemsToBePriced() { - const lineItems = deepClone(store.getters.order.lineItems); - - const glassParts = (this.isRecalibrationOnOrder && this.shouldHideRecalibration) ? - getItemsWithoutRecalParts(lineItems.glassParts) : - lineItems.glassParts ?? []; - - const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(lineItems.supportingItems, {partNumbersToRemove: [partNumberStrings.RECYCLE_FEE]}); - - return { - glassParts: glassParts, - supportingItems: supportingItemsWithoutFees, - vaps: lineItems.vaps ?? [], - promos: lineItems.promos ?? [], - }; - }, - lowestPrice() { - return getDisplayAmountDue(this.lineItemsToBePriced, false); // passing false here so IncludeTax is false - }, - isRecalibrationOnOrder() { - return store.getters.isRecalibrationOnOrder; - }, - shouldHideRecalibration() { - return ( - experimentMixin.methods - .getSettingValue(experimentSettings.RECAL_PRICE_REMOVE) - ?.toLowerCase() === "true" && this.isRecalibrationOnOrder - ); - }, }, methods: { splitCopyOnCMSPlaceHolder, @@ -414,6 +453,7 @@ export default { this.appointmentType, this.$store.getters.order.serviceLocation.provider.providerNumber ); + // ADD API CALL RESULTS TO EXISTING DATE DATA this.selectableDatesData.days = this.selectableDatesData.days.concat( newShopTimeSlots.days @@ -590,6 +630,28 @@ export default { updateSupportingItems() { const supportingItems = this.getSupportingItems(); + // if we have a pricing by day upcharge, then save/update supporting items with it + const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex( + (item) => item.partType == PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE + ); + + if (this.includePricingByDaySurcharge) { + if (pricingByDayUpchargeFeeIndex > -1) { + supportingItems[pricingByDayUpchargeFeeIndex].laborAmount = + this.pricingByDayUpchargeLineItem.laborAmount; + supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice = + this.pricingByDayUpchargeLineItem.sellingPrice; + supportingItems[pricingByDayUpchargeFeeIndex].kitPrice = + this.pricingByDayUpchargeLineItem.kitPrice; + } else { + supportingItems.push(this.pricingByDayUpchargeLineItem); + } + } else { + if (pricingByDayUpchargeFeeIndex >= 0) { + supportingItems.splice(pricingByDayUpchargeFeeIndex, 1); + } + } + // if we have a premium fee(early bird), then save/update supporting items if ( this.appointmentType === AppointmentTypeStrings.MOBILE && @@ -599,7 +661,7 @@ export default { (item) => item.partType == PREMIUM_FEE_PART_TYPE ); - if (premiumFeeIndex >= 0) { + if (premiumFeeIndex > -1) { supportingItems[premiumFeeIndex].laborAmount = this.mobilePremiumAppointmentFee.laborAmount; supportingItems[premiumFeeIndex].sellingPrice = @@ -609,12 +671,6 @@ export default { } else { supportingItems.push(this.mobilePremiumAppointmentFee); } - - this.dispatchStoreAction( - this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, - supportingItems, - false - ); } else { if (!supportingItems) { return; @@ -627,17 +683,28 @@ export default { if (removePremiumFeeIndex >= 0) { supportingItems.splice(removePremiumFeeIndex, 1); - this.dispatchStoreAction( - this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, - supportingItems, - false - ); } } + + this.dispatchStoreAction( + this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, + supportingItems, + false + ); }, handleWaitListRequested(value) { this.waitListRequested = value; }, + handleDateClicked(date) { + // do something to mark this as upcharge day or not... + if (date.isPricingByDayUpchargeDay) { + this.includePricingByDaySurcharge = true; + } else { + this.includePricingByDaySurcharge = false; + } + + this.openInshopTimeSlotsModal(); + }, }, watch: { selectedDate(newValue, oldValue) { diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index b230506c8..2484c0871 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -144,7 +144,6 @@ describe("baseMixin.js", () => { expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled(); }); - }); function getMixInInstance({ isDispatchSuccess = true }) { diff --git a/src/store/index.js b/src/store/index.js index a7e7c6ae9..1a0a18d7a 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1749,6 +1749,18 @@ export const actions = { pageNameToLog: pageNameToLog, }); }, + async getPricingByDayUpchargePart(context, { pageNameToLog }) { + return await globalMethods + .callHttpClient({ + method: endpoints.GetPricingByDayUpchargePart.method, + endpoint: endpoints.GetPricingByDayUpchargePart.url, + logApiCall: true, + pageNameToLog: pageNameToLog, + }) + .then((response) => { + return response; + }); + }, getServicePackageDiscountPart(context, { pageNameToLog }) { const damage = context.getters.damage; const damageType = damage.isRepair ? "Repair" : "Replace"; From b6fa9fc04624e3702c1804e3d0b537112e11a174 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 10:13:33 -0400 Subject: [PATCH 15/26] CASH-152: updates to schedule unit tests --- src/layouts/schedule/schedule.spec.js | 29 +-------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js index 95ad63a40..1c615a961 100644 --- a/src/layouts/schedule/schedule.spec.js +++ b/src/layouts/schedule/schedule.spec.js @@ -635,34 +635,6 @@ describe("schedule.vue...", () => { expect.anything() ); }); - - test("if no EARLY BIRD supporting item, then updateSupportingItems should NOT call store action", async () => { - // Arrange - store.getters.order.serviceLocation.appointmentType = "Mobile"; - store.getters.lineItems.supportingItems = []; - const { wrapper } = setupMocks({}); - wrapper.vm.dispatchStoreAction = jest.fn(() => { - return { - data: [], - }; - }); - wrapper.vm.mobilePremiumAppointmentFee = 14.99; - wrapper.setData({ - selectedTimeSlot: { - date: "2019-01-01", - startTime: "09:00", - endTime: "10:00", - routeCode: null, - isPremiumAppointment: false, - }, - }); - - // Act - await wrapper.vm.updateSupportingItems(); - - // Assert - expect(wrapper.vm.dispatchStoreAction).not.toBeCalled(); - }); }); const mockCmsContent = {}; @@ -690,6 +662,7 @@ function setupMocks({ customMountOptions }) { wrapper.vm.setCmsContent = jest.fn(); wrapper.vm.$refs.datePicker.initializeComponent = jest.fn(); + wrapper.vm.$refs.datePickerForPricingByDay.initializeComponent = jest.fn(); wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn(); wrapper.vm.$refs.navbar.updateButtonText = jest.fn(); wrapper.vm.$refs.timeSlotModalQuestion.openModal = jest.fn(); From f1ca66a365d5724c4a9ea7c89849dcef752a5ad9 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 10:17:18 -0400 Subject: [PATCH 16/26] CASH-152: typo fix --- .../quote/service-package-question/service-package-question.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 830a296d2..ab4772107 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -166,7 +166,7 @@ export default { const discountServicePackage = experimentMixin.methods.getSettingValue( experimentSettings.PROMO_ON_PACKAGE ); - return getDiscountPackageName(discountServicePackage); + return getDiscountedPackageName(discountServicePackage); }, frontWipersApplicableForTierTwo() { return shouldFrontWipersBeAvailable( From edc9abe000de9e4c43260dcaad487bd6390d8df1 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 10:32:56 -0400 Subject: [PATCH 17/26] CASH-152: update endpoints --- src/constants/endpoints.js | 4 ++++ src/helpers/pricing-helper.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 29d36d091..bfc02d034 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -72,6 +72,10 @@ const endpoints = { url: "/parts/api/v1/parts/mobile-fee", method: "GET", }, + GetPricingByDayUpchargePart: { + url: "/parts/api/v1/parts/get-pricing-by-day-part-number", + method: "GET", + }, GetServicePackageDiscountPart: { url: "/parts/api/v1/parts/service-package-discount", method: "POST", diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index b9262f95d..4a215eaa0 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -58,6 +58,34 @@ export function getSubTotal(lineItemsObject) { export function getSalesTax(lineItemsObject) { // this is amountDue minus subTotal return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(2); +export async function getPriceUpchargeByDayPart(pageNameToLog) { + // Get the Pricing By Day Part + const basePriceByDayPart = await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.GET_PRICING_BY_DAY_UPCHARGE_PART, + null, + pageNameToLog, + false + ); + + if ( + basePriceByDayPart?.data === null || + basePriceByDayPart?.data === undefined || + basePriceByDayPart?.data === "" + ) { + return null; + } + + // Get the Pricing By Day Base Part price + const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, + { + availableLineItems: [basePriceByDayPart?.data], + }, + pageNameToLog, + false + ); + + return pricingResults[0]; } export async function getPriceUpchargeByDayPart(pageNameToLog) { From 78b43fee69e321542637b75f9fe91a0e90d32e40 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 10:34:52 -0400 Subject: [PATCH 18/26] CASH-152: fix merge-munged --- src/helpers/pricing-helper.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index 4a215eaa0..b9262f95d 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -58,34 +58,6 @@ export function getSubTotal(lineItemsObject) { export function getSalesTax(lineItemsObject) { // this is amountDue minus subTotal return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(2); -export async function getPriceUpchargeByDayPart(pageNameToLog) { - // Get the Pricing By Day Part - const basePriceByDayPart = await baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.GET_PRICING_BY_DAY_UPCHARGE_PART, - null, - pageNameToLog, - false - ); - - if ( - basePriceByDayPart?.data === null || - basePriceByDayPart?.data === undefined || - basePriceByDayPart?.data === "" - ) { - return null; - } - - // Get the Pricing By Day Base Part price - const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, - { - availableLineItems: [basePriceByDayPart?.data], - }, - pageNameToLog, - false - ); - - return pricingResults[0]; } export async function getPriceUpchargeByDayPart(pageNameToLog) { From 0f7904fb52d8a1631c69c0cc8fddeac967d42988 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 10:43:26 -0400 Subject: [PATCH 19/26] CASH-152: remove commented out code --- src/helpers/service-package-helper.js | 84 --------------------------- 1 file changed, 84 deletions(-) diff --git a/src/helpers/service-package-helper.js b/src/helpers/service-package-helper.js index b6144efc2..fcbe4aee3 100644 --- a/src/helpers/service-package-helper.js +++ b/src/helpers/service-package-helper.js @@ -254,90 +254,6 @@ export function getDiscountedPackageName(discountPackage) { return package_names[discountPackage] || null; } -// export function getDiscountedPackagePriceString2(packageName, servicePackageDiscount) { -// const formattedPriceFloat = parseFloat( -// this.getPackagePrice(packageName, { -// discountedPrice: true, -// servicePackageDiscount, -// }) -// ).toFixed(2); - -// return "$" + formattedPriceFloat; -// } - -// export function getPackagePrice( -// packageName, -// { discountedPrice = false, servicePackageDiscount = false } -// ) { -// let lineItemsToPrice = [...this.shallowLineItems]; - -// if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) { -// lineItemsToPrice = getItemsWithoutRecalParts(lineItemsToPrice); -// } - -// //remove service package discount part -// if (this.isServicePackageDiscountOnOrder) { -// lineItemsToPrice = lineItemsToPrice.filter((item) => { -// return item.partType != this.servicePackageDiscountParts[0].partType; -// }); -// } - -// if (discountedPrice) { -// lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos)); -// } - -// if (servicePackageDiscount) { -// lineItemsToPrice.push(...this.servicePackageDiscountParts); -// } -// let priceFloat = baseMixin.methods.getTierOnePackagePrice( -// baseMixin.methods.filterOutFees(lineItemsToPrice) -// ); - -// priceFloat += this.getVapsPrice(packageName, discountedPrice); - -// // console.log("SPHelper getPackagePrice()... priceFloat: ", priceFloat) -// // console.log(" ") - -// return priceFloat; -// } - -// export function getVapsPrice(packageName, applyPromoDiscounts = false) { -// const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName); -// let price = 0; - -// vapsItems.forEach((item) => { -// price += baseMixin.methods.getTotalLineItemPrice(item); -// }); - -// if (applyPromoDiscounts && this.activePromos) { -// const relevantPromos = getPromosThatMatchLineItemsOnOrder(this.lineItems.promos, vapsItems); -// relevantPromos.forEach((promo) => { -// price += baseMixin.methods.getTotalLineItemPrice(promo); -// }); -// } - -// return price; -// } - -// export function getVapsLineItemsForSelectedPackage(packageName) { -// const packageContentTypes = getPackageContents( -// this.glassToReplace, -// this.shallowLineItems, -// this.isRepair, -// packageName -// ); - -// let vapsLineItemsForSelectedPackage = []; - -// packageContentTypes.forEach((vapType) => { -// vapsLineItemsForSelectedPackage.push( -// ...findLineItemsWithPartType(vapType, this.shallowLineItems) -// ); -// }); - -// return vapsLineItemsForSelectedPackage; -// } - function maxTier(tierA, tierB) { if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) { return packageNames.TIER_THREE; From af02cb3d5711b10cdd133901b7836af2421f1b7f Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Mon, 17 Mar 2025 11:38:44 -0400 Subject: [PATCH 20/26] CASH-185 CASH-185 fixed issue for value not being persisted upon user navigation --- src/layouts/schedule/schedule.vue | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 925683073..65ce54a87 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -512,11 +512,13 @@ export default { false ); - this.dispatchStoreAction( - this.storeActions.SAVE_WAITLIST_REQUESTED, - this.waitListRequested, - false - ); + if(this.waitListRequested !== null && this.waitListRequested !== undefined) { + this.dispatchStoreAction( + this.storeActions.SAVE_WAITLIST_REQUESTED, + this.waitListRequested, + false + ); + } this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); }, From 4daa2527b0d0d021e022bcbf0b13fa6a53f65cb3 Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Mon, 17 Mar 2025 11:44:17 -0400 Subject: [PATCH 21/26] CASH-185 CASH-185 formatting --- src/layouts/schedule/schedule.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 65ce54a87..0d688e4d9 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -512,8 +512,8 @@ export default { false ); - if(this.waitListRequested !== null && this.waitListRequested !== undefined) { - this.dispatchStoreAction( + if (this.waitListRequested !== null && this.waitListRequested !== undefined) { + this.dispatchStoreAction( this.storeActions.SAVE_WAITLIST_REQUESTED, this.waitListRequested, false From 7268a9d4dbcb30df1cf85cae1c57d15d7a1ca48a Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Mon, 17 Mar 2025 12:29:11 -0400 Subject: [PATCH 22/26] CASH-185 Styling fix for CASH-397 defect --- .../time-slot-modal-question/time-slot-modal-question.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue index c6d08069c..82e11ea41 100644 --- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue +++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue @@ -627,8 +627,6 @@ export default { font-size: $h5-font-size; padding: 12px 16px; margin-bottom: -8px; - height: 78px; - width: 326px; .success-text { margin-left: 8px; strong { From 46bbd2694468413ef8d9140921b7e2d34a27a178 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 13:18:01 -0400 Subject: [PATCH 23/26] CASH-152: prettier updates --- src/helpers/pricing-helper.js | 1 + src/mixins/base-mixin.js | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index b9262f95d..56f11336e 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -55,6 +55,7 @@ export function getSubTotal(lineItemsObject) { return getAmountDue(lineItemsObject, false); } +// prettier-ignore export function getSalesTax(lineItemsObject) { // this is amountDue minus subTotal return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(2); diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index 78bde1e1d..6fd6075ab 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -110,7 +110,10 @@ export default { }); return filteredLineItems; }, - filterOutCertainPartTypesOrNumbers(lineItemsArray, {partTypesToRemove = [], partNumbersToRemove = []}) { + filterOutCertainPartTypesOrNumbers( + lineItemsArray, + { partTypesToRemove = [], partNumbersToRemove = [] } + ) { // lineItems s/b an ARRAY here if (!Array.isArray(lineItemsArray)) return; @@ -153,14 +156,9 @@ export default { const laborAmount = lineItem.laborAmount ?? 0; const sellingPrice = lineItem.sellingPrice ?? 0; const salesTax = lineItem.salesTax ?? 0; - + if (includeTax) { - return ( - kitPrice + - laborAmount + - sellingPrice + - salesTax - ); + return kitPrice + laborAmount + sellingPrice + salesTax; } else { return kitPrice + laborAmount + sellingPrice; } From 36da3b78be41aeb42cf1a447553e717d05e5bb7c Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 13:27:15 -0400 Subject: [PATCH 24/26] CASH-152: making code HARDER to read with prettier forced formatting --- src/helpers/pricing-helper.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index 56f11336e..c470aaf31 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -14,7 +14,7 @@ export function getAmountDue(lineItemsObject, includeTax = true) { const order = baseMixin?.methods?.hasSubmittedOrder() ? baseMixin?.methods?.getSubmittedOrder() : store.getters.order; - + if (lineItemsObject?.glassParts) { amountDue += baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts( lineItemsObject.glassParts, @@ -55,10 +55,11 @@ export function getSubTotal(lineItemsObject) { return getAmountDue(lineItemsObject, false); } -// prettier-ignore export function getSalesTax(lineItemsObject) { // this is amountDue minus subTotal - return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(2); + return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed( + 2 + ); } export async function getPriceUpchargeByDayPart(pageNameToLog) { From 5bfbd71c93cd1a5b5a0628eb9d117cb7e2dadca2 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 18:45:18 -0400 Subject: [PATCH 25/26] CASH-152: go back to just using one component for datePicker due to conflicts with 2 --- .../date-picker-for-pricing-by-day.vue | 162 ++++++++++++++---- src/layouts/schedule/schedule.spec.js | 1 - src/layouts/schedule/schedule.vue | 54 ++---- 3 files changed, 141 insertions(+), 76 deletions(-) diff --git a/src/experiment-components/date-picker-for-pricing-by-day.vue b/src/experiment-components/date-picker-for-pricing-by-day.vue index 7d63499ef..45f6ac3fa 100644 --- a/src/experiment-components/date-picker-for-pricing-by-day.vue +++ b/src/experiment-components/date-picker-for-pricing-by-day.vue @@ -1,5 +1,5 @@ - + :baseDayPrice="baseDayPrice" + :pricingByDayUpcharge="pricingByDayUpcharge" + :showPricingByDay="showPricingByDay" + :isPricingByDayExperiment="isPricingByDayExperiment" + @date-clicked="handleDateClicked" /> { vm.setCmsContent(resultMap.cmsContent); vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData); - vm.$refs.datePickerForPricingByDay.initializeComponent(resultMap.datePickerInitialData); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse; vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice @@ -735,7 +712,6 @@ export default { funnelSubHeader, Form, loadingModal, - datePicker, datePickerForPricingByDay, locationAlerts, timeSlotModalQuestion, From fea13aefb5c5e9467802b2e7f3136972c0d33295 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Mon, 17 Mar 2025 18:54:22 -0400 Subject: [PATCH 26/26] CASH-152: prettier - you're killin me smalls --- .../date-picker-for-pricing-by-day.vue | 7 +++++-- src/layouts/schedule/schedule.vue | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/experiment-components/date-picker-for-pricing-by-day.vue b/src/experiment-components/date-picker-for-pricing-by-day.vue index 45f6ac3fa..c94f10a17 100644 --- a/src/experiment-components/date-picker-for-pricing-by-day.vue +++ b/src/experiment-components/date-picker-for-pricing-by-day.vue @@ -72,7 +72,9 @@ :id="`${month.monthLabel}-${date.dateNum.toString()}`" /> @@ -1089,7 +1091,8 @@ export default { } } - &.pricing-by-day { // pricing by day override styles + &.pricing-by-day { + // pricing by day override styles .calendar-grid-container .grid-item { margin: 0; } diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index d5c05c93b..be7009a26 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -293,15 +293,16 @@ export default { ); // While Pricing By Day Experiment is ongoing, using the updated datePicker - const datePickerInitialDataPromise = await datePickerForPricingByDay.methods.loadInitialData({ - // setup config options for date-picker - selectableDatesSetting: "custom", - initialViewRowsToShow: 5, - customSelectableDatesCallback: getAvailableDates, - preSelectedDate: preSelectedDate, - baseDayPrice: baseDayPrice, - pricingByDayUpcharge: pricingByDayUpcharge, - }); + const datePickerInitialDataPromise = + await datePickerForPricingByDay.methods.loadInitialData({ + // setup config options for date-picker + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: getAvailableDates, + preSelectedDate: preSelectedDate, + baseDayPrice: baseDayPrice, + pricingByDayUpcharge: pricingByDayUpcharge, + }); const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_MOBILE_PREMIUM_FEE,