Feat: Add tax inclusivity to admin (#8003)
* feat: Add price preference to sdk * feat: Plug tax inclusivity settings for region in UI * feat: Add price inclusivity indicator to variant and shipping price table columns * fix: Rename price title to correct variable name * feat: Add support for tax inclusive crud on region * fix: Use the region endpoint for updating tax inclusivity * chore: Factor out price columns from hooks
This commit is contained in:
@@ -8,41 +8,39 @@ jest.setTimeout(30000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
beforeAll(() => {})
|
||||
let region1
|
||||
let region2
|
||||
|
||||
beforeEach(async () => {
|
||||
const container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
|
||||
region1 = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "United Kingdom",
|
||||
currency_code: "gbp",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
region2 = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "United States",
|
||||
currency_code: "usd",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
})
|
||||
|
||||
// BREAKING: There is no more `tax_rate` field on the region.
|
||||
// BREAKING: There are no more fulfillment providers list on a region.
|
||||
describe("GET /admin/regions", () => {
|
||||
let region1
|
||||
let region2
|
||||
|
||||
beforeEach(async () => {
|
||||
region1 = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "United Kingdom",
|
||||
currency_code: "gbp",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
region2 = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "United States",
|
||||
currency_code: "usd",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
})
|
||||
|
||||
it("should list regions", async () => {
|
||||
const response = await api.get("/admin/regions", adminHeaders)
|
||||
|
||||
@@ -79,20 +77,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("GET /admin/regions/:id", () => {
|
||||
let region1
|
||||
beforeEach(async () => {
|
||||
region1 = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "United Kingdom",
|
||||
currency_code: "gbp",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
})
|
||||
|
||||
it("should retrieve the region from ID", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/regions/${region1.id}`,
|
||||
@@ -121,18 +105,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("POST /admin/regions", () => {
|
||||
beforeEach(async () => {
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "United States",
|
||||
currency_code: "usd",
|
||||
countries: ["us"],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a region", async () => {
|
||||
const region = (
|
||||
await api.post(
|
||||
@@ -153,6 +125,33 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a region with tax inclusivity setting", async () => {
|
||||
const region = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "Test",
|
||||
currency_code: "usd",
|
||||
// BREAKING: The property used to be called `includes_tax`
|
||||
is_tax_inclusive: true,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
const response = await api.get(`/admin/price-preferences`, adminHeaders)
|
||||
|
||||
expect(response.data.price_preferences).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
attribute: "region_id",
|
||||
value: region.id,
|
||||
is_tax_inclusive: true,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should fails to create when countries exists in different region", async () => {
|
||||
try {
|
||||
await api.post(
|
||||
@@ -173,102 +172,64 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Migrate when tax_inclusive_pricing is implemented
|
||||
// describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/regions", () => {
|
||||
// let medusaProcess
|
||||
// let dbConnection
|
||||
describe("POST /admin/regions/:id", () => {
|
||||
it("should update a region", async () => {
|
||||
const region = (
|
||||
await api.post(
|
||||
`/admin/regions/${region1.id}`,
|
||||
{
|
||||
name: "New test",
|
||||
currency_code: "eur",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
// beforeAll(async () => {
|
||||
// const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
// const [process, connection] = await startServerWithEnvironment({
|
||||
// cwd,
|
||||
// env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
|
||||
// })
|
||||
// dbConnection = connection
|
||||
// medusaProcess = process
|
||||
// })
|
||||
expect(region).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New test",
|
||||
currency_code: "eur",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// afterAll(async () => {
|
||||
// const db = useDb()
|
||||
// await db.shutdown()
|
||||
it("should update a region with tax inclusivity setting", async () => {
|
||||
const beforeResponse = await api.get(
|
||||
`/admin/price-preferences`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(beforeResponse.data.price_preferences).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
attribute: "region_id",
|
||||
value: region1.id,
|
||||
is_tax_inclusive: false,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
// medusaProcess.kill()
|
||||
// })
|
||||
await api.post(
|
||||
`/admin/regions/${region1.id}`,
|
||||
{
|
||||
is_tax_inclusive: true,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
// describe("POST /admin/regions/:id", () => {
|
||||
// const region1TaxInclusiveId = "region-1-tax-inclusive"
|
||||
|
||||
// beforeEach(async () => {
|
||||
// try {
|
||||
// await adminSeeder(dbConnection)
|
||||
// await simpleRegionFactory(dbConnection, {
|
||||
// id: region1TaxInclusiveId,
|
||||
// countries: ["fr"],
|
||||
// })
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// throw err
|
||||
// }
|
||||
// })
|
||||
|
||||
// afterEach(async () => {
|
||||
// const db = useDb()
|
||||
// await db.teardown()
|
||||
// })
|
||||
|
||||
// it("should allow to create a region that includes tax", async function () {
|
||||
// const api = useApi()
|
||||
|
||||
// const payload = {
|
||||
// name: "region-including-taxes",
|
||||
// currency_code: "usd",
|
||||
// tax_rate: 0,
|
||||
// payment_providers: ["test-pay"],
|
||||
// fulfillment_providers: ["test-ful"],
|
||||
// countries: ["us"],
|
||||
// includes_tax: true,
|
||||
// }
|
||||
|
||||
// const response = await api
|
||||
// .post(`/admin/regions`, payload, adminReqConfig)
|
||||
// .catch((err) => {
|
||||
// console.log(err)
|
||||
// })
|
||||
|
||||
// expect(response.data.region).toEqual(
|
||||
// expect.objectContaining({
|
||||
// id: expect.any(String),
|
||||
// includes_tax: true,
|
||||
// name: "region-including-taxes",
|
||||
// })
|
||||
// )
|
||||
// })
|
||||
|
||||
// it("should allow to update a region that includes tax", async function () {
|
||||
// const api = useApi()
|
||||
// let response = await api
|
||||
// .get(`/admin/regions/${region1TaxInclusiveId}`, adminReqConfig)
|
||||
// .catch((err) => {
|
||||
// console.log(err)
|
||||
// })
|
||||
|
||||
// expect(response.data.region.includes_tax).toBe(false)
|
||||
|
||||
// response = await api
|
||||
// .post(
|
||||
// `/admin/regions/${region1TaxInclusiveId}`,
|
||||
// {
|
||||
// includes_tax: true,
|
||||
// },
|
||||
// adminReqConfig
|
||||
// )
|
||||
// .catch((err) => {
|
||||
// console.log(err)
|
||||
// })
|
||||
|
||||
// expect(response.data.region.includes_tax).toBe(true)
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
const afterResponse = await api.get(
|
||||
`/admin/price-preferences`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(afterResponse.data.price_preferences).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
attribute: "region_id",
|
||||
value: region1.id,
|
||||
is_tax_inclusive: true,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BuildingTax } from "@medusajs/icons"
|
||||
import { Tooltip } from "@medusajs/ui"
|
||||
import { Tooltip, clx } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type IncludesTaxTooltipProps = {
|
||||
@@ -11,13 +11,15 @@ export const IncludesTaxTooltip = ({
|
||||
}: IncludesTaxTooltipProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!includesTax) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={t("general.includesTaxTooltip")}>
|
||||
<BuildingTax className="text-ui-fg-muted" />
|
||||
<Tooltip
|
||||
content={
|
||||
includesTax
|
||||
? t("general.includesTaxTooltip")
|
||||
: t("general.excludesTaxTooltip")
|
||||
}
|
||||
>
|
||||
<BuildingTax className={clx({ "text-ui-fg-muted": !includesTax })} />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { DataGridCurrencyCell } from "../data-grid-cells/data-grid-currency-cell"
|
||||
import { createDataGridHelper } from "../utils"
|
||||
import { IncludesTaxTooltip } from "../../../components/common/tax-badge/tax-badge"
|
||||
import { TFunction } from "i18next"
|
||||
import { CellContext } from "@tanstack/react-table"
|
||||
import { DataGridReadOnlyCell } from "../data-grid-cells/data-grid-readonly-cell"
|
||||
|
||||
const columnHelper = createDataGridHelper<string | HttpTypes.AdminRegion>()
|
||||
|
||||
export const getPriceColumns = ({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
isReadyOnly,
|
||||
getFieldName,
|
||||
t,
|
||||
}: {
|
||||
currencies?: string[]
|
||||
regions?: HttpTypes.AdminRegion[]
|
||||
pricePreferences?: HttpTypes.AdminPricePreference[]
|
||||
isReadyOnly?: (
|
||||
context: CellContext<string | HttpTypes.AdminRegion, unknown>
|
||||
) => boolean
|
||||
getFieldName: (
|
||||
context: CellContext<string | HttpTypes.AdminRegion, unknown>,
|
||||
value: string
|
||||
) => string
|
||||
t: TFunction
|
||||
}) => {
|
||||
return [
|
||||
...(currencies?.map((currency) => {
|
||||
const preference = pricePreferences?.find(
|
||||
(p) => p.attribute === "currency_code" && p.value === currency
|
||||
)
|
||||
|
||||
return columnHelper.column({
|
||||
id: `currency_prices.${currency}`,
|
||||
name: t("fields.priceTemplate", {
|
||||
regionOrCurrency: currency.toUpperCase(),
|
||||
}),
|
||||
header: () => (
|
||||
<div className="flex w-full items-center justify-between gap-3">
|
||||
{t("fields.priceTemplate", {
|
||||
regionOrCurrency: currency.toUpperCase(),
|
||||
})}
|
||||
<IncludesTaxTooltip includesTax={preference?.is_tax_inclusive} />
|
||||
</div>
|
||||
),
|
||||
cell: (context) => {
|
||||
if (isReadyOnly?.(context)) {
|
||||
return <DataGridReadOnlyCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={currency}
|
||||
context={context}
|
||||
field={getFieldName(context, currency)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}) ?? []),
|
||||
...(regions?.map((region) => {
|
||||
const preference = pricePreferences?.find(
|
||||
(p) => p.attribute === "region_id" && p.value === region.id
|
||||
)
|
||||
|
||||
return columnHelper.column({
|
||||
id: `region_prices.${region.id}`,
|
||||
name: t("fields.priceTemplate", {
|
||||
regionOrCurrency: region.name,
|
||||
}),
|
||||
header: () => (
|
||||
<div className="flex w-full items-center justify-between gap-3">
|
||||
{t("fields.priceTemplate", {
|
||||
regionOrCurrency: region.name,
|
||||
})}
|
||||
<IncludesTaxTooltip includesTax={preference?.is_tax_inclusive} />
|
||||
</div>
|
||||
),
|
||||
cell: (context) => {
|
||||
if (isReadyOnly?.(context)) {
|
||||
return <DataGridReadOnlyCell />
|
||||
}
|
||||
|
||||
const currency = currencies?.find((c) => c === region.currency_code)
|
||||
if (!currency) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={region.currency_code}
|
||||
context={context}
|
||||
field={getFieldName(context, region.id)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}) ?? []),
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
|
||||
const PRICE_PREFERENCES_QUERY_KEY = "price-preferences" as const
|
||||
export const pricePreferencesQueryKeys = queryKeysFactory(
|
||||
PRICE_PREFERENCES_QUERY_KEY
|
||||
)
|
||||
|
||||
export const usePricePreference = (
|
||||
id: string,
|
||||
query?: HttpTypes.AdminPricePreferenceParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminPricePreferenceResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminPricePreferenceResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.pricePreference.retrieve(id, query),
|
||||
queryKey: pricePreferencesQueryKeys.detail(id),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const usePricePreferences = (
|
||||
query?: HttpTypes.AdminPricePreferenceListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminPricePreferenceListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminPricePreferenceListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.pricePreference.list(query),
|
||||
queryKey: pricePreferencesQueryKeys.list(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useUpsertPricePreference = (
|
||||
id?: string | undefined,
|
||||
query?: HttpTypes.AdminPricePreferenceParams,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminPricePreferenceResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminUpdatePricePreference | HttpTypes.AdminCreatePricePreference
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => {
|
||||
if (id) {
|
||||
return sdk.admin.pricePreference.update(id, payload, query)
|
||||
}
|
||||
return sdk.admin.pricePreference.create(payload, query)
|
||||
},
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.list(),
|
||||
})
|
||||
if (id) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.detail(id),
|
||||
})
|
||||
}
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeletePricePreference = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminPricePreferenceDeleteResponse,
|
||||
FetchError,
|
||||
void
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: () => sdk.admin.pricePreference.delete(id),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.list(),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
@@ -49,7 +49,8 @@
|
||||
"noRecordsMessage": "There are no records to show",
|
||||
"unsavedChangesTitle": "Are you sure you want to leave this form?",
|
||||
"unsavedChangesDescription": "You have unsaved changes that will be lost if you exit this form.",
|
||||
"includesTaxTooltip": "Enter the total amount including tax. The net amount excluding tax will be automatically calculated and saved."
|
||||
"includesTaxTooltip": "Prices in this column are tax inclusive.",
|
||||
"excludesTaxTooltip": "Prices in this column are tax exclusive."
|
||||
},
|
||||
"validation": {
|
||||
"mustBeInt": "The value must be a whole number.",
|
||||
@@ -2010,7 +2011,7 @@
|
||||
"issuedDate": "Issued date",
|
||||
"expiryDate": "Expiry date",
|
||||
"price": "Price",
|
||||
"priceTemplate": "Price {{regionOrCountry}}",
|
||||
"priceTemplate": "Price {{regionOrCurrency}}",
|
||||
"height": "Height",
|
||||
"width": "Width",
|
||||
"length": "Length",
|
||||
|
||||
@@ -1,63 +1,32 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataGridCurrencyCell } from "../../../../components/data-grid/data-grid-cells/data-grid-currency-cell"
|
||||
import { createDataGridHelper } from "../../../../components/data-grid/utils"
|
||||
|
||||
const columnHelper = createDataGridHelper<string | HttpTypes.AdminRegion>()
|
||||
import { getPriceColumns } from "../../../../components/data-grid/data-grid-columns/price-columns"
|
||||
|
||||
export const useShippingOptionPriceColumns = ({
|
||||
currencies = [],
|
||||
regions = [],
|
||||
pricePreferences = [],
|
||||
}: {
|
||||
currencies?: string[]
|
||||
regions?: HttpTypes.AdminRegion[]
|
||||
pricePreferences?: HttpTypes.AdminPricePreference[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
...currencies.map((currency) => {
|
||||
return columnHelper.column({
|
||||
id: `currency_prices.${currency}`,
|
||||
name: t("fields.priceTemplate", {
|
||||
regionOrCountry: currency.toUpperCase(),
|
||||
}),
|
||||
header: t("fields.priceTemplate", {
|
||||
regionOrCountry: currency.toUpperCase(),
|
||||
}),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={currency}
|
||||
context={context}
|
||||
field={`currency_prices.${currency}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}),
|
||||
...regions.map((region) => {
|
||||
return columnHelper.column({
|
||||
id: `region_prices.${region.id}`,
|
||||
name: t("fields.priceTemplate", {
|
||||
regionOrCountry: region.name,
|
||||
}),
|
||||
header: t("fields.priceTemplate", {
|
||||
regionOrCountry: region.name,
|
||||
}),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={region.currency_code}
|
||||
context={context}
|
||||
field={`region_prices.${region.id}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}),
|
||||
] as ColumnDef<(string | HttpTypes.AdminRegion)[]>[]
|
||||
}, [t, currencies, regions])
|
||||
return getPriceColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
getFieldName: (context, value) => {
|
||||
if (context.column.id.startsWith("currency_prices")) {
|
||||
return `currency_prices.${value}`
|
||||
}
|
||||
|
||||
return `region_prices.${value}`
|
||||
},
|
||||
t,
|
||||
})
|
||||
}, [t, currencies, regions, pricePreferences])
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useRegions } from "../../../../../hooks/api/regions"
|
||||
import { useStore } from "../../../../../hooks/api/store"
|
||||
import { useShippingOptionPriceColumns } from "../../../common/hooks/use-shipping-option-price-columns"
|
||||
import { CreateShippingOptionSchema } from "./schema"
|
||||
import { usePricePreferences } from "../../../../../hooks/api/price-preferences"
|
||||
|
||||
type PricingPricesFormProps = {
|
||||
form: UseFormReturn<CreateShippingOptionSchema>
|
||||
@@ -36,9 +37,12 @@ export const CreateShippingOptionsPricesForm = ({
|
||||
limit: 999,
|
||||
})
|
||||
|
||||
const { price_preferences: pricePreferences } = usePricePreferences({})
|
||||
|
||||
const columns = useShippingOptionPriceColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
const initializing = isStoreLoading || !store || isRegionsLoading || !regions
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-opti
|
||||
import { useStore } from "../../../../../hooks/api/store"
|
||||
import { castNumber } from "../../../../../lib/cast-number"
|
||||
import { useShippingOptionPriceColumns } from "../../../common/hooks/use-shipping-option-price-columns"
|
||||
import { usePricePreferences } from "../../../../../hooks/api/price-preferences"
|
||||
|
||||
const getInitialCurrencyPrices = (
|
||||
prices: HttpTypes.AdminShippingOptionPrice[]
|
||||
@@ -108,9 +109,12 @@ export function EditShippingOptionsPricingForm({
|
||||
limit: 999,
|
||||
})
|
||||
|
||||
const { price_preferences: pricePreferences } = usePricePreferences({})
|
||||
|
||||
const columns = useShippingOptionPriceColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
const data = useMemo(
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useRegions } from "../../../../hooks/api/regions"
|
||||
import { useStore } from "../../../../hooks/api/store"
|
||||
import { usePricePreferences } from "../../../../hooks/api/price-preferences"
|
||||
|
||||
type UsePriceListCurrencyDataReturn =
|
||||
| { isReady: false; currencies: undefined; regions: undefined }
|
||||
| {
|
||||
isReady: false
|
||||
currencies: undefined
|
||||
regions: undefined
|
||||
pricePreferences: undefined
|
||||
}
|
||||
| {
|
||||
isReady: true
|
||||
currencies: HttpTypes.AdminStoreCurrency[]
|
||||
regions: HttpTypes.AdminRegion[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
export const usePriceListCurrencyData = (): UsePriceListCurrencyDataReturn => {
|
||||
@@ -32,8 +39,20 @@ export const usePriceListCurrencyData = (): UsePriceListCurrencyDataReturn => {
|
||||
limit: 999,
|
||||
})
|
||||
|
||||
const {
|
||||
price_preferences: pricePreferences,
|
||||
isPending: isPreferencesPending,
|
||||
isError: isPreferencesError,
|
||||
error: preferencesError,
|
||||
} = usePricePreferences({})
|
||||
|
||||
const isReady =
|
||||
!!currencies && !!regions && !isStorePending && !isRegionsPending
|
||||
!!currencies &&
|
||||
!!regions &&
|
||||
!!pricePreferences &&
|
||||
!isStorePending &&
|
||||
!isRegionsPending &&
|
||||
!isPreferencesPending
|
||||
|
||||
if (isRegionsError) {
|
||||
throw regionsError
|
||||
@@ -43,9 +62,18 @@ export const usePriceListCurrencyData = (): UsePriceListCurrencyDataReturn => {
|
||||
throw storeError
|
||||
}
|
||||
|
||||
if (!isReady) {
|
||||
return { regions: undefined, currencies: undefined, isReady: false }
|
||||
if (isPreferencesError) {
|
||||
throw preferencesError
|
||||
}
|
||||
|
||||
return { regions, currencies, isReady }
|
||||
if (!isReady) {
|
||||
return {
|
||||
regions: undefined,
|
||||
currencies: undefined,
|
||||
pricePreferences: undefined,
|
||||
isReady: false,
|
||||
}
|
||||
}
|
||||
|
||||
return { regions, currencies, pricePreferences, isReady }
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Thumbnail } from "../../../../components/common/thumbnail"
|
||||
import { DataGridCurrencyCell } from "../../../../components/data-grid/data-grid-cells/data-grid-currency-cell"
|
||||
import { DataGridReadOnlyCell } from "../../../../components/data-grid/data-grid-cells/data-grid-readonly-cell"
|
||||
import { createDataGridHelper } from "../../../../components/data-grid/utils"
|
||||
import { isProductRow } from "../utils"
|
||||
import { getPriceColumns } from "../../../../components/data-grid/data-grid-columns/price-columns"
|
||||
|
||||
const columnHelper = createDataGridHelper<
|
||||
HttpTypes.AdminProduct | HttpTypes.AdminProductVariant
|
||||
@@ -16,9 +16,11 @@ const columnHelper = createDataGridHelper<
|
||||
export const usePriceListGridColumns = ({
|
||||
currencies = [],
|
||||
regions = [],
|
||||
pricePreferences = [],
|
||||
}: {
|
||||
currencies?: StoreCurrencyDTO[]
|
||||
regions?: HttpTypes.AdminRegion[]
|
||||
pricePreferences?: HttpTypes.AdminPricePreference[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -53,52 +55,25 @@ export const usePriceListGridColumns = ({
|
||||
},
|
||||
disableHiding: true,
|
||||
}),
|
||||
...currencies.map((currency) => {
|
||||
return columnHelper.column({
|
||||
id: `currency-price-${currency.currency_code}`,
|
||||
name: `Price ${currency.currency_code.toUpperCase()}`,
|
||||
header: `Price ${currency.currency_code.toUpperCase()}`,
|
||||
cell: (context) => {
|
||||
const entity = context.row.original
|
||||
|
||||
if (isProductRow(entity)) {
|
||||
return <DataGridReadOnlyCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
context={context}
|
||||
code={currency.currency_code}
|
||||
field={`products.${entity.product_id}.variants.${entity.id}.currency_prices.${currency.currency_code}.amount`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}),
|
||||
...regions.map((region) => {
|
||||
return columnHelper.column({
|
||||
id: `region-price-${region.id}`,
|
||||
name: `Price ${region.name}`,
|
||||
header: `Price ${region.name}`,
|
||||
cell: (context) => {
|
||||
const entity = context.row.original
|
||||
|
||||
if (isProductRow(entity)) {
|
||||
return <DataGridReadOnlyCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
context={context}
|
||||
code={region.currency_code}
|
||||
field={`products.${entity.product_id}.variants.${entity.id}.region_prices.${region.id}.amount`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
...getPriceColumns({
|
||||
currencies: currencies.map((c) => c.currency_code),
|
||||
regions,
|
||||
pricePreferences,
|
||||
isReadyOnly: (context) => {
|
||||
const entity = context.row.original
|
||||
return isProductRow(entity)
|
||||
},
|
||||
getFieldName: (context, value) => {
|
||||
const entity = context.row.original as any
|
||||
if (context.column.id.startsWith("currency_prices")) {
|
||||
return `products.${entity.product_id}.variants.${entity.id}.currency_prices.${value}.amount`
|
||||
}
|
||||
return `products.${entity.product_id}.variants.${entity.id}.region_prices.${value}.amount`
|
||||
},
|
||||
t,
|
||||
}),
|
||||
]
|
||||
}, [t, currencies, regions])
|
||||
}, [t, currencies, regions, pricePreferences])
|
||||
|
||||
return colDefs
|
||||
}
|
||||
|
||||
@@ -44,11 +44,13 @@ const initialTabState: TabState = {
|
||||
type PriceListCreateFormProps = {
|
||||
regions: HttpTypes.AdminRegion[]
|
||||
currencies: HttpTypes.AdminStoreCurrency[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
export const PriceListCreateForm = ({
|
||||
regions,
|
||||
currencies,
|
||||
pricePreferences,
|
||||
}: PriceListCreateFormProps) => {
|
||||
const [tab, setTab] = useState<Tab>(Tab.DETAIL)
|
||||
const [tabState, setTabState] = useState<TabState>(initialTabState)
|
||||
@@ -117,10 +119,13 @@ export const PriceListCreateForm = ({
|
||||
) => {
|
||||
form.clearErrors(fields)
|
||||
|
||||
const values = fields.reduce((acc, key) => {
|
||||
acc[key] = form.getValues(key)
|
||||
return acc
|
||||
}, {} as Record<string, unknown>)
|
||||
const values = fields.reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = form.getValues(key)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
)
|
||||
|
||||
const validationResult = schema.safeParse(values)
|
||||
|
||||
@@ -295,6 +300,7 @@ export const PriceListCreateForm = ({
|
||||
form={form}
|
||||
regions={regions}
|
||||
currencies={currencies}
|
||||
pricePreferences={pricePreferences}
|
||||
/>
|
||||
</ProgressTabs.Content>
|
||||
</RouteFocusModal.Body>
|
||||
|
||||
@@ -12,12 +12,14 @@ type PriceListPricesFormProps = {
|
||||
form: UseFormReturn<PricingCreateSchemaType>
|
||||
currencies: HttpTypes.AdminStoreCurrency[]
|
||||
regions: HttpTypes.AdminRegion[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
export const PriceListPricesForm = ({
|
||||
form,
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
}: PriceListPricesFormProps) => {
|
||||
const ids = useWatch({
|
||||
control: form.control,
|
||||
@@ -63,6 +65,7 @@ export const PriceListPricesForm = ({
|
||||
const columns = usePriceListGridColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
|
||||
@@ -3,12 +3,17 @@ import { usePriceListCurrencyData } from "../common/hooks/use-price-list-currenc
|
||||
import { PriceListCreateForm } from "./components/price-list-create-form"
|
||||
|
||||
export const PriceListCreate = () => {
|
||||
const { isReady, regions, currencies } = usePriceListCurrencyData()
|
||||
const { isReady, regions, currencies, pricePreferences } =
|
||||
usePriceListCurrencyData()
|
||||
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
{isReady && (
|
||||
<PriceListCreateForm regions={regions} currencies={currencies} />
|
||||
<PriceListCreateForm
|
||||
regions={regions}
|
||||
currencies={currencies}
|
||||
pricePreferences={pricePreferences}
|
||||
/>
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ type PriceListPricesAddFormProps = {
|
||||
priceList: HttpTypes.AdminPriceList
|
||||
currencies: HttpTypes.AdminStoreCurrency[]
|
||||
regions: HttpTypes.AdminRegion[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
enum Tab {
|
||||
@@ -45,6 +46,7 @@ export const PriceListPricesAddForm = ({
|
||||
priceList,
|
||||
regions,
|
||||
currencies,
|
||||
pricePreferences,
|
||||
}: PriceListPricesAddFormProps) => {
|
||||
const [tab, setTab] = useState<Tab>(Tab.PRODUCT)
|
||||
const [tabState, setTabState] = useState<TabState>(initialTabState)
|
||||
@@ -87,10 +89,13 @@ export const PriceListPricesAddForm = ({
|
||||
) => {
|
||||
form.clearErrors(fields)
|
||||
|
||||
const values = fields.reduce((acc, key) => {
|
||||
acc[key] = form.getValues(key)
|
||||
return acc
|
||||
}, {} as Record<string, unknown>)
|
||||
const values = fields.reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = form.getValues(key)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
)
|
||||
|
||||
const validationResult = schema.safeParse(values)
|
||||
|
||||
@@ -236,6 +241,7 @@ export const PriceListPricesAddForm = ({
|
||||
form={form}
|
||||
regions={regions}
|
||||
currencies={currencies}
|
||||
pricePreferences={pricePreferences}
|
||||
/>
|
||||
</ProgressTabs.Content>
|
||||
</RouteFocusModal.Body>
|
||||
|
||||
@@ -13,12 +13,14 @@ type PriceListPricesAddPricesFormProps = {
|
||||
form: UseFormReturn<PriceListPricesAddSchema>
|
||||
currencies: HttpTypes.AdminStoreCurrency[]
|
||||
regions: HttpTypes.AdminRegion[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
export const PriceListPricesAddPricesForm = ({
|
||||
form,
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
}: PriceListPricesAddPricesFormProps) => {
|
||||
const ids = useWatch({
|
||||
control: form.control,
|
||||
@@ -64,6 +66,7 @@ export const PriceListPricesAddPricesForm = ({
|
||||
const columns = usePriceListGridColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
|
||||
@@ -8,7 +8,8 @@ export const PriceListProductsAdd = () => {
|
||||
const { id } = useParams<{ id: string }>()
|
||||
|
||||
const { price_list, isPending, isError, error } = usePriceList(id!)
|
||||
const { currencies, regions, isReady } = usePriceListCurrencyData()
|
||||
const { currencies, regions, pricePreferences, isReady } =
|
||||
usePriceListCurrencyData()
|
||||
|
||||
const ready = isReady && !isPending && !!price_list
|
||||
|
||||
@@ -23,6 +24,7 @@ export const PriceListProductsAdd = () => {
|
||||
priceList={price_list}
|
||||
currencies={currencies}
|
||||
regions={regions}
|
||||
pricePreferences={pricePreferences}
|
||||
/>
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
|
||||
@@ -25,6 +25,7 @@ type PriceListPricesEditFormProps = {
|
||||
products: HttpTypes.AdminProduct[]
|
||||
regions: HttpTypes.AdminRegion[]
|
||||
currencies: HttpTypes.AdminStoreCurrency[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
const PricingProductPricesSchema = z.object({
|
||||
@@ -36,6 +37,7 @@ export const PriceListPricesEditForm = ({
|
||||
products,
|
||||
regions,
|
||||
currencies,
|
||||
pricePreferences,
|
||||
}: PriceListPricesEditFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
@@ -79,7 +81,11 @@ export const PriceListPricesEditForm = ({
|
||||
)
|
||||
})
|
||||
|
||||
const columns = usePriceListGridColumns({ currencies, regions })
|
||||
const columns = usePriceListGridColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
@@ -178,10 +184,13 @@ function convertToPriceArray(
|
||||
) {
|
||||
const prices: PriceObject[] = []
|
||||
|
||||
const regionCurrencyMap = regions.reduce((map, region) => {
|
||||
map[region.id] = region.currency_code
|
||||
return map
|
||||
}, {} as Record<string, string>)
|
||||
const regionCurrencyMap = regions.reduce(
|
||||
(map, region) => {
|
||||
map[region.id] = region.currency_code
|
||||
return map
|
||||
},
|
||||
{} as Record<string, string>
|
||||
)
|
||||
|
||||
for (const [_productId, product] of Object.entries(data || {})) {
|
||||
const { variants } = product || {}
|
||||
@@ -233,15 +242,21 @@ function comparePrices(initialPrices: PriceObject[], newPrices: PriceObject[]) {
|
||||
const pricesToCreate: HttpTypes.AdminCreatePriceListPrice[] = []
|
||||
const pricesToDelete: string[] = []
|
||||
|
||||
const initialPriceMap = initialPrices.reduce((map, price) => {
|
||||
map[createMapKey(price)] = price
|
||||
return map
|
||||
}, {} as Record<string, (typeof initialPrices)[0]>)
|
||||
const initialPriceMap = initialPrices.reduce(
|
||||
(map, price) => {
|
||||
map[createMapKey(price)] = price
|
||||
return map
|
||||
},
|
||||
{} as Record<string, (typeof initialPrices)[0]>
|
||||
)
|
||||
|
||||
const newPriceMap = newPrices.reduce((map, price) => {
|
||||
map[createMapKey(price)] = price
|
||||
return map
|
||||
}, {} as Record<string, (typeof newPrices)[0]>)
|
||||
const newPriceMap = newPrices.reduce(
|
||||
(map, price) => {
|
||||
map[createMapKey(price)] = price
|
||||
return map
|
||||
},
|
||||
{} as Record<string, (typeof newPrices)[0]>
|
||||
)
|
||||
|
||||
const keys = new Set([
|
||||
...Object.keys(initialPriceMap),
|
||||
|
||||
@@ -25,7 +25,8 @@ export const PriceListPricesEdit = () => {
|
||||
fields: "title,thumbnail,*variants",
|
||||
})
|
||||
|
||||
const { isReady, regions, currencies } = usePriceListCurrencyData()
|
||||
const { isReady, regions, currencies, pricePreferences } =
|
||||
usePriceListCurrencyData()
|
||||
|
||||
const ready =
|
||||
!isLoading && !!price_list && !isProductsLoading && !!products && isReady
|
||||
@@ -46,6 +47,7 @@ export const PriceListPricesEdit = () => {
|
||||
products={products}
|
||||
regions={regions}
|
||||
currencies={currencies}
|
||||
pricePreferences={pricePreferences}
|
||||
/>
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { CurrencyDTO, HttpTypes, RegionDTO } from "@medusajs/types"
|
||||
import { CurrencyDTO, HttpTypes } from "@medusajs/types"
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataGrid } from "../../../components/grid/data-grid"
|
||||
import { CurrencyCell } from "../../../components/grid/grid-cells/common/currency-cell"
|
||||
import { ReadonlyCell } from "../../../components/grid/grid-cells/common/readonly-cell"
|
||||
import { DataGridMeta } from "../../../components/grid/types"
|
||||
import { useCurrencies } from "../../../hooks/api/currencies"
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
import { ProductCreateSchema } from "../product-create/constants"
|
||||
import { useRegions } from "../../../hooks/api/regions.tsx"
|
||||
import { usePricePreferences } from "../../../hooks/api/price-preferences.tsx"
|
||||
import { getPriceColumns } from "../../../components/data-grid/data-grid-columns/price-columns.tsx"
|
||||
import { DataGridRoot } from "../../../components/data-grid/data-grid-root/data-grid-root.tsx"
|
||||
|
||||
type VariantPricingFormProps = {
|
||||
form: UseFormReturn<ProductCreateSchema>
|
||||
@@ -30,9 +30,12 @@ export const VariantPricingForm = ({ form }: VariantPricingFormProps) => {
|
||||
|
||||
const { regions } = useRegions({ limit: 9999 })
|
||||
|
||||
const { price_preferences: pricePreferences } = usePricePreferences({})
|
||||
|
||||
const columns = useVariantPriceGridColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
const variants = useWatch({
|
||||
@@ -42,7 +45,7 @@ export const VariantPricingForm = ({ form }: VariantPricingFormProps) => {
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col divide-y overflow-hidden">
|
||||
<DataGrid
|
||||
<DataGridRoot
|
||||
columns={columns}
|
||||
data={variants}
|
||||
isLoading={isStoreLoading || isCurrenciesLoading}
|
||||
@@ -57,9 +60,11 @@ const columnHelper = createColumnHelper<HttpTypes.AdminProductVariant>()
|
||||
export const useVariantPriceGridColumns = ({
|
||||
currencies = [],
|
||||
regions = [],
|
||||
pricePreferences = [],
|
||||
}: {
|
||||
currencies?: CurrencyDTO[]
|
||||
regions?: RegionDTO[]
|
||||
regions?: HttpTypes.AdminRegion[]
|
||||
pricePreferences?: HttpTypes.AdminPricePreference[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -79,43 +84,20 @@ export const useVariantPriceGridColumns = ({
|
||||
)
|
||||
},
|
||||
}),
|
||||
...currencies.map((currency) => {
|
||||
return columnHelper.display({
|
||||
header: `Price ${currency.code.toUpperCase()}`,
|
||||
cell: ({ row, table }) => {
|
||||
return (
|
||||
<CurrencyCell
|
||||
currency={currency}
|
||||
meta={table.options.meta as DataGridMeta}
|
||||
field={`variants.${row.index}.prices.${currency.code}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}),
|
||||
...regions.map((region) => {
|
||||
return columnHelper.display({
|
||||
header: `Price ${region.name}`,
|
||||
cell: ({ row, table }) => {
|
||||
const currency = currencies.find(
|
||||
(c) => c.code === region.currency_code
|
||||
)
|
||||
|
||||
if (!currency) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<CurrencyCell
|
||||
currency={currency}
|
||||
meta={table.options.meta as DataGridMeta}
|
||||
field={`variants.${row.index}.prices.${region.id}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
...getPriceColumns({
|
||||
currencies: currencies.map((c) => c.code),
|
||||
regions,
|
||||
pricePreferences,
|
||||
getFieldName: (context, value) => {
|
||||
if (context.column.id.startsWith("currency_prices")) {
|
||||
return `variants.${context.row.index}.prices.${value}`
|
||||
}
|
||||
return `variants.${context.row.index}.prices.${value}`
|
||||
},
|
||||
t,
|
||||
}),
|
||||
]
|
||||
}, [t, currencies, regions])
|
||||
}, [t, currencies, regions, pricePreferences])
|
||||
|
||||
return colDefs
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HttpTypes, RegionDTO } from "@medusajs/types"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useMemo } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
@@ -8,9 +8,10 @@ import { DataGridRoot } from "../../../../../components/data-grid/data-grid-root
|
||||
import { DataGridReadOnlyCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-readonly-cell"
|
||||
import { DataGridTextCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-text-cell"
|
||||
import { createDataGridHelper } from "../../../../../components/data-grid/utils"
|
||||
import { DataGridCurrencyCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-currency-cell"
|
||||
import { DataGridBooleanCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-boolean-cell"
|
||||
import { useRegions } from "../../../../../hooks/api/regions"
|
||||
import { usePricePreferences } from "../../../../../hooks/api/price-preferences"
|
||||
import { getPriceColumns } from "../../../../../components/data-grid/data-grid-columns/price-columns"
|
||||
|
||||
type ProductCreateVariantsFormProps = {
|
||||
form: UseFormReturn<ProductCreateSchemaType>
|
||||
@@ -23,6 +24,8 @@ export const ProductCreateVariantsForm = ({
|
||||
|
||||
const { store, isPending, isError, error } = useStore()
|
||||
|
||||
const { price_preferences: pricePreferences } = usePricePreferences({})
|
||||
|
||||
const variants = useWatch({
|
||||
control: form.control,
|
||||
name: "variants",
|
||||
@@ -39,6 +42,7 @@ export const ProductCreateVariantsForm = ({
|
||||
options,
|
||||
currencies: store?.supported_currencies?.map((c) => c.currency_code) || [],
|
||||
regions,
|
||||
pricePreferences,
|
||||
})
|
||||
|
||||
const variantData = useMemo(
|
||||
@@ -67,10 +71,12 @@ const useColumns = ({
|
||||
options,
|
||||
currencies = [],
|
||||
regions = [],
|
||||
pricePreferences = [],
|
||||
}: {
|
||||
options: any // CreateProductOptionSchemaType[]
|
||||
currencies?: string[]
|
||||
regions: RegionDTO[]
|
||||
regions?: HttpTypes.AdminRegion[]
|
||||
pricePreferences?: HttpTypes.AdminPricePreference[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -166,40 +172,19 @@ const useColumns = ({
|
||||
type: "boolean",
|
||||
}),
|
||||
|
||||
...currencies.map((currency) => {
|
||||
return columnHelper.column({
|
||||
id: `price_${currency}`,
|
||||
name: `Price ${currency.toUpperCase()}`,
|
||||
header: `Price ${currency.toUpperCase()}`,
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={currency}
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.prices.${currency}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}),
|
||||
|
||||
...regions.map((region) => {
|
||||
return columnHelper.column({
|
||||
id: `price_${region.id}`,
|
||||
name: `Price ${region.name}`,
|
||||
header: `Price ${region.name}`,
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={region.currency_code}
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.prices.${region.id}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
...getPriceColumns({
|
||||
currencies,
|
||||
regions,
|
||||
pricePreferences,
|
||||
getFieldName: (context, value) => {
|
||||
if (context.column.id.startsWith("currency_prices")) {
|
||||
return `variants.${context.row.index}.prices.${value}`
|
||||
}
|
||||
return `variants.${context.row.index}.prices.${value}`
|
||||
},
|
||||
t,
|
||||
}),
|
||||
],
|
||||
[currencies, regions, options, t]
|
||||
[currencies, regions, options, pricePreferences, t]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ const CreateRegionSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
currency_code: zod.string().min(2, "Select a currency"),
|
||||
automatic_taxes: zod.boolean(),
|
||||
is_tax_inclusive: zod.boolean(),
|
||||
countries: zod.array(zod.object({ code: zod.string(), name: zod.string() })),
|
||||
payment_providers: zod.array(zod.string()).min(1),
|
||||
})
|
||||
@@ -65,6 +66,7 @@ export const CreateRegionForm = ({
|
||||
name: "",
|
||||
currency_code: "",
|
||||
automatic_taxes: true,
|
||||
is_tax_inclusive: false,
|
||||
countries: [],
|
||||
payment_providers: [],
|
||||
},
|
||||
@@ -79,31 +81,34 @@ export const CreateRegionForm = ({
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mutateAsync, isPending } = useCreateRegion()
|
||||
const { mutateAsync: createRegion, isPending: isPendingRegion } =
|
||||
useCreateRegion()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
await createRegion(
|
||||
{
|
||||
name: values.name,
|
||||
countries: values.countries.map((c) => c.code),
|
||||
currency_code: values.currency_code,
|
||||
payment_providers: values.payment_providers,
|
||||
automatic_taxes: values.automatic_taxes,
|
||||
is_tax_inclusive: values.is_tax_inclusive,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ region }) => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("regions.toast.create"),
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
handleSuccess(`../${region.id}`)
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: e.message,
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
},
|
||||
onSuccess: ({ region }) => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("regions.toast.create"),
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
|
||||
handleSuccess(`../${region.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -206,7 +211,7 @@ export const CreateRegionForm = ({
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isPending}>
|
||||
<Button size="small" type="submit" isLoading={isPendingRegion}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -304,6 +309,35 @@ export const CreateRegionForm = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="is_tax_inclusive"
|
||||
render={({ field: { value, onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<Form.Label>
|
||||
{t("fields.taxInclusivePricing")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch
|
||||
{...field}
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
<Form.Hint>
|
||||
{t("regions.taxInclusiveHint")}
|
||||
</Form.Hint>
|
||||
<Form.ErrorMessage />
|
||||
</div>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="bg-ui-border-base h-px w-full" />
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
|
||||
@@ -9,13 +9,22 @@ import { ListSummary } from "../../../../../components/common/list-summary/index
|
||||
import { useDeleteRegion } from "../../../../../hooks/api/regions.tsx"
|
||||
import { currencies } from "../../../../../lib/currencies.ts"
|
||||
import { formatProvider } from "../../../../../lib/format-provider.ts"
|
||||
import { SectionRow } from "../../../../../components/common/section/section-row.tsx"
|
||||
|
||||
type RegionGeneralSectionProps = {
|
||||
region: HttpTypes.AdminRegion
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
export const RegionGeneralSection = ({ region }: RegionGeneralSectionProps) => {
|
||||
export const RegionGeneralSection = ({
|
||||
region,
|
||||
pricePreferences,
|
||||
}: RegionGeneralSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const pricePreferenceForRegion = pricePreferences?.find(
|
||||
(preference) =>
|
||||
preference.attribute === "region_id" && preference.value === region.id
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
@@ -23,33 +32,48 @@ export const RegionGeneralSection = ({ region }: RegionGeneralSectionProps) => {
|
||||
<Heading>{region.name}</Heading>
|
||||
<RegionActions region={region} />
|
||||
</div>
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("fields.currency")}
|
||||
</Text>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Badge size="2xsmall" className="uppercase">
|
||||
{region.currency_code}
|
||||
</Badge>
|
||||
<Text size="small" leading="compact">
|
||||
{currencies[region.currency_code.toUpperCase()].name}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("fields.paymentProviders")}
|
||||
</Text>
|
||||
<div className="inline-flex">
|
||||
{region.payment_providers?.length > 0 ? (
|
||||
<ListSummary
|
||||
list={region.payment_providers.map((p) => formatProvider(p.id))}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<SectionRow
|
||||
title={t("fields.currency")}
|
||||
value={
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Badge size="2xsmall" className="uppercase">
|
||||
{region.currency_code}
|
||||
</Badge>
|
||||
<Text size="small" leading="compact">
|
||||
{currencies[region.currency_code.toUpperCase()].name}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<SectionRow
|
||||
title={t("fields.automaticTaxes")}
|
||||
value={region.automatic_taxes ? t("fields.true") : t("fields.false")}
|
||||
/>
|
||||
|
||||
<SectionRow
|
||||
title={t("fields.taxInclusivePricing")}
|
||||
value={
|
||||
pricePreferenceForRegion?.is_tax_inclusive
|
||||
? t("fields.true")
|
||||
: t("fields.false")
|
||||
}
|
||||
/>
|
||||
|
||||
<SectionRow
|
||||
title={t("fields.paymentProviders")}
|
||||
value={
|
||||
<div className="inline-flex">
|
||||
{region.payment_providers?.length ? (
|
||||
<ListSummary
|
||||
list={region.payment_providers.map((p) => formatProvider(p.id))}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { regionLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/region/details/after"
|
||||
import before from "virtual:medusa/widgets/region/details/before"
|
||||
import { usePricePreferences } from "../../../hooks/api/price-preferences"
|
||||
|
||||
export const RegionDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
@@ -18,8 +19,8 @@ export const RegionDetail = () => {
|
||||
const {
|
||||
region,
|
||||
isPending: isLoading,
|
||||
isError,
|
||||
error,
|
||||
isError: isRegionError,
|
||||
error: regionError,
|
||||
} = useRegion(
|
||||
id!,
|
||||
{ fields: "*payment_providers,*countries" },
|
||||
@@ -28,13 +29,30 @@ export const RegionDetail = () => {
|
||||
}
|
||||
)
|
||||
|
||||
const {
|
||||
price_preferences: pricePreferences,
|
||||
isPending: isLoadingPreferences,
|
||||
isError: isPreferencesError,
|
||||
error: preferencesError,
|
||||
} = usePricePreferences(
|
||||
{
|
||||
attribute: "region_id",
|
||||
value: id,
|
||||
},
|
||||
{ enabled: !!region }
|
||||
)
|
||||
|
||||
// TODO: Move to loading.tsx and set as Suspense fallback for the route
|
||||
if (isLoading || !region) {
|
||||
if (isLoading || isLoadingPreferences || !region) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
if (isRegionError) {
|
||||
throw regionError
|
||||
}
|
||||
|
||||
if (isPreferencesError) {
|
||||
throw preferencesError
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -46,7 +64,10 @@ export const RegionDetail = () => {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<RegionGeneralSection region={region} />
|
||||
<RegionGeneralSection
|
||||
region={region}
|
||||
pricePreferences={pricePreferences ?? []}
|
||||
/>
|
||||
<RegionCountrySection region={region} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HttpTypes, PaymentProviderDTO } from "@medusajs/types"
|
||||
import { Button, Input, Select, Text, toast } from "@medusajs/ui"
|
||||
import { Button, Input, Select, Switch, Text, toast } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
@@ -18,40 +18,58 @@ type EditRegionFormProps = {
|
||||
region: HttpTypes.AdminRegion
|
||||
currencies: CurrencyInfo[]
|
||||
paymentProviders: PaymentProviderDTO[]
|
||||
pricePreferences: HttpTypes.AdminPricePreference[]
|
||||
}
|
||||
|
||||
const EditRegionSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
currency_code: zod.string(),
|
||||
payment_providers: zod.array(zod.string()),
|
||||
automatic_taxes: zod.boolean(),
|
||||
is_tax_inclusive: zod.boolean(),
|
||||
})
|
||||
|
||||
export const EditRegionForm = ({
|
||||
region,
|
||||
currencies,
|
||||
paymentProviders,
|
||||
pricePreferences,
|
||||
}: EditRegionFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
const pricePreferenceForRegion = pricePreferences?.find(
|
||||
(preference) =>
|
||||
preference.attribute === "region_id" && preference.value === region.id
|
||||
)
|
||||
|
||||
const form = useForm<zod.infer<typeof EditRegionSchema>>({
|
||||
defaultValues: {
|
||||
name: region.name,
|
||||
currency_code: region.currency_code.toUpperCase(),
|
||||
payment_providers: region.payment_providers?.map((pp) => pp.id) || [],
|
||||
automatic_taxes: region.automatic_taxes,
|
||||
is_tax_inclusive: pricePreferenceForRegion?.is_tax_inclusive || false,
|
||||
},
|
||||
})
|
||||
|
||||
const { mutateAsync, isPending: isLoading } = useUpdateRegion(region.id)
|
||||
const { mutateAsync: updateRegion, isPending: isPendingRegion } =
|
||||
useUpdateRegion(region.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
await updateRegion(
|
||||
{
|
||||
name: values.name,
|
||||
currency_code: values.currency_code.toLowerCase(),
|
||||
payment_providers: values.payment_providers,
|
||||
is_tax_inclusive: values.is_tax_inclusive,
|
||||
},
|
||||
{
|
||||
onError: (e) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: e.message,
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("regions.toast.edit"),
|
||||
@@ -59,12 +77,6 @@ export const EditRegionForm = ({
|
||||
})
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: e.message,
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -117,6 +129,59 @@ export const EditRegionForm = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="automatic_taxes"
|
||||
render={({ field: { value, onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<Form.Label>{t("fields.automaticTaxes")}</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch
|
||||
{...field}
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
<Form.Hint>{t("regions.automaticTaxesHint")}</Form.Hint>
|
||||
<Form.ErrorMessage />
|
||||
</div>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="is_tax_inclusive"
|
||||
render={({ field: { value, onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<Form.Label>
|
||||
{t("fields.taxInclusivePricing")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch
|
||||
{...field}
|
||||
checked={value}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
<Form.Hint>{t("regions.taxInclusiveHint")}</Form.Hint>
|
||||
<Form.ErrorMessage />
|
||||
</div>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
@@ -157,7 +222,7 @@ export const EditRegionForm = ({
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
<Button size="small" type="submit" isLoading={isPendingRegion}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useRegion } from "../../../hooks/api/regions"
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
import { currencies } from "../../../lib/currencies"
|
||||
import { EditRegionForm } from "./components/edit-region-form"
|
||||
import { usePricePreferences } from "../../../hooks/api/price-preferences"
|
||||
|
||||
export const RegionEdit = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -27,7 +28,20 @@ export const RegionEdit = () => {
|
||||
error: storeError,
|
||||
} = useStore()
|
||||
|
||||
const isLoading = isRegionLoading || isStoreLoading
|
||||
const {
|
||||
price_preferences: pricePreferences = [],
|
||||
isPending: isPreferenceLoading,
|
||||
isError: isPreferenceError,
|
||||
error: preferenceError,
|
||||
} = usePricePreferences(
|
||||
{
|
||||
attribute: "region_id",
|
||||
value: id,
|
||||
},
|
||||
{ enabled: !!region }
|
||||
)
|
||||
|
||||
const isLoading = isRegionLoading || isStoreLoading || isPreferenceLoading
|
||||
|
||||
const storeCurrencies = (store?.supported_currencies ?? []).map(
|
||||
(c) => currencies[c.currency_code.toUpperCase()]
|
||||
@@ -44,6 +58,10 @@ export const RegionEdit = () => {
|
||||
throw storeError
|
||||
}
|
||||
|
||||
if (isPreferenceError) {
|
||||
throw preferenceError
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
@@ -54,6 +72,7 @@ export const RegionEdit = () => {
|
||||
region={region}
|
||||
currencies={storeCurrencies}
|
||||
paymentProviders={paymentProviders}
|
||||
pricePreferences={pricePreferences}
|
||||
/>
|
||||
)}
|
||||
</RouteDrawer>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
import { CreateRegionDTO, WorkflowTypes } from "@medusajs/types"
|
||||
import {
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
WorkflowData,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { createRegionsStep } from "../steps"
|
||||
import { setRegionsPaymentProvidersStep } from "../steps/set-regions-payment-providers"
|
||||
import { createPricePreferencesWorkflow } from "../../pricing"
|
||||
|
||||
export const createRegionsWorkflowId = "create-regions"
|
||||
export const createRegionsWorkflow = createWorkflow(
|
||||
@@ -14,18 +16,22 @@ export const createRegionsWorkflow = createWorkflow(
|
||||
input: WorkflowData<WorkflowTypes.RegionWorkflow.CreateRegionsWorkflowInput>
|
||||
): WorkflowData<WorkflowTypes.RegionWorkflow.CreateRegionsWorkflowOutput> => {
|
||||
const data = transform(input, (data) => {
|
||||
const regionIndexToPaymentProviders = data.regions.map(
|
||||
(region, index) => {
|
||||
return {
|
||||
region_index: index,
|
||||
payment_providers: region.payment_providers,
|
||||
}
|
||||
const regionIndexToAdditionalData = data.regions.map((region, index) => {
|
||||
return {
|
||||
region_index: index,
|
||||
payment_providers: region.payment_providers,
|
||||
is_tax_inclusive: region.is_tax_inclusive,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
regions: data.regions,
|
||||
regionIndexToPaymentProviders,
|
||||
regions: data.regions.map((r) => {
|
||||
const resp = { ...r }
|
||||
delete resp.is_tax_inclusive
|
||||
delete resp.payment_providers
|
||||
return resp
|
||||
}),
|
||||
regionIndexToAdditionalData,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -33,11 +39,11 @@ export const createRegionsWorkflow = createWorkflow(
|
||||
|
||||
const normalizedRegionProviderData = transform(
|
||||
{
|
||||
regionIndexToPaymentProviders: data.regionIndexToPaymentProviders,
|
||||
regionIndexToAdditionalData: data.regionIndexToAdditionalData,
|
||||
regions,
|
||||
},
|
||||
(data) => {
|
||||
return data.regionIndexToPaymentProviders.map(
|
||||
return data.regionIndexToAdditionalData.map(
|
||||
({ region_index, payment_providers }) => {
|
||||
return {
|
||||
id: data.regions[region_index].id,
|
||||
@@ -48,9 +54,32 @@ export const createRegionsWorkflow = createWorkflow(
|
||||
}
|
||||
)
|
||||
|
||||
setRegionsPaymentProvidersStep({
|
||||
input: normalizedRegionProviderData,
|
||||
})
|
||||
const normalizedRegionPricePreferencesData = transform(
|
||||
{
|
||||
regionIndexToAdditionalData: data.regionIndexToAdditionalData,
|
||||
regions,
|
||||
},
|
||||
(data) => {
|
||||
return data.regionIndexToAdditionalData.map(
|
||||
({ region_index, is_tax_inclusive }) => {
|
||||
return {
|
||||
attribute: "region_id",
|
||||
value: data.regions[region_index].id,
|
||||
is_tax_inclusive,
|
||||
} as WorkflowTypes.PricingWorkflow.CreatePricePreferencesWorkflowInput
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
parallelize(
|
||||
setRegionsPaymentProvidersStep({
|
||||
input: normalizedRegionProviderData,
|
||||
}),
|
||||
createPricePreferencesWorkflow.runAsStep({
|
||||
input: normalizedRegionPricePreferencesData,
|
||||
})
|
||||
)
|
||||
|
||||
return regions
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
import {
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
when,
|
||||
WorkflowData,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { updateRegionsStep } from "../steps"
|
||||
import { setRegionsPaymentProvidersStep } from "../steps/set-regions-payment-providers"
|
||||
import { updatePricePreferencesWorkflow } from "../../pricing"
|
||||
|
||||
export const updateRegionsWorkflowId = "update-regions"
|
||||
export const updateRegionsWorkflow = createWorkflow(
|
||||
@@ -13,30 +16,58 @@ export const updateRegionsWorkflow = createWorkflow(
|
||||
(
|
||||
input: WorkflowData<WorkflowTypes.RegionWorkflow.UpdateRegionsWorkflowInput>
|
||||
): WorkflowData<WorkflowTypes.RegionWorkflow.UpdateRegionsWorkflowOutput> => {
|
||||
const data = transform(input, (data) => {
|
||||
const normalizedInput = transform(input, (data) => {
|
||||
const { selector, update } = data
|
||||
const { payment_providers = [], ...rest } = update
|
||||
const { payment_providers = [], is_tax_inclusive, ...rest } = update
|
||||
return {
|
||||
selector,
|
||||
update: rest,
|
||||
payment_providers,
|
||||
is_tax_inclusive,
|
||||
}
|
||||
})
|
||||
|
||||
const regions = updateRegionsStep(data)
|
||||
const regions = updateRegionsStep(normalizedInput)
|
||||
|
||||
const upsertProvidersNormalizedInput = transform(
|
||||
{ data, regions },
|
||||
{ normalizedInput, regions },
|
||||
(data) => {
|
||||
return data.regions.map((region) => {
|
||||
return {
|
||||
id: region.id,
|
||||
payment_providers: data.data.payment_providers,
|
||||
payment_providers: data.normalizedInput.payment_providers,
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
when({ normalizedInput }, (data) => {
|
||||
return data.normalizedInput.is_tax_inclusive !== undefined
|
||||
}).then(() => {
|
||||
const updatePricePreferencesInput = transform(
|
||||
{ normalizedInput, regions },
|
||||
(data) => {
|
||||
return {
|
||||
selector: {
|
||||
$or: data.regions.map((region) => {
|
||||
return {
|
||||
attribute: "region_id",
|
||||
value: region.id,
|
||||
}
|
||||
}),
|
||||
},
|
||||
update: {
|
||||
is_tax_inclusive: data.normalizedInput.is_tax_inclusive,
|
||||
},
|
||||
} as WorkflowTypes.PricingWorkflow.UpdatePricePreferencesWorkflowInput
|
||||
}
|
||||
)
|
||||
|
||||
updatePricePreferencesWorkflow.runAsStep({
|
||||
input: updatePricePreferencesInput,
|
||||
})
|
||||
})
|
||||
|
||||
setRegionsPaymentProvidersStep({
|
||||
input: upsertProvidersNormalizedInput,
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import { InventoryItem } from "./inventory-item"
|
||||
import { Invite } from "./invite"
|
||||
import { Order } from "./order"
|
||||
import { PriceList } from "./price-list"
|
||||
import { PricePreference } from "./price-preference"
|
||||
import { Product } from "./product"
|
||||
import { ProductCategory } from "./product-category"
|
||||
import { ProductCollection } from "./product-collection"
|
||||
@@ -27,6 +28,7 @@ export class Admin {
|
||||
public productCollection: ProductCollection
|
||||
public productCategory: ProductCategory
|
||||
public priceList: PriceList
|
||||
public pricePreference: PricePreference
|
||||
public product: Product
|
||||
public productType: ProductType
|
||||
public upload: Upload
|
||||
@@ -50,6 +52,7 @@ export class Admin {
|
||||
this.productCollection = new ProductCollection(client)
|
||||
this.productCategory = new ProductCategory(client)
|
||||
this.priceList = new PriceList(client)
|
||||
this.pricePreference = new PricePreference(client)
|
||||
this.product = new Product(client)
|
||||
this.productType = new ProductType(client)
|
||||
this.upload = new Upload(client)
|
||||
|
||||
83
packages/core/js-sdk/src/admin/price-preference.ts
Normal file
83
packages/core/js-sdk/src/admin/price-preference.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Client } from "../client"
|
||||
import { ClientHeaders } from "../types"
|
||||
|
||||
export class PricePreference {
|
||||
private client: Client
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
query?: HttpTypes.AdminPricePreferenceParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminPricePreferenceResponse>(
|
||||
`/admin/price-preferences/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async list(
|
||||
query?: HttpTypes.AdminPricePreferenceListParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminPricePreferenceListResponse>(
|
||||
`/admin/price-preferences`,
|
||||
{
|
||||
method: "GET",
|
||||
headers,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async create(
|
||||
body: HttpTypes.AdminCreatePricePreference,
|
||||
query?: HttpTypes.AdminPricePreferenceParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminPricePreferenceResponse>(
|
||||
`/admin/price-preferences`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
body: HttpTypes.AdminUpdatePricePreference,
|
||||
query?: HttpTypes.AdminPricePreferenceParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminPricePreferenceResponse>(
|
||||
`/admin/price-preferences/${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async delete(id: string, headers?: ClientHeaders) {
|
||||
return this.client.fetch<HttpTypes.AdminPricePreferenceDeleteResponse>(
|
||||
`/admin/price-preferences/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export interface AdminCreateRegion {
|
||||
currency_code: string
|
||||
countries?: string[]
|
||||
automatic_taxes?: boolean
|
||||
is_tax_inclusive?: boolean
|
||||
payment_providers?: string[]
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
@@ -24,6 +25,7 @@ export interface AdminUpdateRegion {
|
||||
currency_code?: string
|
||||
countries?: string[]
|
||||
automatic_taxes?: boolean
|
||||
is_tax_inclusive?: boolean
|
||||
payment_providers?: string[]
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { CreateRegionDTO, RegionDTO } from "../../region"
|
||||
|
||||
export interface CreateRegionsWorkflowInput {
|
||||
regions: CreateRegionDTO[]
|
||||
regions: (CreateRegionDTO & {
|
||||
payment_providers?: string[]
|
||||
is_tax_inclusive?: boolean
|
||||
})[]
|
||||
}
|
||||
|
||||
export type CreateRegionsWorkflowOutput = RegionDTO[]
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FilterableRegionProps, RegionDTO, UpdateRegionDTO } from "../../region"
|
||||
export interface UpdateRegionsWorkflowInput {
|
||||
selector: FilterableRegionProps
|
||||
update: UpdateRegionDTO & {
|
||||
is_tax_inclusive?: boolean
|
||||
payment_providers?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createFindParams, createSelectParams } from "../../utils/validators"
|
||||
export const AdminGetPricePreferenceParams = createSelectParams()
|
||||
export const AdminGetPricePreferencesParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
limit: 300,
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
|
||||
@@ -33,6 +33,7 @@ export const AdminCreateRegion = z
|
||||
currency_code: z.string(),
|
||||
countries: z.array(z.string()).optional(),
|
||||
automatic_taxes: z.boolean().optional(),
|
||||
is_tax_inclusive: z.boolean().optional(),
|
||||
payment_providers: z.array(z.string()).optional(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
@@ -45,6 +46,7 @@ export const AdminUpdateRegion = z
|
||||
currency_code: z.string().optional(),
|
||||
countries: z.array(z.string()).optional(),
|
||||
automatic_taxes: z.boolean().optional(),
|
||||
is_tax_inclusive: z.boolean().optional(),
|
||||
payment_providers: z.array(z.string()).optional(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user