(no commit message provided)
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from app.services.bom_service import BomService
|
||||
from app.models.base import MaterialBase
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
bom_bp = Blueprint('bom', __name__)
|
||||
@ -35,3 +36,19 @@ def save_bom():
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'保存BOM失败: {str(e)}')
|
||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||
|
||||
@bom_bp.route('/base/list', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_material_base_list():
|
||||
"""获取所有基础物料列表,用于前端下拉框"""
|
||||
try:
|
||||
materials = MaterialBase.query.filter_by(is_enabled=True).order_by(MaterialBase.id.desc()).all()
|
||||
data = [item.to_dict() for item in materials]
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'msg': 'success',
|
||||
'data': data
|
||||
})
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取基础物料列表失败: {str(e)}')
|
||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||
|
||||
@ -9,7 +9,7 @@ class BomService:
|
||||
def create_or_update_bom(parent_id, child_list):
|
||||
"""
|
||||
保存/更新父件的BOM子件关系
|
||||
child_list: [{"child_id": int, "dosage": float, "remark": str}, ...]
|
||||
child_list: [{"child_id": int, "dosage": float, "loss_rate": float, "remark": str}, ...]
|
||||
"""
|
||||
# 删除该父件原有的BOM记录
|
||||
BomTable.query.filter_by(parent_id=parent_id).delete()
|
||||
@ -19,6 +19,7 @@ class BomService:
|
||||
parent_id=parent_id,
|
||||
child_id=item['child_id'],
|
||||
dosage=item.get('dosage', 0),
|
||||
loss_rate=item.get('loss_rate', 0),
|
||||
remark=item.get('remark', '')
|
||||
)
|
||||
db.session.add(bom)
|
||||
|
||||
@ -28,4 +28,22 @@ export function printStocktakeReport(data: any) {
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 保存 BOM 结构
|
||||
export function saveBom(data: { parent_id: number; children: any[] }) {
|
||||
return request({
|
||||
url: '/v1/bom',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取基础物料列表
|
||||
export function getMaterialBaseList(params?: any) {
|
||||
return request({
|
||||
url: '/v1/bom/base/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
@ -113,13 +113,68 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- BOM 编辑弹窗 -->
|
||||
<el-dialog v-model="bomDialogVisible" title="创建/编辑 BOM" width="800px">
|
||||
<el-form :model="bomForm" label-width="120px">
|
||||
<el-form-item label="父件 (成品)">
|
||||
<el-select v-model="bomForm.parent_id" placeholder="请选择" filterable style="width:100%">
|
||||
<el-option
|
||||
v-for="item in materialBaseOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div style="font-weight: bold; margin-bottom: 10px;">子件列表</div>
|
||||
<el-table :data="bomForm.children" border style="width:100%; margin-top:10px">
|
||||
<el-table-column label="子件物料" width="300">
|
||||
<template #default="{ row, $index }">
|
||||
<el-select v-model="row.child_id" placeholder="请选择" filterable style="width:100%">
|
||||
<el-option
|
||||
v-for="item in materialBaseOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用量" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.dosage" :min="0" :precision="4" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="损耗率%" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.loss_rate" :min="0" :max="100" :precision="2" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeChildRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top:10px">
|
||||
<el-button type="primary" @click="addChildRow">添加子件</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="bomDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveBom">保存 BOM</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Printer, Search, Upload, Plus } from '@element-plus/icons-vue'
|
||||
import { getAllStock, printSelectionList } from '@/api/inbound/stock'
|
||||
import { getAllStock, printSelectionList, getMaterialBaseList, saveBom as saveBomApi } from '@/api/inbound/stock'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
// --- 类型定义 ---
|
||||
@ -153,6 +208,14 @@ const allStockData = ref<DisplayItem[]>([])
|
||||
// 当前选中的行
|
||||
const selectedItems = ref<DisplayItem[]>([])
|
||||
|
||||
// BOM 相关
|
||||
const bomDialogVisible = ref(false)
|
||||
const materialBaseOptions = ref<any[]>([])
|
||||
const bomForm = ref({
|
||||
parent_id: null as number | null,
|
||||
children: [] as any[]
|
||||
})
|
||||
|
||||
// --- 计算属性:前端模糊搜索过滤 ---
|
||||
const filteredTableData = computed(() => {
|
||||
const keyword = searchKeyword.value.trim().toLowerCase()
|
||||
@ -237,15 +300,81 @@ const confirmPrint = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. BOM 操作占位函数
|
||||
// 5. BOM 操作
|
||||
const handleImportBom = () => {
|
||||
// TODO: 打开上传文件的 Dialog 或者跳转页面
|
||||
ElMessage.info('点击了导入BOM,请实现具体逻辑')
|
||||
}
|
||||
|
||||
const handleCreateBom = () => {
|
||||
// TODO: 打开新建 BOM 的表单
|
||||
ElMessage.info('点击了创建BOM,请实现具体逻辑')
|
||||
const handleCreateBom = async () => {
|
||||
bomDialogVisible.value = true
|
||||
if (materialBaseOptions.value.length === 0) {
|
||||
try {
|
||||
const res = await getMaterialBaseList({})
|
||||
if (res.code === 200) {
|
||||
materialBaseOptions.value = res.data
|
||||
} else {
|
||||
ElMessage.error('获取物料列表失败')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('网络错误,无法获取物料列表')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addChildRow = () => {
|
||||
bomForm.value.children.push({
|
||||
child_id: null,
|
||||
dosage: 0,
|
||||
loss_rate: 0,
|
||||
remark: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removeChildRow = (index: number) => {
|
||||
bomForm.value.children.splice(index, 1)
|
||||
}
|
||||
|
||||
const saveBom = async () => {
|
||||
if (!bomForm.value.parent_id) {
|
||||
ElMessage.warning('请选择父件')
|
||||
return
|
||||
}
|
||||
if (bomForm.value.children.length === 0) {
|
||||
ElMessage.warning('请至少添加一个子件')
|
||||
return
|
||||
}
|
||||
for (const child of bomForm.value.children) {
|
||||
if (!child.child_id) {
|
||||
ElMessage.warning('请为每个子件选择物料')
|
||||
return
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
parent_id: bomForm.value.parent_id,
|
||||
children: bomForm.value.children.map(c => ({
|
||||
child_id: c.child_id,
|
||||
dosage: c.dosage,
|
||||
loss_rate: c.loss_rate,
|
||||
remark: c.remark || ''
|
||||
}))
|
||||
}
|
||||
try {
|
||||
const res = await saveBomApi(payload)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('BOM保存成功')
|
||||
bomDialogVisible.value = false
|
||||
// 清空表单
|
||||
bomForm.value = {
|
||||
parent_id: null,
|
||||
children: []
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('网络错误')
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:高亮关键词 (可选)
|
||||
@ -299,4 +428,4 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user