# Conflicts: # src/components/JoSave/PickTable.tsxmaster
| @@ -526,7 +526,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| stockOutLineId: lot.stockOutLineId, | |||
| stockOutLineStatus: lot.stockOutLineStatus, | |||
| stockOutLineQty: lot.stockOutLineQty, | |||
| suggestedPickLotId: lot.suggestedPickLotId, | |||
| // Router info | |||
| routerIndex: lot.routerIndex, | |||
| secondQrScanStatus: lot.secondQrScanStatus, | |||
| @@ -714,9 +714,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| setQrScanSuccess(true); | |||
| setQrScanError(false); | |||
| setQrScanInput(''); // Clear input after successful processing | |||
| setIsManualScanning(false); | |||
| stopScan(); | |||
| resetScan(); | |||
| } else { | |||
| console.error(`❌ QR Code processing failed: ${errorCount} errors`); | |||
| setQrScanError(true); | |||
| @@ -759,48 +757,87 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| return; | |||
| } | |||
| await confirmLotSubstitution({ | |||
| console.log("=== Lot Confirmation Debug ==="); | |||
| console.log("Selected Lot:", selectedLotForQr); | |||
| console.log("Pick Order Line ID:", selectedLotForQr.pickOrderLineId); | |||
| console.log("Stock Out Line ID:", selectedLotForQr.stockOutLineId); | |||
| console.log("Suggested Pick Lot ID:", selectedLotForQr.suggestedPickLotId); | |||
| console.log("Lot ID (fallback):", selectedLotForQr.lotId); | |||
| console.log("New Inventory Lot Line ID:", newLotLineId); | |||
| // ✅ Call confirmLotSubstitution to update the suggested lot | |||
| const substitutionResult = await confirmLotSubstitution({ | |||
| pickOrderLineId: selectedLotForQr.pickOrderLineId, | |||
| stockOutLineId: selectedLotForQr.stockOutLineId, | |||
| originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId, | |||
| originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId, | |||
| newInventoryLotLineId: newLotLineId | |||
| }); | |||
| setQrScanError(false); | |||
| setQrScanSuccess(false); | |||
| setQrScanInput(''); | |||
| setProcessedQrCodes(new Set()); | |||
| setLastProcessedQr(''); | |||
| console.log("✅ Lot substitution result:", substitutionResult); | |||
| // ✅ Update stock out line status to 'checked' after substitution | |||
| if(selectedLotForQr?.stockOutLineId){ | |||
| await updateStockOutLineStatus({ | |||
| id: selectedLotForQr.stockOutLineId, | |||
| status: 'checked', | |||
| qty: 0 | |||
| }); | |||
| console.log("✅ Stock out line status updated to 'checked'"); | |||
| } | |||
| // ✅ Close modal and clean up state BEFORE refreshing | |||
| setLotConfirmationOpen(false); | |||
| setExpectedLotData(null); | |||
| setScannedLotData(null); | |||
| setSelectedLotForQr(null); | |||
| // ✅ Clear QR processing state but DON'T clear processedQrCodes yet | |||
| setQrScanError(false); | |||
| setQrScanSuccess(true); | |||
| setQrScanInput(''); | |||
| // ✅ Set refreshing flag to prevent QR processing during refresh | |||
| setIsRefreshingData(true); | |||
| // ✅ Refresh data to show updated lot | |||
| console.log("🔄 Refreshing job order data..."); | |||
| await fetchJobOrderData(); | |||
| console.log("✅ Lot substitution confirmed and data refreshed"); | |||
| // ✅ Clear processed QR codes and flags immediately after refresh | |||
| // This allows new QR codes to be processed right away | |||
| setTimeout(() => { | |||
| console.log("✅ Clearing processed QR codes and resuming scan"); | |||
| setProcessedQrCodes(new Set()); | |||
| setLastProcessedQr(''); | |||
| setQrScanSuccess(false); | |||
| setIsRefreshingData(false); | |||
| }, 500); // ✅ Reduced from 3000ms to 500ms - just enough for UI update | |||
| } catch (error) { | |||
| console.error("Error confirming lot substitution:", error); | |||
| setQrScanError(true); | |||
| setQrScanSuccess(false); | |||
| // ✅ Clear refresh flag on error | |||
| setIsRefreshingData(false); | |||
| } finally { | |||
| setIsConfirmingLot(false); | |||
| } | |||
| }, [expectedLotData, scannedLotData, selectedLotForQr, fetchJobOrderData]); | |||
| const processOutsideQrCode = useCallback(async (latestQr: string) => { | |||
| // ✅ Don't process if confirmation modal is open | |||
| if (lotConfirmationOpen) { | |||
| console.log("⏸️ Confirmation modal is open, skipping QR processing"); | |||
| return; | |||
| } | |||
| let qrData: any = null; | |||
| try { | |||
| qrData = JSON.parse(latestQr); | |||
| } catch { | |||
| console.log("QR is not JSON format"); | |||
| // ✅ Handle non-JSON QR codes as direct lot numbers | |||
| // Handle non-JSON QR codes as direct lot numbers | |||
| const directLotNo = latestQr.replace(/[{}]/g, ''); | |||
| if (directLotNo) { | |||
| console.log(`Processing direct lot number: ${directLotNo}`); | |||
| @@ -891,6 +928,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| // Case 2: Same item, different lot - show confirmation modal | |||
| console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); | |||
| // ✅ DON'T stop scanning - just pause QR processing by showing modal | |||
| setSelectedLotForQr(expectedLot); | |||
| handleLotMismatch( | |||
| { | |||
| @@ -912,7 +951,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| setQrScanSuccess(false); | |||
| return; | |||
| } | |||
| }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); | |||
| }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch, lotConfirmationOpen]); | |||
| const handleManualInputSubmit = useCallback(() => { | |||
| if (qrScanInput.trim() !== '') { | |||
| @@ -964,7 +1004,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| useEffect(() => { | |||
| if (qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { | |||
| // ✅ Add isManualScanning check | |||
| if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData || lotConfirmationOpen) { | |||
| return; | |||
| } | |||
| @@ -982,7 +1023,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| processOutsideQrCode(latestQr); | |||
| } | |||
| }, [qrValues, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]); | |||
| }, [qrValues, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData, isManualScanning, lotConfirmationOpen]); | |||
| const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { | |||
| if (value === '' || value === null || value === undefined) { | |||
| @@ -1372,6 +1413,16 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| stopScan(); | |||
| resetScan(); | |||
| }, [stopScan, resetScan]); | |||
| useEffect(() => { | |||
| return () => { | |||
| // Cleanup when component unmounts (e.g., when switching tabs) | |||
| if (isManualScanning) { | |||
| console.log("🧹 Component unmounting, stopping QR scanner..."); | |||
| stopScan(); | |||
| resetScan(); | |||
| } | |||
| }; | |||
| }, [isManualScanning, stopScan, resetScan]); | |||
| const getStatusMessage = useCallback((lot: any) => { | |||
| switch (lot.stockOutLineStatus?.toLowerCase()) { | |||
| @@ -1639,6 +1690,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| </Stack> | |||
| {/* ✅ QR Code Modal */} | |||
| {!lotConfirmationOpen && ( | |||
| <QrCodeModal | |||
| open={qrModalOpen} | |||
| onClose={() => { | |||
| @@ -1651,6 +1703,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| combinedLotData={combinedLotData} | |||
| onQrCodeSubmit={handleQrCodeSubmitFromModal} | |||
| /> | |||
| )} | |||
| {/* ✅ Add Lot Confirmation Modal */} | |||
| {lotConfirmationOpen && expectedLotData && scannedLotData && ( | |||
| <LotConfirmationModal | |||
| @@ -1659,6 +1712,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| setLotConfirmationOpen(false); | |||
| setExpectedLotData(null); | |||
| setScannedLotData(null); | |||
| setSelectedLotForQr(null); | |||
| }} | |||
| onConfirm={handleLotConfirmation} | |||
| expectedLot={expectedLotData} | |||
| @@ -614,7 +614,16 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| 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); | |||
| @@ -626,52 +635,68 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| } | |||
| }, [combinedLotData, fetchJobOrderData, processedQrCodes]); | |||
| useEffect(() => { | |||
| if (qrValues.length > 0 && combinedLotData.length > 0) { | |||
| 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); | |||
| // ✅ 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]); | |||
| }, [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()); | |||
| @@ -721,42 +746,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| }, [selectedLotForQr, fetchJobOrderData]); | |||
| // ✅ Outside QR scanning - process QR codes from outside the page automatically | |||
| useEffect(() => { | |||
| if (qrValues.length > 0 && combinedLotData.length > 0) { | |||
| const latestQr = qrValues[qrValues.length - 1]; | |||
| // 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]); | |||
| const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { | |||
| if (value === '' || value === null || value === undefined) { | |||
| @@ -993,7 +983,16 @@ const paginatedData = useMemo(() => { | |||
| stopScan(); | |||
| resetScan(); | |||
| }, [stopScan, resetScan]); | |||
| 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': | |||
| @@ -1152,8 +1151,8 @@ const paginatedData = useMemo(() => { | |||
| })()} | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| {lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( | |||
| <TableCell align="center"> | |||
| {lot.secondQrScanStatus?.toLowerCase() !== 'pending' ? ( | |||
| <Box sx={{ | |||
| display: 'flex', | |||
| justifyContent: 'center', | |||
| @@ -1162,12 +1161,12 @@ const paginatedData = useMemo(() => { | |||
| height: '100%' | |||
| }}> | |||
| <Checkbox | |||
| checked={lot.secondQrScanStatus?.toLowerCase() !== 'pending'} | |||
| checked={lot.secondQrScanStatus?.toLowerCase() !== 'pending'} | |||
| disabled={true} | |||
| readOnly={true} | |||
| size="large" | |||
| sx={{ | |||
| color: lot.secondQrScanStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400', | |||
| color: lot.secondQrScanStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400', | |||
| '&.Mui-checked': { | |||
| color: 'success.main', | |||
| }, | |||
| @@ -101,7 +101,8 @@ | |||
| "Issue Remark": "問題描述", | |||
| "Received Qty": "接收數量", | |||
| "Create": "創建", | |||
| "Confirm Lot Substitution": "確認批號替換" | |||
| "Confirm Lot Substitution": "確認批號替換", | |||
| "Processing...": "處理中", | |||
| "Processing...": "處理中" | |||
| } | |||
| @@ -224,6 +224,8 @@ | |||
| "Selected items will join above created group": "已選擇的貨品將加入以上建立的分組", | |||
| "Issue":"問題", | |||
| "issue":"問題", | |||
| "Processing...":"處理中", | |||
| "Assigning pick order...":"分派提料單中...", | |||
| "Pick Execution Issue Form":"提料問題表單", | |||
| "This form is for reporting issues only. You must report either missing items or bad items.":"此表單僅用於報告問題。您必須報告缺少的貨品或不良貨品。", | |||
| "Bad item Qty":"不良貨品數量", | |||