feat(medusa,types): add promotion list/get endpoint (#6110)

what:

- adds get promotion endpoint (RESOLVES CORE-1677)
- adds list promotions endpoint (RESOLVES CORE-1676)
- uses new API routes
This commit is contained in:
Riqwan Thamir
2024-01-18 17:01:19 +01:00
committed by GitHub
parent 6941627679
commit a12c28b7d5
20 changed files with 809 additions and 23 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/types": patch
---
feat(medusa,types): add promotion list and get endpoint

View File

@@ -0,0 +1,122 @@
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("GET /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 get all promotions and its count", async () => {
await promotionModuleService.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
},
])
const api = useApi() as any
const response = await api.get(`/admin/promotions`, adminHeaders)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.promotions).toEqual([
expect.objectContaining({
id: expect.any(String),
code: "TEST",
campaign: null,
is_automatic: false,
type: "standard",
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
application_method: expect.objectContaining({
id: expect.any(String),
value: 100,
type: "fixed",
target_type: "order",
allocation: null,
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
}),
}),
])
})
it("should get all promotions and its count filtered", async () => {
const [createdPromotion] = await promotionModuleService.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
},
])
const api = useApi() as any
const response = await api.get(
`/admin/promotions?fields=code,created_at,application_method.id`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.promotions).toEqual([
{
id: expect.any(String),
code: "TEST",
created_at: expect.any(String),
application_method: {
id: expect.any(String),
promotion: expect.any(Object),
},
},
])
})
})

View File

@@ -0,0 +1,124 @@
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("GET /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 id does not exist", async () => {
const api = useApi() as any
const { response } = await api
.get(`/admin/promotions/does-not-exist`, adminHeaders)
.catch((e) => e)
expect(response.status).toEqual(404)
expect(response.data.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})
it("should get the requested promotion", async () => {
const createdPromotion = await promotionModuleService.create({
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
})
const api = useApi() as any
const response = await api.get(
`/admin/promotions/${createdPromotion.id}`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.promotion).toEqual({
id: expect.any(String),
code: "TEST",
campaign: null,
is_automatic: false,
type: "standard",
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
application_method: {
id: expect.any(String),
promotion: expect.any(Object),
value: 100,
type: "fixed",
target_type: "order",
max_quantity: 0,
allocation: null,
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
},
})
})
it("should get the requested promotion with filtered fields and relations", async () => {
const createdPromotion = await promotionModuleService.create({
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
})
const api = useApi() as any
const response = await api.get(
`/admin/promotions/${createdPromotion.id}?fields=id,code&expand=`,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.promotion).toEqual({
id: expect.any(String),
code: "TEST",
})
})
})

View File

@@ -66,5 +66,10 @@ module.exports = {
resources: "shared",
resolve: "@medusajs/pricing",
},
[Modules.PROMOTION]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/promotion",
},
},
}

View File

@@ -16,6 +16,8 @@
"@medusajs/modules-sdk": "workspace:^",
"@medusajs/pricing": "workspace:^",
"@medusajs/product": "workspace:^",
"@medusajs/promotion": "workspace:^",
"@medusajs/utils": "workspace:^",
"faker": "^5.5.3",
"medusa-fulfillment-webshipper": "workspace:*",
"medusa-interfaces": "workspace:*",
@@ -27,6 +29,7 @@
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"@medusajs/types": "workspace:^",
"babel-preset-medusa-package": "*",
"jest": "^26.6.3",
"jest-environment-node": "26.6.2"

View File

@@ -0,0 +1,16 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const promotionModuleService: IPromotionModuleService = req.scope.resolve(
ModuleRegistrationName.PROMOTION
)
const promotion = await promotionModuleService.retrieve(req.params.id, {
select: req.retrieveConfig.select,
relations: req.retrieveConfig.relations,
})
res.status(200).json({ promotion })
}

View File

