Files
klp-oa/klp-ui/src/views/finance/document/components/Voucher.vue
2025-08-15 17:52:43 +08:00

424 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<el-row>
<el-form :model="form" label-width="120px" inline>
<el-form-item label="凭证编号">
<el-input v-model="form.docNo" placeholder="请输入凭证编号" />
</el-form-item>
<el-form-item label="单据日期" prop="docDate">
<el-date-picker clearable v-model="form.docDate" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择单据日期">
</el-date-picker>
</el-form-item>
<el-form-item label="关联订单" prop="relatedOrderId">
<el-input v-model="form.relatedOrderId" placeholder="请输入关联订单" />
</el-form-item>
</el-form>
</el-row>
<el-row>
<el-table :data="tableData" style="width: 100%" empty-text="暂无数据">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column prop="voucherNo" label="摘要">
<template slot-scope="scope">
<el-input v-model="scope.row.voucherNo" placeholder="请输入摘要" @input="handleCreditInput(scope.row)"
@change="handleRowChange(scope.row)" />
</template>
</el-table-column>
<!-- <el-table-column prop="voucherNo" label="单号">
<template slot-scope="scope">
<el-input v-model="scope.row.voucherNo" placeholder="请输入单号" @input="handleCreditInput(scope.row)" @change="handleRowChange(scope.row)" />
</template>
</el-table-column> -->
<el-table-column prop="accountId" label="会计科目">
<template slot-scope="scope">
<amount-select v-model="scope.row.accountId" placeholder="请输入会计科目" @change="handleRowChange(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="debitAmount" label="借方金额">
<template slot-scope="scope">
<el-input v-model.number="scope.row.debitAmount" type="number" placeholder="0.00"
@input="handleDebitInput(scope.row)" @change="handleRowChange(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="creditAmount" label="贷方金额">
<template slot-scope="scope">
<el-input v-model.number="scope.row.creditAmount" type="number" placeholder="0.00"
@input="handleCreditInput(scope.row)" @change="handleRowChange(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注" />
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-delete" @click="handleDelete(scope.$index)"
:disabled="tableData.length <= 1">
删除
</el-button>
<el-button type="text" icon="el-icon-edit" @click="handleEdit(scope.$index)" v-if="!isCreate">
变更
</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
<!-- 合计部分 -->
<el-row>
<el-row>
<el-descriptions>
<el-descriptions-item label="大写金额">
<span>{{ amountInWords }}</span>
</el-descriptions-item>
<el-descriptions-item label="借方合计">
<!-- 增加空值处理确保toFixed调用安全 -->
<span>{{ (debitAmount || 0).toFixed(2) }}</span>
</el-descriptions-item>
<el-descriptions-item label="贷方合计">
<span>{{ (creditAmount || 0).toFixed(2) }}</span>
</el-descriptions-item>
</el-descriptions>
</el-row>
</el-row>
<el-row>
<el-button v-if="isCreate" type="primary" @click="handleCreate">创建凭证</el-button>
<el-button v-else type="primary" @click="handleChange">变更凭证信息</el-button>
</el-row>
</div>
</template>
<script>
import AmountSelect from '@/components/KLPService/AmountSelect/index.vue';
import { addFinancialDocumentWithDetail, updateFinancialDocument } from "@/api/finance/financialDocument";
import { updateJournalEntry } from "@/api/finance/jouneryEntry";
export default {
components: {
AmountSelect
},
data() {
return {
form: {
docNo: undefined,
docDate: undefined,
relatedOrderId: undefined
},
tableData: [{
voucherNo: '',
accountId: undefined,
debitAmount: 0,
creditAmount: 0,
remark: '',
voucherNo: ''
}],
isCreate: false
}
},
computed: {
// 借方总额(修复后)
debitAmount() {
// 确保reduce的初始值为0且返回值为数字
return this.tableData.reduce((total, item) => {
if (this.isRowNotEmpty(item)) {
return total + (Number(item.debitAmount) || 0);
}
return total;
}, 0) || 0; // 最后确保即使结果为undefined也返回0
},
// 贷方总额(修复后)
creditAmount() {
return this.tableData.reduce((total, item) => {
if (this.isRowNotEmpty(item)) {
return total + (Number(item.creditAmount) || 0);
}
return total;
}, 0) || 0; // 同样增加默认值0
},
// 大写金额(保持不变)
amountInWords() {
if (Math.abs(this.debitAmount - this.creditAmount) < 0.01) {
return this.numberToChinese(this.debitAmount);
} else {
return '借贷不相等';
}
}
},
props: {
initData: {
type: Object,
required: false,
default: () => ({
tableData: [{
voucherNo: '',
accountId: undefined,
debitAmount: 0,
creditAmount: 0,
remark: '',
voucherNo: ''
}]
})
}
},
watch: {
initData: {
handler(newVal) {
console.log(newVal, 'watchData');
if (newVal) {
if (newVal.detailList) {
this.tableData = newVal.detailList;
} else {
this.tableData = [{
voucherNo: '',
accountId: undefined,
debitAmount: 0,
creditAmount: 0,
remark: '',
voucherNo: ''
}]
}
this.form.docNo = newVal.docNo;
this.form.docDate = newVal.docDate;
this.form.relatedOrderId = newVal.relatedOrderId;
this.isCreate = false;
} else {
this.isCreate = true;
}
},
immediate: true
}
},
methods: {
// 判断行是否有内容
isRowNotEmpty(row) {
return row.voucherNo || row.accountingId ||
(row.debitAmount && row.debitAmount > 0) ||
(row.creditAmount && row.creditAmount > 0) ||
row.remark;
},
// 处理借方金额输入
handleDebitInput(row) {
// 如果借方金额有值,则清空贷方金额
if (row.debitAmount && row.debitAmount > 0) {
if (row.creditAmount && row.creditAmount > 0) {
this.$message.warning('借方和贷方金额不能同时填写,已自动清空贷方金额');
row.creditAmount = 0;
}
}
},
// 处理贷方金额输入
handleCreditInput(row) {
// 如果贷方金额有值,则清空借方金额
if (row.creditAmount && row.creditAmount > 0) {
if (row.debitAmount && row.debitAmount > 0) {
this.$message.warning('借方和贷方金额不能同时填写,已自动清空借方金额');
row.debitAmount = 0;
}
}
},
// 处理行数据变化
handleRowChange(row) {
// 检查最后一行是否有内容,如果有则添加新的空行
const lastRow = this.tableData[this.tableData.length - 1];
if (this.isRowNotEmpty(lastRow)) {
this.addEmptyRow();
}
},
// 添加空行
addEmptyRow() {
this.tableData.push({
voucherNo: '',
accountId: undefined,
debitAmount: 0,
creditAmount: 0,
remark: '',
voucherNo: ''
});
},
// 删除行
handleDelete(index) {
this.tableData.splice(index, 1);
// 确保至少保留一个空行
if (this.tableData.length === 0) {
this.addEmptyRow();
} else {
// 检查最后一行是否为空,如果不为空则添加新的空行
const lastRow = this.tableData[this.tableData.length - 1];
if (this.isRowNotEmpty(lastRow)) {
this.addEmptyRow();
}
}
},
// 数字转中文大写金额
numberToChinese(num) {
if (num === 0) return '零元整';
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const units = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟'];
const decimals = ['角', '分'];
// 处理整数和小数部分
const parts = num.toFixed(2).split('.');
const integerPart = parts[0];
const decimalPart = parts[1];
let result = '';
// 处理整数部分
for (let i = 0; i < integerPart.length; i++) {
const digit = parseInt(integerPart[i]);
const position = integerPart.length - i - 1;
if (digit !== 0) {
result += digits[digit] + units[position];
} else {
// 处理零的情况
if (position % 4 === 0) { // 万或亿的位置
result += units[position];
}
// 避免连续多个零
if (!result.endsWith('零')) {
result += digits[digit];
}
}
}
result += '元';
// 处理小数部分
if (decimalPart === '00') {
result += '整';
} else {
if (decimalPart[0] !== '0') {
result += digits[parseInt(decimalPart[0])] + decimals[0];
}
if (decimalPart[1] !== '0') {
result += digits[parseInt(decimalPart[1])] + decimals[1];
}
}
// 处理以零开头的情况
return result.replace(/^零+/, '');
},
handleCreate() {
// 收集所有错误信息
const errors = [];
const { form, tableData } = this;
// 1. 基础表单必填项校验
if (!form.docNo) errors.push('凭证编号必填');
if (!form.docDate) errors.push('单据日期必填');
// 注意原模板中是relatedOrderId保持变量名一致
if (!form.relatedOrderId) errors.push('关联订单必填');
// 2. 过滤有效行并检查基本存在性
const validData = tableData.filter(row => this.isRowNotEmpty(row));
if (validData.length === 0) {
errors.push('请至少填写一行凭证数据');
}
// 3. 表格行级校验(仅当有有效行时)
if (validData.length > 0) {
// 3.1 检查每一行的必填项
validData.forEach((row, index) => {
const rowNum = index + 1; // 行号从1开始
if (!row.voucherNo) errors.push(`${rowNum}行:请填写摘要`);
// 注意原模板中是accountingId保持变量名一致
if (!row.accountId) errors.push(`${rowNum}行:请填写会计科目`);
// 检查是否存在voucherNo字段如果实际业务需要
if (row.voucherNo === undefined && !row.voucherNo) {
errors.push(`${rowNum}行:请填写单号`);
}
// 3.2 检查每行是否同时有借贷金额
if (row.debitAmount > 0 && row.creditAmount > 0) {
errors.push(`${rowNum}行:不能同时填写借方和贷方金额`);
}
// 3.3 检查每行是否至少有一个金额
if (!(row.debitAmount > 0 || row.creditAmount > 0)) {
errors.push(`${rowNum}行:请填写借方或贷方金额`);
}
});
// 4. 整体金额校验
// 处理浮点数精度问题
if (Math.abs(this.debitAmount - this.creditAmount) > 0.01) {
errors.push(`借贷金额必须相等(借方:${this.debitAmount.toFixed(2)},贷方:${this.creditAmount.toFixed(2)}`);
}
}
if (errors.length > 0) {
this.$message({
dangerouslyUseHTMLString: true,
message: errors.map(error => `<p>${error}</p>`).join(''),
type: 'error'
})
return;
}
// 6. 提交数据
addFinancialDocumentWithDetail({
docNo: form.docNo,
docDate: form.docDate,
// 保持变量名一致
relatedOrderId: form.relatedOrderId,
amount: this.debitAmount,
details: validData.map((row, idx) => ({
// voucherNo: row.voucherNo,
voucherNo: row.voucherNo,
// 保持变量名一致
accountId: row.accountId,
debitAmount: row.debitAmount,
creditAmount: row.creditAmount,
remark: row.remark,
lineNo: idx + 1,
entryDate: form.docDate
}))
}).then(response => {
this.$message.success('凭证创建成功');
this.$emit('success');
}).catch(error => {
this.$message.error('凭证创建失败:' + (error.message || '未知错误'));
this.$emit('error', error);
});
},
handleEdit(index) {
// 找到对应的列变更
const row = this.tableData[index];
updateJournalEntry(row).then(response => {
this.$message.success('明细变更成功');
}).catch(error => {
this.$message.error('明细变更失败:' + (error.message || '未知错误'));
});
},
handleChange() {
updateFinancialDocument({
documentId: this.initData.documentId,
docNo: this.form.docNo,
docDate: this.form.docDate,
relatedOrderId: this.form.relatedOrderId,
amount: this.debitAmount,
}).then(response => {
this.$message.success('凭证变更成功');
}).catch(error => {
this.$message.error('凭证变更失败:' + (error.message || '未知错误'));
});
}
}
}
</script>