fix(medusa,dashboard): don't send price updates for deleted regions/currencies (#9361)
**What** - Fixes an issue where the admin dashboard would send region prices for deleted regions. - Also, includes the implementation for `GET /admin/shipping-options/:id` and its corresponding SDK function. **Why** - When a region price for a deleted region was sent to the backend it would result in the insert hitting a not null constraint on the currency_code for prices. To avoid this the dashboard should not send region prices for deleted regions. **Additional context** - Prices for deleted regions should ideally not be returned when fetching shipping option prices. However, we don't yet have a mechanism for cleaning up region prices after a region is deleted. Fixes CC-540
This commit is contained in:
1 parent
7484cef0fa
commit
852df3f764
8 files changed
+202
-50
No files matched your search
@@ -134,6 +134,105 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/shipping-options/:id", () => {
|
||||||
|
it("should filters options by stock_location_id", async () => {
|
||||||
|
const shippingOptionPayload = {
|
||||||
|
name: "Test shipping option",
|
||||||
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
price_type: "flat",
|
||||||
|
type: {
|
||||||
|
label: "Test type",
|
||||||
|
description: "Test description",
|
||||||
|
code: "test-code",
|
||||||
|
},
|
||||||
|
prices: [{ currency_code: "usd", amount: 1000 }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { shipping_option: shippingOption },
|
||||||
|
} = await api.post(
|
||||||
|
`/admin/shipping-options`,
|
||||||
|
shippingOptionPayload,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionRes = await api.get(
|
||||||
|
`/admin/shipping-options/${shippingOption.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(shippingOptionRes.data.shipping_option).toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "Test shipping option",
|
||||||
|
price_type: "flat",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 1000,
|
||||||
|
currency_code: "usd",
|
||||||
|
max_quantity: null,
|
||||||
|
min_quantity: null,
|
||||||
|
price_list: null,
|
||||||
|
price_set_id: expect.any(String),
|
||||||
|
raw_amount: {
|
||||||
|
precision: 20,
|
||||||
|
value: "1000",
|
||||||
|
},
|
||||||
|
rules_count: 0,
|
||||||
|
title: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
provider_id: "manual_test-provider",
|
||||||
|
provider: {
|
||||||
|
id: "manual_test-provider",
|
||||||
|
is_enabled: true,
|
||||||
|
},
|
||||||
|
rules: [],
|
||||||
|
service_zone_id: expect.any(String),
|
||||||
|
service_zone: {
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "Test",
|
||||||
|
fulfillment_set: {
|
||||||
|
id: expect.any(String),
|
||||||
|
},
|
||||||
|
fulfillment_set_id: expect.any(String),
|
||||||
|
metadata: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
shipping_profile_id: expect.any(String),
|
||||||
|
shipping_profile: {
|
||||||
|
id: expect.any(String),
|
||||||
|
metadata: null,
|
||||||
|
name: "Test",
|
||||||
|
type: "default",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
code: "test-code",
|
||||||
|
description: "Test description",
|
||||||
|
id: expect.any(String),
|
||||||
|
label: "Test type",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
data: null,
|
||||||
|
metadata: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("POST /admin/shipping-options", () => {
|
describe("POST /admin/shipping-options", () => {
|
||||||
it("should throw error when required params are missing", async () => {
|
it("should throw error when required params are missing", async () => {
|
||||||
const shippingOptionPayload = {
|
const shippingOptionPayload = {
|
||||||
|
|||||||
@@ -18,24 +18,24 @@ export const shippingOptionsQueryKeys = queryKeysFactory(
|
|||||||
SHIPPING_OPTIONS_QUERY_KEY
|
SHIPPING_OPTIONS_QUERY_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Endpoint is not implemented yet
|
export const useShippingOption = (
|
||||||
// export const useShippingOption = (
|
id: string,
|
||||||
// id: string,
|
query?: Record<string, any>,
|
||||||
// options?: UseQueryOptions<
|
options?: UseQueryOptions<
|
||||||
// HttpTypes.AdminShippingOptionResponse,
|
HttpTypes.AdminShippingOptionResponse,
|
||||||
// Error,
|
Error,
|
||||||
// HttpTypes.AdminShippingOptionResponse,
|
HttpTypes.AdminShippingOptionResponse,
|
||||||
// QueryKey
|
QueryKey
|
||||||
// >
|
>
|
||||||
// ) => {
|
) => {
|
||||||
// const { data, ...rest } = useQuery({
|
const { data, ...rest } = useQuery({
|
||||||
// queryFn: () => sdk.admin.shippingOption.retrieve(id),
|
queryFn: () => sdk.admin.shippingOption.retrieve(id, query),
|
||||||
// queryKey: shippingOptionsQueryKeys.retrieve(id),
|
queryKey: shippingOptionsQueryKeys.detail(id),
|
||||||
// ...options,
|
...options,
|
||||||
// })
|
})
|
||||||
|
|
||||||
// return { ...data, ...rest }
|
return { ...data, ...rest }
|
||||||
// }
|
}
|
||||||
|
|
||||||
export const useShippingOptions = (
|
export const useShippingOptions = (
|
||||||
query?: HttpTypes.AdminShippingOptionListParams,
|
query?: HttpTypes.AdminShippingOptionListParams,
|
||||||
|
|||||||
+13
@@ -129,6 +129,11 @@ export function EditShippingOptionsPricingForm({
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currencyExists = currencies.some(currencyCode => currencyCode.toLowerCase() == code.toLowerCase())
|
||||||
|
if (!currencyExists) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const amount = castNumber(value)
|
const amount = castNumber(value)
|
||||||
|
|
||||||
const priceRecord: PriceRecord = {
|
const priceRecord: PriceRecord = {
|
||||||
@@ -155,6 +160,14 @@ export function EditShippingOptionsPricingForm({
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the region_id exists in the regions array to avoid
|
||||||
|
// sending updates of region prices where the region has been
|
||||||
|
// deleted
|
||||||
|
const regionExists = regions?.some(region => region.id === region_id)
|
||||||
|
if (!regionExists) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const amount = castNumber(value)
|
const amount = castNumber(value)
|
||||||
|
|
||||||
const priceRecord: PriceRecord = {
|
const priceRecord: PriceRecord = {
|
||||||
|
|||||||
+10
-11
@@ -1,25 +1,24 @@
|
|||||||
import { json, useParams } from "react-router-dom"
|
import { json, useParams } from "react-router-dom"
|
||||||
|
|
||||||
import { RouteFocusModal } from "../../../components/modals"
|
import { RouteFocusModal } from "../../../components/modals"
|
||||||
import { useShippingOptions } from "../../../hooks/api/shipping-options"
|
import { useShippingOption } from "../../../hooks/api/shipping-options"
|
||||||
import { EditShippingOptionsPricingForm } from "./components/create-shipping-options-form"
|
import { EditShippingOptionsPricingForm } from "./components/create-shipping-options-form"
|
||||||
|
|
||||||
export function LocationServiceZoneShippingOptionPricing() {
|
export function LocationServiceZoneShippingOptionPricing() {
|
||||||
const { so_id, location_id } = useParams()
|
const { so_id, location_id } = useParams()
|
||||||
|
|
||||||
const { shipping_options, isPending, isFetching, isError, error } =
|
if (!so_id) {
|
||||||
useShippingOptions({
|
throw json({
|
||||||
// TODO: change this when GET option by id endpoint is implemented
|
message: "Shipping Option ID paramater is missing",
|
||||||
id: [so_id!],
|
status: 404,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { shipping_option: shippingOption, isError, error } =
|
||||||
|
useShippingOption(so_id, {
|
||||||
fields: "*prices,*prices.price_rules",
|
fields: "*prices,*prices.price_rules",
|
||||||
})
|
})
|
||||||
|
|
||||||
const shippingOption = shipping_options?.find((so) => so.id === so_id)
|
|
||||||
|
|
||||||
if (!isPending && !isFetching && !shippingOption) {
|
|
||||||
throw json(`Shipping option with id: ${so_id} not found`, { status: 404 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,21 @@ export class ShippingOption {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async retrieve(
|
||||||
|
id: string,
|
||||||
|
query?: HttpTypes.SelectParams,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return await this.client.fetch<HttpTypes.AdminShippingOptionResponse>(
|
||||||
|
`/admin/shipping-options/${id}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers,
|
||||||
|
query,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
id: string,
|
id: string,
|
||||||
body: HttpTypes.AdminUpdateShippingOption,
|
body: HttpTypes.AdminUpdateShippingOption,
|
||||||
|
|||||||
@@ -8,7 +8,31 @@ import {
|
|||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../../types/routing"
|
} from "../../../../types/routing"
|
||||||
import { refetchShippingOption } from "../helpers"
|
import { refetchShippingOption } from "../helpers"
|
||||||
import { AdminUpdateShippingOptionType } from "../validators"
|
import {
|
||||||
|
AdminGetShippingOptionParamsType,
|
||||||
|
AdminUpdateShippingOptionType,
|
||||||
|
} from "../validators"
|
||||||
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
|
export const GET = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminGetShippingOptionParamsType>,
|
||||||
|
res: MedusaResponse<HttpTypes.AdminShippingOptionResponse>
|
||||||
|
) => {
|
||||||
|
const shippingOption = await refetchShippingOption(
|
||||||
|
req.params.id,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!shippingOption) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Shipping Option with id: ${req.params.id} not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ shipping_option: shippingOption })
|
||||||
|
}
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest<AdminUpdateShippingOptionType>,
|
req: AuthenticatedMedusaRequest<AdminUpdateShippingOptionType>,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
promiseAll,
|
promiseAll,
|
||||||
remoteQueryObjectFromString,
|
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
|
|
||||||
export const refetchShippingOption = async (
|
export const refetchShippingOption = async (
|
||||||
@@ -14,17 +13,14 @@ export const refetchShippingOption = async (
|
|||||||
scope: MedusaContainer,
|
scope: MedusaContainer,
|
||||||
fields: string[]
|
fields: string[]
|
||||||
) => {
|
) => {
|
||||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
const query = scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||||
const queryObject = remoteQueryObjectFromString({
|
const { data } = await query.graph({
|
||||||
entryPoint: "shipping_option",
|
entity: "shipping_option",
|
||||||
variables: {
|
|
||||||
filters: { id: shippingOptionId },
|
filters: { id: shippingOptionId },
|
||||||
},
|
|
||||||
fields: fields,
|
fields: fields,
|
||||||
})
|
})
|
||||||
|
|
||||||
const shippingOptions = await remoteQuery(queryObject)
|
return data[0]
|
||||||
return shippingOptions[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const refetchBatchRules = async (
|
export const refetchBatchRules = async (
|
||||||
@@ -32,32 +28,28 @@ export const refetchBatchRules = async (
|
|||||||
scope: MedusaContainer,
|
scope: MedusaContainer,
|
||||||
fields: string[]
|
fields: string[]
|
||||||
) => {
|
) => {
|
||||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
const query = scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||||
let created = Promise.resolve<ShippingOptionRuleDTO[]>([])
|
let created = Promise.resolve<ShippingOptionRuleDTO[]>([])
|
||||||
let updated = Promise.resolve<ShippingOptionRuleDTO[]>([])
|
let updated = Promise.resolve<ShippingOptionRuleDTO[]>([])
|
||||||
|
|
||||||
if (batchResult.created.length) {
|
if (batchResult.created.length) {
|
||||||
const createdQuery = remoteQueryObjectFromString({
|
created = query
|
||||||
entryPoint: "shipping_option_rule",
|
.graph({
|
||||||
variables: {
|
entity: "shipping_option_rule",
|
||||||
filters: { id: batchResult.created.map((p) => p.id) },
|
filters: { id: batchResult.created.map((p) => p.id) },
|
||||||
},
|
|
||||||
fields: fields,
|
fields: fields,
|
||||||
})
|
})
|
||||||
|
.then(({ data }) => data)
|
||||||
created = remoteQuery(createdQuery)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (batchResult.updated.length) {
|
if (batchResult.updated.length) {
|
||||||
const updatedQuery = remoteQueryObjectFromString({
|
updated = query
|
||||||
entryPoint: "shipping_option_rule",
|
.graph({
|
||||||
variables: {
|
entity: "shipping_option_rule",
|
||||||
filters: { id: batchResult.updated.map((p) => p.id) },
|
filters: { id: batchResult.updated.map((p) => p.id) },
|
||||||
},
|
|
||||||
fields: fields,
|
fields: fields,
|
||||||
})
|
})
|
||||||
|
.then(({ data }) => data)
|
||||||
updated = remoteQuery(updatedQuery)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [createdRes, updatedRes] = await promiseAll([created, updated])
|
const [createdRes, updatedRes] = await promiseAll([created, updated])
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ export const adminShippingOptionRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/shipping-options/:id",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetShippingOptionParams,
|
||||||
|
retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/admin/shipping-options",
|
matcher: "/admin/shipping-options",
|
||||||
|
|||||||
Reference in new issue
Block a user