| @@ -13,6 +13,8 @@ export interface SaveJo { | |||||
| planEnd: string; | planEnd: string; | ||||
| reqQty: number; | reqQty: number; | ||||
| type: string; | type: string; | ||||
| //jobType?: string; | |||||
| jobTypeId?: number; | |||||
| } | } | ||||
| export interface SaveJoResponse { | export interface SaveJoResponse { | ||||
| @@ -703,6 +705,7 @@ export const isCorrectMachineUsed = async (machineCode: string) => { | |||||
| export const fetchJos = cache(async (data?: SearchJoResultRequest) => { | export const fetchJos = cache(async (data?: SearchJoResultRequest) => { | ||||
| const queryStr = convertObjToURLSearchParams(data) | const queryStr = convertObjToURLSearchParams(data) | ||||
| console.log("queryStr", queryStr) | |||||
| const response = serverFetchJson<SearchJoResultResponse>( | const response = serverFetchJson<SearchJoResultResponse>( | ||||
| `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`, | `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`, | ||||
| { | { | ||||
| @@ -116,8 +116,15 @@ export interface GetPickOrderLineInfo { | |||||
| suggestedList: any[]; | suggestedList: any[]; | ||||
| pickedQty: number; | pickedQty: number; | ||||
| noLotLines: NoLotLineDto[]; | |||||
| } | |||||
| export interface NoLotLineDto { | |||||
| stockOutLineId: number; | |||||
| status: string; | |||||
| qty: number; | |||||
| created: string; | |||||
| modified: string; | |||||
| } | } | ||||
| export interface CurrentInventoryItemInfo { | export interface CurrentInventoryItemInfo { | ||||
| id: number; | id: number; | ||||
| code: string; | code: string; | ||||
| @@ -743,18 +750,26 @@ export const fetchPickOrderDetails = cache(async (ids: string) => { | |||||
| ); | ); | ||||
| }); | }); | ||||
| export interface PickOrderLotDetailResponse { | 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; | requiredQty: number; | ||||
| actualPickQty: 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 { | interface ALLPickOrderLotDetailResponse { | ||||
| // Pick Order Information | // Pick Order Information | ||||
| @@ -423,7 +423,7 @@ const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => { | |||||
| return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>; | return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>; | ||||
| }; | }; | ||||
| const NoRowsOverlay: React.FC = () => { | const NoRowsOverlay: React.FC = () => { | ||||
| const { t } = useTranslation("home"); | |||||
| const { t } = useTranslation("purchaseOrder"); | |||||
| return ( | return ( | ||||
| <Box | <Box | ||||
| display="flex" | display="flex" | ||||
| @@ -3,7 +3,7 @@ import { JoDetail } from "@/app/api/jo"; | |||||
| import { SaveJo, manualCreateJo } from "@/app/api/jo/actions"; | import { SaveJo, manualCreateJo } from "@/app/api/jo/actions"; | ||||
| import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil"; | import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil"; | ||||
| import { Check } from "@mui/icons-material"; | 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 { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; | ||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | ||||
| import dayjs, { Dayjs } from "dayjs"; | import dayjs, { Dayjs } from "dayjs"; | ||||
| @@ -52,6 +52,10 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||||
| const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => { | const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => { | ||||
| data.type = "manual" | data.type = "manual" | ||||
| if (data.planStart) { | |||||
| const dateDayjs = dateStringToDayjs(data.planStart) | |||||
| data.planStart = dayjsToDateTimeString(dateDayjs.startOf('day')) | |||||
| } | |||||
| const response = await manualCreateJo(data) | const response = await manualCreateJo(data) | ||||
| if (response) { | if (response) { | ||||
| onSearch(); | onSearch(); | ||||
| @@ -164,17 +168,20 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} sm={12} md={6}> | <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> | ||||
| <Grid item xs={12} sm={12} md={6}> | <Grid item xs={12} sm={12} md={6}> | ||||
| <Controller | <Controller | ||||
| @@ -192,10 +199,13 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||||
| } | } | ||||
| }} | }} | ||||
| render={({ field, fieldState: { error } }) => ( | render={({ field, fieldState: { error } }) => ( | ||||
| <DateTimePicker | |||||
| // <DateTimePicker | |||||
| <DatePicker | |||||
| label={t("Plan Start")} | 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) => { | onChange={(newValue: Dayjs | null) => { | ||||
| handleDateTimePickerChange(newValue, field.onChange) | handleDateTimePickerChange(newValue, field.onChange) | ||||
| }} | }} | ||||
| @@ -292,10 +292,10 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo }) => | |||||
| const searchDataByPage = useCallback(() => { | const searchDataByPage = useCallback(() => { | ||||
| refetchData(inputs, "paging"); | refetchData(inputs, "paging"); | ||||
| }, [inputs]) | |||||
| }, [inputs,refetchData]) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| searchDataByPage(); | searchDataByPage(); | ||||
| }, [pagingController]); | |||||
| }, [pagingController,searchDataByPage ]); | |||||
| const getButtonSx = (jo : JobOrder) => { // TODO put it in ActionButtons.ts | const getButtonSx = (jo : JobOrder) => { // TODO put it in ActionButtons.ts | ||||
| const joStatus = jo.status?.toLowerCase(); | const joStatus = jo.status?.toLowerCase(); | ||||
| @@ -474,7 +474,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
| <Tab label={t("Pick Order Detail")} iconPosition="end" /> | <Tab label={t("Pick Order Detail")} iconPosition="end" /> | ||||
| <Tab label={t("Complete Job Order Record")} 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" /> */} | {/* <Tab label={t("Finished Job Order Record")} iconPosition="end" /> */} | ||||
| </Tabs> | </Tabs> | ||||
| @@ -486,7 +486,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1; | |||||
| }}> | }}> | ||||
| {tabIndex === 0 && <JobPickExecution filterArgs={filterArgs} />} | {tabIndex === 0 && <JobPickExecution filterArgs={filterArgs} />} | ||||
| {tabIndex === 1 && <CompleteJobOrderRecord filterArgs={filterArgs} printerCombo={printerCombo} />} | {tabIndex === 1 && <CompleteJobOrderRecord filterArgs={filterArgs} printerCombo={printerCombo} />} | ||||
| {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />} | |||||
| {/* {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />} */} | |||||
| {/* {tabIndex === 3 && <FInishedJobOrderRecord filterArgs={filterArgs} />} */} | {/* {tabIndex === 3 && <FInishedJobOrderRecord filterArgs={filterArgs} />} */} | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| @@ -28,10 +28,10 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import | |||||
| import PickExecutionForm from "./PickExecutionForm"; | import PickExecutionForm from "./PickExecutionForm"; | ||||
| interface LotPickData { | interface LotPickData { | ||||
| id: number; | id: number; | ||||
| lotId: number; | |||||
| lotNo: string; | |||||
| lotId: number | null; | |||||
| lotNo: string | null; | |||||
| expiryDate: string; | expiryDate: string; | ||||
| location: string; | |||||
| location: string | null; | |||||
| stockUnit: string; | stockUnit: string; | ||||
| inQty: number; | inQty: number; | ||||
| availableQty: number; | availableQty: number; | ||||
| @@ -45,6 +45,7 @@ interface LotPickData { | |||||
| stockOutLineId?: number; | stockOutLineId?: number; | ||||
| stockOutLineStatus?: string; | stockOutLineStatus?: string; | ||||
| stockOutLineQty?: number; | stockOutLineQty?: number; | ||||
| noLot?: boolean; | |||||
| } | } | ||||
| interface PickQtyData { | interface PickQtyData { | ||||
| @@ -60,7 +61,7 @@ interface LotTableProps { | |||||
| pickQtyData: PickQtyData; | pickQtyData: PickQtyData; | ||||
| selectedLotRowId: string | null; | selectedLotRowId: string | null; | ||||
| selectedLotId: number | 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; | onPickQtyChange: (lineId: number, lotId: number, value: number) => void; | ||||
| onSubmitPickQty: (lineId: number, lotId: number) => void; | onSubmitPickQty: (lineId: number, lotId: number) => void; | ||||
| onCreateStockOutLine: (inventoryLotLineId: number) => void; | onCreateStockOutLine: (inventoryLotLineId: number) => void; | ||||
| @@ -75,6 +76,7 @@ interface LotTableProps { | |||||
| generateInputBody: () => any; | generateInputBody: () => any; | ||||
| onDataRefresh: () => Promise<void>; | onDataRefresh: () => Promise<void>; | ||||
| onLotDataRefresh: () => Promise<void>; | onLotDataRefresh: () => Promise<void>; | ||||
| onIssueNoLotStockOutLine: (stockOutLineId: number) => void; | |||||
| } | } | ||||
| // QR Code Modal Component | // QR Code Modal Component | ||||
| @@ -236,7 +238,7 @@ const QrCodeModal: React.FC<{ | |||||
| const timer = setTimeout(() => { | const timer = setTimeout(() => { | ||||
| setQrScanSuccess(true); | setQrScanSuccess(true); | ||||
| onQrCodeSubmit(lot.lotNo); | |||||
| onQrCodeSubmit(lot.lotNo??''); | |||||
| onClose(); | onClose(); | ||||
| setManualInput(''); | setManualInput(''); | ||||
| setManualInputError(false); | setManualInputError(false); | ||||
| @@ -388,30 +390,28 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| generateInputBody, | generateInputBody, | ||||
| onDataRefresh, | onDataRefresh, | ||||
| onLotDataRefresh, | onLotDataRefresh, | ||||
| onIssueNoLotStockOutLine, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const calculateRemainingRequiredQty = useCallback((lot: LotPickData) => { | const calculateRemainingRequiredQty = useCallback((lot: LotPickData) => { | ||||
| const requiredQty = lot.requiredQty || 0; | const requiredQty = lot.requiredQty || 0; | ||||
| const stockOutLineQty = lot.stockOutLineQty || 0; | const stockOutLineQty = lot.stockOutLineQty || 0; | ||||
| return Math.max(0, requiredQty - stockOutLineQty); | return Math.max(0, requiredQty - stockOutLineQty); | ||||
| }, []); | }, []); | ||||
| // Add QR scanner context | |||||
| const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | ||||
| const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({}); | const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({}); | ||||
| // Add state for QR input modal | |||||
| const [qrModalOpen, setQrModalOpen] = useState(false); | const [qrModalOpen, setQrModalOpen] = useState(false); | ||||
| const [selectedLotForQr, setSelectedLotForQr] = useState<LotPickData | null>(null); | const [selectedLotForQr, setSelectedLotForQr] = useState<LotPickData | null>(null); | ||||
| const [manualQrInput, setManualQrInput] = useState<string>(''); | const [manualQrInput, setManualQrInput] = useState<string>(''); | ||||
| // 分页控制器 | |||||
| const [lotTablePagingController, setLotTablePagingController] = useState({ | const [lotTablePagingController, setLotTablePagingController] = useState({ | ||||
| pageNum: 0, | pageNum: 0, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| }); | }); | ||||
| // 添加状态消息生成函数 | |||||
| const getStatusMessage = useCallback((lot: LotPickData) => { | const getStatusMessage = useCallback((lot: LotPickData) => { | ||||
| switch (lot.stockOutLineStatus?.toLowerCase()) { | switch (lot.stockOutLineStatus?.toLowerCase()) { | ||||
| case 'pending': | case 'pending': | ||||
| return t("Please finish QR code scanand pick order."); | return t("Please finish QR code scanand pick order."); | ||||
| @@ -428,23 +428,21 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| default: | default: | ||||
| return t("Please finish QR code scan and pick order."); | return t("Please finish QR code scan and pick order."); | ||||
| } | } | ||||
| }, []); | |||||
| }, [t]); | |||||
| const prepareLotTableData = useMemo(() => { | const prepareLotTableData = useMemo(() => { | ||||
| return lotData.map((lot) => ({ | return lotData.map((lot) => ({ | ||||
| ...lot, | ...lot, | ||||
| id: lot.lotId, | |||||
| id: lot.lotId ?? lot.id, | |||||
| })); | })); | ||||
| }, [lotData]); | }, [lotData]); | ||||
| // 分页数据 | |||||
| const paginatedLotTableData = useMemo(() => { | const paginatedLotTableData = useMemo(() => { | ||||
| const startIndex = lotTablePagingController.pageNum * lotTablePagingController.pageSize; | const startIndex = lotTablePagingController.pageNum * lotTablePagingController.pageSize; | ||||
| const endIndex = startIndex + lotTablePagingController.pageSize; | const endIndex = startIndex + lotTablePagingController.pageSize; | ||||
| return prepareLotTableData.slice(startIndex, endIndex); | return prepareLotTableData.slice(startIndex, endIndex); | ||||
| }, [prepareLotTableData, lotTablePagingController]); | }, [prepareLotTableData, lotTablePagingController]); | ||||
| // 分页处理函数 | |||||
| const handleLotTablePageChange = useCallback((event: unknown, newPage: number) => { | const handleLotTablePageChange = useCallback((event: unknown, newPage: number) => { | ||||
| setLotTablePagingController(prev => ({ | setLotTablePagingController(prev => ({ | ||||
| ...prev, | ...prev, | ||||
| @@ -459,87 +457,33 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| pageSize: newPageSize, | pageSize: newPageSize, | ||||
| }); | }); | ||||
| }, []); | }, []); | ||||
| const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { | 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); | return Math.max(0, remainingQty); | ||||
| }, [selectedRowId, pickQtyData]); | }, [selectedRowId, pickQtyData]); | ||||
| const validatePickQty = useCallback((lot: LotPickData, inputValue: number) => { | 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) => { | 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 [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); | ||||
| const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null); | const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null); | ||||
| // 添加处理函数 | |||||
| const handlePickExecutionForm = useCallback((lot: LotPickData) => { | const handlePickExecutionForm = useCallback((lot: LotPickData) => { | ||||
| console.log("=== Pick Execution Form ==="); | console.log("=== Pick Execution Form ==="); | ||||
| console.log("Lot data:", lot); | console.log("Lot data:", lot); | ||||
| @@ -560,8 +504,6 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| const handlePickExecutionFormSubmit = useCallback(async (data: any) => { | const handlePickExecutionFormSubmit = useCallback(async (data: any) => { | ||||
| try { | try { | ||||
| console.log("Pick execution form submitted:", data); | console.log("Pick execution form submitted:", data); | ||||
| // 调用 API 提交数据 | |||||
| const result = await recordPickExecutionIssue(data); | const result = await recordPickExecutionIssue(data); | ||||
| console.log("Pick execution issue recorded:", result); | console.log("Pick execution issue recorded:", result); | ||||
| @@ -574,7 +516,6 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| setPickExecutionFormOpen(false); | setPickExecutionFormOpen(false); | ||||
| setSelectedLotForExecutionForm(null); | setSelectedLotForExecutionForm(null); | ||||
| // 刷新数据 | |||||
| if (onDataRefresh) { | if (onDataRefresh) { | ||||
| await 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("Lot Required Pick Qty")}</TableCell> | ||||
| <TableCell align="right">{t("Original Available Qty")}</TableCell> | <TableCell align="right">{t("Original Available Qty")}</TableCell> | ||||
| <TableCell align="center">{t("Lot Actual Pick 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="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> | <TableCell align="center">{t("Action")}</TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| </TableHead> | </TableHead> | ||||
| @@ -623,7 +557,7 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| ) : ( | ) : ( | ||||
| paginatedLotTableData.map((lot, index) => ( | paginatedLotTableData.map((lot, index) => ( | ||||
| <TableRow | <TableRow | ||||
| key={lot.id} | |||||
| key={lot.noLot ? `noLot_${lot.stockOutLineId}_${index}` : `lot_${lot.lotId}_${index}`} | |||||
| sx={{ | sx={{ | ||||
| backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit', | backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit', | ||||
| opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1, | opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1, | ||||
| @@ -633,36 +567,30 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| }} | }} | ||||
| > | > | ||||
| <TableCell> | <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> | <TableCell> | ||||
| <Box> | <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> | </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> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell>{lot.expiryDate}</TableCell> | <TableCell>{lot.expiryDate}</TableCell> | ||||
| @@ -673,154 +601,76 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| {(() => { | {(() => { | ||||
| const inQty = lot.inQty || 0; | const inQty = lot.inQty || 0; | ||||
| const outQty = lot.outQty || 0; | const outQty = lot.outQty || 0; | ||||
| const result = inQty - outQty; | const result = inQty - outQty; | ||||
| return result.toLocaleString(); | return result.toLocaleString(); | ||||
| })()} | })()} | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | <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> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| )) | )) | ||||
| @@ -828,50 +678,9 @@ const LotTable: React.FC<LotTableProps> = ({ | |||||
| </TableBody> | </TableBody> | ||||
| </Table> | </Table> | ||||
| </TableContainer> | </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 && ( | {pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && ( | ||||
| <PickExecutionForm | <PickExecutionForm | ||||
| open={pickExecutionFormOpen} | open={pickExecutionFormOpen} | ||||
| @@ -61,13 +61,13 @@ import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { fetchQcItemCheck, fetchPickOrderQcResult } from "@/app/api/qc/actions"; | import { fetchQcItemCheck, fetchPickOrderQcResult } from "@/app/api/qc/actions"; | ||||
| import { PurchaseQcResult } from "@/app/api/po/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 { fetchPickOrderLineLotDetails, PickOrderLotDetailResponse } from "@/app/api/pickOrder/actions"; | ||||
| import SearchResults, { Column } from "../SearchResults/SearchResults"; | import SearchResults, { Column } from "../SearchResults/SearchResults"; | ||||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | import { defaultPagingController } from "../SearchResults/SearchResults"; | ||||
| import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; | |||||
| import { CreateStockOutLine , NoLotLineDto} from "@/app/api/pickOrder/actions"; | |||||
| import LotTable from './LotTable'; | import LotTable from './LotTable'; | ||||
| import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component | import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component | ||||
| import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; | import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; | ||||
| @@ -80,10 +80,10 @@ interface Props { | |||||
| interface LotPickData { | interface LotPickData { | ||||
| id: number; | id: number; | ||||
| lotId: number; | |||||
| lotNo: string; | |||||
| lotId: number | null; | |||||
| lotNo: string | null; | |||||
| expiryDate: string; | expiryDate: string; | ||||
| location: string; | |||||
| location: string | null; | |||||
| stockUnit: string; | stockUnit: string; | ||||
| inQty: number; | inQty: number; | ||||
| outQty: number; | outQty: number; | ||||
| @@ -93,10 +93,12 @@ interface LotPickData { | |||||
| requiredQty: number; | requiredQty: number; | ||||
| actualPickQty: number; | actualPickQty: number; | ||||
| lotStatus: string; | lotStatus: string; | ||||
| noLot?: boolean; | |||||
| lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; | lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; | ||||
| stockOutLineId?: number; | stockOutLineId?: number; | ||||
| stockOutLineStatus?: string; | stockOutLineStatus?: string; | ||||
| stockOutLineQty?: number; | stockOutLineQty?: number; | ||||
| } | } | ||||
| interface PickQtyData { | 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("=== DEBUG: Lot Selection ==="); | ||||
| console.log("uniqueLotId:", uniqueLotId); | console.log("uniqueLotId:", uniqueLotId); | ||||
| console.log("lotId (inventory lot line ID):", lotId); | console.log("lotId (inventory lot line ID):", lotId); | ||||
| @@ -589,10 +591,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| setSelectedLotId(null); | setSelectedLotId(null); | ||||
| } else { | } else { | ||||
| setSelectedLotRowId(uniqueLotId); | setSelectedLotRowId(uniqueLotId); | ||||
| setSelectedLotId(lotId); | |||||
| setSelectedLotId(lotId ?? null); | |||||
| } | } | ||||
| }, [selectedLotRowId]); | |||||
| }, [selectedLotRowId, lotData]); | |||||
| const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => { | const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => { | ||||
| setSelectedRowId(lineId); | setSelectedRowId(lineId); | ||||
| @@ -604,35 +605,42 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| try { | try { | ||||
| const lotDetails = await fetchPickOrderLineLotDetails(lineId); | const lotDetails = await fetchPickOrderLineLotDetails(lineId); | ||||
| console.log("lineId:", lineId); | |||||
| console.log("Lot details from API:", lotDetails); | 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, | lotId: lot.lotId, | ||||
| lotNo: lot.lotNo, | 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, | actualPickQty: lot.actualPickQty || 0, | ||||
| lotStatus: lot.lotStatus, | |||||
| lotStatus: lot.lotStatus || "unavailable", | |||||
| lotAvailability: lot.lotAvailability, | 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) { | } catch (error) { | ||||
| console.error("Error fetching lot details:", error); | console.error("Error fetching lot details:", error); | ||||
| setLotData([]); | setLotData([]); | ||||
| } | } | ||||
| }, []); | |||||
| }, []); | |||||
| const prepareLotTableData = useMemo(() => { | const prepareLotTableData = useMemo(() => { | ||||
| return lotData.map((lot) => ({ | return lotData.map((lot) => ({ | ||||
| @@ -747,19 +755,50 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| setShowInputBody(true); | setShowInputBody(true); | ||||
| }, []); | }, []); | ||||
| const generateInputBody = useCallback((): CreateStockOutLine | null => { | |||||
| if (!selectedLotForInput || !selectedRowId || !selectedRow || !pickOrderDetails?.consoCode) { | |||||
| const generateInputBody = useCallback(() => { | |||||
| if (!selectedLotForInput || !selectedRowId || !pickOrderDetails?.consoCode) { | |||||
| return null; | return null; | ||||
| } | } | ||||
| // ✅ 處理 lotId 可能為 null 的情況 | |||||
| if (selectedLotForInput.lotId === null) { | |||||
| return null; // no-lot 行不能創建 stock out line | |||||
| } | |||||
| return { | return { | ||||
| consoCode: pickOrderDetails.consoCode, | consoCode: pickOrderDetails.consoCode, | ||||
| pickOrderLineId: selectedRowId, | pickOrderLineId: selectedRowId, | ||||
| inventoryLotLineId: selectedLotForInput.lotId, | |||||
| inventoryLotLineId: selectedLotForInput.lotId, // ✅ 現在確定不是 null | |||||
| qty: 0.0 | qty: 0.0 | ||||
| }; | }; | ||||
| }, [selectedLotForInput, selectedRowId, selectedRow, pickOrderDetails?.consoCode]); | }, [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) => { | const handleCreateStockOutLine = useCallback(async (inventoryLotLineId: number) => { | ||||
| if (!selectedRowId || !pickOrderDetails?.consoCode) { | if (!selectedRowId || !pickOrderDetails?.consoCode) { | ||||
| console.error("Missing required data for creating stock out line."); | console.error("Missing required data for creating stock out line."); | ||||
| @@ -966,6 +1005,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| onLotDataRefresh={handleLotDataRefresh} | onLotDataRefresh={handleLotDataRefresh} | ||||
| onLotSelectForInput={handleLotSelectForInput} | onLotSelectForInput={handleLotSelectForInput} | ||||
| showInputBody={showInputBody} | showInputBody={showInputBody} | ||||
| onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine} | |||||
| setShowInputBody={setShowInputBody} | setShowInputBody={setShowInputBody} | ||||
| selectedLotForInput={selectedLotForInput} | selectedLotForInput={selectedLotForInput} | ||||
| generateInputBody={generateInputBody} | generateInputBody={generateInputBody} | ||||
| @@ -1038,7 +1078,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| </Grid> | </Grid> | ||||
| )} | )} | ||||
| {/* QC Modal */} | {/* QC Modal */} | ||||
| { /* | |||||
| {selectedItemForQc && qcModalOpen && ( | {selectedItemForQc && qcModalOpen && ( | ||||
| <PickQcStockInModalVer2 | <PickQcStockInModalVer2 | ||||
| open={qcModalOpen} | open={qcModalOpen} | ||||
| @@ -1058,6 +1100,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| lotData={lotData} | lotData={lotData} | ||||
| /> | /> | ||||
| )} | )} | ||||
| */} | |||||
| </Stack> | </Stack> | ||||
| </FormProvider> | </FormProvider> | ||||
| ); | ); | ||||
| @@ -26,10 +26,10 @@ import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| interface LotPickData { | interface LotPickData { | ||||
| id: number; | id: number; | ||||
| lotId: number; | |||||
| lotNo: string; | |||||
| lotId: number | null; | |||||
| lotNo: string | null; | |||||
| expiryDate: string; | expiryDate: string; | ||||
| location: string; | |||||
| location: string | null; | |||||
| stockUnit: string; | stockUnit: string; | ||||
| inQty: number; | inQty: number; | ||||
| outQty: number; | outQty: number; | ||||
| @@ -43,6 +43,7 @@ interface LotPickData { | |||||
| stockOutLineId?: number; | stockOutLineId?: number; | ||||
| stockOutLineStatus?: string; | stockOutLineStatus?: string; | ||||
| stockOutLineQty?: number; | stockOutLineQty?: number; | ||||
| noLot?: boolean; | |||||
| } | } | ||||
| interface PickExecutionFormProps { | interface PickExecutionFormProps { | ||||
| @@ -144,9 +145,9 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => { | |||||
| itemId: selectedPickOrderLine.itemId, | itemId: selectedPickOrderLine.itemId, | ||||
| itemCode: selectedPickOrderLine.itemCode, | itemCode: selectedPickOrderLine.itemCode, | ||||
| itemDescription: selectedPickOrderLine.itemName, | 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, | requiredQty: selectedLot.requiredQty, | ||||
| actualPickQty: selectedLot.actualPickQty || 0, | actualPickQty: selectedLot.actualPickQty || 0, | ||||
| missQty: 0, | missQty: 0, | ||||
| @@ -272,7 +272,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| }}> | }}> | ||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
| <Tab label={t("Select Items")} iconPosition="end" /> | <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("Assign")} iconPosition="end" /> | ||||
| <Tab label={t("Release")} iconPosition="end" /> | <Tab label={t("Release")} iconPosition="end" /> | ||||
| <Tab label={t("Pick Execution")} iconPosition="end" /> | <Tab label={t("Pick Execution")} iconPosition="end" /> | ||||
| @@ -283,7 +283,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| <Box sx={{ | <Box sx={{ | ||||
| p: 2 | p: 2 | ||||
| }}> | }}> | ||||
| {tabIndex === 4 && <PickExecution filterArgs={filterArgs} />} | |||||
| {tabIndex === 3 && <PickExecution filterArgs={filterArgs} />} | |||||
| {tabIndex === 0 && ( | {tabIndex === 0 && ( | ||||
| <NewCreateItem | <NewCreateItem | ||||
| filterArgs={filterArgs} | filterArgs={filterArgs} | ||||
| @@ -291,9 +291,9 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| onPickOrderCreated={handlePickOrderCreated} | 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> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| @@ -1,3 +1,4 @@ | |||||
| /* | |||||
| "use client"; | "use client"; | ||||
| import { GetPickOrderLineInfo, updateStockOutLineStatus } from "@/app/api/pickOrder/actions"; | import { GetPickOrderLineInfo, updateStockOutLineStatus } from "@/app/api/pickOrder/actions"; | ||||
| @@ -24,7 +25,7 @@ import { | |||||
| import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form"; | import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { dummyQCData } from "../PoDetail/dummyQcTemplate"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
| import { GridColDef } from "@mui/x-data-grid"; | import { GridColDef } from "@mui/x-data-grid"; | ||||
| import { submitDialogWithWarning } from "../Swal/CustomAlerts"; | import { submitDialogWithWarning } from "../Swal/CustomAlerts"; | ||||
| @@ -636,7 +637,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| {/* Combirne options 2 & 3 into one */} | |||||
| <FormControlLabel | <FormControlLabel | ||||
| value="2" | value="2" | ||||
| control={<Radio />} | control={<Radio />} | ||||
| @@ -649,7 +650,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({ | |||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| {/* Show escalation component when QC Decision = 2 (Report and Re-pick) */} | |||||
| <Grid item xs={12} sx={{ mt: 2 }}> | <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 || ""} | value={processData?.itemCode+"-"+processData?.itemName || ""} | ||||
| /> | /> | ||||
| </Grid> | </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}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("Req. Qty")} | label={t("Req. Qty")} | ||||
| @@ -221,7 +230,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| label={t("Production Priority")} | label={t("Production Priority")} | ||||
| fullWidth | fullWidth | ||||
| disabled={true} | disabled={true} | ||||
| value={processData?.productionPriority || "0"} | |||||
| value={processData?.productionPriority ||processData?.isDense === 0 ? "50" : processData?.productionPriority || "0"} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| @@ -229,10 +238,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance")} | label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance")} | ||||
| fullWidth | fullWidth | ||||
| disabled={true} | 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> | </Grid> | ||||
| @@ -277,7 +283,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| align: "left", | align: "left", | ||||
| headerAlign: "left", | headerAlign: "left", | ||||
| type: "number", | type: "number", | ||||
| }, | }, | ||||
| { | { | ||||
| field: "itemCode", | field: "itemCode", | ||||
| @@ -323,7 +329,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| headerAlign: "right", | headerAlign: "right", | ||||
| type: "number", | type: "number", | ||||
| }, | }, | ||||
| /* | |||||
| { | { | ||||
| field: "seqNoRemark", | field: "seqNoRemark", | ||||
| headerName: t("Seq No Remark"), | headerName: t("Seq No Remark"), | ||||
| @@ -332,6 +338,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| headerAlign: "left", | headerAlign: "left", | ||||
| type: "string", | type: "string", | ||||
| }, | }, | ||||
| */ | |||||
| { | { | ||||
| field: "stockStatus", | field: "stockStatus", | ||||
| headerName: t("Stock Status"), | headerName: t("Stock Status"), | ||||
| @@ -426,13 +433,12 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
| <Tab label={t("Job Order Info")} /> | <Tab label={t("Job Order Info")} /> | ||||
| <Tab label={t("BoM Material")} /> | <Tab label={t("BoM Material")} /> | ||||
| {!fromJosave && ( | |||||
| <Tab label={t("Production Process")} /> | <Tab label={t("Production Process")} /> | ||||
| )} | |||||
| {!fromJosave && ( | |||||
| <Tab label={t("Production Process Line Remark")} /> | <Tab label={t("Production Process Line Remark")} /> | ||||
| )} | |||||
| {!fromJosave && ( | |||||
| {!fromJosave && ( | |||||
| <Tab label={t("Matching Stock")} /> | <Tab label={t("Matching Stock")} /> | ||||
| )} | )} | ||||
| </Tabs> | </Tabs> | ||||
| @@ -453,8 +459,11 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||||
| /> | /> | ||||
| )} | )} | ||||
| {tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />} | {tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />} | ||||
| {tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />} | {tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />} | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| @@ -107,7 +107,7 @@ | |||||
| "Pick Order Code": "提料單編號", | "Pick Order Code": "提料單編號", | ||||
| "Target Date": "需求日期", | "Target Date": "需求日期", | ||||
| "Lot Required Pick Qty": "批號需求數量", | "Lot Required Pick Qty": "批號需求數量", | ||||
| "Job Order Match": "工單匹配", | |||||
| "Job Order Match": "工單對料", | |||||
| "All Pick Order Lots": "所有提料單批號", | "All Pick Order Lots": "所有提料單批號", | ||||
| "Row per page": "每頁行數", | "Row per page": "每頁行數", | ||||
| "No data available": "沒有資料", | "No data available": "沒有資料", | ||||
| @@ -167,18 +167,19 @@ | |||||
| "View": "查看", | "View": "查看", | ||||
| "Back": "返回", | "Back": "返回", | ||||
| "BoM Material": "物料清單", | "BoM Material": "物料清單", | ||||
| "N/A": "不適用", | |||||
| "Is Dark | Dense | Float | Scrap Rate | Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原", | "Is Dark | Dense | Float | Scrap Rate | Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原", | ||||
| "Item Code": "物料編號", | "Item Code": "物料編號", | ||||
| "Item Name": "物料名稱", | "Item Name": "物料名稱", | ||||
| "Job Order Info": "工單信息", | "Job Order Info": "工單信息", | ||||
| "Matching Stock": "匹配庫存", | |||||
| "Matching Stock": "工單對料", | |||||
| "No data found": "沒有找到資料", | "No data found": "沒有找到資料", | ||||
| "Production Priority": "生產優先級", | "Production Priority": "生產優先級", | ||||
| "Production Process": "工藝流程", | "Production Process": "工藝流程", | ||||
| "Production Process Line Remark": "工藝明細", | "Production Process Line Remark": "工藝明細", | ||||
| "Remark": "明細", | "Remark": "明細", | ||||
| "Req. Qty": "需求數量", | "Req. Qty": "需求數量", | ||||
| "Seq No": "序號", | |||||
| "Seq No": "加入步驟", | |||||
| "Seq No Remark": "序號明細", | "Seq No Remark": "序號明細", | ||||
| "Stock Available": "庫存可用", | "Stock Available": "庫存可用", | ||||
| "Stock Status": "庫存狀態", | "Stock Status": "庫存狀態", | ||||
| @@ -200,7 +201,7 @@ | |||||
| "Step Information": "步驟信息", | "Step Information": "步驟信息", | ||||
| "Stop": "停止", | "Stop": "停止", | ||||
| "Putaway Detail": "上架詳情", | "Putaway Detail": "上架詳情", | ||||
| "Lines with sufficient stock: ": "足夠庫存:", | |||||
| "Lines with insufficient stock: ": "庫存不足:", | |||||
| "Lines with sufficient stock: ": "可提料項目數量: ", | |||||
| "Lines with insufficient stock: ": "未能提料項目數量: ", | |||||
| "Total lines: ": "總數量:" | "Total lines: ": "總數量:" | ||||
| } | } | ||||
| @@ -328,11 +328,18 @@ | |||||
| "Submit & Start": "提交並開始", | "Submit & Start": "提交並開始", | ||||
| "Total Steps": "總步驟數", | "Total Steps": "總步驟數", | ||||
| "Unknown": "", | "Unknown": "", | ||||
| "Job Type": "工單類型", | |||||
| "WIP": "半成品", | |||||
| "R&D": "研發", | |||||
| "STF": "樣品", | |||||
| "Other": "其他", | |||||
| "Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.", | "Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.", | ||||
| "View": "查看", | "View": "查看", | ||||
| "Back": "返回", | "Back": "返回", | ||||
| "N/A": "不適用", | |||||
| "BoM Material": "半成品/成品清單", | "BoM Material": "半成品/成品清單", | ||||
| "Is Dark | Dense | Float": "顔色深淺度 | 濃淡 | 浮沉", | |||||
| "Is Dark | Dense | Float| Scrap Rate| Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原", | |||||
| "Item Code": "物料編號", | "Item Code": "物料編號", | ||||
| "Item Name": "物料名稱", | "Item Name": "物料名稱", | ||||
| "Enter the number of cartons: ": "請輸入箱數:", | "Enter the number of cartons: ": "請輸入箱數:", | ||||
| @@ -344,7 +351,7 @@ | |||||
| "Print Pick Record": "打印板頭紙", | "Print Pick Record": "打印板頭紙", | ||||
| "Printed Successfully.": "成功列印", | "Printed Successfully.": "成功列印", | ||||
| "Job Order Info": "工單信息", | "Job Order Info": "工單信息", | ||||
| "Matching Stock": "匹配庫存", | |||||
| "Matching Stock": "工單對料", | |||||
| "No data found": "沒有找到資料", | "No data found": "沒有找到資料", | ||||
| "Print Quantity": "打印數量", | "Print Quantity": "打印數量", | ||||
| "Select Printer": "選擇打印機", | "Select Printer": "選擇打印機", | ||||
| @@ -356,7 +363,7 @@ | |||||
| "Production Process Line Remark": "工藝明細", | "Production Process Line Remark": "工藝明細", | ||||
| "Remark": "明細", | "Remark": "明細", | ||||
| "Req. Qty": "需求數量", | "Req. Qty": "需求數量", | ||||
| "Seq No": "序號", | |||||
| "Seq No": "加入步驟", | |||||
| "Seq No Remark": "序號明細", | "Seq No Remark": "序號明細", | ||||
| "Stock Available": "庫存可用", | "Stock Available": "庫存可用", | ||||
| "Stock Status": "庫存狀態", | "Stock Status": "庫存狀態", | ||||
| @@ -379,10 +386,6 @@ | |||||
| "Production Output Data": "生產輸出數據", | "Production Output Data": "生產輸出數據", | ||||
| "Step Information": "步驟信息", | "Step Information": "步驟信息", | ||||
| "Stop": "停止", | "Stop": "停止", | ||||
| "Putaway Detail": "上架詳情", | |||||
| "Lines with sufficient stock: ": "足夠庫存:", | |||||
| "Lines with insufficient stock: ": "庫存不足:", | |||||
| "Total lines: ": "總數量:", | |||||
| "Demand Forecast Setting": "需求預測設定", | "Demand Forecast Setting": "需求預測設定", | ||||
| "Equipment Type/Code": "使用設備-編號", | "Equipment Type/Code": "使用設備-編號", | ||||
| "Equipment": "設備", | "Equipment": "設備", | ||||
| @@ -446,7 +449,6 @@ | |||||
| "hrs": "小時", | "hrs": "小時", | ||||
| "min": "分鐘", | "min": "分鐘", | ||||
| "mins": "分鐘", | "mins": "分鐘", | ||||
| "Job Order": "工單", | |||||
| "Edit Job Order": "工單詳情", | "Edit Job Order": "工單詳情", | ||||
| "Production": "生產流程", | "Production": "生產流程", | ||||
| "Put Away": "上架", | "Put Away": "上架", | ||||
| @@ -454,19 +456,9 @@ | |||||
| "Finished Good Order": "成品出倉", | "Finished Good Order": "成品出倉", | ||||
| "finishedGood": "成品", | "finishedGood": "成品", | ||||
| "Router": "執貨路線", | "Router": "執貨路線", | ||||
| "Job Order Pickexcution": "工單提料", | |||||
| "No data available": "沒有資料", | |||||
| "Start Scan": "開始掃碼", | "Start Scan": "開始掃碼", | ||||
| "Stop 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": "登出" | "Sign out": "登出" | ||||
| } | } | ||||
| @@ -7,6 +7,7 @@ | |||||
| "Details": "詳情", | "Details": "詳情", | ||||
| "Supplier": "供應商", | "Supplier": "供應商", | ||||
| "Status": "來貨狀態", | "Status": "來貨狀態", | ||||
| "N/A": "不適用", | |||||
| "Release Pick Orders": "放單", | "Release Pick Orders": "放單", | ||||
| "Escalated": "上報狀態", | "Escalated": "上報狀態", | ||||
| "NotEscalated": "無上報", | "NotEscalated": "無上報", | ||||