feat(admin-ui, medusa): Improve fulfillment validation (#3541)
* validate that an inventory level exists as well * improve create-fulfillment handling in admin * pass along location id rather than inventory level id * add changeset * remove dependency
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-ui": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa, admin-ui): refine create-fulfillment flow
|
||||||
+1
-1
@@ -76,7 +76,7 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
|
|||||||
deleteLocations.map(async (location: InventoryLevelDTO) => {
|
deleteLocations.map(async (location: InventoryLevelDTO) => {
|
||||||
await client.admin.inventoryItems.deleteLocationLevel(
|
await client.admin.inventoryItems.deleteLocationLevel(
|
||||||
inventoryItemId!,
|
inventoryItemId!,
|
||||||
location.id
|
location.location_id
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -287,8 +287,8 @@ export const AllocationLineItem: React.FC<{
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p>{availableQuantity || "N/A"} available</p>
|
<p>{availableQuantity || 0} available</p>
|
||||||
<p>({inStockQuantity || "N/A"} in stock)</p>
|
<p>({inStockQuantity || 0} in stock)</p>
|
||||||
</div>
|
</div>
|
||||||
<InputField
|
<InputField
|
||||||
{...register(path(`quantity`), { valueAsNumber: true })}
|
{...register(path(`quantity`), { valueAsNumber: true })}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const CreateFulfillmentModal: React.FC<CreateFulfillmentModalProps> = ({
|
|||||||
value?: string
|
value?: string
|
||||||
label?: string
|
label?: string
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
const [metadata, setMetadata] = useState<MetadataField[]>([
|
const [metadata, setMetadata] = useState<MetadataField[]>([
|
||||||
{ key: "", value: "" },
|
{ key: "", value: "" },
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import FeatureToggle from "../../../../components/fundamentals/feature-toggle"
|
|||||||
import ImagePlaceholder from "../../../../components/fundamentals/image-placeholder"
|
import ImagePlaceholder from "../../../../components/fundamentals/image-placeholder"
|
||||||
import InputField from "../../../../components/molecules/input"
|
import InputField from "../../../../components/molecules/input"
|
||||||
import { LineItem } from "@medusajs/medusa"
|
import { LineItem } from "@medusajs/medusa"
|
||||||
|
import clsx from "clsx"
|
||||||
import { useAdminVariantsInventory } from "medusa-react"
|
import { useAdminVariantsInventory } from "medusa-react"
|
||||||
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
||||||
|
|
||||||
@@ -24,16 +25,20 @@ const CreateFulfillmentItemsTable = ({
|
|||||||
locationId: string
|
locationId: string
|
||||||
setErrors: (errors: React.SetStateAction<{}>) => void
|
setErrors: (errors: React.SetStateAction<{}>) => void
|
||||||
}) => {
|
}) => {
|
||||||
const handleQuantityUpdate = (value: number, id: string) => {
|
const handleQuantityUpdate = React.useCallback(
|
||||||
let newQuantities = { ...quantities }
|
(value: number, id: string) => {
|
||||||
|
let newQuantities = { ...quantities }
|
||||||
|
|
||||||
newQuantities = {
|
newQuantities = {
|
||||||
...newQuantities,
|
...newQuantities,
|
||||||
[id]: value,
|
[id]: value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setQuantities(newQuantities)
|
||||||
|
},
|
||||||
|
[quantities, setQuantities]
|
||||||
|
)
|
||||||
|
|
||||||
setQuantities(newQuantities)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{items.map((item, idx) => {
|
{items.map((item, idx) => {
|
||||||
@@ -118,12 +123,31 @@ const FulfillmentLine = ({
|
|||||||
})
|
})
|
||||||
}, [validQuantity, setErrors, item.id])
|
}, [validQuantity, setErrors, item.id])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!availableQuantity) {
|
||||||
|
handleQuantityUpdate(0, item.id)
|
||||||
|
} else {
|
||||||
|
handleQuantityUpdate(
|
||||||
|
Math.min(getFulfillableQuantity(item), availableQuantity),
|
||||||
|
item.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Note: we can't add handleQuantityUpdate to the dependency array as it will cause an infinite loop
|
||||||
|
}, [availableQuantity, item, item.id])
|
||||||
|
|
||||||
if (getFulfillableQuantity(item) <= 0) {
|
if (getFulfillableQuantity(item) <= 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-rounded hover:bg-grey-5 mx-[-5px] mb-1 flex h-[64px] justify-between py-2 px-[5px]">
|
<div
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex justify-center space-x-4">
|
<div className="flex justify-center space-x-4">
|
||||||
<div className="rounded-rounded flex h-[48px] w-[36px] overflow-hidden">
|
<div className="rounded-rounded flex h-[48px] w-[36px] overflow-hidden">
|
||||||
{item.thumbnail ? (
|
{item.thumbnail ? (
|
||||||
@@ -148,8 +172,8 @@ const FulfillmentLine = ({
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FeatureToggle featureFlag="inventoryService">
|
<FeatureToggle featureFlag="inventoryService">
|
||||||
<div className="inter-base-regular text-grey-50 mr-6 flex flex-col items-end whitespace-nowrap">
|
<div className="inter-base-regular text-grey-50 mr-6 flex flex-col items-end whitespace-nowrap">
|
||||||
<p>{availableQuantity || "N/A"} available</p>
|
<p>{availableQuantity || 0} available</p>
|
||||||
<p>({inStockQuantity || "N/A"} in stock)</p>
|
<p>({inStockQuantity || 0} in stock)</p>
|
||||||
</div>
|
</div>
|
||||||
</FeatureToggle>
|
</FeatureToggle>
|
||||||
<InputField
|
<InputField
|
||||||
@@ -164,7 +188,7 @@ const FulfillmentLine = ({
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
value={quantities[item.id]}
|
value={quantities[item.id]}
|
||||||
max={getFulfillableQuantity(item)}
|
max={Math.min(availableQuantity || 0, getFulfillableQuantity(item))}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleQuantityUpdate(e.target.valueAsNumber, item.id)
|
handleQuantityUpdate(e.target.valueAsNumber, item.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,11 @@ export const updateInventoryAndReservations = async (
|
|||||||
items.map(({ item, quantity }) => ({ ...item, quantity } as LineItem)),
|
items.map(({ item, quantity }) => ({ ...item, quantity } as LineItem)),
|
||||||
locationId
|
locationId
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
fulfillments.map(async ({ items }) => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
items.map(async ({ item, quantity }) => {
|
items.map(async ({ item, quantity }) => {
|
||||||
if (!item.variant_id) {
|
if (!item.variant_id) {
|
||||||
|
|||||||
@@ -514,12 +514,19 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
|||||||
for (const item of itemsToValidate) {
|
for (const item of itemsToValidate) {
|
||||||
const pvInventoryItems = await this.listByVariant(item.variant_id!)
|
const pvInventoryItems = await this.listByVariant(item.variant_id!)
|
||||||
|
|
||||||
const [inventoryLevels] =
|
const [inventoryLevels, inventoryLevelCount] =
|
||||||
await this.inventoryService_.listInventoryLevels({
|
await this.inventoryService_.listInventoryLevels({
|
||||||
inventory_item_id: pvInventoryItems.map((i) => i.inventory_item_id),
|
inventory_item_id: pvInventoryItems.map((i) => i.inventory_item_id),
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!inventoryLevelCount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Inventory item for ${item.title} not found at location`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const pviMap: Map<string, ProductVariantInventoryItem> = new Map(
|
const pviMap: Map<string, ProductVariantInventoryItem> = new Map(
|
||||||
pvInventoryItems.map((pvi) => [pvi.inventory_item_id, pvi])
|
pvInventoryItems.map((pvi) => [pvi.inventory_item_id, pvi])
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user