From 096372463e82e411510e74ce0d8ef5a6d12a69cc Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Thu, 30 May 2024 07:23:57 -0300 Subject: [PATCH] chore(promotion): big number calc (#7537) --- .../src/promotion/common/compute-actions.ts | 12 +- .../core/utils/src/totals/promotion/index.ts | 23 +-- .../promotion-module/compute-actions.spec.ts | 134 +++++++++--------- .../src/services/promotion-module.ts | 16 ++- .../promotion/src/types/application-method.ts | 17 +-- .../promotion/src/types/campaign-budget.ts | 10 +- .../src/utils/compute-actions/buy-get.ts | 36 +++-- .../src/utils/compute-actions/line-items.ts | 17 ++- .../utils/compute-actions/shipping-methods.ts | 56 +++++--- .../src/utils/compute-actions/usage.ts | 11 +- .../utils/validations/application-method.ts | 6 +- .../src/utils/validations/promotion-rule.ts | 4 +- 12 files changed, 194 insertions(+), 148 deletions(-) diff --git a/packages/core/types/src/promotion/common/compute-actions.ts b/packages/core/types/src/promotion/common/compute-actions.ts index 3cd45007ad..4a665f55b6 100644 --- a/packages/core/types/src/promotion/common/compute-actions.ts +++ b/packages/core/types/src/promotion/common/compute-actions.ts @@ -1,3 +1,5 @@ +import { BigNumberInput } from "../../totals" + /** * A compute action informs you what adjustment must be made to a cart item or shipping method. */ @@ -48,7 +50,7 @@ export interface AddItemAdjustmentAction { /** * The amount to remove off the item's total. */ - amount: number + amount: BigNumberInput /** * The promotion's code. @@ -103,7 +105,7 @@ export interface AddShippingMethodAdjustment { /** * The amount to remove off the shipping method's total. */ - amount: number + amount: BigNumberInput /** * The promotion's code. @@ -163,12 +165,12 @@ export interface ComputeActionItemLine extends Record { /** * The quantity of the line item. */ - quantity: number + quantity: BigNumberInput /** * The subtotal of the line item. */ - subtotal: number + subtotal: BigNumberInput /** * The adjustments applied before on the line item. @@ -188,7 +190,7 @@ export interface ComputeActionShippingLine extends Record { /** * The subtotal of the shipping method. */ - subtotal: number + subtotal: BigNumberInput /** * The adjustments applied before on the shipping method. diff --git a/packages/core/utils/src/totals/promotion/index.ts b/packages/core/utils/src/totals/promotion/index.ts index df24544819..b869c32ca3 100644 --- a/packages/core/utils/src/totals/promotion/index.ts +++ b/packages/core/utils/src/totals/promotion/index.ts @@ -1,15 +1,20 @@ +import { BigNumberInput } from "@medusajs/types" import { ApplicationMethodAllocation, ApplicationMethodType, } from "../../promotion" +import { MathBN } from "../math" function getPromotionValueForPercentage(promotion, lineItemTotal) { - return (promotion.value / 100) * lineItemTotal + return MathBN.mult(MathBN.div(promotion.value, 100), lineItemTotal) } function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) { if (promotion.allocation === ApplicationMethodAllocation.ACROSS) { - return (lineItemTotal / lineItemsTotal) * promotion.value + return MathBN.mult( + MathBN.div(lineItemTotal, lineItemsTotal), + promotion.value + ) } return promotion.value @@ -25,26 +30,26 @@ export function getPromotionValue(promotion, lineItemTotal, lineItemsTotal) { export function getApplicableQuantity(lineItem, maxQuantity) { if (maxQuantity && lineItem.quantity) { - return Math.min(lineItem.quantity, maxQuantity) + return MathBN.min(lineItem.quantity, maxQuantity) } return lineItem.quantity } function getLineItemUnitPrice(lineItem) { - return lineItem.subtotal / lineItem.quantity + return MathBN.div(lineItem.subtotal, lineItem.quantity) } export function calculateAdjustmentAmountFromPromotion( lineItem, promotion, - lineItemsTotal = 0 + lineItemsTotal: BigNumberInput = 0 ) { const quantity = getApplicableQuantity(lineItem, promotion.max_quantity) - const lineItemTotal = getLineItemUnitPrice(lineItem) * quantity - const applicableTotal = lineItemTotal - promotion.applied_value + const lineItemTotal = MathBN.mult(getLineItemUnitPrice(lineItem), quantity) + const applicableTotal = MathBN.sub(lineItemTotal, promotion.applied_value) - if (applicableTotal <= 0) { + if (MathBN.lte(applicableTotal, 0)) { return applicableTotal } @@ -54,5 +59,5 @@ export function calculateAdjustmentAmountFromPromotion( lineItemsTotal ) - return Math.min(promotionValue, applicableTotal) + return MathBN.min(promotionValue, applicableTotal) } diff --git a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts index a566f7195b..19ccd55d10 100644 --- a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts +++ b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts @@ -1,7 +1,7 @@ import { Modules } from "@medusajs/modules-sdk" import { IPromotionModuleService } from "@medusajs/types" import { ApplicationMethodType, PromotionType } from "@medusajs/utils" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" +import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" import { createCampaigns } from "../../../__fixtures__/campaigns" import { createDefaultPromotion } from "../../../__fixtures__/promotion" @@ -177,7 +177,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -278,7 +278,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -391,7 +391,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -453,7 +453,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -514,7 +514,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -578,7 +578,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -679,7 +679,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -792,7 +792,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -854,7 +854,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -911,7 +911,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -977,7 +977,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1050,7 +1050,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1150,7 +1150,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1262,7 +1262,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1323,7 +1323,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -1379,7 +1379,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -1442,7 +1442,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1515,7 +1515,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1614,7 +1614,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1725,7 +1725,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -1798,7 +1798,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -1854,7 +1854,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -1919,7 +1919,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -1992,7 +1992,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2069,7 +2069,7 @@ moduleIntegrationTestRunner({ { prevent_auto_promotions: true } ) - expect(result).toEqual([]) + expect(JSON.parse(JSON.stringify(result))).toEqual([]) }) it("should compute the correct item amendments when there are multiple promotions to apply", async () => { @@ -2156,7 +2156,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2262,7 +2262,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2320,7 +2320,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -2373,7 +2373,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -2436,7 +2436,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2509,7 +2509,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2586,7 +2586,7 @@ moduleIntegrationTestRunner({ { prevent_auto_promotions: true } ) - expect(result).toEqual([]) + expect(JSON.parse(JSON.stringify(result))).toEqual([]) }) it("should compute the correct item amendments when there are multiple promotions to apply", async () => { @@ -2674,7 +2674,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2786,7 +2786,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -2856,7 +2856,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -2909,7 +2909,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -2973,17 +2973,17 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", - amount: 166.66666666666669, + amount: 166.66666666666666, code: "PROMOTION_TEST", }, { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_standard", - amount: 33.33333333333333, + amount: 33.333333333333336, code: "PROMOTION_TEST", }, ]) @@ -3045,17 +3045,17 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", - amount: 166.66666666666669, + amount: 166.66666666666666, code: "PROMOTION_TEST", }, { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_standard", - amount: 33.33333333333333, + amount: 33.333333333333336, code: "PROMOTION_TEST", }, ]) @@ -3143,17 +3143,17 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", - amount: 166.66666666666669, + amount: 166.66666666666666, code: "PROMOTION_TEST", }, { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_standard", - amount: 33.33333333333333, + amount: 33.333333333333336, code: "PROMOTION_TEST", }, { @@ -3253,7 +3253,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -3310,7 +3310,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -3362,7 +3362,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -3424,7 +3424,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -3496,7 +3496,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -3594,7 +3594,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -3704,7 +3704,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", @@ -3767,7 +3767,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -3819,7 +3819,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" }, ]) }) @@ -3877,7 +3877,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -3944,7 +3944,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -4031,7 +4031,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -4130,7 +4130,7 @@ moduleIntegrationTestRunner({ } ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt", @@ -4215,7 +4215,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "removeItemAdjustment", adjustment_id: "test-adjustment", @@ -4302,7 +4302,7 @@ moduleIntegrationTestRunner({ ], }) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "removeShippingMethodAdjustment", adjustment_id: "test-adjustment", @@ -4311,13 +4311,13 @@ moduleIntegrationTestRunner({ { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_express", - amount: 166.66666666666669, + amount: 166.66666666666666, code: "PROMOTION_TEST", }, { action: "addShippingMethodAdjustment", shipping_method_id: "shipping_method_standard", - amount: 33.33333333333333, + amount: 33.333333333333336, code: "PROMOTION_TEST", }, ]) @@ -4408,7 +4408,7 @@ moduleIntegrationTestRunner({ context ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt2", @@ -4501,7 +4501,7 @@ moduleIntegrationTestRunner({ context ) - expect(result).toEqual([]) + expect(JSON.parse(JSON.stringify(result))).toEqual([]) }) it("should compute actions for multiple items when conditions for target qty exceed one item", async () => { @@ -4588,7 +4588,7 @@ moduleIntegrationTestRunner({ context ) - expect(result).toEqual([ + expect(JSON.parse(JSON.stringify(result))).toEqual([ { action: "addItemAdjustment", item_id: "item_cotton_tshirt2", @@ -4688,7 +4688,7 @@ moduleIntegrationTestRunner({ context ) - expect(result).toEqual([]) + expect(JSON.parse(JSON.stringify(result))).toEqual([]) }) }) }) diff --git a/packages/modules/promotion/src/services/promotion-module.ts b/packages/modules/promotion/src/services/promotion-module.ts index 5c2ab3967a..505250cf79 100644 --- a/packages/modules/promotion/src/services/promotion-module.ts +++ b/packages/modules/promotion/src/services/promotion-module.ts @@ -14,6 +14,7 @@ import { ComputedActions, InjectManager, InjectTransactionManager, + MathBN, MedusaContext, MedusaError, ModulesSdkUtils, @@ -23,6 +24,7 @@ import { isDefined, isPresent, isString, + transformPropertiesToBigNumber, } from "@medusajs/utils" import { ApplicationMethod, @@ -176,12 +178,14 @@ export default class PromotionModuleService< campaignBudget.id ) || { id: campaignBudget.id, used: campaignBudget.used ?? 0 } - campaignBudgetData.used = - (campaignBudgetData.used ?? 0) + computedAction.amount + campaignBudgetData.used = MathBN.add( + campaignBudgetData.used ?? 0, + computedAction.amount + ) if ( campaignBudget.limit && - campaignBudgetData.used > campaignBudget.limit + MathBN.gt(campaignBudgetData.used, campaignBudget.limit) ) { continue } @@ -202,12 +206,12 @@ export default class PromotionModuleService< const campaignBudgetData = { id: campaignBudget.id, - used: (campaignBudget.used ?? 0) + 1, + used: MathBN.add(campaignBudget.used ?? 0, 1), } if ( campaignBudget.limit && - campaignBudgetData.used > campaignBudget.limit + MathBN.gt(campaignBudgetData.used, campaignBudget.limit) ) { continue } @@ -424,6 +428,8 @@ export default class PromotionModuleService< } } + transformPropertiesToBigNumber(computedActions, { include: ["amount"] }) + return computedActions } diff --git a/packages/modules/promotion/src/types/application-method.ts b/packages/modules/promotion/src/types/application-method.ts index a8ef699d5c..c173d63452 100644 --- a/packages/modules/promotion/src/types/application-method.ts +++ b/packages/modules/promotion/src/types/application-method.ts @@ -2,6 +2,7 @@ import { ApplicationMethodAllocationValues, ApplicationMethodTargetTypeValues, ApplicationMethodTypeValues, + BigNumberInput, PromotionDTO, } from "@medusajs/types" @@ -11,12 +12,12 @@ export interface CreateApplicationMethodDTO { type: ApplicationMethodTypeValues target_type: ApplicationMethodTargetTypeValues allocation?: ApplicationMethodAllocationValues - value?: number + value?: BigNumberInput currency_code: string promotion: Promotion | string | PromotionDTO - max_quantity?: number | null - buy_rules_min_quantity?: number | null - apply_to_quantity?: number | null + max_quantity?: BigNumberInput | null + buy_rules_min_quantity?: BigNumberInput | null + apply_to_quantity?: BigNumberInput | null } export interface UpdateApplicationMethodDTO { @@ -24,10 +25,10 @@ export interface UpdateApplicationMethodDTO { type?: ApplicationMethodTypeValues target_type?: ApplicationMethodTargetTypeValues allocation?: ApplicationMethodAllocationValues - value?: number + value?: BigNumberInput currency_code?: string promotion?: Promotion | string | PromotionDTO - max_quantity?: number | null - buy_rules_min_quantity?: number | null - apply_to_quantity?: number | null + max_quantity?: BigNumberInput | null + buy_rules_min_quantity?: BigNumberInput | null + apply_to_quantity?: BigNumberInput | null } diff --git a/packages/modules/promotion/src/types/campaign-budget.ts b/packages/modules/promotion/src/types/campaign-budget.ts index 6b8e0265a4..d5724e8566 100644 --- a/packages/modules/promotion/src/types/campaign-budget.ts +++ b/packages/modules/promotion/src/types/campaign-budget.ts @@ -1,18 +1,18 @@ -import { CampaignBudgetTypeValues } from "@medusajs/types" +import { BigNumberInput, CampaignBudgetTypeValues } from "@medusajs/types" import { Campaign } from "@models" export interface CreateCampaignBudgetDTO { type?: CampaignBudgetTypeValues - limit?: number | null + limit?: BigNumberInput | null currency_code?: string | null - used?: number + used?: BigNumberInput campaign?: Campaign | string } export interface UpdateCampaignBudgetDTO { id: string type?: CampaignBudgetTypeValues - limit?: number | null + limit?: BigNumberInput | null currency_code?: string | null - used?: number + used?: BigNumberInput } diff --git a/packages/modules/promotion/src/utils/compute-actions/buy-get.ts b/packages/modules/promotion/src/utils/compute-actions/buy-get.ts index e6c33fc83b..3028da49bc 100644 --- a/packages/modules/promotion/src/utils/compute-actions/buy-get.ts +++ b/packages/modules/promotion/src/utils/compute-actions/buy-get.ts @@ -1,7 +1,8 @@ -import { PromotionTypes } from "@medusajs/types" +import { BigNumberInput, PromotionTypes } from "@medusajs/types" import { ApplicationMethodTargetType, ComputedActions, + MathBN, MedusaError, PromotionType, isPresent, @@ -13,7 +14,7 @@ import { computeActionForBudgetExceeded } from "./usage" export function getComputedActionsForBuyGet( promotion: PromotionTypes.PromotionDTO, itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS], - methodIdPromoValueMap: Map + methodIdPromoValueMap: Map ): PromotionTypes.ComputeActions[] { const buyRulesMinQuantity = promotion.application_method?.buy_rules_min_quantity @@ -33,14 +34,16 @@ export function getComputedActionsForBuyGet( return [] } - const validQuantity = itemsContext - .filter((item) => areRulesValidForContext(buyRules, item)) - .reduce((acc, next) => acc + next.quantity, 0) + const validQuantity = MathBN.sum( + ...itemsContext + .filter((item) => areRulesValidForContext(buyRules, item)) + .map((item) => item.quantity) + ) if ( !buyRulesMinQuantity || !applyToQuantity || - buyRulesMinQuantity > validQuantity + MathBN.gt(buyRulesMinQuantity, validQuantity) ) { return [] } @@ -49,21 +52,24 @@ export function getComputedActionsForBuyGet( .filter((item) => areRulesValidForContext(targetRules, item)) .filter((item) => isPresent(item.subtotal) && isPresent(item.quantity)) .sort((a, b) => { - const aPrice = a.subtotal / a.quantity - const bPrice = b.subtotal / b.quantity + const aPrice = MathBN.div(a.subtotal, a.quantity) + const bPrice = MathBN.div(b.subtotal, b.quantity) - return bPrice - aPrice + return MathBN.lt(bPrice, aPrice) ? -1 : 1 }) - let remainingQtyToApply = applyToQuantity + let remainingQtyToApply = MathBN.convert(applyToQuantity) for (const method of validItemsForTargetRules) { const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 - const multiplier = Math.min(method.quantity, remainingQtyToApply) - const amount = (method.subtotal / method.quantity) * multiplier - const newRemainingQtyToApply = remainingQtyToApply - multiplier + const multiplier = MathBN.min(method.quantity, remainingQtyToApply) + const amount = MathBN.mult( + MathBN.div(method.subtotal, method.quantity), + multiplier + ) + const newRemainingQtyToApply = MathBN.sub(remainingQtyToApply, multiplier) - if (newRemainingQtyToApply < 0 || amount <= 0) { + if (MathBN.lt(newRemainingQtyToApply, 0) || MathBN.lte(amount, 0)) { break } else { remainingQtyToApply = newRemainingQtyToApply @@ -80,7 +86,7 @@ export function getComputedActionsForBuyGet( continue } - methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) + methodIdPromoValueMap.set(method.id, MathBN.add(appliedPromoValue, amount)) computedActions.push({ action: ComputedActions.ADD_ITEM_ADJUSTMENT, diff --git a/packages/modules/promotion/src/utils/compute-actions/line-items.ts b/packages/modules/promotion/src/utils/compute-actions/line-items.ts index 1ed3be1429..6570279b49 100644 --- a/packages/modules/promotion/src/utils/compute-actions/line-items.ts +++ b/packages/modules/promotion/src/utils/compute-actions/line-items.ts @@ -1,10 +1,12 @@ import { ApplicationMethodAllocationValues, + BigNumberInput, PromotionTypes, } from "@medusajs/types" import { ApplicationMethodAllocation, ComputedActions, + MathBN, MedusaError, ApplicationMethodTargetType as TargetType, calculateAdjustmentAmountFromPromotion, @@ -68,7 +70,7 @@ function applyPromotionToItems( items: | PromotionTypes.ComputeActionContext[TargetType.ITEMS] | PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS], - appliedPromotionsMap: Map, + appliedPromotionsMap: Map, allocationOverride?: ApplicationMethodAllocationValues ): PromotionTypes.ComputeActions[] { const { application_method: applicationMethod } = promotion @@ -81,13 +83,16 @@ function applyPromotionToItems( const isTargetLineItems = target === TargetType.ITEMS const isTargetOrder = target === TargetType.ORDER - let lineItemsTotal = 0 + let lineItemsTotal = MathBN.convert(0) if (allocation === ApplicationMethodAllocation.ACROSS) { lineItemsTotal = applicableItems.reduce( (acc, item) => - acc + item.subtotal - (appliedPromotionsMap.get(item.id) ?? 0), - 0 + MathBN.sub( + MathBN.add(acc, item.subtotal), + appliedPromotionsMap.get(item.id) ?? 0 + ), + MathBN.convert(0) ) } @@ -113,7 +118,7 @@ function applyPromotionToItems( lineItemsTotal ) - if (amount <= 0) { + if (MathBN.lte(amount, 0)) { continue } @@ -128,7 +133,7 @@ function applyPromotionToItems( continue } - appliedPromotionsMap.set(item.id, appliedPromoValue + amount) + appliedPromotionsMap.set(item.id, MathBN.add(appliedPromoValue, amount)) if (isTargetLineItems || isTargetOrder) { computedActions.push({ diff --git a/packages/modules/promotion/src/utils/compute-actions/shipping-methods.ts b/packages/modules/promotion/src/utils/compute-actions/shipping-methods.ts index ec3fadbc21..f74d00e891 100644 --- a/packages/modules/promotion/src/utils/compute-actions/shipping-methods.ts +++ b/packages/modules/promotion/src/utils/compute-actions/shipping-methods.ts @@ -1,9 +1,10 @@ -import { PromotionTypes } from "@medusajs/types" +import { BigNumberInput, PromotionTypes } from "@medusajs/types" import { ApplicationMethodAllocation, ApplicationMethodTargetType, ApplicationMethodType, ComputedActions, + MathBN, MedusaError, } from "@medusajs/utils" import { areRulesValidForContext } from "../validations" @@ -47,7 +48,7 @@ export function getComputedActionsForShippingMethods( export function applyPromotionToShippingMethods( promotion: PromotionTypes.PromotionDTO, shippingMethods: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS], - methodIdPromoValueMap: Map + methodIdPromoValueMap: Map ): PromotionTypes.ComputeActions[] { const { application_method: applicationMethod } = promotion const allocation = applicationMethod?.allocation! @@ -60,16 +61,19 @@ export function applyPromotionToShippingMethods( } const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 - let promotionValue = applicationMethod?.value ?? 0 - const applicableTotal = method.subtotal - appliedPromoValue + let promotionValue = MathBN.convert(applicationMethod?.value ?? 0) + const applicableTotal = MathBN.sub(method.subtotal, appliedPromoValue) if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { - promotionValue = (promotionValue / 100) * applicableTotal + promotionValue = MathBN.mult( + MathBN.div(promotionValue, 100), + applicableTotal + ) } - const amount = Math.min(promotionValue, applicableTotal) + const amount = MathBN.min(promotionValue, applicableTotal) - if (amount <= 0) { + if (MathBN.lte(amount, 0)) { continue } @@ -84,7 +88,10 @@ export function applyPromotionToShippingMethods( continue } - methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) + methodIdPromoValueMap.set( + method.id, + MathBN.add(appliedPromoValue, amount) + ) computedActions.push({ action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT, @@ -99,10 +106,13 @@ export function applyPromotionToShippingMethods( const totalApplicableValue = shippingMethods!.reduce((acc, method) => { const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 - return acc + (method.subtotal ?? 0) - appliedPromoValue - }, 0) + return MathBN.add( + acc, + MathBN.sub(method.subtotal ?? 0, appliedPromoValue) + ) + }, MathBN.convert(0)) - if (totalApplicableValue <= 0) { + if (MathBN.lte(totalApplicableValue, 0)) { return computedActions } @@ -115,19 +125,24 @@ export function applyPromotionToShippingMethods( const applicableTotal = method.subtotal const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 - // TODO: should we worry about precision here? - let applicablePromotionValue = - (applicableTotal / totalApplicableValue) * promotionValue - + let applicablePromotionValue = MathBN.sub( + MathBN.mult( + MathBN.div(applicableTotal, totalApplicableValue), + promotionValue + ), appliedPromoValue + ) if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { - applicablePromotionValue = - (promotionValue / 100) * (applicableTotal - appliedPromoValue) + applicablePromotionValue = MathBN.sub( + MathBN.mult(MathBN.div(promotionValue, 100), applicableTotal), + appliedPromoValue + ) } - const amount = Math.min(applicablePromotionValue, applicableTotal) + const amount = MathBN.min(applicablePromotionValue, applicableTotal) - if (amount <= 0) { + if (MathBN.lte(amount, 0)) { continue } @@ -142,7 +157,10 @@ export function applyPromotionToShippingMethods( continue } - methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) + methodIdPromoValueMap.set( + method.id, + MathBN.add(appliedPromoValue, amount) + ) computedActions.push({ action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT, diff --git a/packages/modules/promotion/src/utils/compute-actions/usage.ts b/packages/modules/promotion/src/utils/compute-actions/usage.ts index 678fb65064..c037dc873d 100644 --- a/packages/modules/promotion/src/utils/compute-actions/usage.ts +++ b/packages/modules/promotion/src/utils/compute-actions/usage.ts @@ -1,9 +1,10 @@ import { + BigNumberInput, CampaignBudgetExceededAction, ComputeActions, PromotionDTO, } from "@medusajs/types" -import { CampaignBudgetType, ComputedActions } from "@medusajs/utils" +import { CampaignBudgetType, ComputedActions, MathBN } from "@medusajs/utils" export function canRegisterUsage(computedAction: ComputeActions): boolean { return ( @@ -16,7 +17,7 @@ export function canRegisterUsage(computedAction: ComputeActions): boolean { export function computeActionForBudgetExceeded( promotion: PromotionDTO, - amount: number + amount: BigNumberInput ): CampaignBudgetExceededAction | void { const campaignBudget = promotion.campaign?.budget @@ -27,10 +28,10 @@ export function computeActionForBudgetExceeded( const campaignBudgetUsed = campaignBudget.used ?? 0 const totalUsed = campaignBudget.type === CampaignBudgetType.SPEND - ? campaignBudgetUsed + amount - : campaignBudgetUsed + 1 + ? MathBN.add(campaignBudgetUsed, amount) + : MathBN.add(campaignBudgetUsed, 1) - if (campaignBudget.limit && totalUsed > campaignBudget.limit) { + if (campaignBudget.limit && MathBN.gt(totalUsed, campaignBudget.limit)) { return { action: ComputedActions.CAMPAIGN_BUDGET_EXCEEDED, code: promotion.code!, diff --git a/packages/modules/promotion/src/utils/validations/application-method.ts b/packages/modules/promotion/src/utils/validations/application-method.ts index 6e37d1e6c6..7628655c8e 100644 --- a/packages/modules/promotion/src/utils/validations/application-method.ts +++ b/packages/modules/promotion/src/utils/validations/application-method.ts @@ -2,8 +2,10 @@ import { ApplicationMethodAllocation, ApplicationMethodTargetType, ApplicationMethodType, + BigNumber, isDefined, isPresent, + MathBN, MedusaError, PromotionType, } from "@medusajs/utils" @@ -36,14 +38,14 @@ export function validateApplicationMethodAttributes( const targetType = data.target_type || applicationMethod?.target_type const type = data.type || applicationMethod?.type const applicationMethodType = data.type || applicationMethod?.type - const value = data.value || applicationMethod.value + const value = new BigNumber(data.value ?? applicationMethod.value ?? 0) const maxQuantity = data.max_quantity || applicationMethod.max_quantity const allocation = data.allocation || applicationMethod.allocation const allTargetTypes: string[] = Object.values(ApplicationMethodTargetType) if ( type === ApplicationMethodType.PERCENTAGE && - (typeof value !== "number" || value <= 0 || value > 100) + (MathBN.lte(value, 0) || MathBN.gt(value, 100)) ) { throw new MedusaError( MedusaError.Types.INVALID_DATA, diff --git a/packages/modules/promotion/src/utils/validations/promotion-rule.ts b/packages/modules/promotion/src/utils/validations/promotion-rule.ts index 1c9c7e4152..decd93333a 100644 --- a/packages/modules/promotion/src/utils/validations/promotion-rule.ts +++ b/packages/modules/promotion/src/utils/validations/promotion-rule.ts @@ -1,10 +1,10 @@ import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types" import { + MedusaError, + PromotionRuleOperator, isPresent, isString, - MedusaError, pickValueFromObject, - PromotionRuleOperator, } from "@medusajs/utils" import { CreatePromotionRuleDTO } from "@types"