From 8ca9c0a2058d1aaab2b05b02ff477b448b3c9369 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Sat, 20 Sep 2025 15:59:24 +0800 Subject: [PATCH 1/3] update --- .../FinishedGoodSearch/FGPickOrderCard.tsx | 6 +- .../FinishedGoodSearch/FinishedGoodSearch.tsx | 43 +++++---- .../GoodPickExecutiondetail.tsx | 90 +++++++++---------- src/i18n/zh/pickOrder.json | 15 +++- 4 files changed, 79 insertions(+), 75 deletions(-) diff --git a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx index e406fca..f1bfd05 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx @@ -20,7 +20,7 @@ const FGPickOrderCard: React.FC = ({ fgOrder, onQrCodeClick }) => { = ({ fgOrder, onQrCodeClick }) => { = ({ fgOrder, onQrCodeClick }) => { = ({ pickOrders }) => { console.error("Missing user id in session"); return; } - const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId); - console.log("Assign by store result:", res); - // Optionally show toast/refresh list here - }; - // โœ… Manual assignment handler - uses the action function - const handleManualAssign = useCallback(async () => { - if (!currentUserId || isAssigning) return; setIsAssigning(true); try { - console.log("๐ŸŽฏ Manual assignment triggered for user:", currentUserId); - - // โœ… Use the action function instead of direct fetch - const result = await autoAssignAndReleasePickOrder(currentUserId); - console.log("โœ… Manual assignment result:", result); + const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId); + console.log("Assign by store result:", res); - if (result.code === "SUCCESS") { - console.log("โœ… Successfully assigned pick order manually"); - // Trigger refresh of the PickExecution component + // โœ… Handle different response codes + if (res.code === "SUCCESS") { + console.log("โœ… Successfully assigned pick order to store", storeId); + // โœ… Trigger refresh to show newly assigned data window.dispatchEvent(new CustomEvent('pickOrderAssigned')); - } else if (result.code === "EXISTS") { - console.log("โ„น๏ธ User already has active pick orders"); - // Still trigger refresh to show existing orders + } else if (res.code === "USER_BUSY") { + console.warn("โš ๏ธ User already has pick orders in progress:", res.message); + // โœ… Show warning but still refresh to show existing orders + alert(`Warning: ${res.message}`); window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + } else if (res.code === "NO_ORDERS") { + console.log("โ„น๏ธ No available pick orders for store", storeId); + alert(`Info: ${res.message}`); } else { - console.log("โ„น๏ธ No available pick orders or other status:", result.message); + console.log("โ„น๏ธ Assignment result:", res.message); + alert(`Info: ${res.message}`); } } catch (error) { - console.error("โŒ Error in manual assignment:", error); + console.error("โŒ Error assigning by store:", error); + alert("Error occurred during assignment"); } finally { setIsAssigning(false); } - }, [currentUserId, isAssigning]); + }; + // โœ… Manual assignment handler - uses the action function + const handleTabChange = useCallback>( (_e, newValue) => { @@ -316,7 +315,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { borderBottom: '1px solid #e0e0e0' }}> - + diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 36b24c5..aa7142e 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -15,6 +15,7 @@ import { TableHead, TableRow, Paper, + Checkbox, TablePagination, Modal, } from "@mui/material"; @@ -490,25 +491,21 @@ const PickExecution: React.FC = ({ filterArgs }) => { console.log(`โœ… Stock out line already exists for line ${matchingLot.pickOrderLineId}`); existsCount++; } else { - const stockOutLineData: CreateStockOutLine = { - consoCode: matchingLot.pickOrderConsoCode, - pickOrderLineId: matchingLot.pickOrderLineId, - inventoryLotLineId: matchingLot.lotId, - qty: 0.0 - }; + const stockOutLineUpdate = await updateStockOutLineStatus({ + id: selectedLotForQr.stockOutLineId, + status: 'checked', + qty: selectedLotForQr.stockOutLineQty || 0 + }); + console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`,stockOutLineUpdate); - console.log(`Creating stock out line for pick order line ${matchingLot.pickOrderLineId}:`, stockOutLineData); - const result = await createStockOutLine(stockOutLineData); - console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result); - - if (result && result.code === "EXISTS") { + if (stockOutLineUpdate && stockOutLineUpdate.code === "EXISTS") { console.log(`โœ… Stock out line already exists for line ${matchingLot.pickOrderLineId}`); existsCount++; - } else if (result && result.code === "SUCCESS") { + } else if (stockOutLineUpdate && stockOutLineUpdate.code === "SUCCESS") { console.log(`โœ… Stock out line created successfully for line ${matchingLot.pickOrderLineId}`); successCount++; } else { - console.error(`โŒ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result); + console.error(`โŒ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); errorCount++; } } @@ -567,16 +564,15 @@ const PickExecution: React.FC = ({ filterArgs }) => { const lotId = selectedLotForQr.lotId; // Create stock out line - const stockOutLineData: CreateStockOutLine = { - consoCode: selectedLotForQr.pickOrderConsoCode, // โœ… Use pickOrderConsoCode instead of pickOrderCode - pickOrderLineId: selectedLotForQr.pickOrderLineId, - inventoryLotLineId: selectedLotForQr.lotId, - qty: 0.0 - }; + try { - await createStockOutLine(stockOutLineData); - console.log("Stock out line created successfully!"); + const stockOutLineUpdate = await updateStockOutLineStatus({ + id: selectedLotForQr.stockOutLineId, + status: 'checked', + qty: selectedLotForQr.stockOutLineQty || 0 + }); + console.log("Stock out line updated successfully!"); // Close modal setQrModalOpen(false); @@ -907,30 +903,12 @@ const PickExecution: React.FC = ({ filterArgs }) => { }, []); // Pagination data with sorting by routerIndex - const paginatedData = useMemo(() => { - // โœ… Sort by routerIndex first, then by other criteria - const sortedData = [...combinedLotData].sort((a, b) => { - const aIndex = a.routerIndex || 0; - const bIndex = b.routerIndex || 0; - - // Primary sort: by routerIndex - if (aIndex !== bIndex) { - return aIndex - bIndex; - } - - // Secondary sort: by pickOrderCode if routerIndex is the same - if (a.pickOrderCode !== b.pickOrderCode) { - return a.pickOrderCode.localeCompare(b.pickOrderCode); - } - - // Tertiary sort: by lotNo if everything else is the same - return (a.lotNo || '').localeCompare(b.lotNo || ''); - }); - - const startIndex = paginationController.pageNum * paginationController.pageSize; - const endIndex = startIndex + paginationController.pageSize; - return sortedData.slice(startIndex, endIndex); - }, [combinedLotData, paginationController]); + // 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]); return ( @@ -980,7 +958,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { {t("Shop Name")}: {fgPickOrders[0].shopName || '-'} - {t("Delivery Date")}: {(fgPickOrders[0].deliveryDate || '-').split('T')[0]} + {t("Pick Order Code")}:{fgPickOrders[0].pickOrderCode || '-'} {t("Departure Time")}: {fgPickOrders[0].DepartureTime || '-'} @@ -1015,6 +993,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { {/* {t("Original Available Qty")} */} {t("Lot Actual Pick Qty")} {/* {t("Remaining Available Qty")} */} + {t("Finish Scan?")} {t("Action")} @@ -1041,7 +1020,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { > - {lot.routerIndex || index + 1} + {index + 1} @@ -1070,7 +1049,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { const inQty = lot.inQty || 0; const outQty = lot.outQty || 0; const result = inQty - outQty; - return result.toLocaleString(); + return result.toLocaleString()+'('+lot.stockUnit+')'; })()} @@ -1102,7 +1081,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { {t("Scan")} ) : ( - // โœ… When stockOutLineId exists, show TextField + Issue button + = ({ filterArgs }) => { return result.toLocaleString(); })()} */} + + + - - + + + + + diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index aa7142e..158a537 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -350,6 +350,11 @@ const PickExecution: React.FC = ({ filterArgs }) => { 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; @@ -394,13 +399,6 @@ const PickExecution: React.FC = ({ filterArgs }) => { // TODO: Implement QR code functionality }; - useEffect(() => { - startScan(); - return () => { - stopScan(); - resetScan(); - }; - }, [startScan, stopScan, resetScan]); const fetchAllCombinedLotData = useCallback(async (userId?: number) => { setCombinedDataLoading(true); @@ -455,14 +453,12 @@ const PickExecution: React.FC = ({ filterArgs }) => { }; }, [fetchAllCombinedLotData]); - // โœ… Handle QR code submission for matched lot (external scanning) - // โœ… Handle QR code submission for matched lot (external scanning) - const handleQrCodeSubmit = useCallback(async (lotNo: string) => { + 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)); + console.log(`๏ฟฝ๏ฟฝ Available lots:`, currentLotData.map(lot => lot.lotNo)); const matchingLots = currentLotData.filter(lot => lot.lotNo === lotNo || @@ -488,35 +484,50 @@ const PickExecution: React.FC = ({ filterArgs }) => { console.log(`๐Ÿ”„ Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`); if (matchingLot.stockOutLineId) { - console.log(`โœ… Stock out line already exists for line ${matchingLot.pickOrderLineId}`); - existsCount++; - } else { + // โœ… FIXED: Use matchingLot.stockOutLineId instead of selectedLotForQr.stockOutLineId const stockOutLineUpdate = await updateStockOutLineStatus({ - id: selectedLotForQr.stockOutLineId, + id: matchingLot.stockOutLineId, // โœ… Use the correct ID status: 'checked', - qty: selectedLotForQr.stockOutLineQty || 0 + qty: matchingLot.stockOutLineQty || matchingLot.requiredQty || 0 }); - console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`,stockOutLineUpdate); + console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); - if (stockOutLineUpdate && stockOutLineUpdate.code === "EXISTS") { - console.log(`โœ… Stock out line already exists for line ${matchingLot.pickOrderLineId}`); - existsCount++; - } else if (stockOutLineUpdate && stockOutLineUpdate.code === "SUCCESS") { + if (stockOutLineUpdate && stockOutLineUpdate.code === "SUCCESS") { + console.log(`โœ… Stock out line updated successfully for line ${matchingLot.pickOrderLineId}`); + successCount++; + } else { + console.error(`โŒ Failed to update stock out line for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); + errorCount++; + } + } else { + // โœ… If no stock out line exists, create one + const createStockOutLineData = { + consoCode: matchingLot.pickOrderConsoCode, + pickOrderLineId: matchingLot.pickOrderLineId, + inventoryLotLineId: matchingLot.lotId, + qty: matchingLot.requiredQty || 0 + }; + + const createResult = await createStockOutLine(createStockOutLineData); + console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, createResult); + + if (createResult && createResult.code === "SUCCESS") { console.log(`โœ… Stock out line created successfully for line ${matchingLot.pickOrderLineId}`); successCount++; } else { - console.error(`โŒ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); + console.error(`โŒ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, createResult); errorCount++; } } } - // โœ… Always refresh data after processing (success or failure) + // โœ… FIXED: Set refresh flag before refreshing data + setIsRefreshingData(true); console.log("๐Ÿ”„ Refreshing data after QR code processing..."); await fetchAllCombinedLotData(); - if (successCount > 0 || existsCount > 0) { - console.log(`โœ… QR Code processing completed: ${successCount} created, ${existsCount} already existed`); + if (successCount > 0) { + console.log(`โœ… QR Code processing completed: ${successCount} updated/created`); setQrScanSuccess(true); setQrScanInput(''); // Clear input after successful processing @@ -540,12 +551,18 @@ const PickExecution: React.FC = ({ filterArgs }) => { 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]); @@ -598,15 +615,28 @@ const PickExecution: React.FC = ({ filterArgs }) => { // โœ… Outside QR scanning - process QR codes from outside the page automatically useEffect(() => { - if (qrValues.length > 0 && combinedLotData.length > 0) { - const latestQr = qrValues[qrValues.length - 1]; + // โœ… Don't process QR codes when refreshing data or if not manually scanning + if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { + return; + } + + const latestQr = qrValues[qrValues.length - 1]; + + // โœ… Prevent processing the same QR code multiple times + if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { + console.log(" QR code already processed, skipping..."); + return; + } + + if (latestQr && latestQr !== lastProcessedQr) { + console.log(` Processing new QR code: ${latestQr}`); + setLastProcessedQr(latestQr); + setProcessedQrCodes(prev => new Set(prev).add(latestQr)); - // Extract lot number from QR code let lotNo = ''; try { const qrData = JSON.parse(latestQr); if (qrData.stockInLineId && qrData.itemId) { - // For JSON QR codes, we need to fetch the lot number fetchStockInLineInfo(qrData.stockInLineId) .then((stockInLineInfo) => { console.log("Outside QR scan - Stock in line info:", stockInLineInfo); @@ -619,20 +649,18 @@ const PickExecution: React.FC = ({ filterArgs }) => { .catch((error) => { console.error("Outside QR scan - Error fetching stock in line info:", error); }); - return; // Exit early for JSON QR codes + return; } } catch (error) { - // Not JSON format, treat as direct lot number lotNo = latestQr.replace(/[{}]/g, ''); } - // For direct lot number QR codes if (lotNo) { console.log(`Outside QR scan detected (direct): ${lotNo}`); handleQrCodeSubmit(lotNo); } } - }, [qrValues, combinedLotData, handleQrCodeSubmit]); + }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, handleQrCodeSubmit]); const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { @@ -910,39 +938,45 @@ const paginatedData = useMemo(() => { return combinedLotData.slice(startIndex, endIndex); // โœ… No sorting needed }, [combinedLotData, paginationController]); + + + // โœ… Add these functions after line 395 + const handleStartScan = useCallback(() => { + console.log(" Starting manual QR scan..."); + setIsManualScanning(true); + setProcessedQrCodes(new Set()); + setLastProcessedQr(''); + startScan(); + }, [startScan]); + + const handleStopScan = useCallback(() => { + console.log("โน๏ธ Stopping manual QR scan..."); + setIsManualScanning(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 ( - {/* Search Box */} - {/* - - - {fgPickOrdersLoading ? ( - - - - ) : ( - - {fgPickOrders.length === 0 ? ( - - - {t("No FG pick orders found")} - - - ) : ( - fgPickOrders.map((fgOrder) => ( - - )) - )} - - )} - -*/} - + {/* DO Header */} @@ -960,9 +994,13 @@ const paginatedData = useMemo(() => { {t("Pick Order Code")}:{fgPickOrders[0].pickOrderCode || '-'} + + {t("Ticket No.")}: {fgPickOrders[0].ticketNo || '-'} + {t("Departure Time")}: {fgPickOrders[0].DepartureTime || '-'} + ) @@ -975,8 +1013,39 @@ const paginatedData = useMemo(() => { {t("All Pick Order Lots")} - - + + + {!isManualScanning ? ( + + ) : ( + + )} + + {isManualScanning && ( + + + + {t("Scanning...")} + + + )} + @@ -991,10 +1060,11 @@ const paginatedData = useMemo(() => { {/* {t("Lot Location")} */} {t("Lot Required Pick Qty")} {/* {t("Original Available Qty")} */} - {t("Lot Actual Pick Qty")} + {t("Scan Result")} + {t("Submit Required Pick Qty")} {/* {t("Remaining Available Qty")} */} - {t("Finish Scan?")} - {t("Action")} + + {/* {t("Action")} */} @@ -1028,7 +1098,7 @@ const paginatedData = useMemo(() => { {lot.routerRoute || '-'} - {lot.itemName} + {lot.itemName+'('+lot.stockUnit+')'} { const inQty = lot.inQty || 0; const outQty = lot.outQty || 0; const result = inQty - outQty; - return result.toLocaleString()+'('+lot.stockUnit+')'; - })()} - - - {/* โœ… QR Scan Button if not scanned, otherwise show TextField + Issue button */} - {lot.stockOutLineStatus?.toLowerCase() === 'pending' ? ( - - ) : ( - - - { - const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; - handlePickQtyChange(lotKey, parseFloat(e.target.value) || 0); - }} - disabled={ - (lot.lotAvailability === 'expired' || - lot.lotAvailability === 'status_unavailable' || - lot.lotAvailability === 'rejected') || - lot.stockOutLineStatus === 'completed' - } - inputProps={{ - min: 0, - max: calculateRemainingRequiredQty(lot), - step: 0.01 - }} - sx={{ - width: '60px', - height: '28px', - '& .MuiInputBase-input': { - fontSize: '0.7rem', - textAlign: 'center', - padding: '6px 8px' - } - }} - placeholder="0" - /> - - - - )} - - {/* - {(() => { - const inQty = lot.inQty || 0; - const outQty = lot.outQty || 0; - const result = inQty - outQty; - return result.toLocaleString(); + 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)} + + + ))} + +)} Date: Mon, 22 Sep 2025 13:44:07 +0800 Subject: [PATCH 3/3] update --- src/components/DoDetail/DoInfoCard.tsx | 9 +- .../FinishedGoodSearch/FGPickOrderCard.tsx | 2 +- .../GoodPickExecutionForm.tsx | 52 ++++---- .../GoodPickExecutiondetail.tsx | 114 +++++++++++++++--- src/i18n/zh/do.json | 19 ++- src/i18n/zh/pickOrder.json | 6 +- 6 files changed, 148 insertions(+), 54 deletions(-) diff --git a/src/components/DoDetail/DoInfoCard.tsx b/src/components/DoDetail/DoInfoCard.tsx index 44c5850..f15762f 100644 --- a/src/components/DoDetail/DoInfoCard.tsx +++ b/src/components/DoDetail/DoInfoCard.tsx @@ -54,14 +54,7 @@ const DoInfoCard: React.FC = ({ disabled={true} /> - - - + = ({ fgOrder, onQrCodeClick }) => { onClick={() => onQrCodeClick(fgOrder.pickOrderId)} sx={{ minWidth: 120 }} > - {t("View QR Code")} + {t("Print DN/Label")} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index d2eff98..482b843 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -87,10 +87,12 @@ const PickExecutionForm: React.FC = ({ const remainingQty = lot.inQty - lot.outQty; return Math.max(0, remainingQty); }, []); -const calculateRequiredQty = useCallback((lot: LotPickData) => { - const requiredQty = lot.requiredQty-(lot.actualPickQty||0); - return Math.max(0, requiredQty); -}, []); + const calculateRequiredQty = useCallback((lot: LotPickData) => { + // โœ… Use the original required quantity, not subtracting actualPickQty + // The actualPickQty in the form should be independent of the database value + return lot.requiredQty || 0; + }, []); + // ่Žทๅ–ๅค„็†ไบบๅ‘˜ๅˆ—่กจ useEffect(() => { const fetchHandlers = async () => { @@ -166,36 +168,30 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => { // โœ… Update form validation to require either missQty > 0 OR badItemQty > 0 const validateForm = (): boolean => { const newErrors: FormErrors = {}; - + if (formData.actualPickQty === undefined || formData.actualPickQty < 0) { - newErrors.actualPickQty = t('pickOrder.validation.actualPickQtyRequired'); + newErrors.actualPickQty = t('Qty is required'); } - + + // โœ… FIXED: Check if actual pick qty exceeds remaining available qty + if (formData.actualPickQty && formData.actualPickQty > remainingAvailableQty) { + newErrors.actualPickQty = t('Qty is not allowed to be greater than remaining available qty'); + } + + // โœ… FIXED: Check if actual pick qty exceeds required qty (use original required qty) + if (formData.actualPickQty && formData.actualPickQty > (selectedLot?.requiredQty || 0)) { + newErrors.actualPickQty = t('Qty is not allowed to be greater than required qty'); + } + // โœ… NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported) const hasMissQty = formData.missQty && formData.missQty > 0; const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; if (!hasMissQty && !hasBadItemQty) { - newErrors.missQty = t('pickOrder.validation.mustReportMissOrBadItems'); - newErrors.badItemQty = t('pickOrder.validation.mustReportMissOrBadItems'); + newErrors.missQty = t('At least one issue must be reported'); + newErrors.badItemQty = t('At least one issue must be reported'); } - - if (formData.missQty && formData.missQty < 0) { - newErrors.missQty = t('pickOrder.validation.missQtyInvalid'); - } - - if (formData.badItemQty && formData.badItemQty < 0) { - newErrors.badItemQty = t('pickOrder.validation.badItemQtyInvalid'); - } - - if (formData.badItemQty && formData.badItemQty > 0 && !formData.issueRemark) { - newErrors.issueRemark = t('pickOrder.validation.issueRemarkRequired'); - } - - if (formData.badItemQty && formData.badItemQty > 0 && !formData.handledBy) { - newErrors.handledBy = t('pickOrder.validation.handlerRequired'); - } - + setErrors(newErrors); return Object.keys(newErrors).length === 0; }; @@ -251,7 +247,7 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => { { value={formData.actualPickQty || 0} onChange={(e) => handleInputChange('actualPickQty', parseFloat(e.target.value) || 0)} error={!!errors.actualPickQty} - // helperText={errors.actualPickQty || t('Enter the quantity actually picked')} + helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`} variant="outlined" /> diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 158a537..7ac5a59 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -458,7 +458,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { // โœ… Use current data without refreshing to avoid infinite loop const currentLotData = combinedLotData; - console.log(`๏ฟฝ๏ฟฝ Available lots:`, currentLotData.map(lot => lot.lotNo)); + console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo)); const matchingLots = currentLotData.filter(lot => lot.lotNo === lotNo || @@ -477,18 +477,17 @@ const PickExecution: React.FC = ({ filterArgs }) => { try { let successCount = 0; - let existsCount = 0; let errorCount = 0; for (const matchingLot of matchingLots) { console.log(`๐Ÿ”„ Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`); if (matchingLot.stockOutLineId) { - // โœ… FIXED: Use matchingLot.stockOutLineId instead of selectedLotForQr.stockOutLineId + // โœ… FIXED: Only update status to 'checked', keep qty at 0 const stockOutLineUpdate = await updateStockOutLineStatus({ - id: matchingLot.stockOutLineId, // โœ… Use the correct ID + id: matchingLot.stockOutLineId, status: 'checked', - qty: matchingLot.stockOutLineQty || matchingLot.requiredQty || 0 + qty: 0 // โœ… Keep qty at 0 until user actually submits }); console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate); @@ -500,12 +499,12 @@ const PickExecution: React.FC = ({ filterArgs }) => { errorCount++; } } else { - // โœ… If no stock out line exists, create one + // โœ… If no stock out line exists, create one with qty = 0 const createStockOutLineData = { consoCode: matchingLot.pickOrderConsoCode, pickOrderLineId: matchingLot.pickOrderLineId, inventoryLotLineId: matchingLot.lotId, - qty: matchingLot.requiredQty || 0 + qty: 0 // โœ… Create with qty = 0 }; const createResult = await createStockOutLine(createStockOutLineData); @@ -719,13 +718,19 @@ const PickExecution: React.FC = ({ filterArgs }) => { } 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 ===`); @@ -740,7 +745,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { await updateStockOutLineStatus({ id: lot.stockOutLineId, status: newStatus, - qty: cumulativeQty + qty: cumulativeQty // โœ… Use cumulative quantity }); if (newQty > 0) { @@ -752,12 +757,11 @@ const PickExecution: React.FC = ({ filterArgs }) => { }); } - // โœ… FIXED: Use the proper API function instead of direct fetch + // โœ… 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 { - // โœ… Use the imported API function instead of direct fetch const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); console.log(`โœ… Pick order completion check result:`, completionResponse); @@ -937,7 +941,83 @@ const paginatedData = useMemo(() => { 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 @@ -1117,8 +1197,10 @@ const paginatedData = useMemo(() => { {(() => { const inQty = lot.inQty || 0; + const requiredQty = lot.requiredQty || 0; + const actualPickQty = lot.actualPickQty || 0; const outQty = lot.outQty || 0; - const result = inQty - outQty; + const result = requiredQty; return result.toLocaleString()+'('+lot.uomShortDesc+')'; })()} @@ -1143,10 +1225,12 @@ const paginatedData = useMemo(() => {