diff --git a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/campaign.spec.ts b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/campaign.spec.ts index 1c5ad7457b..f22c04f3da 100644 --- a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/campaign.spec.ts +++ b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/campaign.spec.ts @@ -1,9 +1,9 @@ import { IPromotionModuleService } from "@medusajs/types" +import { Modules } from "@medusajs/utils" import { moduleIntegrationTestRunner } from "medusa-test-utils" import { CampaignBudgetType } from "../../../../../../core/utils/src/promotion/index" import { createCampaigns } from "../../../__fixtures__/campaigns" import { createPromotions } from "../../../__fixtures__/promotion" -import { Modules } from "@medusajs/utils" jest.setTimeout(30000) @@ -170,6 +170,35 @@ moduleIntegrationTestRunner({ }) ) }) + + it("should create a campaign with a campaign identitifer of a deleted campaign", async () => { + const campaign = await service.createCampaigns({ + name: "test", + campaign_identifier: "test", + }) + + await service.softDeleteCampaigns(campaign.id) + + const recreatedCampaign = await service.createCampaigns({ + name: "test", + campaign_identifier: "test", + }) + + expect(recreatedCampaign).toEqual( + expect.objectContaining({ + name: "test", + campaign_identifier: "test", + }) + ) + + const error = await service + .restoreCampaigns(campaign.id) + .catch((e) => e) + + expect(error.message).toEqual( + "Promotion campaign with campaign_identifier: test, already exists." + ) + }) }) describe("updateCampaigns", () => { diff --git a/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json b/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json index 6164ef4f7b..597f6c69d6 100644 --- a/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json +++ b/packages/modules/promotion/src/migrations/.snapshot-medusa-promotion.json @@ -99,13 +99,14 @@ "schema": "public", "indexes": [ { - "keyName": "IDX_campaign_identifier_unique", + "keyName": "IDX_promotion_campaign_campaign_identifier_unique", "columnNames": [ "campaign_identifier" ], "composite": false, "primary": false, - "unique": true + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_promotion_campaign_campaign_identifier_unique\" ON \"promotion_campaign\" (campaign_identifier) WHERE deleted_at IS NULL" }, { "keyName": "promotion_campaign_pkey", diff --git a/packages/modules/promotion/src/migrations/Migration20240624153824.ts b/packages/modules/promotion/src/migrations/Migration20240624153824.ts new file mode 100644 index 0000000000..9b858e2e3f --- /dev/null +++ b/packages/modules/promotion/src/migrations/Migration20240624153824.ts @@ -0,0 +1,21 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240624153824 extends Migration { + async up(): Promise { + this.addSql( + 'alter table if exists "promotion_campaign" drop constraint if exists "IDX_campaign_identifier_unique";' + ) + this.addSql( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_promotion_campaign_campaign_identifier_unique" ON "promotion_campaign" (campaign_identifier) WHERE deleted_at IS NULL;' + ) + } + + async down(): Promise { + this.addSql( + 'drop index if exists "IDX_promotion_campaign_campaign_identifier_unique";' + ) + this.addSql( + 'alter table if exists "promotion_campaign" add constraint "IDX_campaign_identifier_unique" unique ("campaign_identifier");' + ) + } +} diff --git a/packages/modules/promotion/src/models/campaign.ts b/packages/modules/promotion/src/models/campaign.ts index b4cf9ed5cb..b8d4f0c3e2 100644 --- a/packages/modules/promotion/src/models/campaign.ts +++ b/packages/modules/promotion/src/models/campaign.ts @@ -1,5 +1,10 @@ import { DAL } from "@medusajs/types" -import { DALUtils, Searchable, generateEntityId } from "@medusajs/utils" +import { + DALUtils, + Searchable, + createPsqlIndexStatementHelper, + generateEntityId, +} from "@medusajs/utils" import { BeforeCreate, Collection, @@ -12,7 +17,6 @@ import { PrimaryKey, Property, Rel, - Unique, } from "@mikro-orm/core" import CampaignBudget from "./campaign-budget" import Promotion from "./promotion" @@ -24,7 +28,15 @@ type OptionalFields = | "ends_at" | DAL.SoftDeletableEntityDateColumns -@Entity({ tableName: "promotion_campaign" }) +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 @@ -41,10 +53,7 @@ export default class Campaign { description: string | null = null @Property({ columnType: "text" }) - @Unique({ - name: "IDX_campaign_identifier_unique", - properties: ["campaign_identifier"], - }) + @CampaignUniqueCampaignIdentifier.MikroORMIndex() campaign_identifier: string @Property({