@@ -0,0 +1,35 @@
import { MedusaV2Flag } from "@medusajs/utils"
import { isFeatureFlagEnabled, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import * as QueryConfig from "./query-config"
import {
AdminGetPromotionsParams,
AdminGetPromotionsPromotionParams,
} from "./validators"
export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
{
matcher: "/admin/promotions*",
middlewares: [isFeatureFlagEnabled(MedusaV2Flag.key)],
},
{
method: ["GET"],
matcher: "/admin/promotions",
middlewares: [
transformQuery(
AdminGetPromotionsParams,
QueryConfig.listTransformQueryConfig
),
],
},
{
method: ["GET"],
matcher: "/admin/promotions/:id",
middlewares: [
transformQuery(
AdminGetPromotionsPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
]

View File

@@ -0,0 +1,26 @@
export const defaultAdminPromotionRelations = ["campaign", "application_method"]
export const allowedAdminPromotionRelations = [
...defaultAdminPromotionRelations,
]
export const defaultAdminPromotionFields = [
"id",
"code",
"campaign",
"is_automatic",
"type",
"created_at",
"updated_at",
"deleted_at",
]
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminPromotionFields,
defaultRelations: defaultAdminPromotionRelations,
allowedRelations: allowedAdminPromotionRelations,
isList: false,
}
export const listTransformQueryConfig = {
...retrieveTransformQueryConfig,
isList: true,
}

View File

@@ -0,0 +1,23 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const promotionModuleService: IPromotionModuleService = req.scope.resolve(
ModuleRegistrationName.PROMOTION
)
const [promotions, count] = await promotionModuleService.listAndCount(
req.filterableFields,
req.listConfig
)
const { limit, offset } = req.validatedQuery
res.json({
count,
promotions,
offset,
limit,
})
}

View File

@@ -0,0 +1,13 @@
import { IsOptional, IsString } from "class-validator"
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
export class AdminGetPromotionsPromotionParams extends FindParams {}
export class AdminGetPromotionsParams extends extendedFindParamsMixin({
limit: 100,
offset: 0,
}) {
@IsString()
@IsOptional()
code?: string
}

View File

@@ -0,0 +1,6 @@
import { MiddlewaresConfig } from "../loaders/helpers/routing/types"
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
export const config: MiddlewaresConfig = {
routes: [...adminPromotionRoutesMiddlewares],
}

View File

@@ -6,11 +6,12 @@ import { default as requireCustomerAuthentication } from "./require-customer-aut
export { default as authenticate } from "./authenticate"
export { default as authenticateCustomer } from "./authenticate-customer"
export { default as errorHandler } from "./error-handler"
export { default as wrapHandler } from "./await-middleware"
export { canAccessBatchJob } from "./batch-job/can-access-batch-job"
export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job"
export { doesConditionBelongToDiscount } from "./discount/does-condition-belong-to-discount"
export { default as errorHandler } from "./error-handler"
export { isFeatureFlagEnabled } from "./feature-flag-enabled"
export { default as normalizeQuery } from "./normalized-query"
export { default as requireCustomerAuthentication } from "./require-customer-authentication"
export { transformBody } from "./transform-body"

View File

@@ -1,8 +1,8 @@
import { AwilixContainer } from "awilix"
import bodyParser from "body-parser"
import { Express } from "express"
import qs from "qs"
import bodyParser from "body-parser"
import routes from "../api"
import { AwilixContainer } from "awilix"
import { ConfigModule } from "../types/global"
type Options = {

View File

@@ -13,6 +13,7 @@ import { asValue } from "awilix"
import { createMedusaContainer } from "medusa-core-utils"
import { track } from "medusa-telemetry"
import { EOL } from "os"
import path from "path"
import requestIp from "request-ip"
import { Connection } from "typeorm"
import { MedusaContainer } from "../types/global"
@@ -21,6 +22,7 @@ import loadConfig from "./config"
import defaultsLoader from "./defaults"
import expressLoader from "./express"
import featureFlagsLoader from "./feature-flags"
import { RoutesLoader } from "./helpers/routing"
import Logger from "./logger"
import loadMedusaApp, { mergeDefaultModules } from "./medusa-app"
import modelsLoader from "./models"
@@ -195,6 +197,22 @@ export default async ({
next()
})
// TODO: Figure out why this is causing issues with test when placed inside ./api.ts
// Adding this here temporarily
// Test: (packages/medusa/src/api/routes/admin/currencies/update-currency.ts)
try {
/**
* Register the Medusa CORE API routes using the file based routing.
*/
await new RoutesLoader({
app: expressApp,
rootDir: path.join(__dirname, "../api-v2"),
configModule,
}).load()
} catch (err) {
throw Error("An error occurred while registering Medusa Core API Routes")
}
const pluginsActivity = Logger.activity(`Initializing plugins${EOL}`)
track("PLUGINS_INIT_STARTED")
await pluginsLoader({

View File

@@ -1,5 +1,9 @@
import { IPromotionModuleService } from "@medusajs/types"
import { CampaignBudgetType, PromotionType } from "@medusajs/utils"
import {
ApplicationMethodType,
CampaignBudgetType,
PromotionType,
} from "@medusajs/utils"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { initialize } from "../../../../src"
import { createCampaigns } from "../../../__fixtures__/campaigns"
@@ -680,6 +684,83 @@ describe("Promotion Service", () => {
})
})
describe("listAndCount", () => {
beforeEach(async () => {
await createPromotions(repositoryManager, [
{
id: "promotion-id-1",
code: "PROMOTION_1",
type: PromotionType.STANDARD,
application_method: {
type: ApplicationMethodType.FIXED,
value: "200",
target_type: "items",
},
},
{
id: "promotion-id-2",
code: "PROMOTION_2",
type: PromotionType.STANDARD,
},
])
})
it("should return all promotions and count", async () => {
const [promotions, count] = await service.listAndCount()
expect(count).toEqual(2)
expect(promotions).toEqual([
{
id: "promotion-id-1",
code: "PROMOTION_1",
campaign: null,
is_automatic: false,
type: "standard",
application_method: expect.any(String),
created_at: expect.any(Date),
updated_at: expect.any(Date),
deleted_at: null,
},
{
id: "promotion-id-2",
code: "PROMOTION_2",
campaign: null,
is_automatic: false,
type: "standard",
application_method: null,
created_at: expect.any(Date),
updated_at: expect.any(Date),
deleted_at: null,
},
])
})
it("should return all promotions based on config select and relations param", async () => {
const [promotions, count] = await service.listAndCount(
{
id: ["promotion-id-1"],
},
{
relations: ["application_method"],
select: ["code", "application_method.type"],
}
)
expect(count).toEqual(1)
expect(promotions).toEqual([
{
id: "promotion-id-1",
code: "PROMOTION_1",
application_method: {
id: expect.any(String),
promotion: expect.any(Object),
type: "fixed",
},
},
])
})
})
describe("delete", () => {
beforeEach(async () => {
await createPromotions(repositoryManager)

View File

@@ -2,6 +2,247 @@
"namespaces": ["public"],
"name": "public",
"tables": [
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"name": {
"name": "name",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"description": {
"name": "description",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"currency": {
"name": "currency",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"campaign_identifier": {
"name": "campaign_identifier",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"starts_at": {
"name": "starts_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
},
"ends_at": {
"name": "ends_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "campaign",
"schema": "public",
"indexes": [
{
"keyName": "IDX_campaign_identifier_unique",
"columnNames": ["campaign_identifier"],
"composite": false,
"primary": false,
"unique": true
},
{
"keyName": "campaign_pkey",
"columnNames": ["id"],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"type": {
"name": "type",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": ["spend", "usage"],
"mappedType": "enum"
},
"campaign_id": {
"name": "campaign_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"limit": {
"name": "limit",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"default": "null",
"mappedType": "decimal"
},
"used": {
"name": "used",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "0",
"mappedType": "decimal"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "campaign_budget",
"schema": "public",
"indexes": [
{
"columnNames": ["type"],
"composite": false,
"keyName": "IDX_campaign_budget_type",
"primary": false,
"unique": false
},
{
"columnNames": ["campaign_id"],
"composite": false,
"keyName": "campaign_budget_campaign_id_unique",
"primary": false,
"unique": true
},
{
"keyName": "campaign_budget_pkey",
"columnNames": ["id"],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"campaign_budget_campaign_id_foreign": {
"constraintName": "campaign_budget_campaign_id_foreign",
"columnNames": ["campaign_id"],
"localTableName": "public.campaign_budget",
"referencedColumnNames": ["id"],
"referencedTableName": "public.campaign",
"updateRule": "cascade"
}
}
},
{
"columns": {
"id": {
@@ -22,6 +263,15 @@
"nullable": false,
"mappedType": "text"
},
"campaign_id": {
"name": "campaign_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"is_automatic": {
"name": "is_automatic",
"type": "boolean",
@@ -108,7 +358,17 @@
}
],
"checks": [],
"foreignKeys": {}
"foreignKeys": {
"promotion_campaign_id_foreign": {
"constraintName": "promotion_campaign_id_foreign",
"columnNames": ["campaign_id"],
"localTableName": "public.promotion",
"referencedColumnNames": ["id"],
"referencedTableName": "public.campaign",
"deleteRule": "set null",
"updateRule": "cascade"
}
}
},
{
"columns": {
@@ -156,7 +416,7 @@
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": ["order", "shipping", "item"],
"enumItems": ["order", "shipping_methods", "items"],
"mappedType": "enum"
},
"allocation": {

View File

@@ -1,9 +1,26 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20240102130345 extends Migration {
export class Migration20240117090706 extends Migration {
async up(): Promise<void> {
this.addSql(
'create table "promotion" ("id" text not null, "code" text not null, "is_automatic" boolean not null default false, "type" text check ("type" in (\'standard\', \'buyget\')) not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_pkey" primary key ("id"));'
'create table "campaign" ("id" text not null, "name" text not null, "description" text null, "currency" text null, "campaign_identifier" text not null, "starts_at" timestamptz null, "ends_at" timestamptz null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "campaign_pkey" primary key ("id"));'
)
this.addSql(
'alter table "campaign" add constraint "IDX_campaign_identifier_unique" unique ("campaign_identifier");'
)
this.addSql(
'create table "campaign_budget" ("id" text not null, "type" text check ("type" in (\'spend\', \'usage\')) not null, "campaign_id" text not null, "limit" numeric null default null, "used" numeric not null default 0, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "campaign_budget_pkey" primary key ("id"));'
)
this.addSql(
'create index "IDX_campaign_budget_type" on "campaign_budget" ("type");'
)
this.addSql(
'alter table "campaign_budget" add constraint "campaign_budget_campaign_id_unique" unique ("campaign_id");'
)
this.addSql(
'create table "promotion" ("id" text not null, "code" text not null, "campaign_id" text null, "is_automatic" boolean not null default false, "type" text check ("type" in (\'standard\', \'buyget\')) not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_pkey" primary key ("id"));'
)
this.addSql('create index "IDX_promotion_code" on "promotion" ("code");')
this.addSql('create index "IDX_promotion_type" on "promotion" ("type");')
@@ -12,7 +29,7 @@ export class Migration20240102130345 extends Migration {
)
this.addSql(
'create table "application_method" ("id" text not null, "value" numeric null, "max_quantity" numeric null, "type" text check ("type" in (\'fixed\', \'percentage\')) not null, "target_type" text check ("target_type" in (\'order\', \'shipping\', \'item\')) not null, "allocation" text check ("allocation" in (\'each\', \'across\')) null, "promotion_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "application_method_pkey" primary key ("id"));'
'create table "application_method" ("id" text not null, "value" numeric null, "max_quantity" numeric null, "type" text check ("type" in (\'fixed\', \'percentage\')) not null, "target_type" text check ("target_type" in (\'order\', \'shipping_methods\', \'items\')) not null, "allocation" text check ("allocation" in (\'each\', \'across\')) null, "promotion_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "application_method_pkey" primary key ("id"));'
)
this.addSql(
'create index "IDX_application_method_type" on "application_method" ("type");'
@@ -52,6 +69,14 @@ export class Migration20240102130345 extends Migration {
'create index "IDX_promotion_rule_promotion_rule_value_id" on "promotion_rule_value" ("promotion_rule_id");'
)
this.addSql(
'alter table "campaign_budget" add constraint "campaign_budget_campaign_id_foreign" foreign key ("campaign_id") references "campaign" ("id") on update cascade;'
)
this.addSql(
'alter table "promotion" add constraint "promotion_campaign_id_foreign" foreign key ("campaign_id") references "campaign" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table "application_method" add constraint "application_method_promotion_id_foreign" foreign key ("promotion_id") references "promotion" ("id") on update cascade;'
)

View File

@@ -368,9 +368,7 @@ export default class PromotionModuleService<
return await this.baseRepository_.serialize<PromotionTypes.PromotionDTO>(
promotion,
{
populate: true,
}
{ populate: true }
)
}
@@ -388,12 +386,31 @@ export default class PromotionModuleService<
return await this.baseRepository_.serialize<PromotionTypes.PromotionDTO[]>(
promotions,
{
populate: true,
}
{ populate: true }
)
}
@InjectManager("baseRepository_")
async listAndCount(
filters: PromotionTypes.FilterablePromotionProps = {},
config: FindConfig<PromotionTypes.PromotionDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[PromotionTypes.PromotionDTO[], number]> {
const [promotions, count] = await this.promotionService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<PromotionTypes.PromotionDTO[]>(
promotions,
{ populate: true }
),
count,
]
}
async create(
data: PromotionTypes.CreatePromotionDTO,
sharedContext?: Context
@@ -899,9 +916,7 @@ export default class PromotionModuleService<
return await this.baseRepository_.serialize<PromotionTypes.CampaignDTO>(
campaign,
{
populate: true,
}
{ populate: true }
)
}
@@ -919,9 +934,7 @@ export default class PromotionModuleService<
return await this.baseRepository_.serialize<PromotionTypes.CampaignDTO[]>(
campaigns,
{
populate: true,
}
{ populate: true }
)
}

View File

@@ -51,6 +51,12 @@ export interface IPromotionModuleService extends IModuleService {
sharedContext?: Context
): Promise<PromotionDTO[]>
listAndCount(
filters?: FilterablePromotionProps,
config?: FindConfig<PromotionDTO>,
sharedContext?: Context
): Promise<[PromotionDTO[], number]>
retrieve(
id: string,
config?: FindConfig<PromotionDTO>,

View File

@@ -8462,7 +8462,7 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/promotion@workspace:packages/promotion":
"@medusajs/promotion@workspace:^, @medusajs/promotion@workspace:packages/promotion":
version: 0.0.0-use.local
resolution: "@medusajs/promotion@workspace:packages/promotion"
dependencies:
@@ -8637,7 +8637,7 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/utils@^1.10.5, @medusajs/utils@^1.11.1, @medusajs/utils@^1.11.2, @medusajs/utils@^1.11.3, @medusajs/utils@^1.9.2, @medusajs/utils@^1.9.4, @medusajs/utils@workspace:packages/utils":
"@medusajs/utils@^1.10.5, @medusajs/utils@^1.11.1, @medusajs/utils@^1.11.2, @medusajs/utils@^1.11.3, @medusajs/utils@^1.9.2, @medusajs/utils@^1.9.4, @medusajs/utils@workspace:^, @medusajs/utils@workspace:packages/utils":
version: 0.0.0-use.local
resolution: "@medusajs/utils@workspace:packages/utils"
dependencies:
@@ -31241,6 +31241,9 @@ __metadata:
"@medusajs/modules-sdk": "workspace:^"
"@medusajs/pricing": "workspace:^"
"@medusajs/product": "workspace:^"
"@medusajs/promotion": "workspace:^"
"@medusajs/types": "workspace:^"
"@medusajs/utils": "workspace:^"
babel-preset-medusa-package: "*"
faker: ^5.5.3
jest: ^26.6.3