feat(medusa): Convert RegionService to TypeScript (#1914)

This commit is contained in:
Frane Polić
2022-08-22 19:59:02 +02:00
committed by GitHub
parent 80e02130b4
commit bda83a84bc
11 changed files with 440 additions and 338 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
Convert RegionService to TypeScript

View File

@@ -2309,7 +2309,6 @@ describe("/admin/discounts", () => {
})
const cond = discountCondition.data.discount_condition
console.log(cond.products)
expect(discountCondition.status).toEqual(200)
expect(cond).toMatchSnapshot({

View File

@@ -1,10 +1,16 @@
import { IsArray, IsNumber, IsOptional, IsString } from "class-validator"
import { defaultAdminRegionFields, defaultAdminRegionRelations } from "."
import {
IsArray,
IsNumber,
IsObject,
IsOptional,
IsString,
} from "class-validator"
import { EntityManager } from "typeorm"
import { validator } from "../../../../utils/validator"
import { Region } from "../../../.."
import RegionService from "../../../../services/region"
import { validator } from "../../../../utils/validator"
import { defaultAdminRegionRelations, defaultAdminRegionFields } from "."
/**
* @oas [post] /regions
@@ -113,4 +119,8 @@ export class AdminPostRegionsReq {
@IsArray()
@IsString({ each: true })
countries: string[]
@IsObject()
@IsOptional()
metadata?: Record<string, unknown>
}

View File

@@ -58,7 +58,7 @@ export default (app) => {
return app
}
export const defaultAdminRegionFields = [
export const defaultAdminRegionFields: (keyof Region)[] = [
"id",
"name",
"automatic_taxes",

View File

@@ -2,14 +2,15 @@ import {
IsArray,
IsBoolean,
IsNumber,
IsObject,
IsOptional,
IsString,
} from "class-validator"
import { defaultAdminRegionFields, defaultAdminRegionRelations } from "."
import { EntityManager } from "typeorm"
import RegionService from "../../../../services/region"
import { validator } from "../../../../utils/validator"
import RegionService from "../../../../services/region"
import { defaultAdminRegionRelations, defaultAdminRegionFields } from "."
/**
* @oas [post] /regions/{id}
@@ -139,4 +140,8 @@ export class AdminPostRegionsRegionReq {
@IsString({ each: true })
@IsOptional()
countries?: string[]
@IsObject()
@IsOptional()
metadata?: Record<string, unknown>
}

View File

@@ -1,69 +1,83 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import RegionService from "../region"
import {
EventBusService,
FulfillmentProviderService,
PaymentProviderService,
StoreService,
} from "../index"
import { CreateRegionInput } from "../../types/region"
const eventBusService = {
emit: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
} as unknown as EventBusService
describe("RegionService", () => {
const regionRepository = MockRepository({})
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const countryRepository = MockRepository({
findOne: (query) => {
if (query.where.iso_2 === "dk") {
return Promise.resolve({
id: IdMap.getId("dk"),
name: "Denmark",
display_name: "Denmark",
region_id: IdMap.getId("dk-reg"),
})
}
return Promise.resolve({
id: IdMap.getId("test-country"),
name: "World",
})
},
})
const currencyRepository = MockRepository({
findOne: () => Promise.resolve({ code: "usd" }),
})
const storeService = {
withTransaction: function () {
return this
},
retrieve: () => {
return {
id: IdMap.getId("test-store"),
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
}
},
} as unknown as StoreService
const taxProviderRepository = MockRepository({})
const fulfillmentProviderService = {} as FulfillmentProviderService
const paymentProviderService = {} as PaymentProviderService
describe("create", () => {
const regionRepository = MockRepository({})
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const countryRepository = MockRepository({
findOne: (query) => {
if (query.where.iso_2 === "dk") {
return Promise.resolve({
id: IdMap.getId("dk"),
name: "Denmark",
display_name: "Denmark",
region_id: IdMap.getId("dk-reg"),
})
}
return Promise.resolve({
id: IdMap.getId("test-country"),
name: "World",
})
},
})
const currencyRepository = MockRepository({
findOne: () => Promise.resolve({ code: "usd" }),
})
const storeService = {
withTransaction: function() {
return this
},
retrieve: () => {
return {
id: IdMap.getId("test-store"),
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
}
},
}
const regionService = new RegionService({
manager: MockManager,
eventBusService,
@@ -73,6 +87,9 @@ describe("RegionService", () => {
regionRepository,
countryRepository,
storeService,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
})
beforeEach(async () => {
@@ -85,7 +102,7 @@ describe("RegionService", () => {
currency_code: "USD",
tax_rate: 0.25,
countries: ["US"],
})
} as CreateRegionInput)
expect(regionRepository.create).toHaveBeenCalledTimes(1)
expect(regionRepository.create).toHaveBeenCalledWith({
@@ -106,7 +123,7 @@ describe("RegionService", () => {
currency_code: "EUR",
tax_rate: 0.25,
countries: ["DK"],
})
} as CreateRegionInput)
} catch (error) {
expect(error.message).toBe(
`Denmark already exists in region ${IdMap.getId("dk-reg")}`
@@ -149,7 +166,7 @@ describe("RegionService", () => {
tax_rate: 0.25,
countries: ["US"],
payment_providers: ["should_fail"],
})
} as CreateRegionInput)
} catch (error) {
expect(error.message).toBe("Payment provider not found")
}
@@ -163,7 +180,7 @@ describe("RegionService", () => {
tax_rate: 0.25,
countries: ["US"],
fulfillment_providers: ["should_fail"],
})
} as CreateRegionInput)
} catch (error) {
expect(error.message).toBe("Fulfillment provider not found")
}
@@ -179,6 +196,14 @@ describe("RegionService", () => {
manager: MockManager,
eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository,
storeService,
})
beforeEach(async () => {
@@ -196,27 +221,6 @@ describe("RegionService", () => {
})
describe("validateFields_", () => {
const regionRepository = MockRepository({})
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const countryRepository = MockRepository({
findOne: (query) => {
if (query.where.iso_2 === "dk") {
@@ -234,24 +238,16 @@ describe("RegionService", () => {
},
})
const storeService = {
withTransaction: function() {
return this
},
retrieve: () => {
return {
id: IdMap.getId("test-store"),
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
}
},
}
const regionService = new RegionService({
manager: MockManager,
eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
regionRepository,
currencyRepository,
countryRepository,
storeService,
})
@@ -262,13 +258,19 @@ describe("RegionService", () => {
it("throws on invalid country code", async () => {
await expect(
regionService.validateFields_({ countries: ["ddd"] })
// @ts-ignore
regionService.validateFields({
countries: ["ddd"],
} as CreateRegionInput)
).rejects.toThrow("Invalid country code")
})
it("throws on in use country code", async () => {
await expect(
regionService.validateFields_({ countries: ["DK"] })
// @ts-ignore
regionService.validateFields({
countries: ["DK"],
} as CreateRegionInput)
).rejects.toThrow(
`Denmark already exists in region ${IdMap.getId("dk-reg")}`
)
@@ -276,15 +278,19 @@ describe("RegionService", () => {
it("throws on unknown payment providers", async () => {
await expect(
regionService.validateFields_({ payment_providers: ["should_fail"] })
// @ts-ignore
regionService.validateFields({
payment_providers: ["should_fail"],
} as CreateRegionInput)
).rejects.toThrow("Payment provider not found")
})
it("throws on unknown fulfillment providers", async () => {
await expect(
regionService.validateFields_({
// @ts-ignore
regionService.validateFields({
fulfillment_providers: ["should_fail"],
})
} as CreateRegionInput)
).rejects.toThrow("Fulfillment provider not found")
})
})
@@ -293,66 +299,18 @@ describe("RegionService", () => {
const regionRepository = MockRepository({
findOne: () => Promise.resolve({ id: IdMap.getId("test-region") }),
})
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const countryRepository = MockRepository({
findOne: (query) => {
if (query.where.iso_2 === "dk") {
return Promise.resolve({
id: IdMap.getId("dk"),
name: "Denmark",
region_id: IdMap.getId("dk-reg"),
})
}
return Promise.resolve({
id: IdMap.getId("test-country"),
name: "World",
})
},
})
const storeService = {
withTransaction: function() {
return this
},
retrieve: () => {
return {
id: IdMap.getId("test-store"),
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
}
},
}
const currencyRepository = MockRepository({
findOne: () => Promise.resolve({ code: "eur" }),
})
const regionService = new RegionService({
manager: MockManager,
eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
regionRepository,
countryRepository,
currencyRepository,
countryRepository,
storeService,
})
@@ -395,14 +353,21 @@ describe("RegionService", () => {
}),
})
const countryRepository = MockRepository({
findOne: (query) => Promise.resolve(),
findOne: () => Promise.resolve(),
})
const regionService = new RegionService({
manager: MockManager,
eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository,
storeService,
})
beforeEach(async () => {
@@ -434,6 +399,7 @@ describe("RegionService", () => {
return Promise.resolve({ id: IdMap.getId("region") })
},
})
const countryRepository = MockRepository({
findOne: (query) => {
if (query.where.iso_2 === "dk") {
@@ -454,7 +420,14 @@ describe("RegionService", () => {
manager: MockManager,
eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository,
storeService,
})
beforeEach(async () => {
@@ -499,7 +472,7 @@ describe("RegionService", () => {
manager: MockManager,
eventBusService,
regionRepository,
})
} as any)
beforeEach(async () => {
jest.clearAllMocks()
@@ -524,33 +497,19 @@ describe("RegionService", () => {
payment_providers: [{ id: "sweden_provider" }],
}),
})
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const regionService = new RegionService({
manager: MockManager,
eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
regionRepository,
currencyRepository,
countryRepository,
storeService,
})
beforeEach(async () => {
@@ -609,8 +568,15 @@ describe("RegionService", () => {
const regionService = new RegionService({
manager: MockManager,
eventBusService,
fulfillmentProviderRepository: fpRepository,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository,
storeService,
})
beforeEach(async () => {
@@ -660,7 +626,7 @@ describe("RegionService", () => {
manager: MockManager,
eventBusService,
regionRepository,
})
} as any)
beforeEach(async () => {
jest.clearAllMocks()
@@ -695,7 +661,7 @@ describe("RegionService", () => {
manager: MockManager,
eventBusService,
regionRepository,
})
} as any)
beforeEach(async () => {
jest.clearAllMocks()

View File

@@ -393,7 +393,7 @@ class CartService extends TransactionBaseService {
}
} else {
if (data.shipping_address) {
if (!regCountries.includes(data.shipping_address.country_code)) {
if (!regCountries.includes(data.shipping_address.country_code!)) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Shipping country not in region"
@@ -403,7 +403,10 @@ class CartService extends TransactionBaseService {
}
if (data.shipping_address_id) {
const addr = await addressRepo.findOne(data.shipping_address_id)
if (addr && !regCountries.includes(addr.country_code)) {
if (
addr?.country_code &&
!regCountries.includes(addr.country_code)
) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Shipping country not in region"

View File

@@ -64,7 +64,7 @@ class PricingService extends TransactionBaseService {
context: PriceSelectionContext
): Promise<PricingContext> {
let automaticTaxes = false
let taxRate = null
let taxRate: number | null = null
let currencyCode = context.currency_code
if (context.region_id) {

View File

@@ -360,7 +360,7 @@ class ProductVariantService extends BaseService {
/**
* Updates a variant's prices.
* Deletes any prices that are not in the update object, and is not associated with a price list.
* @param variantId - the id of variant variant
* @param variantId - the id of variant
* @param prices - the update prices
* @returns {Promise<void>} empty promise
*/

View File

@@ -1,18 +1,64 @@
import { DeepPartial, EntityManager } from "typeorm"
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import StoreService from "./store"
import EventBusService from "./event-bus"
import { countries } from "../utils/countries"
import { TransactionBaseService } from "../interfaces"
import { RegionRepository } from "../repositories/region"
import { CountryRepository } from "../repositories/country"
import { CurrencyRepository } from "../repositories/currency"
import { PaymentProviderRepository } from "../repositories/payment-provider"
import { FulfillmentProviderRepository } from "../repositories/fulfillment-provider"
import { TaxProviderRepository } from "../repositories/tax-provider"
import FulfillmentProviderService from "./fulfillment-provider"
import { Country, Currency, Region } from "../models"
import { FindConfig, Selector } from "../types/common"
import { CreateRegionInput, UpdateRegionInput } from "../types/region"
import { buildQuery, setMetadata } from "../utils"
import { PaymentProviderService } from "./index"
type InjectedDependencies = {
manager: EntityManager
storeService: StoreService
eventBusService: EventBusService
paymentProviderService: PaymentProviderService
fulfillmentProviderService: FulfillmentProviderService
regionRepository: typeof RegionRepository
countryRepository: typeof CountryRepository
currencyRepository: typeof CurrencyRepository
taxProviderRepository: typeof TaxProviderRepository
paymentProviderRepository: typeof PaymentProviderRepository
fulfillmentProviderRepository: typeof FulfillmentProviderRepository
}
/**
* Provides layer to manipulate regions.
* @extends BaseService
*/
class RegionService extends BaseService {
class RegionService extends TransactionBaseService {
static Events = {
UPDATED: "region.updated",
CREATED: "region.created",
DELETED: "region.deleted",
}
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly eventBus_: EventBusService
protected readonly storeService_: StoreService
protected readonly paymentProviderService_: PaymentProviderService
protected readonly fulfillmentProviderService_: FulfillmentProviderService
protected readonly regionRepository_: typeof RegionRepository
protected readonly countryRepository_: typeof CountryRepository
protected readonly currencyRepository_: typeof CurrencyRepository
protected readonly paymentProviderRepository_: typeof PaymentProviderRepository
protected readonly fulfillmentProviderRepository_: typeof FulfillmentProviderRepository
protected readonly taxProviderRepository_: typeof TaxProviderRepository
constructor({
manager,
regionRepository,
@@ -25,75 +71,42 @@ class RegionService extends BaseService {
taxProviderRepository,
paymentProviderService,
fulfillmentProviderService,
}) {
super()
/** @private @const {EntityManager} */
this.manager_ = manager
/** @private @const {RegionRepository} */
this.regionRepository_ = regionRepository
/** @private @const {CountryRepository} */
this.countryRepository_ = countryRepository
/** @private @const {StoreService} */
this.storeService_ = storeService
/** @private @const {EventBus} */
this.eventBus_ = eventBusService
/** @private @const {CurrencyRepository} */
this.currencyRepository_ = currencyRepository
/** @private @const {PaymentProviderRepository} */
this.paymentProviderRepository_ = paymentProviderRepository
/** @private @const {FulfillmentProviderRepository} */
this.fulfillmentProviderRepository_ = fulfillmentProviderRepository
/** @private @const {PaymentProviderService} */
this.paymentProviderService_ = paymentProviderService
/** @private @const {typeof TaxProviderService} */
this.taxProviderRepository_ = taxProviderRepository
/** @private @const {FulfillmentProviderService} */
this.fulfillmentProviderService_ = fulfillmentProviderService
}
withTransaction(transactionManager) {
if (!transactionManager) {
return this
}
const cloned = new RegionService({
manager: transactionManager,
regionRepository: this.regionRepository_,
currencyRepository: this.currencyRepository_,
countryRepository: this.countryRepository_,
storeService: this.storeService_,
eventBusService: this.eventBus_,
paymentProviderRepository: this.paymentProviderRepository_,
paymentProviderService: this.paymentProviderService_,
taxProviderRepository: this.taxProviderRepository_,
taxProviderService: this.taxProviderService_,
fulfillmentProviderRepository: this.fulfillmentProviderRepository_,
fulfillmentProviderService: this.fulfillmentProviderService_,
}: InjectedDependencies) {
super({
manager,
regionRepository,
countryRepository,
storeService,
eventBusService,
currencyRepository,
paymentProviderRepository,
fulfillmentProviderRepository,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderService,
})
cloned.transactionManager_ = transactionManager
return cloned
this.manager_ = manager
this.regionRepository_ = regionRepository
this.countryRepository_ = countryRepository
this.storeService_ = storeService
this.eventBus_ = eventBusService
this.currencyRepository_ = currencyRepository
this.paymentProviderRepository_ = paymentProviderRepository
this.fulfillmentProviderRepository_ = fulfillmentProviderRepository
this.paymentProviderService_ = paymentProviderService
this.taxProviderRepository_ = taxProviderRepository
this.fulfillmentProviderService_ = fulfillmentProviderService
}
/**
* Creates a region.
* @param {Region} regionObject - the unvalidated region
* @return {Region} the newly created region
*
* @param data - the unvalidated region
* @return the newly created region
*/
async create(regionObject) {
return this.atomicPhase_(async (manager) => {
async create(data: CreateRegionInput): Promise<Region> {
return await this.atomicPhase_(async (manager) => {
const regionRepository = manager.getCustomRepository(
this.regionRepository_
)
@@ -101,13 +114,14 @@ class RegionService extends BaseService {
this.currencyRepository_
)
const { metadata, currency_code, ...toValidate } = regionObject
const regionObject = { ...data } as DeepPartial<Region>
const { metadata, currency_code, ...toValidate } = data
const validated = await this.validateFields_(toValidate)
const validated = await this.validateFields(toValidate)
if (currency_code) {
// will throw if currency is not added to store currencies
await this.validateCurrency_(currency_code)
await this.validateCurrency(currency_code)
const currency = await currencyRepository.findOne({
where: { code: currency_code.toLowerCase() },
})
@@ -124,14 +138,17 @@ class RegionService extends BaseService {
}
if (metadata) {
regionObject.metadata = this.setMetadata_(regionObject, metadata)
regionObject.metadata = setMetadata(
{ metadata: regionObject.metadata ?? null },
metadata
)
}
for (const [key, value] of Object.entries(validated)) {
regionObject[key] = value
}
const created = regionRepository.create(regionObject)
const created = regionRepository.create(regionObject) as Region
const result = await regionRepository.save(created)
await this.eventBus_
@@ -146,12 +163,13 @@ class RegionService extends BaseService {
/**
* Updates a region
* @param {string} regionId - the region to update
* @param {object} update - the data to update the region with
* @return {Promise} the result of the update operation
*
* @param regionId - the region to update
* @param update - the data to update the region with
* @return the result of the update operation
*/
async update(regionId, update) {
return this.atomicPhase_(async (manager) => {
async update(regionId: string, update: UpdateRegionInput): Promise<Region> {
return await this.atomicPhase_(async (manager) => {
const regionRepository = manager.getCustomRepository(
this.regionRepository_
)
@@ -163,11 +181,11 @@ class RegionService extends BaseService {
const { metadata, currency_code, ...toValidate } = update
const validated = await this.validateFields_(toValidate, region.id)
const validated = await this.validateFields(toValidate, region.id)
if (currency_code) {
// will throw if currency is not added to store currencies
await this.validateCurrency_(currency_code)
await this.validateCurrency(currency_code)
const currency = await currencyRepository.findOne({
where: { code: currency_code.toLowerCase() },
})
@@ -183,7 +201,7 @@ class RegionService extends BaseService {
}
if (metadata) {
region.metadata = this.setMetadata_(region, metadata)
region.metadata = setMetadata(region, metadata)
}
for (const [key, value] of Object.entries(validated)) {
@@ -204,13 +222,19 @@ class RegionService extends BaseService {
}
/**
* Validates fields for creation and updates. If the region already exisits
* Validates fields for creation and updates. If the region already exists
* the id can be passed to check that country updates are allowed.
* @param {object} region - the region data to validate
* @param {string?} id - optional id of the region to check against
* @return {object} the validated region data
*
* @param regionData - the region data to validate
* @param id - optional id of the region to check against
* @return the validated region data
*/
async validateFields_(region, id = undefined) {
protected async validateFields<
T extends CreateRegionInput | UpdateRegionInput
>(
regionData: Omit<T, "metadata" | "currency_code">,
id?: T extends UpdateRegionInput ? string : undefined
): Promise<DeepPartial<Region>> {
const ppRepository = this.manager_.getCustomRepository(
this.paymentProviderRepository_
)
@@ -221,23 +245,25 @@ class RegionService extends BaseService {
this.taxProviderRepository_
)
const region = { ...regionData } as DeepPartial<Region>
if (region.tax_rate) {
this.validateTaxRate_(region.tax_rate)
this.validateTaxRate(region.tax_rate)
}
if (region.countries) {
if (regionData.countries) {
region.countries = await Promise.all(
region.countries.map((countryCode) =>
this.validateCountry_(countryCode, id)
regionData.countries!.map((countryCode) =>
this.validateCountry(countryCode, id!)
)
).catch((err) => {
throw err
})
}
if (region.tax_provider_id) {
if ((regionData as UpdateRegionInput).tax_provider_id) {
const tp = await tpRepository.findOne({
where: { id: region.tax_provider_id },
where: { id: (regionData as UpdateRegionInput).tax_provider_id },
})
if (!tp) {
throw new MedusaError(
@@ -247,9 +273,9 @@ class RegionService extends BaseService {
}
}
if (region.payment_providers) {
if (regionData.payment_providers) {
region.payment_providers = await Promise.all(
region.payment_providers.map(async (pId) => {
regionData.payment_providers.map(async (pId) => {
const pp = await ppRepository.findOne({ where: { id: pId } })
if (!pp) {
throw new MedusaError(
@@ -263,9 +289,9 @@ class RegionService extends BaseService {
)
}
if (region.fulfillment_providers) {
if (regionData.fulfillment_providers) {
region.fulfillment_providers = await Promise.all(
region.fulfillment_providers.map(async (fId) => {
regionData.fulfillment_providers.map(async (fId) => {
const fp = await fpRepository.findOne({ where: { id: fId } })
if (!fp) {
throw new MedusaError(
@@ -284,9 +310,12 @@ class RegionService extends BaseService {
/**
* Validates a tax rate. Will throw if the tax rate is not between 0 and 1.
* @param {number} taxRate - a number representing the tax rate of the region
*
* @param taxRate - a number representing the tax rate of the region
* @throws if the tax rate isn't number between 0-100
* @return void
*/
validateTaxRate_(taxRate) {
protected validateTaxRate(taxRate: number): void | never {
if (taxRate > 100 || taxRate < 0) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
@@ -297,9 +326,14 @@ class RegionService extends BaseService {
/**
* Validates a currency code. Will throw if the currency code doesn't exist.
* @param {string} currencyCode - an ISO currency code
*
* @param currencyCode - an ISO currency code
* @throws if the provided currency code is invalid
* @return void
*/
async validateCurrency_(currencyCode) {
protected async validateCurrency(
currencyCode: Currency["code"]
): Promise<void | never> {
const store = await this.storeService_
.withTransaction(this.transactionManager_)
.retrieve({ relations: ["currencies"] })
@@ -317,10 +351,15 @@ class RegionService extends BaseService {
/**
* Validates a country code. Will normalize the code before checking for
* existence.
* @param {string} code - a 2 digit alphanumeric ISO country code
* @param {string} regionId - the id of the current region to check against
*
* @param code - a 2 digit alphanumeric ISO country code
* @param regionId - the id of the current region to check against
* @return the validated Country
*/
async validateCountry_(code, regionId) {
protected async validateCountry(
code: Country["iso_2"],
regionId: string
): Promise<Country | never> {
const countryRepository = this.manager_.getCustomRepository(
this.countryRepository_
)
@@ -359,7 +398,17 @@ class RegionService extends BaseService {
return country
}
async retrieveByCountryCode(code, config = {}) {
/**
* Retrieve a region by country code.
*
* @param code - a 2 digit alphanumeric ISO country code
* @param config - region find config
* @return a Region with country code
*/
async retrieveByCountryCode(
code: Country["iso_2"],
config: FindConfig<Region> = {}
): Promise<Region | never> {
const countryRepository = this.manager_.getCustomRepository(
this.countryRepository_
)
@@ -389,17 +438,20 @@ class RegionService extends BaseService {
/**
* Retrieves a region by its id.
* @param {string} regionId - the id of the region to retrieve
* @param {object} config - configuration settings
* @return {Region} the region
*
* @param regionId - the id of the region to retrieve
* @param config - configuration settings
* @return the region
*/
async retrieve(regionId, config = {}) {
async retrieve(
regionId: string,
config: FindConfig<Region> = {}
): Promise<Region | never> {
const regionRepository = this.manager_.getCustomRepository(
this.regionRepository_
)
const validatedId = this.validateId_(regionId)
const query = this.buildQuery_({ id: validatedId }, config)
const query = buildQuery({ id: regionId }, config)
const region = await regionRepository.findOne(query)
if (!region) {
@@ -408,29 +460,39 @@ class RegionService extends BaseService {
`Region with ${regionId} was not found`
)
}
return region
}
/**
* Lists all regions based on a query
*
* @param {object} selector - query object for find
* @param {object} config - configuration settings
* @return {Promise} result of the find operation
*/
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) {
async list(
selector: Selector<Region> = {},
config: FindConfig<Region> = {
relations: [],
skip: 0,
take: 10,
}
): Promise<Region[]> {
const regionRepo = this.manager_.getCustomRepository(this.regionRepository_)
const query = this.buildQuery_(selector, config)
const query = buildQuery(selector, config)
return regionRepo.find(query)
}
/**
* Deletes a region.
* @param {string} regionId - the region to delete
* @return {Promise} the result of the delete operation
*
* @param regionId - the region to delete
* @return the result of the delete operation
*/
async delete(regionId) {
return this.atomicPhase_(async (manager) => {
async delete(regionId: string): Promise<void> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const countryRepo = manager.getCustomRepository(this.countryRepository_)
@@ -455,15 +517,16 @@ class RegionService extends BaseService {
/**
* Adds a country to the region.
* @param {string} regionId - the region to add a country to
* @param {string} code - a 2 digit alphanumeric ISO country code.
* @return {Promise} the result of the update operation
*
* @param regionId - the region to add a country to
* @param code - a 2 digit alphanumeric ISO country code.
* @return the updated Region
*/
async addCountry(regionId, code) {
return this.atomicPhase_(async (manager) => {
async addCountry(regionId: string, code: Country["iso_2"]): Promise<Region> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const country = await this.validateCountry_(code, regionId)
const country = await this.validateCountry(code, regionId)
const region = await this.retrieve(regionId, { relations: ["countries"] })
@@ -491,13 +554,17 @@ class RegionService extends BaseService {
}
/**
* Removes a country from a Region
* @param {string} regionId - the region to remove from
* @param {string} code - a 2 digit alphanumeric ISO country code to remove
* @return {Promise} the result of the update operation
* Removes a country from a Region.
*
* @param regionId - the region to remove from
* @param code - a 2 digit alphanumeric ISO country code to remove
* @return the updated Region
*/
async removeCountry(regionId, code) {
return this.atomicPhase_(async (manager) => {
async removeCountry(
regionId: string,
code: Country["iso_2"]
): Promise<Region> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const region = await this.retrieve(regionId, { relations: ["countries"] })
@@ -521,6 +588,7 @@ class RegionService extends BaseService {
id: updated.id,
fields: ["countries"],
})
return updated
})
}
@@ -528,12 +596,16 @@ class RegionService extends BaseService {
/**
* Adds a payment provider that is available in the region. Fails if the
* provider doesn't exist.
* @param {string} regionId - the region to add the provider to
* @param {string} providerId - the provider to add to the region
* @return {Promise} the result of the update operation
*
* @param regionId - the region to add the provider to
* @param providerId - the provider to add to the region
* @return the updated Region
*/
async addPaymentProvider(regionId, providerId) {
return this.atomicPhase_(async (manager) => {
async addPaymentProvider(
regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const ppRepo = manager.getCustomRepository(
this.paymentProviderRepository_
@@ -575,12 +647,16 @@ class RegionService extends BaseService {
/**
* Adds a fulfillment provider that is available in the region. Fails if the
* provider doesn't exist.
* @param {string} regionId - the region to add the provider to
* @param {string} providerId - the provider to add to the region
* @return {Promise} the result of the update operation
*
* @param regionId - the region to add the provider to
* @param providerId - the provider to add to the region
* @return the updated Region
*/
async addFulfillmentProvider(regionId, providerId) {
return this.atomicPhase_(async (manager) => {
async addFulfillmentProvider(
regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const fpRepo = manager.getCustomRepository(
this.fulfillmentProviderRepository_
@@ -592,7 +668,7 @@ class RegionService extends BaseService {
// Check if region already has payment provider
if (region.fulfillment_providers.find(({ id }) => id === providerId)) {
return Promise.resolve()
return Promise.resolve(region)
}
const fp = await fpRepo.findOne({ where: { id: providerId } })
@@ -613,18 +689,23 @@ class RegionService extends BaseService {
id: updated.id,
fields: ["fulfillment_providers"],
})
return updated
})
}
/**
* Removes a payment provider from a region. Is idempotent.
* @param {string} regionId - the region to remove the provider from
* @param {string} providerId - the provider to remove from the region
* @return {Promise} the result of the update operation
*
* @param regionId - the region to remove the provider from
* @param providerId - the provider to remove from the region
* @return the updated Region
*/
async removePaymentProvider(regionId, providerId) {
return this.atomicPhase_(async (manager) => {
async removePaymentProvider(
regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const region = await this.retrieve(regionId, {
@@ -633,7 +714,7 @@ class RegionService extends BaseService {
// Check if region already has payment provider
if (!region.payment_providers.find(({ id }) => id === providerId)) {
return Promise.resolve()
return Promise.resolve(region)
}
region.payment_providers = region.payment_providers.filter(
@@ -647,18 +728,23 @@ class RegionService extends BaseService {
id: updated.id,
fields: ["payment_providers"],
})
return updated
})
}
/**
* Removes a fulfillment provider from a region. Is idempotent.
* @param {string} regionId - the region to remove the provider from
* @param {string} providerId - the provider to remove from the region
* @return {Promise} the result of the update operation
*
* @param regionId - the region to remove the provider from
* @param providerId - the provider to remove from the region
* @return the updated Region
*/
async removeFulfillmentProvider(regionId, providerId) {
return this.atomicPhase_(async (manager) => {
async removeFulfillmentProvider(
regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_)
const region = await this.retrieve(regionId, {
@@ -667,7 +753,7 @@ class RegionService extends BaseService {
// Check if region already has payment provider
if (!region.fulfillment_providers.find(({ id }) => id === providerId)) {
return Promise.resolve()
return Promise.resolve(region)
}
region.fulfillment_providers = region.fulfillment_providers.filter(
@@ -681,6 +767,7 @@ class RegionService extends BaseService {
id: updated.id,
fields: ["fulfillment_providers"],
})
return updated
})
}

View File

@@ -0,0 +1,27 @@
import { FindConfig } from "./common"
import { Region } from "../models"
export type UpdateRegionInput = {
name?: string
currency_code?: string
tax_code?: string
tax_rate?: number
gift_cards_taxable?: boolean
automatic_taxes?: boolean
tax_provider_id?: string | null
payment_providers?: string[]
fulfillment_providers?: string[]
countries?: string[]
metadata?: Record<string, unknown>
}
export type CreateRegionInput = {
name: string
currency_code: string
tax_code?: string
tax_rate: number
payment_providers: string[]
fulfillment_providers: string[]
countries: string[]
metadata?: Record<string, unknown>
}