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:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
fix(medusa): Resolve on scope instead of req
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"medusa-react": patch
|
||||
---
|
||||
|
||||
fix(medusa-react): invalidate all reservations list queries
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/admin-ui": patch
|
||||
---
|
||||
|
||||
Fix(admin-ui): Fix minor bugs related to the edit-allocation modal
|
||||
+65
-49
@@ -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>
|
||||
)
|
||||
|
||||
+18
-13
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user