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