feat: restore available qty color and add BOM shortage analysis (kitting) for outbound
This commit is contained in:
@ -235,7 +235,7 @@
|
||||
|
||||
<el-table-column v-if="columns.available.visible" prop="availableCount" label="可用数" min-width="100" align="center" sortable="custom">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.availableCount }}</span>
|
||||
<span :style="{ fontWeight: 'bold', color: row.availableCount > 0 ? '#409EFF' : 'inherit' }">{{ row.availableCount }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
@ -150,10 +150,10 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="bomSelectVisible" title="按 BOM 套餐添加" width="600px" destroy-on-close :close-on-click-modal="false">
|
||||
<el-dialog v-model="bomSelectVisible" title="按 BOM 套餐添加" width="700px" destroy-on-close :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择产品">
|
||||
<el-select v-model="selectedBomNo" filterable placeholder="请选择启用状态的 BOM 配方" style="width: 100%">
|
||||
<el-select v-model="selectedBomNo" filterable placeholder="请选择启用状态的 BOM 配方" style="width: 100%" @change="() => {}">
|
||||
<el-option
|
||||
v-for="b in bomOptions"
|
||||
:key="`${b.bom_no}_${b.version}`"
|
||||
@ -164,14 +164,51 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="生产套数">
|
||||
<el-input-number v-model="bomSets" :min="1" label="套" style="width: 200px;" :disabled="!userStore.hasPermission('outbound_selection:operation')" />
|
||||
<el-tag v-if="selectedBomNo && maxBuildableSets >= 0" type="success" style="margin-left: 16px;">
|
||||
当前库存最多可成套出库: {{ maxBuildableSets }} 套
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 齐套性分析警告 -->
|
||||
<el-alert
|
||||
v-if="selectedBomNo && shortageList.length > 0 && bomSets > maxBuildableSets"
|
||||
type="error"
|
||||
:closable="false"
|
||||
style="margin: 16px 0;"
|
||||
>
|
||||
<template #title>
|
||||
<span style="font-weight: bold;">库存不足以组成 {{ bomSets }} 套,缺少以下物料:</span>
|
||||
</template>
|
||||
<el-table :data="shortageList" size="small" border style="margin-top: 8px; width: 100%;">
|
||||
<el-table-column prop="name" label="物料名称" min-width="120" />
|
||||
<el-table-column prop="sku" label="SKU" width="100" />
|
||||
<el-table-column label="需补足数量" width="100">
|
||||
<template #default="{ row }">
|
||||
<span style="color: #F56C6C; font-weight: bold;">{{ row.shortage }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可用库存" width="80">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.available }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-alert>
|
||||
|
||||
<div style="margin-left: 100px; color: #909399; font-size: 12px;">
|
||||
注意:系统将自动计算所需原料数量 ( 配方用量 × 套数 )。
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="bomSelectVisible = false">取消</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" @click="confirmBomAdd">一键计算并添加</el-button>
|
||||
<el-button
|
||||
v-if="userStore.hasPermission('outbound_selection:operation')"
|
||||
type="primary"
|
||||
@click="confirmBomAdd"
|
||||
:disabled="hasShortage"
|
||||
>
|
||||
{{ hasShortage ? '库存不足,无法添加' : '一键计算并添加' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@ -280,7 +317,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
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, printSelectionList } from '@/api/inbound/stock'
|
||||
@ -309,6 +346,7 @@ const manualTableRef = ref<InstanceType<typeof ElTable>>()
|
||||
const bomOptions = ref<any[]>([])
|
||||
const selectedBomNo = ref('')
|
||||
const bomSets = ref(1)
|
||||
const currentBomDetail = ref<any[]>([]) // 当前选中的BOM明细
|
||||
|
||||
// 打印相关
|
||||
const currentTime = ref('')
|
||||
@ -335,6 +373,47 @@ const totalExportCount = computed(() => {
|
||||
return validSelectedItems.value.reduce((sum, item) => sum + (item.export_quantity || 0), 0)
|
||||
})
|
||||
|
||||
// --- BOM 齐套性分析计算属性 ---
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
})
|
||||
return shortages
|
||||
})
|
||||
|
||||
const hasShortage = computed(() => shortageList.value.length > 0 && bomSets.value > maxBuildableSets.value)
|
||||
|
||||
// --- 辅助方法 ---
|
||||
const getTypeTag = (type: string) => {
|
||||
switch (type) {
|
||||
@ -466,6 +545,8 @@ const handleMainQuantityChange = (val: number | undefined, row: any) => {
|
||||
const openBomSelect = async () => {
|
||||
bomSelectVisible.value = true
|
||||
bomSets.value = 1
|
||||
selectedBomNo.value = ''
|
||||
currentBomDetail.value = []
|
||||
try {
|
||||
const res = await getBomList({ active_only: true })
|
||||
bomOptions.value = res.data || []
|
||||
@ -474,6 +555,21 @@ const openBomSelect = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 BOM 选择变化,自动加载明细并计算齐套性
|
||||
watch(selectedBomNo, async (newBomNo) => {
|
||||
if (!newBomNo) {
|
||||
currentBomDetail.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const detailRes = await getBomDetail(newBomNo)
|
||||
currentBomDetail.value = detailRes.data?.children || []
|
||||
} catch (e) {
|
||||
ElMessage.error('加载 BOM 明细失败')
|
||||
currentBomDetail.value = []
|
||||
}
|
||||
})
|
||||
|
||||
const confirmBomAdd = async () => {
|
||||
if(!selectedBomNo.value) return ElMessage.warning('请选择 BOM');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user