From 71494a40897c5194b178402f2d029528a0570081 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 3 Dec 2025 15:36:48 +0800 Subject: [PATCH] update --- .../FinishedGoodSearch/FinishedGoodSearch.tsx | 237 +++++++++++------- .../FinishedGoodSearchWrapper.tsx | 7 +- .../GoodPickExecutionRecord.tsx | 45 +++- .../ProductionProcessDetail.tsx | 4 +- .../ProductionProcessJobOrderDetail.tsx | 39 ++- src/i18n/zh/common.json | 4 +- src/i18n/zh/jo.json | 3 +- src/i18n/zh/pickOrder.json | 2 + 8 files changed, 237 insertions(+), 104 deletions(-) diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index ef423f8..f225179 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -15,7 +15,7 @@ import { import { arrayToDayjs, } from "@/app/utils/formatUtil"; -import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box } from "@mui/material"; +import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box, TextField } from "@mui/material"; import PickOrders from "./FinishedGood"; import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; import PickExecution from "./GoodPickExecution"; @@ -38,10 +38,13 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import dayjs, { Dayjs } from 'dayjs'; +import { PrinterCombo } from "@/app/api/settings/printer"; +import { Autocomplete } from "@mui/material"; import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable"; interface Props { pickOrders: PickOrderResult[]; + printerCombo: PrinterCombo[]; } type SearchQuery = Partial< @@ -50,7 +53,7 @@ type SearchQuery = Partial< type SearchParamNames = keyof SearchQuery; -const PickOrderSearch: React.FC = ({ pickOrders }) => { +const PickOrderSearch: React.FC = ({ pickOrders, printerCombo }) => { const { t } = useTranslation("pickOrder"); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; @@ -67,6 +70,18 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { // const [summary2F, setSummary2F] = useState(null); // const [summary4F, setSummary4F] = useState(null); const [isLoadingSummary, setIsLoadingSummary] = useState(false); + + const [selectedPrinterForAllDraft, setSelectedPrinterForAllDraft] = useState( + printerCombo && printerCombo.length > 0 ? printerCombo[0] : null + ); + const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState( + printerCombo && printerCombo.length > 0 ? printerCombo[0] : null + ); + const [selectedPrinterForRecord, setSelectedPrinterForRecord] = useState( + printerCombo && printerCombo.length > 0 ? printerCombo[0] : null + ); + + const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState( typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' ); @@ -118,11 +133,20 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { }) return; } - + if (!selectedPrinterForDraft) { + Swal.fire({ + position: "bottom-end", + icon: "warning", + text: t("Please select a printer first"), + showConfirmButton: false, + timer: 1500 + }); + return; + } const currentFgOrder = fgPickOrdersData[0]; const printRequest = { - printerId: 1, + printerId: selectedPrinterForDraft.id, printQty: 1, isDraft: true, numOfCarton: 0, @@ -154,13 +178,22 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { } catch(error){ console.error("error: ", error) } - },[t, fgPickOrdersData]); + },[t, fgPickOrdersData, selectedPrinterForDraft]); const handleAllDraft = useCallback(async () =>{ try { const releasedOrders = await fetchReleasedDoPickOrders(); console.log('fgPickOrdersData length:' + releasedOrders.length) - + if (!selectedPrinterForAllDraft) { + Swal.fire({ + position: "bottom-end", + icon: "warning", + text: t("Please select a printer first"), + showConfirmButton: false, + timer: 1500 + }); + return; + } if(releasedOrders.length === 0) { console.log("No released do_pick_order records found"); Swal.fire({ @@ -203,7 +236,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { console.log(`Processing order - DoPickOrder ID: ${doPickOrderId}, Ticket No: ${order.ticketNo}`); const printRequest = { - printerId: 1, + printerId: selectedPrinterForAllDraft.id, printQty: 1, isDraft: true, numOfCarton: 0, @@ -229,7 +262,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { console.error("Error in handleAllDraft:",error); } - },[t, fgPickOrdersData]); + },[t, fgPickOrdersData, selectedPrinterForAllDraft]); @@ -540,86 +573,118 @@ const handleAssignByLane = useCallback(async ( {/* Header section */} - - - - - {t("Finished Good Order")} - - - - - + {/* 左侧标题 */} + + {t("Finished Good Order")} + + + {/* 右侧:打印机 + 按钮 */} + + + {t("A4 Printer")}: + + + option.name || option.label || option.code || `Printer ${option.id}` + } + value={selectedPrinterForAllDraft} + onChange={(_, newValue) => setSelectedPrinterForAllDraft(newValue)} + sx={{ minWidth: 200 }} + size="small" + renderInput={(params) => ( + + )} + /> + + + {t("Label Printer")}: + + + option.name || option.label || option.code || `Printer ${option.id}` + } + value={selectedPrinterForDraft} + onChange={(_, newValue) => setSelectedPrinterForDraft(newValue)} + sx={{ minWidth: 200 }} + size="small" + renderInput={(params) => ( + + )} + /> + + + + - - - - - + justifyContent: 'center', + '&.Mui-disabled': { + height: '40px', + }, + }} + title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} + onClick={handleDraft} + > + {t("Print Draft")} + + + {/* Tabs section - ✅ Move the click handler here */} ) } - {tabIndex === 2 && } + {tabIndex === 2 && } {tabIndex === 3 && } diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearchWrapper.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearchWrapper.tsx index 1df245d..11657f4 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearchWrapper.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearchWrapper.tsx @@ -1,13 +1,13 @@ import { fetchPickOrders } from "@/app/api/pickOrder"; import GeneralLoading from "../General/GeneralLoading"; import PickOrderSearch from "./FinishedGoodSearch"; - +import{fetchPrinterCombo} from "@/app/api/settings/printer"; interface SubComponents { Loading: typeof GeneralLoading; } const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => { - const [pickOrders] = await Promise.all([ + const [pickOrders, printerCombo] = await Promise.all([ fetchPickOrders({ code: undefined, targetDateFrom: undefined, @@ -16,9 +16,10 @@ const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => { status: undefined, itemName: undefined, }), + fetchPrinterCombo(), ]); - return ; + return ; }; FinishedGoodSearchWrapper.Loading = GeneralLoading; diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index 6d1d6d7..bb5670b 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -25,6 +25,8 @@ import { AccordionSummary, AccordionDetails, } from "@mui/material"; +import { PrinterCombo } from "@/app/api/settings/printer"; +import { Autocomplete } from "@mui/material"; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { useCallback, useEffect, useState, useRef, useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -69,6 +71,9 @@ import Swal from "sweetalert2"; interface Props { filterArgs: Record; + printerCombo: PrinterCombo[]; + a4Printer: PrinterCombo | null; // A4 打印机(DN 用) + labelPrinter: PrinterCombo | null; } @@ -82,7 +87,7 @@ interface PickOrderData { lots: any[]; } -const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { +const GoodPickExecutionRecord: React.FC = ({ filterArgs, printerCombo, a4Printer, labelPrinter }) => { const { t } = useTranslation("pickOrder"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; @@ -112,6 +117,16 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { const errors = formProps.formState.errors; const handleDN = useCallback(async (recordId: number) => { + if (!a4Printer) { + Swal.fire({ + position: "bottom-end", + icon: "warning", + text: t("Please select a printer first"), + showConfirmButton: false, + timer: 1500 + }); + return; + } const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), icon: "info", @@ -143,7 +158,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { const numOfCartons = askNumofCarton.value; try{ const printRequest = { - printerId: 1, + printerId: a4Printer.id, printQty: 1, isDraft: false, numOfCarton: numOfCartons, @@ -172,6 +187,26 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { }, [t]); const handleDNandLabel = useCallback(async (recordId: number) => { + if (!a4Printer || !labelPrinter) { + Swal.fire({ + position: "bottom-end", + icon: "warning", + text: t("Please select a printer first"), + showConfirmButton: false, + timer: 1500 + }); + return; + } + if (!labelPrinter) { + Swal.fire({ + position: "bottom-end", + icon: "warning", + text: t("Please select a label printer first"), + showConfirmButton: false, + timer: 1500 + }); + return; + } const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), icon: "info", @@ -203,7 +238,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { const numOfCartons = askNumofCarton.value; try{ const printDNRequest = { - printerId: 1, + printerId: a4Printer.id, printQty: 1, isDraft: false, numOfCarton: numOfCartons, @@ -211,7 +246,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { }; const printDNLabelsRequest = { - printerId: 1, + printerId: labelPrinter.id, printQty: 1, numOfCarton: numOfCartons, doPickOrderId: recordId @@ -280,7 +315,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { const numOfCartons = askNumofCarton.value; try{ const printRequest = { - printerId: 1, + printerId: labelPrinter?.id ?? 0, printQty: 1, numOfCarton: numOfCartons, doPickOrderId: recordId diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 3a5fe7a..e434ae6 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -568,7 +568,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { {t("Production Process Steps")} - + {!isExecutingLine ? ( /* ========== 步骤列表视图 ========== */ @@ -578,7 +578,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { {t("Seq")} {t("Step Name")} {t("Description")} - {t("Equipment Type/Code")} + {t("EquipmentType-EquipmentName-Code")} {t("Operator")} {/*} {t("Processing Time (mins)")} diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 79aa8a9..295e349 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -43,6 +43,7 @@ interface JobOrderLine { uom: string; shortUom: string; availableStatus: string; + type: string; } interface ProductProcessJobOrderDetailProps { @@ -105,6 +106,9 @@ const ProductionProcessJobOrderDetail: React.FC { + if (line.type?.toLowerCase() === "consumables") { + return null; + } const inventory = inventoryData.find(inv => inv.itemCode === line.itemCode || inv.itemName === line.itemName ); @@ -115,12 +119,22 @@ const getStockAvailable = (line: JobOrderLine) => { }; const isStockSufficient = (line: JobOrderLine) => { + if (line.type?.toLowerCase() === "consumables") { + return false; + } const stockAvailable = getStockAvailable(line); + if (stockAvailable === null) { + return false; + } return stockAvailable >= line.reqQty; }; const stockCounts = useMemo(() => { - const total = jobOrderLines.length; - const sufficient = jobOrderLines.filter(isStockSufficient).length; + // 过滤掉 consumables 类型的 lines + const nonConsumablesLines = jobOrderLines.filter( + line => line.type?.toLowerCase() !== "consumables" + ); + const total = nonConsumablesLines.length; + const sufficient = nonConsumablesLines.filter(isStockSufficient).length; return { total, sufficient, @@ -131,7 +145,8 @@ const status = processData?.status?.toLowerCase?.() ?? ""; const handleDeleteJobOrder = useCallback(async ( jobOrderId: number) => { const response = await deleteJobOrder(jobOrderId) if (response) { - setProcessData(response.entity); + //setProcessData(response.entity); + await fetchData(); } }, [jobOrderId]); const handleRelease = useCallback(async ( jobOrderId: number) => { @@ -139,7 +154,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { console.log("Release clicked for jobOrderId:", jobOrderId); const response = await releaseJo({ id: jobOrderId }) if (response) { - setProcessData(response.entity); + //setProcessData(response.entity); + await fetchData(); } }, [jobOrderId]); const handleTabChange = useCallback>( @@ -318,6 +334,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { align: "right", headerAlign: "right", renderCell: (params: GridRenderCellParams) => { + if (params.row.type?.toLowerCase() === "consumables") { + return t("N/A"); + } + return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; }, }, @@ -328,9 +348,15 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { align: "right", headerAlign: "right", type: "number", - renderCell: (params: GridRenderCellParams) => { + // 如果是 consumables,显示 N/A + if (params.row.type?.toLowerCase() === "consumables") { + return t("N/A"); + } const stockAvailable = getStockAvailable(params.row); + if (stockAvailable === null) { + return t("N/A"); + } return `${decimalFormatter.format(stockAvailable)} (${params.row.shortUom})`; }, }, @@ -360,6 +386,9 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { headerAlign: "center", type: "boolean", renderCell: (params: GridRenderCellParams) => { + if (params.row.type?.toLowerCase() === "consumables") { + return {t("N/A")}; + } return isStockSufficient(params.row) ? : ; diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index d173a65..99aeabe 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -18,7 +18,7 @@ "R&D": "研發", "STF": "樣品", "Other": "其他", - + "Add some entries!": "添加條目", "Add Record": "新增", "Clean Record": "重置", @@ -34,7 +34,7 @@ "Items": "物料", "Release": "放單", "Demand Forecast Setting": "需求預測設定", - "Equipment Type/Code": "使用設備-編號", + "EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號", "Equipment": "設備", "Time Information(mins)": "時間信息(分鐘)", "Processing Time": "生產時間", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 1551c2b..4bc477b 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -390,7 +390,8 @@ "Step Information": "步驟信息", "Stop": "停止", "Demand Forecast Setting": "需求預測設定", - "Equipment Type/Code": "使用設備-編號", + "EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號", + "Equipment": "設備", "Time Information(mins)": "時間信息(分鐘)", "Processing Time": "生產時間", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 2382db7..65a0a14 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -205,6 +205,8 @@ "Accept Stock Out": "接受出庫", "Pick Another Lot": "欠數,並重新選擇批號", "Delivery Note Code": "送貨單編號", + "A4 Printer": "A4 打印機", + "Label Printer": "標籤打印機", "Lot No": "批號", "Expiry Date": "到期日", "Location": "位置",