From 72737c0cb3668751c615ae09d862ac49bbca8bcb Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 19 Dec 2025 17:55:25 +0800 Subject: [PATCH] update print qr code scan equipment --- src/app/(main)/jo/edit/page.tsx | 7 +- src/app/api/bom/index.ts | 1 + src/app/api/jo/actions.ts | 41 ++- src/app/api/stockIn/index.ts | 1 + src/components/JoSearch/JoCreateFormModal.tsx | 178 ++++++++++--- src/components/JoSearch/JoSearch.tsx | 1 + src/components/Jodetail/JoPickOrderList.tsx | 11 +- .../Jodetail/JobPickExecutionsecondscan.tsx | 44 +++- .../Jodetail/completeJobOrderRecord.tsx | 5 +- .../Jodetail/newJobPickExecution.tsx | 11 +- .../FinishedQcJobOrderList.tsx | 204 +++++++++++++++ .../ProductionProcessDetail.tsx | 101 ++++---- .../ProductionProcessJobOrderDetail.tsx | 4 +- .../ProductionProcessList.tsx | 5 +- .../ProductionProcessPage.tsx | 112 +++++++-- .../ProductionProcessStepExecution.tsx | 233 ++++++++++++++---- src/components/Qc/QcStockInModal.tsx | 46 ++++ src/i18n/zh/common.json | 99 ++++---- src/i18n/zh/jo.json | 14 +- 19 files changed, 909 insertions(+), 209 deletions(-) create mode 100644 src/components/ProductionProcess/FinishedQcJobOrderList.tsx diff --git a/src/app/(main)/jo/edit/page.tsx b/src/app/(main)/jo/edit/page.tsx index 29b9c7f..6868224 100644 --- a/src/app/(main)/jo/edit/page.tsx +++ b/src/app/(main)/jo/edit/page.tsx @@ -25,10 +25,13 @@ const JoEdit: React.FC = async ({ searchParams }) => { try { await fetchJoDetail(parseInt(id)) } catch (e) { + if (e instanceof ServerFetchError && (e.response?.status === 404 || e.response?.status === 400)) { - console.log(e) - notFound(); + console.log("Job Order not found:", e); + } else { + console.error("Error fetching Job Order detail:", e); } + notFound(); } diff --git a/src/app/api/bom/index.ts b/src/app/api/bom/index.ts index 9e8e3ff..c96c52b 100644 --- a/src/app/api/bom/index.ts +++ b/src/app/api/bom/index.ts @@ -8,6 +8,7 @@ export interface BomCombo { label: string; outputQty: number; outputQtyUom: string; + description: string; } export const preloadBomCombo = (() => { diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 26fdc43..1d191ce 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -215,6 +215,7 @@ export interface ProductProcessLineResponse { seqNo: number, name: string, description: string, + equipmentDetailId: number, equipment_name: string, equipmentDetailCode: string, status: string, @@ -260,6 +261,7 @@ export interface ProductProcessWithLinesResponse { outputQtyUom: string; productionPriority: number; jobOrderLines: JobOrderLineInfo[]; + productProcessLines: ProductProcessLineResponse[]; } @@ -321,6 +323,7 @@ export interface AllJoborderProductProcessInfoResponse { bomId?: number; assignedTo: number; pickOrderId: number; + pickOrderStatus: string; itemName: string; requiredQty: number; jobOrderId: number; @@ -347,6 +350,11 @@ export interface ProductProcessLineQrscanUpadteRequest { equipmentTypeSubTypeEquipmentNo?: string; staffNo?: string; } +export interface NewProductProcessLineQrscanUpadteRequest{ + productProcessLineId: number; + equipmentCode?: string; + staffNo?: string; +} export interface ProductProcessLineDetailResponse { id: number, @@ -556,7 +564,16 @@ export interface LotDetailResponse { matchQty?: number | null; } - +export interface JobOrderListForPrintQrCodeResponse { + id: number; + code: string; + name: string; + reqQty: number; + stockOutLineId: number; + stockOutLineQty: number; + stockOutLineStatus: string; + finihedTime: string; +} export const saveProductProcessIssueTime = cache(async (request: SaveProductProcessIssueTimeRequest) => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/ProcessLine/issue`, @@ -658,6 +675,18 @@ export const updateProductProcessLineQrscan = cache(async (request: ProductProce } ); }); + + +export const newUpdateProductProcessLineQrscan = cache(async (request: NewProductProcessLineQrscanUpadteRequest) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/NewUpdate`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + } + ); +}); export const fetchAllJoborderProductProcessInfo = cache(async () => { return serverFetchJson( `${BASE_API_URL}/product-process/Demo/Process/all`, @@ -879,7 +908,15 @@ export const fetchCompletedJobOrderPickOrdersrecords = cache(async () => { }, ); }); - +export const fetchJoForPrintQrCode = cache(async () => { + return serverFetchJson( + `${BASE_API_URL}/jo/joForPrintQrCode`, + { + method: "GET", + next: { tags: ["jo-print-qr-code"] }, + }, + ); +}); // 获取已完成的 Job Order pick order records export const fetchCompletedJobOrderPickOrderRecords = cache(async (userId: number) => { return serverFetchJson( diff --git a/src/app/api/stockIn/index.ts b/src/app/api/stockIn/index.ts index 2cafaca..c1655d5 100644 --- a/src/app/api/stockIn/index.ts +++ b/src/app/api/stockIn/index.ts @@ -128,6 +128,7 @@ export interface StockInLine { dnNo?: string; dnDate?: number[]; stockQty?: number; + bomDescription?: string; handlerId?: number; putAwayLines?: PutAwayLine[]; qcResult?: QcResult[]; diff --git a/src/components/JoSearch/JoCreateFormModal.tsx b/src/components/JoSearch/JoCreateFormModal.tsx index 62c508c..f8d8103 100644 --- a/src/components/JoSearch/JoCreateFormModal.tsx +++ b/src/components/JoSearch/JoCreateFormModal.tsx @@ -8,14 +8,16 @@ import { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pi import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs, { Dayjs } from "dayjs"; import { isFinite } from "lodash"; -import React, { SetStateAction, SyntheticEvent, useCallback, useEffect } from "react"; +import React, { SetStateAction, SyntheticEvent, useCallback, useEffect, useMemo } from "react"; import { Controller, FormProvider, SubmitErrorHandler, SubmitHandler, useForm, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { msg } from "../Swal/CustomAlerts"; +import { JobTypeResponse } from "@/app/api/jo/actions"; interface Props { open: boolean; bomCombo: BomCombo[]; + jobTypes: JobTypeResponse[]; onClose: () => void; onSearch: () => void; } @@ -23,6 +25,7 @@ interface Props { const JoCreateFormModal: React.FC = ({ open, bomCombo, + jobTypes, onClose, onSearch, }) => { @@ -30,19 +33,130 @@ const JoCreateFormModal: React.FC = ({ const formProps = useForm({ mode: "onChange", }); - const { reset, trigger, watch, control, register, formState: { errors } } = formProps + const { reset, trigger, watch, control, register, formState: { errors }, setValue } = formProps + + // 监听 bomId 变化 + const selectedBomId = watch("bomId"); const onModalClose = useCallback(() => { reset() onClose() - }, []) + }, [reset, onClose]) + + const handleAutoCompleteChange = useCallback( + (event: SyntheticEvent, value: BomCombo, onChange: (...event: any[]) => void) => { + console.log("BOM changed to:", value); + onChange(value.id); + + // 1) 根据 BOM 设置数量 + if (value.outputQty != null) { + formProps.setValue("reqQty", Number(value.outputQty), { shouldValidate: true, shouldDirty: true }); + } - const handleAutoCompleteChange = useCallback((event: SyntheticEvent, value: BomCombo, onChange: (...event: any[]) => void) => { - onChange(value.id) - if (value.outputQty != null) { - formProps.setValue("reqQty", Number(value.outputQty), { shouldValidate: true, shouldDirty: true }) + // 2) 选 BOM 时,把日期默认设为“今天” + const today = dayjs(); + const todayStr = dayjsToDateString(today, "input"); // 你已经有的工具函数 + formProps.setValue("planStart", todayStr, { shouldValidate: true, shouldDirty: true }); + }, + [formProps] + ); + + // 使用 useMemo 来计算过滤后的 jobTypes,响应 selectedBomId 变化 + /* + const filteredJobTypes = useMemo(() => { + console.log("getFilteredJobTypes called, selectedBomId:", selectedBomId); + + if (!selectedBomId) { + console.log("No BOM selected, returning all jobTypes:", jobTypes); + return jobTypes; } - }, []) + + const selectedBom = bomCombo.find(bom => bom.id === selectedBomId); + console.log("Selected BOM:", selectedBom); + console.log("Selected BOM full object:", JSON.stringify(selectedBom, null, 2)); + + if (!selectedBom) { + console.log("BOM not found, returning all jobTypes"); + return jobTypes; + } + + // 检查 description 是否存在 + const description = selectedBom.description; + console.log("BOM description (raw):", description); + console.log("BOM description type:", typeof description); + console.log("BOM description is undefined?", description === undefined); + console.log("BOM description is null?", description === null); + + if (!description) { + console.log("BOM description is missing or empty, returning all jobTypes"); + return jobTypes; + } + + const descriptionUpper = description.toUpperCase(); + console.log("BOM description (uppercase):", descriptionUpper); + console.log("All jobTypes:", jobTypes); + + let filtered: JobTypeResponse[] = []; + + if (descriptionUpper === "WIP") { + filtered = jobTypes.filter(jt => { + const jobTypeName = jt.name.toUpperCase(); + const shouldInclude = jobTypeName !== "FG"; + console.log(`JobType ${jt.name} (${jobTypeName}): ${shouldInclude ? "included" : "excluded"}`); + return shouldInclude; + }); + } else if (descriptionUpper === "FG") { + filtered = jobTypes.filter(jt => { + const jobTypeName = jt.name.toUpperCase(); + const shouldInclude = jobTypeName !== "WIP"; + console.log(`JobType ${jt.name} (${jobTypeName}): ${shouldInclude ? "included" : "excluded"}`); + return shouldInclude; + }); + } else { + filtered = jobTypes; + } + + console.log("Filtered jobTypes:", filtered); + return filtered; + }, [bomCombo, jobTypes, selectedBomId]); +*/ + // 当 BOM 改变时,自动选择匹配的 Job Type + useEffect(() => { + if (!selectedBomId) { + return; + } + + const selectedBom = bomCombo.find(bom => bom.id === selectedBomId); + if (!selectedBom) { + return; + } + + const description = selectedBom.description; + console.log("Auto-select effect - BOM description:", description); + + if (!description) { + console.log("Auto-select effect - No description found, skipping auto-select"); + return; + } + + const descriptionUpper = description.toUpperCase(); + console.log("Auto-selecting Job Type for BOM description:", descriptionUpper); + + // 查找匹配的 Job Type + const matchingJobType = jobTypes.find(jt => { + const jobTypeName = jt.name.toUpperCase(); + const matches = jobTypeName === descriptionUpper; + console.log(`Checking JobType ${jt.name} (${jobTypeName}) against ${descriptionUpper}: ${matches}`); + return matches; + }); + + if (matchingJobType) { + console.log("Found matching Job Type, setting jobTypeId to:", matchingJobType.id); + setValue("jobTypeId", matchingJobType.id, { shouldValidate: true, shouldDirty: true }); + } else { + console.log("No matching Job Type found for description:", descriptionUpper); + } + }, [selectedBomId, bomCombo, jobTypes, setValue]); const handleDateTimePickerChange = useCallback((value: Dayjs | null, onChange: (...event: any[]) => void) => { if (value != null) { @@ -98,7 +212,7 @@ const JoCreateFormModal: React.FC = ({ = ({ /> - ( - - {t("Job Type")} - { const value = event.target.value; + console.log("Job Type changed to:", value); field.onChange(value === "" ? undefined : Number(value)); }} - > + > {t("Please select")} - {t("FG")} - {t("WIP")} - {t("R&D")} - {t("STF")} - {t("Other")} - - {/*{error && {error.message}}*/} - - )} - /> - + {/* {filteredJobTypes.map((jobType) => (*/} + {jobTypes.map((jobType) => ( + + {t(jobType.name)} + + ))} + + + ); + }} + /> + = ({ defaultInputs, bomCombo, printerCombo, jobT { setInputs({ ...defaultInputs }); // 创建新对象,确保引用变化 diff --git a/src/components/Jodetail/JoPickOrderList.tsx b/src/components/Jodetail/JoPickOrderList.tsx index a14176f..907738e 100644 --- a/src/components/Jodetail/JoPickOrderList.tsx +++ b/src/components/Jodetail/JoPickOrderList.tsx @@ -47,7 +47,10 @@ const JoPickOrderList: React.FC = ({ onSwitchToRecordTab }) =>{ useEffect(() => { fetchPickOrders(); }, [fetchPickOrders]); - + const handleBackToList = useCallback(() => { + setSelectedPickOrderId(undefined); + setSelectedJobOrderId(undefined); + }, []); // If a pick order is selected, show JobPickExecution detail view if (selectedPickOrderId !== undefined) { return ( @@ -64,7 +67,11 @@ const JoPickOrderList: React.FC = ({ onSwitchToRecordTab }) =>{ {t("Back to List")} - + ); } diff --git a/src/components/Jodetail/JobPickExecutionsecondscan.tsx b/src/components/Jodetail/JobPickExecutionsecondscan.tsx index 458a372..10471f1 100644 --- a/src/components/Jodetail/JobPickExecutionsecondscan.tsx +++ b/src/components/Jodetail/JobPickExecutionsecondscan.tsx @@ -575,7 +575,8 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { const handleSubmitAllScanned = useCallback(async () => { const scannedLots = combinedLotData.filter(lot => - lot.matchStatus === 'scanned' + lot.matchStatus === 'scanned'|| + lot.stockOutLineStatus === 'completed' ); if (scannedLots.length === 0) { @@ -615,7 +616,13 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { if (successCount > 0) { setQrScanSuccess(true); - setTimeout(() => setQrScanSuccess(false), 2000); + setTimeout(() => { + setQrScanSuccess(false); + // 添加:提交成功后返回到列表 + if (onBack) { + onBack(); + } + }, 2000); } } catch (error: any) { @@ -635,7 +642,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { } finally { setIsSubmittingAll(false); } - }, [combinedLotData, fetchJobOrderData, currentPickOrderId, handleUnassign]); + }, [combinedLotData, fetchJobOrderData, currentPickOrderId, handleUnassign, onBack]); const scannedItemsCount = useMemo(() => { return combinedLotData.filter(lot => lot.matchStatus === 'scanned').length; @@ -1113,7 +1120,25 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { )} {/* Combined Lot Table */} + + + {/* {!isManualScanning ? ( @@ -1167,7 +1192,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { {t("QR code verified.")} )} - + */} @@ -1179,7 +1204,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { {t("Item Name")} {t("Lot No")} {t("Lot Required Pick Qty")} - {t("Scan Result")} + {/* {t("Scan Result")} */} {t("Submit Required Pick Qty")} @@ -1235,7 +1260,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { return requiredQty.toLocaleString()+'('+lot.uomShortDesc+')'; })()} - + {/* {lot.matchStatus?.toLowerCase() === 'scanned' || lot.matchStatus?.toLowerCase() === 'completed' ? ( @@ -1269,7 +1294,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { )} - + */} @@ -1280,9 +1305,10 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; handlePickQtyChange(lotKey, submitQty); handleSubmitPickQtyWithQty(lot, submitQty); + updateSecondQrScanStatus(lot.pickOrderLineId, lot.lotId, currentUserId || 0, submitQty); }} disabled={ - lot.matchStatus !== 'scanned' || + //lot.matchStatus !== 'scanned' || lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'rejected' @@ -1294,7 +1320,7 @@ const JobPickExecution: React.FC = ({ filterArgs, onBack }) => { minWidth: '70px' }} > - {t("Submit")} + {t("Confirm")}
+ + + {t("Code")} + {t("Name")} + {t("Required Qty")} + {t("Finished Time")} + {t("Action")} + + + + {paged.map((jobOrder) => { + const statusColor = jobOrder.stockOutLineStatus === "completed" + ? "success" + : "default"; + + const finishedTimeDisplay = jobOrder.finihedTime + ? dayjs(jobOrder.finihedTime).format(OUTPUT_DATE_FORMAT) + : "-"; + + const isCurrentlyPrinting = isPrinting && printingId === jobOrder.id; + + return ( + + + {jobOrder.code} + + {jobOrder.name} + {jobOrder.reqQty} + + {finishedTimeDisplay} + + + handlePrint(jobOrder)} + disabled={isPrinting || printerCombo.length <= 0 || !selectedPrinter} + size="small" + > + {isCurrentlyPrinting ? ( + + ) : ( + + )} + + + + + ); + })} + +
+
+ + {jobOrders.length > 0 && ( + setPage(p)} + rowsPerPageOptions={[PER_PAGE]} + /> + )} +
+ )} +
+ ); +}; + +export default FinishedQcJobOrderList; \ No newline at end of file diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 6723fc4..3d70605 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -33,16 +33,11 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { - fetchProductProcessById, - updateProductProcessLineQrscan, + // updateProductProcessLineQrscan, + newUpdateProductProcessLineQrscan, fetchProductProcessLineDetail, - ProductProcessLineDetailResponse, JobOrderProcessLineDetailResponse, - updateLineOutput, ProductProcessLineInfoResponse, - ProductProcessResponse, - ProductProcessLineResponse, - completeProductProcessLine, startProductProcessLine, fetchProductProcessesByJobOrderId } from "@/app/api/jo/actions"; @@ -80,8 +75,10 @@ const ProductionProcessDetail: React.FC = ({ const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [scannedOperatorId, setScannedOperatorId] = useState(null); const [scannedEquipmentId, setScannedEquipmentId] = useState(null); - const [scannedEquipmentTypeSubTypeEquipmentNo, setScannedEquipmentTypeSubTypeEquipmentNo] = useState(null); + // const [scannedEquipmentTypeSubTypeEquipmentNo, setScannedEquipmentTypeSubTypeEquipmentNo] = useState(null); const [scannedStaffNo, setScannedStaffNo] = useState(null); + // const [scannedEquipmentDetailId, setScannedEquipmentDetailId] = useState(null); + const [scannedEquipmentCode, setScannedEquipmentCode] = useState(null); const [scanningLineId, setScanningLineId] = useState(null); const [lineDetailForScan, setLineDetailForScan] = useState(null); const [showScanDialog, setShowScanDialog] = useState(false); @@ -224,7 +221,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { const currentLine = lines.find(l => l.id === lineId); if (currentLine && currentLine.equipment_name) { const equipmentTypeSubTypeEquipmentNo = `${currentLine.equipment_name}-${equipmentNo}號`; - setScannedEquipmentTypeSubTypeEquipmentNo(equipmentTypeSubTypeEquipmentNo); + setScannedEquipmentCode(equipmentTypeSubTypeEquipmentNo); console.log(`Generated equipmentTypeSubTypeEquipmentNo: ${equipmentTypeSubTypeEquipmentNo}`); } else { // 如果找不到 line,尝试从 API 获取 line detail @@ -232,11 +229,10 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { fetchProductProcessLineDetail(lineId) .then((lineDetail) => { // 从 lineDetail 中获取 equipment_name - // 注意:lineDetail 的结构可能不同,需要根据实际 API 响应调整 const equipmentName = (lineDetail as any).equipment || (lineDetail as any).equipmentType || ""; if (equipmentName) { const equipmentTypeSubTypeEquipmentNo = `${equipmentName}-${equipmentNo}號`; - setScannedEquipmentTypeSubTypeEquipmentNo(equipmentTypeSubTypeEquipmentNo); + setScannedEquipmentCode(equipmentTypeSubTypeEquipmentNo); console.log(`Generated equipmentTypeSubTypeEquipmentNo from API: ${equipmentTypeSubTypeEquipmentNo}`); } else { console.warn(`Equipment name not found in line detail for lineId: ${lineId}`); @@ -249,7 +245,6 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { return; } - // 员工编号格式:{2fitestu任何内容} - 直接作为 staffNo // 例如:{2fitestu123} = staffNo: "123" // 例如:{2fitestustaff001} = staffNo: "staff001" @@ -271,11 +266,11 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { return; } - // 检查 equipmentTypeSubTypeEquipmentNo 格式 - const equipmentCodeMatch = trimmedValue.match(/^(?:equipmentTypeSubTypeEquipmentNo|EquipmentType-SubType-EquipmentNo):\s*(.+)$/i); + // 检查 equipmentCode 格式 + const equipmentCodeMatch = trimmedValue.match(/^(?:equipmentTypeSubTypeEquipmentNo|EquipmentType-SubType-EquipmentNo|equipmentCode):\s*(.+)$/i); if (equipmentCodeMatch) { const equipmentCode = equipmentCodeMatch[1].trim(); - setScannedEquipmentTypeSubTypeEquipmentNo(equipmentCode); + setScannedEquipmentCode(equipmentCode); return; } @@ -286,11 +281,10 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { setScannedStaffNo(String(qrData.staffNo)); } if (qrData.equipmentTypeSubTypeEquipmentNo || qrData.equipmentCode) { - setScannedEquipmentTypeSubTypeEquipmentNo( + setScannedEquipmentCode( String(qrData.equipmentTypeSubTypeEquipmentNo ?? qrData.equipmentCode) ); } - // TODO: 处理 JSON 格式的 QR 码 } catch { // 普通文本格式 - 尝试判断是 staffNo 还是 equipmentCode if (trimmedValue.length > 0) { @@ -299,7 +293,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { setScannedStaffNo(trimmedValue); } else if (trimmedValue.includes("-")) { // 可能包含 "-" 的是设备代码(如 "包裝機類-真空八爪魚機-1號") - setScannedEquipmentTypeSubTypeEquipmentNo(trimmedValue); + setScannedEquipmentCode(trimmedValue); } } } @@ -323,36 +317,51 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { console.log("submitScanAndStart called with:", { lineId, scannedStaffNo, - scannedEquipmentTypeSubTypeEquipmentNo, + // scannedEquipmentTypeSubTypeEquipmentNo, + scannedEquipmentCode, }); if (!scannedStaffNo) { console.log("No staffNo, cannot submit"); setIsAutoSubmitting(false); - return false; // 没有 staffNo,不能提交 + return false; } try { - // 获取 line detail 以检查 bomProcessEquipmentId const lineDetail = lineDetailForScan || await fetchProductProcessLineDetail(lineId); - - // 提交 staffNo 和 equipmentTypeSubTypeEquipmentNo - console.log("Submitting scan data:", { + + // ✅ 统一使用一个最终的 equipmentCode(优先用 scannedEquipmentCode,其次用 scannedEquipmentTypeSubTypeEquipmentNo) + 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, staffNo: scannedStaffNo, - equipmentTypeSubTypeEquipmentNo: scannedEquipmentTypeSubTypeEquipmentNo, + equipmentCode: effectiveEquipmentCode, }); - const response = await updateProductProcessLineQrscan({ + const response = await newUpdateProductProcessLineQrscan({ productProcessLineId: lineId, - equipmentTypeSubTypeEquipmentNo: scannedEquipmentTypeSubTypeEquipmentNo || undefined, - staffNo: scannedStaffNo || undefined, + equipmentCode: effectiveEquipmentCode, + staffNo: scannedStaffNo, }); console.log("Scan submit response:", response); - // 检查响应中的 message 字段来判断是否成功 - if (response && response.message) { + if (response && response.type === "error") { + console.error("Scan validation failed:", response.message); + alert(t(response.message) || t("Validation failed. Please check your input.")); setIsAutoSubmitting(false); if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); @@ -360,25 +369,31 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { } return false; } - - // 验证通过,继续执行后续步骤 + console.log("Validation passed, starting line..."); handleStopScan(); setShowScanDialog(false); setIsAutoSubmitting(false); - + await handleStartLine(lineId); setSelectedLineId(lineId); setIsExecutingLine(true); await fetchProcessDetail(); - + return true; } catch (error) { console.error("Error submitting scan:", error); + alert("Failed to submit scan data. Please try again."); setIsAutoSubmitting(false); return false; } - }, [scannedStaffNo, scannedEquipmentTypeSubTypeEquipmentNo, lineDetailForScan, t, fetchProcessDetail]); + }, [ + scannedStaffNo, + scannedEquipmentCode, + lineDetailForScan, + t, + fetchProcessDetail, + ]); const handleSubmitScanAndStart = useCallback(async (lineId: number) => { console.log("handleSubmitScanAndStart called with lineId:", lineId); @@ -451,15 +466,16 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { console.log("Auto-submit check:", { scanningLineId, scannedStaffNo, - scannedEquipmentTypeSubTypeEquipmentNo, + scannedEquipmentCode, isAutoSubmitting, isManualScanning, }); + // ✅ Update condition to check for either equipmentTypeSubTypeEquipmentNo OR equipmentDetailId if ( scanningLineId && scannedStaffNo !== null && - scannedEquipmentTypeSubTypeEquipmentNo !== null && + (scannedEquipmentCode !== null) && !isAutoSubmitting && isManualScanning ) { @@ -484,7 +500,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { // 注意:这里不立即清除定时器,因为我们需要它执行 // 只在组件卸载时清除 }; - }, [scanningLineId, scannedStaffNo, scannedEquipmentTypeSubTypeEquipmentNo, isAutoSubmitting, isManualScanning, submitScanAndStart]); + }, [scanningLineId, scannedStaffNo, scannedEquipmentCode, isAutoSubmitting, isManualScanning, submitScanAndStart]); useEffect(() => { return () => { if (autoSubmitTimerRef.current) { @@ -764,9 +780,10 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { - {scannedEquipmentTypeSubTypeEquipmentNo - ? `${t("Equipment Type/Code")}: ${scannedEquipmentTypeSubTypeEquipmentNo}` - : t("Please scan equipment code (optional if not required)") + {/* ✅ Show both options */} + {scannedEquipmentCode + ? `${t("Equipment Code")}: ${scannedEquipmentCode}` + : t("Please scan equipment code or equipment detail id") } @@ -792,7 +809,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 1fc6f08..cd1ea10 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -115,7 +115,7 @@ const ProductionProcessJobOrderDetail: React.FC { - if (line.type?.toLowerCase() === "consumables") { + if (line.type?.toLowerCase() === "consumables" || line.type?.toLowerCase() === "nm") { return null; } const inventory = inventoryData.find(inv => @@ -158,7 +158,7 @@ const isStockSufficient = (line: JobOrderLine) => { const stockCounts = useMemo(() => { // 过滤掉 consumables 类型的 lines const nonConsumablesLines = jobOrderLines.filter( - line => line.type?.toLowerCase() !== "consumables" && line.type?.toLowerCase() !== "cmb" + line => line.type?.toLowerCase() !== "consumables" && line.type?.toLowerCase() !== "cmb" && line.type?.toLowerCase() !== "nm" ); const total = nonConsumablesLines.length; const sufficient = nonConsumablesLines.filter(isStockSufficient).length; diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx index 857979a..d68a821 100644 --- a/src/components/ProductionProcess/ProductionProcessList.tsx +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -156,9 +156,10 @@ const ProductProcessList: React.FC = ({ onSelectProcess const closeNewModal = useCallback(() => { // const response = updateJo({ id: 1, status: "storing" }); setOpenModal(false); // Close the modal first + fetchProcesses(); // setTimeout(() => { // }, 300); // Add a delay to avoid immediate re-trigger of useEffect -}, []); +}, [fetchProcesses]); const startIdx = page * PER_PAGE; const paged = processes.slice(startIdx, startIdx + PER_PAGE); @@ -269,7 +270,7 @@ const ProductProcessList: React.FC = ({ onSelectProcess @@ -462,6 +628,7 @@ const ProductionProcessStepExecution: React.FC setShowOutputTable(true)} > {t("Order Complete")} @@ -521,39 +688,7 @@ const ProductionProcessStepExecution: React.FC - {/* byproduct */} - {/* - - - - {t("By-product")} - - - - setOutputData({ - ...outputData, - byproductQty: parseInt(e.target.value) || 0 - })} - /> - - - setOutputData({ - ...outputData, - byproductUom: e.target.value - })} - /> - - - */} + {/* defect 1 */} diff --git a/src/components/Qc/QcStockInModal.tsx b/src/components/Qc/QcStockInModal.tsx index 3da6c8e..6dcac0c 100644 --- a/src/components/Qc/QcStockInModal.tsx +++ b/src/components/Qc/QcStockInModal.tsx @@ -396,6 +396,52 @@ const QcStockInModal: React.FC = ({ // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", // confirmButtonText: t("confirm putaway"), html: ""}); // onOpenPutaway(); + const isJobOrderBom = (stockInLineInfo?.jobOrderId != null || printSource === "productionProcess") + && stockInLineInfo?.bomDescription === "半成品"; + if (isJobOrderBom) { + // Auto putaway to default warehouse + const defaultWarehouseId = stockInLineInfo?.defaultWarehouseId ?? 1; + + // Get warehouse name from warehouse prop or use default + let defaultWarehouseName = "2F-W201-#A-01"; // Default warehouse name + if (warehouse && warehouse.length > 0) { + const defaultWarehouse = warehouse.find(w => w.id === defaultWarehouseId); + if (defaultWarehouse) { + defaultWarehouseName = `${defaultWarehouse.code} - ${defaultWarehouse.name}`; + } + } + + // Create putaway data + const putawayData = { + id: stockInLineInfo?.id, // Include ID + 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 + acceptQty: stockInLineInfo?.acceptedQty, // Putaway quantity + warehouseId: defaultWarehouseId, + status: "received", // Use string like PutAwayModal + productionDate: data.productionDate ? (Array.isArray(data.productionDate) ? arrayToDateString(data.productionDate, "input") : data.productionDate) : undefined, + expiryDate: data.expiryDate ? (Array.isArray(data.expiryDate) ? arrayToDateString(data.expiryDate, "input") : data.expiryDate) : undefined, + receiptDate: data.receiptDate ? (Array.isArray(data.receiptDate) ? arrayToDateString(data.receiptDate, "input") : data.receiptDate) : undefined, + inventoryLotLines: [{ + warehouseId: defaultWarehouseId, + qty: stockInLineInfo?.acceptedQty, // Simplified like PutAwayModal + }], + } as StockInLineEntry & ModalFormInput; + + try { + // Use updateStockInLine directly like PutAwayModal does + const res = await updateStockInLine(putawayData); + if (Boolean(res.id)) { + console.log("Auto putaway completed for job order bom"); + } + } catch (error) { + console.error("Error during auto putaway:", error); + alert(t("Auto putaway failed. Please complete putaway manually.")); + } + + } closeHandler({}, "backdropClick"); // setTabIndex(1); // Need to go Putaway tab? } else { diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 7fa7091..bf77270 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -1,5 +1,4 @@ { - "dashboard": "資訊展示面板", "Edit": "編輯", "Job Order Production Process": "工單生產流程", @@ -7,12 +6,28 @@ "Search Criteria": "搜尋條件", "All": "全部", "No options": "沒有選項", + "Finished QC Job Orders": "完成QC工單", + "Reset": "重置", "Search": "搜尋", + "Staff No Required": "員工編號必填", + "User Not Found": "用戶不存在", + "Time Remaining": "剩餘時間", + "Select Printer": "選擇打印機", + "Finished Time": "完成時間", + "Printer": "打印機", + "Finished Qc Job Order List": "完成QC工單列表", + "Total finished Qc Job Order": "總完成QC工單數量", + "Timer Paused": "計時器已暫停", + "User not found with staffNo:": "用戶不存在", + "Total finished QC job orders": "總完成QC工單數量", + "Over Time": "超時", "Code": "編號", + "Staff No": "員工編號", "code": "編號", "Name": "名稱", "Assignment successful": "分配成功", + "Pass": "通過", "Unable to get user ID": "無法獲取用戶ID", "Unknown error: ": "未知錯誤: ", "Please try again later.": "請稍後重試。", @@ -25,7 +40,6 @@ "R&D": "研發", "STF": "樣品", "Other": "其他", - "Add some entries!": "添加條目", "Add Record": "新增", "Clean Record": "重置", @@ -49,35 +63,35 @@ "Changeover Time": "生產後轉換時間", "Warehouse": "倉庫", "Supplier": "供應商", - "Purchase Order":"採購單", - "Demand Forecast":"需求預測", + "Purchase Order": "採購單", + "Demand Forecast": "需求預測", "Pick Order": "提料單", - "Deliver Order":"送貨訂單", - "Project":"專案", - "Product":"產品", - "Material":"材料", - "mat":"原料", + "Deliver Order": "送貨訂單", + "Project": "專案", + "Product": "產品", + "Material": "材料", + "mat": "原料", "consumables": "消耗品", "non-consumables": "非消耗品", "fg": "成品", "sfg": "半成品", "item": "貨品", - "FG":"成品", - "Qty":"數量", - "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", - "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", - "Delivery Order":"送貨訂單", - "Detail Scheduling":"詳細排程", - "Customer":"客戶", - "qcItem":"品檢項目", - "Item":"物料", - "Production Date":"生產日期", - "QC Check Item":"QC品檢項目", - "QC Category":"QC品檢模板", - "qcCategory":"品檢模板", - "QC Check Template":"QC檢查模板", - "Mail":"郵件", - "Import Testing":"匯入測試", + "FG": "成品", + "Qty": "數量", + "FG & Material Demand Forecast Detail": "成品及材料需求預測詳情", + "View item In-out And inventory Ledger": "查看物料出入庫及庫存日誌", + "Delivery Order": "送貨訂單", + "Detail Scheduling": "詳細排程", + "Customer": "客戶", + "qcItem": "品檢項目", + "Item": "物料", + "Production Date": "生產日期", + "QC Check Item": "QC品檢項目", + "QC Category": "QC品檢模板", + "qcCategory": "品檢模板", + "QC Check Template": "QC檢查模板", + "Mail": "郵件", + "Import Testing": "匯入測試", "Overview": "總覽", "Projects": "專案", "Create Project": "新增專案", @@ -86,21 +100,21 @@ "Qc Item": "QC 項目", "FG Production Schedule": "FG 生產排程", "Inventory": "庫存", - "scheduling":"排程", + "scheduling": "排程", "settings": "設定", "items": "物料", - "edit":"編輯", - "Edit Equipment Type":"設備類型詳情", - "Edit Equipment":"設備詳情", - "equipmentType":"設備類型", - "Description":"描述", + "edit": "編輯", + "Edit Equipment Type": "設備類型詳情", + "Edit Equipment": "設備詳情", + "equipmentType": "設備類型", + "Description": "描述", "Details": "詳情", - "Equipment Type Details":"設備類型詳情", - "Save":"儲存", - "Cancel":"取消", - "Equipment Details":"設備詳情", - "Exclude Date":"排除日期", - "Finished Goods Name":"成品名稱", + "Equipment Type Details": "設備類型詳情", + "Save": "儲存", + "Cancel": "取消", + "Equipment Details": "設備詳情", + "Exclude Date": "排除日期", + "Finished Goods Name": "成品名稱", "create": "新增", "hr": "小時", "hrs": "小時", @@ -123,7 +137,6 @@ "Stop Scan": "停止掃碼", "Scan Result": "掃碼結果", "Expiry Date": "有效期", - "Pick Order Code": "提料單編號", "Target Date": "需求日期", "Lot Required Pick Qty": "批號需求數量", @@ -133,8 +146,6 @@ "No data available": "沒有資料", "jodetail": "工單細節", "Sign out": "登出", - - "By-product": "副產品", "Complete Step": "完成步驟", "Defect": "不良品", @@ -161,7 +172,6 @@ "Output Qty": "輸出數量", "Pending": "待處理", "pending": "待處理", - "Please scan equipment code (optional if not required)": "請掃描設備編號(可選)", "Please scan operator code": "請掃描操作員編號", "Please scan operator code first": "請先掃描操作員編號", @@ -173,11 +183,10 @@ "Setup Time (mins)": "生產前預備時間(分鐘)", "Start": "開始", "Start QR Scan": "開始掃碼", - "Status": "狀態", + "Status": "狀態", "in_progress": "進行中", "In_Progress": "進行中", "inProgress": "進行中", - "Step Name": "名稱", "Stop QR Scan": "停止掃碼", "Submit & Start": "提交並開始", @@ -188,7 +197,7 @@ "Back": "返回", "BoM Material": "物料清單", "N/A": "不適用", - "Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間順序 | 複雜度", + "Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間次序 | 複雜度", "Item Code": "物料編號", "Item Name": "物料名稱", "Job Order Info": "工單信息", @@ -234,4 +243,4 @@ "Lines with sufficient stock: ": "可提料項目數量: ", "Lines with insufficient stock: ": "未能提料項目數量: ", "Total lines: ": "總數量:" -} +} \ No newline at end of file diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 8c1bc89..678306e 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -8,11 +8,19 @@ "Code": "工單編號", "Name": "成品/半成品名稱", "Picked Qty": "已提料數量", - "Req. Qty": "需求數量", + "Confirm All": "確認所有", "UoM": "銷售單位", "No": "沒有", + "User not found with staffNo:": "用戶不存在", + "Time Remaining": "剩餘時間", + "Over Time": "超時", + "Staff No:": "員工編號:", + "Timer Paused": "計時器已暫停", + "Staff No Required": "員工編號必填", + "Staff No": "員工編號", "Status": "工單狀態", "Lot No.": "批號", + "Pass": "通過", "Delete Job Order": "刪除工單", "Bom": "半成品/成品編號", "Release": "放單", @@ -136,7 +144,7 @@ "Confirm Lot Substitution": "確認批號替換", "Processing...": "處理中", "Complete Job Order Record": "已完成工單記錄", - "Back": "返回", + "Lot Details": "批號細節", "No lot details available": "沒有批號細節", "Second Scan Completed": "對料已完成", @@ -366,7 +374,7 @@ "Number of cartons": "箱數", "You need to enter a number": "您需要輸入一個數字", "Number must be at least 1": "數字必須至少為1", - "Confirm": "確認", + "Cancel": "取消", "Print Pick Record": "打印板頭紙", "Printed Successfully.": "成功列印",