From 170e80e2a5c908ea148bae366232cd0774bafcf2 Mon Sep 17 00:00:00 2001 From: dxc Date: Mon, 9 Feb 2026 15:50:56 +0800 Subject: [PATCH] (no commit message provided) Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) --- inventory-backend/app/api/v1/bom.py | 19 ++++ inventory-web/src/api/inbound/stock.ts | 16 +++ .../src/views/outbound/Selection.vue | 104 +++++++++++++++--- 3 files changed, 125 insertions(+), 14 deletions(-) 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) + } })