From fad85a9d293acee1dae784afa223a080b9b8b85b Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 11 Dec 2024 13:12:39 +0530 Subject: [PATCH] refactor: migrate promotion module (#10410) --- .changeset/little-hounds-build.md | 6 + .../src/dml/__tests__/entity-builder.spec.ts | 16 +- .../entity-builder/define-relationship.ts | 16 +- .../dml/helpers/mikro-orm/apply-indexes.ts | 1 + .../__fixtures__/campaigns/index.ts | 3 +- .../__fixtures__/promotion/index.ts | 4 +- .../promotion-module/promotion.spec.ts | 4 +- .../modules/promotion/mikro-orm.config.dev.ts | 2 +- .../.snapshot-medusa-promotion.json | 410 +++++++++++++----- .../src/migrations/Migration20241211061114.ts | 131 ++++++ .../src/models/application-method.ts | 165 ++----- .../promotion/src/models/campaign-budget.ts | 105 +---- .../modules/promotion/src/models/campaign.ts | 137 ++---- .../src/models/promotion-rule-value.ts | 64 +-- .../promotion/src/models/promotion-rule.ts | 119 ++--- .../modules/promotion/src/models/promotion.ts | 129 ++---- .../src/services/promotion-module.ts | 46 +- .../promotion/src/types/application-method.ts | 5 +- .../promotion/src/types/campaign-budget.ts | 3 +- .../modules/promotion/src/types/campaign.ts | 6 +- .../src/types/promotion-rule-value.ts | 12 +- .../utils/validations/application-method.ts | 3 +- 22 files changed, 662 insertions(+), 725 deletions(-) create mode 100644 .changeset/little-hounds-build.md create mode 100644 packages/modules/promotion/src/migrations/Migration20241211061114.ts diff --git a/.changeset/little-hounds-build.md b/.changeset/little-hounds-build.md new file mode 100644 index 0000000000000..a817b1429bca4 --- /dev/null +++ b/.changeset/little-hounds-build.md @@ -0,0 +1,6 @@ +--- +"@medusajs/promotion": patch +"@medusajs/utils": patch +--- + +refactor: migrate promotion module diff --git a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts index 92891fcd5cecb..22130997b8a76 100644 --- a/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts +++ b/packages/core/utils/src/dml/__tests__/entity-builder.spec.ts @@ -2326,7 +2326,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", }, created_at: { @@ -2508,7 +2507,7 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, + onDelete: undefined, }, created_at: { reference: "scalar", @@ -2595,7 +2594,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "owner", }, created_at: { @@ -2687,7 +2685,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", cascade: ["persist", "soft-remove"], onDelete: "cascade", @@ -2851,9 +2848,7 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", - cascade: ["persist", "soft-remove"], onDelete: "cascade", }, created_at: { @@ -3040,7 +3035,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, }, email_id: { columnType: "text", @@ -3251,7 +3245,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "owner", }, email_id: { @@ -3356,7 +3349,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, cascade: ["persist", "soft-remove"], mappedBy: "user", }, @@ -3532,7 +3524,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, cascade: ["persist", "soft-remove"], mappedBy: "user", }, @@ -4493,7 +4484,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", }, created_at: { @@ -4691,7 +4681,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", }, created_at: { @@ -5326,7 +5315,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", }, created_at: { @@ -5526,7 +5514,6 @@ describe("Entity builder", () => { reference: "1:1", name: "email", entity: "Email", - nullable: false, mappedBy: "user", }, created_at: { @@ -5838,7 +5825,6 @@ describe("Entity builder", () => { entity: "User", mappedBy: "parent", name: "child", - nullable: false, reference: "1:1", }, created_at: { diff --git a/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts b/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts index 49462f165b371..8f95e0e03f083 100644 --- a/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts +++ b/packages/core/utils/src/dml/helpers/entity-builder/define-relationship.ts @@ -25,6 +25,7 @@ import { HasOneWithForeignKey } from "../../relations/has-one-fk" import { ManyToMany as DmlManyToMany } from "../../relations/many-to-many" import { applyEntityIndexes } from "../mikro-orm/apply-indexes" import { parseEntityName } from "./parse-entity-name" +import { BelongsTo } from "../../relations" type Context = { MANY_TO_MANY_TRACKED_RELATIONS: Record @@ -138,24 +139,32 @@ function validateManyToManyRelationshipWithoutMappedBy({ export function defineHasOneRelationship( MikroORMEntity: EntityConstructor, relationship: RelationshipMetadata, + relatedEntity: DmlEntity< + Record | RelationshipType>, + any + >, { relatedModelName }: { relatedModelName: string }, cascades: EntityCascades ) { const shouldRemoveRelated = !!cascades.delete?.includes(relationship.name) + const { schema: relationSchema } = relatedEntity.parse() let mappedBy: string | undefined = camelToSnakeCase(MikroORMEntity.name) if ("mappedBy" in relationship) { mappedBy = relationship.mappedBy } + const isOthersideBelongsTo = + !!mappedBy && BelongsTo.isBelongsTo(relationSchema[mappedBy]) + const oneToOneOptions = { entity: relatedModelName, - nullable: relationship.nullable, + ...(relationship.nullable ? { nullable: relationship.nullable } : {}), ...(mappedBy ? { mappedBy } : {}), onDelete: shouldRemoveRelated ? "cascade" : undefined, } as OneToOneOptions - if (shouldRemoveRelated) { + if (shouldRemoveRelated && !isOthersideBelongsTo) { oneToOneOptions.cascade = ["persist", "soft-remove"] as any } @@ -182,7 +191,7 @@ export function defineHasOneWithFKRelationship( OneToOne({ entity: relatedModelName, - nullable: relationship.nullable, + ...(relationship.nullable ? { nullable: relationship.nullable } : {}), ...(mappedBy ? { mappedBy } : {}), cascade: shouldRemoveRelated ? (["persist", "soft-remove"] as any) @@ -697,6 +706,7 @@ export function defineRelationship( defineHasOneRelationship( MikroORMEntity, relationship, + relatedEntity, relatedEntityInfo, cascades ) diff --git a/packages/core/utils/src/dml/helpers/mikro-orm/apply-indexes.ts b/packages/core/utils/src/dml/helpers/mikro-orm/apply-indexes.ts index d03750807d884..cce0fb5c89b74 100644 --- a/packages/core/utils/src/dml/helpers/mikro-orm/apply-indexes.ts +++ b/packages/core/utils/src/dml/helpers/mikro-orm/apply-indexes.ts @@ -16,6 +16,7 @@ export function applyIndexes( ) { field.indexes.forEach((index) => { const providerEntityIdIndexStatement = createPsqlIndexStatementHelper({ + name: index.name, tableName, columns: [field.fieldName], unique: index.type === "unique", diff --git a/packages/modules/promotion/integration-tests/__fixtures__/campaigns/index.ts b/packages/modules/promotion/integration-tests/__fixtures__/campaigns/index.ts index 0434a6e51c884..f7e9298e9ef82 100644 --- a/packages/modules/promotion/integration-tests/__fixtures__/campaigns/index.ts +++ b/packages/modules/promotion/integration-tests/__fixtures__/campaigns/index.ts @@ -2,6 +2,7 @@ import { CreateCampaignDTO } from "@medusajs/framework/types" import { SqlEntityManager } from "@mikro-orm/postgresql" import { Campaign } from "@models" import { defaultCampaignsData } from "./data" +import { toMikroORMEntity } from "@medusajs/framework/utils" export * from "./data" @@ -12,7 +13,7 @@ export async function createCampaigns( const campaigns: Campaign[] = [] for (let campaignData of campaignsData) { - let campaign = manager.create(Campaign, campaignData) + let campaign = manager.create(toMikroORMEntity(Campaign), campaignData) manager.persist(campaign) diff --git a/packages/modules/promotion/integration-tests/__fixtures__/promotion/index.ts b/packages/modules/promotion/integration-tests/__fixtures__/promotion/index.ts index 297f762541e50..ee616dfb6fa0a 100644 --- a/packages/modules/promotion/integration-tests/__fixtures__/promotion/index.ts +++ b/packages/modules/promotion/integration-tests/__fixtures__/promotion/index.ts @@ -3,7 +3,7 @@ import { IPromotionModuleService, PromotionDTO, } from "@medusajs/framework/types" -import { isPresent } from "@medusajs/framework/utils" +import { isPresent, toMikroORMEntity } from "@medusajs/framework/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" import { Promotion } from "@models" import { defaultPromotionsData } from "./data" @@ -17,7 +17,7 @@ export async function createPromotions( const promotions: Promotion[] = [] for (let promotionData of promotionsData) { - let promotion = manager.create(Promotion, promotionData) + let promotion = manager.create(toMikroORMEntity(Promotion), promotionData) manager.persist(promotion) await manager.flush() diff --git a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts index db7629340951f..dc675df38d475 100644 --- a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts +++ b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/promotion.spec.ts @@ -508,7 +508,7 @@ moduleIntegrationTestRunner({ ) }) - it("should create a buyget promotion with rules successfully", async () => { + it("should create a budget promotion with rules successfully", async () => { const createdPromotion = await createDefaultPromotion(service, { type: PromotionType.BUYGET, application_method: { @@ -638,7 +638,7 @@ moduleIntegrationTestRunner({ application_method: expect.objectContaining({ target_type: "order", allocation: "across", - max_quantity: 0, + max_quantity: null, }), }) ) diff --git a/packages/modules/promotion/mikro-orm.config.dev.ts b/packages/modules/promotion/mikro-orm.config.dev.ts index 81ad33347d55e..1b964a8848a35 100644 --- a/packages/modules/promotion/mikro-orm.config.dev.ts +++ b/packages/modules/promotion/mikro-orm.config.dev.ts @@ -1,6 +1,6 @@ import * as entities from "./src/models" import { defineMikroOrmCliConfig, Modules } from "@medusajs/framework/utils" -export default defineMikroOrmCliConfig(Modules.PRODUCT, { +export default defineMikroOrmCliConfig(Modules.PROMOTION, { entities: Object.values(entities), }) diff --git a/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json b/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json index da37995ce89e1..7f8d1ea64eab6 100644 --- a/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json +++ b/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json @@ -1,5 +1,7 @@ { - "namespaces": ["public"], + "namespaces": [ + "public" + ], "name": "public", "tables": [ { @@ -96,9 +98,17 @@ "name": "promotion_campaign", "schema": "public", "indexes": [ + { + "keyName": "IDX_promotion_campaign_deleted_at", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_campaign_deleted_at\" ON \"promotion_campaign\" (deleted_at) WHERE deleted_at IS NULL" + }, { "keyName": "IDX_promotion_campaign_campaign_identifier_unique", - "columnNames": ["campaign_identifier"], + "columnNames": [], "composite": false, "primary": false, "unique": false, @@ -106,7 +116,9 @@ }, { "keyName": "promotion_campaign_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -133,18 +145,12 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["spend", "usage"], + "enumItems": [ + "spend", + "usage" + ], "mappedType": "enum" }, - "campaign_id": { - "name": "campaign_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, "currency_code": { "name": "currency_code", "type": "text", @@ -163,15 +169,6 @@ "nullable": true, "mappedType": "decimal" }, - "raw_limit": { - "name": "raw_limit", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, "used": { "name": "used", "type": "numeric", @@ -182,6 +179,24 @@ "default": "0", "mappedType": "decimal" }, + "campaign_id": { + "name": "campaign_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "raw_limit": { + "name": "raw_limit", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, "raw_used": { "name": "raw_used", "type": "jsonb", @@ -228,22 +243,43 @@ "schema": "public", "indexes": [ { - "columnNames": ["type"], + "columnNames": [ + "campaign_id" + ], "composite": false, + "keyName": "promotion_campaign_budget_campaign_id_unique", + "primary": false, + "unique": true + }, + { "keyName": "IDX_campaign_budget_type", + "columnNames": [], + "composite": false, "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_campaign_budget_type\" ON \"promotion_campaign_budget\" (type) WHERE deleted_at IS NULL" }, { - "columnNames": ["campaign_id"], + "keyName": "IDX_promotion_campaign_budget_campaign_id", + "columnNames": [], "composite": false, - "keyName": "promotion_campaign_budget_campaign_id_unique", "primary": false, - "unique": true + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_campaign_budget_campaign_id\" ON \"promotion_campaign_budget\" (campaign_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_promotion_campaign_budget_deleted_at", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_campaign_budget_deleted_at\" ON \"promotion_campaign_budget\" (deleted_at) WHERE deleted_at IS NULL" }, { "keyName": "promotion_campaign_budget_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -253,10 +289,15 @@ "foreignKeys": { "promotion_campaign_budget_campaign_id_foreign": { "constraintName": "promotion_campaign_budget_campaign_id_foreign", - "columnNames": ["campaign_id"], + "columnNames": [ + "campaign_id" + ], "localTableName": "public.promotion_campaign_budget", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_campaign", + "deleteRule": "cascade", "updateRule": "cascade" } } @@ -281,15 +322,6 @@ "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", @@ -307,9 +339,21 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["standard", "buyget"], + "enumItems": [ + "standard", + "buyget" + ], "mappedType": "enum" }, + "campaign_id": { + "name": "campaign_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, "created_at": { "name": "created_at", "type": "timestamptz", @@ -347,29 +391,50 @@ "schema": "public", "indexes": [ { - "columnNames": ["code"], + "keyName": "IDX_promotion_code_unique", + "columnNames": [], "composite": false, - "keyName": "IDX_promotion_code", "primary": false, - "unique": false + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_promotion_code_unique\" ON \"promotion\" (code) WHERE deleted_at IS NULL" }, { - "columnNames": ["type"], + "keyName": "IDX_promotion_code", + "columnNames": [], "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_code\" ON \"promotion\" (code) WHERE deleted_at IS NULL" + }, + { "keyName": "IDX_promotion_type", + "columnNames": [], + "composite": false, "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_type\" ON \"promotion\" (type) WHERE deleted_at IS NULL" }, { - "keyName": "IDX_promotion_code_unique", - "columnNames": ["code"], + "keyName": "IDX_promotion_campaign_id", + "columnNames": [], "composite": false, "primary": false, - "unique": true + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_campaign_id\" ON \"promotion\" (campaign_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_promotion_deleted_at", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_deleted_at\" ON \"promotion\" (deleted_at) WHERE deleted_at IS NULL" }, { "keyName": "promotion_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -379,9 +444,13 @@ "foreignKeys": { "promotion_campaign_id_foreign": { "constraintName": "promotion_campaign_id_foreign", - "columnNames": ["campaign_id"], + "columnNames": [ + "campaign_id" + ], "localTableName": "public.promotion", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_campaign", "deleteRule": "set null", "updateRule": "cascade" @@ -405,18 +474,9 @@ "unsigned": false, "autoincrement": false, "primary": false, - "nullable": false, + "nullable": true, "mappedType": "decimal" }, - "raw_value": { - "name": "raw_value", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "json" - }, "currency_code": { "name": "currency_code", "type": "text", @@ -428,30 +488,30 @@ }, "max_quantity": { "name": "max_quantity", - "type": "numeric", + "type": "integer", "unsigned": false, "autoincrement": false, "primary": false, "nullable": true, - "mappedType": "decimal" + "mappedType": "integer" }, "apply_to_quantity": { "name": "apply_to_quantity", - "type": "numeric", + "type": "integer", "unsigned": false, "autoincrement": false, "primary": false, "nullable": true, - "mappedType": "decimal" + "mappedType": "integer" }, "buy_rules_min_quantity": { "name": "buy_rules_min_quantity", - "type": "numeric", + "type": "integer", "unsigned": false, "autoincrement": false, "primary": false, "nullable": true, - "mappedType": "decimal" + "mappedType": "integer" }, "type": { "name": "type", @@ -460,7 +520,10 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["fixed", "percentage"], + "enumItems": [ + "fixed", + "percentage" + ], "mappedType": "enum" }, "target_type": { @@ -470,7 +533,11 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["order", "shipping_methods", "items"], + "enumItems": [ + "order", + "shipping_methods", + "items" + ], "mappedType": "enum" }, "allocation": { @@ -480,7 +547,10 @@ "autoincrement": false, "primary": false, "nullable": true, - "enumItems": ["each", "across"], + "enumItems": [ + "each", + "across" + ], "mappedType": "enum" }, "promotion_id": { @@ -492,6 +562,15 @@ "nullable": false, "mappedType": "text" }, + "raw_value": { + "name": "raw_value", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, "created_at": { "name": "created_at", "type": "timestamptz", @@ -529,36 +608,57 @@ "schema": "public", "indexes": [ { - "columnNames": ["type"], + "columnNames": [ + "promotion_id" + ], "composite": false, - "keyName": "IDX_application_method_type", + "keyName": "promotion_application_method_promotion_id_unique", "primary": false, - "unique": false + "unique": true }, { - "columnNames": ["target_type"], + "keyName": "IDX_application_method_type", + "columnNames": [], "composite": false, - "keyName": "IDX_application_method_target_type", "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_application_method_type\" ON \"promotion_application_method\" (type) WHERE deleted_at IS NULL" }, { - "columnNames": ["allocation"], + "keyName": "IDX_application_method_target_type", + "columnNames": [], "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_application_method_target_type\" ON \"promotion_application_method\" (target_type) WHERE deleted_at IS NULL" + }, + { "keyName": "IDX_application_method_allocation", + "columnNames": [], + "composite": false, "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_application_method_allocation\" ON \"promotion_application_method\" (allocation) WHERE deleted_at IS NULL" }, { - "columnNames": ["promotion_id"], + "keyName": "IDX_promotion_application_method_promotion_id", + "columnNames": [], "composite": false, - "keyName": "promotion_application_method_promotion_id_unique", "primary": false, - "unique": true + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_application_method_promotion_id\" ON \"promotion_application_method\" (promotion_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_promotion_application_method_deleted_at", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_application_method_deleted_at\" ON \"promotion_application_method\" (deleted_at) WHERE deleted_at IS NULL" }, { "keyName": "IDX_promotion_application_method_currency_code", - "columnNames": ["currency_code"], + "columnNames": [], "composite": false, "primary": false, "unique": false, @@ -566,7 +666,9 @@ }, { "keyName": "promotion_application_method_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -576,9 +678,13 @@ "foreignKeys": { "promotion_application_method_promotion_id_foreign": { "constraintName": "promotion_application_method_promotion_id_foreign", - "columnNames": ["promotion_id"], + "columnNames": [ + "promotion_id" + ], "localTableName": "public.promotion_application_method", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion", "deleteRule": "cascade", "updateRule": "cascade" @@ -621,7 +727,15 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["gte", "lte", "gt", "lt", "eq", "ne", "in"], + "enumItems": [ + "gte", + "lte", + "gt", + "lt", + "eq", + "ne", + "in" + ], "mappedType": "enum" }, "created_at": { @@ -661,22 +775,34 @@ "schema": "public", "indexes": [ { - "columnNames": ["attribute"], - "composite": false, "keyName": "IDX_promotion_rule_attribute", + "columnNames": [], + "composite": false, "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_attribute\" ON \"promotion_rule\" (attribute) WHERE deleted_at IS NULL" }, { - "columnNames": ["operator"], - "composite": false, "keyName": "IDX_promotion_rule_operator", + "columnNames": [], + "composite": false, "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_operator\" ON \"promotion_rule\" (operator) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_promotion_rule_deleted_at", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_deleted_at\" ON \"promotion_rule\" (deleted_at) WHERE deleted_at IS NULL" }, { "keyName": "promotion_rule_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -711,7 +837,10 @@ "indexes": [ { "keyName": "promotion_promotion_rule_pkey", - "columnNames": ["promotion_id", "promotion_rule_id"], + "columnNames": [ + "promotion_id", + "promotion_rule_id" + ], "composite": true, "primary": true, "unique": true @@ -721,18 +850,26 @@ "foreignKeys": { "promotion_promotion_rule_promotion_id_foreign": { "constraintName": "promotion_promotion_rule_promotion_id_foreign", - "columnNames": ["promotion_id"], + "columnNames": [ + "promotion_id" + ], "localTableName": "public.promotion_promotion_rule", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion", "deleteRule": "cascade", "updateRule": "cascade" }, "promotion_promotion_rule_promotion_rule_id_foreign": { "constraintName": "promotion_promotion_rule_promotion_rule_id_foreign", - "columnNames": ["promotion_rule_id"], + "columnNames": [ + "promotion_rule_id" + ], "localTableName": "public.promotion_promotion_rule", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_rule", "deleteRule": "cascade", "updateRule": "cascade" @@ -765,7 +902,10 @@ "indexes": [ { "keyName": "application_method_target_rules_pkey", - "columnNames": ["application_method_id", "promotion_rule_id"], + "columnNames": [ + "application_method_id", + "promotion_rule_id" + ], "composite": true, "primary": true, "unique": true @@ -775,18 +915,26 @@ "foreignKeys": { "application_method_target_rules_application_method_id_foreign": { "constraintName": "application_method_target_rules_application_method_id_foreign", - "columnNames": ["application_method_id"], + "columnNames": [ + "application_method_id" + ], "localTableName": "public.application_method_target_rules", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_application_method", "deleteRule": "cascade", "updateRule": "cascade" }, "application_method_target_rules_promotion_rule_id_foreign": { "constraintName": "application_method_target_rules_promotion_rule_id_foreign", - "columnNames": ["promotion_rule_id"], + "columnNames": [ + "promotion_rule_id" + ], "localTableName": "public.application_method_target_rules", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_rule", "deleteRule": "cascade", "updateRule": "cascade" @@ -819,7 +967,10 @@ "indexes": [ { "keyName": "application_method_buy_rules_pkey", - "columnNames": ["application_method_id", "promotion_rule_id"], + "columnNames": [ + "application_method_id", + "promotion_rule_id" + ], "composite": true, "primary": true, "unique": true @@ -829,18 +980,26 @@ "foreignKeys": { "application_method_buy_rules_application_method_id_foreign": { "constraintName": "application_method_buy_rules_application_method_id_foreign", - "columnNames": ["application_method_id"], + "columnNames": [ + "application_method_id" + ], "localTableName": "public.application_method_buy_rules", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_application_method", "deleteRule": "cascade", "updateRule": "cascade" }, "application_method_buy_rules_promotion_rule_id_foreign": { "constraintName": "application_method_buy_rules_promotion_rule_id_foreign", - "columnNames": ["promotion_rule_id"], + "columnNames": [ + "promotion_rule_id" + ], "localTableName": "public.application_method_buy_rules", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_rule", "deleteRule": "cascade", "updateRule": "cascade" @@ -858,8 +1017,8 @@ "nullable": false, "mappedType": "text" }, - "promotion_rule_id": { - "name": "promotion_rule_id", + "value": { + "name": "value", "type": "text", "unsigned": false, "autoincrement": false, @@ -867,8 +1026,8 @@ "nullable": false, "mappedType": "text" }, - "value": { - "name": "value", + "promotion_rule_id": { + "name": "promotion_rule_id", "type": "text", "unsigned": false, "autoincrement": false, @@ -913,15 +1072,26 @@ "schema": "public", "indexes": [ { - "columnNames": ["promotion_rule_id"], + "keyName": "IDX_promotion_rule_value_promotion_rule_id", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_value_promotion_rule_id\" ON \"promotion_rule_value\" (promotion_rule_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_promotion_rule_value_deleted_at", + "columnNames": [], "composite": false, - "keyName": "IDX_promotion_rule_promotion_rule_value_id", "primary": false, - "unique": false + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_rule_value_deleted_at\" ON \"promotion_rule_value\" (deleted_at) WHERE deleted_at IS NULL" }, { "keyName": "promotion_rule_value_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -931,9 +1101,13 @@ "foreignKeys": { "promotion_rule_value_promotion_rule_id_foreign": { "constraintName": "promotion_rule_value_promotion_rule_id_foreign", - "columnNames": ["promotion_rule_id"], + "columnNames": [ + "promotion_rule_id" + ], "localTableName": "public.promotion_rule_value", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.promotion_rule", "deleteRule": "cascade", "updateRule": "cascade" diff --git a/packages/modules/promotion/src/migrations/Migration20241211061114.ts b/packages/modules/promotion/src/migrations/Migration20241211061114.ts new file mode 100644 index 0000000000000..83b4284a2d034 --- /dev/null +++ b/packages/modules/promotion/src/migrations/Migration20241211061114.ts @@ -0,0 +1,131 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20241211061114 extends Migration { + async up(): Promise { + this.addSql( + 'alter table if exists "promotion_campaign_budget" drop constraint if exists "promotion_campaign_budget_campaign_id_foreign";' + ) + + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_campaign_deleted_at" ON "promotion_campaign" (deleted_at) WHERE deleted_at IS NULL;' + ) + + this.addSql( + 'alter table if exists "promotion_campaign_budget" add constraint "promotion_campaign_budget_campaign_id_foreign" foreign key ("campaign_id") references "promotion_campaign" ("id") on update cascade on delete cascade;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_campaign_budget_campaign_id" ON "promotion_campaign_budget" (campaign_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_campaign_budget_deleted_at" ON "promotion_campaign_budget" (deleted_at) WHERE deleted_at IS NULL;' + ) + + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_campaign_id" ON "promotion" (campaign_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_deleted_at" ON "promotion" (deleted_at) WHERE deleted_at IS NULL;' + ) + + this.addSql( + 'alter table if exists "promotion_application_method" alter column "value" type numeric using ("value"::numeric);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "value" drop not null;' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "raw_value" type jsonb using ("raw_value"::jsonb);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "raw_value" drop not null;' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "max_quantity" type integer using ("max_quantity"::integer);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "apply_to_quantity" type integer using ("apply_to_quantity"::integer);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "buy_rules_min_quantity" type integer using ("buy_rules_min_quantity"::integer);' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_application_method_promotion_id" ON "promotion_application_method" (promotion_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_application_method_deleted_at" ON "promotion_application_method" (deleted_at) WHERE deleted_at IS NULL;' + ) + + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_rule_deleted_at" ON "promotion_rule" (deleted_at) WHERE deleted_at IS NULL;' + ) + + this.addSql( + 'drop index if exists "IDX_promotion_rule_promotion_rule_value_id";' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_rule_value_promotion_rule_id" ON "promotion_rule_value" (promotion_rule_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_promotion_rule_value_deleted_at" ON "promotion_rule_value" (deleted_at) WHERE deleted_at IS NULL;' + ) + } + + async down(): Promise { + this.addSql( + 'alter table if exists "promotion_campaign_budget" drop constraint if exists "promotion_campaign_budget_campaign_id_foreign";' + ) + + this.addSql('drop index if exists "IDX_promotion_campaign_deleted_at";') + + this.addSql( + 'drop index if exists "IDX_promotion_campaign_budget_campaign_id";' + ) + this.addSql( + 'drop index if exists "IDX_promotion_campaign_budget_deleted_at";' + ) + this.addSql( + 'alter table if exists "promotion_campaign_budget" add constraint "promotion_campaign_budget_campaign_id_foreign" foreign key ("campaign_id") references "promotion_campaign" ("id") on update cascade;' + ) + + this.addSql('drop index if exists "IDX_promotion_campaign_id";') + this.addSql('drop index if exists "IDX_promotion_deleted_at";') + + this.addSql( + 'alter table if exists "promotion_application_method" alter column "value" type numeric using ("value"::numeric);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "value" set not null;' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "max_quantity" type numeric using ("max_quantity"::numeric);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "apply_to_quantity" type numeric using ("apply_to_quantity"::numeric);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "buy_rules_min_quantity" type numeric using ("buy_rules_min_quantity"::numeric);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "raw_value" type jsonb using ("raw_value"::jsonb);' + ) + this.addSql( + 'alter table if exists "promotion_application_method" alter column "raw_value" set not null;' + ) + this.addSql( + 'drop index if exists "IDX_promotion_application_method_promotion_id";' + ) + this.addSql( + 'drop index if exists "IDX_promotion_application_method_deleted_at";' + ) + + this.addSql('drop index if exists "IDX_promotion_rule_deleted_at";') + + this.addSql( + 'drop index if exists "IDX_promotion_rule_value_promotion_rule_id";' + ) + this.addSql('drop index if exists "IDX_promotion_rule_value_deleted_at";') + this.addSql( + 'create index if not exists "IDX_promotion_rule_promotion_rule_value_id" on "promotion_rule_value" ("promotion_rule_id");' + ) + } +} diff --git a/packages/modules/promotion/src/models/application-method.ts b/packages/modules/promotion/src/models/application-method.ts index 57597e1a67b80..69ec151bc0204 100644 --- a/packages/modules/promotion/src/models/application-method.ts +++ b/packages/modules/promotion/src/models/application-method.ts @@ -1,126 +1,45 @@ -import { - ApplicationMethodAllocationValues, - ApplicationMethodTargetTypeValues, - ApplicationMethodTypeValues, - BigNumberRawValue, -} from "@medusajs/framework/types" -import { - BigNumber, - DALUtils, - MikroOrmBigNumberProperty, - PromotionUtils, - createPsqlIndexStatementHelper, - generateEntityId, -} from "@medusajs/framework/utils" -import { - BeforeCreate, - Collection, - Entity, - Enum, - Filter, - Index, - ManyToMany, - OnInit, - OneToOne, - PrimaryKey, - Property, - Rel, -} from "@mikro-orm/core" +import { PromotionUtils, model } from "@medusajs/framework/utils" import Promotion from "./promotion" import PromotionRule from "./promotion-rule" -const tableName = "promotion_application_method" -const CurrencyCodeIndex = createPsqlIndexStatementHelper({ - tableName, - columns: "currency_code", - where: "deleted_at IS NOT NULL", -}) - -@Entity({ tableName }) -@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) -export default class ApplicationMethod { - @PrimaryKey({ columnType: "text" }) - id!: string - - @MikroOrmBigNumberProperty() - value: BigNumber | number | null - - @Property({ columnType: "jsonb" }) - raw_value: BigNumberRawValue | null - - @Property({ columnType: "text", nullable: true }) - @CurrencyCodeIndex.MikroORMIndex() - currency_code: string | null = null - - @Property({ columnType: "numeric", nullable: true, serializer: Number }) - max_quantity?: number | null = null - - @Property({ columnType: "numeric", nullable: true, serializer: Number }) - apply_to_quantity?: number | null = null - - @Property({ columnType: "numeric", nullable: true, serializer: Number }) - buy_rules_min_quantity?: number | null = null - - @Index({ name: "IDX_application_method_type" }) - @Enum(() => PromotionUtils.ApplicationMethodType) - type: ApplicationMethodTypeValues - - @Index({ name: "IDX_application_method_target_type" }) - @Enum(() => PromotionUtils.ApplicationMethodTargetType) - target_type: ApplicationMethodTargetTypeValues - - @Index({ name: "IDX_application_method_allocation" }) - @Enum({ - items: () => PromotionUtils.ApplicationMethodAllocation, - nullable: true, - }) - allocation?: ApplicationMethodAllocationValues - - @OneToOne({ - entity: () => Promotion, - onDelete: "cascade", - }) - promotion: Rel - - @ManyToMany(() => PromotionRule, "method_target_rules", { - owner: true, - pivotTable: "application_method_target_rules", - cascade: ["soft-remove"] as any, - }) - target_rules = new Collection>(this) - - @ManyToMany(() => PromotionRule, "method_buy_rules", { - owner: true, - pivotTable: "application_method_buy_rules", - cascade: ["soft-remove"] as any, - }) - buy_rules = new Collection>(this) - - @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 | null = null - - @BeforeCreate() - onCreate() { - this.id = generateEntityId(this.id, "proappmet") - } - - @OnInit() - onInit() { - this.id = generateEntityId(this.id, "proappmet") - } -} +const ApplicationMethod = model + .define( + { name: "ApplicationMethod", tableName: "promotion_application_method" }, + { + id: model.id({ prefix: "proappmet" }).primaryKey(), + value: model.bigNumber().nullable(), + currency_code: model.text().nullable(), + max_quantity: model.number().nullable(), + apply_to_quantity: model.number().nullable(), + buy_rules_min_quantity: model.number().nullable(), + type: model + .enum(PromotionUtils.ApplicationMethodType) + .index("IDX_application_method_type"), + target_type: model + .enum(PromotionUtils.ApplicationMethodTargetType) + .index("IDX_application_method_target_type"), + allocation: model + .enum(PromotionUtils.ApplicationMethodAllocation) + .index("IDX_application_method_allocation") + .nullable(), + promotion: model.belongsTo(() => Promotion, { + mappedBy: "application_method", + }), + target_rules: model.manyToMany(() => PromotionRule, { + pivotTable: "application_method_target_rules", + mappedBy: "method_target_rules", + }), + buy_rules: model.manyToMany(() => PromotionRule, { + pivotTable: "application_method_buy_rules", + mappedBy: "method_buy_rules", + }), + } + ) + .indexes([ + { + on: ["currency_code"], + where: "deleted_at IS NOT NULL", + }, + ]) + +export default ApplicationMethod diff --git a/packages/modules/promotion/src/models/campaign-budget.ts b/packages/modules/promotion/src/models/campaign-budget.ts index 34f3dabdf982e..e2a4f87420e70 100644 --- a/packages/modules/promotion/src/models/campaign-budget.ts +++ b/packages/modules/promotion/src/models/campaign-budget.ts @@ -1,93 +1,20 @@ -import { - BigNumberRawValue, - CampaignBudgetTypeValues, - DAL, -} from "@medusajs/framework/types" -import { - BigNumber, - DALUtils, - MikroOrmBigNumberProperty, - PromotionUtils, - generateEntityId, -} from "@medusajs/framework/utils" -import { - BeforeCreate, - Entity, - Enum, - Filter, - Index, - OnInit, - OneToOne, - OptionalProps, - PrimaryKey, - Property, - Rel, -} from "@mikro-orm/core" +import { PromotionUtils, model } from "@medusajs/framework/utils" import Campaign from "./campaign" -type OptionalFields = - | "description" - | "limit" - | "used" - | DAL.SoftDeletableModelDateColumns - -@Entity({ tableName: "promotion_campaign_budget" }) -@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) -export default class CampaignBudget { - [OptionalProps]?: OptionalFields - - @PrimaryKey({ columnType: "text" }) - id!: string - - @Index({ name: "IDX_campaign_budget_type" }) - @Enum(() => PromotionUtils.CampaignBudgetType) - type: CampaignBudgetTypeValues - - @OneToOne({ - entity: () => Campaign, - }) - campaign: Rel | null = null - - @Property({ columnType: "text", nullable: true }) - currency_code: string | null = null - - @MikroOrmBigNumberProperty({ nullable: true }) - limit: BigNumber | number | null = null - - @Property({ columnType: "jsonb", nullable: true }) - raw_limit: BigNumberRawValue | null = null - - @MikroOrmBigNumberProperty({ default: 0 }) - used: BigNumber | number = 0 - - @Property({ columnType: "jsonb" }) - raw_used: BigNumberRawValue - - @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 | null = null - - @BeforeCreate() - onCreate() { - this.id = generateEntityId(this.id, "probudg") +const CampaignBudget = model.define( + { name: "CampaignBudget", tableName: "promotion_campaign_budget" }, + { + id: model.id({ prefix: "probudg" }).primaryKey(), + type: model + .enum(PromotionUtils.CampaignBudgetType) + .index("IDX_campaign_budget_type"), + currency_code: model.text().nullable(), + limit: model.bigNumber().nullable(), + used: model.bigNumber().default(0), + campaign: model.belongsTo(() => Campaign, { + mappedBy: "budget", + }), } +) - @OnInit() - onInit() { - this.id = generateEntityId(this.id, "probudg") - } -} +export default CampaignBudget diff --git a/packages/modules/promotion/src/models/campaign.ts b/packages/modules/promotion/src/models/campaign.ts index cd49a92813832..e114045ec75b6 100644 --- a/packages/modules/promotion/src/models/campaign.ts +++ b/packages/modules/promotion/src/models/campaign.ts @@ -1,109 +1,36 @@ -import { DAL } from "@medusajs/framework/types" -import { - DALUtils, - Searchable, - createPsqlIndexStatementHelper, - generateEntityId, -} from "@medusajs/framework/utils" -import { - BeforeCreate, - Collection, - Entity, - Filter, - OnInit, - OneToMany, - OneToOne, - OptionalProps, - PrimaryKey, - Property, - Rel, -} from "@mikro-orm/core" +import { model } from "@medusajs/framework/utils" import CampaignBudget from "./campaign-budget" import Promotion from "./promotion" -type OptionalRelations = "budget" -type OptionalFields = - | "description" - | "starts_at" - | "ends_at" - | DAL.SoftDeletableModelDateColumns - -const tableName = "promotion_campaign" -const CampaignUniqueCampaignIdentifier = createPsqlIndexStatementHelper({ - tableName, - columns: ["campaign_identifier"], - unique: true, - where: "deleted_at IS NULL", -}) - -@Entity({ tableName }) -@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) -export default class Campaign { - [OptionalProps]?: OptionalFields | OptionalRelations - - @PrimaryKey({ columnType: "text" }) - id!: string - - @Searchable() - @Property({ columnType: "text" }) - name: string - - @Searchable() - @Property({ columnType: "text", nullable: true }) - description: string | null = null - - @Property({ columnType: "text" }) - @CampaignUniqueCampaignIdentifier.MikroORMIndex() - campaign_identifier: string - - @Property({ - columnType: "timestamptz", - nullable: true, +const Campaign = model + .define( + { name: "Campaign", tableName: "promotion_campaign" }, + { + id: model.id({ prefix: "procamp" }).primaryKey(), + name: model.text().searchable(), + description: model.text().searchable().nullable(), + campaign_identifier: model.text(), + starts_at: model.dateTime().nullable(), + ends_at: model.dateTime().nullable(), + budget: model + .hasOne<() => typeof CampaignBudget>(() => CampaignBudget, { + mappedBy: "campaign", + }) + .nullable(), + promotions: model.hasMany(() => Promotion, { + mappedBy: "campaign", + }), + } + ) + .cascades({ + delete: ["budget"], }) - starts_at: Date | null = null - - @Property({ - columnType: "timestamptz", - nullable: true, - }) - ends_at: Date | null = null - - @OneToOne({ - entity: () => CampaignBudget, - mappedBy: (cb) => cb.campaign, - cascade: ["soft-remove"] as any, - nullable: true, - }) - budget: Rel | null = null - - @OneToMany(() => Promotion, (promotion) => promotion.campaign) - promotions = new Collection>(this) - - @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 | null = null - - @BeforeCreate() - onCreate() { - this.id = generateEntityId(this.id, "procamp") - } - - @OnInit() - onInit() { - this.id = generateEntityId(this.id, "procamp") - } -} + .indexes([ + { + on: ["campaign_identifier"], + unique: true, + where: "deleted_at IS NULL", + }, + ]) + +export default Campaign diff --git a/packages/modules/promotion/src/models/promotion-rule-value.ts b/packages/modules/promotion/src/models/promotion-rule-value.ts index 285938c2e0244..366d71daeebea 100644 --- a/packages/modules/promotion/src/models/promotion-rule-value.ts +++ b/packages/modules/promotion/src/models/promotion-rule-value.ts @@ -1,57 +1,15 @@ -import { DALUtils, generateEntityId } from "@medusajs/framework/utils" -import { - BeforeCreate, - Entity, - Filter, - ManyToOne, - OnInit, - PrimaryKey, - Property, - Rel, -} from "@mikro-orm/core" +import { model } from "@medusajs/framework/utils" import PromotionRule from "./promotion-rule" -@Entity({ tableName: "promotion_rule_value" }) -@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) -export default class PromotionRuleValue { - @PrimaryKey({ columnType: "text" }) - id!: string - - @ManyToOne(() => PromotionRule, { - onDelete: "cascade", - fieldName: "promotion_rule_id", - index: "IDX_promotion_rule_promotion_rule_value_id", - }) - promotion_rule: Rel - - @Property({ columnType: "text" }) - value: string - - @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 | null = null - - @BeforeCreate() - onCreate() { - this.id = generateEntityId(this.id, "prorulval") +const PromotionRuleValue = model.define( + { name: "PromotionRuleValue", tableName: "promotion_rule_value" }, + { + id: model.id({ prefix: "prorulval" }).primaryKey(), + value: model.text(), + promotion_rule: model.belongsTo(() => PromotionRule, { + mappedBy: "values", + }), } +) - @OnInit() - onInit() { - this.id = generateEntityId(this.id, "prorulval") - } -} +export default PromotionRuleValue diff --git a/packages/modules/promotion/src/models/promotion-rule.ts b/packages/modules/promotion/src/models/promotion-rule.ts index 9e69abb0f17f3..2c6f4347eefc4 100644 --- a/packages/modules/promotion/src/models/promotion-rule.ts +++ b/packages/modules/promotion/src/models/promotion-rule.ts @@ -1,96 +1,37 @@ -import { DAL, PromotionRuleOperatorValues } from "@medusajs/framework/types" -import { - DALUtils, - PromotionUtils, - generateEntityId, -} from "@medusajs/framework/utils" -import { - BeforeCreate, - Cascade, - Collection, - Entity, - Enum, - Filter, - Index, - ManyToMany, - OnInit, - OneToMany, - OptionalProps, - PrimaryKey, - Property, - Rel, -} from "@mikro-orm/core" +import { PromotionUtils, model } from "@medusajs/framework/utils" import ApplicationMethod from "./application-method" import Promotion from "./promotion" import PromotionRuleValue from "./promotion-rule-value" -type OptionalFields = "description" | DAL.SoftDeletableModelDateColumns -type OptionalRelations = "values" | "promotions" - -@Entity({ tableName: "promotion_rule" }) -@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) -export default class PromotionRule { - [OptionalProps]?: OptionalFields | OptionalRelations - - @PrimaryKey({ columnType: "text" }) - id!: string - - @Property({ columnType: "text", nullable: true }) - description: string | null = null - - @Index({ name: "IDX_promotion_rule_attribute" }) - @Property({ columnType: "text" }) - attribute: string - - @Index({ name: "IDX_promotion_rule_operator" }) - @Enum(() => PromotionUtils.PromotionRuleOperator) - operator: PromotionRuleOperatorValues - - @OneToMany(() => PromotionRuleValue, (prv) => prv.promotion_rule, { - cascade: [Cascade.REMOVE], - }) - values = new Collection>(this) - - @ManyToMany(() => Promotion, (promotion) => promotion.rules) - promotions = new Collection>(this) - - @ManyToMany( - () => ApplicationMethod, - (applicationMethod) => applicationMethod.target_rules - ) - method_target_rules = new Collection>(this) - - @ManyToMany( - () => ApplicationMethod, - (applicationMethod) => applicationMethod.buy_rules +const PromotionRule = model + .define( + { + name: "PromotionRule", + tableName: "promotion_rule", + }, + { + id: model.id({ prefix: "prorul" }).primaryKey(), + description: model.text().nullable(), + attribute: model.text().index("IDX_promotion_rule_attribute"), + operator: model + .enum(PromotionUtils.PromotionRuleOperator) + .index("IDX_promotion_rule_operator"), + values: model.hasMany(() => PromotionRuleValue, { + mappedBy: "promotion_rule", + }), + promotions: model.manyToMany(() => Promotion, { + mappedBy: "rules", + }), + method_target_rules: model.manyToMany(() => ApplicationMethod, { + mappedBy: "target_rules", + }), + method_buy_rules: model.manyToMany(() => ApplicationMethod, { + mappedBy: "buy_rules", + }), + } ) - method_buy_rules = new Collection>(this) - - @Property({ - onCreate: () => new Date(), - columnType: "timestamptz", - defaultRaw: "now()", + .cascades({ + delete: ["values"], }) - 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 | null = null - - @BeforeCreate() - onCreate() { - this.id = generateEntityId(this.id, "prorul") - } - @OnInit() - onInit() { - this.id = generateEntityId(this.id, "prorul") - } -} +export default PromotionRule diff --git a/packages/modules/promotion/src/models/promotion.ts b/packages/modules/promotion/src/models/promotion.ts index 01c6cf2fa1e88..56c57a4acfad2 100644 --- a/packages/modules/promotion/src/models/promotion.ts +++ b/packages/modules/promotion/src/models/promotion.ts @@ -1,108 +1,35 @@ -import { DAL, PromotionTypeValues } from "@medusajs/framework/types" -import { - DALUtils, - PromotionUtils, - Searchable, - generateEntityId, -} from "@medusajs/framework/utils" -import { - BeforeCreate, - Collection, - Entity, - Enum, - Filter, - Index, - ManyToMany, - ManyToOne, - OnInit, - OneToOne, - OptionalProps, - PrimaryKey, - Property, - Rel, - Unique, -} from "@mikro-orm/core" +import { PromotionUtils, model } from "@medusajs/framework/utils" import ApplicationMethod from "./application-method" import Campaign from "./campaign" import PromotionRule from "./promotion-rule" -type OptionalFields = "is_automatic" | DAL.SoftDeletableModelDateColumns -type OptionalRelations = "application_method" | "campaign" - -@Entity({ tableName: "promotion" }) -@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) -export default class Promotion { - [OptionalProps]?: OptionalFields | OptionalRelations - - @PrimaryKey({ columnType: "text" }) - id!: string - - @Searchable() - @Property({ columnType: "text" }) - @Index({ name: "IDX_promotion_code" }) - @Unique({ - name: "IDX_promotion_code_unique", - properties: ["code"], - }) - code: string - - @ManyToOne(() => Campaign, { - columnType: "text", - fieldName: "campaign_id", - nullable: true, - mapToPk: true, +const Promotion = model + .define("Promotion", { + id: model.id({ prefix: "promo" }).primaryKey(), + code: model + .text() + .searchable() + .unique("IDX_promotion_code_unique") + .index("IDX_promotion_code"), + is_automatic: model.boolean().default(false), + type: model.enum(PromotionUtils.PromotionType).index("IDX_promotion_type"), + campaign: model + .belongsTo(() => Campaign, { + mappedBy: "promotions", + }) + .nullable(), + application_method: model + .hasOne<() => typeof ApplicationMethod>(() => ApplicationMethod, { + mappedBy: "promotion", + }) + .nullable(), + rules: model.manyToMany<() => typeof PromotionRule>(() => PromotionRule, { + pivotTable: "promotion_promotion_rule", + mappedBy: "promotions", + }), }) - campaign_id: string | null = null - - @ManyToOne(() => Campaign, { persist: false }) - campaign: Rel | null - - @Property({ columnType: "boolean", default: false }) - is_automatic: boolean = false - - @Index({ name: "IDX_promotion_type" }) - @Enum(() => PromotionUtils.PromotionType) - type: PromotionTypeValues - - @OneToOne({ - entity: () => ApplicationMethod, - mappedBy: (am) => am.promotion, - cascade: ["soft-remove"] as any, + .cascades({ + delete: ["application_method"], }) - application_method: Rel - - @ManyToMany(() => PromotionRule, "promotions", { - owner: true, - pivotTable: "promotion_promotion_rule", - cascade: ["soft-remove"] as any, - }) - rules = new Collection>(this) - - @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 | null = null - - @BeforeCreate() - onCreate() { - this.id = generateEntityId(this.id, "promo") - } - @OnInit() - onInit() { - this.id = generateEntityId(this.id, "promo") - } -} +export default Promotion diff --git a/packages/modules/promotion/src/services/promotion-module.ts b/packages/modules/promotion/src/services/promotion-module.ts index 8ca859f3080cd..96cd6c21178de 100644 --- a/packages/modules/promotion/src/services/promotion-module.ts +++ b/packages/modules/promotion/src/services/promotion-module.ts @@ -2,6 +2,7 @@ import { CampaignBudgetTypeValues, Context, DAL, + InferEntityType, InternalModuleDeclaration, ModuleJoinerConfig, ModulesSdkTypes, @@ -24,6 +25,7 @@ import { MedusaError, MedusaService, PromotionType, + toMikroORMEntity, transformPropertiesToBigNumber, } from "@medusajs/framework/utils" import { @@ -85,12 +87,24 @@ export default class PromotionModuleService implements PromotionTypes.IPromotionModuleService { protected baseRepository_: DAL.RepositoryService - protected promotionService_: ModulesSdkTypes.IMedusaInternalService - protected applicationMethodService_: ModulesSdkTypes.IMedusaInternalService - protected promotionRuleService_: ModulesSdkTypes.IMedusaInternalService - protected promotionRuleValueService_: ModulesSdkTypes.IMedusaInternalService - protected campaignService_: ModulesSdkTypes.IMedusaInternalService - protected campaignBudgetService_: ModulesSdkTypes.IMedusaInternalService + protected promotionService_: ModulesSdkTypes.IMedusaInternalService< + InferEntityType + > + protected applicationMethodService_: ModulesSdkTypes.IMedusaInternalService< + InferEntityType + > + protected promotionRuleService_: ModulesSdkTypes.IMedusaInternalService< + InferEntityType + > + protected promotionRuleValueService_: ModulesSdkTypes.IMedusaInternalService< + InferEntityType + > + protected campaignService_: ModulesSdkTypes.IMedusaInternalService< + InferEntityType + > + protected campaignBudgetService_: ModulesSdkTypes.IMedusaInternalService< + InferEntityType + > constructor( { @@ -841,9 +855,10 @@ export default class PromotionModuleService { relations: ["budget"] } ) - const existingPromotionsMap = new Map( - existingPromotions.map((promotion) => [promotion.id, promotion]) - ) + const existingPromotionsMap = new Map< + string, + InferEntityType + >(existingPromotions.map((promotion) => [promotion.id, promotion])) const promotionsData: UpdatePromotionDTO[] = [] const applicationMethodsData: UpdateApplicationMethodDTO[] = [] @@ -1099,12 +1114,17 @@ export default class PromotionModuleService protected async createPromotionRulesAndValues_( rulesData: PromotionTypes.CreatePromotionRuleDTO[], relationName: "promotions" | "method_target_rules" | "method_buy_rules", - relation: Promotion | ApplicationMethod, + relation: + | InferEntityType + | InferEntityType, @MedusaContext() sharedContext: Context = {} - ): Promise { - const createdPromotionRules: PromotionRule[] = [] + ): Promise[]> { + const MikroORMApplicationMethod = toMikroORMEntity(ApplicationMethod) + const createdPromotionRules: InferEntityType[] = [] const promotion = - relation instanceof ApplicationMethod ? relation.promotion : relation + relation instanceof MikroORMApplicationMethod + ? relation.promotion + : relation if (!rulesData.length) { return [] diff --git a/packages/modules/promotion/src/types/application-method.ts b/packages/modules/promotion/src/types/application-method.ts index 09023530a42a9..fda9a32649c0c 100644 --- a/packages/modules/promotion/src/types/application-method.ts +++ b/packages/modules/promotion/src/types/application-method.ts @@ -3,6 +3,7 @@ import { ApplicationMethodTargetTypeValues, ApplicationMethodTypeValues, BigNumberInput, + InferEntityType, PromotionDTO, } from "@medusajs/framework/types" @@ -14,7 +15,7 @@ export interface CreateApplicationMethodDTO { allocation?: ApplicationMethodAllocationValues value?: BigNumberInput currency_code?: string | null - promotion: Promotion | string | PromotionDTO + promotion: InferEntityType | string | PromotionDTO max_quantity?: BigNumberInput | null buy_rules_min_quantity?: BigNumberInput | null apply_to_quantity?: BigNumberInput | null @@ -27,7 +28,7 @@ export interface UpdateApplicationMethodDTO { allocation?: ApplicationMethodAllocationValues value?: BigNumberInput currency_code?: string | null - promotion?: Promotion | string | PromotionDTO + promotion?: InferEntityType | string | PromotionDTO max_quantity?: BigNumberInput | null buy_rules_min_quantity?: BigNumberInput | null apply_to_quantity?: BigNumberInput | null diff --git a/packages/modules/promotion/src/types/campaign-budget.ts b/packages/modules/promotion/src/types/campaign-budget.ts index 30a9f4e224402..7b37f429b9f02 100644 --- a/packages/modules/promotion/src/types/campaign-budget.ts +++ b/packages/modules/promotion/src/types/campaign-budget.ts @@ -1,6 +1,7 @@ import { BigNumberInput, CampaignBudgetTypeValues, + InferEntityType, } from "@medusajs/framework/types" import { Campaign } from "@models" @@ -9,7 +10,7 @@ export interface CreateCampaignBudgetDTO { limit?: BigNumberInput | null currency_code?: string | null used?: BigNumberInput - campaign?: Campaign | string + campaign?: InferEntityType | string } export interface UpdateCampaignBudgetDTO { diff --git a/packages/modules/promotion/src/types/campaign.ts b/packages/modules/promotion/src/types/campaign.ts index 5e3d4e6ad72e6..b90da78f7dc15 100644 --- a/packages/modules/promotion/src/types/campaign.ts +++ b/packages/modules/promotion/src/types/campaign.ts @@ -1,4 +1,4 @@ -import { PromotionDTO } from "@medusajs/framework/types" +import { InferEntityType, PromotionDTO } from "@medusajs/framework/types" import { Promotion } from "@models" export interface CreateCampaignDTO { @@ -7,7 +7,7 @@ export interface CreateCampaignDTO { campaign_identifier: string starts_at?: Date | null ends_at?: Date | null - promotions?: (PromotionDTO | Promotion)[] + promotions?: (PromotionDTO | InferEntityType)[] } export interface UpdateCampaignDTO { @@ -17,5 +17,5 @@ export interface UpdateCampaignDTO { campaign_identifier?: string starts_at?: Date | null ends_at?: Date | null - promotions?: (PromotionDTO | Promotion)[] + promotions?: (PromotionDTO | InferEntityType)[] } diff --git a/packages/modules/promotion/src/types/promotion-rule-value.ts b/packages/modules/promotion/src/types/promotion-rule-value.ts index e403a1b35d648..c6192a7283a9c 100644 --- a/packages/modules/promotion/src/types/promotion-rule-value.ts +++ b/packages/modules/promotion/src/types/promotion-rule-value.ts @@ -1,13 +1,19 @@ -import { PromotionRuleDTO } from "@medusajs/framework/types" +import { InferEntityType, PromotionRuleDTO } from "@medusajs/framework/types" import { PromotionRule } from "@models" export interface CreatePromotionRuleValueDTO { value: any - promotion_rule: string | PromotionRuleDTO | PromotionRule + promotion_rule: + | string + | PromotionRuleDTO + | InferEntityType } export interface UpdatePromotionRuleValueDTO { id: string value: any - promotion_rule: string | PromotionRuleDTO | PromotionRule + promotion_rule: + | string + | PromotionRuleDTO + | InferEntityType } diff --git a/packages/modules/promotion/src/utils/validations/application-method.ts b/packages/modules/promotion/src/utils/validations/application-method.ts index ae98f075a4d95..570849756d58a 100644 --- a/packages/modules/promotion/src/utils/validations/application-method.ts +++ b/packages/modules/promotion/src/utils/validations/application-method.ts @@ -9,6 +9,7 @@ import { MedusaError, PromotionType, } from "@medusajs/framework/utils" +import { InferEntityType } from "@medusajs/types" import { Promotion } from "@models" import { CreateApplicationMethodDTO, UpdateApplicationMethodDTO } from "@types" @@ -28,7 +29,7 @@ export const allowedAllocationForQuantity: string[] = [ export function validateApplicationMethodAttributes( data: UpdateApplicationMethodDTO | CreateApplicationMethodDTO, - promotion: Promotion + promotion: InferEntityType ) { const applicationMethod = promotion?.application_method || {} const buyRulesMinQuantity =