fix(promotion): validate rules accurately when attribute is scoped by context (#8655)

This commit is contained in:
Riqwan Thamir authored and GitHub committed 2024-08-19 15:38:42 +02:00
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,