# Conflicts: # src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsxmaster
| @@ -113,6 +113,7 @@ export interface GetPickOrderLineInfo { | |||||
| requiredQty: number; | requiredQty: number; | ||||
| uomShortDesc: string; | uomShortDesc: string; | ||||
| uomDesc: string; | uomDesc: string; | ||||
| suggestedList: any[]; | suggestedList: any[]; | ||||
| pickedQty: number; | pickedQty: number; | ||||
| } | } | ||||
| @@ -0,0 +1,91 @@ | |||||
| "use client"; | |||||
| import React, { useState } from 'react'; | |||||
| import { | |||||
| Box, | |||||
| Typography, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| Card, | |||||
| CardContent, | |||||
| Stack, | |||||
| } from '@mui/material'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import dayjs from 'dayjs'; | |||||
| const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||||
| const [selectedFloor, setSelectedFloor] = useState<string>(""); | |||||
| const getDateLabel = (offset: number) => { | |||||
| return dayjs().add(offset, 'day').format('YYYY-MM-DD'); | |||||
| }; | |||||
| return ( | |||||
| <Card sx={{ mb: 2 }}> | |||||
| <CardContent> | |||||
| {/* Title */} | |||||
| <Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}> | |||||
| Ticket Release Table | |||||
| </Typography> | |||||
| {/* Dropdown Menus */} | |||||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | |||||
| {/* Date Selection Dropdown */} | |||||
| <FormControl sx={{ minWidth: 250 }} size="small"> | |||||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | |||||
| <Select | |||||
| labelId="date-select-label" | |||||
| id="date-select" | |||||
| value={selectedDate} | |||||
| label={t("Select Date")} | |||||
| onChange={(e) => setSelectedDate(e.target.value)} | |||||
| > | |||||
| <MenuItem value="today"> | |||||
| {t("Today")} ({getDateLabel(0)}) | |||||
| </MenuItem> | |||||
| <MenuItem value="tomorrow"> | |||||
| {t("Tomorrow")} ({getDateLabel(1)}) | |||||
| </MenuItem> | |||||
| <MenuItem value="dayAfterTomorrow"> | |||||
| {t("Day After Tomorrow")} ({getDateLabel(2)}) | |||||
| </MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| {/* Floor Selection Dropdown */} | |||||
| <FormControl sx={{ minWidth: 150 }} size="small"> | |||||
| <InputLabel id="floor-select-label">{t("Floor")}</InputLabel> | |||||
| <Select | |||||
| labelId="floor-select-label" | |||||
| id="floor-select" | |||||
| value={selectedFloor} | |||||
| label={t("Floor")} | |||||
| onChange={(e) => setSelectedFloor(e.target.value)} | |||||
| displayEmpty | |||||
| > | |||||
| <MenuItem value=""> | |||||
| <em>{t("All Floors")}</em> | |||||
| </MenuItem> | |||||
| <MenuItem value="2F">2/F</MenuItem> | |||||
| <MenuItem value="4F">4/F</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| </Stack> | |||||
| {/* Table content will go here */} | |||||
| <Box sx={{ mt: 2 }}> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {/* Add your table component here */} | |||||
| Table content goes here... | |||||
| </Typography> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default FGPickOrderTicketReleaseTable; | |||||
| @@ -38,6 +38,7 @@ 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 FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable"; | |||||
| interface Props { | interface Props { | ||||
| pickOrders: PickOrderResult[]; | pickOrders: PickOrderResult[]; | ||||
| @@ -217,228 +218,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| },[t]); | },[t]); | ||||
| const handleDN = useCallback(async () =>{ | |||||
| const askNumofCarton = await Swal.fire({ | |||||
| title: t("Enter the number of cartons: "), | |||||
| icon: "info", | |||||
| input: "number", | |||||
| inputPlaceholder: t("Number of cartons"), | |||||
| inputAttributes:{ | |||||
| min: "1", | |||||
| step: "1" | |||||
| }, | |||||
| inputValidator: (value) => { | |||||
| if(!value){ | |||||
| return t("You need to enter a number") | |||||
| } | |||||
| if(parseInt(value) < 1){ | |||||
| return t("Number must be at least 1"); | |||||
| } | |||||
| return null | |||||
| }, | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| showLoaderOnConfirm: true, | |||||
| allowOutsideClick: () => !Swal.isLoading() | |||||
| }); | |||||
| if (askNumofCarton.isConfirmed) { | |||||
| const numOfCartons = askNumofCarton.value; | |||||
| try{ | |||||
| if (fgPickOrdersData.length === 0) { | |||||
| console.error("No FG Pick order data available"); | |||||
| return; | |||||
| } | |||||
| const currentFgOrder = fgPickOrdersData[0]; | |||||
| const printRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| isDraft: false, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: currentFgOrder.deliveryOrderId, | |||||
| pickOrderId: currentFgOrder.pickOrderId | |||||
| }; | |||||
| console.log("Printing Delivery Note with request: ", printRequest); | |||||
| const response = await printDN(printRequest); | |||||
| console.log("Print Delivery Note response: ", response); | |||||
| if(response.success){ | |||||
| Swal.fire({ | |||||
| position: "bottom-end", | |||||
| icon: "success", | |||||
| text: t("Printed Successfully."), | |||||
| showConfirmButton: false, | |||||
| timer: 1500 | |||||
| }); | |||||
| } else { | |||||
| console.error("Print failed: ", response.message); | |||||
| } | |||||
| } catch(error){ | |||||
| console.error("error: ", error) | |||||
| } | |||||
| } | |||||
| },[t, fgPickOrdersData]); | |||||
| const handleDNandLabel = useCallback(async () =>{ | |||||
| const askNumofCarton = await Swal.fire({ | |||||
| title: t("Enter the number of cartons: "), | |||||
| icon: "info", | |||||
| input: "number", | |||||
| inputPlaceholder: t("Number of cartons"), | |||||
| inputAttributes:{ | |||||
| min: "1", | |||||
| step: "1" | |||||
| }, | |||||
| inputValidator: (value) => { | |||||
| if(!value){ | |||||
| return t("You need to enter a number") | |||||
| } | |||||
| if(parseInt(value) < 1){ | |||||
| return t("Number must be at least 1"); | |||||
| } | |||||
| return null | |||||
| }, | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| showLoaderOnConfirm: true, | |||||
| allowOutsideClick: () => !Swal.isLoading() | |||||
| }); | |||||
| if (askNumofCarton.isConfirmed) { | |||||
| const numOfCartons = askNumofCarton.value; | |||||
| try{ | |||||
| if (fgPickOrdersData.length === 0) { | |||||
| console.error("No FG Pick order data available"); | |||||
| return; | |||||
| } | |||||
| const currentFgOrder = fgPickOrdersData[0]; | |||||
| const printDNRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| isDraft: false, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: currentFgOrder.deliveryOrderId, | |||||
| pickOrderId: currentFgOrder.pickOrderId | |||||
| }; | |||||
| const printDNLabelsRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: currentFgOrder.deliveryOrderId | |||||
| }; | |||||
| console.log("Printing Labels with request: ", printDNLabelsRequest); | |||||
| console.log("Printing DN with request: ", printDNRequest); | |||||
| const LabelsResponse = await printDNLabels(printDNLabelsRequest); | |||||
| const DNResponse = await printDN(printDNRequest); | |||||
| console.log("Print Labels response: ", LabelsResponse); | |||||
| console.log("Print DN response: ", DNResponse); | |||||
| if(LabelsResponse.success && DNResponse.success){ | |||||
| Swal.fire({ | |||||
| position: "bottom-end", | |||||
| icon: "success", | |||||
| text: t("Printed Successfully."), | |||||
| showConfirmButton: false, | |||||
| timer: 1500 | |||||
| }); | |||||
| } else { | |||||
| if(!LabelsResponse.success){ | |||||
| console.error("Print failed: ", LabelsResponse.message); | |||||
| } | |||||
| else{ | |||||
| console.error("Print failed: ", DNResponse.message); | |||||
| } | |||||
| } | |||||
| } catch(error){ | |||||
| console.error("error: ", error) | |||||
| } | |||||
| } | |||||
| },[t, fgPickOrdersData]); | |||||
| const handleLabel = useCallback(async () =>{ | |||||
| const askNumofCarton = await Swal.fire({ | |||||
| title: t("Enter the number of cartons: "), | |||||
| icon: "info", | |||||
| input: "number", | |||||
| inputPlaceholder: t("Number of cartons"), | |||||
| inputAttributes:{ | |||||
| min: "1", | |||||
| step: "1" | |||||
| }, | |||||
| inputValidator: (value) => { | |||||
| if(!value){ | |||||
| return t("You need to enter a number") | |||||
| } | |||||
| if(parseInt(value) < 1){ | |||||
| return t("Number must be at least 1"); | |||||
| } | |||||
| return null | |||||
| }, | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| showLoaderOnConfirm: true, | |||||
| allowOutsideClick: () => !Swal.isLoading() | |||||
| }); | |||||
| if (askNumofCarton.isConfirmed) { | |||||
| const numOfCartons = askNumofCarton.value; | |||||
| try{ | |||||
| if (fgPickOrdersData.length === 0) { | |||||
| console.error("No FG Pick order data available"); | |||||
| return; | |||||
| } | |||||
| const currentFgOrder = fgPickOrdersData[0]; | |||||
| const printRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: currentFgOrder.deliveryOrderId, | |||||
| }; | |||||
| console.log("Printing Labels with request: ", printRequest); | |||||
| const response = await printDNLabels(printRequest); | |||||
| console.log("Print Labels response: ", response); | |||||
| if(response.success){ | |||||
| Swal.fire({ | |||||
| position: "bottom-end", | |||||
| icon: "success", | |||||
| text: t("Printed Successfully."), | |||||
| showConfirmButton: false, | |||||
| timer: 1500 | |||||
| }); | |||||
| } else { | |||||
| console.error("Print failed: ", response.message); | |||||
| } | |||||
| } catch(error){ | |||||
| console.error("error: ", error) | |||||
| } | |||||
| } | |||||
| },[t, fgPickOrdersData]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchReleasedOrderCount(); | fetchReleasedOrderCount(); | ||||
| @@ -776,7 +556,6 @@ const handleAssignByLane = useCallback(async ( | |||||
| > | > | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| size="small" | |||||
| sx={{ | sx={{ | ||||
| py: 0.5, // 增加垂直padding | py: 0.5, // 增加垂直padding | ||||
| px: 1.25, // 增加水平padding | px: 1.25, // 增加水平padding | ||||
| @@ -796,7 +575,6 @@ const handleAssignByLane = useCallback(async ( | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| size="small" | |||||
| sx={{ | sx={{ | ||||
| py: 0.5, | py: 0.5, | ||||
| px: 1.25, | px: 1.25, | ||||
| @@ -815,72 +593,6 @@ const handleAssignByLane = useCallback(async ( | |||||
| > | > | ||||
| {t("Print Draft")} | {t("Print Draft")} | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| size="small" | |||||
| 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={handleDNandLabel} | |||||
| > | |||||
| {t("Print Pick Order and DN Label")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| size="small" | |||||
| 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={handleDN} | |||||
| > | |||||
| {t("Print Pick Order")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| size="small" | |||||
| 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={handleLabel} | |||||
| > | |||||
| {t("Print DN Label")} | |||||
| </Button> | |||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| @@ -895,6 +607,7 @@ const handleAssignByLane = useCallback(async ( | |||||
| <Tab label={t("Pick Order Detail")} iconPosition="end" /> | <Tab label={t("Pick Order Detail")} iconPosition="end" /> | ||||
| <Tab label={t("Finished Good Detail")} iconPosition="end" /> | <Tab label={t("Finished Good Detail")} iconPosition="end" /> | ||||
| <Tab label={t("Finished Good Record")} iconPosition="end" /> | <Tab label={t("Finished Good Record")} iconPosition="end" /> | ||||
| <Tab label={t("Ticket Release Table")} iconPosition="end" /> | |||||
| </Tabs> | </Tabs> | ||||
| </Box> | </Box> | ||||
| @@ -906,6 +619,7 @@ const handleAssignByLane = useCallback(async ( | |||||
| {tabIndex === 0 && <PickExecution filterArgs={filterArgs} onFgPickOrdersChange={setFgPickOrdersData}/>} | {tabIndex === 0 && <PickExecution filterArgs={filterArgs} onFgPickOrdersChange={setFgPickOrdersData}/>} | ||||
| {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />} | {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />} | ||||
| {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />} | {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />} | ||||
| {tabIndex === 3 && <FGPickOrderTicketReleaseTable/>} | |||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| @@ -41,10 +41,10 @@ import { | |||||
| checkPickOrderCompletion, | checkPickOrderCompletion, | ||||
| PickOrderCompletionResponse, | PickOrderCompletionResponse, | ||||
| checkAndCompletePickOrderByConsoCode, | checkAndCompletePickOrderByConsoCode, | ||||
| fetchCompletedDoPickOrders, // ✅ 新增:使用新的 API | |||||
| fetchCompletedDoPickOrders, | |||||
| CompletedDoPickOrderResponse, | CompletedDoPickOrderResponse, | ||||
| CompletedDoPickOrderSearchParams, | CompletedDoPickOrderSearchParams, | ||||
| fetchLotDetailsByPickOrderId // ✅ 修复:导入类型 | |||||
| fetchLotDetailsByPickOrderId | |||||
| } from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | import { fetchNameList, NameList } from "@/app/api/user/actions"; | ||||
| import { | import { | ||||
| @@ -63,6 +63,9 @@ import GoodPickExecutionForm from "./GoodPickExecutionForm"; | |||||
| import FGPickOrderCard from "./FGPickOrderCard"; | import FGPickOrderCard from "./FGPickOrderCard"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| import { printDN, printDNLabels } from "@/app/api/do/actions"; | |||||
| import Swal from "sweetalert2"; | |||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| @@ -108,6 +111,205 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const formProps = useForm(); | const formProps = useForm(); | ||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| // ✅ Print handler functions | |||||
| const handleDN = useCallback(async (deliveryOrderId: number, pickOrderId: number) => { | |||||
| const askNumofCarton = await Swal.fire({ | |||||
| title: t("Enter the number of cartons: "), | |||||
| icon: "info", | |||||
| input: "number", | |||||
| inputPlaceholder: t("Number of cartons"), | |||||
| inputAttributes:{ | |||||
| min: "1", | |||||
| step: "1" | |||||
| }, | |||||
| inputValidator: (value) => { | |||||
| if(!value){ | |||||
| return t("You need to enter a number") | |||||
| } | |||||
| if(parseInt(value) < 1){ | |||||
| return t("Number must be at least 1"); | |||||
| } | |||||
| return null | |||||
| }, | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| showLoaderOnConfirm: true, | |||||
| allowOutsideClick: () => !Swal.isLoading() | |||||
| }); | |||||
| if (askNumofCarton.isConfirmed) { | |||||
| const numOfCartons = askNumofCarton.value; | |||||
| try{ | |||||
| const printRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| isDraft: false, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: deliveryOrderId, | |||||
| pickOrderId: pickOrderId | |||||
| }; | |||||
| console.log("Printing Delivery Note with request: ", printRequest); | |||||
| const response = await printDN(printRequest); | |||||
| console.log("Print Delivery Note response: ", response); | |||||
| if(response.success){ | |||||
| Swal.fire({ | |||||
| position: "bottom-end", | |||||
| icon: "success", | |||||
| text: t("Printed Successfully."), | |||||
| showConfirmButton: false, | |||||
| timer: 1500 | |||||
| }); | |||||
| } else { | |||||
| console.error("Print failed: ", response.message); | |||||
| } | |||||
| } catch(error){ | |||||
| console.error("error: ", error) | |||||
| } | |||||
| } | |||||
| }, [t]); | |||||
| const handleDNandLabel = useCallback(async (deliveryOrderId: number, pickOrderId: number) => { | |||||
| const askNumofCarton = await Swal.fire({ | |||||
| title: t("Enter the number of cartons: "), | |||||
| icon: "info", | |||||
| input: "number", | |||||
| inputPlaceholder: t("Number of cartons"), | |||||
| inputAttributes:{ | |||||
| min: "1", | |||||
| step: "1" | |||||
| }, | |||||
| inputValidator: (value) => { | |||||
| if(!value){ | |||||
| return t("You need to enter a number") | |||||
| } | |||||
| if(parseInt(value) < 1){ | |||||
| return t("Number must be at least 1"); | |||||
| } | |||||
| return null | |||||
| }, | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| showLoaderOnConfirm: true, | |||||
| allowOutsideClick: () => !Swal.isLoading() | |||||
| }); | |||||
| if (askNumofCarton.isConfirmed) { | |||||
| const numOfCartons = askNumofCarton.value; | |||||
| try{ | |||||
| const printDNRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| isDraft: false, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: deliveryOrderId, | |||||
| pickOrderId: pickOrderId | |||||
| }; | |||||
| const printDNLabelsRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: deliveryOrderId | |||||
| }; | |||||
| console.log("Printing Labels with request: ", printDNLabelsRequest); | |||||
| console.log("Printing DN with request: ", printDNRequest); | |||||
| const LabelsResponse = await printDNLabels(printDNLabelsRequest); | |||||
| const DNResponse = await printDN(printDNRequest); | |||||
| console.log("Print Labels response: ", LabelsResponse); | |||||
| console.log("Print DN response: ", DNResponse); | |||||
| if(LabelsResponse.success && DNResponse.success){ | |||||
| Swal.fire({ | |||||
| position: "bottom-end", | |||||
| icon: "success", | |||||
| text: t("Printed Successfully."), | |||||
| showConfirmButton: false, | |||||
| timer: 1500 | |||||
| }); | |||||
| } else { | |||||
| if(!LabelsResponse.success){ | |||||
| console.error("Print failed: ", LabelsResponse.message); | |||||
| } | |||||
| else{ | |||||
| console.error("Print failed: ", DNResponse.message); | |||||
| } | |||||
| } | |||||
| } catch(error){ | |||||
| console.error("error: ", error) | |||||
| } | |||||
| } | |||||
| }, [t]); | |||||
| const handleLabel = useCallback(async (deliveryOrderId: number) => { | |||||
| const askNumofCarton = await Swal.fire({ | |||||
| title: t("Enter the number of cartons: "), | |||||
| icon: "info", | |||||
| input: "number", | |||||
| inputPlaceholder: t("Number of cartons"), | |||||
| inputAttributes:{ | |||||
| min: "1", | |||||
| step: "1" | |||||
| }, | |||||
| inputValidator: (value) => { | |||||
| if(!value){ | |||||
| return t("You need to enter a number") | |||||
| } | |||||
| if(parseInt(value) < 1){ | |||||
| return t("Number must be at least 1"); | |||||
| } | |||||
| return null | |||||
| }, | |||||
| showCancelButton: true, | |||||
| confirmButtonText: t("Confirm"), | |||||
| cancelButtonText: t("Cancel"), | |||||
| confirmButtonColor: "#8dba00", | |||||
| cancelButtonColor: "#F04438", | |||||
| showLoaderOnConfirm: true, | |||||
| allowOutsideClick: () => !Swal.isLoading() | |||||
| }); | |||||
| if (askNumofCarton.isConfirmed) { | |||||
| const numOfCartons = askNumofCarton.value; | |||||
| try{ | |||||
| const printRequest = { | |||||
| printerId: 1, | |||||
| printQty: 1, | |||||
| numOfCarton: numOfCartons, | |||||
| deliveryOrderId: deliveryOrderId, | |||||
| }; | |||||
| console.log("Printing Labels with request: ", printRequest); | |||||
| const response = await printDNLabels(printRequest); | |||||
| console.log("Print Labels response: ", response); | |||||
| if(response.success){ | |||||
| Swal.fire({ | |||||
| position: "bottom-end", | |||||
| icon: "success", | |||||
| text: t("Printed Successfully."), | |||||
| showConfirmButton: false, | |||||
| timer: 1500 | |||||
| }); | |||||
| } else { | |||||
| console.error("Print failed: ", response.message); | |||||
| } | |||||
| } catch(error){ | |||||
| console.error("error: ", error) | |||||
| } | |||||
| } | |||||
| }, [t]); | |||||
| // ✅ 修改:使用新的 API 获取已完成的 DO Pick Orders | // ✅ 修改:使用新的 API 获取已完成的 DO Pick Orders | ||||
| const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { | const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { | ||||
| if (!currentUserId) return; | if (!currentUserId) return; | ||||
| @@ -406,6 +608,37 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| > | > | ||||
| {t("View Details")} | {t("View Details")} | ||||
| </Button> | </Button> | ||||
| {doPickOrder.fgPickOrders && doPickOrder.fgPickOrders.length > 0 && ( | |||||
| <> | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={() => handleDN( | |||||
| doPickOrder.fgPickOrders[0].deliveryOrderId, | |||||
| doPickOrder.fgPickOrders[0].pickOrderId | |||||
| )} | |||||
| > | |||||
| {t("Print Pick Order")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={() => handleDNandLabel( | |||||
| doPickOrder.fgPickOrders[0].deliveryOrderId, | |||||
| doPickOrder.fgPickOrders[0].pickOrderId | |||||
| )} | |||||
| > | |||||
| {t("Print DN & Label")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={() => handleLabel( | |||||
| doPickOrder.fgPickOrders[0].deliveryOrderId | |||||
| )} | |||||
| > | |||||
| {t("Print Label")} | |||||
| </Button> | |||||
| </> | |||||
| )} | |||||
| </CardActions> | </CardActions> | ||||
| </Card> | </Card> | ||||
| ))} | ))} | ||||
| @@ -19,6 +19,7 @@ import { | |||||
| TablePagination, | TablePagination, | ||||
| Modal, | Modal, | ||||
| Chip, | Chip, | ||||
| Chip, | |||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider'; | import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider'; | ||||
| import { fetchLotDetail } from "@/app/api/inventory/actions"; | import { fetchLotDetail } from "@/app/api/inventory/actions"; | ||||
| @@ -34,6 +35,7 @@ import { | |||||
| fetchFGPickOrders, // ✅ Add this import | fetchFGPickOrders, // ✅ Add this import | ||||
| FGPickOrderResponse, | FGPickOrderResponse, | ||||
| checkPickOrderCompletion, | checkPickOrderCompletion, | ||||
| fetchAllPickOrderLotsHierarchical, | fetchAllPickOrderLotsHierarchical, | ||||
| PickOrderCompletionResponse, | PickOrderCompletionResponse, | ||||
| @@ -45,6 +47,7 @@ import { | |||||
| fetchFGPickOrdersByUserId | fetchFGPickOrdersByUserId | ||||
| } from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | ||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||||
| import LotConfirmationModal from "./LotConfirmationModal"; | import LotConfirmationModal from "./LotConfirmationModal"; | ||||
| //import { fetchItem } from "@/app/api/settings/item"; | //import { fetchItem } from "@/app/api/settings/item"; | ||||
| import { updateInventoryLotLineStatus, analyzeQrCode } from "@/app/api/inventory/actions"; | import { updateInventoryLotLineStatus, analyzeQrCode } from "@/app/api/inventory/actions"; | ||||
| @@ -441,11 +444,13 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| setAllLotsCompleted(allCompleted); | setAllLotsCompleted(allCompleted); | ||||
| return allCompleted; | return allCompleted; | ||||
| }, []); | }, []); | ||||
| const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { | |||||
| const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { | const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { | ||||
| setCombinedDataLoading(true); | setCombinedDataLoading(true); | ||||
| try { | try { | ||||
| const userIdToUse = userId || currentUserId; | const userIdToUse = userId || currentUserId; | ||||
| console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); | |||||
| console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); | console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); | ||||
| if (!userIdToUse) { | if (!userIdToUse) { | ||||
| @@ -456,6 +461,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| return; | return; | ||||
| } | } | ||||
| // ✅ 获取新结构的层级数据 | |||||
| // ✅ 获取新结构的层级数据 | // ✅ 获取新结构的层级数据 | ||||
| const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); | const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); | ||||
| console.log("✅ Hierarchical data (new structure):", hierarchicalData); | console.log("✅ Hierarchical data (new structure):", hierarchicalData); | ||||
| @@ -565,6 +571,99 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| // ✅ 将层级数据转换为平铺格式(用于表格显示) | // ✅ 将层级数据转换为平铺格式(用于表格显示) | ||||
| const flatLotData: any[] = []; | const flatLotData: any[] = []; | ||||
| targetPickOrder.pickOrderLines.forEach((line: any) => { | |||||
| if (line.lots && line.lots.length > 0) { | |||||
| // ✅ 有 lots 的情况 | |||||
| line.lots.forEach((lot: any) => { | |||||
| flatLotData.push({ | |||||
| pickOrderId: targetPickOrder.pickOrderId, | |||||
| pickOrderCode: targetPickOrder.pickOrderCode, | |||||
| pickOrderConsoCode: targetPickOrder.consoCode, | |||||
| pickOrderTargetDate: targetPickOrder.targetDate, | |||||
| pickOrderStatus: targetPickOrder.status, | |||||
| pickOrderLineId: line.id, | |||||
| pickOrderLineRequiredQty: line.requiredQty, | |||||
| pickOrderLineStatus: line.status, | |||||
| itemId: line.item.id, | |||||
| itemCode: line.item.code, | |||||
| itemName: line.item.name, | |||||
| //uomCode: line.item.uomCode, | |||||
| uomDesc: line.item.uomDesc, | |||||
| uomShortDesc: line.item.uomShortDesc, | |||||
| lotId: lot.id, | |||||
| lotNo: lot.lotNo, | |||||
| expiryDate: lot.expiryDate, | |||||
| location: lot.location, | |||||
| stockUnit: lot.stockUnit, | |||||
| availableQty: lot.availableQty, | |||||
| requiredQty: lot.requiredQty, | |||||
| actualPickQty: lot.actualPickQty, | |||||
| inQty: lot.inQty, | |||||
| outQty: lot.outQty, | |||||
| holdQty: lot.holdQty, | |||||
| lotStatus: lot.lotStatus, | |||||
| lotAvailability: lot.lotAvailability, | |||||
| processingStatus: lot.processingStatus, | |||||
| suggestedPickLotId: lot.suggestedPickLotId, | |||||
| stockOutLineId: lot.stockOutLineId, | |||||
| stockOutLineStatus: lot.stockOutLineStatus, | |||||
| stockOutLineQty: lot.stockOutLineQty, | |||||
| routerId: lot.router?.id, | |||||
| routerIndex: lot.router?.index, | |||||
| routerRoute: lot.router?.route, | |||||
| routerArea: lot.router?.area, | |||||
| }); | |||||
| }); | |||||
| } else { | |||||
| // ✅ 没有 lots 的情况(null stock)- 也要显示 | |||||
| flatLotData.push({ | |||||
| pickOrderId: targetPickOrder.pickOrderId, | |||||
| pickOrderCode: targetPickOrder.pickOrderCode, | |||||
| pickOrderConsoCode: targetPickOrder.consoCode, | |||||
| pickOrderTargetDate: targetPickOrder.targetDate, | |||||
| pickOrderStatus: targetPickOrder.status, | |||||
| pickOrderLineId: line.id, | |||||
| pickOrderLineRequiredQty: line.requiredQty, | |||||
| pickOrderLineStatus: line.status, | |||||
| itemId: line.item.id, | |||||
| itemCode: line.item.code, | |||||
| itemName: line.item.name, | |||||
| //uomCode: line.item.uomCode, | |||||
| uomDesc: line.item.uomDesc, | |||||
| // ✅ Null stock 字段 | |||||
| lotId: null, | |||||
| lotNo: null, | |||||
| expiryDate: null, | |||||
| location: null, | |||||
| stockUnit: line.item.uomDesc, | |||||
| availableQty: 0, | |||||
| requiredQty: line.requiredQty, | |||||
| actualPickQty: 0, | |||||
| inQty: 0, | |||||
| outQty: 0, | |||||
| holdQty: 0, | |||||
| lotStatus: 'unavailable', | |||||
| lotAvailability: 'insufficient_stock', | |||||
| processingStatus: 'pending', | |||||
| suggestedPickLotId: null, | |||||
| stockOutLineId: null, | |||||
| stockOutLineStatus: null, | |||||
| stockOutLineQty: 0, | |||||
| routerId: null, | |||||
| routerIndex: 999999, // ✅ 放到最后 | |||||
| routerRoute: null, | |||||
| routerArea: null, | |||||
| uomShortDesc: line.item.uomShortDesc | |||||
| }); | |||||
| } | |||||
| }); | |||||
| targetPickOrder.pickOrderLines.forEach((line: any) => { | targetPickOrder.pickOrderLines.forEach((line: any) => { | ||||
| if (line.lots && line.lots.length > 0) { | if (line.lots && line.lots.length > 0) { | ||||
| // ✅ 有 lots 的情况 | // ✅ 有 lots 的情况 | ||||
| @@ -662,10 +761,13 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| console.log("✅ Transformed flat lot data:", flatLotData); | console.log("✅ Transformed flat lot data:", flatLotData); | ||||
| console.log("🔍 Total items (including null stock):", flatLotData.length); | console.log("🔍 Total items (including null stock):", flatLotData.length); | ||||
| console.log("🔍 Total items (including null stock):", flatLotData.length); | |||||
| setCombinedLotData(flatLotData); | setCombinedLotData(flatLotData); | ||||
| setOriginalCombinedData(flatLotData); | setOriginalCombinedData(flatLotData); | ||||
| checkAllLotsCompleted(flatLotData); | checkAllLotsCompleted(flatLotData); | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("❌ Error fetching combined lot data:", error); | console.error("❌ Error fetching combined lot data:", error); | ||||
| setCombinedLotData([]); | setCombinedLotData([]); | ||||
| @@ -675,6 +777,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| setCombinedDataLoading(false); | setCombinedDataLoading(false); | ||||
| } | } | ||||
| }, [currentUserId, selectedPickOrderId, checkAllLotsCompleted]); | }, [currentUserId, selectedPickOrderId, checkAllLotsCompleted]); | ||||
| }, [currentUserId, selectedPickOrderId, checkAllLotsCompleted]); | |||||
| // ✅ Add effect to check completion when lot data changes | // ✅ Add effect to check completion when lot data changes | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (combinedLotData.length > 0) { | if (combinedLotData.length > 0) { | ||||
| @@ -1508,6 +1611,22 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| setPickOrderSwitching(false); | setPickOrderSwitching(false); | ||||
| } | } | ||||
| }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]); | }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]); | ||||
| const handlePickOrderSwitch = useCallback(async (pickOrderId: number) => { | |||||
| if (pickOrderSwitching) return; | |||||
| setPickOrderSwitching(true); | |||||
| try { | |||||
| console.log("🔍 Switching to pick order:", pickOrderId); | |||||
| setSelectedPickOrderId(pickOrderId); | |||||
| // ✅ 强制刷新数据,确保显示正确的 pick order 数据 | |||||
| await fetchAllCombinedLotData(currentUserId, pickOrderId); | |||||
| } catch (error) { | |||||
| console.error("Error switching pick order:", error); | |||||
| } finally { | |||||
| setPickOrderSwitching(false); | |||||
| } | |||||
| }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]); | |||||
| const handleStopScan = useCallback(() => { | const handleStopScan = useCallback(() => { | ||||
| console.log("⏹️ Stopping manual QR scan..."); | console.log("⏹️ Stopping manual QR scan..."); | ||||
| setIsManualScanning(false); | setIsManualScanning(false); | ||||
| @@ -1645,6 +1764,40 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| lot.stockOutLineStatus !== 'rejected' && | lot.stockOutLineStatus !== 'rejected' && | ||||
| lot.stockOutLineStatus !== 'completed' | lot.stockOutLineStatus !== 'completed' | ||||
| )} | )} | ||||
| > | |||||
| <FormProvider {...formProps}> | |||||
| <Stack spacing={2}> | |||||
| {/* DO Header */} | |||||
| {fgPickOrdersLoading ? ( | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 2 }}> | |||||
| <CircularProgress size={20} /> | |||||
| </Box> | |||||
| ) : ( | |||||
| fgPickOrders.length > 0 && ( | |||||
| <Paper sx={{ p: 2 }}> | |||||
| <Stack spacing={2}> | |||||
| {/* 基本信息 */} | |||||
| <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap"> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Shop Name")}:</strong> {fgPickOrders[0].shopName || '-'} | |||||
| </Typography> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Store ID")}:</strong> {fgPickOrders[0].storeId || '-'} | |||||
| </Typography> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Ticket No.")}:</strong> {fgPickOrders[0].ticketNo || '-'} | |||||
| </Typography> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Departure Time")}:</strong> {fgPickOrders[0].DepartureTime || '-'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| lotData={combinedLotData} | |||||
| onScanLot={handleQrCodeSubmit} | |||||
| filterActive={(lot) => ( | |||||
| lot.lotAvailability !== 'rejected' && | |||||
| lot.stockOutLineStatus !== 'rejected' && | |||||
| lot.stockOutLineStatus !== 'completed' | |||||
| )} | |||||
| > | > | ||||
| <FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| @@ -1981,6 +2134,160 @@ paginatedData.map((lot, index) => { | |||||
| /> | /> | ||||
| )} | )} | ||||
| {/* ✅ 保留:Good Pick Execution Form Modal */} | |||||
| {pickExecutionFormOpen && selectedLotForExecutionForm && ( | |||||
| <GoodPickExecutionForm | |||||
| open={pickExecutionFormOpen} | |||||
| onClose={() => { | |||||
| setPickExecutionFormOpen(false); | |||||
| setSelectedLotForExecutionForm(null); | |||||
| }} | |||||
| onSubmit={handlePickExecutionFormSubmit} | |||||
| selectedLot={selectedLotForExecutionForm} | |||||
| selectedPickOrderLine={{ | |||||
| id: selectedLotForExecutionForm.pickOrderLineId, | |||||
| itemId: selectedLotForExecutionForm.itemId, | |||||
| itemCode: selectedLotForExecutionForm.itemCode, | |||||
| itemName: selectedLotForExecutionForm.itemName, | |||||
| pickOrderCode: selectedLotForExecutionForm.pickOrderCode, | |||||
| availableQty: selectedLotForExecutionForm.availableQty || 0, | |||||
| requiredQty: selectedLotForExecutionForm.requiredQty || 0, | |||||
| // uomCode: selectedLotForExecutionForm.uomCode || '', | |||||
| uomDesc: selectedLotForExecutionForm.uomDesc || '', | |||||
| pickedQty: selectedLotForExecutionForm.actualPickQty || 0, | |||||
| uomShortDesc: selectedLotForExecutionForm.uomShortDesc || '', | |||||
| suggestedList: [] | |||||
| }} | |||||
| pickOrderId={selectedLotForExecutionForm.pickOrderId} | |||||
| pickOrderCreateDate={new Date()} | |||||
| /> | |||||
| )} | |||||
| </FormProvider> | |||||
| <TableCell align="center"> | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center' }}> | |||||
| {isIssueLot ? ( | |||||
| // ✅ Issue lot 只显示 Issue 按钮 | |||||
| <Button | |||||
| variant="outlined" | |||||
| size="small" | |||||
| onClick={() => handlePickExecutionForm(lot)} | |||||
| disabled={true} | |||||
| sx={{ | |||||
| fontSize: '0.7rem', | |||||
| py: 0.5, | |||||
| minHeight: '28px', | |||||
| minWidth: '60px', | |||||
| borderColor: 'warning.main', | |||||
| color: 'warning.main' | |||||
| }} | |||||
| title="Rejected lot - Issue only" | |||||
| > | |||||
| {t("Issue")} | |||||
| </Button> | |||||
| ) : ( | |||||
| // ✅ Normal lot 显示两个按钮 | |||||
| <Stack direction="row" spacing={1} alignItems="center"> | |||||
| <Button | |||||
| variant="contained" | |||||
| onClick={() => { | |||||
| const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; | |||||
| const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; | |||||
| handlePickQtyChange(lotKey, submitQty); | |||||
| handleSubmitPickQtyWithQty(lot, submitQty); | |||||
| }} | |||||
| disabled={ | |||||
| lot.lotAvailability === 'expired' || | |||||
| lot.lotAvailability === 'status_unavailable' || | |||||
| lot.lotAvailability === 'rejected' || | |||||
| lot.stockOutLineStatus === 'completed' || | |||||
| lot.stockOutLineStatus === 'pending' | |||||
| } | |||||
| sx={{ fontSize: '0.75rem', py: 0.5, minHeight: '28px', minWidth: '70px' }} | |||||
| > | |||||
| {t("Submit")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="outlined" | |||||
| size="small" | |||||
| onClick={() => handlePickExecutionForm(lot)} | |||||
| disabled={ | |||||
| lot.lotAvailability === 'expired' || | |||||
| lot.lotAvailability === 'status_unavailable' || | |||||
| lot.lotAvailability === 'rejected' || | |||||
| lot.stockOutLineStatus === 'completed' || | |||||
| lot.stockOutLineStatus === 'pending' | |||||
| } | |||||
| sx={{ | |||||
| fontSize: '0.7rem', | |||||
| py: 0.5, | |||||
| minHeight: '28px', | |||||
| minWidth: '60px', | |||||
| borderColor: 'warning.main', | |||||
| color: 'warning.main' | |||||
| }} | |||||
| title="Report missing or bad items" | |||||
| > | |||||
| {t("Issue")} | |||||
| </Button> | |||||
| </Stack> | |||||
| )} | |||||
| </Box> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| ); | |||||
| }) | |||||
| )} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| <TablePagination | |||||
| component="div" | |||||
| count={combinedLotData.length} | |||||
| page={paginationController.pageNum} | |||||
| rowsPerPage={paginationController.pageSize} | |||||
| onPageChange={handlePageChange} | |||||
| onRowsPerPageChange={handlePageSizeChange} | |||||
| rowsPerPageOptions={[10, 25, 50]} | |||||
| labelRowsPerPage={t("Rows per page")} | |||||
| labelDisplayedRows={({ from, to, count }) => | |||||
| `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` | |||||
| } | |||||
| /> | |||||
| </Box> | |||||
| </Stack> | |||||
| {/* ✅ 保留:QR Code Modal */} | |||||
| <QrCodeModal | |||||
| open={qrModalOpen} | |||||
| onClose={() => { | |||||
| setQrModalOpen(false); | |||||
| setSelectedLotForQr(null); | |||||
| stopScan(); | |||||
| resetScan(); | |||||
| }} | |||||
| lot={selectedLotForQr} | |||||
| combinedLotData={combinedLotData} | |||||
| onQrCodeSubmit={handleQrCodeSubmitFromModal} | |||||
| /> | |||||
| {/* ✅ 保留:Lot Confirmation Modal */} | |||||
| {lotConfirmationOpen && expectedLotData && scannedLotData && ( | |||||
| <LotConfirmationModal | |||||
| open={lotConfirmationOpen} | |||||
| onClose={() => { | |||||
| setLotConfirmationOpen(false); | |||||
| setExpectedLotData(null); | |||||
| setScannedLotData(null); | |||||
| }} | |||||
| onConfirm={handleLotConfirmation} | |||||
| expectedLot={expectedLotData} | |||||
| scannedLot={scannedLotData} | |||||
| isLoading={isConfirmingLot} | |||||
| /> | |||||
| )} | |||||
| {/* ✅ 保留:Good Pick Execution Form Modal */} | {/* ✅ 保留:Good Pick Execution Form Modal */} | ||||
| {pickExecutionFormOpen && selectedLotForExecutionForm && ( | {pickExecutionFormOpen && selectedLotForExecutionForm && ( | ||||
| <GoodPickExecutionForm | <GoodPickExecutionForm | ||||
| @@ -25,17 +25,19 @@ import dayjs from "dayjs"; | |||||
| import { fetchInventories } from "@/app/api/inventory/actions"; | import { fetchInventories } from "@/app/api/inventory/actions"; | ||||
| import { InventoryResult } from "@/app/api/inventory"; | import { InventoryResult } from "@/app/api/inventory"; | ||||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||||
| interface Props { | interface Props { | ||||
| defaultInputs: SearchJoResultRequest, | defaultInputs: SearchJoResultRequest, | ||||
| bomCombo: BomCombo[] | bomCombo: BomCombo[] | ||||
| printerCombo: PrinterCombo[]; | |||||
| } | } | ||||
| type SearchQuery = Partial<Omit<JobOrder, "id">>; | type SearchQuery = Partial<Omit<JobOrder, "id">>; | ||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo }) => { | |||||
| const { t } = useTranslation("jo"); | const { t } = useTranslation("jo"); | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const [filteredJos, setFilteredJos] = useState<JobOrder[]>([]); | const [filteredJos, setFilteredJos] = useState<JobOrder[]>([]); | ||||
| @@ -426,7 +428,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| open={openModal} | open={openModal} | ||||
| onClose={closeNewModal} | onClose={closeNewModal} | ||||
| inputDetail={modalInfo} | inputDetail={modalInfo} | ||||
| printerCombo={[]} | |||||
| printerCombo={printerCombo} | |||||
| // skipQc={true} | // skipQc={true} | ||||
| /> | /> | ||||
| </> | </> | ||||
| @@ -3,6 +3,7 @@ import GeneralLoading from "../General/GeneralLoading"; | |||||
| import JoSearch from "./JoSearch"; | import JoSearch from "./JoSearch"; | ||||
| import { SearchJoResultRequest } from "@/app/api/jo/actions"; | import { SearchJoResultRequest } from "@/app/api/jo/actions"; | ||||
| import { fetchBomCombo } from "@/app/api/bom"; | import { fetchBomCombo } from "@/app/api/bom"; | ||||
| import { fetchPrinterCombo } from "@/app/api/settings/printer"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof GeneralLoading; | Loading: typeof GeneralLoading; | ||||
| @@ -15,12 +16,14 @@ const JoSearchWrapper: React.FC & SubComponents = async () => { | |||||
| } | } | ||||
| const [ | const [ | ||||
| bomCombo | |||||
| bomCombo, | |||||
| printerCombo | |||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchBomCombo() | |||||
| fetchBomCombo(), | |||||
| fetchPrinterCombo() | |||||
| ]) | ]) | ||||
| return <JoSearch defaultInputs={defaultInputs} bomCombo={bomCombo}/> | |||||
| return <JoSearch defaultInputs={defaultInputs} bomCombo={bomCombo} printerCombo={printerCombo}/> | |||||
| } | } | ||||
| JoSearchWrapper.Loading = GeneralLoading; | JoSearchWrapper.Loading = GeneralLoading; | ||||
| @@ -14,16 +14,16 @@ const Logo: React.FC<Props> = ({ width, height }) => { | |||||
| <g | <g | ||||
| id="svgGroup" | id="svgGroup" | ||||
| strokeLinecap="round" | strokeLinecap="round" | ||||
| fill-rule="evenodd" | |||||
| font-size="9pt" | |||||
| fillRule="evenodd" | |||||
| fontSize="9pt" | |||||
| stroke="#000" | stroke="#000" | ||||
| stroke-width="0.25mm" | |||||
| strokeWidth="0.25mm" | |||||
| fill="#000" | fill="#000" | ||||
| // style="stroke:#000;stroke-width:0.25mm;fill:#000" | // style="stroke:#000;stroke-width:0.25mm;fill:#000" | ||||
| > | > | ||||
| <path | <path | ||||
| d="M 72.768 0.48 L 75.744 0.48 L 88.224 29.568 L 88.56 29.568 L 101.04 0.48 L 103.92 0.48 L 103.92 34.416 L 101.136 34.416 L 101.136 7.344 L 100.896 7.344 L 89.136 34.416 L 87.504 34.416 L 75.744 7.344 L 75.552 7.344 L 75.552 34.416 L 72.768 34.416 L 72.768 0.48 Z M 137.808 0.48 L 140.784 0.48 L 153.264 29.568 L 153.6 29.568 L 166.08 0.48 L 168.96 0.48 L 168.96 34.416 L 166.176 34.416 L 166.176 7.344 L 165.936 7.344 L 154.176 34.416 L 152.544 34.416 L 140.784 7.344 L 140.592 7.344 L 140.592 34.416 L 137.808 34.416 L 137.808 0.48 Z M 198.72 7.824 L 195.84 7.824 Q 195.456 4.848 193.224 3.696 Q 190.992 2.544 187.344 2.544 Q 183.168 2.544 181.152 4.152 Q 179.136 5.76 179.136 8.88 Q 179.136 10.704 179.832 11.856 Q 180.528 13.008 181.632 13.704 Q 182.736 14.4 183.984 14.808 Q 185.232 15.216 186.288 15.504 L 189.984 16.512 Q 191.376 16.896 193.008 17.472 Q 194.64 18.048 196.104 19.056 Q 197.568 20.064 198.48 21.648 Q 199.392 23.232 199.392 25.584 Q 199.392 28.272 198.096 30.432 Q 196.8 32.592 194.112 33.816 Q 191.424 35.04 187.248 35.04 Q 181.68 35.04 178.704 32.784 Q 175.728 30.528 175.344 26.688 L 178.32 26.688 Q 178.608 28.992 179.808 30.24 Q 181.008 31.488 182.904 31.992 Q 184.8 32.496 187.248 32.496 Q 191.664 32.496 194.088 30.792 Q 196.512 29.088 196.512 25.488 Q 196.512 23.328 195.48 22.08 Q 194.448 20.832 192.744 20.112 Q 191.04 19.392 189.072 18.864 L 184.464 17.616 Q 180.528 16.512 178.392 14.544 Q 176.256 12.576 176.256 9.12 Q 176.256 6.288 177.624 4.248 Q 178.992 2.208 181.512 1.104 Q 184.032 0 187.488 0 Q 190.848 0 193.272 0.984 Q 195.696 1.968 197.112 3.72 Q 198.528 5.472 198.72 7.824 Z M 0 34.416 L 0 0.48 L 19.344 0.48 L 19.344 2.976 L 2.88 2.976 L 2.88 16.176 L 17.76 16.176 L 17.76 18.672 L 2.88 18.672 L 2.88 34.416 L 0 34.416 Z M 108.336 2.976 L 108.336 0.48 L 133.392 0.48 L 133.392 2.976 L 122.304 2.976 L 122.304 34.416 L 119.424 34.416 L 119.424 2.976 L 108.336 2.976 Z M 25.152 34.416 L 25.152 0.48 L 36.48 0.48 Q 40.56 0.48 43.056 1.752 Q 45.552 3.024 46.704 5.328 Q 47.856 7.632 47.856 10.8 Q 47.856 13.968 46.704 16.32 Q 45.552 18.672 43.08 19.968 Q 40.608 21.264 36.576 21.264 L 28.032 21.264 L 28.032 34.416 L 25.152 34.416 Z M 28.032 18.768 L 36.384 18.768 Q 39.744 18.768 41.616 17.784 Q 43.488 16.8 44.232 15 Q 44.976 13.2 44.976 10.8 Q 44.976 8.352 44.232 6.6 Q 43.488 4.848 41.616 3.912 Q 39.744 2.976 36.288 2.976 L 28.032 2.976 L 28.032 18.768 Z M 65.664 18 L 65.664 20.496 L 52.704 20.496 L 52.704 18 L 65.664 18 Z" | d="M 72.768 0.48 L 75.744 0.48 L 88.224 29.568 L 88.56 29.568 L 101.04 0.48 L 103.92 0.48 L 103.92 34.416 L 101.136 34.416 L 101.136 7.344 L 100.896 7.344 L 89.136 34.416 L 87.504 34.416 L 75.744 7.344 L 75.552 7.344 L 75.552 34.416 L 72.768 34.416 L 72.768 0.48 Z M 137.808 0.48 L 140.784 0.48 L 153.264 29.568 L 153.6 29.568 L 166.08 0.48 L 168.96 0.48 L 168.96 34.416 L 166.176 34.416 L 166.176 7.344 L 165.936 7.344 L 154.176 34.416 L 152.544 34.416 L 140.784 7.344 L 140.592 7.344 L 140.592 34.416 L 137.808 34.416 L 137.808 0.48 Z M 198.72 7.824 L 195.84 7.824 Q 195.456 4.848 193.224 3.696 Q 190.992 2.544 187.344 2.544 Q 183.168 2.544 181.152 4.152 Q 179.136 5.76 179.136 8.88 Q 179.136 10.704 179.832 11.856 Q 180.528 13.008 181.632 13.704 Q 182.736 14.4 183.984 14.808 Q 185.232 15.216 186.288 15.504 L 189.984 16.512 Q 191.376 16.896 193.008 17.472 Q 194.64 18.048 196.104 19.056 Q 197.568 20.064 198.48 21.648 Q 199.392 23.232 199.392 25.584 Q 199.392 28.272 198.096 30.432 Q 196.8 32.592 194.112 33.816 Q 191.424 35.04 187.248 35.04 Q 181.68 35.04 178.704 32.784 Q 175.728 30.528 175.344 26.688 L 178.32 26.688 Q 178.608 28.992 179.808 30.24 Q 181.008 31.488 182.904 31.992 Q 184.8 32.496 187.248 32.496 Q 191.664 32.496 194.088 30.792 Q 196.512 29.088 196.512 25.488 Q 196.512 23.328 195.48 22.08 Q 194.448 20.832 192.744 20.112 Q 191.04 19.392 189.072 18.864 L 184.464 17.616 Q 180.528 16.512 178.392 14.544 Q 176.256 12.576 176.256 9.12 Q 176.256 6.288 177.624 4.248 Q 178.992 2.208 181.512 1.104 Q 184.032 0 187.488 0 Q 190.848 0 193.272 0.984 Q 195.696 1.968 197.112 3.72 Q 198.528 5.472 198.72 7.824 Z M 0 34.416 L 0 0.48 L 19.344 0.48 L 19.344 2.976 L 2.88 2.976 L 2.88 16.176 L 17.76 16.176 L 17.76 18.672 L 2.88 18.672 L 2.88 34.416 L 0 34.416 Z M 108.336 2.976 L 108.336 0.48 L 133.392 0.48 L 133.392 2.976 L 122.304 2.976 L 122.304 34.416 L 119.424 34.416 L 119.424 2.976 L 108.336 2.976 Z M 25.152 34.416 L 25.152 0.48 L 36.48 0.48 Q 40.56 0.48 43.056 1.752 Q 45.552 3.024 46.704 5.328 Q 47.856 7.632 47.856 10.8 Q 47.856 13.968 46.704 16.32 Q 45.552 18.672 43.08 19.968 Q 40.608 21.264 36.576 21.264 L 28.032 21.264 L 28.032 34.416 L 25.152 34.416 Z M 28.032 18.768 L 36.384 18.768 Q 39.744 18.768 41.616 17.784 Q 43.488 16.8 44.232 15 Q 44.976 13.2 44.976 10.8 Q 44.976 8.352 44.232 6.6 Q 43.488 4.848 41.616 3.912 Q 39.744 2.976 36.288 2.976 L 28.032 2.976 L 28.032 18.768 Z M 65.664 18 L 65.664 20.496 L 52.704 20.496 L 52.704 18 L 65.664 18 Z" | ||||
| vector-effect="non-scaling-stroke" | |||||
| vectorEffect="non-scaling-stroke" | |||||
| /> | /> | ||||
| </g> | </g> | ||||
| </svg> | </svg> | ||||
| @@ -123,7 +123,7 @@ | |||||
| /* Input styles */ | /* Input styles */ | ||||
| /* .tiptap-input { | /* .tiptap-input { | ||||
| font-size: 14px; | |||||
| fontSize: 14px; | |||||
| font-weight: 500; | font-weight: 500; | ||||
| line-height: 12px; | line-height: 12px; | ||||
| } */ | } */ | ||||
| @@ -67,7 +67,7 @@ const textfieldSx = { | |||||
| transform: "translate(14px, 1.2rem) scale(1)", | transform: "translate(14px, 1.2rem) scale(1)", | ||||
| "&.MuiInputLabel-shrink": { | "&.MuiInputLabel-shrink": { | ||||
| fontSize: 24, | fontSize: 24, | ||||
| transform: "translate(14px, -0.5rem) scale(1)", | |||||
| transform: "translate(14px, -9px) scale(1)", | |||||
| }, | }, | ||||
| // [theme.breakpoints.down("sm")]: { | // [theme.breakpoints.down("sm")]: { | ||||
| // fontSize: "1rem", | // fontSize: "1rem", | ||||
| @@ -60,7 +60,7 @@ const textfieldSx = { | |||||
| transform: "translate(14px, 1.2rem) scale(1)", | transform: "translate(14px, 1.2rem) scale(1)", | ||||
| "&.MuiInputLabel-shrink": { | "&.MuiInputLabel-shrink": { | ||||
| fontSize: 24, | fontSize: 24, | ||||
| transform: "translate(14px, -0.5rem) scale(1)", | |||||
| transform: "translate(14px, -9px) scale(1)", | |||||
| }, | }, | ||||
| // [theme.breakpoints.down("sm")]: { | // [theme.breakpoints.down("sm")]: { | ||||
| // fontSize: "1rem", | // fontSize: "1rem", | ||||
| @@ -278,9 +278,9 @@ | |||||
| "Confirm":"確認", | "Confirm":"確認", | ||||
| "Update your suggested lot to the this scanned lot":"更新您的建議批次為此掃描的批次", | "Update your suggested lot to the this scanned lot":"更新您的建議批次為此掃描的批次", | ||||
| "Print Draft":"列印草稿", | "Print Draft":"列印草稿", | ||||
| "Print Pick Order and DN Label":"列印提料單和送貨單標貼", | |||||
| "Print Pick Order and DN Label":"列印提料單和送貨單標籤", | |||||
| "Print Pick Order":"列印提料單", | "Print Pick Order":"列印提料單", | ||||
| "Print DN Label":"列印送貨單標貼", | |||||
| "Print DN Label":"列印送貨單標籤", | |||||
| "Print All Draft" : "列印全部草稿", | "Print All Draft" : "列印全部草稿", | ||||
| "If you confirm, the system will:":"如果您確認,系統將:", | "If you confirm, the system will:":"如果您確認,系統將:", | ||||
| "QR code verified.":"QR 碼驗證成功。", | "QR code verified.":"QR 碼驗證成功。", | ||||
| @@ -292,7 +292,6 @@ | |||||
| "Completed DO pick orders: ":"已完成送貨單提料單:", | "Completed DO pick orders: ":"已完成送貨單提料單:", | ||||
| "No completed DO pick orders found":"沒有已完成送貨單提料單", | "No completed DO pick orders found":"沒有已完成送貨單提料單", | ||||
| "Print DN Label":"列印送貨單標貼", | |||||
| "Enter the number of cartons: ": "請輸入總箱數", | "Enter the number of cartons: ": "請輸入總箱數", | ||||
| "Number of cartons": "箱數", | "Number of cartons": "箱數", | ||||
| "Select an action for the assigned pick orders.": "選擇分配提料單的動作。", | "Select an action for the assigned pick orders.": "選擇分配提料單的動作。", | ||||
| @@ -390,5 +389,8 @@ | |||||
| "Today": "是日", | "Today": "是日", | ||||
| "Tomorrow": "翌日", | "Tomorrow": "翌日", | ||||
| "Day After Tomorrow": "後日", | "Day After Tomorrow": "後日", | ||||
| "Select Date": "請選擇日期" | |||||
| "Select Date": "請選擇日期", | |||||
| "Print DN & Label": "列印提料單和送貨單標籤", | |||||
| "Print Label": "列印送貨單標籤", | |||||
| "Ticket Release Table": "查看提貨情況" | |||||
| } | } | ||||
| @@ -164,5 +164,6 @@ | |||||
| "Expiry Date cannot be earlier than Production Date": "到期日不可早於生產日期", | "Expiry Date cannot be earlier than Production Date": "到期日不可早於生產日期", | ||||
| "Production Date must be earlier than Expiry Date": "生產日期必須早於到期日", | "Production Date must be earlier than Expiry Date": "生產日期必須早於到期日", | ||||
| "confirm expiry date": "確認到期日", | "confirm expiry date": "確認到期日", | ||||
| "Invalid Date": "無效日期" | |||||
| "Invalid Date": "無效日期", | |||||
| "Missing QC Template, please contact administrator": "找不到品檢模板,請聯絡管理員" | |||||
| } | } | ||||