fix(admin-ui, medusa): Allocations and fulfillments for variants without inventory items (#3660)
* fix fulfillment for order lines without inventory items * fix summary card on order for variants without inventory items * add changeset * minor fixes * remove variants without inventory items from allocate modal * naming * Update .changeset/kind-rings-wave.md Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> * remove line item indicator * cleanup --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
6
.changeset/kind-rings-wave.md
Normal file
6
.changeset/kind-rings-wave.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/admin-ui": patch
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
fix(admin-ui, medusa): resolve bugs for orders with variants without inventory items
|
||||
@@ -246,6 +246,10 @@ export const AllocationLineItem: React.FC<{
|
||||
}
|
||||
}, [variant, locationId, isLoading])
|
||||
|
||||
if (!variant.inventory?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const lineItemReservationCapacity =
|
||||
getFulfillableQuantity(item) - (reservedQuantity || 0)
|
||||
|
||||
@@ -256,6 +260,7 @@ export const AllocationLineItem: React.FC<{
|
||||
lineItemReservationCapacity,
|
||||
inventoryItemReservationCapacity
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="mt-8 flex w-full items-start justify-between">
|
||||
<div className="gap-x-base flex w-7/12">
|
||||
|
||||
@@ -22,7 +22,7 @@ const CreateFulfillmentItemsTable = ({
|
||||
items: LineItem[]
|
||||
quantities: Record<string, number>
|
||||
setQuantities: (quantities: Record<string, number>) => void
|
||||
locationId: string
|
||||
locationId?: string
|
||||
setErrors: (errors: React.SetStateAction<{}>) => void
|
||||
}) => {
|
||||
const handleQuantityUpdate = React.useCallback(
|
||||
@@ -80,6 +80,8 @@ const FulfillmentLine = ({
|
||||
{ enabled: isLocationFulfillmentEnabled }
|
||||
)
|
||||
|
||||
const hasInventoryItem = !!variant?.inventory.length
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isLocationFulfillmentEnabled) {
|
||||
refetch()
|
||||
@@ -100,7 +102,7 @@ const FulfillmentLine = ({
|
||||
|
||||
const { inventory } = variant
|
||||
|
||||
const locationInventory = inventory[0].location_levels?.find(
|
||||
const locationInventory = inventory[0]?.location_levels?.find(
|
||||
(inv) => inv.location_id === locationId
|
||||
)
|
||||
|
||||
@@ -138,11 +140,14 @@ const FulfillmentLine = ({
|
||||
}, [validQuantity, setErrors, item.id])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!availableQuantity) {
|
||||
if (!availableQuantity && hasInventoryItem) {
|
||||
handleQuantityUpdate(0, item.id)
|
||||
} else {
|
||||
handleQuantityUpdate(
|
||||
Math.min(getFulfillableQuantity(item), availableQuantity),
|
||||
Math.min(
|
||||
getFulfillableQuantity(item),
|
||||
...[hasInventoryItem ? availableQuantity : Number.MAX_VALUE]
|
||||
),
|
||||
item.id
|
||||
)
|
||||
}
|
||||
@@ -158,7 +163,9 @@ const FulfillmentLine = ({
|
||||
className={clsx(
|
||||
"rounded-rounded hover:bg-grey-5 mx-[-5px] mb-1 flex h-[64px] justify-between py-2 px-[5px]",
|
||||
{
|
||||
"pointer-events-none opacity-50": !availableQuantity,
|
||||
"pointer-events-none opacity-50":
|
||||
(!availableQuantity && hasInventoryItem) ||
|
||||
(!locationId && isLocationFulfillmentEnabled),
|
||||
}
|
||||
)}
|
||||
>
|
||||
@@ -185,10 +192,12 @@ const FulfillmentLine = ({
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<FeatureToggle featureFlag="inventoryService">
|
||||
<div className="inter-base-regular text-grey-50 mr-6 flex flex-col items-end whitespace-nowrap">
|
||||
<p>{availableQuantity || 0} available</p>
|
||||
<p>({inStockQuantity || 0} in stock)</p>
|
||||
</div>
|
||||
{hasInventoryItem && (
|
||||
<div className="inter-base-regular text-grey-50 mr-6 flex flex-col items-end whitespace-nowrap">
|
||||
<p>{availableQuantity || 0} available</p>
|
||||
<p>({inStockQuantity || 0} in stock)</p>
|
||||
</div>
|
||||
)}
|
||||
</FeatureToggle>
|
||||
<InputField
|
||||
type="number"
|
||||
@@ -202,7 +211,10 @@ const FulfillmentLine = ({
|
||||
</span>
|
||||
}
|
||||
value={quantities[item.id]}
|
||||
max={Math.min(availableQuantity || 0, getFulfillableQuantity(item))}
|
||||
max={Math.min(
|
||||
getFulfillableQuantity(item),
|
||||
...[hasInventoryItem ? availableQuantity || 0 : Number.MAX_VALUE]
|
||||
)}
|
||||
onChange={(e) =>
|
||||
handleQuantityUpdate(e.target.valueAsNumber, item.id)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import {
|
||||
AdminGetVariantsVariantInventoryRes,
|
||||
Order,
|
||||
VariantInventory,
|
||||
} from "@medusajs/medusa"
|
||||
import { DisplayTotal, PaymentDetails } from "../templates"
|
||||
import { Order } from "@medusajs/medusa"
|
||||
import React, { useContext, useMemo } from "react"
|
||||
|
||||
import { ActionType } from "../../../../components/molecules/actionables"
|
||||
@@ -9,11 +13,13 @@ import BodyCard from "../../../../components/organisms/body-card"
|
||||
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
|
||||
import { OrderEditContext } from "../../edit/context"
|
||||
import OrderLine from "../order-line"
|
||||
import { ReservationItemDTO } from "@medusajs/types"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
import StatusIndicator from "../../../../components/fundamentals/status-indicator"
|
||||
import { sum } from "lodash"
|
||||
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
||||
import { useMedusa } from "medusa-react"
|
||||
import useToggleState from "../../../../hooks/use-toggle-state"
|
||||
import { ReservationItemDTO } from "@medusajs/types"
|
||||
|
||||
type SummaryCardProps = {
|
||||
order: Order
|
||||
@@ -28,9 +34,49 @@ const SummaryCard: React.FC<SummaryCardProps> = ({ order, reservations }) => {
|
||||
} = useToggleState()
|
||||
|
||||
const { showModal } = useContext(OrderEditContext)
|
||||
const { client } = useMedusa()
|
||||
const { isFeatureEnabled } = useFeatureFlag()
|
||||
const inventoryEnabled = isFeatureEnabled("inventoryService")
|
||||
|
||||
const [variantInventoryMap, setVariantInventoryMap] = React.useState<
|
||||
Map<string, VariantInventory>
|
||||
>(new Map())
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!inventoryEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const fetchInventory = async () => {
|
||||
const inventory = await Promise.all(
|
||||
order.items.map(async (item) => {
|
||||
if (!item.variant_id) {
|
||||
return
|
||||
}
|
||||
return await client.admin.variants.getInventory(item.variant_id)
|
||||
})
|
||||
)
|
||||
|
||||
setVariantInventoryMap(
|
||||
new Map(
|
||||
inventory
|
||||
.filter(
|
||||
(
|
||||
inventoryItem
|
||||
// eslint-disable-next-line max-len
|
||||
): inventoryItem is Response<AdminGetVariantsVariantInventoryRes> =>
|
||||
!!inventoryItem
|
||||
)
|
||||
.map((i) => {
|
||||
return [i.variant.id, i.variant]
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fetchInventory()
|
||||
}, [order.items, inventoryEnabled, client.admin.variants])
|
||||
|
||||
const reservationItemsMap = useMemo(() => {
|
||||
if (!reservations?.length || !inventoryEnabled) {
|
||||
return {}
|
||||
@@ -52,6 +98,13 @@ const SummaryCard: React.FC<SummaryCardProps> = ({ order, reservations }) => {
|
||||
|
||||
const allItemsReserved = useMemo(() => {
|
||||
return order.items.every((item) => {
|
||||
if (
|
||||
!item.variant_id ||
|
||||
!variantInventoryMap.get(item.variant_id)?.inventory.length
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
const reservations = reservationItemsMap[item.id]
|
||||
|
||||
return (
|
||||
@@ -61,7 +114,7 @@ const SummaryCard: React.FC<SummaryCardProps> = ({ order, reservations }) => {
|
||||
item.quantity - (item.fulfilled_quantity || 0))
|
||||
)
|
||||
})
|
||||
}, [reservationItemsMap, order])
|
||||
}, [order.items, variantInventoryMap, reservationItemsMap])
|
||||
|
||||
const { hasMovements, swapAmount, manualRefund, swapRefund, returnRefund } =
|
||||
useMemo(() => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LineItem, ReservationItemDTO } from "@medusajs/medusa"
|
||||
import { useAdminStockLocations, useAdminVariantsInventory } from "medusa-react"
|
||||
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import CheckCircleFillIcon from "../../../../components/fundamentals/icons/check-circle-fill-icon"
|
||||
@@ -10,7 +11,6 @@ import Tooltip from "../../../../components/atoms/tooltip"
|
||||
import WarningCircleIcon from "../../../../components/fundamentals/icons/warning-circle"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
import { sum } from "lodash"
|
||||
import { useAdminStockLocations } from "medusa-react"
|
||||
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
||||
|
||||
type OrderLineProps = {
|
||||
@@ -84,6 +84,13 @@ const ReservationIndicator = ({
|
||||
reservations?: ReservationItemDTO[]
|
||||
lineItem: LineItem
|
||||
}) => {
|
||||
const { variant, isLoading, isFetching } = useAdminVariantsInventory(
|
||||
lineItem.variant_id!,
|
||||
{
|
||||
enabled: !!lineItem?.variant_id,
|
||||
}
|
||||
)
|
||||
|
||||
const { stock_locations } = useAdminStockLocations({
|
||||
id: reservations?.map((r) => r.location_id) || [],
|
||||
})
|
||||
@@ -99,6 +106,15 @@ const ReservationIndicator = ({
|
||||
|
||||
const awaitingAllocation = allocatableSum - reservationsSum
|
||||
|
||||
if (
|
||||
isLoading ||
|
||||
isFetching ||
|
||||
!lineItem.variant_id ||
|
||||
!variant?.inventory.length
|
||||
) {
|
||||
return <div className="w-[20px]" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={awaitingAllocation ? "text-rose-50" : "text-grey-40"}>
|
||||
<Tooltip
|
||||
|
||||
@@ -514,11 +514,15 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
return
|
||||
}
|
||||
|
||||
const itemsToValidate = items.filter((item) => item.variant_id)
|
||||
const itemsToValidate = items.filter((item) => !!item.variant_id)
|
||||
|
||||
for (const item of itemsToValidate) {
|
||||
const pvInventoryItems = await this.listByVariant(item.variant_id!)
|
||||
|
||||
if (!pvInventoryItems.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
const [inventoryLevels, inventoryLevelCount] =
|
||||
await this.inventoryService_.listInventoryLevels({
|
||||
inventory_item_id: pvInventoryItems.map((i) => i.inventory_item_id),
|
||||
|
||||
Reference in New Issue
Block a user