diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index a4c932c..e3685d8 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -195,7 +195,7 @@ export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => { }); export const fetchCompletedJobOrderPickOrders = cache(async (userId: number) => { return serverFetchJson( - `${BASE_API_URL}/jo/completed-job-order-pick-orders${userId}`, + `${BASE_API_URL}/jo/completed-job-order-pick-orders/${userId}`, { method: "GET", next: { tags: ["jo-completed"] }, diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index 9a5ae19..eb59ec0 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -328,7 +328,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { {lot.actualPickQty} @@ -394,7 +394,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { >(new Set()); const [lastProcessedQr, setLastProcessedQr] = useState(''); const [isRefreshingData, setIsRefreshingData] = useState(false); - + const [isSubmittingAll, setIsSubmittingAll] = useState(false); const fetchFgPickOrdersData = useCallback(async () => { if (!currentUserId) return; @@ -1360,6 +1360,108 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe stopScan(); resetScan(); }, [stopScan, resetScan]); + const handleSubmitAllScanned = useCallback(async () => { + const scannedLots = combinedLotData.filter(lot => + lot.stockOutLineStatus === 'checked' // 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; + const currentActualPickQty = lot.actualPickQty || 0; + const cumulativeQty = currentActualPickQty + submitQty; + + let newStatus = 'partially_completed'; + if (cumulativeQty >= lot.requiredQty) { + newStatus = 'completed'; + } + + console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`); + + // Update stock out line + await updateStockOutLineStatus({ + id: lot.stockOutLineId, + status: newStatus, + qty: cumulativeQty + }); + + // Update inventory + if (submitQty > 0) { + await updateInventoryLotLineQuantities({ + inventoryLotLineId: lot.lotId, + qty: submitQty, + status: 'available', + operation: 'pick' + }); + } + + // Check if pick order is completed + if (newStatus === 'completed' && lot.pickOrderConsoCode) { + await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); + } + + return { success: true, lotNo: lot.lotNo }; + }); + + // ✅ 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 fetchAllCombinedLotData(); + + if (successCount > 0) { + setQrScanSuccess(true); + setTimeout(() => { + setQrScanSuccess(false); + checkAndAutoAssignNext(); + }, 2000); + } + + } catch (error) { + console.error("Error submitting all scanned items:", error); + setQrScanError(true); + } finally { + setIsSubmittingAll(false); + } + }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext]); + + // ✅ Calculate scanned items count + const scannedItemsCount = useMemo(() => { + return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length; + }, [combinedLotData]); + + // ✅ ADD THIS: Auto-stop scan when no data available + 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("🧹 Pick execution component unmounting, stopping QR scanner..."); + stopScan(); + resetScan(); + } + }; + }, [isManualScanning, stopScan, resetScan]); + const getStatusMessage = useCallback((lot: any) => { switch (lot.stockOutLineStatus?.toLowerCase()) { case 'pending': @@ -1445,14 +1547,23 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe )} - {isManualScanning && ( - - - - {t("Scanning...")} - - - )} + {/* ✅ ADD THIS: Submit All Scanned Button */} + diff --git a/src/components/Jodetail/JobPickExecution.tsx b/src/components/Jodetail/JobPickExecution.tsx index e14271b..a1917c3 100644 --- a/src/components/Jodetail/JobPickExecution.tsx +++ b/src/components/Jodetail/JobPickExecution.tsx @@ -356,6 +356,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const formProps = useForm(); const errors = formProps.formState.errors; + const [isSubmittingAll, setIsSubmittingAll] = useState(false); // ✅ Add QR modal states const [qrModalOpen, setQrModalOpen] = useState(false); @@ -1223,6 +1224,87 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { console.error("Error submitting pick quantity:", error); } }, [fetchJobOrderData, checkAndAutoAssignNext]); + const handleSubmitAllScanned = useCallback(async () => { + const scannedLots = combinedLotData.filter(lot => + lot.stockOutLineStatus === 'checked' // 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; + const currentActualPickQty = lot.actualPickQty || 0; + const cumulativeQty = currentActualPickQty + submitQty; + + let newStatus = 'partially_completed'; + if (cumulativeQty >= lot.requiredQty) { + newStatus = 'completed'; + } + + console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`); + + // Update stock out line + await updateStockOutLineStatus({ + id: lot.stockOutLineId, + status: newStatus, + qty: cumulativeQty + }); + + // Update inventory + if (submitQty > 0) { + await updateInventoryLotLineQuantities({ + inventoryLotLineId: lot.lotId, + qty: submitQty, + status: 'available', + operation: 'pick' + }); + } + + // Check if pick order is completed + if (newStatus === 'completed' && lot.pickOrderConsoCode) { + await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); + } + + return { success: true, lotNo: lot.lotNo }; + }); + + // ✅ 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); + checkAndAutoAssignNext(); + }, 2000); + } + + } catch (error) { + console.error("Error submitting all scanned items:", error); + setQrScanError(true); + } finally { + setIsSubmittingAll(false); + } + }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext]); + + // ✅ Calculate scanned items count + const scannedItemsCount = useMemo(() => { + return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length; + }, [combinedLotData]); // ✅ Handle reject lot const handleRejectLot = useCallback(async (lot: any) => { if (!lot.stockOutLineId) { @@ -1509,15 +1591,24 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { {t("Stop QR Scan")} )} - - {isManualScanning && ( - - - - {t("Scanning...")} - - - )} + {/* ✅ ADD THIS: Submit All Scanned Button */} + + diff --git a/src/components/Jodetail/JobPickExecutionsecondscan.tsx b/src/components/Jodetail/JobPickExecutionsecondscan.tsx index 47a74bc..fd46db4 100644 --- a/src/components/Jodetail/JobPickExecutionsecondscan.tsx +++ b/src/components/Jodetail/JobPickExecutionsecondscan.tsx @@ -332,7 +332,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const [pickQtyData, setPickQtyData] = useState>({}); const [searchQuery, setSearchQuery] = useState>({}); - + const [isSubmittingAll, setIsSubmittingAll] = useState(false); const [paginationController, setPaginationController] = useState({ pageNum: 0, pageSize: 10, @@ -532,6 +532,66 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { setCombinedDataLoading(false); } }, [currentUserId]); + const handleSubmitAllScanned = useCallback(async () => { + const scannedLots = combinedLotData.filter(lot => + lot.secondQrScanStatus === '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.secondQrScanStatus === 'scanned').length; + }, [combinedLotData]); // ✅ 修改:初始化时加载数据 useEffect(() => { @@ -983,6 +1043,14 @@ const paginatedData = useMemo(() => { 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) @@ -1020,7 +1088,7 @@ const paginatedData = useMemo(() => { - {t("Job Order")}: {jobOrderData.pickOrder?.jobOrder?.name || '-'} + {t("Job Order")}: {jobOrderData.pickOrder?.jobOrder?.code || '-'} {t("Pick Order Code")}: {jobOrderData.pickOrder?.code || '-'} @@ -1063,14 +1131,23 @@ const paginatedData = useMemo(() => { )} - {isManualScanning && ( - - - - {t("Scanning...")} - - - )} + {/* ✅ ADD THIS: Submit All Scanned Button */} + diff --git a/src/components/Jodetail/completeJobOrderRecord.tsx b/src/components/Jodetail/completeJobOrderRecord.tsx index 23f327a..3742a0f 100644 --- a/src/components/Jodetail/completeJobOrderRecord.tsx +++ b/src/components/Jodetail/completeJobOrderRecord.tsx @@ -568,7 +568,7 @@ const CompleteJobOrderRecord: React.FC = ({ filterArgs }) => {