| @@ -601,77 +601,64 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => { | |||||
| }, [currentPickOrderId, currentUserId, handleUnassign, onBack]); | }, [currentPickOrderId, currentUserId, handleUnassign, onBack]); | ||||
| const handleSubmitAllScanned = useCallback(async () => { | const handleSubmitAllScanned = useCallback(async () => { | ||||
| const scannedLots = combinedLotData.filter(lot => | |||||
| lot.matchStatus === 'scanned'|| | |||||
| lot.stockOutLineStatus === 'completed' | |||||
| // A) 先算每个 item 的 actual pick 总量(仅给 Confirm All 用) | |||||
| const itemTotalActualPickQtyMap = combinedLotData.reduce<Record<string, number>>((acc, lot) => { | |||||
| const itemKey = `${lot.pickOrderId}-${lot.pickOrderLineId}-${lot.itemId}`; // 或 getItemKey(lot) | |||||
| const qty = Number(lot.actualPickQty ?? 0); | |||||
| acc[itemKey] = (acc[itemKey] ?? 0) + qty; | |||||
| return acc; | |||||
| }, {}); | |||||
| // B) 只选 pending(不要 completed) | |||||
| const pendingLots = combinedLotData.filter( | |||||
| (lot) => String(lot.matchStatus || "").toLowerCase() === "pending" | |||||
| ); | ); | ||||
| if (scannedLots.length === 0) { | |||||
| console.log("No scanned items to submit"); | |||||
| // C) 同一个 item 只提交一次(避免重复) | |||||
| const uniqueLots = Array.from( | |||||
| new Map( | |||||
| pendingLots.map((lot) => [ | |||||
| `${lot.pickOrderId}-${lot.pickOrderLineId}-${lot.itemId}`, | |||||
| lot, | |||||
| ]) | |||||
| ).values() | |||||
| ); | |||||
| if (uniqueLots.length === 0) { | |||||
| console.log("No pending items to submit"); | |||||
| return; | return; | ||||
| } | } | ||||
| setIsSubmittingAll(true); | setIsSubmittingAll(true); | ||||
| console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); | |||||
| try { | 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 | |||||
| } | |||||
| ); | |||||
| const submitPromises = uniqueLots.map(async (lot) => { | |||||
| const itemKey = `${lot.pickOrderId}-${lot.pickOrderLineId}-${lot.itemId}`; | |||||
| const submitQty = Number(itemTotalActualPickQtyMap[itemKey] ?? 0); // Confirm All 用 actual total | |||||
| if (submitQty <= 0) { | |||||
| return { success: false, itemCode: lot.itemCode }; | |||||
| } | |||||
| 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 }; | return { success: result.code === "SUCCESS", itemCode: lot.itemCode }; | ||||
| }); | }); | ||||
| const results = await Promise.all(submitPromises); | const results = await Promise.all(submitPromises); | ||||
| const successCount = results.filter(r => r.success).length; | |||||
| console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); | |||||
| const successCount = results.filter((r) => r.success).length; | |||||
| console.log(`Batch submit completed: ${successCount}/${uniqueLots.length}`); | |||||
| await fetchJobOrderData(); | 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 { | } finally { | ||||
| setIsSubmittingAll(false); | setIsSubmittingAll(false); | ||||
| } | } | ||||
| }, [combinedLotData, fetchJobOrderData, currentPickOrderId, handleUnassign, onBack]); | |||||
| }, [combinedLotData, currentUserId, fetchJobOrderData]); | |||||
| const scannedItemsCount = useMemo(() => { | const scannedItemsCount = useMemo(() => { | ||||
| return combinedLotData.filter(lot => lot.matchStatus === 'scanned').length; | return combinedLotData.filter(lot => lot.matchStatus === 'scanned').length; | ||||
| }, [combinedLotData]); | }, [combinedLotData]); | ||||