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:
Philip Korsholm
2023-03-27 20:36:59 +02:00
committed by GitHub
parent 53c4a43ca2
commit feaf8d2e19
7 changed files with 58 additions and 16 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/admin-ui": patch
"@medusajs/medusa": patch
---
fix(medusa, admin-ui): refine create-fulfillment flow
@@ -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])
) )