"use client"; import { Box, Button, Stack, TextField, Typography, Alert, CircularProgress, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Checkbox, TablePagination, Modal, } from "@mui/material"; import { fetchLotDetail } from "@/app/api/inventory/actions"; import { useCallback, useEffect, useState, useRef, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/navigation"; import { fetchALLPickOrderLineLotDetails, updateStockOutLineStatus, createStockOutLine, updateStockOutLine, recordPickExecutionIssue, fetchFGPickOrders, // ✅ Add this import FGPickOrderResponse, autoAssignAndReleasePickOrder, AutoAssignReleaseResponse, checkPickOrderCompletion, PickOrderCompletionResponse, checkAndCompletePickOrderByConsoCode, updateSuggestedLotLineId, confirmLotSubstitution } from "@/app/api/pickOrder/actions"; import LotConfirmationModal from "./LotConfirmationModal"; //import { fetchItem } from "@/app/api/settings/item"; import { updateInventoryLotLineStatus, analyzeQrCode } from "@/app/api/inventory/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 "./GoodPickExecutionForm"; import FGPickOrderCard from "./FGPickOrderCard"; interface Props { filterArgs: Record; } // ✅ QR Code Modal Component (from LotTable) const QrCodeModal: React.FC<{ open: boolean; onClose: () => void; lot: any | null; onQrCodeSubmit: (lotNo: string) => void; combinedLotData: any[]; // ✅ Add this prop }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { const { t } = useTranslation("pickOrder"); 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 [fgPickOrder, setFgPickOrder] = useState(null); // 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 PickExecution: React.FC = ({ filterArgs }) => { const { t } = useTranslation("pickOrder"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; const [combinedLotData, setCombinedLotData] = useState([]); const [combinedDataLoading, setCombinedDataLoading] = useState(false); const [originalCombinedData, setOriginalCombinedData] = useState([]); 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 [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); const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false); const [expectedLotData, setExpectedLotData] = useState(null); const [scannedLotData, setScannedLotData] = useState(null); const [isConfirmingLot, setIsConfirmingLot] = useState(false); // ✅ Add GoodPickExecutionForm states const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState(null); const [fgPickOrders, setFgPickOrders] = useState([]); const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); // ✅ Add these missing state variables after line 352 const [isManualScanning, setIsManualScanning] = useState(false); const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [lastProcessedQr, setLastProcessedQr] = useState(''); const [isRefreshingData, setIsRefreshingData] = useState(false); const fetchFgPickOrdersData = useCallback(async () => { if (!currentUserId) return; setFgPickOrdersLoading(true); try { // Get all pick order IDs from combinedLotData const pickOrderIds = Array.from(new Set(combinedLotData.map(lot => lot.pickOrderId))); if (pickOrderIds.length === 0) { setFgPickOrders([]); return; } // Fetch FG pick orders for each pick order ID const fgPickOrdersPromises = pickOrderIds.map(pickOrderId => fetchFGPickOrders(pickOrderId) ); const fgPickOrdersResults = await Promise.all(fgPickOrdersPromises); // Flatten the results (each fetchFGPickOrders returns an array) const allFgPickOrders = fgPickOrdersResults.flat(); setFgPickOrders(allFgPickOrders); console.log("✅ Fetched FG pick orders:", allFgPickOrders); } catch (error) { console.error("❌ Error fetching FG pick orders:", error); setFgPickOrders([]); } finally { setFgPickOrdersLoading(false); } }, [currentUserId, combinedLotData]); useEffect(() => { if (combinedLotData.length > 0) { fetchFgPickOrdersData(); } }, [combinedLotData, fetchFgPickOrdersData]); // ✅ Handle QR code button click const handleQrCodeClick = (pickOrderId: number) => { console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); // TODO: Implement QR code functionality }; const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any) => { console.log("Lot mismatch detected:", { expectedLot, scannedLot }); setExpectedLotData(expectedLot); setScannedLotData(scannedLot); setLotConfirmationOpen(true); }, []); const fetchAllCombinedLotData = useCallback(async (userId?: number) => { setCombinedDataLoading(true); try { const userIdToUse = userId || currentUserId; console.log(" fetchAllCombinedLotData called with userId:", userIdToUse); if (!userIdToUse) { console.warn("⚠️ No userId available, skipping API call"); setCombinedLotData([]); setOriginalCombinedData([]); return; } // ✅ Use the non-auto-assign endpoint - this only fetches existing data const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); console.log("✅ All combined lot details:", allLotDetails); setCombinedLotData(allLotDetails); setOriginalCombinedData(allLotDetails); } catch (error) { console.error("❌ Error fetching combined lot data:", error); setCombinedLotData([]); setOriginalCombinedData([]); } finally { setCombinedDataLoading(false); } }, [currentUserId]); const handleLotConfirmation = useCallback(async () => { if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; setIsConfirmingLot(true); try { let newLotLineId = scannedLotData?.inventoryLotLineId; if (!newLotLineId && scannedLotData?.stockInLineId) { const ld = await fetchLotDetail(scannedLotData.stockInLineId); newLotLineId = ld.inventoryLotLineId; } if (!newLotLineId) { console.error("No inventory lot line id for scanned lot"); return; } await confirmLotSubstitution({ pickOrderLineId: selectedLotForQr.pickOrderLineId, stockOutLineId: selectedLotForQr.stockOutLineId, originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId, newInventoryLotLineId: newLotLineId }); setLotConfirmationOpen(false); setExpectedLotData(null); setScannedLotData(null); setSelectedLotForQr(null); await fetchAllCombinedLotData(); } catch (error) { console.error("Error confirming lot substitution:", error); } finally { setIsConfirmingLot(false); } }, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]); const handleQrCodeSubmit = useCallback(async (lotNo: string) => { console.log(`✅ Processing QR Code for lot: ${lotNo}`); // ✅ Use current data without refreshing to avoid infinite loop const currentLotData = combinedLotData; console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo)); 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); const availableLotNos = currentLotData.map(lot => lot.lotNo).join(', '); console.log(`❌ QR Code "${lotNo}" does not match any expected lots. Available lots: ${availableLotNos}`); return; } console.log(`✅ Found ${matchingLots.length} matching lots:`, matchingLots); setQrScanError(false); try { let successCount = 0; let errorCount = 0; for (const matchingLot of matchingLots) { console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`); if (matchingLot.stockOutLineId) { const stockOutLineUpdate = await updateStockOutLineStatus({ id: matchingLot.stockOutLineId, status: 'checked', qty: 0 }); console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); // Treat multiple backend shapes as success (type-safe via any) const r: any = stockOutLineUpdate as any; const updateOk = r?.code === 'SUCCESS' || typeof r?.id === 'number' || r?.type === 'checked' || r?.status === 'checked' || typeof r?.entity?.id === 'number' || r?.entity?.status === 'checked'; if (updateOk) { successCount++; } else { errorCount++; } } else { const createStockOutLineData = { consoCode: matchingLot.pickOrderConsoCode, pickOrderLineId: matchingLot.pickOrderLineId, inventoryLotLineId: matchingLot.lotId, qty: 0 }; const createResult = await createStockOutLine(createStockOutLineData); console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, createResult); if (createResult && createResult.code === "SUCCESS") { // Immediately set status to checked for new line let newSolId: number | undefined; const anyRes: any = createResult as any; if (typeof anyRes?.id === 'number') { newSolId = anyRes.id; } else if (anyRes?.entity) { newSolId = Array.isArray(anyRes.entity) ? anyRes.entity[0]?.id : anyRes.entity?.id; } if (newSolId) { const setChecked = await updateStockOutLineStatus({ id: newSolId, status: 'checked', qty: 0 }); if (setChecked && setChecked.code === "SUCCESS") { successCount++; } else { errorCount++; } } else { console.warn("Created stock out line but no ID returned; cannot set to checked"); errorCount++; } } else { errorCount++; } } } // ✅ FIXED: Set refresh flag before refreshing data setIsRefreshingData(true); console.log("🔄 Refreshing data after QR code processing..."); await fetchAllCombinedLotData(); if (successCount > 0) { console.log(`✅ QR Code processing completed: ${successCount} updated/created`); setQrScanSuccess(true); setQrScanError(false); setQrScanInput(''); // Clear input after successful processing setIsManualScanning(false); stopScan(); resetScan(); // ✅ Clear success state after a delay //setTimeout(() => { //setQrScanSuccess(false); //}, 2000); } else { console.error(`❌ QR Code processing failed: ${errorCount} errors`); setQrScanError(true); setQrScanSuccess(false); // ✅ Clear error state after a delay // setTimeout(() => { // setQrScanError(false); //}, 3000); } } catch (error) { console.error("❌ Error processing QR code:", error); setQrScanError(true); setQrScanSuccess(false); // ✅ Still refresh data even on error setIsRefreshingData(true); await fetchAllCombinedLotData(); // ✅ Clear error state after a delay setTimeout(() => { setQrScanError(false); }, 3000); } finally { // ✅ Clear refresh flag after a short delay setTimeout(() => { setIsRefreshingData(false); }, 1000); } }, [combinedLotData, fetchAllCombinedLotData]); const processOutsideQrCode = useCallback(async (latestQr: string) => { // 1) Parse JSON safely let qrData: any = null; try { qrData = JSON.parse(latestQr); } catch { console.log("QR content is not JSON; skipping lotNo direct submit to avoid false matches."); setQrScanError(true); setQrScanSuccess(false); return; } try { // Only use the new API when we have JSON with stockInLineId + itemId if (!(qrData?.stockInLineId && qrData?.itemId)) { console.log("QR JSON missing required fields (itemId, stockInLineId)."); setQrScanError(true); setQrScanSuccess(false); return; } // Call new analyze-qr-code API const analysis = await analyzeQrCode({ itemId: qrData.itemId, stockInLineId: qrData.stockInLineId }); if (!analysis) { console.error("analyzeQrCode returned no data"); setQrScanError(true); setQrScanSuccess(false); return; } const { itemId: analyzedItemId, itemCode: analyzedItemCode, itemName: analyzedItemName, scanned, } = analysis || {}; // 1) Find all lots for the same item from current expected list const sameItemLotsInExpected = combinedLotData.filter(l => (l.itemId && analyzedItemId && l.itemId === analyzedItemId) || (l.itemCode && analyzedItemCode && l.itemCode === analyzedItemCode) ); if (!sameItemLotsInExpected || sameItemLotsInExpected.length === 0) { // Case 3: No item code match console.error("No item match in expected lots for scanned code"); setQrScanError(true); setQrScanSuccess(false); return; } // 2) Check if scanned lot is exactly in expected lots const exactLotMatch = sameItemLotsInExpected.find(l => (scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) || (scanned?.lotNo && l.lotNo === scanned.lotNo) ); if (exactLotMatch && scanned?.lotNo) { // Case 1: Normal case - item matches AND lot matches -> proceed console.log(`Exact lot match found for ${scanned.lotNo}, submitting QR`); handleQrCodeSubmit(scanned.lotNo); return; } // Case 2: Item matches but lot number differs -> open confirmation modal const expectedLot = sameItemLotsInExpected[0]; if (!expectedLot) { console.error("Could not determine expected lot for confirmation"); setQrScanError(true); setQrScanSuccess(false); return; } setSelectedLotForQr(expectedLot); handleLotMismatch( { lotNo: expectedLot.lotNo, itemCode: analyzedItemCode || expectedLot.itemCode, itemName: analyzedItemName || expectedLot.itemName }, { lotNo: scanned?.lotNo || '', itemCode: analyzedItemCode || expectedLot.itemCode, itemName: analyzedItemName || expectedLot.itemName, inventoryLotLineId: scanned?.inventoryLotLineId, stockInLineId: qrData.stockInLineId } ); } catch (error) { console.error("Error during analyzeQrCode flow:", error); setQrScanError(true); setQrScanSuccess(false); return; } }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); // ✅ Update the outside QR scanning effect to use enhanced processing useEffect(() => { if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { return; } const latestQr = qrValues[qrValues.length - 1]; if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { console.log("QR code already processed, skipping..."); return; } if (latestQr && latestQr !== lastProcessedQr) { console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); setLastProcessedQr(latestQr); setProcessedQrCodes(prev => new Set(prev).add(latestQr)); processOutsideQrCode(latestQr); } }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode]); // ✅ Only fetch existing data when session is ready, no auto-assignment useEffect(() => { if (session && currentUserId && !initializationRef.current) { console.log("✅ Session loaded, initializing pick order..."); initializationRef.current = true; // ✅ Only fetch existing data, no auto-assignment fetchAllCombinedLotData(); } }, [session, currentUserId, fetchAllCombinedLotData]); // ✅ Add event listener for manual assignment useEffect(() => { const handlePickOrderAssigned = () => { console.log("🔄 Pick order assigned event received, refreshing data..."); fetchAllCombinedLotData(); }; window.addEventListener('pickOrderAssigned', handlePickOrderAssigned); return () => { window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned); }; }, [fetchAllCombinedLotData]); 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 try { const stockOutLineUpdate = await updateStockOutLineStatus({ id: selectedLotForQr.stockOutLineId, status: 'checked', qty: selectedLotForQr.stockOutLineQty || 0 }); console.log("Stock out line updated successfully!"); setQrScanSuccess(true); setQrScanError(false); // 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 fetchAllCombinedLotData(); } catch (error) { console.error("Error creating stock out line:", error); } } }, [selectedLotForQr, fetchAllCombinedLotData]); 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 [completionStatus, setCompletionStatus] = useState(null); const checkAndAutoAssignNext = useCallback(async () => { if (!currentUserId) return; try { const completionResponse = await checkPickOrderCompletion(currentUserId); if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) { console.log("Found completed pick orders, auto-assigning next..."); // ✅ 移除前端的自动分配逻辑,因为后端已经处理了 // await handleAutoAssignAndRelease(); // 删除这个函数 } } catch (error) { console.error("Error checking pick order completion:", error); } }, [currentUserId]); // ✅ Handle submit pick quantity const handleSubmitPickQty = useCallback(async (lot: any) => { const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const newQty = pickQtyData[lotKey] || 0; if (!lot.stockOutLineId) { console.error("No stock out line found for this lot"); return; } try { // ✅ FIXED: Calculate cumulative quantity correctly const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + newQty; // ✅ FIXED: Determine status based on cumulative quantity vs required quantity let newStatus = 'partially_completed'; if (cumulativeQty >= lot.requiredQty) { newStatus = 'completed'; } else if (cumulativeQty > 0) { newStatus = 'partially_completed'; } else { newStatus = 'checked'; // QR scanned but no quantity submitted yet } console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`); console.log(`Lot: ${lot.lotNo}`); console.log(`Required Qty: ${lot.requiredQty}`); console.log(`Current Actual Pick Qty: ${currentActualPickQty}`); console.log(`New Submitted Qty: ${newQty}`); console.log(`Cumulative Qty: ${cumulativeQty}`); console.log(`New Status: ${newStatus}`); console.log(`=====================================`); await updateStockOutLineStatus({ id: lot.stockOutLineId, status: newStatus, qty: cumulativeQty // ✅ Use cumulative quantity }); if (newQty > 0) { await updateInventoryLotLineQuantities({ inventoryLotLineId: lot.lotId, qty: newQty, status: 'available', operation: 'pick' }); } // ✅ Check if pick order is completed when lot status becomes 'completed' if (newStatus === 'completed' && lot.pickOrderConsoCode) { console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); try { const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); console.log(`✅ Pick order completion check result:`, completionResponse); if (completionResponse.code === "SUCCESS") { console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); } else if (completionResponse.message === "not completed") { console.log(`⏳ Pick order not completed yet, more lines remaining`); } else { console.error(`❌ Error checking completion: ${completionResponse.message}`); } } catch (error) { console.error("Error checking pick order completion:", error); } } await fetchAllCombinedLotData(); console.log("Pick quantity submitted successfully!"); setTimeout(() => { checkAndAutoAssignNext(); }, 1000); } catch (error) { console.error("Error submitting pick quantity:", error); } }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]); // ✅ Handle reject lot const handleRejectLot = useCallback(async (lot: any) => { if (!lot.stockOutLineId) { console.error("No stock out line found for this lot"); return; } try { await updateStockOutLineStatus({ id: lot.stockOutLineId, status: 'rejected', qty: 0 }); await fetchAllCombinedLotData(); console.log("Lot rejected successfully!"); setTimeout(() => { checkAndAutoAssignNext(); }, 1000); } catch (error) { console.error("Error rejecting lot:", error); } }, [fetchAllCombinedLotData, checkAndAutoAssignNext]); // ✅ Handle pick execution form const handlePickExecutionForm = useCallback((lot: any) => { console.log("=== Pick Execution Form ==="); console.log("Lot data:", lot); 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); const result = await recordPickExecutionIssue(data); 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 fetchAllCombinedLotData(); } catch (error) { console.error("Error submitting pick execution form:", error); } }, [fetchAllCombinedLotData]); // ✅ 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 // Remove the sorting logic and just do pagination const paginatedData = useMemo(() => { const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; return combinedLotData.slice(startIndex, endIndex); // ✅ No sorting needed }, [combinedLotData, paginationController]); const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => { if (!lot.stockOutLineId) { console.error("No stock out line found for this lot"); return; } try { // ✅ FIXED: Calculate cumulative quantity correctly const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + submitQty; // ✅ FIXED: Determine status based on cumulative quantity vs required quantity let newStatus = 'partially_completed'; if (cumulativeQty >= lot.requiredQty) { newStatus = 'completed'; } else if (cumulativeQty > 0) { newStatus = 'partially_completed'; } else { newStatus = 'checked'; // QR scanned but no quantity submitted yet } console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`); console.log(`Lot: ${lot.lotNo}`); console.log(`Required Qty: ${lot.requiredQty}`); console.log(`Current Actual Pick Qty: ${currentActualPickQty}`); console.log(`New Submitted Qty: ${submitQty}`); console.log(`Cumulative Qty: ${cumulativeQty}`); console.log(`New Status: ${newStatus}`); console.log(`=====================================`); await updateStockOutLineStatus({ id: lot.stockOutLineId, status: newStatus, qty: cumulativeQty // ✅ Use cumulative quantity }); if (submitQty > 0) { await updateInventoryLotLineQuantities({ inventoryLotLineId: lot.lotId, qty: submitQty, status: 'available', operation: 'pick' }); } // ✅ Check if pick order is completed when lot status becomes 'completed' if (newStatus === 'completed' && lot.pickOrderConsoCode) { console.log(`✅ Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`); try { const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); console.log(`✅ Pick order completion check result:`, completionResponse); if (completionResponse.code === "SUCCESS") { console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); } else if (completionResponse.message === "not completed") { console.log(`⏳ Pick order not completed yet, more lines remaining`); } else { console.error(`❌ Error checking completion: ${completionResponse.message}`); } } catch (error) { console.error("Error checking pick order completion:", error); } } await fetchAllCombinedLotData(); console.log("Pick quantity submitted successfully!"); setTimeout(() => { checkAndAutoAssignNext(); }, 1000); } catch (error) { console.error("Error submitting pick quantity:", error); } }, [fetchAllCombinedLotData, checkAndAutoAssignNext]); // ✅ Add these functions after line 395 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]); 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 ( {/* DO Header */} {fgPickOrdersLoading ? ( ) : ( fgPickOrders.length > 0 && ( {t("Shop Name")}: {fgPickOrders[0].shopName || '-'} {t("Pick Order Code")}:{fgPickOrders[0].pickOrderCode || '-'} {t("Store ID")}: {fgPickOrders[0].storeId || '-'} {t("Ticket No.")}: {fgPickOrders[0].ticketNo || '-'} {t("Departure Time")}: {fgPickOrders[0].DepartureTime || '-'} ) )} {/* Combined Lot Table */} {t("All Pick Order Lots")} {!isManualScanning ? ( ) : ( )} {isManualScanning && ( {t("Scanning...")} )} {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#")} {/* {t("Target Date")} */} {/* {t("Lot Location")} */} {t("Lot Required Pick Qty")} {/* {t("Original Available Qty")} */} {t("Scan Result")} {t("Submit Required Pick Qty")} {/* {t("Remaining Available Qty")} */} {/* {t("Action")} */} {paginatedData.length === 0 ? ( {t("No data available")} ) : ( paginatedData.map((lot, index) => ( {index + 1} {lot.routerRoute || '-'} {lot.itemCode} {lot.itemName+'('+lot.stockUnit+')'} {lot.lotNo} {/* {lot.pickOrderTargetDate} */} {/* {lot.location} */} {/* {calculateRemainingRequiredQty(lot).toLocaleString()} */} {(() => { const inQty = lot.inQty || 0; const requiredQty = lot.requiredQty || 0; const actualPickQty = lot.actualPickQty || 0; const outQty = lot.outQty || 0; const result = requiredQty; return result.toLocaleString()+'('+lot.uomShortDesc+')'; })()} {lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( ) : null} )) )}
{/* ✅ Status Messages Display - Move here, outside the table */} {/* {paginatedData.length > 0 && ( {paginatedData.map((lot, index) => ( {t("Lot")} {lot.lotNo}: {getStatusMessage(lot)} ))} )} */} `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` } />
{/* ✅ QR Code Modal */} { setQrModalOpen(false); setSelectedLotForQr(null); stopScan(); resetScan(); }} lot={selectedLotForQr} combinedLotData={combinedLotData} // ✅ Add this prop onQrCodeSubmit={handleQrCodeSubmitFromModal} /> {/* ✅ Lot Confirmation Modal */} {lotConfirmationOpen && expectedLotData && scannedLotData && ( { setLotConfirmationOpen(false); setExpectedLotData(null); setScannedLotData(null); }} onConfirm={handleLotConfirmation} expectedLot={expectedLotData} scannedLot={scannedLotData} isLoading={isConfirmingLot} /> )} {/* ✅ Good 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 PickExecution;