Browse Source

2026-09-30

销售报价明细增加“保税料”、“BOM税率”和“保税”
master
fengyuan_yang 1 month ago
parent
commit
70261dd9e5
  1. 4
      src/api/quote/quoteDetailBom.js
  2. 61
      src/views/modules/quote/detail/primary/quoteDetailBom.vue
  3. 15
      src/views/modules/quote/detail/primary/quoteDetailCost.vue
  4. 144
      src/views/modules/quote/detail/quoteDetail.vue

4
src/api/quote/quoteDetailBom.js

@ -2,4 +2,8 @@ import {createAPI} from "../../utils/httpRequest";
export const queryQuoteDetailBom = (data) => createAPI("/quote/detail/bom", "post", data); export const queryQuoteDetailBom = (data) => createAPI("/quote/detail/bom", "post", data);
export const listQuoteDetailBomBonded = (data) => createAPI("/quote/detail/bom/listBonded", "post", data);
export const saveQuoteDetailBomBonded = (data) => createAPI("/quote/detail/bom/saveBonded", "post", data);
export const checkZeroUnitPrice = (data) => createAPI("/quote/detail/bom/checkZeroPrice", "post", data); export const checkZeroUnitPrice = (data) => createAPI("/quote/detail/bom/checkZeroPrice", "post", data);

61
src/views/modules/quote/detail/primary/quoteDetailBom.vue

