feat(core-flows,types,medusa): API to add promotions to campaign (#7277)
what: - adds an API to add promotions to campaign - reworks module to perform atomic actions
This commit is contained in:
1 parent
489a54e1fb
commit
9a14aeebcf
31 files changed
+1024
-930
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core-flows,types,medusa): API to add promotions to campaign
|
||||||
@@ -0,0 +1,600 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { CampaignBudgetType, PromotionType } from "@medusajs/utils"
|
||||||
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
|
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||||
|
|
||||||
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
|
export const campaignData = {
|
||||||
|
name: "campaign 1",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
starts_at: new Date("01/01/2023").toISOString(),
|
||||||
|
ends_at: new Date("01/01/2024").toISOString(),
|
||||||
|
budget: {
|
||||||
|
type: CampaignBudgetType.SPEND,
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const campaignsData = [
|
||||||
|
{
|
||||||
|
id: "campaign-id-1",
|
||||||
|
name: "campaign 1",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
starts_at: new Date("01/01/2023"),
|
||||||
|
ends_at: new Date("01/01/2024"),
|
||||||
|
budget: {
|
||||||
|
type: CampaignBudgetType.SPEND,
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "campaign-id-2",
|
||||||
|
name: "campaign 2",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-2",
|
||||||
|
starts_at: new Date("01/01/2023"),
|
||||||
|
ends_at: new Date("01/01/2024"),
|
||||||
|
budget: {
|
||||||
|
type: CampaignBudgetType.USAGE,
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
|
}
|
||||||
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
env,
|
||||||
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
|
describe("Admin Campaigns API", () => {
|
||||||
|
let appContainer
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
appContainer = getContainer()
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
})
|
||||||
|
|
||||||
|
const generatePromotionData = () => {
|
||||||
|
const code = Math.random().toString(36).substring(7)
|
||||||
|
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
application_method: {
|
||||||
|
target_type: "items",
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
value: 100,
|
||||||
|
max_quantity: 100,
|
||||||
|
target_rules: [],
|
||||||
|
},
|
||||||
|
rules: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("GET /admin/campaigns", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await promotionModuleService.createCampaigns(campaignsData)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get all campaigns and its count", async () => {
|
||||||
|
const response = await api.get(`/admin/campaigns`, adminHeaders)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(2)
|
||||||
|
expect(response.data.campaigns).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "campaign 1",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
starts_at: expect.any(String),
|
||||||
|
ends_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
type: "spend",
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
raw_limit: {
|
||||||
|
precision: 20,
|
||||||
|
value: "1000",
|
||||||
|
},
|
||||||
|
raw_used: {
|
||||||
|
precision: 20,
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "campaign 2",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-2",
|
||||||
|
starts_at: expect.any(String),
|
||||||
|
ends_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
type: "usage",
|
||||||
|
limit: 1000,
|
||||||
|
used: 0,
|
||||||
|
raw_limit: {
|
||||||
|
precision: 20,
|
||||||
|
value: "1000",
|
||||||
|
},
|
||||||
|
raw_used: {
|
||||||
|
precision: 20,
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should support search on campaigns", async () => {
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/campaigns?q=ign%202`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.campaigns).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "campaign 2",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get all campaigns and its count filtered", async () => {
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/campaigns?fields=name,created_at,budget.id`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(2)
|
||||||
|
expect(response.data.campaigns).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "campaign 1",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "campaign 2",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/campaigns/:id", () => {
|
||||||
|
it("should throw an error if id does not exist", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.get(`/admin/campaigns/does-not-exist`, adminHeaders)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(404)
|
||||||
|
expect(response.data.message).toEqual(
|
||||||
|
"Campaign with id: does-not-exist was not found"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get the requested campaign", async () => {
|
||||||
|
const createdCampaign = await promotionModuleService.createCampaigns(
|
||||||
|
campaignData
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/campaigns/${createdCampaign.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.campaign).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "campaign 1",
|
||||||
|
description: "test description",
|
||||||
|
currency: "USD",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
starts_at: expect.any(String),
|
||||||
|
ends_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
type: "spend",
|
||||||
|
limit: 1000,
|
||||||
|
raw_limit: {
|
||||||
|
precision: 20,
|
||||||
|
value: "1000",
|
||||||
|
},
|
||||||
|
raw_used: {
|
||||||
|
precision: 20,
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
used: 0,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get the requested campaign with filtered fields and relations", async () => {
|
||||||
|
const createdCampaign = await promotionModuleService.createCampaigns(
|
||||||
|
campaignData
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/campaigns/${createdCampaign.id}?fields=name`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.campaign).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "campaign 1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/campaigns", () => {
|
||||||
|
it("should throw an error if required params are not passed", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.post(`/admin/campaigns`, {}, adminHeaders)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(400)
|
||||||
|
// expect(response.data.message).toEqual(
|
||||||
|
// "name must be a string, name should not be empty"
|
||||||
|
// )
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a campaign successfully", async () => {
|
||||||
|
const createdPromotion = await promotionModuleService.create({
|
||||||
|
code: "TEST",
|
||||||
|
type: "standard",
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/campaigns?fields=*promotions`,
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: new Date("01/01/2024").toISOString(),
|
||||||
|
ends_at: new Date("01/01/2029").toISOString(),
|
||||||
|
budget: {
|
||||||
|
limit: 1000,
|
||||||
|
type: "usage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: expect.any(String),
|
||||||
|
ends_at: expect.any(String),
|
||||||
|
budget: expect.objectContaining({
|
||||||
|
limit: 1000,
|
||||||
|
type: "usage",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create 3 campaigns in parallel and have the context passed as argument when calling createCampaigns with different transactionId", async () => {
|
||||||
|
const parallelPromotion = await promotionModuleService.create({
|
||||||
|
code: "PARALLEL",
|
||||||
|
type: "standard",
|
||||||
|
})
|
||||||
|
|
||||||
|
const spyCreateCampaigns = jest.spyOn(
|
||||||
|
promotionModuleService.constructor.prototype,
|
||||||
|
"createCampaigns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const a = async () => {
|
||||||
|
return await api.post(
|
||||||
|
`/admin/campaigns`,
|
||||||
|
{
|
||||||
|
name: "camp_1",
|
||||||
|
campaign_identifier: "camp_1",
|
||||||
|
starts_at: new Date("01/01/2024").toISOString(),
|
||||||
|
ends_at: new Date("01/02/2024").toISOString(),
|
||||||
|
budget: {
|
||||||
|
limit: 1000,
|
||||||
|
type: "usage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = async () => {
|
||||||
|
return await api.post(
|
||||||
|
`/admin/campaigns`,
|
||||||
|
{
|
||||||
|
name: "camp_2",
|
||||||
|
campaign_identifier: "camp_2",
|
||||||
|
starts_at: new Date("01/02/2024").toISOString(),
|
||||||
|
ends_at: new Date("01/03/2029").toISOString(),
|
||||||
|
budget: {
|
||||||
|
limit: 500,
|
||||||
|
type: "usage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = async () => {
|
||||||
|
return await api.post(
|
||||||
|
`/admin/campaigns`,
|
||||||
|
{
|
||||||
|
name: "camp_3",
|
||||||
|
campaign_identifier: "camp_3",
|
||||||
|
starts_at: new Date("01/03/2024").toISOString(),
|
||||||
|
ends_at: new Date("01/04/2029").toISOString(),
|
||||||
|
budget: {
|
||||||
|
limit: 250,
|
||||||
|
type: "usage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
...adminHeaders.headers,
|
||||||
|
"x-request-id": "my-custom-request-id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([a(), b(), c()])
|
||||||
|
|
||||||
|
expect(spyCreateCampaigns).toHaveBeenCalledTimes(3)
|
||||||
|
expect(spyCreateCampaigns.mock.calls[0][1].__type).toBe(
|
||||||
|
"MedusaContext"
|
||||||
|
)
|
||||||
|
|
||||||
|
const distinctTransactionId = [
|
||||||
|
...new Set(
|
||||||
|
spyCreateCampaigns.mock.calls.map((call) => call[1].transactionId)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
expect(distinctTransactionId).toHaveLength(3)
|
||||||
|
|
||||||
|
const distinctRequestId = [
|
||||||
|
...new Set(
|
||||||
|
spyCreateCampaigns.mock.calls.map((call) => call[1].requestId)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(distinctRequestId).toHaveLength(3)
|
||||||
|
expect(distinctRequestId).toContain("my-custom-request-id")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/campaigns/:id", () => {
|
||||||
|
it("should throw an error if id does not exist", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.post(`/admin/campaigns/does-not-exist`, {}, adminHeaders)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(404)
|
||||||
|
expect(response.data.message).toEqual(
|
||||||
|
`Campaign with id "does-not-exist" not found`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a campaign successfully", async () => {
|
||||||
|
const createdPromotion = await promotionModuleService.create({
|
||||||
|
code: "TEST",
|
||||||
|
type: "standard",
|
||||||
|
})
|
||||||
|
|
||||||
|
const createdCampaign = await promotionModuleService.createCampaigns({
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: new Date("01/01/2024").toISOString(),
|
||||||
|
ends_at: new Date("01/01/2029").toISOString(),
|
||||||
|
budget: {
|
||||||
|
limit: 1000,
|
||||||
|
type: "usage",
|
||||||
|
used: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await promotionModuleService.addPromotionsToCampaign({
|
||||||
|
id: createdCampaign.id,
|
||||||
|
promotion_ids: [createdPromotion.id],
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/campaigns/${createdCampaign.id}?fields=*promotions`,
|
||||||
|
{
|
||||||
|
name: "test-2",
|
||||||
|
campaign_identifier: "test-2",
|
||||||
|
budget: {
|
||||||
|
limit: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "test-2",
|
||||||
|
campaign_identifier: "test-2",
|
||||||
|
budget: expect.objectContaining({
|
||||||
|
limit: 2000,
|
||||||
|
type: "usage",
|
||||||
|
used: 10,
|
||||||
|
}),
|
||||||
|
promotions: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: createdPromotion.id,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("DELETE /admin/campaigns/:id", () => {
|
||||||
|
it("should delete campaign successfully", async () => {
|
||||||
|
const [createdCampaign] =
|
||||||
|
await promotionModuleService.createCampaigns([
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test",
|
||||||
|
starts_at: new Date("01/01/2024"),
|
||||||
|
ends_at: new Date("01/01/2025"),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const deleteRes = await api.delete(
|
||||||
|
`/admin/campaigns/${createdCampaign.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(deleteRes.status).toEqual(200)
|
||||||
|
|
||||||
|
const campaigns = await promotionModuleService.listCampaigns({
|
||||||
|
id: [createdCampaign.id],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(campaigns.length).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/campaigns/:id/promotions", () => {
|
||||||
|
it("should add or remove promotions from campaign", async () => {
|
||||||
|
const campaign = (
|
||||||
|
await api.post(`/admin/campaigns`, campaignData, adminHeaders)
|
||||||
|
).data.campaign
|
||||||
|
|
||||||
|
const promotion1 = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/promotions`,
|
||||||
|
generatePromotionData(),
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.promotion
|
||||||
|
|
||||||
|
const promotion2 = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/promotions`,
|
||||||
|
generatePromotionData(),
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.promotion
|
||||||
|
|
||||||
|
let response = await api.post(
|
||||||
|
`/admin/campaigns/${campaign.id}/promotions`,
|
||||||
|
{ add: [promotion1.id, promotion2.id] },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await api.get(
|
||||||
|
`/admin/promotions?campaign_id=${campaign.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.promotions).toHaveLength(2)
|
||||||
|
expect(response.data.promotions).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: promotion1.id,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: promotion2.id,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/campaigns/${campaign.id}/promotions`,
|
||||||
|
{ remove: [promotion1.id] },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await api.get(
|
||||||
|
`/admin/promotions?campaign_id=${campaign.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.promotions).toHaveLength(1)
|
||||||
|
expect(response.data.promotions).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: promotion2.id,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import { IPromotionModuleService } from "@medusajs/types"
|
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
|
||||||
const adminHeaders = {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
}
|
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
|
||||||
env,
|
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
||||||
describe("POST /admin/campaigns", () => {
|
|
||||||
let appContainer
|
|
||||||
let promotionModuleService: IPromotionModuleService
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
appContainer = getContainer()
|
|
||||||
promotionModuleService = appContainer.resolve(
|
|
||||||
ModuleRegistrationName.PROMOTION
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw an error if required params are not passed", async () => {
|
|
||||||
const { response } = await api
|
|
||||||
.post(`/admin/campaigns`, {}, adminHeaders)
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(400)
|
|
||||||
// expect(response.data.message).toEqual(
|
|
||||||
// "name must be a string, name should not be empty"
|
|
||||||
// )
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should create a campaign successfully", async () => {
|
|
||||||
const createdPromotion = await promotionModuleService.create({
|
|
||||||
code: "TEST",
|
|
||||||
type: "standard",
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = await api.post(
|
|
||||||
`/admin/campaigns?fields=*promotions`,
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test",
|
|
||||||
starts_at: new Date("01/01/2024").toISOString(),
|
|
||||||
ends_at: new Date("01/01/2029").toISOString(),
|
|
||||||
promotions: [{ id: createdPromotion.id }],
|
|
||||||
budget: {
|
|
||||||
limit: 1000,
|
|
||||||
type: "usage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.campaign).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test",
|
|
||||||
starts_at: expect.any(String),
|
|
||||||
ends_at: expect.any(String),
|
|
||||||
budget: expect.objectContaining({
|
|
||||||
limit: 1000,
|
|
||||||
type: "usage",
|
|
||||||
}),
|
|
||||||
promotions: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: createdPromotion.id,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should create 3 campaigns in parallel and have the context passed as argument when calling createCampaigns with different transactionId", async () => {
|
|
||||||
const parallelPromotion = await promotionModuleService.create({
|
|
||||||
code: "PARALLEL",
|
|
||||||
type: "standard",
|
|
||||||
})
|
|
||||||
|
|
||||||
const spyCreateCampaigns = jest.spyOn(
|
|
||||||
promotionModuleService.constructor.prototype,
|
|
||||||
"createCampaigns"
|
|
||||||
)
|
|
||||||
|
|
||||||
const a = async () => {
|
|
||||||
return await api.post(
|
|
||||||
`/admin/campaigns`,
|
|
||||||
{
|
|
||||||
name: "camp_1",
|
|
||||||
campaign_identifier: "camp_1",
|
|
||||||
starts_at: new Date("01/01/2024").toISOString(),
|
|
||||||
ends_at: new Date("01/02/2024").toISOString(),
|
|
||||||
promotions: [{ id: parallelPromotion.id }],
|
|
||||||
budget: {
|
|
||||||
limit: 1000,
|
|
||||||
type: "usage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const b = async () => {
|
|
||||||
return await api.post(
|
|
||||||
`/admin/campaigns`,
|
|
||||||
{
|
|
||||||
name: "camp_2",
|
|
||||||
campaign_identifier: "camp_2",
|
|
||||||
starts_at: new Date("01/02/2024").toISOString(),
|
|
||||||
ends_at: new Date("01/03/2029").toISOString(),
|
|
||||||
promotions: [{ id: parallelPromotion.id }],
|
|
||||||
budget: {
|
|
||||||
limit: 500,
|
|
||||||
type: "usage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const c = async () => {
|
|
||||||
return await api.post(
|
|
||||||
`/admin/campaigns`,
|
|
||||||
{
|
|
||||||
name: "camp_3",
|
|
||||||
campaign_identifier: "camp_3",
|
|
||||||
starts_at: new Date("01/03/2024").toISOString(),
|
|
||||||
ends_at: new Date("01/04/2029").toISOString(),
|
|
||||||
promotions: [{ id: parallelPromotion.id }],
|
|
||||||
budget: {
|
|
||||||
limit: 250,
|
|
||||||
type: "usage",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
...adminHeaders.headers,
|
|
||||||
"x-request-id": "my-custom-request-id",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([a(), b(), c()])
|
|
||||||
|
|
||||||
expect(spyCreateCampaigns).toHaveBeenCalledTimes(3)
|
|
||||||
expect(spyCreateCampaigns.mock.calls[0][1].__type).toBe("MedusaContext")
|
|
||||||
|
|
||||||
const distinctTransactionId = [
|
|
||||||
...new Set(
|
|
||||||
spyCreateCampaigns.mock.calls.map((call) => call[1].transactionId)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
expect(distinctTransactionId).toHaveLength(3)
|
|
||||||
|
|
||||||
const distinctRequestId = [
|
|
||||||
...new Set(
|
|
||||||
spyCreateCampaigns.mock.calls.map((call) => call[1].requestId)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
expect(distinctRequestId).toHaveLength(3)
|
|
||||||
expect(distinctRequestId).toContain("my-custom-request-id")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import { IPromotionModuleService } from "@medusajs/types"
|
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
|
||||||
const adminHeaders = {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
}
|
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
|
||||||
env,
|
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
||||||
describe("DELETE /admin/campaigns/:id", () => {
|
|
||||||
let appContainer
|
|
||||||
let promotionModuleService: IPromotionModuleService
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
appContainer = getContainer()
|
|
||||||
promotionModuleService = appContainer.resolve(
|
|
||||||
ModuleRegistrationName.PROMOTION
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should delete campaign successfully", async () => {
|
|
||||||
const [createdCampaign] = await promotionModuleService.createCampaigns([
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test",
|
|
||||||
starts_at: new Date("01/01/2024"),
|
|
||||||
ends_at: new Date("01/01/2025"),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const deleteRes = await api.delete(
|
|
||||||
`/admin/campaigns/${createdCampaign.id}`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(deleteRes.status).toEqual(200)
|
|
||||||
|
|
||||||
const campaigns = await promotionModuleService.listCampaigns({
|
|
||||||
id: [createdCampaign.id],
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(campaigns.length).toEqual(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
|
||||||
import { CampaignBudgetType } from "@medusajs/utils"
|
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
|
||||||
|
|
||||||
export const campaignsData = [
|
|
||||||
{
|
|
||||||
id: "campaign-id-1",
|
|
||||||
name: "campaign 1",
|
|
||||||
description: "test description",
|
|
||||||
currency: "USD",
|
|
||||||
campaign_identifier: "test-1",
|
|
||||||
starts_at: new Date("01/01/2023"),
|
|
||||||
ends_at: new Date("01/01/2024"),
|
|
||||||
budget: {
|
|
||||||
type: CampaignBudgetType.SPEND,
|
|
||||||
limit: 1000,
|
|
||||||
used: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "campaign-id-2",
|
|
||||||
name: "campaign 2",
|
|
||||||
description: "test description",
|
|
||||||
currency: "USD",
|
|
||||||
campaign_identifier: "test-2",
|
|
||||||
starts_at: new Date("01/01/2023"),
|
|
||||||
ends_at: new Date("01/01/2024"),
|
|
||||||
budget: {
|
|
||||||
type: CampaignBudgetType.USAGE,
|
|
||||||
limit: 1000,
|
|
||||||
used: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
|
||||||
const adminHeaders = {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
}
|
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
|
||||||
env,
|
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
||||||
describe("GET /admin/campaigns", () => {
|
|
||||||
let appContainer
|
|
||||||
let promotionModuleService: IPromotionModuleService
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
appContainer = getContainer()
|
|
||||||
promotionModuleService = appContainer.resolve(
|
|
||||||
ModuleRegistrationName.PROMOTION
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
|
||||||
await promotionModuleService.createCampaigns(campaignsData)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get all campaigns and its count", async () => {
|
|
||||||
const response = await api.get(`/admin/campaigns`, adminHeaders)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.count).toEqual(2)
|
|
||||||
expect(response.data.campaigns).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 1",
|
|
||||||
description: "test description",
|
|
||||||
currency: "USD",
|
|
||||||
campaign_identifier: "test-1",
|
|
||||||
starts_at: expect.any(String),
|
|
||||||
ends_at: expect.any(String),
|
|
||||||
budget: {
|
|
||||||
id: expect.any(String),
|
|
||||||
type: "spend",
|
|
||||||
limit: 1000,
|
|
||||||
used: 0,
|
|
||||||
raw_limit: {
|
|
||||||
precision: 20,
|
|
||||||
value: "1000",
|
|
||||||
},
|
|
||||||
raw_used: {
|
|
||||||
precision: 20,
|
|
||||||
value: "0",
|
|
||||||
},
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
deleted_at: null,
|
|
||||||
},
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
deleted_at: null,
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 2",
|
|
||||||
description: "test description",
|
|
||||||
currency: "USD",
|
|
||||||
campaign_identifier: "test-2",
|
|
||||||
starts_at: expect.any(String),
|
|
||||||
ends_at: expect.any(String),
|
|
||||||
budget: {
|
|
||||||
id: expect.any(String),
|
|
||||||
type: "usage",
|
|
||||||
limit: 1000,
|
|
||||||
used: 0,
|
|
||||||
raw_limit: {
|
|
||||||
precision: 20,
|
|
||||||
value: "1000",
|
|
||||||
},
|
|
||||||
raw_used: {
|
|
||||||
precision: 20,
|
|
||||||
value: "0",
|
|
||||||
},
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
deleted_at: null,
|
|
||||||
},
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
deleted_at: null,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should support search on campaigns", async () => {
|
|
||||||
const response = await api.get(
|
|
||||||
`/admin/campaigns?q=ign%202`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.campaigns).toEqual([
|
|
||||||
expect.objectContaining({
|
|
||||||
name: "campaign 2",
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get all campaigns and its count filtered", async () => {
|
|
||||||
const response = await api.get(
|
|
||||||
`/admin/campaigns?fields=name,created_at,budget.id`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.count).toEqual(2)
|
|
||||||
expect(response.data.campaigns).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 1",
|
|
||||||
created_at: expect.any(String),
|
|
||||||
budget: {
|
|
||||||
id: expect.any(String),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 2",
|
|
||||||
created_at: expect.any(String),
|
|
||||||
budget: {
|
|
||||||
id: expect.any(String),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -113,7 +113,7 @@ medusaIntegrationTestRunner({
|
|||||||
])
|
])
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/admin/promotions?fields=code,created_at,application_method.id&expand=application_method`,
|
`/admin/promotions?fields=code,created_at,application_method.id`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
|
||||||
import { CampaignBudgetType } from "@medusajs/utils"
|
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
|
||||||
|
|
||||||
export const campaignData = {
|
|
||||||
name: "campaign 1",
|
|
||||||
description: "test description",
|
|
||||||
currency: "USD",
|
|
||||||
campaign_identifier: "test-1",
|
|
||||||
starts_at: new Date("01/01/2023"),
|
|
||||||
ends_at: new Date("01/01/2024"),
|
|
||||||
budget: {
|
|
||||||
type: CampaignBudgetType.SPEND,
|
|
||||||
limit: 1000,
|
|
||||||
used: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
|
||||||
const adminHeaders = {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
}
|
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
|
||||||
env,
|
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
||||||
describe("GET /admin/campaigns", () => {
|
|
||||||
let appContainer
|
|
||||||
let promotionModuleService: IPromotionModuleService
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
appContainer = getContainer()
|
|
||||||
promotionModuleService = appContainer.resolve(
|
|
||||||
ModuleRegistrationName.PROMOTION
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw an error if id does not exist", async () => {
|
|
||||||
const { response } = await api
|
|
||||||
.get(`/admin/campaigns/does-not-exist`, adminHeaders)
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(404)
|
|
||||||
expect(response.data.message).toEqual(
|
|
||||||
"Campaign with id: does-not-exist was not found"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get the requested campaign", async () => {
|
|
||||||
const createdCampaign = await promotionModuleService.createCampaigns(
|
|
||||||
campaignData
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await api.get(
|
|
||||||
`/admin/campaigns/${createdCampaign.id}`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.campaign).toEqual({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 1",
|
|
||||||
description: "test description",
|
|
||||||
currency: "USD",
|
|
||||||
campaign_identifier: "test-1",
|
|
||||||
starts_at: expect.any(String),
|
|
||||||
ends_at: expect.any(String),
|
|
||||||
budget: {
|
|
||||||
id: expect.any(String),
|
|
||||||
type: "spend",
|
|
||||||
limit: 1000,
|
|
||||||
raw_limit: {
|
|
||||||
precision: 20,
|
|
||||||
value: "1000",
|
|
||||||
},
|
|
||||||
raw_used: {
|
|
||||||
precision: 20,
|
|
||||||
value: "0",
|
|
||||||
},
|
|
||||||
used: 0,
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
deleted_at: null,
|
|
||||||
},
|
|
||||||
created_at: expect.any(String),
|
|
||||||
updated_at: expect.any(String),
|
|
||||||
deleted_at: null,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should get the requested campaign with filtered fields and relations", async () => {
|
|
||||||
const createdCampaign = await promotionModuleService.createCampaigns(
|
|
||||||
campaignData
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await api.get(
|
|
||||||
`/admin/campaigns/${createdCampaign.id}?fields=name`,
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.campaign).toEqual({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 1",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import { IPromotionModuleService } from "@medusajs/types"
|
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
|
||||||
const adminHeaders = {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
}
|
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
|
||||||
env,
|
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
||||||
describe("POST /admin/campaigns/:id", () => {
|
|
||||||
let appContainer
|
|
||||||
let promotionModuleService: IPromotionModuleService
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
appContainer = getContainer()
|
|
||||||
promotionModuleService = appContainer.resolve(
|
|
||||||
ModuleRegistrationName.PROMOTION
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw an error if id does not exist", async () => {
|
|
||||||
const { response } = await api
|
|
||||||
.post(`/admin/campaigns/does-not-exist`, {}, adminHeaders)
|
|
||||||
.catch((e) => e)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(404)
|
|
||||||
expect(response.data.message).toEqual(
|
|
||||||
`Campaign with id "does-not-exist" not found`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should update a campaign successfully", async () => {
|
|
||||||
const createdPromotion = await promotionModuleService.create({
|
|
||||||
code: "TEST",
|
|
||||||
type: "standard",
|
|
||||||
})
|
|
||||||
|
|
||||||
const createdPromotion2 = await promotionModuleService.create({
|
|
||||||
code: "TEST_2",
|
|
||||||
type: "standard",
|
|
||||||
})
|
|
||||||
|
|
||||||
const createdCampaign = await promotionModuleService.createCampaigns({
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test",
|
|
||||||
starts_at: new Date("01/01/2024").toISOString(),
|
|
||||||
ends_at: new Date("01/01/2029").toISOString(),
|
|
||||||
promotions: [{ id: createdPromotion.id }],
|
|
||||||
budget: {
|
|
||||||
limit: 1000,
|
|
||||||
type: "usage",
|
|
||||||
used: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = await api.post(
|
|
||||||
`/admin/campaigns/${createdCampaign.id}?fields=*promotions`,
|
|
||||||
{
|
|
||||||
name: "test-2",
|
|
||||||
campaign_identifier: "test-2",
|
|
||||||
budget: {
|
|
||||||
limit: 2000,
|
|
||||||
},
|
|
||||||
promotions: [{ id: createdPromotion2.id }],
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.campaign).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "test-2",
|
|
||||||
campaign_identifier: "test-2",
|
|
||||||
budget: expect.objectContaining({
|
|
||||||
limit: 2000,
|
|
||||||
type: "usage",
|
|
||||||
used: 10,
|
|
||||||
}),
|
|
||||||
promotions: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: createdPromotion2.id,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService, LinkWorkflowInput } from "@medusajs/types"
|
||||||
|
import { StepResponse, WorkflowData, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const addCampaignPromotionsStepId = "add-campaign-promotions"
|
||||||
|
export const addCampaignPromotionsStep = createStep(
|
||||||
|
addCampaignPromotionsStepId,
|
||||||
|
async (input: WorkflowData<LinkWorkflowInput>, { container }) => {
|
||||||
|
const { id: campaignId, add: promotionIdsToAdd = [] } = input
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (promotionIdsToAdd.length) {
|
||||||
|
await promotionModule.addPromotionsToCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToAdd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(null, input)
|
||||||
|
},
|
||||||
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id: campaignId, add: promotionIdsToRemove = [] } = data
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (promotionIdsToRemove.length) {
|
||||||
|
await promotionModule.removePromotionsFromCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
export * from "./add-campaign-promotions"
|
||||||
export * from "./add-rules-to-promotions"
|
export * from "./add-rules-to-promotions"
|
||||||
export * from "./create-campaigns"
|
export * from "./create-campaigns"
|
||||||
export * from "./create-promotions"
|
export * from "./create-promotions"
|
||||||
export * from "./delete-campaigns"
|
export * from "./delete-campaigns"
|
||||||
export * from "./delete-promotions"
|
export * from "./delete-promotions"
|
||||||
|
export * from "./remove-campaign-promotions"
|
||||||
export * from "./remove-rules-from-promotions"
|
export * from "./remove-rules-from-promotions"
|
||||||
export * from "./update-campaigns"
|
export * from "./update-campaigns"
|
||||||
export * from "./update-promotion-rules"
|
export * from "./update-promotion-rules"
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService, LinkWorkflowInput } from "@medusajs/types"
|
||||||
|
import { StepResponse, WorkflowData, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const removeCampaignPromotionsStepId = "remove-campaign-promotions"
|
||||||
|
export const removeCampaignPromotionsStep = createStep(
|
||||||
|
removeCampaignPromotionsStepId,
|
||||||
|
async (input: WorkflowData<LinkWorkflowInput>, { container }) => {
|
||||||
|
const { id: campaignId, remove: promotionIdsToRemove = [] } = input
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (promotionIdsToRemove.length) {
|
||||||
|
await promotionModule.removePromotionsFromCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(null, input)
|
||||||
|
},
|
||||||
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id: campaignId, remove: promotionIdsToAdd = [] } = data
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (promotionIdsToAdd.length) {
|
||||||
|
await promotionModule.addPromotionsToCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToAdd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { LinkWorkflowInput } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import {
|
||||||
|
addCampaignPromotionsStep,
|
||||||
|
removeCampaignPromotionsStep,
|
||||||
|
} from "../steps"
|
||||||
|
|
||||||
|
export const addOrRemoveCampaignPromotionsWorkflowId =
|
||||||
|
"add-or-remove-campaign-promotions"
|
||||||
|
export const addOrRemoveCampaignPromotionsWorkflow = createWorkflow(
|
||||||
|
addOrRemoveCampaignPromotionsWorkflowId,
|
||||||
|
(input: WorkflowData<LinkWorkflowInput>): WorkflowData<void> => {
|
||||||
|
addCampaignPromotionsStep(input)
|
||||||
|
removeCampaignPromotionsStep(input)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
export * from "./add-or-remove-campaign-promotions"
|
||||||
export * from "./batch-promotion-rules"
|
export * from "./batch-promotion-rules"
|
||||||
export * from "./create-campaigns"
|
export * from "./create-campaigns"
|
||||||
|
export * from "./create-promotion-rules"
|
||||||
export * from "./create-promotions"
|
export * from "./create-promotions"
|
||||||
export * from "./delete-campaigns"
|
export * from "./delete-campaigns"
|
||||||
|
export * from "./delete-promotion-rules"
|
||||||
export * from "./delete-promotions"
|
export * from "./delete-promotions"
|
||||||
export * from "./update-campaigns"
|
export * from "./update-campaigns"
|
||||||
export * from "./update-promotion-rules"
|
export * from "./update-promotion-rules"
|
||||||
export * from "./delete-promotion-rules"
|
|
||||||
export * from "./create-promotion-rules"
|
|
||||||
export * from "./update-promotions"
|
export * from "./update-promotions"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BaseFilterable } from "../../dal"
|
import { BaseFilterable } from "../../dal"
|
||||||
import { CampaignBudgetDTO } from "./campaign-budget"
|
import { CampaignBudgetDTO } from "./campaign-budget"
|
||||||
|
import { PromotionDTO } from "./promotion"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The campaign details.
|
* The campaign details.
|
||||||
@@ -44,6 +45,11 @@ export interface CampaignDTO {
|
|||||||
* The associated campaign budget.
|
* The associated campaign budget.
|
||||||
*/
|
*/
|
||||||
budget?: CampaignBudgetDTO
|
budget?: CampaignBudgetDTO
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The associated promotions.
|
||||||
|
*/
|
||||||
|
promotions?: PromotionDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export interface UpdatePromotionDTO {
|
|||||||
/**
|
/**
|
||||||
* The associated campaign's ID.
|
* The associated campaign's ID.
|
||||||
*/
|
*/
|
||||||
campaign_id?: string
|
campaign_id?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CampaignBudgetTypeValues, PromotionDTO } from "./common"
|
import { CampaignBudgetTypeValues } from "./common"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The campaign budget to be created.
|
* The campaign budget to be created.
|
||||||
@@ -83,11 +83,6 @@ export interface CreateCampaignDTO {
|
|||||||
* The associated campaign budget.
|
* The associated campaign budget.
|
||||||
*/
|
*/
|
||||||
budget?: CreateCampaignBudgetDTO
|
budget?: CreateCampaignBudgetDTO
|
||||||
|
|
||||||
/**
|
|
||||||
* The promotions of the campaign.
|
|
||||||
*/
|
|
||||||
promotions?: Pick<PromotionDTO, "id">[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,9 +128,28 @@ export interface UpdateCampaignDTO {
|
|||||||
* The budget of the campaign.
|
* The budget of the campaign.
|
||||||
*/
|
*/
|
||||||
budget?: Omit<UpdateCampaignBudgetDTO, "id">
|
budget?: Omit<UpdateCampaignBudgetDTO, "id">
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddPromotionsToCampaignDTO {
|
||||||
|
/**
|
||||||
|
* The ID of the campaign.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The promotions of the campaign.
|
* Ids of promotions to add
|
||||||
*/
|
*/
|
||||||
promotions?: Pick<PromotionDTO, "id">[]
|
promotion_ids: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemovePromotionsFromCampaignDTO {
|
||||||
|
/**
|
||||||
|
* The ID of the campaign.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ids of promotions to add
|
||||||
|
*/
|
||||||
|
promotion_ids: string[]
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,12 @@ import {
|
|||||||
UpdatePromotionDTO,
|
UpdatePromotionDTO,
|
||||||
UpdatePromotionRuleDTO,
|
UpdatePromotionRuleDTO,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
import { CreateCampaignDTO, UpdateCampaignDTO } from "./mutations"
|
import {
|
||||||
|
AddPromotionsToCampaignDTO,
|
||||||
|
CreateCampaignDTO,
|
||||||
|
RemovePromotionsFromCampaignDTO,
|
||||||
|
UpdateCampaignDTO,
|
||||||
|
} from "./mutations"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main service interface for the Promotion Module.
|
* The main service interface for the Promotion Module.
|
||||||
@@ -967,4 +972,14 @@ export interface IPromotionModuleService extends IModuleService {
|
|||||||
config?: RestoreReturn<TReturnableLinkableKeys>,
|
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<Record<string, string[]> | void>
|
): Promise<Record<string, string[]> | void>
|
||||||
|
|
||||||
|
addPromotionsToCampaign(
|
||||||
|
data: AddPromotionsToCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<{ ids: string[] }>
|
||||||
|
|
||||||
|
removePromotionsFromCampaign(
|
||||||
|
data: RemovePromotionsFromCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<{ ids: string[] }>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../../../types/routing"
|
||||||
|
|
||||||
|
import { addOrRemoveCampaignPromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { LinkMethodRequest } from "@medusajs/types/src"
|
||||||
|
import { refetchCampaign } from "../../helpers"
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const { add, remove } = req.validatedBody
|
||||||
|
const { errors } = await addOrRemoveCampaignPromotionsWorkflow(req.scope).run(
|
||||||
|
{
|
||||||
|
input: { id, add, remove },
|
||||||
|
throwOnError: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
const campaign = await refetchCampaign(
|
||||||
|
req.params.id,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ campaign })
|
||||||
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import * as QueryConfig from "./query-config"
|
|
||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
|
import { createLinkBody } from "../../utils/validators"
|
||||||
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
AdminCreateCampaign,
|
AdminCreateCampaign,
|
||||||
AdminGetCampaignParams,
|
AdminGetCampaignParams,
|
||||||
AdminGetCampaignsParams,
|
AdminGetCampaignsParams,
|
||||||
AdminUpdateCampaign,
|
AdminUpdateCampaign,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
|
||||||
|
|
||||||
export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
{
|
{
|
||||||
@@ -57,4 +58,15 @@ export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/campaigns/:id/promotions",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformBody(createLinkBody()),
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetCampaignParams,
|
||||||
|
QueryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import * as QueryConfig from "./query-config"
|
|
||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
|
import { createBatchBody } from "../../utils/validators"
|
||||||
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
AdminCreatePromotion,
|
AdminCreatePromotion,
|
||||||
AdminCreatePromotionRule,
|
AdminCreatePromotionRule,
|
||||||
@@ -13,8 +15,6 @@ import {
|
|||||||
AdminUpdatePromotion,
|
AdminUpdatePromotion,
|
||||||
AdminUpdatePromotionRule,
|
AdminUpdatePromotionRule,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
|
||||||
import { createBatchBody } from "../../utils/validators"
|
|
||||||
|
|
||||||
export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { createPromotionsWorkflow } from "@medusajs/core-flows"
|
import { createPromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
import {
|
|
||||||
AuthenticatedMedusaRequest,
|
|
||||||
MedusaResponse,
|
|
||||||
} from "../../../types/routing"
|
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
remoteQueryObjectFromString,
|
remoteQueryObjectFromString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../types/routing"
|
||||||
|
import { refetchPromotion } from "./helpers"
|
||||||
import {
|
import {
|
||||||
AdminCreatePromotionType,
|
AdminCreatePromotionType,
|
||||||
AdminGetPromotionsParamsType,
|
AdminGetPromotionsParamsType,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { refetchPromotion } from "./helpers"
|
|
||||||
|
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: AuthenticatedMedusaRequest<AdminGetPromotionsParamsType>,
|
req: AuthenticatedMedusaRequest<AdminGetPromotionsParamsType>,
|
||||||
|
|||||||
@@ -24,17 +24,20 @@ export type AdminGetPromotionsParamsType = z.infer<
|
|||||||
export const AdminGetPromotionsParams = createFindParams({
|
export const AdminGetPromotionsParams = createFindParams({
|
||||||
limit: 50,
|
limit: 50,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}).merge(
|
})
|
||||||
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
q: z.string().optional(),
|
q: z.string().optional(),
|
||||||
code: z.union([z.string(), z.array(z.string())]).optional(),
|
code: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
campaign_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
created_at: createOperatorMap().optional(),
|
created_at: createOperatorMap().optional(),
|
||||||
updated_at: createOperatorMap().optional(),
|
updated_at: createOperatorMap().optional(),
|
||||||
deleted_at: createOperatorMap().optional(),
|
deleted_at: createOperatorMap().optional(),
|
||||||
$and: z.lazy(() => AdminGetPromotionsParams.array()).optional(),
|
$and: z.lazy(() => AdminGetPromotionsParams.array()).optional(),
|
||||||
$or: z.lazy(() => AdminGetPromotionsParams.array()).optional(),
|
$or: z.lazy(() => AdminGetPromotionsParams.array()).optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.strict()
|
||||||
|
|
||||||
export type AdminGetPromotionRuleParamsType = z.infer<
|
export type AdminGetPromotionRuleParamsType = z.infer<
|
||||||
typeof AdminGetPromotionRuleParams
|
typeof AdminGetPromotionRuleParams
|
||||||
@@ -152,7 +155,6 @@ export const AdminCreateCampaign = z.object({
|
|||||||
budget: CreateCampaignBudget.optional(),
|
budget: CreateCampaignBudget.optional(),
|
||||||
starts_at: z.coerce.date().optional(),
|
starts_at: z.coerce.date().optional(),
|
||||||
ends_at: z.coerce.date().optional(),
|
ends_at: z.coerce.date().optional(),
|
||||||
promotions: z.array(z.object({ id: z.string() })).optional(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AdminCreatePromotionType = z.infer<typeof AdminCreatePromotion>
|
export type AdminCreatePromotionType = z.infer<typeof AdminCreatePromotion>
|
||||||
|
|||||||
+71
-97
@@ -152,43 +152,6 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a basic campaign with promotions successfully", async () => {
|
|
||||||
await createPromotions(MikroOrmWrapper.forkManager())
|
|
||||||
|
|
||||||
const startsAt = new Date("01/01/2024")
|
|
||||||
const endsAt = new Date("01/01/2025")
|
|
||||||
const [createdCampaign] = await service.createCampaigns([
|
|
||||||
{
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test",
|
|
||||||
starts_at: startsAt,
|
|
||||||
ends_at: endsAt,
|
|
||||||
promotions: [{ id: "promotion-id-1" }, { id: "promotion-id-2" }],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const campaign = await service.retrieveCampaign(createdCampaign.id, {
|
|
||||||
relations: ["promotions"],
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(campaign).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
name: "test",
|
|
||||||
campaign_identifier: "test",
|
|
||||||
starts_at: startsAt,
|
|
||||||
ends_at: endsAt,
|
|
||||||
promotions: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: "promotion-id-1",
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
id: "promotion-id-2",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("updateCampaigns", () => {
|
describe("updateCampaigns", () => {
|
||||||
@@ -251,66 +214,6 @@ moduleIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should update promotions of a campaign successfully", async () => {
|
|
||||||
await createCampaigns(MikroOrmWrapper.forkManager())
|
|
||||||
await createPromotions(MikroOrmWrapper.forkManager())
|
|
||||||
|
|
||||||
const [updatedCampaign] = await service.updateCampaigns([
|
|
||||||
{
|
|
||||||
id: "campaign-id-1",
|
|
||||||
description: "test description 1",
|
|
||||||
currency: "EUR",
|
|
||||||
campaign_identifier: "new",
|
|
||||||
starts_at: new Date("01/01/2024"),
|
|
||||||
ends_at: new Date("01/01/2025"),
|
|
||||||
promotions: [{ id: "promotion-id-1" }, { id: "promotion-id-2" }],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
expect(updatedCampaign).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
description: "test description 1",
|
|
||||||
currency: "EUR",
|
|
||||||
campaign_identifier: "new",
|
|
||||||
starts_at: new Date("01/01/2024"),
|
|
||||||
ends_at: new Date("01/01/2025"),
|
|
||||||
promotions: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: "promotion-id-1",
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
id: "promotion-id-2",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should remove promotions of the campaign successfully", async () => {
|
|
||||||
await createCampaigns(MikroOrmWrapper.forkManager())
|
|
||||||
await createPromotions(MikroOrmWrapper.forkManager())
|
|
||||||
|
|
||||||
await service.updateCampaigns({
|
|
||||||
id: "campaign-id-1",
|
|
||||||
promotions: [{ id: "promotion-id-1" }, { id: "promotion-id-2" }],
|
|
||||||
})
|
|
||||||
|
|
||||||
const updatedCampaign = await service.updateCampaigns({
|
|
||||||
id: "campaign-id-1",
|
|
||||||
promotions: [{ id: "promotion-id-1" }],
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(updatedCampaign).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
promotions: [
|
|
||||||
expect.objectContaining({
|
|
||||||
id: "promotion-id-1",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("retrieveCampaign", () => {
|
describe("retrieveCampaign", () => {
|
||||||
@@ -438,6 +341,77 @@ moduleIntegrationTestRunner({
|
|||||||
expect(campaigns).toHaveLength(1)
|
expect(campaigns).toHaveLength(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("addPromotionsToCampaign", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createCampaigns(MikroOrmWrapper.forkManager())
|
||||||
|
await createPromotions(MikroOrmWrapper.forkManager())
|
||||||
|
|
||||||
|
await service.addPromotionsToCampaign({
|
||||||
|
id,
|
||||||
|
promotion_ids: ["promotion-id-1"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = "campaign-id-1"
|
||||||
|
|
||||||
|
it("should add promotions to a campaign", async () => {
|
||||||
|
await service.addPromotionsToCampaign({
|
||||||
|
id,
|
||||||
|
promotion_ids: ["promotion-id-2"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const campaign = await service.retrieveCampaign(id, {
|
||||||
|
relations: ["promotions"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(campaign.promotions).toHaveLength(2)
|
||||||
|
expect(campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id,
|
||||||
|
promotions: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ id: "promotion-id-1" }),
|
||||||
|
expect.objectContaining({ id: "promotion-id-2" }),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("removePromotionsFromCampaign", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createCampaigns(MikroOrmWrapper.forkManager())
|
||||||
|
await createPromotions(MikroOrmWrapper.forkManager())
|
||||||
|
|
||||||
|
await service.addPromotionsToCampaign({
|
||||||
|
id,
|
||||||
|
promotion_ids: ["promotion-id-1", "promotion-id-2"],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = "campaign-id-1"
|
||||||
|
|
||||||
|
it("should remove promotions to a campaign", async () => {
|
||||||
|
await service.removePromotionsFromCampaign({
|
||||||
|
id,
|
||||||
|
promotion_ids: ["promotion-id-1"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const campaign = await service.retrieveCampaign(id, {
|
||||||
|
relations: ["promotions"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(campaign.promotions).toHaveLength(1)
|
||||||
|
expect(campaign).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id,
|
||||||
|
promotions: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ id: "promotion-id-2" }),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
+1
-1
@@ -539,7 +539,7 @@ moduleIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
await service.updateCampaigns({
|
const updated = await service.updateCampaigns({
|
||||||
id: "campaign-id-2",
|
id: "campaign-id-2",
|
||||||
budget: { used: 1000 },
|
budget: { used: 1000 },
|
||||||
})
|
})
|
||||||
|
|||||||
+3
-1
@@ -6,9 +6,9 @@ import {
|
|||||||
CampaignBudgetType,
|
CampaignBudgetType,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||||
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
import { createCampaigns } from "../../../__fixtures__/campaigns"
|
||||||
import { createPromotions } from "../../../__fixtures__/promotion"
|
import { createPromotions } from "../../../__fixtures__/promotion"
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
@@ -918,6 +918,7 @@ moduleIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
id: "promotion-id-1",
|
id: "promotion-id-1",
|
||||||
code: "PROMOTION_1",
|
code: "PROMOTION_1",
|
||||||
|
campaign_id: null,
|
||||||
campaign: null,
|
campaign: null,
|
||||||
is_automatic: false,
|
is_automatic: false,
|
||||||
type: "standard",
|
type: "standard",
|
||||||
@@ -929,6 +930,7 @@ moduleIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
id: "promotion-id-2",
|
id: "promotion-id-2",
|
||||||
code: "PROMOTION_2",
|
code: "PROMOTION_2",
|
||||||
|
campaign_id: null,
|
||||||
campaign: null,
|
campaign: null,
|
||||||
is_automatic: false,
|
is_automatic: false,
|
||||||
type: "standard",
|
type: "standard",
|
||||||
|
|||||||
@@ -70,9 +70,7 @@ export default class Campaign {
|
|||||||
})
|
})
|
||||||
budget: CampaignBudget | null = null
|
budget: CampaignBudget | null = null
|
||||||
|
|
||||||
@OneToMany(() => Promotion, (promotion) => promotion.campaign, {
|
@OneToMany(() => Promotion, (promotion) => promotion.campaign)
|
||||||
orphanRemoval: true,
|
|
||||||
})
|
|
||||||
promotions = new Collection<Promotion>(this)
|
promotions = new Collection<Promotion>(this)
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
|
|||||||
@@ -45,13 +45,17 @@ export default class Promotion {
|
|||||||
})
|
})
|
||||||
code: string
|
code: string
|
||||||
|
|
||||||
@Searchable()
|
|
||||||
@ManyToOne(() => Campaign, {
|
@ManyToOne(() => Campaign, {
|
||||||
|
columnType: "text",
|
||||||
fieldName: "campaign_id",
|
fieldName: "campaign_id",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
cascade: ["soft-remove"] as any,
|
mapToPk: true,
|
||||||
|
onDelete: "set null",
|
||||||
})
|
})
|
||||||
campaign: Campaign | null = null
|
campaign_id: string | null = null
|
||||||
|
|
||||||
|
@ManyToOne(() => Campaign, { persist: false })
|
||||||
|
campaign: Campaign | null
|
||||||
|
|
||||||
@Property({ columnType: "boolean", default: false })
|
@Property({ columnType: "boolean", default: false })
|
||||||
is_automatic: boolean = false
|
is_automatic: boolean = false
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
import { Context } from "@medusajs/types"
|
|
||||||
import { DALUtils } from "@medusajs/utils"
|
|
||||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
|
||||||
import { Campaign, Promotion } from "@models"
|
|
||||||
import { CreateCampaignDTO, UpdateCampaignDTO } from "@types"
|
|
||||||
|
|
||||||
export class CampaignRepository extends DALUtils.mikroOrmBaseRepositoryFactory<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: { entity: Campaign; update: 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(({ update: campaignData }) => {
|
|
||||||
const campaignPromotionIds = campaignData.promotions?.map((p) => p.id)
|
|
||||||
|
|
||||||
campaignIds.push(campaignData.id)
|
|
||||||
|
|
||||||
if (campaignPromotionIds) {
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (!upsertPromotionIds) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||||
export { CampaignRepository } from "./campaign"
|
|
||||||
@@ -528,7 +528,7 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
promotionsData.push({
|
promotionsData.push({
|
||||||
...promotionData,
|
...promotionData,
|
||||||
campaign: campaignId,
|
campaign_id: campaignId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,6 +536,7 @@ export default class PromotionModuleService<
|
|||||||
promotionsData,
|
promotionsData,
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
const promotionsToAdd: PromotionTypes.AddPromotionsToCampaignDTO[] = []
|
||||||
|
|
||||||
for (const promotion of createdPromotions) {
|
for (const promotion of createdPromotions) {
|
||||||
const applMethodData = promotionCodeApplicationMethodDataMap.get(
|
const applMethodData = promotionCodeApplicationMethodDataMap.get(
|
||||||
@@ -617,8 +618,25 @@ export default class PromotionModuleService<
|
|||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
if (campaignsData.length) {
|
const createdCampaigns = await this.createCampaigns(
|
||||||
await this.createCampaigns(campaignsData, sharedContext)
|
campaignsData,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const campaignData of campaignsData) {
|
||||||
|
const promotions = campaignData.promotions
|
||||||
|
const campaign = createdCampaigns.find(
|
||||||
|
(c) => c.campaign_identifier === campaignData.campaign_identifier
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!campaign || !promotions || !promotions.length) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.addPromotionsToCampaign(
|
||||||
|
{ id: campaign.id, promotion_ids: promotions.map((p) => p.id) },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const applicationMethod of createdApplicationMethods) {
|
for (const applicationMethod of createdApplicationMethods) {
|
||||||
@@ -704,7 +722,7 @@ export default class PromotionModuleService<
|
|||||||
...promotionData
|
...promotionData
|
||||||
} of data) {
|
} of data) {
|
||||||
if (campaignId) {
|
if (campaignId) {
|
||||||
promotionsData.push({ ...promotionData, campaign: campaignId })
|
promotionsData.push({ ...promotionData, campaign_id: campaignId })
|
||||||
} else {
|
} else {
|
||||||
promotionsData.push(promotionData)
|
promotionsData.push(promotionData)
|
||||||
}
|
}
|
||||||
@@ -1110,19 +1128,7 @@ export default class PromotionModuleService<
|
|||||||
>()
|
>()
|
||||||
|
|
||||||
for (const createCampaignData of data) {
|
for (const createCampaignData of data) {
|
||||||
const {
|
const { budget: campaignBudgetData, ...campaignData } = createCampaignData
|
||||||
budget: campaignBudgetData,
|
|
||||||
promotions,
|
|
||||||
...campaignData
|
|
||||||
} = createCampaignData
|
|
||||||
|
|
||||||
const promotionsToAdd = promotions
|
|
||||||
? await this.list(
|
|
||||||
{ id: promotions.map((p) => p.id) },
|
|
||||||
{ take: null },
|
|
||||||
sharedContext
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
|
|
||||||
if (campaignBudgetData) {
|
if (campaignBudgetData) {
|
||||||
campaignIdentifierBudgetMap.set(
|
campaignIdentifierBudgetMap.set(
|
||||||
@@ -1133,7 +1139,6 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
campaignsData.push({
|
campaignsData.push({
|
||||||
...campaignData,
|
...campaignData,
|
||||||
promotions: promotionsToAdd,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1244,4 +1249,104 @@ export default class PromotionModuleService<
|
|||||||
|
|
||||||
return updatedCampaigns
|
return updatedCampaigns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async addPromotionsToCampaign(
|
||||||
|
data: PromotionTypes.AddPromotionsToCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<{ ids: string[] }> {
|
||||||
|
const ids = await this.addPromotionsToCampaign_(data, sharedContext)
|
||||||
|
|
||||||
|
return { ids }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - introduce currency_code to promotion
|
||||||
|
// - allow promotions to be queried by currency code
|
||||||
|
// - when the above is present, validate adding promotion to campaign based on currency code
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async addPromotionsToCampaign_(
|
||||||
|
data: PromotionTypes.AddPromotionsToCampaignDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const { id, promotion_ids: promotionIds = [] } = data
|
||||||
|
|
||||||
|
const campaign = await this.campaignService_.retrieve(id, {}, sharedContext)
|
||||||
|
const promotionsToAdd = await this.promotionService_.list(
|
||||||
|
{ id: promotionIds, campaign_id: null },
|
||||||
|
{ take: null },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
const diff = arrayDifference(
|
||||||
|
promotionsToAdd.map((p) => p.id),
|
||||||
|
promotionIds
|
||||||
|
)
|
||||||
|
|
||||||
|
if (diff.length > 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Cannot add promotions (${diff.join(
|
||||||
|
","
|
||||||
|
)}) to campaign. These promotions are either already part of a campaign or not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.promotionService_.update(
|
||||||
|
promotionsToAdd.map((promotion) => ({
|
||||||
|
id: promotion.id,
|
||||||
|
campaign_id: campaign.id,
|
||||||
|
})),
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return promotionsToAdd.map((promo) => promo.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async removePromotionsFromCampaign(
|
||||||
|
data: PromotionTypes.AddPromotionsToCampaignDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<{ ids: string[] }> {
|
||||||
|
const ids = await this.removePromotionsFromCampaign_(data, sharedContext)
|
||||||
|
|
||||||
|
return { ids }
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async removePromotionsFromCampaign_(
|
||||||
|
data: PromotionTypes.AddPromotionsToCampaignDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
) {
|
||||||
|
const { id, promotion_ids: promotionIds = [] } = data
|
||||||
|
|
||||||
|
await this.campaignService_.retrieve(id, {}, sharedContext)
|
||||||
|
const promotionsToRemove = await this.promotionService_.list(
|
||||||
|
{ id: promotionIds },
|
||||||
|
{ take: null },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
const diff = arrayDifference(
|
||||||
|
promotionsToRemove.map((p) => p.id),
|
||||||
|
promotionIds
|
||||||
|
)
|
||||||
|
|
||||||
|
if (diff.length > 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Promotions with ids (${diff.join(",")}) not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.promotionService_.update(
|
||||||
|
promotionsToRemove.map((promotion) => ({
|
||||||
|
id: promotion.id,
|
||||||
|
campaign_id: null,
|
||||||
|
})),
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return promotionsToRemove.map((promo) => promo.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ export interface CreatePromotionDTO {
|
|||||||
code: string
|
code: string
|
||||||
type: PromotionTypeValues
|
type: PromotionTypeValues
|
||||||
is_automatic?: boolean
|
is_automatic?: boolean
|
||||||
campaign?: string
|
campaign_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdatePromotionDTO {
|
export interface UpdatePromotionDTO {
|
||||||
@@ -12,5 +12,5 @@ export interface UpdatePromotionDTO {
|
|||||||
code?: string
|
code?: string
|
||||||
type?: PromotionTypeValues
|
type?: PromotionTypeValues
|
||||||
is_automatic?: boolean
|
is_automatic?: boolean
|
||||||
campaign?: string
|
campaign_id?: string
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user