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(() => {