| @@ -49,7 +49,10 @@ export const arrayToDayjs = (arr: ConfigType | (number | undefined)[], showTime: | |||
| tempArr = `${arr[0]?.toString().padStart(4, "0")}-${arr[1]?.toString().padStart(2, "0")}-${arr[2]?.toString().padStart(2, "0")}`; | |||
| if (showTime) { | |||
| // [year, month, day, hour, minute, second] | |||
| tempArr += ` ${arr[3]?.toString().padStart(2, "0")}:${arr[4]?.toString().padStart(2, "0")}:${arr[5]?.toString().padStart(2, "0")}`; | |||
| tempArr += ` ${ | |||
| arr[3]?.toString().padStart(2, "0")}:${ | |||
| arr[4]?.toString().padStart(2, "0")}:${ | |||
| (arr[5] ?? 0)?.toString().padStart(2, "0")}`; | |||
| } | |||
| } | |||
| @@ -39,6 +39,7 @@ import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { | |||
| arrayToDateTimeString, | |||
| OUTPUT_DATE_FORMAT, | |||
| stockInLineStatusMap, | |||
| } from "@/app/utils/formatUtil"; | |||
| @@ -231,19 +232,33 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| return `${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}.` | |||
| }, | |||
| }, | |||
| { | |||
| field: "putawayDate", | |||
| headerName: t("putawayDatetime"), | |||
| flex: 1, | |||
| editable: false, | |||
| renderCell(params) { | |||
| return `${(arrayToDateTimeString(params.value))}`; | |||
| }, | |||
| }, | |||
| { | |||
| field: "putawayUser", | |||
| headerName: t("putawayUser"), | |||
| flex: 1, | |||
| editable: false, | |||
| }, | |||
| { | |||
| field: "qty", | |||
| headerName: t("qty"), | |||
| headerName: t("putawayQty"), | |||
| flex: 0.5, | |||
| editable: false, | |||
| // renderCell(params) { | |||
| // return <>100</> | |||
| // }, | |||
| headerAlign: "right", | |||
| align: "right", | |||
| }, | |||
| { | |||
| field: "warehouse", | |||
| headerName: t("warehouse"), | |||
| flex: 1, | |||
| flex: 2, | |||
| editable: false, | |||
| renderEditCell: (params) => { | |||
| const index = params.api.getRowIndexRelativeToVisibleRows(params.row.id) | |||
| @@ -275,6 +290,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| // return <>{filteredWarehouse[0].name}</> | |||
| // }, | |||
| }, | |||
| // { | |||
| // field: "printQty", | |||
| // headerName: t("printQty"), | |||
| @@ -163,7 +163,6 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| setViewOnly(isViewOnly) | |||
| } | |||
| console.log("Modal ItemDetail updated:", itemDetail); | |||
| console.log("%c SHOW PUTAWAY? ", "color:lime", showPutaway); | |||
| if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); } | |||
| }, [itemDetail]); | |||
| @@ -48,7 +48,7 @@ const PoSearch: React.FC<Props> = ({ | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | |||
| const searchCriteria: Criterion<SearchParamNames>[] = [ | |||
| { label: t("Supplier"), paramName: "supplier", type: "text" }, | |||
| { label: t("Po No."), paramName: "code", type: "text" }, | |||
| { label: t("PO No."), paramName: "code", type: "text" }, | |||
| { | |||
| label: t("Escalated"), | |||
| paramName: "escalated", | |||
| @@ -141,7 +141,7 @@ const PoSearch: React.FC<Props> = ({ | |||
| }, | |||
| { | |||
| name: "code", | |||
| label: `${t("Po No.")} &\n${t("Supplier")}`, | |||
| label: `${t("PO No.")} &\n${t("Supplier")}`, | |||
| renderCell: (params) => { | |||
| return <>{params.code}<br/>{params.supplier}</> | |||
| }, | |||
| @@ -35,6 +35,7 @@ import StockInForm from "../PoDetail/StockInForm"; | |||
| import { arrayToDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||
| import { msg } from "../Swal/CustomAlerts"; | |||
| import { PutAwayRecord } from "."; | |||
| interface Props extends Omit<ModalProps, "children"> { | |||
| @@ -42,6 +43,7 @@ interface Props extends Omit<ModalProps, "children"> { | |||
| stockInLineId: number; | |||
| warehouseId: number; | |||
| scanner: QrCodeScanner; | |||
| addPutAwayHistory: (putAwayData: PutAwayRecord) => void; | |||
| } | |||
| const style = { | |||
| position: "absolute", | |||
| @@ -69,7 +71,7 @@ const scannerStyle = { | |||
| width: { xs: "60%", sm: "60%", md: "60%" }, | |||
| }; | |||
| const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner }) => { | |||
| const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner, addPutAwayHistory }) => { | |||
| const { t } = useTranslation("putAway"); | |||
| const [serverError, setServerError] = useState(""); | |||
| const params = useSearchParams(); | |||
| @@ -77,7 +79,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| const [isOpenScanner, setIsOpenScanner] = useState<boolean>(false); | |||
| const [itemDetail, setItemDetail] = useState<StockInLine>(); | |||
| const [putAwayQty, setPutAwayQty] = useState<number>(0); | |||
| const [totalPutAwayQty, setTotalPutAwayQty] = useState<number>(0); | |||
| const [unavailableText, setUnavailableText] = useState<string | undefined>( | |||
| undefined, | |||
| ); | |||
| @@ -124,7 +126,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| (...args) => { | |||
| setVerified(false); | |||
| setItemDetail(undefined); | |||
| setPutAwayQty(0); | |||
| setTotalPutAwayQty(0); | |||
| onClose?.(...args); | |||
| // reset(); | |||
| }, | |||
| @@ -177,7 +179,8 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| formProps.reset({ | |||
| ...defaultNewValue | |||
| }) | |||
| setPutQty(itemDetail?.demandQty); | |||
| const total = itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; | |||
| setPutQty(itemDetail?.demandQty - total); | |||
| console.log("%c Loaded data:", "color:lime", defaultNewValue); | |||
| } else { | |||
| switch (itemDetail.status) { | |||
| @@ -201,8 +204,8 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| const res = await fetchStockInLineInfo(stockInLineId); | |||
| console.log("%c Fetched Stock In Line Info:", "color:gold", res); | |||
| const totalPutAwayQty = res.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; | |||
| setPutAwayQty(totalPutAwayQty); | |||
| const total = res.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; | |||
| setTotalPutAwayQty(total); | |||
| setItemDetail(res); | |||
| } catch (e) { | |||
| console.log("%c Error when fetching Stock In Line: ", "color:red", e); | |||
| @@ -224,9 +227,9 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| if (!Number.isInteger(qty)) { | |||
| setQtyError(t("value must be integer")); | |||
| } | |||
| if (qty > itemDetail?.acceptedQty!! - putAwayQty) { | |||
| if (qty > itemDetail?.demandQty!! - totalPutAwayQty) { | |||
| setQtyError(`${t("putQty must not greater than")} ${ | |||
| itemDetail?.acceptedQty!! - putAwayQty}` ); | |||
| itemDetail?.demandQty!! - totalPutAwayQty}` ); | |||
| } else | |||
| // if (qty > itemDetail?.acceptedQty!!) { | |||
| // setQtyError(`${t("putQty must not greater than")} ${ | |||
| @@ -290,11 +293,24 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| // update entries | |||
| console.log("%c Update Success:", "color:green", res); | |||
| // add loading | |||
| const putAwayData = { | |||
| itemName: itemDetail?.itemName, | |||
| itemCode: itemDetail?.itemNo, | |||
| poCode: itemDetail?.poCode, | |||
| lotNo: itemDetail?.lotNo, | |||
| warehouse: warehouse.find((w) => w.id == warehouseId)?.name, | |||
| putQty: putQty, | |||
| uom: itemDetail?.uom?.udfudesc, | |||
| } as PutAwayRecord; | |||
| addPutAwayHistory(putAwayData); | |||
| msg("貨品上架成功!"); | |||
| closeHandler({}, "backdropClick"); | |||
| } | |||
| console.log(res); | |||
| // console.log(res); | |||
| // if (res) | |||
| } catch (e) { | |||
| // server error | |||
| @@ -393,7 +409,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| lineHeight: "1.2", | |||
| }, | |||
| }} | |||
| defaultValue={itemDetail?.acceptedQty!! - putAwayQty} | |||
| defaultValue={itemDetail?.demandQty!! - totalPutAwayQty} | |||
| // defaultValue={itemDetail.demandQty} | |||
| onChange={(e) => { | |||
| const value = e.target.value; | |||
| @@ -0,0 +1,93 @@ | |||
| "use client"; | |||
| import { | |||
| Paper, | |||
| } from "@mui/material"; | |||
| import { useEffect, useMemo } from "react"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { GridColDef } from "@mui/x-data-grid"; | |||
| import { PutAwayRecord } from "."; | |||
| type Props = { | |||
| putAwayHistory : PutAwayRecord[]; | |||
| }; | |||
| const PutAwayReviewGrid: React.FC<Props> = ({ putAwayHistory }) => { | |||
| const { t } = useTranslation("putAway"); | |||
| const columns: GridColDef[] = useMemo(() => [ | |||
| { | |||
| field: "index", | |||
| headerName: t(""), | |||
| flex: 0.5, | |||
| renderCell: (params) => { | |||
| return (<b>{params.id}.</b>); | |||
| }, | |||
| disableColumnMenu: true, | |||
| }, | |||
| { | |||
| field: "poCode", | |||
| headerName: t("poCode"), | |||
| flex: 2, | |||
| disableColumnMenu: true, | |||
| }, | |||
| { | |||
| field: "itemCode", | |||
| headerName: t("itemCode"), | |||
| flex: 1, | |||
| disableColumnMenu: true, | |||
| }, | |||
| { | |||
| field: "itemName", | |||
| headerName: t("itemName"), | |||
| flex: 2, | |||
| disableColumnMenu: true, | |||
| }, | |||
| { | |||
| field: "putQty", | |||
| headerName: t("putawayQty"), | |||
| flex: 1, | |||
| headerAlign: 'right', | |||
| align: 'right', | |||
| disableColumnMenu: true, | |||
| }, | |||
| { | |||
| field: "uom", | |||
| headerName: t("uom"), | |||
| flex: 1.5, | |||
| disableColumnMenu: true, | |||
| }, | |||
| { | |||
| field: "warehouse", | |||
| headerName: t("warehouse"), | |||
| flex: 2, | |||
| disableColumnMenu: true, | |||
| }, | |||
| ], [] | |||
| ) | |||
| return (<> | |||
| <Paper> | |||
| <StyledDataGrid | |||
| columns={columns} | |||
| rows={putAwayHistory} | |||
| autoHeight | |||
| sx={{ | |||
| '& .MuiDataGrid-columnHeaderTitle': { | |||
| whiteSpace: 'nowrap', | |||
| overflow: 'visible', | |||
| textOverflow: 'clip', | |||
| lineHeight: 'normal', | |||
| paddingRight: '0px', | |||
| }, | |||
| '& .MuiDataGrid-columnHeader': { | |||
| padding: '0 4px 0 4px', | |||
| }, | |||
| }} | |||
| /> | |||
| </Paper> | |||
| </>) | |||
| } | |||
| export default PutAwayReviewGrid; | |||
| @@ -29,6 +29,8 @@ import { | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||
| import PutAwayModal from "./PutAwayModal"; | |||
| import { PutAwayRecord } from "."; | |||
| import PutAwayReviewGrid from "./PutAwayReviewGrid"; | |||
| type Props = { | |||
| warehouse : WarehouseResult[]; | |||
| @@ -43,6 +45,7 @@ const PutAwayScan: React.FC<Props> = ({ warehouse }) => { | |||
| const [openPutAwayModal, setOpenPutAwayModal] = useState(false); | |||
| const [scannedSilId, setScannedSilId] = useState<number>(0); // TODO use QR code info | |||
| const [scannedWareHouseId, setScannedWareHouseId] = useState<number>(0); // TODO use QR code info | |||
| const [putAwayHistory, setPutAwayHistory] = useState<PutAwayRecord[]>([]); | |||
| // QR Code Scanner | |||
| const scanner = useQrCodeScannerContext(); | |||
| @@ -115,6 +118,12 @@ const PutAwayScan: React.FC<Props> = ({ warehouse }) => { | |||
| }; | |||
| } | |||
| const addPutAwayHistory = (putAwayData: PutAwayRecord) => { | |||
| console.log("%c Added new data to Putaway history: ", "color:orange", putAwayData); | |||
| const newPutaway = { ...putAwayData, id: putAwayHistory.length + 1 }; | |||
| setPutAwayHistory([...putAwayHistory, newPutaway]); // Create a new array with the new row | |||
| // putAwayHistory.push(putAwayData); | |||
| }; | |||
| useEffect(() => { | |||
| if (scannedSilId > 0) { | |||
| @@ -170,29 +179,38 @@ const PutAwayScan: React.FC<Props> = ({ warehouse }) => { | |||
| } | |||
| }, [scanner.values]); | |||
| return (<> | |||
| <Paper sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| textAlign: 'center',}} | |||
| > | |||
| <Typography variant="h4"> | |||
| {scanDisplay == "pending" ? t("Pending scan") : t("Rescan")} | |||
| </Typography> | |||
| <QrCodeScanner sx={{padding: "10px", fontSize : "150px"}}/> | |||
| </Paper> | |||
| <PutAwayModal | |||
| open={openPutAwayModal} | |||
| onClose={closeModal} | |||
| warehouse={warehouse} | |||
| stockInLineId={scannedSilId} | |||
| warehouseId={scannedWareHouseId} | |||
| scanner={scanner} | |||
| return (<> | |||
| <Paper sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| textAlign: 'center',}} | |||
| > | |||
| <Typography variant="h4"> | |||
| {scanDisplay == "pending" ? t("Pending scan") : t("Rescan")} | |||
| </Typography> | |||
| <QrCodeScanner sx={{padding: "10px", fontSize : "150px"}}/> | |||
| </Paper> | |||
| {putAwayHistory.length > 0 && (<> | |||
| <Typography variant="h5"> | |||
| {t("putAwayHistory")} | |||
| </Typography> | |||
| <PutAwayReviewGrid | |||
| putAwayHistory={putAwayHistory} | |||
| /> | |||
| </>) | |||
| </>)} | |||
| <PutAwayModal | |||
| open={openPutAwayModal} | |||
| onClose={closeModal} | |||
| warehouse={warehouse} | |||
| stockInLineId={scannedSilId} | |||
| warehouseId={scannedWareHouseId} | |||
| scanner={scanner} | |||
| addPutAwayHistory={addPutAwayHistory} | |||
| /> | |||
| </>) | |||
| } | |||
| export default PutAwayScan; | |||
| @@ -1 +1,12 @@ | |||
| export { default } from "./PutAwayScanWrapper" | |||
| export { default } from "./PutAwayScanWrapper" | |||
| export interface PutAwayRecord { | |||
| id: number; | |||
| itemName: string; | |||
| itemCode?: string; | |||
| warehouse: string; | |||
| putQty: number; | |||
| lotNo?: string; | |||
| poCode?: string; | |||
| uom?: string; | |||
| }; | |||
| @@ -25,7 +25,6 @@ | |||
| "Complete PO": "完成採購訂單", | |||
| "General": "一般", | |||
| "Bind Storage": "綁定倉位", | |||
| "Po No.": "採購訂單編號", | |||
| "PO No.": "採購訂單編號", | |||
| "itemNo": "貨品編號", | |||
| "itemName": "貨品名稱", | |||
| @@ -149,5 +148,7 @@ | |||
| "value must be integer": "請輸入整數", | |||
| "dn and qc info": "來貨及品檢詳情", | |||
| "Qc Decision": "品檢詳情", | |||
| "Print Qty": "列印數量" | |||
| "Print Qty": "列印數量", | |||
| "putawayDatetime": "上架時間", | |||
| "putawayUser": "上架同事" | |||
| } | |||
| @@ -9,9 +9,16 @@ | |||
| "Please scan warehouse qr code": "請掃瞄倉庫二維碼", | |||
| "scan loading": "載入中,請稍候…", | |||
| "warehouse": "倉庫", | |||
| "putawayQty": "上架數量", | |||
| "putQty": "是次上架數量", | |||
| "minimal value is 1": "最小為1", | |||
| "putQty must not greater than": "上架數量不得大於", | |||
| "value must be integer": "必須是整數", | |||
| "value must be a number": "必須是數字" | |||
| "value must be a number": "必須是數字", | |||
| "putAwayHistory": "是次上架記錄", | |||
| "itemName": "貨品名稱", | |||
| "lotNo": "貨品批號", | |||
| "poCode": "採購訂單編號", | |||
| "itemCode": "貨品編號", | |||
| "uom": "單位" | |||
| } | |||