| @@ -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<void>; | |||
| onLotDataRefresh: () => Promise<void>; | |||
| 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<{ | |||
| </Box> | |||
| )} | |||
| {/* Manual Input with Submit-Triggered Helper Text */} | |||
| {false &&( | |||
| <Box sx={{ mb: 2 }}> | |||
| <Typography variant="body2" gutterBottom> | |||
| <strong>{t("Manual Input")}:</strong> | |||
| @@ -339,7 +339,8 @@ const QrCodeModal: React.FC<{ | |||
| {t("Submit")} | |||
| </Button> | |||
| </Box> | |||
| )} | |||
| {/* Show QR Scan Status */} | |||
| {qrValues.length > 0 && ( | |||
| <Box sx={{ | |||
| @@ -390,28 +391,30 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| 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<LotPickData | null>(null); | |||
| const [manualQrInput, setManualQrInput] = useState<string>(''); | |||
| // 分页控制器 | |||
| 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<LotTableProps> = ({ | |||
| 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<LotTableProps> = ({ | |||
| 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<LotTableProps> = ({ | |||
| 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<LotTableProps> = ({ | |||
| alert(`QR scan mismatch! Expected: ${selectedLotForQr?.lotNo}, Scanned: ${lotNo}`); | |||
| } | |||
| }, [selectedLotForQr, selectedRowId, onPickQtyChange]); | |||
| // PickExecutionForm 狀態與提交(保持原本邏輯) | |||
| // 添加 PickExecutionForm 相关的状态 | |||
| const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); | |||
| const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null); | |||
| // 添加处理函数 | |||
| const handlePickExecutionForm = useCallback((lot: LotPickData) => { | |||
| console.log("=== Pick Execution Form ==="); | |||
| console.log("Lot data:", lot); | |||
| @@ -564,6 +563,8 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| 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<LotTableProps> = ({ | |||
| setPickExecutionFormOpen(false); | |||
| setSelectedLotForExecutionForm(null); | |||
| // 刷新数据 | |||
| if (onDataRefresh) { | |||
| await onDataRefresh(); | |||
| } | |||
| @@ -601,7 +603,14 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> | |||
| <TableCell align="right">{t("Original Available Qty")}</TableCell> | |||
| <TableCell align="center">{t("Lot Actual Pick Qty")}</TableCell> | |||
| {/*<TableCell align="right">{t("Available Lot")}</TableCell>*/} | |||
| <TableCell align="right">{t("Remaining Available Qty")}</TableCell> | |||
| {/*<TableCell align="center">{t("QR Code Scan")}</TableCell>*/} | |||
| {/*} | |||
| <TableCell align="center">{t("Reject")}</TableCell> | |||
| */} | |||
| <TableCell align="center">{t("Action")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| @@ -617,7 +626,7 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| ) : ( | |||
| paginatedLotTableData.map((lot, index) => ( | |||
| <TableRow | |||
| key={lot.noLot ? `noLot_${lot.stockOutLineId}_${index}` : `lot_${lot.lotId}_${index}`} | |||
| key={lot.id} | |||
| sx={{ | |||
| backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit', | |||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1, | |||
| @@ -627,30 +636,36 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| }} | |||
| > | |||
| <TableCell> | |||
| <Checkbox | |||
| checked={selectedLotRowId === `row_${index}`} | |||
| onChange={() => { | |||
| 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" | |||
| /> | |||
| </TableCell> | |||
| <Checkbox | |||
| checked={selectedLotRowId === `row_${index}`} | |||
| onChange={() => 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" | |||
| /> | |||
| </TableCell> | |||
| <TableCell> | |||
| <Box> | |||
| <Typography> | |||
| {lot.noLot | |||
| ? t('⚠️ No Stock Available') | |||
| : lot.lotNo} | |||
| <Typography | |||
| sx={{ | |||
| color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit', | |||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1 | |||
| }} | |||
| > | |||
| {lot.lotNo} | |||
| </Typography> | |||
| {/* | |||
| {lot.lotAvailability !== 'available' && ( | |||
| <Typography variant="caption" color="error" display="block"> | |||
| ({lot.lotAvailability === 'expired' ? 'Expired' : | |||
| lot.lotAvailability === 'insufficient_stock' ? 'Insufficient' : | |||
| lot.lotAvailability === 'rejected' ? 'Rejected' : // 添加 rejected 显示 | |||
| 'Unavailable'}) | |||
| </Typography> | |||
| )} */} | |||
| </Box> | |||
| </TableCell> | |||
| <TableCell>{lot.expiryDate}</TableCell> | |||
| @@ -661,140 +676,154 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| {(() => { | |||
| const inQty = lot.inQty || 0; | |||
| const outQty = lot.outQty || 0; | |||
| const result = inQty - outQty; | |||
| return result.toLocaleString(); | |||
| })()} | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| {lot.stockOutLineStatus?.toLowerCase() === 'pending' ? ( | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| startIcon={<QrCodeIcon />} | |||
| onClick={() => handleOpenQrModal(lot)} | |||
| disabled={ | |||
| lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected' || | |||
| selectedLotRowId !== `row_${index}` | |||
| } | |||
| sx={{ fontSize: '0.7rem', minHeight: 40, minWidth: 100 }} | |||
| title={ | |||
| selectedLotRowId !== `row_${index}` | |||
| ? t("Please select this lot first to enable QR scanning") | |||
| : t("Click to scan QR code") | |||
| } | |||
| > | |||
| {t("Scan")} | |||
| </Button> | |||
| ) : ( | |||
| <Stack direction="row" spacing={1} alignItems="center" justifyContent="center"> | |||
| <TextField | |||
| type="number" | |||
| size="small" | |||
| value={ | |||
| selectedRowId && lot.lotId != null | |||
| ? pickQtyData[selectedRowId]?.[lot.lotId] ?? '' | |||
| : '' | |||
| } | |||
| onChange={(e) => { | |||
| 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" | |||
| /> | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={() => handlePickExecutionForm(lot)} | |||
| disabled={ | |||
| lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected' || | |||
| selectedLotRowId !== `row_${index}` | |||
| } | |||
| sx={{ fontSize: '0.7rem', minWidth: 70, borderColor: 'warning.main', color: 'warning.main' }} | |||
| title={t("Report missing or bad items")} | |||
| > | |||
| {t("Issue")} | |||
| </Button> | |||
| </Stack> | |||
| )} | |||
| </TableCell> | |||
| <TableCell align="right"> | |||
| {calculateRemainingAvailableQty(lot).toLocaleString()} | |||
| </TableCell> | |||
| {/* Show QR Scan Button if not scanned, otherwise show TextField + Pick Form */} | |||
| {lot.stockOutLineStatus?.toLowerCase() === 'pending' ? ( | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={() => { | |||
| setSelectedLotForQr(lot); | |||
| setQrModalOpen(true); | |||
| resetScan(); | |||
| }} | |||
| disabled={ | |||
| (lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected') || | |||
| selectedLotRowId !== `row_${index}` | |||
| } | |||
| sx={{ | |||
| fontSize: '0.7rem', | |||
| py: 0.5, | |||
| minHeight: '40px', | |||
| whiteSpace: 'nowrap', | |||
| minWidth: '80px', | |||
| opacity: selectedLotRowId === `row_${index}` ? 1 : 0.5 | |||
| }} | |||
| startIcon={<QrCodeIcon />} | |||
| title={ | |||
| selectedLotRowId !== `row_${index}` | |||
| ? "Please select this lot first to enable QR scanning" | |||
| : "Click to scan QR code" | |||
| } | |||
| > | |||
| {t("Scan")} | |||
| </Button> | |||
| ) : ( | |||
| <Stack | |||
| direction="row" | |||
| spacing={1} | |||
| alignItems="center" | |||
| justifyContent="center" // 添加水平居中 | |||
| sx={{ | |||
| width: '100%', // 确保占满整个单元格宽度 | |||
| minHeight: '40px' // 设置最小高度确保垂直居中 | |||
| }} | |||
| > | |||
| {/* 恢复 TextField 用于正常数量输入 */} | |||
| <TextField | |||
| type="number" | |||
| size="small" | |||
| value={pickQtyData[selectedRowId!]?.[lot.lotId] || ''} | |||
| onChange={(e) => { | |||
| 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 按钮用于问题情况 */} | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={() => handlePickExecutionForm(lot)} | |||
| disabled={ | |||
| (lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected') || | |||
| selectedLotRowId !== `row_${index}` | |||
| } | |||
| sx={{ | |||
| fontSize: '0.7rem', | |||
| py: 0.5, | |||
| minHeight: '28px', | |||
| minWidth: '60px', | |||
| borderColor: 'warning.main', | |||
| color: 'warning.main' | |||
| }} | |||
| title="Report missing or bad items" | |||
| > | |||
| {t("Issue")} | |||
| </Button> | |||
| </Stack> | |||
| )} | |||
| </TableCell> | |||
| {/*<TableCell align="right">{lot.availableQty.toLocaleString()}</TableCell>*/} | |||
| <TableCell align="right">{calculateRemainingAvailableQty(lot).toLocaleString()}</TableCell> | |||
| {/* ✅ Action 欄位:區分 noLot / 正常 lot */} | |||
| <TableCell align="center"> | |||
| <Stack direction="column" spacing={1} alignItems="center"> | |||
| {lot.noLot ? ( | |||
| // 沒有批次:只允許 Issue(報告 miss) | |||
| <Button | |||
| variant="outlined" | |||
| size="small" | |||
| onClick={() => { | |||
| if (lot.stockOutLineId) { | |||
| onIssueNoLotStockOutLine(lot.stockOutLineId); | |||
| } | |||
| }} | |||
| disabled={ | |||
| lot.stockOutLineStatus === 'completed' || | |||
| lot.lotAvailability === 'rejected' | |||
| } | |||
| sx={{ | |||
| fontSize: '0.7rem', | |||
| py: 0.5, | |||
| minHeight: '28px', | |||
| minWidth: '60px', | |||
| borderColor: 'warning.main', | |||
| color: 'warning.main' | |||
| }} | |||
| title="Report missing items (no lot available)" | |||
| > | |||
| {t("Issue")} | |||
| </Button> | |||
| ) : ( | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => { | |||
| if (selectedRowId && lot.lotId != null) { | |||
| onSubmitPickQty(selectedRowId, lot.lotId); | |||
| } | |||
| }} | |||
| disabled={ | |||
| lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected' || | |||
| !selectedRowId || | |||
| !pickQtyData[selectedRowId]?.[lot.lotId ?? 0] || | |||
| !lot.stockOutLineStatus || | |||
| !['pending','checked', 'partially_completed'].includes( | |||
| lot.stockOutLineStatus.toLowerCase() | |||
| ) | |||
| } | |||
| sx={{ | |||
| fontSize: '0.75rem', | |||
| py: 0.5, | |||
| minHeight: '28px' | |||
| }} | |||
| > | |||
| {t("Submit")} | |||
| </Button> | |||
| )} | |||
| </Stack> | |||
| <TableCell align="center"> | |||
| <Stack direction="column" spacing={1} alignItems="center"> | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => { | |||
| if (selectedRowId) { | |||
| onSubmitPickQty(selectedRowId, lot.lotId); | |||
| } | |||
| }} | |||
| disabled={ | |||
| (lot.lotAvailability === 'expired' || | |||
| lot.lotAvailability === 'status_unavailable' || | |||
| lot.lotAvailability === 'rejected') || // 添加 rejected | |||
| !pickQtyData[selectedRowId!]?.[lot.lotId] || | |||
| !lot.stockOutLineStatus || | |||
| !['pending','checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase()) | |||
| } | |||
| // Allow submission for available AND insufficient_stock lots | |||
| sx={{ | |||
| fontSize: '0.75rem', | |||
| py: 0.5, | |||
| minHeight: '28px' | |||
| }} | |||
| > | |||
| {t("Submit")} | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| </TableRow> | |||
| )) | |||
| @@ -802,14 +831,50 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| {/* Status Messages Display */} | |||
| {paginatedLotTableData.length > 0 && ( | |||
| <Box sx={{ mt: 2, p: 2, backgroundColor: 'grey.50', borderRadius: 1 }}> | |||
| {paginatedLotTableData.map((lot, index) => ( | |||
| <Box key={lot.id} sx={{ mb: 1 }}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| <strong>{t("Lot")} {lot.lotNo}:</strong> {getStatusMessage(lot)} | |||
| </Typography> | |||
| </Box> | |||
| ))} | |||
| </Box> | |||
| )} | |||
| {/* Status message & pagination & modals 保持原有程式不變(略) */} | |||
| <TablePagination | |||
| component="div" | |||
| count={prepareLotTableData.length} | |||
| page={lotTablePagingController.pageNum} | |||
| rowsPerPage={lotTablePagingController.pageSize} | |||
| onPageChange={handleLotTablePageChange} | |||
| onRowsPerPageChange={handleLotTablePageSizeChange} | |||
| rowsPerPageOptions={[10, 25, 50]} | |||
| labelRowsPerPage={t("Rows per page")} | |||
| labelDisplayedRows={({ from, to, count }) => | |||
| `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` | |||
| } | |||
| /> | |||
| {/* QR Code Modal */} | |||
| <QrCodeModal | |||
| open={qrModalOpen} | |||
| onClose={handleCloseQrModal} | |||
| lot={selectedLotForQr} | |||
| onQrCodeSubmit={handleQrCodeSubmit} | |||
| /> | |||
| open={qrModalOpen} | |||
| onClose={() => { | |||
| setQrModalOpen(false); | |||
| setSelectedLotForQr(null); | |||
| stopScan(); | |||
| resetScan(); | |||
| }} | |||
| lot={selectedLotForQr} | |||
| onQrCodeSubmit={handleQrCodeSubmit} | |||
| /> | |||
| {/* Pick Execution Form Modal */} | |||
| {pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && ( | |||
| <PickExecutionForm | |||
| open={pickExecutionFormOpen} | |||