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