diff --git a/src/components/PickOrderSearch/LotTable.tsx b/src/components/PickOrderSearch/LotTable.tsx index b62ecfe..3ff86f2 100644 --- a/src/components/PickOrderSearch/LotTable.tsx +++ b/src/components/PickOrderSearch/LotTable.tsx @@ -28,10 +28,10 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import import PickExecutionForm from "./PickExecutionForm"; interface LotPickData { id: number; - lotId: number | null; - lotNo: string | null; + lotId: number; + lotNo: string; expiryDate: string; - location: string | null; + location: string; stockUnit: string; inQty: number; availableQty: number; @@ -45,7 +45,6 @@ interface LotPickData { stockOutLineId?: number; stockOutLineStatus?: string; stockOutLineQty?: number; - noLot?: boolean; } interface PickQtyData { @@ -61,7 +60,7 @@ interface LotTableProps { pickQtyData: PickQtyData; selectedLotRowId: string | null; selectedLotId: number | null; - onLotSelection: (uniqueLotId: string, lotId: number | null) => void; + onLotSelection: (uniqueLotId: string, lotId: number) => void; onPickQtyChange: (lineId: number, lotId: number, value: number) => void; onSubmitPickQty: (lineId: number, lotId: number) => void; onCreateStockOutLine: (inventoryLotLineId: number) => void; @@ -76,7 +75,6 @@ interface LotTableProps { generateInputBody: () => any; onDataRefresh: () => Promise; onLotDataRefresh: () => Promise; - onIssueNoLotStockOutLine: (stockOutLineId: number) => void; } // QR Code Modal Component @@ -238,7 +236,7 @@ const QrCodeModal: React.FC<{ const timer = setTimeout(() => { setQrScanSuccess(true); - onQrCodeSubmit(lot.lotNo??''); + onQrCodeSubmit(lot.lotNo); onClose(); setManualInput(''); setManualInputError(false); @@ -303,7 +301,9 @@ const QrCodeModal: React.FC<{ )} + {/* Manual Input with Submit-Triggered Helper Text */} + {false &&( {t("Manual Input")}: @@ -339,7 +339,8 @@ const QrCodeModal: React.FC<{ {t("Submit")} - + )} + {/* Show QR Scan Status */} {qrValues.length > 0 && ( = ({ generateInputBody, onDataRefresh, onLotDataRefresh, - onIssueNoLotStockOutLine, }) => { const { t } = useTranslation("pickOrder"); - const calculateRemainingRequiredQty = useCallback((lot: LotPickData) => { const requiredQty = lot.requiredQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0; return Math.max(0, requiredQty - stockOutLineQty); }, []); - + // Add QR scanner context const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({}); + // Add state for QR input modal const [qrModalOpen, setQrModalOpen] = useState(false); const [selectedLotForQr, setSelectedLotForQr] = useState(null); const [manualQrInput, setManualQrInput] = useState(''); - + + // 分页控制器 const [lotTablePagingController, setLotTablePagingController] = useState({ pageNum: 0, pageSize: 10, }); + // 添加状态消息生成函数 const getStatusMessage = useCallback((lot: LotPickData) => { + switch (lot.stockOutLineStatus?.toLowerCase()) { case 'pending': return t("Please finish QR code scanand pick order."); @@ -428,35 +431,23 @@ const LotTable: React.FC = ({ default: return t("Please finish QR code scan and pick order."); } - }, [t]); - const handleOpenQrModal = useCallback((lot: LotPickData) => { - setSelectedLotForQr(lot); - setManualQrInput(lot.lotNo ?? ""); - setValidationErrors({}); - setQrModalOpen(true); - resetScan(); - startScan(); - }, [startScan, resetScan]); - - const handleCloseQrModal = useCallback(() => { - setQrModalOpen(false); - setSelectedLotForQr(null); - stopScan(); - resetScan(); - }, [stopScan, resetScan]); + }, []); + const prepareLotTableData = useMemo(() => { return lotData.map((lot) => ({ ...lot, - id: lot.lotId ?? lot.id, + id: lot.lotId, })); }, [lotData]); + // 分页数据 const paginatedLotTableData = useMemo(() => { const startIndex = lotTablePagingController.pageNum * lotTablePagingController.pageSize; const endIndex = startIndex + lotTablePagingController.pageSize; return prepareLotTableData.slice(startIndex, endIndex); }, [prepareLotTableData, lotTablePagingController]); + // 分页处理函数 const handleLotTablePageChange = useCallback((event: unknown, newPage: number) => { setLotTablePagingController(prev => ({ ...prev, @@ -471,25 +462,31 @@ const LotTable: React.FC = ({ pageSize: newPageSize, }); }, []); - const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { - if (!selectedRowId || lot.noLot) return lot.availableQty; - const actualPickQty = pickQtyData[selectedRowId]?.[lot.lotId ?? 0] || 0; - const remainingQty = (lot.inQty || 0) - (lot.outQty || 0) - actualPickQty; + if (!selectedRowId) return lot.availableQty; + const lactualPickQty = lot.actualPickQty || 0; + const actualPickQty = pickQtyData[selectedRowId]?.[lot.lotId] || 0; + + const remainingQty = lot.inQty - lot.outQty-actualPickQty; + + // Ensure it doesn't go below 0 return Math.max(0, remainingQty); }, [selectedRowId, pickQtyData]); - const validatePickQty = useCallback((lot: LotPickData, inputValue: number) => { - const maxAllowed = Math.min(calculateRemainingAvailableQty(lot), calculateRemainingRequiredQty(lot)); - if (inputValue > maxAllowed) { - return `${t('Input quantity cannot exceed')} ${maxAllowed}`; - } - if (inputValue < 0) { - return t('Quantity cannot be negative'); - } - return null; - }, [calculateRemainingAvailableQty, calculateRemainingRequiredQty, t]); + const maxAllowed = Math.min(calculateRemainingAvailableQty(lot), calculateRemainingRequiredQty(lot)); + + if (inputValue > maxAllowed) { + return `${t('Input quantity cannot exceed')} ${maxAllowed}`; + } + + if (inputValue < 0) { + return t('Quantity cannot be negative'); + } + + return null; +}, [calculateRemainingAvailableQty, calculateRemainingRequiredQty, t]); + // Handle QR code submission const handleQrCodeSubmit = useCallback(async (lotNo: string) => { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { console.log(` QR Code verified for lot: ${lotNo}`); @@ -522,7 +519,7 @@ const LotTable: React.FC = ({ if (selectedRowId) { // Add a small delay to ensure the data refresh is complete setTimeout(() => { - onPickQtyChange(selectedRowId, lotId ?? 0, requiredQty); + onPickQtyChange(selectedRowId, lotId, requiredQty); console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); }, 500); // 500ms delay to ensure refresh is complete } @@ -540,10 +537,12 @@ const LotTable: React.FC = ({ alert(`QR scan mismatch! Expected: ${selectedLotForQr?.lotNo}, Scanned: ${lotNo}`); } }, [selectedLotForQr, selectedRowId, onPickQtyChange]); - // PickExecutionForm 狀態與提交(保持原本邏輯) + + // 添加 PickExecutionForm 相关的状态 const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState(null); + // 添加处理函数 const handlePickExecutionForm = useCallback((lot: LotPickData) => { console.log("=== Pick Execution Form ==="); console.log("Lot data:", lot); @@ -564,6 +563,8 @@ const LotTable: React.FC = ({ const handlePickExecutionFormSubmit = useCallback(async (data: any) => { try { console.log("Pick execution form submitted:", data); + + // 调用 API 提交数据 const result = await recordPickExecutionIssue(data); console.log("Pick execution issue recorded:", result); @@ -576,6 +577,7 @@ const LotTable: React.FC = ({ setPickExecutionFormOpen(false); setSelectedLotForExecutionForm(null); + // 刷新数据 if (onDataRefresh) { await onDataRefresh(); } @@ -601,7 +603,14 @@ const LotTable: React.FC = ({ {t("Lot Required Pick Qty")} {t("Original Available Qty")} {t("Lot Actual Pick Qty")} + {/*{t("Available Lot")}*/} {t("Remaining Available Qty")} + + {/*{t("QR Code Scan")}*/} + {/*} + {t("Reject")} + */} + {t("Action")} @@ -617,7 +626,7 @@ const LotTable: React.FC = ({ ) : ( paginatedLotTableData.map((lot, index) => ( = ({ }} > - { - if (!lot.noLot && lot.lotId != null) { - onLotSelection(`row_${index}`, lot.lotId); - } - }} - disabled={ - lot.noLot || // 無批次行不支援勾選 - lot.lotAvailability === 'expired' || - lot.lotAvailability === 'status_unavailable' || - lot.lotAvailability === 'rejected' - } - value={`row_${index}`} - name="lot-selection" - /> - + onLotSelection(`row_${index}`, lot.lotId)} + // 禁用 rejected、expired 和 status_unavailable 的批次 + disabled={lot.lotAvailability === 'expired' || + lot.lotAvailability === 'status_unavailable' || + lot.lotAvailability === 'rejected'} // 添加 rejected + value={`row_${index}`} + name="lot-selection" + /> + - - {lot.noLot - ? t('⚠️ No Stock Available') - : lot.lotNo} + + {lot.lotNo} + {/* + {lot.lotAvailability !== 'available' && ( + + ({lot.lotAvailability === 'expired' ? 'Expired' : + lot.lotAvailability === 'insufficient_stock' ? 'Insufficient' : + lot.lotAvailability === 'rejected' ? 'Rejected' : // 添加 rejected 显示 + 'Unavailable'}) + + )} */} {lot.expiryDate} @@ -661,140 +676,154 @@ const LotTable: React.FC = ({ {(() => { const inQty = lot.inQty || 0; const outQty = lot.outQty || 0; + + const result = inQty - outQty; return result.toLocaleString(); })()} - {lot.stockOutLineStatus?.toLowerCase() === 'pending' ? ( - - ) : ( - - { - if (selectedRowId && lot.lotId != null) { - onPickQtyChange(selectedRowId, lot.lotId, Number(e.target.value) || 0); - } - }} - disabled={ - lot.lotAvailability === 'expired' || - lot.lotAvailability === 'status_unavailable' || - lot.lotAvailability === 'rejected' || - selectedLotRowId !== `row_${index}` || - lot.stockOutLineStatus === 'completed' - } - error={!!validationErrors[`lot_${lot.lotId}`]} - helperText={validationErrors[`lot_${lot.lotId}`]} - inputProps={{ min: 0, max: calculateRemainingRequiredQty(lot) }} - sx={{ width: 70 }} - placeholder="0" - /> - - - )} - - - {calculateRemainingAvailableQty(lot).toLocaleString()} - + {/* Show QR Scan Button if not scanned, otherwise show TextField + Pick Form */} + {lot.stockOutLineStatus?.toLowerCase() === 'pending' ? ( + + ) : ( + + {/* 恢复 TextField 用于正常数量输入 */} + { + if (selectedRowId) { + const inputValue = parseFloat(e.target.value) || 0; + const maxAllowed = Math.min(calculateRemainingAvailableQty(lot), calculateRemainingRequiredQty(lot)); + + onPickQtyChange(selectedRowId, lot.lotId, inputValue); + } + }} + disabled={ + (lot.lotAvailability === 'expired' || + lot.lotAvailability === 'status_unavailable' || + lot.lotAvailability === 'rejected') || + selectedLotRowId !== `row_${index}` || + lot.stockOutLineStatus === 'completed' + } + error={!!validationErrors[`lot_${lot.lotId}`]} + helperText={validationErrors[`lot_${lot.lotId}`]} + 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" + /> + + {/* 添加 Pick Form 按钮用于问题情况 */} + + + )} + + {/*{lot.availableQty.toLocaleString()}*/} + {calculateRemainingAvailableQty(lot).toLocaleString()} - {/* ✅ Action 欄位:區分 noLot / 正常 lot */} - - - {lot.noLot ? ( - // 沒有批次:只允許 Issue(報告 miss) - - ) : ( - - )} - + + + + + )) @@ -802,14 +831,50 @@ const LotTable: React.FC = ({ + + {/* Status Messages Display */} + {paginatedLotTableData.length > 0 && ( + + {paginatedLotTableData.map((lot, index) => ( + + + {t("Lot")} {lot.lotNo}: {getStatusMessage(lot)} + + + ))} + + )} + - {/* Status message & pagination & modals 保持原有程式不變(略) */} + + + `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` + } + /> + + {/* QR Code Modal */} + open={qrModalOpen} + onClose={() => { + setQrModalOpen(false); + setSelectedLotForQr(null); + stopScan(); + resetScan(); + }} + lot={selectedLotForQr} + onQrCodeSubmit={handleQrCodeSubmit} + /> + + {/* Pick Execution Form Modal */} {pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && (