Bläddra i källkod

Merge branch 'MergeProblem1' of http://svn.2fi-solutions.com:8300/derek/FPSMS-frontend into MergeProblem1

MergeProblem1
CANCERYS\kw093 2 veckor sedan
förälder
incheckning
8b12ae623b
1 ändrade filer med 279 tillägg och 10 borttagningar
  1. +279
    -10
      src/components/InventorySearch/InventoryLotLineTable.tsx

+ 279
- 10
src/components/InventorySearch/InventoryLotLineTable.tsx Visa fil

@@ -1,16 +1,21 @@
import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory";
import { Dispatch, SetStateAction, useCallback, useMemo } from "react";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Column } from "../SearchResults";
import SearchResults, { defaultPagingController, defaultSetPagingController } from "../SearchResults/SearchResults";
import { CheckCircleOutline, DoDisturb, EditNote } from "@mui/icons-material";
import { arrayToDateString } from "@/app/utils/formatUtil";
import { Typography } from "@mui/material";
import { isFinite } from "lodash";
import { Box, Card, Grid, IconButton, Modal, TextField, Typography, Button } from "@mui/material";
import useUploadContext from "../UploadProvider/useUploadContext";
import { downloadFile } from "@/app/utils/commonUtil";
import { fetchQrCodeByLotLineId, LotLineToQrcode } from "@/app/api/pdf/actions";
import QrCodeIcon from "@mui/icons-material/QrCode";
import PrintIcon from "@mui/icons-material/Print";
import SwapHoriz from "@mui/icons-material/SwapHoriz";
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";

