|
- "use client";
- import { StockInLine } from "@/app/api/po";
- import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/po/actions";
- import { QcItemWithChecks, QcData } from "@/app/api/qc";
- import {
- Autocomplete,
- Box,
- Button,
- Grid,
- Modal,
- ModalProps,
- Stack,
- 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 StockInFormVer2 from "./StockInFormVer2";
- import QcComponent from "./QcComponent";
- import { dummyPutAwayLine, dummyQCData } from "./dummyQcTemplate";
- // import QcFormVer2 from "./QcFormVer2";
- import PutAwayForm from "./PutAwayForm";
- import { GridRowModes, useGridApiRef } from "@mui/x-data-grid";
- import {submitDialogWithWarning} from "../Swal/CustomAlerts";
- import { INPUT_DATE_FORMAT, arrayToDateString, arrayToInputDateString, 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";
-
-
- 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: "60%", sm: "60%", md: "60%" },
- };
- interface CommonProps extends Omit<ModalProps, "children"> {
- // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>;
- setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>;
- setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>;
- itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
- setItemDetail: Dispatch<
- SetStateAction<
- | (StockInLine & {
- warehouseId?: number;
- })
- | undefined
- >
- >;
- session: SessionWithTokens | null;
- qc?: QcItemWithChecks[];
- warehouse?: any[];
- // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject";
- handleMailTemplateForStockInLine: (stockInLineId: number) => void;
- printerCombo: PrinterCombo[];
- onClose: () => void;
- }
- interface Props extends CommonProps {
- itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
- }
- const PoQcStockInModalVer2: React.FC<Props> = ({
- // type,
- // setRows,
- setEntries,
- setStockInLine,
- open,
- onClose,
- itemDetail,
- setItemDetail,
- session,
- qc,
- warehouse,
- handleMailTemplateForStockInLine,
- printerCombo
- }) => {
- const {
- t,
- i18n: { language },
- } = useTranslation("purchaseOrder");
-
- // Select Printer
- const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]);
-
- const defaultNewValue = useMemo(() => {
- return (
- {
- ...itemDetail,
- status: itemDetail.status ?? "pending",
- dnDate: arrayToInputDateString(itemDetail.dnDate)?? dayjsToInputDateString(dayjs()),
- // 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 : [],
- receiptDate: itemDetail.receiptDate ?? dayjs().add(0, "month").format(INPUT_DATE_FORMAT),
- acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty,
- warehouseId: itemDetail.defaultWarehouseId ?? 1
- }
- )
- },[itemDetail])
-
- const [qcItems, setQcItems] = useState(dummyQCData)
- const formProps = useForm<ModalFormInput>({
- defaultValues: {
- ...defaultNewValue,
- },
- });
-
- const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
- () => {
- onClose?.();
- // reset();
- },
- [onClose],
- );
-
- const isPutaway = () => {
- if (itemDetail) {
- const status = itemDetail.status;
- return status == "received";
-
- } else return false;
- };
-
- const [viewOnly, setViewOnly] = useState(false);
- useEffect(() => {
- if (itemDetail && itemDetail.status) {
- const isViewOnly = itemDetail.status.toLowerCase() == "completed"
- || itemDetail.status.toLowerCase() == "partially_completed" // TODO update DB
- || itemDetail.status.toLowerCase() == "rejected"
- || (itemDetail.status.toLowerCase() == "escalated" && session?.id != itemDetail.handlerId)
- setViewOnly(isViewOnly)
- }
- console.log("Modal ItemDetail updated:", itemDetail);
- }, [itemDetail]);
-
- useEffect(() => {
- const qcRes = itemDetail?.qcResult;
- // if (!qcRes || qcRes?.length <= 0) {
- // itemDetail.qcResult = dummyQCData;
- // }
- formProps.reset({
- ...defaultNewValue
- })
-
- setQcItems(dummyQCData);
- setOpenPutaway(isPutaway);
-
- }, [open])
-
- 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;
- const acceptQty = Number(data.acceptQty);
- const qcResults = data.qcResult || dummyQCData; // qcItems;
- // 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 (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 && itemDetail.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 && itemDetail.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? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()),
- productionDate : arrayToInputDateString(data.productionDate),
- expiryDate : arrayToInputDateString(data.expiryDate),
- receiptDate : arrayToInputDateString(data.receiptDate),
-
- qcAccept: qcAccept? qcAccept : false,
- acceptQty: acceptQty? acceptQty : 0,
- qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({
- id: item.id,
- qcItemId: item.id,
- // 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
- const escalationLog = {
- type : "qc",
- status : "pending", // TODO: update with supervisor decision
- reason : data.escalationLog?.reason,
- recordDate : dayjsToInputDateString(dayjs()),
- handlerId : Number(session?.id),
- }
- 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();
- } else {
- closeHandler({}, "backdropClick");
- }
- return ;
-
- },
- [onOpenPutaway, qcItems, formProps.formState.errors],
- );
-
- const postStockInLine = useCallback(async (args: ModalFormInput) => {
- const submitData = {
- ...itemDetail, ...args
- } as StockInLineEntry & ModalFormInput;
- console.log("Submitting", submitData);
-
- const res = await updateStockInLine(submitData);
- return res;
- },[itemDetail])
-
- // Email supplier handler
- const onSubmitEmailSupplier = useCallback<SubmitHandler<ModalFormInput>>(
- async (data, event) => {
- console.log("Email Supplier Submission:", event!.nativeEvent);
- // Extract only email supplier related fields
- const emailData = {
- // supplierEmail: data.supplierEmail,
- // issueDescription: data.issueDescription,
- // qcComments: data.qcComments,
- // defectNotes: data.defectNotes,
- // attachments: data.attachments,
- // escalationReason: data.escalationReason,
- data: data,
-
- // Add other email-specific fields
- };
- console.log("Email Supplier Data:", emailData);
- // Handle email supplier logic here
- // e.g., send email to supplier, log escalation, etc.
- },
- [],
- );
-
- // Put away model
- const [pafRowModesModel, setPafRowModesModel] = useState<GridRowModesModel>({})
- const pafSubmitDisable = useMemo(() => {
- // console.log("%c mode: ", "background:#90EE90; color:red", Object.entries(pafRowModesModel))
- // console.log("%c mode: ", "background:pink; color:#87CEEB", Object.entries(pafRowModesModel))
- 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) => {
- // console.log("Putaway Submission:", event!.nativeEvent);
- // console.log(data.putAwayLines)
- // console.log(data.putAwayLines?.filter((line) => line._isNew !== false))
- // Extract only putaway related fields
- const putawayData = {
- // putawayLine: data.putawayLine,
- // putawayLocation: data.putawayLocation,
- // binLocation: data.binLocation,
- // putawayQuantity: data.putawayQuantity,
- // putawayNotes: data.putawayNotes,
- acceptQty: Number(data.acceptQty?? (itemDetail.demandQty?? (itemDetail.acceptedQty))), //TODO improve
- warehouseId: data.warehouseId,
- status: data.status, //TODO Fix it!
- // ...data,
-
- dnDate : data.dnDate? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()),
- productionDate : arrayToInputDateString(data.productionDate),
- expiryDate : arrayToInputDateString(data.expiryDate),
- receiptDate : arrayToInputDateString(data.receiptDate),
-
- // 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;
- }
- console.log(typeof data.putAwayLines!![0].qty + " = 'number'");
- console.log(typeof data.putAwayLines!![0].qty !== "number");
- 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)
- const data: PrintQrCodeForSilRequest = {
- stockInLineId: itemDetail.id,
- printerId: selectedPrinter.id,
- printQty: formProps.watch("putAwayLines")?.reduce((acc, cur) => acc + cur.printQty, 0)
- }
- const response = await printQrCodeForSil(data);
- if (response) {
- console.log(response)
- }
- } finally {
- setIsPrinting(() => false)
- }
- }, [itemDetail.id]);
-
- 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])
-
- useEffect(() => {
- // maybe check if submitted before
- console.log("Modal QC Items updated:", qcItems);
- // checkQcIsPassed(qcItems)
- }, [qcItems, checkQcIsPassed])
-
- return (
- <>
- <FormProvider {...formProps}>
- <Modal open={open} onClose={closeHandler}>
- <Box
- sx={{
- ...style,
- padding: 2,
- maxHeight: "90vh",
- overflowY: "auto",
- marginLeft: 3,
- marginRight: 3,
- }}
- >
- {openPutaway ? (
- <Box
- component="form"
- onSubmit={formProps.handleSubmit(onSubmitPutaway)}
- >
- <PutAwayForm
- printerCombo={printerCombo}
- itemDetail={itemDetail}
- warehouse={warehouse!}
- disabled={viewOnly}
- setRowModesModel={setPafRowModesModel}
- />
- <Stack direction="row" justifyContent="flex-end" gap={1}>
- <Autocomplete
- disableClearable
- options={printerCombo}
- defaultValue={selectedPrinter}
- onChange={(event, value) => {
- setSelectedPrinter(value)
- }}
- renderInput={(params) => (
- <TextField
- {...params}
- variant="outlined"
- label={t("Printer")}
- sx={{ width: 300}}
- />
- )}
- />
- <Button
- id="printButton"
- type="button"
- variant="contained"
- color="primary"
- sx={{ mt: 1 }}
- onClick={handlePrint}
- disabled={isPrinting || printerCombo.length <= 0}
- >
- {isPrinting ? t("Printing") : t("print")}
- </Button>
- <Button
- id="putawaySubmit"
- type="submit"
- variant="contained"
- color="primary"
- sx={{ mt: 1 }}
- onClick={formProps.handleSubmit(onSubmitPutaway)}
- disabled={pafSubmitDisable}
- >
- {t("confirm putaway")}
- </Button>
- </Stack>
- </Box>
- ) : (
- <>
- <Grid
- container
- justifyContent="flex-start"
- alignItems="flex-start"
- >
- <Grid item xs={12}>
- <Typography variant="h6" display="block" marginBlockEnd={1}>
- {t("qc processing")}
- </Typography>
- </Grid>
- <Grid item xs={12}>
- <StockInFormVer2 itemDetail={itemDetail} disabled={viewOnly} />
- </Grid>
- </Grid>
- {/* <Stack direction="row" justifyContent="flex-end" gap={1}>
- <Button
- id="stockInSubmit"
- type="button"
- variant="contained"
- color="primary"
- onClick={formProps.handleSubmit(onSubmitStockIn)}
- >
- {t("submitStockIn")}
- </Button>
- </Stack> */}
- <Grid
- container
- justifyContent="flex-start"
- alignItems="flex-start"
- >
- <QcComponent
- qc={qc!}
- itemDetail={itemDetail}
- disabled={viewOnly}
- // qcItems={qcItems}
- // setQcItems={setQcItems}
- />
- </Grid>
- <Stack direction="row" justifyContent="flex-end" gap={1}>
- {!viewOnly && (<Button
- id="qcSubmit"
- type="button"
- variant="contained"
- color="primary"
- sx={{ mt: 1 }}
- onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)}
- >
- {t("confirm qc result")}
- </Button>)}
- </Stack>
- </>
- )}
- </Box>
- </Modal>
- </FormProvider>
- </>
- );
- };
- export default PoQcStockInModalVer2;
|