diff --git a/packages/region/integration-tests/__tests__/region-module.spec.ts b/packages/region/integration-tests/__tests__/region-module.spec.ts index 0a1871cf54..cf43c02bee 100644 --- a/packages/region/integration-tests/__tests__/region-module.spec.ts +++ b/packages/region/integration-tests/__tests__/region-module.spec.ts @@ -86,6 +86,87 @@ describe("Region Module Service", () => { ) }) + it("should create a region with countries", async () => { + const createdRegion = await service.create({ + name: "North America", + currency_code: "USD", + countries: ["us", "ca"], + }) + + const region = await service.retrieve(createdRegion.id, { + relations: ["countries", "currency"], + }) + + expect(region).toEqual( + expect.objectContaining({ + id: region.id, + name: "North America", + currency_code: "usd", + currency: expect.objectContaining({ + code: "usd", + name: "US Dollar", + }), + countries: [ + expect.objectContaining({ + display_name: "Canada", + iso_2: "ca", + }), + expect.objectContaining({ + display_name: "United States", + iso_2: "us", + }), + ], + }) + ) + }) + + it("should throw when country doesn't exist", async () => { + await expect( + service.create({ + name: "North America", + currency_code: "USD", + countries: ["neverland"], + }) + ).rejects.toThrowError("Countries with codes neverland does not exist") + }) + + it("should throw when country is already assigned to a region", async () => { + await service.create({ + name: "North America", + currency_code: "USD", + countries: ["us"], + }) + + await expect( + service.create({ + name: "United States", + currency_code: "USD", + countries: ["us"], + }) + ).rejects.toThrowError( + "Country with code us is already assigned to a region" + ) + }) + + it("should throw when country is being assigned to multiple regions", async () => { + await expect( + service.create([ + { + name: "United States", + currency_code: "USD", + countries: ["us"], + }, + { + name: "North America", + currency_code: "USD", + countries: ["us"], + }, + ]) + ).rejects.toThrowError( + "Country with code us is already assigned to a region" + ) + }) + it("should fail when currency does not exist", async () => { await expect( service.create({ diff --git a/packages/region/src/models/country.ts b/packages/region/src/models/country.ts index 4f65828288..9df8d781c6 100644 --- a/packages/region/src/models/country.ts +++ b/packages/region/src/models/country.ts @@ -30,13 +30,15 @@ export default class Country { @Property({ columnType: "text" }) display_name: string + @Property({ columnType: "text", nullable: true }) + region_id?: string | null = null + @ManyToOne({ entity: () => Region, - fieldName: "region_id", index: "IDX_country_region_id", nullable: true, }) - region: Region | null = null + region?: Region | null @BeforeCreate() onCreate() { diff --git a/packages/region/src/services/region-module.ts b/packages/region/src/services/region-module.ts index d39c18745d..0089b06c54 100644 --- a/packages/region/src/services/region-module.ts +++ b/packages/region/src/services/region-module.ts @@ -23,7 +23,11 @@ import { import { Country, Currency, Region } from "@models" import { DefaultsUtils } from "@medusajs/utils" -import { CreateCountryDTO, CreateCurrencyDTO } from "@types" +import { + CreateCountryDTO, + CreateCurrencyDTO, + CreateRegionDTO as InternalCreateRegionDTO, +} from "@types" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" const COUNTRIES_LIMIT = 1000 @@ -115,20 +119,70 @@ export default class RegionModuleService< sharedContext ) - let currencyMap = new Map(currencies.map((c) => [c.code.toLowerCase(), c])) - for (const reg of data) { - const lowerCasedCurrency = reg.currency_code.toLowerCase() + const countriesInDb = await this.countryService_.list( + { iso_2: data.map((d) => d.countries).flat() }, + { select: ["iso_2", "region_id"] }, + sharedContext + ) + + const countriesInDbMap = new Map( + countriesInDb.map((c) => [c.iso_2, c]) + ) + + const currencyMap = new Map( + currencies.map((c) => [c.code.toLowerCase(), c]) + ) + + const toCreate: InternalCreateRegionDTO[] = [] + const seenCountries = new Set() + for (const region of data) { + const reg = { ...region } as InternalCreateRegionDTO + const countriesToAdd = region.countries || [] + + if (countriesToAdd) { + const notInDb = countriesToAdd.filter( + (c) => !countriesInDbMap.has(c.toLowerCase()) + ) + + if (notInDb.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Countries with codes ${countriesToAdd.join(", ")} does not exist` + ) + } + + const regionCountries = countriesToAdd.map((code) => { + const country = countriesInDbMap.get(code.toLowerCase()) + + if (country?.region_id || seenCountries.has(code.toLowerCase())) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Country with code ${code} is already assigned to a region` + ) + } + + seenCountries.add(code.toLowerCase()) + + return country + }) as unknown as TCountry[] + + reg.countries = regionCountries + } + + const lowerCasedCurrency = region.currency_code.toLowerCase() if (!currencyMap.has(lowerCasedCurrency)) { throw new MedusaError( MedusaError.Types.INVALID_DATA, - `Currency with code: ${reg.currency_code} was not found` + `Currency with code: ${region.currency_code} was not found` ) } reg.currency_code = lowerCasedCurrency + + toCreate.push(reg as InternalCreateRegionDTO) } - const result = await this.regionService_.create(data, sharedContext) + const result = await this.regionService_.create(toCreate, sharedContext) return result } diff --git a/packages/region/src/types/index.ts b/packages/region/src/types/index.ts index 66a2deb8e0..c1841fd471 100644 --- a/packages/region/src/types/index.ts +++ b/packages/region/src/types/index.ts @@ -1,4 +1,5 @@ import { Logger } from "@medusajs/types" +import { Country } from "@models" export type InitializeModuleInjectableDependencies = { logger?: Logger @@ -22,4 +23,10 @@ export type CreateCountryDTO = { num_code: string name: string display_name: string +} + +export type CreateRegionDTO = { + name: string + currency_code: string + countries?: Country[] } \ No newline at end of file diff --git a/packages/types/src/region/mutations.ts b/packages/types/src/region/mutations.ts index 2ce1ba27b8..4b429dfcbc 100644 --- a/packages/types/src/region/mutations.ts +++ b/packages/types/src/region/mutations.ts @@ -4,6 +4,7 @@ export interface CreateRegionDTO { name: string currency_code: string currency?: RegionCurrencyDTO + countries?: string[] tax_code?: string tax_rate?: number tax_provider_id?: string @@ -13,6 +14,7 @@ export interface UpdateRegionDTO { id: string currency_code?: string currency?: RegionCurrencyDTO + countries: string[] name?: string tax_code?: string tax_rate?: number