合同流程 前端

master
Harry Yang 2022-12-30 18:07:03 +08:00
parent 6cf9459452
commit dd0c111ebf
2 changed files with 325 additions and 158 deletions

View File

@ -204,20 +204,20 @@
<div>
<el-form-item v-if="isSalesContractMode" label="收款条件" prop="paymentTerms"
:rules="[{ required: true, message: '收款条件不能为空'}]">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" cols="90" maxlength="5000" show-word-limit
<el-input type="textarea" :autosize="{ minRows: 5, maxRows: 10}" cols="90" maxlength="5000" show-word-limit
v-model="processForm.paymentTerms" placeholder="请输入收款条件限制5000字"></el-input>
</el-form-item>
<el-form-item v-if="isProcurementContractMode" label="付款条件" prop="paymentTerms"
:rules="[{ required: true, message: '付款条件不能为空'}]">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" cols="90" maxlength="5000" show-word-limit
<el-input type="textarea" :autosize="{ minRows: 5, maxRows: 10}" cols="90" maxlength="5000" show-word-limit
v-model="processForm.paymentTerms" placeholder="请输入付款条件限制5000字"></el-input>
</el-form-item>
</div>
<div>
<el-form-item label="备注">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" maxlength="5000" show-word-limit
<el-input type="textarea" :autosize="{ minRows: 5, maxRows: 10}" maxlength="5000" show-word-limit
v-model="processForm.remark" placeholder="请输入备注限制5000字" cols="90"></el-input>
</el-form-item>
</div>
@ -261,7 +261,7 @@
<el-table-column prop="totalAmount" label="合计金额" width="160" align="center">
<template slot-scope="scope">
<el-input-number size="mini" :precision="2" :step="0.1" :max="100" :min="0"
v-model="scope.row.totalAmount"></el-input-number>
v-model="scope.row.totalAmount" controls-position="right"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="serviceTerms" label="服务条款" width="180" align="center">
@ -277,7 +277,7 @@
<el-table-column prop="taxRate" label="税率(%" width="160" align="center">
<template slot-scope="scope">
<el-input-number size="mini" :precision="2" :step="0.1" :max="100" :min="0"
v-model="scope.row.taxRate"></el-input-number>
v-model="scope.row.taxRate" controls-position="right"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" align="center">
@ -292,32 +292,33 @@
</el-table-column>
<el-table-column prop="attachment" label="附件" align="center" width="150">
<template slot-scope="scope">
<el-tag size="small" v-if="scope.row.attachment">
<a :href="scope.row.attachment.uri">
<i class="el-icon-document"></i> {{scope.row.attachment.name}}
</a>
</el-tag>
<el-upload v-else class="upload-demo"
action="${base}/file/upload"
name="files[]"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-success="handleFileUploaded"
:limit="1"
size="mini">
<el-upload :file-list="scope.row.fileList"
:limit="10" size="mini" name="files[]" action="${base}/file/upload"
:on-remove="(_, fileList) => handleSupplierMaterialRemove(scope, fileList)"
:before-remove="(file, fileList) => beforeSupplierMaterialRemove(scope, file, fileList)"
:on-success="(response, file, fileList) => handleSupplierMaterialFileUploaded(scope, response, file, fileList)"
:on-exceed="(files, fileList) => handleSupplierMaterialExceed(scope, files, fileList)">
<el-button size="small" type="text">上传附件</el-button>
</el-upload>
</template>
</el-table-column>
<el-table-column width="49" fixed="right">
<template slot-scope="scope">
<el-popconfirm title="确定删除吗?填写的表单将丢弃" @confirm="removeSupplierMaterialRow(scope)">
<el-button slot="reference" type="danger" icon="el-icon-delete" circle size="mini"></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-button type="primary" icon="el-icon-plus" circle size="mini" @click="addSupplierRow"
style="float: right;margin-top: 10px;"></el-button>
style="float: right;margin: 10px 10px 0 0"></el-button>
</div>
<el-row>
<el-row class="am-u-sm-12 am-u-md-12" v-cloak>
<el-button type="info" @click="backLastPage">返回上一级</el-button>
<el-button type="primary" @click="saveDraft">保存草稿</el-button>
<el-button type="success" @click="submitForm">提交</el-button>
@ -381,7 +382,7 @@
<el-table-column prop="amountAlready" label="已采购数量" width="100"></el-table-column>
<el-table-column prop="amountCurrent" label="本次采购数量" width="180">
<template slot-scope="scope">
<span v-if="scope.row.newRow">{{scope.row.parent.amountCurrent}}</span>
<span v-if="scope.row.newRow"></span>
<el-input-number v-else :precision="2" :step="0.1" :min="0" v-model="scope.row.amountCurrent"
:disabled="scope.row.amount - scope.row.amountAlready===0" size="mini"
:max="scope.row.amount - scope.row.amountAlready" controls-position="right">
@ -539,7 +540,7 @@
// 销售合同收入明细
incomeDetails: [],
supplierMaterialsForm: [],
procurementDetailsRowKey: 0,
rowKeyCounter: 0,
procurementDetails: [],
filteredProcurementDetails: [],
}
@ -628,7 +629,10 @@
.then(checkStatus)
.then(parseJSON)
.then(data => {
const { incomeDetails, process, contract, procurementDetails, attachments, ...form } = data
const {
incomeDetails, process, supplierMaterials,
contract, procurementDetails, attachments, ...form
} = data
// 转换数据
// @formatter:off
const computeType = type => {
@ -662,6 +666,12 @@
name: item.name, url: item.uri
}))
this.supplierMaterialsForm = supplierMaterials && supplierMaterials.map(material => ({
...material, attachment: material.attachment && JSON.parse(material.attachment).map(item => ({
name: item.name, url: item.uri
}))
}))
let rowKey = 0
const convertCommon = detail => {
@ -682,12 +692,13 @@
// mapChildren
let { purchaseDetails } = parent
if (isNotEmpty(purchaseDetails)) {
// 先取出第一个合并
const first = purchaseDetails.shift();
// 再处理剩余的子元素
purchaseDetails = purchaseDetails.map(purchase => ({
...purchase, ...convertCommon(detail), parent, newRow: true
}))
// 合并第一行到父级
const first = purchaseDetails.shift();
Object.assign(parent, first)
delete parent['parent']
delete parent['purchaseDetails']
@ -700,7 +711,7 @@
}
this.procurementDetails = computeProcurementDetails(procurementDetails)
this.procurementDetailsRowKey = rowKey
this.rowKeyCounter = rowKey
if (process.processType === saleContract) {
this.changeMode(saleContractProcess)
@ -711,10 +722,15 @@
})
.catch(err => {
if (err.response) {
parseJSON(err.response)
.then(json => {
this.$message.error(json.message || "项目加载失败");
})
}
else {
this.$message.error("项目加载失败")
}
})
.finally(() => loading.close())
},
@ -735,17 +751,29 @@
},
submit(needValid) {
this.$refs["contractProcessForm"].validate((valid) => {
if (valid || !needValid) {
const processForm = this.processForm
const processType = this.processType
const fileList = this.fileList
if (needValid && fileList.length === 0) {
let validStatus = !needValid
if (needValid) {
// 校验表单
this.$refs["contractProcessForm"].validate((valid) => {
if (valid) {
if (fileList.length === 0) {
this.$message.error("未上传附件");
return false
}
if (processType === saleContract) {
if (!this.checkExpirationDate()) {
return false
}
}
const processType = this.processType
const processForm = this.processForm
if (needValid && processType === procurementContract) {
// 采购合同需要验证 供应商比选材料
if (processType === procurementContract) {
if (!this.checkProcurementDetails()) {
return false
}
const { procurementMode } = processForm
// specify_purchase("指定采购"),
@ -757,19 +785,22 @@
|| procurementMode === 'competitive_evaluation') {
if (isEmpty(this.supplierMaterialsForm)) {
this.$message.error("供应商比选材料未填写")
this.$message.error("供应商比选材料未填写,请检查表单")
return false
}
let idx = 0
for (const item in this.supplierMaterialsForm) {
if (isEmpty(Object.keys(item))) {
this.$message.error("供应商比选材料未填写")
idx++
if (isEmptyObject(item)) {
this.$message.error("供应商比选材料第'" + idx + "'行未填写,请检查表单")
return false
}
for (const [key, value] of Object.entries(item)) {
if (value) {
if (typeof value === 'string') {
if (isBlank(value)) {
this.$message.error("有未填写的表单,请检查表单");
this.$message.error("供应商比选材料第'" + idx + "'行数据未填写,请检查表单")
return false
}
}
@ -777,7 +808,7 @@
else {
// 没有值
if (key !== 'remark') {
this.$message.error("有未填写的表单,请检查表单");
this.$message.error("供应商比选材料第'" + idx + "'行数据未填写,请检查表单")
}
}
}
@ -786,11 +817,26 @@
}
}
if (needValid && !processForm.applyDeptId) {
if (!processForm.applyDeptId) {
this.$message.error("申请部门还未选择");
return false
}
// 验证通过
validStatus = true
return true;
}
else {
return false
}
})
}
if (!validStatus) {
return false
}
const loading = this.$loading({
lock: true,
text: processForm.status === 'draft' ? '正在保存草稿' : "正在提交",
@ -798,26 +844,50 @@
background: 'rgba(0, 0, 0, 0.7)'
})
const applyDeptId = processForm.applyDeptId.join(',') // (逗号分割)
const budgetCostIdMap = new Map()
this.procurementDetails.forEach(detail => {
budgetCostIdMap.set(detail.budgetCostId, detail)
})
const computePurchaseAmountDetail = budgetCostId => {
const detail = budgetCostIdMap.get(budgetCostId)
const ret = []
const map = detail => {
const ret = {}
procurementDetailProperties.forEach(property => {
ret[property] = detail[property]
})
return ret
}
ret.push(map(detail))
detail.children.forEach(detail => {
ret.push(map(detail))
})
return ret
}
const form = {
...this.processForm,
applyDeptId,
...processForm,
processType,
applyDeptId: processForm.applyDeptId?.join(','), // (逗号分割),
attachments: fileList.map(file => {
if (file.url) {
return {
uri: file.url,
name: file.name
}
}
return {
uri: file.response.data.url,
name: file.name
return { uri: file.url, name: file.name }
}
return { uri: file.response.data.url, name: file.name }
}),
incomeDetails: this.incomeDetails.map(detail => ({
id: detail.id, expirationDate: detail.expirationDate
}))
})),
purchaseAmount: this.filteredProcurementDetails.map(detail => ({
amount: detail.amount,
budgetCostId: detail.budgetCostId,
amountAlready: detail.amountAlready,
amountCurrent: detail.amountCurrent,
details: computePurchaseAmountDetail(detail.budgetCostId)
})),
supplierMaterials: this.supplierMaterialsForm.filter(hasProperties), // 剔除空行
}
fetch("${base}/process", {
@ -826,25 +896,26 @@
'Content-Type': 'application/json',
},
body: JSON.stringify(form),
}).then(response => {
if (response.ok) {
}).then(checkStatus).then(data => {
this.$message({
showClose: true,
message: '提交成功',
message: processForm.status === 'draft' ? '草稿保存成功' : "提交成功",
type: 'success'
})
}).catch(({ response }) => {
const defaultMessage = () => {
return processForm.status === 'draft' ? '草稿保存失败' : "项目提交失败"
}
else {
return Promise.reject("失败")
}
}).catch(err => {
this.$message.error("项目提交失败");
}).finally(() => loading.close())
}
else {
return false;
}
if (response) {
parseJSON(response)
.then(json => {
this.$message.error(json.message || defaultMessage())
})
}
else {
this.$message.error(defaultMessage())
}
}).finally(() => loading.close())
},
submitToSaleContractProcess() {
@ -873,12 +944,36 @@
}
},
handleSupplierMaterialRemove(scope, fileList) {
scope.row['attachment'] = fileList
},
handleSupplierMaterialExceed(scope, files, fileList) {
this.$message.warning("当前限制选择只能选择10个文件");
scope.row['attachment'] = fileList
},
beforeSupplierMaterialRemove(scope, file, fileList) {
return this.$confirm("确定移除 " + file.name + "");
},
handleSupplierMaterialFileUploaded(scope, response, file, fileList) {
if (response.success) {
scope.row['attachment'] = fileList
}
else {
this.$message.warning("上传失败");
}
},
indexMethod(index) {
return index * 1;
},
addSupplierRow() {
this.supplierMaterialsForm.push({})
this.supplierMaterialsForm.push({
rowKey: this.rowKeyCounter++,
})
},
addProcurementDetailRow(scope) {
@ -886,7 +981,7 @@
const { children } = row
const newRow = {
...row,
rowKey: this.procurementDetailsRowKey++,
rowKey: this.rowKeyCounter++,
newRow: true,
parent: row // 记录上一级元素,在删除的时候使用
}
@ -916,6 +1011,11 @@
return '';
},
removeSupplierMaterialRow(scope) {
const { rowKey } = scope.row
this.supplierMaterialsForm = this.supplierMaterialsForm.filter(child => child.rowKey !== rowKey)
},
applyDeptSelected(value) {
if (value.length === 0) {
this.processForm['applyDept'] = null

View File

@ -341,11 +341,11 @@
<el-upload class="upload-demo"
action="${base}/file/upload"
name="files[]"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-success="handleFileUploaded"
:limit="10"
:on-exceed="handleExceed">
:on-remove="handleRemove"
:on-exceed="handleExceed"
:before-remove="beforeRemove"
:on-success="handleFileUploaded">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传PDF、excel、word、图片、压缩包且不超过50MB</div>
</el-upload>
@ -411,24 +411,30 @@
<i class="el-icon-document"></i> {{scope.row.attachment.name}}
</a>
</el-tag>
<el-upload v-else class="upload-demo"
action="${base}/file/upload"
name="files[]"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-success="handleFileUploaded"
:limit="1"
size="mini">
<el-upload v-else :limit="10" size="mini" name="files[]" action="${base}/file/upload"
:on-remove="(_, fileList) => handleSupplierMaterialRemove(scope, fileList)"
:before-remove="(file, fileList) => beforeSupplierMaterialRemove(scope, file, fileList)"
:on-success="(response, file, fileList) => handleSupplierMaterialFileUploaded(scope, response, file, fileList)"
:on-exceed="(files, fileList) => handleSupplierMaterialExceed(scope, files, fileList)"
>
<el-button size="small" type="text">上传附件</el-button>
</el-upload>
</template>
</el-table-column>
<el-table-column width="49" fixed="right">
<template slot-scope="scope">
<el-popconfirm title="确定删除吗?填写的表单将丢弃" @confirm="removeSupplierMaterialRow(scope)">
<el-button slot="reference" type="danger" icon="el-icon-delete" circle size="mini"></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-button type="primary" icon="el-icon-plus" circle size="mini" @click="addSupplierRow"
style="float: right;margin-top: 10px;"></el-button>
style="float: right;margin: 10px 10px 0 0"></el-button>
</div>
<el-row class="am-u-sm-12 am-u-md-12" v-cloak>
@ -624,6 +630,29 @@
return !isBlank(obj)
}
const hasProperties = obj => {
return isNotEmpty(Object.keys(obj));
}
const isEmptyObject = obj => {
return isEmpty(Object.keys(obj));
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
}
else {
const error = new Error(response.statusText);
error.response = response
throw error
}
}
function parseJSON(response) {
return response.json()
}
const data = () => {
return {
mode: "btn", // btn , procurementContractProcess
@ -641,7 +670,7 @@
processType: "",
procurementProjectSelectorVisible: false,
procurementDetailSelectorVisible: false,
procurementDetailsRowKey: 0,
rowKeyCounter: 0,
}
}
@ -867,7 +896,7 @@
rowKey: rowKey++, children: []
}))
this.procurementDetailsRowKey = rowKey
this.rowKeyCounter = rowKey
})
.catch(err => {
@ -938,19 +967,22 @@
|| procurementMode === 'competitive_evaluation') {
if (isEmpty(this.supplierMaterialsForm)) {
this.$message.error("供应商比选材料未填写")
this.$message.error("供应商比选材料未填写,请检查表单")
return false
}
let idx = 0
for (const item in this.supplierMaterialsForm) {
if (isEmpty(Object.keys(item))) {
this.$message.error("供应商比选材料未填写")
idx++
if (isEmptyObject(item)) {
this.$message.error("供应商比选材料第'" + idx + "'行未填写,请检查表单")
return false
}
for (const [key, value] of Object.entries(item)) {
if (value) {
if (typeof value === 'string') {
if (isBlank(value)) {
this.$message.error("有未填写的表单,请检查表单");
this.$message.error("供应商比选材料第'" + idx + "'行数据未填写,请检查表单")
return false
}
}
@ -958,7 +990,7 @@
else {
// 没有值
if (key !== 'remark') {
this.$message.error("有未填写的表单,请检查表单");
this.$message.error("供应商比选材料第'" + idx + "'行数据未填写,请检查表单")
}
}
}
@ -1000,7 +1032,6 @@
budgetCostIdMap.set(detail.budgetCostId, detail)
})
debugger
const computePurchaseAmountDetail = budgetCostId => {
const detail = budgetCostIdMap.get(budgetCostId)
const ret = []
@ -1019,16 +1050,14 @@
return ret
}
const applyDeptId = processForm.applyDeptId?.join(',') // (逗号分割)
const form = {
...processForm,
processType,
applyDeptId,
applyDeptId: processForm.applyDeptId?.join(','),// (逗号分割),
attachments: fileList.map(file => ({
uri: file.response.data.url,
name: file.response.data.originName
})),
supplierMaterials: this.supplierMaterialsForm,
incomeDetails: this.incomeDetails.map(detail => ({
id: detail.id, expirationDate: detail.expirationDate
})),
@ -1038,7 +1067,8 @@
amountAlready: detail.amountAlready,
amountCurrent: detail.amountCurrent,
details: computePurchaseAmountDetail(detail.budgetCostId)
}))
})),
supplierMaterials: this.supplierMaterialsForm.filter(hasProperties), // 剔除空行
}
fetch("${base}/process", {
@ -1047,19 +1077,25 @@
'Content-Type': 'application/json',
},
body: JSON.stringify(form),
}).then(response => {
if (response.ok) {
}).then(checkStatus).then(data => {
this.$message({
showClose: true,
message: '提交成功',
message: processForm.status === 'draft' ? '草稿保存成功' : "提交成功",
type: 'success'
})
}).catch(({ response }) => {
const defaultMessage = () => {
return processForm.status === 'draft' ? '草稿保存失败' : "项目提交失败"
}
if (response) {
parseJSON(response)
.then(json => {
this.$message.error(json.message || defaultMessage())
})
}
else {
return Promise.reject("失败")
this.$message.error(defaultMessage())
}
}).catch(err => {
this.$message.error("项目提交失败");
}).finally(() => loading.close())
},
@ -1101,12 +1137,38 @@
}
},
handleSupplierMaterialRemove(scope, fileList) {
scope.row['fileList'] = fileList
},
handleSupplierMaterialExceed(scope, files, fileList) {
this.$message.warning("当前限制选择只能选择10个文件");
scope.row['fileList'] = fileList
},
beforeSupplierMaterialRemove(scope, file, fileList) {
return this.$confirm("确定移除 " + file.name + "");
},
handleSupplierMaterialFileUploaded(scope, response, file, fileList) {
if (response.success) {
scope.row['fileList'] = fileList
this.fileListSupplierMaterial = fileList
}
else {
this.$message.warning("上传失败");
}
},
indexMethod(index) {
return index * 1;
},
addSupplierRow() {
this.supplierMaterialsForm.push({})
this.supplierMaterialsForm.push({
rowKey: this.rowKeyCounter++,
})
},
addProcurementDetailRow(scope) {
@ -1114,7 +1176,7 @@
const { children } = row
const newRow = {
...row,
rowKey: this.procurementDetailsRowKey++,
rowKey: this.rowKeyCounter++,
newRow: true,
parent: row // 记录上一级元素,在删除的时候使用
}
@ -1131,8 +1193,8 @@
},
removeProcurementDetailRow(scope) {
const { rowKey, parent } = scope.row
parent.children = parent.children.filter(child => child.rowKey !== rowKey)
const { rowKey } = scope.row
.filter(child => child.rowKey !== rowKey)
},
// 区别子项
@ -1144,6 +1206,11 @@
return '';
},
removeSupplierMaterialRow(scope) {
const { rowKey } = scope.row
this.supplierMaterialsForm = this.supplierMaterialsForm.filter(child => child.rowKey !== rowKey)
},
applyDeptSelected(value) {
if (value.length === 0) {
this.processForm['applyDept'] = null