feat(dashboard, js-sdk, medusa, tax, types): custom tax providers (#12297)
* wip: setup loaders, add endpoints, module work, types, js sdk * fix: tax module provider loader * feat: select provider on region create, fix enpoint middleware registration * feat: edit form * fix: rename param * chore: changeset * fix: don't default to system provider * fix: admin fixes, dispalt tax provider * fix: some tests and types * fix: remove provider from province regions in test * fix: more tests, optional provider for sublevel regions, fix few types * fix: OE test * feat: edit tax region admin, update tax region core flow changes * feat: migrate script * fix: refactor * chore: use query graph * feat: provider section
This commit is contained in:
@@ -27,14 +27,17 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
|
||||
const [us, dk, de, ca] = await service.createTaxRegions([
|
||||
{
|
||||
country_code: "US",
|
||||
provider_id: "tp_system_system",
|
||||
default_tax_rate: { name: "US Default Rate", rate: 2, code: "US" },
|
||||
},
|
||||
{
|
||||
country_code: "DK",
|
||||
provider_id: "tp_system_system",
|
||||
default_tax_rate: { name: "Denmark Default Rate", rate: 25, code: "DK" },
|
||||
},
|
||||
{
|
||||
country_code: "DE",
|
||||
provider_id: "tp_system_system",
|
||||
default_tax_rate: {
|
||||
code: "DE19",
|
||||
name: "Germany Default Rate",
|
||||
@@ -43,6 +46,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
|
||||
},
|
||||
{
|
||||
country_code: "CA",
|
||||
provider_id: "tp_system_system",
|
||||
default_tax_rate: { name: "Canada Default Rate", rate: 5, code: "CA" },
|
||||
},
|
||||
])
|
||||
|
||||
@@ -4,25 +4,35 @@ import {
|
||||
LoaderOptions,
|
||||
ModuleProvider,
|
||||
ModulesSdkTypes,
|
||||
CreateTaxProviderDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { asFunction, Lifetime } from "awilix"
|
||||
import { asFunction, asValue, Lifetime } from "awilix"
|
||||
|
||||
import * as providers from "../providers"
|
||||
import TaxProviderService from "../services/tax-provider"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
|
||||
const PROVIDER_REGISTRATION_KEY = "tax_providers" as const
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
if (!klass?.identifier) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
`Trying to register a tax provider without a provider identifier.`
|
||||
)
|
||||
}
|
||||
|
||||
const key = `tp_${klass.identifier}${
|
||||
pluginOptions.id ? `_${pluginOptions.id}` : ""
|
||||
}`
|
||||
|
||||
container.register({
|
||||
[`tp_${klass.identifier}`]: asFunction(
|
||||
(cradle) => new klass(cradle, pluginOptions),
|
||||
{ lifetime: klass.LIFE_TIME || Lifetime.SINGLETON }
|
||||
),
|
||||
[key]: asFunction((cradle) => new klass(cradle, pluginOptions.options), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}),
|
||||
})
|
||||
|
||||
container.registerAdd(
|
||||
"tax_providers",
|
||||
asFunction((cradle) => new klass(cradle, pluginOptions), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
})
|
||||
)
|
||||
container.registerAdd(PROVIDER_REGISTRATION_KEY, asValue(key))
|
||||
}
|
||||
|
||||
export default async ({
|
||||
@@ -36,7 +46,7 @@ export default async ({
|
||||
>): Promise<void> => {
|
||||
// Local providers
|
||||
for (const provider of Object.values(providers)) {
|
||||
await registrationFn(provider, container, {})
|
||||
await registrationFn(provider, container, { id: "system" })
|
||||
}
|
||||
|
||||
await moduleProviderLoader({
|
||||
@@ -44,4 +54,33 @@ export default async ({
|
||||
providers: options?.providers || [],
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
|
||||
await registerProvidersInDb({ container })
|
||||
}
|
||||
|
||||
const registerProvidersInDb = async ({
|
||||
container,
|
||||
}: LoaderOptions): Promise<void> => {
|
||||
const providersToLoad = container.resolve<string[]>(PROVIDER_REGISTRATION_KEY)
|
||||
const taxProviderService =
|
||||
container.resolve<TaxProviderService>("taxProviderService")
|
||||
|
||||
const existingProviders = await taxProviderService.list(
|
||||
{ id: providersToLoad },
|
||||
{}
|
||||
)
|
||||
|
||||
const upsertData: CreateTaxProviderDTO[] = []
|
||||
|
||||
for (const { id } of existingProviders) {
|
||||
if (!providersToLoad.includes(id)) {
|
||||
upsertData.push({ id, is_enabled: false })
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of providersToLoad) {
|
||||
upsertData.push({ id, is_enabled: true })
|
||||
}
|
||||
|
||||
await taxProviderService.upsert(upsertData)
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { default as TaxModuleService } from "./tax-module-service"
|
||||
export { default as TaxProviderService } from "./tax-provider"
|
||||
|
||||
@@ -20,13 +20,14 @@ import {
|
||||
promiseAll,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { TaxProvider, TaxRate, TaxRateRule, TaxRegion } from "@models"
|
||||
import { TaxProviderService } from "@services"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
taxRateService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
taxRegionService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
taxRateRuleService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
taxProviderService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
taxProviderService: TaxProviderService
|
||||
[key: `tp_${string}`]: ITaxProvider
|
||||
}
|
||||
|
||||
@@ -57,9 +58,7 @@ export default class TaxModuleService
|
||||
protected taxRateRuleService_: ModulesSdkTypes.IMedusaInternalService<
|
||||
InferEntityType<typeof TaxRateRule>
|
||||
>
|
||||
protected taxProviderService_: ModulesSdkTypes.IMedusaInternalService<
|
||||
InferEntityType<typeof TaxProvider>
|
||||
>
|
||||
protected taxProviderService_: TaxProviderService
|
||||
|
||||
constructor(
|
||||
{
|
||||
@@ -445,7 +444,7 @@ export default class TaxModuleService
|
||||
)
|
||||
|
||||
const taxLines = await this.getTaxLinesFromProvider(
|
||||
parentRegion.provider_id,
|
||||
parentRegion.provider_id as string,
|
||||
toReturn,
|
||||
calculationContext
|
||||
)
|
||||
@@ -454,20 +453,11 @@ export default class TaxModuleService
|
||||
}
|
||||
|
||||
private async getTaxLinesFromProvider(
|
||||
rawProviderId: string | null,
|
||||
providerId: string,
|
||||
items: ItemWithRates[],
|
||||
calculationContext: TaxTypes.TaxCalculationContext
|
||||
) {
|
||||
const providerId = rawProviderId || "system"
|
||||
let provider: ITaxProvider
|
||||
try {
|
||||
provider = this.container_[`tp_${providerId}`] as ITaxProvider
|
||||
} catch (err) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Failed to resolve Tax Provider with id: ${providerId}. Make sure it's installed and configured in the Tax Module's options.`
|
||||
)
|
||||
}
|
||||
const provider = this.taxProviderService_.retrieveProvider(providerId)
|
||||
|
||||
const [itemLines, shippingLines] = items.reduce(
|
||||
(acc, line) => {
|
||||
@@ -730,27 +720,4 @@ export default class TaxModuleService
|
||||
private normalizeRegionCodes(code: string) {
|
||||
return code.toLowerCase()
|
||||
}
|
||||
|
||||
// @InjectTransactionManager()
|
||||
// async createProvidersOnLoad(@MedusaContext() sharedContext: Context = {}) {
|
||||
// const providersToLoad = this.container_["tax_providers"] as ITaxProvider[]
|
||||
|
||||
// const ids = providersToLoad.map((p) => p.getIdentifier())
|
||||
|
||||
// const existing = await this.taxProviderService_.update(
|
||||
// { selector: { id: { $in: ids } }, data: { is_enabled: true } },
|
||||
// sharedContext
|
||||
// )
|
||||
|
||||
// const existingIds = existing.map((p) => p.id)
|
||||
// const diff = arrayDifference(ids, existingIds)
|
||||
// await this.taxProviderService_.create(
|
||||
// diff.map((id) => ({ id, is_enabled: true }))
|
||||
// )
|
||||
|
||||
// await this.taxProviderService_.update({
|
||||
// selector: { id: { $nin: ids } },
|
||||
// data: { is_enabled: false },
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { DAL, ITaxProvider, Logger, TaxTypes } from "@medusajs/framework/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/framework/utils"
|
||||
|
||||
import TaxProvider from "../models/tax-provider"
|
||||
|
||||
type InjectedDependencies = {
|
||||
logger?: Logger
|
||||
taxProviderRepository: DAL.RepositoryService
|
||||
[key: `tp_${string}`]: ITaxProvider
|
||||
}
|
||||
|
||||
export default class TaxProviderService extends ModulesSdkUtils.MedusaInternalService<InjectedDependencies>(
|
||||
TaxProvider
|
||||
) {
|
||||
#logger: Logger
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.#logger = container["logger"]
|
||||
? container.logger
|
||||
: (console as unknown as Logger)
|
||||
}
|
||||
|
||||
retrieveProvider(providerId: string): ITaxProvider {
|
||||
try {
|
||||
return this.__container__[providerId] as ITaxProvider
|
||||
} catch (err) {
|
||||
if (err.name === "AwilixResolutionError") {
|
||||
const errMessage = `
|
||||
Unable to retrieve the tax provider with id: ${providerId}
|
||||
Please make sure that the provider is registered in the container and it is configured correctly in your project configuration file.`
|
||||
throw new Error(errMessage)
|
||||
}
|
||||
|
||||
const errMessage = `Unable to retrieve the tax provider with id: ${providerId}, the following error occurred: ${err.message}`
|
||||
this.#logger.error(errMessage)
|
||||
|
||||
throw new Error(errMessage)
|
||||
}
|
||||
}
|
||||
|
||||
async getTaxLines(
|
||||
providerId: string,
|
||||
itemLines: TaxTypes.ItemTaxCalculationLine[],
|
||||
shippingLines: TaxTypes.ShippingTaxCalculationLine[],
|
||||
context: TaxTypes.TaxCalculationContext
|
||||
): Promise<(TaxTypes.ItemTaxLineDTO | TaxTypes.ShippingTaxLineDTO)[]> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return provider.getTaxLines(itemLines, shippingLines, context)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user