diff --git a/inventory-web/src/views/outbound/Selection.vue b/inventory-web/src/views/outbound/Selection.vue index 3351644..ac1ba97 100644 --- a/inventory-web/src/views/outbound/Selection.vue +++ b/inventory-web/src/views/outbound/Selection.vue @@ -432,7 +432,7 @@ import { ref, computed, watch } from 'vue' import { Printer, Search, Plus, Download, List } from '@element-plus/icons-vue' import { ElMessage, ElTable, ElMessageBox } from 'element-plus' -import { getAllStock, getStockList, printSelectionList } from '@/api/inbound/stock' +import { getStockList, printSelectionList } from '@/api/inbound/stock' import { getBomList, getBomDetail } from '@/api/bom' import { useUserStore } from '@/stores/user' import { submitOutboundRequest } from '@/api/outbound' @@ -467,7 +467,6 @@ const requestApproverId = ref(null) const approvers = ref([]) const requestSubmitting = ref(false) -const allStockData = ref([]) const stockList = ref([]) // 服务端分页数据 const stockTotal = ref(0) const stockPage = ref(1) @@ -525,43 +524,35 @@ const totalExportCount = computed(() => { return validSelectedItems.value.reduce((sum, item) => sum + (item.export_quantity || 0), 0) }) -// --- BOM 齐套性分析计算属性 --- +// --- BOM 齐套性分析计算属性(使用后端已计算的 current_stock,O(N),无嵌套循环)--- const maxBuildableSets = computed(() => { - if (currentBomDetail.value.length === 0 || allStockData.value.length === 0) return 0 - let minSets = Infinity - currentBomDetail.value.forEach((bomItem: any) => { - const dosage = parseFloat(bomItem.dosage) || 0 // 单套需求量 - if (dosage <= 0) return - // 匹配库存中的可用数量 - const stockItem = allStockData.value.find((s: any) => s.base_id && s.base_id == bomItem.child_id) - const available = stockItem ? (stockItem.availableCount || 0) : 0 - const buildable = Math.floor(available / dosage) - if (buildable < minSets) minSets = buildable - }) - return minSets === Infinity ? 0 : minSets + if (!currentBomDetail.value?.length) return 0 + return currentBomDetail.value.reduce((minSets, bomItem: any) => { + const dosage = parseFloat(bomItem.dosage) || 0 + if (dosage <= 0) return minSets + const stock = parseFloat(bomItem.current_stock) || 0 + return Math.min(minSets, Math.floor(stock / dosage)) + }, Infinity) }) const shortageList = computed(() => { - if (currentBomDetail.value.length === 0 || allStockData.value.length === 0 || bomSets.value <= 0) return [] - const target = bomSets.value - const shortages: any[] = [] - currentBomDetail.value.forEach((bomItem: any) => { - const dosage = parseFloat(bomItem.dosage) || 0 // 单套需求量 - const totalNeed = dosage * target - const stockItem = allStockData.value.find((s: any) => s.base_id && s.base_id == bomItem.child_id) - const available = stockItem ? (stockItem.availableCount || 0) : 0 - const shortage = totalNeed - available - if (shortage > 0) { - shortages.push({ - name: bomItem.child_name || bomItem.name || '未知物料', - sku: bomItem.child_sku || bomItem.sku || '-', - need: totalNeed, - available: available, - shortage: shortage + if (!currentBomDetail.value?.length || bomSets.value <= 0) return [] + return currentBomDetail.value + .map((bomItem: any) => { + const dosage = parseFloat(bomItem.dosage) || 0 + const totalNeed = dosage * bomSets.value + const stock = parseFloat(bomItem.current_stock) || 0 + const shortage = Math.max(0, totalNeed - stock) + return { ...bomItem, shortage, available: stock } }) - } - }) - return shortages + .filter((item: any) => item.shortage > 0) + .map((item: any) => ({ + name: item.child_name || item.name || '未知物料', + sku: item.child_sku || item.sku || '-', + need: item.dosage * bomSets.value, + available: item.available, + shortage: item.shortage + })) }) const hasShortage = computed(() => shortageList.value.length > 0 && bomSets.value > maxBuildableSets.value) @@ -576,31 +567,6 @@ const getTypeTag = (type: string) => { } } -// --- 核心逻辑 0:加载全量库存数据(BOM 齐套计算依赖此数据) --- -const ensureAllStockLoaded = async () => { - if (allStockData.value.length === 0) { - try { - const res: any = await getAllStock() - const rawMaterials = (res.materials || []).map((i: any) => ({ ...i, type: 'material', typeLabel: '采购件' })) - const rawSemis = (res.semis || []).map((i: any) => ({ ...i, type: 'semi', typeLabel: '半成品' })) - const rawProducts = (res.products || []).map((i: any) => ({ ...i, type: 'product', typeLabel: '成品' })) - const list = [...rawMaterials, ...rawSemis, ...rawProducts] - allStockData.value = list.map((i: any) => ({ - ...i, - name: i.name || i.material_name || i.product_name || '未知名称', - standard: i.standard || i.spec_model || '', - warehouse_location: i.warehouse_location || i.warehouse_loc || i.full_path || '', - uniqueKey: `${i.type}_${i.id}`, - available_quantity: parseFloat(i.available_quantity) || 0, - availableCount: parseFloat(i.available_quantity) || 0, - export_quantity: 1 - })) - } catch (e) { - ElMessage.error('加载全量库存数据失败(BOM 功能可能受影响)') - } - } -} - // --- 核心逻辑 1:手动添加库存 --- // 服务端加载库存列表 @@ -633,8 +599,6 @@ const openManualSelect = async () => { stockPage.value = 1 searchKeyword.value = '' await loadStockList() - await ensureAllStockLoaded() - allStockData.value.forEach(item => item.export_quantity = 0) } // 搜索框防抖触发服务端过滤 @@ -739,7 +703,6 @@ const openBomSelect = async () => { } catch (e) { ElMessage.error('加载 BOM 列表失败') } - await ensureAllStockLoaded() } // 监听 BOM 选择变化,自动加载明细并计算齐套性