feat(core-flows,medusa,utils): promotion and campaign create/update endpoint (#6130)
what: - adds create endpoint for promotions including workflows and endpoint (RESOLVES CORE-1678) - adds update endpoint for promotions including workflows and endpoint (RESOLVES CORE-1679) - adds create endpoint for campaigns including workflows and endpoint (RESOLVES CORE-1684) - adds update endpoint for campaigns including workflows and endpoint (RESOLVES CORE-1685)
This commit is contained in:
1 parent
a52586880c
commit
da5cc4cf7f
30 files changed
+1240
-50
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core-flows,medusa,utils): promotion and campaign create/update endpoint
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("POST /admin/campaigns", () => {
|
||||||
|
let dbConnection
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
appContainer = getContainer()
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if required params are not passed", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
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 api = useApi() as any
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/campaigns`,
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { PromotionType } from "@medusajs/utils"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("POST /admin/promotions", () => {
|
||||||
|
let dbConnection
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
appContainer = getContainer()
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if required params are not passed", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions`,
|
||||||
|
{
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(400)
|
||||||
|
expect(response.data.message).toEqual(
|
||||||
|
"code must be a string, code should not be empty, application_method should not be empty"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a promotion successfully", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/promotions`,
|
||||||
|
{
|
||||||
|
code: "TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
campaign: {
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
budget: {
|
||||||
|
type: "usage",
|
||||||
|
limit: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
application_method: {
|
||||||
|
target_type: "items",
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
value: "100",
|
||||||
|
max_quantity: 100,
|
||||||
|
target_rules: [
|
||||||
|
{
|
||||||
|
attribute: "test.test",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["test1", "test2"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test.test",
|
||||||
|
operator: "eq",
|
||||||
|
values: ["test1", "test2"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.promotion).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
code: "TEST",
|
||||||
|
type: "standard",
|
||||||
|
is_automatic: true,
|
||||||
|
campaign: expect.objectContaining({
|
||||||
|
name: "test",
|
||||||
|
campaign_identifier: "test-1",
|
||||||
|
budget: expect.objectContaining({
|
||||||
|
type: "usage",
|
||||||
|
limit: 100,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
application_method: expect.objectContaining({
|
||||||
|
value: 100,
|
||||||
|
max_quantity: 100,
|
||||||
|
type: "fixed",
|
||||||
|
target_type: "items",
|
||||||
|
allocation: "each",
|
||||||
|
target_rules: [
|
||||||
|
expect.objectContaining({
|
||||||
|
operator: "eq",
|
||||||
|
attribute: "test.test",
|
||||||
|
values: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ value: "test1" }),
|
||||||
|
expect.objectContaining({ value: "test2" }),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
rules: [
|
||||||
|
expect.objectContaining({
|
||||||
|
operator: "eq",
|
||||||
|
attribute: "test.test",
|
||||||
|
values: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ value: "test1" }),
|
||||||
|
expect.objectContaining({ value: "test2" }),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -141,25 +141,27 @@ describe("GET /admin/campaigns", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.count).toEqual(2)
|
expect(response.data.count).toEqual(2)
|
||||||
expect(response.data.campaigns).toEqual([
|
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),
|
||||||
campaign: expect.any(Object),
|
name: "campaign 1",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
campaign: expect.any(Object),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
id: expect.any(String),
|
|
||||||
name: "campaign 2",
|
|
||||||
created_at: expect.any(String),
|
|
||||||
budget: {
|
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
campaign: expect.any(Object),
|
name: "campaign 2",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
budget: {
|
||||||
|
id: expect.any(String),
|
||||||
|
campaign: expect.any(Object),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
])
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("POST /admin/campaigns/:id", () => {
|
||||||
|
let dbConnection
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
appContainer = getContainer()
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if id does not exist", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
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 api = useApi() as any
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/campaigns/${createdCampaign.id}`,
|
||||||
|
{
|
||||||
|
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,135 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { PromotionType } from "@medusajs/utils"
|
||||||
|
import path from "path"
|
||||||
|
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||||
|
import { useApi } from "../../../../environment-helpers/use-api"
|
||||||
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
|
||||||
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
const adminHeaders = {
|
||||||
|
headers: { "x-medusa-access-token": "test_token" },
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("POST /admin/promotions/:id", () => {
|
||||||
|
let dbConnection
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let promotionModuleService: IPromotionModuleService
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd, env } as any)
|
||||||
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
|
appContainer = getContainer()
|
||||||
|
promotionModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
await shutdownServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if id does not exist", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions/does-not-exist`,
|
||||||
|
{ type: PromotionType.STANDARD },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(404)
|
||||||
|
expect(response.data.message).toEqual(
|
||||||
|
`Promotion with id "does-not-exist" not found`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error when both campaign and campaign_id params are passed", async () => {
|
||||||
|
const createdPromotion = await promotionModuleService.create({
|
||||||
|
code: "TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
application_method: {
|
||||||
|
target_type: "items",
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
value: "100",
|
||||||
|
max_quantity: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/promotions/${createdPromotion.id}`,
|
||||||
|
{
|
||||||
|
campaign: {
|
||||||
|
name: "test campaign",
|
||||||
|
},
|
||||||
|
campaign_id: "test",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(400)
|
||||||
|
expect(response.data.message).toContain(
|
||||||
|
`Failed XOR relation between "campaign_id" and "campaign"`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a promotion successfully", async () => {
|
||||||
|
const createdPromotion = await promotionModuleService.create({
|
||||||
|
code: "TEST",
|
||||||
|
type: PromotionType.STANDARD,
|
||||||
|
is_automatic: true,
|
||||||
|
application_method: {
|
||||||
|
target_type: "items",
|
||||||
|
type: "fixed",
|
||||||
|
allocation: "each",
|
||||||
|
value: "100",
|
||||||
|
max_quantity: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/promotions/${createdPromotion.id}`,
|
||||||
|
{
|
||||||
|
code: "TEST_TWO",
|
||||||
|
application_method: {
|
||||||
|
value: "200",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.promotion).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
code: "TEST_TWO",
|
||||||
|
application_method: expect.objectContaining({
|
||||||
|
value: 200,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./cart"
|
export * from "./cart"
|
||||||
export * from "./product"
|
|
||||||
export * from "./inventory"
|
export * from "./inventory"
|
||||||
export * from "./price-list"
|
export * from "./price-list"
|
||||||
|
export * from "./product"
|
||||||
|
export * from "./promotion"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { CampaignDTO, CreateCampaignDTO } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { createCampaignsStep } from "../../handlers/promotion"
|
||||||
|
|
||||||
|
type WorkflowInput = { campaignsData: CreateCampaignDTO[] }
|
||||||
|
|
||||||
|
export const createCampaignsWorkflowId = "create-campaigns"
|
||||||
|
export const createCampaignsWorkflow = createWorkflow(
|
||||||
|
createCampaignsWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>): WorkflowData<CampaignDTO[]> => {
|
||||||
|
return createCampaignsStep(input.campaignsData)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { CreatePromotionDTO, PromotionDTO } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { createPromotionsStep } from "../../handlers/promotion"
|
||||||
|
|
||||||
|
type WorkflowInput = { promotionsData: CreatePromotionDTO[] }
|
||||||
|
type WorkflowOutput = PromotionDTO[]
|
||||||
|
|
||||||
|
export const createPromotionsWorkflowId = "create-promotions"
|
||||||
|
export const createPromotionsWorkflow = createWorkflow(
|
||||||
|
createPromotionsWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>): WorkflowData<PromotionDTO[]> => {
|
||||||
|
return createPromotionsStep(input.promotionsData)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./create-campaigns"
|
||||||
|
export * from "./create-promotions"
|
||||||
|
export * from "./update-campaigns"
|
||||||
|
export * from "./update-promotions"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { CampaignDTO, UpdateCampaignDTO } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { updateCampaignsStep } from "../../handlers/promotion"
|
||||||
|
|
||||||
|
type WorkflowInput = { campaignsData: UpdateCampaignDTO[] }
|
||||||
|
|
||||||
|
export const updateCampaignsWorkflowId = "update-campaigns"
|
||||||
|
export const updateCampaignsWorkflow = createWorkflow(
|
||||||
|
updateCampaignsWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>): WorkflowData<CampaignDTO[]> => {
|
||||||
|
return updateCampaignsStep(input.campaignsData)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { PromotionDTO, UpdatePromotionDTO } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { updatePromotionsStep } from "../../handlers/promotion"
|
||||||
|
|
||||||
|
type WorkflowInput = { promotionsData: UpdatePromotionDTO[] }
|
||||||
|
|
||||||
|
export const updatePromotionsWorkflowId = "update-promotions"
|
||||||
|
export const updatePromotionsWorkflow = createWorkflow(
|
||||||
|
updatePromotionsWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>): WorkflowData<PromotionDTO[]> => {
|
||||||
|
return updatePromotionsStep(input.promotionsData)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { CreateCampaignDTO, IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const createCampaignsStepId = "create-campaigns"
|
||||||
|
export const createCampaignsStep = createStep(
|
||||||
|
createCampaignsStepId,
|
||||||
|
async (data: CreateCampaignDTO[], { container }) => {
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdCampaigns = await promotionModule.createCampaigns(data)
|
||||||
|
|
||||||
|
return new StepResponse(
|
||||||
|
createdCampaigns,
|
||||||
|
createdCampaigns.map((createdCampaigns) => createdCampaigns.id)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async (createdCampaignIds, { container }) => {
|
||||||
|
if (!createdCampaignIds?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
await promotionModule.delete(createdCampaignIds)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { CreatePromotionDTO, IPromotionModuleService } from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const createPromotionsStepId = "create-promotions"
|
||||||
|
export const createPromotionsStep = createStep(
|
||||||
|
createPromotionsStepId,
|
||||||
|
async (data: CreatePromotionDTO[], { container }) => {
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdPromotions = await promotionModule.create(data)
|
||||||
|
|
||||||
|
return new StepResponse(
|
||||||
|
createdPromotions,
|
||||||
|
createdPromotions.map((createdPromotions) => createdPromotions.id)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async (createdPromotionIds, { container }) => {
|
||||||
|
if (!createdPromotionIds?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
await promotionModule.delete(createdPromotionIds)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./create-campaigns"
|
||||||
|
export * from "./create-promotions"
|
||||||
|
export * from "./update-campaigns"
|
||||||
|
export * from "./update-promotions"
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService, UpdateCampaignDTO } from "@medusajs/types"
|
||||||
|
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const updateCampaignsStepId = "update-campaigns"
|
||||||
|
export const updateCampaignsStep = createStep(
|
||||||
|
updateCampaignsStepId,
|
||||||
|
async (data: UpdateCampaignDTO[], { container }) => {
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
|
||||||
|
const dataBeforeUpdate = await promotionModule.listCampaigns(
|
||||||
|
{ id: data.map((d) => d.id) },
|
||||||
|
{ relations, select: selects }
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedCampaigns = await promotionModule.updateCampaigns(data)
|
||||||
|
|
||||||
|
return new StepResponse(updatedCampaigns, dataBeforeUpdate)
|
||||||
|
},
|
||||||
|
async (dataBeforeUpdate, { container }) => {
|
||||||
|
if (!dataBeforeUpdate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: This still requires some sanitation of data and transformation of
|
||||||
|
// shapes for manytomany and oneToMany relations. Create a common util.
|
||||||
|
await promotionModule.updateCampaigns(dataBeforeUpdate)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService, UpdatePromotionDTO } from "@medusajs/types"
|
||||||
|
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const updatePromotionsStepId = "update-promotions"
|
||||||
|
export const updatePromotionsStep = createStep(
|
||||||
|
updatePromotionsStepId,
|
||||||
|
async (data: UpdatePromotionDTO[], { container }) => {
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
|
||||||
|
const dataBeforeUpdate = await promotionModule.list(
|
||||||
|
{ id: data.map((d) => d.id) },
|
||||||
|
{ relations, select: selects }
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedPromotions = await promotionModule.update(data)
|
||||||
|
|
||||||
|
return new StepResponse(updatedPromotions, dataBeforeUpdate)
|
||||||
|
},
|
||||||
|
async (dataBeforeUpdate, { container }) => {
|
||||||
|
if (!dataBeforeUpdate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: This still requires some sanitation of data and transformation of
|
||||||
|
// shapes for manytomany and oneToMany relations. Create a common util.
|
||||||
|
await promotionModule.update(dataBeforeUpdate)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { updateCampaignsWorkflow } from "@medusajs/core-flows"
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||||
@@ -17,3 +18,24 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
|
|
||||||
res.status(200).json({ campaign })
|
res.status(200).json({ campaign })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const updateCampaigns = updateCampaignsWorkflow(req.scope)
|
||||||
|
const campaignsData = [
|
||||||
|
{
|
||||||
|
id: req.params.id,
|
||||||
|
...(req.validatedBody || {}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { result, errors } = await updateCampaigns.run({
|
||||||
|
input: { campaignsData },
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ campaign: result[0] })
|
||||||
|
}
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
import { MedusaV2Flag } from "@medusajs/utils"
|
import { MedusaV2Flag } from "@medusajs/utils"
|
||||||
import { isFeatureFlagEnabled, transformQuery } from "../../../api/middlewares"
|
import {
|
||||||
|
isFeatureFlagEnabled,
|
||||||
|
transformBody,
|
||||||
|
transformQuery,
|
||||||
|
} from "../../../api/middlewares"
|
||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
AdminGetCampaignsCampaignParams,
|
AdminGetCampaignsCampaignParams,
|
||||||
AdminGetCampaignsParams,
|
AdminGetCampaignsParams,
|
||||||
|
AdminPostCampaignsCampaignReq,
|
||||||
|
AdminPostCampaignsReq,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
@@ -22,6 +28,11 @@ export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/campaigns",
|
||||||
|
middlewares: [transformBody(AdminPostCampaignsReq)],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/campaigns/:id",
|
matcher: "/admin/campaigns/:id",
|
||||||
@@ -32,4 +43,9 @@ export const adminCampaignRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/campaigns/:id",
|
||||||
|
middlewares: [transformBody(AdminPostCampaignsCampaignReq)],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { createCampaignsWorkflow } from "@medusajs/core-flows"
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
import { CreateCampaignDTO, IPromotionModuleService } from "@medusajs/types"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||||
|
|
||||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
@@ -21,3 +22,19 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
limit,
|
limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const createCampaigns = createCampaignsWorkflow(req.scope)
|
||||||
|
const campaignsData = [req.validatedBody as CreateCampaignDTO]
|
||||||
|
|
||||||
|
const { result, errors } = await createCampaigns.run({
|
||||||
|
input: { campaignsData },
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ campaign: result[0] })
|
||||||
|
}
|
||||||
@@ -1,4 +1,15 @@
|
|||||||
import { IsOptional, IsString } from "class-validator"
|
import { CampaignBudgetType } from "@medusajs/utils"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsDateString,
|
||||||
|
IsEnum,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||||
|
|
||||||
export class AdminGetCampaignsCampaignParams extends FindParams {}
|
export class AdminGetCampaignsCampaignParams extends FindParams {}
|
||||||
@@ -15,3 +26,93 @@ export class AdminGetCampaignsParams extends extendedFindParamsMixin({
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
currency?: string
|
currency?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AdminPostCampaignsReq {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
campaign_identifier?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
currency?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => CampaignBudget)
|
||||||
|
budget?: CampaignBudget
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString()
|
||||||
|
starts_at?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString()
|
||||||
|
ends_at?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => IdObject)
|
||||||
|
promotions?: IdObject[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IdObject {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CampaignBudget {
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(CampaignBudgetType)
|
||||||
|
type?: CampaignBudgetType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
limit?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostCampaignsCampaignReq {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
campaign_identifier?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
currency?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => CampaignBudget)
|
||||||
|
budget?: CampaignBudget
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString()
|
||||||
|
starts_at?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString()
|
||||||
|
ends_at?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => IdObject)
|
||||||
|
promotions?: IdObject[]
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { updatePromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
import { IPromotionModuleService } from "@medusajs/types"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||||
@@ -14,3 +15,24 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
|
|
||||||
res.status(200).json({ promotion })
|
res.status(200).json({ promotion })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const updatePromotions = updatePromotionsWorkflow(req.scope)
|
||||||
|
const promotionsData = [
|
||||||
|
{
|
||||||
|
id: req.params.id,
|
||||||
|
...(req.validatedBody || {}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { result, errors } = await updatePromotions.run({
|
||||||
|
input: { promotionsData },
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ promotion: result[0] })
|
||||||
|
}
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
import { MedusaV2Flag } from "@medusajs/utils"
|
import { MedusaV2Flag } from "@medusajs/utils"
|
||||||
import { isFeatureFlagEnabled, transformQuery } from "../../../api/middlewares"
|
|
||||||
|
import {
|
||||||
|
isFeatureFlagEnabled,
|
||||||
|
transformBody,
|
||||||
|
transformQuery,
|
||||||
|
} from "../../../api/middlewares"
|
||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
AdminGetPromotionsParams,
|
AdminGetPromotionsParams,
|
||||||
AdminGetPromotionsPromotionParams,
|
AdminGetPromotionsPromotionParams,
|
||||||
|
AdminPostPromotionsPromotionReq,
|
||||||
|
AdminPostPromotionsReq,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
@@ -22,6 +29,11 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/promotions",
|
||||||
|
middlewares: [transformBody(AdminPostPromotionsReq)],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/promotions/:id",
|
matcher: "/admin/promotions/:id",
|
||||||
@@ -32,4 +44,9 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/promotions/:id",
|
||||||
|
middlewares: [transformBody(AdminPostPromotionsPromotionReq)],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { createPromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { IPromotionModuleService } from "@medusajs/types"
|
import { CreatePromotionDTO, IPromotionModuleService } from "@medusajs/types"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||||
|
|
||||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
@@ -21,3 +22,19 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
limit,
|
limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const createPromotions = createPromotionsWorkflow(req.scope)
|
||||||
|
const promotionsData = [req.validatedBody as CreatePromotionDTO]
|
||||||
|
|
||||||
|
const { result, errors } = await createPromotions.run({
|
||||||
|
input: { promotionsData },
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ promotion: result[0] })
|
||||||
|
}
|
||||||
@@ -1,5 +1,25 @@
|
|||||||
import { IsOptional, IsString } from "class-validator"
|
import {
|
||||||
|
ApplicationMethodAllocation,
|
||||||
|
ApplicationMethodTargetType,
|
||||||
|
ApplicationMethodType,
|
||||||
|
PromotionRuleOperator,
|
||||||
|
PromotionType,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
|
IsEnum,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Validate,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||||
|
import { XorConstraint } from "../../../types/validators/xor"
|
||||||
|
import { AdminPostCampaignsReq } from "../campaigns/validators"
|
||||||
|
|
||||||
export class AdminGetPromotionsPromotionParams extends FindParams {}
|
export class AdminGetPromotionsPromotionParams extends FindParams {}
|
||||||
|
|
||||||
@@ -11,3 +31,122 @@ export class AdminGetPromotionsParams extends extendedFindParamsMixin({
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
code?: string
|
code?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AdminPostPromotionsReq {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
code: string
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
is_automatic?: boolean
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PromotionType)
|
||||||
|
type?: PromotionType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
campaign_id?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => AdminPostCampaignsReq)
|
||||||
|
campaign?: AdminPostCampaignsReq
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => ApplicationMethod)
|
||||||
|
application_method: ApplicationMethod
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => PromotionRule)
|
||||||
|
rules?: PromotionRule[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PromotionRule {
|
||||||
|
@IsEnum(PromotionRuleOperator)
|
||||||
|
operator: PromotionRuleOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string | null
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
attribute: string
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => String)
|
||||||
|
values: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApplicationMethod {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
value?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
max_quantity?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(ApplicationMethodType)
|
||||||
|
type?: ApplicationMethodType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(ApplicationMethodTargetType)
|
||||||
|
target_type?: ApplicationMethodTargetType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(ApplicationMethodAllocation)
|
||||||
|
allocation?: ApplicationMethodAllocation
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => PromotionRule)
|
||||||
|
target_rules?: PromotionRule[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostPromotionsPromotionReq {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
code?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
is_automatic?: boolean
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PromotionType)
|
||||||
|
type?: PromotionType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Validate(XorConstraint, ["campaign"])
|
||||||
|
@IsString()
|
||||||
|
campaign_id?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Validate(XorConstraint, ["campaign_id"])
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => AdminPostCampaignsReq)
|
||||||
|
campaign?: AdminPostCampaignsReq
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => ApplicationMethod)
|
||||||
|
application_method?: ApplicationMethod
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => PromotionRule)
|
||||||
|
rules?: PromotionRule[]
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
IsInt,
|
IsInt,
|
||||||
Min,
|
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsObject,
|
IsObject,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Min,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { Request, Response } from "express"
|
import { Request, Response } from "express"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
import { ProductCategoryService } from "../../../../services"
|
import { ProductCategoryService } from "../../../../services"
|
||||||
import { AdminProductCategoriesReqBase } from "../../../../types/product-category"
|
|
||||||
import { FindParams } from "../../../../types/common"
|
import { FindParams } from "../../../../types/common"
|
||||||
|
import { AdminProductCategoriesReqBase } from "../../../../types/product-category"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /admin/product-categories/{id}
|
* @oas [post] /admin/product-categories/{id}
|
||||||
|
|||||||
@@ -441,6 +441,7 @@ export default class PromotionModuleService<
|
|||||||
"rules",
|
"rules",
|
||||||
"rules.values",
|
"rules.values",
|
||||||
"campaign",
|
"campaign",
|
||||||
|
"campaign.budget",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
@@ -558,7 +559,7 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createPromotionRulesAndValues(
|
await this.createPromotionRulesAndValues_(
|
||||||
promotionCodeRulesDataMap.get(promotion.code) || [],
|
promotionCodeRulesDataMap.get(promotion.code) || [],
|
||||||
"promotions",
|
"promotions",
|
||||||
promotion,
|
promotion,
|
||||||
@@ -577,7 +578,7 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const applicationMethod of createdApplicationMethods) {
|
for (const applicationMethod of createdApplicationMethods) {
|
||||||
await this.createPromotionRulesAndValues(
|
await this.createPromotionRulesAndValues_(
|
||||||
applicationMethodRuleMap.get(applicationMethod.promotion.id) || [],
|
applicationMethodRuleMap.get(applicationMethod.promotion.id) || [],
|
||||||
"application_methods",
|
"application_methods",
|
||||||
applicationMethod,
|
applicationMethod,
|
||||||
@@ -614,9 +615,11 @@ export default class PromotionModuleService<
|
|||||||
relations: [
|
relations: [
|
||||||
"application_method",
|
"application_method",
|
||||||
"application_method.target_rules",
|
"application_method.target_rules",
|
||||||
|
"application_method.target_rules.values",
|
||||||
"rules",
|
"rules",
|
||||||
"rules.values",
|
"rules.values",
|
||||||
"campaign",
|
"campaign",
|
||||||
|
"campaign.budget",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
@@ -686,7 +689,10 @@ export default class PromotionModuleService<
|
|||||||
existingApplicationMethod.max_quantity,
|
existingApplicationMethod.max_quantity,
|
||||||
})
|
})
|
||||||
|
|
||||||
applicationMethodsData.push(applicationMethodData)
|
applicationMethodsData.push({
|
||||||
|
...applicationMethodData,
|
||||||
|
id: existingApplicationMethod.id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPromotions = this.promotionService_.update(
|
const updatedPromotions = this.promotionService_.update(
|
||||||
@@ -705,7 +711,6 @@ export default class PromotionModuleService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
@InjectTransactionManager("baseRepository_")
|
|
||||||
async addPromotionRules(
|
async addPromotionRules(
|
||||||
promotionId: string,
|
promotionId: string,
|
||||||
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
|
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
|
||||||
@@ -713,20 +718,21 @@ export default class PromotionModuleService<
|
|||||||
): Promise<PromotionTypes.PromotionDTO> {
|
): Promise<PromotionTypes.PromotionDTO> {
|
||||||
const promotion = await this.promotionService_.retrieve(promotionId)
|
const promotion = await this.promotionService_.retrieve(promotionId)
|
||||||
|
|
||||||
await this.createPromotionRulesAndValues(
|
await this.createPromotionRulesAndValues_(
|
||||||
rulesData,
|
rulesData,
|
||||||
"promotions",
|
"promotions",
|
||||||
promotion,
|
promotion,
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
return this.retrieve(promotionId, {
|
return this.retrieve(
|
||||||
relations: ["rules", "rules.values"],
|
promotionId,
|
||||||
})
|
{ relations: ["rules", "rules.values"] },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
@InjectTransactionManager("baseRepository_")
|
|
||||||
async addPromotionTargetRules(
|
async addPromotionTargetRules(
|
||||||
promotionId: string,
|
promotionId: string,
|
||||||
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
|
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
|
||||||
@@ -745,29 +751,34 @@ export default class PromotionModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createPromotionRulesAndValues(
|
await this.createPromotionRulesAndValues_(
|
||||||
rulesData,
|
rulesData,
|
||||||
"application_methods",
|
"application_methods",
|
||||||
applicationMethod,
|
applicationMethod,
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
return this.retrieve(promotionId, {
|
return this.retrieve(
|
||||||
relations: [
|
promotionId,
|
||||||
"rules",
|
{
|
||||||
"rules.values",
|
relations: [
|
||||||
"application_method",
|
"rules",
|
||||||
"application_method.target_rules",
|
"rules.values",
|
||||||
"application_method.target_rules.values",
|
"application_method",
|
||||||
],
|
"application_method.target_rules",
|
||||||
})
|
"application_method.target_rules.values",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async createPromotionRulesAndValues(
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
protected async createPromotionRulesAndValues_(
|
||||||
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
|
rulesData: PromotionTypes.CreatePromotionRuleDTO[],
|
||||||
relationName: "promotions" | "application_methods",
|
relationName: "promotions" | "application_methods",
|
||||||
relation: Promotion | ApplicationMethod,
|
relation: Promotion | ApplicationMethod,
|
||||||
sharedContext: Context
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
validatePromotionRuleAttributes(rulesData)
|
validatePromotionRuleAttributes(rulesData)
|
||||||
|
|
||||||
@@ -789,7 +800,10 @@ export default class PromotionModuleService<
|
|||||||
promotion_rule: createdPromotionRule,
|
promotion_rule: createdPromotionRule,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await this.promotionRuleValueService_.create(promotionRuleValuesData)
|
await this.promotionRuleValueService_.create(
|
||||||
|
promotionRuleValuesData,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,7 +994,7 @@ export default class PromotionModuleService<
|
|||||||
const campaigns = await this.listCampaigns(
|
const campaigns = await this.listCampaigns(
|
||||||
{ id: createdCampaigns.map((p) => p!.id) },
|
{ id: createdCampaigns.map((p) => p!.id) },
|
||||||
{
|
{
|
||||||
relations: ["budget"],
|
relations: ["budget", "promotions"],
|
||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
import { getSelectsAndRelationsFromObjectArray } from "../get-selects-and-relations-from-object-array"
|
||||||
|
|
||||||
|
describe("getSelectsAndRelationsFromObjectArray", function () {
|
||||||
|
it("should return true or false for different types of data", function () {
|
||||||
|
const expectations = [
|
||||||
|
{
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
attr_string: "string",
|
||||||
|
attr_boolean: true,
|
||||||
|
attr_null: null,
|
||||||
|
attr_undefined: undefined,
|
||||||
|
attr_object: {
|
||||||
|
attr_string: "string",
|
||||||
|
attr_boolean: true,
|
||||||
|
attr_null: null,
|
||||||
|
attr_undefined: undefined,
|
||||||
|
},
|
||||||
|
attr_array: [
|
||||||
|
{
|
||||||
|
attr_object: {
|
||||||
|
attr_string: "string",
|
||||||
|
attr_boolean: true,
|
||||||
|
attr_null: null,
|
||||||
|
attr_undefined: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attr_object: {
|
||||||
|
attr_string: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
selects: [
|
||||||
|
"attr_string",
|
||||||
|
"attr_boolean",
|
||||||
|
"attr_null",
|
||||||
|
"attr_undefined",
|
||||||
|
"attr_object.attr_string",
|
||||||
|
"attr_object.attr_boolean",
|
||||||
|
"attr_object.attr_null",
|
||||||
|
"attr_object.attr_undefined",
|
||||||
|
"attr_array.attr_object.attr_string",
|
||||||
|
"attr_array.attr_object.attr_boolean",
|
||||||
|
"attr_array.attr_object.attr_null",
|
||||||
|
"attr_array.attr_object.attr_undefined",
|
||||||
|
],
|
||||||
|
relations: ["attr_object", "attr_array", "attr_array.attr_object"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
expectations.forEach((expectation) => {
|
||||||
|
expect(getSelectsAndRelationsFromObjectArray(expectation.input)).toEqual(
|
||||||
|
expectation.output
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { deduplicate } from "./deduplicate"
|
||||||
|
import { isObject } from "./is-object"
|
||||||
|
|
||||||
|
export function getSelectsAndRelationsFromObjectArray(
|
||||||
|
dataArray: object[],
|
||||||
|
prefix?: string
|
||||||
|
): {
|
||||||
|
selects: string[]
|
||||||
|
relations: string[]
|
||||||
|
} {
|
||||||
|
const selects: string[] = []
|
||||||
|
const relations: string[] = []
|
||||||
|
|
||||||
|
for (const data of dataArray) {
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
if (isObject(value)) {
|
||||||
|
relations.push(setKey(key, prefix))
|
||||||
|
const res = getSelectsAndRelationsFromObjectArray(
|
||||||
|
[value],
|
||||||
|
setKey(key, prefix)
|
||||||
|
)
|
||||||
|
selects.push(...res.selects)
|
||||||
|
relations.push(...res.relations)
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
relations.push(setKey(key, prefix))
|
||||||
|
const res = getSelectsAndRelationsFromObjectArray(
|
||||||
|
value,
|
||||||
|
setKey(key, prefix)
|
||||||
|
)
|
||||||
|
selects.push(...res.selects)
|
||||||
|
relations.push(...res.relations)
|
||||||
|
} else {
|
||||||
|
selects.push(setKey(key, prefix))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueSelects: string[] = deduplicate(selects)
|
||||||
|
const uniqueRelations: string[] = deduplicate(relations)
|
||||||
|
|
||||||
|
return {
|
||||||
|
selects: uniqueSelects,
|
||||||
|
relations: uniqueRelations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKey(key: string, prefix?: string) {
|
||||||
|
if (prefix) {
|
||||||
|
return `${prefix}.${key}`
|
||||||
|
} else {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ export * from "./errors"
|
|||||||
export * from "./generate-entity-id"
|
export * from "./generate-entity-id"
|
||||||
export * from "./get-config-file"
|
export * from "./get-config-file"
|
||||||
export * from "./get-iso-string-from-date"
|
export * from "./get-iso-string-from-date"
|
||||||
|
export * from "./get-selects-and-relations-from-object-array"
|
||||||
export * from "./group-by"
|
export * from "./group-by"
|
||||||
export * from "./handle-postgres-database-error"
|
export * from "./handle-postgres-database-error"
|
||||||
export * from "./is-date"
|
export * from "./is-date"
|
||||||
|
|||||||
Reference in new issue
Block a user