| | |
| | | 编辑 |
| | | </el-button> |
| | | <el-button v-if="bomDataValue.isEdit" |
| | | type="primary" |
| | | plain |
| | | @click="addBomItem()"> |
| | | 添加物料 |
| | | </el-button> |
| | | <el-button v-if="bomDataValue.isEdit" |
| | | @click="cancelEditBom"> |
| | | 取消 |
| | | </el-button> |
| | |
| | | :model="bomDataValue"> |
| | | <el-table :data="bomDataValue.dataList" |
| | | row-key="tempId" |
| | | default-expand-all |
| | | :tree-props="{children: 'children', hasChildren: 'hasChildren'}" |
| | | style="width: 100%"> |
| | | <el-table-column prop="productName" |
| | | label="产品" /> |
| | |
| | | <el-select v-model="row.model" |
| | | placeholder="请选择规格" |
| | | clearable |
| | | :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" |
| | | :disabled="!bomDataValue.isEdit" |
| | | style="width: 100%" |
| | | @visible-change="(v) => { if (v) openBomDialog(row.tempId) }"> |
| | | <el-option v-if="row.model" |
| | |
| | | label="消耗工序"> |
| | | <template #default="{ row }"> |
| | | <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit" |
| | | :rules="bomDataValue.dataList.some(item => (item).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]" |
| | | :rules="[{ required: true, message: '请选择消耗工序', trigger: 'change' }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.processId" |
| | | placeholder="请选择" |
| | |
| | | clearable |
| | | style="width: 100%" |
| | | @change="value => handleBomProcessChange(row, value)" |
| | | :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)"> |
| | | :disabled="!bomDataValue.isEdit"> |
| | | <el-option v-for="item in bomDataValue.processOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitQuantity" |
| | | label="单位产出所需数量"> |
| | | <template #default="{ row }"> |
| | | <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.unitQuantity" |
| | | :min="0" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | @change="handleUnitQuantityChange" |
| | | :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column v-if="pageType === 'order'" |
| | | prop="demandedQuantity" |
| | | label="需求总量"> |
| | | <template #default="{ row }"> |
| | | <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.demandedQuantity" |
| | | :min="0" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="true" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" |
| | | label="单位"> |
| | | <template #default="{ row }"> |
| | | <el-form-item v-if="pageType === 'order' && bomDataValue.isEdit" |
| | | :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]" |
| | | style="margin: 0"> |
| | | <el-input v-model="row.unit" |
| | | placeholder="请输入单位" |
| | | clearable |
| | | :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" |
| | | fixed="right" |
| | | width="200" |
| | | width="100" |
| | | v-if="pageType === 'order' && bomDataValue.isEdit"> |
| | | <template #default="{ row }"> |
| | | <el-button v-if="bomDataValue.isEdit && !bomDataValue.dataList.some(item => (item).tempId === row.tempId)" |
| | | type="danger" |
| | | <el-button type="danger" |
| | | text |
| | | @click="removeBomItem(row.tempId)">删除 |
| | | </el-button> |
| | | <el-button v-if="bomDataValue.isEdit" |
| | | type="primary" |
| | | text |
| | | @click="addBomItem(row.tempId)">添加 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | item.operationName = processName; |
| | | }; |
| | | |
| | | const normalizeTreeData = items => { |
| | | const normalizeBomData = items => { |
| | | items.forEach(item => { |
| | | item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`; |
| | | syncProcessOperationFields(item); |
| | | if (Array.isArray(item.children) && item.children.length > 0) { |
| | | normalizeTreeData(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const toQuantityNumber = value => { |
| | | const numberValue = Number(value); |
| | | if (!Number.isFinite(numberValue)) { |
| | | return 0; |
| | | } |
| | | return Number(numberValue.toFixed(4)); |
| | | }; |
| | | |
| | | const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => { |
| | | items.forEach(item => { |
| | | if (parentDemandedQuantity !== null) { |
| | | item.demandedQuantity = toQuantityNumber( |
| | | parentDemandedQuantity * toQuantityNumber(item.unitQuantity) |
| | | ); |
| | | } |
| | | |
| | | if (Array.isArray(item.children) && item.children.length > 0) { |
| | | syncDemandedQuantityTree( |
| | | item.children, |
| | | toQuantityNumber(item.demandedQuantity) |
| | | ); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const recalculateDemandedQuantities = () => { |
| | | if (pageType.value !== "order") { |
| | | return; |
| | | } |
| | | |
| | | const rootDemandedQuantity = routeInfo.value.quantity; |
| | | if ( |
| | | rootDemandedQuantity === undefined || |
| | | rootDemandedQuantity === null || |
| | | rootDemandedQuantity === "" |
| | | ) { |
| | | syncDemandedQuantityTree(bomDataValue.value.dataList); |
| | | return; |
| | | } |
| | | |
| | | syncDemandedQuantityTree( |
| | | bomDataValue.value.dataList, |
| | | toQuantityNumber(rootDemandedQuantity) |
| | | ); |
| | | }; |
| | | |
| | | const processChange = value => { |
| | |
| | | }); |
| | | }; |
| | | |
| | | const findSiblings = (items, tempId) => { |
| | | if (!items || items.length === 0) return null; |
| | | // 检查当前层级 |
| | | if (items.some(item => item.tempId === tempId)) { |
| | | return items; |
| | | } |
| | | // 递归查找子级 |
| | | for (const item of items) { |
| | | if (item.children && item.children.length > 0) { |
| | | const result = findSiblings(item.children, tempId); |
| | | if (result) return result; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const handleBomProcessChange = (row, value) => { |
| | | row.processId = value || ""; |
| | | syncProcessOperationFields(row); |
| | | |
| | | // 检查同一层级是否已经有其他不同的工序被选中 |
| | | const siblings = findSiblings(bomDataValue.value.dataList, row.tempId); |
| | | if (siblings && value) { |
| | | const hasDifferentProcess = siblings.some(sibling => { |
| | | return ( |
| | | sibling.tempId !== row.tempId && |
| | | sibling.processId && |
| | | sibling.processId !== value |
| | | ); |
| | | }); |
| | | if (hasDifferentProcess) { |
| | | ElMessage.warning("同一层级已存在不同的工序,请先统一工序后再进行修改"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const openBomDialog = tempId => { |
| | |
| | | routeInfo.value.bomId |
| | | ); |
| | | bomDataValue.value.dataList = data || []; |
| | | normalizeTreeData(bomDataValue.value.dataList); |
| | | recalculateDemandedQuantities(); |
| | | normalizeBomData(bomDataValue.value.dataList); |
| | | } catch (err) { |
| | | console.error("获取BOM数据失败:", err); |
| | | } |
| | | }; |
| | | |
| | | const childItem = (item, tempId, productData) => { |
| | | if (item.tempId === tempId) { |
| | | item.productName = productData.productName; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (childItem(child, tempId, productData)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | const handleBomProduct = row => { |
| | |
| | | return; |
| | | } |
| | | const productData = row[row.length - 1]; |
| | | |
| | | const isTopLevel = bomDataValue.value.dataList.some( |
| | | const currentItem = bomDataValue.value.dataList.find( |
| | | item => item.tempId === bomDataValue.value.currentRowName |
| | | ); |
| | | if (isTopLevel) { |
| | | if ( |
| | | productData.productName === bomTableData.value[0].productName && |
| | | productData.model === bomTableData.value[0].model |
| | | ) { |
| | | const hasOther = bomDataValue.value.dataList.some( |
| | | item => |
| | | item.tempId !== bomDataValue.value.currentRowName && |
| | | item.productName === bomTableData.value[0].productName && |
| | | item.model === bomTableData.value[0].model |
| | | ); |
| | | if (hasOther) { |
| | | ElMessage.warning("最外层和当前产品一样的一级只能有一个"); |
| | | return; |
| | | } |
| | | } |
| | | if (currentItem) { |
| | | currentItem.productName = productData.productName; |
| | | currentItem.model = productData.model; |
| | | currentItem.productModelId = productData.id; |
| | | } |
| | | bomDataValue.value.dataList.forEach(item => { |
| | | if (item.tempId === bomDataValue.value.currentRowName) { |
| | | item.productName = productData.productName; |
| | | item.model = productData.model; |
| | | item.productModelId = productData.id; |
| | | item.unit = productData.unit || ""; |
| | | return; |
| | | } |
| | | childItem(item, bomDataValue.value.currentRowName, productData); |
| | | }); |
| | | bomDataValue.value.showProductDialog = false; |
| | | }; |
| | | |
| | | const removeBomItem = tempId => { |
| | | const topIndex = bomDataValue.value.dataList.findIndex( |
| | | const index = bomDataValue.value.dataList.findIndex( |
| | | item => item.tempId === tempId |
| | | ); |
| | | if (topIndex !== -1) { |
| | | bomDataValue.value.dataList.splice(topIndex, 1); |
| | | return; |
| | | if (index !== -1) { |
| | | bomDataValue.value.dataList.splice(index, 1); |
| | | } |
| | | |
| | | const delchildItem = (items, tempId) => { |
| | | for (let i = 0; i < items.length; i++) { |
| | | const item = items[i]; |
| | | if (item.tempId === tempId) { |
| | | items.splice(i, 1); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | if (delchildItem(item.children, tempId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | bomDataValue.value.dataList.forEach(item => { |
| | | if (item.children && item.children.length > 0) { |
| | | delchildItem(item.children, tempId); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleUnitQuantityChange = () => { |
| | | recalculateDemandedQuantities(); |
| | | }; |
| | | |
| | | const addchildItem = (item, tempId) => { |
| | | if (item.tempId === tempId) { |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | item.children.push({ |
| | | parentId: item.id || "", |
| | | parentTempId: item.tempId || "", |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | [pageType.value === "order" ? "technologyOperationId" : "operationId"]: |
| | | "", |
| | | operationName: "", |
| | | unitQuantity: 1, |
| | | demandedQuantity: 0, |
| | | children: [], |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | | for (let child of item.children) { |
| | | if (addchildItem(child, tempId)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | const addBomItem = tempId => { |
| | | bomDataValue.value.dataList.forEach(item => { |
| | | if (item.tempId === tempId) { |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | item.children.push({ |
| | | parentId: item.id || "", |
| | | parentTempId: item.tempId || "", |
| | | productName: "", |
| | | productId: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | [pageType.value === "order" ? "technologyOperationId" : "operationId"]: |
| | | "", |
| | | operationName: "", |
| | | unitQuantity: 1, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | children: [], |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return; |
| | | } |
| | | addchildItem(item, tempId); |
| | | const addBomItem = () => { |
| | | bomDataValue.value.dataList.push({ |
| | | productName: "", |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | technologyOperationId: "", |
| | | operationName: "", |
| | | tempId: `${Date.now()}_${Math.random()}`, |
| | | }); |
| | | }; |
| | | |
| | | const validateAllBom = () => { |
| | | let isValid = true; |
| | | const isOrderPage = pageType.value === "order"; |
| | | |
| | | const validateItem = (item, isTopLevel = false) => { |
| | | if (!item.model) { |
| | | ElMessage.error("请选择规格"); |
| | | isValid = false; |
| | | return; |
| | | for (const item of bomDataValue.value.dataList) { |
| | | if (!item.productModelId) { |
| | | ElMessage.error("请选择物料规格"); |
| | | return false; |
| | | } |
| | | if (!isTopLevel && !item.processId) { |
| | | if (!item.processId) { |
| | | ElMessage.error("请选择消耗工序"); |
| | | isValid = false; |
| | | return; |
| | | return false; |
| | | } |
| | | if (!item.unitQuantity) { |
| | | ElMessage.error("请输入单位产出所需数量"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | if (isOrderPage && !item.demandedQuantity) { |
| | | ElMessage.error("请输入需求总量"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(child => { |
| | | validateItem(child, false); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 校验同一层级的工序是否一致 |
| | | const validateProcessConsistency = items => { |
| | | if (!items || items.length === 0) return; |
| | | |
| | | // 检查当前层级 |
| | | const processes = items |
| | | .filter(item => item.processId) |
| | | .map(item => item.processId); |
| | | if (processes.length > 1) { |
| | | const uniqueProcesses = [...new Set(processes)]; |
| | | if (uniqueProcesses.length > 1) { |
| | | ElMessage.error("同一层级的工序必须一致"); |
| | | isValid = false; |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 递归检查子级 |
| | | items.forEach(item => { |
| | | if (item.children && item.children.length > 0) { |
| | | validateProcessConsistency(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | bomDataValue.value.dataList.forEach(item => { |
| | | validateItem(item, true); |
| | | }); |
| | | |
| | | validateProcessConsistency(bomDataValue.value.dataList); |
| | | |
| | | return isValid; |
| | | }; |
| | | |
| | | const buildSubmitTree = items => { |
| | | return items.map(item => { |
| | | const current = { ...item }; |
| | | syncProcessOperationFields(current); |
| | | current.children = Array.isArray(current.children) |
| | | ? buildSubmitTree(current.children) |
| | | : []; |
| | | return current; |
| | | }); |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | const cancelEditBom = () => { |
| | |
| | | |
| | | const handleSaveBom = () => { |
| | | bomDataValue.value.loading = true; |
| | | console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList"); |
| | | |
| | | normalizeTreeData(bomDataValue.value.dataList); |
| | | recalculateDemandedQuantities(); |
| | | normalizeBomData(bomDataValue.value.dataList); |
| | | |
| | | const valid = validateAllBom(); |
| | | if (valid) { |
| | | add2({ |
| | | // bomId: Number(routeInfo.value.bomId), |
| | | productionOrderBomId: Number(routeInfo.value.bomId) || null, |
| | | children: buildSubmitTree(bomDataValue.value.dataList || []), |
| | | productionOrderId: Number(orderId.value) || null, |
| | | detailList: bomDataValue.value.dataList.map(item => ({ |
| | | id: item.id || undefined, |
| | | productModelId: item.productModelId, |
| | | technologyOperationId: item.technologyOperationId || item.processId, |
| | | })), |
| | | }) |
| | | .then(() => { |
| | | ElMessage.success("BOM保存成功"); |