interface Props {
inventoryLotLines: InventoryLotLineResult[] | null;
@@ -23,8 +28,26 @@ interface Props {
const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingController, setPagingController, totalCount, inventory }) => {
const { t } = useTranslation(["inventory"]);
const { setIsUploading } = useUploadContext();
const [stockTransferModalOpen, setStockTransferModalOpen] = useState(false);
const [selectedLotLine, setSelectedLotLine] = useState<InventoryLotLineResult | null>(null);
const [startLocation, setStartLocation] = useState<string>("");
const [targetLocation, setTargetLocation] = useState<string>("");
const [targetLocationInput, setTargetLocationInput] = useState<string>("");
const [qtyToBeTransferred, setQtyToBeTransferred] = useState<number>(0);
const [warehouses, setWarehouses] = useState<WarehouseResult[]>([]);

const printQrcode = useCallback(async (lotLineId: number) => {
useEffect(() => {
if (stockTransferModalOpen) {
fetchWarehouseListClient()
.then(setWarehouses)
.catch(console.error);
}
}, [stockTransferModalOpen]);

const originalQty = selectedLotLine?.availableQty || 0;
const remainingQty = originalQty - qtyToBeTransferred;

const downloadQrCode = useCallback(async (lotLineId: number) => {
setIsUploading(true);
// const postData = { stockInLineIds: [42,43,44] };
const postData: LotLineToQrcode = {
@@ -37,12 +60,24 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
setIsUploading(false);
}, [setIsUploading]);
const handleStockTransfer = useCallback(
(lotLine: InventoryLotLineResult) => {
setSelectedLotLine(lotLine);
setStockTransferModalOpen(true);
setStartLocation(lotLine.warehouse.code || "");
setTargetLocation("");
setTargetLocationInput("");
setQtyToBeTransferred(0);
},
[],
);

const onDetailClick = useCallback(
(lotLine: InventoryLotLineResult) => {
printQrcode(lotLine.id)
downloadQrCode(lotLine.id)
// lot line id to find stock in line
},
[printQrcode],
[downloadQrCode],
);
const columns = useMemo<Column<InventoryLotLineResult>[]>(
() => [
@@ -108,14 +143,32 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
name: "warehouse",
label: t("Warehouse"),
renderCell: (params) => {
return `${params.warehouse.code} - ${params.warehouse.name}`
return `${params.warehouse.code}`
},
},
{
name: "id",
label: t("qrcode"),
label: t("Download QR Code"),
onClick: onDetailClick,
buttonIcon: <QrCodeIcon />,
align: "center",
headerAlign: "center",
},
{
name: "id",
label: t("Print QR Code"),
onClick: () => {},
buttonIcon: <PrintIcon />,
align: "center",
headerAlign: "center",
},
{
name: "id",
label: t("Stock Transfer"),
onClick: handleStockTransfer,
buttonIcon: <SwapHoriz />,
align: "center",
headerAlign: "center",
},
// {
// name: "status",
@@ -131,8 +184,39 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
// }
// },
],
[t],
[t, onDetailClick, downloadQrCode, handleStockTransfer],
);

const handleCloseStockTransferModal = useCallback(() => {
setStockTransferModalOpen(false);
setSelectedLotLine(null);
setStartLocation("");
setTargetLocation("");
setTargetLocationInput("");
setQtyToBeTransferred(0);
}, []);

const handleSubmitStockTransfer = useCallback(async () => {
try {
setIsUploading(true);
// Decrease the inQty (availableQty) in the source inventory lot line


// 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);
}
}, [selectedLotLine, targetLocation, qtyToBeTransferred, originalQty, 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>
<SearchResults<InventoryLotLineResult>
@@ -142,6 +226,191 @@ const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingContr
setPagingController={setPagingController}
totalCount={totalCount}
/>
<Modal
open={stockTransferModalOpen}
onClose={handleCloseStockTransferModal}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Card
sx={{
position: 'relative',
width: '95%',
maxWidth: '1200px',
maxHeight: '90vh',
overflow: 'auto',
p: 3,
}}
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h6">
{inventory && selectedLotLine
? `${inventory.itemCode} ${inventory.itemName} (${selectedLotLine.lotNo})`
: t("Stock Transfer")
}
</Typography>
<IconButton onClick={handleCloseStockTransferModal}>
<CloseIcon />
</IconButton>
</Box>
<Grid container spacing={1} sx={{ mt: 2 }}>
<Grid item xs={5.5}>
<TextField
label={t("Start Location")}
fullWidth
variant="outlined"
value={startLocation}
disabled
InputLabelProps={{
shrink: !!startLocation,
sx: { fontSize: "0.9375rem" },
}}
/>
</Grid>
<Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="body1">{t("to")}</Typography>
</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)
);
}}
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>
<Grid container spacing={1} sx={{ mt: 2 }}>
<Grid item xs={2}>
<TextField
label={t("Original Qty")}
fullWidth
variant="outlined"
value={originalQty}
disabled
InputLabelProps={{
shrink: true,
sx: { fontSize: "0.9375rem" },
}}
/>
</Grid>
<Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="body1">-</Typography>
</Grid>
<Grid item xs={2}>
<TextField
label={t("Qty To Be Transferred")}
fullWidth
variant="outlined"
type="number"
value={qtyToBeTransferred}
onChange={(e) => {
const value = parseInt(e.target.value) || 0;
const maxValue = Math.max(0, originalQty);
setQtyToBeTransferred(Math.min(Math.max(0, value), maxValue));
}}
inputProps={{ min: 0, max: originalQty, step: 1 }}
InputLabelProps={{
shrink: true,
sx: { fontSize: "0.9375rem" },
}}
/>
</Grid>
<Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="body1">=</Typography>
</Grid>
<Grid item xs={2}>
<TextField
label={t("Remaining Qty")}
fullWidth
variant="outlined"
value={remainingQty}
disabled
InputLabelProps={{
shrink: true,
sx: { fontSize: "0.9375rem" },
}}
/>
</Grid>
<Grid item xs={2}>
<TextField
label={t("Stock UoM")}
fullWidth
variant="outlined"
value={selectedLotLine?.uom || ""}
disabled
InputLabelProps={{
shrink: true,
sx: { fontSize: "0.9375rem" },
}}
/>
</Grid>
<Grid item xs={2} sx={{ display: 'flex', alignItems: 'center' }}>
<Button
variant="contained"
fullWidth
sx={{
height: '56px',
fontSize: '0.9375rem',
}}
onClick={handleSubmitStockTransfer}
disabled={!selectedLotLine || !targetLocation || qtyToBeTransferred <= 0 || qtyToBeTransferred > originalQty}
>
{t("Submit")}
</Button>
</Grid>
</Grid>
</Card>
</Modal>

</>
}


Laddar…
Avbryt
Spara