|
|
|
@@ -80,9 +80,7 @@ const QrCodeModal: React.FC<{ |
|
|
|
const { t } = useTranslation("pickOrder"); |
|
|
|
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); |
|
|
|
const [manualInput, setManualInput] = useState<string>(''); |
|
|
|
const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null); |
|
|
|
const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); |
|
|
|
const [pickOrderSwitching, setPickOrderSwitching] = useState(false); |
|
|
|
|
|
|
|
const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); |
|
|
|
const [manualInputError, setManualInputError] = useState<boolean>(false); |
|
|
|
const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false); |
|
|
|
@@ -378,33 +376,8 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); |
|
|
|
const [lastProcessedQr, setLastProcessedQr] = useState<string>(''); |
|
|
|
const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false); |
|
|
|
const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(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 |
|
|
|
</Paper> |
|
|
|
) |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} |
|
|
|
<Box> |
|
|
|
{/* ✅ FG Info Card */} |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> |
|
|
|
<Typography variant="h6" gutterBottom sx={{ mb: 0 }}> |
|
|
|
{t("All Pick Order Lots")} |
|
|
|
</Typography> |
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}> |
|
|
|
{!isManualScanning ? ( |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
startIcon={<QrCodeIcon />} |
|
|
|
onClick={handleStartScan} |
|
|
|
color="primary" |
|
|
|
sx={{ minWidth: '120px' }} |
|
|
|
> |
|
|
|
{t("Start QR Scan")} |
|
|
|
</Button> |
|
|
|
) : ( |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
startIcon={<QrCodeIcon />} |
|
|
|
onClick={handleStopScan} |
|
|
|
color="secondary" |
|
|
|
sx={{ minWidth: '120px' }} |
|
|
|
> |
|
|
|
{t("Stop QR Scan")} |
|
|
|
</Button> |
|
|
|
)} |
|
|
|
|
|
|
|
{/* ✅ 保留:Submit All Scanned Button */} |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
color="success" |
|
|
|
onClick={handleSubmitAllScanned} |
|
|
|
disabled={scannedItemsCount === 0 || isSubmittingAll} |
|
|
|
sx={{ minWidth: '160px' }} |
|
|
|
> |
|
|
|
{isSubmittingAll ? ( |
|
|
|
<> |
|
|
|
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} /> |
|
|
|
{t("Submitting...")} |
|
|
|
</> |
|
|
|
) : ( |
|
|
|
`${t("Submit All Scanned")} (${scannedItemsCount})` |
|
|
|
)} |
|
|
|
</Button> |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
|
|
|
|
|
|
|
|
{fgPickOrders.length > 0 && ( |
|
|
|
<Paper sx={{ p: 2, mb: 2 }}> |
|
|
|
<Stack spacing={2}> |
|
|
|
{/* 基本信息 */} |
|
|
|
<Stack direction="row" spacing={4} useFlexGap flexWrap="wrap"> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Shop Name")}:</strong> {fgPickOrders[0].shopName || '-'} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Store ID")}:</strong> {fgPickOrders[0].storeId || '-'} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Ticket No.")}:</strong> {fgPickOrders[0].ticketNo || '-'} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Departure Time")}:</strong> {fgPickOrders[0].DepartureTime || '-'} |
|
|
|
</Typography> |
|
|
|
</Stack> |
|
|
|
|
|
|
|
{/* ✅ 改进:三个字段显示在一起,使用表格式布局 */} |
|
|
|
{/* ✅ 改进:三个字段合并显示 */} |
|
|
|
{/* ✅ 改进:表格式显示每个 pick order */} |
|
|
|
<Box sx={{ |
|
|
|
p: 2, |
|
|
|
backgroundColor: '#f5f5f5', |
|
|
|
borderRadius: 1 |
|
|
|
}}> |
|
|
|
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}> |
|
|
|
{t("Pick Orders Details")}: |
|
|
|
</Typography> |
|
|
|
|
|
|
|
{(() => { |
|
|
|
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 && ( |
|
|
|
<Box sx={{ mb: 2, mt: 1 }}> |
|
|
|
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}> |
|
|
|
{t("Select Pick Order:")} |
|
|
|
const lineCountsArray = Array.isArray(lineCounts) ? lineCounts : []; |
|
|
|
|
|
|
|
const maxLength = Math.max( |
|
|
|
pickOrderCodesArray.length, |
|
|
|
deliveryNosArray.length, |
|
|
|
lineCountsArray.length |
|
|
|
); |
|
|
|
|
|
|
|
if (maxLength === 0) { |
|
|
|
return <Typography variant="body2" color="text.secondary">-</Typography>; |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ 使用与外部基本信息相同的样式 |
|
|
|
return Array.from({ length: maxLength }, (_, idx) => ( |
|
|
|
<Stack |
|
|
|
key={idx} |
|
|
|
direction="row" |
|
|
|
spacing={4} |
|
|
|
useFlexGap |
|
|
|
flexWrap="wrap" |
|
|
|
sx={{ mb: idx < maxLength - 1 ? 1 : 0 }} // 除了最后一行,都添加底部间距 |
|
|
|
> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Delivery Order")}:</strong> {deliveryNosArray[idx] || '-'} |
|
|
|
</Typography> |
|
|
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> |
|
|
|
{doPickOrderDetail.pickOrders.map((po: any) => ( |
|
|
|
<Chip |
|
|
|
key={po.pick_order_id} |
|
|
|
label={`${po.pick_order_code} (${po.delivery_order_code})`} |
|
|
|
onClick={() => 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' } |
|
|
|
}} |
|
|
|
/> |
|
|
|
))} |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Pick Order")}:</strong> {pickOrderCodesArray[idx] || '-'} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Finsihed good items")}:</strong> {lineCountsArray[idx] || '-'}<strong>{t("kinds")}</strong> |
|
|
|
</Typography> |
|
|
|
</Stack> |
|
|
|
)); |
|
|
|
})()} |
|
|
|
</Box> |
|
|
|
</Stack> |
|
|
|
</Paper> |
|
|
|
)} |
|
|
|
</Box> |
|
|
|
{/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} |
|
|
|
<Box> |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> |
|
|
|
<Typography variant="h6" gutterBottom sx={{ mb: 0 }}> |
|
|
|
{t("All Pick Order Lots")} |
|
|
|
</Typography> |
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}> |
|
|
|
{!isManualScanning ? ( |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
startIcon={<QrCodeIcon />} |
|
|
|
onClick={handleStartScan} |
|
|
|
color="primary" |
|
|
|
sx={{ minWidth: '120px' }} |
|
|
|
> |
|
|
|
{t("Start QR Scan")} |
|
|
|
</Button> |
|
|
|
) : ( |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
startIcon={<QrCodeIcon />} |
|
|
|
onClick={handleStopScan} |
|
|
|
color="secondary" |
|
|
|
sx={{ minWidth: '120px' }} |
|
|
|
> |
|
|
|
{t("Stop QR Scan")} |
|
|
|
</Button> |
|
|
|
)} |
|
|
|
|
|
|
|
{/* ✅ 保留:Submit All Scanned Button */} |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
color="success" |
|
|
|
onClick={handleSubmitAllScanned} |
|
|
|
disabled={scannedItemsCount === 0 || isSubmittingAll} |
|
|
|
sx={{ minWidth: '160px' }} |
|
|
|
> |
|
|
|
{isSubmittingAll ? ( |
|
|
|
<> |
|
|
|
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} /> |
|
|
|
{t("Submitting...")} |
|
|
|
</> |
|
|
|
) : ( |
|
|
|
`${t("Submit All Scanned")} (${scannedItemsCount})` |
|
|
|
)} |
|
|
|
</Button> |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
|
|
|
|
{qrScanError && !qrScanSuccess && ( |
|
|
|
<Alert severity="error" sx={{ mb: 2 }}> |
|
|
|
{t("QR code does not match any item in current orders.")} |
|
|
|
</Alert> |
|
|
|
)} |
|
|
|
{qrScanSuccess && ( |
|
|
|
<Alert severity="success" sx={{ mb: 2 }}> |
|
|
|
{t("QR code verified.")} |
|
|
|
</Alert> |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<TableContainer component={Paper}> |
|
|
|
<Table> |
|
|
|
<TableHead> |
|
|
|
|