diff --git a/inventory-web/src/views/outbound/Selection.vue b/inventory-web/src/views/outbound/Selection.vue
index 769e6cc..b989974 100644
--- a/inventory-web/src/views/outbound/Selection.vue
+++ b/inventory-web/src/views/outbound/Selection.vue
@@ -239,14 +239,21 @@
{{ row.shortage > 0 ? row.shortage : '✓' }}
+
+
+ 缺货未出
+ 可出
+
+
- 注意:系统将自动计算所需原料数量 ( 配方用量 × 套数 )。
+ 注意:系统将自动计算所需原料数量 ( 配方用量 × 套数 )。缺货物料不会加入出库单,请先补货。
取消
+ 打印 BOM 清单
+
+
+
BOM 出库清单
+
+ BOM: {{ selectedBomNo }} | 套数: {{ bomSets }} 套 | 生成时间: {{ bomPrintTime }}
+
+
+
+
+ | 序号 | 物料名称 | SKU | 需求量 | 可用库存 | 缺料 | 结果 |
+
+
+
+
+ | {{ idx + 1 }} |
+ {{ row.name }} |
+ {{ row.sku }} |
+ {{ row.need }} |
+ {{ row.available }} |
+ {{ row.shortage > 0 ? row.shortage : '-' }} |
+ {{ row.shortage > 0 ? '缺货未出' : '可出' }} |
+
+
+
+
+ 可出 {{ bomAvailableCount }} 种 |
+ 缺货 {{ bomShortageCount }} 种
+
+
缺货物料不会加入本次出库单,请补货后重新按 BOM 出库。
+
+
{
const hasShortage = computed(() => bomDetailList.value.some((item: any) => item.shortage > 0) && bomSets.value > maxBuildableSets.value)
+// ★ BOM 打印统计
+const bomPrintTime = ref('')
+const bomAvailableCount = computed(() => bomDetailList.value.filter(i => i.shortage === 0).length)
+const bomShortageCount = computed(() => bomDetailList.value.filter(i => i.shortage > 0).length)
+
// --- 辅助方法 ---
const getTypeTag = (type: string) => {
switch (type) {
@@ -741,6 +784,23 @@ const openBomSelect = async () => {
bomOptions.value = Array.from(groupMap.entries()).map(([category, items]) => ({
category, count: items.length, items
}))
+
+ // ★ 提示有历史缺货记录的 BOM,防止下次重复出/漏出
+ const pendingBoms = Object.keys(bomShortageRecords.value).filter(no =>
+ flatItems.some(i => i.bom_no === no)
+ )
+ if (pendingBoms.length > 0) {
+ ElMessageBox.confirm(
+ `检测到以下 BOM 存在未完成的缺货记录(上次按 BOM 出库时缺货):\n\n` +
+ pendingBoms.map(no => {
+ const rec = bomShortageRecords.value[no]
+ return `• ${no}(${rec.items.length} 种缺货,${rec.time})`
+ }).join('\n') +
+ `\n\n建议先补货后再出库,避免漏出。是否查看?`,
+ '存在未完成出库',
+ { confirmButtonText: '知道了', cancelButtonText: '忽略', type: 'warning' }
+ ).catch(() => {})
+ }
} catch (e) {
ElMessage.error('加载 BOM 列表失败')
}
@@ -818,12 +878,36 @@ const confirmBomAdd = async () => {
if (addedCount > 0) {
const tip = skippedCount > 0 ? `(跳过 ${skippedCount} 种缺货物料)` : ''
ElMessage.success(`成功添加 ${addedCount} 类物料${tip}`)
+
+ // ★ 记录本次缺货清单(持久化,防止下次重复出/漏出)
+ saveBomShortage(selectedBomNo.value, bomDetailList.value.filter(i => i.shortage > 0))
bomSelectVisible.value = false
} else {
ElMessage.warning('该 BOM 所有物料库存均为 0')
+ // 全部缺货也要记录
+ saveBomShortage(selectedBomNo.value, bomDetailList.value.filter(i => i.shortage > 0))
}
}
+// ★ 缺货记录持久化:按 BOM 编号保存到 localStorage
+const BOM_SHORTAGE_KEY = 'MOM_BOM_SHORTAGE_RECORDS'
+const bomShortageRecords = ref>(JSON.parse(localStorage.getItem(BOM_SHORTAGE_KEY) || '{}'))
+
+const saveBomShortage = (bomNo: string, shortageList: any[]) => {
+ const records = { ...bomShortageRecords.value }
+ if (shortageList.length > 0) {
+ records[bomNo] = {
+ time: new Date().toLocaleString('zh-CN'),
+ items: shortageList.map(i => ({ name: i.name, sku: i.sku, need: i.need, available: i.available, shortage: i.shortage }))
+ }
+ } else {
+ // 本次没有缺货 → 清除该 BOM 的历史缺货记录
+ delete records[bomNo]
+ }
+ bomShortageRecords.value = records
+ localStorage.setItem(BOM_SHORTAGE_KEY, JSON.stringify(records))
+}
+
// --- 通用逻辑 ---
const handleSelectionChange = (val: any[]) => {
@@ -963,9 +1047,54 @@ const confirmSubmitRequest = async () => {
}
}
+// ★ BOM 清单打印(复用与 confirmPrint 相同的 iframe 打印机制)
+const printBomChecklist = () => {
+ if (bomDetailList.value.length === 0) return ElMessage.warning('请先选择 BOM 并加载清单')
+ bomPrintTime.value = new Date().toLocaleString('zh-CN')
+
+ setTimeout(() => {
+ const printElement = document.getElementById('bom-print-area')
+ if (!printElement) return
+
+ const iframe = document.createElement('iframe')
+ iframe.style.position = 'fixed'
+ iframe.style.right = '0'
+ iframe.style.bottom = '0'
+ iframe.style.width = '0'
+ iframe.style.height = '0'
+ iframe.style.border = '0'
+ document.body.appendChild(iframe)
+
+ const iframeDoc = iframe.contentWindow?.document
+ if (!iframeDoc) return
+ iframeDoc.open()
+ iframeDoc.write('BOM 出库清单')
+ iframeDoc.close()
+
+ const style = `
+ body { font-family: 'Microsoft YaHei', sans-serif; }
+ .print-bom-area { display: block !important; padding: 20px; }
+ .print-table { width: 100%; border-collapse: collapse; }
+ .print-table th, .print-table td { border: 1px solid #000; padding: 6px 8px; font-size: 13px; text-align: center; }
+ .print-table th { background: #f0f0f0; }
+ `;
+ const styleEl = iframeDoc.createElement('style')
+ styleEl.textContent = style
+ iframeDoc.head.appendChild(styleEl)
+
+ iframeDoc.body.appendChild(printElement.cloneNode(true))
+
+ setTimeout(() => {
+ iframe.contentWindow?.focus()
+ iframe.contentWindow?.print()
+ setTimeout(() => document.body.removeChild(iframe), 1000)
+ }, 500)
+ }, 300)
+}
+
const confirmPrint = async () => {
previewVisible.value = false;
-
+
try {
const payload = validSelectedItems.value.map(item => ({
name: item.name, standard: item.standard, quantity: item.export_quantity