chore(promotion): big number calc (#7537)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user