From eed784d7d0b58aeddc9f6f5ea56fe80c608b22f5 Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Fri, 31 Mar 2023 15:53:56 +0200 Subject: [PATCH] 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> --- .changeset/kind-rings-wave.md | 6 ++ .../allocations/allocate-items-modal.tsx | 5 ++ .../details/create-fulfillment/item-table.tsx | 32 ++++++---- .../orders/details/detail-cards/summary.tsx | 59 ++++++++++++++++++- .../orders/details/order-line/index.tsx | 18 +++++- .../src/services/product-variant-inventory.ts | 6 +- 6 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 .changeset/kind-rings-wave.md diff --git a/.changeset/kind-rings-wave.md b/.changeset/kind-rings-wave.md new file mode 100644 index 0000000000..2a4ce246b3 --- /dev/null +++ b/.changeset/kind-rings-wave.md @@ -0,0 +1,6 @@ +--- +"@medusajs/admin-ui": patch +"@medusajs/medusa": patch +--- + +fix(admin-ui, medusa): resolve bugs for orders with variants without inventory items diff --git a/packages/admin-ui/ui/src/domain/orders/details/allocations/allocate-items-modal.tsx b/packages/admin-ui/ui/src/domain/orders/details/allocations/allocate-items-modal.tsx index f101f966f0..2af4ac1d9e 100644 --- a/packages/admin-ui/ui/src/domain/orders/details/allocations/allocate-items-modal.tsx +++ b/packages/admin-ui/ui/src/domain/orders/details/allocations/allocate-items-modal.tsx @@ -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 (
diff --git a/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/item-table.tsx b/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/item-table.tsx index 99f32b9806..19593b40fa 100644 --- a/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/item-table.tsx +++ b/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/item-table.tsx @@ -22,7 +22,7 @@ const CreateFulfillmentItemsTable = ({ items: LineItem[] quantities: Record setQuantities: (quantities: Record) => 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 = ({
-
-

{availableQuantity || 0} available

-

({inStockQuantity || 0} in stock)

-
+ {hasInventoryItem && ( +
+

{availableQuantity || 0} available

+

({inStockQuantity || 0} in stock)

+
+ )}
} 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) } diff --git a/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx b/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx index bce293ea10..eaaff40d69 100644 --- a/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx +++ b/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx @@ -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 = ({ order, reservations }) => { } = useToggleState() const { showModal } = useContext(OrderEditContext) + const { client } = useMedusa() const { isFeatureEnabled } = useFeatureFlag() const inventoryEnabled = isFeatureEnabled("inventoryService") + const [variantInventoryMap, setVariantInventoryMap] = React.useState< + Map + >(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 => + !!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 = ({ 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 = ({ order, reservations }) => { item.quantity - (item.fulfilled_quantity || 0)) ) }) - }, [reservationItemsMap, order]) + }, [order.items, variantInventoryMap, reservationItemsMap]) const { hasMovements, swapAmount, manualRefund, swapRefund, returnRefund } = useMemo(() => { diff --git a/packages/admin-ui/ui/src/domain/orders/details/order-line/index.tsx b/packages/admin-ui/ui/src/domain/orders/details/order-line/index.tsx index 08ffa20d62..bf99b4a8a8 100644 --- a/packages/admin-ui/ui/src/domain/orders/details/order-line/index.tsx +++ b/packages/admin-ui/ui/src/domain/orders/details/order-line/index.tsx @@ -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
+ } + return (
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),