diff --git a/inventory-backend/app/api/v1/bom.py b/inventory-backend/app/api/v1/bom.py
index 9b09d25..4cb002c 100644
--- a/inventory-backend/app/api/v1/bom.py
+++ b/inventory-backend/app/api/v1/bom.py
@@ -1,7 +1,9 @@
from flask import Blueprint, request, jsonify, current_app
from app.services.bom_service import BomService
from app.models.base import MaterialBase
+from app.extensions import db
from flask_jwt_extended import jwt_required
+from sqlalchemy import distinct
bom_bp = Blueprint('bom', __name__)
@@ -54,3 +56,20 @@ def get_material_base_list():
except Exception as e:
current_app.logger.error(f'获取基础物料列表失败: {str(e)}')
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
+
+@bom_bp.route('/parents', methods=['GET'])
+@jwt_required()
+def get_bom_parents():
+ """获取所有已定义BOM的父件物料列表"""
+ try:
+ subq = db.session.query(distinct(BomTable.parent_id)).subquery()
+ parents = MaterialBase.query.join(subq, MaterialBase.id == subq.c.parent_id).all()
+ data = [item.to_dict() for item in parents]
+ return jsonify({
+ 'code': 200,
+ 'msg': 'success',
+ 'data': data
+ })
+ except Exception as e:
+ current_app.logger.error(f'获取BOM父件列表失败: {str(e)}')
+ return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
diff --git a/inventory-web/src/api/inbound/stock.ts b/inventory-web/src/api/inbound/stock.ts
index 9bd18a5..bb74985 100644
--- a/inventory-web/src/api/inbound/stock.ts
+++ b/inventory-web/src/api/inbound/stock.ts
@@ -47,3 +47,19 @@ export function getMaterialBaseList(params?: any) {
params
})
}
+
+// 获取 BOM 父件列表
+export function getBomParents() {
+ return request({
+ url: '/v1/bom/parents',
+ method: 'get'
+ })
+}
+
+// 获取指定BOM详情
+export function getBom(parentId: number) {
+ return request({
+ url: `/v1/bom/${parentId}`,
+ method: 'get'
+ })
+}
diff --git a/inventory-web/src/views/outbound/Selection.vue b/inventory-web/src/views/outbound/Selection.vue
index 79743cc..45d5474 100644
--- a/inventory-web/src/views/outbound/Selection.vue
+++ b/inventory-web/src/views/outbound/Selection.vue
@@ -28,16 +28,31 @@
-
- 导入 BOM 表
-
创建 BOM 表
+
+
+
+
+
+
+
+
+ 添加子件到出库选单
+
+
+
+
+
+
+
+
+
import { ref, computed, onMounted } from 'vue'
-import { Printer, Search, Upload, Plus } from '@element-plus/icons-vue'
-import { getAllStock, printSelectionList, getMaterialBaseList, saveBom as saveBomApi } from '@/api/inbound/stock'
+import { Printer, Search, Plus } from '@element-plus/icons-vue'
+import { getAllStock, printSelectionList, getMaterialBaseList, saveBom as saveBomApi, getBomParents, getBom } from '@/api/inbound/stock'
import { ElMessage, ElMessageBox } from 'element-plus'
// --- 类型定义 ---
@@ -206,6 +221,12 @@ const selectedItems = ref([])
// BOM 相关
const bomDialogVisible = ref(false)
const materialBaseOptions = ref([])
+
+// BOM 选择功能
+const bomParents = ref([])
+const selectedParentId = ref(null)
+const bomChildren = ref([])
+
const bomForm = ref({
parent_id: null as number | null,
children: [] as any[]
@@ -238,19 +259,22 @@ const fetchData = async () => {
...item,
name: item.material_name,
type: 'material',
- typeLabel: '采购件'
+ typeLabel: '采购件',
+ base_id: item.base_id
}))
const semis = (res.semis || []).map((item: any) => ({
...item,
name: item.material_name || item.product_name, // 半成品字段名不确定,做个兼容
type: 'semi',
- typeLabel: '半成品'
+ typeLabel: '半成品',
+ base_id: item.base_id
}))
const products = (res.products || []).map((item: any) => ({
...item,
name: item.product_name,
type: 'product',
- typeLabel: '成品'
+ typeLabel: '成品',
+ base_id: item.base_id
}))
// 合并所有数据
@@ -295,11 +319,7 @@ const confirmPrint = async () => {
}
}
-// 5. BOM 操作
-const handleImportBom = () => {
- // TODO: 打开上传文件的 Dialog 或者跳转页面
- console.log('导入BOM功能开发中')
-}
+// 5. BOM 操作 (只保留创建)
const handleCreateBom = async () => {
bomDialogVisible.value = true
@@ -387,8 +407,64 @@ const getTypeTag = (type: string) => {
}
}
-onMounted(() => {
+// 6. BOM 选择功能
+const onBomParentChange = async (val: number) => {
+ selectedParentId.value = val
+ if (val) {
+ try {
+ const res = await getBom(val)
+ if (res.code === 200) {
+ bomChildren.value = res.data
+ } else {
+ ElMessage.error(res.msg || '获取BOM详情失败')
+ }
+ } catch (err) {
+ ElMessage.error('网络错误,无法获取BOM详情')
+ }
+ } else {
+ bomChildren.value = []
+ }
+}
+
+const addChildrenToSelection = () => {
+ if (bomChildren.value.length === 0) {
+ ElMessage.warning('当前没有可添加的子件')
+ return
+ }
+ let addedCount = 0
+ for (const child of bomChildren.value) {
+ // 寻找匹配的库存项 (根据 base_id)
+ const matchingItems = allStockData.value.filter(item => item.base_id == child.child_id)
+ if (matchingItems.length > 0) {
+ const existingIds = selectedItems.value.map(s => s.id)
+ // 最多添加 dosage 个 (简单起见每个匹配项添加一个)
+ for (let i = 0; i < Math.min(child.dosage, matchingItems.length); i++) {
+ const stock = matchingItems[i]
+ if (!existingIds.includes(stock.id)) {
+ selectedItems.value.push(stock)
+ addedCount++
+ }
+ }
+ } else {
+ ElMessage.warning(`物料 ${child.child_name} 暂无库存`)
+ }
+ }
+ if (addedCount > 0) {
+ ElMessage.success(`已添加 ${addedCount} 个子件到选单`)
+ }
+}
+
+onMounted(async () => {
fetchData()
+ // 加载BOM父件列表
+ try {
+ const res = await getBomParents()
+ if (res.code === 200) {
+ bomParents.value = res.data
+ }
+ } catch (err) {
+ console.error('加载BOM父件列表失败', err)
+ }
})