From ea2633bccf8c80fc74e1e8f9b53a3c3260856918 Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Sun, 19 Mar 2023 21:28:59 +0100 Subject: [PATCH] Fix/minor mw fixes (#3521) **What** - Fix stock locations quantities being shown as `undefined` and `NaN` - Throw if updates to location levels are made with negative quantities through the api - Show "allocated" in order summary for partially fulfilled orders Fixes CORE-1268, CORE-1267, CORE-1265 --- .../inventory/inventory-items/index.js | 34 +++++++++++++++++++ .../templates/inventory-table/index.tsx | 2 ++ .../orders/details/detail-cards/summary.tsx | 3 +- .../variant-stock-form/index.tsx | 22 ++++++------ .../inventory-items/update-location-level.ts | 4 ++- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/integration-tests/plugins/__tests__/inventory/inventory-items/index.js b/integration-tests/plugins/__tests__/inventory/inventory-items/index.js index 43a3a8ca7e..96a0e8044e 100644 --- a/integration-tests/plugins/__tests__/inventory/inventory-items/index.js +++ b/integration-tests/plugins/__tests__/inventory/inventory-items/index.js @@ -207,6 +207,40 @@ describe("Inventory Items endpoints", () => { ) }) + it.only("fails to update location level to negative quantity", async () => { + const api = useApi() + + const inventoryItemId = inventoryItems[0].id + + await api.post( + `/admin/inventory-items/${inventoryItemId}/location-levels`, + { + location_id: locationId, + stocked_quantity: 17, + incoming_quantity: 2, + }, + adminHeaders + ) + + const res = await api + .post( + `/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`, + { + incoming_quantity: -1, + stocked_quantity: -1, + }, + adminHeaders + ) + .catch((error) => error) + + expect(res.response.status).toEqual(400) + expect(res.response.data).toEqual({ + type: "invalid_data", + message: + "incoming_quantity must not be less than 0, stocked_quantity must not be less than 0", + }) + }) + it("Retrieve an inventory item", async () => { const api = useApi() const inventoryItemId = inventoryItems[0].id diff --git a/packages/admin-ui/ui/src/components/templates/inventory-table/index.tsx b/packages/admin-ui/ui/src/components/templates/inventory-table/index.tsx index 041c0d3861..6ca1696b05 100644 --- a/packages/admin-ui/ui/src/components/templates/inventory-table/index.tsx +++ b/packages/admin-ui/ui/src/components/templates/inventory-table/index.tsx @@ -456,6 +456,8 @@ const AdjustAvailabilityModal = ({ onChange={(e) => setStockedQuantity(e.target.valueAsNumber)} autoFocus type="number" + placeholder="0" + min={0} value={stockedQuantity} /> diff --git a/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx b/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx index 1e2efba9d4..7d876333c3 100644 --- a/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx +++ b/packages/admin-ui/ui/src/domain/orders/details/detail-cards/summary.tsx @@ -56,7 +56,8 @@ const SummaryCard: React.FC = ({ order, reservations }) => { return ( item.quantity === item.fulfilled_quantity || (reservations && - sum(reservations.map((r) => r.quantity)) === item.quantity) + sum(reservations.map((r) => r.quantity)) === + item.quantity - (item.fulfilled_quantity || 0)) ) }) }, [reservationItemsMap, order]) diff --git a/packages/admin-ui/ui/src/domain/products/components/variant-inventory-form/variant-stock-form/index.tsx b/packages/admin-ui/ui/src/domain/products/components/variant-inventory-form/variant-stock-form/index.tsx index 42c4680928..502072dfcd 100644 --- a/packages/admin-ui/ui/src/domain/products/components/variant-inventory-form/variant-stock-form/index.tsx +++ b/packages/admin-ui/ui/src/domain/products/components/variant-inventory-form/variant-stock-form/index.tsx @@ -1,15 +1,16 @@ -import React, { useMemo, useState, useContext } from "react" -import Modal from "../../../../../components/molecules/modal" -import { LayeredModalContext } from "../../../../../components/molecules/modal/layered-modal" -import { useAdminStockLocations } from "medusa-react" -import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa" import { Controller, useFieldArray } from "react-hook-form" -import Button from "../../../../../components/fundamentals/button" -import Switch from "../../../../../components/atoms/switch" -import InputField from "../../../../../components/molecules/input" -import { NestedForm } from "../../../../../utils/nested-form" -import IconBadge from "../../../../../components/fundamentals/icon-badge" +import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa" +import React, { useContext, useMemo, useState } from "react" + import BuildingsIcon from "../../../../../components/fundamentals/icons/buildings-icon" +import Button from "../../../../../components/fundamentals/button" +import IconBadge from "../../../../../components/fundamentals/icon-badge" +import InputField from "../../../../../components/molecules/input" +import { LayeredModalContext } from "../../../../../components/molecules/modal/layered-modal" +import Modal from "../../../../../components/molecules/modal" +import { NestedForm } from "../../../../../utils/nested-form" +import Switch from "../../../../../components/atoms/switch" +import { useAdminStockLocations } from "medusa-react" export type VariantStockFormType = { manage_inventory?: boolean @@ -64,6 +65,7 @@ const VariantStockForm = ({ form, locationLevels }: Props) => { append({ location_id: added, stocked_quantity: locationLevelMap.get(added)?.stocked_quantity ?? 0, + reserved_quantity: locationLevelMap.get(added)?.reserved_quantity ?? 0, }) }) } diff --git a/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts b/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts index 4730a71f84..120da3039d 100644 --- a/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts +++ b/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express" -import { IsNumber, IsOptional } from "class-validator" +import { IsNumber, IsOptional, Min } from "class-validator" import { IInventoryService } from "../../../../interfaces" import { FindParams } from "../../../../types/common" @@ -105,10 +105,12 @@ export default async (req: Request, res: Response) => { export class AdminPostInventoryItemsItemLocationLevelsLevelReq { @IsOptional() @IsNumber() + @Min(0) incoming_quantity?: number @IsOptional() @IsNumber() + @Min(0) stocked_quantity?: number }