feat(utils,types): added item/shipping adjustments for order/items/shipping_methods (#6050)
what: - adds compute actions for the following cases: - items => each & across - shipping_method => each & across - order - adds a remove compute actions when code is no longer present in adjustments array RESOLVES CORE-1625 RESOLVES CORE-1626 RESOLVES CORE-1627 RESOLVES CORE-1628 RESOLVES CORE-1585
This commit is contained in:
1 parent
192bc336cc
commit
b782d3bcb7
24 files changed
+2653
-96
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(utils,types): added item/shipping adjustments for order/items/shipping_methods
|
||||||
+1852
File diff suppressed because it is too large.
Load diff
+52
-82
@@ -99,8 +99,50 @@ describe("Promotion Service", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a promotion with order application method with rules successfully", async () => {
|
it("should throw error when creating an item application method without allocation", async () => {
|
||||||
const [createdPromotion] = await service.create([
|
const error = await service
|
||||||
|
.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
value: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain(
|
||||||
|
"application_method.allocation should be either 'across OR each' when application_method.target_type is either 'shipping_methods OR items'"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when creating an item application, each allocation, without max quanity", async () => {
|
||||||
|
const error = await service
|
||||||
|
.create([
|
||||||
|
{
|
||||||
|
code: "PROMOTION_TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
application_method: {
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
target_type: "shipping_methods",
|
||||||
|
value: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toContain(
|
||||||
|
"application_method.max_quantity is required when application_method.allocation is 'each'"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when creating an order application method with rules", async () => {
|
||||||
|
const error = await service
|
||||||
|
.create([
|
||||||
{
|
{
|
||||||
code: "PROMOTION_TEST",
|
code: "PROMOTION_TEST",
|
||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
@@ -118,82 +160,10 @@ describe("Promotion Service", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const [promotion] = await service.list(
|
|
||||||
{
|
|
||||||
id: [createdPromotion.id],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
relations: [
|
|
||||||
"application_method",
|
|
||||||
"application_method.target_rules.values",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(promotion).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
is_automatic: false,
|
|
||||||
type: "standard",
|
|
||||||
application_method: expect.objectContaining({
|
|
||||||
type: "fixed",
|
|
||||||
target_type: "order",
|
|
||||||
value: 100,
|
|
||||||
target_rules: [
|
|
||||||
expect.objectContaining({
|
|
||||||
attribute: "product_id",
|
|
||||||
operator: "eq",
|
|
||||||
values: expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
value: "prod_tshirt",
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw error when creating an item application method without allocation", async () => {
|
|
||||||
const error = await service
|
|
||||||
.create([
|
|
||||||
{
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
type: PromotionType.STANDARD,
|
|
||||||
application_method: {
|
|
||||||
type: "fixed",
|
|
||||||
target_type: "item",
|
|
||||||
value: "100",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain(
|
||||||
"application_method.allocation should be either 'across OR each' when application_method.target_type is either 'shipping OR item'"
|
"Target rules for application method with target type (order) is not allowed"
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw error when creating an item application, each allocation, without max quanity", async () => {
|
|
||||||
const error = await service
|
|
||||||
.create([
|
|
||||||
{
|
|
||||||
code: "PROMOTION_TEST",
|
|
||||||
type: PromotionType.STANDARD,
|
|
||||||
application_method: {
|
|
||||||
type: "fixed",
|
|
||||||
allocation: "each",
|
|
||||||
target_type: "shipping",
|
|
||||||
value: "100",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(error.message).toContain(
|
|
||||||
"application_method.max_quantity is required when application_method.allocation is 'each'"
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -390,7 +360,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "across",
|
allocation: "across",
|
||||||
value: "100",
|
value: "100",
|
||||||
},
|
},
|
||||||
@@ -424,7 +394,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -483,7 +453,7 @@ describe("Promotion Service", () => {
|
|||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain(
|
||||||
`application_method.target_type should be one of order, shipping, item`
|
`application_method.target_type should be one of order, shipping_methods, items`
|
||||||
)
|
)
|
||||||
|
|
||||||
error = await service
|
error = await service
|
||||||
@@ -604,7 +574,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -676,7 +646,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -760,7 +730,7 @@ describe("Promotion Service", () => {
|
|||||||
],
|
],
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
@@ -821,7 +791,7 @@ describe("Promotion Service", () => {
|
|||||||
type: PromotionType.STANDARD,
|
type: PromotionType.STANDARD,
|
||||||
application_method: {
|
application_method: {
|
||||||
type: "fixed",
|
type: "fixed",
|
||||||
target_type: "item",
|
target_type: "items",
|
||||||
allocation: "each",
|
allocation: "each",
|
||||||
value: "100",
|
value: "100",
|
||||||
max_quantity: 500,
|
max_quantity: 500,
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import {
|
|||||||
PromotionTypes,
|
PromotionTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
|
ApplicationMethodTargetType,
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
|
isString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { ApplicationMethod, Promotion } from "@models"
|
import { ApplicationMethod, Promotion } from "@models"
|
||||||
import {
|
import {
|
||||||
@@ -23,11 +25,14 @@ import { joinerConfig } from "../joiner-config"
|
|||||||
import {
|
import {
|
||||||
CreateApplicationMethodDTO,
|
CreateApplicationMethodDTO,
|
||||||
CreatePromotionDTO,
|
CreatePromotionDTO,
|
||||||
|
CreatePromotionRuleDTO,
|
||||||
UpdateApplicationMethodDTO,
|
UpdateApplicationMethodDTO,
|
||||||
UpdatePromotionDTO,
|
UpdatePromotionDTO,
|
||||||
} from "../types"
|
} from "../types"
|
||||||
import {
|
import {
|
||||||
|
ComputeActionUtils,
|
||||||
allowedAllocationForQuantity,
|
allowedAllocationForQuantity,
|
||||||
|
areRulesValidForContext,
|
||||||
validateApplicationMethodAttributes,
|
validateApplicationMethodAttributes,
|
||||||
validatePromotionRuleAttributes,
|
validatePromotionRuleAttributes,
|
||||||
} from "../utils"
|
} from "../utils"
|
||||||
@@ -71,6 +76,161 @@ export default class PromotionModuleService<
|
|||||||
return joinerConfig
|
return joinerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async computeActions(
|
||||||
|
promotionCodesToApply: string[],
|
||||||
|
applicationContext: PromotionTypes.ComputeActionContext,
|
||||||
|
// TODO: specify correct type with options
|
||||||
|
options: Record<string, any> = {}
|
||||||
|
): Promise<PromotionTypes.ComputeActions[]> {
|
||||||
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
|
const { items = [], shipping_methods: shippingMethods = [] } =
|
||||||
|
applicationContext
|
||||||
|
const appliedItemCodes: string[] = []
|
||||||
|
const appliedShippingCodes: string[] = []
|
||||||
|
const codeAdjustmentMap = new Map<
|
||||||
|
string,
|
||||||
|
PromotionTypes.ComputeActionAdjustmentLine
|
||||||
|
>()
|
||||||
|
const methodIdPromoValueMap = new Map<string, number>()
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
item.adjustments?.forEach((adjustment) => {
|
||||||
|
if (isString(adjustment.code)) {
|
||||||
|
codeAdjustmentMap.set(adjustment.code, adjustment)
|
||||||
|
appliedItemCodes.push(adjustment.code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
shippingMethods.forEach((shippingMethod) => {
|
||||||
|
shippingMethod.adjustments?.forEach((adjustment) => {
|
||||||
|
if (isString(adjustment.code)) {
|
||||||
|
codeAdjustmentMap.set(adjustment.code, adjustment)
|
||||||
|
appliedShippingCodes.push(adjustment.code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const promotions = await this.list(
|
||||||
|
{
|
||||||
|
code: [
|
||||||
|
...promotionCodesToApply,
|
||||||
|
...appliedItemCodes,
|
||||||
|
...appliedShippingCodes,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relations: [
|
||||||
|
"application_method",
|
||||||
|
"application_method.target_rules",
|
||||||
|
"application_method.target_rules.values",
|
||||||
|
"rules",
|
||||||
|
"rules.values",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingPromotionsMap = new Map<string, PromotionTypes.PromotionDTO>(
|
||||||
|
promotions.map((promotion) => [promotion.code!, promotion])
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const appliedCode of [...appliedShippingCodes, ...appliedItemCodes]) {
|
||||||
|
const promotion = existingPromotionsMap.get(appliedCode)
|
||||||
|
|
||||||
|
if (!promotion) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Applied Promotion for code (${appliedCode}) not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promotionCodesToApply.includes(appliedCode)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedItemCodes.includes(appliedCode)) {
|
||||||
|
computedActions.push({
|
||||||
|
action: "removeItemAdjustment",
|
||||||
|
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedShippingCodes.includes(appliedCode)) {
|
||||||
|
computedActions.push({
|
||||||
|
action: "removeShippingMethodAdjustment",
|
||||||
|
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const promotionCode of promotionCodesToApply) {
|
||||||
|
const promotion = existingPromotionsMap.get(promotionCode)
|
||||||
|
|
||||||
|
if (!promotion) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Promotion for code (${promotionCode}) not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
application_method: applicationMethod,
|
||||||
|
rules: promotionRules = [],
|
||||||
|
} = promotion
|
||||||
|
|
||||||
|
if (!applicationMethod) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPromotionApplicable = areRulesValidForContext(
|
||||||
|
promotionRules,
|
||||||
|
applicationContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isPromotionApplicable) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationMethod.target_type === ApplicationMethodTargetType.ORDER) {
|
||||||
|
const computedActionsForItems =
|
||||||
|
ComputeActionUtils.getComputedActionsForOrder(
|
||||||
|
promotion,
|
||||||
|
applicationContext,
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
|
||||||
|
computedActions.push(...computedActionsForItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationMethod.target_type === ApplicationMethodTargetType.ITEMS) {
|
||||||
|
const computedActionsForItems =
|
||||||
|
ComputeActionUtils.getComputedActionsForItems(
|
||||||
|
promotion,
|
||||||
|
applicationContext[ApplicationMethodTargetType.ITEMS],
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
|
||||||
|
computedActions.push(...computedActionsForItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
applicationMethod.target_type ===
|
||||||
|
ApplicationMethodTargetType.SHIPPING_METHODS
|
||||||
|
) {
|
||||||
|
const computedActionsForShippingMethods =
|
||||||
|
ComputeActionUtils.getComputedActionsForShippingMethods(
|
||||||
|
promotion,
|
||||||
|
applicationContext[ApplicationMethodTargetType.SHIPPING_METHODS],
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
|
||||||
|
computedActions.push(...computedActionsForShippingMethods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async retrieve(
|
async retrieve(
|
||||||
id: string,
|
id: string,
|
||||||
@@ -194,6 +354,17 @@ export default class PromotionModuleService<
|
|||||||
promotion,
|
promotion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
applicationMethodData.target_type ===
|
||||||
|
ApplicationMethodTargetType.ORDER &&
|
||||||
|
targetRulesData.length
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Target rules for application method with target type (${ApplicationMethodTargetType.ORDER}) is not allowed`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
validateApplicationMethodAttributes(applicationMethodData)
|
validateApplicationMethodAttributes(applicationMethodData)
|
||||||
applicationMethodsData.push(applicationMethodData)
|
applicationMethodsData.push(applicationMethodData)
|
||||||
|
|
||||||
@@ -394,7 +565,7 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
for (const ruleData of rulesData) {
|
for (const ruleData of rulesData) {
|
||||||
const { values, ...rest } = ruleData
|
const { values, ...rest } = ruleData
|
||||||
const promotionRuleData = {
|
const promotionRuleData: CreatePromotionRuleDTO = {
|
||||||
...rest,
|
...rest,
|
||||||
[relationName]: [relation],
|
[relationName]: [relation],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { PromotionRuleDTO } from "@medusajs/types"
|
import { PromotionRuleDTO } from "@medusajs/types"
|
||||||
|
import { PromotionRule } from "@models"
|
||||||
|
|
||||||
export interface CreatePromotionRuleValueDTO {
|
export interface CreatePromotionRuleValueDTO {
|
||||||
value: any
|
value: any
|
||||||
promotion_rule: string | PromotionRuleDTO
|
promotion_rule: string | PromotionRuleDTO | PromotionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdatePromotionRuleValueDTO {
|
export interface UpdatePromotionRuleValueDTO {
|
||||||
id: string
|
id: string
|
||||||
value: any
|
value: any
|
||||||
promotion_rule: string | PromotionRuleDTO
|
promotion_rule: string | PromotionRuleDTO | PromotionRule
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
||||||
|
|
||||||
export interface CreatePromotionRuleDTO {
|
export interface CreatePromotionRuleDTO {
|
||||||
description?: string
|
description?: string | null
|
||||||
attribute: string
|
attribute: string
|
||||||
operator: PromotionRuleOperatorValues
|
operator: PromotionRuleOperatorValues
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./items"
|
||||||
|
export * from "./order"
|
||||||
|
export * from "./shipping-methods"
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
ApplicationMethodAllocationValues,
|
||||||
|
PromotionTypes,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
|
MedusaError,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { areRulesValidForContext } from "../validations"
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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!) {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const applicableTotal =
|
||||||
|
method.unit_price *
|
||||||
|
Math.min(method.quantity, applicationMethod?.max_quantity!) -
|
||||||
|
appliedPromoValue
|
||||||
|
|
||||||
|
const amount = Math.min(promotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
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
|
||||||
|
return (
|
||||||
|
acc +
|
||||||
|
method.unit_price *
|
||||||
|
Math.min(method.quantity, applicationMethod?.max_quantity!) -
|
||||||
|
appliedPromoValue
|
||||||
|
)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
for (const method of items!) {
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
|
||||||
|
const applicableTotal =
|
||||||
|
method.unit_price *
|
||||||
|
Math.min(method.quantity, applicationMethod?.max_quantity!) -
|
||||||
|
appliedPromoValue
|
||||||
|
|
||||||
|
// TODO: should we worry about precision here?
|
||||||
|
const applicablePromotionValue =
|
||||||
|
(applicableTotal / totalApplicableValue) * promotionValue
|
||||||
|
|
||||||
|
const amount = Math.min(applicablePromotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addItemAdjustment",
|
||||||
|
item_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { PromotionTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
|
MedusaError,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { areRulesValidForContext } from "../validations"
|
||||||
|
|
||||||
|
export function getComputedActionsForShippingMethods(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
shippingMethodApplicationContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS],
|
||||||
|
methodIdPromoValueMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const applicableShippingItems: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS] =
|
||||||
|
[]
|
||||||
|
|
||||||
|
if (!shippingMethodApplicationContext) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`"shipping_methods" should be present as an array in the context for computeActions`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const shippingMethodContext of shippingMethodApplicationContext) {
|
||||||
|
const isPromotionApplicableToItem = areRulesValidForContext(
|
||||||
|
promotion.application_method?.target_rules!,
|
||||||
|
shippingMethodContext
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isPromotionApplicableToItem) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
applicableShippingItems.push(shippingMethodContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyPromotionToShippingMethods(
|
||||||
|
promotion,
|
||||||
|
applicableShippingItems,
|
||||||
|
methodIdPromoValueMap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPromotionToShippingMethods(
|
||||||
|
promotion: PromotionTypes.PromotionDTO,
|
||||||
|
shippingMethods: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.SHIPPING_METHODS],
|
||||||
|
methodIdPromoValueMap: Map<string, number>
|
||||||
|
): PromotionTypes.ComputeActions[] {
|
||||||
|
const { application_method: applicationMethod } = promotion
|
||||||
|
const allocation = applicationMethod?.allocation!
|
||||||
|
const computedActions: PromotionTypes.ComputeActions[] = []
|
||||||
|
|
||||||
|
if (allocation === ApplicationMethodAllocation.EACH) {
|
||||||
|
for (const method of shippingMethods!) {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const applicableTotal = method.unit_price - appliedPromoValue
|
||||||
|
const amount = Math.min(promotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocation === ApplicationMethodAllocation.ACROSS) {
|
||||||
|
const totalApplicableValue = shippingMethods!.reduce((acc, method) => {
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
|
||||||
|
return acc + method.unit_price - appliedPromoValue
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
if (totalApplicableValue <= 0) {
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const method of shippingMethods!) {
|
||||||
|
const promotionValue = parseFloat(applicationMethod!.value!)
|
||||||
|
const applicableTotal = method.unit_price
|
||||||
|
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
|
||||||
|
|
||||||
|
// TODO: should we worry about precision here?
|
||||||
|
const applicablePromotionValue =
|
||||||
|
(applicableTotal / totalApplicableValue) * promotionValue -
|
||||||
|
appliedPromoValue
|
||||||
|
|
||||||
|
const amount = Math.min(applicablePromotionValue, applicableTotal)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
methodIdPromoValueMap.set(method.id, appliedPromoValue + amount)
|
||||||
|
|
||||||
|
computedActions.push({
|
||||||
|
action: "addShippingMethodAdjustment",
|
||||||
|
shipping_method_id: method.id,
|
||||||
|
amount,
|
||||||
|
code: promotion.code!,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedActions
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
export * as ComputeActionUtils from "./compute-actions"
|
||||||
export * from "./validations"
|
export * from "./validations"
|
||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
|
||||||
export const allowedAllocationTargetTypes: string[] = [
|
export const allowedAllocationTargetTypes: string[] = [
|
||||||
ApplicationMethodTargetType.SHIPPING,
|
ApplicationMethodTargetType.SHIPPING_METHODS,
|
||||||
ApplicationMethodTargetType.ITEM,
|
ApplicationMethodTargetType.ITEMS,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const allowedAllocationTypes: string[] = [
|
export const allowedAllocationTypes: string[] = [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { PromotionRuleOperatorValues } from "@medusajs/types"
|
import { PromotionRuleDTO, PromotionRuleOperatorValues } from "@medusajs/types"
|
||||||
import { MedusaError, PromotionRuleOperator, isPresent } from "@medusajs/utils"
|
import {
|
||||||
|
MedusaError,
|
||||||
|
PromotionRuleOperator,
|
||||||
|
isPresent,
|
||||||
|
isString,
|
||||||
|
pickValueFromObject,
|
||||||
|
} from "@medusajs/utils"
|
||||||
import { CreatePromotionRuleDTO } from "../../types"
|
import { CreatePromotionRuleDTO } from "../../types"
|
||||||
|
|
||||||
export function validatePromotionRuleAttributes(
|
export function validatePromotionRuleAttributes(
|
||||||
@@ -37,3 +43,62 @@ export function validatePromotionRuleAttributes(
|
|||||||
|
|
||||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, errors.join(", "))
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, errors.join(", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function areRulesValidForContext(
|
||||||
|
rules: PromotionRuleDTO[],
|
||||||
|
context: Record<string, any>
|
||||||
|
): boolean {
|
||||||
|
return rules.every((rule) => {
|
||||||
|
const validRuleValues = rule.values?.map((ruleValue) => ruleValue.value)
|
||||||
|
|
||||||
|
if (!rule.attribute) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const valuesToCheck = pickValueFromObject(rule.attribute, context)
|
||||||
|
|
||||||
|
return evaluateRuleValueCondition(
|
||||||
|
validRuleValues.filter(isString),
|
||||||
|
rule.operator!,
|
||||||
|
valuesToCheck
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evaluateRuleValueCondition(
|
||||||
|
ruleValues: string[],
|
||||||
|
operator: string,
|
||||||
|
ruleValuesToCheck: string[] | string
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(ruleValuesToCheck)) {
|
||||||
|
ruleValuesToCheck = [ruleValuesToCheck]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleValuesToCheck.every((ruleValueToCheck: string) => {
|
||||||
|
if (operator === "in" || operator === "eq") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue === ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "ne") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue !== ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "gt") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue > ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "gte") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue >= ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "lt") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue < ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operator === "lte") {
|
||||||
|
return ruleValues.some((ruleValue) => ruleValue <= ruleValueToCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,7 +3,10 @@ import { PromotionDTO } from "./promotion"
|
|||||||
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
||||||
|
|
||||||
export type ApplicationMethodTypeValues = "fixed" | "percentage"
|
export type ApplicationMethodTypeValues = "fixed" | "percentage"
|
||||||
export type ApplicationMethodTargetTypeValues = "order" | "shipping" | "item"
|
export type ApplicationMethodTargetTypeValues =
|
||||||
|
| "order"
|
||||||
|
| "shipping_methods"
|
||||||
|
| "items"
|
||||||
export type ApplicationMethodAllocationValues = "each" | "across"
|
export type ApplicationMethodAllocationValues = "each" | "across"
|
||||||
|
|
||||||
export interface ApplicationMethodDTO {
|
export interface ApplicationMethodDTO {
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
export type ComputeActions =
|
||||||
|
| AddItemAdjustmentAction
|
||||||
|
| RemoveItemAdjustmentAction
|
||||||
|
| AddShippingMethodAdjustment
|
||||||
|
| RemoveShippingMethodAdjustment
|
||||||
|
|
||||||
|
export interface AddItemAdjustmentAction {
|
||||||
|
action: "addItemAdjustment"
|
||||||
|
item_id: string
|
||||||
|
amount: number
|
||||||
|
code: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveItemAdjustmentAction {
|
||||||
|
action: "removeItemAdjustment"
|
||||||
|
adjustment_id: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddShippingMethodAdjustment {
|
||||||
|
action: "addShippingMethodAdjustment"
|
||||||
|
shipping_method_id: string
|
||||||
|
amount: number
|
||||||
|
code: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveShippingMethodAdjustment {
|
||||||
|
action: "removeShippingMethodAdjustment"
|
||||||
|
adjustment_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionAdjustmentLine {
|
||||||
|
id: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionItemLine {
|
||||||
|
id: string
|
||||||
|
quantity: number
|
||||||
|
unit_price: number
|
||||||
|
adjustments?: ComputeActionAdjustmentLine[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionShippingLine {
|
||||||
|
id: string
|
||||||
|
unit_price: number
|
||||||
|
adjustments?: ComputeActionAdjustmentLine[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputeActionContext {
|
||||||
|
items?: ComputeActionItemLine[]
|
||||||
|
shipping_methods?: ComputeActionShippingLine[]
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./application-method"
|
export * from "./application-method"
|
||||||
|
export * from "./compute-actions"
|
||||||
export * from "./promotion"
|
export * from "./promotion"
|
||||||
export * from "./promotion-rule"
|
export * from "./promotion-rule"
|
||||||
export * from "./promotion-rule-value"
|
export * from "./promotion-rule-value"
|
||||||
@@ -3,10 +3,11 @@ import { PromotionRuleDTO } from "./promotion-rule"
|
|||||||
|
|
||||||
export interface PromotionRuleValueDTO {
|
export interface PromotionRuleValueDTO {
|
||||||
id: string
|
id: string
|
||||||
|
value?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromotionRuleValueDTO {
|
export interface CreatePromotionRuleValueDTO {
|
||||||
value: any
|
value: string
|
||||||
promotion_rule: PromotionRuleDTO
|
promotion_rule: PromotionRuleDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BaseFilterable } from "../../dal"
|
import { BaseFilterable } from "../../dal"
|
||||||
|
import { PromotionRuleValueDTO } from "./promotion-rule-value"
|
||||||
|
|
||||||
export type PromotionRuleOperatorValues =
|
export type PromotionRuleOperatorValues =
|
||||||
| "gt"
|
| "gt"
|
||||||
@@ -11,10 +12,14 @@ export type PromotionRuleOperatorValues =
|
|||||||
|
|
||||||
export interface PromotionRuleDTO {
|
export interface PromotionRuleDTO {
|
||||||
id: string
|
id: string
|
||||||
|
description?: string | null
|
||||||
|
attribute?: string
|
||||||
|
operator?: PromotionRuleOperatorValues
|
||||||
|
values: PromotionRuleValueDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromotionRuleDTO {
|
export interface CreatePromotionRuleDTO {
|
||||||
description?: string
|
description?: string | null
|
||||||
attribute: string
|
attribute: string
|
||||||
operator: PromotionRuleOperatorValues
|
operator: PromotionRuleOperatorValues
|
||||||
values: string[] | string
|
values: string[] | string
|
||||||
@@ -31,4 +36,5 @@ export interface RemovePromotionRuleDTO {
|
|||||||
export interface FilterablePromotionRuleProps
|
export interface FilterablePromotionRuleProps
|
||||||
extends BaseFilterable<FilterablePromotionRuleProps> {
|
extends BaseFilterable<FilterablePromotionRuleProps> {
|
||||||
id?: string[]
|
id?: string[]
|
||||||
|
code?: string[]
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
CreateApplicationMethodDTO,
|
CreateApplicationMethodDTO,
|
||||||
UpdateApplicationMethodDTO,
|
UpdateApplicationMethodDTO,
|
||||||
} from "./application-method"
|
} from "./application-method"
|
||||||
import { CreatePromotionRuleDTO } from "./promotion-rule"
|
import { CreatePromotionRuleDTO, PromotionRuleDTO } from "./promotion-rule"
|
||||||
|
|
||||||
export type PromotionType = "standard" | "buyget"
|
export type PromotionType = "standard" | "buyget"
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ export interface PromotionDTO {
|
|||||||
type?: PromotionType
|
type?: PromotionType
|
||||||
is_automatic?: boolean
|
is_automatic?: boolean
|
||||||
application_method?: ApplicationMethodDTO
|
application_method?: ApplicationMethodDTO
|
||||||
|
rules?: PromotionRuleDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromotionDTO {
|
export interface CreatePromotionDTO {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { FindConfig } from "../common"
|
|||||||
import { IModuleService } from "../modules-sdk"
|
import { IModuleService } from "../modules-sdk"
|
||||||
import { Context } from "../shared-context"
|
import { Context } from "../shared-context"
|
||||||
import {
|
import {
|
||||||
|
ComputeActionContext,
|
||||||
|
ComputeActions,
|
||||||
CreatePromotionDTO,
|
CreatePromotionDTO,
|
||||||
CreatePromotionRuleDTO,
|
CreatePromotionRuleDTO,
|
||||||
FilterablePromotionProps,
|
FilterablePromotionProps,
|
||||||
@@ -11,6 +13,12 @@ import {
|
|||||||
} from "./common"
|
} from "./common"
|
||||||
|
|
||||||
export interface IPromotionModuleService extends IModuleService {
|
export interface IPromotionModuleService extends IModuleService {
|
||||||
|
computeActions(
|
||||||
|
promotionCodesToApply: string[],
|
||||||
|
applicationContext: ComputeActionContext,
|
||||||
|
options?: Record<string, any>
|
||||||
|
): Promise<ComputeActions[]>
|
||||||
|
|
||||||
create(
|
create(
|
||||||
data: CreatePromotionDTO[],
|
data: CreatePromotionDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { pickValueFromObject } from "../pick-value-from-object"
|
||||||
|
|
||||||
|
describe("pickValueFromObject", function () {
|
||||||
|
it("should return true or false for different types of data", function () {
|
||||||
|
const expectations = [
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute.array_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: [
|
||||||
|
{
|
||||||
|
array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: ["test 1", "test 2"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute.array_attribute.deep_array_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: [
|
||||||
|
{
|
||||||
|
array_attribute: [
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
array_attribute: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: ["test 1", "test 2"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.another_attribute.array_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: [
|
||||||
|
{
|
||||||
|
array_attribute: [
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
array_attribute: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep_array_attribute: "test 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
1: "attribute.missing_attribute",
|
||||||
|
2: {
|
||||||
|
attribute: {
|
||||||
|
another_attribute: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: undefined,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
expectations.forEach((expectation) => {
|
||||||
|
expect(
|
||||||
|
pickValueFromObject(expectation.input["1"], expectation.input["2"])
|
||||||
|
).toEqual(expectation.output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -23,6 +23,7 @@ export * from "./medusa-container"
|
|||||||
export * from "./object-from-string-path"
|
export * from "./object-from-string-path"
|
||||||
export * from "./object-to-string-path"
|
export * from "./object-to-string-path"
|
||||||
export * from "./optional-numeric-serializer"
|
export * from "./optional-numeric-serializer"
|
||||||
|
export * from "./pick-value-from-object"
|
||||||
export * from "./promise-all"
|
export * from "./promise-all"
|
||||||
export * from "./remote-query-object-from-string"
|
export * from "./remote-query-object-from-string"
|
||||||
export * from "./remote-query-object-to-string"
|
export * from "./remote-query-object-to-string"
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { isObject } from "./is-object"
|
||||||
|
|
||||||
|
export function pickValueFromObject(
|
||||||
|
path: string,
|
||||||
|
object: Record<any, any>
|
||||||
|
): any {
|
||||||
|
const segments = path.split(".")
|
||||||
|
let result: any = undefined
|
||||||
|
|
||||||
|
for (const segment of segments) {
|
||||||
|
const segmentsLeft = [...segments].splice(1, segments.length - 1)
|
||||||
|
const segmentOutput = object[segment]
|
||||||
|
|
||||||
|
if (segmentsLeft.length === 0) {
|
||||||
|
result = segmentOutput
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(segmentOutput)) {
|
||||||
|
result = pickValueFromObject(segmentsLeft.join("."), segmentOutput)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(segmentOutput)) {
|
||||||
|
result = segmentOutput
|
||||||
|
.map((segmentOutput_) =>
|
||||||
|
pickValueFromObject(segmentsLeft.join("."), segmentOutput_)
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
result = segmentOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ export enum ApplicationMethodType {
|
|||||||
|
|
||||||
export enum ApplicationMethodTargetType {
|
export enum ApplicationMethodTargetType {
|
||||||
ORDER = "order",
|
ORDER = "order",
|
||||||
SHIPPING = "shipping",
|
SHIPPING_METHODS = "shipping_methods",
|
||||||
ITEM = "item",
|
ITEMS = "items",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApplicationMethodAllocation {
|
export enum ApplicationMethodAllocation {
|
||||||
|
|||||||
Reference in new issue
Block a user