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