feat(dashboard): inventory create flow (#7650)

This commit is contained in:
Frane Polić authored and GitHub committed 2024-06-10 17:37:36 +02:00
1 parent 69410162f6
commit f08f0d6cc9
37 files changed
+1010 -271

No files matched your search

@@ -5,7 +5,12 @@ import { DataGridCellContainer } from "./data-grid-cell-container"
export const DataGridNumberCell = <TData, TValue = any>({ export const DataGridNumberCell = <TData, TValue = any>({
field, field,
context, context,
}: DataGridCellProps<TData, TValue>) => { ...rest
}: DataGridCellProps<TData, TValue> & {
min?: number
max?: number
placeholder?: string
}) => {
const { register, attributes, container } = useDataGridCell({ const { register, attributes, container } = useDataGridCell({
field, field,
context, context,
@@ -18,7 +23,19 @@ export const DataGridNumberCell = <TData, TValue = any>({
type="number" type="number"
{...register(field, { {...register(field, {
valueAsNumber: true, valueAsNumber: true,
onChange: (e) => {
if (e.target.value) {
const parsedValue = Number(e.target.value)
if (Number.isNaN(parsedValue)) {
return undefined
}
return parsedValue
}
},
})} })}
className="h-full w-full bg-transparent p-2 text-right [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
{...rest}
/> />
</DataGridCellContainer> </DataGridCellContainer>
) )
@@ -1,4 +1,4 @@
import { AdminInventoryItemResponse, InventoryNext } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { import {
QueryKey, QueryKey,
UseMutationOptions, UseMutationOptions,
@@ -6,19 +6,9 @@ import {
useMutation, useMutation,
useQuery, useQuery,
} from "@tanstack/react-query" } from "@tanstack/react-query"
import { import { FetchError } from "@medusajs/js-sdk"
InventoryItemLocationBatch,
UpdateInventoryItemReq,
UpdateInventoryLevelReq,
} from "../../types/api-payloads"
import {
InventoryItemDeleteRes,
InventoryItemListRes,
InventoryItemLocationLevelsRes,
InventoryItemRes,
} from "../../types/api-responses"
import { client } from "../../lib/client" import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client" import { queryClient } from "../../lib/query-client"
import { queryKeysFactory } from "../../lib/query-key-factory" import { queryKeysFactory } from "../../lib/query-key-factory"
@@ -36,16 +26,16 @@ export const useInventoryItems = (
query?: Record<string, any>, query?: Record<string, any>,
options?: Omit< options?: Omit<
UseQueryOptions< UseQueryOptions<
InventoryItemListRes, HttpTypes.AdminInventoryItemListResponse,
Error, FetchError,
InventoryItemListRes, HttpTypes.AdminInventoryItemListResponse,
QueryKey QueryKey
>, >,
"queryKey" | "queryFn" "queryKey" | "queryFn"
> >
) => { ) => {
const { data, ...rest } = useQuery({ const { data, ...rest } = useQuery({
queryFn: () => client.inventoryItems.list(query), queryFn: () => sdk.admin.inventoryItem.list(query),
queryKey: inventoryItemsQueryKeys.list(query), queryKey: inventoryItemsQueryKeys.list(query),
...options, ...options,
}) })
@@ -57,12 +47,17 @@ export const useInventoryItem = (
id: string, id: string,
query?: Record<string, any>, query?: Record<string, any>,
options?: Omit< options?: Omit<
UseQueryOptions<InventoryItemRes, Error, InventoryItemRes, QueryKey>, UseQueryOptions<
HttpTypes.AdminInventoryItemResponse,
FetchError,
HttpTypes.AdminInventoryItemResponse,
QueryKey
>,
"queryKey" | "queryFn" "queryKey" | "queryFn"
> >
) => { ) => {
const { data, ...rest } = useQuery({ const { data, ...rest } = useQuery({
queryFn: () => client.inventoryItems.retrieve(id, query), queryFn: () => sdk.admin.inventoryItem.retrieve(id, query),
queryKey: inventoryItemsQueryKeys.detail(id), queryKey: inventoryItemsQueryKeys.detail(id),
...options, ...options,
}) })
@@ -70,13 +65,37 @@ export const useInventoryItem = (
return { ...data, ...rest } return { ...data, ...rest }
} }
export const useUpdateInventoryItem = ( export const useCreateInventoryItem = (
id: string, options?: UseMutationOptions<
options?: UseMutationOptions<InventoryItemRes, Error, UpdateInventoryItemReq> HttpTypes.AdminInventoryItemResponse,
FetchError,
HttpTypes.AdminCreateInventoryItem
>
) => { ) => {
return useMutation({ return useMutation({
mutationFn: (payload: InventoryNext.UpdateInventoryItemInput) => mutationFn: (payload: HttpTypes.AdminCreateInventoryItem) =>
client.inventoryItems.update(id, payload), sdk.admin.inventoryItem.create(payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(),
})
options?.onSuccess?.(data, variables, context)
},
...options,
})
}
export const useUpdateInventoryItem = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminInventoryItemResponse,
FetchError,
HttpTypes.AdminUpdateInventoryItem
>
) => {
return useMutation({
mutationFn: (payload: HttpTypes.AdminUpdateInventoryItem) =>
sdk.admin.inventoryItem.update(id, payload),
onSuccess: (data, variables, context) => { onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(), queryKey: inventoryItemsQueryKeys.lists(),
@@ -92,10 +111,14 @@ export const useUpdateInventoryItem = (
export const useDeleteInventoryItem = ( export const useDeleteInventoryItem = (
id: string, id: string,
options?: UseMutationOptions<InventoryItemDeleteRes, Error, void> options?: UseMutationOptions<
HttpTypes.AdminInventoryItemDeleteResponse,
FetchError,
void
>
) => { ) => {
return useMutation({ return useMutation({
mutationFn: () => client.inventoryItems.delete(id), mutationFn: () => sdk.admin.inventoryItem.delete(id),
onSuccess: (data, variables, context) => { onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(), queryKey: inventoryItemsQueryKeys.lists(),
@@ -112,14 +135,15 @@ export const useDeleteInventoryItem = (
export const useDeleteInventoryItemLevel = ( export const useDeleteInventoryItemLevel = (
inventoryItemId: string, inventoryItemId: string,
locationId: string, locationId: string,
options?: UseMutationOptions<InventoryItemDeleteRes, Error, void> options?: UseMutationOptions<
HttpTypes.AdminInventoryItemDeleteResponse,
FetchError,
void
>
) => { ) => {
return useMutation({ return useMutation({
mutationFn: () => mutationFn: () =>
client.inventoryItems.deleteInventoryItemLevel( sdk.admin.inventoryItem.deleteLevel(inventoryItemId, locationId),
inventoryItemId,
locationId
),
onSuccess: (data, variables, context) => { onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(), queryKey: inventoryItemsQueryKeys.lists(),
@@ -141,17 +165,16 @@ export const useInventoryItemLevels = (
query?: Record<string, any>, query?: Record<string, any>,
options?: Omit< options?: Omit<
UseQueryOptions< UseQueryOptions<
InventoryItemLocationLevelsRes, HttpTypes.AdminInventoryLevelListResponse,
Error, FetchError,
InventoryItemLocationLevelsRes, HttpTypes.AdminInventoryLevelListResponse,
QueryKey QueryKey
>, >,
"queryKey" | "queryFn" "queryKey" | "queryFn"
> >
) => { ) => {
const { data, ...rest } = useQuery({ const { data, ...rest } = useQuery({
queryFn: () => queryFn: () => sdk.admin.inventoryItem.listLevels(inventoryItemId, query),
client.inventoryItems.listLocationLevels(inventoryItemId, query),
queryKey: inventoryItemLevelsQueryKeys.detail(inventoryItemId), queryKey: inventoryItemLevelsQueryKeys.detail(inventoryItemId),
...options, ...options,
}) })
@@ -159,22 +182,18 @@ export const useInventoryItemLevels = (
return { ...data, ...rest } return { ...data, ...rest }
} }
export const useUpdateInventoryItemLevel = ( export const useUpdateInventoryLevel = (
inventoryItemId: string, inventoryItemId: string,
locationId: string, locationId: string,
options?: UseMutationOptions< options?: UseMutationOptions<
AdminInventoryItemResponse, HttpTypes.AdminInventoryItemResponse,
Error, FetchError,
UpdateInventoryLevelReq HttpTypes.AdminUpdateInventoryLevel
> >
) => { ) => {
return useMutation({ return useMutation({
mutationFn: (payload: UpdateInventoryLevelReq) => mutationFn: (payload: HttpTypes.AdminUpdateInventoryLevel) =>
client.inventoryItems.updateInventoryLevel( sdk.admin.inventoryItem.updateLevel(inventoryItemId, locationId, payload),
inventoryItemId,
locationId,
payload
),
onSuccess: (data, variables, context) => { onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(), queryKey: inventoryItemsQueryKeys.lists(),
@@ -191,17 +210,17 @@ export const useUpdateInventoryItemLevel = (
}) })
} }
export const useBatchInventoryItemLevels = ( export const useBatchUpdateInventoryLevels = (
inventoryItemId: string, inventoryItemId: string,
options?: UseMutationOptions< options?: UseMutationOptions<
InventoryItemLocationLevelsRes, HttpTypes.AdminInventoryItemResponse,
Error, FetchError,
InventoryItemLocationBatch HttpTypes.AdminBatchUpdateInventoryLevelLocation
> >
) => { ) => {
return useMutation({ return useMutation({
mutationFn: (payload: InventoryItemLocationBatch) => mutationFn: (payload: HttpTypes.AdminBatchUpdateInventoryLevelLocation) =>
client.inventoryItems.batchPostLocationLevels(inventoryItemId, payload), sdk.admin.inventoryItem.batchUpdateLevels(inventoryItemId, payload),
onSuccess: (data, variables, context) => { onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(), queryKey: inventoryItemsQueryKeys.lists(),
@@ -403,6 +403,15 @@
"associatedVariants": "Associated variants", "associatedVariants": "Associated variants",
"manageLocations": "Manage locations", "manageLocations": "Manage locations",
"deleteWarning": "You are about to delete an inventory item. This action cannot be undone.", "deleteWarning": "You are about to delete an inventory item. This action cannot be undone.",
"create": {
"title": "Add item",
"details": "Details",
"availability": "Availability",
"locations": "Locations",
"attributes": "Attributes",
"requiresShipping": "Requires shipping",
"requiresShippingHint": "Does the inventory item require shipping?"
},
"reservation": { "reservation": {
"header": "Reservation of {{itemName}}", "header": "Reservation of {{itemName}}",
"editItemDetails": "Edit item details", "editItemDetails": "Edit item details",
@@ -6,7 +6,6 @@ import { currencies } from "./currencies"
import { customerGroups } from "./customer-groups" import { customerGroups } from "./customer-groups"
import { fulfillmentProviders } from "./fulfillment-providers" import { fulfillmentProviders } from "./fulfillment-providers"
import { fulfillments } from "./fulfillments" import { fulfillments } from "./fulfillments"
import { inventoryItems } from "./inventory"
import { invites } from "./invites" import { invites } from "./invites"
import { payments } from "./payments" import { payments } from "./payments"
import { priceLists } from "./price-lists" import { priceLists } from "./price-lists"
@@ -40,7 +39,6 @@ export const client = {
productTags: tags, productTags: tags,
users: users, users: users,
invites: invites, invites: invites,
inventoryItems: inventoryItems,
reservations: reservations, reservations: reservations,
fulfillments: fulfillments, fulfillments: fulfillments,
fulfillmentProviders: fulfillmentProviders, fulfillmentProviders: fulfillmentProviders,
@@ -1,108 +0,0 @@
import {
AdminInventoryItemListResponse,
AdminInventoryItemResponse,
AdminInventoryLevelResponse,
} from "@medusajs/types"
import {
CreateInventoryItemReq,
InventoryItemLocationBatch,
UpdateInventoryItemReq,
UpdateInventoryLevelReq,
} from "../../types/api-payloads"
import {
InventoryItemLevelDeleteRes,
InventoryItemLocationLevelsRes,
} from "../../types/api-responses"
import { deleteRequest, getRequest, postRequest } from "./common"
async function retrieveInventoryItem(id: string, query?: Record<string, any>) {
return getRequest<AdminInventoryItemResponse>(
`/admin/inventory-items/${id}`,
query
)
}
async function listInventoryItems(query?: Record<string, any>) {
return getRequest<AdminInventoryItemListResponse>(
`/admin/inventory-items`,
query
)
}
async function createInventoryItem(payload: CreateInventoryItemReq) {
return postRequest<AdminInventoryItemResponse>(
`/admin/inventory-items`,
payload
)
}
async function updateInventoryItem(
id: string,
payload: UpdateInventoryItemReq
) {
return postRequest<AdminInventoryItemResponse>(
`/admin/inventory-items/${id}`,
payload
)
}
async function deleteInventoryItem(id: string) {
return deleteRequest<AdminInventoryItemResponse>(
`/admin/inventory-items/${id}`
)
}
async function listInventoryItemLevels(
id: string,
query?: Record<string, any>
) {
return getRequest<InventoryItemLocationLevelsRes>(
`/admin/inventory-items/${id}/location-levels`,
query
)
}
async function deleteInventoryItemLevel(
inventoryItemId: string,
locationId: string
) {
return deleteRequest<InventoryItemLevelDeleteRes>(
`/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`
)
}
async function updateInventoryLevel(
inventoryItemId: string,
locationId: string,
payload: UpdateInventoryLevelReq
) {
return postRequest<AdminInventoryItemResponse>(
`/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`,
payload
)
}
async function batchPostLocationLevels(
inventoryItemId: string,
payload: InventoryItemLocationBatch
) {
return postRequest<AdminInventoryLevelResponse>(
`/admin/inventory-items/${inventoryItemId}/location-levels/batch`,
{
create: payload.creates,
delete: payload.deletes,
}
)
}
export const inventoryItems = {
retrieve: retrieveInventoryItem,
list: listInventoryItems,
create: createInventoryItem,
update: updateInventoryItem,
delete: deleteInventoryItem,
listLocationLevels: listInventoryItemLevels,
updateInventoryLevel,
deleteInventoryItemLevel,
batchPostLocationLevels,
}
@@ -13,7 +13,7 @@ import { ProtectedRoute } from "../../components/authentication/protected-route"
import { MainLayout } from "../../components/layout/main-layout" import { MainLayout } from "../../components/layout/main-layout"
import { SettingsLayout } from "../../components/layout/settings-layout" import { SettingsLayout } from "../../components/layout/settings-layout"
import { ErrorBoundary } from "../../components/utilities/error-boundary" import { ErrorBoundary } from "../../components/utilities/error-boundary"
import { InventoryItemRes, PriceListRes } from "../../types/api-responses" import { PriceListRes } from "../../types/api-responses"
import { RouteExtensions } from "./route-extensions" import { RouteExtensions } from "./route-extensions"
import { SettingsExtensions } from "./settings-extensions" import { SettingsExtensions } from "./settings-extensions"
@@ -467,12 +467,19 @@ export const RouteMap: RouteObject[] = [
{ {
path: "", path: "",
lazy: () => import("../../routes/inventory/inventory-list"), lazy: () => import("../../routes/inventory/inventory-list"),
children: [
{
path: "create",
lazy: () =>
import("../../routes/inventory/inventory-create"),
},
],
}, },
{ {
path: ":id", path: ":id",
lazy: () => import("../../routes/inventory/inventory-detail"), lazy: () => import("../../routes/inventory/inventory-detail"),
handle: { handle: {
crumb: (data: InventoryItemRes) => crumb: (data: HttpTypes.AdminInventoryItemResponse) =>
data.inventory_item.title ?? data.inventory_item.sku, data.inventory_item.title ?? data.inventory_item.sku,
}, },
children: [ children: [
@@ -0,0 +1,71 @@
import { useMemo } from "react"
import { StockLocationDTO } from "@medusajs/types"
import { UseFormReturn } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { DataGridRoot } from "../../../../../components/data-grid/data-grid-root"
import { createDataGridHelper } from "../../../../../components/data-grid/utils"
import { DataGridReadOnlyCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-readonly-cell"
import { DataGridNumberCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-number-cell"
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
type Props = {
form: UseFormReturn<{}>
}
export const CreateInventoryAvailabilityForm = ({ form }: Props) => {
const { isPending, stock_locations = [] } = useStockLocations({ limit: 999 })
const columns = useColumns()
return (
<div className="flex size-full flex-col divide-y overflow-hidden">
{isPending ? (
<div>Loading...</div>
) : (
<DataGridRoot columns={columns} data={stock_locations} state={form} />
)}
</div>
)
}
const columnHelper = createDataGridHelper<StockLocationDTO>()
const useColumns = () => {
const { t } = useTranslation()
return useMemo(
() => [
columnHelper.column({
id: "location",
header: () => (
<div className="flex size-full items-center overflow-hidden">
<span className="truncate">{t("locations.domain")}</span>
</div>
),
cell: ({ row }) => {
return (
<DataGridReadOnlyCell>{row.original.name}</DataGridReadOnlyCell>
)
},
disableHidding: true,
}),
columnHelper.column({
id: "in-stock",
name: t("fields.inStock"),
header: t("fields.inStock"),
cell: (context) => {
return (
<DataGridNumberCell
min={0}
placeholder="0"
context={context}
field={`locations.${context.row.original.id}`}
/>
)
},
}),
],
[t]
)
}
@@ -0,0 +1,553 @@
import { zodResolver } from "@hookform/resolvers/zod"
import React, { useEffect } from "react"
import { useForm } from "react-hook-form"
import * as zod from "zod"
import {
Button,
Heading,
ProgressStatus,
ProgressTabs,
clx,
Input,
Textarea,
Switch,
toast,
} from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import {
RouteFocusModal,
useRouteModal,
} from "../../../../../components/route-modal"
import { CreateInventoryAvailabilityForm } from "./create-inventory-availability-form"
import { CountrySelect } from "../../../../../components/inputs/country-select"
import { Form } from "../../../../../components/common/form"
import {
inventoryItemsQueryKeys,
useCreateInventoryItem,
} from "../../../../../hooks/api/inventory"
import { sdk } from "../../../../../lib/client"
import { optionalInt } from "../../../../../lib/validation"
import { queryClient } from "../../../../../lib/query-client"
enum Tab {
DETAILS = "details",
AVAILABILITY = "availability",
}
type StepStatus = {
[key in Tab]: ProgressStatus
}
const CreateInventoryItemSchema = zod.object({
title: zod.string().min(1),
sku: zod.string().optional(),
hs_code: zod.string().optional(),
weight: optionalInt,
length: optionalInt,
height: optionalInt,
width: optionalInt,
origin_country: zod.string().optional(),
mid_code: zod.string().optional(),
material: zod.string().optional(),
description: zod.string().optional(),
requires_shipping: zod.boolean().optional(),
thumbnail: zod.string().optional(),
locations: zod.record(zod.string(), zod.number().optional()).optional(),
// metadata: zod.record(zod.string(), zod.unknown()).optional(),
})
type CreateInventoryItemFormProps = {}
export function CreateInventoryItemForm({}: CreateInventoryItemFormProps) {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
const [tab, setTab] = React.useState<Tab>(Tab.DETAILS)
const form = useForm<zod.infer<typeof CreateInventoryItemSchema>>({
defaultValues: {
title: "",
sku: "",
hs_code: "",
weight: "",
length: "",
height: "",
width: "",
origin_country: "",
mid_code: "",
material: "",
description: "",
requires_shipping: true,
thumbnail: "",
},
resolver: zodResolver(CreateInventoryItemSchema),
})
const { mutateAsync: createInventoryItem, isPending: isLoading } =
useCreateInventoryItem()
const handleSubmit = form.handleSubmit(async (data) => {
let { locations, ...payload } = data
for (const k in payload) {
if (payload[k] === "") {
delete payload[k]
continue
}
if (["weight", "length", "height", "width"].includes(k)) {
payload[k] = parseInt(payload[k])
}
}
try {
const { inventory_item } = await createInventoryItem(payload)
try {
await sdk.admin.inventoryItem.batchUpdateLevels(inventory_item.id, {
create: Object.entries(locations)
.filter(([_, quantiy]) => !!quantiy)
.map(([location_id, stocked_quantity]) => ({
location_id,
stocked_quantity,
})),
})
await queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(),
})
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
handleSuccess()
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
dismissLabel: t("actions.close"),
})
}
})
const [status, setStatus] = React.useState<StepStatus>({
[Tab.AVAILABILITY]: "not-started",
[Tab.DETAILS]: "not-started",
})
const onTabChange = React.useCallback(async (value: Tab) => {
const result = await form.trigger()
if (!result) {
return
}
setTab(value)
}, [])
const onNext = React.useCallback(async () => {
const result = await form.trigger()
if (!result) {
return
}
switch (tab) {
case Tab.DETAILS: {
setTab(Tab.AVAILABILITY)
break
}
case Tab.AVAILABILITY:
break
}
}, [tab])
useEffect(() => {
if (form.formState.isDirty) {
setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "in-progress" }))
} else {
setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "not-started" }))
}
}, [form.formState.isDirty])
useEffect(() => {
if (tab === Tab.DETAILS && form.formState.isDirty) {
setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "in-progress" }))
}
if (tab === Tab.AVAILABILITY) {
setStatus((prev) => ({
...prev,
[Tab.DETAILS]: "completed",
[Tab.AVAILABILITY]: "in-progress",
}))
}
}, [tab])
return (
<RouteFocusModal.Form form={form}>
<form
className="flex h-full flex-col overflow-hidden"
onSubmit={handleSubmit}
>
<ProgressTabs
value={tab}
className="h-full"
onValueChange={(tab) => onTabChange(tab as Tab)}
>
<RouteFocusModal.Header>
<ProgressTabs.List className="border-ui-border-base -my-2 ml-2 min-w-0 flex-1 border-l">
<ProgressTabs.Trigger
value={Tab.DETAILS}
status={status[Tab.DETAILS]}
className="w-full max-w-[200px]"
>
<span className="w-full cursor-auto overflow-hidden text-ellipsis whitespace-nowrap">
{t("inventory.create.details")}
</span>
</ProgressTabs.Trigger>
<ProgressTabs.Trigger
value={Tab.AVAILABILITY}
className="w-full max-w-[200px]"
status={status[Tab.AVAILABILITY]}
>
<span className="w-full overflow-hidden text-ellipsis whitespace-nowrap">
{t("inventory.create.availability")}
</span>
</ProgressTabs.Trigger>
</ProgressTabs.List>
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button variant="secondary" size="small">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
size="small"
className="whitespace-nowrap"
isLoading={isLoading}
onClick={tab !== Tab.AVAILABILITY ? onNext : undefined}
key={tab === Tab.AVAILABILITY ? "details" : "pricing"}
type={tab === Tab.AVAILABILITY ? "submit" : "button"}
>
{tab === Tab.AVAILABILITY
? t("actions.save")
: t("general.next")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body
className={clx(
"flex h-full w-fit flex-col items-center divide-y overflow-hidden",
{ "mx-auto": tab === Tab.DETAILS }
)}
>
<ProgressTabs.Content value={Tab.DETAILS} className="h-full w-full">
<div className="container mx-auto w-[720px] px-1 py-8">
<Heading level="h2" className="mb-12 mt-8 text-2xl">
{t("inventory.create.title")}
</Heading>
<div className="flex flex-col gap-y-6">
<div className="grid grid-cols-1 gap-x-3 gap-y-6 lg:grid-cols-2">
<Form.Field
control={form.control}
name="title"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("fields.title")}</Form.Label>
<Form.Control>
<Input
{...field}
placeholder={t("fields.title")}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="sku"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("fields.sku")}</Form.Label>
<Form.Control>
<Input {...field} placeholder="sku-123" />
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<div className="col-span-2">
<Form.Field
control={form.control}
name="description"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.description.label")}
</Form.Label>
<Form.Control>
<Textarea
{...field}
placeholder="The item description"
/>
</Form.Control>
</Form.Item>
)
}}
/>
</div>
<div className="col-span-2">
<Form.Field
control={form.control}
name="requires_shipping"
render={({ field: { value, onChange, ...field } }) => {
return (
<Form.Item>
<div className="flex flex-col gap-y-1">
<div className="flex items-center justify-between">
<Form.Label>
{t("inventory.create.requiresShipping")}
</Form.Label>
<Form.Control>
<Switch
checked={value}
onCheckedChange={(checked) =>
onChange(!!checked)
}
{...field}
/>
</Form.Control>
</div>
<Form.Hint>
{t("inventory.create.requiresShippingHint")}
</Form.Hint>
</div>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div>
{/*<Form.Field*/}
{/* className="col-span-1"*/}
{/* control={form.control}*/}
{/* name="location_ids"*/}
{/* render={({ field }) => {*/}
{/* return (*/}
{/* <Form.Item>*/}
{/* <Form.Label optional>*/}
{/* {t("inventory.create.locations")}*/}
{/* </Form.Label>*/}
{/* <Form.Control>*/}
{/* <Combobox*/}
{/* {...field}*/}
{/* multiple*/}
{/* options={locations.options}*/}
{/* searchValue={locations.searchValue}*/}
{/* onSearchValueChange={*/}
{/* locations.onSearchValueChange*/}
{/* }*/}
{/* fetchNextPage={locations.fetchNextPage}*/}
{/* />*/}
{/* </Form.Control>*/}
{/* <Form.ErrorMessage />*/}
{/* </Form.Item>*/}
{/* )*/}
{/* }}*/}
{/*/>*/}
</div>
<Heading level="h3" className="my-6">
{t("inventory.create.attributes")}
</Heading>
<div className="grid grid-cols-1 gap-x-4 gap-y-8 lg:grid-cols-2">
<Form.Field
control={form.control}
name="width"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.width.label")}
</Form.Label>
<Form.Control>
<Input
{...field}
type="number"
min={0}
placeholder="100"
/>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="length"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.length.label")}
</Form.Label>
<Form.Control>
<Input
{...field}
type="number"
min={0}
placeholder="100"
/>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="height"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.height.label")}
</Form.Label>
<Form.Control>
<Input
{...field}
type="number"
min={0}
placeholder="100"
/>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="weight"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.weight.label")}
</Form.Label>
<Form.Control>
<Input
{...field}
type="number"
min={0}
placeholder="100"
/>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="mid_code"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.mid_code.label")}
</Form.Label>
<Form.Control>
<Input {...field} />
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="hs_code"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.hs_code.label")}
</Form.Label>
<Form.Control>
<Input {...field} />
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="origin_country"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.countryOrigin.label")}
</Form.Label>
<Form.Control>
<CountrySelect {...field} />
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="material"
render={({ field }) => {
return (
<Form.Item>
<Form.Label optional>
{t("products.fields.material.label")}
</Form.Label>
<Form.Control>
<Input {...field} />
</Form.Control>
</Form.Item>
)
}}
/>
</div>
</div>
</div>
</ProgressTabs.Content>
<ProgressTabs.Content
value={Tab.AVAILABILITY}
className="h-full w-full"
style={{ width: "100vw" }}
>
<CreateInventoryAvailabilityForm form={form} />
</ProgressTabs.Content>
</RouteFocusModal.Body>
</ProgressTabs>
</form>
</RouteFocusModal.Form>
)
}
@@ -0,0 +1 @@
export * from "./create-inventory-item-form.tsx"
@@ -0,0 +1 @@
export { InventoryCreate as Component } from "./inventory-create"
@@ -0,0 +1,10 @@
import { RouteFocusModal } from "../../../components/route-modal"
import { CreateInventoryItemForm } from "./components/create-inventory-item-form"
export function InventoryCreate() {
return (
<RouteFocusModal>
<CreateInventoryItemForm />
</RouteFocusModal>
)
}
@@ -1,22 +1,20 @@
import * as zod from "zod" import * as zod from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Input, Text, toast } from "@medusajs/ui" import { Button, Input, Text, toast } from "@medusajs/ui"
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/types" import { InventoryLevelDTO, StockLocationDTO, HttpTypes } from "@medusajs/types"
import { import {
RouteDrawer, RouteDrawer,
useRouteModal, useRouteModal,
} from "../../../../../../components/route-modal" } from "../../../../../../components/route-modal"
import { Form } from "../../../../../../components/common/form" import { Form } from "../../../../../../components/common/form"
import { InventoryItemRes } from "../../../../../../types/api-responses"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useUpdateInventoryItemLevel } from "../../../../../../hooks/api/inventory" import { useUpdateInventoryLevel } from "../../../../../../hooks/api/inventory"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
type AdjustInventoryFormProps = { type AdjustInventoryFormProps = {
item: InventoryItemRes["inventory_item"] item: HttpTypes.AdminInventoryItem
level: InventoryLevelDTO level: InventoryLevelDTO
location: StockLocationDTO location: StockLocationDTO
} }
@@ -48,8 +46,8 @@ export const AdjustInventoryForm = ({
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const AdjustInventorySchema = z.object({ const AdjustInventorySchema = zod.object({
stocked_quantity: z.number().min(level.reserved_quantity), stocked_quantity: zod.number().min(level.reserved_quantity),
}) })
const form = useForm<zod.infer<typeof AdjustInventorySchema>>({ const form = useForm<zod.infer<typeof AdjustInventorySchema>>({
@@ -61,7 +59,7 @@ export const AdjustInventoryForm = ({
const stockedQuantityUpdate = form.watch("stocked_quantity") const stockedQuantityUpdate = form.watch("stocked_quantity")
const { mutateAsync, isPending: isLoading } = useUpdateInventoryItemLevel( const { mutateAsync, isPending: isLoading } = useUpdateInventoryLevel(
item.id, item.id,
level.location_id level.location_id
) )
@@ -5,7 +5,7 @@ import {
RouteDrawer, RouteDrawer,
useRouteModal, useRouteModal,
} from "../../../../../../components/route-modal" } from "../../../../../../components/route-modal"
import { useBatchInventoryItemLevels } from "../../../../../../hooks/api/inventory" import { useBatchUpdateInventoryLevels } from "../../../../../../hooks/api/inventory"
import { useFieldArray, useForm } from "react-hook-form" import { useFieldArray, useForm } from "react-hook-form"
import { InventoryItemRes } from "../../../../../../types/api-responses" import { InventoryItemRes } from "../../../../../../types/api-responses"
@@ -64,7 +64,7 @@ export const ManageLocationsForm = ({
name: "locations", name: "locations",
}) })
const { mutateAsync } = useBatchInventoryItemLevels(item.id) const { mutateAsync } = useBatchUpdateInventoryLevels(item.id)
const handleSubmit = form.handleSubmit(async ({ locations }) => { const handleSubmit = form.handleSubmit(async ({ locations }) => {
// Changes in selected locations // Changes in selected locations
@@ -96,10 +96,10 @@ export const ManageLocationsForm = ({
try { try {
await mutateAsync({ await mutateAsync({
creates: selectedLocations.map((location_id) => ({ create: selectedLocations.map((location_id) => ({
location_id, location_id,
})), })),
deletes: unselectedLocations, delete: unselectedLocations,
}) })
handleSuccess() handleSuccess()
@@ -33,7 +33,6 @@ export const InventoryDetail = () => {
if (isLoading) { if (isLoading) {
return <div>Loading...</div> return <div>Loading...</div>
} }
if (isError || !inventory_item) { if (isError || !inventory_item) {
if (error) { if (error) {
throw error throw error
@@ -1,13 +1,14 @@
import { LoaderFunctionArgs } from "react-router-dom" import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { inventoryItemsQueryKeys } from "../../../hooks/api/inventory" import { inventoryItemsQueryKeys } from "../../../hooks/api/inventory"
import { client } from "../../../lib/client" import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client" import { queryClient } from "../../../lib/query-client"
import { InventoryItemRes } from "../../../types/api-responses"
const inventoryDetailQuery = (id: string) => ({ const inventoryDetailQuery = (id: string) => ({
queryKey: inventoryItemsQueryKeys.detail(id), queryKey: inventoryItemsQueryKeys.detail(id),
queryFn: async () => queryFn: async () =>
client.inventoryItems.retrieve(id, { sdk.admin.inventoryItem.retrieve(id, {
fields: "*variant", fields: "*variant",
}), }),
}) })
@@ -17,7 +18,8 @@ export const inventoryItemLoader = async ({ params }: LoaderFunctionArgs) => {
const query = inventoryDetailQuery(id!) const query = inventoryDetailQuery(id!)
return ( return (
queryClient.getQueryData<InventoryItemRes>(query.queryKey) ?? queryClient.getQueryData<HttpTypes.AdminInventoryItemResponse>(
(await queryClient.fetchQuery(query)) query.queryKey
) ?? (await queryClient.fetchQuery(query))
) )
} }
@@ -1,4 +1,4 @@
import { Container, Heading } from "@medusajs/ui" import { Button, Container, Heading } from "@medusajs/ui"
import { InventoryNext } from "@medusajs/types" import { InventoryNext } from "@medusajs/types"
import { DataTable } from "../../../../components/table/data-table" import { DataTable } from "../../../../components/table/data-table"
@@ -8,6 +8,7 @@ import { useInventoryTableColumns } from "./use-inventory-table-columns"
import { useInventoryTableFilters } from "./use-inventory-table-filters" import { useInventoryTableFilters } from "./use-inventory-table-filters"
import { useInventoryTableQuery } from "./use-inventory-table-query" import { useInventoryTableQuery } from "./use-inventory-table-query"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom"
const PAGE_SIZE = 20 const PAGE_SIZE = 20
@@ -48,6 +49,9 @@ export const InventoryListTable = () => {
<Container className="divide-y p-0"> <Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4"> <div className="flex items-center justify-between px-6 py-4">
<Heading>{t("inventory.domain")}</Heading> <Heading>{t("inventory.domain")}</Heading>
<Button size="small" variant="secondary" asChild>
<Link to="create">{t("actions.create")}</Link>
</Button>
</div> </div>
<DataTable <DataTable
table={table} table={table}
@@ -110,27 +110,9 @@ export type BatchUpdatePromotionRulesReq = { rules: UpdatePromotionRuleDTO[] }
export type CreateCampaignReq = CreateCampaignDTO export type CreateCampaignReq = CreateCampaignDTO
export type UpdateCampaignReq = UpdateCampaignDTO export type UpdateCampaignReq = UpdateCampaignDTO
// Inventory Items
export type CreateInventoryItemReq = InventoryNext.CreateInventoryItemInput
export type UpdateInventoryItemReq = Omit<
InventoryNext.UpdateInventoryItemInput,
"id"
>
// Reservations // Reservations
export type UpdateReservationReq = Omit< export type UpdateReservationReq = Omit<
InventoryNext.UpdateReservationItemInput, InventoryNext.UpdateReservationItemInput,
"id" "id"
> >
export type CreateReservationReq = InventoryNext.CreateReservationItemInput export type CreateReservationReq = InventoryNext.CreateReservationItemInput
// Inventory Item Levels
export type InventoryItemLocationBatch = {
creates: { location_id: string; stocked_quantity?: number }[]
deletes: string[]
}
export type UpdateInventoryLevelReq = {
reserved_quantity?: number
stocked_quantity?: number
}
@@ -176,27 +176,6 @@ export type WorkflowExecutionListRes = {
export type TaxRegionDeleteRes = DeleteRes export type TaxRegionDeleteRes = DeleteRes
export type TaxRateDeleteRes = DeleteRes export type TaxRateDeleteRes = DeleteRes
// Inventory Items
export type InventoryItemRes = {
inventory_item: InventoryNext.InventoryItemDTO & {
stocked_quantity: number
reserved_quantity: number
location_levels?: InventoryNext.InventoryLevelDTO[]
variant?: ProductVariantDTO | ProductVariantDTO[]
}
}
export type InventoryItemListRes = {
inventory_items: InventoryNext.InventoryItemDTO[]
} & ListRes
export type InventoryItemDeleteRes = DeleteRes
export type InventoryItemLocationLevelsRes = {
inventory_levels: InventoryNext.InventoryLevelDTO[]
} & ListRes
export type InventoryItemLevelDeleteRes = DeleteRes
// Reservations // Reservations
export type ReservationItemDeleteRes = DeleteRes export type ReservationItemDeleteRes = DeleteRes
@@ -39,14 +39,9 @@ export const deleteInventoryLevelsFromItemAndLocationsStep = createStep(
} }
const deletedIds = items.map((i) => i.id) const deletedIds = items.map((i) => i.id)
const deleted = await service.softDeleteInventoryLevels(deletedIds) await service.softDeleteInventoryLevels(deletedIds)
return new StepResponse( return new StepResponse(void 0, deletedIds)
{
[Modules.INVENTORY]: deleted,
} as DeleteEntityInput,
deletedIds
)
}, },
async (prevLevelIds, { container }) => { async (prevLevelIds, { container }) => {
if (!prevLevelIds?.length) { if (!prevLevelIds?.length) {
@@ -5,8 +5,6 @@ import {
deleteInventoryLevelsFromItemAndLocationsStep, deleteInventoryLevelsFromItemAndLocationsStep,
} from "../steps" } from "../steps"
import { removeRemoteLinkStep } from "../../common"
interface WorkflowInput { interface WorkflowInput {
creates: InventoryNext.CreateInventoryLevelInput[] creates: InventoryNext.CreateInventoryLevelInput[]
deletes: { inventory_item_id: string; location_id: string }[] deletes: { inventory_item_id: string; location_id: string }[]
@@ -17,9 +15,7 @@ export const bulkCreateDeleteLevelsWorkflowId =
export const bulkCreateDeleteLevelsWorkflow = createWorkflow( export const bulkCreateDeleteLevelsWorkflow = createWorkflow(
bulkCreateDeleteLevelsWorkflowId, bulkCreateDeleteLevelsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<InventoryLevelDTO[]> => { (input: WorkflowData<WorkflowInput>): WorkflowData<InventoryLevelDTO[]> => {
const deleted = deleteInventoryLevelsFromItemAndLocationsStep(input.deletes) deleteInventoryLevelsFromItemAndLocationsStep(input.deletes)
removeRemoteLinkStep(deleted)
return createInventoryLevelsStep(input.creates) return createInventoryLevelsStep(input.creates)
} }
+3
View File
@@ -14,6 +14,7 @@ import { StockLocation } from "./stock-location"
import { TaxRate } from "./tax-rate" import { TaxRate } from "./tax-rate"
import { TaxRegion } from "./tax-region" import { TaxRegion } from "./tax-region"
import { Upload } from "./upload" import { Upload } from "./upload"
import { InventoryItem } from "./inventory-item"
export class Admin { export class Admin {
public invite: Invite public invite: Invite
@@ -28,6 +29,7 @@ export class Admin {
public fulfillment: Fulfillment public fulfillment: Fulfillment
public shippingOption: ShippingOption public shippingOption: ShippingOption
public shippingProfile: ShippingProfile public shippingProfile: ShippingProfile
public inventoryItem: InventoryItem
public order: Order public order: Order
public taxRate: TaxRate public taxRate: TaxRate
public taxRegion: TaxRegion public taxRegion: TaxRegion
@@ -45,6 +47,7 @@ export class Admin {
this.fulfillment = new Fulfillment(client) this.fulfillment = new Fulfillment(client)
this.shippingOption = new ShippingOption(client) this.shippingOption = new ShippingOption(client)
this.shippingProfile = new ShippingProfile(client) this.shippingProfile = new ShippingProfile(client)
this.inventoryItem = new InventoryItem(client)
this.order = new Order(client) this.order = new Order(client)
this.taxRate = new TaxRate(client) this.taxRate = new TaxRate(client)
this.taxRegion = new TaxRegion(client) this.taxRegion = new TaxRegion(client)
@@ -0,0 +1,135 @@
import { HttpTypes, SelectParams } from "@medusajs/types"
import { Client } from "../client"
import { ClientHeaders } from "../types"
export class InventoryItem {
private client: Client
constructor(client: Client) {
this.client = client
}
async create(
body: HttpTypes.AdminCreateInventoryItem,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminInventoryItemResponse>(
`/admin/inventory-items`,
{
method: "POST",
headers,
body,
query,
}
)
}
async update(
id: string,
body: HttpTypes.AdminUpdateInventoryItem,
query?: SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminInventoryItemResponse>(
`/admin/inventory-items/${id}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async list(
query?: HttpTypes.AdminInventoryItemParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminInventoryItemListResponse>(
`/admin/inventory-items`,
{
query,
headers,
}
)
}
async retrieve(id: string, query?: SelectParams, headers?: ClientHeaders) {
return await this.client.fetch<HttpTypes.AdminInventoryItemResponse>(
`/admin/inventory-items/${id}`,
{
query,
headers,
}
)
}
async delete(id: string, headers?: ClientHeaders) {
return await this.client.fetch<HttpTypes.AdminInventoryItemDeleteResponse>(
`/admin/inventory-items/${id}`,
{
method: "DELETE",
headers,
}
)
}
async listLevels(
id: string,
query?: HttpTypes.AdminInventoryLevelFilters,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminInventoryLevelListResponse>(
`/admin/inventory-items/${id}/location-levels`,
{
query,
headers,
}
)
}
async updateLevel(
id: string,
locationId: string,
body: HttpTypes.AdminUpdateInventoryLevel,
query?: SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminInventoryItemResponse>(
`/admin/inventory-items/${id}/location-levels/${locationId}`,
{
method: "POST",
headers,
body,
query,
}
)
}
async deleteLevel(id: string, locationId: string, headers?: ClientHeaders) {
return await this.client.fetch<HttpTypes.AdminInventoryItemDeleteResponse>(
`/admin/inventory-items/${id}/location-levels/${locationId}`,
{
method: "DELETE",
headers,
}
)
}
async batchUpdateLevels(
id: string,
body: HttpTypes.AdminBatchUpdateInventoryLevelLocation,
query?: SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminInventoryItemResponse>(
`/admin/inventory-items/${id}/location-levels/batch`,
{
method: "POST",
headers,
body,
query,
}
)
}
}
+1
View File
@@ -9,6 +9,7 @@ export * from "./fulfillment"
export * from "./fulfillment-provider" export * from "./fulfillment-provider"
export * from "./fulfillment-set" export * from "./fulfillment-set"
export * from "./inventory" export * from "./inventory"
export * from "./inventory-level"
export * from "./invite" export * from "./invite"
export * from "./order" export * from "./order"
export * from "./payment" export * from "./payment"
@@ -0,0 +1,10 @@
export interface InventoryLevel {
id: string
inventory_item_id: string
location_id: string
stocked_quantity: number
reserved_quantity: number
available_quantity: number
incoming_quantity: number
metadata?: Record<string, unknown> | null
}
@@ -0,0 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
@@ -0,0 +1,16 @@
export interface AdminUpdateInventoryLevel {
stocked_quantity?: number
incoming_quantity?: number
}
export interface AdminCreateInventoryLevel {
location_id: string
stocked_quantity?: number
incoming_quantity?: number
}
export interface AdminBatchUpdateInventoryLevelLocation {
delete?: string[] // a list of location_ids
update?: never // TODO - not implemented // AdminUpdateInventoryLevel[]
create?: AdminCreateInventoryLevel[]
}
@@ -0,0 +1,5 @@
import { FindParams } from "../../common"
export interface AdminInventoryLevelFilters extends FindParams {
location_id?: string | string[]
}
@@ -0,0 +1,10 @@
import { PaginatedResponse } from "../../common"
import { InventoryLevel } from "./entities"
export interface AdminInventoryLevelResponse {
inventory_level: InventoryLevel
}
export type AdminInventoryLevelListResponse = PaginatedResponse<{
inventory_levels: InventoryLevel[]
}>
@@ -0,0 +1 @@
export * from "./admin"
@@ -1,6 +1,4 @@
import { PaginatedResponse } from "../common" export interface AdminInventoryItem {
interface InventoryItemResponse {
id: string id: string
sku?: string | null sku?: string | null
origin_country?: string | null origin_country?: string | null
@@ -17,11 +15,3 @@ interface InventoryItemResponse {
thumbnail?: string | null thumbnail?: string | null
metadata?: Record<string, unknown> | null metadata?: Record<string, unknown> | null
} }
export interface AdminInventoryItemResponse {
inventory_item: InventoryItemResponse
}
export type AdminInventoryItemListResponse = PaginatedResponse<{
inventory_items: InventoryItemResponse[]
}>
@@ -0,0 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
@@ -0,0 +1,18 @@
export interface AdminCreateInventoryItem {
sku?: string
hs_code?: string
weight?: number
length?: number
height?: number
width?: number
origin_country?: string
mid_code?: string
material?: string
title?: string
description?: string
requires_shipping?: boolean
thumbnail?: string
metadata?: Record<string, unknown>
}
export interface AdminUpdateInventoryItem extends AdminCreateInventoryItem {}
@@ -0,0 +1,17 @@
import { FindParams } from "../../common"
import { OperatorMap } from "../../../dal"
export interface AdminInventoryItemParams extends FindParams {
id?: string | string[]
q?: string
sku?: string | string[]
origin_country?: string | string[]
hs_code?: string | string[]
material?: string | string[]
requires_shipping?: boolean
weight?: number | OperatorMap<number>
length?: number | OperatorMap<number>
height?: number | OperatorMap<number>
width?: number | OperatorMap<number>
location_levels?: Record<"location_id", string | string[]>
}
@@ -0,0 +1,12 @@
import { DeleteResponse, PaginatedResponse } from "../../common"
import { AdminInventoryItem } from "./entities"
export interface AdminInventoryItemResponse {
inventory_item: AdminInventoryItem
}
export type AdminInventoryItemListResponse = PaginatedResponse<{
inventory_items: AdminInventoryItem[]
}>
export type AdminInventoryItemDeleteResponse = DeleteResponse<"inventory_item">
@@ -1,2 +1 @@
export * from "./inventory" export * from "./admin"
export * from "./inventory-level"
@@ -1,21 +0,0 @@
import { AdminInventoryItemResponse } from "./inventory"
import { PaginatedResponse } from "../common"
interface InventoryLevelResponse {
id: string
inventory_item_id: string
location_id: string
stocked_quantity: number
reserved_quantity: number
available_quantity: number
incoming_quantity: number
metadata?: Record<string, unknown> | null
}
export interface AdminInventoryLevelResponse {
inventory_item: AdminInventoryItemResponse
}
export type AdminInventoryLevelListResponse = PaginatedResponse<{
inventory_levels: InventoryLevelResponse[]
}>
@@ -41,12 +41,14 @@ export const GET = async (
req: MedusaRequest<AdminGetInventoryLocationLevelsParamsType>, req: MedusaRequest<AdminGetInventoryLocationLevelsParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({ const query = remoteQueryObjectFromString({
entryPoint: "inventory_levels", entryPoint: "inventory_levels",
variables: { variables: {
filters: req.filterableFields, filters: { ...req.filterableFields, inventory_item_id: id },
...req.remoteQueryConfig.pagination, ...req.remoteQueryConfig.pagination,
}, },
fields: req.remoteQueryConfig.fields, fields: req.remoteQueryConfig.fields,