diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index d05db53..d63d7b4 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -247,12 +247,13 @@ export interface UpdateSuggestedLotLineIdRequest { } export interface FGPickOrderResponse { // ✅ 新增:支持多个 pick orders - doPickOrderId: number; // ✅ 新增:do_pick_order 的 ID - pickOrderIds?: number[]; // ✅ 新增:所有 pick order IDs - pickOrderCodes?: string; // ✅ 新增:所有 pick order codes(逗号分隔) - deliveryOrderIds?: number[]; // ✅ 新增:所有 delivery order IDs - deliveryNos?: string; // ✅ 新增:所有 delivery order codes(逗号分隔) - numberOfPickOrders?: number; // ✅ 新增:pick order 数量 + doPickOrderId: number; + pickOrderIds?: number[]; + pickOrderCodes?: string[]; // ✅ 改为数组 + deliveryOrderIds?: number[]; + deliveryNos?: string[]; // ✅ 改为数组 + numberOfPickOrders?: number; + lineCountsPerPickOrder?: number[];// ✅ 新增:pick order 数量 // ✅ 保留原有字段用于向后兼容(显示第一个 pick order) pickOrderId: number; @@ -274,6 +275,7 @@ export interface FGPickOrderResponse { truckLanceCode: string; storeId: string; qrCodeData: number; + } export interface DoPickOrderDetail { doPickOrder: { diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index 6829595..1338ec6 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -426,6 +426,7 @@ const GoodPickExecutionRecord: React.FC = ({ filterArgs }) => { itemName: line.item.name, lotNo: lot.lotNo, location: lot.location, + deliveryOrderCode: po.deliveryOrderCode, requiredQty: lot.requiredQty, actualPickQty: lot.actualPickQty, processingStatus: lot.processingStatus, @@ -549,23 +550,30 @@ if (showDetailView && selectedDoPickOrder) { {/* ✅ 按 pickOrderCode 分组 */} {Object.entries( - detailLotData.reduce((acc: any, lot: any) => { - const key = lot.pickOrderCode || 'Unknown'; - if (!acc[key]) acc[key] = []; - acc[key].push(lot); - return acc; - }, {}) - ).map(([pickOrderCode, lots]: [string, any]) => ( - - }> - - {t("Pick Order")}: {pickOrderCode} ({(lots as any[]).length} {t("items")}) - - - - - - + detailLotData.reduce((acc: any, lot: any) => { + const key = lot.pickOrderCode || 'Unknown'; + if (!acc[key]) { + acc[key] = { + lots: [], + deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // ✅ 保存对应的 deliveryOrderCode + }; + } + acc[key].lots.push(lot); + return acc; + }, {}) + ).map(([pickOrderCode, data]: [string, any]) => ( + + }> + + {t("Pick Order")}: {pickOrderCode} ({data.lots.length} {t("items")}) + {" | "} + {t("Delivery Order")}: {data.deliveryOrderCode} {/* ✅ 使用保存的 deliveryOrderCode */} + + + + +
+ {t("Index")} {t("Item Code")} @@ -578,7 +586,7 @@ if (showDetailView && selectedDoPickOrder) { - {(lots as any[]).map((lot: any, index: number) => ( + {data.lots.map((lot: any, index: number) => ( {index + 1} {lot.itemCode || 'N/A'} @@ -651,6 +659,7 @@ if (showDetailView && selectedDoPickOrder) { {doPickOrder.pickOrderCode} + {doPickOrder.shopName} - {doPickOrder.deliveryNo} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 637a2fe..6864342 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -80,9 +80,7 @@ const QrCodeModal: React.FC<{ const { t } = useTranslation("pickOrder"); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const [manualInput, setManualInput] = useState(''); - const [doPickOrderDetail, setDoPickOrderDetail] = useState(null); -const [selectedPickOrderId, setSelectedPickOrderId] = useState(null); -const [pickOrderSwitching, setPickOrderSwitching] = useState(false); + const [manualInputSubmitted, setManualInputSubmitted] = useState(false); const [manualInputError, setManualInputError] = useState(false); const [isProcessingQr, setIsProcessingQr] = useState(false); @@ -378,33 +376,8 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); const [lastProcessedQr, setLastProcessedQr] = useState(''); const [isRefreshingData, setIsRefreshingData] = useState(false); const [isSubmittingAll, setIsSubmittingAll] = useState(false); - const fetchFgPickOrdersData = useCallback(async () => { - if (!currentUserId) return; - - setFgPickOrdersLoading(true); - try { - const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId); - - console.log("🔍 DEBUG: Fetched FG pick orders:", fgPickOrders); - console.log("🔍 DEBUG: First order numberOfPickOrders:", fgPickOrders[0]?.numberOfPickOrders); - - setFgPickOrders(fgPickOrders); - - // ✅ 移除:不需要再单独调用 fetchDoPickOrderDetail - // all-lots-hierarchical API 已经包含了所有需要的数据 - - } catch (error) { - console.error("❌ Error fetching FG pick orders:", error); - setFgPickOrders([]); - } finally { - setFgPickOrdersLoading(false); - } - }, [currentUserId]); - useEffect(() => { - if (combinedLotData.length > 0) { - fetchFgPickOrdersData(); - } - }, [combinedLotData, fetchFgPickOrdersData]); + + // ✅ Handle QR code button click const handleQrCodeClick = (pickOrderId: number) => { @@ -443,187 +416,102 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); setAllLotsCompleted(allCompleted); return allCompleted; }, []); - const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { - - setCombinedDataLoading(true); - try { - const userIdToUse = userId || currentUserId; - - console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); +// 在 fetchAllCombinedLotData 函数中(约 446-684 行) - - if (!userIdToUse) { - console.warn("⚠️ No userId available, skipping API call"); - setCombinedLotData([]); - setOriginalCombinedData([]); - setAllLotsCompleted(false); - return; - } - - // ✅ 获取新结构的层级数据 - // ✅ 获取新结构的层级数据 - const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); - console.log("✅ Hierarchical data (new structure):", hierarchicalData); - - // ✅ 检查数据结构 - if (!hierarchicalData.fgInfo || !hierarchicalData.pickOrders) { - console.warn("⚠️ No FG info or pick orders found"); - setCombinedLotData([]); - setOriginalCombinedData([]); - setAllLotsCompleted(false); - return; - } - - // ✅ 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片) - const fgOrder: FGPickOrderResponse = { - doPickOrderId: hierarchicalData.fgInfo.doPickOrderId, - ticketNo: hierarchicalData.fgInfo.ticketNo, - storeId: hierarchicalData.fgInfo.storeId, - shopCode: hierarchicalData.fgInfo.shopCode, - shopName: hierarchicalData.fgInfo.shopName, - truckLanceCode: hierarchicalData.fgInfo.truckLanceCode, - DepartureTime: hierarchicalData.fgInfo.departureTime, - shopAddress: "", - - // ✅ 从第一个 pick order 获取兼容字段 - pickOrderId: hierarchicalData.pickOrders[0]?.pickOrderId || 0, - pickOrderCode: hierarchicalData.pickOrders[0]?.pickOrderCode || "", - pickOrderConsoCode: hierarchicalData.pickOrders[0]?.consoCode || "", - pickOrderTargetDate: hierarchicalData.pickOrders[0]?.targetDate || "", - pickOrderStatus: hierarchicalData.pickOrders[0]?.status || "", - deliveryOrderId: hierarchicalData.pickOrders[0]?.doOrderId || 0, - deliveryNo: hierarchicalData.pickOrders[0]?.deliveryOrderCode || "", - deliveryDate: "", - shopId: 0, - shopPoNo: "", - numberOfCartons: 0, - qrCodeData: hierarchicalData.fgInfo.doPickOrderId, - - // ✅ 新增:多个 pick orders 信息 - numberOfPickOrders: hierarchicalData.pickOrders.length, - pickOrderIds: hierarchicalData.pickOrders.map((po: any) => po.pickOrderId), - pickOrderCodes: hierarchicalData.pickOrders.map((po: any) => po.pickOrderCode).join(", "), - deliveryOrderIds: hierarchicalData.pickOrders.map((po: any) => po.doOrderId), - deliveryNos: hierarchicalData.pickOrders.map((po: any) => po.deliveryOrderCode).join(", ") - }; - - setFgPickOrders([fgOrder]); - - // ✅ 构建 doPickOrderDetail(用于 switcher) - if (hierarchicalData.pickOrders.length > 1) { - const detail: DoPickOrderDetail = { - doPickOrder: { - id: hierarchicalData.fgInfo.doPickOrderId, - store_id: hierarchicalData.fgInfo.storeId, - ticket_no: hierarchicalData.fgInfo.ticketNo, - ticket_status: "", - truck_id: 0, - truck_departure_time: hierarchicalData.fgInfo.departureTime, - shop_id: 0, - handled_by: null, - loading_sequence: 0, - ticket_release_time: null, - TruckLanceCode: hierarchicalData.fgInfo.truckLanceCode, - ShopCode: hierarchicalData.fgInfo.shopCode, - ShopName: hierarchicalData.fgInfo.shopName, - RequiredDeliveryDate: "" - }, - pickOrders: hierarchicalData.pickOrders.map((po: any) => ({ - pick_order_id: po.pickOrderId, - pick_order_code: po.pickOrderCode, - do_order_id: po.doOrderId, - delivery_order_code: po.deliveryOrderCode, - consoCode: po.consoCode, - status: po.status, - targetDate: po.targetDate - })), - selectedPickOrderId: pickOrderIdOverride || hierarchicalData.pickOrders[0]?.pickOrderId || 0, - lotDetails: [] - }; - - setDoPickOrderDetail(detail); - - // ✅ 设置默认选中的 pick order ID - if (!selectedPickOrderId) { - setSelectedPickOrderId(pickOrderIdOverride || hierarchicalData.pickOrders[0]?.pickOrderId); - } - } - - // ✅ 确定要显示的 pick order - const targetPickOrderId = pickOrderIdOverride || selectedPickOrderId || hierarchicalData.pickOrders[0]?.pickOrderId; - - // ✅ 找到对应的 pick order 数据 - const targetPickOrder = hierarchicalData.pickOrders.find((po: any) => - po.pickOrderId === targetPickOrderId - ); - - if (!targetPickOrder) { - console.warn("⚠️ Target pick order not found:", targetPickOrderId); - setCombinedLotData([]); - setOriginalCombinedData([]); - setAllLotsCompleted(false); - return; - } - - console.log("🎯 Displaying pick order:", targetPickOrder.pickOrderCode); - - // ✅ 将层级数据转换为平铺格式(用于表格显示) - const flatLotData: any[] = []; - - targetPickOrder.pickOrderLines.forEach((line: any) => { - if (line.lots && line.lots.length > 0) { - // ✅ 有 lots 的情况 - line.lots.forEach((lot: any) => { - flatLotData.push({ - pickOrderId: targetPickOrder.pickOrderId, - pickOrderCode: targetPickOrder.pickOrderCode, - pickOrderConsoCode: targetPickOrder.consoCode, - pickOrderTargetDate: targetPickOrder.targetDate, - pickOrderStatus: targetPickOrder.status, - - pickOrderLineId: line.id, - pickOrderLineRequiredQty: line.requiredQty, - pickOrderLineStatus: line.status, - - itemId: line.item.id, - itemCode: line.item.code, - itemName: line.item.name, - //uomCode: line.item.uomCode, - uomDesc: line.item.uomDesc, - uomShortDesc: line.item.uomShortDesc, - lotId: lot.id, - lotNo: lot.lotNo, - expiryDate: lot.expiryDate, - location: lot.location, - stockUnit: lot.stockUnit, - availableQty: lot.availableQty, - requiredQty: lot.requiredQty, - actualPickQty: lot.actualPickQty, - inQty: lot.inQty, - outQty: lot.outQty, - holdQty: lot.holdQty, - lotStatus: lot.lotStatus, - lotAvailability: lot.lotAvailability, - processingStatus: lot.processingStatus, - suggestedPickLotId: lot.suggestedPickLotId, - stockOutLineId: lot.stockOutLineId, - stockOutLineStatus: lot.stockOutLineStatus, - stockOutLineQty: lot.stockOutLineQty, - - routerId: lot.router?.id, - routerIndex: lot.router?.index, - routerRoute: lot.router?.route, - routerArea: lot.router?.area, - }); - }); - } else { - // ✅ 没有 lots 的情况(null stock)- 也要显示 +const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { + setCombinedDataLoading(true); + try { + const userIdToUse = userId || currentUserId; + + console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); + + if (!userIdToUse) { + console.warn("⚠️ No userId available, skipping API call"); + setCombinedLotData([]); + setOriginalCombinedData([]); + setAllLotsCompleted(false); + return; + } + + // ✅ 获取新结构的层级数据 + const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); + console.log("✅ Hierarchical data (new structure):", hierarchicalData); + + // ✅ 检查数据结构 + if (!hierarchicalData.fgInfo || !hierarchicalData.pickOrders || hierarchicalData.pickOrders.length === 0) { + console.warn("⚠️ No FG info or pick orders found"); + setCombinedLotData([]); + setOriginalCombinedData([]); + setAllLotsCompleted(false); + return; + } + + // ✅ 使用合并后的 pick order 对象(现在只有一个对象,包含所有数据) + const mergedPickOrder = hierarchicalData.pickOrders[0]; + + // ✅ 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片) + // 修改第 478-509 行的 fgOrder 构建逻辑: + +const fgOrder: FGPickOrderResponse = { + doPickOrderId: hierarchicalData.fgInfo.doPickOrderId, + ticketNo: hierarchicalData.fgInfo.ticketNo, + storeId: hierarchicalData.fgInfo.storeId, + shopCode: hierarchicalData.fgInfo.shopCode, + shopName: hierarchicalData.fgInfo.shopName, + truckLanceCode: hierarchicalData.fgInfo.truckLanceCode, + DepartureTime: hierarchicalData.fgInfo.departureTime, + shopAddress: "", + pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", + // ✅ 兼容字段 + pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, + pickOrderConsoCode: mergedPickOrder.consoCode || "", + pickOrderTargetDate: mergedPickOrder.targetDate || "", + pickOrderStatus: mergedPickOrder.status || "", + deliveryOrderId: mergedPickOrder.doOrderIds?.[0] || 0, + deliveryNo: mergedPickOrder.deliveryOrderCodes?.[0] || "", + deliveryDate: "", + shopId: 0, + shopPoNo: "", + numberOfCartons: mergedPickOrder.pickOrderLines?.length || 0, + qrCodeData: hierarchicalData.fgInfo.doPickOrderId, + + // ✅ 新增:多个 pick orders 信息 - 保持数组格式,不要 join + numberOfPickOrders: mergedPickOrder.pickOrderIds?.length || 0, + pickOrderIds: mergedPickOrder.pickOrderIds || [], + pickOrderCodes: Array.isArray(mergedPickOrder.pickOrderCodes) + ? mergedPickOrder.pickOrderCodes + : [], // ✅ 改:保持数组 + deliveryOrderIds: mergedPickOrder.doOrderIds || [], + deliveryNos: Array.isArray(mergedPickOrder.deliveryOrderCodes) + ? mergedPickOrder.deliveryOrderCodes + : [], // ✅ 改:保持数组 + lineCountsPerPickOrder: Array.isArray(mergedPickOrder.lineCountsPerPickOrder) + ? mergedPickOrder.lineCountsPerPickOrder + : [] +}; + + setFgPickOrders([fgOrder]); + console.log("🔍 DEBUG fgOrder.lineCountsPerPickOrder:", fgOrder.lineCountsPerPickOrder); +console.log("🔍 DEBUG fgOrder.pickOrderCodes:", fgOrder.pickOrderCodes); +console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); + // ❌ 移除:不需要 doPickOrderDetail 和 switcher 逻辑 + // if (hierarchicalData.pickOrders.length > 1) { ... } + + // ✅ 直接使用合并后的 pickOrderLines + console.log("🎯 Displaying merged pick order lines"); + + // ✅ 将层级数据转换为平铺格式(用于表格显示) + const flatLotData: any[] = []; + + mergedPickOrder.pickOrderLines.forEach((line: any) => { + if (line.lots && line.lots.length > 0) { + // ✅ 有 lots 的情况 + line.lots.forEach((lot: any) => { flatLotData.push({ - pickOrderId: targetPickOrder.pickOrderId, - pickOrderCode: targetPickOrder.pickOrderCode, - pickOrderConsoCode: targetPickOrder.consoCode, - pickOrderTargetDate: targetPickOrder.targetDate, - pickOrderStatus: targetPickOrder.status, + // ✅ 使用合并后的数据 + pickOrderConsoCode: mergedPickOrder.consoCode, + pickOrderTargetDate: mergedPickOrder.targetDate, + pickOrderStatus: mergedPickOrder.status, pickOrderLineId: line.id, pickOrderLineRequiredQty: line.requiredQty, @@ -632,56 +520,95 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); itemId: line.item.id, itemCode: line.item.code, itemName: line.item.name, - //uomCode: line.item.uomCode, uomDesc: line.item.uomDesc, + uomShortDesc: line.item.uomShortDesc, - // ✅ Null stock 字段 - lotId: null, - lotNo: null, - expiryDate: null, - location: null, - stockUnit: line.item.uomDesc, - availableQty: 0, - requiredQty: line.requiredQty, - actualPickQty: 0, - inQty: 0, - outQty: 0, - holdQty: 0, - lotStatus: 'unavailable', - lotAvailability: 'insufficient_stock', - processingStatus: 'pending', - suggestedPickLotId: null, - stockOutLineId: null, - stockOutLineStatus: null, - stockOutLineQty: 0, + lotId: lot.id, + lotNo: lot.lotNo, + expiryDate: lot.expiryDate, + location: lot.location, + stockUnit: lot.stockUnit, + availableQty: lot.availableQty, + requiredQty: lot.requiredQty, + actualPickQty: lot.actualPickQty, + inQty: lot.inQty, + outQty: lot.outQty, + holdQty: lot.holdQty, + lotStatus: lot.lotStatus, + lotAvailability: lot.lotAvailability, + processingStatus: lot.processingStatus, + suggestedPickLotId: lot.suggestedPickLotId, + stockOutLineId: lot.stockOutLineId, + stockOutLineStatus: lot.stockOutLineStatus, + stockOutLineQty: lot.stockOutLineQty, - routerId: null, - routerIndex: 999999, // ✅ 放到最后 - routerRoute: null, - routerArea: null, - uomShortDesc: line.item.uomShortDesc + routerId: lot.router?.id, + routerIndex: lot.router?.index, + routerRoute: lot.router?.route, + routerArea: lot.router?.area, }); - } - }); + }); + } else { + // ✅ 没有 lots 的情况(null stock) + flatLotData.push({ + pickOrderConsoCode: mergedPickOrder.consoCode, + pickOrderTargetDate: mergedPickOrder.targetDate, + pickOrderStatus: mergedPickOrder.status, + + pickOrderLineId: line.id, + pickOrderLineRequiredQty: line.requiredQty, + pickOrderLineStatus: line.status, + + itemId: line.item.id, + itemCode: line.item.code, + itemName: line.item.name, + uomDesc: line.item.uomDesc, + uomShortDesc: line.item.uomShortDesc, + + // ✅ Null stock 字段 + lotId: null, + lotNo: null, + expiryDate: null, + location: null, + stockUnit: line.item.uomDesc, + availableQty: 0, + requiredQty: line.requiredQty, + actualPickQty: 0, + inQty: 0, + outQty: 0, + holdQty: 0, + lotStatus: 'unavailable', + lotAvailability: 'insufficient_stock', + processingStatus: 'pending', + suggestedPickLotId: null, + stockOutLineId: null, + stockOutLineStatus: null, + stockOutLineQty: 0, + + routerId: null, + routerIndex: 999999, + routerRoute: null, + routerArea: null, + }); + } + }); - console.log("✅ Transformed flat lot data:", flatLotData); - console.log("🔍 Total items (including null stock):", flatLotData.length); - - - setCombinedLotData(flatLotData); - setOriginalCombinedData(flatLotData); - checkAllLotsCompleted(flatLotData); - - - } catch (error) { - console.error("❌ Error fetching combined lot data:", error); - setCombinedLotData([]); - setOriginalCombinedData([]); - setAllLotsCompleted(false); - } finally { - setCombinedDataLoading(false); - } - }, [currentUserId, selectedPickOrderId, checkAllLotsCompleted]); + console.log("✅ Transformed flat lot data:", flatLotData); + console.log("🔍 Total items (including null stock):", flatLotData.length); + + setCombinedLotData(flatLotData); + setOriginalCombinedData(flatLotData); + checkAllLotsCompleted(flatLotData); + + } catch (error) { + console.error("❌ Error fetching combined lot data:", error); + setCombinedLotData([]); + setOriginalCombinedData([]); + setAllLotsCompleted(false); + } finally { + setCombinedDataLoading(false); + } +}, [currentUserId, checkAllLotsCompleted]); // ❌ 移除 selectedPickOrderId 依赖 // ✅ Add effect to check completion when lot data changes useEffect(() => { if (combinedLotData.length > 0) { @@ -1687,94 +1614,144 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe ) )} + + + + {/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} - {/* ✅ FG Info Card */} + + + {t("All Pick Order Lots")} + + + + {!isManualScanning ? ( + + ) : ( + + )} + + {/* ✅ 保留:Submit All Scanned Button */} + + + + + +{fgPickOrders.length > 0 && ( + + + {/* 基本信息 */} + + + {t("Shop Name")}: {fgPickOrders[0].shopName || '-'} + + + {t("Store ID")}: {fgPickOrders[0].storeId || '-'} + + + {t("Ticket No.")}: {fgPickOrders[0].ticketNo || '-'} + + + {t("Departure Time")}: {fgPickOrders[0].DepartureTime || '-'} + + + + {/* ✅ 改进:三个字段显示在一起,使用表格式布局 */} + {/* ✅ 改进:三个字段合并显示 */} +{/* ✅ 改进:表格式显示每个 pick order */} + + + {t("Pick Orders Details")}: + + + {(() => { + const pickOrderCodes = fgPickOrders[0].pickOrderCodes as string[] | string | undefined; + const deliveryNos = fgPickOrders[0].deliveryNos as string[] | string | undefined; + const lineCounts = fgPickOrders[0].lineCountsPerPickOrder; + + const pickOrderCodesArray = Array.isArray(pickOrderCodes) + ? pickOrderCodes + : (typeof pickOrderCodes === 'string' ? pickOrderCodes.split(', ') : []); + + const deliveryNosArray = Array.isArray(deliveryNos) + ? deliveryNos + : (typeof deliveryNos === 'string' ? deliveryNos.split(', ') : []); - {/* ✅ Pick Order Switcher - 放在 FG Info 下面,QR 按钮上面 */} - {doPickOrderDetail && doPickOrderDetail.pickOrders.length > 1 && ( - - - {t("Select Pick Order:")} + const lineCountsArray = Array.isArray(lineCounts) ? lineCounts : []; + + const maxLength = Math.max( + pickOrderCodesArray.length, + deliveryNosArray.length, + lineCountsArray.length + ); + + if (maxLength === 0) { + return -; + } + + // ✅ 使用与外部基本信息相同的样式 + return Array.from({ length: maxLength }, (_, idx) => ( + + + {t("Delivery Order")}: {deliveryNosArray[idx] || '-'} - - {doPickOrderDetail.pickOrders.map((po: any) => ( - handlePickOrderSwitch(po.pick_order_id)} - color={selectedPickOrderId === po.pick_order_id ? "primary" : "default"} - variant={selectedPickOrderId === po.pick_order_id ? "filled" : "outlined"} - sx={{ - cursor: 'pointer', - '&:hover': { backgroundColor: 'primary.light', color: 'white' } - }} - /> - ))} - - + + {t("Pick Order")}: {pickOrderCodesArray[idx] || '-'} + + + {t("Finsihed good items")}: {lineCountsArray[idx] || '-'}{t("kinds")} + + + )); + })()} + + + )} - - {/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} - - - - {t("All Pick Order Lots")} - - - - {!isManualScanning ? ( - - ) : ( - - )} - - {/* ✅ 保留:Submit All Scanned Button */} - - - - - {qrScanError && !qrScanSuccess && ( - - {t("QR code does not match any item in current orders.")} - - )} - {qrScanSuccess && ( - - {t("QR code verified.")} - - )} - + +
diff --git a/src/components/ProductionProcess/MachineScanner.tsx b/src/components/ProductionProcess/MachineScanner.tsx index 8e082e9..49dcf65 100644 --- a/src/components/ProductionProcess/MachineScanner.tsx +++ b/src/components/ProductionProcess/MachineScanner.tsx @@ -19,6 +19,9 @@ interface MachineScannerProps { machines: Machine[]; onMachinesChange: (machines: Machine[]) => void; error?: string; + isActive?: boolean; + onActivate?: () => void; + onDeactivate?: () => void; } const machineDatabase: Machine[] = [ @@ -33,6 +36,9 @@ const MachineScanner: React.FC = ({ machines, onMachinesChange, error, + isActive=false, + onActivate, + onDeactivate, }) => { const [scanningMode, setScanningMode] = useState(false); const [scanError, setScanError] = useState(null); @@ -41,6 +47,7 @@ const MachineScanner: React.FC = ({ const startScanning = (): void => { setScanningMode(true); + onActivate?.(); setTimeout(() => { if (machineScanRef.current) { machineScanRef.current.focus(); @@ -50,34 +57,86 @@ const MachineScanner: React.FC = ({ const stopScanning = (): void => { setScanningMode(false); + onDeactivate?.(); }; const handleMachineScan = async ( e: React.KeyboardEvent, ): Promise => { const target = e.target as HTMLInputElement; - const scannedCodeJSON = target.value.trim(); + let scannedInput = target.value.trim(); - if (e.key === "Enter" || scannedCodeJSON.endsWith("}")) { - const scannedObj: MachineQrCode = JSON.parse(scannedCodeJSON); + if (e.key === "Enter" || scannedInput.endsWith("}")) { + console.log("Raw machine input:", scannedInput); + + try { + let machineCode: string; + + // ✅ 尝试解析 JSON + try { + const scannedObj: MachineQrCode = JSON.parse(scannedInput); + machineCode = scannedObj.code; + } catch (jsonError) { + // ✅ 如果不是 JSON,尝试从花括号中提取 + const match = scannedInput.match(/\{([^}?]+)\??}?/); + if (match && match[1]) { + machineCode = match[1].trim(); + console.log("Extracted machine code from braces:", machineCode); + } else { + // ✅ 如果没有花括号,直接使用输入值 + machineCode = scannedInput.replace(/[{}?]/g, '').trim(); + console.log("Using plain machine code:", machineCode); + } + } - const response = await isCorrectMachineUsed(scannedObj?.code); + if (!machineCode) { + setScanError("Invalid input format"); + return; + } - if (response.message === "Success") { - const isAlreadyAdded = machines.some( - (m) => m.code === response.entity.code, - ); + // ✅ 首先尝试从 API 获取 + const response = await isCorrectMachineUsed(machineCode); - if (!isAlreadyAdded) { - onMachinesChange([...machines, response.entity]); - } + if (response.message === "Success") { + const isAlreadyAdded = machines.some( + (m) => m.code === response.entity.code, + ); - target.value = ""; - // stopScanning(); - } else { - alert("Machine not found. Please check the code and try again."); + if (!isAlreadyAdded) { + onMachinesChange([...machines, response.entity]); + } + + target.value = ""; + setScanError(null); + } else { + // ✅ 如果 API 失败,尝试从本地默认数据查找 + const localMachine = machineDatabase.find( + (m) => m.code.toLowerCase() === machineCode.toLowerCase() + ); + + if (localMachine) { + const isAlreadyAdded = machines.some( + (m) => m.code === localMachine.code + ); + + if (!isAlreadyAdded) { + onMachinesChange([...machines, localMachine]); + } + + target.value = ""; + setScanError(null); + console.log("✅ Used local machine data:", localMachine); + } else { + setScanError( + "Machine not found. Please check the code and try again." + ); + target.value = ""; + } + } + } catch (error) { + console.error("Error processing machine scan:", error); setScanError( - "An error occurred while checking the operator. Please try again.", + "An error occurred while checking the machine. Please try again." ); target.value = ""; } @@ -123,7 +182,7 @@ const MachineScanner: React.FC = ({ void; error?: string; + isActive?: boolean; + onActivate?: () => void; + onDeactivate?: () => void; } const OperatorScanner: React.FC = ({ operators, onOperatorsChange, error, + isActive=false, + onActivate, + onDeactivate, }) => { const [scanningMode, setScanningMode] = useState(false); const [scanError, setScanError] = useState(null); const operatorScanRef = useRef(null); + useEffect(() => { + if (!isActive && scanningMode) { + stopScanning(); + } + }, [isActive]); const startScanning = (): void => { setScanningMode(true); setScanError(null); + onActivate?.(); setTimeout(() => { if (operatorScanRef.current) { operatorScanRef.current.focus(); @@ -42,20 +54,44 @@ const OperatorScanner: React.FC = ({ const stopScanning = (): void => { setScanningMode(false); setScanError(null); + onDeactivate?.(); }; const handleOperatorScan = async ( e: React.KeyboardEvent, ): Promise => { const target = e.target as HTMLInputElement; - const usernameJSON: string = target.value.trim(); + let usernameInput: string = target.value.trim(); - if (e.key === "Enter" || usernameJSON.endsWith("}")) { - console.log(usernameJSON); + if (e.key === "Enter" || usernameInput.endsWith("}")) { + console.log("Raw input:", usernameInput); + try { - const usernameObj: OperatorQrCode = JSON.parse(usernameJSON); + let username: string; + + // ✅ 尝试解析 JSON + try { + const usernameObj: OperatorQrCode = JSON.parse(usernameInput); + username = usernameObj.username; + } catch (jsonError) { + // ✅ 如果不是 JSON,尝试从花括号中提取 + const match = usernameInput.match(/\{([^}?]+)\??}?/); + if (match && match[1]) { + username = match[1].trim(); + console.log("Extracted username from braces:", username); + } else { + // ✅ 如果没有花括号,直接使用输入值 + username = usernameInput.replace(/[{}?]/g, '').trim(); + console.log("Using plain username:", username); + } + } + + if (!username) { + setScanError("Invalid input format"); + return; + } - const response = await isOperatorExist(usernameObj.username); + const response = await isOperatorExist(username); if (response.message === "Success") { const isAlreadyAdded = operators.some( @@ -66,7 +102,6 @@ const OperatorScanner: React.FC = ({ } target.value = ""; setScanError(null); - // stopScanning(); } else { setScanError( "Operator not found. Please check the ID and try again.", diff --git a/src/components/ProductionProcess/ProductionRecordingModal.tsx b/src/components/ProductionProcess/ProductionRecordingModal.tsx index 9bb5a51..372f119 100644 --- a/src/components/ProductionProcess/ProductionRecordingModal.tsx +++ b/src/components/ProductionProcess/ProductionRecordingModal.tsx @@ -1,6 +1,7 @@ "use client"; import React from "react"; import { X, Save } from "@mui/icons-material"; +import { useState } from "react"; import { Dialog, DialogTitle, @@ -56,6 +57,16 @@ const ProductionRecordingModal: React.FC = ({ const watchedOperators = watch("operators"); const watchedMachines = watch("machines"); const watchedMaterials = watch("materials"); + const [activeScannerType, setActiveScannerType] = useState<'operator' | 'machine' | 'material' | null>(null); + + + const handleScannerActivate = (scannerType: 'operator' | 'machine' | 'material') => { + setActiveScannerType(scannerType); + }; + + const handleScannerDeactivate = () => { + setActiveScannerType(null); + }; const validateForm = (): boolean => { let isValid = true; @@ -206,6 +217,9 @@ const ProductionRecordingModal: React.FC = ({ } }} error={errors.operators?.message} + isActive={activeScannerType === 'operator'} + onActivate={() => handleScannerActivate('operator')} + onDeactivate={handleScannerDeactivate} /> {/* Machine Scanner */} @@ -218,6 +232,9 @@ const ProductionRecordingModal: React.FC = ({ } }} error={errors.machines?.message} + isActive={activeScannerType === 'machine'} + onActivate={() => handleScannerActivate('machine')} + onDeactivate={handleScannerDeactivate} /> {/* Material Lot Scanner */} diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 4515ad2..226b26c 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -17,8 +17,19 @@ "Delivery Order Code(s)": "送貨單編號", "Start Success": "開始成功", "Truck Lance Code": "車牌號碼", + "Pick Order Codes": "提料單編號", + "Pick Order Lines": "提料單行數", + "Delivery Order Codes": "送貨單編號", + "Delivery Order Lines": "送貨單行數", + "Lines Per Pick Order": "每提料單行數", + "Pick Orders Details": "提料單詳情", + "Lines": "行數", + "Finsihed good items": "成品項目", + "kinds": "款", "Completed Date": "完成日期", "Completed Time": "完成時間", + "Delivery Order": "送貨單", + "items": "項目", "Select Pick Order:": "選擇提料單:", "⚠️ No Stock Available": "⚠️ 沒有庫存", "Start Fail": "開始失敗",