Browse Source

测试申请页面材料中添加BOM导入功能,将料号对应的BOM信息导入当前测试单

1、bom导入时需要选择对应版本进行导入
            1.1  如果是正式料号,那么需要调用接口获取成本信息
master
han\hanst 1 month ago
parent
commit
f8a2e76e4a
  1. 1
      src/api/test/testSoBom.js
  2. 454
      src/views/modules/test/testSoBom/testTable.vue

1
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 updateTestSoBom = (data) => createAPI(`/test/soBom/update`,'post',data)
export const removeTestSoBom = (data) => createAPI(`/test/soBom/remove`,'post',data) export const removeTestSoBom = (data) => createAPI(`/test/soBom/remove`,'post',data)
export const removeBatchTestSoBom = (data) => createAPI(`/test/soBom/removeBatch`,'post',data) export const removeBatchTestSoBom = (data) => createAPI(`/test/soBom/removeBatch`,'post',data)
export const saveBatchTestSoBom = (data) => createAPI(`/test/soBom/saveBatch`,'post',data)

454
src/views/modules/test/testSoBom/testTable.vue

@ -4,7 +4,8 @@ import {
saveTestSoBom, saveTestSoBom,
removeTestSoBom, removeTestSoBom,
updateTestSoBom, updateTestSoBom,
removeBatchTestSoBom
removeBatchTestSoBom,
saveBatchTestSoBom
} from "../../../../api/test/testSoBom"; } from "../../../../api/test/testSoBom";
import {updateMaterialTotalAmount} from "../../../../api/test/testInformation"; import {updateMaterialTotalAmount} from "../../../../api/test/testInformation";
import {searchPart, searchPartList} from '@/api/part/partInformation.js'; 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 {searchAllUmInformationList} from "../../../../api/part/umInformation";
import {Decimal} from "decimal.js"; import {Decimal} from "decimal.js";
import {queryPart, onlyQueryPartUnitCostList} from "../../../../api/part/partInformation"; import {queryPart, onlyQueryPartUnitCostList} from "../../../../api/part/partInformation";
import {bomManagementSearch, queryBomDetail, queryBomComponent} from "../../../../api/part/bomManagement";
export default { export default {
name: "testTable", name: "testTable",
components:{ components:{
@ -55,6 +57,7 @@ export default {
site:this.$store.state.user.site site:this.$store.state.user.site
}, },
partDialogFlag:false, partDialogFlag:false,
_partSelectContext: 'sobom', // 'sobom' | 'bomImport'
testSoBomLabel:{ testSoBomLabel:{
componentPartNo: "物料编码", componentPartNo: "物料编码",
partDesc:"物料名称", partDesc:"物料名称",
@ -391,7 +394,19 @@ export default {
no:1, no:1,
size:20, size:20,
total:0, total:0,
queryLoading:false
queryLoading:false,
// BOM
bomImportDialogFlag: false,
bomImportLoading: false,
bomSearchPartNo: '',
bomHeaderList: [],
bomHeaderSelection: [],
bomAlternativeList: [],
bomAlternativeSelection: [],
bomComponentTableList: [],
bomComponentLoading: false,
bomSaveLoading: false,
} }
}, },
created() { created() {
@ -410,10 +425,17 @@ export default {
this.partList = []; this.partList = [];
}, },
openPartDialog(){ openPartDialog(){
this._partSelectContext = 'sobom';
this.partDialogFlag = true; this.partDialogFlag = true;
this.partData.partNo = this.testSoBom.componentPartNo this.partData.partNo = this.testSoBom.componentPartNo
this.initPartList(); this.initPartList();
}, },
openPartDialogForBomImport(){
this._partSelectContext = 'bomImport';
this.partDialogFlag = true;
this.partData.partNo = '';
this.initPartList();
},
initPartList(){ initPartList(){
let params = { let params = {
...this.partData, ...this.partData,
@ -432,6 +454,14 @@ export default {
}) })
}, },
dblClickPartTable(row){ 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'){ if (row.status === 'Y'){
let params = { let params = {
site:row.site, site:row.site,
@ -673,6 +703,277 @@ export default {
this.no = val this.no = val
this.initPartList() 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(){ updateTestMaterialTotalAmount(){
if (!this.testNo) { if (!this.testNo) {
@ -730,6 +1031,7 @@ export default {
<template v-if="isAuth('107001:tab3:remove')"> <template v-if="isAuth('107001:tab3:remove')">
<el-button type="primary" v-if="!disabled" @click="removeBatchTestSoBom">删除</el-button> <el-button type="primary" v-if="!disabled" @click="removeBatchTestSoBom">删除</el-button>
</template> </template>
<el-button type="primary" v-if="!disabled" @click="openBomImportDialog">BOM导入</el-button>
</div> </div>
<el-table <el-table
:height="height" border :height="height" border
@ -821,6 +1123,141 @@ export default {
</span> </span>
</el-dialog> </el-dialog>
<!-- BOM导入弹框 -->
<el-dialog
title="BOM导入"
width="1200px"
append-to-body
:close-on-click-modal="false"
v-drag
:visible.sync="bomImportDialogFlag"
@close="closeBomImportDialog">
<!-- 第一步查询料号 -->
<el-row :gutter="10" style="margin-bottom: 10px;">
<el-col :span="24">
<span style="font-weight: bold; margin-right: 10px;" @click="openPartDialogForBomImport"><a>物料编码</a></span>
<el-input
v-model="bomSearchPartNo"
placeholder="请输入PLM物料编码"
style="width: 220px; margin-right: 8px;"
@keyup.enter.native="queryBomHeaders"
clearable>
</el-input>
<el-button type="primary" :loading="bomImportLoading" @click="queryBomHeaders">查询</el-button>
</el-col>
</el-row>
<!-- 第二步选择版本和替代编码 -->
<el-row :gutter="12" style="margin-bottom: 10px;">
<!-- BOM版本 -->
<el-col :span="12">
<div style="margin-bottom: 6px;">
<span style="font-weight: bold;">选择BOM版本</span>
<span style="color: #909399; font-size: 12px; margin-left: 6px;">勾选后自动加载替代编码</span>
<el-tag v-if="bomImportLoading" size="mini" type="info" style="margin-left: 8px;">加载中...</el-tag>
</div>
<el-table
:data="bomHeaderList"
border
height="180px"
ref="bomHeaderTable"
@selection-change="onBomHeaderSelectionChange"
style="width: 100%;">
<el-table-column type="selection" width="45" align="center"></el-table-column>
<el-table-column label="BOM版本" prop="engChgLevel" align="center" width="80"></el-table-column>
<el-table-column label="制造类型" prop="bomType" align="center" min-width="110"></el-table-column>
<el-table-column label="生效日期" prop="effPhaseInDate" align="center" min-width="100"></el-table-column>
<el-table-column label="失效日期" prop="effPhaseOutDate" align="center" min-width="100"></el-table-column>
</el-table>
</el-col>
<!-- 替代编码 -->
<el-col :span="12">
<div style="margin-bottom: 6px;">
<span style="font-weight: bold;">选择替代编码</span>
<span style="color: #909399; font-size: 12px; margin-left: 6px;">勾选后自动加载子物料</span>
<el-tag v-if="bomComponentLoading" size="mini" type="info" style="margin-left: 8px;">加载中...</el-tag>
</div>
<el-table
:data="bomAlternativeList"
border
height="180px"
ref="bomAlternativeTable"
@selection-change="onBomAlternativeSelectionChange"
style="width: 100%;">
<el-table-column type="selection" width="45" align="center"></el-table-column>
<el-table-column label="替代编码" prop="alternativeNo" align="center" width="90"></el-table-column>
<el-table-column label="替代名称" prop="alternativeDescription" align="left" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column label="状态" prop="status" align="center" width="100"></el-table-column>
</el-table>
</el-col>
</el-row>
<!-- 第四步子物料清单可编辑 -->
<div style="margin-bottom: 6px;">
<span style="font-weight: bold;">填写需求数量和备注后点击保存</span>
<span style="color: #909399; font-size: 12px; margin-left: 10px;">
{{bomComponentTableList.length}} 正式料号成本自动获取不可修改可删除不需要的行
</span>
</div>
<el-table
:data="bomComponentTableList"
border
height="280px"
v-loading="bomComponentLoading"
class="bom-import-table"
style="width: 100%;">
<el-table-column label="物料编码" prop="componentPartNo" align="left" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column label="物料名称" prop="partDesc" align="left" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="单位" prop="umId" align="center" width="60"></el-table-column>
<el-table-column label="需求数量*" align="center" width="155">
<template slot-scope="{row}">
<el-input
v-model="row.requiredQty"
style="width: 140px;"
@change="onBomComponentRequiredQtyChange(row)">
</el-input>
</template>
</el-table-column>
<el-table-column label="单价" align="center" width="140">
<template slot-scope="{row}">
<el-input
v-model="row.unitCost"
:disabled="row._status === 'Y' && row.unitCost !== ''"
:formatter="val => formatDecimal(val)"
:parser="val => (val === '' || val === undefined) ? 0 : Number(val)"
style="width: 125px;"
@change="onBomComponentUnitCostChange(row)">
</el-input>
</template>
</el-table-column>
<el-table-column label="总价" align="right" width="110">
<template slot-scope="{row}">
<span :title="String(row.totalCost)">{{formatDecimal(row.totalCost)}}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="left" min-width="130">
<template slot-scope="{row}">
<el-input v-model="row.remark" placeholder="备注" size="mini"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="60" fixed="right">
<template slot-scope="{row}">
<a style="color: #F56C6C;" @click="removeBomComponentRow(row)">删除</a>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button
type="primary"
:loading="bomSaveLoading"
:disabled="bomComponentTableList.length === 0"
@click="saveBomImport"> </el-button>
<el-button @click="bomImportDialogFlag = false"> </el-button>
</span>
</el-dialog>
<el-dialog title="物料信息" width="800px" append-to-body :close-on-click-modal="false" v-drag :visible.sync="partDialogFlag"> <el-dialog title="物料信息" width="800px" append-to-body :close-on-click-modal="false" v-drag :visible.sync="partDialogFlag">
<!--搜索条件--> <!--搜索条件-->
<el-form :model="partData" ref="partDataForm" style="width: 600px;" label-position="top"> <el-form :model="partData" ref="partDataForm" style="width: 600px;" label-position="top">
@ -873,9 +1310,20 @@ export default {
</div> </div>
</template> </template>
<style scoped>
<style lang="scss">
.el-input-number /deep/ .el-input__inner{ .el-input-number /deep/ .el-input__inner{
text-align: right; text-align: right;
padding-right: 5px !important; padding-right: 5px !important;
} }
.bom-import-table .el-input-number /deep/ .el-input__inner {
text-align: right;
padding-left: 4px !important;
padding-right: 4px !important;
}
.bom-import-table .cell {
line-height: 24px;
font-size: 12px;
height: 24px;
}
</style> </style>
Loading…
Cancel
Save