|
|
|
@ -36,6 +36,13 @@
|
|
|
|
|
<el-button type='primary' plain icon='Plus' @click='handleAdd()' v-hasPermi="['mes:materialBom:add']">新增
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
<el-col :span='1.5'>
|
|
|
|
|
<el-button type='primary' plain icon='Plus' @click='handleAddMaterialBom()' v-hasPermi="['mes:materialBom:add']">
|
|
|
|
|
新增BOM
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
<el-col :span='1.5'>
|
|
|
|
|
<el-button type='info' plain icon='Sort' @click='handleToggleExpandAll'>展开/折叠</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
@ -53,7 +60,7 @@
|
|
|
|
|
<!-- <el-table-column label='主键标识' align='center' prop='materialBomId' />-->
|
|
|
|
|
<!-- <el-table-column label='父级标识' align='center' prop='parentId' />-->
|
|
|
|
|
<!-- <el-table-column label='物料ID' align='center' prop='materialId' />-->
|
|
|
|
|
<el-table-column label='物料名称' align='left' prop='materialName' width='300'/>
|
|
|
|
|
<el-table-column label='物料名称' align='left' prop='materialName' width='500'/>
|
|
|
|
|
<el-table-column label='bom说明' align='center' prop='materialBomDesc' />
|
|
|
|
|
<el-table-column label='bom版本' align='center' prop='materialBomVersion' />
|
|
|
|
|
<!-- <el-table-column label='祖级列表' align='center' prop='ancestors' />-->
|
|
|
|
@ -208,6 +215,94 @@
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 根据结构BOM添加物料BOM信息对话框 -->
|
|
|
|
|
<el-dialog :title="materialBomDialog.title" v-model="materialBomDialog.visible" width="1600px" append-to-body>
|
|
|
|
|
<el-table
|
|
|
|
|
ref="materialBomTableRef"
|
|
|
|
|
:data="materialBomList"
|
|
|
|
|
row-key="materialBomId"
|
|
|
|
|
border
|
|
|
|
|
:tree-props="{
|
|
|
|
|
children: 'children',
|
|
|
|
|
hasChildren: 'hasChildren'
|
|
|
|
|
}"
|
|
|
|
|
:default-expand-all="true"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column label="序号" type="index" width="50" align="center" />
|
|
|
|
|
<el-table-column label="物料类型" prop="materialTypeName" min-width="120" />
|
|
|
|
|
<el-table-column label="物料" prop="materialName" min-width="180">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="scope.row.materialName"
|
|
|
|
|
placeholder="点击选择物料"
|
|
|
|
|
readonly
|
|
|
|
|
@click="handleMaterialSelect(scope.row)">
|
|
|
|
|
<template #append>
|
|
|
|
|
<el-button icon="Search" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-input>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="BOM说明" prop="materialBomDesc" min-width="120">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-input v-model="scope.row.materialBomDesc" placeholder="请输入BOM说明" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="BOM版本" prop="materialBomVersion" min-width="120">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-input v-model="scope.row.materialBomVersion" placeholder="请输入BOM版本" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="标准数量" prop="standardAmount" width="160">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-input-number
|
|
|
|
|
v-model="scope.row.standardAmount"
|
|
|
|
|
:min="1"
|
|
|
|
|
:precision="2"
|
|
|
|
|
:step="1"
|
|
|
|
|
:controls="true"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="校验类型" prop="checkType" width="120">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-select v-model="scope.row.checkType" placeholder="请选择">
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="dict in mes_check_type"
|
|
|
|
|
:key="dict.value"
|
|
|
|
|
:label="dict.label"
|
|
|
|
|
:value="dict.value"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="激活标识" prop="activeFlag" width="120">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-select v-model="scope.row.activeFlag" placeholder="请选择">
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="dict in active_flag"
|
|
|
|
|
:key="dict.value"
|
|
|
|
|
:label="dict.label"
|
|
|
|
|
:value="dict.value"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="备注" prop="remark" min-width="120">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-input v-model="scope.row.remark" placeholder="请输入备注" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
<el-button type="primary" @click="submitMaterialBom">确 定</el-button>
|
|
|
|
|
<el-button @click="cancelMaterialBom">取 消</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
@ -217,10 +312,12 @@ import {
|
|
|
|
|
getMaterialBom,
|
|
|
|
|
delMaterialBom,
|
|
|
|
|
addMaterialBom,
|
|
|
|
|
updateMaterialBom
|
|
|
|
|
updateMaterialBom, addBatchMaterialBom
|
|
|
|
|
} from '@/api/mes/materialBom';
|
|
|
|
|
import { MaterialBomVO, MaterialBomQuery, MaterialBomForm } from '@/api/mes/materialBom/types';
|
|
|
|
|
import MaterialSelect from '@/views/mes/baseMaterialInfo/addMaterial.vue';
|
|
|
|
|
import { BaseStructureBomVO } from '@/api/mes/baseStructureBom/types';
|
|
|
|
|
import { getBaseStructureBomList } from '@/api/mes/baseStructureBom';
|
|
|
|
|
|
|
|
|
|
type MaterialBomOption = {
|
|
|
|
|
materialBomId: number;
|
|
|
|
@ -233,7 +330,7 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
|
|
|
|
const { mes_check_type, material_classfication, active_flag } = toRefs<any>(proxy?.useDict('mes_check_type', 'material_classfication', 'active_flag'));
|
|
|
|
|
|
|
|
|
|
const materialBomList = ref([]);
|
|
|
|
|
const materialBomList = ref<any[]>([]);
|
|
|
|
|
const materialBomOptions = ref<MaterialBomOption[]>([]);
|
|
|
|
|
const buttonLoading = ref(false);
|
|
|
|
|
const showSearch = ref(true);
|
|
|
|
@ -420,15 +517,253 @@ const handleDelete = async (row: MaterialBomVO) => {
|
|
|
|
|
/** 新增按钮操作 */
|
|
|
|
|
const handleMaterialAdd = () => {
|
|
|
|
|
materialOpen.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 提交物料BOM信息按钮 */
|
|
|
|
|
};
|
|
|
|
|
// 提交物料选择
|
|
|
|
|
const submitMaterialForm = () => {
|
|
|
|
|
let selectedRow = materialSelectRef.value.tableRef.store.states.currentRow.value;
|
|
|
|
|
form.value.materialId = selectedRow.materialId
|
|
|
|
|
form.value.materialName = selectedRow.materialName
|
|
|
|
|
materialOpen.value = false;
|
|
|
|
|
}
|
|
|
|
|
const selectedRow = materialSelectRef.value.tableRef.store.states.currentRow.value;
|
|
|
|
|
if (materialOpen.value) {
|
|
|
|
|
if (currentRow.value && selectedRow) {
|
|
|
|
|
currentRow.value.materialId = selectedRow.materialId;
|
|
|
|
|
currentRow.value.materialName = selectedRow.materialName;
|
|
|
|
|
materialOpen.value = false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
form.value.materialId = selectedRow.materialId;
|
|
|
|
|
form.value.materialName = selectedRow.materialName;
|
|
|
|
|
materialOpen.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 当前选中的行 */
|
|
|
|
|
const currentRow = ref<any>(null);
|
|
|
|
|
|
|
|
|
|
// 打开物料选择对话框
|
|
|
|
|
const handleMaterialSelect = (row: any) => {
|
|
|
|
|
currentRow.value = row;
|
|
|
|
|
materialOpen.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 物料选择回调
|
|
|
|
|
const handleSelection = (selection: any) => {
|
|
|
|
|
if (currentRow.value) {
|
|
|
|
|
currentRow.value.materialId = selection.materialId;
|
|
|
|
|
currentRow.value.materialName = selection.materialName;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//根据结构BOM新增物料BOM对话框
|
|
|
|
|
const materialBomDialog = reactive({
|
|
|
|
|
visible: false,
|
|
|
|
|
title: ''
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// const materialBomForm = ref({});
|
|
|
|
|
// const currentStructureBom = ref<BaseStructureBomVO>({} as BaseStructureBomVO);
|
|
|
|
|
const materialOptions = ref<any>({});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 打开添加物料BOM对话框
|
|
|
|
|
const handleAddMaterialBom = async () => {
|
|
|
|
|
materialBomDialog.visible = true;
|
|
|
|
|
materialBomDialog.title = "新增BOM";
|
|
|
|
|
// 重置物料BOM列表,避免影响页面主列表
|
|
|
|
|
materialBomList.value = [];
|
|
|
|
|
// 获取结构BOM数据并生成物料BOM列表
|
|
|
|
|
await generateMaterialBomList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 生成物料BOM列表
|
|
|
|
|
const generateMaterialBomList = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// 获取结构BOM数据
|
|
|
|
|
const res = await getBaseStructureBomList(null);
|
|
|
|
|
|
|
|
|
|
// 递归处理结构BOM树
|
|
|
|
|
const processBomTree = (items: any[]): MaterialBomForm[] => {
|
|
|
|
|
return items.map(item => ({
|
|
|
|
|
materialBomId: -Math.abs(item.structureBomId), // 使用负数作为临时ID
|
|
|
|
|
parentId: item.parentId === 0 ? 0 : -Math.abs(item.parentId), // 保持父子关系
|
|
|
|
|
materialTypeId: item.materialTypeId,
|
|
|
|
|
materialTypeName: item.materialTypeName,
|
|
|
|
|
materialId: undefined,
|
|
|
|
|
materialName: undefined,
|
|
|
|
|
materialBomDesc: '',
|
|
|
|
|
materialBomVersion: '',
|
|
|
|
|
standardAmount: undefined,
|
|
|
|
|
checkType: undefined,
|
|
|
|
|
activeFlag: undefined,
|
|
|
|
|
remark: '',
|
|
|
|
|
children: item.children && item.children.length > 0
|
|
|
|
|
? processBomTree(item.children)
|
|
|
|
|
: []
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 构建完整的树形结构
|
|
|
|
|
const structureBomData = buildTree(res.data);
|
|
|
|
|
// 处理数据并赋值
|
|
|
|
|
materialBomList.value = processBomTree(structureBomData);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取结构BOM数据失败:', error);
|
|
|
|
|
proxy?.$modal.msgError('获取数据失败');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 构建树形结构
|
|
|
|
|
const buildTree = (data: any[]) => {
|
|
|
|
|
const tree: any[] = [];
|
|
|
|
|
const map = new Map();
|
|
|
|
|
|
|
|
|
|
// 首先创建一个以id为键的映射
|
|
|
|
|
data.forEach(item => {
|
|
|
|
|
// 创建新对象,避免修改原始数据
|
|
|
|
|
const node = { ...item, children: [] };
|
|
|
|
|
map.set(item.structureBomId, node);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 然后构建树形结构
|
|
|
|
|
data.forEach(item => {
|
|
|
|
|
const node = map.get(item.structureBomId);
|
|
|
|
|
if (item.parentId === 0) {
|
|
|
|
|
// 顶级节点直接加入树中
|
|
|
|
|
tree.push(node);
|
|
|
|
|
} else {
|
|
|
|
|
// 将当前节点添加到父节点的children中
|
|
|
|
|
const parent = map.get(item.parentId);
|
|
|
|
|
if (parent) {
|
|
|
|
|
if (!parent.children) {
|
|
|
|
|
parent.children = [];
|
|
|
|
|
}
|
|
|
|
|
parent.children.push(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return tree;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 物料选择变更处理
|
|
|
|
|
const handleMaterialChange = (materialId: string | number, row: any) => {
|
|
|
|
|
const material = materialOptions.value[row.materialTypeId]?.find(
|
|
|
|
|
(item: any) => item.materialId === materialId
|
|
|
|
|
);
|
|
|
|
|
if (material) {
|
|
|
|
|
row.materialName = material.materialName;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 提交物料BOM
|
|
|
|
|
const submitMaterialBom = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// 验证数据完整性
|
|
|
|
|
const validateTreeData = (nodes: any[], path: string = ''): string | null => {
|
|
|
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
|
|
|
const node = nodes[i];
|
|
|
|
|
const currentPath = path ? `${path} -> ${node.materialTypeName}` : node.materialTypeName;
|
|
|
|
|
|
|
|
|
|
// 如果当前节点没有选择物料
|
|
|
|
|
if (!node.materialId) {
|
|
|
|
|
// 但是有子节点被选择了,说明跳过了当前层。例如一三层填充但跳过了第二层
|
|
|
|
|
if (hasSelectedDescendant(node)) {
|
|
|
|
|
return `请先为 "${currentPath}" 选择物料,不能跳过中间层级`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 递归检查子节点
|
|
|
|
|
if (node.children && node.children.length > 0) {
|
|
|
|
|
const childResult = validateTreeData(node.children, currentPath);
|
|
|
|
|
if (childResult) {
|
|
|
|
|
return childResult;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 检查节点是否有被选择的后代节点
|
|
|
|
|
const hasSelectedDescendant = (node: any): boolean => {
|
|
|
|
|
if (!node.children) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return node.children.some((child: any) => {
|
|
|
|
|
if (child.materialId) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return hasSelectedDescendant(child);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 执行验证
|
|
|
|
|
const validationError = validateTreeData(materialBomList.value);
|
|
|
|
|
if (validationError) {
|
|
|
|
|
proxy?.$modal.msgError(validationError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将树形结构转换为扁平数组,并处理父子关系
|
|
|
|
|
const flattenTree = (nodes: any[], parentId: number = 0): any[] => {
|
|
|
|
|
let result: any[] = [];
|
|
|
|
|
nodes.forEach((node, index) => {
|
|
|
|
|
const { children, ...nodeWithoutChildren } = node;
|
|
|
|
|
|
|
|
|
|
// 只处理选择了物料的节点
|
|
|
|
|
if (nodeWithoutChildren.materialId) {
|
|
|
|
|
// 检查必填字段
|
|
|
|
|
if (!nodeWithoutChildren.standardAmount) {
|
|
|
|
|
throw new Error(`物料 "${nodeWithoutChildren.materialName}" 的标准数量不能为空`);
|
|
|
|
|
}
|
|
|
|
|
if (nodeWithoutChildren.standardAmount <= 0) {
|
|
|
|
|
throw new Error(`物料 "${nodeWithoutChildren.materialName}" 的标准数量必须大于0`);
|
|
|
|
|
}
|
|
|
|
|
if (!nodeWithoutChildren.checkType) {
|
|
|
|
|
throw new Error(`物料 "${nodeWithoutChildren.materialName}" 的校验类型不能为空`);
|
|
|
|
|
}
|
|
|
|
|
if (!nodeWithoutChildren.activeFlag) {
|
|
|
|
|
throw new Error(`物料 "${nodeWithoutChildren.materialName}" 的激活标识不能为空`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newNode = {
|
|
|
|
|
...nodeWithoutChildren,
|
|
|
|
|
parentId: parentId
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
result.push(newNode);
|
|
|
|
|
|
|
|
|
|
// 如果有子节点,递归处理
|
|
|
|
|
if (children && children.length > 0) {
|
|
|
|
|
result = result.concat(flattenTree(children, newNode.materialBomId));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const flattenedData = flattenTree(materialBomList.value);
|
|
|
|
|
if (flattenedData.length === 0) {
|
|
|
|
|
proxy?.$modal.msgError('请至少输入一行有效数据');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提交数据
|
|
|
|
|
await addBatchMaterialBom(flattenedData);
|
|
|
|
|
proxy?.$modal.msgSuccess('新增成功');
|
|
|
|
|
materialBomDialog.visible = false;
|
|
|
|
|
getList(); // 刷新列表
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error('提交失败:', error);
|
|
|
|
|
proxy?.$modal.msgError(error.message || '提交失败');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 取消
|
|
|
|
|
const cancelMaterialBom = () => {
|
|
|
|
|
materialBomDialog.visible = false;
|
|
|
|
|
// 重新获取主列表数据
|
|
|
|
|
getList();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 选中数据
|
|
|
|
|
const selection = ref([]);
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getList();
|
|
|
|
|