| @@ -15,7 +15,7 @@ import { | |||||
| import { | import { | ||||
| arrayToDayjs, | arrayToDayjs, | ||||
| } from "@/app/utils/formatUtil"; | } 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 PickOrders from "./FinishedGood"; | ||||
| import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; | import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; | ||||
| import PickExecution from "./GoodPickExecution"; | 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 { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; | ||||
| import { DatePicker } from '@mui/x-date-pickers/DatePicker'; | import { DatePicker } from '@mui/x-date-pickers/DatePicker'; | ||||
| import dayjs, { Dayjs } from 'dayjs'; | import dayjs, { Dayjs } from 'dayjs'; | ||||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||||
| import { Autocomplete } from "@mui/material"; | |||||
| import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable"; | import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable"; | ||||
| interface Props { | interface Props { | ||||
| pickOrders: PickOrderResult[]; | pickOrders: PickOrderResult[]; | ||||
| printerCombo: PrinterCombo[]; | |||||
| } | } | ||||
| type SearchQuery = Partial< | type SearchQuery = Partial< | ||||
| @@ -50,7 +53,7 @@ type SearchQuery = Partial< | |||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| const PickOrderSearch: React.FC<Props> = ({ pickOrders, printerCombo }) => { | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| @@ -67,6 +70,18 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| // const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | // const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | ||||
| // const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | // const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | ||||
| const [isLoadingSummary, setIsLoadingSummary] = useState(false); | const [isLoadingSummary, setIsLoadingSummary] = useState(false); | ||||
| const [selectedPrinterForAllDraft, setSelectedPrinterForAllDraft] = useState<PrinterCombo | null>( | |||||
| printerCombo && printerCombo.length > 0 ? printerCombo[0] : null | |||||
| ); | |||||
| const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCombo | null>( | |||||
| printerCombo && printerCombo.length > 0 ? printerCombo[0] : null | |||||
| ); | |||||
| const [selectedPrinterForRecord, setSelectedPrinterForRecord] = useState<PrinterCombo | null>( | |||||
| printerCombo && printerCombo.length > 0 ? printerCombo[0] : null | |||||
| ); | |||||
| const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>( | const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>( | ||||
| typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' | typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' | ||||
| ); | ); | ||||
| @@ -118,11 +133,20 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| }) | }) | ||||
| return; | 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 currentFgOrder = fgPickOrdersData[0]; | ||||
| const printRequest = { | const printRequest = { | ||||
| printerId: 1, | |||||
| printerId: selectedPrinterForDraft.id, | |||||
| printQty: 1, | printQty: 1, | ||||
| isDraft: true, | isDraft: true, | ||||
| numOfCarton: 0, | numOfCarton: 0, | ||||
| @@ -154,13 +178,22 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| } catch(error){ | } catch(error){ | ||||
| console.error("error: ", error) | console.error("error: ", error) | ||||
| } | } | ||||
| },[t, fgPickOrdersData]); | |||||
| },[t, fgPickOrdersData, selectedPrinterForDraft]); | |||||
| const handleAllDraft = useCallback(async () =>{ | const handleAllDraft = useCallback(async () =>{ | ||||
| try { | try { | ||||
| const releasedOrders = await fetchReleasedDoPickOrders(); | const releasedOrders = await fetchReleasedDoPickOrders(); | ||||
| console.log('fgPickOrdersData length:' + releasedOrders.length) | 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) { | if(releasedOrders.length === 0) { | ||||
| console.log("No released do_pick_order records found"); | console.log("No released do_pick_order records found"); | ||||
| Swal.fire({ | Swal.fire({ | ||||
| @@ -203,7 +236,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| console.log(`Processing order - DoPickOrder ID: ${doPickOrderId}, Ticket No: ${order.ticketNo}`); | console.log(`Processing order - DoPickOrder ID: ${doPickOrderId}, Ticket No: ${order.ticketNo}`); | ||||
| const printRequest = { | const printRequest = { | ||||
| printerId: 1, | |||||
| printerId: selectedPrinterForAllDraft.id, | |||||
| printQty: 1, | printQty: 1, | ||||
| isDraft: true, | isDraft: true, | ||||
| numOfCarton: 0, | numOfCarton: 0, | ||||
| @@ -229,7 +262,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| console.error("Error in handleAllDraft:",error); | console.error("Error in handleAllDraft:",error); | ||||
| } | } | ||||
| },[t, fgPickOrdersData]); | |||||
| },[t, fgPickOrdersData, selectedPrinterForAllDraft]); | |||||
| @@ -540,86 +573,118 @@ const handleAssignByLane = useCallback(async ( | |||||
| {/* Header section */} | {/* Header section */} | ||||
| <Box sx={{ | |||||
| p: 1, | |||||
| borderBottom: '1px solid #e0e0e0', | |||||
| minHeight: 'auto' // 确保最小高度自适应 | |||||
| }}> | |||||
| <Grid container alignItems="center" spacing={1}> | |||||
| <Grid item xs={8}> | |||||
| <Typography | |||||
| variant="h5" | |||||
| sx={{ | |||||
| lineHeight: 1.4, // 调整行高 | |||||
| m: 0, | |||||
| fontWeight: 500 | |||||
| }} | |||||
| > | |||||
| {t("Finished Good Order")} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <Box sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'flex-end', | |||||
| <Box | |||||
| sx={{ | |||||
| p: 1, | |||||
| borderBottom: '1px solid #e0e0e0', | |||||
| minHeight: 'auto', | |||||
| display: 'flex', | |||||
| alignItems: 'center', | |||||
| justifyContent: 'space-between', // 左标题,右控件 | |||||
| gap: 2, | |||||
| flexWrap: 'wrap', // 如果屏幕窄就自动换行 | |||||
| }} | |||||
| > | |||||
| {/* 左侧标题 */} | |||||
| <Typography | |||||
| variant="h5" | |||||
| sx={{ | |||||
| lineHeight: 1.4, | |||||
| m: 0, | |||||
| fontWeight: 500, | |||||
| }} | |||||
| > | |||||
| {t("Finished Good Order")} | |||||
| </Typography> | |||||
| {/* 右侧:打印机 + 按钮 */} | |||||
| <Stack | |||||
| direction="row" | |||||
| spacing={2} | |||||
| sx={{ | |||||
| alignItems: 'center', | |||||
| flexWrap: 'wrap', // 控件太多时换行,不会撑出横向滚动 | |||||
| rowGap: 1, | |||||
| }} | |||||
| > | |||||
| <Typography variant="body2" sx={{ minWidth: 'fit-content', mr: 1.5 }}> | |||||
| {t("A4 Printer")}: | |||||
| </Typography> | |||||
| <Autocomplete | |||||
| options={printerCombo || []} | |||||
| getOptionLabel={(option) => | |||||
| option.name || option.label || option.code || `Printer ${option.id}` | |||||
| } | |||||
| value={selectedPrinterForAllDraft} | |||||
| onChange={(_, newValue) => setSelectedPrinterForAllDraft(newValue)} | |||||
| sx={{ minWidth: 200 }} | |||||
| size="small" | |||||
| renderInput={(params) => ( | |||||
| <TextField {...params} placeholder={t("A4 Printer")} /> | |||||
| )} | |||||
| /> | |||||
| <Typography variant="body2" sx={{ minWidth: 'fit-content', ml: 1 }}> | |||||
| {t("Label Printer")}: | |||||
| </Typography> | |||||
| <Autocomplete | |||||
| options={printerCombo || []} | |||||
| getOptionLabel={(option) => | |||||
| option.name || option.label || option.code || `Printer ${option.id}` | |||||
| } | |||||
| value={selectedPrinterForDraft} | |||||
| onChange={(_, newValue) => setSelectedPrinterForDraft(newValue)} | |||||
| sx={{ minWidth: 200 }} | |||||
| size="small" | |||||
| renderInput={(params) => ( | |||||
| <TextField {...params} placeholder={t("Label Printer")} /> | |||||
| )} | |||||
| /> | |||||
| <Button | |||||
| variant="contained" | |||||
| sx={{ | |||||
| py: 0.5, | |||||
| px: 1.25, | |||||
| height: '40px', | |||||
| fontSize: '0.75rem', | |||||
| lineHeight: 1.2, | |||||
| display: 'flex', | |||||
| alignItems: 'center', | |||||
| justifyContent: 'center', | |||||
| '&.Mui-disabled': { | |||||
| height: '40px', | |||||
| }, | |||||
| }} | |||||
| onClick={handleAllDraft} | |||||
| > | |||||
| {t("Print All Draft")} ({releasedOrderCount}) | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| sx={{ | |||||
| py: 0.5, | |||||
| px: 1.25, | |||||
| height: '40px', | |||||
| fontSize: '0.75rem', | |||||
| lineHeight: 1.2, | |||||
| display: 'flex', | |||||
| alignItems: 'center', | alignItems: 'center', | ||||
| height: '100%' | |||||
| }}> | |||||
| <Stack | |||||
| direction="row" | |||||
| spacing={0.5} | |||||
| sx={{ | |||||
| alignItems: 'center', | |||||
| height: '100%' | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| variant="contained" | |||||
| sx={{ | |||||
| py: 0.5, // 增加垂直padding | |||||
| px: 1.25, // 增加水平padding | |||||
| height: '40px', // 增加按钮高度 | |||||
| fontSize: '0.75rem', | |||||
| lineHeight: 1.2, // 添加行高控制 | |||||
| display: 'flex', | |||||
| alignItems: 'center', | |||||
| justifyContent: 'center', | |||||
| '&.Mui-disabled': { | |||||
| height: '40px' | |||||
| } | |||||
| }} | |||||
| onClick={handleAllDraft} | |||||
| > | |||||
| {t("Print All Draft")} ({releasedOrderCount}) | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| sx={{ | |||||
| py: 0.5, | |||||
| px: 1.25, | |||||
| height: '40px', | |||||
| fontSize: '0.75rem', | |||||
| lineHeight: 1.2, | |||||
| display: 'flex', | |||||
| alignItems: 'center', | |||||
| justifyContent: 'center', | |||||
| '&.Mui-disabled': { | |||||
| height: '40px' | |||||
| } | |||||
| }} | |||||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||||
| onClick={handleDraft} | |||||
| > | |||||
| {t("Print Draft")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Box> | |||||
| </Grid> | |||||
| </Grid> | |||||
| justifyContent: 'center', | |||||
| '&.Mui-disabled': { | |||||
| height: '40px', | |||||
| }, | |||||
| }} | |||||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||||
| onClick={handleDraft} | |||||
| > | |||||
| {t("Print Draft")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Box> | </Box> | ||||
| {/* Tabs section - ✅ Move the click handler here */} | {/* Tabs section - ✅ Move the click handler here */} | ||||
| <Box sx={{ | <Box sx={{ | ||||
| borderBottom: '1px solid #e0e0e0' | borderBottom: '1px solid #e0e0e0' | ||||
| @@ -650,7 +715,7 @@ const handleAssignByLane = useCallback(async ( | |||||
| onRefreshReleasedOrderCount={fetchReleasedOrderCount} | onRefreshReleasedOrderCount={fetchReleasedOrderCount} | ||||
| /> | /> | ||||
| ) } | ) } | ||||
| {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />} | |||||
| {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} printerCombo={printerCombo} a4Printer={selectedPrinterForAllDraft} labelPrinter={selectedPrinterForDraft} />} | |||||
| {tabIndex === 3 && <FGPickOrderTicketReleaseTable/>} | {tabIndex === 3 && <FGPickOrderTicketReleaseTable/>} | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| @@ -1,13 +1,13 @@ | |||||
| import { fetchPickOrders } from "@/app/api/pickOrder"; | import { fetchPickOrders } from "@/app/api/pickOrder"; | ||||
| import GeneralLoading from "../General/GeneralLoading"; | import GeneralLoading from "../General/GeneralLoading"; | ||||
| import PickOrderSearch from "./FinishedGoodSearch"; | import PickOrderSearch from "./FinishedGoodSearch"; | ||||
| import{fetchPrinterCombo} from "@/app/api/settings/printer"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof GeneralLoading; | Loading: typeof GeneralLoading; | ||||
| } | } | ||||
| const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => { | const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => { | ||||
| const [pickOrders] = await Promise.all([ | |||||
| const [pickOrders, printerCombo] = await Promise.all([ | |||||
| fetchPickOrders({ | fetchPickOrders({ | ||||
| code: undefined, | code: undefined, | ||||
| targetDateFrom: undefined, | targetDateFrom: undefined, | ||||
| @@ -16,9 +16,10 @@ const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => { | |||||
| status: undefined, | status: undefined, | ||||
| itemName: undefined, | itemName: undefined, | ||||
| }), | }), | ||||
| fetchPrinterCombo(), | |||||
| ]); | ]); | ||||
| return <PickOrderSearch pickOrders={pickOrders} />; | |||||
| return <PickOrderSearch pickOrders={pickOrders} printerCombo={printerCombo} />; | |||||
| }; | }; | ||||
| FinishedGoodSearchWrapper.Loading = GeneralLoading; | FinishedGoodSearchWrapper.Loading = GeneralLoading; | ||||
| @@ -25,6 +25,8 @@ import { | |||||
| AccordionSummary, | AccordionSummary, | ||||
| AccordionDetails, | AccordionDetails, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||||
| import { Autocomplete } from "@mui/material"; | |||||
| import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | ||||
| import { useCallback, useEffect, useState, useRef, useMemo } from "react"; | import { useCallback, useEffect, useState, useRef, useMemo } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -69,6 +71,9 @@ import Swal from "sweetalert2"; | |||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| printerCombo: PrinterCombo[]; | |||||
| a4Printer: PrinterCombo | null; // A4 打印机(DN 用) | |||||
| labelPrinter: PrinterCombo | null; | |||||
| } | } | ||||
| @@ -82,7 +87,7 @@ interface PickOrderData { | |||||
| lots: any[]; | lots: any[]; | ||||
| } | } | ||||
| const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs, printerCombo, a4Printer, labelPrinter }) => { | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -112,6 +117,16 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| const handleDN = useCallback(async (recordId: number) => { | 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({ | const askNumofCarton = await Swal.fire({ | ||||
| title: t("Enter the number of cartons: "), | title: t("Enter the number of cartons: "), | ||||
| icon: "info", | icon: "info", | ||||
| @@ -143,7 +158,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const numOfCartons = askNumofCarton.value; | const numOfCartons = askNumofCarton.value; | ||||
| try{ | try{ | ||||
| const printRequest = { | const printRequest = { | ||||
| printerId: 1, | |||||
| printerId: a4Printer.id, | |||||
| printQty: 1, | printQty: 1, | ||||
| isDraft: false, | isDraft: false, | ||||
| numOfCarton: numOfCartons, | numOfCarton: numOfCartons, | ||||
| @@ -172,6 +187,26 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| }, [t]); | }, [t]); | ||||
| const handleDNandLabel = useCallback(async (recordId: number) => { | 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({ | const askNumofCarton = await Swal.fire({ | ||||
| title: t("Enter the number of cartons: "), | title: t("Enter the number of cartons: "), | ||||
| icon: "info", | icon: "info", | ||||
| @@ -203,7 +238,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const numOfCartons = askNumofCarton.value; | const numOfCartons = askNumofCarton.value; | ||||
| try{ | try{ | ||||
| const printDNRequest = { | const printDNRequest = { | ||||
| printerId: 1, | |||||
| printerId: a4Printer.id, | |||||
| printQty: 1, | printQty: 1, | ||||
| isDraft: false, | isDraft: false, | ||||
| numOfCarton: numOfCartons, | numOfCarton: numOfCartons, | ||||
| @@ -211,7 +246,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| }; | }; | ||||
| const printDNLabelsRequest = { | const printDNLabelsRequest = { | ||||
| printerId: 1, | |||||
| printerId: labelPrinter.id, | |||||
| printQty: 1, | printQty: 1, | ||||
| numOfCarton: numOfCartons, | numOfCarton: numOfCartons, | ||||
| doPickOrderId: recordId | doPickOrderId: recordId | ||||
| @@ -280,7 +315,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const numOfCartons = askNumofCarton.value; | const numOfCartons = askNumofCarton.value; | ||||
| try{ | try{ | ||||
| const printRequest = { | const printRequest = { | ||||
| printerId: 1, | |||||
| printerId: labelPrinter?.id ?? 0, | |||||
| printQty: 1, | printQty: 1, | ||||
| numOfCarton: numOfCartons, | numOfCarton: numOfCartons, | ||||
| doPickOrderId: recordId | doPickOrderId: recordId | ||||
| @@ -568,7 +568,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||||
| <Typography variant="h6" gutterBottom fontWeight="bold"> | <Typography variant="h6" gutterBottom fontWeight="bold"> | ||||
| {t("Production Process Steps")} | {t("Production Process Steps")} | ||||
| </Typography> | </Typography> | ||||
| <ProcessSummaryHeader t={t} processData={processData} /> | |||||
| <ProcessSummaryHeader processData={processData} /> | |||||
| {!isExecutingLine ? ( | {!isExecutingLine ? ( | ||||
| /* ========== 步骤列表视图 ========== */ | /* ========== 步骤列表视图 ========== */ | ||||
| <TableContainer> | <TableContainer> | ||||
| @@ -578,7 +578,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||||
| <TableCell>{t("Seq")}</TableCell> | <TableCell>{t("Seq")}</TableCell> | ||||
| <TableCell>{t("Step Name")}</TableCell> | <TableCell>{t("Step Name")}</TableCell> | ||||
| <TableCell>{t("Description")}</TableCell> | <TableCell>{t("Description")}</TableCell> | ||||
| <TableCell>{t("Equipment Type/Code")}</TableCell> | |||||
| <TableCell>{t("EquipmentType-EquipmentName-Code")}</TableCell> | |||||
| <TableCell>{t("Operator")}</TableCell> | <TableCell>{t("Operator")}</TableCell> | ||||
| {/*} | {/*} | ||||
| <TableCell>{t("Processing Time (mins)")}</TableCell> | <TableCell>{t("Processing Time (mins)")}</TableCell> | ||||
| @@ -43,6 +43,7 @@ interface JobOrderLine { | |||||
| uom: string; | uom: string; | ||||
| shortUom: string; | shortUom: string; | ||||
| availableStatus: string; | availableStatus: string; | ||||
| type: string; | |||||
| } | } | ||||
| interface ProductProcessJobOrderDetailProps { | interface ProductProcessJobOrderDetailProps { | ||||
| @@ -105,6 +106,9 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp | |||||
| }, [fetchData]); | }, [fetchData]); | ||||
| // PickTable 组件内容 | // PickTable 组件内容 | ||||
| const getStockAvailable = (line: JobOrderLine) => { | const getStockAvailable = (line: JobOrderLine) => { | ||||
| if (line.type?.toLowerCase() === "consumables") { | |||||
| return null; | |||||
| } | |||||
| const inventory = inventoryData.find(inv => | const inventory = inventoryData.find(inv => | ||||
| inv.itemCode === line.itemCode || inv.itemName === line.itemName | inv.itemCode === line.itemCode || inv.itemName === line.itemName | ||||
| ); | ); | ||||
| @@ -115,12 +119,22 @@ const getStockAvailable = (line: JobOrderLine) => { | |||||
| }; | }; | ||||
| const isStockSufficient = (line: JobOrderLine) => { | const isStockSufficient = (line: JobOrderLine) => { | ||||
| if (line.type?.toLowerCase() === "consumables") { | |||||
| return false; | |||||
| } | |||||
| const stockAvailable = getStockAvailable(line); | const stockAvailable = getStockAvailable(line); | ||||
| if (stockAvailable === null) { | |||||
| return false; | |||||
| } | |||||
| return stockAvailable >= line.reqQty; | return stockAvailable >= line.reqQty; | ||||
| }; | }; | ||||
| const stockCounts = useMemo(() => { | 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 { | return { | ||||
| total, | total, | ||||
| sufficient, | sufficient, | ||||
| @@ -131,7 +145,8 @@ const status = processData?.status?.toLowerCase?.() ?? ""; | |||||
| const handleDeleteJobOrder = useCallback(async ( jobOrderId: number) => { | const handleDeleteJobOrder = useCallback(async ( jobOrderId: number) => { | ||||
| const response = await deleteJobOrder(jobOrderId) | const response = await deleteJobOrder(jobOrderId) | ||||
| if (response) { | if (response) { | ||||
| setProcessData(response.entity); | |||||
| //setProcessData(response.entity); | |||||
| await fetchData(); | |||||
| } | } | ||||
| }, [jobOrderId]); | }, [jobOrderId]); | ||||
| const handleRelease = useCallback(async ( jobOrderId: number) => { | const handleRelease = useCallback(async ( jobOrderId: number) => { | ||||
| @@ -139,7 +154,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| console.log("Release clicked for jobOrderId:", jobOrderId); | console.log("Release clicked for jobOrderId:", jobOrderId); | ||||
| const response = await releaseJo({ id: jobOrderId }) | const response = await releaseJo({ id: jobOrderId }) | ||||
| if (response) { | if (response) { | ||||
| setProcessData(response.entity); | |||||
| //setProcessData(response.entity); | |||||
| await fetchData(); | |||||
| } | } | ||||
| }, [jobOrderId]); | }, [jobOrderId]); | ||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | ||||
| @@ -318,6 +334,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| align: "right", | align: "right", | ||||
| headerAlign: "right", | headerAlign: "right", | ||||
| renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | ||||
| if (params.row.type?.toLowerCase() === "consumables") { | |||||
| return t("N/A"); | |||||
| } | |||||
| return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; | return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`; | ||||
| }, | }, | ||||
| }, | }, | ||||
| @@ -328,9 +348,15 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| align: "right", | align: "right", | ||||
| headerAlign: "right", | headerAlign: "right", | ||||
| type: "number", | type: "number", | ||||
| renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | ||||
| // 如果是 consumables,显示 N/A | |||||
| if (params.row.type?.toLowerCase() === "consumables") { | |||||
| return t("N/A"); | |||||
| } | |||||
| const stockAvailable = getStockAvailable(params.row); | const stockAvailable = getStockAvailable(params.row); | ||||
| if (stockAvailable === null) { | |||||
| return t("N/A"); | |||||
| } | |||||
| return `${decimalFormatter.format(stockAvailable)} (${params.row.shortUom})`; | return `${decimalFormatter.format(stockAvailable)} (${params.row.shortUom})`; | ||||
| }, | }, | ||||
| }, | }, | ||||
| @@ -360,6 +386,9 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| headerAlign: "center", | headerAlign: "center", | ||||
| type: "boolean", | type: "boolean", | ||||
| renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | renderCell: (params: GridRenderCellParams<JobOrderLine>) => { | ||||
| if (params.row.type?.toLowerCase() === "consumables") { | |||||
| return <Typography>{t("N/A")}</Typography>; | |||||
| } | |||||
| return isStockSufficient(params.row) | return isStockSufficient(params.row) | ||||
| ? <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" /> | ? <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" /> | ||||
| : <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />; | : <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />; | ||||
| @@ -18,7 +18,7 @@ | |||||
| "R&D": "研發", | "R&D": "研發", | ||||
| "STF": "樣品", | "STF": "樣品", | ||||
| "Other": "其他", | "Other": "其他", | ||||
| "Add some entries!": "添加條目", | "Add some entries!": "添加條目", | ||||
| "Add Record": "新增", | "Add Record": "新增", | ||||
| "Clean Record": "重置", | "Clean Record": "重置", | ||||
| @@ -34,7 +34,7 @@ | |||||
| "Items": "物料", | "Items": "物料", | ||||
| "Release": "放單", | "Release": "放單", | ||||
| "Demand Forecast Setting": "需求預測設定", | "Demand Forecast Setting": "需求預測設定", | ||||
| "Equipment Type/Code": "使用設備-編號", | |||||
| "EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號", | |||||
| "Equipment": "設備", | "Equipment": "設備", | ||||
| "Time Information(mins)": "時間信息(分鐘)", | "Time Information(mins)": "時間信息(分鐘)", | ||||
| "Processing Time": "生產時間", | "Processing Time": "生產時間", | ||||
| @@ -390,7 +390,8 @@ | |||||
| "Step Information": "步驟信息", | "Step Information": "步驟信息", | ||||
| "Stop": "停止", | "Stop": "停止", | ||||
| "Demand Forecast Setting": "需求預測設定", | "Demand Forecast Setting": "需求預測設定", | ||||
| "Equipment Type/Code": "使用設備-編號", | |||||
| "EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號", | |||||
| "Equipment": "設備", | "Equipment": "設備", | ||||
| "Time Information(mins)": "時間信息(分鐘)", | "Time Information(mins)": "時間信息(分鐘)", | ||||
| "Processing Time": "生產時間", | "Processing Time": "生產時間", | ||||
| @@ -205,6 +205,8 @@ | |||||
| "Accept Stock Out": "接受出庫", | "Accept Stock Out": "接受出庫", | ||||
| "Pick Another Lot": "欠數,並重新選擇批號", | "Pick Another Lot": "欠數,並重新選擇批號", | ||||
| "Delivery Note Code": "送貨單編號", | "Delivery Note Code": "送貨單編號", | ||||
| "A4 Printer": "A4 打印機", | |||||
| "Label Printer": "標籤打印機", | |||||
| "Lot No": "批號", | "Lot No": "批號", | ||||
| "Expiry Date": "到期日", | "Expiry Date": "到期日", | ||||
| "Location": "位置", | "Location": "位置", | ||||