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:
Frane Polić
2025-05-06 19:26:33 +02:00
committed by GitHub
parent 405ee7f7f3
commit 9cedeb182d
48 changed files with 847 additions and 116 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"@medusajs/dashboard": patch
"@medusajs/js-sdk": patch
"@medusajs/tax": patch
"@medusajs/types": patch
"@medusajs/medusa": patch
---
feat(dashboard, js-sdk, medusa, tax, types): custom tax providers
@@ -58,6 +58,7 @@ medusaIntegrationTestRunner({
await api.post( await api.post(
"/admin/tax-regions", "/admin/tax-regions",
{ {
provider_id: "tp_system_system",
country_code: "US", country_code: "US",
}, },
adminHeaders adminHeaders
@@ -2380,6 +2380,7 @@ medusaIntegrationTestRunner({
`/admin/tax-regions`, `/admin/tax-regions`,
{ {
country_code: "us", country_code: "us",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
code: "default", code: "default",
rate: 5, rate: 5,
@@ -2393,6 +2394,7 @@ medusaIntegrationTestRunner({
`/admin/tax-regions`, `/admin/tax-regions`,
{ {
country_code: "it", country_code: "it",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
code: "default", code: "default",
rate: 10, rate: 10,
@@ -2406,6 +2408,7 @@ medusaIntegrationTestRunner({
`/admin/tax-regions`, `/admin/tax-regions`,
{ {
country_code: "dk", country_code: "dk",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
code: "default", code: "default",
rate: 20, rate: 20,
@@ -26,10 +26,12 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
const [us, dk, de, ca] = await service.createTaxRegions([ const [us, dk, de, ca] = await service.createTaxRegions([
{ {
country_code: "US", country_code: "US",
provider_id: "tp_system_system",
default_tax_rate: { name: "US Default Rate", rate: 2, code: "US_DEF" }, default_tax_rate: { name: "US Default Rate", rate: 2, code: "US_DEF" },
}, },
{ {
country_code: "DK", country_code: "DK",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
name: "Denmark Default Rate", name: "Denmark Default Rate",
rate: 25, rate: 25,
@@ -38,6 +40,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
}, },
{ {
country_code: "DE", country_code: "DE",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
code: "DE19", code: "DE19",
name: "Germany Default Rate", name: "Germany Default Rate",
@@ -46,6 +49,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
}, },
{ {
country_code: "CA", country_code: "CA",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
name: "Canada Default Rate", name: "Canada Default Rate",
rate: 5, rate: 5,
@@ -1626,6 +1626,11 @@ export function getRouteMap({
}, },
], ],
}, },
{
path: "edit",
lazy: () =>
import("../../routes/tax-regions/tax-region-edit"),
},
{ {
path: "provinces/:province_id", path: "provinces/:province_id",
lazy: async () => { lazy: async () => {
@@ -74,6 +74,30 @@ export const useCreateTaxRegion = (
}) })
} }
export const useUpdateTaxRegion = (
id: string,
query?: HttpTypes.AdminTaxRegionParams,
options?: UseMutationOptions<
HttpTypes.AdminTaxRegionResponse,
FetchError,
HttpTypes.AdminUpdateTaxRegion,
QueryKey
>
) => {
return useMutation({
mutationFn: (payload) => sdk.admin.taxRegion.update(id, payload, query),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: taxRegionsQueryKeys.detail(id),
})
queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.lists() })
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
export const useDeleteTaxRegion = ( export const useDeleteTaxRegion = (
id: string, id: string,
options?: UseMutationOptions< options?: UseMutationOptions<
@@ -2038,7 +2038,7 @@
"type": "string" "type": "string"
} }
}, },
"required": ["label", "hint"], "required": ["label", "hint", "placeholder"],
"additionalProperties": false "additionalProperties": false
}, },
"subtitle": { "subtitle": {
@@ -2051,7 +2051,7 @@
"type": "string" "type": "string"
} }
}, },
"required": ["label"], "required": ["label", "placeholder"],
"additionalProperties": false "additionalProperties": false
}, },
"handle": { "handle": {
@@ -2067,7 +2067,7 @@
"type": "string" "type": "string"
} }
}, },
"required": ["label", "tooltip"], "required": ["label", "tooltip", "placeholder"],
"additionalProperties": false "additionalProperties": false
}, },
"description": { "description": {
@@ -2083,7 +2083,7 @@
"type": "string" "type": "string"
} }
}, },
"required": ["label", "hint"], "required": ["label", "hint", "placeholder"],
"additionalProperties": false "additionalProperties": false
}, },
"discountable": { "discountable": {
@@ -6479,6 +6479,16 @@
"required": ["header", "hint", "errors", "successToast"], "required": ["header", "hint", "errors", "successToast"],
"additionalProperties": false "additionalProperties": false
}, },
"edit": {
"type": "object",
"properties": {
"successToast": {
"type": "string"
}
},
"required": ["successToast"],
"additionalProperties": false
},
"province": { "province": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -6502,6 +6512,16 @@
"required": ["header", "create"], "required": ["header", "create"],
"additionalProperties": false "additionalProperties": false
}, },
"provider": {
"type": "object",
"properties": {
"header": {
"type": "string"
}
},
"required": ["header"],
"additionalProperties": false
},
"state": { "state": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -6910,6 +6930,9 @@
"taxCode": { "taxCode": {
"type": "string" "type": "string"
}, },
"taxProvider": {
"type": "string"
},
"targets": { "targets": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -7240,6 +7263,7 @@
"defaultTaxRate", "defaultTaxRate",
"taxRate", "taxRate",
"taxCode", "taxCode",
"taxProvider",
"targets", "targets",
"sublevels", "sublevels",
"noDefaultRate" "noDefaultRate"
@@ -7252,7 +7276,9 @@
"list", "list",
"delete", "delete",
"create", "create",
"edit",
"province", "province",
"provider",
"state", "state",
"stateOrTerritory", "stateOrTerritory",
"county", "county",
@@ -9192,9 +9218,6 @@
"deleteRateDescription": { "deleteRateDescription": {
"type": "string" "type": "string"
}, },
"editTaxRate": {
"type": "string"
},
"editRateAction": { "editRateAction": {
"type": "string" "type": "string"
}, },
@@ -9252,7 +9275,6 @@
"createTaxRate", "createTaxRate",
"createTaxRateHint", "createTaxRateHint",
"deleteRateDescription", "deleteRateDescription",
"editTaxRate",
"editRateAction", "editRateAction",
"editOverridesAction", "editOverridesAction",
"editOverridesTitle", "editOverridesTitle",
@@ -1723,6 +1723,9 @@
}, },
"successToast": "The tax region was successfully created." "successToast": "The tax region was successfully created."
}, },
"edit": {
"successToast": "The tax region was successfully updated."
},
"province": { "province": {
"header": "Provinces", "header": "Provinces",
"create": { "create": {
@@ -1730,6 +1733,9 @@
"hint": "Create a new tax region to define tax rates for a specific province." "hint": "Create a new tax region to define tax rates for a specific province."
} }
}, },
"provider": {
"header": "Tax Provider"
},
"state": { "state": {
"header": "States", "header": "States",
"create": { "create": {
@@ -1855,6 +1861,7 @@
}, },
"taxRate": "Tax rate", "taxRate": "Tax rate",
"taxCode": "Tax code", "taxCode": "Tax code",
"taxProvider": "Tax provider",
"targets": { "targets": {
"label": "Targets", "label": "Targets",
"hint": "Select the targets that this tax rate will apply to.", "hint": "Select the targets that this tax rate will apply to.",
@@ -2471,7 +2478,6 @@
"createTaxRate": "Create Tax Rate", "createTaxRate": "Create Tax Rate",
"createTaxRateHint": "Create a new tax rate for the region.", "createTaxRateHint": "Create a new tax rate for the region.",
"deleteRateDescription": "You are about to delete the tax rate {{name}}. This action cannot be undone.", "deleteRateDescription": "You are about to delete the tax rate {{name}}. This action cannot be undone.",
"editTaxRate": "Edit Tax Rate",
"editRateAction": "Edit rate", "editRateAction": "Edit rate",
"editOverridesAction": "Edit overrides", "editOverridesAction": "Edit overrides",
"editOverridesTitle": "Edit Tax Rate Overrides", "editOverridesTitle": "Edit Tax Rate Overrides",
@@ -2,11 +2,20 @@ import { HttpTypes } from "@medusajs/types"
import { Heading, Text, Tooltip, clx } from "@medusajs/ui" import { Heading, Text, Tooltip, clx } from "@medusajs/ui"
import ReactCountryFlag from "react-country-flag" import ReactCountryFlag from "react-country-flag"
import { ExclamationCircle, MapPin, Plus, Trash } from "@medusajs/icons" import {
ExclamationCircle,
MapPin,
Plus,
Trash,
PencilSquare,
} from "@medusajs/icons"
import { ComponentPropsWithoutRef, ReactNode } from "react" import { ComponentPropsWithoutRef, ReactNode } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { ActionMenu } from "../../../../../components/common/action-menu" import {
Action,
ActionMenu,
} from "../../../../../components/common/action-menu"
import { IconAvatar } from "../../../../../components/common/icon-avatar" import { IconAvatar } from "../../../../../components/common/icon-avatar"
import { getCountryByIso2 } from "../../../../../lib/data/countries" import { getCountryByIso2 } from "../../../../../lib/data/countries"
import { import {
@@ -107,7 +116,9 @@ export const TaxRegionCard = ({
{name} {name}
</Text> </Text>
) : ( ) : (
<Heading>{name}</Heading> <div className="flex items-center gap-x-2">
<Heading>{name}</Heading>
</div>
)} )}
</div> </div>
</div> </div>
@@ -164,7 +175,9 @@ const TaxRegionCardActions = ({
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const to = taxRegion.parent_id const hasParent = !!taxRegion.parent_id
const to = hasParent
? `/settings/tax-regions/${taxRegion.parent_id}` ? `/settings/tax-regions/${taxRegion.parent_id}`
: undefined : undefined
const handleDelete = useDeleteTaxRegionAction({ taxRegion, to }) const handleDelete = useDeleteTaxRegionAction({ taxRegion, to })
@@ -187,12 +200,17 @@ const TaxRegionCardActions = ({
: []), : []),
{ {
actions: [ actions: [
!hasParent && {
icon: <PencilSquare />,
label: t("actions.edit"),
to: `/settings/tax-regions/${taxRegion.id}/edit`,
},
{ {
icon: <Trash />, icon: <Trash />,
label: t("actions.delete"), label: t("actions.delete"),
onClick: handleDelete, onClick: handleDelete,
}, },
], ].filter(Boolean) as unknown as Action[],
}, },
]} ]}
/> />
@@ -14,6 +14,10 @@ import {
} from "../../../../../components/modals" } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useCreateTaxRegion } from "../../../../../hooks/api" import { useCreateTaxRegion } from "../../../../../hooks/api"
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { Combobox } from "../../../../../components/inputs/combobox"
import { formatProvider } from "../../../../../lib/format-provider"
import { sdk } from "../../../../../lib/client"
type TaxRegionCreateFormProps = { type TaxRegionCreateFormProps = {
parentId?: string parentId?: string
@@ -27,12 +31,23 @@ const TaxRegionCreateSchema = z.object({
value: z.string().optional(), value: z.string().optional(),
}), }),
country_code: z.string().min(1), country_code: z.string().min(1),
provider_id: z.string(),
}) })
export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => { export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const taxProviders = useComboboxData({
queryKey: ["tax_providers"],
queryFn: (params) => sdk.admin.taxProvider.list(params),
getOptions: (data) =>
data.tax_providers.map((provider) => ({
label: formatProvider(provider.id),
value: provider.id,
})),
})
const form = useForm<z.infer<typeof TaxRegionCreateSchema>>({ const form = useForm<z.infer<typeof TaxRegionCreateSchema>>({
defaultValues: { defaultValues: {
name: "", name: "",
@@ -41,6 +56,7 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
}, },
code: "", code: "",
country_code: "", country_code: "",
provider_id: "",
}, },
resolver: zodResolver(TaxRegionCreateSchema), resolver: zodResolver(TaxRegionCreateSchema),
}) })
@@ -64,6 +80,7 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
country_code: values.country_code, country_code: values.country_code,
parent_id: parentId, parent_id: parentId,
default_tax_rate: defaultRate, default_tax_rate: defaultRate,
provider_id: values.provider_id,
}, },
{ {
onSuccess: ({ tax_region }) => { onSuccess: ({ tax_region }) => {
@@ -192,6 +209,29 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
) )
}} }}
/> />
<Form.Field
control={form.control}
name="provider_id"
render={({ field }) => (
<Form.Item>
<Form.Label>
{t("taxRegions.fields.taxProvider")}
</Form.Label>
<Form.Control>
<Combobox
{...field}
options={taxProviders.options}
searchValue={taxProviders.searchValue}
onSearchValueChange={
taxProviders.onSearchValueChange
}
fetchNextPage={taxProviders.fetchNextPage}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -1,15 +1,16 @@
import { useLoaderData, useParams } from "react-router-dom" import { useLoaderData, useParams } from "react-router-dom"
import { useState } from "react"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useTaxRegion } from "../../../hooks/api/tax-regions" import { useTaxRegion } from "../../../hooks/api/tax-regions"
import { TaxRegionDetailSection } from "./components/tax-region-detail-section" import { TaxRegionDetailSection } from "./components/tax-region-detail-section"
import { TaxRegionProvinceSection } from "./components/tax-region-province-section" import { TaxRegionProvinceSection } from "./components/tax-region-province-section"
import { useState } from "react"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { useExtension } from "../../../providers/extension-provider" import { useExtension } from "../../../providers/extension-provider"
import { TaxRegionOverrideSection } from "./components/tax-region-override-section" import { TaxRegionOverrideSection } from "./components/tax-region-override-section"
import { TaxRegionSublevelAlert } from "./components/tax-region-sublevel-alert" import { TaxRegionSublevelAlert } from "./components/tax-region-sublevel-alert"
import { TaxRegionProviderSection } from "./tax-region-provider-section"
import { taxRegionLoader } from "./loader" import { taxRegionLoader } from "./loader"
export const TaxRegionDetail = () => { export const TaxRegionDetail = () => {
@@ -41,7 +42,7 @@ export const TaxRegionDetail = () => {
<SingleColumnPage <SingleColumnPage
data={taxRegion} data={taxRegion}
showJSON showJSON
// showMetadata // TOOD -> enable when tax region update is added to the API // showMetadata // TOOD -> enable tax region metadata
widgets={{ widgets={{
after: getWidgets("tax.details.after"), after: getWidgets("tax.details.after"),
before: getWidgets("tax.details.before"), before: getWidgets("tax.details.before"),
@@ -58,6 +59,7 @@ export const TaxRegionDetail = () => {
showSublevelRegions={showSublevelRegions} showSublevelRegions={showSublevelRegions}
/> />
<TaxRegionOverrideSection taxRegion={taxRegion} /> <TaxRegionOverrideSection taxRegion={taxRegion} />
<TaxRegionProviderSection taxRegion={taxRegion} />
</SingleColumnPage> </SingleColumnPage>
) )
} }
@@ -0,0 +1 @@
export * from "./tax-region-provider-section"
@@ -0,0 +1,28 @@
import { useTranslation } from "react-i18next"
import { HttpTypes } from "@medusajs/types"
import { Container, Heading } from "@medusajs/ui"
import { formatProvider } from "../../../../lib/format-provider"
export function TaxRegionProviderSection({
taxRegion,
}: {
taxRegion: HttpTypes.AdminTaxRegion
}) {
const { t } = useTranslation()
return (
<Container className="divide-y p-0">
<Heading level="h2" className="px-6 py-4">
{t("taxRegions.provider.header")}
</Heading>
<div className="px-6 py-4">
{taxRegion.provider_id && (
<span className="text-ui-fg-subtle">
{formatProvider(taxRegion.provider_id!)}
</span>
)}
</div>
</Container>
)
}
@@ -0,0 +1 @@
export * from "./tax-region-edit-form"
@@ -0,0 +1,109 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { HttpTypes } from "@medusajs/types"
import { Button, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { z } from "zod"
import { Form } from "../../../../../components/common/form"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client"
import { Combobox } from "../../../../../components/inputs/combobox"
import { formatProvider } from "../../../../../lib/format-provider"
import { useUpdateTaxRegion } from "../../../../../hooks/api"
type TaxRegionEditFormProps = {
taxRegion: HttpTypes.AdminTaxRegion
}
const TaxRegionEditSchema = z.object({
provider_id: z.string().min(1),
})
export const TaxRegionEditForm = ({ taxRegion }: TaxRegionEditFormProps) => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
const taxProviders = useComboboxData({
queryKey: ["tax_providers"],
queryFn: (params) => sdk.admin.taxProvider.list(params),
getOptions: (data) =>
data.tax_providers.map((provider) => ({
label: formatProvider(provider.id),
value: provider.id,
})),
})
const form = useForm<z.infer<typeof TaxRegionEditSchema>>({
defaultValues: {
provider_id: taxRegion.provider_id,
},
resolver: zodResolver(TaxRegionEditSchema),
})
const { mutateAsync, isPending } = useUpdateTaxRegion(taxRegion.id)
const handleSubmit = form.handleSubmit(async (values) => {
await mutateAsync(
{
provider_id: values.provider_id,
},
{
onSuccess: () => {
toast.success(t("taxRegions.edit.successToast"))
handleSuccess()
},
onError: (error) => {
toast.error(error.message)
},
}
)
})
return (
<RouteDrawer.Form form={form}>
<KeyboundForm
className="flex flex-1 flex-col overflow-hidden"
onSubmit={handleSubmit}
>
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-6 overflow-auto">
<div className="flex flex-col gap-y-4">
<Form.Field
control={form.control}
name="provider_id"
render={({ field }) => (
<Form.Item>
<Form.Label>{t("taxRegions.fields.taxProvider")}</Form.Label>
<Form.Control>
<Combobox
{...field}
options={taxProviders.options}
searchValue={taxProviders.searchValue}
onSearchValueChange={taxProviders.onSearchValueChange}
fetchNextPage={taxProviders.fetchNextPage}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)}
/>
</div>
</RouteDrawer.Body>
<RouteDrawer.Footer className="shrink-0">
<div className="flex items-center justify-end gap-x-2">
<RouteDrawer.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteDrawer.Close>
<Button size="small" type="submit" isLoading={isPending}>
{t("actions.save")}
</Button>
</div>
</RouteDrawer.Footer>
</KeyboundForm>
</RouteDrawer.Form>
)
}
@@ -0,0 +1 @@
export { TaxRegionEdit as Component } from "./tax-region-edit"
@@ -0,0 +1,33 @@
import { Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { useParams } from "react-router-dom"
import { RouteDrawer } from "../../../components/modals"
import { TaxRegionEditForm } from "./components/tax-region-edit"
import { useTaxRegion } from "../../../hooks/api"
export const TaxRegionEdit = () => {
const { t } = useTranslation()
const { id } = useParams()
const { tax_region, isPending, isError, error } = useTaxRegion(id!)
const ready = !isPending && !!tax_region
if (isError) {
throw error
}
return (
<RouteDrawer>
<RouteDrawer.Header>
<RouteDrawer.Title asChild>
<Heading>{t("taxRegions.taxRates.edit.header")}</Heading>
</RouteDrawer.Title>
<RouteDrawer.Description className="sr-only">
{t("taxRegions.taxRates.edit.hint")}
</RouteDrawer.Description>
</RouteDrawer.Header>
{ready && <TaxRegionEditForm taxRegion={tax_region} />}
</RouteDrawer>
)
}
@@ -33,6 +33,7 @@ export const TaxRegionProvinceDetailSection = ({
) )
} }
/> />
{defaultRates.map((rate) => { {defaultRates.map((rate) => {
return <TaxRateLine key={rate.id} taxRate={rate} isSublevelTaxRate /> return <TaxRateLine key={rate.id} taxRate={rate} isSublevelTaxRate />
})} })}
@@ -17,7 +17,7 @@ export type UpdateTaxRegionsStepInput = UpdateTaxRegionDTO[]
export const updateTaxRegionsStepId = "update-tax-regions" export const updateTaxRegionsStepId = "update-tax-regions"
/** /**
* This step updates tax regions. * This step updates tax regions.
* *
* @example * @example
* const data = updateTaxRegionsStep([ * const data = updateTaxRegionsStep([
* { * {
@@ -45,6 +45,7 @@ export const updateTaxRegionsStep = createStep(
id: d.id, id: d.id,
province_code: d.province_code, province_code: d.province_code,
metadata: d.metadata, metadata: d.metadata,
provider_id: d.provider_id,
})) }))
) )
@@ -20,10 +20,10 @@ export const updateTaxRegionsWorkflowId = "update-tax-regions"
/** /**
* This workflow updates one or more tax regions. It's used by the * This workflow updates one or more tax regions. It's used by the
* [Update Tax Regions Admin API Route](https://docs.medusajs.com/api/admin#tax-regions_posttaxregionsid). * [Update Tax Regions Admin API Route](https://docs.medusajs.com/api/admin#tax-regions_posttaxregionsid).
* *
* You can use this workflow within your own customizations or custom workflows, allowing you * You can use this workflow within your own customizations or custom workflows, allowing you
* to update tax regions in your custom flows. * to update tax regions in your custom flows.
* *
* @example * @example
* const { result } = await updateTaxRegionsWorkflow(container) * const { result } = await updateTaxRegionsWorkflow(container)
* .run({ * .run({
@@ -34,9 +34,9 @@ export const updateTaxRegionsWorkflowId = "update-tax-regions"
* } * }
* ] * ]
* }) * })
* *
* @summary * @summary
* *
* Update one or more tax regions. * Update one or more tax regions.
*/ */
export const updateTaxRegionsWorkflow = createWorkflow( export const updateTaxRegionsWorkflow = createWorkflow(
+6
View File
@@ -37,6 +37,7 @@ import { ShippingOption } from "./shipping-option"
import { ShippingProfile } from "./shipping-profile" import { ShippingProfile } from "./shipping-profile"
import { StockLocation } from "./stock-location" import { StockLocation } from "./stock-location"
import { Store } from "./store" import { Store } from "./store"
import { TaxProvider } from "./tax-provider"
import { TaxRate } from "./tax-rate" import { TaxRate } from "./tax-rate"
import { TaxRegion } from "./tax-region" import { TaxRegion } from "./tax-region"
import { Upload } from "./upload" import { Upload } from "./upload"
@@ -208,6 +209,10 @@ export class Admin {
* @tags promotion * @tags promotion
*/ */
public promotion: Promotion public promotion: Promotion
/**
* @tags tax
*/
public taxProvider: TaxProvider
/** /**
* @tags promotion * @tags promotion
*/ */
@@ -261,5 +266,6 @@ export class Admin {
this.promotion = new Promotion(client) this.promotion = new Promotion(client)
this.campaign = new Campaign(client) this.campaign = new Campaign(client)
this.plugin = new Plugin(client) this.plugin = new Plugin(client)
this.taxProvider = new TaxProvider(client)
} }
} }
@@ -0,0 +1,51 @@
import { HttpTypes } from "@medusajs/types"
import { Client } from "../client"
import { ClientHeaders } from "../types"
const taxProviderUrl = "/admin/tax-providers"
export class TaxProvider {
/**
* @ignore
*/
private client: Client
/**
* @ignore
*/
constructor(client: Client) {
this.client = client
}
/**
* This method retrieves a list of tax providers. It sends a request to the
* [List Tax Providers](https://docs.medusajs.com/api/admin#tax-providers_gettaxproviders)
* API route.
*
* @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request.
* @returns The list of tax providers.
*
* @example
* To retrieve the list of tax providers:
*
* ```ts
* sdk.admin.taxProvider.list()
* .then(({ tax_providers, count, limit, offset }) => {
* console.log(tax_providers)
* })
* ```
*/
async list(
query?: HttpTypes.AdminGetTaxProvidersParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminTaxProviderListResponse>(
taxProviderUrl,
{
method: "GET",
headers,
query,
}
)
}
}
+21 -21
View File
@@ -20,12 +20,12 @@ export class TaxRate {
* This method creates a tax rate. It sends a request to the * This method creates a tax rate. It sends a request to the
* [Create Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_posttaxrates) * [Create Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_posttaxrates)
* API route. * API route.
* *
* @param body - The details of the tax rate to create. * @param body - The details of the tax rate to create.
* @param query - Configure the fields and relations to retrieve in the tax rate. * @param query - Configure the fields and relations to retrieve in the tax rate.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The tax rate's details. * @returns The tax rate's details.
* *
* @example * @example
* sdk.admin.taxRate.create({ * sdk.admin.taxRate.create({
* name: "VAT", * name: "VAT",
@@ -54,13 +54,13 @@ export class TaxRate {
* This method updates a tax rate. It sends a request to the * This method updates a tax rate. It sends a request to the
* [Update Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_posttaxratesid) * [Update Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_posttaxratesid)
* API route. * API route.
* *
* @param id - The ID of the tax rate to update. * @param id - The ID of the tax rate to update.
* @param body - The details of the tax rate to update. * @param body - The details of the tax rate to update.
* @param query - Configure the fields and relations to retrieve in the tax rate. * @param query - Configure the fields and relations to retrieve in the tax rate.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The tax rate's details. * @returns The tax rate's details.
* *
* @example * @example
* sdk.admin.taxRate.update("txrat_123", { * sdk.admin.taxRate.update("txrat_123", {
* name: "VAT", * name: "VAT",
@@ -91,11 +91,11 @@ export class TaxRate {
* This method deletes a tax rate. It sends a request to the * This method deletes a tax rate. It sends a request to the
* [Delete Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_deletetaxratesid) * [Delete Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_deletetaxratesid)
* API route. * API route.
* *
* @param id - The ID of the tax rate to delete. * @param id - The ID of the tax rate to delete.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The deletion's details. * @returns The deletion's details.
* *
* @example * @example
* sdk.admin.taxRate.delete("txrat_123") * sdk.admin.taxRate.delete("txrat_123")
* .then(({ deleted }) => { * .then(({ deleted }) => {
@@ -116,24 +116,24 @@ export class TaxRate {
* This method retrieves a tax rate. It sends a request to the * This method retrieves a tax rate. It sends a request to the
* [Get Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_gettaxratesid) * [Get Tax Rate](https://docs.medusajs.com/api/admin#tax-rates_gettaxratesid)
* API route. * API route.
* *
* @param id - The ID of the tax rate to retrieve. * @param id - The ID of the tax rate to retrieve.
* @param query - Configure the fields and relations to retrieve in the tax rate. * @param query - Configure the fields and relations to retrieve in the tax rate.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The tax rate's details. * @returns The tax rate's details.
* *
* @example * @example
* To retrieve a tax rate by its ID: * To retrieve a tax rate by its ID:
* *
* ```ts * ```ts
* sdk.admin.taxRate.retrieve("txrat_123") * sdk.admin.taxRate.retrieve("txrat_123")
* .then(({ tax_rate }) => { * .then(({ tax_rate }) => {
* console.log(tax_rate) * console.log(tax_rate)
* }) * })
* ``` * ```
* *
* To specify the fields and relations to retrieve: * To specify the fields and relations to retrieve:
* *
* ```ts * ```ts
* sdk.admin.taxRate.retrieve("txrat_123", { * sdk.admin.taxRate.retrieve("txrat_123", {
* fields: "id,*tax_region" * fields: "id,*tax_region"
@@ -142,7 +142,7 @@ export class TaxRate {
* console.log(tax_rate) * console.log(tax_rate)
* }) * })
* ``` * ```
* *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/ */
async retrieve( async retrieve(
@@ -164,25 +164,25 @@ export class TaxRate {
* This method retrieves a list of tax rates. It sends a request to the * This method retrieves a list of tax rates. It sends a request to the
* [List Tax Rates](https://docs.medusajs.com/api/admin#tax-rates_gettaxrates) * [List Tax Rates](https://docs.medusajs.com/api/admin#tax-rates_gettaxrates)
* API route. * API route.
* *
* @param query - Filters and pagination configurations. * @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The list of tax rates. * @returns The list of tax rates.
* *
* @example * @example
* To retrieve the list of tax rates: * To retrieve the list of tax rates:
* *
* ```ts * ```ts
* sdk.admin.taxRate.list() * sdk.admin.taxRate.list()
* .then(({ tax_rates, count, limit, offset }) => { * .then(({ tax_rates, count, limit, offset }) => {
* console.log(tax_rates) * console.log(tax_rates)
* }) * })
* ``` * ```
* *
* To configure the pagination, pass the `limit` and `offset` query parameters. * To configure the pagination, pass the `limit` and `offset` query parameters.
* *
* For example, to retrieve only 10 items and skip 10 items: * For example, to retrieve only 10 items and skip 10 items:
* *
* ```ts * ```ts
* sdk.admin.taxRate.list({ * sdk.admin.taxRate.list({
* limit: 10, * limit: 10,
@@ -192,10 +192,10 @@ export class TaxRate {
* console.log(tax_rates) * console.log(tax_rates)
* }) * })
* ``` * ```
* *
* Using the `fields` query parameter, you can specify the fields and relations to retrieve * Using the `fields` query parameter, you can specify the fields and relations to retrieve
* in each tax rate: * in each tax rate:
* *
* ```ts * ```ts
* sdk.admin.taxRate.list({ * sdk.admin.taxRate.list({
* fields: "id,*tax_region" * fields: "id,*tax_region"
@@ -204,7 +204,7 @@ export class TaxRate {
* console.log(tax_rates) * console.log(tax_rates)
* }) * })
* ``` * ```
* *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/ */
async list( async list(
+56 -20
View File
@@ -6,7 +6,7 @@ const taxRegionUrl = "/admin/tax-regions"
/** /**
* @privateRemarks * @privateRemarks
* *
* TODO: Add support for updating a tax region * TODO: Add support for updating a tax region
*/ */
export class TaxRegion { export class TaxRegion {
@@ -25,12 +25,12 @@ export class TaxRegion {
* This method creates a tax region. It sends a request to the * This method creates a tax region. It sends a request to the
* [Create Tax Region](https://docs.medusajs.com/api/admin#tax-regions_posttaxregions) * [Create Tax Region](https://docs.medusajs.com/api/admin#tax-regions_posttaxregions)
* API route. * API route.
* *
* @param body - The details of the tax region to create. * @param body - The details of the tax region to create.
* @param query - Configure the fields and relations to retrieve in the tax region. * @param query - Configure the fields and relations to retrieve in the tax region.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The tax region's details. * @returns The tax region's details.
* *
* @example * @example
* sdk.admin.taxRegion.create({ * sdk.admin.taxRegion.create({
* country_code: "us", * country_code: "us",
@@ -62,15 +62,51 @@ export class TaxRegion {
) )
} }
/**
* This method updates a tax region. It sends a request to the
* [Update Tax Region](https://docs.medusajs.com/api/admin#tax-regions_posttaxregionsid)
* API route.
*
* @param id - The ID of the tax region to update.
* @param body - The details of the tax region to update.
* @param query - Configure the fields and relations to retrieve in the tax region.
* @param headers - Headers to pass in the request.
* @returns The tax region's details.
*
* @example
* sdk.admin.taxRegion.update("txreg_123", {
* province_code: "ca",
* })
* .then(({ tax_region }) => {
* console.log(tax_region)
* })
*/
async update(
id: string,
body: HttpTypes.AdminUpdateTaxRegion,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminTaxRegionResponse>(
`${taxRegionUrl}/${id}`,
{
method: "POST",
headers,
body,
query,
}
)
}
/** /**
* This method deletes a tax region. It sends a request to the * This method deletes a tax region. It sends a request to the
* [Delete Tax Region](https://docs.medusajs.com/api/admin#tax-regions_deletetaxregionsid) * [Delete Tax Region](https://docs.medusajs.com/api/admin#tax-regions_deletetaxregionsid)
* API route. * API route.
* *
* @param id - The ID of the tax region to delete. * @param id - The ID of the tax region to delete.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The deletion's details. * @returns The deletion's details.
* *
* @example * @example
* sdk.admin.taxRegion.delete("txreg_123") * sdk.admin.taxRegion.delete("txreg_123")
* .then(({ deleted }) => { * .then(({ deleted }) => {
@@ -91,24 +127,24 @@ export class TaxRegion {
* This method retrieves a tax region. It sends a request to the * This method retrieves a tax region. It sends a request to the
* [Get Tax Region](https://docs.medusajs.com/api/admin#tax-regions_gettaxregionsid) * [Get Tax Region](https://docs.medusajs.com/api/admin#tax-regions_gettaxregionsid)
* API route. * API route.
* *
* @param id - The ID of the tax region to retrieve. * @param id - The ID of the tax region to retrieve.
* @param query - Configure the fields and relations to retrieve in the tax region. * @param query - Configure the fields and relations to retrieve in the tax region.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The tax region's details. * @returns The tax region's details.
* *
* @example * @example
* To retrieve a tax region by its ID: * To retrieve a tax region by its ID:
* *
* ```ts * ```ts
* sdk.admin.taxRegion.retrieve("txreg_123") * sdk.admin.taxRegion.retrieve("txreg_123")
* .then(({ tax_region }) => { * .then(({ tax_region }) => {
* console.log(tax_region) * console.log(tax_region)
* }) * })
* ``` * ```
* *
* To specify the fields and relations to retrieve: * To specify the fields and relations to retrieve:
* *
* ```ts * ```ts
* sdk.admin.taxRegion.retrieve("txreg_123", { * sdk.admin.taxRegion.retrieve("txreg_123", {
* fields: "id,*tax_rates" * fields: "id,*tax_rates"
@@ -117,7 +153,7 @@ export class TaxRegion {
* console.log(tax_region) * console.log(tax_region)
* }) * })
* ``` * ```
* *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/ */
async retrieve( async retrieve(
@@ -139,25 +175,25 @@ export class TaxRegion {
* This method retrieves a list of tax regions. It sends a request to the * This method retrieves a list of tax regions. It sends a request to the
* [List Tax Regions](https://docs.medusajs.com/api/admin#tax-regions_gettaxregions) * [List Tax Regions](https://docs.medusajs.com/api/admin#tax-regions_gettaxregions)
* API route. * API route.
* *
* @param query - Filters and pagination configurations. * @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request. * @param headers - Headers to pass in the request.
* @returns The list of tax regions. * @returns The list of tax regions.
* *
* @example * @example
* To retrieve the list of tax regions: * To retrieve the list of tax regions:
* *
* ```ts * ```ts
* sdk.admin.taxRegion.list() * sdk.admin.taxRegion.list()
* .then(({ tax_regions, count, limit, offset }) => { * .then(({ tax_regions, count, limit, offset }) => {
* console.log(tax_regions) * console.log(tax_regions)
* }) * })
* ``` * ```
* *
* To configure the pagination, pass the `limit` and `offset` query parameters. * To configure the pagination, pass the `limit` and `offset` query parameters.
* *
* For example, to retrieve only 10 items and skip 10 items: * For example, to retrieve only 10 items and skip 10 items:
* *
* ```ts * ```ts
* sdk.admin.taxRegion.list({ * sdk.admin.taxRegion.list({
* limit: 10, * limit: 10,
@@ -167,10 +203,10 @@ export class TaxRegion {
* console.log(tax_regions) * console.log(tax_regions)
* }) * })
* ``` * ```
* *
* Using the `fields` query parameter, you can specify the fields and relations to retrieve * Using the `fields` query parameter, you can specify the fields and relations to retrieve
* in each tax region: * in each tax region:
* *
* ```ts * ```ts
* sdk.admin.taxRegion.list({ * sdk.admin.taxRegion.list({
* fields: "id,*tax_rates" * fields: "id,*tax_rates"
@@ -179,7 +215,7 @@ export class TaxRegion {
* console.log(tax_regions) * console.log(tax_regions)
* }) * })
* ``` * ```
* *
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). * Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
*/ */
async list( async list(
+1
View File
@@ -39,6 +39,7 @@ export * from "./shipping-option"
export * from "./shipping-profile" export * from "./shipping-profile"
export * from "./stock-locations" export * from "./stock-locations"
export * from "./store" export * from "./store"
export * from "./tax-provider"
export * from "./tax-rate" export * from "./tax-rate"
export * from "./tax-region" export * from "./tax-region"
export * from "./user" export * from "./user"
@@ -0,0 +1,10 @@
export interface AdminTaxProvider {
/**
* The ID of the tax provider.
*/
id: string
/**
* Whether the tax provider is enabled.
*/
is_enabled: boolean
}
@@ -0,0 +1,3 @@
export * from "./entities"
export * from "./queries"
export * from "./responses"
@@ -0,0 +1,15 @@
import { BaseFilterable } from "../../../dal"
import { FindParams } from "../../common"
export interface AdminGetTaxProvidersParams
extends FindParams,
BaseFilterable<AdminGetTaxProvidersParams> {
/**
* Filter by tax provider ID(s).
*/
id?: string | string[]
/**
* Whether the tax provider is enabled.
*/
is_enabled?: boolean
}
@@ -0,0 +1,9 @@
import { PaginatedResponse } from "../../common"
import { AdminTaxProvider } from "./entities"
export type AdminTaxProviderListResponse = PaginatedResponse<{
/**
* The list of tax providers.
*/
tax_providers: AdminTaxProvider[]
}>
@@ -0,0 +1 @@
export * from "./admin"
@@ -7,14 +7,14 @@ export interface AdminTaxRegion {
id: string id: string
/** /**
* The tax region's country code. * The tax region's country code.
* *
* @example * @example
* "us" * "us"
*/ */
country_code: string | null country_code: string | null
/** /**
* The tax region's lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code. * The tax region's lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code.
* *
* @example * @example
* "us-ca" * "us-ca"
*/ */
@@ -27,6 +27,10 @@ export interface AdminTaxRegion {
* The ID of the parent tax region. * The ID of the parent tax region.
*/ */
parent_id: string | null parent_id: string | null
/**
* The ID of the tax provider for the region.
*/
provider_id: string | null
/** /**
* The date the tax region was created. * The date the tax region was created.
*/ */
@@ -3,6 +3,10 @@ export interface AdminCreateTaxRegion {
* The country code of the tax region. * The country code of the tax region.
*/ */
country_code: string country_code: string
/**
* The ID of the tax provider.
*/
provider_id?: string
/** /**
* The lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code of the tax region. * The lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code of the tax region.
*/ */
@@ -41,3 +45,21 @@ export interface AdminCreateTaxRegion {
*/ */
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
export interface AdminUpdateTaxRegion {
/**
* The lower-case [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) province or state code of the tax region.
*/
province_code?: string
/**
* The ID of the tax provider.
*/
provider_id?: string
/**
* Custom key-value pairs that can be added to the tax region.
*/
/**
* Custom key-value pairs that can be added to the tax region.
*/
metadata?: Record<string, unknown>
}
+5
View File
@@ -163,6 +163,11 @@ export interface TaxRegionDTO {
*/ */
parent_id: string | null parent_id: string | null
/**
* The ID of the tax provider for the region.
*/
provider_id: string | null
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
*/ */
+44
View File
@@ -1,4 +1,7 @@
import { MetadataType } from "../common" import { MetadataType } from "../common"
import { BaseFilterable } from "../dal"
import { OperatorMap } from "../dal"
import { TaxProviderDTO } from "./common"
/** /**
* The tax rate to be created. * The tax rate to be created.
@@ -49,6 +52,37 @@ export interface CreateTaxRateDTO {
metadata?: MetadataType metadata?: MetadataType
} }
/**
* The tax provider to be created.
*/
export interface CreateTaxProviderDTO {
/**
* The provider's ID.
*/
id: string
/**
* Whether the provider is enabled.
*/
is_enabled?: boolean
}
/**
* The filters to apply on the retrieved tax providers.
*/
export interface FilterableTaxProviderProps
extends BaseFilterable<TaxProviderDTO> {
/**
* The IDs to filter the tax provider by.
*/
id?: string | string[] | OperatorMap<string | string[]>
/**
* Filter by whether the tax provider is enabled.
*/
is_enabled?: boolean
}
/** /**
* The attributes in the tax rate to be created or updated. * The attributes in the tax rate to be created or updated.
*/ */
@@ -166,6 +200,11 @@ export interface CreateTaxRegionDTO {
*/ */
parent_id?: string | null parent_id?: string | null
/**
* The ID of the tax provider for the region.
*/
provider_id?: string | null
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
*/ */
@@ -220,6 +259,11 @@ export interface UpdateTaxRegionDTO {
*/ */
province_code?: string | null province_code?: string | null
/**
* The ID of the tax provider for the region.
*/
provider_id?: string | null
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
*/ */
+20
View File
@@ -11,6 +11,7 @@ import {
TaxableItemDTO, TaxableItemDTO,
TaxableShippingDTO, TaxableShippingDTO,
TaxCalculationContext, TaxCalculationContext,
TaxProviderDTO,
TaxRateDTO, TaxRateDTO,
TaxRateRuleDTO, TaxRateRuleDTO,
TaxRegionDTO, TaxRegionDTO,
@@ -19,6 +20,7 @@ import {
CreateTaxRateDTO, CreateTaxRateDTO,
CreateTaxRateRuleDTO, CreateTaxRateRuleDTO,
CreateTaxRegionDTO, CreateTaxRegionDTO,
FilterableTaxProviderProps,
UpdateTaxRateDTO, UpdateTaxRateDTO,
UpdateTaxRegionDTO, UpdateTaxRegionDTO,
UpsertTaxRateDTO, UpsertTaxRateDTO,
@@ -580,6 +582,24 @@ export interface ITaxModuleService extends IModuleService {
sharedContext?: Context sharedContext?: Context
): Promise<TaxRateRuleDTO> ): Promise<TaxRateRuleDTO>
/**
* This method retrieves a paginated list of tax providers based on optional filters and configuration.
*
* @param {FilterableTaxProviderProps} filters - The filters to apply on the retrieved tax providers.
* @param {FindConfig<TaxProviderDTO>} config - The configurations determining how the tax provider is retrieved.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<TaxProviderDTO[]>} The list of tax providers.
*
* @example
* const taxProviders = await taxModuleService.listTaxProviders()
*/
listTaxProviders(
filters?: FilterableTaxProviderProps,
config?: FindConfig<TaxProviderDTO>,
sharedContext?: Context
): Promise<TaxProviderDTO[]>
/** /**
* This method creates tax rate rules. * This method creates tax rate rules.
* *
@@ -0,0 +1,19 @@
import * as QueryConfig from "./query-config"
import { validateAndTransformQuery } from "@medusajs/framework"
import { MiddlewareRoute } from "@medusajs/framework/http"
import { AdminGetTaxProvidersParams } from "./validators"
export const adminTaxProviderRoutesMiddlewares: MiddlewareRoute[] = [
{
method: "GET",
matcher: "/admin/tax-providers",
middlewares: [
validateAndTransformQuery(
AdminGetTaxProvidersParams,
QueryConfig.listTransformQueryConfig
),
],
},
]
@@ -0,0 +1,11 @@
export const defaults = ["id", "is_enabled"]
export const retrieveTransformQueryConfig = {
defaults,
isList: false,
}
export const listTransformQueryConfig = {
...retrieveTransformQueryConfig,
isList: true,
}
@@ -0,0 +1,28 @@
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminGetTaxProvidersParams>,
res: MedusaResponse<HttpTypes.AdminTaxProviderListResponse>
) => {
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
const result = await query.graph({
entity: "tax_providers",
filters: req.filterableFields,
pagination: req.queryConfig.pagination,
fields: req.queryConfig.fields,
})
res.status(200).json({
tax_providers: result.data,
count: result.metadata?.count ?? 0,
offset: result.metadata?.skip ?? 0,
limit: result.metadata?.take ?? 0,
})
}
@@ -0,0 +1,22 @@
import { z } from "zod"
import { createFindParams } from "../../utils/validators"
import { applyAndAndOrOperators } from "../../utils/common-validators"
export const AdminGetTaxProvidersParamsFields = z.object({
id: z.union([z.string(), z.array(z.string())]).optional(),
is_enabled: z.boolean().optional(),
})
export type AdminGetTaxProvidersParamsFieldsType = z.infer<
typeof AdminGetTaxProvidersParamsFields
>
export type AdminGetTaxProvidersParamsType = z.infer<
typeof AdminGetTaxProvidersParams
>
export const AdminGetTaxProvidersParams = createFindParams({
limit: 20,
offset: 0,
})
.merge(AdminGetTaxProvidersParamsFields)
.merge(applyAndAndOrOperators(AdminGetTaxProvidersParamsFields))
@@ -14,7 +14,7 @@ import {
import { AdminUpdateTaxRegionType } from "../validators" import { AdminUpdateTaxRegionType } from "../validators"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateTaxRegion>,
res: MedusaResponse<HttpTypes.AdminTaxRegionResponse> res: MedusaResponse<HttpTypes.AdminTaxRegionResponse>
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
@@ -42,6 +42,7 @@ export const AdminGetTaxRegionsParams = createFindParams({
export type AdminCreateTaxRegionType = z.infer<typeof AdminCreateTaxRegion> export type AdminCreateTaxRegionType = z.infer<typeof AdminCreateTaxRegion>
export const AdminCreateTaxRegion = z.object({ export const AdminCreateTaxRegion = z.object({
country_code: z.string(), country_code: z.string(),
provider_id: z.string().nullish(),
province_code: z.string().nullish(), province_code: z.string().nullish(),
parent_id: z.string().nullish(), parent_id: z.string().nullish(),
default_tax_rate: z default_tax_rate: z
@@ -59,5 +60,6 @@ export const AdminCreateTaxRegion = z.object({
export type AdminUpdateTaxRegionType = z.infer<typeof AdminUpdateTaxRegion> export type AdminUpdateTaxRegionType = z.infer<typeof AdminUpdateTaxRegion>
export const AdminUpdateTaxRegion = z.object({ export const AdminUpdateTaxRegion = z.object({
province_code: z.string().nullish(), province_code: z.string().nullish(),
provider_id: z.string().optional(),
metadata: z.record(z.unknown()).nullish(), metadata: z.record(z.unknown()).nullish(),
}) })
+2
View File
@@ -38,6 +38,7 @@ import { adminStockLocationRoutesMiddlewares } from "./admin/stock-locations/mid
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares" import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares" import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares"
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares" import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
import { adminTaxProviderRoutesMiddlewares } from "./admin/tax-providers/middlewares"
import { adminUploadRoutesMiddlewares } from "./admin/uploads/middlewares" import { adminUploadRoutesMiddlewares } from "./admin/uploads/middlewares"
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares" import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares" import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares"
@@ -120,6 +121,7 @@ export default defineMiddlewares([
...adminRefundReasonsRoutesMiddlewares, ...adminRefundReasonsRoutesMiddlewares,
...adminExchangeRoutesMiddlewares, ...adminExchangeRoutesMiddlewares,
...adminProductVariantRoutesMiddlewares, ...adminProductVariantRoutesMiddlewares,
...adminTaxProviderRoutesMiddlewares,
...adminOrderEditRoutesMiddlewares, ...adminOrderEditRoutesMiddlewares,
...adminPaymentCollectionsMiddlewares, ...adminPaymentCollectionsMiddlewares,
]) ])
@@ -0,0 +1,49 @@
import { updateTaxRegionsStep, useQueryGraphStep } from "@medusajs/core-flows"
import { ExecArgs } from "@medusajs/framework/types"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import { transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
const assignSystemProviderToTaxRegionsWorkflow = createWorkflow(
"assign-system-provider-to-tax-regions",
() => {
const { data: taxRegions } = useQueryGraphStep({
entity: "tax_region",
fields: ["id", "provider_id", "province_code"],
})
const updateData = transform({ taxRegions }, ({ taxRegions }) => {
/**
* Update only parent regions that don't have a provider set.
*/
return taxRegions
.filter(
(taxRegion) => !taxRegion.province_code && !taxRegion.provider_id
)
.map((taxRegion) => ({
id: taxRegion.id,
provider_id: "tp_system_system",
}))
})
updateTaxRegionsStep(updateData)
return new WorkflowResponse(void 0)
}
)
export default async function assignTaxSystemProviderToTaxRegions({
container,
}: ExecArgs) {
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
logger.info("Assigning tax system provider to tax regions")
try {
await assignSystemProviderToTaxRegionsWorkflow(container).run()
logger.info("System provider assigned to tax regions")
} catch (e) {
logger.error(e)
throw e
}
}
@@ -27,14 +27,17 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
const [us, dk, de, ca] = await service.createTaxRegions([ const [us, dk, de, ca] = await service.createTaxRegions([
{ {
country_code: "US", country_code: "US",
provider_id: "tp_system_system",
default_tax_rate: { name: "US Default Rate", rate: 2, code: "US" }, default_tax_rate: { name: "US Default Rate", rate: 2, code: "US" },
}, },
{ {
country_code: "DK", country_code: "DK",
provider_id: "tp_system_system",
default_tax_rate: { name: "Denmark Default Rate", rate: 25, code: "DK" }, default_tax_rate: { name: "Denmark Default Rate", rate: 25, code: "DK" },
}, },
{ {
country_code: "DE", country_code: "DE",
provider_id: "tp_system_system",
default_tax_rate: { default_tax_rate: {
code: "DE19", code: "DE19",
name: "Germany Default Rate", name: "Germany Default Rate",
@@ -43,6 +46,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
}, },
{ {
country_code: "CA", country_code: "CA",
provider_id: "tp_system_system",
default_tax_rate: { name: "Canada Default Rate", rate: 5, code: "CA" }, default_tax_rate: { name: "Canada Default Rate", rate: 5, code: "CA" },
}, },
]) ])
+51 -12
View File
@@ -4,25 +4,35 @@ import {
LoaderOptions, LoaderOptions,
ModuleProvider, ModuleProvider,
ModulesSdkTypes, ModulesSdkTypes,
CreateTaxProviderDTO,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { asFunction, Lifetime } from "awilix" import { asFunction, asValue, Lifetime } from "awilix"
import * as providers from "../providers" 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) => { 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({ container.register({
[`tp_${klass.identifier}`]: asFunction( [key]: asFunction((cradle) => new klass(cradle, pluginOptions.options), {
(cradle) => new klass(cradle, pluginOptions), lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
{ lifetime: klass.LIFE_TIME || Lifetime.SINGLETON } }),
),
}) })
container.registerAdd( container.registerAdd(PROVIDER_REGISTRATION_KEY, asValue(key))
"tax_providers",
asFunction((cradle) => new klass(cradle, pluginOptions), {
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
})
)
} }
export default async ({ export default async ({
@@ -36,7 +46,7 @@ export default async ({
>): Promise<void> => { >): Promise<void> => {
// Local providers // Local providers
for (const provider of Object.values(providers)) { for (const provider of Object.values(providers)) {
await registrationFn(provider, container, {}) await registrationFn(provider, container, { id: "system" })
} }
await moduleProviderLoader({ await moduleProviderLoader({
@@ -44,4 +54,33 @@ export default async ({
providers: options?.providers || [], providers: options?.providers || [],
registerServiceFn: registrationFn, 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 TaxModuleService } from "./tax-module-service"
export { default as TaxProviderService } from "./tax-provider"
@@ -20,13 +20,14 @@ import {
promiseAll, promiseAll,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { TaxProvider, TaxRate, TaxRateRule, TaxRegion } from "@models" import { TaxProvider, TaxRate, TaxRateRule, TaxRegion } from "@models"
import { TaxProviderService } from "@services"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
taxRateService: ModulesSdkTypes.IMedusaInternalService<any> taxRateService: ModulesSdkTypes.IMedusaInternalService<any>
taxRegionService: ModulesSdkTypes.IMedusaInternalService<any> taxRegionService: ModulesSdkTypes.IMedusaInternalService<any>
taxRateRuleService: ModulesSdkTypes.IMedusaInternalService<any> taxRateRuleService: ModulesSdkTypes.IMedusaInternalService<any>
taxProviderService: ModulesSdkTypes.IMedusaInternalService<any> taxProviderService: TaxProviderService
[key: `tp_${string}`]: ITaxProvider [key: `tp_${string}`]: ITaxProvider
} }
@@ -57,9 +58,7 @@ export default class TaxModuleService
protected taxRateRuleService_: ModulesSdkTypes.IMedusaInternalService< protected taxRateRuleService_: ModulesSdkTypes.IMedusaInternalService<
InferEntityType<typeof TaxRateRule> InferEntityType<typeof TaxRateRule>
> >
protected taxProviderService_: ModulesSdkTypes.IMedusaInternalService< protected taxProviderService_: TaxProviderService
InferEntityType<typeof TaxProvider>
>
constructor( constructor(
{ {
@@ -445,7 +444,7 @@ export default class TaxModuleService
) )
const taxLines = await this.getTaxLinesFromProvider( const taxLines = await this.getTaxLinesFromProvider(
parentRegion.provider_id, parentRegion.provider_id as string,
toReturn, toReturn,
calculationContext calculationContext
) )
@@ -454,20 +453,11 @@ export default class TaxModuleService
} }
private async getTaxLinesFromProvider( private async getTaxLinesFromProvider(
rawProviderId: string | null, providerId: string,
items: ItemWithRates[], items: ItemWithRates[],
calculationContext: TaxTypes.TaxCalculationContext calculationContext: TaxTypes.TaxCalculationContext
) { ) {
const providerId = rawProviderId || "system" const provider = this.taxProviderService_.retrieveProvider(providerId)
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 [itemLines, shippingLines] = items.reduce( const [itemLines, shippingLines] = items.reduce(
(acc, line) => { (acc, line) => {
@@ -730,27 +720,4 @@ export default class TaxModuleService
private normalizeRegionCodes(code: string) { private normalizeRegionCodes(code: string) {
return code.toLowerCase() 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)
}
}