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

This commit is contained in:
Carlos R. L. Rodrigues
2024-05-30 07:23:57 -03:00
committed by GitHub
parent 61977bd392
commit 096372463e
12 changed files with 194 additions and 148 deletions

View File

@@ -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<string, unknown> {
/**
* 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<string, unknown> {
/**
* The subtotal of the shipping method.
*/
subtotal: number
subtotal: BigNumberInput
/**
* The adjustments applied before on the shipping method.

View File

@@ -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)
}

View File

@@ -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([])
})
})
})

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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<string, number>
methodIdPromoValueMap: Map<string, BigNumberInput>
): 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,

View File

@@ -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<string, number>,
appliedPromotionsMap: Map<string, BigNumberInput>,
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({

View File

@@ -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<string, number>
methodIdPromoValueMap: Map<string, BigNumberInput>
): 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,

View File

@@ -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!,

View File

@@ -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,

View File

@@ -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"