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:
Philip Korsholm
2023-03-31 15:53:56 +02:00
committed by GitHub
parent ca3b32d53c
commit eed784d7d0
6 changed files with 111 additions and 15 deletions

View 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

View File

@@ -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">

View File

@@ -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)
}

View File

@@ -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(() => {

View File

@@ -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

View File

@@ -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),