"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"; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; // 修改:使用 Job Order API import { fetchCompletedJobOrderPickOrders, updateSecondQrScanStatus, submitSecondScanQuantity, recordSecondScanIssue, unAssignJobOrderPickOrder, fetchJobOrderLotsHierarchicalByPickOrderId } 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 { BASE_API_URL } from "@/config/api"; interface Props { filterArgs: Record; onBack?: () => void; // 添加返回回调 } // 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 }; // 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, onBack }) => { const { t } = useTranslation("jo"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; const authTokenRef = useRef(null); // 修改:使用 Job Order 数据结构 const [jobOrderData, setJobOrderData] = useState(null); 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 unassignExecutedRef = useRef(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 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); const [currentPickOrderId, setCurrentPickOrderId] = useState(null); // 添加:unassign 函数 const handleUnassign = useCallback(async (pickOrderId: number | null) => { if (!pickOrderId || !currentUserId) { console.log("No pickOrderId or userId to unassign"); return; } try { console.log(`🔄 Unassigning pick order: ${pickOrderId} for user: ${currentUserId}`); await unAssignJobOrderPickOrder(pickOrderId); console.log(`✅ Successfully unassigned pick order: ${pickOrderId}`); } catch (error) { console.error(`❌ Error unassigning pick order ${pickOrderId}:`, error); // 即使 unassign 失败,也继续执行,不阻塞用户操作 } }, [currentUserId]); useEffect(() => { if (session && (session as any).accessToken) { authTokenRef.current = (session as any).accessToken; } }, [session]); const handleUnassignSync = useCallback((pickOrderId: number | null) => { if (!pickOrderId || !currentUserId) { return; } const url = `${BASE_API_URL}/jo/unassign-job-order-pick-order/${pickOrderId}`; const headers: HeadersInit = { 'Content-Type': 'application/json', }; if (authTokenRef.current) { headers['Authorization'] = `Bearer ${authTokenRef.current}`; } fetch(url, { method: 'POST', headers: headers, keepalive: true, body: JSON.stringify({}) }).catch((error) => { console.error(`❌ Error in sync unassign:`, error); // 备用方案:sendBeacon(但无法发送自定义 headers) if (navigator.sendBeacon) { const blob = new Blob([JSON.stringify({})], { type: 'application/json' }); navigator.sendBeacon(url, blob); } }); }, [currentUserId]); // 修改:使用 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([]); setCurrentPickOrderId(null); 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); setJobOrderData(jobOrderData); // 保存 pickOrderId if (jobOrderData?.pickOrder?.id) { setCurrentPickOrderId(jobOrderData.pickOrder.id); } // 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, handler: lot.handler, }); }); } }); } 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: 0 } })); } catch (error: any) { console.error("❌ Error fetching job order data:", error); // 检查是否是 token 错误或认证错误 const isAuthError = error?.status === 401 || error?.status === 403 || error?.message?.toLowerCase().includes('unauthorized') || error?.message?.toLowerCase().includes('token'); if (isAuthError) { console.log("🔒 Authentication error detected, unassigning pick order"); // Token 错误时也要 unassign await handleUnassign(currentPickOrderId); } setJobOrderData(null); setCombinedLotData([]); setOriginalCombinedData([]); setCurrentPickOrderId(null); // 如果加载失败,禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, tabIndex: 0 } })); } finally { setCombinedDataLoading(false); } }, [currentUserId, currentPickOrderId, handleUnassign]); useEffect(() => { return () => { // Cleanup QR scanner if (isManualScanning) { console.log("🧹 Second scan component unmounting, stopping QR scanner..."); stopScan(); resetScan(); } }; }, [isManualScanning, stopScan, resetScan]); // 添加:处理返回按钮 const handleBack = useCallback(async () => { if (currentPickOrderId && currentUserId) { console.log("🔄 Back button clicked, unassigning pick order:", currentPickOrderId); await handleUnassign(currentPickOrderId); } if (onBack) { onBack(); } }, [currentPickOrderId, currentUserId, handleUnassign, onBack]); const handleSubmitAllScanned = useCallback(async () => { const scannedLots = combinedLotData.filter(lot => lot.matchStatus === 'scanned'|| lot.stockOutLineStatus === 'completed' ); if (scannedLots.length === 0) { console.log("No scanned items to submit"); return; } setIsSubmittingAll(true); console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); try { 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, userId: currentUserId !!, isMissing: false, isBad: false, reason: undefined } ); return { success: result.code === "SUCCESS", itemCode: lot.itemCode }; }); const results = await Promise.all(submitPromises); const successCount = results.filter(r => r.success).length; console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); await fetchJobOrderData(); if (successCount > 0) { setQrScanSuccess(true); setTimeout(() => { setQrScanSuccess(false); // 添加:提交成功后返回到列表 if (onBack) { onBack(); } }, 2000); } } catch (error: any) { console.error("Error submitting all scanned items:", error); const isAuthError = error?.status === 401 || error?.status === 403 || error?.message?.toLowerCase().includes('unauthorized') || error?.message?.toLowerCase().includes('token'); if (isAuthError) { console.log("🔒 Authentication error in submit, unassigning pick order"); await handleUnassign(currentPickOrderId); } setQrScanError(true); } finally { setIsSubmittingAll(false); } }, [combinedLotData, fetchJobOrderData, currentPickOrderId, handleUnassign, onBack]); const scannedItemsCount = useMemo(() => { return combinedLotData.filter(lot => lot.matchStatus === 'scanned').length; }, [combinedLotData]); // 修改:初始化时加载数据(移除 loadUnassignedOrders) useEffect(() => { if (session && currentUserId && !initializationRef.current) { console.log(" Session loaded, initializing job order..."); initializationRef.current = true; fetchJobOrderData(); } }, [session, currentUserId, fetchJobOrderData]); const handleQrCodeSubmit = useCallback(async (lotNo: string) => { console.log(` Processing Second QR Code for lot: ${lotNo}`); 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) { const itemKey = `${matchingLot.pickOrderId}_${matchingLot.itemId}`; if (processedQrCodes.has(itemKey)) { console.log(`⏭️ Item ${matchingLot.itemId} already processed, skipping...`); continue; } const result = await updateSecondQrScanStatus( matchingLot.pickOrderId, matchingLot.itemId, currentUserId !!, matchingLot.requiredQty || 1 ); console.log("result", result); console.log("currentUserId", currentUserId); if (result.code === "SUCCESS") { successCount++; 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); setIsRefreshingData(true); await fetchJobOrderData(); setTimeout(() => { setQrScanSuccess(false); setIsRefreshingData(false); }, 500); } else { setQrScanError(true); setQrScanSuccess(false); } } catch (error: any) { console.error("❌ Error processing second QR code:", error); const isAuthError = error?.status === 401 || error?.status === 403 || error?.message?.toLowerCase().includes('unauthorized') || error?.message?.toLowerCase().includes('token'); if (isAuthError) { console.log("🔒 Authentication error in QR submit, unassigning pick order"); await handleUnassign(currentPickOrderId); } setQrScanError(true); setQrScanSuccess(false); } }, [combinedLotData, fetchJobOrderData, processedQrCodes, currentUserId, currentPickOrderId, handleUnassign]); 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; } setProcessedQrCodes(prev => new Set(prev).add(latestQr)); setLastProcessedQr(latestQr); let lotNo = ''; try { const qrData = JSON.parse(latestQr); if (qrData.stockInLineId && qrData.itemId) { 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; } } catch (error) { lotNo = latestQr.replace(/[{}]/g, ''); } if (lotNo) { console.log(`Outside QR scan detected (direct): ${lotNo}`); handleQrCodeSubmit(lotNo); } }, [qrValues, combinedLotData, handleQrCodeSubmit, processedQrCodes, lastProcessedQr, isManualScanning, isRefreshingData]); useEffect(() => { return () => { 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]); 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; const stockOutLineData: CreateStockOutLine = { consoCode: selectedLotForQr.pickOrderConsoCode, pickOrderLineId: selectedLotForQr.pickOrderLineId, inventoryLotLineId: selectedLotForQr.lotId, qty: 0.0 }; try { setQrModalOpen(false); setSelectedLotForQr(null); const lotKey = `${selectedLotForQr.pickOrderLineId}-${lotId}`; setTimeout(() => { setPickQtyData(prev => ({ ...prev, [lotKey]: requiredQty })); console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); }, 500); await fetchJobOrderData(); } catch (error) { console.error("Error creating stock out line:", error); } } }, [selectedLotForQr, fetchJobOrderData]); 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 handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => { try { const result = await submitSecondScanQuantity( lot.pickOrderId, lot.itemId, { qty: submitQty, isMissing: false, isBad: false, reason: undefined, userId: currentUserId ?? 0 } ); if (result.code === "SUCCESS") { console.log(` Second scan quantity submitted: ${submitQty}`); await fetchJobOrderData(); } else { console.error(`❌ Failed to submit second scan quantity: ${result.message}`); } } catch (error) { console.error("Error submitting second scan quantity:", error); } }, [fetchJobOrderData, currentUserId]); 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); if (!currentUserId) { console.error("❌ No current user ID available"); return; } const result = await recordSecondScanIssue( selectedLotForExecutionForm.pickOrderId, selectedLotForExecutionForm.itemId, { qty: data.actualPickQty, missQty: data.missQty || 0, badItemQty: data.badItemQty || 0, 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]); const calculateRemainingRequiredQty = useCallback((lot: any) => { const requiredQty = lot.requiredQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0; return Math.max(0, requiredQty - stockOutLineQty); }, []); 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, }); }, []); const paginatedData = useMemo(() => { const sortedData = [...combinedLotData].sort((a, b) => { const aIndex = a.routerIndex || 0; const bIndex = b.routerIndex || 0; if (aIndex !== bIndex) { return aIndex - bIndex; } if (a.pickOrderCode !== b.pickOrderCode) { return a.pickOrderCode.localeCompare(b.pickOrderCode); } return (a.lotNo || '').localeCompare(b.lotNo || ''); }); const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; return sortedData.slice(startIndex, endIndex); }, [combinedLotData, paginationController]); 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]); 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' )} > {/* 添加返回按钮 */} {onBack && ( )} {/* 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 ? ( ) : ( )} {qrScanError && !qrScanSuccess && ( {t("QR code does not match any item in current orders.")} )} {qrScanSuccess && ( {t("QR code verified.")} )} */} {t("Index")} {t("Route")} {t("Handler")} {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.handler || '-'} {lot.itemCode} {lot.itemName+'('+lot.uomDesc+')'} {lot.lotNo} {(() => { const requiredQty = lot.requiredQty || 0; return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')'; })()} )) )}
`${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, availableQty: selectedLotForExecutionForm.availableQty || 0, requiredQty: selectedLotForExecutionForm.requiredQty || 0, uomDesc: selectedLotForExecutionForm.uomDesc || '', uomShortDesc: selectedLotForExecutionForm.uomShortDesc || '', pickedQty: selectedLotForExecutionForm.actualPickQty || 0, suggestedList: [], noLotLines: [] }} pickOrderId={selectedLotForExecutionForm.pickOrderId} pickOrderCreateDate={new Date()} /> )}
); }; export default JobPickExecution;