|
|
@@ -0,0 +1,419 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import { |
|
|
|
Box, |
|
|
|
Button, |
|
|
|
Grid, |
|
|
|
Modal, |
|
|
|
ModalProps, |
|
|
|
Stack, |
|
|
|
TextField, |
|
|
|
Typography, |
|
|
|
Paper, |
|
|
|
} from "@mui/material"; |
|
|
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react"; |
|
|
|
import ReactQrCodeScanner, { |
|
|
|
ScannerConfig, |
|
|
|
} from "../ReactQrCodeScanner/ReactQrCodeScanner"; |
|
|
|
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; |
|
|
|
import { |
|
|
|
fetchStockInLineInfo, |
|
|
|
ModalFormInput, |
|
|
|
StockInLineEntry, |
|
|
|
updateStockInLine, |
|
|
|
} from "@/app/api/po/actions"; |
|
|
|
import { StockInLine } from "@/app/api/po"; |
|
|
|
import { WarehouseResult } from "@/app/api/warehouse"; |
|
|
|
// import { QrCodeInfo } from "@/app/api/qrcde"; |
|
|
|
import { Check, QrCode } from "@mui/icons-material"; |
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
import { useSearchParams } from "next/navigation"; |
|
|
|
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; |
|
|
|
import LoadingComponent from "../General/LoadingComponent"; |
|
|
|
import StockInForm from "../PoDetail/StockInForm"; |
|
|
|
import { arrayToDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; |
|
|
|
import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; |
|
|
|
import { msg } from "../Swal/CustomAlerts"; |
|
|
|
|
|
|
|
|
|
|
|
interface Props extends Omit<ModalProps, "children"> { |
|
|
|
warehouse: WarehouseResult[]; |
|
|
|
stockInLineId: number; |
|
|
|
warehouseId: number; |
|
|
|
scanner: QrCodeScanner; |
|
|
|
} |
|
|
|
const style = { |
|
|
|
position: "absolute", |
|
|
|
top: "50%", |
|
|
|
left: "50%", |
|
|
|
transform: "translate(-50%, -50%)", |
|
|
|
bgcolor: "background.paper", |
|
|
|
pt: 5, |
|
|
|
px: 5, |
|
|
|
pb: 10, |
|
|
|
// width: "auto", |
|
|
|
width: { xs: "90%", sm: "90%", md: "90%" }, |
|
|
|
}; |
|
|
|
|
|
|
|
const scannerStyle = { |
|
|
|
position: "absolute", |
|
|
|
top: "50%", |
|
|
|
left: "50%", |
|
|
|
transform: "translate(-50%, -50%)", |
|
|
|
bgcolor: "background.paper", |
|
|
|
pt: 5, |
|
|
|
px: 5, |
|
|
|
pb: 10, |
|
|
|
// width: "auto", |
|
|
|
width: { xs: "60%", sm: "60%", md: "60%" }, |
|
|
|
}; |
|
|
|
|
|
|
|
const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner }) => { |
|
|
|
const { t } = useTranslation("putAway"); |
|
|
|
const [serverError, setServerError] = useState(""); |
|
|
|
const params = useSearchParams(); |
|
|
|
|
|
|
|
const [isOpenScanner, setIsOpenScanner] = useState<boolean>(false); |
|
|
|
|
|
|
|
const [itemDetail, setItemDetail] = useState<StockInLine>(); |
|
|
|
const [unavailableText, setUnavailableText] = useState<string | undefined>( |
|
|
|
undefined, |
|
|
|
); |
|
|
|
|
|
|
|
const [putQty, setPutQty] = useState<number>(itemDetail?.demandQty ?? 0); |
|
|
|
|
|
|
|
const defaultNewValue = useMemo(() => { |
|
|
|
// console.log("%c ItemDetail", "color:purple", itemDetail); |
|
|
|
return ( |
|
|
|
{ |
|
|
|
...itemDetail, |
|
|
|
// status: itemDetail.status ?? "pending", |
|
|
|
dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined, |
|
|
|
// // putAwayLines: dummyPutAwayLine, |
|
|
|
// // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [], |
|
|
|
// putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [], |
|
|
|
// // qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData], |
|
|
|
// escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [], |
|
|
|
productionDate: itemDetail?.productionDate ? arrayToDateString(itemDetail?.productionDate, "input") : undefined, |
|
|
|
expiryDate: itemDetail?.expiryDate ? arrayToDateString(itemDetail?.expiryDate, "input") : undefined, |
|
|
|
receiptDate: itemDetail?.receiptDate ? arrayToDateString(itemDetail?.receiptDate, "input") : undefined, |
|
|
|
// acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, |
|
|
|
defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1, |
|
|
|
} |
|
|
|
) |
|
|
|
}, [itemDetail]) |
|
|
|
|
|
|
|
const formProps = useForm<ModalFormInput>({ |
|
|
|
defaultValues: { |
|
|
|
...defaultNewValue, |
|
|
|
}, |
|
|
|
}); |
|
|
|
const errors = formProps.formState.errors; |
|
|
|
|
|
|
|
|
|
|
|
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( |
|
|
|
(...args) => { |
|
|
|
setVerified(false); |
|
|
|
setItemDetail(undefined); |
|
|
|
onClose?.(...args); |
|
|
|
// reset(); |
|
|
|
}, |
|
|
|
[onClose], |
|
|
|
); |
|
|
|
|
|
|
|
const scannerCloseHandler = useCallback<NonNullable<ModalProps["onClose"]>>( |
|
|
|
(...args) => { |
|
|
|
setIsOpenScanner(false); |
|
|
|
scanner.stopScan(); |
|
|
|
console.log("%c Scanning stopped ", "color:cyan"); |
|
|
|
}, |
|
|
|
[], |
|
|
|
); |
|
|
|
|
|
|
|
const openScanner = () => { |
|
|
|
setIsOpenScanner(true); |
|
|
|
scanner.startScan(); |
|
|
|
console.log("%c Scanning started ", "color:cyan"); |
|
|
|
}; |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (warehouseId > 0) { // Scanned Warehouse |
|
|
|
if (isOpenScanner) { |
|
|
|
setIsOpenScanner(false); |
|
|
|
setVerified(true); |
|
|
|
scanner.stopScan(); |
|
|
|
console.log("%c Scanner stopped", "color:cyan"); |
|
|
|
} |
|
|
|
} |
|
|
|
}, [warehouseId]) |
|
|
|
|
|
|
|
useLayoutEffect(() => { |
|
|
|
if (itemDetail !== undefined) { |
|
|
|
if (itemDetail.status == "received") { |
|
|
|
formProps.reset({ |
|
|
|
...defaultNewValue |
|
|
|
}) |
|
|
|
setPutQty(itemDetail?.demandQty); |
|
|
|
console.log("%c Loaded data:", "color:lime", defaultNewValue); |
|
|
|
} else { |
|
|
|
switch (itemDetail.status) { |
|
|
|
case "pending": alert("此貨品有待品檢"); break; |
|
|
|
case "rejected": alert("此貨品已被拒收"); break; |
|
|
|
case "escalated": alert("此貨品已被上報"); break; |
|
|
|
case "partially_completed": alert("此貨品已部分上架"); break; |
|
|
|
case "completed": alert("此貨品已上架"); break; |
|
|
|
default: alert("此貨品暫時無法上架"); break; |
|
|
|
} |
|
|
|
|
|
|
|
closeHandler({}, "backdropClick"); |
|
|
|
} |
|
|
|
} |
|
|
|
}, [open, itemDetail, defaultNewValue]) |
|
|
|
|
|
|
|
const fetchStockInLine = useCallback( |
|
|
|
async (stockInLineId: number) => { |
|
|
|
setUnavailableText(undefined); |
|
|
|
const res = await fetchStockInLineInfo(stockInLineId); |
|
|
|
console.log("%c Fetched Stock In Line Info:", "color:gold", res); |
|
|
|
setItemDetail(res); |
|
|
|
}, |
|
|
|
[formProps, itemDetail, fetchStockInLineInfo], |
|
|
|
); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (stockInLineId) { fetchStockInLine(stockInLineId); } |
|
|
|
}, [stockInLineId]); |
|
|
|
|
|
|
|
const [verified, setVerified] = useState<boolean>(false); |
|
|
|
|
|
|
|
const [qtyError, setQtyError] = useState<string>(""); |
|
|
|
|
|
|
|
const validateQty = useCallback((qty : number = putQty) => { |
|
|
|
// if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") { |
|
|
|
// setQtyError(t("value must be a number")); |
|
|
|
// } else |
|
|
|
if (!Number.isInteger(qty)) { |
|
|
|
setQtyError(t("value must be integer")); |
|
|
|
} |
|
|
|
if (qty > itemDetail?.acceptedQty!!) { |
|
|
|
setQtyError(`${t("putQty must not greater than")} ${ |
|
|
|
itemDetail?.acceptedQty}` ); |
|
|
|
} else |
|
|
|
if (qty < 1) { |
|
|
|
setQtyError(t("minimal value is 1")); |
|
|
|
} else { |
|
|
|
setQtyError(""); |
|
|
|
} |
|
|
|
console.log("%c Validated putQty:", "color:yellow", putQty); |
|
|
|
},[setQtyError, putQty, itemDetail]) |
|
|
|
|
|
|
|
const onSubmit = useCallback<SubmitHandler<ModalFormInput>>( |
|
|
|
async (data, event) => { |
|
|
|
// console.log("errors", errors); |
|
|
|
// const lotLine = { |
|
|
|
// warehouseId: warehouseId, |
|
|
|
// qty: acceptQty; |
|
|
|
// } |
|
|
|
try { |
|
|
|
const args = { |
|
|
|
// ...itemDetail, |
|
|
|
id: itemDetail?.id, |
|
|
|
purchaseOrderId: itemDetail?.purchaseOrderId, |
|
|
|
purchaseOrderLineId: itemDetail?.purchaseOrderLineId, |
|
|
|
itemId: itemDetail?.itemId, |
|
|
|
acceptedQty: itemDetail?.acceptedQty, |
|
|
|
acceptQty: itemDetail?.demandQty, |
|
|
|
status: "received", |
|
|
|
// purchaseOrderId: parseInt(params.get("id")!), |
|
|
|
// purchaseOrderLineId: itemDetail?.purchaseOrderLineId, |
|
|
|
// itemId: itemDetail?.itemId, |
|
|
|
// acceptedQty: data.acceptedQty, |
|
|
|
// status: data.status, |
|
|
|
// ...data, |
|
|
|
// productionDate: productionDate, |
|
|
|
|
|
|
|
// for putaway data |
|
|
|
|
|
|
|
inventoryLotLines: [{ |
|
|
|
warehouseId: warehouseId, |
|
|
|
qty: putQty, |
|
|
|
}], |
|
|
|
// data.putAwayLines?.filter((line) => line._isNew !== false) |
|
|
|
|
|
|
|
} as StockInLineEntry & ModalFormInput; |
|
|
|
|
|
|
|
console.log(args); |
|
|
|
// return |
|
|
|
// if (formProps.formState.errors) { |
|
|
|
// setServerError(t("An error has occurred. Please try again later.")); |
|
|
|
// return false; |
|
|
|
// } |
|
|
|
if (qtyError !== "") { |
|
|
|
return; |
|
|
|
} |
|
|
|
console.log("%c Submitting Data:", "color:blue", args); |
|
|
|
const res = await updateStockInLine(args); |
|
|
|
if (Boolean(res.id)) { |
|
|
|
// update entries |
|
|
|
console.log("%c Update Success:", "color:green", res); |
|
|
|
// add loading |
|
|
|
msg("貨品上架成功!"); |
|
|
|
|
|
|
|
closeHandler({}, "backdropClick"); |
|
|
|
} |
|
|
|
console.log(res); |
|
|
|
// if (res) |
|
|
|
} catch (e) { |
|
|
|
// server error |
|
|
|
setServerError(t("An error has occurred. Please try again later.")); |
|
|
|
console.log(e); |
|
|
|
} |
|
|
|
}, |
|
|
|
[t, itemDetail, putQty, warehouseId], |
|
|
|
); |
|
|
|
|
|
|
|
return ( |
|
|
|
<FormProvider {...formProps}> |
|
|
|
<Modal open={open} onClose={closeHandler}> |
|
|
|
<Box |
|
|
|
sx={style} |
|
|
|
component="form" |
|
|
|
onSubmit={formProps.handleSubmit(onSubmit)} |
|
|
|
> |
|
|
|
<Grid container xs={12}> |
|
|
|
<Grid item xs={12}> |
|
|
|
{itemDetail != undefined ? ( |
|
|
|
<> |
|
|
|
<Stack direction="column" justifyContent="flex-end" gap={1}> |
|
|
|
<Typography variant="h4"> |
|
|
|
處理上架 |
|
|
|
</Typography> |
|
|
|
|
|
|
|
<Grid item xs={12}> |
|
|
|
<StockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/> |
|
|
|
</Grid> |
|
|
|
<Paper sx={{ padding: 2, width: "100%" }}> |
|
|
|
<Grid container spacing={2}> |
|
|
|
<Grid item xs={3}> |
|
|
|
<TextField |
|
|
|
type="number" // TODO fix the "e" input |
|
|
|
label={t("putQty")} |
|
|
|
fullWidth |
|
|
|
defaultValue={itemDetail.demandQty} |
|
|
|
onChange={(e) => { |
|
|
|
const value = e.target.value; |
|
|
|
validateQty(Number(value)); |
|
|
|
setPutQty(Number(value)); |
|
|
|
}} |
|
|
|
// onBlur={(e) => { |
|
|
|
// const value = e.target.value; |
|
|
|
// setPutQty(Number(value)); |
|
|
|
// validateQty(Number(value)); |
|
|
|
// }} |
|
|
|
// disabled={true} |
|
|
|
// {...register("acceptedQty", { |
|
|
|
// required: "acceptedQty required!", |
|
|
|
// })} |
|
|
|
error={qtyError !== ""} |
|
|
|
helperText={qtyError} |
|
|
|
/> |
|
|
|
</Grid> |
|
|
|
<Grid item xs={3}> |
|
|
|
<Button |
|
|
|
id="scanWarehouse" |
|
|
|
variant="contained" |
|
|
|
startIcon={<QrCode />} |
|
|
|
color="primary" |
|
|
|
// sx={{ mx: 3, minWidth : "120px", height: "80px", |
|
|
|
// padding: "12px 12px", fontSize: "24px"}} |
|
|
|
sx={{ |
|
|
|
flex: "0 0 auto", |
|
|
|
padding: "8px 16px", |
|
|
|
fontSize: { xs: "16px", sm: "20px", md: "24px" }, |
|
|
|
whiteSpace: "nowrap", |
|
|
|
textAlign: "center",}} |
|
|
|
onClick={openScanner}> |
|
|
|
|
|
|
|
{t("scan warehouse")} |
|
|
|
</Button> |
|
|
|
</Grid> |
|
|
|
<Grid item xs={3}> |
|
|
|
<TextField |
|
|
|
key={warehouseId} |
|
|
|
label={t("warehouse")} |
|
|
|
fullWidth |
|
|
|
disabled={true} |
|
|
|
value={ |
|
|
|
warehouseId > 0 ? warehouse.find((w) => w.id == warehouseId)?.name |
|
|
|
: warehouse.find((w) => w.id == 3)?.name |
|
|
|
// : warehouse.find((w) => w.id == itemDetail.defaultWarehouseId)?.name //TODO fix empty |
|
|
|
} |
|
|
|
// {...register("acceptedQty", { |
|
|
|
// required: "acceptedQty required!", |
|
|
|
// })} |
|
|
|
error={!verified} |
|
|
|
helperText={verified ? "掃碼完成" : "等待掃碼"} |
|
|
|
/> |
|
|
|
</Grid> |
|
|
|
{/* <Stack direction="row" justifyContent="flex-end" sx={{ mt: 1 }}> */} |
|
|
|
<Grid item xs={3}> |
|
|
|
<Button |
|
|
|
id="putawaySubmit" |
|
|
|
type="submit" |
|
|
|
variant="contained" |
|
|
|
startIcon={<Check />} |
|
|
|
color="primary" |
|
|
|
// sx={{ mx: 3, minWidth: "120px", height: "120px", |
|
|
|
// padding: "12px 12px", fontSize: "24px"}} |
|
|
|
sx={{ |
|
|
|
flex: "0 0 auto", |
|
|
|
padding: "8px 16px", |
|
|
|
fontSize: { xs: "16px", sm: "20px", md: "24px" }, |
|
|
|
whiteSpace: "nowrap", |
|
|
|
textAlign: "center",}} |
|
|
|
// onClick={formProps.handleSubmit()} |
|
|
|
disabled={!verified} |
|
|
|
> |
|
|
|
{t("confirm putaway")} |
|
|
|
</Button> |
|
|
|
</Grid> |
|
|
|
|
|
|
|
</Grid> |
|
|
|
</Paper> |
|
|
|
</Stack> |
|
|
|
</> |
|
|
|
) : ( |
|
|
|
// <ReactQrCodeScanner scannerConfig={scannerConfig} /> |
|
|
|
<> |
|
|
|
<Typography variant="h4"> |
|
|
|
{t("scan loading")} |
|
|
|
</Typography> |
|
|
|
<LoadingComponent/> |
|
|
|
</> |
|
|
|
)} |
|
|
|
</Grid> |
|
|
|
</Grid> |
|
|
|
|
|
|
|
<Modal open={isOpenScanner} onClose={scannerCloseHandler}> |
|
|
|
<Box sx={scannerStyle}> |
|
|
|
<Typography variant="h4" sx={{ |
|
|
|
display: 'flex', |
|
|
|
flexDirection: 'column', |
|
|
|
justifyContent: 'center', |
|
|
|
margin: 0, |
|
|
|
alignItems: 'center', |
|
|
|
textAlign: 'center',}} |
|
|
|
> |
|
|
|
{t("Please scan warehouse qr code")} |
|
|
|
</Typography> |
|
|
|
{/* <ReactQrCodeScanner scannerConfig={scannerConfig} /> */} |
|
|
|
</Box> |
|
|
|
</Modal> |
|
|
|
|
|
|
|
</Box> |
|
|
|
</Modal> |
|
|
|
</FormProvider> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
export default PutAwayModal; |