feat(utils): consolidate promotion utils + refactor + fixes (#6531)
This commit is contained in:
1 parent
860d56041a
commit
0b9fcb6324
10 files changed
+281
-242
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(utils): consolidate promotion utils + refactor
|
||||||
+2
-2
@@ -3424,13 +3424,13 @@ describe("Promotion Service: computeActions", () => {
|
|||||||
{
|
{
|
||||||
action: "addShippingMethodAdjustment",
|
action: "addShippingMethodAdjustment",
|
||||||
shipping_method_id: "shipping_method_express",
|
shipping_method_id: "shipping_method_express",
|
||||||
amount: 83.33333333333331,
|
amount: 166.66666666666666,
|
||||||
code: "PROMOTION_TEST_2",
|
code: "PROMOTION_TEST_2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: "addShippingMethodAdjustment",
|
action: "addShippingMethodAdjustment",
|
||||||
shipping_method_id: "shipping_method_standard",
|
shipping_method_id: "shipping_method_standard",
|
||||||
amount: 16.66666666666667,
|
amount: 33.333333333333336,
|
||||||
code: "PROMOTION_TEST_2",
|
code: "PROMOTION_TEST_2",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import {
|
|||||||
PromotionTypes,
|
PromotionTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
ApplicationMethodTargetType,
|
ApplicationMethodTargetType,
|
||||||
CampaignBudgetType,
|
CampaignBudgetType,
|
||||||
|
ComputedActions,
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
@@ -251,6 +253,8 @@ export default class PromotionModuleService<
|
|||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Promotions we need to apply includes all the codes that are passed as an argument
|
||||||
|
// to this method, along with any automatic promotions that can be applied to the context
|
||||||
const automaticPromotionCodes = automaticPromotions.map((p) => p.code!)
|
const automaticPromotionCodes = automaticPromotions.map((p) => p.code!)
|
||||||
const promotionCodesToApply = [
|
const promotionCodesToApply = [
|
||||||
...promotionCodes,
|
...promotionCodes,
|
||||||
@@ -307,17 +311,21 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const appliedCodes = [...appliedShippingCodes, ...appliedItemCodes]
|
|
||||||
const sortedPermissionsToApply = promotions
|
|
||||||
.filter((p) => promotionCodesToApply.includes(p.code!))
|
|
||||||
.sort(ComputeActionUtils.sortByBuyGetType)
|
|
||||||
|
|
||||||
const existingPromotionsMap = new Map<string, PromotionTypes.PromotionDTO>(
|
const existingPromotionsMap = new Map<string, PromotionTypes.PromotionDTO>(
|
||||||
promotions.map((promotion) => [promotion.code!, promotion])
|
promotions.map((promotion) => [promotion.code!, promotion])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// We look at any existing promo codes applied in the context and recommend
|
||||||
|
// them to be removed to start calculations from the beginning and refresh
|
||||||
|
// the adjustments if they are requested to be applied again
|
||||||
|
const appliedCodes = [...appliedShippingCodes, ...appliedItemCodes]
|
||||||
|
|
||||||
for (const appliedCode of appliedCodes) {
|
for (const appliedCode of appliedCodes) {
|
||||||
const promotion = existingPromotionsMap.get(appliedCode)
|
const promotion = existingPromotionsMap.get(appliedCode)
|
||||||
|
const adjustments = codeAdjustmentMap.get(appliedCode) || []
|
||||||
|
const action = appliedShippingCodes.includes(appliedCode)
|
||||||
|
? ComputedActions.REMOVE_SHIPPING_METHOD_ADJUSTMENT
|
||||||
|
: ComputedActions.REMOVE_ITEM_ADJUSTMENT
|
||||||
|
|
||||||
if (!promotion) {
|
if (!promotion) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -326,30 +334,20 @@ export default class PromotionModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appliedItemCodes.includes(appliedCode)) {
|
|
||||||
const adjustments = codeAdjustmentMap.get(appliedCode) || []
|
|
||||||
|
|
||||||
adjustments.forEach((adjustment) =>
|
adjustments.forEach((adjustment) =>
|
||||||
computedActions.push({
|
computedActions.push({
|
||||||
action: "removeItemAdjustment",
|
action,
|
||||||
adjustment_id: adjustment.id,
|
adjustment_id: adjustment.id,
|
||||||
code: appliedCode,
|
code: appliedCode,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appliedShippingCodes.includes(appliedCode)) {
|
// We sort the promo codes to apply with buy get type first as they
|
||||||
const adjustments = codeAdjustmentMap.get(appliedCode) || []
|
// are likely to be most valuable.
|
||||||
|
const sortedPermissionsToApply = promotions
|
||||||
adjustments.forEach((adjustment) =>
|
.filter((p) => promotionCodesToApply.includes(p.code!))
|
||||||
computedActions.push({
|
.sort(ComputeActionUtils.sortByBuyGetType)
|
||||||
action: "removeShippingMethodAdjustment",
|
|
||||||
adjustment_id: adjustment.id,
|
|
||||||
code: appliedCode,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const promotionToApply of sortedPermissionsToApply) {
|
for (const promotionToApply of sortedPermissionsToApply) {
|
||||||
const promotion = existingPromotionsMap.get(promotionToApply.code!)!
|
const promotion = existingPromotionsMap.get(promotionToApply.code!)!
|
||||||
@@ -384,36 +382,30 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (promotion.type === PromotionType.STANDARD) {
|
if (promotion.type === PromotionType.STANDARD) {
|
||||||
if (
|
const isTargetOrder =
|
||||||
applicationMethod.target_type === ApplicationMethodTargetType.ORDER
|
applicationMethod.target_type === ApplicationMethodTargetType.ORDER
|
||||||
) {
|
const isTargetItems =
|
||||||
const computedActionsForItems =
|
|
||||||
ComputeActionUtils.getComputedActionsForOrder(
|
|
||||||
promotion,
|
|
||||||
applicationContext,
|
|
||||||
methodIdPromoValueMap
|
|
||||||
)
|
|
||||||
|
|
||||||
computedActions.push(...computedActionsForItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
applicationMethod.target_type === ApplicationMethodTargetType.ITEMS
|
applicationMethod.target_type === ApplicationMethodTargetType.ITEMS
|
||||||
) {
|
const isTargetShipping =
|
||||||
|
applicationMethod.target_type ===
|
||||||
|
ApplicationMethodTargetType.SHIPPING_METHODS
|
||||||
|
const allocationOverride = isTargetOrder
|
||||||
|
? ApplicationMethodAllocation.ACROSS
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (isTargetOrder || isTargetItems) {
|
||||||
const computedActionsForItems =
|
const computedActionsForItems =
|
||||||
ComputeActionUtils.getComputedActionsForItems(
|
ComputeActionUtils.getComputedActionsForItems(
|
||||||
promotion,
|
promotion,
|
||||||
applicationContext[ApplicationMethodTargetType.ITEMS],
|
applicationContext[ApplicationMethodTargetType.ITEMS],
|
||||||
methodIdPromoValueMap
|
methodIdPromoValueMap,
|
||||||
|
allocationOverride
|
||||||
)
|
)
|
||||||
|
|
||||||
computedActions.push(...computedActionsForItems)
|
computedActions.push(...computedActionsForItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (isTargetShipping) {
|
||||||
applicationMethod.target_type ===
|
|
||||||
ApplicationMethodTargetType.SHIPPING_METHODS
|
|
||||||
) {
|
|
||||||
const computedActionsForShippingMethods =
|
const computedActionsForShippingMethods =
|
||||||
ComputeActionUtils.getComputedActionsForShippingMethods(
|
ComputeActionUtils.getComputedActionsForShippingMethods(
|
||||||
promotion,
|
promotion,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export * from "./buy-get"
|
export * from "./buy-get"
|
||||||
export * from "./items"
|
export * from "./line-items"
|
||||||
export * from "./order"
|
|
||||||
export * from "./shipping-methods"
|
|
||||||
export * from "./usage"
|
export * from "./usage"
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import {
|
|
||||||
ApplicationMethodAllocationValues,
|
|
||||||
PromotionTypes,
|
|
||||||
} from "@medusajs/types"
|
|
||||||
import {
|
|
||||||
ApplicationMethodAllocation,
|
|
||||||
ApplicationMethodTargetType,
|
|
||||||
ApplicationMethodType,
|
|
||||||
ComputedActions,
|
|
||||||
MedusaError,
|
|
||||||
} from "@medusajs/utils"
|
|
||||||
import { areRulesValidForContext } from "../validations"
|
|
||||||
import { computeActionForBudgetExceeded } from "./usage"
|
|
||||||
|
|
||||||
export function getComputedActionsForItems(
|
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
|
||||||
itemApplicationContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
|
||||||
methodIdPromoValueMap: Map<string, number>,
|
|
||||||
allocationOverride?: ApplicationMethodAllocationValues
|
|
||||||
): PromotionTypes.ComputeActions[] {
|
|
||||||
const applicableItems: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS] =
|
|
||||||
[]
|
|
||||||
|
|
||||||
if (!itemApplicationContext) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`"items" should be present as an array in the context for computeActions`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const itemContext of itemApplicationContext) {
|
|
||||||
const isPromotionApplicableToItem = areRulesValidForContext(
|
|
||||||
promotion?.application_method?.target_rules!,
|
|
||||||
itemContext
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isPromotionApplicableToItem) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
applicableItems.push(itemContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
return applyPromotionToItems(
|
|
||||||
promotion,
|
|
||||||
applicableItems,
|
|
||||||
methodIdPromoValueMap,
|
|
||||||
allocationOverride
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: calculations should eventually move to a totals util outside of the module
|
|
||||||
export function applyPromotionToItems(
|
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
|
||||||
items: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
|
|
||||||
methodIdPromoValueMap: Map<string, number>,
|
|
||||||
allocationOverride?: ApplicationMethodAllocationValues
|
|
||||||
): PromotionTypes.ComputeActions[] {
|
|
||||||
const { application_method: applicationMethod } = promotion
|
|
||||||
const allocation = applicationMethod?.allocation!
|
|
||||||
const computedActions: PromotionTypes.ComputeActions[] = []
|
|
||||||
|
|
||||||
if (
|
|
||||||
[allocation, allocationOverride].includes(ApplicationMethodAllocation.EACH)
|
|
||||||
) {
|
|
||||||
for (const method of items!) {
|
|
||||||
if (!method.subtotal || !method.quantity) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
|
||||||
const quantityMultiplier = Math.min(
|
|
||||||
method.quantity,
|
|
||||||
applicationMethod?.max_quantity!
|
|
||||||
)
|
|
||||||
const totalItemValue =
|
|
||||||
(method.subtotal / method.quantity) * quantityMultiplier
|
|
||||||
let promotionValue = applicationMethod?.value ?? 0
|
|
||||||
const applicableTotal = totalItemValue - appliedPromoValue
|
|
||||||
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
|
|
||||||
promotionValue = (promotionValue / 100) * applicableTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
const amount = Math.min(promotionValue, applicableTotal)
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const budgetExceededAction = computeActionForBudgetExceeded(
|
|
||||||
promotion,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
|
|
||||||
if (budgetExceededAction) {
|
|
||||||
computedActions.push(budgetExceededAction)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
|
||||||
|
|
||||||
computedActions.push({
|
|
||||||
action: ComputedActions.ADD_ITEM_ADJUSTMENT,
|
|
||||||
item_id: method.id,
|
|
||||||
amount,
|
|
||||||
code: promotion.code!,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
[allocation, allocationOverride].includes(
|
|
||||||
ApplicationMethodAllocation.ACROSS
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const totalApplicableValue = items!.reduce((acc, method) => {
|
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
|
||||||
const perItemCost = method.subtotal
|
|
||||||
? method.subtotal / method.quantity
|
|
||||||
: 0
|
|
||||||
|
|
||||||
return acc + perItemCost * method.quantity - appliedPromoValue
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
for (const method of items!) {
|
|
||||||
if (!method.subtotal || !method.quantity) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
|
|
||||||
const promotionValue = applicationMethod?.value ?? 0
|
|
||||||
const applicableTotal =
|
|
||||||
(method.subtotal / method.quantity) * method.quantity -
|
|
||||||
appliedPromoValue
|
|
||||||
|
|
||||||
if (applicableTotal <= 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should we worry about precision here?
|
|
||||||
let applicablePromotionValue =
|
|
||||||
(applicableTotal / totalApplicableValue) * promotionValue
|
|
||||||
|
|
||||||
if (applicationMethod?.type === ApplicationMethodType.PERCENTAGE) {
|
|
||||||
applicablePromotionValue = (promotionValue / 100) * applicableTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
const amount = Math.min(applicablePromotionValue, applicableTotal)
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const budgetExceededAction = computeActionForBudgetExceeded(
|
|
||||||
promotion,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
|
|
||||||
if (budgetExceededAction) {
|
|
||||||
computedActions.push(budgetExceededAction)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
|
||||||
|
|
||||||
computedActions.push({
|
|
||||||
action: ComputedActions.ADD_ITEM_ADJUSTMENT,
|
|
||||||
item_id: method.id,
|
|
||||||
amount,
|
|
||||||
code: promotion.code!,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return computedActions
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import {
|
||||||
|
ApplicationMethodAllocationValues,
|
||||||
|
PromotionTypes,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ComputedActions,
|
||||||
|
MedusaError,
|
||||||
|
ApplicationMethodTargetType as TargetType,
|
||||||
|
calculateAdjustmentAmountFromPromotion,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { areRulesValidForContext } from "../validations"
|
||||||
|
import { computeActionForBudgetExceeded } from "./usage"
|
||||||
|
|
||||||
|
function validateContext(
|
||||||
|
contextKey: string,
|
||||||
|
context: PromotionTypes.ComputeActionContext[TargetType]
|
||||||
|
) {
|
||||||
|
if (!context) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`"${contextKey}" should be present as an array in the context for computeActions`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComputedActionsForItems(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
items: PromotionTypes.ComputeActionContext[TargetType.ITEMS],
|
||||||
|
appliedPromotionsMap: Map<string, number>,
|
||||||
|
allocationOverride?: ApplicationMethodAllocationValues
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
validateContext("items", items)
|
||||||
|
|
||||||
|
return applyPromotionToItems(
|
||||||
|
promotion,
|
||||||
|
items,
|
||||||
|
appliedPromotionsMap,
|
||||||
|
allocationOverride
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComputedActionsForShippingMethods(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
shippingMethods: PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
||||||
|
appliedPromotionsMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
validateContext("shipping_methods", shippingMethods)
|
||||||
|
|
||||||
|
return applyPromotionToItems(promotion, shippingMethods, appliedPromotionsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComputedActionsForOrder(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
itemApplicationContext: PromotionTypes.ComputeActionContext,
|
||||||
|
methodIdPromoValueMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
return getComputedActionsForItems(
|
||||||
|
promotion,
|
||||||
|
itemApplicationContext[TargetType.ITEMS],
|
||||||
|
methodIdPromoValueMap,
|
||||||
|
ApplicationMethodAllocation.ACROSS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPromotionToItems(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
items:
|
||||||
|
| PromotionTypes.ComputeActionContext[TargetType.ITEMS]
|
||||||
|
| PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
||||||
|
appliedPromotionsMap: Map<string, number>,
|
||||||
|
allocationOverride?: ApplicationMethodAllocationValues
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const { application_method: applicationMethod } = promotion
|
||||||
|
const allocation = applicationMethod?.allocation! || allocationOverride
|
||||||
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
|
const applicableItems = getValidItemsForPromotion(items, promotion)
|
||||||
|
const target = applicationMethod?.target_type
|
||||||
|
|
||||||
|
const isTargetShippingMethod = target === TargetType.SHIPPING_METHODS
|
||||||
|
const isTargetLineItems = target === TargetType.ITEMS
|
||||||
|
const isTargetOrder = target === TargetType.ORDER
|
||||||
|
|
||||||
|
let lineItemsTotal = 0
|
||||||
|
|
||||||
|
if (allocation === ApplicationMethodAllocation.ACROSS) {
|
||||||
|
lineItemsTotal = applicableItems.reduce(
|
||||||
|
(acc, item) =>
|
||||||
|
acc + item.subtotal - (appliedPromotionsMap.get(item.id) ?? 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of applicableItems!) {
|
||||||
|
const appliedPromoValue = appliedPromotionsMap.get(item.id) ?? 0
|
||||||
|
const maxQuantity = isTargetShippingMethod
|
||||||
|
? 1
|
||||||
|
: applicationMethod?.max_quantity!
|
||||||
|
|
||||||
|
if (isTargetShippingMethod) {
|
||||||
|
item.quantity = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = calculateAdjustmentAmountFromPromotion(
|
||||||
|
item,
|
||||||
|
{
|
||||||
|
value: applicationMethod?.value ?? 0,
|
||||||
|
applied_value: appliedPromoValue,
|
||||||
|
max_quantity: maxQuantity,
|
||||||
|
type: applicationMethod?.type!,
|
||||||
|
allocation,
|
||||||
|
},
|
||||||
|
lineItemsTotal
|
||||||
|
)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const budgetExceededAction = computeActionForBudgetExceeded(
|
||||||
|
promotion,
|
||||||
|
amount
|
||||||
|
)
|
||||||
|
|
||||||
|
if (budgetExceededAction) {
|
||||||
|
computedActions.push(budgetExceededAction)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
appliedPromotionsMap.set(item.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
if (isTargetLineItems || isTargetOrder) {
|
||||||
|
computedActions.push({
|
||||||
|
action: ComputedActions.ADD_ITEM_ADJUSTMENT,
|
||||||
|
item_id: item.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTargetShippingMethod) {
|
||||||
|
computedActions.push({
|
||||||
|
action: ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
|
||||||
|
shipping_method_id: item.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValidItemsForPromotion(
|
||||||
|
items:
|
||||||
|
| PromotionTypes.ComputeActionContext[TargetType.ITEMS]
|
||||||
|
| PromotionTypes.ComputeActionContext[TargetType.SHIPPING_METHODS],
|
||||||
|
promotion: PromotionTypes.PromotionDTO
|
||||||
|
) {
|
||||||
|
const isTargetShippingMethod =
|
||||||
|
promotion.application_method?.target_type === TargetType.SHIPPING_METHODS
|
||||||
|
|
||||||
|
return (
|
||||||
|
items?.filter((item) => {
|
||||||
|
const isSubtotalPresent = "subtotal" in item
|
||||||
|
const isQuantityPresent = "quantity" in item
|
||||||
|
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||||
|
promotion?.application_method?.target_rules!,
|
||||||
|
item
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
isPromotionApplicableToItem &&
|
||||||
|
(isQuantityPresent || isTargetShippingMethod) &&
|
||||||
|
isSubtotalPresent
|
||||||
|
)
|
||||||
|
}) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { PromotionTypes } from "@medusajs/types"
|
|
||||||
import {
|
|
||||||
ApplicationMethodAllocation,
|
|
||||||
ApplicationMethodTargetType,
|
|
||||||
} from "@medusajs/utils"
|
|
||||||
import { getComputedActionsForItems } from "./items"
|
|
||||||
|
|
||||||
export function getComputedActionsForOrder(
|
|
||||||
promotion: PromotionTypes.PromotionDTO,
|
|
||||||
itemApplicationContext: PromotionTypes.ComputeActionContext,
|
|
||||||
methodIdPromoValueMap: Map<string, number>
|
|
||||||
): PromotionTypes.ComputeActions[] {
|
|
||||||
return getComputedActionsForItems(
|
|
||||||
promotion,
|
|
||||||
itemApplicationContext[ApplicationMethodTargetType.ITEMS],
|
|
||||||
methodIdPromoValueMap,
|
|
||||||
ApplicationMethodAllocation.ACROSS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,8 @@ import { BigNumber as BigNumberJs } from "bignumber.js"
|
|||||||
import { BigNumber } from "./big-number"
|
import { BigNumber } from "./big-number"
|
||||||
import { toBigNumberJs } from "./to-big-number-js"
|
import { toBigNumberJs } from "./to-big-number-js"
|
||||||
|
|
||||||
|
export * from "./promotion"
|
||||||
|
|
||||||
type GetLineItemTotalsContext = {
|
type GetLineItemTotalsContext = {
|
||||||
includeTax?: boolean
|
includeTax?: boolean
|
||||||
taxRate?: number | null
|
taxRate?: number | null
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./totals"
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodType,
|
||||||
|
} from "../../promotion"
|
||||||
|
|
||||||
|
function getPromotionValueForPercentage(promotion, lineItemTotal) {
|
||||||
|
return (promotion.value / 100) * lineItemTotal
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) {
|
||||||
|
if (promotion.allocation === ApplicationMethodAllocation.ACROSS) {
|
||||||
|
return (lineItemTotal / lineItemsTotal) * promotion.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return promotion.value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPromotionValue(promotion, lineItemTotal, lineItemsTotal) {
|
||||||
|
if (promotion.type === ApplicationMethodType.PERCENTAGE) {
|
||||||
|
return getPromotionValueForPercentage(promotion, lineItemTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getApplicableQuantity(lineItem, maxQuantity) {
|
||||||
|
if (maxQuantity && lineItem.quantity) {
|
||||||
|
return Math.min(lineItem.quantity, maxQuantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineItem.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLineItemUnitPrice(lineItem) {
|
||||||
|
return lineItem.subtotal / lineItem.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateAdjustmentAmountFromPromotion(
|
||||||
|
lineItem,
|
||||||
|
promotion,
|
||||||
|
lineItemsTotal = 0
|
||||||
|
) {
|
||||||
|
const quantity = getApplicableQuantity(lineItem, promotion.max_quantity)
|
||||||
|
const lineItemTotal = getLineItemUnitPrice(lineItem) * quantity
|
||||||
|
const applicableTotal = lineItemTotal - promotion.applied_value
|
||||||
|
|
||||||
|
if (applicableTotal <= 0) {
|
||||||
|
return applicableTotal
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionValue = getPromotionValue(
|
||||||
|
promotion,
|
||||||
|
applicableTotal,
|
||||||
|
lineItemsTotal
|
||||||
|
)
|
||||||
|
|
||||||
|
return Math.min(promotionValue, applicableTotal)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user