diff --git a/.changeset/sour-donuts-fail.md b/.changeset/sour-donuts-fail.md new file mode 100644 index 0000000000..fddfb4127a --- /dev/null +++ b/.changeset/sour-donuts-fail.md @@ -0,0 +1,6 @@ +--- +"@medusajs/types": patch +"@medusajs/utils": patch +--- + +feat(types,utils): add promotions create with application method diff --git a/packages/promotion/integration-tests/__fixtures__/promotion/data.ts b/packages/promotion/integration-tests/__fixtures__/promotion/data.ts new file mode 100644 index 0000000000..f340c73dcf --- /dev/null +++ b/packages/promotion/integration-tests/__fixtures__/promotion/data.ts @@ -0,0 +1,14 @@ +import { PromotionType } from "@medusajs/utils" + +export const defaultPromotionsData = [ + { + id: "promotion-id-1", + code: "PROMOTION_1", + type: PromotionType.STANDARD, + }, + { + id: "promotion-id-2", + code: "PROMOTION_2", + type: PromotionType.STANDARD, + }, +] diff --git a/packages/promotion/integration-tests/__fixtures__/promotion/index.ts b/packages/promotion/integration-tests/__fixtures__/promotion/index.ts new file mode 100644 index 0000000000..80ef2f7e66 --- /dev/null +++ b/packages/promotion/integration-tests/__fixtures__/promotion/index.ts @@ -0,0 +1,22 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { Promotion } from "@models" +import { defaultPromotionsData } from "./data" + +export * from "./data" + +export async function createPromotions( + manager: SqlEntityManager, + promotionsData = defaultPromotionsData +): Promise { + const promotion: Promotion[] = [] + + for (let promotionData of promotionsData) { + let promotion = manager.create(Promotion, promotionData) + + manager.persist(promotion) + + await manager.flush() + } + + return promotion +} diff --git a/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts b/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts new file mode 100644 index 0000000000..1f64f0352c --- /dev/null +++ b/packages/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts @@ -0,0 +1,132 @@ +import { IPromotionModuleService } from "@medusajs/types" +import { PromotionType } from "@medusajs/utils" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { initialize } from "../../../../src" +import { DB_URL, MikroOrmWrapper } from "../../../utils" + +jest.setTimeout(30000) + +describe("Promotion Service", () => { + let service: IPromotionModuleService + let repositoryManager: SqlEntityManager + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + repositoryManager = MikroOrmWrapper.forkManager() + + service = await initialize({ + database: { + clientUrl: DB_URL, + schema: process.env.MEDUSA_PROMOTION_DB_SCHEMA, + }, + }) + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + describe("create", () => { + it("should throw an error when required params are not passed", async () => { + const error = await service + .create([ + { + type: PromotionType.STANDARD, + } as any, + ]) + .catch((e) => e) + + expect(error.message).toContain( + "Value for Promotion.code is required, 'undefined' found" + ) + }) + + it("should create a basic promotion successfully", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + }, + ]) + + const [promotion] = await service.list({ + id: [createdPromotion.id], + }) + + expect(promotion).toEqual( + expect.objectContaining({ + code: "PROMOTION_TEST", + is_automatic: false, + type: "standard", + }) + ) + }) + + it("should create a promotion with order application method successfully", async () => { + const [createdPromotion] = await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + application_method: { + type: "fixed", + target_type: "order", + value: 100, + }, + }, + ]) + + const [promotion] = await service.list({ + id: [createdPromotion.id], + }) + + expect(promotion).toEqual( + expect.objectContaining({ + code: "PROMOTION_TEST", + is_automatic: false, + type: "standard", + }) + ) + }) + + it("should throw error when creating an item application method without allocation", async () => { + const error = await service + .create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + application_method: { + type: "fixed", + target_type: "item", + value: 100, + }, + }, + ]) + .catch((e) => e) + + expect(error.message).toContain( + "application_method.allocation should be either 'across OR each' when application_method.target_type is either 'shipping OR item'" + ) + }) + + it("should throw error when creating an item application, each allocation, without max quanity", async () => { + const error = await service + .create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + application_method: { + type: "fixed", + allocation: "each", + target_type: "shipping", + value: 100, + }, + }, + ]) + .catch((e) => e) + + expect(error.message).toContain( + "application_method.max_quantity is required when application_method.allocation is 'each'" + ) + }) + }) +}) diff --git a/packages/promotion/integration-tests/__tests__/services/promotion/index.spec.ts b/packages/promotion/integration-tests/__tests__/services/promotion/index.spec.ts new file mode 100644 index 0000000000..0f47bdae3a --- /dev/null +++ b/packages/promotion/integration-tests/__tests__/services/promotion/index.spec.ts @@ -0,0 +1,71 @@ +import { PromotionType } from "@medusajs/utils" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { PromotionRepository } from "@repositories" +import { PromotionService } from "@services" +import { createPromotions } from "../../../__fixtures__/promotion" +import { MikroOrmWrapper } from "../../../utils" + +jest.setTimeout(30000) + +describe("Promotion Service", () => { + let service: PromotionService + let testManager: SqlEntityManager + let repositoryManager: SqlEntityManager + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + repositoryManager = await MikroOrmWrapper.forkManager() + testManager = await MikroOrmWrapper.forkManager() + + const promotionRepository = new PromotionRepository({ + manager: repositoryManager, + }) + + service = new PromotionService({ + promotionRepository: promotionRepository, + }) + + await createPromotions(testManager) + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + describe("create", () => { + it("should throw an error when required params are not passed", async () => { + const error = await service + .create([ + { + type: PromotionType.STANDARD, + } as any, + ]) + .catch((e) => e) + + expect(error.message).toContain( + "Value for Promotion.code is required, 'undefined' found" + ) + }) + + it("should create a promotion successfully", async () => { + await service.create([ + { + code: "PROMOTION_TEST", + type: PromotionType.STANDARD, + }, + ]) + + const [promotion] = await service.list({ + code: ["PROMOTION_TEST"], + }) + + expect(promotion).toEqual( + expect.objectContaining({ + code: "PROMOTION_TEST", + is_automatic: false, + type: "standard", + }) + ) + }) + }) +}) diff --git a/packages/promotion/src/initialize/index.ts b/packages/promotion/src/initialize/index.ts index 5a7caa6bf9..af913a2118 100644 --- a/packages/promotion/src/initialize/index.ts +++ b/packages/promotion/src/initialize/index.ts @@ -10,7 +10,11 @@ import { moduleDefinition } from "../module-definition" import { InitializeModuleInjectableDependencies } from "../types" export const initialize = async ( - options?: ModulesSdkTypes.ModuleBootstrapDeclaration, + options?: + | ModulesSdkTypes.ModuleServiceInitializeOptions + | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions + | ExternalModuleDeclaration + | InternalModuleDeclaration, injectedDependencies?: InitializeModuleInjectableDependencies ): Promise => { const loaded = await MedusaModule.bootstrap({ diff --git a/packages/promotion/src/loaders/connection.ts b/packages/promotion/src/loaders/connection.ts index a1b7cf9790..fbbff60e87 100644 --- a/packages/promotion/src/loaders/connection.ts +++ b/packages/promotion/src/loaders/connection.ts @@ -1,4 +1,8 @@ -import { InternalModuleDeclaration, LoaderOptions, Modules } from "@medusajs/modules-sdk" +import { + InternalModuleDeclaration, + LoaderOptions, + Modules, +} from "@medusajs/modules-sdk" import { ModulesSdkTypes } from "@medusajs/types" import { ModulesSdkUtils } from "@medusajs/utils" import { EntitySchema } from "@mikro-orm/core" diff --git a/packages/promotion/src/loaders/container.ts b/packages/promotion/src/loaders/container.ts index ac06bd7677..aa9eac90d7 100644 --- a/packages/promotion/src/loaders/container.ts +++ b/packages/promotion/src/loaders/container.ts @@ -3,6 +3,7 @@ import * as defaultRepositories from "@repositories" import { LoaderOptions } from "@medusajs/modules-sdk" import { ModulesSdkTypes } from "@medusajs/types" import { loadCustomRepositories } from "@medusajs/utils" +import * as defaultServices from "@services" import { asClass } from "awilix" export default async ({ @@ -17,7 +18,10 @@ export default async ({ )?.repositories container.register({ - // promotionService: asClass(defaultServices.PromotionService).singleton(), + promotionService: asClass(defaultServices.PromotionService).singleton(), + applicationMethodService: asClass( + defaultServices.ApplicationMethodService + ).singleton(), }) if (customRepositories) { @@ -34,5 +38,11 @@ export default async ({ function loadDefaultRepositories({ container }) { container.register({ baseRepository: asClass(defaultRepositories.BaseRepository).singleton(), + applicationMethodRepository: asClass( + defaultRepositories.ApplicationMethodRepository + ).singleton(), + promotionRepository: asClass( + defaultRepositories.PromotionRepository + ).singleton(), }) } diff --git a/packages/promotion/src/migrations/.snapshot-medusa-promotion.json b/packages/promotion/src/migrations/.snapshot-medusa-promotion.json new file mode 100644 index 0000000000..bb21f5e21d --- /dev/null +++ b/packages/promotion/src/migrations/.snapshot-medusa-promotion.json @@ -0,0 +1,303 @@ +{ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "code": { + "name": "code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "is_automatic": { + "name": "is_automatic", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "standard", + "buyget" + ], + "mappedType": "enum" + }, + "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": "promotion", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "code" + ], + "composite": false, + "keyName": "IDX_promotion_code", + "primary": false, + "unique": false + }, + { + "columnNames": [ + "type" + ], + "composite": false, + "keyName": "IDX_promotion_type", + "primary": false, + "unique": false + }, + { + "keyName": "IDX_promotion_code_unique", + "columnNames": [ + "code" + ], + "composite": false, + "primary": false, + "unique": true + }, + { + "keyName": "promotion_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" + }, + "value": { + "name": "value", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "max_quantity": { + "name": "max_quantity", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "fixed", + "percentage" + ], + "mappedType": "enum" + }, + "target_type": { + "name": "target_type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "order", + "shipping", + "item" + ], + "mappedType": "enum" + }, + "allocation": { + "name": "allocation", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "enumItems": [ + "each", + "across" + ], + "mappedType": "enum" + }, + "promotion_id": { + "name": "promotion_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "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": "application_method", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "type" + ], + "composite": false, + "keyName": "IDX_application_method_type", + "primary": false, + "unique": false + }, + { + "columnNames": [ + "target_type" + ], + "composite": false, + "keyName": "IDX_application_method_target_type", + "primary": false, + "unique": false + }, + { + "columnNames": [ + "allocation" + ], + "composite": false, + "keyName": "IDX_application_method_allocation", + "primary": false, + "unique": false + }, + { + "columnNames": [ + "promotion_id" + ], + "composite": false, + "keyName": "application_method_promotion_id_unique", + "primary": false, + "unique": true + }, + { + "keyName": "application_method_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "application_method_promotion_id_foreign": { + "constraintName": "application_method_promotion_id_foreign", + "columnNames": [ + "promotion_id" + ], + "localTableName": "public.application_method", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.promotion", + "updateRule": "cascade" + } + } + } + ] +} diff --git a/packages/promotion/src/migrations/.snapshot-medusa-promotions.json b/packages/promotion/src/migrations/.snapshot-medusa-promotions.json deleted file mode 100644 index aed248bc5e..0000000000 --- a/packages/promotion/src/migrations/.snapshot-medusa-promotions.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "namespaces": [ - "public" - ], - "name": "public", - "tables": [ - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "promotion", - "schema": "public", - "indexes": [ - { - "keyName": "promotion_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {} - } - ] -} diff --git a/packages/promotion/src/migrations/Migration20231218142613.ts b/packages/promotion/src/migrations/Migration20231218142613.ts deleted file mode 100644 index ee0c83775d..0000000000 --- a/packages/promotion/src/migrations/Migration20231218142613.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20231218142613 extends Migration { - - async up(): Promise { - this.addSql('create table "promotion" ("id" text not null, constraint "promotion_pkey" primary key ("id"));'); - } - -} diff --git a/packages/promotion/src/migrations/Migration20231221104256.ts b/packages/promotion/src/migrations/Migration20231221104256.ts new file mode 100644 index 0000000000..edd403da56 --- /dev/null +++ b/packages/promotion/src/migrations/Migration20231221104256.ts @@ -0,0 +1,34 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20231221104256 extends Migration { + async up(): Promise { + 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"));' + ) + this.addSql('create index "IDX_promotion_code" on "promotion" ("code");') + this.addSql('create index "IDX_promotion_type" on "promotion" ("type");') + this.addSql( + 'alter table "promotion" add constraint "IDX_promotion_code_unique" unique ("code");' + ) + + 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"));' + ) + this.addSql( + 'create index "IDX_application_method_type" on "application_method" ("type");' + ) + this.addSql( + 'create index "IDX_application_method_target_type" on "application_method" ("target_type");' + ) + this.addSql( + 'create index "IDX_application_method_allocation" on "application_method" ("allocation");' + ) + this.addSql( + 'alter table "application_method" add constraint "application_method_promotion_id_unique" unique ("promotion_id");' + ) + + this.addSql( + 'alter table "application_method" add constraint "application_method_promotion_id_foreign" foreign key ("promotion_id") references "promotion" ("id") on update cascade;' + ) + } +} diff --git a/packages/promotion/src/models/application-method.ts b/packages/promotion/src/models/application-method.ts new file mode 100644 index 0000000000..712c661fc2 --- /dev/null +++ b/packages/promotion/src/models/application-method.ts @@ -0,0 +1,81 @@ +import { + ApplicationMethodAllocation, + ApplicationMethodTargetType, + ApplicationMethodType, +} from "@medusajs/types" +import { PromotionUtils, generateEntityId } from "@medusajs/utils" +import { + BeforeCreate, + Entity, + Enum, + Index, + OnInit, + OneToOne, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import Promotion from "./promotion" + +type OptionalFields = "value" | "max_quantity" | "allocation" +@Entity() +export default class ApplicationMethod { + [OptionalProps]?: OptionalFields + + @PrimaryKey({ columnType: "text" }) + id!: string + + @Property({ columnType: "numeric", nullable: true, serializer: Number }) + value?: number + + @Property({ columnType: "numeric", nullable: true, serializer: Number }) + max_quantity?: number + + @Index({ name: "IDX_application_method_type" }) + @Enum(() => PromotionUtils.ApplicationMethodType) + type: ApplicationMethodType + + @Index({ name: "IDX_application_method_target_type" }) + @Enum(() => PromotionUtils.ApplicationMethodTargetType) + target_type: ApplicationMethodTargetType + + @Index({ name: "IDX_application_method_allocation" }) + @Enum({ + items: () => PromotionUtils.ApplicationMethodAllocation, + nullable: true, + }) + allocation?: ApplicationMethodAllocation + + @OneToOne({ + entity: () => Promotion, + }) + promotion: Promotion + + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at?: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at?: Date + + @Property({ columnType: "timestamptz", nullable: true }) + deleted_at?: Date + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "app_method") + } + + @OnInit() + onInit() { + this.id = generateEntityId(this.id, "promo") + } +} diff --git a/packages/promotion/src/models/index.ts b/packages/promotion/src/models/index.ts index a5c0052922..0ee3108f94 100644 --- a/packages/promotion/src/models/index.ts +++ b/packages/promotion/src/models/index.ts @@ -1 +1,2 @@ +export { default as ApplicationMethod } from "./application-method" export { default as Promotion } from "./promotion" diff --git a/packages/promotion/src/models/promotion.ts b/packages/promotion/src/models/promotion.ts index ad99440f91..1b395f5fe2 100644 --- a/packages/promotion/src/models/promotion.ts +++ b/packages/promotion/src/models/promotion.ts @@ -1,16 +1,72 @@ -import { generateEntityId } from "@medusajs/utils" -import { BeforeCreate, Entity, PrimaryKey, OnInit } from "@mikro-orm/core" +import { PromotionType } from "@medusajs/types" +import { PromotionUtils, generateEntityId } from "@medusajs/utils" +import { + BeforeCreate, + Entity, + Enum, + Index, + OnInit, + OneToOne, + OptionalProps, + PrimaryKey, + Property, + Unique, +} from "@mikro-orm/core" +import ApplicationMethod from "./application-method" +type OptionalFields = "is_automatic" +type OptionalRelations = "application_method" @Entity() export default class Promotion { + [OptionalProps]?: OptionalFields | OptionalRelations + @PrimaryKey({ columnType: "text" }) id!: string + @Property({ columnType: "text" }) + @Index({ name: "IDX_promotion_code" }) + @Unique({ + name: "IDX_promotion_code_unique", + properties: ["code"], + }) + code: string + + @Property({ columnType: "boolean", default: false }) + is_automatic?: boolean = false + + @Index({ name: "IDX_promotion_type" }) + @Enum(() => PromotionUtils.PromotionType) + type: PromotionType + + @OneToOne({ + entity: () => ApplicationMethod, + mappedBy: (am) => am.promotion, + }) + application_method: ApplicationMethod + + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at?: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at?: Date + + @Property({ columnType: "timestamptz", nullable: true }) + deleted_at?: Date + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "promo") } - + @OnInit() onInit() { this.id = generateEntityId(this.id, "promo") diff --git a/packages/promotion/src/repositories/application-method.ts b/packages/promotion/src/repositories/application-method.ts new file mode 100644 index 0000000000..d656a721b0 --- /dev/null +++ b/packages/promotion/src/repositories/application-method.ts @@ -0,0 +1,121 @@ +import { Context, DAL } from "@medusajs/types" +import { DALUtils, MedusaError } from "@medusajs/utils" +import { + FilterQuery as MikroFilterQuery, + FindOptions as MikroOptions, +} from "@mikro-orm/core" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { ApplicationMethod } from "@models" +import { + CreateApplicationMethodDTO, + UpdateApplicationMethodDTO, +} from "../types" + +export class ApplicationMethodRepository extends DALUtils.MikroOrmBaseRepository { + protected readonly manager_: SqlEntityManager + + constructor({ manager }: { manager: SqlEntityManager }) { + // @ts-ignore + // eslint-disable-next-line prefer-rest-params + super(...arguments) + this.manager_ = manager + } + + async find( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + return await manager.find( + ApplicationMethod, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async findAndCount( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[ApplicationMethod[], number]> { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + return await manager.findAndCount( + ApplicationMethod, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async delete(ids: string[], context: Context = {}): Promise { + const manager = this.getActiveManager(context) + + await manager.nativeDelete(ApplicationMethod, { id: { $in: ids } }, {}) + } + + async create( + data: CreateApplicationMethodDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const applicationMethods = data.map((applicationMethodData) => { + return manager.create(ApplicationMethod, applicationMethodData) + }) + + manager.persist(applicationMethods) + + return applicationMethods + } + + async update( + data: UpdateApplicationMethodDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + const applicationMethodIds = data.map( + (applicationMethodData) => applicationMethodData.id + ) + const existingApplicationMethods = await this.find( + { + where: { + id: { + $in: applicationMethodIds, + }, + }, + }, + context + ) + + const existingApplicationMethodMap = new Map( + existingApplicationMethods.map<[string, ApplicationMethod]>( + (applicationMethod) => [applicationMethod.id, applicationMethod] + ) + ) + + const applicationMethods = data.map((applicationMethodData) => { + const existingApplicationMethod = existingApplicationMethodMap.get( + applicationMethodData.id + ) + + if (!existingApplicationMethod) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `ApplicationMethod with id "${applicationMethodData.id}" not found` + ) + } + + return manager.assign(existingApplicationMethod, applicationMethodData) + }) + + manager.persist(applicationMethods) + + return applicationMethods + } +} diff --git a/packages/promotion/src/repositories/index.ts b/packages/promotion/src/repositories/index.ts index 147c9cc259..5b9bb3a67f 100644 --- a/packages/promotion/src/repositories/index.ts +++ b/packages/promotion/src/repositories/index.ts @@ -1 +1,3 @@ export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils" +export { ApplicationMethodRepository } from "./application-method" +export { PromotionRepository } from "./promotion" diff --git a/packages/promotion/src/repositories/promotion.ts b/packages/promotion/src/repositories/promotion.ts new file mode 100644 index 0000000000..a3b3b9a1b1 --- /dev/null +++ b/packages/promotion/src/repositories/promotion.ts @@ -0,0 +1,124 @@ +import { Context, DAL } from "@medusajs/types" +import { DALUtils, MedusaError } from "@medusajs/utils" +import { + LoadStrategy, + FilterQuery as MikroFilterQuery, + FindOptions as MikroOptions, +} from "@mikro-orm/core" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { Promotion } from "@models" +import { CreatePromotionDTO, UpdatePromotionDTO } from "../types" + +export class PromotionRepository extends DALUtils.MikroOrmBaseRepository { + protected readonly manager_: SqlEntityManager + + constructor({ manager }: { manager: SqlEntityManager }) { + // @ts-ignore + // eslint-disable-next-line prefer-rest-params + super(...arguments) + this.manager_ = manager + } + + async find( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.find( + Promotion, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async findAndCount( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[Promotion[], number]> { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.findAndCount( + Promotion, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async delete(ids: string[], context: Context = {}): Promise { + const manager = this.getActiveManager(context) + + await manager.nativeDelete(Promotion, { id: { $in: ids } }, {}) + } + + async create( + data: CreatePromotionDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const promotions = data.map((promotionData) => { + return manager.create(Promotion, promotionData) + }) + + manager.persist(promotions) + + return promotions + } + + async update( + data: UpdatePromotionDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + const promotionIds = data.map((promotionData) => promotionData.id) + const existingPromotions = await this.find( + { + where: { + id: { + $in: promotionIds, + }, + }, + }, + context + ) + + const existingPromotionMap = new Map( + existingPromotions.map<[string, Promotion]>((promotion) => [ + promotion.id, + promotion, + ]) + ) + + const promotions = data.map((promotionData) => { + const existingPromotion = existingPromotionMap.get(promotionData.id) + + if (!existingPromotion) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Promotion with id "${promotionData.id}" not found` + ) + } + + return manager.assign(existingPromotion, promotionData) + }) + + manager.persist(promotions) + + return promotions + } +} diff --git a/packages/promotion/src/services/application-method.ts b/packages/promotion/src/services/application-method.ts new file mode 100644 index 0000000000..250e6c8433 --- /dev/null +++ b/packages/promotion/src/services/application-method.ts @@ -0,0 +1,108 @@ +import { Context, DAL, FindConfig, PromotionTypes } from "@medusajs/types" +import { + InjectManager, + InjectTransactionManager, + MedusaContext, + ModulesSdkUtils, + retrieveEntity, +} from "@medusajs/utils" +import { ApplicationMethod } from "@models" +import { ApplicationMethodRepository } from "@repositories" +import { + CreateApplicationMethodDTO, + UpdateApplicationMethodDTO, +} from "../types" + +type InjectedDependencies = { + applicationMethodRepository: DAL.RepositoryService +} + +export default class ApplicationMethodService< + TEntity extends ApplicationMethod = ApplicationMethod +> { + protected readonly applicationMethodRepository_: DAL.RepositoryService + + constructor({ applicationMethodRepository }: InjectedDependencies) { + this.applicationMethodRepository_ = applicationMethodRepository + } + + @InjectManager("applicationMethodRepository_") + async retrieve( + applicationMethodId: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await retrieveEntity< + ApplicationMethod, + PromotionTypes.ApplicationMethodDTO + >({ + id: applicationMethodId, + entityName: ApplicationMethod.name, + repository: this.applicationMethodRepository_, + config, + sharedContext, + })) as TEntity + } + + @InjectManager("applicationMethodRepository_") + async list( + filters: PromotionTypes.FilterableApplicationMethodProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const queryOptions = ModulesSdkUtils.buildQuery( + filters, + config + ) + + return (await this.applicationMethodRepository_.find( + queryOptions, + sharedContext + )) as TEntity[] + } + + @InjectManager("applicationMethodRepository_") + async listAndCount( + filters: PromotionTypes.FilterableApplicationMethodProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[TEntity[], number]> { + const queryOptions = ModulesSdkUtils.buildQuery( + filters, + config + ) + + return (await this.applicationMethodRepository_.findAndCount( + queryOptions, + sharedContext + )) as [TEntity[], number] + } + + @InjectTransactionManager("applicationMethodRepository_") + async create( + data: CreateApplicationMethodDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await ( + this.applicationMethodRepository_ as ApplicationMethodRepository + ).create(data, sharedContext)) as TEntity[] + } + + @InjectTransactionManager("applicationMethodRepository_") + async update( + data: UpdateApplicationMethodDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await ( + this.applicationMethodRepository_ as ApplicationMethodRepository + ).update(data, sharedContext)) as TEntity[] + } + + @InjectTransactionManager("applicationMethodRepository_") + async delete( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.applicationMethodRepository_.delete(ids, sharedContext) + } +} diff --git a/packages/promotion/src/services/index.ts b/packages/promotion/src/services/index.ts index c127ea20f4..891d81673c 100644 --- a/packages/promotion/src/services/index.ts +++ b/packages/promotion/src/services/index.ts @@ -1 +1,3 @@ +export { default as ApplicationMethodService } from "./application-method" +export { default as PromotionService } from "./promotion" export { default as PromotionModuleService } from "./promotion-module" diff --git a/packages/promotion/src/services/promotion-module.ts b/packages/promotion/src/services/promotion-module.ts index 468a140f3f..aa6dadd764 100644 --- a/packages/promotion/src/services/promotion-module.ts +++ b/packages/promotion/src/services/promotion-module.ts @@ -1,16 +1,26 @@ import { + Context, DAL, + FindConfig, InternalModuleDeclaration, ModuleJoinerConfig, PromotionTypes, } from "@medusajs/types" - +import { + InjectManager, + InjectTransactionManager, + MedusaContext, +} from "@medusajs/utils" import { Promotion } from "@models" - +import { ApplicationMethodService, PromotionService } from "@services" import { joinerConfig } from "../joiner-config" +import { CreateApplicationMethodDTO, CreatePromotionDTO } from "../types" +import { validateApplicationMethodAttributes } from "../utils" type InjectedDependencies = { baseRepository: DAL.RepositoryService + promotionService: PromotionService + applicationMethodService: ApplicationMethodService } export default class PromotionModuleService< @@ -18,15 +28,112 @@ export default class PromotionModuleService< > implements PromotionTypes.IPromotionModuleService { protected baseRepository_: DAL.RepositoryService + protected promotionService_: PromotionService + protected applicationMethodService_: ApplicationMethodService constructor( - { baseRepository }: InjectedDependencies, + { + baseRepository, + promotionService, + applicationMethodService, + }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { this.baseRepository_ = baseRepository + this.promotionService_ = promotionService + this.applicationMethodService_ = applicationMethodService } __joinerConfig(): ModuleJoinerConfig { return joinerConfig } + + @InjectManager("baseRepository_") + async list( + filters: PromotionTypes.FilterablePromotionProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const promotions = await this.promotionService_.list( + filters, + config, + sharedContext + ) + + return this.baseRepository_.serialize( + promotions, + { + populate: true, + } + ) + } + + @InjectManager("baseRepository_") + async create( + data: PromotionTypes.CreatePromotionDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const promotions = await this.create_(data, sharedContext) + + return await this.list( + { id: promotions.map((p) => p!.id) }, + { + relations: ["application_method"], + }, + sharedContext + ) + } + + @InjectTransactionManager("baseRepository_") + protected async create_( + data: PromotionTypes.CreatePromotionDTO[], + @MedusaContext() sharedContext: Context = {} + ) { + const promotionCodeApplicationMethodDataMap = new Map< + string, + PromotionTypes.CreateApplicationMethodDTO + >() + const promotionsData: CreatePromotionDTO[] = [] + const applicationMethodsData: CreateApplicationMethodDTO[] = [] + + for (const { + application_method: applicationMethodData, + ...promotionData + } of data) { + if (applicationMethodData) { + promotionCodeApplicationMethodDataMap.set( + promotionData.code, + applicationMethodData + ) + } + + promotionsData.push(promotionData) + } + + const createdPromotions = await this.promotionService_.create( + data, + sharedContext + ) + + for (const promotion of createdPromotions) { + const data = promotionCodeApplicationMethodDataMap.get(promotion.code) + + if (!data) continue + + const applicationMethodData = { + ...data, + promotion, + } + + validateApplicationMethodAttributes(applicationMethodData) + applicationMethodsData.push(applicationMethodData) + } + + await this.applicationMethodService_.create( + applicationMethodsData, + sharedContext + ) + + return createdPromotions + } } diff --git a/packages/promotion/src/services/promotion.ts b/packages/promotion/src/services/promotion.ts new file mode 100644 index 0000000000..5461be7df4 --- /dev/null +++ b/packages/promotion/src/services/promotion.ts @@ -0,0 +1,96 @@ +import { Context, DAL, FindConfig, PromotionTypes } from "@medusajs/types" +import { + InjectManager, + InjectTransactionManager, + MedusaContext, + ModulesSdkUtils, + retrieveEntity, +} from "@medusajs/utils" +import { Promotion } from "@models" +import { PromotionRepository } from "@repositories" +import { CreatePromotionDTO, UpdatePromotionDTO } from "../types" + +type InjectedDependencies = { + promotionRepository: DAL.RepositoryService +} + +export default class PromotionService { + protected readonly promotionRepository_: DAL.RepositoryService + + constructor({ promotionRepository }: InjectedDependencies) { + this.promotionRepository_ = promotionRepository + } + + @InjectManager("promotionRepository_") + async retrieve( + promotionId: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await retrieveEntity({ + id: promotionId, + entityName: Promotion.name, + repository: this.promotionRepository_, + config, + sharedContext, + })) as TEntity + } + + @InjectManager("promotionRepository_") + async list( + filters: PromotionTypes.FilterablePromotionProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const queryOptions = ModulesSdkUtils.buildQuery(filters, config) + + return (await this.promotionRepository_.find( + queryOptions, + sharedContext + )) as TEntity[] + } + + @InjectManager("promotionRepository_") + async listAndCount( + filters: PromotionTypes.FilterablePromotionProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[TEntity[], number]> { + const queryOptions = ModulesSdkUtils.buildQuery(filters, config) + + return (await this.promotionRepository_.findAndCount( + queryOptions, + sharedContext + )) as [TEntity[], number] + } + + @InjectTransactionManager("promotionRepository_") + async create( + data: CreatePromotionDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await (this.promotionRepository_ as PromotionRepository).create( + data, + sharedContext + )) as TEntity[] + } + + @InjectTransactionManager("promotionRepository_") + async update( + data: UpdatePromotionDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await (this.promotionRepository_ as PromotionRepository).update( + data, + sharedContext + )) as TEntity[] + } + + @InjectTransactionManager("promotionRepository_") + async delete( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.promotionRepository_.delete(ids, sharedContext) + } +} diff --git a/packages/promotion/src/types/application-method.ts b/packages/promotion/src/types/application-method.ts new file mode 100644 index 0000000000..b683958f03 --- /dev/null +++ b/packages/promotion/src/types/application-method.ts @@ -0,0 +1,19 @@ +import { + ApplicationMethodAllocation, + ApplicationMethodTargetType, + ApplicationMethodType, + PromotionDTO, +} from "@medusajs/types" + +export interface CreateApplicationMethodDTO { + type: ApplicationMethodType + target_type: ApplicationMethodTargetType + allocation?: ApplicationMethodAllocation + value?: number + promotion: PromotionDTO | string + max_quantity?: number +} + +export interface UpdateApplicationMethodDTO { + id: string +} diff --git a/packages/promotion/src/types/index.ts b/packages/promotion/src/types/index.ts index 0f252977b0..c35015eca9 100644 --- a/packages/promotion/src/types/index.ts +++ b/packages/promotion/src/types/index.ts @@ -3,3 +3,6 @@ import { Logger } from "@medusajs/types" export type InitializeModuleInjectableDependencies = { logger?: Logger } + +export * from "./application-method" +export * from "./promotion" diff --git a/packages/promotion/src/types/promotion.ts b/packages/promotion/src/types/promotion.ts new file mode 100644 index 0000000000..835f82df1b --- /dev/null +++ b/packages/promotion/src/types/promotion.ts @@ -0,0 +1,11 @@ +import { PromotionType } from "@medusajs/types" + +export interface CreatePromotionDTO { + code: string + type: PromotionType + is_automatic?: boolean +} + +export interface UpdatePromotionDTO { + id: string +} diff --git a/packages/promotion/src/utils/index.ts b/packages/promotion/src/utils/index.ts new file mode 100644 index 0000000000..81a5199502 --- /dev/null +++ b/packages/promotion/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./validations" diff --git a/packages/promotion/src/utils/validations/application-method.ts b/packages/promotion/src/utils/validations/application-method.ts new file mode 100644 index 0000000000..99e193a387 --- /dev/null +++ b/packages/promotion/src/utils/validations/application-method.ts @@ -0,0 +1,51 @@ +import { + ApplicationMethodAllocation, + ApplicationMethodTargetType, + MedusaError, + isDefined, +} from "@medusajs/utils" +import { CreateApplicationMethodDTO } from "../../types" + +const allowedTargetTypes: string[] = [ + ApplicationMethodTargetType.SHIPPING, + ApplicationMethodTargetType.ITEM, +] + +const allowedAllocationTypes: string[] = [ + ApplicationMethodAllocation.ACROSS, + ApplicationMethodAllocation.EACH, +] + +const allowedAllocationForQuantity: string[] = [ + ApplicationMethodAllocation.EACH, +] + +export function validateApplicationMethodAttributes( + data: CreateApplicationMethodDTO +) { + if ( + allowedTargetTypes.includes(data.target_type) && + !allowedAllocationTypes.includes(data.allocation || "") + ) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `application_method.allocation should be either '${allowedAllocationTypes.join( + " OR " + )}' when application_method.target_type is either '${allowedTargetTypes.join( + " OR " + )}'` + ) + } + + if ( + allowedAllocationForQuantity.includes(data.allocation || "") && + !isDefined(data.max_quantity) + ) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `application_method.max_quantity is required when application_method.allocation is '${allowedAllocationForQuantity.join( + " OR " + )}'` + ) + } +} diff --git a/packages/promotion/src/utils/validations/index.ts b/packages/promotion/src/utils/validations/index.ts new file mode 100644 index 0000000000..d5886d02f7 --- /dev/null +++ b/packages/promotion/src/utils/validations/index.ts @@ -0,0 +1 @@ +export * from "./application-method" diff --git a/packages/promotion/tsconfig.json b/packages/promotion/tsconfig.json index 213e38fc55..bd71a38e32 100644 --- a/packages/promotion/tsconfig.json +++ b/packages/promotion/tsconfig.json @@ -4,6 +4,7 @@ "target": "es2020", "outDir": "./dist", "esModuleInterop": true, + "declarationMap": true, "declaration": true, "module": "commonjs", "moduleResolution": "node", diff --git a/packages/types/src/promotion/common/application-method.ts b/packages/types/src/promotion/common/application-method.ts new file mode 100644 index 0000000000..9020ab339c --- /dev/null +++ b/packages/types/src/promotion/common/application-method.ts @@ -0,0 +1,31 @@ +import { BaseFilterable } from "../../dal" +import { PromotionDTO } from "./promotion" + +export type ApplicationMethodType = "fixed" | "percentage" +export type ApplicationMethodTargetType = "order" | "shipping" | "item" +export type ApplicationMethodAllocation = "each" | "across" + +export interface ApplicationMethodDTO { + id: string +} + +export interface CreateApplicationMethodDTO { + type: ApplicationMethodType + target_type: ApplicationMethodTargetType + allocation?: ApplicationMethodAllocation + value?: number + max_quantity?: number + promotion?: PromotionDTO | string +} + +export interface UpdateApplicationMethodDTO { + id: string +} + +export interface FilterableApplicationMethodProps + extends BaseFilterable { + id?: string[] + type?: ApplicationMethodType[] + target_type?: ApplicationMethodTargetType[] + allocation?: ApplicationMethodAllocation[] +} diff --git a/packages/types/src/promotion/common/index.ts b/packages/types/src/promotion/common/index.ts new file mode 100644 index 0000000000..47aac75452 --- /dev/null +++ b/packages/types/src/promotion/common/index.ts @@ -0,0 +1,2 @@ +export * from "./application-method" +export * from "./promotion" diff --git a/packages/types/src/promotion/common/promotion.ts b/packages/types/src/promotion/common/promotion.ts new file mode 100644 index 0000000000..4cb00464ca --- /dev/null +++ b/packages/types/src/promotion/common/promotion.ts @@ -0,0 +1,27 @@ +import { BaseFilterable } from "../../dal" +import { CreateApplicationMethodDTO } from "./application-method" + +export type PromotionType = "standard" | "buyget" + +export interface PromotionDTO { + id: string +} + +export interface CreatePromotionDTO { + code: string + type: PromotionType + is_automatic?: boolean + application_method?: CreateApplicationMethodDTO +} + +export interface UpdatePromotionDTO { + id: string +} + +export interface FilterablePromotionProps + extends BaseFilterable { + id?: string[] + code?: string[] + is_automatic?: boolean + type?: PromotionType[] +} diff --git a/packages/types/src/promotion/index.ts b/packages/types/src/promotion/index.ts index 9376fea807..eade309433 100644 --- a/packages/types/src/promotion/index.ts +++ b/packages/types/src/promotion/index.ts @@ -1 +1,2 @@ +export * from "./common" export * from "./service" diff --git a/packages/types/src/promotion/service.ts b/packages/types/src/promotion/service.ts index 6a12a02aaa..dbbba24ffa 100644 --- a/packages/types/src/promotion/service.ts +++ b/packages/types/src/promotion/service.ts @@ -1,3 +1,21 @@ +import { FindConfig } from "../common" import { IModuleService } from "../modules-sdk" +import { Context } from "../shared-context" +import { + CreatePromotionDTO, + FilterablePromotionProps, + PromotionDTO, +} from "./common" -export interface IPromotionModuleService extends IModuleService {} +export interface IPromotionModuleService extends IModuleService { + create( + data: CreatePromotionDTO[], + sharedContext?: Context + ): Promise + + list( + filters?: FilterablePromotionProps, + config?: FindConfig, + sharedContext?: Context + ): Promise +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index 2c99c7503d..8911c0e380 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -4,6 +4,7 @@ "target": "es2020", "outDir": "./dist", "esModuleInterop": true, + "declarationMap": true, "declaration": true, "module": "commonjs", "moduleResolution": "node", diff --git a/packages/utils/src/bundles.ts b/packages/utils/src/bundles.ts index 96d38b19bf..bcbf67c442 100644 --- a/packages/utils/src/bundles.ts +++ b/packages/utils/src/bundles.ts @@ -4,6 +4,7 @@ export * as EventBusUtils from "./event-bus" export * as FeatureFlagUtils from "./feature-flags" export * as ModulesSdkUtils from "./modules-sdk" export * as ProductUtils from "./product" +export * as PromotionUtils from "./promotion" export * as SearchUtils from "./search" export * as ShippingProfileUtils from "./shipping" export * as OrchestrationUtils from "./orchestration" diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index dd2be395ca..7674050a26 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -7,6 +7,7 @@ export * from "./feature-flags" export * from "./modules-sdk" export * from "./pricing" export * from "./product" +export * from "./promotion" export * from "./search" export * from "./shipping" export * from "./orchestration" diff --git a/packages/utils/src/promotion/index.ts b/packages/utils/src/promotion/index.ts new file mode 100644 index 0000000000..4dcff9e8d2 --- /dev/null +++ b/packages/utils/src/promotion/index.ts @@ -0,0 +1,20 @@ +export enum PromotionType { + STANDARD = "standard", + BUYGET = "buyget", +} + +export enum ApplicationMethodType { + FIXED = "fixed", + PERCENTAGE = "percentage", +} + +export enum ApplicationMethodTargetType { + ORDER = "order", + SHIPPING = "shipping", + ITEM = "item", +} + +export enum ApplicationMethodAllocation { + EACH = "each", + ACROSS = "across", +}