diff --git a/packages/modules/tax/integration-tests/__tests__/index.spec.ts b/packages/modules/tax/integration-tests/__tests__/index.spec.ts index 1534dc6bc5..a10ddd5c86 100644 --- a/packages/modules/tax/integration-tests/__tests__/index.spec.ts +++ b/packages/modules/tax/integration-tests/__tests__/index.spec.ts @@ -1,8 +1,8 @@ -import { moduleIntegrationTestRunner } from "medusa-test-utils" import { ITaxModuleService } from "@medusajs/types" -import { setupTaxStructure } from "../utils/setup-tax-structure" import { Module, Modules } from "@medusajs/utils" import { TaxModuleService } from "@services" +import { moduleIntegrationTestRunner } from "medusa-test-utils" +import { setupTaxStructure } from "../utils/setup-tax-structure" jest.setTimeout(30000) @@ -62,6 +62,191 @@ moduleIntegrationTestRunner({ }) }) + it("should create tax region", async () => { + const region = await service.createTaxRegions({ + country_code: "US", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + expect(region).toEqual( + expect.objectContaining({ + id: region.id, + country_code: "us", + }) + ) + }) + + it("should create two tax regions with the same country code but different province", async () => { + const regionOne = await service.createTaxRegions({ + country_code: "US", + province_code: "CA", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + const regionTwo = await service.createTaxRegions({ + country_code: "US", + province_code: "NY", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + expect(regionOne).toEqual( + expect.objectContaining({ + id: regionOne.id, + country_code: "us", + province_code: "ca", + }) + ) + + expect(regionTwo).toEqual( + expect.objectContaining({ + id: regionTwo.id, + country_code: "us", + province_code: "ny", + }) + ) + }) + + it("should create two tax regions in a child-parent-like relationship", async () => { + const regionOne = await service.createTaxRegions({ + country_code: "US", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + const regionTwo = await service.createTaxRegions({ + country_code: "US", + parent_id: regionOne.id, + province_code: "NY", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + expect(regionOne).toEqual( + expect.objectContaining({ + id: regionOne.id, + country_code: "us", + province_code: null, + }) + ) + + expect(regionTwo).toEqual( + expect.objectContaining({ + id: regionTwo.id, + country_code: "us", + province_code: "ny", + }) + ) + }) + + it("should create three tax regions in a child-parent-like relationship", async () => { + const regionOne = await service.createTaxRegions({ + country_code: "US", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + const regionTwo = await service.createTaxRegions({ + country_code: "US", + parent_id: regionOne.id, + province_code: "NY", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + const regionThree = await service.createTaxRegions({ + country_code: "US", + parent_id: regionOne.id, + province_code: "NE", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + expect(regionOne).toEqual( + expect.objectContaining({ + id: regionOne.id, + country_code: "us", + province_code: null, + }) + ) + + expect(regionTwo).toEqual( + expect.objectContaining({ + id: regionTwo.id, + country_code: "us", + province_code: "ny", + }) + ) + + expect(regionThree).toEqual( + expect.objectContaining({ + id: regionThree.id, + country_code: "us", + province_code: "ne", + }) + ) + }) + + it("should throw when creating a tax region with a country code of an existing region", async () => { + await service.createTaxRegions({ + country_code: "US", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + const error = await service.createTaxRegions({ + country_code: "US", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }).catch(e => e) + + expect(error.message).toEqual("Tax region with country_code: us, already exists.") + }) + + it("should throw when creating a tax region with a country code and province code of an existing region", async () => { + await service.createTaxRegions({ + country_code: "US", + province_code: "CA", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }) + + const error = await service.createTaxRegions({ + country_code: "US", + province_code: "CA", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }).catch(e => e) + + expect(error.message).toEqual("Tax region with country_code: us, province_code: ca, already exists.") + }) + it("should create tax rates and update them", async () => { const region = await service.createTaxRegions({ country_code: "US", diff --git a/packages/modules/tax/src/migrations/.snapshot-medusa-tax.json b/packages/modules/tax/src/migrations/.snapshot-medusa-tax.json index 5c37511474..b367b1a332 100644 --- a/packages/modules/tax/src/migrations/.snapshot-medusa-tax.json +++ b/packages/modules/tax/src/migrations/.snapshot-medusa-tax.json @@ -168,7 +168,15 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_tax_region_unique_country_province\" ON \"tax_region\" (country_code, province_code)" + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_tax_region_unique_country_province\" ON \"tax_region\" (country_code, province_code) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_tax_region_unique_country_nullable_province", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_tax_region_unique_country_nullable_province\" ON \"tax_region\" (country_code) WHERE province_code IS NULL AND deleted_at IS NULL" }, { "keyName": "tax_region_pkey", diff --git a/packages/modules/tax/src/migrations/Migration20240710135844.ts b/packages/modules/tax/src/migrations/Migration20240710135844.ts new file mode 100644 index 0000000000..dc889699e7 --- /dev/null +++ b/packages/modules/tax/src/migrations/Migration20240710135844.ts @@ -0,0 +1,17 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240710135844 extends Migration { + + async up(): Promise { + this.addSql('drop index if exists "IDX_tax_region_unique_country_province";'); + this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_tax_region_unique_country_province" ON "tax_region" (country_code, province_code) WHERE deleted_at IS NULL;'); + this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_tax_region_unique_country_nullable_province" ON "tax_region" (country_code) WHERE province_code IS NULL AND deleted_at IS NULL;'); + } + + async down(): Promise { + this.addSql('drop index if exists "IDX_tax_region_unique_country_province";'); + this.addSql('drop index if exists "IDX_tax_region_unique_country_nullable_province";'); + this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_tax_region_unique_country_province" ON "tax_region" (country_code, province_code);'); + } + +} diff --git a/packages/modules/tax/src/models/tax-region.ts b/packages/modules/tax/src/models/tax-region.ts index 82580a07e5..e37f824c73 100644 --- a/packages/modules/tax/src/models/tax-region.ts +++ b/packages/modules/tax/src/models/tax-region.ts @@ -27,6 +27,8 @@ type OptionalTaxRegionProps = DAL.SoftDeletableModelDateColumns const TABLE_NAME = "tax_region" +export const countryCodeNullProvinceIndexName = + "IDX_tax_region_unique_country_nullable_province" export const countryCodeProvinceIndexName = "IDX_tax_region_unique_country_province" const countryCodeProvinceIndexStatement = createPsqlIndexStatementHelper({ @@ -34,8 +36,18 @@ const countryCodeProvinceIndexStatement = createPsqlIndexStatementHelper({ tableName: TABLE_NAME, columns: ["country_code", "province_code"], unique: true, + where: "deleted_at IS NULL", }) +const countryCodeNullableProvinceIndexStatement = + createPsqlIndexStatementHelper({ + name: countryCodeNullProvinceIndexName, + tableName: TABLE_NAME, + columns: ["country_code"], + unique: true, + where: "province_code IS NULL AND deleted_at IS NULL", + }) + export const taxRegionProviderTopLevelCheckName = "CK_tax_region_provider_top_level" export const taxRegionCountryTopLevelCheckName = @@ -49,6 +61,7 @@ export const taxRegionCountryTopLevelCheckName = name: taxRegionCountryTopLevelCheckName, expression: `parent_id IS NULL OR province_code IS NOT NULL`, }) +@countryCodeNullableProvinceIndexStatement.MikroORMIndex() @countryCodeProvinceIndexStatement.MikroORMIndex() @Entity({ tableName: TABLE_NAME }) @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)