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:
Stevche Radevski
2024-06-24 15:25:44 +00:00
committed by GitHub
parent 79d90fadc4
commit e8d6025374
45 changed files with 580 additions and 408 deletions
@@ -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",
@@ -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
@@ -22,7 +22,7 @@ export const CreateShippingOptionsPricesForm = ({
} = useStore()
const currencies = useMemo(
() => store?.supported_currency_codes || [],
() => store?.supported_currencies?.map((c) => c.currency_code) || [],
[store]
)
@@ -94,7 +94,7 @@ export function EditShippingOptionsPricingForm({
} = useStore()
const currencies = useMemo(
() => store?.supported_currency_codes || [],
() => store?.supported_currencies?.map((c) => c.currency_code) || [],
[store]
)
@@ -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,
@@ -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,
@@ -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,
})
@@ -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),
}
}
@@ -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,
@@ -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"),
@@ -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}
/>
)
@@ -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>
) : (
@@ -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