From 46555cbaf9fdb0404a7a5b819e466ce5ad7dbcad Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Thu, 6 Mar 2025 16:27:09 -0500 Subject: [PATCH] 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 }) {