chore(promotion): big number calc (#7537)

This commit is contained in:
Carlos R. L. Rodrigues authored and GitHub committed 2024-05-30 07:23:57 -03:00
1 parent 61977bd392
commit 096372463e
12 files changed
+193 -147

No files matched your search

@@ -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. * 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. * The amount to remove off the item's total.
*/ */
amount: number amount: BigNumberInput
/** /**
* The promotion's code. * The promotion's code.
@@ -103,7 +105,7 @@ export interface AddShippingMethodAdjustment {
/** /**
* The amount to remove off the shipping method's total. * The amount to remove off the shipping method's total.
*/ */
amount: number amount: BigNumberInput
/** /**
* The promotion's code. * The promotion's code.
@@ -163,12 +165,12 @@ export interface ComputeActionItemLine extends Record<string, unknown> {
/** /**
* The quantity of the line item. * The quantity of the line item.
*/ */
quantity: number quantity: BigNumberInput
/** /**
* The subtotal of the line item. * The subtotal of the line item.
*/ */
subtotal: number subtotal: BigNumberInput
/** /**
* The adjustments applied before on the line item. * The adjustments applied before on the line item.
@@ -188,7 +190,7 @@ export interface ComputeActionShippingLine extends Record<string, unknown> {
/** /**
* The subtotal of the shipping method. * The subtotal of the shipping method.
*/ */
subtotal: number subtotal: BigNumberInput
/** /**
* The adjustments applied before on the shipping method. * The adjustments applied before on the shipping method.
@@ -1,15 +1,20 @@
import { BigNumberInput } from "@medusajs/types"
import { import {
ApplicationMethodAllocation, ApplicationMethodAllocation,
ApplicationMethodType, ApplicationMethodType,
} from "../../promotion" } from "../../promotion"
import { MathBN } from "../math"
function getPromotionValueForPercentage(promotion, lineItemTotal) { function getPromotionValueForPercentage(promotion, lineItemTotal) {
return (promotion.value / 100) * lineItemTotal return MathBN.mult(MathBN.div(promotion.value, 100), lineItemTotal)
} }
function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) { function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) {
if (promotion.allocation === ApplicationMethodAllocation.ACROSS) { if (promotion.allocation === ApplicationMethodAllocation.ACROSS) {
return (lineItemTotal / lineItemsTotal) * promotion.value return MathBN.mult(
MathBN.div(lineItemTotal, lineItemsTotal),
promotion.value
)
} }
return promotion.value return promotion.value
@@ -25,26 +30,26 @@ export function getPromotionValue(promotion, lineItemTotal, lineItemsTotal) {
export function getApplicableQuantity(lineItem, maxQuantity) { export function getApplicableQuantity(lineItem, maxQuantity) {
if (maxQuantity && lineItem.quantity) { if (maxQuantity && lineItem.quantity) {
return Math.min(lineItem.quantity, maxQuantity) return MathBN.min(lineItem.quantity, maxQuantity)
} }
return lineItem.quantity return lineItem.quantity
} }
function getLineItemUnitPrice(lineItem) { function getLineItemUnitPrice(lineItem) {
return lineItem.subtotal / lineItem.quantity return MathBN.div(lineItem.subtotal, lineItem.quantity)
} }
export function calculateAdjustmentAmountFromPromotion( export function calculateAdjustmentAmountFromPromotion(
lineItem, lineItem,
promotion, promotion,
lineItemsTotal = 0 lineItemsTotal: BigNumberInput = 0
) { ) {
const quantity = getApplicableQuantity(lineItem, promotion.max_quantity) const quantity = getApplicableQuantity(lineItem, promotion.max_quantity)
const lineItemTotal = getLineItemUnitPrice(lineItem) * quantity const lineItemTotal = MathBN.mult(getLineItemUnitPrice(lineItem), quantity)
const applicableTotal = lineItemTotal - promotion.applied_value const applicableTotal = MathBN.sub(lineItemTotal, promotion.applied_value)
if (applicableTotal <= 0) { if (MathBN.lte(applicableTotal, 0)) {
return applicableTotal return applicableTotal
} }
@@ -54,5 +59,5 @@ export function calculateAdjustmentAmountFromPromotion(
lineItemsTotal lineItemsTotal
) )
return Math.min(promotionValue, applicableTotal) return MathBN.min(promotionValue, applicableTotal)
} }
@@ -1,7 +1,7 @@
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types" import { IPromotionModuleService } from "@medusajs/types"
import { ApplicationMethodType, PromotionType } from "@medusajs/utils" 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 { createCampaigns } from "../../../__fixtures__/campaigns"
import { createDefaultPromotion } from "../../../__fixtures__/promotion" import { createDefaultPromotion } from "../../../__fixtures__/promotion"
@@ -177,7 +177,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -278,7 +278,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -391,7 +391,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -453,7 +453,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -578,7 +578,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -679,7 +679,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -792,7 +792,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -977,7 +977,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -1050,7 +1050,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -1150,7 +1150,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -1262,7 +1262,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -1442,7 +1442,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -1515,7 +1515,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -1614,7 +1614,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -1725,7 +1725,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -1919,7 +1919,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -1992,7 +1992,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -2069,7 +2069,7 @@ moduleIntegrationTestRunner({
{ prevent_auto_promotions: true } { 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 () => { 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", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -2262,7 +2262,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -2436,7 +2436,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -2509,7 +2509,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -2586,7 +2586,7 @@ moduleIntegrationTestRunner({
{ prevent_auto_promotions: true } { 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 () => { 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", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -2786,7 +2786,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -2973,17 +2973,17 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
amount: 166.66666666666669, amount: 166.66666666666666,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard", shipping_method_id: "shipping_method_standard",
amount: 33.33333333333333, amount: 33.333333333333336,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
]) ])
@@ -3045,17 +3045,17 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
amount: 166.66666666666669, amount: 166.66666666666666,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard", shipping_method_id: "shipping_method_standard",
amount: 33.33333333333333, amount: 33.333333333333336,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
]) ])
@@ -3143,17 +3143,17 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
amount: 166.66666666666669, amount: 166.66666666666666,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard", shipping_method_id: "shipping_method_standard",
amount: 33.33333333333333, amount: 33.333333333333336,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
{ {
@@ -3253,7 +3253,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -3424,7 +3424,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -3496,7 +3496,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -3594,7 +3594,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
@@ -3704,7 +3704,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", 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" }, { 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" }, { action: "campaignBudgetExceeded", code: "PROMOTION_TEST" },
]) ])
}) })
@@ -3877,7 +3877,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -3944,7 +3944,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -4031,7 +4031,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -4130,7 +4130,7 @@ moduleIntegrationTestRunner({
} }
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt", item_id: "item_cotton_tshirt",
@@ -4215,7 +4215,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "removeItemAdjustment", action: "removeItemAdjustment",
adjustment_id: "test-adjustment", adjustment_id: "test-adjustment",
@@ -4302,7 +4302,7 @@ moduleIntegrationTestRunner({
], ],
}) })
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "removeShippingMethodAdjustment", action: "removeShippingMethodAdjustment",
adjustment_id: "test-adjustment", adjustment_id: "test-adjustment",
@@ -4311,13 +4311,13 @@ moduleIntegrationTestRunner({
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_express", shipping_method_id: "shipping_method_express",
amount: 166.66666666666669, amount: 166.66666666666666,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
{ {
action: "addShippingMethodAdjustment", action: "addShippingMethodAdjustment",
shipping_method_id: "shipping_method_standard", shipping_method_id: "shipping_method_standard",
amount: 33.33333333333333, amount: 33.333333333333336,
code: "PROMOTION_TEST", code: "PROMOTION_TEST",
}, },
]) ])
@@ -4408,7 +4408,7 @@ moduleIntegrationTestRunner({
context context
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt2", item_id: "item_cotton_tshirt2",
@@ -4501,7 +4501,7 @@ moduleIntegrationTestRunner({
context 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 () => { it("should compute actions for multiple items when conditions for target qty exceed one item", async () => {
@@ -4588,7 +4588,7 @@ moduleIntegrationTestRunner({
context context
) )
expect(result).toEqual([ expect(JSON.parse(JSON.stringify(result))).toEqual([
{ {
action: "addItemAdjustment", action: "addItemAdjustment",
item_id: "item_cotton_tshirt2", item_id: "item_cotton_tshirt2",
@@ -4688,7 +4688,7 @@ moduleIntegrationTestRunner({
context context
) )
expect(result).toEqual([]) expect(JSON.parse(JSON.stringify(result))).toEqual([])
}) })
}) })
}) })
@@ -14,6 +14,7 @@ import {
ComputedActions, ComputedActions,
InjectManager, InjectManager,
InjectTransactionManager, InjectTransactionManager,
MathBN,
MedusaContext, MedusaContext,
MedusaError, MedusaError,
ModulesSdkUtils, ModulesSdkUtils,
@@ -23,6 +24,7 @@ import {
isDefined, isDefined,
isPresent, isPresent,
isString, isString,
transformPropertiesToBigNumber,
} from "@medusajs/utils" } from "@medusajs/utils"
import { import {
ApplicationMethod, ApplicationMethod,
@@ -176,12 +178,14 @@ export default class PromotionModuleService<
campaignBudget.id campaignBudget.id
) || { id: campaignBudget.id, used: campaignBudget.used ?? 0 } ) || { id: campaignBudget.id, used: campaignBudget.used ?? 0 }
campaignBudgetData.used = campaignBudgetData.used = MathBN.add(
(campaignBudgetData.used ?? 0) + computedAction.amount campaignBudgetData.used ?? 0,
computedAction.amount
)
if ( if (
campaignBudget.limit && campaignBudget.limit &&
campaignBudgetData.used > campaignBudget.limit MathBN.gt(campaignBudgetData.used, campaignBudget.limit)
) { ) {
continue continue
} }
@@ -202,12 +206,12 @@ export default class PromotionModuleService<
const campaignBudgetData = { const campaignBudgetData = {
id: campaignBudget.id, id: campaignBudget.id,
used: (campaignBudget.used ?? 0) + 1, used: MathBN.add(campaignBudget.used ?? 0, 1),
} }
if ( if (
campaignBudget.limit && campaignBudget.limit &&
campaignBudgetData.used > campaignBudget.limit MathBN.gt(campaignBudgetData.used, campaignBudget.limit)
) { ) {
continue continue
} }
@@ -424,6 +428,8 @@ export default class PromotionModuleService<
} }
} }
transformPropertiesToBigNumber(computedActions, { include: ["amount"] })
return computedActions return computedActions
} }
@@ -2,6 +2,7 @@ import {
ApplicationMethodAllocationValues, ApplicationMethodAllocationValues,
ApplicationMethodTargetTypeValues, ApplicationMethodTargetTypeValues,
ApplicationMethodTypeValues, ApplicationMethodTypeValues,
BigNumberInput,
PromotionDTO, PromotionDTO,
} from "@medusajs/types" } from "@medusajs/types"
@@ -11,12 +12,12 @@ export interface CreateApplicationMethodDTO {
type: ApplicationMethodTypeValues type: ApplicationMethodTypeValues
target_type: ApplicationMethodTargetTypeValues target_type: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues allocation?: ApplicationMethodAllocationValues
value?: number value?: BigNumberInput
currency_code: string currency_code: string
promotion: Promotion | string | PromotionDTO promotion: Promotion | string | PromotionDTO
max_quantity?: number | null max_quantity?: BigNumberInput | null
buy_rules_min_quantity?: number | null buy_rules_min_quantity?: BigNumberInput | null
apply_to_quantity?: number | null apply_to_quantity?: BigNumberInput | null
} }
export interface UpdateApplicationMethodDTO { export interface UpdateApplicationMethodDTO {
@@ -24,10 +25,10 @@ export interface UpdateApplicationMethodDTO {
type?: ApplicationMethodTypeValues type?: ApplicationMethodTypeValues
target_type?: ApplicationMethodTargetTypeValues target_type?: ApplicationMethodTargetTypeValues
allocation?: ApplicationMethodAllocationValues allocation?: ApplicationMethodAllocationValues
value?: number value?: BigNumberInput
currency_code?: string currency_code?: string
promotion?: Promotion | string | PromotionDTO promotion?: Promotion | string | PromotionDTO
max_quantity?: number | null max_quantity?: BigNumberInput | null
buy_rules_min_quantity?: number | null buy_rules_min_quantity?: BigNumberInput | null
apply_to_quantity?: number | null apply_to_quantity?: BigNumberInput | null
} }
@@ -1,18 +1,18 @@
import { CampaignBudgetTypeValues } from "@medusajs/types" import { BigNumberInput, CampaignBudgetTypeValues } from "@medusajs/types"
import { Campaign } from "@models" import { Campaign } from "@models"
export interface CreateCampaignBudgetDTO { export interface CreateCampaignBudgetDTO {
type?: CampaignBudgetTypeValues type?: CampaignBudgetTypeValues
limit?: number | null limit?: BigNumberInput | null
currency_code?: string | null currency_code?: string | null
used?: number used?: BigNumberInput
campaign?: Campaign | string campaign?: Campaign | string
} }
export interface UpdateCampaignBudgetDTO { export interface UpdateCampaignBudgetDTO {
id: string id: string
type?: CampaignBudgetTypeValues type?: CampaignBudgetTypeValues
limit?: number | null limit?: BigNumberInput | null
currency_code?: string | null currency_code?: string | null
used?: number used?: BigNumberInput
} }
@@ -1,7 +1,8 @@
import { PromotionTypes } from "@medusajs/types" import { BigNumberInput, PromotionTypes } from "@medusajs/types"
import { import {
ApplicationMethodTargetType, ApplicationMethodTargetType,
ComputedActions, ComputedActions,
MathBN,
MedusaError, MedusaError,
PromotionType, PromotionType,
isPresent, isPresent,
@@ -13,7 +14,7 @@ import { computeActionForBudgetExceeded } from "./usage"
export function getComputedActionsForBuyGet( export function getComputedActionsForBuyGet(
promotion: PromotionTypes.PromotionDTO, promotion: PromotionTypes.PromotionDTO,
itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS], itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
methodIdPromoValueMap: Map<string, number> methodIdPromoValueMap: Map<string, BigNumberInput>
): PromotionTypes.ComputeActions[] { ): PromotionTypes.ComputeActions[] {
const buyRulesMinQuantity = const buyRulesMinQuantity =
promotion.application_method?.buy_rules_min_quantity promotion.application_method?.buy_rules_min_quantity
@@ -33,14 +34,16 @@ export function getComputedActionsForBuyGet(
return [] return []
} }
const validQuantity = itemsContext const validQuantity = MathBN.sum(
...itemsContext
.filter((item) => areRulesValidForContext(buyRules, item)) .filter((item) => areRulesValidForContext(buyRules, item))
.reduce((acc, next) => acc + next.quantity, 0) .map((item) => item.quantity)
)
if ( if (
!buyRulesMinQuantity || !buyRulesMinQuantity ||
!applyToQuantity || !applyToQuantity ||
buyRulesMinQuantity > validQuantity MathBN.gt(buyRulesMinQuantity, validQuantity)
) { ) {
return [] return []
} }
@@ -49,21 +52,24 @@ export function getComputedActionsForBuyGet(
.filter((item) => areRulesValidForContext(targetRules, item)) .filter((item) => areRulesValidForContext(targetRules, item))
.filter((item) => isPresent(item.subtotal) && isPresent(item.quantity)) .filter((item) => isPresent(item.subtotal) && isPresent(item.quantity))
.sort((a, b) => { .sort((a, b) => {
const aPrice = a.subtotal / a.quantity const aPrice = MathBN.div(a.subtotal, a.quantity)
const bPrice = b.subtotal / b.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) { for (const method of validItemsForTargetRules) {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
const multiplier = Math.min(method.quantity, remainingQtyToApply) const multiplier = MathBN.min(method.quantity, remainingQtyToApply)
const amount = (method.subtotal / method.quantity) * multiplier const amount = MathBN.mult(
const newRemainingQtyToApply = remainingQtyToApply - multiplier 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 break
} else { } else {
remainingQtyToApply = newRemainingQtyToApply remainingQtyToApply = newRemainingQtyToApply
@@ -80,7 +86,7 @@ export function getComputedActionsForBuyGet(
continue continue
} }
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) methodIdPromoValueMap.set(method.id, MathBN.add(appliedPromoValue, amount))
computedActions.push({ computedActions.push({
action: ComputedActions.ADD_ITEM_ADJUSTMENT, action: ComputedActions.ADD_ITEM_ADJUSTMENT,
@@ -1,10 +1,12 @@
import { import {
ApplicationMethodAllocationValues, ApplicationMethodAllocationValues,
BigNumberInput,
PromotionTypes, PromotionTypes,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
ApplicationMethodAllocation, ApplicationMethodAllocation,
ComputedActions, ComputedActions,
MathBN,
MedusaError, MedusaError,
ApplicationMethodTargetType as TargetType, ApplicationMethodTargetType as TargetType,
calculateAdjustmentAmountFromPromotion, calculateAdjustmentAmountFromPromotion,
@@ -68,7 +70,7 @@ function applyPromotionToItems(
items: items:
| PromotionTypes.ComputeActionContext[TargetType.ITEMS] | PromotionTypes.ComputeActionContext[TargetType.ITEMS]
| PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS], | PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
appliedPromotionsMap: Map<string, number>, appliedPromotionsMap: Map<string, BigNumberInput>,
allocationOverride?: ApplicationMethodAllocationValues allocationOverride?: ApplicationMethodAllocationValues
): PromotionTypes.ComputeActions[] { ): PromotionTypes.ComputeActions[] {
const { application_method: applicationMethod } = promotion const { application_method: applicationMethod } = promotion
@@ -81,13 +83,16 @@ function applyPromotionToItems(
const isTargetLineItems = target === TargetType.ITEMS const isTargetLineItems = target === TargetType.ITEMS
const isTargetOrder = target === TargetType.ORDER const isTargetOrder = target === TargetType.ORDER
let lineItemsTotal = 0 let lineItemsTotal = MathBN.convert(0)
if (allocation === ApplicationMethodAllocation.ACROSS) { if (allocation === ApplicationMethodAllocation.ACROSS) {
lineItemsTotal = applicableItems.reduce( lineItemsTotal = applicableItems.reduce(
(acc, item) => (acc, item) =>
acc + item.subtotal - (appliedPromotionsMap.get(item.id) ?? 0), MathBN.sub(
0 MathBN.add(acc, item.subtotal),
appliedPromotionsMap.get(item.id) ?? 0
),
MathBN.convert(0)
) )
} }
@@ -113,7 +118,7 @@ function applyPromotionToItems(
lineItemsTotal lineItemsTotal
) )
if (amount <= 0) { if (MathBN.lte(amount, 0)) {
continue continue
} }
@@ -128,7 +133,7 @@ function applyPromotionToItems(
continue continue
} }
appliedPromotionsMap.set(item.id, appliedPromoValue + amount) appliedPromotionsMap.set(item.id, MathBN.add(appliedPromoValue, amount))
if (isTargetLineItems || isTargetOrder) { if (isTargetLineItems || isTargetOrder) {
computedActions.push({ computedActions.push({
@@ -1,9 +1,10 @@
import { PromotionTypes } from "@medusajs/types" import { BigNumberInput, PromotionTypes } from "@medusajs/types"
import { import {
ApplicationMethodAllocation, ApplicationMethodAllocation,
ApplicationMethodTargetType, ApplicationMethodTargetType,
ApplicationMethodType, ApplicationMethodType,
ComputedActions, ComputedActions,
MathBN,
MedusaError, MedusaError,
} from "@medusajs/utils" } from "@medusajs/utils"
import { areRulesValidForContext } from "../validations" import { areRulesValidForContext } from "../validations"
@@ -47,7 +48,7 @@ export function getComputedActionsForShippingMethods(
export function applyPromotionToShippingMethods( export function applyPromotionToShippingMethods(
promotion: PromotionTypes.PromotionDTO, promotion: PromotionTypes.PromotionDTO,
shippingMethods: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS], shippingMethods: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS],
methodIdPromoValueMap: Map<string, number> methodIdPromoValueMap: Map<string, BigNumberInput>
): PromotionTypes.ComputeActions[] { ): PromotionTypes.ComputeActions[] {
const { application_method: applicationMethod } = promotion const { application_method: applicationMethod } = promotion
const allocation = applicationMethod?.allocation! const allocation = applicationMethod?.allocation!
@@ -60,16 +61,19 @@ export function applyPromotionToShippingMethods(
} }
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
let promotionValue = applicationMethod?.value ?? 0 let promotionValue = MathBN.convert(applicationMethod?.value ?? 0)
const applicableTotal = method.subtotal - appliedPromoValue const applicableTotal = MathBN.sub(method.subtotal, appliedPromoValue)
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { 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 continue
} }
@@ -84,7 +88,10 @@ export function applyPromotionToShippingMethods(
continue continue
} }
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) methodIdPromoValueMap.set(
method.id,
MathBN.add(appliedPromoValue, amount)
)
computedActions.push({ computedActions.push({
action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT, action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
@@ -99,10 +106,13 @@ export function applyPromotionToShippingMethods(
const totalApplicableValue = shippingMethods!.reduce((acc, method) => { const totalApplicableValue = shippingMethods!.reduce((acc, method) => {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
return acc + (method.subtotal ?? 0) - appliedPromoValue return MathBN.add(
}, 0) acc,
MathBN.sub(method.subtotal ?? 0, appliedPromoValue)
)
}, MathBN.convert(0))
if (totalApplicableValue <= 0) { if (MathBN.lte(totalApplicableValue, 0)) {
return computedActions return computedActions
} }
@@ -115,19 +125,24 @@ export function applyPromotionToShippingMethods(
const applicableTotal = method.subtotal const applicableTotal = method.subtotal
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0 const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
// TODO: should we worry about precision here? let applicablePromotionValue = MathBN.sub(
let applicablePromotionValue = MathBN.mult(
(applicableTotal / totalApplicableValue) * promotionValue - MathBN.div(applicableTotal, totalApplicableValue),
promotionValue
),
appliedPromoValue appliedPromoValue
)
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) { if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
applicablePromotionValue = applicablePromotionValue = MathBN.sub(
(promotionValue / 100) * (applicableTotal - appliedPromoValue) 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 continue
} }
@@ -142,7 +157,10 @@ export function applyPromotionToShippingMethods(
continue continue
} }
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount) methodIdPromoValueMap.set(
method.id,
MathBN.add(appliedPromoValue, amount)
)
computedActions.push({ computedActions.push({
action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT, action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
@@ -1,9 +1,10 @@
import { import {
BigNumberInput,
CampaignBudgetExceededAction, CampaignBudgetExceededAction,
ComputeActions, ComputeActions,
PromotionDTO, PromotionDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { CampaignBudgetType, ComputedActions } from "@medusajs/utils" import { CampaignBudgetType, ComputedActions, MathBN } from "@medusajs/utils"
export function canRegisterUsage(computedAction: ComputeActions): boolean { export function canRegisterUsage(computedAction: ComputeActions): boolean {
return ( return (
@@ -16,7 +17,7 @@ export function canRegisterUsage(computedAction: ComputeActions): boolean {
export function computeActionForBudgetExceeded( export function computeActionForBudgetExceeded(
promotion: PromotionDTO, promotion: PromotionDTO,
amount: number amount: BigNumberInput
): CampaignBudgetExceededAction | void { ): CampaignBudgetExceededAction | void {
const campaignBudget = promotion.campaign?.budget const campaignBudget = promotion.campaign?.budget
@@ -27,10 +28,10 @@ export function computeActionForBudgetExceeded(
const campaignBudgetUsed = campaignBudget.used ?? 0 const campaignBudgetUsed = campaignBudget.used ?? 0
const totalUsed = const totalUsed =
campaignBudget.type === CampaignBudgetType.SPEND campaignBudget.type === CampaignBudgetType.SPEND
? campaignBudgetUsed + amount ? MathBN.add(campaignBudgetUsed, amount)
: campaignBudgetUsed + 1 : MathBN.add(campaignBudgetUsed, 1)
if (campaignBudget.limit && totalUsed > campaignBudget.limit) { if (campaignBudget.limit && MathBN.gt(totalUsed, campaignBudget.limit)) {
return { return {
action: ComputedActions.CAMPAIGN_BUDGET_EXCEEDED, action: ComputedActions.CAMPAIGN_BUDGET_EXCEEDED,
code: promotion.code!, code: promotion.code!,
@@ -2,8 +2,10 @@ import {
ApplicationMethodAllocation, ApplicationMethodAllocation,
ApplicationMethodTargetType, ApplicationMethodTargetType,
ApplicationMethodType, ApplicationMethodType,
BigNumber,
isDefined, isDefined,
isPresent, isPresent,
MathBN,
MedusaError, MedusaError,
PromotionType, PromotionType,
} from "@medusajs/utils" } from "@medusajs/utils"
@@ -36,14 +38,14 @@ export function validateApplicationMethodAttributes(
const targetType = data.target_type || applicationMethod?.target_type const targetType = data.target_type || applicationMethod?.target_type
const type = data.type || applicationMethod?.type const type = data.type || applicationMethod?.type
const applicationMethodType = 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 maxQuantity = data.max_quantity || applicationMethod.max_quantity
const allocation = data.allocation || applicationMethod.allocation const allocation = data.allocation || applicationMethod.allocation
const allTargetTypes: string[] = Object.values(ApplicationMethodTargetType) const allTargetTypes: string[] = Object.values(ApplicationMethodTargetType)
if ( if (
type === ApplicationMethodType.PERCENTAGE && type === ApplicationMethodType.PERCENTAGE &&
(typeof value !== "number" || value <= 0 || value > 100) (MathBN.lte(value, 0) || MathBN.gt(value, 100))
) { ) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
@@ -1,10 +1,10 @@
import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types" import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types"
import { import {
MedusaError,
PromotionRuleOperator,
isPresent, isPresent,
isString, isString,
MedusaError,
pickValueFromObject, pickValueFromObject,
PromotionRuleOperator,
} from "@medusajs/utils" } from "@medusajs/utils"
import { CreatePromotionRuleDTO } from "@types" import { CreatePromotionRuleDTO } from "@types"