"use client"; import { QcItemWithChecks, QcData } from "@/app/api/qc"; import { Autocomplete, Box, Button, Divider, Grid, Modal, ModalProps, Stack, Tab, Tabs, TabsProps, TextField, Typography, } from "@mui/material"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; import { StockInLineRow } from "./PoInputGrid"; import { useTranslation } from "react-i18next"; import StockInForm from "./StockInForm"; import QcComponent from "./QcComponent"; import { dummyPutAwayLine, dummyQCData } from "./dummyQcTemplate"; import PutAwayForm from "./PutAwayForm"; import { GridRowModes, GridRowSelectionModel, useGridApiRef } from "@mui/x-data-grid"; import {msg, submitDialogWithWarning} from "../Swal/CustomAlerts"; import { INPUT_DATE_FORMAT, arrayToDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; import { fetchPoQrcode } from "@/app/api/pdf/actions"; import { downloadFile } from "@/app/utils/commonUtil"; import { PrinterCombo } from "@/app/api/settings/printer"; import { EscalationResult } from "@/app/api/escalation"; import { SessionWithTokens } from "@/config/authConfig"; import { GridRowModesModel } from "@mui/x-data-grid"; import { isEmpty } from "lodash"; import { EscalationCombo } from "@/app/api/user"; import { truncateSync } from "fs"; import { ModalFormInput, StockInLineInput, StockInLine } from "@/app/api/stockIn"; import { PurchaseQcResult, StockInLineEntry, updateStockInLine, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/stockIn/actions"; import { fetchStockInLineInfo } from "@/app/api/stockIn/actions"; import { fetchQcResult } from "@/app/api/qc/actions"; import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; import LoadingComponent from "../General/LoadingComponent"; const style = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", bgcolor: "background.paper", // pt: 5, // px: 5, // pb: 10, display: "block", width: { xs: "90%", sm: "90%", md: "90%" }, height: { xs: "90%", sm: "90%", md: "90%" }, }; interface CommonProps extends Omit { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] } | undefined; inputDetail: StockInLineInput | undefined; session: SessionWithTokens | null; warehouse?: any[]; printerCombo: PrinterCombo[]; onClose: () => void; } interface Props extends CommonProps { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; } const PoQcStockInModalVer2: React.FC = ({ open, onClose, // itemDetail, inputDetail, session, warehouse, printerCombo, }) => { const { t, i18n: { language }, } = useTranslation("purchaseOrder"); const [stockInLineInfo, setStockInLineInfo] = useState(); const [isLoading, setIsLoading] = useState(false); // const [viewOnly, setViewOnly] = useState(false); // Select Printer const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]); const [printQty, setPrintQty] = useState(1); const [tabIndex, setTabIndex] = useState(0); const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); }, [], ); const fetchStockInLineData = useCallback( async (stockInLineId: number) => { try { const res = await fetchStockInLineInfo(stockInLineId); if (res) { console.log("%c Fetched Stock In Line: ", "color:orange", res); setStockInLineInfo({...inputDetail, ...res}); fetchQcResultData(stockInLineId); } else throw("Result is undefined"); } catch (e) { console.log("%c Error when fetching Stock In Line: ", "color:red", e); } },[fetchStockInLineInfo, inputDetail] ); const fetchQcResultData = useCallback( // TODO: put this inside QC Component async (stockInLineId: number) => { try { const res = await fetchQcResult(stockInLineId); if (res.length > 0) { console.log("%c Fetched Qc Result: ", "color:orange", res); setStockInLineInfo((prev) => ({...prev, qcResult: res} as StockInLine)); formProps.setValue("qcResult", res); fetchEscalationLogData(stockInLineId); } else {setStockInLineInfo((prev) => ({...prev, qcResult: []} as StockInLine));} // } else throw("Result is undefined"); } catch (e) { console.log("%c Error when fetching Qc Result: ", "color:red", e); } },[fetchQcResult] ); const fetchEscalationLogData = useCallback( async (stockInLineId: number) => { try { const res = await fetchEscalationLogsByStockInLines([stockInLineId]); if (res.length > 0) { console.log("%c Fetched Escalation Log: ", "color:orange", res[0]); setStockInLineInfo((prev) => ({...prev, escResult: res} as StockInLine)); // formProps.setValue("escalationLog", res[0]); } else throw("Result is undefined"); } catch (e) { console.log("%c Error when fetching EscalationLog: ", "color:red", e); } },[fetchEscalationLogsByStockInLines] ); // Fetch info if id is input useEffect(() => { setIsLoading(true); if (inputDetail && open) { console.log("%c Opened Modal with input:", "color:yellow", inputDetail); if (inputDetail.id) { const id = inputDetail.id; fetchStockInLineData(id); } } }, [open]); // Make sure stock in line info is fetched useEffect(() => { if (stockInLineInfo) { if (stockInLineInfo.id) { if (isLoading) { formProps.reset({ ...defaultNewValue }); console.log("%c Modal loaded successfully", "color:lime"); setIsLoading(false); } } } }, [stockInLineInfo]); const defaultNewValue = useMemo(() => { const d = stockInLineInfo; if (d !== undefined) { // console.log("%c sil info", "color:yellow", d ) return ( { ...d, // status: d.status ?? "pending", productionDate: d.productionDate ? arrayToDateString(d.productionDate, "input") : undefined, expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined, receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), acceptQty: d.demandQty?? d.acceptedQty, // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], warehouseId: d.defaultWarehouseId ?? 1, putAwayLines: d.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [], } as ModalFormInput ) } return undefined }, [stockInLineInfo]) // const [qcItems, setQcItems] = useState(dummyQCData) const formProps = useForm({ defaultValues: { ...defaultNewValue, }, }); const closeHandler = useCallback>( () => { setStockInLineInfo(undefined); formProps.reset({}); onClose?.(); }, [onClose], ); const isPutaway = () => { if (stockInLineInfo) { const status = stockInLineInfo.status; return status == "received"; } else return false; }; // Get show putaway const showPutaway = useMemo(() => { if (stockInLineInfo) { const status = stockInLineInfo.status; return status !== "pending" && status !== "escalated" && status !== "rejected"; } return false; }, [stockInLineInfo]); // Get is view only const viewOnly = useMemo(() => { if (stockInLineInfo) { if (stockInLineInfo.status) { const status = stockInLineInfo.status; const isViewOnly = status.toLowerCase() == "completed" || status.toLowerCase() == "partially_completed" // TODO update DB || status.toLowerCase() == "rejected" || (status.toLowerCase() == "escalated" && session?.id != stockInLineInfo.handlerId) if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); } return isViewOnly; } } return true; }, [stockInLineInfo]) const [openPutaway, setOpenPutaway] = useState(false); const onOpenPutaway = useCallback(() => { setOpenPutaway(true); }, []); const onClosePutaway = useCallback(() => { setOpenPutaway(false); }, []); // Stock In submission handler const onSubmitStockIn = useCallback>( async (data, event) => { console.log("Stock In Submission:", event!.nativeEvent); // Extract only stock-in related fields const stockInData = { // quantity: data.quantity, // receiptDate: data.receiptDate, // batchNumber: data.batchNumber, // expiryDate: data.expiryDate, // warehouseId: data.warehouseId, // location: data.location, // unitCost: data.unitCost, data: data, // Add other stock-in specific fields from your form }; console.log("Stock In Data:", stockInData); // Handle stock-in submission logic here // e.g., call API, update state, etc. }, [], ); // QC submission handler const onSubmitErrorQc = useCallback>( async (data, event) => { console.log("Error", data); }, [] ); // QC submission handler const onSubmitQc = useCallback>( async (data, event) => { console.log("QC Submission:", event!.nativeEvent); // TODO: Move validation into QC page // if (errors.length > 0) { // alert(`未完成品檢: ${errors.map((err) => err[1].message)}`); // return; // } // Get QC data from the shared form context const qcAccept = data.qcDecision == 1; // const qcAccept = data.qcAccept; let acceptQty = Number(data.acceptQty); const qcResults = data.qcResult?.filter((qc) => qc.escalationLogId === undefined) || []; // Remove old QC data // const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems; // const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; // Validate QC data const validationErrors : string[] = []; // Check if failed items have failed quantity const failedItemsWithoutQty = qcResults.filter(item => item.qcPassed === false && (!item.failQty || item.failQty <= 0) ); if (failedItemsWithoutQty.length > 0) { validationErrors.push(`${t("Failed items must have failed quantity")}`); // validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.code).join(', ')}`); } // Check if QC accept decision is made if (data.qcDecision === undefined) { // if (qcAccept === undefined) { validationErrors.push(t("QC decision is required")); } // Check if accept quantity is valid if (data.qcDecision == 2) { acceptQty = 0; } else { if (acceptQty === undefined || acceptQty <= 0) { validationErrors.push("Accept quantity must be greater than 0"); } } // Check if dates are input if (data.productionDate === undefined || data.productionDate == null) { alert("請輸入生產日期!"); return; } if (data.expiryDate === undefined || data.expiryDate == null) { alert("請輸入到期日!"); return; } if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != "escalated") { //TODO: fix it please! validationErrors.push("有不合格檢查項目,無法收貨!"); // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", // confirmButtonText: t("confirm putaway"), html: ""}); // return; } // Check if all QC items have results const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != "escalated") { //TODO: fix it please! validationErrors.push(`${t("QC items without result")}`); // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); } if (validationErrors.length > 0) { console.error("QC Validation failed:", validationErrors); alert(`未完成品檢: ${validationErrors}`); return; } const qcData = { dnNo : data.dnNo? data.dnNo : "DN00000", // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), productionDate : arrayToDateString(data.productionDate, "input"), expiryDate : arrayToDateString(data.expiryDate, "input"), receiptDate : arrayToDateString(data.receiptDate, "input"), qcAccept: qcAccept? qcAccept : false, acceptQty: acceptQty? acceptQty : 0, // qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({ qcResult: qcResults.map(item => ({ // id: item.id, qcItemId: item.qcItemId, // code: item.code, // qcDescription: item.qcDescription, qcPassed: item.qcPassed? item.qcPassed : false, failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0, // failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, remarks: item.remarks || '', })), }; // const qcData = data; console.log("QC Data for submission:", qcData); if (data.qcDecision == 3) { // Escalate if (data.escalationLog?.handlerId == undefined) { alert("請選擇上報負責同事!"); return; } else if (data.escalationLog?.handlerId < 1) { alert("上報負責同事資料有誤"); return; } const escalationLog = { type : "qc", status : "pending", // TODO: update with supervisor decision reason : data.escalationLog?.reason, recordDate : dayjsToInputDateString(dayjs()), handlerId : data.escalationLog?.handlerId, } console.log("Escalation Data for submission", escalationLog); await postStockInLine({...qcData, escalationLog}); } else { await postStockInLine(qcData); } if (qcData.qcAccept) { // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", // confirmButtonText: t("confirm putaway"), html: ""}); // onOpenPutaway(); closeHandler({}, "backdropClick"); // setTabIndex(1); // Need to go Putaway tab? } else { closeHandler({}, "backdropClick"); } msg("已更新來貨狀態"); return ; }, [onOpenPutaway, formProps.formState.errors], ); const postStockInLine = useCallback(async (args: ModalFormInput) => { const submitData = { ...stockInLineInfo, ...args } as StockInLineEntry & ModalFormInput; console.log("Submitting", submitData); const res = await updateStockInLine(submitData); return res; }, [stockInLineInfo]) // Put away model const [pafRowModesModel, setPafRowModesModel] = useState({}) const [pafRowSelectionModel, setPafRowSelectionModel] = useState([]) const pafSubmitDisable = useMemo(() => { return Object.entries(pafRowModesModel).length > 0 || Object.entries(pafRowModesModel).some(([key, value], index) => value.mode === GridRowModes.Edit) }, [pafRowModesModel]) // Putaway submission handler const onSubmitPutaway = useCallback>( async (data, event) => { // Extract only putaway related fields const putawayData = { acceptQty: Number(data.acceptQty?? (stockInLineInfo?.demandQty?? (stockInLineInfo?.acceptedQty))), //TODO improve warehouseId: data.warehouseId, status: data.status, //TODO Fix it! // ...data, // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), productionDate : arrayToDateString(data.productionDate, "input"), expiryDate : arrayToDateString(data.expiryDate, "input"), receiptDate : arrayToDateString(data.receiptDate, "input"), // for putaway data inventoryLotLines: data.putAwayLines?.filter((line) => line._isNew !== false) // Add other putaway specific fields } as ModalFormInput; console.log("Putaway Data:", putawayData); console.log("DEBUG",data.putAwayLines); // if (data.putAwayLines!!.filter((line) => line._isNew !== false).length <= 0) { // alert("請新增上架資料!"); // return; // } if (data.putAwayLines!!.filter((line) => /[^0-9]/.test(String(line.qty))).length > 0) { //TODO Improve alert("上架數量不正確!"); return; } if (data.putAwayLines!!.reduce((acc, cur) => acc + Number(cur.qty), 0) > putawayData.acceptQty!!) { alert(`上架數量不能大於 ${putawayData.acceptQty}!`); return; } // Handle putaway submission logic here const res = await postStockInLine(putawayData); console.log("result ", res); // Close modal after successful putaway closeHandler({}, "backdropClick"); }, [closeHandler], ); // Print handler const [isPrinting, setIsPrinting] = useState(false) const handlePrint = useCallback(async () => { // console.log("Print putaway documents"); console.log("%c data", "background: white; color: red", formProps.watch("putAwayLines")); // Handle print logic here // window.print(); // const postData = { stockInLineIds: [itemDetail.id]}; // const response = await fetchPoQrcode(postData); // if (response) { // downloadFile(new Uint8Array(response.blobValue), response.filename) // } try { setIsPrinting(() => true) if ((formProps.watch("putAwayLines") ?? []).filter((line) => /[^0-9]/.test(String(line.printQty))).length > 0) { //TODO Improve alert("列印數量不正確!"); return; } // console.log(pafRowSelectionModel) const printList = formProps.watch("putAwayLines")?.filter((line) => ((pafRowSelectionModel ?? []).some((model) => model === line.id))) ?? [] // const printQty = printList.reduce((acc, cur) => acc + cur.printQty, 0) // console.log(printQty) const data: PrintQrCodeForSilRequest = { stockInLineId: stockInLineInfo?.id ?? 0, printerId: selectedPrinter.id, printQty: printQty } const response = await printQrCodeForSil(data); if (response) { console.log(response) } } finally { setIsPrinting(() => false) } // }, [pafRowSelectionModel, printQty, selectedPrinter]); }, [stockInLineInfo?.id, pafRowSelectionModel, printQty, selectedPrinter]); const acceptQty = formProps.watch("acceptedQty") // const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { // const isPassed = qcItems.every((qc) => qc.qcPassed); // console.log(isPassed) // if (isPassed) { // formProps.setValue("passingQty", acceptQty) // } else { // formProps.setValue("passingQty", 0) // } // return isPassed // }, [acceptQty, formProps]) return ( <> {(!isLoading && stockInLineInfo) ? (<> {showPutaway && } {tabIndex === 0 && {t("Delivery Detail")} {stockInLineInfo.qcResult ? : } {(!viewOnly && !showPutaway) && ()} } {tabIndex === 1 && } {tabIndex == 1 && ( { setSelectedPrinter(value) }} renderInput={(params) => ( )} /> { event.target.value = event.target.value.replace(/[^0-9]/g, '') setPrintQty(Number(event.target.value)) }} sx={{ width: 300}} /> )} ) : } ); }; export default PoQcStockInModalVer2;