Fix(medusa, admin-ui): Order allocations (#3419)

**What**
- Fix `delete-reservation` endpoint to allow deletions of reservations 
- remove `inventory_item_id` from reservation update call to properly update reservation
- invalidate all list caches to properly update order overview after an allocation has been created
- Fix overlap for long product titles in edit-allocation-modal

Fixes CORE-1214
This commit is contained in:
Philip Korsholm
2023-03-09 17:07:49 +00:00
committed by GitHub
parent cdbc5ff3d8
commit 80b95a2300
7 changed files with 107 additions and 69 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): Resolve on scope instead of req
+5
View File
@@ -0,0 +1,5 @@
---
"medusa-react": patch
---
fix(medusa-react): invalidate all reservations list queries
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/admin-ui": patch
---
Fix(admin-ui): Fix minor bugs related to the edit-allocation modal
@@ -1,24 +1,25 @@
import React, { useEffect, useMemo } from "react"
import { Controller, useForm, useWatch } from "react-hook-form"
import { LineItem, Order, ReservationItemDTO } from "@medusajs/medusa"
import FocusModal from "../../../../components/molecules/modal/focus-modal"
import Button from "../../../../components/fundamentals/button"
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
import Select from "../../../../components/molecules/select/next-select/select"
import { NestedForm, nestedForm } from "../../../../utils/nested-form"
import React, { useEffect, useMemo } from "react"
import {
useAdminCreateReservation,
useAdminStockLocations,
useAdminVariantsInventory,
useMedusa,
} from "medusa-react"
import { Controller, useForm, useWatch } from "react-hook-form"
import Thumbnail from "../../../../components/atoms/thumbnail"
import Button from "../../../../components/fundamentals/button"
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
import FocusModal from "../../../../components/molecules/modal/focus-modal"
import InputField from "../../../../components/molecules/input"
import { NestedForm, nestedForm } from "../../../../utils/nested-form"
import { sum } from "lodash"
import Select from "../../../../components/molecules/select/next-select/select"
import Thumbnail from "../../../../components/atoms/thumbnail"
import clsx from "clsx"
import { getFulfillableQuantity } from "../create-fulfillment/item-table"
import useNotification from "../../../../hooks/use-notification"
import { getErrorMessage } from "../../../../utils/error-messages"
import { getFulfillableQuantity } from "../create-fulfillment/item-table"
import { sum } from "lodash"
import useNotification from "../../../../hooks/use-notification"
type AllocationModalFormData = {
location?: { label: string; value: string }
@@ -121,11 +122,11 @@ const AllocateItemsModal: React.FC<AllocateItemsModalProps> = ({
<form onSubmit={handleSubmit(onSubmit)}>
<FocusModal>
<FocusModal.Header>
<div className="flex w-full justify-between px-8 medium:w-8/12">
<div className="medium:w-8/12 flex w-full justify-between px-8">
<Button size="small" variant="ghost" type="button" onClick={close}>
<CrossIcon size={20} />
</Button>
<div className="flex gap-x-small">
<div className="gap-x-small flex">
<Button
size="small"
variant="secondary"
@@ -170,7 +171,7 @@ const AllocateItemsModal: React.FC<AllocateItemsModalProps> = ({
</div>
<div
className={clsx(
"mt-8 flex w-full flex-col border-t border-grey-20",
"border-grey-20 mt-8 flex w-full flex-col border-t",
{
"pointer-events-none opacity-50": !selectedLocation?.value,
}
@@ -217,7 +218,8 @@ export const AllocationLineItem: React.FC<{
item: LineItem
locationId?: string
reservedQuantity?: number
}> = ({ form, item, locationId, reservedQuantity }) => {
compact?: boolean
}> = ({ form, item, locationId, reservedQuantity, compact }) => {
const { variant, isLoading } = useAdminVariantsInventory(
item.variant_id as string
)
@@ -260,44 +262,58 @@ export const AllocationLineItem: React.FC<{
inventoryItemReservationCapacity
)
return (
<div>
<div className="mt-8 flex w-full items-center justify-between">
<div className="flex gap-x-base">
<div className="mt-8 flex w-full items-start justify-between">
<div className="gap-x-base flex w-7/12">
<div className="min-w-9">
<Thumbnail size="medium" src={item.thumbnail} />
<div className="text-grey-50">
<p className="flex gap-x-2xsmall">
<p className="inter-base-semibold text-grey-90">{item.title}</p>
{`(${item.variant.sku})`}
</p>
<p className="inter-base-regular ">
{item.variant.options?.map((option) => option.value) ||
item.variant.title ||
"-"}
</p>
</div>
</div>
<div className="flex items-center gap-x-large">
<div className="inter-base-regular flex flex-col items-end whitespace-nowrap text-grey-50">
<p>{availableQuantity || "N/A"} available</p>
<p>({inStockQuantity || "N/A"} in stock)</p>
</div>
<InputField
{...register(path(`quantity`), { valueAsNumber: true })}
type="number"
defaultValue={0}
disabled={lineItemReservationCapacity < 0}
min={0}
max={maxReservation > 0 ? maxReservation : 0}
suffix={
<span className="flex">
{"/"}{" "}
<span className="ml-1">
{maxReservation > 0 ? maxReservation : 0}
</span>
</span>
<div className="text-grey-50 truncate">
<p className="gap-x-2xsmall nowrap flex grow ">
<p className="inter-base-semibold text-grey-90 truncate">
{item.title}
</p>
{`(${item.variant.sku})`}
</p>
<p className="inter-base-regular ">
{item.variant.options?.map((option) => option.value) ||
item.variant.title ||
"-"}
</p>
</div>
</div>
<div
className={clsx("gap-x-large flex items-center", {
"gap-y-xsmall flex-col-reverse": compact,
})}
>
<div
className={clsx(
"inter-base-regular text-grey-50 gap-x-xsmall flex items-end whitespace-nowrap",
{
"flex-col": !compact,
}
/>
)}
>
<p>{availableQuantity || "N/A"} available</p>
<p>({inStockQuantity || "N/A"} in stock)</p>
</div>
<InputField
{...register(path(`quantity`), { valueAsNumber: true })}
type="number"
className="min-w-[120px]"
defaultValue={0}
disabled={lineItemReservationCapacity < 0}
min={0}
max={maxReservation > 0 ? maxReservation : 0}
suffix={
<span className="flex">
{"/"}{" "}
<span className="ml-1">
{maxReservation > 0 ? maxReservation : 0}
</span>
</span>
}
/>
</div>
</div>
)
@@ -1,21 +1,22 @@
import { useEffect, useMemo } from "react"
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
import Button from "../../../../components/fundamentals/button"
import {
AllocationLineItem,
AllocationLineItemForm,
} from "./allocate-items-modal"
import { Controller, useForm, useWatch } from "react-hook-form"
import { LineItem, ReservationItemDTO } from "@medusajs/medusa"
import {
useAdminDeleteReservation,
useAdminStockLocations,
useAdminUpdateReservation,
} from "medusa-react"
import { useEffect, useMemo } from "react"
import Button from "../../../../components/fundamentals/button"
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
import Select from "../../../../components/molecules/select/next-select/select"
import { LineItem, ReservationItemDTO } from "@medusajs/medusa"
import useNotification from "../../../../hooks/use-notification"
import { nestedForm } from "../../../../utils/nested-form"
import SideModal from "../../../../components/molecules/modal/side-modal"
import { nestedForm } from "../../../../utils/nested-form"
import useNotification from "../../../../hooks/use-notification"
type EditAllocationLineItemForm = {
location: { label: string; value: string }
@@ -104,11 +105,14 @@ const EditAllocationDrawer = ({
}, [reservation, setValue])
const submit = (data: EditAllocationLineItemForm) => {
if (!data.item.quantity) {
return handleDelete()
}
updateReservation(
{
quantity: data.item.quantity,
location_id: data.location.value,
inventory_item_id: data.item.inventory_item_id,
},
{
onSuccess: () => {
@@ -125,18 +129,18 @@ const EditAllocationDrawer = ({
return (
<SideModal isVisible close={close}>
<form
className="w-full h-full text-grey-90"
className="text-grey-90 h-full w-full"
onSubmit={handleSubmit(submit)}
>
<div className="flex flex-col justify-between h-full ">
<div className="flex h-full flex-col justify-between ">
<div>
<div className="flex items-center justify-between px-8 py-6 border-b border-grey-20">
<div className="border-grey-20 flex items-center justify-between border-b px-8 py-6">
<h1 className="inter-large-semibold ">Edit allocation</h1>
<Button variant="ghost" className="p-1.5" onClick={close}>
<CrossIcon />
</Button>
</div>
<div className="flex flex-col px-8 pt-6 gap-y-8">
<div className="flex flex-col gap-y-8 px-8 pt-6">
<div>
<h2 className="inter-base-semibold">Location</h2>
<span className="inter-base-regular text-grey-50">
@@ -163,6 +167,7 @@ const EditAllocationDrawer = ({
<AllocationLineItem
form={nestedForm(form, `item` as "item")}
item={item}
compact
locationId={selectedLocation?.value}
reservedQuantity={
totalReservedQuantity - (reservation?.quantity || 0)
@@ -171,7 +176,7 @@ const EditAllocationDrawer = ({
</div>
<Button
variant="ghost"
className="w-full my-1 border text-rose-50"
className="my-1 w-full border text-rose-50"
size="small"
onClick={handleDelete}
>
@@ -179,7 +184,7 @@ const EditAllocationDrawer = ({
</Button>
</div>
</div>
<div className="flex justify-end w-full px-8 pt-4 pb-6 border-t gap-x-xsmall">
<div className="gap-x-xsmall flex w-full justify-end border-t px-8 pt-4 pb-6">
<Button
variant="ghost"
size="small"
@@ -4,15 +4,16 @@ import {
AdminReservationsDeleteRes,
AdminReservationsRes,
} from "@medusajs/medusa"
import { Response } from "@medusajs/medusa-js/src"
import {
useMutation,
UseMutationOptions,
useMutation,
useQueryClient,
} from "@tanstack/react-query"
import { useMedusa } from "../../../contexts"
import { buildOptions } from "../../utils/buildOptions"
import { Response } from "@medusajs/medusa-js/src"
import { adminReservationsKeys } from "./queries"
import { buildOptions } from "../../utils/buildOptions"
import { useMedusa } from "../../../contexts"
export const useAdminCreateReservation = (
options?: UseMutationOptions<
@@ -27,7 +28,7 @@ export const useAdminCreateReservation = (
return useMutation(
(payload: AdminPostReservationsReq) =>
client.admin.reservations.create(payload),
buildOptions(queryClient, [adminReservationsKeys.list()], options)
buildOptions(queryClient, [adminReservationsKeys.lists()], options)
)
}
@@ -54,8 +54,9 @@ import { IInventoryService } from "../../../../interfaces"
*/
export default async (req, res) => {
const { id } = req.params
const inventoryService: IInventoryService = req.resolve("inventoryService")
const manager: EntityManager = req.resolve("manager")
const inventoryService: IInventoryService =
req.scope.resolve("inventoryService")
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (manager) => {
await inventoryService.withTransaction(manager).deleteReservationItem(id)