perf: 消除 BOM 齐套分析的全量库存拉取和 O(N·M) 嵌套循环,改为使用后端返回的 current_stock
This commit is contained in:
@ -432,7 +432,7 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { Printer, Search, Plus, Download, List } from '@element-plus/icons-vue'
|
import { Printer, Search, Plus, Download, List } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElTable, ElMessageBox } from 'element-plus'
|
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 { getBomList, getBomDetail } from '@/api/bom'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { submitOutboundRequest } from '@/api/outbound'
|
import { submitOutboundRequest } from '@/api/outbound'
|
||||||
@ -467,7 +467,6 @@ const requestApproverId = ref<number | null>(null)
|
|||||||
const approvers = ref<any[]>([])
|
const approvers = ref<any[]>([])
|
||||||
const requestSubmitting = ref(false)
|
const requestSubmitting = ref(false)
|
||||||
|
|
||||||
const allStockData = ref<any[]>([])
|
|
||||||
const stockList = ref<any[]>([]) // 服务端分页数据
|
const stockList = ref<any[]>([]) // 服务端分页数据
|
||||||
const stockTotal = ref(0)
|
const stockTotal = ref(0)
|
||||||
const stockPage = ref(1)
|
const stockPage = ref(1)
|
||||||
@ -525,43 +524,35 @@ const totalExportCount = computed(() => {
|
|||||||
return validSelectedItems.value.reduce((sum, item) => sum + (item.export_quantity || 0), 0)
|
return validSelectedItems.value.reduce((sum, item) => sum + (item.export_quantity || 0), 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- BOM 齐套性分析计算属性 ---
|
// --- BOM 齐套性分析计算属性(使用后端已计算的 current_stock,O(N),无嵌套循环)---
|
||||||
const maxBuildableSets = computed(() => {
|
const maxBuildableSets = computed(() => {
|
||||||
if (currentBomDetail.value.length === 0 || allStockData.value.length === 0) return 0
|
if (!currentBomDetail.value?.length) return 0
|
||||||
let minSets = Infinity
|
return currentBomDetail.value.reduce((minSets, bomItem: any) => {
|
||||||
currentBomDetail.value.forEach((bomItem: any) => {
|
const dosage = parseFloat(bomItem.dosage) || 0
|
||||||
const dosage = parseFloat(bomItem.dosage) || 0 // 单套需求量
|
if (dosage <= 0) return minSets
|
||||||
if (dosage <= 0) return
|
const stock = parseFloat(bomItem.current_stock) || 0
|
||||||
// 匹配库存中的可用数量
|
return Math.min(minSets, Math.floor(stock / dosage))
|
||||||
const stockItem = allStockData.value.find((s: any) => s.base_id && s.base_id == bomItem.child_id)
|
}, Infinity)
|
||||||
const available = stockItem ? (stockItem.availableCount || 0) : 0
|
|
||||||
const buildable = Math.floor(available / dosage)
|
|
||||||
if (buildable < minSets) minSets = buildable
|
|
||||||
})
|
|
||||||
return minSets === Infinity ? 0 : minSets
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const shortageList = computed(() => {
|
const shortageList = computed(() => {
|
||||||
if (currentBomDetail.value.length === 0 || allStockData.value.length === 0 || bomSets.value <= 0) return []
|
if (!currentBomDetail.value?.length || bomSets.value <= 0) return []
|
||||||
const target = bomSets.value
|
return currentBomDetail.value
|
||||||
const shortages: any[] = []
|
.map((bomItem: any) => {
|
||||||
currentBomDetail.value.forEach((bomItem: any) => {
|
const dosage = parseFloat(bomItem.dosage) || 0
|
||||||
const dosage = parseFloat(bomItem.dosage) || 0 // 单套需求量
|
const totalNeed = dosage * bomSets.value
|
||||||
const totalNeed = dosage * target
|
const stock = parseFloat(bomItem.current_stock) || 0
|
||||||
const stockItem = allStockData.value.find((s: any) => s.base_id && s.base_id == bomItem.child_id)
|
const shortage = Math.max(0, totalNeed - stock)
|
||||||
const available = stockItem ? (stockItem.availableCount || 0) : 0
|
return { ...bomItem, shortage, available: stock }
|
||||||
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
|
|
||||||
})
|
})
|
||||||
}
|
.filter((item: any) => item.shortage > 0)
|
||||||
})
|
.map((item: any) => ({
|
||||||
return shortages
|
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)
|
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:手动添加库存 ---
|
// --- 核心逻辑 1:手动添加库存 ---
|
||||||
|
|
||||||
// 服务端加载库存列表
|
// 服务端加载库存列表
|
||||||
@ -633,8 +599,6 @@ const openManualSelect = async () => {
|
|||||||
stockPage.value = 1
|
stockPage.value = 1
|
||||||
searchKeyword.value = ''
|
searchKeyword.value = ''
|
||||||
await loadStockList()
|
await loadStockList()
|
||||||
await ensureAllStockLoaded()
|
|
||||||
allStockData.value.forEach(item => item.export_quantity = 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索框防抖触发服务端过滤
|
// 搜索框防抖触发服务端过滤
|
||||||
@ -739,7 +703,6 @@ const openBomSelect = async () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error('加载 BOM 列表失败')
|
ElMessage.error('加载 BOM 列表失败')
|
||||||
}
|
}
|
||||||
await ensureAllStockLoaded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 BOM 选择变化,自动加载明细并计算齐套性
|
// 监听 BOM 选择变化,自动加载明细并计算齐套性
|
||||||
|
|||||||
Reference in New Issue
Block a user