diff --git a/src/app/api/do/actions.tsx b/src/app/api/do/actions.tsx index 1aa4ded..6f70823 100644 --- a/src/app/api/do/actions.tsx +++ b/src/app/api/do/actions.tsx @@ -181,7 +181,17 @@ export const fetchTicketReleaseTable = cache(async ()=> { } ); }); - +export const startBatchReleaseAsyncSingle = cache(async (data: { doId: number; userId: number }) => { + const { doId, userId } = data; + return await serverFetchJson<{ id: number|null; code: string; entity?: any }>( + `${BASE_API_URL}/doPickOrder/batch-release/async-single?userId=${userId}`, + { + method: "POST", + body: JSON.stringify(doId), + headers: { "Content-Type": "application/json" }, + } + ); +}); export const startBatchReleaseAsync = cache(async (data: { ids: number[]; userId: number }) => { const { ids, userId } = data; return await serverFetchJson<{ id: number|null; code: string; entity?: any }>( diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 2b92e31..3050215 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -383,6 +383,8 @@ export interface JobOrderProcessLineDetailResponse { byproductName: string; byproductQty: number; byproductUom: string; + productProcessIssueId: number; + productProcessIssueStatus: string; } export interface JobOrderLineInfo { id: number, @@ -453,6 +455,90 @@ export interface JobTypeResponse { id: number; name: string; } +export interface SaveProductProcessIssueTimeRequest { + productProcessLineId: number; + reason: string; +} +export interface JobOrderLotsHierarchicalResponse { + pickOrder: PickOrderInfoResponse; + pickOrderLines: PickOrderLineWithLotsResponse[]; +} + +export interface PickOrderInfoResponse { + id: number | null; + code: string | null; + consoCode: string | null; + targetDate: string | null; + type: string | null; + status: string | null; + assignTo: number | null; + jobOrder: JobOrderBasicInfoResponse; +} + +export interface JobOrderBasicInfoResponse { + id: number; + code: string; + name: string; +} + +export interface PickOrderLineWithLotsResponse { + id: number; + itemId: number | null; + itemCode: string | null; + itemName: string | null; + requiredQty: number | null; + uomCode: string | null; + uomDesc: string | null; + status: string | null; + lots: LotDetailResponse[]; +} + +export interface LotDetailResponse { + lotId: number | null; + lotNo: string | null; + expiryDate: string | null; + location: string | null; + availableQty: number | null; + requiredQty: number | null; + actualPickQty: number | null; + processingStatus: string | null; + lotAvailability: string | null; + pickOrderId: number | null; + pickOrderCode: string | null; + pickOrderConsoCode: string | null; + pickOrderLineId: number | null; + stockOutLineId: number | null; + suggestedPickLotId: number | null; + stockOutLineQty: number | null; + stockOutLineStatus: string | null; + routerIndex: number | null; + routerArea: string | null; + routerRoute: string | null; + uomShortDesc: string | null; + matchStatus?: string | null; + matchBy?: number | null; + matchQty?: number | null; +} + + +export const saveProductProcessIssueTime = cache(async (request: SaveProductProcessIssueTimeRequest) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/ProcessLine/issue`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + } + ); +}); +export const saveProductProcessResumeTime = cache(async (productProcessIssueId: number) => { + return serverFetchJson( + `${BASE_API_URL}/product-process/Demo/ProcessLine/resume/${productProcessIssueId}`, + { + method: "POST", + } + ); +}); export const deleteJobOrder=cache(async (jobOrderId: number) => { return serverFetchJson( `${BASE_API_URL}/jo/demo/deleteJobOrder/${jobOrderId}`, @@ -480,7 +566,7 @@ export const updateJoPickOrderHandledBy = cache(async (request: UpdateJoPickOrde ); }); export const fetchJobOrderLotsHierarchicalByPickOrderId = cache(async (pickOrderId: number) => { - return serverFetchJson( + return serverFetchJson( `${BASE_API_URL}/jo/all-lots-hierarchical-by-pick-order/${pickOrderId}`, { method: "GET", @@ -568,7 +654,7 @@ export const startProductProcessLine = async (lineId: number) => { }; export const completeProductProcessLine = async (lineId: number) => { return serverFetchJson( - `${BASE_API_URL}/product-process/demo/ProcessLine/complete/${lineId}`, + `${BASE_API_URL}/product-process/Demo/ProcessLine/complete/${lineId}`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -715,7 +801,7 @@ export const assignJobOrderPickOrder = async (pickOrderId: number, userId: numbe // 获取 Job Order 分层数据 export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => { - return serverFetchJson( + return serverFetchJson( `${BASE_API_URL}/jo/all-lots-hierarchical/${userId}`, { method: "GET", diff --git a/src/app/api/jo/index.ts b/src/app/api/jo/index.ts index 43cd7bf..76c1d59 100644 --- a/src/app/api/jo/index.ts +++ b/src/app/api/jo/index.ts @@ -30,6 +30,8 @@ export interface JobOrder { type: string; jobTypeId: number; jobTypeName: string; + sufficientCount: number; + insufficientCount: number; // TODO pack below into StockInLineInfo stockInLineId?: number; stockInLineStatus?: string; diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 5c39506..ece6600 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -1063,27 +1063,7 @@ export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId: }; } }; -// Update the existing function to use the non-auto-assign endpoint -export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise => { - try { - console.log("🔍 Fetching all pick order line lot details for userId:", userId); - - // Use the non-auto-assign endpoint - const data = await serverFetchJson( - `${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`, - { - method: 'GET', - next: { tags: ["pickorder"] }, - } - ); - - console.log(" Fetched lot details:", data); - return data; - } catch (error) { - console.error("❌ Error fetching lot details:", error); - return []; - } -}); + export const fetchAllPickOrderDetails = cache(async (userId?: number) => { if (!userId) { return { diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx index b655715..5f73626 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -25,7 +25,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw const [isAssigning, setIsAssigning] = useState(false); //const [selectedDate, setSelectedDate] = useState("today"); const [selectedDate, setSelectedDate] = useState("today"); - + const [releaseType, setReleaseType] = useState("batch"); const loadSummaries = useCallback(async () => { setIsLoadingSummary(true); try { @@ -40,8 +40,8 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw } const [s2, s4] = await Promise.all([ - fetchStoreLaneSummary("2/F", dateParam), - fetchStoreLaneSummary("4/F", dateParam) + fetchStoreLaneSummary("2/F", dateParam, releaseType), + fetchStoreLaneSummary("4/F", dateParam, releaseType) ]); setSummary2F(s2); setSummary4F(s4); @@ -50,7 +50,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw } finally { setIsLoadingSummary(false); } - }, [selectedDate]); + }, [selectedDate, releaseType]); // 初始化 useEffect(() => { @@ -168,10 +168,34 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw {t("Day After Tomorrow")} ({getDateLabel(2)}) - + + + {t("Release Type")} + + + + = ({ onPickOrderAssigned, onSw {t("EDT - Lane Code (Unassigned/Total)")} + + + + + + + @@ -391,6 +422,11 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw + + + + + ); }; diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx index 99d64bc..8c3bd7e 100644 --- a/src/components/JoSearch/JoSearch.tsx +++ b/src/components/JoSearch/JoSearch.tsx @@ -120,20 +120,10 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT }; const getStockCounts = (jo: JobOrder) => { - const detailedJo = detailedJos.get(jo.id); - - if (!detailedJo?.pickLines || detailedJo.pickLines.length === 0) { - return { total: 0, sufficient: 0, insufficient: 0 }; - } - - const totalLines = detailedJo.pickLines.length; - const sufficientLines = detailedJo.pickLines.filter(pickLine => isStockSufficient(pickLine)).length; - const insufficientLines = totalLines - sufficientLines; return { - total: totalLines, - sufficient: sufficientLines, - insufficient: insufficientLines + sufficient: jo.sufficientCount, + insufficient: jo.insufficientCount }; }; @@ -210,7 +200,7 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT const stockCounts = getStockCounts(row); return ( 0 ? 'red' : 'green' }}> - {stockCounts.sufficient}/{stockCounts.total} + {stockCounts.sufficient}/{stockCounts.sufficient + stockCounts.insufficient} ); } @@ -229,17 +219,20 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT // onClick: (record) => onDetailClick(record), // buttonIcon: , renderCell: (row) => { - const btnSx = getButtonSx(row); + //const btnSx = getButtonSx(row); return ( + // >{btnSx.label} + >{t("View")} + ) } }, @@ -349,7 +342,7 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT const [openModal, setOpenModal] = useState(false); const [modalInfo, setModalInfo] = useState(); - +/* const onDetailClick = useCallback((record: JobOrder) => { if (record.status == "processing") { @@ -367,7 +360,10 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT router.push(`/jo/edit?id=${record.id}`) } }, []) - +*/ + const onDetailClick = useCallback((record: JobOrder) => { + router.push(`/jo/edit?id=${record.id}`) + }, []) const closeNewModal = useCallback(() => { // const response = updateJo({ id: 1, status: "storing" }); setOpenModal(false); // Close the modal first diff --git a/src/components/Jodetail/JobPickExecution.tsx b/src/components/Jodetail/JobPickExecution.tsx index 15bddda..c86d203 100644 --- a/src/components/Jodetail/JobPickExecution.tsx +++ b/src/components/Jodetail/JobPickExecution.tsx @@ -34,13 +34,19 @@ import { checkPickOrderCompletion, PickOrderCompletionResponse, checkAndCompletePickOrderByConsoCode, - confirmLotSubstitution + confirmLotSubstitution, + updateStockOutLineStatusByQRCodeAndLotNo, + batchSubmitList, + batchSubmitListRequest, + batchSubmitListLineRequest, } from "@/app/api/pickOrder/actions"; // 修改:使用 Job Order API import { fetchJobOrderLotsHierarchical, fetchUnassignedJobOrderPickOrders, - assignJobOrderPickOrder + assignJobOrderPickOrder, + updateJo, + JobOrderLotsHierarchicalResponse, } from "@/app/api/jo/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import { @@ -322,14 +328,13 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; - + const [jobOrderData, setJobOrderData] = useState(null); // 修改:使用 Job Order 数据结构 - const [jobOrderData, setJobOrderData] = useState(null); - const [combinedLotData, setCombinedLotData] = useState([]); - const [combinedDataLoading, setCombinedDataLoading] = useState(false); - const [originalCombinedData, setOriginalCombinedData] = useState([]); + const [filteredLotData, setFilteredLotData] = useState([]); - // 添加未分配订单状态 + + const [combinedDataLoading, setCombinedDataLoading] = useState(false); + const [unassignedOrders, setUnassignedOrders] = useState([]); const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false); @@ -375,6 +380,60 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const [lastProcessedQr, setLastProcessedQr] = useState(''); const [isRefreshingData, setIsRefreshingData] = useState(false); + + + const getAllLotsFromHierarchical = useCallback(( + data: JobOrderLotsHierarchicalResponse | null + ): any[] => { + if (!data || !data.pickOrder || !data.pickOrderLines) { + return []; + } + + const allLots: any[] = []; + + data.pickOrderLines.forEach((line) => { + if (line.lots && line.lots.length > 0) { + line.lots.forEach((lot) => { + allLots.push({ + ...lot, + pickOrderLineId: line.id, + itemId: line.itemId, + itemCode: line.itemCode, + itemName: line.itemName, + uomCode: line.uomCode, + uomDesc: line.uomDesc, + pickOrderLineRequiredQty: line.requiredQty, + pickOrderLineStatus: line.status, + jobOrderId: data.pickOrder.jobOrder.id, + jobOrderCode: data.pickOrder.jobOrder.code, + // 添加 pickOrder 信息(如果需要) + pickOrderId: data.pickOrder.id, + pickOrderCode: data.pickOrder.code, + pickOrderConsoCode: data.pickOrder.consoCode, + pickOrderTargetDate: data.pickOrder.targetDate, + pickOrderType: data.pickOrder.type, + pickOrderStatus: data.pickOrder.status, + pickOrderAssignTo: data.pickOrder.assignTo, + }); + }); + } + }); + + return allLots; + }, []); + const allLotsFromData = useMemo(() => { + return getAllLotsFromHierarchical(jobOrderData); + }, [jobOrderData, getAllLotsFromHierarchical]); + + // 用于显示的 combinedLotData(支持搜索过滤) + const combinedLotData = useMemo(() => { + return filteredLotData.length > 0 ? filteredLotData : allLotsFromData; + }, [filteredLotData, allLotsFromData]); + + // 用于搜索的原始数据 + const originalCombinedData = useMemo(() => { + return allLotsFromData; + }, [allLotsFromData]); // 修改:加载未分配的 Job Order 订单 const loadUnassignedOrders = useCallback(async () => { setIsLoadingUnassigned(true); @@ -466,113 +525,53 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { try { const userIdToUse = userId || currentUserId; - console.log(" fetchJobOrderData called with userId:", userIdToUse); - if (!userIdToUse) { console.warn("⚠️ No userId available, skipping API call"); setJobOrderData(null); - setCombinedLotData([]); - setOriginalCombinedData([]); return; } + window.dispatchEvent(new CustomEvent('jobOrderDataStatus', { detail: { hasData: false, tabIndex: 0 } })); - // 使用 Job Order API + + // 直接使用类型化的响应 const jobOrderData = await fetchJobOrderLotsHierarchical(userIdToUse); - console.log(" Job Order data:", jobOrderData); + console.log(" Job Order data (hierarchical):", jobOrderData); setJobOrderData(jobOrderData); - // Transform hierarchical data to flat structure for the table - const flatLotData: any[] = []; - - if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) { - jobOrderData.pickOrderLines.forEach((line: any) => { - if (line.lots && line.lots.length > 0) { - line.lots.forEach((lot: any) => { - flatLotData.push({ - // Pick order info - pickOrderId: jobOrderData.pickOrder.id, - pickOrderCode: jobOrderData.pickOrder.code, - pickOrderConsoCode: jobOrderData.pickOrder.consoCode, - pickOrderTargetDate: jobOrderData.pickOrder.targetDate, - pickOrderType: jobOrderData.pickOrder.type, - pickOrderStatus: jobOrderData.pickOrder.status, - pickOrderAssignTo: jobOrderData.pickOrder.assignTo, - - // Pick order line info - pickOrderLineId: line.id, - pickOrderLineRequiredQty: line.requiredQty, - pickOrderLineStatus: line.status, - - // Item info - itemId: line.itemId, - itemCode: line.itemCode, - itemName: line.itemName, - uomCode: line.uomCode, - uomDesc: line.uomDesc, - - // Lot info - lotId: lot.lotId, - lotNo: lot.lotNo, - expiryDate: lot.expiryDate, - location: lot.location, - availableQty: lot.availableQty, - requiredQty: lot.requiredQty, - actualPickQty: lot.actualPickQty, - lotStatus: lot.lotStatus, - lotAvailability: lot.lotAvailability, - processingStatus: lot.processingStatus, - stockOutLineId: lot.stockOutLineId, - stockOutLineStatus: lot.stockOutLineStatus, - stockOutLineQty: lot.stockOutLineQty, - suggestedPickLotId: lot.suggestedPickLotId, - // Router info - routerIndex: lot.routerIndex, - secondQrScanStatus: lot.secondQrScanStatus, - routerArea: lot.routerArea, - routerRoute: lot.routerRoute, - uomShortDesc: lot.uomShortDesc - }); - }); - } - }); - } + // 使用辅助函数获取所有 lots(用于计算完成状态等) + const allLots = getAllLotsFromHierarchical(jobOrderData); + setFilteredLotData(allLots); + const hasData = allLots.length > 0; - console.log(" Transformed flat lot data:", flatLotData); - setCombinedLotData(flatLotData); - setOriginalCombinedData(flatLotData); - const hasData = flatLotData.length > 0; window.dispatchEvent(new CustomEvent('jobOrderDataStatus', { detail: { hasData: hasData, tabIndex: 0 } })); - // 计算完成状态并发送事件 - const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) => + + // 计算完成状态 + const allCompleted = allLots.length > 0 && allLots.every((lot) => lot.processingStatus === 'completed' ); - // 发送完成状态事件,包含标签页信息 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: allCompleted, - tabIndex: 0 // 明确指定这是来自标签页 0 的事件 + tabIndex: 0 } })); } catch (error) { console.error("❌ Error fetching job order data:", error); setJobOrderData(null); - setCombinedLotData([]); - setOriginalCombinedData([]); - // 如果加载失败,禁用打印按钮 window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { detail: { allLotsCompleted: false, @@ -582,7 +581,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } finally { setCombinedDataLoading(false); } - }, [currentUserId]); + }, [currentUserId, getAllLotsFromHierarchical]); // 修改:初始化时加载数据 useEffect(() => { @@ -828,28 +827,70 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { setIsConfirmingLot(false); } }, [expectedLotData, scannedLotData, selectedLotForQr, fetchJobOrderData]); - + const handleFastQrScan = useCallback(async (lotNo: string) => { + const matchingLot = combinedLotData.find(lot => + lot.lotNo && lot.lotNo === lotNo + ); + + if (!matchingLot || !matchingLot.stockOutLineId) { + console.warn(`⚠️ Fast scan: Lot ${lotNo} not found or no stockOutLineId`); + return; + } + + try { + const res = await updateStockOutLineStatusByQRCodeAndLotNo({ + pickOrderLineId: matchingLot.pickOrderLineId, + inventoryLotNo: lotNo, + stockOutLineId: matchingLot.stockOutLineId, + itemId: matchingLot.itemId, + status: "checked", + }); + + if (res.code === "checked" || res.code === "SUCCESS") { + const entity = res.entity as any; + + // ✅ 更新 filteredLotData(如果存在)或刷新数据 + if (filteredLotData.length > 0) { + setFilteredLotData(prev => prev.map((lot: any) => { + if (lot.stockOutLineId === matchingLot.stockOutLineId && + lot.pickOrderLineId === matchingLot.pickOrderLineId) { + return { + ...lot, + stockOutLineStatus: 'checked', + stockOutLineQty: entity?.qty ? Number(entity.qty) : lot.stockOutLineQty, + }; + } + return lot; + })); + } + + // ✅ 刷新 jobOrderData 以更新所有计算值 + await fetchJobOrderData(); + + console.log("✅ Fast scan completed successfully"); + } + } catch (error) { + console.error(`❌ Fast scan error for ${lotNo}:`, error); + } + }, [combinedLotData, filteredLotData, fetchJobOrderData]); const processOutsideQrCode = useCallback(async (latestQr: string) => { - // Don't process if confirmation modal is open + // Don't process if confirmation modal is open if (lotConfirmationOpen) { console.log("⏸️ Confirmation modal is open, skipping QR processing"); return; } - + + // 1) Parse JSON safely let qrData: any = null; try { qrData = JSON.parse(latestQr); } catch { - console.log("QR is not JSON format"); - // Handle non-JSON QR codes as direct lot numbers - const directLotNo = latestQr.replace(/[{}]/g, ''); - if (directLotNo) { - console.log(`Processing direct lot number: ${directLotNo}`); - await handleQrCodeSubmit(directLotNo); - } + console.log("QR content is not JSON; skipping lotNo direct submit to avoid false matches."); + setQrScanError(true); + setQrScanSuccess(false); return; } - + try { // Only use the new API when we have JSON with stockInLineId + itemId if (!(qrData?.stockInLineId && qrData?.itemId)) { @@ -859,18 +900,6 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { return; } - // First, fetch stock in line info to get the lot number - let stockInLineInfo: any; - try { - stockInLineInfo = await fetchStockInLineInfo(qrData.stockInLineId); - console.log("Stock in line info:", stockInLineInfo); - } catch (error) { - console.error("Error fetching stock in line info:", error); - setQrScanError(true); - setQrScanSuccess(false); - return; - } - // Call new analyze-qr-code API const analysis = await analyzeQrCode({ itemId: qrData.itemId, @@ -892,13 +921,12 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } = analysis || {}; // 1) Find all lots for the same item from current expected list - const sameItemLotsInExpected = combinedLotData.filter(l => + const sameItemLotsInExpected = combinedLotData.filter((l: any) => (l.itemId && analyzedItemId && l.itemId === analyzedItemId) || (l.itemCode && analyzedItemCode && l.itemCode === analyzedItemCode) ); if (!sameItemLotsInExpected || sameItemLotsInExpected.length === 0) { - // Case 3: No item code match console.error("No item match in expected lots for scanned code"); setQrScanError(true); setQrScanSuccess(false); @@ -906,7 +934,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } // Find the ACTIVE suggested lot (not rejected lots) - const activeSuggestedLots = sameItemLotsInExpected.filter(lot => + const activeSuggestedLots = sameItemLotsInExpected.filter((lot: any) => lot.lotAvailability !== 'rejected' && lot.stockOutLineStatus !== 'rejected' && lot.stockOutLineStatus !== 'completed' @@ -919,21 +947,78 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { return; } - // Use the first active suggested lot as the "expected" lot + // 2) Check if scanned lot is exactly in active suggested lots + const exactLotMatch = activeSuggestedLots.find((l: any) => + (scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) || + (scanned?.lotNo && l.lotNo === scanned.lotNo) + ); + + if (exactLotMatch && scanned?.lotNo) { + // ✅ Case 1: 使用 updateStockOutLineStatusByQRCodeAndLotNo API(更快) + console.log(`✅ Exact lot match found for ${scanned.lotNo}, using fast API`); + + if (!exactLotMatch.stockOutLineId) { + console.warn("No stockOutLineId on exactLotMatch, cannot update status by QR."); + setQrScanError(true); + setQrScanSuccess(false); + return; + } + + try { + const res = await updateStockOutLineStatusByQRCodeAndLotNo({ + pickOrderLineId: exactLotMatch.pickOrderLineId, + inventoryLotNo: scanned.lotNo, + stockOutLineId: exactLotMatch.stockOutLineId, + itemId: exactLotMatch.itemId, + status: "checked", + }); + + if (res.code === "checked" || res.code === "SUCCESS") { + setQrScanError(false); + setQrScanSuccess(true); + + // ✅ 刷新数据而不是直接更新 state + await fetchJobOrderData(); + console.log("✅ Status updated, data refreshed"); + } else if (res.code === "LOT_NUMBER_MISMATCH") { + console.warn("Backend reported LOT_NUMBER_MISMATCH:", res.message); + setQrScanError(true); + setQrScanSuccess(false); + } else if (res.code === "ITEM_MISMATCH") { + console.warn("Backend reported ITEM_MISMATCH:", res.message); + setQrScanError(true); + setQrScanSuccess(false); + } else { + console.warn("Unexpected response code from backend:", res.code); + setQrScanError(true); + setQrScanSuccess(false); + } + } catch (e) { + console.error("Error calling updateStockOutLineStatusByQRCodeAndLotNo:", e); + setQrScanError(true); + setQrScanSuccess(false); + } + + return; // ✅ 直接返回,不再调用 handleQrCodeSubmit + } + + // Case 2: Item matches but lot number differs -> open confirmation modal const expectedLot = activeSuggestedLots[0]; + if (!expectedLot) { + console.error("Could not determine expected lot for confirmation"); + setQrScanError(true); + setQrScanSuccess(false); + return; + } - // 2) Check if the scanned lot matches exactly - if (scanned?.lotNo === expectedLot.lotNo) { - // Case 1: Exact match - process normally - console.log(` Exact lot match: ${scanned.lotNo}`); + // Check if the expected lot is already the scanned lot (after substitution) + if (expectedLot.lotNo === scanned?.lotNo) { + console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`); await handleQrCodeSubmit(scanned.lotNo); return; } - // Case 2: Same item, different lot - show confirmation modal console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); - - // DON'T stop scanning - just pause QR processing by showing modal setSelectedLotForQr(expectedLot); handleLotMismatch( { @@ -955,7 +1040,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { setQrScanSuccess(false); return; } - }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch, lotConfirmationOpen]); + }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch, lotConfirmationOpen, fetchJobOrderData]); const handleManualInputSubmit = useCallback(() => { @@ -1229,7 +1314,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { }, [fetchJobOrderData, checkAndAutoAssignNext]); const handleSubmitAllScanned = useCallback(async () => { const scannedLots = combinedLotData.filter(lot => - lot.stockOutLineStatus === 'checked' // Only submit items that are scanned but not yet submitted + lot.stockOutLineStatus === 'checked' ); if (scannedLots.length === 0) { @@ -1238,62 +1323,53 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } setIsSubmittingAll(true); - console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); + console.log(`📦 Submitting ${scannedLots.length} scanned items using batchSubmitList...`); try { - // Submit all items in parallel using Promise.all - const submitPromises = scannedLots.map(async (lot) => { - const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; + // 转换为 batchSubmitList 所需的格式 + const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { + const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty || 0; const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + submitQty; let newStatus = 'partially_completed'; - if (cumulativeQty >= lot.requiredQty) { + if (cumulativeQty >= (lot.requiredQty || 0)) { newStatus = 'completed'; } - console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`); - - // Update stock out line - await updateStockOutLineStatus({ - id: lot.stockOutLineId, - status: newStatus, - qty: cumulativeQty - }); - - // Update inventory - if (submitQty > 0) { - await updateInventoryLotLineQuantities({ - inventoryLotLineId: lot.lotId, - qty: submitQty, - status: 'available', - operation: 'pick' - }); - } - - // Check if pick order is completed - if (newStatus === 'completed' && lot.pickOrderConsoCode) { - await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); - } - - return { success: true, lotNo: lot.lotNo }; + return { + stockOutLineId: Number(lot.stockOutLineId) || 0, + pickOrderLineId: Number(lot.pickOrderLineId), + inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, + requiredQty: Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0), + actualPickQty: Number(cumulativeQty), + stockOutLineStatus: newStatus, + pickOrderConsoCode: String(lot.pickOrderConsoCode || ''), + noLot: Boolean(false) // Job Order 通常都有 lot + }; }); - // Wait for all submissions to complete - const results = await Promise.all(submitPromises); - const successCount = results.filter(r => r.success).length; + const request: batchSubmitListRequest = { + userId: currentUserId || 0, + lines: lines + }; - console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); + // 使用 batchSubmitList API + const result = await batchSubmitList(request); + console.log(`📥 Batch submit result:`, result); - // Refresh data once after all submissions - await fetchJobOrderData(); + // 刷新数据 + await fetchJobOrderData(); // 或 pickOrderId,根据页面 - if (successCount > 0) { + if (result && result.code === "SUCCESS") { setQrScanSuccess(true); setTimeout(() => { setQrScanSuccess(false); checkAndAutoAssignNext(); }, 2000); + } else { + console.error("Batch submit failed:", result); + setQrScanError(true); } } catch (error) { @@ -1302,7 +1378,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } finally { setIsSubmittingAll(false); } - }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext]); + }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext, currentUserId]); // Calculate scanned items count const scannedItemsCount = useMemo(() => { @@ -1409,38 +1485,8 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { }, ]; - const handleSearch = useCallback((query: Record) => { - setSearchQuery({ ...query }); - console.log("Search query:", query); - - if (!originalCombinedData) return; - - const filtered = originalCombinedData.filter((lot: any) => { - const pickOrderCodeMatch = !query.pickOrderCode || - lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase()); - - const itemCodeMatch = !query.itemCode || - lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase()); - - const itemNameMatch = !query.itemName || - lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase()); - - const lotNoMatch = !query.lotNo || - lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase()); - - return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch; - }); - - setCombinedLotData(filtered); - console.log("Filtered lots count:", filtered.length); - }, [originalCombinedData]); + - const handleReset = useCallback(() => { - setSearchQuery({}); - if (originalCombinedData) { - setCombinedLotData(originalCombinedData); - } - }, [originalCombinedData]); const handlePageChange = useCallback((event: unknown, newPage: number) => { setPaginationController(prev => ({ diff --git a/src/components/Jodetail/newJobPickExecution.tsx b/src/components/Jodetail/newJobPickExecution.tsx index 5a89614..6f7fda2 100644 --- a/src/components/Jodetail/newJobPickExecution.tsx +++ b/src/components/Jodetail/newJobPickExecution.tsx @@ -34,7 +34,11 @@ import { checkPickOrderCompletion, PickOrderCompletionResponse, checkAndCompletePickOrderByConsoCode, - confirmLotSubstitution + confirmLotSubstitution, + updateStockOutLineStatusByQRCodeAndLotNo, // ✅ 添加 + batchSubmitList, // ✅ 添加 + batchSubmitListRequest, // ✅ 添加 + batchSubmitListLineRequest, } from "@/app/api/pickOrder/actions"; // 修改:使用 Job Order API import { @@ -42,7 +46,8 @@ import { //fetchUnassignedJobOrderPickOrders, assignJobOrderPickOrder, fetchJobOrderLotsHierarchicalByPickOrderId, - updateJoPickOrderHandledBy + updateJoPickOrderHandledBy, + JobOrderLotsHierarchicalResponse, } from "@/app/api/jo/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import { @@ -326,11 +331,9 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const currentUserId = session?.id ? parseInt(session.id) : undefined; // 修改:使用 Job Order 数据结构 - const [jobOrderData, setJobOrderData] = useState(null); - const [combinedLotData, setCombinedLotData] = useState([]); + const [combinedDataLoading, setCombinedDataLoading] = useState(false); - const [originalCombinedData, setOriginalCombinedData] = useState([]); - + // 添加未分配订单状态 const [unassignedOrders, setUnassignedOrders] = useState([]); const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false); @@ -343,7 +346,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const [qrScanInput, setQrScanInput] = useState(''); const [qrScanError, setQrScanError] = useState(false); const [qrScanSuccess, setQrScanSuccess] = useState(false); - + const [jobOrderData, setJobOrderData] = useState(null); const [pickQtyData, setPickQtyData] = useState>({}); const [searchQuery, setSearchQuery] = useState>({}); @@ -376,7 +379,52 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [lastProcessedQr, setLastProcessedQr] = useState(''); const [isRefreshingData, setIsRefreshingData] = useState(false); + const getAllLotsFromHierarchical = useCallback(( + data: JobOrderLotsHierarchicalResponse | null + ): any[] => { + if (!data || !data.pickOrder || !data.pickOrderLines) { + return []; + } + const allLots: any[] = []; + + data.pickOrderLines.forEach((line) => { + if (line.lots && line.lots.length > 0) { + line.lots.forEach((lot) => { + allLots.push({ + ...lot, + pickOrderLineId: line.id, + itemId: line.itemId, + itemCode: line.itemCode, + itemName: line.itemName, + uomCode: line.uomCode, + uomDesc: line.uomDesc, + pickOrderLineRequiredQty: line.requiredQty, + pickOrderLineStatus: line.status, + jobOrderId: data.pickOrder.jobOrder.id, + jobOrderCode: data.pickOrder.jobOrder.code, + // 添加 pickOrder 信息(如果需要) + pickOrderId: data.pickOrder.id, + pickOrderCode: data.pickOrder.code, + pickOrderConsoCode: data.pickOrder.consoCode, + pickOrderTargetDate: data.pickOrder.targetDate, + pickOrderType: data.pickOrder.type, + pickOrderStatus: data.pickOrder.status, + pickOrderAssignTo: data.pickOrder.assignTo, + }); + }); + } + }); + + return allLots; + }, []); + const combinedLotData = useMemo(() => { + return getAllLotsFromHierarchical(jobOrderData); + }, [jobOrderData, getAllLotsFromHierarchical]); + + const originalCombinedData = useMemo(() => { + return getAllLotsFromHierarchical(jobOrderData); + }, [jobOrderData, getAllLotsFromHierarchical]); // 修改:加载未分配的 Job Order 订单 const loadUnassignedOrders = useCallback(async () => { setIsLoadingUnassigned(true); @@ -467,120 +515,27 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { if (!pickOrderId) { console.warn("⚠️ No pickOrderId provided, skipping API call"); setJobOrderData(null); - setCombinedLotData([]); - setOriginalCombinedData([]); return; } - console.log("🔍 Fetching job order data by pickOrderId:", pickOrderId); - - window.dispatchEvent(new CustomEvent('jobOrderDataStatus', { - detail: { - hasData: false, - tabIndex: 0 - } - })); - + // 直接使用类型化的响应 const jobOrderData = await fetchJobOrderLotsHierarchicalByPickOrderId(pickOrderId); - console.log("✅ Job Order data:", jobOrderData); + console.log("✅ Job Order data (hierarchical):", jobOrderData); setJobOrderData(jobOrderData); - // Transform hierarchical data to flat structure for the table - const flatLotData: any[] = []; - - if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) { - jobOrderData.pickOrderLines.forEach((line: any) => { - if (line.lots && line.lots.length > 0) { - line.lots.forEach((lot: any) => { - flatLotData.push({ - pickOrderId: jobOrderData.pickOrder.id, - pickOrderCode: jobOrderData.pickOrder.code, - pickOrderConsoCode: jobOrderData.pickOrder.consoCode, - pickOrderTargetDate: jobOrderData.pickOrder.targetDate, - pickOrderType: jobOrderData.pickOrder.type, - pickOrderStatus: jobOrderData.pickOrder.status, - pickOrderAssignTo: jobOrderData.pickOrder.assignTo, - - // Pick order line info - pickOrderLineId: line.id, - pickOrderLineRequiredQty: line.requiredQty, - pickOrderLineStatus: line.status, - - // Item info - itemId: line.itemId, - itemCode: line.itemCode, - itemName: line.itemName, - uomCode: line.uomCode, - uomDesc: line.uomDesc, - - // Lot info - lotId: lot.lotId, - lotNo: lot.lotNo, - expiryDate: lot.expiryDate, - location: lot.location, - availableQty: lot.availableQty, - requiredQty: lot.requiredQty, - actualPickQty: lot.actualPickQty, - lotStatus: lot.lotStatus, - lotAvailability: lot.lotAvailability, - processingStatus: lot.processingStatus, - stockOutLineId: lot.stockOutLineId, - stockOutLineStatus: lot.stockOutLineStatus, - stockOutLineQty: lot.stockOutLineQty, - suggestedPickLotId: lot.suggestedPickLotId, - // Router info - routerIndex: lot.routerIndex, - secondQrScanStatus: lot.secondQrScanStatus, - routerArea: lot.routerArea, - routerRoute: lot.routerRoute, - uomShortDesc: lot.uomShortDesc - }); - }); - } - }); - } - - console.log("✅ Transformed flat lot data:", flatLotData); - setCombinedLotData(flatLotData); - setOriginalCombinedData(flatLotData); - - const hasData = flatLotData.length > 0; - window.dispatchEvent(new CustomEvent('jobOrderDataStatus', { - detail: { - hasData: hasData, - tabIndex: 0 - } - })); + // 使用辅助函数获取所有 lots(不再扁平化) + const allLots = getAllLotsFromHierarchical(jobOrderData); - // Calculate completion status and send event - const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) => - lot.processingStatus === 'completed' - ); - - window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { - detail: { - allLotsCompleted: allCompleted, - tabIndex: 0 - } - })); + // ... 其他逻辑保持不变 ... } catch (error) { console.error("❌ Error fetching job order data:", error); setJobOrderData(null); - setCombinedLotData([]); - setOriginalCombinedData([]); - - window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { - detail: { - allLotsCompleted: false, - tabIndex: 0 - } - })); } finally { setCombinedDataLoading(false); } - }, []); + }, [getAllLotsFromHierarchical]); const updateHandledBy = useCallback(async (pickOrderId: number, itemId: number) => { if (!currentUserId || !pickOrderId || !itemId) { return; @@ -796,7 +751,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { pickOrderLineId: selectedLotForQr.pickOrderLineId, stockOutLineId: selectedLotForQr.stockOutLineId, originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId || selectedLotForQr.lotId, - newInventoryLotLineId: newLotLineId + newInventoryLotNo: scannedLotData.lotNo }); console.log(" Lot substitution result:", substitutionResult); @@ -947,10 +902,53 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { // 2) Check if the scanned lot matches exactly if (scanned?.lotNo === expectedLot.lotNo) { - // Case 1: Exact match - process normally - console.log(` Exact lot match: ${scanned.lotNo}`); - await handleQrCodeSubmit(scanned.lotNo); - return; + // ✅ Case 1: 使用 updateStockOutLineStatusByQRCodeAndLotNo API(更快) + console.log(`✅ Exact lot match found for ${scanned.lotNo}, using fast API`); + + if (!expectedLot.stockOutLineId) { + console.warn("No stockOutLineId on expectedLot, cannot update status by QR."); + setQrScanError(true); + setQrScanSuccess(false); + return; + } + + try { + const res = await updateStockOutLineStatusByQRCodeAndLotNo({ + pickOrderLineId: expectedLot.pickOrderLineId, + inventoryLotNo: scanned.lotNo, + stockOutLineId: expectedLot.stockOutLineId, + itemId: expectedLot.itemId, + status: "checked", + }); + + if (res.code === "checked" || res.code === "SUCCESS") { + setQrScanError(false); + setQrScanSuccess(true); + + // ✅ 刷新数据而不是直接更新 state + const pickOrderId = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; + await fetchJobOrderData(pickOrderId); + console.log("✅ Status updated, data refreshed"); + } else if (res.code === "LOT_NUMBER_MISMATCH") { + console.warn("Backend reported LOT_NUMBER_MISMATCH:", res.message); + setQrScanError(true); + setQrScanSuccess(false); + } else if (res.code === "ITEM_MISMATCH") { + console.warn("Backend reported ITEM_MISMATCH:", res.message); + setQrScanError(true); + setQrScanSuccess(false); + } else { + console.warn("Unexpected response code from backend:", res.code); + setQrScanError(true); + setQrScanSuccess(false); + } + } catch (e) { + console.error("Error calling updateStockOutLineStatusByQRCodeAndLotNo:", e); + setQrScanError(true); + setQrScanSuccess(false); + } + + return; // ✅ 直接返回,不再调用 handleQrCodeSubmit } // Case 2: Same item, different lot - show confirmation modal @@ -1255,7 +1253,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { }, [fetchJobOrderData, checkAndAutoAssignNext]); const handleSubmitAllScanned = useCallback(async () => { const scannedLots = combinedLotData.filter(lot => - lot.stockOutLineStatus === 'checked' // Only submit items that are scanned but not yet submitted + lot.stockOutLineStatus === 'checked' ); if (scannedLots.length === 0) { @@ -1264,94 +1262,54 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } setIsSubmittingAll(true); - console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); + console.log(`📦 Submitting ${scannedLots.length} scanned items using batchSubmitList...`); try { - // Submit all items in parallel using Promise.all - const submitPromises = scannedLots.map(async (lot) => { - const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; + // ✅ 转换为 batchSubmitList 所需的格式 + const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { + const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty || 0; const currentActualPickQty = lot.actualPickQty || 0; const cumulativeQty = currentActualPickQty + submitQty; let newStatus = 'partially_completed'; - if (cumulativeQty >= lot.requiredQty) { + if (cumulativeQty >= (lot.requiredQty || 0)) { newStatus = 'completed'; } - console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`); - - // Update stock out line - await updateStockOutLineStatus({ - id: lot.stockOutLineId, - status: newStatus, - qty: cumulativeQty - }); - - // Update inventory - if (submitQty > 0) { - await updateInventoryLotLineQuantities({ - inventoryLotLineId: lot.lotId, - qty: submitQty, - status: 'available', - operation: 'pick' - }); - } - - // REMOVED: Don't check completion here - do it after all submissions - // Return the lot info for completion check - return { - success: true, - lotNo: lot.lotNo, - pickOrderConsoCode: lot.pickOrderConsoCode, - newStatus: newStatus + return { + stockOutLineId: Number(lot.stockOutLineId) || 0, + pickOrderLineId: Number(lot.pickOrderLineId), + inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, + requiredQty: Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0), + actualPickQty: Number(cumulativeQty), + stockOutLineStatus: newStatus, + pickOrderConsoCode: String(lot.pickOrderConsoCode || ''), + noLot: Boolean(false) // Job Order 通常都有 lot }; }); - // Wait for all submissions to complete - const results = await Promise.all(submitPromises); - const successCount = results.filter(r => r.success).length; - - console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`); + const request: batchSubmitListRequest = { + userId: currentUserId || 0, + lines: lines + }; - // FIXED: Check completion AFTER all submissions are done - // Collect unique consoCodes from completed lots - const completedConsoCodes = new Set(); - results.forEach(result => { - if (result.success && result.newStatus === 'completed' && result.pickOrderConsoCode) { - completedConsoCodes.add(result.pickOrderConsoCode); - } - }); + // ✅ 使用 batchSubmitList API + const result = await batchSubmitList(request); + console.log(`📥 Batch submit result:`, result); - // Check completion for each unique consoCode - await Promise.all( - Array.from(completedConsoCodes).map(async (consoCode) => { - try { - console.log(`🔍 Checking completion for pick order: ${consoCode}`); - const completionResponse = await checkAndCompletePickOrderByConsoCode(consoCode); - console.log(` Pick order completion check result for ${consoCode}:`, completionResponse); - - if (completionResponse.code === "SUCCESS") { - console.log(`✅ Pick order ${consoCode} completed successfully!`); - } else if (completionResponse.message === "not completed") { - console.log(`⏳ Pick order ${consoCode} not completed yet, more lines remaining`); - } else { - console.error(`❌ Error checking completion for ${consoCode}: ${completionResponse.message}`); - } - } catch (error) { - console.error(`❌ Error checking pick order completion for ${consoCode}:`, error); - } - })); - - // Refresh data once after all submissions and completion checks + // 刷新数据 const pickOrderId = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; await fetchJobOrderData(pickOrderId); - if (successCount > 0) { + if (result && result.code === "SUCCESS") { setQrScanSuccess(true); setTimeout(() => { setQrScanSuccess(false); checkAndAutoAssignNext(); }, 2000); + } else { + console.error("Batch submit failed:", result); + setQrScanError(true); } } catch (error) { @@ -1360,7 +1318,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { } finally { setIsSubmittingAll(false); } - }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext]); + }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext, currentUserId, filterArgs?.pickOrderId]) // Calculate scanned items count const scannedItemsCount = useMemo(() => { @@ -1469,38 +1427,7 @@ const JobPickExecution: React.FC = ({ filterArgs }) => { }, ]; - const handleSearch = useCallback((query: Record) => { - setSearchQuery({ ...query }); - console.log("Search query:", query); - - if (!originalCombinedData) return; - - const filtered = originalCombinedData.filter((lot: any) => { - const pickOrderCodeMatch = !query.pickOrderCode || - lot.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase()); - - const itemCodeMatch = !query.itemCode || - lot.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase()); - - const itemNameMatch = !query.itemName || - lot.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase()); - - const lotNoMatch = !query.lotNo || - lot.lotNo?.toLowerCase().includes((query.lotNo || "").toLowerCase()); - - return pickOrderCodeMatch && itemCodeMatch && itemNameMatch && lotNoMatch; - }); - - setCombinedLotData(filtered); - console.log("Filtered lots count:", filtered.length); - }, [originalCombinedData]); - - const handleReset = useCallback(() => { - setSearchQuery({}); - if (originalCombinedData) { - setCombinedLotData(originalCombinedData); - } - }, [originalCombinedData]); + const handlePageChange = useCallback((event: unknown, newPage: number) => { setPaginationController(prev => ({ diff --git a/src/components/ProductionProcess/ProductionProcessDetail.tsx b/src/components/ProductionProcess/ProductionProcessDetail.tsx index 00d8935..7b38b0b 100644 --- a/src/components/ProductionProcess/ProductionProcessDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessDetail.tsx @@ -620,6 +620,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { const isCompleted = statusLower === 'completed'; const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress'; + const isPaused = statusLower === 'paused'; const isPending = statusLower === 'pending' || status === ''; return ( @@ -657,6 +658,8 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { }} /> ) : isPending ? ( + ) : isPaused ? ( + ) : ( )} @@ -672,7 +675,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => { > {t("Start")} - ) : statusLower === 'in_progress' || statusLower === 'in progress' ? ( + ) : statusLower === 'in_progress' || statusLower === 'in progress' || statusLower === 'paused' ? ( + {statusLower !== "completed" && ( + + )} {statusLower === "completed" && ( - {!isPaused ? ( + */ + } + { lineDetail?.status === 'InProgress'? ( @@ -425,12 +453,12 @@ const ProductionProcessStepExecution: React.FC} - onClick={handleContinue} + onClick={() => saveProductProcessResumeTime(lineDetail?.productProcessIssueId || 0 as number)} > {t("Continue")} )} - */} + + + + ); }; diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 99aeabe..f225dd1 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -13,6 +13,8 @@ "code": "編號", "Name": "名稱", "Type": "類型", + "Update Job Order": "更新工單", + "No": "沒有", "WIP": "半成品", "R&D": "研發", @@ -195,10 +197,16 @@ "Seq No": "加入步驟", "Seq No Remark": "序號明細", "Stock Available": "庫存可用", + "Confirm": "確認", "Stock Status": "庫存狀態", "Target Production Date": "目標生產日期", "id": "ID", - "Finished lines": "完成行", + "Finished lines": "已完成流程", + "Please scan staff no": "請掃描員工編號", + "Paused": "已暫停", + "paused": "已暫停", + "Pause Reason": "暫停原因", + "Reason": "原因", "Invalid Stock In Line Id": "無效庫存行ID", "Production date": "生產日期", "Required Qty": "需求數量", diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 4bc477b..b297f35 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -10,6 +10,7 @@ "Picked Qty": "已提料數量", "Req. Qty": "需求數量", "UoM": "銷售單位", + "No": "沒有", "Status": "工單狀態", "Lot No.": "批號", "Delete Job Order": "刪除工單", @@ -40,7 +41,13 @@ "Production Priority": "生產優先度", "Sequence": "序", "Item Code": "成品/半成品編號", + "Paused": "已暫停", + "paused": "已暫停", + "Pause Reason": "暫停原因", + "Reason": "原因", "Stock Available": "倉庫可用數", + "Staff No": "員工編號", + "Please scan staff no": "請掃描員工編號", "Stock Status": "可提料", "Total lines: ": "所需貨品項目數量: ", "Lines with sufficient stock: ": "可提料項目數量: ",