|
- "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<ModalProps, "children"> {
- // 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<Props> = ({
- open,
- onClose,
- // itemDetail,
- inputDetail,
- session,
- warehouse,
- printerCombo,
- }) => {
- const {
- t,
- i18n: { language },
- } = useTranslation("purchaseOrder");
-
- const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>();
- const [isLoading, setIsLoading] = useState<Boolean>(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<NonNullable<TabsProps["onChange"]>>(
- (_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<ModalFormInput>({
- defaultValues: {
- ...defaultNewValue,
- },
- });
-
- const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
- () => {
- 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<SubmitHandler<ModalFormInput>>(
- 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<SubmitErrorHandler<ModalFormInput>>(
- async (data, event) => {
- console.log("Error", data);
- }, []
- );
-
- // QC submission handler
- const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>(
- 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<GridRowModesModel>({})
- const [pafRowSelectionModel, setPafRowSelectionModel] = useState<GridRowSelectionModel>([])
- 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<SubmitHandler<ModalFormInput>>(
- 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 (
- <>
- <FormProvider {...formProps}>
- <Modal open={open} onClose={closeHandler}>
- <Box
- sx={{
- ...style,
- // padding: 2,
- maxHeight: "90vh",
- overflowY: "auto",
- marginLeft: 3,
- marginRight: 3,
- // overflow: "hidden",
- display: 'flex',
- flexDirection: 'column',
- }}
- >
- {(!isLoading && stockInLineInfo) ? (<>
- <Box sx={{ position: 'sticky', top: 0, bgcolor: 'background.paper',
- zIndex: 5, borderBottom: 2, borderColor: 'divider', width: "100%"}}>
- <Tabs
- value={tabIndex}
- onChange={handleTabChange}
- variant="scrollable"
- sx={{pl: 2, pr: 2, pt: 2}}
- >
- <Tab label={
- showPutaway ? t("dn and qc info") : t("qc processing")
- } iconPosition="end" />
- {showPutaway && <Tab label={t("putaway processing")} iconPosition="end" />}
- </Tabs>
- </Box>
- <Grid
- container
- justifyContent="flex-start"
- alignItems="flex-start"
- sx={{padding: 2}}
- >
- <Grid item xs={12}>
- {tabIndex === 0 &&
- <Box>
- <Grid item xs={12}>
- <Typography variant="h6" display="block" marginBlockEnd={1}>
- {t("Delivery Detail")}
- </Typography>
- </Grid>
- <StockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} />
- {stockInLineInfo.qcResult ?
- <QcComponent
- itemDetail={stockInLineInfo}
- disabled={viewOnly || showPutaway}
- /> : <LoadingComponent/>
- }
- <Stack direction="row" justifyContent="flex-end" gap={1} sx={{pt:2}}>
- {(!viewOnly && !showPutaway) && (<Button
- id="Submit"
- type="button"
- variant="contained"
- color="primary"
- sx={{ mt: 1 }}
- onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)}
- >
- {t("confirm qc result")}
- </Button>)}
- </Stack>
- </Box>
- }
-
- {tabIndex === 1 &&
- <Box>
- <PutAwayForm
- itemDetail={stockInLineInfo}
- warehouse={warehouse!}
- disabled={viewOnly}
- setRowModesModel={setPafRowModesModel}
- setRowSelectionModel={setPafRowSelectionModel}
- />
- </Box>
- }
- </Grid>
- </Grid>
-
- {tabIndex == 1 && (
- <Stack direction="row" justifyContent="flex-end" gap={1} sx={{m:3, mt:"auto"}}>
- <Autocomplete
- disableClearable
- options={printerCombo}
- defaultValue={selectedPrinter}
- onChange={(event, value) => {
- setSelectedPrinter(value)
- }}
- renderInput={(params) => (
- <TextField
- {...params}
- variant="outlined"
- label={t("Printer")}
- sx={{ width: 300}}
- />
- )}
- />
- <TextField
- variant="outlined"
- label={t("Print Qty")}
- defaultValue={printQty}
- onChange={(event) => {
- event.target.value = event.target.value.replace(/[^0-9]/g, '')
-
- setPrintQty(Number(event.target.value))
- }}
- sx={{ width: 300}}
- />
- <Button
- id="printButton"
- type="button"
- variant="contained"
- color="primary"
- sx={{ mt: 1 }}
- onClick={handlePrint}
- disabled={isPrinting || printerCombo.length <= 0 || pafSubmitDisable}
- >
- {isPrinting ? t("Printing") : t("print")}
- </Button>
- </Stack>
- )}
- </>) : <LoadingComponent/>}
- </Box>
- </Modal>
- </FormProvider>
- </>
- );
- };
- export default PoQcStockInModalVer2;
|