From f8a2e76e4a7129489697b13e9cead2540c7d5963 Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Mon, 16 Mar 2026 12:58:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=B3=E8=AF=B7=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=9D=90=E6=96=99=E4=B8=AD=E6=B7=BB=E5=8A=A0BOM?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B0=86=E6=96=99?= =?UTF-8?q?=E5=8F=B7=E5=AF=B9=E5=BA=94=E7=9A=84BOM=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=BD=93=E5=89=8D=E6=B5=8B=E8=AF=95=E5=8D=95?= =?UTF-8?q?=20=20=20=20=20=20=201=E3=80=81bom=E5=AF=BC=E5=85=A5=E6=97=B6?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E9=80=89=E6=8B=A9=E5=AF=B9=E5=BA=94=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=BF=9B=E8=A1=8C=E5=AF=BC=E5=85=A5=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=201.1=20=20=E5=A6=82=E6=9E=9C=E6=98=AF?= =?UTF-8?q?=E6=AD=A3=E5=BC=8F=E6=96=99=E5=8F=B7=EF=BC=8C=E9=82=A3=E4=B9=88?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E8=B0=83=E7=94=A8=E6=8E=A5=E5=8F=A3=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=88=90=E6=9C=AC=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/test/testSoBom.js | 1 + .../modules/test/testSoBom/testTable.vue | 454 +++++++++++++++++- 2 files changed, 452 insertions(+), 3 deletions(-) diff --git a/src/api/test/testSoBom.js b/src/api/test/testSoBom.js index 6bebd85..b951a93 100644 --- a/src/api/test/testSoBom.js +++ b/src/api/test/testSoBom.js @@ -10,3 +10,4 @@ export const saveTestSoBom = (data) => createAPI(`/test/soBom/save`,'post',data) export const updateTestSoBom = (data) => createAPI(`/test/soBom/update`,'post',data) export const removeTestSoBom = (data) => createAPI(`/test/soBom/remove`,'post',data) export const removeBatchTestSoBom = (data) => createAPI(`/test/soBom/removeBatch`,'post',data) +export const saveBatchTestSoBom = (data) => createAPI(`/test/soBom/saveBatch`,'post',data) diff --git a/src/views/modules/test/testSoBom/testTable.vue b/src/views/modules/test/testSoBom/testTable.vue index 65e0fb0..2cf8aa1 100644 --- a/src/views/modules/test/testSoBom/testTable.vue +++ b/src/views/modules/test/testSoBom/testTable.vue @@ -4,7 +4,8 @@ import { saveTestSoBom, removeTestSoBom, updateTestSoBom, - removeBatchTestSoBom + removeBatchTestSoBom, + saveBatchTestSoBom } from "../../../../api/test/testSoBom"; import {updateMaterialTotalAmount} from "../../../../api/test/testInformation"; import {searchPart, searchPartList} from '@/api/part/partInformation.js'; @@ -12,6 +13,7 @@ import numberInput from "../../common/numberInput.vue"; import {searchAllUmInformationList} from "../../../../api/part/umInformation"; import {Decimal} from "decimal.js"; import {queryPart, onlyQueryPartUnitCostList} from "../../../../api/part/partInformation"; +import {bomManagementSearch, queryBomDetail, queryBomComponent} from "../../../../api/part/bomManagement"; export default { name: "testTable", components:{ @@ -55,6 +57,7 @@ export default { site:this.$store.state.user.site }, partDialogFlag:false, + _partSelectContext: 'sobom', // 'sobom' | 'bomImport' testSoBomLabel:{ componentPartNo: "物料编码", partDesc:"物料名称", @@ -391,7 +394,19 @@ export default { no:1, size:20, total:0, - queryLoading:false + queryLoading:false, + + // BOM导入相关 + bomImportDialogFlag: false, + bomImportLoading: false, + bomSearchPartNo: '', + bomHeaderList: [], + bomHeaderSelection: [], + bomAlternativeList: [], + bomAlternativeSelection: [], + bomComponentTableList: [], + bomComponentLoading: false, + bomSaveLoading: false, } }, created() { @@ -410,10 +425,17 @@ export default { this.partList = []; }, openPartDialog(){ + this._partSelectContext = 'sobom'; this.partDialogFlag = true; this.partData.partNo = this.testSoBom.componentPartNo this.initPartList(); }, + openPartDialogForBomImport(){ + this._partSelectContext = 'bomImport'; + this.partDialogFlag = true; + this.partData.partNo = ''; + this.initPartList(); + }, initPartList(){ let params = { ...this.partData, @@ -432,6 +454,14 @@ export default { }) }, dblClickPartTable(row){ + // BOM导入弹框中选料:填入料号并自动查询 + if (this._partSelectContext === 'bomImport') { + this.bomSearchPartNo = row.partNo; + this.partDialogFlag = false; + this._partSelectContext = 'sobom'; + this.$nextTick(() => this.queryBomHeaders()); + return; + } if (row.status === 'Y'){ let params = { site:row.site, @@ -673,6 +703,277 @@ export default { this.no = val this.initPartList() }, + // ============ BOM导入相关方法 ============ + openBomImportDialog() { + this.bomImportDialogFlag = true; + this.bomSearchPartNo = ''; + this.bomHeaderList = []; + this.bomHeaderSelection = []; + this.bomAlternativeList = []; + this.bomAlternativeSelection = []; + this.bomComponentTableList = []; + }, + closeBomImportDialog() { + this.bomImportDialogFlag = false; + }, + // Step1: 查询BOM版本列表 + async queryBomHeaders() { + if (!this.bomSearchPartNo || !this.bomSearchPartNo.trim()) { + this.$message.warning('请输入料号'); + return; + } + this.bomImportLoading = true; + try { + const { data } = await bomManagementSearch({ + plmPartNo: this.bomSearchPartNo.trim().toUpperCase(), + site: this.$store.state.user.site, + page: 1, + limit: 200 + }); + if (data && data.code === 0) { + this.bomHeaderList = (data.page ? data.page.list : []).filter( + item => !item.effPhaseOutDate + ); + this.bomHeaderSelection = []; + this.bomAlternativeList = []; + this.bomAlternativeSelection = []; + this.bomComponentTableList = []; + if (this.bomHeaderList.length === 0) { + this.$message.warning('未查询到BOM信息,请确认料号是否正确'); + } else if (this.bomHeaderList.length === 1) { + // 只有一条版本时,自动勾选并触发替代编码加载 + this.$nextTick(() => { + if (this.$refs.bomHeaderTable) { + this.$refs.bomHeaderTable.toggleRowSelection(this.bomHeaderList[0], true); + } + }); + } + } else { + this.$message.warning(data && data.msg ? data.msg : '查询失败'); + } + } catch (e) { + this.$message.error('查询BOM失败'); + } + this.bomImportLoading = false; + }, + // Step2: 加载所选版本的替代编码 + async loadBomAlternatives() { + if (this.bomHeaderSelection.length === 0) { + this.$message.warning('请先选择BOM版本'); + return; + } + this.bomImportLoading = true; + const alternativeMap = new Map(); + try { + for (const header of this.bomHeaderSelection) { + const { data } = await queryBomDetail({ + site: this.$store.state.user.site, + partNo: header.partNo, + engChgLevel: header.engChgLevel, + bomType: header.bomType + }); + if (data && data.code === 0 && data.rows && data.rows.detailList) { + data.rows.detailList.forEach(alt => { + const key = `${alt.partNo}-${alt.engChgLevel}-${alt.bomType}-${alt.alternativeNo}`; + if (!alternativeMap.has(key)) { + alternativeMap.set(key, alt); + } + }); + } + } + this.bomAlternativeList = [...alternativeMap.values()]; + this.bomAlternativeSelection = []; + this.bomComponentTableList = []; + // 只有一条替代编码时,自动勾选并触发子物料加载 + if (this.bomAlternativeList.length === 1) { + this.$nextTick(() => { + if (this.$refs.bomAlternativeTable) { + this.$refs.bomAlternativeTable.toggleRowSelection(this.bomAlternativeList[0], true); + } + }); + } + } catch (e) { + this.$message.error('加载替代编码失败'); + } + this.bomImportLoading = false; + }, + // Step3: 加载所选替代编码的子物料 + async loadBomComponents() { + if (this.bomAlternativeSelection.length === 0) { + this.$message.warning('请先选择替代编码'); + return; + } + this.bomComponentLoading = true; + const componentMap = new Map(); + try { + for (const alt of this.bomAlternativeSelection) { + const { data } = await queryBomComponent({ + site: this.$store.state.user.site, + partNo: alt.partNo, + engChgLevel: alt.engChgLevel, + bomType: alt.bomType, + alternativeNo: alt.alternativeNo + }); + if (data && data.code === 0 && data.rows && data.rows.subDetailList) { + data.rows.subDetailList.forEach(comp => { + if (!componentMap.has(comp.componentPart)) { + const qtyPerAssembly = Number(comp.qtyPerAssembly) || 0; + const preRequiredQty = this.testNumber + ? new Decimal(this.testNumber).mul(new Decimal(qtyPerAssembly)).toNumber() + : 0; + //const headerUnit = this.bomHeaderList.length > 0 ? (this.bomHeaderList[0].printUnit || '') : ''; + componentMap.set(comp.componentPart, { + componentPartNo: comp.componentPart, + partDesc: comp.componentPartDesc || '', + umId: comp.printUnit || '', + assemblyQty: qtyPerAssembly, + fixedScrapQty: Number(comp.componentScrap) || 0, + scrapFactor: Number(comp.shrinkageFactor) || 0, + requiredQty: '', + unitCost: '', + totalCost: 0, + remark: '', + _status: 'N', + _costLoading: false + }); + } + }); + } + } + this.bomComponentTableList = [...componentMap.values()]; + if (this.bomComponentTableList.length === 0) { + this.$message.warning('所选版本和替代编码下暂无子物料'); + } else { + await this.enrichBomComponentsCost(); + } + } catch (e) { + this.$message.error('加载子物料失败'); + } + this.bomComponentLoading = false; + }, + // 查询各子物料的状态和成本信息(正式料号自动获取成本) + async enrichBomComponentsCost() { + const promises = this.bomComponentTableList.map(async (comp) => { + try { + const { data } = await queryPart({ + site: this.$store.state.user.site, + partNo: comp.componentPartNo + }); + if (data && data.code === 0 && data.rows && data.rows.length > 0) { + const partInfo = data.rows[0]; + comp._status = partInfo.status || 'N'; + if (partInfo.status === 'Y') { + // 正式料号,自动获取成本 + const { data: costData } = await onlyQueryPartUnitCostList({ + site: this.$store.state.user.site, + partNo: comp.componentPartNo, + configurationId: partInfo.configurationId, + userName: this.$store.state.user.name + }); + if (costData && costData.code === 0 && costData.rows && costData.rows.length === 1) { + comp.unitCost = parseFloat(costData.rows[0].inventoryValue) || 0; + comp.totalCost = new Decimal(comp.requiredQty).mul(new Decimal(comp.unitCost)).toNumber(); + } + } + } + } catch (e) { + // 忽略单个物料查询错误 + } + }); + await Promise.all(promises); + this.$set(this, 'bomComponentTableList', [...this.bomComponentTableList]); + }, + // 需求数量变化时重新计算总价 + onBomComponentRequiredQtyChange(row) { + const requiredQty = Number(row.requiredQty) || 0; + if (this.testNumber && this.testNumber > 0) { + row.assemblyQty = new Decimal(requiredQty).div(new Decimal(this.testNumber)).toNumber(); + } + row.totalCost = new Decimal(requiredQty).mul(new Decimal(row.unitCost || 0)).toNumber(); + }, + // 单价变化时重新计算总价(仅非正式料号可手动输入) + onBomComponentUnitCostChange(row) { + row.totalCost = new Decimal(row.requiredQty || 0).mul(new Decimal(row.unitCost || 0)).toNumber(); + }, + // 删除预览行 + removeBomComponentRow(row) { + const idx = this.bomComponentTableList.indexOf(row); + if (idx > -1) this.bomComponentTableList.splice(idx, 1); + }, + // 确认保存BOM导入 + async saveBomImport() { + if (this.bomComponentTableList.length === 0) { + this.$message.warning('请先加载子物料'); + return; + } + const saveList = this.bomComponentTableList.map(comp => ({ + site: this.$store.state.user.site, + testNo: this.testNo, + componentPartNo: comp.componentPartNo, + assemblyQty: comp.assemblyQty || 0, + fixedScrapQty: comp.fixedScrapQty || 0, + scrapFactor: comp.scrapFactor || 0, + requiredQty: comp.requiredQty || 0, + reserveQty: 0, + rmTypeDb: 0, + unitCost: comp.unitCost || 0, + totalCost: comp.totalCost || 0, + remark: comp.remark || '' + })); + this.bomSaveLoading = true; + try { + const { data } = await saveBatchTestSoBom(saveList); + if (data && data.code === 0) { + this.$message.success('BOM导入成功'); + this.bomImportDialogFlag = false; + this.selectTestSoBom(); + } else { + this.$message.error(data && data.msg ? data.msg : '导入失败'); + } + } catch (e) { + this.$message.error('导入失败,请重试'); + } + this.bomSaveLoading = false; + }, + // 格式化小数显示,避免科学计数法 + formatDecimal(val) { + if (val === null || val === undefined || val === '') return '0'; + const num = Number(val); + if (isNaN(num) || num === 0) return '0'; + const str = num.toString(); + if (!str.includes('e') && !str.includes('E')) { + // 普通小数,去除多余的尾部零(最多保留10位) + return parseFloat(num.toFixed(10)).toString(); + } + // 科学计数法转为普通小数,最多20位有效数字后去尾零 + return num.toFixed(20).replace(/\.?0+$/, ''); + }, + // BOM版本勾选变化 → 防抖自动加载替代编码 + onBomHeaderSelectionChange(rows) { + this.bomHeaderSelection = rows; + this.bomAlternativeList = []; + this.bomAlternativeSelection = []; + this.bomComponentTableList = []; + if (this._headerSelectTimer) clearTimeout(this._headerSelectTimer); + if (rows.length > 0) { + this._headerSelectTimer = setTimeout(() => { + this.loadBomAlternatives(); + }, 400); + } + }, + // 替代编码勾选变化 → 防抖自动加载子物料 + onBomAlternativeSelectionChange(rows) { + this.bomAlternativeSelection = rows; + this.bomComponentTableList = []; + if (this._altSelectTimer) clearTimeout(this._altSelectTimer); + if (rows.length > 0) { + this._altSelectTimer = setTimeout(() => { + this.loadBomComponents(); + }, 400); + } + }, + // ============ BOM导入相关方法结束 ============ + // 更新测试主信息的材料总金额 updateTestMaterialTotalAmount(){ if (!this.testNo) { @@ -730,6 +1031,7 @@ export default { + BOM导入 + + + + + + 物料编码 + + + 查询 + + + + + + + +
+ 选择BOM版本 + (勾选后自动加载替代编码) + 加载中... +
+ + + + + + + +
+ + + +
+ 选择替代编码 + (勾选后自动加载子物料) + 加载中... +
+ + + + + + +
+
+ + +
+ 填写需求数量和备注后点击保存 + + (共 {{bomComponentTableList.length}} 条;正式料号成本自动获取不可修改;可删除不需要的行) + +
+ + + + + + + + + + + + + + + + + + + + + + + 保 存 + 取 消 + +
+ @@ -873,9 +1310,20 @@ export default { -