| @@ -13,6 +13,8 @@ export interface SaveJo { | |||
| planEnd: string; | |||
| reqQty: number; | |||
| type: string; | |||
| //jobType?: string; | |||
| jobTypeId?: number; | |||
| } | |||
| export interface SaveJoResponse { | |||
| @@ -703,6 +705,7 @@ export const isCorrectMachineUsed = async (machineCode: string) => { | |||
| export const fetchJos = cache(async (data?: SearchJoResultRequest) => { | |||
| const queryStr = convertObjToURLSearchParams(data) | |||
| console.log("queryStr", queryStr) | |||
| const response = serverFetchJson<SearchJoResultResponse>( | |||
| `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`, | |||
| { | |||
| @@ -116,8 +116,15 @@ export interface GetPickOrderLineInfo { | |||
| suggestedList: any[]; | |||
| pickedQty: number; | |||
| noLotLines: NoLotLineDto[]; | |||
| } | |||
| export interface NoLotLineDto { | |||
| stockOutLineId: number; | |||
| status: string; | |||
| qty: number; | |||
| created: string; | |||
| modified: string; | |||
| } | |||
| export interface CurrentInventoryItemInfo { | |||
| id: number; | |||
| code: string; | |||
| @@ -743,18 +750,26 @@ export const fetchPickOrderDetails = cache(async (ids: string) => { | |||
| ); | |||
| }); | |||
| export interface PickOrderLotDetailResponse { | |||
| lotId: number; | |||
| lotNo: string; | |||
| expiryDate: string; | |||
| location: string; | |||
| stockUnit: string; | |||
| inQty: number; | |||
| availableQty: number; | |||
| lotId: number | null; // ✅ 改为可空 | |||
| lotNo: string | null; // ✅ 改为可空 | |||
| expiryDate: string | null; // ✅ 改为可空 | |||
| location: string | null; // ✅ 改为可空 | |||
| stockUnit: string | null; | |||
| inQty: number | null; | |||
| availableQty: number | null; // ✅ 改为可空 | |||
| requiredQty: number; | |||
| actualPickQty: number; | |||
| suggestedPickLotId: number; | |||
| lotStatus: string; | |||
| lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; | |||
| suggestedPickLotId: number | null; | |||
| lotStatus: string | null; | |||
| lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable' | 'rejected'; | |||
| stockOutLineId: number | null; // ✅ 添加 | |||
| stockOutLineStatus: string | null; // ✅ 添加 | |||
| stockOutLineQty: number | null; // ✅ 添加 | |||
| totalPickedByAllPickOrders: number | null; // ✅ 添加 | |||
| remainingAfterAllPickOrders: number | null; // ✅ 添加 | |||
| noLot: boolean; // ✅ 关键:添加 noLot 字段 | |||
| outQty?: number; // ✅ 添加 | |||
| holdQty?: number; // ✅ 添加 | |||
| } | |||
| interface ALLPickOrderLotDetailResponse { | |||
| // Pick Order Information | |||
| @@ -423,7 +423,7 @@ const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => { | |||
| return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>; | |||
| }; | |||
| const NoRowsOverlay: React.FC = () => { | |||
| const { t } = useTranslation("home"); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| return ( | |||
| <Box | |||
| display="flex" | |||
| @@ -3,7 +3,7 @@ import { JoDetail } from "@/app/api/jo"; | |||
| import { SaveJo, manualCreateJo } from "@/app/api/jo/actions"; | |||
| import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil"; | |||
| import { Check } from "@mui/icons-material"; | |||
| import { Autocomplete, Box, Button, Card, Grid, Modal, Stack, TextField, Typography } from "@mui/material"; | |||
| import { Autocomplete, Box, Button, Card, Grid, Modal, Stack, TextField, Typography ,FormControl, InputLabel, Select, MenuItem} from "@mui/material"; | |||
| import { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import dayjs, { Dayjs } from "dayjs"; | |||
| @@ -52,6 +52,10 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||
| const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => { | |||
| data.type = "manual" | |||
| if (data.planStart) { | |||
| const dateDayjs = dateStringToDayjs(data.planStart) | |||
| data.planStart = dayjsToDateTimeString(dateDayjs.startOf('day')) | |||
| } | |||
| const response = await manualCreateJo(data) | |||
| if (response) { | |||
| onSearch(); | |||
| @@ -164,17 +168,20 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12} sm={12} md={6}> | |||
| <TextField | |||
| {...register("reqQty", { | |||
| required: "Req. Qty. required!", | |||
| validate: (value) => value > 0 | |||
| })} | |||
| label={t("Job Type")} | |||
| fullWidth | |||
| error={Boolean(errors.reqQty)} | |||
| variant="outlined" | |||
| type="number" | |||
| /> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Job Type")}</InputLabel> | |||
| <Select | |||
| label={t("Job Type")} | |||
| defaultValue="" | |||
| > | |||
| <MenuItem value="1">{t("FG")}</MenuItem> | |||
| <MenuItem value="2">{t("WIP")}</MenuItem> | |||
| <MenuItem value="3">{t("R&D")}</MenuItem> | |||
| <MenuItem value="4">{t("STF")}</MenuItem> | |||
| <MenuItem value="5">{t("Other")}</MenuItem> | |||
| </Select> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={12} sm={12} md={6}> | |||
| <Controller | |||
| @@ -192,10 +199,13 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||
| } | |||
| }} | |||
| render={({ field, fieldState: { error } }) => ( | |||
| <DateTimePicker | |||
| // <DateTimePicker | |||
| <DatePicker | |||
| label={t("Plan Start")} | |||
| views={['year','month','day','hours', 'minutes', 'seconds']} | |||
| format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | |||
| // views={['year','month','day','hours', 'minutes', 'seconds']} | |||
| //format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | |||
| format={OUTPUT_DATE_FORMAT} | |||
| value={field.value ? dateStringToDayjs(field.value) : null} | |||
| onChange={(newValue: Dayjs | null) => { | |||
| handleDateTimePickerChange(newValue, field.onChange) | |||
| }} | |||
| @@ -292,10 +292,10 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo }) => | |||
| const searchDataByPage = useCallback(() => { | |||
| refetchData(inputs, "paging"); | |||
| }, [inputs]) | |||
| }, [inputs,refetchData]) | |||
| useEffect(() => { | |||
| searchDataByPage(); | |||
| }, [pagingController]); | |||
| }, [pagingController,searchDataByPage ]); | |||
| const getButtonSx = (jo : JobOrder) => { // TODO put it in ActionButtons.ts | |||
| const joStatus = jo.status?.toLowerCase(); | |||
| @@ -474,7 +474,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; | |||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||
| <Tab label={t("Pick Order Detail")} iconPosition="end" /> | |||
| <Tab label={t("Complete Job Order Record")} iconPosition="end" /> | |||
| <Tab label={t("Job Order Match")} iconPosition="end" /> | |||
| {/* <Tab label={t("Job Order Match")} iconPosition="end" /> */} | |||
| {/* <Tab label={t("Finished Job Order Record")} iconPosition="end" /> */} | |||
| </Tabs> | |||
| @@ -486,7 +486,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; | |||
| }}> | |||
| {tabIndex === 0 && <JobPickExecution filterArgs={filterArgs} />} | |||
| {tabIndex === 1 && <CompleteJobOrderRecord filterArgs={filterArgs} printerCombo={printerCombo} />} | |||
| {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />} | |||
| {/* {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />} */} | |||
| {/* {tabIndex === 3 && <FInishedJobOrderRecord filterArgs={filterArgs} />} */} | |||
| </Box> | |||
| </Box> | |||
| @@ -28,10 +28,10 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import | |||
| import PickExecutionForm from "./PickExecutionForm"; | |||
| interface LotPickData { | |||
| id: number; | |||
| lotId: number; | |||
| lotNo: string; | |||
| lotId: number | null; | |||
| lotNo: string | null; | |||
| expiryDate: string; | |||
| location: string; | |||
| location: string | null; | |||
| stockUnit: string; | |||
| inQty: number; | |||
| availableQty: number; | |||
| @@ -45,6 +45,7 @@ interface LotPickData { | |||
| stockOutLineId?: number; | |||
| stockOutLineStatus?: string; | |||
| stockOutLineQty?: number; | |||
| noLot?: boolean; | |||
| } | |||
| interface PickQtyData { | |||
| @@ -60,7 +61,7 @@ interface LotTableProps { | |||
| pickQtyData: PickQtyData; | |||
| selectedLotRowId: string | null; | |||
| selectedLotId: number | null; | |||
| onLotSelection: (uniqueLotId: string, lotId: number) => void; | |||
| onLotSelection: (uniqueLotId: string, lotId: number | null) => void; | |||
| onPickQtyChange: (lineId: number, lotId: number, value: number) => void; | |||
| onSubmitPickQty: (lineId: number, lotId: number) => void; | |||
| onCreateStockOutLine: (inventoryLotLineId: number) => void; | |||
| @@ -75,6 +76,7 @@ interface LotTableProps { | |||
| generateInputBody: () => any; | |||
| onDataRefresh: () => Promise<void>; | |||
| onLotDataRefresh: () => Promise<void>; | |||
| onIssueNoLotStockOutLine: (stockOutLineId: number) => void; | |||
| } | |||
| // QR Code Modal Component | |||
| @@ -236,7 +238,7 @@ const QrCodeModal: React.FC<{ | |||
| const timer = setTimeout(() => { | |||
| setQrScanSuccess(true); | |||
| onQrCodeSubmit(lot.lotNo); | |||
| onQrCodeSubmit(lot.lotNo??''); | |||
| onClose(); | |||
| setManualInput(''); | |||
| setManualInputError(false); | |||
| @@ -388,30 +390,28 @@ 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,23 +428,21 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| default: | |||
| return t("Please finish QR code scan and pick order."); | |||
| } | |||
| }, []); | |||
| }, [t]); | |||
| const prepareLotTableData = useMemo(() => { | |||
| return lotData.map((lot) => ({ | |||
| ...lot, | |||
| id: lot.lotId, | |||
| id: lot.lotId ?? lot.id, | |||
| })); | |||
| }, [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, | |||
| @@ -459,87 +457,33 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| pageSize: newPageSize, | |||
| }); | |||
| }, []); | |||
| const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { | |||
| 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 | |||
| if (!selectedRowId || lot.noLot) return lot.availableQty; | |||
| const actualPickQty = pickQtyData[selectedRowId]?.[lot.lotId ?? 0] || 0; | |||
| const remainingQty = (lot.inQty || 0) - (lot.outQty || 0) - actualPickQty; | |||
| 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}`); | |||
| if (!selectedLotForQr.stockOutLineId) { | |||
| console.error("No stock out line ID found for this lot"); | |||
| alert("No stock out line found for this lot. Please contact administrator."); | |||
| return; | |||
| } | |||
| // Store the required quantity before creating stock out line | |||
| const requiredQty = selectedLotForQr.requiredQty; | |||
| const lotId = selectedLotForQr.lotId; | |||
| try { | |||
| // Update stock out line status to 'checked' (QR scan completed) | |||
| const stockOutLineUpdate = await updateStockOutLineStatus({ | |||
| id: selectedLotForQr.stockOutLineId, | |||
| status: 'checked', | |||
| qty: selectedLotForQr.stockOutLineQty || 0 | |||
| }); | |||
| console.log(" Stock out line updated to 'checked':", stockOutLineUpdate); | |||
| // Close modal | |||
| setQrModalOpen(false); | |||
| setSelectedLotForQr(null); | |||
| if (onLotDataRefresh) { | |||
| await onLotDataRefresh(); | |||
| } | |||
| // Set pick quantity AFTER stock out line update is complete | |||
| if (selectedRowId) { | |||
| // Add a small delay to ensure the data refresh is complete | |||
| setTimeout(() => { | |||
| onPickQtyChange(selectedRowId, lotId, requiredQty); | |||
| console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`); | |||
| }, 500); // 500ms delay to ensure refresh is complete | |||
| } | |||
| // Show success message | |||
| console.log("Stock out line updated successfully!"); | |||
| } catch (error) { | |||
| console.error("❌ Error updating stock out line status:", error); | |||
| alert("Failed to update lot status. Please try again."); | |||
| } | |||
| } else { | |||
| // Handle case where lot numbers don't match | |||
| console.error("QR scan mismatch:", { scanned: lotNo, expected: selectedLotForQr?.lotNo }); | |||
| alert(`QR scan mismatch! Expected: ${selectedLotForQr?.lotNo}, Scanned: ${lotNo}`); | |||
| } | |||
| }, [selectedLotForQr, selectedRowId, onPickQtyChange]); | |||
| // 添加 PickExecutionForm 相关的状态 | |||
| // ... 保持你原本的 QR 提交邏輯 ... | |||
| }, [selectedLotForQr, selectedRowId, onPickQtyChange, onLotDataRefresh]); | |||
| // 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); | |||
| @@ -560,8 +504,6 @@ 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); | |||
| @@ -574,7 +516,6 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| setPickExecutionFormOpen(false); | |||
| setSelectedLotForExecutionForm(null); | |||
| // 刷新数据 | |||
| if (onDataRefresh) { | |||
| await onDataRefresh(); | |||
| } | |||
| @@ -600,14 +541,7 @@ 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> | |||
| @@ -623,7 +557,7 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| ) : ( | |||
| paginatedLotTableData.map((lot, index) => ( | |||
| <TableRow | |||
| key={lot.id} | |||
| key={lot.noLot ? `noLot_${lot.stockOutLineId}_${index}` : `lot_${lot.lotId}_${index}`} | |||
| sx={{ | |||
| backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit', | |||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1, | |||
| @@ -633,36 +567,30 @@ const LotTable: React.FC<LotTableProps> = ({ | |||
| }} | |||
| > | |||
| <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> | |||
| <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> | |||
| <TableCell> | |||
| <Box> | |||
| <Typography | |||
| sx={{ | |||
| color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit', | |||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1 | |||
| }} | |||
| > | |||
| {lot.lotNo} | |||
| <Typography> | |||
| {lot.noLot | |||
| ? t('⚠️ No Stock Available') | |||
| : 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> | |||
| @@ -673,154 +601,76 @@ 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"> | |||
| {/* 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> | |||
| <TableCell align="center"> | |||
| {/* 已掃碼後的實際數量 + Issue 按鈕(保持原有邏輯,略) */} | |||
| {/* 對 noLot 行而言這裡通常保持為 0/禁用 */} | |||
| </TableCell> | |||
| <TableCell align="right"> | |||
| {calculateRemainingAvailableQty(lot).toLocaleString()} | |||
| </TableCell> | |||
| <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> | |||
| {/* ✅ 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> | |||
| </TableRow> | |||
| )) | |||
| @@ -828,50 +678,9 @@ 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> | |||
| )} | |||
| <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={() => { | |||
| setQrModalOpen(false); | |||
| setSelectedLotForQr(null); | |||
| stopScan(); | |||
| resetScan(); | |||
| }} | |||
| lot={selectedLotForQr} | |||
| onQrCodeSubmit={handleQrCodeSubmit} | |||
| /> | |||
| {/* Status message & pagination & modals 保持原有程式不變(略) */} | |||
| {/* Pick Execution Form Modal */} | |||
| {pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && ( | |||
| <PickExecutionForm | |||
| open={pickExecutionFormOpen} | |||
| @@ -61,13 +61,13 @@ import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { fetchQcItemCheck, fetchPickOrderQcResult } from "@/app/api/qc/actions"; | |||
| import { PurchaseQcResult } from "@/app/api/po/actions"; | |||
| import PickQcStockInModalVer2 from "./PickQcStockInModalVer3"; | |||
| //import PickQcStockInModalVer2 from "./PickQcStockInModalVer3"; | |||
| import { fetchPickOrderLineLotDetails, PickOrderLotDetailResponse } from "@/app/api/pickOrder/actions"; | |||
| import SearchResults, { Column } from "../SearchResults/SearchResults"; | |||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | |||
| import SearchBox, { Criterion } from "../SearchBox"; | |||
| import dayjs from "dayjs"; | |||
| import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; | |||
| import { CreateStockOutLine , NoLotLineDto} from "@/app/api/pickOrder/actions"; | |||
| import LotTable from './LotTable'; | |||
| import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component | |||
| import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; | |||
| @@ -80,10 +80,10 @@ interface Props { | |||
| interface LotPickData { | |||
| id: number; | |||
| lotId: number; | |||
| lotNo: string; | |||
| lotId: number | null; | |||
| lotNo: string | null; | |||
| expiryDate: string; | |||
| location: string; | |||
| location: string | null; | |||
| stockUnit: string; | |||
| inQty: number; | |||
| outQty: number; | |||
| @@ -93,10 +93,12 @@ interface LotPickData { | |||
| requiredQty: number; | |||
| actualPickQty: number; | |||
| lotStatus: string; | |||
| noLot?: boolean; | |||
| lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; | |||
| stockOutLineId?: number; | |||
| stockOutLineStatus?: string; | |||
| stockOutLineQty?: number; | |||
| } | |||
| interface PickQtyData { | |||
| @@ -576,7 +578,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| }); | |||
| }, []); | |||
| const handleLotSelection = useCallback((uniqueLotId: string, lotId: number) => { | |||
| const handleLotSelection = useCallback((uniqueLotId: string, lotId: number | null) => { | |||
| console.log("=== DEBUG: Lot Selection ==="); | |||
| console.log("uniqueLotId:", uniqueLotId); | |||
| console.log("lotId (inventory lot line ID):", lotId); | |||
| @@ -589,10 +591,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| setSelectedLotId(null); | |||
| } else { | |||
| setSelectedLotRowId(uniqueLotId); | |||
| setSelectedLotId(lotId); | |||
| setSelectedLotId(lotId ?? null); | |||
| } | |||
| }, [selectedLotRowId]); | |||
| }, [selectedLotRowId, lotData]); | |||
| const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => { | |||
| setSelectedRowId(lineId); | |||
| @@ -604,35 +605,42 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| try { | |||
| const lotDetails = await fetchPickOrderLineLotDetails(lineId); | |||
| console.log("lineId:", lineId); | |||
| console.log("Lot details from API:", lotDetails); | |||
| const realLotData: LotPickData[] = lotDetails.map((lot: any) => ({ | |||
| id: lot.id, | |||
| // ✅ 直接使用 API 返回的数据,包括 noLot 字段 | |||
| const allLotData: LotPickData[] = lotDetails.map((lot: PickOrderLotDetailResponse) => ({ | |||
| id: lot.lotId ?? -(lineId * 1000 + Math.random()), // 如果 lotId 为 null,生成一个临时 ID | |||
| lotId: lot.lotId, | |||
| lotNo: lot.lotNo, | |||
| expiryDate: lot.expiryDate ? dayjs(lot.expiryDate).format(OUTPUT_DATE_FORMAT) : 'N/A', | |||
| location: lot.location, | |||
| stockUnit: lot.stockUnit, | |||
| inQty: lot.inQty, | |||
| outQty: lot.outQty, | |||
| holdQty: lot.holdQty, | |||
| totalPickedByAllPickOrders: lot.totalPickedByAllPickOrders, | |||
| availableQty: lot.availableQty, | |||
| requiredQty: lot.requiredQty, | |||
| expiryDate: lot.expiryDate ? dayjs(lot.expiryDate).format(OUTPUT_DATE_FORMAT) : t("N/A"), | |||
| location: lot.location?? t("N/A"), | |||
| stockUnit: lot.stockUnit || "", | |||
| inQty: lot.inQty ?? 0, | |||
| outQty: lot.outQty ?? 0, | |||
| holdQty: lot.holdQty ?? 0, | |||
| totalPickedByAllPickOrders: lot.totalPickedByAllPickOrders ?? 0, | |||
| availableQty: lot.availableQty ?? 0, | |||
| requiredQty: lot.requiredQty ?? 0, | |||
| actualPickQty: lot.actualPickQty || 0, | |||
| lotStatus: lot.lotStatus, | |||
| lotStatus: lot.lotStatus || "unavailable", | |||
| lotAvailability: lot.lotAvailability, | |||
| stockOutLineId: lot.stockOutLineId, | |||
| stockOutLineStatus: lot.stockOutLineStatus, | |||
| stockOutLineQty: lot.stockOutLineQty | |||
| stockOutLineId: lot.stockOutLineId ?? undefined, | |||
| stockOutLineStatus: lot.stockOutLineStatus ?? undefined, | |||
| stockOutLineQty: lot.stockOutLineQty ?? undefined, | |||
| noLot: lot.noLot, // ✅ 关键:使用 API 返回的 noLot 字段 | |||
| })); | |||
| setLotData(realLotData); | |||
| console.log("✅ Combined lot data:", allLotData); | |||
| console.log(" - Total rows:", allLotData.length); | |||
| console.log(" - No-lot rows:", allLotData.filter(l => l.noLot).length); | |||
| setLotData(allLotData); | |||
| } catch (error) { | |||
| console.error("Error fetching lot details:", error); | |||
| setLotData([]); | |||
| } | |||
| }, []); | |||
| }, []); | |||
| const prepareLotTableData = useMemo(() => { | |||
| return lotData.map((lot) => ({ | |||
| @@ -747,19 +755,50 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| setShowInputBody(true); | |||
| }, []); | |||
| const generateInputBody = useCallback((): CreateStockOutLine | null => { | |||
| if (!selectedLotForInput || !selectedRowId || !selectedRow || !pickOrderDetails?.consoCode) { | |||
| const generateInputBody = useCallback(() => { | |||
| if (!selectedLotForInput || !selectedRowId || !pickOrderDetails?.consoCode) { | |||
| return null; | |||
| } | |||
| // ✅ 處理 lotId 可能為 null 的情況 | |||
| if (selectedLotForInput.lotId === null) { | |||
| return null; // no-lot 行不能創建 stock out line | |||
| } | |||
| return { | |||
| consoCode: pickOrderDetails.consoCode, | |||
| pickOrderLineId: selectedRowId, | |||
| inventoryLotLineId: selectedLotForInput.lotId, | |||
| inventoryLotLineId: selectedLotForInput.lotId, // ✅ 現在確定不是 null | |||
| qty: 0.0 | |||
| }; | |||
| }, [selectedLotForInput, selectedRowId, selectedRow, pickOrderDetails?.consoCode]); | |||
| // 在 handleSubmitPickQty 函数附近添加新函数 | |||
| const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number) => { | |||
| if (!stockOutLineId) { | |||
| console.error("Cannot issue: stockOutLineId is missing"); | |||
| return; | |||
| } | |||
| try { | |||
| console.log(`提交 no-lot stock out line: ${stockOutLineId}`); | |||
| // ✅ 直接完成 no-lot 的 stock out line(设置状态为 completed,qty 为 0) | |||
| const result = await updateStockOutLineStatus({ | |||
| id: stockOutLineId, | |||
| status: 'completed', | |||
| qty: 0 // no-lot 行没有实际数量 | |||
| }); | |||
| console.log("✅ No-lot stock out line completed:", result); | |||
| // 刷新数据 | |||
| if (selectedRowId) { | |||
| handleRowSelect(selectedRowId); | |||
| } | |||
| } catch (error) { | |||
| console.error("❌ Error completing no-lot stock out line:", error); | |||
| } | |||
| }, [selectedRowId, handleRowSelect]); | |||
| const handleCreateStockOutLine = useCallback(async (inventoryLotLineId: number) => { | |||
| if (!selectedRowId || !pickOrderDetails?.consoCode) { | |||
| console.error("Missing required data for creating stock out line."); | |||
| @@ -966,6 +1005,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| onLotDataRefresh={handleLotDataRefresh} | |||
| onLotSelectForInput={handleLotSelectForInput} | |||
| showInputBody={showInputBody} | |||
| onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine} | |||
| setShowInputBody={setShowInputBody} | |||
| selectedLotForInput={selectedLotForInput} | |||
| generateInputBody={generateInputBody} | |||
| @@ -1038,7 +1078,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| </Grid> | |||
| )} | |||
| {/* QC Modal */} | |||
| { /* | |||
| {selectedItemForQc && qcModalOpen && ( | |||
| <PickQcStockInModalVer2 | |||
| open={qcModalOpen} | |||
| @@ -1058,6 +1100,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| lotData={lotData} | |||
| /> | |||
| )} | |||
| */} | |||
| </Stack> | |||
| </FormProvider> | |||
| ); | |||
| @@ -26,10 +26,10 @@ import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| interface LotPickData { | |||
| id: number; | |||
| lotId: number; | |||
| lotNo: string; | |||
| lotId: number | null; | |||
| lotNo: string | null; | |||
| expiryDate: string; | |||
| location: string; | |||
| location: string | null; | |||
| stockUnit: string; | |||
| inQty: number; | |||
| outQty: number; | |||
| @@ -43,6 +43,7 @@ interface LotPickData { | |||
| stockOutLineId?: number; | |||
| stockOutLineStatus?: string; | |||
| stockOutLineQty?: number; | |||
| noLot?: boolean; | |||
| } | |||
| interface PickExecutionFormProps { | |||
| @@ -144,9 +145,9 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => { | |||
| itemId: selectedPickOrderLine.itemId, | |||
| itemCode: selectedPickOrderLine.itemCode, | |||
| itemDescription: selectedPickOrderLine.itemName, | |||
| lotId: selectedLot.lotId, | |||
| lotNo: selectedLot.lotNo, | |||
| storeLocation: selectedLot.location, | |||
| lotId: selectedLot.lotId??undefined, | |||
| lotNo: selectedLot.lotNo ??undefined, | |||
| storeLocation: selectedLot.location?? '', | |||
| requiredQty: selectedLot.requiredQty, | |||
| actualPickQty: selectedLot.actualPickQty || 0, | |||
| missQty: 0, | |||
| @@ -272,7 +272,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| }}> | |||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||
| <Tab label={t("Select Items")} iconPosition="end" /> | |||
| <Tab label={t("Select Job Order Items")} iconPosition="end" /> | |||
| {/* <Tab label={t("Select Job Order Items")} iconPosition="end" /> */} | |||
| <Tab label={t("Assign")} iconPosition="end" /> | |||
| <Tab label={t("Release")} iconPosition="end" /> | |||
| <Tab label={t("Pick Execution")} iconPosition="end" /> | |||
| @@ -283,7 +283,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| <Box sx={{ | |||
| p: 2 | |||
| }}> | |||
| {tabIndex === 4 && <PickExecution filterArgs={filterArgs} />} | |||
| {tabIndex === 3 && <PickExecution filterArgs={filterArgs} />} | |||
| {tabIndex === 0 && ( | |||
| <NewCreateItem | |||
| filterArgs={filterArgs} | |||
| @@ -291,9 +291,9 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| onPickOrderCreated={handlePickOrderCreated} | |||
| /> | |||
| )} | |||
| {tabIndex === 1 && <Jobcreatitem filterArgs={filterArgs} />} | |||
| {tabIndex === 2 && <AssignAndRelease filterArgs={filterArgs} />} | |||
| {tabIndex === 3 && <AssignTo filterArgs={filterArgs} />} | |||
| {/* {tabIndex === 1 && <Jobcreatitem filterArgs={filterArgs} />} */} | |||
| {tabIndex === 1 && <AssignAndRelease filterArgs={filterArgs} />} | |||
| {tabIndex === 2 && <AssignTo filterArgs={filterArgs} />} | |||
| </Box> | |||
| </Box> | |||
| ); | |||
| @@ -1,3 +1,4 @@ | |||
| /* | |||
| "use client"; | |||
| import { GetPickOrderLineInfo, updateStockOutLineStatus } from "@/app/api/pickOrder/actions"; | |||
| @@ -24,7 +25,7 @@ import { | |||
| import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { dummyQCData } from "../PoDetail/dummyQcTemplate"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { GridColDef } from "@mui/x-data-grid"; | |||
| import { submitDialogWithWarning } from "../Swal/CustomAlerts"; | |||
| @@ -636,7 +637,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||
| /> | |||
| {/* Combirne options 2 & 3 into one */} | |||
| <FormControlLabel | |||
| value="2" | |||
| control={<Radio />} | |||
| @@ -649,7 +650,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||
| </FormControl> | |||
| </Grid> | |||
| {/* Show escalation component when QC Decision = 2 (Report and Re-pick) */} | |||
| <Grid item xs={12} sx={{ mt: 2 }}> | |||
| @@ -680,4 +681,5 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||
| ); | |||
| }; | |||
| export default PickQcStockInModalVer3; | |||
| export default PickQcStockInModalVer3; | |||
| */ | |||
| @@ -199,6 +199,15 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| value={processData?.itemCode+"-"+processData?.itemName || ""} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Job Type")} | |||
| fullWidth | |||
| disabled={true} | |||
| // value={processData?.jobType || ""} | |||
| value={t("N/A")} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Req. Qty")} | |||
| @@ -221,7 +230,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| label={t("Production Priority")} | |||
| fullWidth | |||
| disabled={true} | |||
| value={processData?.productionPriority || "0"} | |||
| value={processData?.productionPriority ||processData?.isDense === 0 ? "50" : processData?.productionPriority || "0"} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| @@ -229,10 +238,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance")} | |||
| fullWidth | |||
| disabled={true} | |||
| value={processData?.isDark + " | " + processData?.isDense + " | " + processData?.isFloat + " | " | |||
| //+ processData?.scrapRate + " | " + processData?.allergicSubstance | |||
| + " " + " | " + " " | |||
| || ""} | |||
| value={`${processData?.isDark == null || processData?.isDark === "" ? t("N/A") : processData.isDark} | ${processData?.isDense == null || processData?.isDense === "" || processData?.isDense === 0 ? t("N/A") : processData.isDense} | ${processData?.isFloat == null || processData?.isFloat === "" ? t("N/A") : processData.isFloat} | ${t("N/A")} | ${t("N/A")}`} | |||
| /> | |||
| </Grid> | |||
| @@ -277,7 +283,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| align: "left", | |||
| headerAlign: "left", | |||
| type: "number", | |||
| }, | |||
| { | |||
| field: "itemCode", | |||
| @@ -323,7 +329,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| headerAlign: "right", | |||
| type: "number", | |||
| }, | |||
| /* | |||
| { | |||
| field: "seqNoRemark", | |||
| headerName: t("Seq No Remark"), | |||
| @@ -332,6 +338,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| headerAlign: "left", | |||
| type: "string", | |||
| }, | |||
| */ | |||
| { | |||
| field: "stockStatus", | |||
| headerName: t("Stock Status"), | |||
| @@ -426,13 +433,12 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||
| <Tab label={t("Job Order Info")} /> | |||
| <Tab label={t("BoM Material")} /> | |||
| {!fromJosave && ( | |||
| <Tab label={t("Production Process")} /> | |||
| )} | |||
| {!fromJosave && ( | |||
| <Tab label={t("Production Process Line Remark")} /> | |||
| )} | |||
| {!fromJosave && ( | |||
| {!fromJosave && ( | |||
| <Tab label={t("Matching Stock")} /> | |||
| )} | |||
| </Tabs> | |||
| @@ -453,8 +459,11 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| /> | |||
| )} | |||
| {tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />} | |||
| {tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />} | |||
| </Box> | |||
| </Box> | |||
| ); | |||
| @@ -107,7 +107,7 @@ | |||
| "Pick Order Code": "提料單編號", | |||
| "Target Date": "需求日期", | |||
| "Lot Required Pick Qty": "批號需求數量", | |||
| "Job Order Match": "工單匹配", | |||
| "Job Order Match": "工單對料", | |||
| "All Pick Order Lots": "所有提料單批號", | |||
| "Row per page": "每頁行數", | |||
| "No data available": "沒有資料", | |||
| @@ -167,18 +167,19 @@ | |||
| "View": "查看", | |||
| "Back": "返回", | |||
| "BoM Material": "物料清單", | |||
| "N/A": "不適用", | |||
| "Is Dark | Dense | Float | Scrap Rate | Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原", | |||
| "Item Code": "物料編號", | |||
| "Item Name": "物料名稱", | |||
| "Job Order Info": "工單信息", | |||
| "Matching Stock": "匹配庫存", | |||
| "Matching Stock": "工單對料", | |||
| "No data found": "沒有找到資料", | |||
| "Production Priority": "生產優先級", | |||
| "Production Process": "工藝流程", | |||
| "Production Process Line Remark": "工藝明細", | |||
| "Remark": "明細", | |||
| "Req. Qty": "需求數量", | |||
| "Seq No": "序號", | |||
| "Seq No": "加入步驟", | |||
| "Seq No Remark": "序號明細", | |||
| "Stock Available": "庫存可用", | |||
| "Stock Status": "庫存狀態", | |||
| @@ -200,7 +201,7 @@ | |||
| "Step Information": "步驟信息", | |||
| "Stop": "停止", | |||
| "Putaway Detail": "上架詳情", | |||
| "Lines with sufficient stock: ": "足夠庫存:", | |||
| "Lines with insufficient stock: ": "庫存不足:", | |||
| "Lines with sufficient stock: ": "可提料項目數量: ", | |||
| "Lines with insufficient stock: ": "未能提料項目數量: ", | |||
| "Total lines: ": "總數量:" | |||
| } | |||
| @@ -328,11 +328,18 @@ | |||
| "Submit & Start": "提交並開始", | |||
| "Total Steps": "總步驟數", | |||
| "Unknown": "", | |||
| "Job Type": "工單類型", | |||
| "WIP": "半成品", | |||
| "R&D": "研發", | |||
| "STF": "樣品", | |||
| "Other": "其他", | |||
| "Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.", | |||
| "View": "查看", | |||
| "Back": "返回", | |||
| "N/A": "不適用", | |||
| "BoM Material": "半成品/成品清單", | |||
| "Is Dark | Dense | Float": "顔色深淺度 | 濃淡 | 浮沉", | |||
| "Is Dark | Dense | Float| Scrap Rate| Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原", | |||
| "Item Code": "物料編號", | |||
| "Item Name": "物料名稱", | |||
| "Enter the number of cartons: ": "請輸入箱數:", | |||
| @@ -344,7 +351,7 @@ | |||
| "Print Pick Record": "打印板頭紙", | |||
| "Printed Successfully.": "成功列印", | |||
| "Job Order Info": "工單信息", | |||
| "Matching Stock": "匹配庫存", | |||
| "Matching Stock": "工單對料", | |||
| "No data found": "沒有找到資料", | |||
| "Print Quantity": "打印數量", | |||
| "Select Printer": "選擇打印機", | |||
| @@ -356,7 +363,7 @@ | |||
| "Production Process Line Remark": "工藝明細", | |||
| "Remark": "明細", | |||
| "Req. Qty": "需求數量", | |||
| "Seq No": "序號", | |||
| "Seq No": "加入步驟", | |||
| "Seq No Remark": "序號明細", | |||
| "Stock Available": "庫存可用", | |||
| "Stock Status": "庫存狀態", | |||
| @@ -379,10 +386,6 @@ | |||
| "Production Output Data": "生產輸出數據", | |||
| "Step Information": "步驟信息", | |||
| "Stop": "停止", | |||
| "Putaway Detail": "上架詳情", | |||
| "Lines with sufficient stock: ": "足夠庫存:", | |||
| "Lines with insufficient stock: ": "庫存不足:", | |||
| "Total lines: ": "總數量:", | |||
| "Demand Forecast Setting": "需求預測設定", | |||
| "Equipment Type/Code": "使用設備-編號", | |||
| "Equipment": "設備", | |||
| @@ -446,7 +449,6 @@ | |||
| "hrs": "小時", | |||
| "min": "分鐘", | |||
| "mins": "分鐘", | |||
| "Job Order": "工單", | |||
| "Edit Job Order": "工單詳情", | |||
| "Production": "生產流程", | |||
| "Put Away": "上架", | |||
| @@ -454,19 +456,9 @@ | |||
| "Finished Good Order": "成品出倉", | |||
| "finishedGood": "成品", | |||
| "Router": "執貨路線", | |||
| "Job Order Pickexcution": "工單提料", | |||
| "No data available": "沒有資料", | |||
| "Start Scan": "開始掃碼", | |||
| "Stop Scan": "停止掃碼", | |||
| "Scan Result": "掃碼結果", | |||
| "Expiry Date": "有效期", | |||
| "Pick Order Code": "提料單編號", | |||
| "Target Date": "需求日期", | |||
| "Lot Required Pick Qty": "批號需求數量", | |||
| "Job Order Match": "工單匹配", | |||
| "All Pick Order Lots": "所有提料單批號", | |||
| "Rows per page": "每頁行數", | |||
| "No data available": "沒有資料", | |||
| "jodetail": "工單細節", | |||
| "Sign out": "登出" | |||
| } | |||
| @@ -7,6 +7,7 @@ | |||
| "Details": "詳情", | |||
| "Supplier": "供應商", | |||
| "Status": "來貨狀態", | |||
| "N/A": "不適用", | |||
| "Release Pick Orders": "放單", | |||
| "Escalated": "上報狀態", | |||
| "NotEscalated": "無上報", | |||