From de2f012c24154396bf7a2d157c96b57d6802014f Mon Sep 17 00:00:00 2001 From: "kelvin.yau" Date: Mon, 19 Jan 2026 00:50:01 +0800 Subject: [PATCH] stock transfer ui --- src/app/api/inventory/actions.ts | 30 ++++ .../InventorySearch/InventoryLotLineTable.tsx | 158 ++++++++++-------- 2 files changed, 115 insertions(+), 73 deletions(-) diff --git a/src/app/api/inventory/actions.ts b/src/app/api/inventory/actions.ts index 5f09c65..fab5c80 100644 --- a/src/app/api/inventory/actions.ts +++ b/src/app/api/inventory/actions.ts @@ -152,3 +152,33 @@ export const updateInventoryLotLineQuantities = async (data: { revalidateTag("pickorder"); return result; }; + +//STOCK TRANSFER +export interface CreateStockTransferRequest { + inventoryLotLineId: number; + transferredQty: number; + warehouseId: number; +} + +export interface MessageResponse { + id: number | null; + name: string; + code: string; + type: string; + message: string | null; + errorPosition: string | null; +} + +export const createStockTransfer = async (data: CreateStockTransferRequest) => { + const result = await serverFetchJson( + `${BASE_API_URL}/stockTransferRecord/create`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, + ); + revalidateTag("inventoryLotLines"); + revalidateTag("inventories"); + return result; +}; \ No newline at end of file diff --git a/src/components/InventorySearch/InventoryLotLineTable.tsx b/src/components/InventorySearch/InventoryLotLineTable.tsx index b325aff..14d0ba4 100644 --- a/src/components/InventorySearch/InventoryLotLineTable.tsx +++ b/src/components/InventorySearch/InventoryLotLineTable.tsx @@ -15,7 +15,7 @@ import CloseIcon from "@mui/icons-material/Close"; import { Autocomplete } from "@mui/material"; import { WarehouseResult } from "@/app/api/warehouse"; import { fetchWarehouseListClient } from "@/app/api/warehouse/client"; -import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; +import { createStockTransfer } from "@/app/api/inventory/actions"; interface Props { inventoryLotLines: InventoryLotLineResult[] | null; @@ -31,7 +31,7 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr const [stockTransferModalOpen, setStockTransferModalOpen] = useState(false); const [selectedLotLine, setSelectedLotLine] = useState(null); const [startLocation, setStartLocation] = useState(""); - const [targetLocation, setTargetLocation] = useState(""); + const [targetLocation, setTargetLocation] = useState(null); // Store warehouse ID instead of code const [targetLocationInput, setTargetLocationInput] = useState(""); const [qtyToBeTransferred, setQtyToBeTransferred] = useState(0); const [warehouses, setWarehouses] = useState([]); @@ -65,7 +65,7 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr setSelectedLotLine(lotLine); setStockTransferModalOpen(true); setStartLocation(lotLine.warehouse.code || ""); - setTargetLocation(""); + setTargetLocation(null); setTargetLocationInput(""); setQtyToBeTransferred(0); }, @@ -188,34 +188,46 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr ); const handleCloseStockTransferModal = useCallback(() => { - setStockTransferModalOpen(false); - setSelectedLotLine(null); - setStartLocation(""); - setTargetLocation(""); - setTargetLocationInput(""); - setQtyToBeTransferred(0); + setStockTransferModalOpen(false); + setSelectedLotLine(null); + setStartLocation(""); + setTargetLocation(null); + setTargetLocationInput(""); + setQtyToBeTransferred(0); }, []); const handleSubmitStockTransfer = useCallback(async () => { - try { - setIsUploading(true); - - // Decrease the inQty (availableQty) in the source inventory lot line + if (!selectedLotLine || !targetLocation || qtyToBeTransferred <= 0) { + return; + } + try { + setIsUploading(true); + + const request = { + inventoryLotLineId: selectedLotLine.id, + transferredQty: qtyToBeTransferred, + warehouseId: targetLocation, // targetLocation now contains warehouse ID + }; - // TODO: Add logic to increase qty in target location warehouse - - alert(t("Stock transfer successful")); - handleCloseStockTransferModal(); - - // TODO: Refresh the inventory lot lines list - } catch (error: any) { - console.error("Error transferring stock:", error); - alert(error?.message || t("Failed to transfer stock. Please try again.")); - } finally { - setIsUploading(false); + const response = await createStockTransfer(request); + + if (response && response.type === "success") { + alert(t("Stock transfer successful")); + handleCloseStockTransferModal(); + + // Refresh the inventory lot lines list + window.location.reload(); // Or use your preferred refresh method + } else { + throw new Error(response?.message || t("Failed to transfer stock")); } - }, [selectedLotLine, targetLocation, qtyToBeTransferred, originalQty, handleCloseStockTransferModal, setIsUploading, t]); + } catch (error: any) { + console.error("Error transferring stock:", error); + alert(error?.message || t("Failed to transfer stock. Please try again.")); + } finally { + setIsUploading(false); + } + }, [selectedLotLine, targetLocation, qtyToBeTransferred, handleCloseStockTransferModal, setIsUploading, t]); return <> {inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)})` : t("No items are selected yet.")} @@ -276,55 +288,55 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr w.code !== startLocation)} - getOptionLabel={(option) => option.code || ""} - value={targetLocation ? warehouses.find(w => w.code === targetLocation) || null : null} - inputValue={targetLocationInput} - onInputChange={(event, newInputValue) => { - setTargetLocationInput(newInputValue); - if (targetLocation && newInputValue !== targetLocation) { - setTargetLocation(""); - } - }} - onChange={(event, newValue) => { - if (newValue) { - setTargetLocation(newValue.code); - setTargetLocationInput(newValue.code); - } else { - setTargetLocation(""); - setTargetLocationInput(""); - } - }} - filterOptions={(options, { inputValue }) => { - if (!inputValue || inputValue.trim() === "") return options; - const searchTerm = inputValue.toLowerCase().trim(); - return options.filter((option) => - (option.code || "").toLowerCase().includes(searchTerm) || - (option.name || "").toLowerCase().includes(searchTerm) || - (option.description || "").toLowerCase().includes(searchTerm) - ); + options={warehouses.filter(w => w.code !== startLocation)} + getOptionLabel={(option) => option.code || ""} + value={targetLocation ? warehouses.find(w => w.id === targetLocation) || null : null} + inputValue={targetLocationInput} + onInputChange={(event, newInputValue) => { + setTargetLocationInput(newInputValue); + if (targetLocation && newInputValue !== warehouses.find(w => w.id === targetLocation)?.code) { + setTargetLocation(null); + } + }} + onChange={(event, newValue) => { + if (newValue) { + setTargetLocation(newValue.id); + setTargetLocationInput(newValue.code); + } else { + setTargetLocation(null); + setTargetLocationInput(""); + } + }} + filterOptions={(options, { inputValue }) => { + if (!inputValue || inputValue.trim() === "") return options; + const searchTerm = inputValue.toLowerCase().trim(); + return options.filter((option) => + (option.code || "").toLowerCase().includes(searchTerm) || + (option.name || "").toLowerCase().includes(searchTerm) || + (option.description || "").toLowerCase().includes(searchTerm) + ); + }} + isOptionEqualToValue={(option, value) => option.id === value.id} + autoHighlight={false} + autoSelect={false} + clearOnBlur={false} + renderOption={(props, option) => ( +
  • + {option.code} +
  • + )} + renderInput={(params) => ( + option.code === value.code} - autoHighlight={false} - autoSelect={false} - clearOnBlur={false} - renderOption={(props, option) => ( -
  • - {option.code} -
  • - )} - renderInput={(params) => ( - - )} + /> + )} />