|
|
|
@@ -34,6 +34,7 @@ import { |
|
|
|
autoAssignAndReleasePickOrder, |
|
|
|
AutoAssignReleaseResponse, |
|
|
|
checkPickOrderCompletion, |
|
|
|
fetchAllPickOrderLotsHierarchical, |
|
|
|
PickOrderCompletionResponse, |
|
|
|
checkAndCompletePickOrderByConsoCode, |
|
|
|
updateSuggestedLotLineId, |
|
|
|
@@ -322,7 +323,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { |
|
|
|
const { data: session } = useSession() as { data: SessionWithTokens | null }; |
|
|
|
|
|
|
|
const currentUserId = session?.id ? parseInt(session.id) : undefined; |
|
|
|
|
|
|
|
const [allLotsCompleted, setAllLotsCompleted] = useState(false); |
|
|
|
const [combinedLotData, setCombinedLotData] = useState<any[]>([]); |
|
|
|
const [combinedDataLoading, setCombinedDataLoading] = useState(false); |
|
|
|
const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); |
|
|
|
@@ -403,7 +404,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
fetchFgPickOrdersData(); |
|
|
|
} |
|
|
|
}, [combinedLotData, fetchFgPickOrdersData]); |
|
|
|
|
|
|
|
|
|
|
|
// ✅ Handle QR code button click |
|
|
|
const handleQrCodeClick = (pickOrderId: number) => { |
|
|
|
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); |
|
|
|
@@ -416,7 +417,31 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
setScannedLotData(scannedLot); |
|
|
|
setLotConfirmationOpen(true); |
|
|
|
}, []); |
|
|
|
const checkAllLotsCompleted = useCallback((lotData: any[]) => { |
|
|
|
if (lotData.length === 0) { |
|
|
|
setAllLotsCompleted(false); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Filter out rejected lots |
|
|
|
const nonRejectedLots = lotData.filter(lot => |
|
|
|
lot.lotAvailability !== 'rejected' && |
|
|
|
lot.stockOutLineStatus !== 'rejected' |
|
|
|
); |
|
|
|
|
|
|
|
if (nonRejectedLots.length === 0) { |
|
|
|
setAllLotsCompleted(false); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Check if all non-rejected lots are completed |
|
|
|
const allCompleted = nonRejectedLots.every(lot => |
|
|
|
lot.stockOutLineStatus === 'completed' |
|
|
|
); |
|
|
|
|
|
|
|
setAllLotsCompleted(allCompleted); |
|
|
|
return allCompleted; |
|
|
|
}, []); |
|
|
|
const fetchAllCombinedLotData = useCallback(async (userId?: number) => { |
|
|
|
setCombinedDataLoading(true); |
|
|
|
try { |
|
|
|
@@ -428,22 +453,111 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
console.warn("⚠️ No userId available, skipping API call"); |
|
|
|
setCombinedLotData([]); |
|
|
|
setOriginalCombinedData([]); |
|
|
|
setAllLotsCompleted(false); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ Use the non-auto-assign endpoint - this only fetches existing data |
|
|
|
const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); |
|
|
|
console.log("✅ All combined lot details:", allLotDetails); |
|
|
|
setCombinedLotData(allLotDetails); |
|
|
|
setOriginalCombinedData(allLotDetails); |
|
|
|
// ✅ Use the hierarchical endpoint that includes rejected lots |
|
|
|
const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); |
|
|
|
console.log("✅ Hierarchical lot details:", hierarchicalData); |
|
|
|
|
|
|
|
// ✅ Transform hierarchical data to flat structure for the table |
|
|
|
const flatLotData: any[] = []; |
|
|
|
|
|
|
|
if (hierarchicalData.pickOrder && hierarchicalData.pickOrderLines) { |
|
|
|
hierarchicalData.pickOrderLines.forEach((line: any) => { |
|
|
|
if (line.lots && line.lots.length > 0) { |
|
|
|
line.lots.forEach((lot: any) => { |
|
|
|
flatLotData.push({ |
|
|
|
// Pick order info |
|
|
|
pickOrderId: hierarchicalData.pickOrder.id, |
|
|
|
pickOrderCode: hierarchicalData.pickOrder.code, |
|
|
|
pickOrderConsoCode: hierarchicalData.pickOrder.consoCode, |
|
|
|
pickOrderTargetDate: hierarchicalData.pickOrder.targetDate, |
|
|
|
pickOrderType: hierarchicalData.pickOrder.type, |
|
|
|
pickOrderStatus: hierarchicalData.pickOrder.status, |
|
|
|
pickOrderAssignTo: hierarchicalData.pickOrder.assignTo, |
|
|
|
|
|
|
|
// Pick order line info |
|
|
|
pickOrderLineId: line.id, |
|
|
|
pickOrderLineRequiredQty: line.requiredQty, |
|
|
|
pickOrderLineStatus: line.status, |
|
|
|
|
|
|
|
// Item info |
|
|
|
itemId: line.item.id, |
|
|
|
itemCode: line.item.code, |
|
|
|
itemName: line.item.name, |
|
|
|
uomCode: line.item.uomCode, |
|
|
|
uomDesc: line.item.uomDesc, |
|
|
|
|
|
|
|
// Lot info |
|
|
|
lotId: lot.id, |
|
|
|
lotNo: lot.lotNo, |
|
|
|
expiryDate: lot.expiryDate, |
|
|
|
location: lot.location, |
|
|
|
stockUnit: lot.stockUnit, |
|
|
|
availableQty: lot.availableQty, |
|
|
|
requiredQty: lot.requiredQty, |
|
|
|
actualPickQty: lot.actualPickQty, |
|
|
|
inQty: lot.inQty, |
|
|
|
outQty: lot.outQty, |
|
|
|
holdQty: lot.holdQty, |
|
|
|
lotStatus: lot.lotStatus, |
|
|
|
lotAvailability: lot.lotAvailability, |
|
|
|
processingStatus: lot.processingStatus, |
|
|
|
suggestedPickLotId: lot.suggestedPickLotId, |
|
|
|
stockOutLineId: lot.stockOutLineId, |
|
|
|
stockOutLineStatus: lot.stockOutLineStatus, |
|
|
|
stockOutLineQty: lot.stockOutLineQty, |
|
|
|
|
|
|
|
// Router info |
|
|
|
routerId: lot.router?.id, |
|
|
|
routerIndex: lot.router?.index, |
|
|
|
routerRoute: lot.router?.route, |
|
|
|
routerArea: lot.router?.area, |
|
|
|
uomShortDesc: lot.router?.uomId |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
console.log("✅ Transformed flat lot data:", flatLotData); |
|
|
|
setCombinedLotData(flatLotData); |
|
|
|
setOriginalCombinedData(flatLotData); |
|
|
|
|
|
|
|
// ✅ Check completion status |
|
|
|
checkAllLotsCompleted(flatLotData); |
|
|
|
} catch (error) { |
|
|
|
console.error("❌ Error fetching combined lot data:", error); |
|
|
|
setCombinedLotData([]); |
|
|
|
setOriginalCombinedData([]); |
|
|
|
setAllLotsCompleted(false); |
|
|
|
} finally { |
|
|
|
setCombinedDataLoading(false); |
|
|
|
} |
|
|
|
}, [currentUserId]); |
|
|
|
}, [currentUserId, checkAllLotsCompleted]); |
|
|
|
|
|
|
|
// ✅ Add effect to check completion when lot data changes |
|
|
|
useEffect(() => { |
|
|
|
if (combinedLotData.length > 0) { |
|
|
|
checkAllLotsCompleted(combinedLotData); |
|
|
|
} |
|
|
|
}, [combinedLotData, checkAllLotsCompleted]); |
|
|
|
|
|
|
|
// ✅ Add function to expose completion status to parent |
|
|
|
const getCompletionStatus = useCallback(() => { |
|
|
|
return allLotsCompleted; |
|
|
|
}, [allLotsCompleted]); |
|
|
|
|
|
|
|
// ✅ Expose completion status to parent component |
|
|
|
useEffect(() => { |
|
|
|
// Dispatch custom event with completion status |
|
|
|
const event = new CustomEvent('pickOrderCompletionStatus', { |
|
|
|
detail: { allLotsCompleted } |
|
|
|
}); |
|
|
|
window.dispatchEvent(event); |
|
|
|
}, [allLotsCompleted]); |
|
|
|
const handleLotConfirmation = useCallback(async () => { |
|
|
|
if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; |
|
|
|
setIsConfirmingLot(true); |
|
|
|
@@ -465,6 +579,15 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
newInventoryLotLineId: newLotLineId |
|
|
|
}); |
|
|
|
|
|
|
|
setQrScanError(false); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanInput(''); |
|
|
|
setIsManualScanning(false); |
|
|
|
stopScan(); |
|
|
|
resetScan(); |
|
|
|
setProcessedQrCodes(new Set()); |
|
|
|
setLastProcessedQr(''); |
|
|
|
|
|
|
|
setLotConfirmationOpen(false); |
|
|
|
setExpectedLotData(null); |
|
|
|
setScannedLotData(null); |
|
|
|
@@ -672,9 +795,9 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
console.error("No item match in expected lots for scanned code"); |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 2) Check if scanned lot is exactly in expected lots |
|
|
|
const exactLotMatch = sameItemLotsInExpected.find(l => |
|
|
|
(scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) || |
|
|
|
@@ -697,6 +820,13 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ Check if the expected lot is already the scanned lot (after substitution) |
|
|
|
if (expectedLot.lotNo === scanned?.lotNo) { |
|
|
|
console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`); |
|
|
|
handleQrCodeSubmit(scanned.lotNo); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setSelectedLotForQr(expectedLot); |
|
|
|
handleLotMismatch( |
|
|
|
{ |
|
|
|
@@ -720,26 +850,27 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
} |
|
|
|
}, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); |
|
|
|
// ✅ Update the outside QR scanning effect to use enhanced processing |
|
|
|
useEffect(() => { |
|
|
|
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { |
|
|
|
return; |
|
|
|
} |
|
|
|
// ✅ Update the outside QR scanning effect to use enhanced processing |
|
|
|
useEffect(() => { |
|
|
|
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const latestQr = qrValues[qrValues.length - 1]; |
|
|
|
|
|
|
|
const latestQr = qrValues[qrValues.length - 1]; |
|
|
|
if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { |
|
|
|
console.log("QR code already processed, skipping..."); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (latestQr && latestQr !== lastProcessedQr) { |
|
|
|
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); |
|
|
|
setLastProcessedQr(latestQr); |
|
|
|
setProcessedQrCodes(prev => new Set(prev).add(latestQr)); |
|
|
|
|
|
|
|
if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { |
|
|
|
console.log("QR code already processed, skipping..."); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (latestQr && latestQr !== lastProcessedQr) { |
|
|
|
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); |
|
|
|
setLastProcessedQr(latestQr); |
|
|
|
setProcessedQrCodes(prev => new Set(prev).add(latestQr)); |
|
|
|
|
|
|
|
processOutsideQrCode(latestQr); |
|
|
|
} |
|
|
|
}, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode]); |
|
|
|
processOutsideQrCode(latestQr); |
|
|
|
} |
|
|
|
}, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]); |
|
|
|
// ✅ Only fetch existing data when session is ready, no auto-assignment |
|
|
|
useEffect(() => { |
|
|
|
if (session && currentUserId && !initializationRef.current) { |
|
|
|
@@ -1315,7 +1446,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
{/* <TableCell>{t("Lot Location")}</TableCell> */} |
|
|
|
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> |
|
|
|
{/* <TableCell align="right">{t("Original Available Qty")}</TableCell> */} |
|
|
|
<TableCell align="right">{t("Scan Result")}</TableCell> |
|
|
|
<TableCell align="center">{t("Scan Result")}</TableCell> |
|
|
|
<TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> |
|
|
|
{/* <TableCell align="right">{t("Remaining Available Qty")}</TableCell> */} |
|
|
|
|
|
|
|
@@ -1380,21 +1511,36 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe |
|
|
|
return result.toLocaleString()+'('+lot.uomShortDesc+')'; |
|
|
|
})()} |
|
|
|
</TableCell> |
|
|
|
<TableCell align="center"> |
|
|
|
{lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( |
|
|
|
<Checkbox |
|
|
|
checked={lot.stockOutLineStatus?.toLowerCase() !== 'pending'} |
|
|
|
disabled={true} |
|
|
|
readOnly={true} |
|
|
|
sx={{ |
|
|
|
color: lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400', |
|
|
|
'&.Mui-checked': { |
|
|
|
color: 'success.main', |
|
|
|
}, |
|
|
|
}} |
|
|
|
/> |
|
|
|
) : null} |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
<TableCell align="center"> |
|
|
|
{lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( |
|
|
|
<Box sx={{ |
|
|
|
display: 'flex', |
|
|
|
justifyContent: 'center', |
|
|
|
alignItems: 'center', |
|
|
|
width: '100%', |
|
|
|
height: '100%' |
|
|
|
}}> |
|
|
|
<Checkbox |
|
|
|
checked={lot.stockOutLineStatus?.toLowerCase() !== 'pending'} |
|
|
|
disabled={true} |
|
|
|
readOnly={true} |
|
|
|
size="large" |
|
|
|
sx={{ |
|
|
|
color: lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400', |
|
|
|
'&.Mui-checked': { |
|
|
|
color: 'success.main', |
|
|
|
}, |
|
|
|
transform: 'scale(1.3)', |
|
|
|
'& .MuiSvgIcon-root': { |
|
|
|
fontSize: '1.5rem', |
|
|
|
} |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
) : null} |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
<TableCell align="center"> |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center' }}> |
|
|
|
<Stack direction="row" spacing={1} alignItems="center"> |
|
|
|
|