From afd0921326dbc73125f4ce7627fa7554a30f698e Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Tue, 10 Sep 2024 10:59:22 +0200 Subject: [PATCH] fix(utils,medusa,order,cart): fix totals when promotions are included (#9014) * fix(utils): fix totals when promotions are included * chore: update totals calc * chore: ignore taxes when taxable amount is 0 * chore: use subtotals everywhere * chore: fix shipping totals + tests --- .../__tests__/cart/store/carts.spec.ts | 22 +- .../modules/__tests__/order/order.spec.ts | 71 ++--- .../core/utils/src/totals/__tests__/totals.ts | 290 ++++++++++-------- .../core/utils/src/totals/adjustment/index.ts | 41 ++- packages/core/utils/src/totals/cart/index.ts | 68 ++-- .../core/utils/src/totals/line-item/index.ts | 99 +++--- .../utils/src/totals/shipping-method/index.ts | 87 +++--- packages/core/utils/src/totals/tax/index.ts | 23 +- .../src/api/store/carts/query-config.ts | 1 + .../src/api/store/orders/query-config.ts | 1 + .../services/cart-module/index.spec.ts | 24 +- .../__tests__/create-order.ts | 10 +- 12 files changed, 378 insertions(+), 359 deletions(-) diff --git a/integration-tests/modules/__tests__/cart/store/carts.spec.ts b/integration-tests/modules/__tests__/cart/store/carts.spec.ts index 1da0877175..6a959b4e43 100644 --- a/integration-tests/modules/__tests__/cart/store/carts.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/carts.spec.ts @@ -2053,16 +2053,17 @@ medusaIntegrationTestRunner({ type: "order", order: expect.objectContaining({ id: expect.any(String), - total: 94.764, + total: 95.4, subtotal: 100, - tax_total: 5.364, + tax_total: 5.4, discount_total: 10.6, - discount_tax_total: 0.636, - original_total: 95.4, + discount_subtotal: 10, + discount_tax_total: 0.6, + original_total: 106, original_tax_total: 6, - item_total: 94.764, + item_total: 95.4, item_subtotal: 100, - item_tax_total: 5.364, + item_tax_total: 5.4, original_item_total: 106, original_item_subtotal: 100, original_item_tax_total: 6, @@ -2078,12 +2079,13 @@ medusaIntegrationTestRunner({ product_id: product.id, unit_price: 100, quantity: 1, - tax_total: 5.364, - total: 94.764, + tax_total: 5.4, + total: 95.4, subtotal: 100, original_total: 106, discount_total: 10.6, - discount_tax_total: 0.636, + discount_subtotal: 10, + discount_tax_total: 0.6, original_tax_total: 6, tax_lines: [ expect.objectContaining({ @@ -2140,7 +2142,7 @@ medusaIntegrationTestRunner({ payment_collections: [ expect.objectContaining({ currency_code: "usd", - amount: 94.764, + amount: 95.4, status: "authorized", }), ], diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts index 0338f20270..cedd49800b 100644 --- a/integration-tests/modules/__tests__/order/order.spec.ts +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -1,18 +1,5 @@ -import { - ICartModuleService, - IFulfillmentModuleService, - IInventoryService, - IOrderModuleService, - IPaymentModuleService, - IPricingModuleService, - IProductModuleService, - IRegionModuleService, - IStockLocationService, -} from "@medusajs/types" -import { - ContainerRegistrationKeys, - ModuleRegistrationName, -} from "@medusajs/utils" +import { IOrderModuleService } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/utils" import { medusaIntegrationTestRunner } from "medusa-test-utils" import { adminHeaders, @@ -27,29 +14,10 @@ medusaIntegrationTestRunner({ env, testSuite: ({ dbConnection, getContainer, api }) => { let appContainer - let cartModuleService: ICartModuleService - let regionModuleService: IRegionModuleService - let productModule: IProductModuleService - let paymentModule: IPaymentModuleService - let pricingModule: IPricingModuleService - let inventoryModule: IInventoryService - let stockLocationModule: IStockLocationService - let fulfillmentModule: IFulfillmentModuleService let orderModule: IOrderModuleService - let remoteLink, remoteQuery beforeAll(async () => { appContainer = getContainer() - cartModuleService = appContainer.resolve(ModuleRegistrationName.CART) - regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION) - productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT) - paymentModule = appContainer.resolve(ModuleRegistrationName.PAYMENT) - inventoryModule = appContainer.resolve(ModuleRegistrationName.INVENTORY) - fulfillmentModule = appContainer.resolve( - ModuleRegistrationName.FULFILLMENT - ) - remoteLink = appContainer.resolve(ContainerRegistrationKeys.REMOTE_LINK) - remoteQuery = appContainer.resolve(ContainerRegistrationKeys.REMOTE_QUERY) orderModule = appContainer.resolve(ModuleRegistrationName.ORDER) }) @@ -144,12 +112,12 @@ medusaIntegrationTestRunner({ summary: expect.objectContaining({ // TODO: add all summary fields }), - total: 59.79, + total: 59.9, subtotal: 60, - tax_total: 0.89, + tax_total: 0.9, discount_total: 1.1, - discount_tax_total: 0.11, - original_total: 60.9, + discount_tax_total: 0.1, + original_total: 61, original_tax_total: 1, item_total: 50, item_subtotal: 50, @@ -157,16 +125,16 @@ medusaIntegrationTestRunner({ original_item_total: 50, original_item_subtotal: 50, original_item_tax_total: 0, - shipping_total: 9.79, + shipping_total: 9.9, shipping_subtotal: 10, - shipping_tax_total: 0.89, + shipping_tax_total: 0.9, original_shipping_tax_total: 1, original_shipping_subtotal: 10, original_shipping_total: 11, created_at: expect.any(String), updated_at: expect.any(String), raw_total: { - value: "59.789999999999999995", + value: "59.899999999999999995", precision: 20, }, raw_subtotal: { @@ -294,6 +262,7 @@ medusaIntegrationTestRunner({ original_total: 50, discount_total: 5e-18, discount_tax_total: 0, + discount_subtotal: 5e-18, tax_total: 0, original_tax_total: 0, refundable_total: 50, @@ -320,6 +289,10 @@ medusaIntegrationTestRunner({ value: "5e-18", precision: 20, }, + raw_discount_subtotal: { + precision: 20, + value: "5e-18", + }, raw_discount_tax_total: { value: "0", precision: 20, @@ -432,10 +405,10 @@ medusaIntegrationTestRunner({ deleted_at: null, shipping_method_id: expect.any(String), rate: 10, - total: 0.89, + total: 0.9, subtotal: 1, raw_total: { - value: "0.89", + value: "0.9", precision: 20, }, raw_subtotal: { @@ -474,18 +447,18 @@ medusaIntegrationTestRunner({ ]), amount: 10, subtotal: 10, - total: 9.79, + total: 9.9, original_total: 11, discount_total: 1.1, - discount_tax_total: 0.11, - tax_total: 0.89, + discount_tax_total: 0.1, + tax_total: 0.9, original_tax_total: 1, raw_subtotal: { value: "10", precision: 20, }, raw_total: { - value: "9.79", + value: "9.9", precision: 20, }, raw_original_total: { @@ -497,11 +470,11 @@ medusaIntegrationTestRunner({ precision: 20, }, raw_discount_tax_total: { - value: "0.11", + value: "0.1", precision: 20, }, raw_tax_total: { - value: "0.89", + value: "0.9", precision: 20, }, raw_original_tax_total: { diff --git a/packages/core/utils/src/totals/__tests__/totals.ts b/packages/core/utils/src/totals/__tests__/totals.ts index 383a5385e1..817c49733e 100644 --- a/packages/core/utils/src/totals/__tests__/totals.ts +++ b/packages/core/utils/src/totals/__tests__/totals.ts @@ -42,6 +42,7 @@ describe("Total calculation", function () { total: 66, original_total: 66, discount_total: 0, + discount_subtotal: 0, discount_tax_total: 0, tax_total: 6, original_tax_total: 6, @@ -60,6 +61,7 @@ describe("Total calculation", function () { total: 7.5, original_total: 7.5, discount_total: 0, + discount_subtotal: 0, discount_tax_total: 0, tax_total: 2.5, original_tax_total: 2.5, @@ -69,6 +71,7 @@ describe("Total calculation", function () { subtotal: 65, tax_total: 8.5, discount_total: 0, + discount_subtotal: 0, discount_tax_total: 0, item_total: 73.5, item_subtotal: 65, @@ -111,7 +114,7 @@ describe("Total calculation", function () { tax_lines: [ { rate: 10, - total: 8.9, + total: 9, subtotal: 10, }, ], @@ -123,24 +126,26 @@ describe("Total calculation", function () { }, ], subtotal: 100, - total: 97.9, + total: 99, original_total: 110, discount_total: 11, - discount_tax_total: 1.1, - tax_total: 8.9, + discount_subtotal: 10, + discount_tax_total: 1, + tax_total: 9, original_tax_total: 10, }, ], - total: 97.9, + total: 99, subtotal: 100, - tax_total: 8.9, + tax_total: 9, discount_total: 11, - discount_tax_total: 1.1, - original_total: 99, + discount_subtotal: 10, + discount_tax_total: 1, + original_total: 110, original_tax_total: 10, - item_total: 97.9, + item_total: 99, item_subtotal: 100, - item_tax_total: 8.9, + item_tax_total: 9, original_item_total: 110, original_item_subtotal: 100, original_item_tax_total: 10, @@ -151,7 +156,7 @@ describe("Total calculation", function () { const cartMixed = { items: [ { - unit_price: 100, + unit_price: 99, quantity: 1, is_tax_inclusive: true, tax_lines: [ @@ -161,12 +166,12 @@ describe("Total calculation", function () { ], adjustments: [ { - amount: 10, + amount: 9, }, ], }, { - unit_price: 10, + unit_price: 9, quantity: 1, is_tax_inclusive: false, tax_lines: [ @@ -183,21 +188,21 @@ describe("Total calculation", function () { ], shipping_methods: [ { - amount: 10, + amount: 99, is_tax_inclusive: true, tax_lines: [ { - rate: 5, + rate: 10, }, ], adjustments: [ { - amount: 2, + amount: 9, }, ], }, { - amount: 5, + amount: 9, is_tax_inclusive: false, tax_lines: [ { @@ -206,7 +211,7 @@ describe("Total calculation", function () { ], adjustments: [ { - amount: 2, + amount: 3, }, ], }, @@ -220,40 +225,41 @@ describe("Total calculation", function () { expect(serializedMixed).toEqual({ items: [ { - unit_price: 100, + unit_price: 99, quantity: 1, is_tax_inclusive: true, tax_lines: [ { rate: 10, - total: 8.181818181818182, - subtotal: 9.090909090909092, + subtotal: 9, + total: 8.1, }, ], adjustments: [ { - amount: 10, - subtotal: 9.090909090909092, - total: 10, + amount: 9, + subtotal: 8.181818181818182, + total: 9, }, ], - subtotal: 90.9090909090909, - total: 90, - original_total: 100, - discount_total: 10, - discount_tax_total: 1, - tax_total: 8.181818181818182, - original_tax_total: 9.090909090909092, + subtotal: 90, + total: 89.1, + original_total: 99, + discount_total: 9, + discount_subtotal: 9, + discount_tax_total: 0.8181818181818182, + tax_total: 8.1, + original_tax_total: 9, }, { - unit_price: 10, + unit_price: 9, quantity: 1, is_tax_inclusive: false, tax_lines: [ { rate: 10, - total: 0.67, - subtotal: 1, + total: 0.6, + subtotal: 0.9, }, ], adjustments: [ @@ -263,86 +269,90 @@ describe("Total calculation", function () { total: 3.3, }, ], - subtotal: 10, - total: 7.37, - original_total: 11, + subtotal: 9, + total: 6.6, + original_total: 9.9, discount_total: 3.3, - discount_tax_total: 0.33, - tax_total: 0.67, - original_tax_total: 1, + discount_subtotal: 3, + discount_tax_total: 0.3, + tax_total: 0.6, + original_tax_total: 0.9, }, ], shipping_methods: [ { - amount: 10, is_tax_inclusive: true, tax_lines: [ { - rate: 5, - total: 0.38095238095238093, - subtotal: 0.47619047619047616, + rate: 10, + subtotal: 9, + total: 8.1, }, ], adjustments: [ { - amount: 2, - subtotal: 1.9047619047619047, - total: 2, + amount: 9, + subtotal: 8.181818181818182, + total: 9, }, ], - subtotal: 9.523809523809524, - total: 8, - original_total: 10, - discount_total: 2, - discount_tax_total: 0.1, - tax_total: 0.38095238095238093, - original_tax_total: 0.47619047619047616, + amount: 99, + subtotal: 90, + total: 89.1, + original_total: 99, + discount_total: 9, + discount_subtotal: 9, + discount_tax_total: 0.8181818181818182, + tax_total: 8.1, + original_tax_total: 9, }, { - amount: 5, + amount: 9, is_tax_inclusive: false, tax_lines: [ { rate: 10, - total: 0.28, - subtotal: 0.5, + total: 0.6, + subtotal: 0.9, }, ], adjustments: [ { - amount: 2, - subtotal: 2, - total: 2.2, + amount: 3, + subtotal: 3, + total: 3.3, }, ], - subtotal: 5, - total: 3.08, - original_total: 5.5, - discount_total: 2.2, - discount_tax_total: 0.22, - tax_total: 0.28, - original_tax_total: 0.5, + subtotal: 9, + total: 6.6, + original_total: 9.9, + discount_total: 3.3, + discount_subtotal: 3, + discount_tax_total: 0.3, + tax_total: 0.6, + original_tax_total: 0.9, }, ], - total: 107.445670995671, - subtotal: 115.43290043290044, - tax_total: 9.512770562770562, - discount_total: 17.5, - discount_tax_total: 1.65, - original_total: 109.97619047619048, - original_tax_total: 11.067099567099566, - item_total: 97.37, - item_subtotal: 100.9090909090909, - item_tax_total: 8.851818181818182, - original_item_total: 111, - original_item_subtotal: 100.9090909090909, - original_item_tax_total: 10.090909090909092, - shipping_total: 11.08, - shipping_subtotal: 14.523809523809524, - shipping_tax_total: 0.660952380952381, - original_shipping_tax_total: 0.9761904761904762, - original_shipping_subtotal: 14.523809523809524, - original_shipping_total: 15.5, + total: 191.4, + subtotal: 198, + tax_total: 17.4, + discount_total: 24.6, + discount_subtotal: 24, + discount_tax_total: 2.2363636363636363, + original_total: 217.8, + original_tax_total: 19.8, + item_total: 95.7, + item_subtotal: 99, + item_tax_total: 8.7, + original_item_total: 108.9, + original_item_subtotal: 99, + original_item_tax_total: 9.9, + shipping_total: 95.7, + shipping_subtotal: 99, + shipping_tax_total: 8.7, + original_shipping_tax_total: 9.9, + original_shipping_subtotal: 99, + original_shipping_total: 108.9, }) }) @@ -394,6 +404,7 @@ describe("Total calculation", function () { expect(serializedWith).toEqual({ items: [ { + discount_subtotal: 0, unit_price: 50, quantity: 2, is_tax_inclusive: true, @@ -413,6 +424,7 @@ describe("Total calculation", function () { original_tax_total: 9.090909090909092, }, ], + discount_subtotal: 0, total: 100, subtotal: 90.9090909090909, tax_total: 9.090909090909092, @@ -431,6 +443,7 @@ describe("Total calculation", function () { expect(serializedWithout).toEqual({ items: [ { + discount_subtotal: 0, unit_price: 50, quantity: 2, is_tax_inclusive: false, @@ -451,6 +464,7 @@ describe("Total calculation", function () { }, ], total: 110, + discount_subtotal: 0, subtotal: 100, tax_total: 10, discount_total: 0, @@ -468,6 +482,7 @@ describe("Total calculation", function () { expect(serializedMixed).toEqual({ items: [ { + discount_subtotal: 0, unit_price: 50, quantity: 2, is_tax_inclusive: true, @@ -487,6 +502,7 @@ describe("Total calculation", function () { original_tax_total: 9.090909090909092, }, { + discount_subtotal: 0, unit_price: 50, quantity: 2, is_tax_inclusive: false, @@ -506,6 +522,7 @@ describe("Total calculation", function () { original_tax_total: 10, }, ], + discount_subtotal: 0, total: 210, subtotal: 190.9090909090909, tax_total: 19.09090909090909, @@ -567,23 +584,24 @@ describe("Total calculation", function () { tax_lines: [ { rate: 10, - total: 7.8, + total: 8, subtotal: 10, }, ], adjustments: [ { amount: 20, - total: 22, subtotal: 20, + total: 22, }, ], subtotal: 100, - total: 85.8, + total: 88, original_total: 110, discount_total: 22, - discount_tax_total: 2.2, - tax_total: 7.8, + discount_subtotal: 20, + discount_tax_total: 2, + tax_total: 8, original_tax_total: 10, }, ], @@ -593,42 +611,44 @@ describe("Total calculation", function () { tax_lines: [ { rate: 10, - total: 2.28, + total: 2.3, subtotal: 2.5, }, ], adjustments: [ { amount: 2, - total: 2.2, subtotal: 2, + total: 2.2, }, ], subtotal: 25, - total: 25.08, + total: 25.3, original_total: 27.5, discount_total: 2.2, - discount_tax_total: 0.22, - tax_total: 2.28, + discount_subtotal: 2, + discount_tax_total: 0.2, + tax_total: 2.3, original_tax_total: 2.5, }, ], - total: 110.88, + total: 113.3, subtotal: 125, - tax_total: 10.08, + tax_total: 10.3, discount_total: 24.2, - discount_tax_total: 2.42, - original_total: 115.8, + discount_subtotal: 22, + discount_tax_total: 2.2, + original_total: 137.5, original_tax_total: 12.5, - item_total: 85.8, + item_total: 88, item_subtotal: 100, - item_tax_total: 7.8, + item_tax_total: 8, original_item_total: 110, original_item_subtotal: 100, original_item_tax_total: 10, - shipping_total: 25.08, + shipping_total: 25.3, shipping_subtotal: 25, - shipping_tax_total: 2.28, + shipping_tax_total: 2.3, original_shipping_tax_total: 2.5, original_shipping_subtotal: 25, original_shipping_total: 27.5, @@ -670,10 +690,18 @@ describe("Total calculation", function () { { unit_price: 50, quantity: 2, + detail: { + fulfilled_quantity: 2, + shipped_quantity: 2, + return_requested_quantity: 0, + return_received_quantity: 1, + return_dismissed_quantity: 1, + written_off_quantity: 1, + }, tax_lines: [ { rate: 10, - total: 7.8, + total: 8, subtotal: 10, }, ], @@ -684,50 +712,44 @@ describe("Total calculation", function () { total: 22, }, ], - detail: { - fulfilled_quantity: 2, - return_dismissed_quantity: 1, - return_received_quantity: 1, - return_requested_quantity: 0, - shipped_quantity: 2, - written_off_quantity: 1, - }, subtotal: 100, - total: 85.8, + total: 88, original_total: 110, discount_total: 22, - discount_tax_total: 2.2, - tax_total: 7.8, + discount_subtotal: 20, + discount_tax_total: 2, + tax_total: 8, original_tax_total: 10, - fulfilled_total: 85.8, - shipped_total: 85.8, - return_requested_total: 0, - return_received_total: 42.9, - return_dismissed_total: 42.9, - write_off_total: 42.9, - refundable_total: 0, refundable_total_per_unit: 0, + refundable_total: 0, + fulfilled_total: 88, + shipped_total: 88, + return_requested_total: 0, + return_received_total: 44, + return_dismissed_total: 44, + write_off_total: 44, }, ], - total: 85.8, + total: 88, subtotal: 100, - tax_total: 7.8, + tax_total: 8, discount_total: 22, - discount_tax_total: 2.2, - original_total: 88, + discount_subtotal: 20, + discount_tax_total: 2, + original_total: 110, original_tax_total: 10, - item_total: 85.8, + item_total: 88, item_subtotal: 100, - item_tax_total: 7.8, + item_tax_total: 8, original_item_total: 110, original_item_subtotal: 100, original_item_tax_total: 10, - fulfilled_total: 85.8, - shipped_total: 85.8, + fulfilled_total: 88, + shipped_total: 88, return_requested_total: 0, - return_received_total: 42.9, - return_dismissed_total: 42.9, - write_off_total: 42.9, + return_received_total: 44, + return_dismissed_total: 44, + write_off_total: 44, }) }) }) diff --git a/packages/core/utils/src/totals/adjustment/index.ts b/packages/core/utils/src/totals/adjustment/index.ts index 70feb29090..64fde1ae86 100644 --- a/packages/core/utils/src/totals/adjustment/index.ts +++ b/packages/core/utils/src/totals/adjustment/index.ts @@ -12,31 +12,44 @@ export function calculateAdjustmentTotal({ includesTax?: boolean taxRate?: BigNumberInput }) { - let total = MathBN.convert(0) + // the sum of all adjustment amounts excluding tax + let adjustmentsSubtotal = MathBN.convert(0) + // the sum of all adjustment amounts including tax + let adjustmentsTotal = MathBN.convert(0) + // the sum of all taxes on subtotals + let adjustmentsTaxTotal = MathBN.convert(0) + for (const adj of adjustments) { if (!isDefined(adj.amount)) { continue } - total = MathBN.add(total, adj.amount) + const adjustmentAmount = MathBN.convert(adj.amount) + adjustmentsSubtotal = MathBN.add(adjustmentsSubtotal, adjustmentAmount) if (isDefined(taxRate)) { - const rate = MathBN.div(taxRate, 100) - let taxAmount = MathBN.mult(adj.amount, rate) + const adjustmentSubtotal = includesTax + ? MathBN.div(adjustmentAmount, MathBN.add(1, taxRate)) + : adjustmentAmount - if (includesTax) { - taxAmount = MathBN.div(taxAmount, MathBN.add(1, rate)) + const adjustmentTaxTotal = MathBN.mult(adjustmentSubtotal, taxRate) + const adjustmentTotal = MathBN.add(adjustmentSubtotal, adjustmentTaxTotal) - adj["subtotal"] = new BigNumber(MathBN.sub(adj.amount, taxAmount)) - adj["total"] = new BigNumber(adj.amount) - } else { - total = MathBN.add(adj.amount, taxAmount) + adj["subtotal"] = new BigNumber(adjustmentSubtotal) + adj["total"] = new BigNumber(adjustmentTotal) - adj["subtotal"] = new BigNumber(adj.amount) - adj["total"] = new BigNumber(total) - } + adjustmentsTotal = MathBN.add(adjustmentsTotal, adjustmentTotal) + adjustmentsTaxTotal = MathBN.add(adjustmentsTaxTotal, adjustmentTaxTotal) + } else { + adj["subtotal"] = new BigNumber(adjustmentAmount) + adj["adjustmentAmount"] = new BigNumber(adjustmentAmount) + adjustmentsTotal = MathBN.add(adjustmentsTotal, adjustmentAmount) } } - return total + return { + adjustmentsTotal, + adjustmentsSubtotal, + adjustmentsTaxTotal, + } } diff --git a/packages/core/utils/src/totals/cart/index.ts b/packages/core/utils/src/totals/cart/index.ts index 72de3ac6d2..0192401e22 100644 --- a/packages/core/utils/src/totals/cart/index.ts +++ b/packages/core/utils/src/totals/cart/index.ts @@ -72,6 +72,7 @@ export function decorateCartTotals( let subtotal = MathBN.convert(0) let discountTotal = MathBN.convert(0) + let discountSubtotal = MathBN.convert(0) let discountTaxTotal = MathBN.convert(0) let itemsSubtotal = MathBN.convert(0) @@ -96,7 +97,6 @@ export function decorateCartTotals( const cartItems = items.map((item, index) => { const itemTotals = Object.assign(item, itemsTotals[item.id ?? index] ?? {}) - const itemSubtotal = itemTotals.subtotal const itemTotal = MathBN.convert(itemTotals.total) @@ -106,12 +106,14 @@ export function decorateCartTotals( const itemOriginalTaxTotal = MathBN.convert(itemTotals.original_tax_total) const itemDiscountTotal = MathBN.convert(itemTotals.discount_total) + const itemDiscountSubTotal = MathBN.convert(itemTotals.discount_subtotal) const itemDiscountTaxTotal = MathBN.convert(itemTotals.discount_tax_total) subtotal = MathBN.add(subtotal, itemSubtotal) discountTotal = MathBN.add(discountTotal, itemDiscountTotal) + discountSubtotal = MathBN.add(discountSubtotal, itemDiscountSubTotal) discountTaxTotal = MathBN.add(discountTaxTotal, itemDiscountTaxTotal) itemsTotal = MathBN.add(itemsTotal, itemTotal) @@ -138,47 +140,56 @@ export function decorateCartTotals( }) const cartShippingMethods = shippingMethods.map((shippingMethod, index) => { - const methodTotals = Object.assign( + const shippingMethodTotals = Object.assign( shippingMethod, shippingMethodsTotals[shippingMethod.id ?? index] ?? {} ) - const methodSubtotal = MathBN.convert(methodTotals.subtotal) + subtotal = MathBN.add(subtotal, shippingMethodTotals.subtotal) - subtotal = MathBN.add(subtotal, methodSubtotal) - - const methodTotal = MathBN.convert(methodTotals.total) - const methodOriginalTotal = MathBN.convert(methodTotals.original_total) - const methodTaxTotal = MathBN.convert(methodTotals.tax_total) - const methodOriginalTaxTotal = MathBN.convert( - methodTotals.original_tax_total + shippingSubtotal = MathBN.add( + shippingSubtotal, + shippingMethodTotals.subtotal ) - const methodDiscountTotal = MathBN.convert(methodTotals.discount_total) - const methodDiscountTaxTotal = MathBN.convert( - methodTotals.discount_tax_total - ) + shippingTotal = MathBN.add(shippingTotal, shippingMethodTotals.total) - shippingSubtotal = MathBN.add(shippingSubtotal, methodSubtotal) - shippingTotal = MathBN.add(shippingTotal, methodTotal) shippingOriginalTotal = MathBN.add( shippingOriginalTotal, - methodOriginalTotal + shippingMethodTotals.original_total ) + shippingOriginalSubtotal = MathBN.add( shippingOriginalSubtotal, - methodSubtotal + shippingMethodTotals.subtotal + ) + + shippingTaxTotal = MathBN.add( + shippingTaxTotal, + shippingMethodTotals.tax_total ) - shippingTaxTotal = MathBN.add(shippingTaxTotal, methodTaxTotal) shippingOriginalTaxTotal = MathBN.add( shippingOriginalTaxTotal, - methodOriginalTaxTotal + shippingMethodTotals.original_tax_total ) - discountTotal = MathBN.add(discountTotal, methodDiscountTotal) - discountTaxTotal = MathBN.add(discountTaxTotal, methodDiscountTaxTotal) - return methodTotals + discountTotal = MathBN.add( + discountTotal, + shippingMethodTotals.discount_total + ) + + discountSubtotal = MathBN.add( + discountSubtotal, + shippingMethodTotals.discount_subtotal + ) + + discountTaxTotal = MathBN.add( + discountTaxTotal, + shippingMethodTotals.discount_tax_total + ) + + return shippingMethodTotals }) const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal) @@ -189,17 +200,11 @@ export function decorateCartTotals( ) // TODO: Gift Card calculations + const originalTotal = MathBN.add(itemsOriginalTotal, shippingOriginalTotal) - const originalTempTotal = MathBN.add( - itemsOriginalSubtotal, - shippingOriginalTotal, - originalTaxTotal - ) - const originalTotal = MathBN.sub(originalTempTotal, discountTotal) // TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total) const tempTotal = MathBN.add(subtotal, taxTotal) - const total = MathBN.sub(tempTotal, discountTotal) - + const total = MathBN.sub(tempTotal, discountSubtotal) const cart = cartLike as any cart.total = new BigNumber(total) @@ -207,6 +212,7 @@ export function decorateCartTotals( cart.tax_total = new BigNumber(taxTotal) cart.discount_total = new BigNumber(discountTotal) + cart.discount_subtotal = new BigNumber(discountSubtotal) cart.discount_tax_total = new BigNumber(discountTaxTotal) // cart.gift_card_total = giftCardTotal.total || 0 diff --git a/packages/core/utils/src/totals/line-item/index.ts b/packages/core/utils/src/totals/line-item/index.ts index 249276db1d..6dbdab899a 100644 --- a/packages/core/utils/src/totals/line-item/index.ts +++ b/packages/core/utils/src/totals/line-item/index.ts @@ -37,6 +37,7 @@ export interface GetItemTotalOutput { original_total: BigNumber discount_total: BigNumber + discount_subtotal: BigNumber discount_tax_total: BigNumber refundable_total?: BigNumber @@ -73,7 +74,7 @@ export function getLineItemsTotals( function setRefundableTotal( item: GetItemTotalInput, - discountTotal: BigNumberInput, + discountsTotal: BigNumberInput, totals: GetItemTotalOutput, context: GetLineItemsTotalsContext ) { @@ -84,7 +85,7 @@ function setRefundableTotal( itemDetail.return_dismissed_quantity ?? 0 ) const currentQuantity = MathBN.sub(item.quantity, totalReturnedQuantity) - const discountPerUnit = MathBN.div(discountTotal, item.quantity) + const discountPerUnit = MathBN.div(discountsTotal, item.quantity) const refundableSubTotal = MathBN.sub( MathBN.mult(currentQuantity, item.unit_price), @@ -93,7 +94,6 @@ function setRefundableTotal( const taxTotal = calculateTaxTotal({ taxLines: item.tax_lines || [], - includesTax: context.includeTax, taxableAmount: refundableSubTotal, }) const refundableTotal = MathBN.add(refundableSubTotal, taxTotal) @@ -110,76 +110,67 @@ function getLineItemTotals( item: GetItemTotalInput, context: GetLineItemsTotalsContext ) { - const subtotal = MathBN.mult(item.unit_price, item.quantity) - - const sumTaxRate = MathBN.sum( + const isTaxInclusive = item.is_tax_inclusive ?? context.includeTax + const sumTax = MathBN.sum( ...((item.tax_lines ?? []).map((taxLine) => taxLine.rate) ?? []) ) - const discountTotal = calculateAdjustmentTotal({ + + const sumTaxRate = MathBN.div(sumTax, 100) + const totalItemPrice = MathBN.mult(item.unit_price, item.quantity) + + /* + If the price is inclusive of tax, we need to remove the taxed amount from the subtotal + Original Price = Total Price / (1 + Tax Rate) + */ + const subtotal = isTaxInclusive + ? MathBN.div(totalItemPrice, MathBN.add(1, sumTaxRate)) + : totalItemPrice + + const { + adjustmentsTotal: discountsTotal, + adjustmentsSubtotal: discountsSubtotal, + adjustmentsTaxTotal: discountTaxTotal, + } = calculateAdjustmentTotal({ adjustments: item.adjustments || [], - includesTax: context.includeTax, + includesTax: isTaxInclusive, taxRate: sumTaxRate, }) - const discountTaxTotal = MathBN.mult( - discountTotal, - MathBN.div(sumTaxRate, 100) - ) - const total = MathBN.sub(subtotal, discountTotal) + const taxTotal = calculateTaxTotal({ + taxLines: item.tax_lines || [], + taxableAmount: MathBN.sub(subtotal, discountsSubtotal), + setTotalField: "total", + }) + + const originalTaxTotal = calculateTaxTotal({ + taxLines: item.tax_lines || [], + taxableAmount: subtotal, + setTotalField: "subtotal", + }) const totals: GetItemTotalOutput = { quantity: item.quantity, unit_price: item.unit_price, subtotal: new BigNumber(subtotal), - total: new BigNumber(total), + total: new BigNumber( + MathBN.sum(MathBN.sub(subtotal, discountsSubtotal), taxTotal) + ), - original_total: new BigNumber(subtotal), + original_total: new BigNumber( + isTaxInclusive ? totalItemPrice : MathBN.add(subtotal, originalTaxTotal) + ), - discount_total: new BigNumber(discountTotal), + discount_total: new BigNumber(discountsTotal), + discount_subtotal: new BigNumber(discountsSubtotal), discount_tax_total: new BigNumber(discountTaxTotal), - tax_total: new BigNumber(0), - original_tax_total: new BigNumber(0), + tax_total: new BigNumber(taxTotal), + original_tax_total: new BigNumber(originalTaxTotal), } - const taxableAmountWithDiscount = MathBN.sub(subtotal, discountTotal) - const taxableAmount = subtotal - - const taxTotal = calculateTaxTotal({ - taxLines: item.tax_lines || [], - includesTax: context.includeTax, - taxableAmount: taxableAmountWithDiscount, - setTotalField: "total", - }) - totals.tax_total = new BigNumber(taxTotal) - if (isDefined(item.detail?.return_requested_quantity)) { - setRefundableTotal(item, discountTotal, totals, context) - } - - const originalTaxTotal = calculateTaxTotal({ - taxLines: item.tax_lines || [], - includesTax: context.includeTax, - taxableAmount, - setTotalField: "subtotal", - }) - totals.original_tax_total = new BigNumber(originalTaxTotal) - - const isTaxInclusive = context.includeTax ?? item.is_tax_inclusive - - if (isTaxInclusive) { - totals.subtotal = new BigNumber( - MathBN.sub( - MathBN.mult(item.unit_price, totals.quantity), - originalTaxTotal - ) - ) - } else { - const newTotal = MathBN.add(total, totals.tax_total) - const originalTotal = MathBN.add(subtotal, totals.original_tax_total) - totals.total = new BigNumber(newTotal) - totals.original_total = new BigNumber(originalTotal) + setRefundableTotal(item, discountsTotal, totals, context) } const div = MathBN.eq(item.quantity, 0) ? 1 : item.quantity diff --git a/packages/core/utils/src/totals/shipping-method/index.ts b/packages/core/utils/src/totals/shipping-method/index.ts index a91a587301..8736eb9267 100644 --- a/packages/core/utils/src/totals/shipping-method/index.ts +++ b/packages/core/utils/src/totals/shipping-method/index.ts @@ -25,6 +25,7 @@ export interface GetShippingMethodTotalOutput { original_total: BigNumber discount_total: BigNumber + discount_subtotal: BigNumber discount_tax_total: BigNumber tax_total: BigNumber @@ -35,17 +36,13 @@ export function getShippingMethodsTotals( shippingMethods: GetShippingMethodTotalInput[], context: GetShippingMethodsTotalsContext ): Record { - const { includeTax } = context - const shippingMethodsTotals = {} let index = 0 for (const shippingMethod of shippingMethods) { shippingMethodsTotals[shippingMethod.id ?? index] = getShippingMethodTotals( shippingMethod, - { - includeTax: includeTax || shippingMethod.is_tax_inclusive, - } + context ) index++ } @@ -57,75 +54,61 @@ export function getShippingMethodTotals( shippingMethod: GetShippingMethodTotalInput, context: GetShippingMethodsTotalsContext ) { - const amount = MathBN.convert(shippingMethod.amount) - const subtotal = MathBN.convert(shippingMethod.amount) + const isTaxInclusive = shippingMethod.is_tax_inclusive ?? context.includeTax - const sumTaxRate = MathBN.sum( + const shippingMethodAmount = MathBN.convert(shippingMethod.amount) + const sumTax = MathBN.sum( ...(shippingMethod.tax_lines?.map((taxLine) => taxLine.rate) ?? []) ) + const sumTaxRate = MathBN.div(sumTax, 100) - const discountTotal = calculateAdjustmentTotal({ + const subtotal = isTaxInclusive + ? MathBN.div(shippingMethodAmount, MathBN.add(1, sumTaxRate)) + : shippingMethodAmount + + const { + adjustmentsTotal: discountsTotal, + adjustmentsSubtotal: discountsSubtotal, + adjustmentsTaxTotal: discountsTaxTotal, + } = calculateAdjustmentTotal({ adjustments: shippingMethod.adjustments || [], - includesTax: context.includeTax, + includesTax: isTaxInclusive, taxRate: sumTaxRate, }) - const discountTaxTotal = MathBN.mult( - discountTotal, - MathBN.div(sumTaxRate, 100) - ) - - const total = MathBN.sub(amount, discountTotal) - - const totals: GetShippingMethodTotalOutput = { - amount: new BigNumber(amount), - - subtotal: new BigNumber(subtotal), - - total: new BigNumber(total), - original_total: new BigNumber(amount), - - discount_total: new BigNumber(discountTotal), - discount_tax_total: new BigNumber(discountTaxTotal), - - tax_total: new BigNumber(0), - original_tax_total: new BigNumber(0), - } const taxLines = shippingMethod.tax_lines || [] - const taxableAmountWithDiscount = MathBN.sub(subtotal, discountTotal) - const taxableAmount = subtotal - const taxTotal = calculateTaxTotal({ taxLines, - includesTax: context.includeTax, - taxableAmount: taxableAmountWithDiscount, + taxableAmount: MathBN.sub(subtotal, discountsSubtotal), setTotalField: "total", }) - totals.tax_total = new BigNumber(taxTotal) const originalTaxTotal = calculateTaxTotal({ taxLines, - includesTax: context.includeTax, - taxableAmount, + taxableAmount: subtotal, setTotalField: "subtotal", }) - totals.original_tax_total = new BigNumber(originalTaxTotal) - const isTaxInclusive = context.includeTax ?? shippingMethod.is_tax_inclusive + const totals: GetShippingMethodTotalOutput = { + amount: new BigNumber(shippingMethodAmount), - if (isTaxInclusive) { - const subtotal = MathBN.sub(shippingMethod.amount, originalTaxTotal) - totals.subtotal = new BigNumber(subtotal) - } else { - const originalTotal = MathBN.add( - shippingMethod.amount, - totals.original_tax_total - ) - const total = MathBN.add(totals.total, totals.tax_total) + subtotal: new BigNumber(subtotal), + total: new BigNumber( + MathBN.sum(MathBN.sub(subtotal, discountsSubtotal), taxTotal) + ), + original_total: new BigNumber( + isTaxInclusive + ? shippingMethodAmount + : MathBN.add(subtotal, originalTaxTotal) + ), - totals.total = new BigNumber(total) - totals.original_total = new BigNumber(originalTotal) + discount_total: new BigNumber(discountsTotal), + discount_subtotal: new BigNumber(discountsSubtotal), + discount_tax_total: new BigNumber(discountsTaxTotal), + + tax_total: new BigNumber(taxTotal), + original_tax_total: new BigNumber(originalTaxTotal), } return totals diff --git a/packages/core/utils/src/totals/tax/index.ts b/packages/core/utils/src/totals/tax/index.ts index dec517981f..de62883e20 100644 --- a/packages/core/utils/src/totals/tax/index.ts +++ b/packages/core/utils/src/totals/tax/index.ts @@ -4,24 +4,23 @@ import { MathBN } from "../math" export function calculateTaxTotal({ taxLines, - includesTax, taxableAmount, setTotalField, }: { taxLines: Pick[] - includesTax?: boolean taxableAmount: BigNumberInput setTotalField?: string }) { + if (MathBN.lte(taxableAmount, 0)) { + return MathBN.convert(0) + } + let taxTotal = MathBN.convert(0) + for (const taxLine of taxLines) { const rate = MathBN.div(taxLine.rate, 100) let taxAmount = MathBN.mult(taxableAmount, rate) - if (includesTax) { - taxAmount = MathBN.div(taxAmount, MathBN.add(1, rate)) - } - if (setTotalField) { ;(taxLine as any)[setTotalField] = new BigNumber(taxAmount) } @@ -41,10 +40,18 @@ export function calculateAmountsWithTax({ amount: number includesTax?: boolean }) { + const sumTaxRate = MathBN.div( + MathBN.sum(...((taxLines ?? []).map((taxLine) => taxLine.rate) ?? [])), + 100 + ) + + const taxableAmount = includesTax + ? MathBN.div(amount, MathBN.add(1, sumTaxRate)) + : amount + const tax = calculateTaxTotal({ taxLines, - includesTax, - taxableAmount: amount, + taxableAmount, }) return { diff --git a/packages/medusa/src/api/store/carts/query-config.ts b/packages/medusa/src/api/store/carts/query-config.ts index 658ee7d49b..ce7d772183 100644 --- a/packages/medusa/src/api/store/carts/query-config.ts +++ b/packages/medusa/src/api/store/carts/query-config.ts @@ -9,6 +9,7 @@ export const defaultStoreCartFields = [ "subtotal", "tax_total", "discount_total", + "discount_subtotal", "discount_tax_total", "original_total", "original_tax_total", diff --git a/packages/medusa/src/api/store/orders/query-config.ts b/packages/medusa/src/api/store/orders/query-config.ts index 108a6bc7e2..cd1efbf35b 100644 --- a/packages/medusa/src/api/store/orders/query-config.ts +++ b/packages/medusa/src/api/store/orders/query-config.ts @@ -26,6 +26,7 @@ export const defaultStoreRetrieveOrderFields = [ "subtotal", "tax_total", "discount_total", + "discount_subtotal", "discount_tax_total", "original_total", "original_tax_total", diff --git a/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts b/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts index d28418b60f..13c407af64 100644 --- a/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts +++ b/packages/modules/cart/integration-tests/__tests__/services/cart-module/index.spec.ts @@ -2548,6 +2548,7 @@ moduleIntegrationTestRunner({ total: 0, original_total: 100, discount_total: 100, + discount_subtotal: 100, discount_tax_total: 0, tax_total: 0, original_tax_total: 0, @@ -2567,6 +2568,10 @@ moduleIntegrationTestRunner({ value: "100", precision: 20, }, + raw_discount_subtotal: { + value: "100", + precision: 20, + }, raw_discount_tax_total: { value: "0", precision: 20, @@ -2647,6 +2652,7 @@ moduleIntegrationTestRunner({ total: 200, original_total: 400, discount_total: 200, + discount_subtotal: 200, discount_tax_total: 0, tax_total: 0, original_tax_total: 0, @@ -2666,6 +2672,10 @@ moduleIntegrationTestRunner({ value: "200", precision: 20, }, + raw_discount_subtotal: { + value: "200", + precision: 20, + }, raw_discount_tax_total: { value: "0", precision: 20, @@ -2704,6 +2714,7 @@ moduleIntegrationTestRunner({ total: 10, original_total: 10, discount_total: 0, + discount_subtotal: 0, discount_tax_total: 0, tax_total: 0, original_tax_total: 0, @@ -2723,6 +2734,10 @@ moduleIntegrationTestRunner({ value: "0", precision: 20, }, + raw_discount_subtotal: { + value: "0", + precision: 20, + }, raw_discount_tax_total: { value: "0", precision: 20, @@ -2741,8 +2756,9 @@ moduleIntegrationTestRunner({ subtotal: 510, tax_total: 0, discount_total: 300, + discount_subtotal: 300, discount_tax_total: 0, - original_total: 210, + original_total: 510, original_tax_total: 0, item_total: 200, item_subtotal: 500, @@ -2772,12 +2788,16 @@ moduleIntegrationTestRunner({ value: "300", precision: 20, }, + raw_discount_subtotal: { + value: "300", + precision: 20, + }, raw_discount_tax_total: { value: "0", precision: 20, }, raw_original_total: { - value: "210", + value: "510", precision: 20, }, raw_original_tax_total: { diff --git a/packages/modules/order/integration-tests/__tests__/create-order.ts b/packages/modules/order/integration-tests/__tests__/create-order.ts index 515d3c2442..e8fc192695 100644 --- a/packages/modules/order/integration-tests/__tests__/create-order.ts +++ b/packages/modules/order/integration-tests/__tests__/create-order.ts @@ -211,7 +211,7 @@ moduleIntegrationTestRunner({ expect(created.summary).toEqual( expect.objectContaining({ transaction_total: 68, - pending_difference: -20.21999000999001, + pending_difference: -20.10799200799201, paid_total: 68, refunded_total: 0, }) @@ -236,7 +236,7 @@ moduleIntegrationTestRunner({ expect(serializedOrder.summary).toEqual( expect.objectContaining({ transaction_total: 48, - pending_difference: -0.21999000999001, + pending_difference: -0.10799200799201, paid_total: 68, refunded_total: 20, }) @@ -255,7 +255,7 @@ moduleIntegrationTestRunner({ expect(serializedOrder2.summary).toEqual( expect.objectContaining({ transaction_total: 68, - pending_difference: -20.21999000999001, + pending_difference: -20.10799200799201, paid_total: 68, refunded_total: 0, }) @@ -282,7 +282,7 @@ moduleIntegrationTestRunner({ paid_total: 68, refunded_total: 50, transaction_total: 18, - pending_difference: 29.78000999000999, + pending_difference: 29.89200799200799, }) ) @@ -301,7 +301,7 @@ moduleIntegrationTestRunner({ paid_total: 68, refunded_total: 70, transaction_total: -2, - pending_difference: 49.78000999000999, + pending_difference: 49.89200799200799, }) ) })