@ -14,6 +14,10 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
bondedDraft: {
type: Object,
default: null
}
}, },
model: { model: {
prop: "quoteDetail", prop: "quoteDetail",
@ -94,6 +98,23 @@ export default {
status: true, status: true,
fixed: '', fixed: '',
columnWidth: 160, columnWidth: 160,
}, {
userId: this.$store.state.user.name,
functionId: 5011,
serialNumber: '5011Table4BondedFlag',
tableId: "5011Table4",
tableName: "报价材料信息",
columnProp: 'bondedFlag',
headerAlign: "center",
align: "center",
columnLabel: '保税',
columnHidden: false,
columnImage: false,
columnSortable: false,
sortLv: 0,
status: true,
fixed: '',
columnWidth: 70,
}, { }, {
userId: this.$store.state.user.name, userId: this.$store.state.user.name,
functionId: 5011, functionId: 5011,
@ -389,7 +410,7 @@ export default {
this.queryLoading = true; this.queryLoading = true;
queryQuoteDetailBom(params).then(({data}) => { queryQuoteDetailBom(params).then(({data}) => {
if (data && data.code === 0) { if (data && data.code === 0) {
this.dataList = data.rows;
this.dataList = this.mergeBondedDraft(data.rows);
} else { } else {
this.$message.warning(data.msg); this.$message.warning(data.msg);
} }
@ -527,7 +548,8 @@ export default {
quoteDetailItemNo: this.quoteDetail.itemNo, quoteDetailItemNo: this.quoteDetail.itemNo,
versionNo:this.quoteDetail.versionNo, versionNo:this.quoteDetail.versionNo,
recalculateCost: this.recalculateCost, // 是否重新计算成本 recalculateCost: this.recalculateCost, // 是否重新计算成本
bondedFlag: this.quoteDetail.bondedFlag,
bondedTaxRate: this.quoteDetail.bondedTaxRate,
} }
if (node) { if (node) {
params.id = node.id params.id = node.id
@ -542,6 +564,7 @@ export default {
this.$message.success(data.msg); this.$message.success(data.msg);
this.versionVisible = false; this.versionVisible = false;
this.recalculateCost = false; // 重置勾选状态 this.recalculateCost = false; // 重置勾选状态
this.$emit('bonded-draft-reset')
this.handleQueryQuoteDetailBomTree(); this.handleQueryQuoteDetailBomTree();
} else { } else {
this.$message.warning(data.msg); this.$message.warning(data.msg);
@ -619,6 +642,30 @@ export default {
}, },
finishDownload() { finishDownload() {
this.exportLoading = false this.exportLoading = false
},
bondedDisabled() {
return this.quoteDetail.showModalFlag
|| this.quoteDetail.status === '已完成'
|| this.quoteDetail.bondedFlag !== 'Y'
},
mergeBondedDraft(rows) {
if (!this.bondedDraft || !rows) {
return rows || []
}
return rows.map(row => {
const flag = this.bondedDraft[row.id]
if (flag == null) {
return row
}
return {
...row,
bondedFlag: flag
}
})
},
handleBondedLineChange(row, checked) {
this.$set(row, 'bondedFlag', checked ? 'Y' : 'N')
this.$emit('bonded-change', row, checked)
} }
}, },
created() { created() {
@ -632,6 +679,9 @@ export default {
}, },
isAllBom(newVal, oldVal) { isAllBom(newVal, oldVal) {
this.handleQueryQuoteDetailBom(); this.handleQueryQuoteDetailBom();
},
bondedDraft() {
this.dataList = this.mergeBondedDraft(this.dataList)
} }
} }
} }
@ -689,6 +739,13 @@ export default {
<template v-if="item.columnProp === 'partNo'"> <template v-if="item.columnProp === 'partNo'">
<el-link @click="handleJumpToBom(scope.row)">{{ scope.row[item.columnProp] }}</el-link> <el-link @click="handleJumpToBom(scope.row)">{{ scope.row[item.columnProp] }}</el-link>
</template> </template>
<template v-else-if="item.columnProp === 'bondedFlag'">
<el-checkbox
:value="scope.row.bondedFlag === 'Y'"
:disabled="bondedDisabled()"
@change="handleBondedLineChange(scope.row, $event)">
</el-checkbox>
</template>
<template v-else-if="item.columnProp === 'calcUnitCost'"> <template v-else-if="item.columnProp === 'calcUnitCost'">
<span>{{ getCalcUnitCost(scope.row) }}</span> <span>{{ getCalcUnitCost(scope.row) }}</span>
</template> </template>

15
src/views/modules/quote/detail/primary/quoteDetailCost.vue

@ -11,6 +11,10 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
bondedDraft: {
type: Object,
default: null
}
}, },
model: { model: {
prop: "quoteDetail", prop: "quoteDetail",
@ -167,8 +171,16 @@ export default {
this.computeCurrencyTotalCost(); this.computeCurrencyTotalCost();
}, },
handleQueryAllCost(){ handleQueryAllCost(){
let bondedLines = null
if (this.bondedDraft) {
bondedLines = Object.keys(this.bondedDraft).map(id => ({
id: Number(id),
bondedFlag: this.bondedDraft[id]
}))
}
let params = { let params = {
...this.quoteDetail
...this.quoteDetail,
bondedLines
} }
this.computeLoading = true this.computeLoading = true
queryQuoteDetailAllCost(params).then(({data})=>{ queryQuoteDetailAllCost(params).then(({data})=>{
@ -194,6 +206,7 @@ export default {
this.quoteDetail.otherCost = data.row.otherCost; this.quoteDetail.otherCost = data.row.otherCost;
this.computedTotalPartCost();// 重新计算成本 this.computedTotalPartCost();// 重新计算成本
this.$emit('calculated')
}else { }else {
this.$message.warning(data.msg); this.$message.warning(data.msg);
} }

144
src/views/modules/quote/detail/quoteDetail.vue

@ -13,6 +13,7 @@ import {getProjectPartList} from "../../../../api/project/project";
import QuoteDetailCost from "./primary/quoteDetailCost.vue"; import QuoteDetailCost from "./primary/quoteDetailCost.vue";
import QuoteDetailTool from "./primary/quoteDetailTool.vue"; import QuoteDetailTool from "./primary/quoteDetailTool.vue";
import QuoteDetailBom from "./primary/quoteDetailBom.vue"; import QuoteDetailBom from "./primary/quoteDetailBom.vue";
import {listQuoteDetailBomBonded, saveQuoteDetailBomBonded} from "../../../../api/quote/quoteDetailBom";
import QuoteDetailRouting from "./primary/quoteDetailRouting.vue"; import QuoteDetailRouting from "./primary/quoteDetailRouting.vue";
import QuoteDetailOtherCost from "./primary/quoteDetailOtherCost.vue"; import QuoteDetailOtherCost from "./primary/quoteDetailOtherCost.vue";
import DictDataSelect from "../../sys/dict-data-select.vue"; import DictDataSelect from "../../sys/dict-data-select.vue";
@ -130,6 +131,10 @@ export default {
saveVisible:false, saveVisible:false,
showModalFlag:false, showModalFlag:false,
showFlagChecked:false, showFlagChecked:false,
bondedChecked:false,
bomBondedDraft: null,
bondedManualOverride: {},
bondedLoadSeq: 0,
saveQuoteDetailRules:{ saveQuoteDetailRules:{
partNo: [{required: true, message: '请输入物料编码', trigger: ['blur','change']}], partNo: [{required: true, message: '请输入物料编码', trigger: ['blur','change']}],
partDesc: [{required: true, message: '请输入物料名称', trigger: ['blur','change']}], partDesc: [{required: true, message: '请输入物料名称', trigger: ['blur','change']}],
@ -793,6 +798,12 @@ export default {
} }
// 设置勾选状态 // 设置勾选状态
this.showFlagChecked = row.showFlag === 'Y' this.showFlagChecked = row.showFlag === 'Y'
this.bomBondedDraft = null
this.bondedManualOverride = {}
this.bondedChecked = row.bondedFlag === 'Y'
if (row.bondedFlag !== 'Y') {
this.$set(this.saveQuoteDetail, 'bondedTaxRate', 0)
}
}else { }else {
this.saveQuoteDetail = { this.saveQuoteDetail = {
...this.quoteDetail, ...this.quoteDetail,
@ -801,6 +812,8 @@ export default {
active:'Y', active:'Y',
qty:1, qty:1,
isDetail:false, isDetail:false,
bondedFlag:'N',
bondedTaxRate:0,
} }
if (this.quoteGroupDetail){ if (this.quoteGroupDetail){
this.saveQuoteDetail.partNo = this.quoteGroupDetail.partNo this.saveQuoteDetail.partNo = this.quoteGroupDetail.partNo
@ -809,8 +822,11 @@ export default {
this.$nextTick(()=>{ this.$nextTick(()=>{
this.saveQuoteDetail.projectNo = this.quote.projectNo this.saveQuoteDetail.projectNo = this.quote.projectNo
}) })
// 新建时默认勾选
// 新建时默认勾选询价展示,保税料默认不勾选
this.showFlagChecked = true this.showFlagChecked = true
this.bomBondedDraft = null
this.bondedManualOverride = {}
this.bondedChecked = false
} }
this.saveQuoteDetail.showModalFlag = false this.saveQuoteDetail.showModalFlag = false
this.activeName = 'bom'; this.activeName = 'bom';
@ -824,6 +840,12 @@ export default {
this.saveQuoteDetail.showModalFlag = true this.saveQuoteDetail.showModalFlag = true
// 设置勾选状态 // 设置勾选状态
this.showFlagChecked = row.showFlag === 'Y' this.showFlagChecked = row.showFlag === 'Y'
this.bomBondedDraft = null
this.bondedManualOverride = {}
this.bondedChecked = row.bondedFlag === 'Y'
if (row.bondedFlag !== 'Y') {
this.$set(this.saveQuoteDetail, 'bondedTaxRate', 0)
}
this.activeName = 'bom'; this.activeName = 'bom';
this.saveVisible = true; this.saveVisible = true;
this.showModalFlag = true; this.showModalFlag = true;
@ -884,6 +906,93 @@ export default {
this.showFlagChecked = !val this.showFlagChecked = !val
}) })
}, },
isBondedCandidate(row) {
const partNo = row.componentPart || ''
return row.status === 'Y'
&& partNo.indexOf('DF') >= 0
&& (row.partType === 'Purchased' || row.partType === 'Purchased (raw)')
},
handleBondedChange(val) {
this.bondedChecked = val
this.bondedManualOverride = {}
this.$set(this.saveQuoteDetail, 'bondedFlag', val ? 'Y' : 'N')
this.$set(this.saveQuoteDetail, 'bondedTaxRate', val ? 13 : 0)
if (!this.saveQuoteDetail.id) {
return
}
const requestSeq = ++this.bondedLoadSeq
listQuoteDetailBomBonded({quoteDetailId: this.saveQuoteDetail.id}).then(({data}) => {
if (requestSeq !== this.bondedLoadSeq) {
return
}
if (!(data && data.code === 0)) {
this.$message.warning((data && data.msg) || '读取材料失败')
return
}
const draft = {}
;(data.rows || []).forEach(row => {
const manualFlag = this.bondedManualOverride[row.id]
if (manualFlag) {
draft[row.id] = manualFlag
} else {
draft[row.id] = val && this.isBondedCandidate(row) ? 'Y' : 'N'
}
})
this.bomBondedDraft = draft
}).catch((error) => {
this.$message.error(error)
})
},
handleBomBondedChange(row, checked) {
const flag = checked ? 'Y' : 'N'
if (!this.bondedManualOverride) {
this.bondedManualOverride = {}
}
this.$set(this.bondedManualOverride, row.id, flag)
if (!this.bomBondedDraft) {
this.bomBondedDraft = {}
}
this.$set(this.bomBondedDraft, row.id, flag)
},
syncOpenDetailToList() {
if (!this.saveQuoteDetail || !this.saveQuoteDetail.id || !this.dataList) {
return
}
const index = this.dataList.findIndex(item => item.id === this.saveQuoteDetail.id)
if (index < 0) {
return
}
this.$set(this.dataList, index, {
...this.dataList[index],
bondedFlag: this.saveQuoteDetail.bondedFlag,
bondedTaxRate: this.saveQuoteDetail.bondedTaxRate
})
},
handleCostSaved() {
this.syncOpenDetailToList()
this.handleQueryQuoteDetail()
},
handleDetailDialogClose() {
this.handleQueryQuoteDetail()
},
saveBomBondedDraft() {
if (!this.bomBondedDraft || !this.saveQuoteDetail.id) {
return Promise.resolve()
}
const lines = Object.keys(this.bomBondedDraft).map(id => ({
id: Number(id),
bondedFlag: this.bomBondedDraft[id]
}))
if (!lines.length) {
return Promise.resolve()
}
return saveQuoteDetailBomBonded(lines).then(({data}) => {
if (!(data && data.code === 0)) {
return Promise.reject((data && data.msg) || '保税保存失败')
}
this.bomBondedDraft = null
})
},
handleDeleteQuoteDetail(row){ handleDeleteQuoteDetail(row){
if (!this.canOperateQuoteDetail()) { if (!this.canOperateQuoteDetail()) {
this.$message.warning('审批中状态下仅当前审批人可删除') this.$message.warning('审批中状态下仅当前审批人可删除')
@ -1023,6 +1132,7 @@ export default {
...data.row, ...data.row,
} }
this.showFlagChecked = this.saveQuoteDetail.showFlag === 'Y' this.showFlagChecked = this.saveQuoteDetail.showFlag === 'Y'
this.bondedChecked = this.saveQuoteDetail.bondedFlag === 'Y'
}else { }else {
this.saveVisible = false; this.saveVisible = false;
} }
@ -1045,9 +1155,13 @@ export default {
} }
updateQuoteDetail(params).then(({data})=>{ updateQuoteDetail(params).then(({data})=>{
if (data && data.code === 0){ if (data && data.code === 0){
this.$message.success(data.msg);
this.handleQueryQuoteDetail();
this.saveVisible = false;
this.saveBomBondedDraft().then(() => {
this.$message.success(data.msg);
this.handleQueryQuoteDetail();
this.saveVisible = false;
}).catch((error) => {
this.$message.error(error);
})
}else { }else {
this.$message.warning(data.msg); this.$message.warning(data.msg);
} }
@ -1352,7 +1466,7 @@ export default {
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog :title="`报价明细`" top="10vh" v-drag :visible.sync="saveVisible" append-to-body :width="`${saveQuoteDetail.id?1200:600}px`" :close-on-click-modal="false">
<el-dialog :title="`报价明细`" top="10vh" v-drag :visible.sync="saveVisible" append-to-body :width="`${saveQuoteDetail.id?1200:600}px`" :close-on-click-modal="false" @close="handleDetailDialogClose">
<el-form :model="saveQuoteDetail" ref="saveQuoteDetailForm" :rules="saveQuoteDetailRules" label-position="top" v-if="!saveQuoteDetail.id"> <el-form :model="saveQuoteDetail" ref="saveQuoteDetailForm" :rules="saveQuoteDetailRules" label-position="top" v-if="!saveQuoteDetail.id">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
@ -1428,7 +1542,21 @@ export default {
<el-form-item label=" " :show-message="false"> <el-form-item label=" " :show-message="false">
<el-button v-if="saveQuoteDetail.id && isAuth('5011:detail:dataEntry')" type="warning" @click="handleDataEntry">数据录入</el-button> </el-form-item> <el-button v-if="saveQuoteDetail.id && isAuth('5011:detail:dataEntry')" type="warning" @click="handleDataEntry">数据录入</el-button> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="18">
<el-form-item label=" " :show-message="false"></el-form-item>
</el-col>
<el-col :span="2">
<el-form-item label=" " :show-message="false">
<el-checkbox v-model="bondedChecked" :disabled="showModalFlag || saveQuoteDetail.status === '已完成'" @change="handleBondedChange">保税料</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item label="BOM税率" :show-message="false">
<el-input-number v-model="saveQuoteDetail.bondedTaxRate" :controls="false" :precision="2" :min="0" :step="0.01" :disabled="showModalFlag || saveQuoteDetail.status === '已完成' || !bondedChecked" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24"> <el-col :span="24">
@ -1441,7 +1569,7 @@ export default {
<el-tabs v-model="activeName" v-if="saveQuoteDetail.id" @tab-click="handleClickTab"> <el-tabs v-model="activeName" v-if="saveQuoteDetail.id" @tab-click="handleClickTab">
<el-tab-pane label="材料" name="bom"> <el-tab-pane label="材料" name="bom">
<quote-detail-bom v-if="saveVisible && activeName === 'bom'" @close="handleClose" v-model:quoteDetail="saveQuoteDetail"></quote-detail-bom>
<quote-detail-bom v-if="saveVisible && activeName === 'bom'" @close="handleClose" @bonded-change="handleBomBondedChange" @bonded-draft-reset="bomBondedDraft = null" :bonded-draft="bomBondedDraft" v-model:quoteDetail="saveQuoteDetail"></quote-detail-bom>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="工艺" name="routing"> <el-tab-pane label="工艺" name="routing">
<quote-detail-routing ref="routing" v-if="saveVisible && activeName === 'routing'" @close="handleClose" v-model:quoteDetail="saveQuoteDetail"></quote-detail-routing> <quote-detail-routing ref="routing" v-if="saveVisible && activeName === 'routing'" @close="handleClose" v-model:quoteDetail="saveQuoteDetail"></quote-detail-routing>
@ -1459,7 +1587,7 @@ export default {
<quote-detail-other v-if="saveVisible && activeName === 'other'" v-model:quoteDetail="saveQuoteDetail"></quote-detail-other> <quote-detail-other v-if="saveVisible && activeName === 'other'" v-model:quoteDetail="saveQuoteDetail"></quote-detail-other>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="成本&价格" name="cost"> <el-tab-pane label="成本&价格" name="cost">
<quote-detail-cost v-if="saveVisible && activeName === 'cost'" ref="cost" v-model:quoteDetail="saveQuoteDetail"></quote-detail-cost>
<quote-detail-cost v-if="saveVisible && activeName === 'cost'" ref="cost" :bonded-draft="bomBondedDraft" @calculated="handleCostSaved" v-model:quoteDetail="saveQuoteDetail"></quote-detail-cost>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">

Loading…
Cancel
Save