fix(promotion): validate rules accurately when attribute is scoped by context (#8655)
This commit is contained in:
1 parent
a66bd3bd6b
commit
4791d1d775
6 files changed
+66
-9
No files matched your search
@@ -56,7 +56,7 @@ medusaIntegrationTestRunner({
|
|||||||
max_quantity: 1,
|
max_quantity: 1,
|
||||||
target_rules: [
|
target_rules: [
|
||||||
{
|
{
|
||||||
attribute: "product_id",
|
attribute: "items.product_id",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
values: "prod_tshirt",
|
values: "prod_tshirt",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -362,7 +362,8 @@ export default class PromotionModuleService
|
|||||||
|
|
||||||
const isPromotionApplicable = areRulesValidForContext(
|
const isPromotionApplicable = areRulesValidForContext(
|
||||||
promotionRules,
|
promotionRules,
|
||||||
applicationContext
|
applicationContext,
|
||||||
|
ApplicationMethodTargetType.ORDER
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isPromotionApplicable) {
|
if (!isPromotionApplicable) {
|
||||||
|
|||||||
@@ -36,7 +36,13 @@ export function getComputedActionsForBuyGet(
|
|||||||
|
|
||||||
const validQuantity = MathBN.sum(
|
const validQuantity = MathBN.sum(
|
||||||
...itemsContext
|
...itemsContext
|
||||||
.filter((item) => areRulesValidForContext(buyRules, item))
|
.filter((item) =>
|
||||||
|
areRulesValidForContext(
|
||||||
|
buyRules,
|
||||||
|
item,
|
||||||
|
ApplicationMethodTargetType.ITEMS
|
||||||
|
)
|
||||||
|
)
|
||||||
.map((item) => item.quantity)
|
.map((item) => item.quantity)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +55,13 @@ export function getComputedActionsForBuyGet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validItemsForTargetRules = itemsContext
|
const validItemsForTargetRules = itemsContext
|
||||||
.filter((item) => areRulesValidForContext(targetRules, item))
|
.filter((item) =>
|
||||||
|
areRulesValidForContext(
|
||||||
|
targetRules,
|
||||||
|
item,
|
||||||
|
ApplicationMethodTargetType.ITEMS
|
||||||
|
)
|
||||||
|
)
|
||||||
.filter((item) => isPresent(item.subtotal) && isPresent(item.quantity))
|
.filter((item) => isPresent(item.subtotal) && isPresent(item.quantity))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const aPrice = MathBN.div(a.subtotal, a.quantity)
|
const aPrice = MathBN.div(a.subtotal, a.quantity)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ApplicationMethodAllocation,
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
ComputedActions,
|
ComputedActions,
|
||||||
MathBN,
|
MathBN,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
@@ -172,7 +173,8 @@ function getValidItemsForPromotion(
|
|||||||
const isQuantityPresent = "quantity" in item
|
const isQuantityPresent = "quantity" in item
|
||||||
const isPromotionApplicableToItem = areRulesValidForContext(
|
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||||
promotion?.application_method?.target_rules!,
|
promotion?.application_method?.target_rules!,
|
||||||
item
|
item,
|
||||||
|
ApplicationMethodTargetType.ITEMS
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export function getComputedActionsForShippingMethods(
|
|||||||
for (const shippingMethodContext of shippingMethodApplicationContext) {
|
for (const shippingMethodContext of shippingMethodApplicationContext) {
|
||||||
const isPromotionApplicableToItem = areRulesValidForContext(
|
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||||
promotion.application_method?.target_rules!,
|
promotion.application_method?.target_rules!,
|
||||||
shippingMethodContext
|
shippingMethodContext,
|
||||||
|
ApplicationMethodTargetType.SHIPPING_METHODS
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isPromotionApplicableToItem) {
|
if (!isPromotionApplicableToItem) {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types"
|
|
||||||
import {
|
import {
|
||||||
|
ApplicationMethodTargetTypeValues,
|
||||||
|
PromotionRuleDTO,
|
||||||
|
PromotionRuleOperatorValues,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodTargetType,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
PromotionRuleOperator,
|
PromotionRuleOperator,
|
||||||
isPresent,
|
isPresent,
|
||||||
@@ -46,7 +51,8 @@ export function validatePromotionRuleAttributes(
|
|||||||
|
|
||||||
export function areRulesValidForContext(
|
export function areRulesValidForContext(
|
||||||
rules: PromotionRuleDTO[],
|
rules: PromotionRuleDTO[],
|
||||||
context: Record<string, any>
|
context: Record<string, any>,
|
||||||
|
contextScope: ApplicationMethodTargetTypeValues
|
||||||
): boolean {
|
): boolean {
|
||||||
return rules.every((rule) => {
|
return rules.every((rule) => {
|
||||||
const validRuleValues = rule.values?.map((ruleValue) => ruleValue.value)
|
const validRuleValues = rule.values?.map((ruleValue) => ruleValue.value)
|
||||||
@@ -55,7 +61,10 @@ export function areRulesValidForContext(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const valuesToCheck = pickValueFromObject(rule.attribute, context)
|
const valuesToCheck = pickValueFromObject(
|
||||||
|
fetchRuleAttributeForContext(rule.attribute, contextScope),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
return evaluateRuleValueCondition(
|
return evaluateRuleValueCondition(
|
||||||
validRuleValues.filter(isString),
|
validRuleValues.filter(isString),
|
||||||
@@ -65,6 +74,38 @@ export function areRulesValidForContext(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The context here can either be either:
|
||||||
|
- a cart context
|
||||||
|
- an item context under a cart
|
||||||
|
- a shipping method context under a cart
|
||||||
|
|
||||||
|
The rule's attributes are set from the perspective of the cart context. For example: items.product.id
|
||||||
|
|
||||||
|
When the context here is item or shipping_method, we need to drop the "items."" or "shipping_method."
|
||||||
|
from the rule attribute string to accurate pick the values from the context.
|
||||||
|
*/
|
||||||
|
function fetchRuleAttributeForContext(
|
||||||
|
ruleAttribute: string,
|
||||||
|
contextScope: ApplicationMethodTargetTypeValues
|
||||||
|
): string {
|
||||||
|
if (contextScope === ApplicationMethodTargetType.ITEMS) {
|
||||||
|
ruleAttribute = ruleAttribute.replace(
|
||||||
|
`${ApplicationMethodTargetType.ITEMS}.`,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextScope === ApplicationMethodTargetType.SHIPPING_METHODS) {
|
||||||
|
ruleAttribute = ruleAttribute.replace(
|
||||||
|
`${ApplicationMethodTargetType.SHIPPING_METHODS}.`,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleAttribute
|
||||||
|
}
|
||||||
|
|
||||||
export function evaluateRuleValueCondition(
|
export function evaluateRuleValueCondition(
|
||||||
ruleValues: string[],
|
ruleValues: string[],
|
||||||
operator: string,
|
operator: string,
|
||||||
|
|||||||
Reference in new issue
Block a user