(no commit message provided)
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -1,7 +1,9 @@
|
|||||||
from flask import Blueprint, request, jsonify, current_app
|
from flask import Blueprint, request, jsonify, current_app
|
||||||
from app.services.bom_service import BomService
|
from app.services.bom_service import BomService
|
||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
|
from app.extensions import db
|
||||||
from flask_jwt_extended import jwt_required
|
from flask_jwt_extended import jwt_required
|
||||||
|
from sqlalchemy import distinct
|
||||||
|
|
||||||
bom_bp = Blueprint('bom', __name__)
|
bom_bp = Blueprint('bom', __name__)
|
||||||
|
|
||||||
@ -54,3 +56,20 @@ def get_material_base_list():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f'获取基础物料列表失败: {str(e)}')
|
current_app.logger.error(f'获取基础物料列表失败: {str(e)}')
|
||||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
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
|
||||||
|
|||||||
@ -47,3 +47,19 @@ export function getMaterialBaseList(params?: any) {
|
|||||||
params
|
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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -28,16 +28,31 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="12" style="text-align: right;">
|
<el-col :span="12" style="text-align: right;">
|
||||||
<el-button type="primary" plain :icon="Upload" @click="handleImportBom">
|
|
||||||
导入 BOM 表
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" plain :icon="Plus" @click="handleCreateBom">
|
<el-button type="primary" plain :icon="Plus" @click="handleCreateBom">
|
||||||
创建 BOM 表
|
创建 BOM 表
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20" style="margin-top:15px">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-select v-model="selectedParentId" placeholder="选择 BOM 表" filterable style="width:100%" @change="onBomParentChange">
|
||||||
|
<el-option v-for="item in bomParents" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" style="text-align:right">
|
||||||
|
<el-button type="primary" plain @click="addChildrenToSelection">添加子件到出库选单</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-table v-if="bomChildren.length > 0" :data="bomChildren" border style="width:100%; margin-bottom:15px">
|
||||||
|
<el-table-column prop="child_name" label="子件名称" />
|
||||||
|
<el-table-column prop="dosage" label="所需个数" />
|
||||||
|
<el-table-column prop="current_stock" label="当前库存" />
|
||||||
|
<el-table-column prop="max_producible" label="最大可生产" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="selectedItems.length > 0"
|
v-if="selectedItems.length > 0"
|
||||||
:title="`当前已选中 ${selectedItems.length} 项物品`"
|
:title="`当前已选中 ${selectedItems.length} 项物品`"
|
||||||
@ -168,8 +183,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { Printer, Search, Upload, Plus } from '@element-plus/icons-vue'
|
import { Printer, Search, Plus } from '@element-plus/icons-vue'
|
||||||
import { getAllStock, printSelectionList, getMaterialBaseList, saveBom as saveBomApi } from '@/api/inbound/stock'
|
import { getAllStock, printSelectionList, getMaterialBaseList, saveBom as saveBomApi, getBomParents, getBom } from '@/api/inbound/stock'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
// --- 类型定义 ---
|
// --- 类型定义 ---
|
||||||
@ -206,6 +221,12 @@ const selectedItems = ref<DisplayItem[]>([])
|
|||||||
// BOM 相关
|
// BOM 相关
|
||||||
const bomDialogVisible = ref(false)
|
const bomDialogVisible = ref(false)
|
||||||
const materialBaseOptions = ref<any[]>([])
|
const materialBaseOptions = ref<any[]>([])
|
||||||
|
|
||||||
|
// BOM 选择功能
|
||||||
|
const bomParents = ref<any[]>([])
|
||||||
|
const selectedParentId = ref<number|null>(null)
|
||||||
|
const bomChildren = ref<any[]>([])
|
||||||
|
|
||||||
const bomForm = ref({
|
const bomForm = ref({
|
||||||
parent_id: null as number | null,
|
parent_id: null as number | null,
|
||||||
children: [] as any[]
|
children: [] as any[]
|
||||||
@ -238,19 +259,22 @@ const fetchData = async () => {
|
|||||||
...item,
|
...item,
|
||||||
name: item.material_name,
|
name: item.material_name,
|
||||||
type: 'material',
|
type: 'material',
|
||||||
typeLabel: '采购件'
|
typeLabel: '采购件',
|
||||||
|
base_id: item.base_id
|
||||||
}))
|
}))
|
||||||
const semis = (res.semis || []).map((item: any) => ({
|
const semis = (res.semis || []).map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
name: item.material_name || item.product_name, // 半成品字段名不确定,做个兼容
|
name: item.material_name || item.product_name, // 半成品字段名不确定,做个兼容
|
||||||
type: 'semi',
|
type: 'semi',
|
||||||
typeLabel: '半成品'
|
typeLabel: '半成品',
|
||||||
|
base_id: item.base_id
|
||||||
}))
|
}))
|
||||||
const products = (res.products || []).map((item: any) => ({
|
const products = (res.products || []).map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
name: item.product_name,
|
name: item.product_name,
|
||||||
type: 'product',
|
type: 'product',
|
||||||
typeLabel: '成品'
|
typeLabel: '成品',
|
||||||
|
base_id: item.base_id
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 合并所有数据
|
// 合并所有数据
|
||||||
@ -295,11 +319,7 @@ const confirmPrint = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. BOM 操作
|
// 5. BOM 操作 (只保留创建)
|
||||||
const handleImportBom = () => {
|
|
||||||
// TODO: 打开上传文件的 Dialog 或者跳转页面
|
|
||||||
console.log('导入BOM功能开发中')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateBom = async () => {
|
const handleCreateBom = async () => {
|
||||||
bomDialogVisible.value = true
|
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()
|
fetchData()
|
||||||
|
// 加载BOM父件列表
|
||||||
|
try {
|
||||||
|
const res = await getBomParents()
|
||||||
|
if (res.code === 200) {
|
||||||
|
bomParents.value = res.data
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载BOM父件列表失败', err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user