| @@ -324,9 +324,11 @@ export interface AllJoborderProductProcessInfoResponse { | |||
| assignedTo: number; | |||
| pickOrderId: number; | |||
| pickOrderStatus: string; | |||
| itemCode: string; | |||
| itemName: string; | |||
| requiredQty: number; | |||
| jobOrderId: number; | |||
| uom: string; | |||
| stockInLineId: number; | |||
| jobOrderCode: string; | |||
| productProcessLineCount: number; | |||
| @@ -908,9 +910,9 @@ export const fetchCompletedJobOrderPickOrdersrecords = cache(async () => { | |||
| }, | |||
| ); | |||
| }); | |||
| export const fetchJoForPrintQrCode = cache(async () => { | |||
| export const fetchJoForPrintQrCode = cache(async (date: string) => { | |||
| return serverFetchJson<JobOrderListForPrintQrCodeResponse[]>( | |||
| `${BASE_API_URL}/jo/joForPrintQrCode`, | |||
| `${BASE_API_URL}/jo/joForPrintQrCode/${date}`, | |||
| { | |||
| method: "GET", | |||
| next: { tags: ["jo-print-qr-code"] }, | |||
| @@ -17,6 +17,10 @@ import { | |||
| Paper, | |||
| IconButton, | |||
| Tooltip, | |||
| FormControl, | |||
| InputLabel, | |||
| Select, | |||
| MenuItem, | |||
| } from "@mui/material"; | |||
| import QrCodeIcon from '@mui/icons-material/QrCode'; | |||
| import { useTranslation } from "react-i18next"; | |||
| @@ -50,11 +54,28 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||
| const [page, setPage] = useState(0); | |||
| const [isPrinting, setIsPrinting] = useState(false); | |||
| const [printingId, setPrintingId] = useState<number | null>(null); | |||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||
| const getDateLabel = (offset: number) => { | |||
| return dayjs().subtract(offset, 'day').format('YYYY-MM-DD'); | |||
| }; | |||
| // 根据选择的日期获取实际日期字符串 | |||
| const getDateParam = (dateOption: string): string => { | |||
| if (dateOption === "today") { | |||
| return dayjs().format('YYYY-MM-DD'); | |||
| } else if (dateOption === "yesterday") { | |||
| return dayjs().subtract(1, 'day').format('YYYY-MM-DD'); | |||
| } else if (dateOption === "dayBeforeYesterday") { | |||
| return dayjs().subtract(2, 'day').format('YYYY-MM-DD'); | |||
| } | |||
| return dayjs().format('YYYY-MM-DD'); | |||
| }; | |||
| const fetchJobOrders = useCallback(async () => { | |||
| setLoading(true); | |||
| try { | |||
| const data = await fetchJoForPrintQrCode(); | |||
| const dateParam = getDateParam(selectedDate); | |||
| const data = await fetchJoForPrintQrCode(dateParam); | |||
| setJobOrders(data || []); | |||
| setPage(0); | |||
| } catch (e) { | |||
| @@ -63,7 +84,9 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||
| } finally { | |||
| setLoading(false); | |||
| } | |||
| }, []); | |||
| }, [selectedDate]); | |||
| useEffect(() => { | |||
| fetchJobOrders(); | |||
| @@ -114,6 +137,34 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||
| return ( | |||
| <Box> | |||
| {/* Date Selector */} | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: 'flex-start' }}> | |||
| <Box sx={{ maxWidth: 300 }}> | |||
| <FormControl fullWidth size="small"> | |||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | |||
| <Select | |||
| labelId="date-select-label" | |||
| id="date-select" | |||
| value={selectedDate} | |||
| // label={t("Select Date")} | |||
| onChange={(e) => { | |||
| setSelectedDate(e.target.value); | |||
| }} | |||
| > | |||
| <MenuItem value="today"> | |||
| {t("Today")} ({getDateLabel(0)}) | |||
| </MenuItem> | |||
| <MenuItem value="yesterday"> | |||
| {t("Yesterday")} ({getDateLabel(1)}) | |||
| </MenuItem> | |||
| <MenuItem value="dayBeforeYesterday"> | |||
| {t("Day Before Yesterday")} ({getDateLabel(2)}) | |||
| </MenuItem> | |||
| </Select> | |||
| </FormControl> | |||
| </Box> | |||
| </Stack> | |||
| {loading ? ( | |||
| <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}> | |||
| <CircularProgress /> | |||
| @@ -56,7 +56,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ | |||
| onBack, | |||
| fromJosave, | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const { t } = useTranslation("common"); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | |||
| const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | |||
| @@ -334,16 +334,6 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| const effectiveEquipmentCode = | |||
| scannedEquipmentCode ?? null; | |||
| if (!effectiveEquipmentCode) { | |||
| console.error("No equipment code available"); | |||
| alert(t("Please scan equipment code or equipment detail ID")); | |||
| setIsAutoSubmitting(false); | |||
| if (autoSubmitTimerRef.current) { | |||
| clearTimeout(autoSubmitTimerRef.current); | |||
| autoSubmitTimerRef.current = null; | |||
| } | |||
| return false; | |||
| } | |||
| console.log("Submitting scan data with equipmentCode:", { | |||
| productProcessLineId: lineId, | |||
| @@ -353,7 +343,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| const response = await newUpdateProductProcessLineQrscan({ | |||
| productProcessLineId: lineId, | |||
| equipmentCode: effectiveEquipmentCode, | |||
| equipmentCode: effectiveEquipmentCode ?? "", | |||
| staffNo: scannedStaffNo, | |||
| }); | |||
| @@ -423,6 +413,8 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| setProcessedQrCodes(new Set()); | |||
| setScannedOperatorId(null); | |||
| setScannedEquipmentId(null); | |||
| setScannedStaffNo(null); // ✅ Add this | |||
| setScannedEquipmentCode(null); | |||
| setIsAutoSubmitting(false); // 添加:重置自动提交状态 | |||
| setLineDetailForScan(null); | |||
| // 获取 line detail 以获取 bomProcessEquipmentId | |||
| @@ -446,7 +438,9 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| } | |||
| setIsManualScanning(false); | |||
| setIsAutoSubmitting(false); // 添加:重置自动提交状态 | |||
| setIsAutoSubmitting(false); | |||
| setScannedStaffNo(null); // ✅ Add this | |||
| setScannedEquipmentCode(null); | |||
| stopScan(); | |||
| resetScan(); | |||
| }, [stopScan, resetScan]); | |||
| @@ -461,7 +455,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| } | |||
| }; | |||
| // 提交扫描结果并验证 | |||
| /* | |||
| useEffect(() => { | |||
| console.log("Auto-submit check:", { | |||
| scanningLineId, | |||
| @@ -501,6 +495,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| // 只在组件卸载时清除 | |||
| }; | |||
| }, [scanningLineId, scannedStaffNo, scannedEquipmentCode, isAutoSubmitting, isManualScanning, submitScanAndStart]); | |||
| */ | |||
| useEffect(() => { | |||
| return () => { | |||
| if (autoSubmitTimerRef.current) { | |||
| @@ -518,6 +513,9 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| setScannedEquipmentId(null); | |||
| setProcessedQrCodes(new Set()); | |||
| setScannedStaffNo(null); | |||
| setScannedEquipmentCode(null); | |||
| setProcessedQrCodes(new Set()); | |||
| // 清除之前的定时器 | |||
| if (autoSubmitTimerRef.current) { | |||
| clearTimeout(autoSubmitTimerRef.current); | |||
| @@ -783,7 +781,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| {/* ✅ Show both options */} | |||
| {scannedEquipmentCode | |||
| ? `${t("Equipment Code")}: ${scannedEquipmentCode}` | |||
| : t("Please scan equipment code or equipment detail id") | |||
| : t("Please scan equipment code") | |||
| } | |||
| </Typography> | |||
| </Box> | |||
| @@ -809,7 +807,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => scanningLineId && handleSubmitScanAndStart(scanningLineId)} | |||
| disabled={!scannedStaffNo || (!scannedEquipmentCode)} | |||
| disabled={!scannedStaffNo } | |||
| > | |||
| {t("Submit & Start")} | |||
| </Button> | |||
| @@ -316,7 +316,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| headerAlign: "left", | |||
| type: "number", | |||
| renderCell: (params) => { | |||
| return <Typography sx={{ fontSize: "18px" }}>{params.value}</Typography>; | |||
| return <Typography sx={{ fontWeight: 500 }}>{params.value}</Typography>; | |||
| }, | |||
| }, | |||
| { | |||
| @@ -326,16 +326,28 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| align: "left", | |||
| headerAlign: "left", | |||
| renderCell: (params) => { | |||
| return <Typography sx={{ fontSize: "18px" }}>{params.value || ""}</Typography>; | |||
| return( | |||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | |||
| <Typography sx={{ fontWeight: 500 }}> </Typography> | |||
| <Typography sx={{ fontWeight: 500 }}>{params.value || ""}</Typography> | |||
| <Typography sx={{ fontWeight: 500 }}> </Typography> | |||
| </Box> | |||
| ) | |||
| }, | |||
| }, | |||
| ]; | |||
| const productionProcessesLineRemarkTableRows = | |||
| processData?.productProcessLines?.map((line: any) => ({ | |||
| id: line.seqNo, | |||
| seqNo: line.seqNo, | |||
| description: line.description ?? "", | |||
| })) ?? []; | |||
| @@ -487,21 +499,37 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { | |||
| /> | |||
| </Box> | |||
| ); | |||
| const ProductionProcessesLineRemarkTableContent = () => ( | |||
| <Box sx={{ mt: 2 }}> | |||
| <ProcessSummaryHeader processData={processData} /> | |||
| <StyledDataGrid | |||
| sx={{ | |||
| "--DataGrid-overlayHeight": "100px", | |||
| }} | |||
| disableColumnMenu | |||
| rows={productionProcessesLineRemarkTableRows ?? []} | |||
| columns={productionProcessesLineRemarkTableColumns} | |||
| getRowHeight={() => 'auto'} | |||
| /> | |||
| </Box> | |||
| ); | |||
| const ProductionProcessesLineRemarkTableContent = () => ( | |||
| <Box sx={{ mt: 2 }}> | |||
| <ProcessSummaryHeader processData={processData} /> | |||
| <StyledDataGrid | |||
| sx={{ | |||
| "--DataGrid-overlayHeight": "100px", | |||
| // ✅ Match ProductionProcessDetail font size (default body2 = 0.875rem) | |||
| "& .MuiDataGrid-cell": { | |||
| fontSize: "0.875rem", // ✅ Match default body2 size | |||
| fontWeight: 500, | |||
| }, | |||
| "& .MuiDataGrid-columnHeader": { | |||
| fontSize: "0.875rem", // ✅ Match header size | |||
| fontWeight: 600, | |||
| }, | |||
| // ✅ Ensure empty columns are visible | |||
| "& .MuiDataGrid-columnHeaders": { | |||
| display: "flex", | |||
| }, | |||
| "& .MuiDataGrid-row": { | |||
| display: "flex", | |||
| }, | |||
| }} | |||
| disableColumnMenu | |||
| rows={productionProcessesLineRemarkTableRows ?? []} | |||
| columns={productionProcessesLineRemarkTableColumns} | |||
| getRowHeight={() => 'auto'} | |||
| hideFooter={false} // ✅ Ensure footer is visible | |||
| /> | |||
| </Box> | |||
| ); | |||
| return ( | |||
| @@ -235,10 +235,10 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess | |||
| </Stack> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Item Name")}: {process.itemName} | |||
| {t("Item Name")}: {process.itemCode} {process.itemName} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Required Qty")}: {process.requiredQty} | |||
| {t("Required Qty")}: {process.requiredQty} {process.uom} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Production date")}: {process.date ? dayjs(process.date as any).format(OUTPUT_DATE_FORMAT) : "-"} | |||
| @@ -410,7 +410,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| <Card sx={{ bgcolor: 'success.50', border: '2px solid', borderColor: 'success.main', mb: 3 }}> | |||
| <CardContent> | |||
| <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold"> | |||
| {t("Completed Step")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo}) | |||
| {t("Completed Step")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo}) | |||
| </Typography> | |||
| {/*<Divider sx={{ my: 2 }} />*/} | |||
| @@ -420,27 +420,27 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| {t("Step Information")} | |||
| </Typography> | |||
| <Grid container spacing={2} sx={{ mb: 3 }}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| <strong>{t("Description")}:</strong> {lineDetail?.description || "-"} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| <strong>{t("Operator")}:</strong> {lineDetail?.operatorName || "-"} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| <strong>{t("Equipment")}:</strong> {equipmentName} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| <strong>{t("Status")}:</strong> {lineDetail?.status || "-"} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}> | |||
| <strong>{t("Description")}:</strong> {lineDetail?.description || "-"} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}> | |||
| <strong>{t("Operator")}:</strong> {lineDetail?.operatorName || "-"} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}> | |||
| <strong>{t("Equipment")}:</strong> {equipmentName} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}> | |||
| <strong>{t("Status")}:</strong> {t(lineDetail?.status || "-")} | |||
| </Typography> | |||
| </Grid> | |||
| </Grid> | |||
| {/*<Divider sx={{ my: 2 }} />*/} | |||
| @@ -563,7 +563,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| <Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}> | |||
| <CardContent> | |||
| <Typography variant="h6" color="primary.main" gutterBottom> | |||
| {t("Executing")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo}) | |||
| {t("Executing")}: {lineDetail?.name} ({t("Seq")}:{lineDetail?.seqNo}) | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {lineDetail?.description} | |||
| @@ -397,7 +397,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| // confirmButtonText: t("confirm putaway"), html: ""}); | |||
| // onOpenPutaway(); | |||
| const isJobOrderBom = (stockInLineInfo?.jobOrderId != null || printSource === "productionProcess") | |||
| && stockInLineInfo?.bomDescription === "半成品"; | |||
| && stockInLineInfo?.bomDescription === "WIP"; | |||
| if (isJobOrderBom) { | |||
| // Auto putaway to default warehouse | |||
| const defaultWarehouseId = stockInLineInfo?.defaultWarehouseId ?? 1; | |||
| @@ -417,7 +417,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| itemId: stockInLineInfo?.itemId, // Include Item ID | |||
| purchaseOrderId: stockInLineInfo?.purchaseOrderId, // Include PO ID if exists | |||
| purchaseOrderLineId: stockInLineInfo?.purchaseOrderLineId, // Include POL ID if exists | |||
| acceptedQty: stockInLineInfo?.acceptedQty, // Include acceptedQty | |||
| acceptedQty:acceptQty, // Include acceptedQty | |||
| acceptQty: stockInLineInfo?.acceptedQty, // Putaway quantity | |||
| warehouseId: defaultWarehouseId, | |||
| status: "received", // Use string like PutAwayModal | |||
| @@ -84,7 +84,14 @@ | |||
| "Detail Scheduling": "詳細排程", | |||
| "Customer": "客戶", | |||
| "qcItem": "品檢項目", | |||
| "Item": "物料", | |||
| "Item": "成品/半成品", | |||
| "Today": "今天", | |||
| "Yesterday": "昨天", | |||
| "Input Equipment is not match with process": "輸入的設備與流程不匹配", | |||
| "Staff No is required": "員工編號必填", | |||
| "Day Before Yesterday": "前天", | |||
| "Select Date": "選擇日期", | |||
| "Production Date": "生產日期", | |||
| "QC Check Item": "QC品檢項目", | |||
| "QC Category": "QC品檢模板", | |||
| @@ -179,7 +186,6 @@ | |||
| "Production Process Information": "生產流程信息", | |||
| "Production Process Steps": "生產流程步驟", | |||
| "Scan Operator & Equipment": "掃描操作員和設備", | |||
| "Seq": "序號", | |||
| "Setup Time (mins)": "生產前預備時間(分鐘)", | |||
| "Start": "開始", | |||
| "Start QR Scan": "開始掃碼", | |||
| @@ -195,10 +201,13 @@ | |||
| "Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.", | |||
| "View": "查看", | |||
| "Back": "返回", | |||
| "BoM Material": "物料清單", | |||
| "BoM Material": "成品/半成品清單", | |||
| "N/A": "不適用", | |||
| "Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間次序 | 複雜度", | |||
| "Item Code": "物料編號", | |||
| "Item Code": "成品/半成品名稱", | |||
| "Please scan equipment code": "請掃描設備編號", | |||
| "Equipment Code": "設備編號", | |||
| "Seq": "步驟", | |||
| "Item Name": "物料名稱", | |||
| "Job Order Info": "工單信息", | |||
| "Matching Stock": "工單對料", | |||
| @@ -8,7 +8,7 @@ | |||
| "Code": "工單編號", | |||
| "Name": "成品/半成品名稱", | |||
| "Picked Qty": "已提料數量", | |||
| "Confirm All": "確認所有", | |||
| "Confirm All": "確認所有提料", | |||
| "UoM": "銷售單位", | |||
| "No": "沒有", | |||
| "User not found with staffNo:": "用戶不存在", | |||
| @@ -156,7 +156,9 @@ | |||
| "Reject": "拒絕", | |||
| "Stock Unit": "庫存單位", | |||
| "Group": "組", | |||
| "Item": "物料", | |||
| "Input Equipment is not match with process": "輸入的設備與流程不匹配", | |||
| "Item": "成品/半成品", | |||
| "Select Date": "選擇日期", | |||
| "No Group": "沒有組", | |||
| "No created items": "沒有創建物料", | |||
| "Order Quantity": "需求數量", | |||
| @@ -284,7 +286,6 @@ | |||
| "acceptQty must not greater than": "接受數量不能大於", | |||
| "escalation": "升級", | |||
| "failedQty": "失敗數量", | |||
| "qcItem": "QC物料", | |||
| "qcResult": "QC結果", | |||
| "remarks": "備註", | |||
| "supervisor": "主管", | |||
| @@ -334,13 +335,15 @@ | |||
| "pending": "待處理", | |||
| "Please scan equipment code (optional if not required)": "請掃描設備編號(可選)", | |||
| "Please scan equipment code": "請掃描設備編號", | |||
| "Equipment Code": "設備編號", | |||
| "Please scan operator code": "請掃描操作員編號", | |||
| "Please scan operator code first": "請先掃描操作員編號", | |||
| "Processing Time (mins)": "步驟時間(分鐘)", | |||
| "Production Process Information": "生產流程信息", | |||
| "Production Process Steps": "生產流程步驟", | |||
| "Scan Operator & Equipment": "掃描操作員和設備", | |||
| "Seq": "序號", | |||
| "Seq:": "步驟", | |||
| "Setup Time (mins)": "生產前預備時間(分鐘)", | |||
| "Start": "開始", | |||
| "Start QR Scan": "開始掃碼", | |||
| @@ -366,18 +369,13 @@ | |||
| "View": "查看", | |||
| "Back": "返回", | |||
| "N/A": "不適用", | |||
| "BoM Material": "物料清單", | |||
| "BoM Material": "成品/半成品清單", | |||
| "Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間順序 | 複雜度", | |||
| "Item Code": "物料編號", | |||
| "Item Name": "物料名稱", | |||
| "Enter the number of cartons: ": "請輸入箱數:", | |||
| "Number of cartons": "箱數", | |||
| "You need to enter a number": "您需要輸入一個數字", | |||
| "Number must be at least 1": "數字必須至少為1", | |||
| "Cancel": "取消", | |||
| "Print Pick Record": "打印板頭紙", | |||
| "Printed Successfully.": "成功列印", | |||
| "Job Order Info": "工單信息", | |||
| "Matching Stock": "工單對料", | |||
| "No data found": "沒有找到資料", | |||