feat(utils): add campaign + promotion create / update (#6077)
* chore: add campaign + promotion operations * chore: update type * chore: added validation for campaign_id and campaign * chore: added update promotions for campaigns * chore: update campaign on promotion * chore: fix lint * chore: add test to remove promotions
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Context } from "@medusajs/types"
|
||||
import { DALUtils } from "@medusajs/utils"
|
||||
import { Campaign } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Campaign, Promotion } from "@models"
|
||||
import { CreateCampaignDTO, UpdateCampaignDTO } from "@types"
|
||||
|
||||
export class CampaignRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
||||
@@ -8,4 +10,138 @@ export class CampaignRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
||||
create: CreateCampaignDTO
|
||||
update: UpdateCampaignDTO
|
||||
}
|
||||
>(Campaign) {}
|
||||
>(Campaign) {
|
||||
async create(
|
||||
data: CreateCampaignDTO[],
|
||||
context: Context = {}
|
||||
): Promise<Campaign[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const promotionIdsToUpsert: string[] = []
|
||||
const campaignIdentifierPromotionsMap = new Map<string, string[]>()
|
||||
|
||||
data.forEach((campaignData) => {
|
||||
const campaignPromotionIds =
|
||||
campaignData.promotions?.map((p) => p.id) || []
|
||||
|
||||
promotionIdsToUpsert.push(...campaignPromotionIds)
|
||||
|
||||
campaignIdentifierPromotionsMap.set(
|
||||
campaignData.campaign_identifier,
|
||||
campaignPromotionIds
|
||||
)
|
||||
|
||||
delete campaignData.promotions
|
||||
})
|
||||
|
||||
const existingPromotions = await manager.find(Promotion, {
|
||||
id: promotionIdsToUpsert,
|
||||
})
|
||||
|
||||
const existingPromotionsMap = new Map<string, Promotion>(
|
||||
existingPromotions.map((promotion) => [promotion.id, promotion])
|
||||
)
|
||||
|
||||
const createdCampaigns = await super.create(data, context)
|
||||
|
||||
for (const createdCampaign of createdCampaigns) {
|
||||
const campaignPromotionIds =
|
||||
campaignIdentifierPromotionsMap.get(
|
||||
createdCampaign.campaign_identifier
|
||||
) || []
|
||||
|
||||
for (const campaignPromotionId of campaignPromotionIds) {
|
||||
const promotion = existingPromotionsMap.get(campaignPromotionId)
|
||||
|
||||
if (!promotion) {
|
||||
continue
|
||||
}
|
||||
|
||||
createdCampaign.promotions.add(promotion)
|
||||
}
|
||||
}
|
||||
|
||||
return createdCampaigns
|
||||
}
|
||||
|
||||
async update(
|
||||
data: UpdateCampaignDTO[],
|
||||
context: Context = {}
|
||||
): Promise<Campaign[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const promotionIdsToUpsert: string[] = []
|
||||
const campaignIds: string[] = []
|
||||
const campaignPromotionIdsMap = new Map<string, string[]>()
|
||||
|
||||
data.forEach((campaignData) => {
|
||||
const campaignPromotionIds =
|
||||
campaignData.promotions?.map((p) => p.id) || []
|
||||
|
||||
campaignIds.push(campaignData.id)
|
||||
promotionIdsToUpsert.push(...campaignPromotionIds)
|
||||
campaignPromotionIdsMap.set(campaignData.id, campaignPromotionIds)
|
||||
|
||||
delete campaignData.promotions
|
||||
})
|
||||
|
||||
const existingCampaigns = await manager.find(
|
||||
Campaign,
|
||||
{ id: campaignIds },
|
||||
{ populate: ["promotions"] }
|
||||
)
|
||||
|
||||
const promotionIds = existingCampaigns
|
||||
.map((campaign) => campaign.promotions?.map((p) => p.id))
|
||||
.flat(1)
|
||||
.concat(promotionIdsToUpsert)
|
||||
|
||||
const existingPromotions = await manager.find(Promotion, {
|
||||
id: promotionIds,
|
||||
})
|
||||
|
||||
const existingCampaignsMap = new Map<string, Campaign>(
|
||||
existingCampaigns.map((campaign) => [campaign.id, campaign])
|
||||
)
|
||||
|
||||
const existingPromotionsMap = new Map<string, Promotion>(
|
||||
existingPromotions.map((promotion) => [promotion.id, promotion])
|
||||
)
|
||||
|
||||
const updatedCampaigns = await super.update(data, context)
|
||||
|
||||
for (const updatedCampaign of updatedCampaigns) {
|
||||
const upsertPromotionIds =
|
||||
campaignPromotionIdsMap.get(updatedCampaign.id) || []
|
||||
const existingPromotionIds = (
|
||||
existingCampaignsMap.get(updatedCampaign.id)?.promotions || []
|
||||
).map((p) => p.id)
|
||||
|
||||
for (const existingPromotionId of existingPromotionIds) {
|
||||
const promotion = existingPromotionsMap.get(existingPromotionId)
|
||||
|
||||
if (!promotion) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!upsertPromotionIds.includes(existingPromotionId)) {
|
||||
updatedCampaign.promotions.remove(promotion)
|
||||
}
|
||||
}
|
||||
|
||||
for (const promotionIdToAdd of upsertPromotionIds) {
|
||||
const promotion = existingPromotionsMap.get(promotionIdToAdd)
|
||||
|
||||
if (!promotion) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (existingPromotionIds.includes(promotionIdToAdd)) {
|
||||
continue
|
||||
} else {
|
||||
updatedCampaign.promotions.add(promotion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updatedCampaigns
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +314,7 @@ export default class PromotionModuleService<
|
||||
"application_method.target_rules.values",
|
||||
"rules",
|
||||
"rules.values",
|
||||
"campaign",
|
||||
],
|
||||
},
|
||||
sharedContext
|
||||
@@ -329,12 +330,12 @@ export default class PromotionModuleService<
|
||||
) {
|
||||
const promotionsData: CreatePromotionDTO[] = []
|
||||
const applicationMethodsData: CreateApplicationMethodDTO[] = []
|
||||
const campaignsData: CreateCampaignDTO[] = []
|
||||
|
||||
const promotionCodeApplicationMethodDataMap = new Map<
|
||||
string,
|
||||
PromotionTypes.CreateApplicationMethodDTO
|
||||
>()
|
||||
|
||||
const promotionCodeRulesDataMap = new Map<
|
||||
string,
|
||||
PromotionTypes.CreatePromotionRuleDTO[]
|
||||
@@ -343,10 +344,16 @@ export default class PromotionModuleService<
|
||||
string,
|
||||
PromotionTypes.CreatePromotionRuleDTO[]
|
||||
>()
|
||||
const promotionCodeCampaignMap = new Map<
|
||||
string,
|
||||
PromotionTypes.CreateCampaignDTO
|
||||
>()
|
||||
|
||||
for (const {
|
||||
application_method: applicationMethodData,
|
||||
rules: rulesData,
|
||||
campaign: campaignData,
|
||||
campaign_id: campaignId,
|
||||
...promotionData
|
||||
} of data) {
|
||||
if (applicationMethodData) {
|
||||
@@ -360,7 +367,21 @@ export default class PromotionModuleService<
|
||||
promotionCodeRulesDataMap.set(promotionData.code, rulesData)
|
||||
}
|
||||
|
||||
promotionsData.push(promotionData)
|
||||
if (campaignData && campaignId) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Provide either the 'campaign' or 'campaign_id' parameter; both cannot be used simultaneously.`
|
||||
)
|
||||
}
|
||||
|
||||
if (campaignData) {
|
||||
promotionCodeCampaignMap.set(promotionData.code, campaignData)
|
||||
}
|
||||
|
||||
promotionsData.push({
|
||||
...promotionData,
|
||||
campaign: campaignId,
|
||||
})
|
||||
}
|
||||
|
||||
const createdPromotions = await this.promotionService_.create(
|
||||
@@ -373,6 +394,15 @@ export default class PromotionModuleService<
|
||||
promotion.code
|
||||
)
|
||||
|
||||
const campaignData = promotionCodeCampaignMap.get(promotion.code)
|
||||
|
||||
if (campaignData) {
|
||||
campaignsData.push({
|
||||
...campaignData,
|
||||
promotions: [promotion],
|
||||
})
|
||||
}
|
||||
|
||||
if (applMethodData) {
|
||||
const {
|
||||
target_rules: targetRulesData = [],
|
||||
@@ -416,6 +446,10 @@ export default class PromotionModuleService<
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (campaignsData.length) {
|
||||
await this.createCampaigns(campaignsData, sharedContext)
|
||||
}
|
||||
|
||||
for (const applicationMethod of createdApplicationMethods) {
|
||||
await this.createPromotionRulesAndValues(
|
||||
applicationMethodRuleMap.get(applicationMethod.promotion.id) || [],
|
||||
@@ -456,6 +490,7 @@ export default class PromotionModuleService<
|
||||
"application_method.target_rules",
|
||||
"rules",
|
||||
"rules.values",
|
||||
"campaign",
|
||||
],
|
||||
},
|
||||
sharedContext
|
||||
@@ -471,13 +506,10 @@ export default class PromotionModuleService<
|
||||
) {
|
||||
const promotionIds = data.map((d) => d.id)
|
||||
const existingPromotions = await this.promotionService_.list(
|
||||
{
|
||||
id: promotionIds,
|
||||
},
|
||||
{
|
||||
relations: ["application_method"],
|
||||
}
|
||||
{ id: promotionIds },
|
||||
{ relations: ["application_method"] }
|
||||
)
|
||||
|
||||
const existingPromotionsMap = new Map<string, Promotion>(
|
||||
existingPromotions.map((promotion) => [promotion.id, promotion])
|
||||
)
|
||||
@@ -487,9 +519,14 @@ export default class PromotionModuleService<
|
||||
|
||||
for (const {
|
||||
application_method: applicationMethodData,
|
||||
campaign_id: campaignId,
|
||||
...promotionData
|
||||
} of data) {
|
||||
promotionsData.push(promotionData)
|
||||
if (campaignId) {
|
||||
promotionsData.push({ ...promotionData, campaign: campaignId })
|
||||
} else {
|
||||
promotionsData.push(promotionData)
|
||||
}
|
||||
|
||||
if (!applicationMethodData) {
|
||||
continue
|
||||
@@ -820,7 +857,19 @@ export default class PromotionModuleService<
|
||||
>()
|
||||
|
||||
for (const createCampaignData of data) {
|
||||
const { budget: campaignBudgetData, ...campaignData } = createCampaignData
|
||||
const {
|
||||
budget: campaignBudgetData,
|
||||
promotions,
|
||||
...campaignData
|
||||
} = createCampaignData
|
||||
|
||||
const promotionsToAdd = promotions
|
||||
? await this.list(
|
||||
{ id: promotions.map((p) => p.id) },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
: []
|
||||
|
||||
if (campaignBudgetData) {
|
||||
campaignIdentifierBudgetMap.set(
|
||||
@@ -829,7 +878,10 @@ export default class PromotionModuleService<
|
||||
)
|
||||
}
|
||||
|
||||
campaignsData.push(campaignData)
|
||||
campaignsData.push({
|
||||
...campaignData,
|
||||
promotions: promotionsToAdd,
|
||||
})
|
||||
}
|
||||
|
||||
const createdCampaigns = await this.campaignService_.create(
|
||||
@@ -882,7 +934,7 @@ export default class PromotionModuleService<
|
||||
const campaigns = await this.listCampaigns(
|
||||
{ id: updatedCampaigns.map((p) => p!.id) },
|
||||
{
|
||||
relations: ["budget"],
|
||||
relations: ["budget", "promotions"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { Promotion } from "@models"
|
||||
|
||||
export interface CreateCampaignDTO {
|
||||
name: string
|
||||
description?: string
|
||||
@@ -5,6 +8,7 @@ export interface CreateCampaignDTO {
|
||||
campaign_identifier: string
|
||||
starts_at: Date
|
||||
ends_at: Date
|
||||
promotions?: (PromotionDTO | Promotion)[]
|
||||
}
|
||||
|
||||
export interface UpdateCampaignDTO {
|
||||
@@ -15,4 +19,5 @@ export interface UpdateCampaignDTO {
|
||||
campaign_identifier?: string
|
||||
starts_at?: Date
|
||||
ends_at?: Date
|
||||
promotions?: (PromotionDTO | Promotion)[]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface CreatePromotionDTO {
|
||||
code: string
|
||||
type: PromotionType
|
||||
is_automatic?: boolean
|
||||
campaign?: string
|
||||
}
|
||||
|
||||
export interface UpdatePromotionDTO {
|
||||
@@ -12,4 +13,5 @@ export interface UpdatePromotionDTO {
|
||||
// TODO: add this when buyget is available
|
||||
// type: PromotionType
|
||||
is_automatic?: boolean
|
||||
campaign?: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user