feat(dashboard): inventory create flow (#7650)
This commit is contained in:
1 parent
69410162f6
commit
f08f0d6cc9
37 files changed
+1010
-271
No files matched your search
+18
-1
@@ -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: [
|
||||||
|
|||||||
+71
@@ -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]
|
||||||
|
)
|
||||||
|
}
|
||||||
+553
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./create-inventory-item-form.tsx"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { InventoryCreate as Component } from "./inventory-create"
|
||||||
+10
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+7
-9
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -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()
|
||||||
|
|||||||
-1
@@ -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))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+5
-1
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
-11
@@ -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,
|
||||||
|
|||||||
Reference in new issue
Block a user