feat(core-flows,types,promotion): register promotion campaign usage upon cart completion (#8970)

* feat(core-flows,types,promotion): register promotion campaign usage upon cart completion

* Apply suggestions from code review

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Riqwan Thamir
2024-09-05 10:43:29 +02:00
committed by GitHub
co-authored by Oli Juhl
parent 2a055b71ef
commit 5d7179b7d0
8 changed files with 421 additions and 56 deletions
@@ -123,33 +123,39 @@ export default class PromotionModuleService
@InjectManager("baseRepository_")
async registerUsage(
computedActions: PromotionTypes.UsageComputedActions[],
@MedusaContext()
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
const promotionCodes = computedActions
.map((computedAction) => computedAction.code)
.filter(Boolean)
const promotionCodeCampaignBudgetMap = new Map<
string,
UpdateCampaignBudgetDTO
>()
const campaignBudgetMap = new Map<string, UpdateCampaignBudgetDTO>()
const promotionCodeUsageMap = new Map<string, boolean>()
const existingPromotions = await this.listPromotions(
{ code: promotionCodes },
{ relations: ["application_method", "campaign", "campaign.budget"] },
{
relations: ["campaign", "campaign.budget"],
take: null,
},
sharedContext
)
for (const promotion of existingPromotions) {
if (promotion.campaign?.budget) {
campaignBudgetMap.set(
promotion.campaign?.budget.id,
promotion.campaign?.budget
)
}
}
const existingPromotionsMap = new Map<string, PromotionTypes.PromotionDTO>(
existingPromotions.map((promotion) => [promotion.code!, promotion])
)
for (let computedAction of computedActions) {
if (!ComputeActionUtils.canRegisterUsage(computedAction)) {
continue
}
const promotion = existingPromotionsMap.get(computedAction.code)
if (!promotion) {
@@ -163,9 +169,11 @@ export default class PromotionModuleService
}
if (campaignBudget.type === CampaignBudgetType.SPEND) {
const campaignBudgetData = promotionCodeCampaignBudgetMap.get(
campaignBudget.id
) || { id: campaignBudget.id, used: campaignBudget.used ?? 0 }
const campaignBudgetData = campaignBudgetMap.get(campaignBudget.id)
if (!campaignBudgetData) {
continue
}
campaignBudgetData.used = MathBN.add(
campaignBudgetData.used ?? 0,
@@ -179,10 +187,7 @@ export default class PromotionModuleService
continue
}
promotionCodeCampaignBudgetMap.set(
campaignBudget.id,
campaignBudgetData
)
campaignBudgetMap.set(campaignBudget.id, campaignBudgetData)
}
if (campaignBudget.type === CampaignBudgetType.USAGE) {
@@ -205,17 +210,105 @@ export default class PromotionModuleService
continue
}
promotionCodeCampaignBudgetMap.set(
campaignBudget.id,
campaignBudgetData
)
campaignBudgetMap.set(campaignBudget.id, campaignBudgetData)
promotionCodeUsageMap.set(promotion.code!, true)
}
const campaignBudgetsData: UpdateCampaignBudgetDTO[] = []
for (const [_, campaignBudgetData] of promotionCodeCampaignBudgetMap) {
for (const [_, campaignBudgetData] of campaignBudgetMap) {
campaignBudgetsData.push(campaignBudgetData)
}
await this.campaignBudgetService_.update(
campaignBudgetsData,
sharedContext
)
}
}
@InjectManager("baseRepository_")
async revertUsage(
computedActions: PromotionTypes.UsageComputedActions[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
const promotionCodeUsageMap = new Map<string, boolean>()
const campaignBudgetMap = new Map<string, UpdateCampaignBudgetDTO>()
const existingPromotions = await this.listPromotions(
{
code: computedActions
.map((computedAction) => computedAction.code)
.filter(Boolean),
},
{
relations: ["campaign", "campaign.budget"],
take: null,
},
sharedContext
)
for (const promotion of existingPromotions) {
if (promotion.campaign?.budget) {
campaignBudgetMap.set(
promotion.campaign?.budget.id,
promotion.campaign?.budget
)
}
}
const existingPromotionsMap = new Map<string, PromotionTypes.PromotionDTO>(
existingPromotions.map((promotion) => [promotion.code!, promotion])
)
for (let computedAction of computedActions) {
const promotion = existingPromotionsMap.get(computedAction.code)
if (!promotion) {
continue
}
const campaignBudget = promotion.campaign?.budget
if (!campaignBudget) {
continue
}
if (campaignBudget.type === CampaignBudgetType.SPEND) {
const campaignBudgetData = campaignBudgetMap.get(campaignBudget.id)
if (!campaignBudgetData) {
continue
}
campaignBudgetData.used = MathBN.sub(
campaignBudgetData.used ?? 0,
computedAction.amount
)
campaignBudgetMap.set(campaignBudget.id, campaignBudgetData)
}
if (campaignBudget.type === CampaignBudgetType.USAGE) {
const promotionAlreadyUsed =
promotionCodeUsageMap.get(promotion.code!) || false
if (promotionAlreadyUsed) {
continue
}
campaignBudgetMap.set(campaignBudget.id, {
id: campaignBudget.id,
used: MathBN.sub(campaignBudget.used ?? 0, 1),
})
promotionCodeUsageMap.set(promotion.code!, true)
}
const campaignBudgetsData: UpdateCampaignBudgetDTO[] = []
for (const [_, campaignBudgetData] of campaignBudgetMap) {
campaignBudgetsData.push(campaignBudgetData)
}
@@ -1,20 +1,10 @@
import {
BigNumberInput,
CampaignBudgetExceededAction,
ComputeActions,
PromotionDTO,
} from "@medusajs/types"
import { CampaignBudgetType, ComputedActions, MathBN } from "@medusajs/utils"
export function canRegisterUsage(computedAction: ComputeActions): boolean {
return (
[
ComputedActions.ADD_ITEM_ADJUSTMENT,
ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT,
] as string[]
).includes(computedAction.action)
}
export function computeActionForBudgetExceeded(
promotion: PromotionDTO,
amount: BigNumberInput