feat(workflows-sdk,core-flows,medusa,types): add workflow to update promotions to cart (#6474)

what:

- adds API + workflow to add/remove promotions in a cart
- minor fixes in promotions module
- minor type fixes in cart module
- typing fix in workflows-sdk (Thanks @adrien2p)
- fix step result in workflows-sdk (Thanks @adrien2p)

RESOLVES CORE-1768

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Riqwan ThamirandAdrien de Peretti authored and GitHub committed 2024-02-26 12:43:57 +00:00
1 parent 63be07031b
commit ac86362e81
30 files changed
+1187 -101

No files matched your search

@@ -11,11 +11,11 @@ import {
CampaignBudgetType,
InjectManager,
InjectTransactionManager,
isString,
MedusaContext,
MedusaError,
ModulesSdkUtils,
PromotionType,
isString,
} from "@medusajs/utils"
import {
ApplicationMethod,
@@ -38,9 +38,9 @@ import {
UpdatePromotionDTO,
} from "@types"
import {
ComputeActionUtils,
allowedAllocationForQuantity,
areRulesValidForContext,
ComputeActionUtils,
validateApplicationMethodAttributes,
validatePromotionRuleAttributes,
} from "@utils"
@@ -166,10 +166,10 @@ export default class PromotionModuleService<
if (campaignBudget.type === CampaignBudgetType.SPEND) {
const campaignBudgetData = promotionCodeCampaignBudgetMap.get(
campaignBudget.id
) || { id: campaignBudget.id, used: campaignBudget.used || 0 }
) || { id: campaignBudget.id, used: campaignBudget.used ?? 0 }
campaignBudgetData.used =
(campaignBudgetData.used || 0) + computedAction.amount
(campaignBudgetData.used ?? 0) + computedAction.amount
if (
campaignBudget.limit &&
@@ -194,7 +194,7 @@ export default class PromotionModuleService<
const campaignBudgetData = {
id: campaignBudget.id,
used: (campaignBudget.used || 0) + 1,
used: (campaignBudget.used ?? 0) + 1,
}
if (
@@ -225,10 +225,12 @@ export default class PromotionModuleService<
}
}
@InjectManager("baseRepository_")
async computeActions(
promotionCodes: string[],
applicationContext: PromotionTypes.ComputeActionContext,
options: PromotionTypes.ComputeActionOptions = {}
options: PromotionTypes.ComputeActionOptions = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PromotionTypes.ComputeActions[]> {
const { prevent_auto_promotions: preventAutoPromotions } = options
const computedActions: PromotionTypes.ComputeActions[] = []
@@ -238,12 +240,16 @@ export default class PromotionModuleService<
const appliedShippingCodes: string[] = []
const codeAdjustmentMap = new Map<
string,
PromotionTypes.ComputeActionAdjustmentLine
PromotionTypes.ComputeActionAdjustmentLine[]
>()
const methodIdPromoValueMap = new Map<string, number>()
const automaticPromotions = preventAutoPromotions
? []
: await this.list({ is_automatic: true }, { select: ["code"] })
: await this.list(
{ is_automatic: true },
{ select: ["code"] },
sharedContext
)
const automaticPromotionCodes = automaticPromotions.map((p) => p.code!)
const promotionCodesToApply = [
@@ -254,7 +260,11 @@ export default class PromotionModuleService<
items.forEach((item) => {
item.adjustments?.forEach((adjustment) => {
if (isString(adjustment.code)) {
codeAdjustmentMap.set(adjustment.code, adjustment)
const adjustments = codeAdjustmentMap.get(adjustment.code) || []
adjustments.push(adjustment)
codeAdjustmentMap.set(adjustment.code, adjustments)
appliedItemCodes.push(adjustment.code)
}
})
@@ -263,7 +273,11 @@ export default class PromotionModuleService<
shippingMethods.forEach((shippingMethod) => {
shippingMethod.adjustments?.forEach((adjustment) => {
if (isString(adjustment.code)) {
codeAdjustmentMap.set(adjustment.code, adjustment)
const adjustments = codeAdjustmentMap.get(adjustment.code) || []
adjustments.push(adjustment)
codeAdjustmentMap.set(adjustment.code, adjustments)
appliedShippingCodes.push(adjustment.code)
}
})
@@ -292,6 +306,7 @@ export default class PromotionModuleService<
}
)
const appliedCodes = [...appliedShippingCodes, ...appliedItemCodes]
const sortedPermissionsToApply = promotions
.filter((p) => promotionCodesToApply.includes(p.code!))
.sort(ComputeActionUtils.sortByBuyGetType)
@@ -300,7 +315,7 @@ export default class PromotionModuleService<
promotions.map((promotion) => [promotion.code!, promotion])
)
for (const appliedCode of [...appliedShippingCodes, ...appliedItemCodes]) {
for (const appliedCode of appliedCodes) {
const promotion = existingPromotionsMap.get(appliedCode)
if (!promotion) {
@@ -310,24 +325,28 @@ export default class PromotionModuleService<
)
}
if (promotionCodes.includes(appliedCode)) {
continue
}
if (appliedItemCodes.includes(appliedCode)) {
computedActions.push({
action: "removeItemAdjustment",
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
code: appliedCode,
})
const adjustments = codeAdjustmentMap.get(appliedCode) || []
adjustments.forEach((adjustment) =>
computedActions.push({
action: "removeItemAdjustment",
adjustment_id: adjustment.id,
code: appliedCode,
})
)
}
if (appliedShippingCodes.includes(appliedCode)) {
computedActions.push({
action: "removeShippingMethodAdjustment",
adjustment_id: codeAdjustmentMap.get(appliedCode)!.id,
code: appliedCode,
})
const adjustments = codeAdjustmentMap.get(appliedCode) || []
adjustments.forEach((adjustment) =>
computedActions.push({
action: "removeShippingMethodAdjustment",
adjustment_id: adjustment.id,
code: appliedCode,
})
)
}
}
@@ -4,10 +4,12 @@ import {
ComputedActions,
MedusaError,
PromotionType,
isPresent,
} from "@medusajs/utils"
import { areRulesValidForContext } from "../validations"
import { computeActionForBudgetExceeded } from "./usage"
// TODO: calculations should eventually move to a totals util outside of the module
export function getComputedActionsForBuyGet(
promotion: PromotionTypes.PromotionDTO,
itemsContext: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
@@ -45,6 +47,7 @@ export function getComputedActionsForBuyGet(
const validItemsForTargetRules = itemsContext
.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
@@ -55,7 +58,7 @@ export function getComputedActionsForBuyGet(
let remainingQtyToApply = applyToQuantity
for (const method of validItemsForTargetRules) {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
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
@@ -49,6 +49,7 @@ export function getComputedActionsForItems(
)
}
// TODO: calculations should eventually move to a totals util outside of the module
export function applyPromotionToItems(
promotion: PromotionTypes.PromotionDTO,
items: PromotionTypes.ComputeActionContext[ApplicationMethodTargetType.ITEMS],
@@ -63,7 +64,11 @@ export function applyPromotionToItems(
[allocation, allocationOverride].includes(ApplicationMethodAllocation.EACH)
) {
for (const method of items!) {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
if (!method.subtotal || !method.quantity) {
continue
}
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
const quantityMultiplier = Math.min(
method.quantity,
applicationMethod?.max_quantity!
@@ -111,16 +116,20 @@ export function applyPromotionToItems(
)
) {
const totalApplicableValue = items!.reduce((acc, method) => {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
return (
acc +
(method.subtotal / method.quantity) * method.quantity -
appliedPromoValue
)
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!) {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
if (!method.subtotal || !method.quantity) {
continue
}
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
const promotionValue = parseFloat(applicationMethod!.value!)
const applicableTotal =
(method.subtotal / method.quantity) * method.quantity -
@@ -55,7 +55,11 @@ export function applyPromotionToShippingMethods(
if (allocation === ApplicationMethodAllocation.EACH) {
for (const method of shippingMethods!) {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
if (!method.subtotal) {
continue
}
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
let promotionValue = parseFloat(applicationMethod!.value!)
const applicableTotal = method.subtotal - appliedPromoValue
@@ -93,9 +97,9 @@ export function applyPromotionToShippingMethods(
if (allocation === ApplicationMethodAllocation.ACROSS) {
const totalApplicableValue = shippingMethods!.reduce((acc, method) => {
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
return acc + method.subtotal - appliedPromoValue
return acc + (method.subtotal ?? 0) - appliedPromoValue
}, 0)
if (totalApplicableValue <= 0) {
@@ -103,9 +107,13 @@ export function applyPromotionToShippingMethods(
}
for (const method of shippingMethods!) {
if (!method.subtotal) {
continue
}
const promotionValue = parseFloat(applicationMethod!.value!)
const applicableTotal = method.subtotal
const appliedPromoValue = methodIdPromoValueMap.get(method.id) || 0
const appliedPromoValue = methodIdPromoValueMap.get(method.id) ?? 0
// TODO: should we worry about precision here?
let applicablePromotionValue =
@@ -24,7 +24,7 @@ export function computeActionForBudgetExceeded(
return
}
const campaignBudgetUsed = campaignBudget.used || 0
const campaignBudgetUsed = campaignBudget.used ?? 0
const totalUsed =
campaignBudget.type === CampaignBudgetType.SPEND
? campaignBudgetUsed + amount