diff --git a/src/app/(main)/settings/qcItemAll/page.tsx b/src/app/(main)/settings/qcItemAll/page.tsx index ff0d328..142179a 100644 --- a/src/app/(main)/settings/qcItemAll/page.tsx +++ b/src/app/(main)/settings/qcItemAll/page.tsx @@ -45,3 +45,15 @@ const qcItemAll: React.FC = async () => { export default qcItemAll; + + + + + + + + + + + + diff --git a/src/app/api/dashboard/actions.ts b/src/app/api/dashboard/actions.ts index bd0d240..bd71988 100644 --- a/src/app/api/dashboard/actions.ts +++ b/src/app/api/dashboard/actions.ts @@ -190,3 +190,21 @@ export const testing = cache(async (queryParams?: Record) => { ); } }); + +export interface GoodsReceiptStatusRow { + supplierId: number | null; + supplierName: string; + expectedNoOfDelivery: number; + noOfOrdersReceivedAtDock: number; + noOfItemsInspected: number; + noOfItemsWithIqcIssue: number; + noOfItemsCompletedPutAwayAtStore: number; +} + +export const fetchGoodsReceiptStatus = cache(async (date?: string) => { + const url = date + ? `${BASE_API_URL}/dashboard/goods-receipt-status?date=${date}` + : `${BASE_API_URL}/dashboard/goods-receipt-status`; + + return await serverFetchJson(url, { method: "GET" }); +}); diff --git a/src/app/api/dashboard/client.ts b/src/app/api/dashboard/client.ts new file mode 100644 index 0000000..ff0facb --- /dev/null +++ b/src/app/api/dashboard/client.ts @@ -0,0 +1,17 @@ +"use client"; + +import { + fetchGoodsReceiptStatus, + type GoodsReceiptStatusRow, +} from "./actions"; + +export const fetchGoodsReceiptStatusClient = async ( + date?: string, +): Promise => { + return await fetchGoodsReceiptStatus(date); +}; + +export type { GoodsReceiptStatusRow }; + +export default fetchGoodsReceiptStatusClient; + diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 3de7cdc..22c519a 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -207,6 +207,7 @@ export interface PickExecutionIssueData { actualPickQty: number; missQty: number; badItemQty: number; + badPackageQty?: number; issueRemark: string; pickerName: string; handledBy?: number; @@ -996,6 +997,7 @@ export interface LotSubstitutionConfirmRequest { stockOutLineId: number; originalSuggestedPickLotId: number; newInventoryLotNo: string; + newStockInLineId: number; } export const confirmLotSubstitution = async (data: LotSubstitutionConfirmRequest) => { const response = await serverFetchJson( diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index 840adcd..c14faf2 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -2,23 +2,43 @@ import { useTranslation } from "react-i18next"; import { ThemeProvider } from "@mui/material/styles"; import theme from "../../theme"; -import { TabsProps } from "@mui/material/Tabs"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState, ReactNode } from "react"; import { useRouter } from "next/navigation"; -import { Card, CardContent, CardHeader, Grid } from "@mui/material"; +import { Card, CardContent, CardHeader, Grid, Tabs, Tab, Box, FormControlLabel, Checkbox } from "@mui/material"; import DashboardProgressChart from "./chart/DashboardProgressChart"; import DashboardLineChart from "./chart/DashboardLineChart"; import PendingInspectionChart from "./chart/PendingInspectionChart"; import PendingStorageChart from "./chart/PendingStorageChart"; import ApplicationCompletionChart from "./chart/ApplicationCompletionChart"; import OrderCompletionChart from "./chart/OrderCompletionChart"; -import DashboardBox from "./Dashboardbox"; -import CollapsibleCard from "../CollapsibleCard"; // import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval"; import { EscalationResult } from "@/app/api/escalation"; import EscalationLogTable from "./escalation/EscalationLogTable"; import { TruckScheduleDashboard } from "./truckSchedule"; import { GoodsReceiptStatus } from "./goodsReceiptStatus"; +import { CardFilterContext } from "../CollapsibleCard/CollapsibleCard"; + +interface TabPanelProps { + children?: ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} type Props = { // iqc: IQCItems[] | undefined escalationLogs: EscalationResult[] @@ -32,6 +52,8 @@ const DashboardPage: React.FC = ({ const router = useRouter(); const [escLog, setEscLog] = useState([]); + const [currentTab, setCurrentTab] = useState(0); + const [showCompletedLogs, setShowCompletedLogs] = useState(false); const getPendingLog = () => { return escLog.filter(esc => esc.status == "pending"); @@ -41,35 +63,66 @@ const DashboardPage: React.FC = ({ setEscLog(escalationLogs); }, [escalationLogs]) + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setCurrentTab(newValue); + }; + + const handleFilterChange = (checked: boolean) => { + setShowCompletedLogs(checked); + }; + return ( - + + + + + + + 0 ? getPendingLog().length : t("No")})`} + id="dashboard-tab-2" + aria-controls="dashboard-tabpanel-2" + /> + + - + + + + + + + + {} + }}> + + handleFilterChange(e.target.checked)} + /> + } + label={t("show completed logs")} + /> + + + + - - - - - - - - - - - 0 ? getPendingLog().length : t("No")})`} - showFilter={true} - filterText={t("show completed logs")} - - > - - - - + {/* Hidden: Progress chart - not in use currently */} {/* diff --git a/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx b/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx index 4e725d2..c788078 100644 --- a/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx +++ b/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx @@ -1,13 +1,9 @@ "use client"; -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Box, Typography, - FormControl, - InputLabel, - Select, - MenuItem, Card, CardContent, Stack, @@ -19,88 +15,112 @@ import { TableRow, Paper, CircularProgress, - Chip + Button } from '@mui/material'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; +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 { fetchGoodsReceiptStatusClient, type GoodsReceiptStatusRow } from '@/app/api/dashboard/client'; -interface GoodsReceiptStatusItem { - id: string; -} +const REFRESH_MS = 15 * 60 * 1000; const GoodsReceiptStatus: React.FC = () => { const { t } = useTranslation("dashboard"); - const [selectedFilter, setSelectedFilter] = useState(""); - const [data, setData] = useState([]); - const [loading, setLoading] = useState(false); - const [currentTime, setCurrentTime] = useState(null); - const [isClient, setIsClient] = useState(false); - - useEffect(() => { - setIsClient(true); - setCurrentTime(dayjs()); - }, []); + const [selectedDate, setSelectedDate] = useState(dayjs()); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [lastUpdated, setLastUpdated] = useState(null); + const [screenCleared, setScreenCleared] = useState(false); const loadData = useCallback(async () => { + if (screenCleared) return; try { - setData([]); + setLoading(true); + const dateParam = selectedDate.format('YYYY-MM-DD'); + const result = await fetchGoodsReceiptStatusClient(dateParam); + setData(result ?? []); + setLastUpdated(dayjs()); } catch (error) { console.error('Error fetching goods receipt status:', error); + setData([]); } finally { setLoading(false); } - }, []); + }, [selectedDate, screenCleared]); useEffect(() => { + if (screenCleared) return; loadData(); const refreshInterval = setInterval(() => { loadData(); - }, 5 * 60 * 1000); + }, REFRESH_MS); return () => clearInterval(refreshInterval); - }, [loadData]); + }, [loadData, screenCleared]); - useEffect(() => { - if (!isClient) return; - - const timeInterval = setInterval(() => { - setCurrentTime(dayjs()); - }, 60 * 1000); - - return () => clearInterval(timeInterval); - }, [isClient]); - const filteredData = useMemo(() => { - if (!selectedFilter) return data; - return data.filter(item => true); - }, [data, selectedFilter]); + const selectedDateLabel = useMemo(() => { + return selectedDate.format('YYYY-MM-DD'); + }, [selectedDate]); + + if (screenCleared) { + return ( + + + + + {t("Screen cleared")} + + + + + + ); + } return ( - {/* Filter */} - - - - {t("Filter")} - - - - - - {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && currentTime ? currentTime.format('HH:mm:ss') : '--:--:--'} + {/* Header */} + + + + {t("Date")}: + + + { + if (!value) return; + setSelectedDate(value); + }} + slotProps={{ + textField: { + size: "small", + sx: { minWidth: 160 } + } + }} + /> + + + {t("Allow to select Date to view history.")} + + + + + + + {t("Auto-refresh every 15 minutes")} | {t("Last updated")}: {lastUpdated ? lastUpdated.format('HH:mm:ss') : '--:--:--'} + + {/* Table */} @@ -114,38 +134,80 @@ const GoodsReceiptStatus: React.FC = () => { - {t("Column 1")} - {t("Column 2")} - {t("Column 3")} - {/* TODO: Add table columns when implementing */} + {t("Supplier")} + {t("Expected No. of Delivery")} + {t("No. of Orders Received at Dock")} + {t("No. of Items Inspected")} + {t("No. of Items with IQC Issue")} + {t("No. of Items Completed Put Away at Store")} + + + + + {t("Show Supplier Name")} + + + + + {t("Based on Expected Delivery Date")} + + + + + {t("Upon entry of DN and Lot No. for all items of the order")} + + + + + {t("Upon any IQC decision received")} + + + + + {t("Count any item with IQC defect in any IQC criteria")} + + + + + {t("Upon completion of put away for an material in order. Count no. of items being put away")} + + - {filteredData.length === 0 ? ( + {data.length === 0 ? ( - + - {t("No data available")} + {t("No data available")} ({selectedDateLabel}) ) : ( - filteredData.map((row, index) => ( + data.map((row, index) => ( - {/* TODO: Add table cell content when implementing */} - - + {row.supplierName || '-'} - - - + + {row.expectedNoOfDelivery ?? 0} - - - + + {row.noOfOrdersReceivedAtDock ?? 0} + + + {row.noOfItemsInspected ?? 0} + + + {row.noOfItemsWithIqcIssue ?? 0} + + + {row.noOfItemsCompletedPutAwayAtStore ?? 0} )) diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx index 5f73626..3f14e5f 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -57,72 +57,119 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw loadSummaries(); }, [loadSummaries]); - const handleAssignByLane = useCallback(async ( - storeId: string, - truckDepartureTime: string, - truckLanceCode: string, - requiredDate: string + const handleAssignByLane = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string, + requiredDate: string - ) => { - if (!currentUserId) { - console.error("Missing user id in session"); - return; - } - let dateParam: string | undefined; - if (requiredDate === "today") { - dateParam = dayjs().format('YYYY-MM-DD'); - } else if (requiredDate === "tomorrow") { - dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD'); - } else if (requiredDate === "dayAfterTomorrow") { - dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD'); - } - setIsAssigning(true); - try { - const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam); - - if (res.code === "SUCCESS") { - console.log(" Successfully assigned pick order from lane", truckLanceCode); - window.dispatchEvent(new CustomEvent('pickOrderAssigned')); - loadSummaries(); // 刷新按钮状态 - onPickOrderAssigned?.(); - onSwitchToDetailTab?.(); - } else if (res.code === "USER_BUSY") { - Swal.fire({ - icon: "warning", - title: t("Warning"), - text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), - confirmButtonText: t("Confirm"), - confirmButtonColor: "#8dba00" - }); - window.dispatchEvent(new CustomEvent('pickOrderAssigned')); - } else if (res.code === "NO_ORDERS") { - Swal.fire({ - icon: "info", - title: t("Info"), - text: t("No available pick order(s) for this lane."), - confirmButtonText: t("Confirm"), - confirmButtonColor: "#8dba00" - }); - } else { - console.log("ℹ️ Assignment result:", res.message); - } - } catch (error) { - console.error("❌ Error assigning by lane:", error); + ) => { + if (!currentUserId) { + console.error("Missing user id in session"); + return; + } + let dateParam: string | undefined; + if (requiredDate === "today") { + dateParam = dayjs().format('YYYY-MM-DD'); + } else if (requiredDate === "tomorrow") { + dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD'); + } else if (requiredDate === "dayAfterTomorrow") { + dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD'); + } + setIsAssigning(true); + try { + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam); + + if (res.code === "SUCCESS") { + console.log(" Successfully assigned pick order from lane", truckLanceCode); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + loadSummaries(); // 刷新按钮状态 + onPickOrderAssigned?.(); + onSwitchToDetailTab?.(); + } else if (res.code === "USER_BUSY") { Swal.fire({ - icon: "error", - title: t("Error"), - text: t("Error occurred during assignment."), + icon: "warning", + title: t("Warning"), + text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), confirmButtonText: t("Confirm"), confirmButtonColor: "#8dba00" }); - } finally { - setIsAssigning(false); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + } else if (res.code === "NO_ORDERS") { + Swal.fire({ + icon: "info", + title: t("Info"), + text: t("No available pick order(s) for this lane."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } else { + console.log("ℹ️ Assignment result:", res.message); } - }, [currentUserId, t, selectedDate, onPickOrderAssigned, onSwitchToDetailTab, loadSummaries]); + } catch (error) { + console.error("❌ Error assigning by lane:", error); + Swal.fire({ + icon: "error", + title: t("Error"), + text: t("Error occurred during assignment."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } finally { + setIsAssigning(false); + } +}, [currentUserId, t, selectedDate, onPickOrderAssigned, onSwitchToDetailTab, loadSummaries]); - const getDateLabel = (offset: number) => { - return dayjs().add(offset, 'day').format('YYYY-MM-DD'); - }; +const handleLaneButtonClick = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string, + requiredDate: string, + unassigned: number, + total: number +) => { + // Format the date for display + let dateDisplay: string; + if (requiredDate === "today") { + dateDisplay = dayjs().format('YYYY-MM-DD'); + } else if (requiredDate === "tomorrow") { + dateDisplay = dayjs().add(1, 'day').format('YYYY-MM-DD'); + } else if (requiredDate === "dayAfterTomorrow") { + dateDisplay = dayjs().add(2, 'day').format('YYYY-MM-DD'); + } else { + dateDisplay = requiredDate; + } + + // Show confirmation dialog + const result = await Swal.fire({ + title: t("Confirm Assignment"), + html: ` +
+

${t("Store")}: ${storeId}

+

${t("Lane Code")}: ${truckLanceCode}

+

${t("Departure Time")}: ${truckDepartureTime}

+

${t("Required Date")}: ${dateDisplay}

+

${t("Available Orders")}: ${unassigned}/${total}

+
+ `, + icon: "question", + showCancelButton: true, + confirmButtonText: t("Confirm"), + cancelButtonText: t("Cancel"), + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438", + reverseButtons: true + }); + + // Only proceed if user confirmed + if (result.isConfirmed) { + await handleAssignByLane(storeId, truckDepartureTime, truckLanceCode, requiredDate); + } +}, [handleAssignByLane, t]); + +const getDateLabel = (offset: number) => { + return dayjs().add(offset, 'day').format('YYYY-MM-DD'); +}; // Flatten rows to create one box per lane const flattenRows = (rows: any[]) => { @@ -296,7 +343,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} + onClick={() => handleLaneButtonClick("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate, item.lane.unassigned, item.lane.total)} sx={{ flex: 1, fontSize: '1.1rem', @@ -396,7 +443,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} + onClick={() => handleLaneButtonClick("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate, item.lane.unassigned, item.lane.total)} sx={{ flex: 1, fontSize: '1.1rem', diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 4637e41..ef47f79 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -655,7 +655,7 @@ const handleAssignByLane = useCallback(async ( > {t("Print All Draft")} ({releasedOrderCount}) - +{/* + */} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 9ab4802..5ff744d 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -1,4 +1,3 @@ -// FPSMS-frontend/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx "use client"; import { @@ -16,16 +15,18 @@ import { TextField, Typography, } from "@mui/material"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; -import { GetPickOrderLineInfo, PickExecutionIssueData } from "@/app/api/pickOrder/actions"; +import { + GetPickOrderLineInfo, + PickExecutionIssueData, +} from "@/app/api/pickOrder/actions"; import { fetchEscalationCombo } from "@/app/api/user/actions"; -import { useRef } from "react"; -import dayjs from 'dayjs'; +import dayjs from "dayjs"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; interface LotPickData { - id: number; + id: number; lotId: number; lotNo: string; expiryDate: string; @@ -39,7 +40,12 @@ interface LotPickData { requiredQty: number; actualPickQty: number; lotStatus: string; - lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; + lotAvailability: + | "available" + | "insufficient_stock" + | "expired" + | "status_unavailable" + | "rejected"; stockOutLineId?: number; stockOutLineStatus?: string; stockOutLineQty?: number; @@ -77,12 +83,14 @@ const PickExecutionForm: React.FC = ({ const [formData, setFormData] = useState>({}); const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); - const [handlers, setHandlers] = useState>([]); - + const [handlers, setHandlers] = useState>( + [] + ); + const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { return lot.availableQty || 0; }, []); - + const calculateRequiredQty = useCallback((lot: LotPickData) => { return lot.requiredQty || 0; }, []); @@ -96,7 +104,7 @@ const PickExecutionForm: React.FC = ({ console.error("Error fetching handlers:", error); } }; - + fetchHandlers(); }, []); @@ -136,92 +144,119 @@ const PickExecutionForm: React.FC = ({ requiredQty: selectedLot.requiredQty, actualPickQty: selectedLot.actualPickQty || 0, missQty: 0, - badItemQty: 0, - issueRemark: '', - pickerName: '', + badItemQty: 0, // Bad Item Qty + badPackageQty: 0, // Bad Package Qty (frontend only) + issueRemark: "", + pickerName: "", handledBy: undefined, - reason: '', - badReason: '', + reason: "", + badReason: "", }); initKeyRef.current = key; - }, [open, selectedPickOrderLine?.id, selectedLot?.lotId, pickOrderId, pickOrderCreateDate]); + }, [ + open, + selectedPickOrderLine?.id, + selectedLot?.lotId, + pickOrderId, + pickOrderCreateDate, + ]); - const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); - if (errors[field as keyof FormErrors]) { - setErrors(prev => ({ ...prev, [field]: undefined })); - } - }, [errors]); + const handleInputChange = useCallback( + (field: keyof PickExecutionIssueData, value: any) => { + setFormData((prev) => ({ ...prev, [field]: value })); + if (errors[field as keyof FormErrors]) { + setErrors((prev) => ({ ...prev, [field]: undefined })); + } + }, + [errors] + ); // Updated validation logic const validateForm = (): boolean => { const newErrors: FormErrors = {}; - const req = selectedLot?.requiredQty || 0; const ap = Number(formData.actualPickQty) || 0; const miss = Number(formData.missQty) || 0; - const bad = Number(formData.badItemQty) || 0; - const total = ap + miss + bad; + const badItem = Number(formData.badItemQty) || 0; + const badPackage = Number((formData as any).badPackageQty) || 0; + const totalBad = badItem + badPackage; + const total = ap + miss + totalBad; const availableQty = selectedLot?.availableQty || 0; // 1. Check actualPickQty cannot be negative if (ap < 0) { - newErrors.actualPickQty = t('Qty cannot be negative'); + newErrors.actualPickQty = t("Qty cannot be negative"); } - + // 2. Check actualPickQty cannot exceed available quantity if (ap > availableQty) { - newErrors.actualPickQty = t('Actual pick qty cannot exceed available qty'); + newErrors.actualPickQty = t("Actual pick qty cannot exceed available qty"); } - - // 3. Check missQty and badItemQty cannot be negative + + // 3. Check missQty and both bad qtys cannot be negative if (miss < 0) { - newErrors.missQty = t('Invalid qty'); + newErrors.missQty = t("Invalid qty"); } - if (bad < 0) { - newErrors.badItemQty = t('Invalid qty'); + if (badItem < 0 || badPackage < 0) { + newErrors.badItemQty = t("Invalid qty"); } - - // 4. NEW: Total (actualPickQty + missQty + badItemQty) cannot exceed lot available qty + + // 4. Total (actualPickQty + missQty + badItemQty + badPackageQty) cannot exceed lot available qty if (total > availableQty) { - const errorMsg = t('Total qty (actual pick + miss + bad) cannot exceed available qty: {available}', { available: availableQty }); + const errorMsg = t( + "Total qty (actual pick + miss + bad) cannot exceed available qty: {available}", + { available: availableQty } + ); newErrors.actualPickQty = errorMsg; newErrors.missQty = errorMsg; newErrors.badItemQty = errorMsg; } - - // 5. If badItemQty > 0, badReason is required - if (bad > 0 && !formData.badReason) { - newErrors.badReason = t('Bad reason is required when bad item qty > 0'); - newErrors.badItemQty = t('Bad reason is required'); - } - - // 6. At least one field must have a value - if (ap === 0 && miss === 0 && bad === 0) { - newErrors.actualPickQty = t('Enter pick qty or issue qty'); + + // 5. At least one field must have a value + if (ap === 0 && miss === 0 && totalBad === 0) { + newErrors.actualPickQty = t("Enter pick qty or issue qty"); } - + setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async () => { if (!validateForm()) { - console.error('Form validation failed:', errors); + console.error("Form validation failed:", errors); return; } - + if (!formData.pickOrderId) { - console.error('Missing pickOrderId'); + console.error("Missing pickOrderId"); return; } + const badItem = Number(formData.badItemQty) || 0; + const badPackage = Number((formData as any).badPackageQty) || 0; + const totalBadQty = badItem + badPackage; + + let badReason: string | undefined; + if (totalBadQty > 0) { + // assumption: only one of them is > 0 + badReason = badPackage > 0 ? "package_problem" : "quantity_problem"; + } + + const submitData: PickExecutionIssueData = { + ...(formData as PickExecutionIssueData), + badItemQty: totalBadQty, + badReason, + }; + setLoading(true); try { - await onSubmit(formData as PickExecutionIssueData); + await onSubmit(submitData); } catch (error: any) { - console.error('Error submitting pick execution issue:', error); - alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : '')); + console.error("Error submitting pick execution issue:", error); + alert( + t("Failed to submit issue. Please try again.") + + (error.message ? `: ${error.message}` : "") + ); } finally { setLoading(false); } @@ -239,11 +274,15 @@ const PickExecutionForm: React.FC = ({ const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot); const requiredQty = calculateRequiredQty(selectedLot); - + return ( - {t('Pick Execution Issue Form')} + {t("Pick Execution Issue Form") } +
+ {selectedPickOrderLine.itemCode+ " "+ selectedPickOrderLine.itemName} +
+ {selectedLot.lotNo}
@@ -251,17 +290,17 @@ const PickExecutionForm: React.FC = ({ - + = ({ - handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} - error={!!errors.actualPickQty} - helperText={errors.actualPickQty || `${t('Max')}: ${remainingAvailableQty}`} - variant="outlined" - /> - - - - - {t('Reason')} - - + + handleInputChange( + "actualPickQty", + e.target.value === "" + ? undefined + + : Math.max(0, Number(e.target.value) || 0) + ) + } + error={!!errors.actualPickQty} + helperText={ + errors.actualPickQty || `${t("Max")}: ${remainingAvailableQty}` + } + variant="outlined" + /> + handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + onChange={(e) => + handleInputChange( + "missQty", + e.target.value === "" + ? undefined + : Math.max(0, Number(e.target.value) || 0) + ) + } error={!!errors.missQty} variant="outlined" /> @@ -314,53 +363,76 @@ const PickExecutionForm: React.FC = ({ handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + onChange={(e) => + handleInputChange( + "badItemQty", + e.target.value === "" + ? undefined + : Math.max(0, Number(e.target.value) || 0) + ) + } error={!!errors.badItemQty} + //helperText={t("Quantity Problem")} variant="outlined" /> - {/* Show bad reason dropdown when badItemQty > 0 */} - {(formData.badItemQty && formData.badItemQty > 0) ? ( - - - {t('Bad Reason')} - - {errors.badReason && ( - - {errors.badReason} - - )} - - - ) : null} - - + + + handleInputChange( + "badPackageQty", + e.target.value === "" + ? undefined + : Math.max(0, Number(e.target.value) || 0) + ) + } + error={!!errors.badItemQty} + //helperText={t("Package Problem")} + variant="outlined" + /> + + + + + {t("Remark")} + + + -
diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 1f3a3f7..3dab329 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -19,6 +19,7 @@ import { TablePagination, Modal, Chip, + LinearProgress, } from "@mui/material"; import dayjs from 'dayjs'; import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider'; @@ -78,7 +79,33 @@ interface Props { onSwitchToRecordTab?: () => void; onRefreshReleasedOrderCount?: () => void; } - +const LinearProgressWithLabel: React.FC<{ completed: number; total: number }> = ({ completed, total }) => { + const { t } = useTranslation(["pickOrder", "do"]); + const progress = total > 0 ? (completed / total) * 100 : 0; + + return ( + + + + + + + + {t("Progress")}: {completed}/{total} + + + + + + ); +}; // QR Code Modal Component (from LotTable) const QrCodeModal: React.FC<{ open: boolean; @@ -86,7 +113,8 @@ const QrCodeModal: React.FC<{ lot: any | null; onQrCodeSubmit: (lotNo: string) => void; combinedLotData: any[]; // Add this prop -}> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { + lotConfirmationOpen: boolean; +}> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData,lotConfirmationOpen = false }) => { const { t } = useTranslation("pickOrder"); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const [manualInput, setManualInput] = useState(''); @@ -100,8 +128,20 @@ const QrCodeModal: React.FC<{ const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [scannedQrResult, setScannedQrResult] = useState(''); const [fgPickOrder, setFgPickOrder] = useState(null); + const fetchingRef = useRef>(new Set()); // Process scanned QR codes useEffect(() => { + // ✅ Don't process if modal is not open + if (!open) { + return; + } + + // ✅ Don't process if lot confirmation modal is open + if (lotConfirmationOpen) { + console.log("Lot confirmation modal is open, skipping QrCodeModal processing..."); + return; + } + if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) { const latestQr = qrValues[qrValues.length - 1]; @@ -110,17 +150,39 @@ const QrCodeModal: React.FC<{ return; } - setProcessedQrCodes(prev => new Set(prev).add(latestQr)); - try { const qrData = JSON.parse(latestQr); if (qrData.stockInLineId && qrData.itemId) { + // ✅ Check if we're already fetching this stockInLineId + if (fetchingRef.current.has(qrData.stockInLineId)) { + console.log(`⏱️ [QR MODAL] Already fetching stockInLineId: ${qrData.stockInLineId}, skipping duplicate call`); + return; + } + + setProcessedQrCodes(prev => new Set(prev).add(latestQr)); setIsProcessingQr(true); setQrScanFailed(false); + // ✅ Mark as fetching + fetchingRef.current.add(qrData.stockInLineId); + + const fetchStartTime = performance.now(); + console.log(`⏱️ [QR MODAL] Starting fetchStockInLineInfo for stockInLineId: ${qrData.stockInLineId}`); + fetchStockInLineInfo(qrData.stockInLineId) .then((stockInLineInfo) => { + // ✅ Remove from fetching set + fetchingRef.current.delete(qrData.stockInLineId); + + // ✅ Check again if modal is still open and lot confirmation is not open + if (!open || lotConfirmationOpen) { + console.log("Modal state changed, skipping result processing"); + return; + } + + const fetchTime = performance.now() - fetchStartTime; + console.log(`⏱️ [QR MODAL] fetchStockInLineInfo time: ${fetchTime.toFixed(2)}ms (${(fetchTime / 1000).toFixed(3)}s)`); console.log("Stock in line info:", stockInLineInfo); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); @@ -138,7 +200,17 @@ const QrCodeModal: React.FC<{ } }) .catch((error) => { - console.error("Error fetching stock in line info:", error); + // ✅ Remove from fetching set + fetchingRef.current.delete(qrData.stockInLineId); + + // ✅ Check again if modal is still open + if (!open || lotConfirmationOpen) { + console.log("Modal state changed, skipping error handling"); + return; + } + + const fetchTime = performance.now() - fetchStartTime; + console.error(`❌ [QR MODAL] fetchStockInLineInfo failed after ${fetchTime.toFixed(2)}ms:`, error); setScannedQrResult('Error fetching data'); setQrScanFailed(true); setManualInputError(true); @@ -179,7 +251,7 @@ const QrCodeModal: React.FC<{ } } } - }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes]); + }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes, lotConfirmationOpen, open]); // Clear states when modal opens useEffect(() => { @@ -477,7 +549,7 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const [paginationController, setPaginationController] = useState({ pageNum: 0, - pageSize: 10, + pageSize: -1, }); const [usernameList, setUsernameList] = useState([]); @@ -515,12 +587,79 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); // TODO: Implement QR code functionality }; + const progress = useMemo(() => { + if (combinedLotData.length === 0) { + return { completed: 0, total: 0 }; + } + + const nonPendingCount = combinedLotData.filter(lot => { + const status = lot.stockOutLineStatus?.toLowerCase(); + return status !== 'pending'; + }).length; + + return { + completed: nonPendingCount, + total: combinedLotData.length + }; + }, [combinedLotData]); + const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any) => { + const mismatchStartTime = performance.now(); + console.log(`⏱️ [HANDLE LOT MISMATCH START]`); console.log("Lot mismatch detected:", { expectedLot, scannedLot }); - setExpectedLotData(expectedLot); - setScannedLotData(scannedLot); - setLotConfirmationOpen(true); + + // Check if we need to fetch scanned lot info + const needsFetch = !scannedLot.lotNo && scannedLot.stockInLineId; + + if (needsFetch) { + console.log(`⏱️ [HANDLE LOT MISMATCH] Need to fetch lot info for stockInLineId: ${scannedLot.stockInLineId}`); + const fetchStartTime = performance.now(); + + fetchStockInLineInfo(scannedLot.stockInLineId) + .then((stockInLineInfo) => { + const fetchTime = performance.now() - fetchStartTime; + console.log(`⏱️ [HANDLE LOT MISMATCH] fetchStockInLineInfo time: ${fetchTime.toFixed(2)}ms (${(fetchTime / 1000).toFixed(3)}s)`); + console.log("Stock in line info:", stockInLineInfo); + + const updateStartTime = performance.now(); + setExpectedLotData(expectedLot); + setScannedLotData({ + ...scannedLot, + lotNo: stockInLineInfo.lotNo || null, + }); + setLotConfirmationOpen(true); + const updateTime = performance.now() - updateStartTime; + console.log(`⏱️ [HANDLE LOT MISMATCH] State update time: ${updateTime.toFixed(2)}ms`); + + const totalTime = performance.now() - mismatchStartTime; + console.log(`⏱️ [HANDLE LOT MISMATCH END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`); + console.log(`📊 Breakdown: fetch=${fetchTime.toFixed(2)}ms, update=${updateTime.toFixed(2)}ms`); + }) + .catch((error) => { + const fetchTime = performance.now() - fetchStartTime; + console.error(`❌ [HANDLE LOT MISMATCH] fetchStockInLineInfo failed after ${fetchTime.toFixed(2)}ms:`, error); + + // Still open modal with partial data + setExpectedLotData(expectedLot); + setScannedLotData(scannedLot); + setLotConfirmationOpen(true); + + const totalTime = performance.now() - mismatchStartTime; + console.log(`⏱️ [HANDLE LOT MISMATCH END] Total time (with error): ${totalTime.toFixed(2)}ms`); + }); + } else { + // No fetch needed, open modal immediately + const updateStartTime = performance.now(); + setExpectedLotData(expectedLot); + setScannedLotData(scannedLot); + setLotConfirmationOpen(true); + const updateTime = performance.now() - updateStartTime; + console.log(`⏱️ [HANDLE LOT MISMATCH] State update time (no fetch): ${updateTime.toFixed(2)}ms`); + + const totalTime = performance.now() - mismatchStartTime; + console.log(`⏱️ [HANDLE LOT MISMATCH END] Total time: ${totalTime.toFixed(2)}ms`); + } }, []); const checkAllLotsCompleted = useCallback((lotData: any[]) => { if (lotData.length === 0) { @@ -937,18 +1076,15 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setIsConfirmingLot(true); try { const newLotNo = scannedLotData?.lotNo; - if (!newLotNo) { - console.error("No lot number for scanned lot"); - alert(t("Cannot find lot number for scanned lot. Please verify the lot number is correct.")); - setIsConfirmingLot(false); - return; - } + const newStockInLineId = scannedLotData?.stockInLineId; + await confirmLotSubstitution({ pickOrderLineId: selectedLotForQr.pickOrderLineId, stockOutLineId: selectedLotForQr.stockOutLineId, originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId, - newInventoryLotNo: newLotNo + newInventoryLotNo: "", + newStockInLineId: newStockInLineId }); setQrScanError(false); @@ -1261,12 +1397,19 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); return { byItemId, byItemCode, byLotId, byLotNo, byStockInLineId }; // ✅ 添加 byStockInLineId }, [combinedLotData]); - const processOutsideQrCode = useCallback(async (latestQr: string) => { + const totalStartTime = performance.now(); + console.log(`⏱️ [PROCESS OUTSIDE QR START] QR: ${latestQr.substring(0, 50)}...`); + console.log(`⏰ Start time: ${new Date().toISOString()}`); + // 1) Parse JSON safely + const parseStartTime = performance.now(); let qrData: any = null; + let parseTime = 0; // ✅ Declare parseTime in outer scope try { qrData = JSON.parse(latestQr); + parseTime = performance.now() - parseStartTime; // ✅ Assign value + console.log(`⏱️ JSON parse time: ${parseTime.toFixed(2)}ms`); } catch { console.log("QR content is not JSON; skipping lotNo direct submit to avoid false matches."); setQrScanError(true); @@ -1287,12 +1430,15 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); const scannedStockInLineId = qrData.stockInLineId; // ✅ OPTIMIZATION: 使用索引快速查找相同 item 的 lots + const lookupStartTime = performance.now(); const sameItemLots: any[] = []; // 使用索引快速查找 if (lotDataIndexes.byItemId.has(scannedItemId)) { sameItemLots.push(...lotDataIndexes.byItemId.get(scannedItemId)!); } + const lookupTime = performance.now() - lookupStartTime; + console.log(`⏱️ Index lookup time: ${lookupTime.toFixed(2)}ms, found ${sameItemLots.length} lots`); if (sameItemLots.length === 0) { console.error("No item match in expected lots for scanned code"); @@ -1302,12 +1448,15 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } // ✅ OPTIMIZATION: 过滤出活跃的 lots(非 rejected) + const filterStartTime = performance.now(); const rejectedStatuses = new Set(['rejected']); const activeSuggestedLots = sameItemLots.filter(lot => !rejectedStatuses.has(lot.lotAvailability) && !rejectedStatuses.has(lot.stockOutLineStatus) && !rejectedStatuses.has(lot.processingStatus) ); + const filterTime = performance.now() - filterStartTime; + console.log(`⏱️ Filter active lots time: ${filterTime.toFixed(2)}ms, active: ${activeSuggestedLots.length}`); if (activeSuggestedLots.length === 0) { console.error("No active suggested lots found for this item"); @@ -1317,10 +1466,13 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } // ✅ OPTIMIZATION: 按优先级查找匹配的 lot + const matchStartTime = performance.now(); // 1. 首先查找 stockInLineId 完全匹配的(正确的 lot) let exactMatch = activeSuggestedLots.find(lot => lot.stockInLineId === scannedStockInLineId ); + const matchTime = performance.now() - matchStartTime; + console.log(`⏱️ Find exact match time: ${matchTime.toFixed(2)}ms, found: ${exactMatch ? 'yes' : 'no'}`); if (exactMatch) { // ✅ Case 1: stockInLineId 匹配 - 直接处理,不需要确认 @@ -1334,6 +1486,8 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } try { + const apiStartTime = performance.now(); + console.log(`⏱️ [API CALL START] Calling updateStockOutLineStatusByQRCodeAndLotNo`); const res = await updateStockOutLineStatusByQRCodeAndLotNo({ pickOrderLineId: exactMatch.pickOrderLineId, inventoryLotNo: exactMatch.lotNo, @@ -1341,8 +1495,11 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); itemId: exactMatch.itemId, status: "checked", }); + const apiTime = performance.now() - apiStartTime; + console.log(`⏱️ [API CALL END] Total API time: ${apiTime.toFixed(2)}ms (${(apiTime / 1000).toFixed(3)}s)`); if (res.code === "checked" || res.code === "SUCCESS") { + const stateUpdateStartTime = performance.now(); setQrScanError(false); setQrScanSuccess(true); @@ -1371,7 +1528,13 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } return lot; })); + const stateUpdateTime = performance.now() - stateUpdateStartTime; + console.log(`⏱️ State update time: ${stateUpdateTime.toFixed(2)}ms`); + const totalTime = performance.now() - totalStartTime; + console.log(`✅ [PROCESS OUTSIDE QR END] Total time: ${totalTime.toFixed(2)}ms (${(totalTime / 1000).toFixed(3)}s)`); + console.log(`⏰ End time: ${new Date().toISOString()}`); + console.log(`📊 Breakdown: parse=${parseTime.toFixed(2)}ms, lookup=${lookupTime.toFixed(2)}ms, filter=${filterTime.toFixed(2)}ms, match=${matchTime.toFixed(2)}ms, api=${apiTime.toFixed(2)}ms, state=${stateUpdateTime.toFixed(2)}ms`); console.log("✅ Status updated locally, no full data refresh needed"); } else { console.warn("Unexpected response code from backend:", res.code); @@ -1379,6 +1542,8 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setQrScanSuccess(false); } } catch (e) { + const totalTime = performance.now() - totalStartTime; + console.error(`❌ [PROCESS OUTSIDE QR ERROR] Total time: ${totalTime.toFixed(2)}ms`); console.error("Error calling updateStockOutLineStatusByQRCodeAndLotNo:", e); setQrScanError(true); setQrScanSuccess(false); @@ -1417,6 +1582,8 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); } ); } catch (error) { + const totalTime = performance.now() - totalStartTime; + console.error(`❌ [PROCESS OUTSIDE QR ERROR] Total time: ${totalTime.toFixed(2)}ms`); console.error("Error during QR code processing:", error); setQrScanError(true); setQrScanSuccess(false); @@ -1811,13 +1978,16 @@ useEffect(() => { const newPageSize = parseInt(event.target.value, 10); setPaginationController({ pageNum: 0, - pageSize: newPageSize, + pageSize: newPageSize === -1 ? -1 : newPageSize, }); }, []); // Pagination data with sorting by routerIndex // Remove the sorting logic and just do pagination const paginatedData = useMemo(() => { + if (paginationController.pageSize === -1) { + return combinedLotData; // Show all items + } const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; return combinedLotData.slice(startIndex, endIndex); // No sorting needed @@ -2340,6 +2510,24 @@ const handleSubmitAllScanned = useCallback(async () => { > + + + {/* DO Header */} @@ -2543,7 +2731,7 @@ paginatedData.map((lot, index) => { }} > {lot.lotNo || - t('⚠️ No Stock Available')} + t('No Stock Available')} @@ -2698,7 +2886,7 @@ paginatedData.map((lot, index) => { }} title="Report missing or bad items" > - {t("Issue")} + {t("Edit")} ); @@ -2729,7 +2917,7 @@ paginatedData.map((lot, index) => { rowsPerPage={paginationController.pageSize} onPageChange={handlePageChange} onRowsPerPageChange={handlePageSizeChange} - rowsPerPageOptions={[10, 25, 50]} + rowsPerPageOptions={[10, 25, 50,-1]} labelRowsPerPage={t("Rows per page")} labelDisplayedRows={({ from, to, count }) => `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` @@ -2750,6 +2938,7 @@ paginatedData.map((lot, index) => { lot={selectedLotForQr} combinedLotData={combinedLotData} onQrCodeSubmit={handleQrCodeSubmitFromModal} + lotConfirmationOpen={lotConfirmationOpen} // ✅ Add this prop /> { variant="contained" color="primary" onClick={() => handleRelease(jobOrderId)} - disabled={stockCounts.insufficient > 0 || processData?.jobOrderStatus !== "planning"} + //disabled={stockCounts.insufficient > 0 || processData?.jobOrderStatus !== "planning"} > {t("Release")} diff --git a/src/i18n/en/dashboard.json b/src/i18n/en/dashboard.json index 5a1c9aa..9434bbf 100644 --- a/src/i18n/en/dashboard.json +++ b/src/i18n/en/dashboard.json @@ -79,6 +79,25 @@ "Tomorrow": "Tomorrow", "Day After Tomorrow": "Day After Tomorrow", "Goods Receipt Status": "Goods Receipt Status", + "Date": "Date", + "Time": "Time", + "Allow to select Date to view history.": "Allow to select Date to view history.", + "Auto-refresh every 15 minutes": "Auto-refresh every 15 minutes", + "Exit Screen": "Exit Screen", + "Restore Screen": "Restore Screen", + "Screen cleared": "Screen cleared", + "Supplier": "Supplier", + "Expected No. of Delivery": "Expected No. of Delivery", + "No. of Orders Received at Dock": "No. of Orders Received at Dock", + "No. of Items Inspected": "No. of Items Inspected", + "No. of Items with IQC Issue": "No. of Items with IQC Issue", + "No. of Items Completed Put Away at Store": "No. of Items Completed Put Away at Store", + "Show Supplier Name": "Show Supplier Name", + "Based on Expected Delivery Date": "Based on Expected Delivery Date", + "Upon entry of DN and Lot No. for all items of the order": "Upon entry of DN and Lot No. for all items of the order", + "Upon any IQC decision received": "Upon any IQC decision received", + "Count any item with IQC defect in any IQC criteria": "Count any item with IQC defect in any IQC criteria", + "Upon completion of put away for an material in order. Count no. of items being put away": "Upon completion of put away for an material in order. Count no. of items being put away", "Filter": "Filter", "All": "All", "Column 1": "Column 1", diff --git a/src/i18n/zh/dashboard.json b/src/i18n/zh/dashboard.json index c2cf9d7..78646d2 100644 --- a/src/i18n/zh/dashboard.json +++ b/src/i18n/zh/dashboard.json @@ -79,6 +79,25 @@ "Tomorrow": "翌日", "Day After Tomorrow": "後日", "Goods Receipt Status": "貨物接收狀態", + "Date": "日期", + "Time": "時間", + "Allow to select Date to view history.": "可選擇日期查看歷史記錄。", + "Auto-refresh every 15 minutes": "每15分鐘自動刷新", + "Exit Screen": "退出畫面", + "Restore Screen": "恢復畫面", + "Screen cleared": "畫面已清除", + "Supplier": "供應商", + "Expected No. of Delivery": "預計送貨單數", + "No. of Orders Received at Dock": "已收訂單數", + "No. of Items Inspected": "已檢驗貨品數", + "No. of Items with IQC Issue": "IQC異常貨品數", + "No. of Items Completed Put Away at Store": "已完成上架貨品數", + "Show Supplier Name": "顯示供應商名稱", + "Based on Expected Delivery Date": "按預計送貨日期統計", + "Upon entry of DN and Lot No. for all items of the order": "當訂單所有貨品已輸入DN及批號時", + "Upon any IQC decision received": "當收到任何IQC判定", + "Count any item with IQC defect in any IQC criteria": "統計任何IQC準則不合格的貨品", + "Upon completion of put away for an material in order. Count no. of items being put away": "當訂單物料完成上架。統計正在上架的貨品數", "Filter": "篩選", "All": "全部", "Column 1": "欄位1", diff --git a/src/i18n/zh/do.json b/src/i18n/zh/do.json index 4c6584d..05c735d 100644 --- a/src/i18n/zh/do.json +++ b/src/i18n/zh/do.json @@ -11,14 +11,24 @@ "Status": "來貨狀態", "Order Date From": "訂單日期", "Delivery Order Code": "送貨訂單編號", + "Select Remark": "選擇備註", + "Confirm Assignment": "確認分配", + "Required Date": "所需日期", + "Store": "位置", + "Lane Code": "車線號碼", + "Available Orders": "可用訂單", + "Just Complete": "已完成", "Order Date To": "訂單日期至", "Warning: Some delivery orders do not have matching trucks for the target date.": "警告:部分送貨訂單於目標日期沒有可匹配的車輛。", "Truck Availability Warning": "車輛可用性警告", "Problem DO(s): ": "問題送貨訂單", "Fetching all matching records...": "正在獲取所有匹配的記錄...", + "Progress": "進度", "Loading...": "正在加載...", "Available Trucks": "可用車輛", "No trucks available": "沒有車輛可用", + "Remark": "備註", + "Just Completed": "已完成", "Code": "門店訂單編號", "code": "門店訂單編號", "Create": "新增", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index d63e378..2635290 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -9,12 +9,22 @@ "Status": "來貨狀態", "N/A": "不適用", "Release Pick Orders": "放單", + "Remark": "備註", "Escalated": "上報狀態", "NotEscalated": "無上報", "Assigned To": "已分配", + "Progress": "進度", + "Select Remark": "選擇備註", + "Just Complete": "已完成", "Skip": "跳過", + "Confirm Assignment": "確認分配", + "Required Date": "所需日期", + "Store": "位置", + "Available Orders": "可用訂單", + "Lane Code": "車線號碼", "Fetching all matching records...": "正在獲取所有匹配的記錄...", - + "Edit": "改數", + "Just Completed": "已完成", "Do you want to start?": "確定開始嗎?", "Start": "開始", "Pick Order Code(s)": "提料單編號", @@ -257,9 +267,11 @@ "Pick Execution Issue Form":"提料問題表單", "This form is for reporting issues only. You must report either missing items or bad items.":"此表單僅用於報告問題。您必須報告缺少的貨品或不良貨品。", "Bad item Qty":"不良貨品數量", - "Missing item Qty":"缺少貨品數量", + "Missing item Qty":"貨品遺失數量", + "Missing Item Qty":"貨品遺失數量", "Bad Item Qty":"不良貨品數量", - "Missing Item Qty":"缺少貨品數量", + "Bad Package Qty":"不良包裝數量", + "Actual Pick Qty":"實際提料數量", "Required Qty":"所需數量", "Issue Remark":"問題描述",