Преглед на файлове

stock transfer ui

MergeProblem1
kelvin.yau преди 2 седмици
родител
ревизия
de2f012c24
променени са 2 файла, в които са добавени 115 реда и са изтрити 73 реда
  1. +30
    -0
      src/app/api/inventory/actions.ts
  2. +85
    -73
      src/components/InventorySearch/InventoryLotLineTable.tsx

+ 30
- 0
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<MessageResponse>(
`${BASE_API_URL}/stockTransferRecord/create`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);
revalidateTag("inventoryLotLines");
revalidateTag("inventories");
return result;
};

+ 85
- 73
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<Props> = ({ inventoryLotLines, pagingContr
const [stockTransferModalOpen, setStockTransferModalOpen] = useState(false);
const [selectedLotLine, setSelectedLotLine] = useState<InventoryLotLineResult | null>(null);
const [startLocation, setStartLocation] = useState<string>("");
const [targetLocation, setTargetLocation] = useState<string>("");
const [targetLocation, setTargetLocation] = useState<number | null>(null); // Store warehouse ID instead of code
const [targetLocationInput, setTargetLocationInput] = useState<string>("");
const [qtyToBeTransferred, setQtyToBeTransferred] = useState<number>(0);
const [warehouses, setWarehouses] = useState<WarehouseResult[]>([]);
@@ -65,7 +65,7 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
setSelectedLotLine(lotLine);
setStockTransferModalOpen(true);
setStartLocation(lotLine.warehouse.code || "");
setTargetLocation("");
setTargetLocation(null);
setTargetLocationInput("");
setQtyToBeTransferred(0);
},
@@ -188,34 +188,46 @@ const InventoryLotLineTable: React.FC<Props> = ({ 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 <>
<Typography variant="h6">{inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)})` : t("No items are selected yet.")}</Typography>
@@ -276,55 +288,55 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
</Grid>
<Grid item xs={5.5}>
<Autocomplete
options={warehouses.filter(w => 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) => (
<li {...props}>
{option.code}
</li>
)}
renderInput={(params) => (
<TextField
{...params}
label={t("Target Location")}
variant="outlined"
fullWidth
InputLabelProps={{
shrink: !!targetLocation || !!targetLocationInput,
sx: { fontSize: "0.9375rem" },
}}
isOptionEqualToValue={(option, value) => option.code === value.code}
autoHighlight={false}
autoSelect={false}
clearOnBlur={false}
renderOption={(props, option) => (
<li {...props}>
{option.code}
</li>
)}
renderInput={(params) => (
<TextField
{...params}
label={t("Target Location")}
variant="outlined"
fullWidth
InputLabelProps={{
shrink: !!targetLocation || !!targetLocationInput,
sx: { fontSize: "0.9375rem" },
}}
/>
)}
/>
)}
/>
</Grid>
</Grid>


Зареждане…
Отказ
Запис