(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 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 flask_jwt_extended import jwt_required
|
from flask_jwt_extended import jwt_required
|
||||||
|
|
||||||
bom_bp = Blueprint('bom', __name__)
|
bom_bp = Blueprint('bom', __name__)
|
||||||
@ -35,3 +36,19 @@ def save_bom():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f'保存BOM失败: {str(e)}')
|
current_app.logger.error(f'保存BOM失败: {str(e)}')
|
||||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
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):
|
def create_or_update_bom(parent_id, child_list):
|
||||||
"""
|
"""
|
||||||
保存/更新父件的BOM子件关系
|
保存/更新父件的BOM子件关系
|
||||||
child_list: [{"child_id": int, "dosage": float, "remark": str}, ...]
|
child_list: [{"child_id": int, "dosage": float, "loss_rate": float, "remark": str}, ...]
|
||||||
"""
|
"""
|
||||||
# 删除该父件原有的BOM记录
|
# 删除该父件原有的BOM记录
|
||||||
BomTable.query.filter_by(parent_id=parent_id).delete()
|
BomTable.query.filter_by(parent_id=parent_id).delete()
|
||||||
@ -19,6 +19,7 @@ class BomService:
|
|||||||
parent_id=parent_id,
|
parent_id=parent_id,
|
||||||
child_id=item['child_id'],
|
child_id=item['child_id'],
|
||||||
dosage=item.get('dosage', 0),
|
dosage=item.get('dosage', 0),
|
||||||
|
loss_rate=item.get('loss_rate', 0),
|
||||||
remark=item.get('remark', '')
|
remark=item.get('remark', '')
|
||||||
)
|
)
|
||||||
db.session.add(bom)
|
db.session.add(bom)
|
||||||
|
|||||||
@ -29,3 +29,21 @@ export function printStocktakeReport(data: any) {
|
|||||||
data
|
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>
|
</template>
|
||||||
</el-dialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<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, 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'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
// --- 类型定义 ---
|
// --- 类型定义 ---
|
||||||
@ -153,6 +208,14 @@ const allStockData = ref<DisplayItem[]>([])
|
|||||||
// 当前选中的行
|
// 当前选中的行
|
||||||
const selectedItems = 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 filteredTableData = computed(() => {
|
||||||
const keyword = searchKeyword.value.trim().toLowerCase()
|
const keyword = searchKeyword.value.trim().toLowerCase()
|
||||||
@ -237,15 +300,81 @@ const confirmPrint = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. BOM 操作占位函数
|
// 5. BOM 操作
|
||||||
const handleImportBom = () => {
|
const handleImportBom = () => {
|
||||||
// TODO: 打开上传文件的 Dialog 或者跳转页面
|
// TODO: 打开上传文件的 Dialog 或者跳转页面
|
||||||
ElMessage.info('点击了导入BOM,请实现具体逻辑')
|
ElMessage.info('点击了导入BOM,请实现具体逻辑')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateBom = () => {
|
const handleCreateBom = async () => {
|
||||||
// TODO: 打开新建 BOM 的表单
|
bomDialogVisible.value = true
|
||||||
ElMessage.info('点击了创建BOM,请实现具体逻辑')
|
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('网络错误')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:高亮关键词 (可选)
|
// 辅助函数:高亮关键词 (可选)
|
||||||
|
|||||||
Reference in New Issue
Block a user