Add support for tax inclusivity to region and store (#7808)
This also includes rework of the currency model for the Store module. This change is breaking as existing stores won't have any supported currencies set, so users would need to go to the store settings again and choose the supported currencies there.
This commit is contained in:
@@ -848,7 +848,6 @@
|
||||
"shippingProfilesDesc": "Shipping rules for different types of products",
|
||||
"shippingOptionTypes": "Shipping Option Types",
|
||||
"shippingOptionTypesDesc": "Group options based on characteristic"
|
||||
|
||||
},
|
||||
"salesChannels": {
|
||||
"header": "Sales Channels",
|
||||
@@ -1422,6 +1421,7 @@
|
||||
"removeCountriesWarning_one": "You are about to remove {{count}} country from the region. This action cannot be undone.",
|
||||
"removeCountriesWarning_other": "You are about to remove {{count}} countries from the region. This action cannot be undone.",
|
||||
"removeCountryWarning": "You are about to remove the country {{name}} from the region. This action cannot be undone.",
|
||||
"automaticTaxesHint": "When enabled taxes will only be calculated at checkout based on the shipping address.",
|
||||
"taxInclusiveHint": "When enabled prices in the region will be tax inclusive.",
|
||||
"providersHint": " Add which payment providers should be available in this region.",
|
||||
"shippingOptions": "Shipping Options",
|
||||
@@ -1791,6 +1791,7 @@
|
||||
"inventory": "Inventory",
|
||||
"optional": "Optional",
|
||||
"note": "Note",
|
||||
"automaticTaxes": "Automatic Taxes",
|
||||
"taxInclusivePricing": "Tax inclusive pricing",
|
||||
"taxRate": "Tax Rate",
|
||||
"taxCode": "Tax Code",
|
||||
|
||||
+7
-4
@@ -248,10 +248,13 @@ export const CreateCampaignFormFields = ({ form, fieldScope = "" }) => {
|
||||
|
||||
<Select.Content>
|
||||
{Object.values(currencies)
|
||||
.filter((currency) =>
|
||||
store?.supported_currency_codes?.includes(
|
||||
currency.code.toLocaleLowerCase()
|
||||
)
|
||||
.filter(
|
||||
(currency) =>
|
||||
!!store?.supported_currencies?.find(
|
||||
(c) =>
|
||||
c.currency_code ===
|
||||
currency.code.toLocaleLowerCase()
|
||||
)
|
||||
)
|
||||
.map((currency) => (
|
||||
<Select.Item
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ export const CreateShippingOptionsPricesForm = ({
|
||||
} = useStore()
|
||||
|
||||
const currencies = useMemo(
|
||||
() => store?.supported_currency_codes || [],
|
||||
() => store?.supported_currencies?.map((c) => c.currency_code) || [],
|
||||
[store]
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ export function EditShippingOptionsPricingForm({
|
||||
} = useStore()
|
||||
|
||||
const currencies = useMemo(
|
||||
() => store?.supported_currency_codes || [],
|
||||
() => store?.supported_currencies?.map((c) => c.currency_code) || [],
|
||||
[store]
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ export const PricingPricesForm = ({ form }: PricingPricesFormProps) => {
|
||||
error: currencyError,
|
||||
} = useCurrencies(
|
||||
{
|
||||
code: store?.supported_currency_codes,
|
||||
limit: store?.supported_currency_codes?.length,
|
||||
code: store?.supported_currencies?.map((c) => c.currency_code),
|
||||
limit: store?.supported_currencies?.length,
|
||||
},
|
||||
{
|
||||
enabled: !!store,
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ export const PricingProductPricesForm = ({
|
||||
error: currencyError,
|
||||
} = useCurrencies(
|
||||
{
|
||||
code: store?.supported_currency_codes,
|
||||
code: store?.supported_currencies?.map((c) => c.currency_code),
|
||||
},
|
||||
{
|
||||
enabled: !!store,
|
||||
|
||||
@@ -19,8 +19,8 @@ export const VariantPricingForm = ({ form }: VariantPricingFormProps) => {
|
||||
const { store, isLoading: isStoreLoading } = useStore()
|
||||
const { currencies, isLoading: isCurrenciesLoading } = useCurrencies(
|
||||
{
|
||||
code: store?.supported_currency_codes,
|
||||
limit: store?.supported_currency_codes?.length,
|
||||
code: store?.supported_currencies?.map((c) => c.currency_code),
|
||||
limit: store?.supported_currencies?.length,
|
||||
},
|
||||
{
|
||||
enabled: !!store,
|
||||
|
||||
+2
-4
@@ -21,9 +21,7 @@ export const ProductCreateVariantsForm = ({
|
||||
}: ProductCreateVariantsFormProps) => {
|
||||
const { regions } = useRegions({ limit: 9999 })
|
||||
|
||||
const { store, isPending, isError, error } = useStore({
|
||||
fields: "supported_currency_codes",
|
||||
})
|
||||
const { store, isPending, isError, error } = useStore()
|
||||
|
||||
const variants = useWatch({
|
||||
control: form.control,
|
||||
@@ -39,7 +37,7 @@ export const ProductCreateVariantsForm = ({
|
||||
|
||||
const columns = useColumns({
|
||||
options,
|
||||
currencies: store?.supported_currency_codes,
|
||||
currencies: store?.supported_currencies?.map((c) => c.currency_code) || [],
|
||||
regions,
|
||||
})
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ const buildFilters = (attribute?: string, store?: StoreDTO) => {
|
||||
|
||||
if (attribute === "currency_code") {
|
||||
return {
|
||||
value: store.supported_currency_codes,
|
||||
value: store.supported_currencies?.map((c) => c.currency_code),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -44,7 +44,7 @@ type CreateRegionFormProps = {
|
||||
const CreateRegionSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
currency_code: zod.string().min(2, "Select a currency"),
|
||||
includes_tax: zod.boolean(),
|
||||
automatic_taxes: zod.boolean(),
|
||||
countries: zod.array(zod.object({ code: zod.string(), name: zod.string() })),
|
||||
payment_providers: zod.array(zod.string()).min(1),
|
||||
})
|
||||
@@ -64,7 +64,7 @@ export const CreateRegionForm = ({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
currency_code: "",
|
||||
includes_tax: false,
|
||||
automatic_taxes: true,
|
||||
countries: [],
|
||||
payment_providers: [],
|
||||
},
|
||||
@@ -88,7 +88,7 @@ export const CreateRegionForm = ({
|
||||
countries: values.countries.map((c) => c.code),
|
||||
currency_code: values.currency_code,
|
||||
payment_providers: values.payment_providers,
|
||||
automatic_taxes: values.includes_tax,
|
||||
automatic_taxes: values.automatic_taxes,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ region }) => {
|
||||
@@ -277,14 +277,14 @@ export const CreateRegionForm = ({
|
||||
</div>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="includes_tax"
|
||||
name="automatic_taxes"
|
||||
render={({ field: { value, onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<Form.Label>
|
||||
{t("fields.taxInclusivePricing")}
|
||||
{t("fields.automaticTaxes")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch
|
||||
@@ -295,7 +295,7 @@ export const CreateRegionForm = ({
|
||||
</Form.Control>
|
||||
</div>
|
||||
<Form.Hint>
|
||||
{t("regions.taxInclusiveHint")}
|
||||
{t("regions.automaticTaxesHint")}
|
||||
</Form.Hint>
|
||||
<Form.ErrorMessage />
|
||||
</div>
|
||||
@@ -303,6 +303,7 @@ export const CreateRegionForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="bg-ui-border-base h-px w-full" />
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
|
||||
@@ -7,8 +7,8 @@ import { useStore } from "../../../hooks/api/store"
|
||||
export const RegionCreate = () => {
|
||||
const { store, isPending: isLoading, isError, error } = useStore()
|
||||
|
||||
const storeCurrencies = (store?.supported_currency_codes ?? []).map(
|
||||
(code) => currencies[code.toUpperCase()]
|
||||
const storeCurrencies = (store?.supported_currencies ?? []).map(
|
||||
(c) => currencies[c.currency_code.toUpperCase()]
|
||||
)
|
||||
const { payment_providers: paymentProviders = [] } = usePaymentProviders()
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ export const RegionEdit = () => {
|
||||
|
||||
const isLoading = isRegionLoading || isStoreLoading
|
||||
|
||||
const storeCurrencies = (store?.supported_currency_codes ?? []).map(
|
||||
(code) => currencies[code.toUpperCase()]
|
||||
const storeCurrencies = (store?.supported_currencies ?? []).map(
|
||||
(c) => currencies[c.currency_code.toUpperCase()]
|
||||
)
|
||||
const { payment_providers: paymentProviders = [] } = usePaymentProviders({
|
||||
limit: 999,
|
||||
|
||||
+14
-2
@@ -78,7 +78,8 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
const preSelectedRows = store.supported_currency_codes.map((c) => c)
|
||||
const preSelectedRows =
|
||||
store.supported_currencies?.map((c) => c.currency_code) ?? []
|
||||
|
||||
const columns = useColumns()
|
||||
|
||||
@@ -104,9 +105,20 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
new Set([...data.currencies, ...preSelectedRows])
|
||||
) as string[]
|
||||
|
||||
let defaultCurrency = store.supported_currencies?.find(
|
||||
(c) => c.is_default
|
||||
)?.currency_code
|
||||
|
||||
if (!currencies.includes(defaultCurrency ?? "")) {
|
||||
defaultCurrency = currencies?.[0]
|
||||
}
|
||||
|
||||
try {
|
||||
await mutateAsync({
|
||||
supported_currency_codes: currencies,
|
||||
supported_currencies: currencies.map((c) => ({
|
||||
currency_code: c,
|
||||
is_default: c === defaultCurrency,
|
||||
})),
|
||||
})
|
||||
toast.success(t("general.success"), {
|
||||
description: t("store.toast.currenciesUpdated"),
|
||||
|
||||
+17
-15
@@ -1,5 +1,5 @@
|
||||
import { Plus, Trash } from "@medusajs/icons"
|
||||
import { CurrencyDTO } from "@medusajs/types"
|
||||
import { CurrencyDTO, StoreCurrencyDTO } from "@medusajs/types"
|
||||
import {
|
||||
Checkbox,
|
||||
CommandBar,
|
||||
@@ -40,7 +40,7 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
error,
|
||||
} = useCurrencies(
|
||||
{
|
||||
code: store.supported_currency_codes,
|
||||
code: store.supported_currencies?.map((c) => c.currency_code),
|
||||
...searchParams,
|
||||
},
|
||||
{
|
||||
@@ -64,8 +64,9 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
pageSize: PAGE_SIZE,
|
||||
meta: {
|
||||
storeId: store.id,
|
||||
currencyCodes: store.supported_currency_codes,
|
||||
defaultCurrencyCode: store.default_currency_code,
|
||||
supportedCurrencies: store.supported_currencies,
|
||||
defaultCurrencyCode: store.supported_currencies?.find((c) => c.is_default)
|
||||
?.currency_code,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -91,9 +92,10 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
|
||||
try {
|
||||
await mutateAsync({
|
||||
supported_currency_codes: store.supported_currency_codes.filter(
|
||||
(c) => !ids.includes(c)
|
||||
),
|
||||
supported_currencies:
|
||||
store.supported_currencies?.filter(
|
||||
(c) => !ids.includes(c.currency_code)
|
||||
) ?? [],
|
||||
})
|
||||
setRowSelection({})
|
||||
|
||||
@@ -164,12 +166,12 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
const CurrencyActions = ({
|
||||
storeId,
|
||||
currency,
|
||||
currencyCodes,
|
||||
supportedCurrencies,
|
||||
defaultCurrencyCode,
|
||||
}: {
|
||||
storeId: string
|
||||
currency: CurrencyDTO
|
||||
currencyCodes: string[]
|
||||
supportedCurrencies: StoreCurrencyDTO[]
|
||||
defaultCurrencyCode: string
|
||||
}) => {
|
||||
const { mutateAsync } = useUpdateStore(storeId)
|
||||
@@ -195,8 +197,8 @@ const CurrencyActions = ({
|
||||
|
||||
try {
|
||||
await mutateAsync({
|
||||
supported_currency_codes: currencyCodes.filter(
|
||||
(c) => c !== currency.code
|
||||
supported_currencies: supportedCurrencies.filter(
|
||||
(c) => c.currency_code !== currency.code
|
||||
),
|
||||
})
|
||||
|
||||
@@ -269,9 +271,9 @@ const useColumns = () => {
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row, table }) => {
|
||||
const { currencyCodes, storeId, defaultCurrencyCode } = table.options
|
||||
.meta as {
|
||||
currencyCodes: string[]
|
||||
const { supportedCurrencies, storeId, defaultCurrencyCode } = table
|
||||
.options.meta as {
|
||||
supportedCurrencies: StoreCurrencyDTO[]
|
||||
storeId: string
|
||||
defaultCurrencyCode: string
|
||||
}
|
||||
@@ -280,7 +282,7 @@ const useColumns = () => {
|
||||
<CurrencyActions
|
||||
storeId={storeId}
|
||||
currency={row.original}
|
||||
currencyCodes={currencyCodes}
|
||||
supportedCurrencies={supportedCurrencies}
|
||||
defaultCurrencyCode={defaultCurrencyCode}
|
||||
/>
|
||||
)
|
||||
|
||||
+5
-3
@@ -16,6 +16,8 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
|
||||
enabled: !!store.default_region_id,
|
||||
})
|
||||
|
||||
const defaultCurrency = store.supported_currencies?.find((c) => c.is_default)
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
@@ -51,13 +53,13 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("store.defaultCurrency")}
|
||||
</Text>
|
||||
{store.default_currency ? (
|
||||
{defaultCurrency ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Badge size="2xsmall">
|
||||
{store.default_currency.code.toUpperCase()}
|
||||
{defaultCurrency.currency_code.toUpperCase()}
|
||||
</Badge>
|
||||
<Text size="small" leading="compact">
|
||||
{store.default_currency.name}
|
||||
{defaultCurrency.currency.name}
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
+18
-5
@@ -32,7 +32,9 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
||||
defaultValues: {
|
||||
name: store.name,
|
||||
default_region_id: store.default_region_id || undefined,
|
||||
default_currency_code: store.default_currency_code || undefined,
|
||||
default_currency_code:
|
||||
store.supported_currencies?.find((c) => c.is_default)?.currency_code ||
|
||||
undefined,
|
||||
},
|
||||
resolver: zodResolver(EditStoreSchema),
|
||||
})
|
||||
@@ -43,7 +45,15 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
try {
|
||||
await mutateAsync(values)
|
||||
const normalizedMutation = {
|
||||
...values,
|
||||
default_currency_code: undefined,
|
||||
supported_currencies: store.supported_currencies?.map((c) => ({
|
||||
...c,
|
||||
is_default: c.currency_code === values.default_currency_code,
|
||||
})),
|
||||
}
|
||||
await mutateAsync(normalizedMutation)
|
||||
|
||||
handleSuccess()
|
||||
|
||||
@@ -91,9 +101,12 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{store.supported_currency_codes.map((code) => (
|
||||
<Select.Item key={code} value={code}>
|
||||
{code.toUpperCase()}
|
||||
{store.supported_currencies?.map((currency) => (
|
||||
<Select.Item
|
||||
key={currency.currency_code}
|
||||
value={currency.currency_code}
|
||||
>
|
||||
{currency.currency_code.toUpperCase()}
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
StockLocationDTO,
|
||||
StoreDTO,
|
||||
UserDTO,
|
||||
HttpTypes,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { WorkflowExecutionDTO } from "../routes/workflow-executions/types"
|
||||
@@ -57,9 +56,7 @@ export type UserListRes = { users: UserDTO[] } & ListRes
|
||||
export type UserDeleteRes = DeleteRes
|
||||
|
||||
// Stores
|
||||
export type ExtendedStoreDTO = StoreDTO & {
|
||||
default_currency: CurrencyDTO | null
|
||||
}
|
||||
export type ExtendedStoreDTO = StoreDTO
|
||||
|
||||
export type StoreRes = { store: ExtendedStoreDTO }
|
||||
export type StoreListRes = { stores: ExtendedStoreDTO[] } & ListRes
|
||||
|
||||
Reference in New Issue
Block a user