"use client"; import { Box, Button, Stack, TextField, Typography, Alert, CircularProgress, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Checkbox, TablePagination, Modal, } from "@mui/material"; import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider'; import { useCallback, useEffect, useState, useRef, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/navigation"; // 修改:使用 Job Order API import { fetchCompletedJobOrderPickOrders, fetchUnassignedJobOrderPickOrders, assignJobOrderPickOrder, updateSecondQrScanStatus, submitSecondScanQuantity, recordSecondScanIssue } from "@/app/api/jo/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import { FormProvider, useForm, } from "react-hook-form"; import SearchBox, { Criterion } from "../SearchBox"; import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; import QrCodeIcon from '@mui/icons-material/QrCode'; import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider'; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import { fetchStockInLineInfo } from "@/app/api/po/actions"; import GoodPickExecutionForm from "./JobmatchForm"; import FGPickOrderCard from "./FGPickOrderCard"; interface Props { filterArgs: Record; } // QR Code Modal Component (from GoodPickExecution) const QrCodeModal: React.FC<{ open: boolean; onClose: () => void; lot: any | null; onQrCodeSubmit: (lotNo: string) => void; combinedLotData: any[]; }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { const { t } = useTranslation("jo"); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const [manualInput, setManualInput] = useState(''); const [manualInputSubmitted, setManualInputSubmitted] = useState(false); const [manualInputError, setManualInputError] = useState(false); const [isProcessingQr, setIsProcessingQr] = useState(false); const [qrScanFailed, setQrScanFailed] = useState(false); const [qrScanSuccess, setQrScanSuccess] = useState(false); const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [scannedQrResult, setScannedQrResult] = useState(''); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : // Process scanned QR codes useEffect(() => { if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) { const latestQr = qrValues[qrValues.length - 1]; if (processedQrCodes.has(latestQr)) { console.log("QR code already processed, skipping..."); return; } setProcessedQrCodes(prev => new Set(prev).add(latestQr)); try { const qrData = JSON.parse(latestQr); if (qrData.stockInLineId && qrData.itemId) { setIsProcessingQr(true); setQrScanFailed(false); fetchStockInLineInfo(qrData.stockInLineId) .then((stockInLineInfo) => { console.log("Stock in line info:", stockInLineInfo); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); if (stockInLineInfo.lotNo === lot.lotNo) { console.log(` QR Code verified for lot: ${lot.lotNo}`); setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); resetScan(); } else { console.log(`❌ QR Code mismatch. Expected: ${lot.lotNo}, Got: ${stockInLineInfo.lotNo}`); setQrScanFailed(true); setManualInputError(true); setManualInputSubmitted(true); } }) .catch((error) => { console.error("Error fetching stock in line info:", error); setScannedQrResult('Error fetching data'); setQrScanFailed(true); setManualInputError(true); setManualInputSubmitted(true); }) .finally(() => { setIsProcessingQr(false); }); } else { const qrContent = latestQr.replace(/[{}]/g, ''); setScannedQrResult(qrContent); if (qrContent === lot.lotNo) { setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); resetScan(); } else { setQrScanFailed(true); setManualInputError(true); setManualInputSubmitted(true); } } } catch (error) { console.log("QR code is not JSON format, trying direct comparison"); const qrContent = latestQr.replace(/[{}]/g, ''); setScannedQrResult(qrContent); if (qrContent === lot.lotNo) { setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); resetScan(); } else { setQrScanFailed(true); setManualInputError(true); setManualInputSubmitted(true); } } } }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes]); // Clear states when modal opens useEffect(() => { if (open) { setManualInput(''); setManualInputSubmitted(false); setManualInputError(false); setIsProcessingQr(false); setQrScanFailed(false); setQrScanSuccess(false); setScannedQrResult(''); setProcessedQrCodes(new Set()); } }, [open]); useEffect(() => { if (lot) { setManualInput(''); setManualInputSubmitted(false); setManualInputError(false); setIsProcessingQr(false); setQrScanFailed(false); setQrScanSuccess(false); setScannedQrResult(''); setProcessedQrCodes(new Set()); } }, [lot]); // Auto-submit manual input when it matches useEffect(() => { if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) { console.log(' Auto-submitting manual input:', manualInput.trim()); const timer = setTimeout(() => { setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); setManualInput(''); setManualInputError(false); setManualInputSubmitted(false); }, 200); return () => clearTimeout(timer); } }, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]); const handleManualSubmit = () => { if (manualInput.trim() === lot?.lotNo) { setQrScanSuccess(true); onQrCodeSubmit(lot.lotNo); onClose(); setManualInput(''); } else { setQrScanFailed(true); setManualInputError(true); setManualInputSubmitted(true); } }; useEffect(() => { if (open) { startScan(); } }, [open, startScan]); return ( {t("QR Code Scan for Lot")}: {lot?.lotNo} {isProcessingQr && ( {t("Processing QR code...")} )} {t("Manual Input")}: { setManualInput(e.target.value); if (qrScanFailed || manualInputError) { setQrScanFailed(false); setManualInputError(false); setManualInputSubmitted(false); } }} sx={{ mb: 1 }} error={manualInputSubmitted && manualInputError} helperText={ manualInputSubmitted && manualInputError ? `${t("The input is not the same as the expected lot number.")}` : '' } /> {qrValues.length > 0 && ( {t("QR Scan Result:")} {scannedQrResult} {qrScanSuccess && ( {t("Verified successfully!")} )} )} ); }; const JobPickExecution: React.FC = ({ filterArgs }) => { const { t } = useTranslation("jo"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; // 修改:使用 Job Order 数据结构 const [jobOrderData, setJobOrderData] = useState(null); const [combinedLotData, setCombinedLotData] = useState([]); const [combinedDataLoading, setCombinedDataLoading] = useState(false); const [originalCombinedData, setOriginalCombinedData] = useState([]); // 添加未分配订单状态 const [unassignedOrders, setUnassignedOrders] = useState([]); const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const [qrScanInput, setQrScanInput] = useState(''); const [qrScanError, setQrScanError] = useState(false); const [qrScanSuccess, setQrScanSuccess] = useState(false); const [pickQtyData, setPickQtyData] = useState>({}); const [searchQuery, setSearchQuery] = useState>({}); const [isSubmittingAll, setIsSubmittingAll] = useState(false); const [paginationController, setPaginationController] = useState({ pageNum: 0, pageSize: 10, }); const [usernameList, setUsernameList] = useState([]); const initializationRef = useRef(false); const autoAssignRef = useRef(false); const formProps = useForm(); const errors = formProps.formState.errors; // Add QR modal states const [qrModalOpen, setQrModalOpen] = useState(false); const [selectedLotForQr, setSelectedLotForQr] = useState(null); // Add GoodPickExecutionForm states const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState(null); // Add these missing state variables const [isManualScanning, setIsManualScanning] = useState(false); const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [lastProcessedQr, setLastProcessedQr] = useState(''); const [isRefreshingData, setIsRefreshingData] = useState(false); // 修改:加载未分配的 Job Order 订单 const loadUnassignedOrders = useCallback(async () => { setIsLoadingUnassigned(true); try { const orders = await fetchUnassignedJobOrderPickOrders(); setUnassignedOrders(orders); } catch (error) { console.error("Error loading unassigned orders:", error); } finally { setIsLoadingUnassigned(false); } }, []); // 修改:分配订单给当前用户 const handleAssignOrder = useCallback(async (pickOrderId: number) => { if (!currentUserId) { console.error("Missing user id in session"); return; } try { const result = await assignJobOrderPickOrder(pickOrderId, currentUserId); if (result.message === "Successfully assigned") { console.log(" Successfully assigned pick order"); // 刷新数据 window.dispatchEvent(new CustomEvent('pickOrderAssigned')); // 重新加载未分配订单列表 loadUnassignedOrders(); } else { console.warn("⚠️ Assignment failed:", result.message); alert(`Assignment failed: ${result.message}`); } } catch (error) { console.error("❌ Error assigning order:", error); alert("Error occurred during assignment"); } }, [currentUserId, loadUnassignedOrders]); // Handle QR code button click const handleQrCodeClick = (pickOrderId: number) => { console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); // TODO: Implement QR code functionality }; // 修改:使用 Job Order API 获取数据 const fetchJobOrderData = useCallback(async (userId?: number) => { setCombinedDataLoading(true); try { const userIdToUse = userId || currentUserId; console.log(" fetchJobOrderData called with userId:", userIdToUse); if (!userIdToUse) { console.warn("⚠️ No userId available, skipping API call"); setJobOrderData(null); setCombinedLotData([]); setOriginalCombinedData([]); return; } window.dispatchEvent(new CustomEvent('jobOrderDataStatus', { detail: { hasData: false, tabIndex: 1 } })); // 使用 Job Order API const jobOrderData = await fetchCompletedJobOrderPickOrders(userIdToUse); console.log(" Job Order data:", jobOrderData); console.log(" Pick Order Code from API:", jobOrderData.pickOrder?.code); console.log(" Expected Pick Order Code: P-20251003-001"); setJobOrderData(jobOrderData); // Transform hierarchical data to flat structure for the table const flatLotData: any[] = []; if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) { jobOrderData.pickOrderLines.forEach((line: any) => { if (line.lots && line.lots.length > 0) { line.lots.forEach((lot: any) => { flatLotData.push({ // Pick order info pickOrderId: jobOrderData.pickOrder.id, pickOrderCode: jobOrderData.pickOrder.code, pickOrderConsoCode: jobOrderData.pickOrder.consoCode, pickOrderTargetDate: jobOrderData.pickOrder.targetDate, pickOrderType: jobOrderData.pickOrder.type, pickOrderStatus: jobOrderData.pickOrder.status, pickOrderAssignTo: jobOrderData.pickOrder.assignTo, // Pick order line info pickOrderLineId: line.id, pickOrderLineRequiredQty: line.requiredQty, pickOrderLineStatus: line.status, // Item info itemId: line.itemId, itemCode: line.itemCode, itemName: line.itemName, uomCode: line.uomCode, uomDesc: line.uomDesc, // Lot info lotId: lot.lotId, lotNo: lot.lotNo, expiryDate: lot.expiryDate, location: lot.location, availableQty: lot.availableQty, requiredQty: lot.requiredQty, actualPickQty: lot.actualPickQty, lotStatus: lot.lotStatus, lotAvailability: lot.lotAvailability, processingStatus: lot.processingStatus, stockOutLineId: lot.stockOutLineId, stockOutLineStatus: lot.stockOutLineStatus, stockOutLineQty: lot.stockOutLineQty, // Router info routerIndex: lot.routerIndex, matchStatus: lot.matchStatus, routerArea: lot.routerArea, routerRoute: lot.routerRoute, uomShortDesc: lot.uomShortDesc }); }); } }); } console.log(" Transformed flat lot data:", flatLotData); setCombinedLotData(flatLotData); setOriginalCombinedData(flatLotData); const hasData = flatLotData.length > 0; window.dispatchEvent(new CustomEvent('jobOrderDataStatus', { detail: { hasData: hasData, tabIndex: 1 } })); // 计算完成状态并发送事件 const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) => lot.processingStatus === 'completed' ); window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: allCompleted, tabIndex: 1 } })); // 发送完成状态事件,包含标签页信息 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: allCompleted, tabIndex: 0 // 明确指定这是来自标签页 0 的事件 } })); } catch (error) { console.error("❌ Error fetching job order data:", error); setJobOrderData(null); setCombinedLotData([]); setOriginalCombinedData([]); // 如果加载失败,禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, tabIndex: 0 } })); } finally { setCombinedDataLoading(false); } }, [currentUserId]); const handleSubmitAllScanned = useCallback(async () => { const scannedLots = combinedLotData.filter(lot => lot.matchStatus === 'scanned' // Only submit items that are scanned but not yet submitted ); if (scannedLots.length === 0) { console.log("No scanned items to submit"); return; } setIsSubmittingAll(true); console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); try { // Submit all items in parallel using Promise.all const submitPromises = scannedLots.map(async (lot) => { const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; console.log(`Submitting item ${lot.itemCode}: qty=${submitQty}`); const result = await submitSecondScanQuantity( lot.pickOrderId, lot.itemId, { qty: submitQty, isMissing: false, isBad: false, reason: undefined } ); return { success: result.code === "SUCCESS", itemCode: lot.itemCode }; }); // Wait for all submissions to complete const results = await Promise.all(submitPromises); const successCount = results.filter(r => r.success).length; console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); // Refresh data once after all submissions await fetchJobOrderData(); if (successCount > 0) { setQrScanSuccess(true); setTimeout(() => setQrScanSuccess(false), 2000); } } catch (error) { console.error("Error submitting all scanned items:", error); setQrScanError(true); } finally { setIsSubmittingAll(false); } }, [combinedLotData, fetchJobOrderData]); // Calculate scanned items count const scannedItemsCount = useMemo(() => { return combinedLotData.filter(lot => lot.matchStatus === 'scanned').length; }, [combinedLotData]); // 修改:初始化时加载数据 useEffect(() => { if (session && currentUserId && !initializationRef.current) { console.log(" Session loaded, initializing job order..."); initializationRef.current = true; // 加载 Job Order 数据 fetchJobOrderData(); // 加载未分配订单 loadUnassignedOrders(); } }, [session, currentUserId, fetchJobOrderData, loadUnassignedOrders]); // Add event listener for manual assignment useEffect(() => { const handlePickOrderAssigned = () => { console.log("🔄 Pick order assigned event received, refreshing data..."); fetchJobOrderData(); }; window.addEventListener('pickOrderAssigned', handlePickOrderAssigned); return () => { window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned); }; }, [fetchJobOrderData]); // Handle QR code submission for matched lot (external scanning) const handleQrCodeSubmit = useCallback(async (lotNo: string) => { console.log(` Processing Second QR Code for lot: ${lotNo}`); // Check if this lot was already processed recently const lotKey = `${lotNo}_${Date.now()}`; if (processedQrCodes.has(lotNo)) { console.log(`⏭️ Lot ${lotNo} already processed, skipping...`); return; } const currentLotData = combinedLotData; const matchingLots = currentLotData.filter(lot => lot.lotNo === lotNo || lot.lotNo?.toLowerCase() === lotNo.toLowerCase() ); if (matchingLots.length === 0) { console.error(`❌ Lot not found: ${lotNo}`); setQrScanError(true); setQrScanSuccess(false); return; } try { let successCount = 0; for (const matchingLot of matchingLots) { // Check if this specific item was already processed const itemKey = `${matchingLot.pickOrderId}_${matchingLot.itemId}`; if (processedQrCodes.has(itemKey)) { console.log(`⏭️ Item ${matchingLot.itemId} already processed, skipping...`); continue; } // Use the new second scan API const result = await updateSecondQrScanStatus( matchingLot.pickOrderId, matchingLot.itemId, currentUserId || 0, matchingLot.requiredQty || 1 // 传递实际的 required quantity ); if (result.code === "SUCCESS") { successCount++; // Mark this item as processed setProcessedQrCodes(prev => new Set(prev).add(itemKey)); console.log(` Second QR scan status updated for item ${matchingLot.itemId}`); } else { console.error(`❌ Failed to update second QR scan status: ${result.message}`); } } if (successCount > 0) { setQrScanSuccess(true); setQrScanError(false); // Set refreshing flag briefly to prevent duplicate processing setIsRefreshingData(true); await fetchJobOrderData(); // Refresh data // Clear refresh flag and success message after a short delay setTimeout(() => { setQrScanSuccess(false); setIsRefreshingData(false); }, 500); } else { setQrScanError(true); setQrScanSuccess(false); } } catch (error) { console.error("❌ Error processing second QR code:", error); setQrScanError(true); setQrScanSuccess(false); } }, [combinedLotData, fetchJobOrderData, processedQrCodes]); useEffect(() => { // Add isManualScanning and isRefreshingData checks if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { return; } const latestQr = qrValues[qrValues.length - 1]; // Check if this QR was already processed recently if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { console.log("⏭️ QR code already processed, skipping..."); return; } // Mark as processed setProcessedQrCodes(prev => new Set(prev).add(latestQr)); setLastProcessedQr(latestQr); // Extract lot number from QR code let lotNo = ''; try { const qrData = JSON.parse(latestQr); if (qrData.stockInLineId && qrData.itemId) { // For JSON QR codes, we need to fetch the lot number fetchStockInLineInfo(qrData.stockInLineId) .then((stockInLineInfo) => { console.log("Outside QR scan - Stock in line info:", stockInLineInfo); const extractedLotNo = stockInLineInfo.lotNo; if (extractedLotNo) { console.log(`Outside QR scan detected (JSON): ${extractedLotNo}`); handleQrCodeSubmit(extractedLotNo); } }) .catch((error) => { console.error("Outside QR scan - Error fetching stock in line info:", error); }); return; // Exit early for JSON QR codes } } catch (error) { // Not JSON format, treat as direct lot number lotNo = latestQr.replace(/[{}]/g, ''); } // For direct lot number QR codes if (lotNo) { console.log(`Outside QR scan detected (direct): ${lotNo}`); handleQrCodeSubmit(lotNo); } }, [qrValues, combinedLotData, handleQrCodeSubmit, processedQrCodes, lastProcessedQr, isManualScanning, isRefreshingData]); // ADD THIS: Cleanup effect useEffect(() => { return () => { // Cleanup when component unmounts (e.g., when switching tabs) if (isManualScanning) { console.log("🧹 Second scan component unmounting, stopping QR scanner..."); stopScan(); resetScan(); } }; }, [isManualScanning, stopScan, resetScan]); const handleManualInputSubmit = useCallback(() => { if (qrScanInput.trim() !== '') { handleQrCodeSubmit(qrScanInput.trim()); } }, [qrScanInput, handleQrCodeSubmit]); // Handle QR code submission from modal (internal scanning) const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { console.log(` QR Code verified for lot: ${lotNo}`); const requiredQty = selectedLotForQr.requiredQty; const lotId = selectedLotForQr.lotId; // Create stock out line const stockOutLineData: CreateStockOutLine = { consoCode: selectedLotForQr.pickOrderConsoCode, pickOrderLineId: selectedLotForQr.pickOrderLineId, inventoryLotLineId: selectedLotForQr.lotId, qty: 0.0 }; try { // Close modal setQrModalOpen(false); setSelectedLotForQr(null); // Set pick quantity const lotKey = `${selectedLotForQr.pickOrderLineId}-${lotId}`; setTimeout(() => { setPickQtyData(prev => ({ ...prev, [lotKey]: requiredQty })); console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); }, 500); // Refresh data await fetchJobOrderData(); } catch (error) { console.error("Error creating stock out line:", error); } } }, [selectedLotForQr, fetchJobOrderData]); // Outside QR scanning - process QR codes from outside the page automatically const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { if (value === '' || value === null || value === undefined) { setPickQtyData(prev => ({ ...prev, [lotKey]: 0 })); return; } const numericValue = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(numericValue)) { setPickQtyData(prev => ({ ...prev, [lotKey]: 0 })); return; } setPickQtyData(prev => ({ ...prev, [lotKey]: numericValue })); }, []); const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle'); const [autoAssignMessage, setAutoAssignMessage] = useState(''); const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => { try { // Use the new second scan submit API const result = await submitSecondScanQuantity( lot.pickOrderId, lot.itemId, { qty: submitQty, isMissing: false, isBad: false, reason: undefined // Fix TypeScript error } ); if (result.code === "SUCCESS") { console.log(` Second scan quantity submitted: ${submitQty}`); await fetchJobOrderData(); // Refresh data } else { console.error(`❌ Failed to submit second scan quantity: ${result.message}`); } } catch (error) { console.error("Error submitting second scan quantity:", error); } }, [fetchJobOrderData]); // Handle reject lot // Handle pick execution form const handlePickExecutionForm = useCallback((lot: any) => { console.log("=== Pick Execution Form ==="); console.log("Lot data:", lot); console.log("lot.pickOrderCode:", lot.pickOrderCode); // 添加 console.log("lot.pickOrderId:", lot.pickOrderId); if (!lot) { console.warn("No lot data provided for pick execution form"); return; } console.log("Opening pick execution form for lot:", lot.lotNo); setSelectedLotForExecutionForm(lot); setPickExecutionFormOpen(true); console.log("Pick execution form opened for lot ID:", lot.lotId); }, []); const handlePickExecutionFormSubmit = useCallback(async (data: any) => { try { console.log("Pick execution form submitted:", data); if (!currentUserId) { console.error("❌ No current user ID available"); return; } const result = await recordSecondScanIssue( selectedLotForExecutionForm.pickOrderId, selectedLotForExecutionForm.itemId, { qty: data.actualPickQty, // verified qty missQty: data.missQty || 0, // 添加:实际的 miss qty badItemQty: data.badItemQty || 0, // 添加:实际的 bad item qty isMissing: data.missQty > 0, isBad: data.badItemQty > 0, reason: data.issueRemark || '', createdBy: currentUserId, type: "match" } ); console.log("Pick execution issue recorded:", result); if (result && result.code === "SUCCESS") { console.log(" Pick execution issue recorded successfully"); } else { console.error("❌ Failed to record pick execution issue:", result); } setPickExecutionFormOpen(false); setSelectedLotForExecutionForm(null); await fetchJobOrderData(); } catch (error) { console.error("Error submitting pick execution form:", error); } }, [currentUserId, selectedLotForExecutionForm, fetchJobOrderData,]); // Calculate remaining required quantity const calculateRemainingRequiredQty = useCallback((lot: any) => { const requiredQty = lot.requiredQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0; return Math.max(0, requiredQty - stockOutLineQty); }, []); // Search criteria const searchCriteria: Criterion[] = [ { label: t("Pick Order Code"), paramName: "pickOrderCode", type: "text", }, { label: t("Item Code"), paramName: "itemCode", type: "text", }, { label: t("Item Name"), paramName: "itemName", type: "text", }, { label: t("Lot No"), paramName: "lotNo", type: "text", }, ]; const handleSearch = useCallback((query: Record) => { setSearchQuery({ ...query }); console.log("Search query:", query); if (!originalCombinedData) return; const filtered = originalCombinedData.filter((lot: any) => { const pickOrderCodeMatch = !query.pickOrderCode || lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase()); const itemCodeMatch = !query.itemCode || lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase()); const itemNameMatch = !query.itemName || lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase()); const lotNoMatch = !query.lotNo || lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase()); return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch; }); setCombinedLotData(filtered); console.log("Filtered lots count:", filtered.length); }, [originalCombinedData]); const handleReset = useCallback(() => { setSearchQuery({}); if (originalCombinedData) { setCombinedLotData(originalCombinedData); } }, [originalCombinedData]); const handlePageChange = useCallback((event: unknown, newPage: number) => { setPaginationController(prev => ({ ...prev, pageNum: newPage, })); }, []); const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { const newPageSize = parseInt(event.target.value, 10); setPaginationController({ pageNum: 0, pageSize: newPageSize, }); }, []); // Pagination data with sorting by routerIndex const paginatedData = useMemo(() => { // Sort by routerIndex first, then by other criteria const sortedData = [...combinedLotData].sort((a, b) => { const aIndex = a.routerIndex || 0; const bIndex = b.routerIndex || 0; // Primary sort: by routerIndex if (aIndex !== bIndex) { return aIndex - bIndex; } // Secondary sort: by pickOrderCode if routerIndex is the same if (a.pickOrderCode !== b.pickOrderCode) { return a.pickOrderCode.localeCompare(b.pickOrderCode); } // Tertiary sort: by lotNo if everything else is the same return (a.lotNo || '').localeCompare(b.lotNo || ''); }); const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; return sortedData.slice(startIndex, endIndex); }, [combinedLotData, paginationController]); // Add these functions for manual scanning const handleStartScan = useCallback(() => { console.log(" Starting manual QR scan..."); setIsManualScanning(true); setProcessedQrCodes(new Set()); setLastProcessedQr(''); setQrScanError(false); setQrScanSuccess(false); startScan(); }, [startScan]); const handleStopScan = useCallback(() => { console.log("⏹️ Stopping manual QR scan..."); setIsManualScanning(false); setQrScanError(false); setQrScanSuccess(false); stopScan(); resetScan(); }, [stopScan, resetScan]); useEffect(() => { if (isManualScanning && combinedLotData.length === 0) { console.log("⏹️ No data available, auto-stopping QR scan..."); handleStopScan(); } }, [combinedLotData.length, isManualScanning, handleStopScan]); // Cleanup effect useEffect(() => { return () => { // Cleanup when component unmounts (e.g., when switching tabs) if (isManualScanning) { console.log("🧹 Second scan component unmounting, stopping QR scanner..."); stopScan(); resetScan(); } }; }, [isManualScanning, stopScan, resetScan]); const getStatusMessage = useCallback((lot: any) => { switch (lot.stockOutLineStatus?.toLowerCase()) { case 'pending': return t("Please finish QR code scan and pick order."); case 'checked': return t("Please submit the pick order."); case 'partially_completed': return t("Partial quantity submitted. Please submit more or complete the order."); case 'completed': return t("Pick order completed successfully!"); case 'rejected': return t("Lot has been rejected and marked as unavailable."); case 'unavailable': return t("This order is insufficient, please pick another lot."); default: return t("Please finish QR code scan and pick order."); } }, [t]); return ( ( lot.matchStatus !== 'completed' && lot.lotAvailability !== 'rejected' )} > {/* Job Order Header */} {jobOrderData && ( {t("Job Order")}: {jobOrderData.pickOrder?.jobOrder?.code || '-'} {t("Pick Order Code")}: {jobOrderData.pickOrder?.code || '-'} {t("Target Date")}: {jobOrderData.pickOrder?.targetDate || '-'} )} {/* Combined Lot Table */} {!isManualScanning ? ( ) : ( )} {/* ADD THIS: Submit All Scanned Button */} {qrScanError && !qrScanSuccess && ( {t("QR code does not match any item in current orders.")} )} {qrScanSuccess && ( {t("QR code verified.")} )} {t("Index")} {t("Route")} {t("Item Code")} {t("Item Name")} {t("Lot No")} {t("Lot Required Pick Qty")} {t("Scan Result")} {t("Submit Required Pick Qty")} {paginatedData.length === 0 ? ( {t("No data available")} ) : ( paginatedData.map((lot, index) => ( {index + 1} {lot.routerRoute || '-'} {lot.itemCode} {lot.itemName+'('+lot.uomDesc+')'} {lot.lotNo} {(() => { const requiredQty = lot.requiredQty || 0; return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')'; })()} {lot.matchStatus?.toLowerCase() === 'scanned' || lot.matchStatus?.toLowerCase() === 'completed' ? ( ) : ( {t(" ")} )} )) )}
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` } />
{/* QR Code Modal */} { setQrModalOpen(false); setSelectedLotForQr(null); stopScan(); resetScan(); }} lot={selectedLotForQr} combinedLotData={combinedLotData} onQrCodeSubmit={handleQrCodeSubmitFromModal} /> {/* Pick Execution Form Modal */} {pickExecutionFormOpen && selectedLotForExecutionForm && ( { setPickExecutionFormOpen(false); setSelectedLotForExecutionForm(null); }} onSubmit={handlePickExecutionFormSubmit} selectedLot={selectedLotForExecutionForm} selectedPickOrderLine={{ id: selectedLotForExecutionForm.pickOrderLineId, itemId: selectedLotForExecutionForm.itemId, itemCode: selectedLotForExecutionForm.itemCode, itemName: selectedLotForExecutionForm.itemName, pickOrderCode: selectedLotForExecutionForm.pickOrderCode, // Add missing required properties from GetPickOrderLineInfo interface availableQty: selectedLotForExecutionForm.availableQty || 0, requiredQty: selectedLotForExecutionForm.requiredQty || 0, uomCode: selectedLotForExecutionForm.uomCode || '', uomDesc: selectedLotForExecutionForm.uomDesc || '', pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // Use pickedQty instead of actualPickQty suggestedList: [] // Add required suggestedList property }} pickOrderId={selectedLotForExecutionForm.pickOrderId} pickOrderCreateDate={new Date()} /> )}
); }; export default JobPickExecution