From a249363da46729b88657cbfa58206a02867510fd Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 17 Mar 2026 23:32:01 +0800 Subject: [PATCH] updated --- .../GoodPickExecutionForm.tsx | 1 + .../GoodPickExecutiondetail.tsx | 30 ++++-- .../Jodetail/JobPickExecutionForm.tsx | 1 + .../Jodetail/newJobPickExecution.tsx | 98 +++++++++++-------- src/i18n/zh/pickOrder.json | 1 + 5 files changed, 84 insertions(+), 47 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 4ef02b4..a3abddf 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -140,6 +140,7 @@ const PickExecutionForm: React.FC = ({ itemDescription: selectedPickOrderLine.itemName, lotId: selectedLot.lotId, lotNo: selectedLot.lotNo, + stockOutLineId: selectedLot.stockOutLineId, storeLocation: selectedLot.location, requiredQty: selectedLot.requiredQty, actualPickQty: selectedLot.actualPickQty || 0, diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 5e12d87..b912101 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -511,6 +511,8 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const [combinedLotData, setCombinedLotData] = useState([]); const [combinedDataLoading, setCombinedDataLoading] = useState(false); const [originalCombinedData, setOriginalCombinedData] = useState([]); + // issue form 里填的 actualPickQty(用于 batch submit 只提交实际拣到数量,而不是补拣到 required) + const [issuePickedQtyBySolId, setIssuePickedQtyBySolId] = useState>({}); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); @@ -2246,6 +2248,19 @@ useEffect(() => { if (result && result.code === "SUCCESS") { console.log(" Pick execution issue recorded successfully"); + // 关键:issue form 只记录问题,不会更新 SOL.qty + // 但 batch submit 需要知道“实际拣到多少”,否则会按 requiredQty 补拣到满 + const solId = Number(issueData.stockOutLineId || issueData.stockOutLineId === 0 ? issueData.stockOutLineId : data?.stockOutLineId); + if (solId > 0) { + const picked = Number(issueData.actualPickQty || 0); + setIssuePickedQtyBySolId(prev => ({ ...prev, [solId]: picked })); + setCombinedLotData(prev => prev.map(lot => { + if (Number(lot.stockOutLineId) === solId) { + return { ...lot, actualPickQty: picked, stockOutLineQty: picked }; + } + return lot; + })); + } } else { console.error(" Failed to record pick execution issue:", result); } @@ -2265,7 +2280,7 @@ useEffect(() => { } catch (error) { console.error("Error submitting pick execution form:", error); } - }, [fetchAllCombinedLotData]); + }, [fetchAllCombinedLotData, session]); // Calculate remaining required quantity const calculateRemainingRequiredQty = useCallback((lot: any) => { @@ -2744,13 +2759,16 @@ const handleSubmitAllScanned = useCallback(async () => { const requiredQty = Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0); - // 2. 当前已经拣到的数量(数据库里的 qty) - const currentActualPickQty = Number(lot.actualPickQty || 0); + // 2. 当前已经拣到的数量 + // issue form 不会写回 SOL.qty,所以如果这条 SOL 有 issue,就用 issue form 的 actualPickQty 作为“已拣到数量” + const solId = Number(lot.stockOutLineId) || 0; + const issuePicked = solId > 0 ? issuePickedQtyBySolId[solId] : undefined; + const currentActualPickQty = Number(issuePicked ?? lot.actualPickQty ?? 0); // 🔹 判断是否走“只改状态模式” // 这里先给一个简单条件示例:如果你不想再补拣,只想把当前数量标记完成, // 就让这个条件为 true(后面你可以根据业务加 UI 开关或别的 flag)。 - const onlyComplete = lot.stockOutLineStatus === "partially_completed"; + const onlyComplete = lot.stockOutLineStatus === "partially_completed" || issuePicked !== undefined; // lot.stockOutLineStatus === "partially_completed" && false === true; let targetActual: number; @@ -2836,7 +2854,7 @@ const handleSubmitAllScanned = useCallback(async () => { } finally { setIsSubmittingAll(false); } -}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount]); +}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, currentUserId, onSwitchToRecordTab, onRefreshReleasedOrderCount, issuePickedQtyBySolId]); // Calculate scanned items count // Calculate scanned items count (should match handleSubmitAllScanned filter logic) @@ -3171,7 +3189,7 @@ paginatedData.map((lot, index) => { }} > {lot.lotNo || - t('This lot is not available, please scan another lot.')} + t('Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.')} diff --git a/src/components/Jodetail/JobPickExecutionForm.tsx b/src/components/Jodetail/JobPickExecutionForm.tsx index 80b18e2..c3b2e04 100644 --- a/src/components/Jodetail/JobPickExecutionForm.tsx +++ b/src/components/Jodetail/JobPickExecutionForm.tsx @@ -161,6 +161,7 @@ useEffect(() => { itemDescription: selectedPickOrderLine.itemName, lotId: selectedLot.lotId, lotNo: selectedLot.lotNo, + stockOutLineId: selectedLot.stockOutLineId, storeLocation: selectedLot.location, requiredQty: selectedLot.requiredQty, actualPickQty: initialVerifiedQty, diff --git a/src/components/Jodetail/newJobPickExecution.tsx b/src/components/Jodetail/newJobPickExecution.tsx index adfdd97..42644c0 100644 --- a/src/components/Jodetail/newJobPickExecution.tsx +++ b/src/components/Jodetail/newJobPickExecution.tsx @@ -457,6 +457,8 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { const [jobOrderData, setJobOrderData] = useState(null); const [pickQtyData, setPickQtyData] = useState>({}); const [searchQuery, setSearchQuery] = useState>({}); + // issue form 里填的 actualPickQty(用于 submit/batch submit 不补拣到 required) + const [issuePickedQtyBySolId, setIssuePickedQtyBySolId] = useState>({}); const [paginationController, setPaginationController] = useState({ pageNum: 0, @@ -631,8 +633,24 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { return isNaN(n) ? 0 : n; }; const combinedLotData = useMemo(() => { - return getAllLotsFromHierarchical(jobOrderData); - }, [jobOrderData, getAllLotsFromHierarchical]); + const lots = getAllLotsFromHierarchical(jobOrderData); + // 前端覆盖:issue form/submit0 不会立刻改写后端 qty 时,用本地缓存让 UI 与 batch submit 计算一致 + return lots.map((lot: any) => { + const solId = Number(lot.stockOutLineId) || 0; + if (solId > 0 && Object.prototype.hasOwnProperty.call(issuePickedQtyBySolId, solId)) { + const picked = Number(issuePickedQtyBySolId[solId] ?? 0); + const status = String(lot.stockOutLineStatus || '').toLowerCase(); + const isEnded = status === 'completed' || status === 'rejected'; + return { + ...lot, + actualPickQty: picked, + stockOutLineQty: picked, + stockOutLineStatus: isEnded ? lot.stockOutLineStatus : 'checked', + }; + } + return lot; + }); + }, [jobOrderData, getAllLotsFromHierarchical, issuePickedQtyBySolId]); const originalCombinedData = useMemo(() => { return getAllLotsFromHierarchical(jobOrderData); @@ -1786,16 +1804,18 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { // Continue even if handler update fails } } - // Special case: If submitQty is 0 and all values are 0, mark as completed with qty: 0 + // ✅ 两步完成(与 DO 对齐): + // 1) Skip/Submit0 只把 SOL 标记为 checked(不直接 completed) + // 2) 之后由 batch submit 把 SOL 推到 completed(允许 0) if (submitQty === 0) { console.log(`=== SUBMITTING ALL ZEROS CASE ===`); console.log(`Lot: ${lot.lotNo}`); console.log(`Stock Out Line ID: ${lot.stockOutLineId}`); - console.log(`Setting status to 'completed' with qty: 0`); + console.log(`Setting status to 'checked' with qty: 0 (will complete in batch submit)`); const updateResult = await updateStockOutLineStatus({ id: lot.stockOutLineId, - status: 'completed', + status: 'checked', qty: 0 }); @@ -1813,34 +1833,15 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { } - // Check if pick order is completed - if (lot.pickOrderConsoCode) { - console.log(` Lot ${lot.lotNo} completed (all zeros), 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!`); - setTimeout(() => { - if (onBackToList) { - onBackToList(); - } - }, 1500); - } 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); - } + // 记录该 SOL 的“目标实际拣货量=0”,让 batch submit 走 onlyComplete(不补拣到 required) + const solId = Number(lot.stockOutLineId) || 0; + if (solId > 0) { + setIssuePickedQtyBySolId(prev => ({ ...prev, [solId]: 0 })); } const pickOrderId = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; await fetchJobOrderData(pickOrderId); - console.log("All zeros submission completed successfully!"); + console.log("All zeros submission marked as checked successfully (waiting for batch submit)."); setTimeout(() => { checkAndAutoAssignNext(); @@ -1965,24 +1966,32 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { // ✅ 转换为 batchSubmitList 所需的格式 const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { - const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty || 0; - const currentActualPickQty = lot.actualPickQty || 0; - const cumulativeQty = currentActualPickQty + submitQty; - - let newStatus = 'partially_completed'; - if (cumulativeQty >= (lot.requiredQty || 0)) { + const requiredQty = Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0); + const solId = Number(lot.stockOutLineId) || 0; + const issuePicked = solId > 0 ? issuePickedQtyBySolId[solId] : undefined; + const currentActualPickQty = Number(issuePicked ?? lot.actualPickQty ?? 0); + const onlyComplete = lot.stockOutLineStatus === 'partially_completed' || issuePicked !== undefined; + + let targetActual: number; + let newStatus: string; + if (onlyComplete) { + targetActual = currentActualPickQty; newStatus = 'completed'; + } else { + const remainingQty = Math.max(0, requiredQty - currentActualPickQty); + targetActual = currentActualPickQty + remainingQty; + newStatus = (requiredQty > 0 && targetActual >= requiredQty) ? 'completed' : 'partially_completed'; } return { stockOutLineId: Number(lot.stockOutLineId) || 0, pickOrderLineId: Number(lot.pickOrderLineId), inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, - requiredQty: Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0), - actualPickQty: Number(cumulativeQty), + requiredQty, + actualPickQty: Number(targetActual), stockOutLineStatus: newStatus, pickOrderConsoCode: String(lot.pickOrderConsoCode || ''), - noLot: Boolean(false) // Job Order 通常都有 lot + noLot: Boolean(lot.noLot === true) }; }); @@ -2019,9 +2028,11 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { } finally { setIsSubmittingAll(false); } - }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext, currentUserId, filterArgs?.pickOrderId, onBackToList, updateHandledBy]) + }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext, currentUserId, filterArgs?.pickOrderId, onBackToList, updateHandledBy, issuePickedQtyBySolId]) const scannedItemsCount = useMemo(() => { - return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length; + return combinedLotData.filter(lot => + lot.stockOutLineStatus === 'checked' || lot.stockOutLineStatus === 'partially_completed' + ).length; }, [combinedLotData]); // 先定义 filteredByFloor 和 availableFloors @@ -2114,6 +2125,11 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { if (result && result.code === "SUCCESS") { console.log(" Pick execution issue recorded successfully"); + const solId = Number(issueData.stockOutLineId || data?.stockOutLineId); + if (solId > 0) { + const picked = Number(issueData.actualPickQty || 0); + setIssuePickedQtyBySolId(prev => ({ ...prev, [solId]: picked })); + } } else { console.error("❌ Failed to record pick execution issue:", result); } @@ -2126,7 +2142,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { } catch (error) { console.error("Error submitting pick execution form:", error); } - }, [fetchJobOrderData]); + }, [fetchJobOrderData, currentUserId, selectedLotForExecutionForm, updateHandledBy, filterArgs?.pickOrderId]); // Calculate remaining required quantity const calculateRemainingRequiredQty = useCallback((lot: any) => { diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index ceb43c2..cb6d98c 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -438,6 +438,7 @@ "Tomorrow": "翌日", "No Stock Available": "沒有庫存可用", "This lot is not available, please scan another lot.": "此批號不可用,請掃描其他批號。", + "Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.": "請檢查周圍是否有 QR 碼,可能有剛剛入庫或轉移入庫 QR 碼。", "Day After Tomorrow": "後日", "Select Date": "請選擇日期", "Search by Shop": "搜尋商店",