feat(dashboard,core-flows,medusa): update fulfillment flows (#7589)
* fix: fulfillment ops * fix: cancel fulfillment route * fix: adjustInventoryLevelsStep throwing * feat: cancel order and fix endpoint * fix: type * feat: order domain sdk * feat: delete unused file * fix: import
This commit is contained in:
1 parent
da3837f2bc
commit
d285e60961
26 files changed
+288
-175
No files matched your search
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
import type { FulfillmentStatus } from "@medusajs/medusa"
|
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { FulfillmentStatus } from "@medusajs/types"
|
||||||
|
|
||||||
import { getOrderFulfillmentStatus } from "../../../../../lib/order-helpers"
|
import { getOrderFulfillmentStatus } from "../../../../../lib/order-helpers"
|
||||||
import { StatusCell } from "../../common/status-cell"
|
import { StatusCell } from "../../common/status-cell"
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { QueryKey, useQuery, UseQueryOptions } from "@tanstack/react-query"
|
|
||||||
|
|
||||||
import { client } from "../../lib/client"
|
|
||||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
|
||||||
|
|
||||||
const ORDERS_QUERY_KEY = "orders" as const
|
|
||||||
export const ordersQueryKeys = queryKeysFactory(ORDERS_QUERY_KEY)
|
|
||||||
|
|
||||||
export const useOrder = (
|
|
||||||
id: string,
|
|
||||||
query?: Record<string, any>,
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<any, Error, any, QueryKey>,
|
|
||||||
"queryFn" | "queryKey"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const { data, ...rest } = useQuery({
|
|
||||||
queryFn: async () => client.orders.retrieve(id, query),
|
|
||||||
queryKey: ordersQueryKeys.detail(id, query),
|
|
||||||
...options,
|
|
||||||
})
|
|
||||||
|
|
||||||
return { ...data, ...rest }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useOrders = (
|
|
||||||
query?: Record<string, any>,
|
|
||||||
options?: Omit<
|
|
||||||
UseQueryOptions<any, Error, any, QueryKey>,
|
|
||||||
"queryFn" | "queryKey"
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const { data, ...rest } = useQuery({
|
|
||||||
queryFn: async () => client.orders.list(query),
|
|
||||||
queryKey: ordersQueryKeys.list(query),
|
|
||||||
...options,
|
|
||||||
})
|
|
||||||
|
|
||||||
return { ...data, ...rest }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import {
|
||||||
|
QueryKey,
|
||||||
|
useMutation,
|
||||||
|
UseMutationOptions,
|
||||||
|
useQuery,
|
||||||
|
UseQueryOptions,
|
||||||
|
} from "@tanstack/react-query"
|
||||||
|
|
||||||
|
import { queryClient } from "../../lib/query-client"
|
||||||
|
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||||
|
import { sdk } from "../../lib/client"
|
||||||
|
|
||||||
|
const ORDERS_QUERY_KEY = "orders" as const
|
||||||
|
export const ordersQueryKeys = queryKeysFactory(ORDERS_QUERY_KEY)
|
||||||
|
|
||||||
|
export const useOrder = (
|
||||||
|
id: string,
|
||||||
|
query?: Record<string, any>,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<any, Error, any, QueryKey>,
|
||||||
|
"queryFn" | "queryKey"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { data, ...rest } = useQuery({
|
||||||
|
queryFn: async () => sdk.admin.order.retrieve(id, query),
|
||||||
|
queryKey: ordersQueryKeys.detail(id, query),
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { ...data, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOrders = (
|
||||||
|
query?: Record<string, any>,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<any, Error, any, QueryKey>,
|
||||||
|
"queryFn" | "queryKey"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { data, ...rest } = useQuery({
|
||||||
|
queryFn: async () => sdk.admin.order.list(query),
|
||||||
|
queryKey: ordersQueryKeys.list(query),
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { ...data, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateOrderFulfillment = (
|
||||||
|
orderId: string,
|
||||||
|
options?: UseMutationOptions<any, Error, any>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload: any) =>
|
||||||
|
sdk.admin.order.createFulfillment(orderId, payload),
|
||||||
|
onSuccess: (data: any, variables: any, context: any) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ordersQueryKeys.details(),
|
||||||
|
})
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCancelOrderFulfillment = (
|
||||||
|
orderId: string,
|
||||||
|
fulfillmentId: string,
|
||||||
|
options?: UseMutationOptions<any, Error, any>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload: { no_notification?: boolean }) =>
|
||||||
|
sdk.admin.order.cancelFulfillment(orderId, fulfillmentId, payload),
|
||||||
|
onSuccess: (data: any, variables: any, context: any) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ordersQueryKeys.details(),
|
||||||
|
})
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCancelOrder = (
|
||||||
|
orderId: string,
|
||||||
|
options?: UseMutationOptions<any, Error, any>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: () => sdk.admin.order.cancel(orderId),
|
||||||
|
onSuccess: (data: any, variables: any, context: any) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ordersQueryKeys.details(),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ordersQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -76,6 +76,7 @@
|
|||||||
"back": "Back",
|
"back": "Back",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
|
"reset": "Reset",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
@@ -572,6 +573,7 @@
|
|||||||
"inStock": "In stock",
|
"inStock": "In stock",
|
||||||
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
|
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
|
||||||
"locationDescription": "Choose which location you want to fulfill items from.",
|
"locationDescription": "Choose which location you want to fulfill items from.",
|
||||||
|
"sendNotificationHint": "Notify customers about the created fulfillment.",
|
||||||
"error": {
|
"error": {
|
||||||
"wrongQuantity": "Only one item is available for fulfillment",
|
"wrongQuantity": "Only one item is available for fulfillment",
|
||||||
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
|
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { fulfillmentProviders } from "./fulfillment-providers"
|
|||||||
import { fulfillments } from "./fulfillments"
|
import { fulfillments } from "./fulfillments"
|
||||||
import { inventoryItems } from "./inventory"
|
import { inventoryItems } from "./inventory"
|
||||||
import { invites } from "./invites"
|
import { invites } from "./invites"
|
||||||
import { orders } from "./orders"
|
|
||||||
import { payments } from "./payments"
|
import { payments } from "./payments"
|
||||||
import { priceLists } from "./price-lists"
|
import { priceLists } from "./price-lists"
|
||||||
import { productTypes } from "./product-types"
|
import { productTypes } from "./product-types"
|
||||||
|
|||||||
@@ -3,10 +3,18 @@ import { CreateFulfillmentDTO } from "@medusajs/types"
|
|||||||
import { FulfillmentRes } from "../../types/api-responses"
|
import { FulfillmentRes } from "../../types/api-responses"
|
||||||
import { postRequest } from "./common"
|
import { postRequest } from "./common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a fulfillment.
|
||||||
|
* NOTE: to create a fulfillment on an order use orders/:id/fulfillments endpoint
|
||||||
|
*/
|
||||||
async function createFulfillment(payload: CreateFulfillmentDTO) {
|
async function createFulfillment(payload: CreateFulfillmentDTO) {
|
||||||
return postRequest<FulfillmentRes>(`/admin/fulfillments`, payload)
|
return postRequest<FulfillmentRes>(`/admin/fulfillments`, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a fulfillment.
|
||||||
|
* NOTE: to cancel a fulfillment on an order use orders/:id/fulfillments/:id/cancel endpoint
|
||||||
|
*/
|
||||||
async function cancelFulfillment(id: string) {
|
async function cancelFulfillment(id: string) {
|
||||||
return postRequest<FulfillmentRes>(`/admin/fulfillments/${id}/cancel`)
|
return postRequest<FulfillmentRes>(`/admin/fulfillments/${id}/cancel`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { getRequest } from "./common"
|
|
||||||
import { OrderListRes, OrderRes } from "../../types/api-responses"
|
|
||||||
|
|
||||||
async function retrieveOrder(id: string, query?: Record<string, any>) {
|
|
||||||
return getRequest<OrderRes, Record<string, any>>(`/admin/orders/${id}`, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listOrders(query?: Record<string, any>) {
|
|
||||||
return getRequest<OrderListRes, Record<string, any>>(`/admin/orders`, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const orders = {
|
|
||||||
list: listOrders,
|
|
||||||
retrieve: retrieveOrder,
|
|
||||||
}
|
|
||||||
+10
-10
@@ -14,22 +14,22 @@ import { useTranslation } from "react-i18next"
|
|||||||
|
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||||
import { DataGrid } from "../../../../../components/grid/data-grid/index.ts"
|
import { DataGrid } from "../../../../../components/grid/data-grid/index"
|
||||||
import { CurrencyCell } from "../../../../../components/grid/grid-cells/common/currency-cell/index.ts"
|
import { CurrencyCell } from "../../../../../components/grid/grid-cells/common/currency-cell/index"
|
||||||
import { DataGridMeta } from "../../../../../components/grid/types.ts"
|
import { DataGridMeta } from "../../../../../components/grid/types"
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal/index.ts"
|
} from "../../../../../components/route-modal/index"
|
||||||
import { useCurrencies } from "../../../../../hooks/api/currencies.tsx"
|
import { useCurrencies } from "../../../../../hooks/api/currencies"
|
||||||
import { useRegions } from "../../../../../hooks/api/regions.tsx"
|
import { useRegions } from "../../../../../hooks/api/regions"
|
||||||
import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-options.ts"
|
import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||||
import { useStore } from "../../../../../hooks/api/store.tsx"
|
import { useStore } from "../../../../../hooks/api/store"
|
||||||
import {
|
import {
|
||||||
getDbAmount,
|
getDbAmount,
|
||||||
getPresentationalAmount,
|
getPresentationalAmount,
|
||||||
} from "../../../../../lib/money-amount-helpers.ts"
|
} from "../../../../../lib/money-amount-helpers"
|
||||||
import { ExtendedProductDTO } from "../../../../../types/api-responses.ts"
|
import { ExtendedProductDTO } from "../../../../../types/api-responses"
|
||||||
|
|
||||||
const getInitialCurrencyPrices = (prices: PriceDTO[]) => {
|
const getInitialCurrencyPrices = (prices: PriceDTO[]) => {
|
||||||
const ret: Record<string, number> = {}
|
const ret: Record<string, number> = {}
|
||||||
|
|||||||
+42
-82
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"
|
|||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
|
|
||||||
import { AdminOrder } from "@medusajs/types"
|
import { AdminOrder } from "@medusajs/types"
|
||||||
import { Alert, Button, Select, toast } from "@medusajs/ui"
|
import { Alert, Button, Select, Switch, toast } from "@medusajs/ui"
|
||||||
import { useForm, useWatch } from "react-hook-form"
|
import { useForm, useWatch } from "react-hook-form"
|
||||||
|
|
||||||
import { Form } from "../../../../../components/common/form"
|
import { Form } from "../../../../../components/common/form"
|
||||||
@@ -12,13 +12,12 @@ import {
|
|||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
import { useCreateFulfillment } from "../../../../../hooks/api/fulfillment"
|
|
||||||
import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers"
|
import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers"
|
||||||
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
||||||
import { cleanNonValues, pick } from "../../../../../lib/common"
|
|
||||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||||
import { CreateFulfillmentSchema } from "./constants"
|
|
||||||
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
||||||
|
import { useCreateOrderFulfillment } from "../../../../../hooks/api/orders"
|
||||||
|
import { CreateFulfillmentSchema } from "./constants"
|
||||||
|
|
||||||
type OrderCreateFulfillmentFormProps = {
|
type OrderCreateFulfillmentFormProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -30,8 +29,8 @@ export function OrderCreateFulfillmentForm({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleSuccess } = useRouteModal()
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
const { mutateAsync: createOrderFulfillment, isLoading: isMutating } =
|
const { mutateAsync: createOrderFulfillment, isPending: isMutating } =
|
||||||
useCreateFulfillment()
|
useCreateOrderFulfillment(order.id)
|
||||||
|
|
||||||
const { fulfillment_providers } = useFulfillmentProviders({
|
const { fulfillment_providers } = useFulfillmentProviders({
|
||||||
region_id: order.region_id,
|
region_id: order.region_id,
|
||||||
@@ -43,14 +42,11 @@ export function OrderCreateFulfillmentForm({
|
|||||||
|
|
||||||
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
quantity: fulfillableItems.reduce(
|
quantity: fulfillableItems.reduce((acc, item) => {
|
||||||
(acc, item) => {
|
|
||||||
acc[item.id] = getFulfillableQuantity(item)
|
acc[item.id] = getFulfillableQuantity(item)
|
||||||
return acc
|
return acc
|
||||||
},
|
}, {} as Record<string, number>),
|
||||||
{} as Record<string, number>
|
send_notification: !order.no_notification,
|
||||||
),
|
|
||||||
// send_notification: !order.no_notification,
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(CreateFulfillmentSchema),
|
resolver: zodResolver(CreateFulfillmentSchema),
|
||||||
})
|
})
|
||||||
@@ -61,49 +57,13 @@ export function OrderCreateFulfillmentForm({
|
|||||||
try {
|
try {
|
||||||
await createOrderFulfillment({
|
await createOrderFulfillment({
|
||||||
location_id: data.location_id,
|
location_id: data.location_id,
|
||||||
/**
|
no_notification: !data.send_notification,
|
||||||
* TODO: send notification flag
|
|
||||||
*/
|
|
||||||
// no_notification: !data.send_notification,
|
|
||||||
delivery_address: cleanNonValues(
|
|
||||||
pick(order.shipping_address, [
|
|
||||||
"first_name",
|
|
||||||
"last_name",
|
|
||||||
"phone",
|
|
||||||
"company",
|
|
||||||
"address_1",
|
|
||||||
"address_2",
|
|
||||||
"city",
|
|
||||||
"country_code",
|
|
||||||
"province",
|
|
||||||
"postal_code",
|
|
||||||
"metadata",
|
|
||||||
])
|
|
||||||
), // TODO: this should be pulled from order in the workflow
|
|
||||||
provider_id: fulfillment_providers[0]?.id,
|
|
||||||
items: Object.entries(data.quantity)
|
items: Object.entries(data.quantity)
|
||||||
.filter(([, value]) => !!value)
|
.filter(([, value]) => !!value)
|
||||||
.map(([item_id, quantity]) => {
|
.map(([id, quantity]) => ({
|
||||||
const item = order.items.find((i) => i.id === item_id)
|
id,
|
||||||
|
|
||||||
return {
|
|
||||||
quantity,
|
quantity,
|
||||||
line_item_id: item_id,
|
})),
|
||||||
title: item.title,
|
|
||||||
barcode: item.variant.barcode || "",
|
|
||||||
sku: item.variant_sku || "",
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
// TODO: should be optional in the enpoint?
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
tracking_number: "TODO",
|
|
||||||
tracking_url: "TODO",
|
|
||||||
label_url: "TODO",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
order: {}, // TODO ?
|
|
||||||
order_id: order.id, // TEMP link for now
|
|
||||||
})
|
})
|
||||||
|
|
||||||
handleSuccess(`/orders/${order.id}`)
|
handleSuccess(`/orders/${order.id}`)
|
||||||
@@ -252,36 +212,36 @@ export function OrderCreateFulfillmentForm({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*<div className="mt-8 pt-8 ">*/}
|
<div className="mt-8 pt-8 ">
|
||||||
{/* <Form.Field*/}
|
<Form.Field
|
||||||
{/* control={form.control}*/}
|
control={form.control}
|
||||||
{/* name="send_notification"*/}
|
name="send_notification"
|
||||||
{/* render={({ field: { onChange, value, ...field } }) => {*/}
|
render={({ field: { onChange, value, ...field } }) => {
|
||||||
{/* return (*/}
|
return (
|
||||||
{/* <Form.Item>*/}
|
<Form.Item>
|
||||||
{/* <div className="flex items-center justify-between">*/}
|
<div className="flex items-center justify-between">
|
||||||
{/* <Form.Label>*/}
|
<Form.Label>
|
||||||
{/* {t("orders.returns.sendNotification")}*/}
|
{t("orders.returns.sendNotification")}
|
||||||
{/* </Form.Label>*/}
|
</Form.Label>
|
||||||
{/* <Form.Control>*/}
|
<Form.Control>
|
||||||
{/* <Form.Control>*/}
|
<Form.Control>
|
||||||
{/* <Switch*/}
|
<Switch
|
||||||
{/* checked={!!value}*/}
|
checked={!!value}
|
||||||
{/* onCheckedChange={onChange}*/}
|
onCheckedChange={onChange}
|
||||||
{/* {...field}*/}
|
{...field}
|
||||||
{/* />*/}
|
/>
|
||||||
{/* </Form.Control>*/}
|
</Form.Control>
|
||||||
{/* </Form.Control>*/}
|
</Form.Control>
|
||||||
{/* </div>*/}
|
</div>
|
||||||
{/* <Form.Hint className="!mt-1">*/}
|
<Form.Hint className="!mt-1">
|
||||||
{/* {t("orders.returns.sendNotificationHint")}*/}
|
{t("orders.fulfillment.sendNotificationHint")}
|
||||||
{/* </Form.Hint>*/}
|
</Form.Hint>
|
||||||
{/* <Form.ErrorMessage />*/}
|
<Form.ErrorMessage />
|
||||||
{/* </Form.Item>*/}
|
</Form.Item>
|
||||||
{/* )*/}
|
)
|
||||||
{/* }}*/}
|
}}
|
||||||
{/* />*/}
|
/>
|
||||||
{/*</div>*/}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
-3
@@ -21,10 +21,10 @@ import { Link } from "react-router-dom"
|
|||||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
import { Skeleton } from "../../../../../components/common/skeleton"
|
import { Skeleton } from "../../../../../components/common/skeleton"
|
||||||
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
||||||
import { useCancelFulfillment } from "../../../../../hooks/api/fulfillment"
|
|
||||||
import { useStockLocation } from "../../../../../hooks/api/stock-locations"
|
import { useStockLocation } from "../../../../../hooks/api/stock-locations"
|
||||||
import { formatProvider } from "../../../../../lib/format-provider"
|
import { formatProvider } from "../../../../../lib/format-provider"
|
||||||
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
||||||
|
import { useCancelOrderFulfillment } from "../../../../../hooks/api/orders"
|
||||||
|
|
||||||
type OrderFulfillmentSectionProps = {
|
type OrderFulfillmentSectionProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -186,7 +186,7 @@ const Fulfillment = ({
|
|||||||
statusTimestamp = fulfillment.shipped_at
|
statusTimestamp = fulfillment.shipped_at
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mutateAsync } = useCancelFulfillment(fulfillment.id)
|
const { mutateAsync } = useCancelOrderFulfillment(order.id, fulfillment.id)
|
||||||
|
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
if (fulfillment.shipped_at) {
|
if (fulfillment.shipped_at) {
|
||||||
@@ -206,7 +206,7 @@ const Fulfillment = ({
|
|||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
try {
|
try {
|
||||||
await mutateAsync(fulfillment.id)
|
await mutateAsync()
|
||||||
|
|
||||||
toast.success(t("general.success"), {
|
toast.success(t("general.success"), {
|
||||||
description: t("orders.fulfillment.toast.canceled"),
|
description: t("orders.fulfillment.toast.canceled"),
|
||||||
|
|||||||
+9
-12
@@ -15,6 +15,7 @@ import {
|
|||||||
getOrderFulfillmentStatus,
|
getOrderFulfillmentStatus,
|
||||||
getOrderPaymentStatus,
|
getOrderPaymentStatus,
|
||||||
} from "../../../../../lib/order-helpers"
|
} from "../../../../../lib/order-helpers"
|
||||||
|
import { useCancelOrder } from "../../../../../hooks/api/orders"
|
||||||
|
|
||||||
type OrderGeneralSectionProps = {
|
type OrderGeneralSectionProps = {
|
||||||
order: Order
|
order: Order
|
||||||
@@ -24,7 +25,7 @@ export const OrderGeneralSection = ({ order }: OrderGeneralSectionProps) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
|
||||||
const { mutateAsync } = { mutateAsync: () => {} } // cancel order
|
const { mutateAsync } = useCancelOrder(order.id)
|
||||||
|
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
const res = await prompt({
|
const res = await prompt({
|
||||||
@@ -40,7 +41,7 @@ export const OrderGeneralSection = ({ order }: OrderGeneralSectionProps) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await mutateAsync(undefined)
|
await mutateAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -60,17 +61,18 @@ export const OrderGeneralSection = ({ order }: OrderGeneralSectionProps) => {
|
|||||||
<div className="flex items-center gap-x-4">
|
<div className="flex items-center gap-x-4">
|
||||||
<div className="flex items-center gap-x-1.5">
|
<div className="flex items-center gap-x-1.5">
|
||||||
<PaymentBadge order={order} />
|
<PaymentBadge order={order} />
|
||||||
|
{/*TODO: SHOW ORDER STATUS INSTEAD OF FULFILLMENT STATUS HERE - if the last fulfillment is canceled it looks like the order is canceled*/}
|
||||||
<FulfillmentBadge order={order} />
|
<FulfillmentBadge order={order} />
|
||||||
</div>
|
</div>
|
||||||
<ActionMenu
|
<ActionMenu
|
||||||
groups={[
|
groups={[
|
||||||
{
|
{
|
||||||
actions: [
|
actions: [
|
||||||
// {
|
{
|
||||||
// label: t("actions.cancel"),
|
label: t("actions.cancel"),
|
||||||
// onClick: handleCancel,
|
onClick: handleCancel,
|
||||||
// icon: <XCircle />,
|
icon: <XCircle />,
|
||||||
// },
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -83,11 +85,6 @@ export const OrderGeneralSection = ({ order }: OrderGeneralSectionProps) => {
|
|||||||
const FulfillmentBadge = ({ order }: { order: Order }) => {
|
const FulfillmentBadge = ({ order }: { order: Order }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: revisit when Order<>Fulfillment are linked
|
|
||||||
*/
|
|
||||||
return null
|
|
||||||
|
|
||||||
const { label, color } = getOrderFulfillmentStatus(
|
const { label, color } = getOrderFulfillmentStatus(
|
||||||
t,
|
t,
|
||||||
order.fulfillment_status
|
order.fulfillment_status
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const DEFAULT_PROPERTIES = [
|
|||||||
"status",
|
"status",
|
||||||
"created_at",
|
"created_at",
|
||||||
"email",
|
"email",
|
||||||
// "fulfillment_status", // -> TODO replacement for this
|
|
||||||
// "payment_status", // -> TODO replacement for this
|
// "payment_status", // -> TODO replacement for this
|
||||||
"display_id",
|
"display_id",
|
||||||
"currency_code",
|
"currency_code",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const DEFAULT_PROPERTIES = [
|
|||||||
"created_at",
|
"created_at",
|
||||||
"email",
|
"email",
|
||||||
"display_id",
|
"display_id",
|
||||||
// "fulfillment_status", // -> TODO replacement for this
|
|
||||||
// "payment_status", // -> TODO replacement for this
|
// "payment_status", // -> TODO replacement for this
|
||||||
"total",
|
"total",
|
||||||
"currency_code",
|
"currency_code",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
|||||||
|
|
||||||
type CancelOrderFulfillmentStepInput = CancelOrderFulfillmentDTO
|
type CancelOrderFulfillmentStepInput = CancelOrderFulfillmentDTO
|
||||||
|
|
||||||
export const cancelOrderFulfillmentStepId = "cancel-order-fullfillment"
|
export const cancelOrderFulfillmentStepId = "cancel-order-fulfillment"
|
||||||
export const cancelOrderFulfillmentStep = createStep(
|
export const cancelOrderFulfillmentStep = createStep(
|
||||||
cancelOrderFulfillmentStepId,
|
cancelOrderFulfillmentStepId,
|
||||||
async (data: CancelOrderFulfillmentStepInput, { container }) => {
|
async (data: CancelOrderFulfillmentStepInput, { container }) => {
|
||||||
|
|||||||
@@ -87,12 +87,15 @@ function prepareInventoryUpdate({
|
|||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
for (const item of fulfillment.items) {
|
for (const item of fulfillment.items) {
|
||||||
|
// if this is `null` this means that item is from variant that has `manage_inventory` false
|
||||||
|
if (item.inventory_item_id) {
|
||||||
inventoryAdjustment.push({
|
inventoryAdjustment.push({
|
||||||
inventory_item_id: item.inventory_item_id as string,
|
inventory_item_id: item.inventory_item_id as string,
|
||||||
location_id: fulfillment.location_id,
|
location_id: fulfillment.location_id,
|
||||||
adjustment: item.quantity,
|
adjustment: item.quantity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inventoryAdjustment,
|
inventoryAdjustment,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Product } from "./product"
|
|||||||
import { ProductCollection } from "./product-collection"
|
import { ProductCollection } from "./product-collection"
|
||||||
import { Region } from "./region"
|
import { Region } from "./region"
|
||||||
import { Upload } from "./upload"
|
import { Upload } from "./upload"
|
||||||
|
import { Order } from "./order"
|
||||||
|
|
||||||
export class Admin {
|
export class Admin {
|
||||||
public invite: Invite
|
public invite: Invite
|
||||||
@@ -13,6 +14,7 @@ export class Admin {
|
|||||||
public product: Product
|
public product: Product
|
||||||
public upload: Upload
|
public upload: Upload
|
||||||
public region: Region
|
public region: Region
|
||||||
|
public order: Order
|
||||||
|
|
||||||
constructor(client: Client) {
|
constructor(client: Client) {
|
||||||
this.invite = new Invite(client)
|
this.invite = new Invite(client)
|
||||||
@@ -21,5 +23,6 @@ export class Admin {
|
|||||||
this.product = new Product(client)
|
this.product = new Product(client)
|
||||||
this.upload = new Upload(client)
|
this.upload = new Upload(client)
|
||||||
this.region = new Region(client)
|
this.region = new Region(client)
|
||||||
|
this.order = new Order(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
AdminCancelOrderFulfillment,
|
||||||
|
FindParams,
|
||||||
|
HttpTypes,
|
||||||
|
PaginatedResponse,
|
||||||
|
SelectParams,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { Client } from "../client"
|
||||||
|
import { ClientHeaders } from "../types"
|
||||||
|
|
||||||
|
export class Order {
|
||||||
|
private client: Client
|
||||||
|
constructor(client: Client) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieve(id: string, query?: SelectParams, headers?: ClientHeaders) {
|
||||||
|
return await this.client.fetch<{ order: HttpTypes.AdminOrder }>(
|
||||||
|
`/admin/orders/${id}`,
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async list(
|
||||||
|
queryParams?: FindParams & HttpTypes.AdminOrderFilters,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return await this.client.fetch<
|
||||||
|
PaginatedResponse<{ orders: HttpTypes.AdminOrder[] }>
|
||||||
|
>(`/admin/orders`, {
|
||||||
|
query: queryParams,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancel(id: string, headers?: ClientHeaders) {
|
||||||
|
return await this.client.fetch<{ order: HttpTypes.AdminOrder }>(
|
||||||
|
`/admin/orders/${id}/cancel`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFulfillment(
|
||||||
|
id: string,
|
||||||
|
body: HttpTypes.AdminCreateOrderFulfillment,
|
||||||
|
query?: SelectParams,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return await this.client.fetch<{ order: HttpTypes.AdminOrder }>(
|
||||||
|
`/admin/orders/${id}/fulfillments`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
query,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelFulfillment(
|
||||||
|
id: string,
|
||||||
|
fulfillmentId: string,
|
||||||
|
body: HttpTypes.AdminCancelOrderFulfillment,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return await this.client.fetch<{ order: HttpTypes.AdminOrder }>(
|
||||||
|
`/admin/orders/${id}/fulfillments/${fulfillmentId}/cancel`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
BaseOrder,
|
BaseOrder,
|
||||||
BaseOrderAddress,
|
BaseOrderAddress,
|
||||||
|
BaseOrderFilters,
|
||||||
BaseOrderLineItem,
|
BaseOrderLineItem,
|
||||||
BaseOrderShippingMethod,
|
BaseOrderShippingMethod,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
|
|
||||||
export interface AdminOrder extends BaseOrder {}
|
export interface AdminOrder extends BaseOrder {}
|
||||||
export interface AdminOrderLineItem extends BaseOrderLineItem {}
|
export interface AdminOrderLineItem extends BaseOrderLineItem {}
|
||||||
|
export interface AdminOrderFilters extends BaseOrderFilters {}
|
||||||
export interface AdminOrderAddress extends BaseOrderAddress {}
|
export interface AdminOrderAddress extends BaseOrderAddress {}
|
||||||
export interface AdminOrderShippingMethod extends BaseOrderShippingMethod {}
|
export interface AdminOrderShippingMethod extends BaseOrderShippingMethod {}
|
||||||
|
|
||||||
|
export interface AdminCreateOrderFulfillment {
|
||||||
|
items: { id: string; quantity: number }[]
|
||||||
|
location_id?: string
|
||||||
|
no_notification?: boolean
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminCancelOrderFulfillment {
|
||||||
|
no_notification?: boolean
|
||||||
|
}
|
||||||
@@ -1109,7 +1109,7 @@ export interface OrderDTO {
|
|||||||
raw_original_shipping_tax_total: BigNumberRawValue
|
raw_original_shipping_tax_total: BigNumberRawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentStatus =
|
export type PaymentStatus =
|
||||||
| "not_paid"
|
| "not_paid"
|
||||||
| "awaiting"
|
| "awaiting"
|
||||||
| "authorized"
|
| "authorized"
|
||||||
@@ -1121,7 +1121,7 @@ type PaymentStatus =
|
|||||||
| "canceled"
|
| "canceled"
|
||||||
| "requires_action"
|
| "requires_action"
|
||||||
|
|
||||||
type FulfillmentStatus =
|
export type FulfillmentStatus =
|
||||||
| "not_fulfilled"
|
| "not_fulfilled"
|
||||||
| "partially_fulfilled"
|
| "partially_fulfilled"
|
||||||
| "fulfilled"
|
| "fulfilled"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../../../types/routing"
|
} from "../../../../../types/routing"
|
||||||
|
|
||||||
export const GET = async (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest,
|
req: AuthenticatedMedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
+1
@@ -20,6 +20,7 @@ export const POST = async (
|
|||||||
const input = {
|
const input = {
|
||||||
...req.validatedBody,
|
...req.validatedBody,
|
||||||
order_id: req.params.id,
|
order_id: req.params.id,
|
||||||
|
fulfillment_id: req.params.fulfillment_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
await cancelOrderFulfillmentWorkflow(req.scope).run({
|
await cancelOrderFulfillmentWorkflow(req.scope).run({
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export const GET = async (
|
|||||||
req: AuthenticatedMedusaRequest,
|
req: AuthenticatedMedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
) => {
|
) => {
|
||||||
const worklow = getOrderDetailWorkflow(req.scope)
|
const workflow = getOrderDetailWorkflow(req.scope)
|
||||||
const { result } = await worklow.run({
|
const { result } = await workflow.run({
|
||||||
input: {
|
input: {
|
||||||
fields: req.remoteQueryConfig.fields,
|
fields: req.remoteQueryConfig.fields,
|
||||||
order_id: req.params.id,
|
order_id: req.params.id,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AdminCompleteOrder,
|
AdminCompleteOrder,
|
||||||
AdminGetOrdersOrderParams,
|
AdminGetOrdersOrderParams,
|
||||||
AdminGetOrdersParams,
|
AdminGetOrdersParams,
|
||||||
|
AdminOrderCancelFulfillment,
|
||||||
AdminOrderCreateFulfillment,
|
AdminOrderCreateFulfillment,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
@@ -78,9 +79,9 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
matcher: "/admin/orders/:id/fulfillments/cancel",
|
matcher: "/admin/orders/:id/fulfillments/:fulfillment_id/cancel",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
// validateAndTransformBody(),
|
validateAndTransformBody(AdminOrderCancelFulfillment),
|
||||||
validateAndTransformQuery(
|
validateAndTransformQuery(
|
||||||
AdminGetOrdersOrderParams,
|
AdminGetOrdersOrderParams,
|
||||||
QueryConfig.retrieveTransformQueryConfig
|
QueryConfig.retrieveTransformQueryConfig
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export type AdminOrderCreateShipmentType = z.infer<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export const AdminOrderCancelFulfillment = z.object({
|
export const AdminOrderCancelFulfillment = z.object({
|
||||||
fulfillment_id: z.string(),
|
|
||||||
no_notification: z.boolean().optional(),
|
no_notification: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user