| @@ -1,11 +1,12 @@ | |||
| "use server"; | |||
| import { cache } from 'react'; | |||
| import { Pageable, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
| import { Pageable, serverFetchBlob, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
| import { JobOrder, JoStatus, Machine, Operator } from "."; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { revalidateTag } from "next/cache"; | |||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||
| import { FileResponse } from "@/app/api/pdf/actions"; | |||
| export interface SaveJo { | |||
| bomId: number; | |||
| @@ -155,7 +156,7 @@ export const printFGStockInLabel = cache(async(data: PrintFGStockInLabelRequest) | |||
| } | |||
| return serverFetchWithNoContent( | |||
| `${BASE_API_URL}/jo/print-FGPickRecordLabel?${params.toString()}`, | |||
| `${BASE_API_URL}/jo/print-FGStockInLabel?${params.toString()}`, | |||
| { | |||
| method: "GET", | |||
| next: { | |||
| @@ -1027,3 +1028,20 @@ export async function PrintPickRecord(request: PrintPickRecordRequest){ | |||
| return { success: true, message: "Print job sent successfully (Pick Record)" } as PrintPickRecordResponse; | |||
| } | |||
| export interface ExportFGStockInLabelRequest { | |||
| stockInLineId: number; | |||
| } | |||
| export const fetchFGStockInLabel = async (data: ExportFGStockInLabelRequest): Promise<FileResponse> => { | |||
| const reportBlob = await serverFetchBlob<FileResponse>( | |||
| `${BASE_API_URL}/jo/FGStockInLabel`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }, | |||
| ); | |||
| return reportBlob; | |||
| }; | |||
| @@ -94,7 +94,8 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess | |||
| setModalInfo({ | |||
| id: process.stockInLineId, | |||
| expiryDate: dayjs().add(1, "month").format(OUTPUT_DATE_FORMAT), | |||
| //expiryDate: dayjs().add(1, "month").format(OUTPUT_DATE_FORMAT), | |||
| // 视需要补 itemId、jobOrderId 等 | |||
| }); | |||
| setOpenModal(true); | |||
| @@ -413,10 +413,11 @@ useEffect(() => { | |||
| } else { return 60} | |||
| }; | |||
| const formattedDesc = (content: string = "") => { | |||
| const formattedDesc = (content: string | null | undefined = "") => { | |||
| const safeContent = content || ""; | |||
| return ( | |||
| <> | |||
| {content.split("\\n").map((line, index) => ( | |||
| {safeContent.split("\\n").map((line, index) => ( | |||
| <span key={index}> {line} <br/></span> | |||
| ))} | |||
| </> | |||
| @@ -40,7 +40,7 @@ import { StockInLineEntry, updateStockInLine, printQrCodeForSil, PrintQrCodeForS | |||
| import { fetchStockInLineInfo } from "@/app/api/stockIn/actions"; | |||
| import FgStockInForm from "../StockIn/FgStockInForm"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| import { printFGStockInLabel, PrintFGStockInLabelRequest } from "@/app/api/jo/actions"; | |||
| import { printFGStockInLabel, PrintFGStockInLabelRequest, fetchFGStockInLabel } from "@/app/api/jo/actions"; | |||
| const style = { | |||
| position: "absolute", | |||
| @@ -119,7 +119,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| const res = await fetchStockInLineInfo(stockInLineId); | |||
| if (res) { | |||
| console.log("%c Fetched Stock In Line: ", "color:orange", res); | |||
| setStockInLineInfo({...inputDetail, ...res, expiryDate: inputDetail?.expiryDate}); // TODO review to overwrite res with inputDetail instead (revise PO fetching data) | |||
| setStockInLineInfo({...inputDetail, ...res, expiryDate: res.expiryDate}); | |||
| // fetchQcResultData(stockInLineId); | |||
| } else throw("Result is undefined"); | |||
| @@ -168,8 +168,8 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| { | |||
| ...d, | |||
| // status: d.status ?? "pending", | |||
| productionDate: d.productionDate ? arrayToDateString(d.productionDate, "input") : undefined, | |||
| expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined, | |||
| productionDate: d.productionDate ? arrayToDateString(d.productionDate, "input") : dayjs().format(INPUT_DATE_FORMAT), | |||
| expiryDate: d.expiryDate ? (Array.isArray(d.expiryDate) ? arrayToDateString(d.expiryDate, "input") : d.expiryDate) : undefined, | |||
| receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") | |||
| : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | |||
| acceptQty: d.status != StockInStatus.REJECTED ? (d.demandQty?? d.acceptedQty) : 0, | |||
| @@ -350,9 +350,9 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| 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"), | |||
| productionDate : data.productionDate ? (Array.isArray(data.productionDate) ? arrayToDateString(data.productionDate, "input") : data.productionDate) : undefined, | |||
| expiryDate : data.expiryDate ? (Array.isArray(data.expiryDate) ? arrayToDateString(data.expiryDate, "input") : data.expiryDate) : undefined, | |||
| receiptDate : data.receiptDate ? (Array.isArray(data.receiptDate) ? arrayToDateString(data.receiptDate, "input") : data.receiptDate) : undefined, | |||
| qcAccept: qcAccept? qcAccept : false, | |||
| acceptQty: acceptQty? acceptQty : 0, | |||
| @@ -540,24 +540,38 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| // return isPassed | |||
| // }, [acceptQty, formProps]) | |||
| const printQrcode = useCallback( | |||
| async () => { | |||
| setIsPrinting(true); | |||
| try { | |||
| const postData = { stockInLineIds: [stockInLineInfo?.id] }; | |||
| const response = await fetchPoQrcode(postData); | |||
| if (response) { | |||
| console.log(response); | |||
| downloadFile(new Uint8Array(response.blobValue), response.filename!); | |||
| } | |||
| } catch (e) { | |||
| console.log("%c Error downloading QR Code", "color:red", e); | |||
| } finally { | |||
| const printQrcode = useCallback( | |||
| async () => { | |||
| setIsPrinting(true); | |||
| try { | |||
| let response; | |||
| if (printSource === "productionProcess") { | |||
| // Use FG Stock In Label download API for production process | |||
| if (!stockInLineInfo?.id) { | |||
| console.error("Stock In Line ID is required for download"); | |||
| setIsPrinting(false); | |||
| return; | |||
| } | |||
| }, | |||
| [stockInLineInfo], | |||
| ); | |||
| const postData = { stockInLineId: stockInLineInfo.id }; | |||
| response = await fetchFGStockInLabel(postData); | |||
| } else { | |||
| const postData = { stockInLineIds: [stockInLineInfo?.id] }; | |||
| response = await fetchPoQrcode(postData); | |||
| } | |||
| if (response) { | |||
| console.log(response); | |||
| downloadFile(new Uint8Array(response.blobValue), response.filename!); | |||
| } | |||
| } catch (e) { | |||
| console.log("%c Error downloading QR Code", "color:red", e); | |||
| } finally { | |||
| setIsPrinting(false); | |||
| } | |||
| }, | |||
| [stockInLineInfo, printSource], | |||
| ); | |||
| return ( | |||
| <> | |||
| @@ -12,11 +12,12 @@ import { | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| Button, | |||
| } from "@mui/material"; | |||
| import { Controller, useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useEffect, useMemo } from "react"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| @@ -35,6 +36,11 @@ import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { INPUT_DATE_FORMAT, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| import dayjs from "dayjs"; | |||
| import CalculateExpiryDateModal from "./CalculateExpiryDateModal"; | |||
| import { InputAdornment } from "@mui/material"; | |||
| import { dayjsToDateString } from "@/app/utils/formatUtil"; | |||
| // change PurchaseQcResult to stock in entry props | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| @@ -115,6 +121,8 @@ const FgStockInForm: React.FC<Props> = ({ | |||
| console.log(errors); | |||
| }, [errors]); | |||
| const [openModal, setOpenModal] = useState<boolean>(false); | |||
| const [openExpDatePicker, setOpenExpDatePicker] = useState<boolean>(false); | |||
| const productionDate = watch("productionDate"); | |||
| const expiryDate = watch("expiryDate"); | |||
| const uom = watch("uom"); | |||
| @@ -140,7 +148,23 @@ const FgStockInForm: React.FC<Props> = ({ | |||
| console.log("%c StockInForm itemDetail update: ", "color: brown", itemDetail); | |||
| }, [itemDetail]); | |||
| return ( | |||
| const handleOpenModal = useCallback(() => { | |||
| setOpenModal(true); | |||
| }, []); | |||
| const handleOnModalClose = useCallback(() => { | |||
| setOpenExpDatePicker(false); | |||
| setOpenModal(false); | |||
| }, []); | |||
| const handleReturnExpiryDate = useCallback((result: dayjs.Dayjs) => { | |||
| if (result) { | |||
| setValue("expiryDate", dayjsToDateString(result)); | |||
| } | |||
| }, [setValue]); | |||
| return ( | |||
| <> | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| {/* <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| @@ -250,6 +274,44 @@ const FgStockInForm: React.FC<Props> = ({ | |||
| />) | |||
| } | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <Controller | |||
| control={control} | |||
| name="productionDate" | |||
| render={({ field }) => { | |||
| return ( | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={`${language}-hk`} | |||
| > | |||
| <DatePicker | |||
| {...field} | |||
| sx={textfieldSx} | |||
| label={t("productionDate")} | |||
| value={productionDate ? dayjs(productionDate) : undefined} | |||
| format={OUTPUT_DATE_FORMAT} | |||
| disabled={disabled} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| setValue( | |||
| "productionDate", | |||
| date.format(INPUT_DATE_FORMAT), | |||
| ); | |||
| }} | |||
| inputRef={field.ref} | |||
| slotProps={{ | |||
| textField: { | |||
| error: Boolean(errors.productionDate?.message), | |||
| helperText: errors.productionDate?.message, | |||
| }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| ); | |||
| }} | |||
| /> | |||
| </Grid> | |||
| {/* {putawayMode || (<> | |||
| {/* {putawayMode || (<> | |||
| <Grid item xs={6}> | |||
| <Controller | |||
| @@ -313,28 +375,47 @@ const FgStockInForm: React.FC<Props> = ({ | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={`${language}-hk`} | |||
| > | |||
| <DatePicker | |||
| {...field} | |||
| sx={textfieldSx} | |||
| label={t("expiryDate")} | |||
| value={expiryDate ? dayjs(expiryDate) : undefined} | |||
| format={OUTPUT_DATE_FORMAT} | |||
| disabled={disabled} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| console.log(date.format(INPUT_DATE_FORMAT)); | |||
| setValue("expiryDate", date.format(INPUT_DATE_FORMAT)); | |||
| // field.onChange(date); | |||
| }} | |||
| inputRef={field.ref} | |||
| slotProps={{ | |||
| textField: { | |||
| // required: true, | |||
| error: Boolean(errors.expiryDate?.message), | |||
| helperText: errors.expiryDate?.message, | |||
| }, | |||
| }} | |||
| /> | |||
| <DatePicker | |||
| {...field} | |||
| sx={textfieldSx} | |||
| label={t("expiryDate")} | |||
| value={expiryDate ? dayjs(expiryDate) : undefined} | |||
| format={OUTPUT_DATE_FORMAT} | |||
| disabled={disabled} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| console.log(date.format(INPUT_DATE_FORMAT)); | |||
| setValue("expiryDate", date.format(INPUT_DATE_FORMAT)); | |||
| }} | |||
| inputRef={field.ref} | |||
| open={openExpDatePicker && !openModal} | |||
| onOpen={() => setOpenExpDatePicker(true)} | |||
| onClose={() => setOpenExpDatePicker(false)} | |||
| slotProps={{ | |||
| textField: { | |||
| InputProps: { | |||
| ...(!disabled && { | |||
| endAdornment: ( | |||
| <InputAdornment position='end'> | |||
| <Button | |||
| type="button" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ fontSize: '24px' }} | |||
| onClick={handleOpenModal} | |||
| > | |||
| {t("Calculate Expiry Date")} | |||
| </Button> | |||
| </InputAdornment> | |||
| ), | |||
| }) | |||
| }, | |||
| error: Boolean(errors.expiryDate?.message), | |||
| helperText: errors.expiryDate?.message, | |||
| onClick: () => setOpenExpDatePicker(true), | |||
| }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| ); | |||
| }} | |||
| @@ -442,6 +523,13 @@ const FgStockInForm: React.FC<Props> = ({ | |||
| </Grid> */} | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| <CalculateExpiryDateModal | |||
| open={openModal} | |||
| onClose={handleOnModalClose} | |||
| onSubmit={handleReturnExpiryDate} | |||
| textfieldSx={textfieldSx} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| export default FgStockInForm; | |||