Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import request from '@/axios';
|
||||||
|
|
||||||
|
export const getList = (current, size, params) =>
|
||||||
|
request({
|
||||||
|
url: '/blade-system/invoice-item/list',
|
||||||
|
method: 'get',
|
||||||
|
params: { ...params, current, size },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getAll = () => getList(1, 1000, {});
|
||||||
|
|
||||||
|
export const getDetail = id =>
|
||||||
|
request({
|
||||||
|
url: '/blade-system/invoice-item/detail',
|
||||||
|
method: 'get',
|
||||||
|
params: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const submit = row =>
|
||||||
|
request({
|
||||||
|
url: '/blade-system/invoice-item/submit',
|
||||||
|
method: 'post',
|
||||||
|
data: row,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const remove = ids =>
|
||||||
|
request({
|
||||||
|
url: '/blade-system/invoice-item/remove',
|
||||||
|
method: 'post',
|
||||||
|
params: { ids },
|
||||||
|
});
|
||||||
@@ -12,7 +12,7 @@ export const getSettlementCandidates = (keyword, flowId) =>
|
|||||||
params: { keyword, flowId },
|
params: { keyword, flowId },
|
||||||
});
|
});
|
||||||
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
|
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
|
||||||
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post' });
|
export const sync = data => request({ url: `${baseUrl}/sync`, method: 'post', data });
|
||||||
|
|
||||||
export const claimStatusOptions = [
|
export const claimStatusOptions = [
|
||||||
{ label: '未认领', value: 'unclaimed' },
|
{ label: '未认领', value: 'unclaimed' },
|
||||||
|
|||||||
@@ -36,3 +36,5 @@ export const adjustDetail = data =>
|
|||||||
request({ url: `${baseUrl}/adjust-detail`, method: 'post', data });
|
request({ url: `${baseUrl}/adjust-detail`, method: 'post', data });
|
||||||
export const applyPayment = data =>
|
export const applyPayment = data =>
|
||||||
request({ url: `${baseUrl}/apply-payment`, method: 'post', data });
|
request({ url: `${baseUrl}/apply-payment`, method: 'post', data });
|
||||||
|
export const applyPayments = data =>
|
||||||
|
request({ url: `${baseUrl}/apply-payments`, method: 'post', data });
|
||||||
|
|||||||
@@ -250,7 +250,16 @@ export default {
|
|||||||
},
|
},
|
||||||
batchDownload() {
|
batchDownload() {
|
||||||
const rows = this.selectedRows.length ? this.selectedRows : this.rows;
|
const rows = this.selectedRows.length ? this.selectedRows : this.rows;
|
||||||
rows.forEach(this.downloadAttachment);
|
const downloadableRows = rows.filter(row => this.attachmentUrl(row));
|
||||||
|
if (!downloadableRows.length) {
|
||||||
|
this.$message.warning(
|
||||||
|
this.selectedRows.length ? '所选附件暂无可下载地址' : '暂无可下载附件'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadableRows.forEach((row, index) => {
|
||||||
|
window.setTimeout(() => this.downloadAttachment(row), index * 500);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
previewAttachment(row) {
|
previewAttachment(row) {
|
||||||
const url = this.attachmentUrl(row);
|
const url = this.attachmentUrl(row);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const billLedgerTableColumns = [
|
export const billLedgerTableColumns = [
|
||||||
{ prop: 'billNo', label: '票据编号', minWidth: 170 },
|
{ prop: 'billNo', label: '票据编号', minWidth: 170, link: true },
|
||||||
{ prop: 'receiverName', label: '收票单位', minWidth: 170 },
|
{ prop: 'receiverName', label: '收票单位', minWidth: 170 },
|
||||||
{ prop: 'issuerName', label: '出票单位', minWidth: 170 },
|
{ prop: 'issuerName', label: '出票单位', minWidth: 170 },
|
||||||
{ prop: 'billTypeName', label: '汇票类型', minWidth: 110 },
|
{ prop: 'billTypeName', label: '汇票类型', minWidth: 110 },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const invoiceApplicationTableColumns = [
|
export const invoiceApplicationTableColumns = [
|
||||||
{ prop: 'applicationNo', label: '单据号', minWidth: 170, link: true },
|
{ prop: 'applicationNo', label: '单据号', minWidth: 170, link: true },
|
||||||
{ prop: 'settlementNos', label: '结算单号', minWidth: 190, link: true },
|
{ prop: 'settlementNos', label: '结算单号', minWidth: 190 },
|
||||||
{ prop: 'projectName', label: '所属项目', minWidth: 140 },
|
{ prop: 'projectName', label: '所属项目', minWidth: 140 },
|
||||||
{ prop: 'deptName', label: '所属组织', minWidth: 140 },
|
{ prop: 'deptName', label: '所属组织', minWidth: 140 },
|
||||||
{ prop: 'issuerName', label: '开票方', minWidth: 150 },
|
{ prop: 'issuerName', label: '开票方', minWidth: 150 },
|
||||||
@@ -30,5 +30,4 @@ export const invoiceApplicationDetailColumns = [
|
|||||||
{ prop: 'mileage', label: '里程(KM)', minWidth: 120 },
|
{ prop: 'mileage', label: '里程(KM)', minWidth: 120 },
|
||||||
{ prop: 'batchNo', label: '批次号', minWidth: 120 },
|
{ prop: 'batchNo', label: '批次号', minWidth: 120 },
|
||||||
{ prop: 'freightAmount', label: '运费', minWidth: 120, money: true },
|
{ prop: 'freightAmount', label: '运费', minWidth: 120, money: true },
|
||||||
{ prop: 'feeItemsJson', label: '费用项目', minWidth: 180 },
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<template>
|
||||||
|
<basic-container class="invoice-item-page">
|
||||||
|
<avue-crud
|
||||||
|
ref="crud"
|
||||||
|
v-model="form"
|
||||||
|
v-model:page="page"
|
||||||
|
:option="option"
|
||||||
|
:data="data"
|
||||||
|
:table-loading="loading"
|
||||||
|
:permission="permissionList"
|
||||||
|
:before-open="beforeOpen"
|
||||||
|
@row-save="rowSave"
|
||||||
|
@row-update="rowUpdate"
|
||||||
|
@row-del="rowDel"
|
||||||
|
@search-change="searchChange"
|
||||||
|
@search-reset="searchReset"
|
||||||
|
@selection-change="selectionChange"
|
||||||
|
@current-change="currentChange"
|
||||||
|
@size-change="sizeChange"
|
||||||
|
@refresh-change="refreshChange"
|
||||||
|
@on-load="onLoad"
|
||||||
|
>
|
||||||
|
<template #menu="scope">
|
||||||
|
<el-link
|
||||||
|
v-if="permission.invoice_item_edit"
|
||||||
|
type="primary"
|
||||||
|
@click="$refs.crud.rowEdit(scope.row, scope.index)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</el-link>
|
||||||
|
<el-link
|
||||||
|
v-if="permission.invoice_item_delete"
|
||||||
|
type="danger"
|
||||||
|
@click="rowDel(scope.row)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
<template #defaultTaxRate="scope">{{ scope.row.defaultTaxRate }}%</template>
|
||||||
|
<template #defaultTaxRate-form>
|
||||||
|
<el-input
|
||||||
|
v-model="form.defaultTaxRate"
|
||||||
|
inputmode="decimal"
|
||||||
|
maxlength="6"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入默认税率"
|
||||||
|
@input="handleTaxRateInput"
|
||||||
|
>
|
||||||
|
<template #append>%</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
</avue-crud>
|
||||||
|
</basic-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getList, getDetail, submit, remove } from '@/api/base/invoice-item';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {},
|
||||||
|
data: [],
|
||||||
|
loading: true,
|
||||||
|
query: {},
|
||||||
|
selectionList: [],
|
||||||
|
page: {
|
||||||
|
pageSize: 10,
|
||||||
|
pageSizes: [10, 30, 50, 100, 200],
|
||||||
|
currentPage: 1,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
height: 'auto',
|
||||||
|
calcHeight: 32,
|
||||||
|
tip: false,
|
||||||
|
border: true,
|
||||||
|
stripe: true,
|
||||||
|
index: true,
|
||||||
|
indexLabel: '序号',
|
||||||
|
indexWidth: 70,
|
||||||
|
selection: false,
|
||||||
|
addBtn: true,
|
||||||
|
editBtn: false,
|
||||||
|
delBtn: false,
|
||||||
|
viewBtn: false,
|
||||||
|
menuWidth: 140,
|
||||||
|
dialogWidth: 900,
|
||||||
|
dialogClickModal: false,
|
||||||
|
labelPosition: 'right',
|
||||||
|
labelWidth: 'auto',
|
||||||
|
searchShow: true,
|
||||||
|
searchIcon: true,
|
||||||
|
searchIndex: 4,
|
||||||
|
searchMenuSpan: 24,
|
||||||
|
searchMenuPosition: 'right',
|
||||||
|
column: [
|
||||||
|
{
|
||||||
|
label: '货物或服务简称',
|
||||||
|
prop: 'shortName',
|
||||||
|
minWidth: 160,
|
||||||
|
span: 12,
|
||||||
|
maxlength: 100,
|
||||||
|
rules: [{ required: true, message: '请输入货物或服务简称', trigger: 'blur' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '税收分类编码',
|
||||||
|
prop: 'taxClassificationCode',
|
||||||
|
minWidth: 190,
|
||||||
|
span: 12,
|
||||||
|
maxlength: 30,
|
||||||
|
rules: [{ required: true, message: '请输入税收分类编码', trigger: 'blur' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '商品和服务分类名称',
|
||||||
|
prop: 'categoryName',
|
||||||
|
minWidth: 220,
|
||||||
|
span: 12,
|
||||||
|
maxlength: 200,
|
||||||
|
rules: [{ required: true, message: '请输入商品和服务分类名称', trigger: 'blur' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '默认税率',
|
||||||
|
prop: 'defaultTaxRate',
|
||||||
|
type: 'input',
|
||||||
|
formslot: true,
|
||||||
|
slot: true,
|
||||||
|
minWidth: 120,
|
||||||
|
span: 12,
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入默认税率', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
const text = String(value ?? '');
|
||||||
|
const rate = Number(text);
|
||||||
|
if (!/^\d+(\.\d+)?$/.test(text) || rate < 0 || rate > 100) {
|
||||||
|
callback(new Error('默认税率必须是0到100之间的数字'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '税收项目',
|
||||||
|
prop: 'taxProject',
|
||||||
|
search: true,
|
||||||
|
searchOrder: 2,
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
searchPlaceholder: '请输入税收项目',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '税收项目简称',
|
||||||
|
prop: 'taxProjectShortName',
|
||||||
|
search: true,
|
||||||
|
searchOrder: 1,
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
searchPlaceholder: '请输入税收项目简称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '创建时间',
|
||||||
|
prop: 'createTime',
|
||||||
|
type: 'datetime',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
addDisplay: false,
|
||||||
|
editDisplay: false,
|
||||||
|
viewDisplay: false,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '更新人',
|
||||||
|
prop: 'updateUserName',
|
||||||
|
addDisplay: false,
|
||||||
|
editDisplay: false,
|
||||||
|
viewDisplay: false,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['permission']),
|
||||||
|
permissionList() {
|
||||||
|
return {
|
||||||
|
addBtn: this.validData(this.permission.invoice_item_add, false),
|
||||||
|
editBtn: false,
|
||||||
|
delBtn: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
rowSave(row, done, loading) {
|
||||||
|
submit(this.normalizeTaxRateRow(row)).then(
|
||||||
|
() => {
|
||||||
|
this.onLoad(this.page);
|
||||||
|
this.$message.success('操作成功');
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
() => loading()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
rowUpdate(row, index, done, loading) {
|
||||||
|
submit(this.normalizeTaxRateRow(row)).then(
|
||||||
|
() => {
|
||||||
|
this.onLoad(this.page);
|
||||||
|
this.$message.success('操作成功');
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
() => loading()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
rowDel(row) {
|
||||||
|
this.$confirm('确定将选择数据删除?', '提示', { type: 'warning' })
|
||||||
|
.then(() => remove(row.id))
|
||||||
|
.then(() => {
|
||||||
|
this.onLoad(this.page);
|
||||||
|
this.$message.success('操作成功');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeOpen(done, type) {
|
||||||
|
if (['edit', 'view'].includes(type)) {
|
||||||
|
getDetail(this.form.id).then(res => {
|
||||||
|
this.form = res.data.data || {};
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.form = { defaultTaxRate: 0 };
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
handleTaxRateInput(value) {
|
||||||
|
let normalized = String(value || '').replace(/[^\d.]/g, '');
|
||||||
|
normalized = normalized.replace(/(\..*)\./g, '$1');
|
||||||
|
if (normalized.startsWith('.')) normalized = `0${normalized}`;
|
||||||
|
this.form.defaultTaxRate = normalized;
|
||||||
|
},
|
||||||
|
normalizeTaxRateRow(row) {
|
||||||
|
const values = { ...row };
|
||||||
|
values.defaultTaxRate = Number(values.defaultTaxRate);
|
||||||
|
return values;
|
||||||
|
},
|
||||||
|
searchChange(params, done) {
|
||||||
|
this.query = {
|
||||||
|
...params,
|
||||||
|
categoryName: params.taxProject,
|
||||||
|
shortName: params.taxProjectShortName,
|
||||||
|
};
|
||||||
|
delete this.query.taxProject;
|
||||||
|
delete this.query.taxProjectShortName;
|
||||||
|
this.page.currentPage = 1;
|
||||||
|
this.onLoad(this.page);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
searchReset() {
|
||||||
|
this.query = {};
|
||||||
|
this.page.currentPage = 1;
|
||||||
|
this.onLoad(this.page);
|
||||||
|
},
|
||||||
|
selectionChange(list) {
|
||||||
|
this.selectionList = list;
|
||||||
|
},
|
||||||
|
currentChange(currentPage) {
|
||||||
|
this.page.currentPage = currentPage;
|
||||||
|
},
|
||||||
|
sizeChange(pageSize) {
|
||||||
|
this.page.pageSize = pageSize;
|
||||||
|
},
|
||||||
|
refreshChange() {
|
||||||
|
this.onLoad(this.page);
|
||||||
|
},
|
||||||
|
onLoad(page) {
|
||||||
|
this.loading = true;
|
||||||
|
getList(page.currentPage, page.pageSize, this.query).then(res => {
|
||||||
|
const result = res.data.data || {};
|
||||||
|
this.data = result.records || [];
|
||||||
|
this.page.total = result.total || 0;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.invoice-item-page :deep(.avue-crud__menu) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-item-page :deep(.avue-crud__dialog .el-form-item) {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-item-page :deep(.avue-crud__dialog .el-form-item__error) {
|
||||||
|
position: relative;
|
||||||
|
line-height: 20px;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-item-page :deep(.avue-crud__menu .el-link) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -195,48 +195,7 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息">
|
<section-card title="附件信息">
|
||||||
<el-table :data="form.attachments" border>
|
<vehicle-attachment-table v-model="form.attachments" />
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
|
||||||
<el-table-column label="文件名" min-width="220">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-link
|
|
||||||
v-if="row.url || row.link"
|
|
||||||
type="primary"
|
|
||||||
:href="row.url || row.link"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ row.originalName || row.name || '附件' }}
|
|
||||||
</el-link>
|
|
||||||
<span v-else>{{ row.originalName || row.name || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="附件描述" min-width="240">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input v-model="row.description" maxlength="200" placeholder="请输入" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件大小" width="120" align="center">
|
|
||||||
<template #default="{ row }">{{ fileSize(row) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="130" align="center" />
|
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
|
||||||
<el-table-column label="操作" width="100" align="center">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-link type="danger" @click="form.attachments.splice($index, 1)">删除</el-link>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<vehicle-attachment-upload
|
|
||||||
v-model="form.attachments"
|
|
||||||
class="bill-ledger-form-page__uploader"
|
|
||||||
:multiple="true"
|
|
||||||
:limit="20"
|
|
||||||
:max-size="500"
|
|
||||||
:file-types="attachmentFileTypes"
|
|
||||||
:show-file-list="false"
|
|
||||||
button-text="上传"
|
|
||||||
@change="normalizeAttachments"
|
|
||||||
/>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="备注">
|
<section-card title="备注">
|
||||||
@@ -252,7 +211,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="使用记录">
|
<section-card v-if="readonly" title="使用记录">
|
||||||
<el-table :data="usageRecords" border>
|
<el-table :data="usageRecords" border>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column prop="applicationNo" label="申请单号" min-width="180" align="center" />
|
<el-table-column prop="applicationNo" label="申请单号" min-width="180" align="center" />
|
||||||
@@ -279,6 +238,7 @@ import { mapGetters } from 'vuex';
|
|||||||
import * as api from '@/api/payment/billLedger';
|
import * as api from '@/api/payment/billLedger';
|
||||||
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -302,6 +262,7 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BillLedgerForm',
|
name: 'BillLedgerForm',
|
||||||
|
components: { VehicleAttachmentTable },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
@@ -350,7 +311,7 @@ export default {
|
|||||||
billType: [{ required: true, message: '请选择汇票类型', trigger: 'change' }],
|
billType: [{ required: true, message: '请选择汇票类型', trigger: 'change' }],
|
||||||
faceAmount: [{ validator: this.validateFaceAmount, trigger: 'change' }],
|
faceAmount: [{ validator: this.validateFaceAmount, trigger: 'change' }],
|
||||||
issueDate: [{ required: true, message: '请选择出票日期', trigger: 'change' }],
|
issueDate: [{ required: true, message: '请选择出票日期', trigger: 'change' }],
|
||||||
maturityDate: [{ validator: this.validateMaturityDate, trigger: 'change' }],
|
maturityDate: [{ required: true, validator: this.validateMaturityDate, trigger: 'change' }],
|
||||||
availableDeptIds: [{ validator: this.validateDepartments, trigger: 'change' }],
|
availableDeptIds: [{ validator: this.validateDepartments, trigger: 'change' }],
|
||||||
feeBearerId: [{ required: true, message: '请选择费用承担方', trigger: 'change' }],
|
feeBearerId: [{ required: true, message: '请选择费用承担方', trigger: 'change' }],
|
||||||
confirmedDiscountRate: [{ validator: this.validateRate, trigger: 'change' }],
|
confirmedDiscountRate: [{ validator: this.validateRate, trigger: 'change' }],
|
||||||
@@ -398,6 +359,8 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
...emptyForm(),
|
...emptyForm(),
|
||||||
...data,
|
...data,
|
||||||
|
confirmedDiscountRate: this.normalizeDiscountRate(data.confirmedDiscountRate),
|
||||||
|
bankDiscountReferenceRate: this.normalizeDiscountRate(data.bankDiscountReferenceRate),
|
||||||
availableDeptIds: this.parse(data.availableDeptIdsJson),
|
availableDeptIds: this.parse(data.availableDeptIdsJson),
|
||||||
attachments: this.parse(data.attachmentsJson),
|
attachments: this.parse(data.attachmentsJson),
|
||||||
};
|
};
|
||||||
@@ -500,6 +463,11 @@ export default {
|
|||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
|
normalizeDiscountRate(value) {
|
||||||
|
return value === null || value === undefined || value === '' || Number(value) === -1
|
||||||
|
? null
|
||||||
|
: value;
|
||||||
|
},
|
||||||
normalizeAttachments(files) {
|
normalizeAttachments(files) {
|
||||||
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||||||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
@@ -531,9 +499,9 @@ export default {
|
|||||||
availableDeptIdsJson: JSON.stringify(this.form.availableDeptIds || []),
|
availableDeptIdsJson: JSON.stringify(this.form.availableDeptIds || []),
|
||||||
availableDeptNames: this.form.availableDeptNames,
|
availableDeptNames: this.form.availableDeptNames,
|
||||||
feeBearerId: this.form.feeBearerId,
|
feeBearerId: this.form.feeBearerId,
|
||||||
confirmedDiscountRate: this.form.confirmedDiscountRate,
|
confirmedDiscountRate: this.normalizeDiscountRate(this.form.confirmedDiscountRate),
|
||||||
issuingBank: this.form.issuingBank,
|
issuingBank: this.form.issuingBank,
|
||||||
bankDiscountReferenceRate: this.form.bankDiscountReferenceRate,
|
bankDiscountReferenceRate: this.normalizeDiscountRate(this.form.bankDiscountReferenceRate),
|
||||||
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,16 +12,89 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:page="page"
|
:page="page"
|
||||||
@add="openCreate"
|
@add="openCreate"
|
||||||
|
@view="openView"
|
||||||
@edit="openEdit"
|
@edit="openEdit"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@page-change="handlePageChange"
|
@page-change="handlePageChange"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
/>
|
/>
|
||||||
|
<el-dialog
|
||||||
|
v-model="detailDialog.visible"
|
||||||
|
title="汇票台账详情"
|
||||||
|
width="86%"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div v-loading="detailDialog.loading" class="bill-ledger-detail">
|
||||||
|
<section-card title="基本信息">
|
||||||
|
<el-descriptions :column="3" border>
|
||||||
|
<el-descriptions-item label="票据编号">{{
|
||||||
|
detailValue('billNo')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="汇票类型">{{
|
||||||
|
detailValue('billTypeName', 'billType')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="出票单位">{{
|
||||||
|
detailValue('issuerName')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="收票单位">{{
|
||||||
|
detailValue('receiverName')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="票面金额">{{
|
||||||
|
formatMoney(detailDialog.row.faceAmount)
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="可用余额">{{
|
||||||
|
formatMoney(detailDialog.row.availableBalance)
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="出票日期">{{
|
||||||
|
detailValue('issueDate')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="到期日期">{{
|
||||||
|
detailValue('maturityDate')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="出票行">{{
|
||||||
|
detailValue('issuingBank')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="可用部门">{{
|
||||||
|
detailValue('availableDeptNames')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="费用承担方">{{
|
||||||
|
detailValue('feeBearerName')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="银行贴现参考率">
|
||||||
|
{{ formatRate(detailDialog.row.bankDiscountReferenceRate) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="双方确认贴现率">
|
||||||
|
{{ formatRate(detailDialog.row.confirmedDiscountRate) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="预计贴现费用">
|
||||||
|
{{ formatMoney(estimatedDiscountFee()) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注" :span="3">
|
||||||
|
{{ detailValue('remark') }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</section-card>
|
||||||
|
<section-card title="使用记录">
|
||||||
|
<el-table :data="detailDialog.usageRecords" border>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column prop="applicationNo" label="申请单号" min-width="180" align="center" />
|
||||||
|
<el-table-column label="使用金额" min-width="150" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.usedAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="useDeptName" label="使用部门" min-width="180" align="center" />
|
||||||
|
<el-table-column prop="statusName" label="状态" min-width="120" align="center" />
|
||||||
|
</el-table>
|
||||||
|
<el-empty v-if="!detailDialog.usageRecords.length" description="暂无使用记录" />
|
||||||
|
</section-card>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as api from '@/api/payment/billLedger';
|
import * as api from '@/api/payment/billLedger';
|
||||||
|
import * as billPaymentApi from '@/api/payment/billPayment';
|
||||||
import BillLedgerSearch from './components/bill-ledger-search.vue';
|
import BillLedgerSearch from './components/bill-ledger-search.vue';
|
||||||
import BillLedgerTable from './components/bill-ledger-table.vue';
|
import BillLedgerTable from './components/bill-ledger-table.vue';
|
||||||
|
|
||||||
@@ -45,6 +118,7 @@ export default {
|
|||||||
rows: [],
|
rows: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
|
detailDialog: { visible: false, loading: false, row: {}, usageRecords: [] },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -89,6 +163,31 @@ export default {
|
|||||||
openCreate() {
|
openCreate() {
|
||||||
this.$router.push({ path: '/payment/bill-ledger/form', query: { mode: 'add' } });
|
this.$router.push({ path: '/payment/bill-ledger/form', query: { mode: 'add' } });
|
||||||
},
|
},
|
||||||
|
async openView(row) {
|
||||||
|
this.detailDialog = { visible: true, loading: true, row: { ...row }, usageRecords: [] };
|
||||||
|
try {
|
||||||
|
const [detailResponse, paymentResponse] = await Promise.all([
|
||||||
|
api.getDetail(row.id),
|
||||||
|
billPaymentApi.getList(1, 1000, { billLedgerId: row.id }),
|
||||||
|
]);
|
||||||
|
const detail = this.unwrapData(detailResponse) || {};
|
||||||
|
const paymentData = this.unwrapData(paymentResponse) || {};
|
||||||
|
const paymentRows = paymentData.records || (Array.isArray(paymentData) ? paymentData : []);
|
||||||
|
const sourceRecords = paymentRows.length ? paymentRows : detail.usageRecords || [];
|
||||||
|
this.detailDialog.row = { ...row, ...detail };
|
||||||
|
this.detailDialog.usageRecords = sourceRecords.map(item => ({
|
||||||
|
...item,
|
||||||
|
applicationNo: item.applicationNo || item.paymentNo || '-',
|
||||||
|
usedAmount: Number(item.usedAmount || 0),
|
||||||
|
useDeptName: item.useDeptName || item.deptName || '-',
|
||||||
|
statusName: item.statusName || item.approvalStatusName || '已审核',
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('票据详情加载失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
this.detailDialog.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
openEdit(row) {
|
openEdit(row) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/payment/bill-ledger/form',
|
path: '/payment/bill-ledger/form',
|
||||||
@@ -111,6 +210,26 @@ export default {
|
|||||||
this.page.size = size;
|
this.page.size = size;
|
||||||
this.loadData();
|
this.loadData();
|
||||||
},
|
},
|
||||||
|
detailValue(...fields) {
|
||||||
|
const value = fields
|
||||||
|
.map(field => this.detailDialog.row?.[field])
|
||||||
|
.find(item => item !== undefined && item !== null && item !== '');
|
||||||
|
return value === undefined ? '-' : value;
|
||||||
|
},
|
||||||
|
formatMoney(value) {
|
||||||
|
return Number(value || 0).toFixed(2);
|
||||||
|
},
|
||||||
|
formatRate(value) {
|
||||||
|
if (Number(value) === -1) return '';
|
||||||
|
return value === null || value === undefined || value === '' ? '-' : `${Number(value)}%`;
|
||||||
|
},
|
||||||
|
estimatedDiscountFee() {
|
||||||
|
return (
|
||||||
|
(Number(this.detailDialog.row.faceAmount || 0) *
|
||||||
|
Number(this.detailDialog.row.confirmedDiscountRate || 0)) /
|
||||||
|
100
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -75,51 +75,7 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息">
|
<section-card title="附件信息">
|
||||||
<el-table :data="form.attachments" border>
|
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
|
||||||
<el-table-column label="文件名" min-width="220">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-link
|
|
||||||
v-if="row.url || row.link"
|
|
||||||
type="primary"
|
|
||||||
:href="row.url || row.link"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ row.originalName || row.name || '附件' }}
|
|
||||||
</el-link>
|
|
||||||
<span v-else>{{ row.originalName || row.name || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="附件描述" min-width="240">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input v-model="row.description" :disabled="readonly" maxlength="200" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文件大小" width="120" align="center">
|
|
||||||
<template #default="{ row }">{{ fileSize(row) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="130" align="center" />
|
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
|
||||||
<el-table-column label="操作" width="100" align="center">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-link v-if="!readonly" type="danger" @click="form.attachments.splice($index, 1)"
|
|
||||||
>删除</el-link
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<vehicle-attachment-upload
|
|
||||||
v-if="!readonly"
|
|
||||||
v-model="form.attachments"
|
|
||||||
class="bill-payment-form-page__uploader"
|
|
||||||
:multiple="true"
|
|
||||||
:limit="20"
|
|
||||||
:max-size="500"
|
|
||||||
:file-types="attachmentFileTypes"
|
|
||||||
:show-file-list="false"
|
|
||||||
button-text="上传"
|
|
||||||
@change="normalizeAttachments"
|
|
||||||
/>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<div class="bill-payment-form-page__actions">
|
<div class="bill-payment-form-page__actions">
|
||||||
@@ -163,6 +119,11 @@
|
|||||||
<template #default="{ row }">{{ formatMoney(row.availableBalance) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.availableBalance) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="maturityDate" label="到期日期" min-width="120" />
|
<el-table-column prop="maturityDate" label="到期日期" min-width="120" />
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="selectBill(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="billDialog.visible = false">取消</el-button>
|
<el-button @click="billDialog.visible = false">取消</el-button>
|
||||||
@@ -177,6 +138,7 @@ import { Search } from '@element-plus/icons-vue';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/billPayment';
|
import * as api from '@/api/payment/billPayment';
|
||||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -194,6 +156,7 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BillPaymentForm',
|
name: 'BillPaymentForm',
|
||||||
|
components: { VehicleAttachmentTable },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Search,
|
Search,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="bill-ledger-table">
|
<section class="bill-ledger-table">
|
||||||
<div class="bill-ledger-table__toolbar">
|
<div class="bill-ledger-table__toolbar">
|
||||||
<div class="bill-ledger-table__title">汇票台账列表</div>
|
|
||||||
<el-button v-if="hasPermission('bill_ledger_add')" type="primary" @click="$emit('add')">
|
<el-button v-if="hasPermission('bill_ledger_add')" type="primary" @click="$emit('add')">
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -16,7 +15,10 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<el-link v-if="column.link" type="primary" @click="$emit('view', row)">{{
|
||||||
|
displayValue(row[column.prop])
|
||||||
|
}}</el-link>
|
||||||
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -66,7 +68,7 @@ export default {
|
|||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
page: { type: Object, required: true },
|
page: { type: Object, required: true },
|
||||||
},
|
},
|
||||||
emits: ['add', 'edit', 'delete', 'page-change', 'size-change'],
|
emits: ['add', 'view', 'edit', 'delete', 'page-change', 'size-change'],
|
||||||
data() {
|
data() {
|
||||||
return { columns: billLedgerTableColumns };
|
return { columns: billLedgerTableColumns };
|
||||||
},
|
},
|
||||||
@@ -94,13 +96,9 @@ export default {
|
|||||||
.bill-ledger-table__toolbar {
|
.bill-ledger-table__toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
.bill-ledger-table__title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.bill-ledger-table__links {
|
.bill-ledger-table__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -93,9 +93,9 @@
|
|||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
multiple
|
multiple
|
||||||
filterable
|
filterable
|
||||||
allow-create
|
|
||||||
default-first-option
|
|
||||||
:multiple-limit="3"
|
:multiple-limit="3"
|
||||||
|
placeholder="请选择客商开票信息邮箱"
|
||||||
|
no-data-text="当前客商开票信息未配置邮箱"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="email in departmentEmailOptions"
|
v-for="email in departmentEmailOptions"
|
||||||
@@ -192,20 +192,19 @@
|
|||||||
<div v-for="(sheet, sheetIndex) in form.sheets" :key="sheet.localId" class="invoice-sheet">
|
<div v-for="(sheet, sheetIndex) in form.sheets" :key="sheet.localId" class="invoice-sheet">
|
||||||
<div class="invoice-sheet__header">
|
<div class="invoice-sheet__header">
|
||||||
<span>第{{ sheetIndex + 1 }}张</span>
|
<span>第{{ sheetIndex + 1 }}张</span>
|
||||||
<el-link
|
|
||||||
v-if="!readonly && form.sheets.length > 1"
|
|
||||||
type="danger"
|
|
||||||
@click="removeSheet(sheetIndex)"
|
|
||||||
>删除</el-link
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="sheet.lines" border>
|
<el-table :data="sheet.lines" border>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column label="商品和服务分类" min-width="160">
|
<el-table-column label="商品和服务分类" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select v-model="row.goodsCategory" :disabled="readonly">
|
<el-select
|
||||||
|
v-model="row.goodsCategory"
|
||||||
|
:disabled="readonly"
|
||||||
|
filterable
|
||||||
|
@change="handleGoodsCategoryChange(row)"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in goodsCategoryOptions"
|
v-for="item in invoiceItemCategories"
|
||||||
:key="item"
|
:key="item"
|
||||||
:label="item"
|
:label="item"
|
||||||
:value="item"
|
:value="item"
|
||||||
@@ -215,12 +214,17 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="货物或服务简称" min-width="170">
|
<el-table-column label="货物或服务简称" min-width="170">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select v-model="row.goodsName" :disabled="readonly" filterable allow-create>
|
<el-select
|
||||||
|
v-model="row.goodsName"
|
||||||
|
:disabled="readonly"
|
||||||
|
filterable
|
||||||
|
@change="handleGoodsNameChange(row)"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in goodsNameOptions"
|
v-for="item in invoiceItemNames(row.goodsCategory)"
|
||||||
:key="item"
|
:key="item.shortName"
|
||||||
:label="item"
|
:label="item.shortName"
|
||||||
:value="item"
|
:value="item.shortName"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@@ -304,6 +308,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div v-if="!readonly && form.sheets.length > 1" class="invoice-sheet__actions">
|
||||||
|
<el-link type="danger" @click="removeSheet(sheetIndex)">删除</el-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
@@ -358,7 +365,15 @@
|
|||||||
<el-link v-if="column.link && row[column.prop]" type="primary">
|
<el-link v-if="column.link && row[column.prop]" type="primary">
|
||||||
{{ row[column.prop] }}
|
{{ row[column.prop] }}
|
||||||
</el-link>
|
</el-link>
|
||||||
|
<span
|
||||||
|
v-else-if="
|
||||||
|
['transportQuantity', 'mileage'].includes(column.prop) &&
|
||||||
|
Number(row[column.prop]) === -1
|
||||||
|
"
|
||||||
|
></span>
|
||||||
|
<span v-else-if="column.feeItemKeys">{{ formatMoney(getFeeItemValue(row, column.feeItemKeys)) }}</span>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">{{ transportTypeLabel(row.transportType) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -366,41 +381,7 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息">
|
<section-card title="附件信息">
|
||||||
<el-table :data="form.attachments" border>
|
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
|
||||||
<el-table-column prop="originalName" label="文件名" min-width="220" />
|
|
||||||
<el-table-column label="附件描述" min-width="240">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input v-model="row.description" :disabled="readonly" maxlength="200" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="size" label="文件大小" width="120" />
|
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
|
||||||
<el-table-column label="操作" width="110" align="center">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<div class="invoice-form-page__links">
|
|
||||||
<el-link type="primary" :href="row.url || row.link" target="_blank">查看</el-link>
|
|
||||||
<el-link v-if="!readonly" type="danger" @click="form.attachments.splice($index, 1)"
|
|
||||||
>删除</el-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<div class="invoice-form-page__attachment-upload">
|
|
||||||
<vehicle-attachment-upload
|
|
||||||
v-model="form.attachments"
|
|
||||||
:readonly="readonly"
|
|
||||||
:multiple="true"
|
|
||||||
:limit="20"
|
|
||||||
:max-size="500"
|
|
||||||
:file-types="attachmentFileTypes"
|
|
||||||
:show-file-list="false"
|
|
||||||
button-text="上传"
|
|
||||||
@change="normalizeAttachments"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="备注">
|
<section-card title="备注">
|
||||||
@@ -469,6 +450,11 @@
|
|||||||
<el-table-column label="可开票金额" min-width="140" align="right">
|
<el-table-column label="可开票金额" min-width="140" align="right">
|
||||||
<template #default="{ row }">{{ formatMoney(row.availableInvoiceAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.availableInvoiceAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="selectSettlement(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||||
@@ -481,13 +467,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/invoiceApplication';
|
import * as api from '@/api/payment/invoiceApplication';
|
||||||
|
import { getAll as getInvoiceItems } from '@/api/base/invoice-item';
|
||||||
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { invoiceApplicationDetailColumns } from '@/option/payment/invoiceApplication';
|
import { invoiceApplicationDetailColumns } from '@/option/payment/invoiceApplication';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
let localId = 0;
|
let localId = 0;
|
||||||
const emptyLine = () => ({
|
const emptyLine = () => ({
|
||||||
localId: ++localId,
|
localId: ++localId,
|
||||||
goodsCategory: '交通运输服务',
|
goodsCategory: '',
|
||||||
goodsName: '运费',
|
goodsName: '',
|
||||||
unit: '吨',
|
unit: '吨',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
unitPriceNoTax: 0,
|
unitPriceNoTax: 0,
|
||||||
@@ -525,6 +514,9 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InvoiceApplicationForm',
|
name: 'InvoiceApplicationForm',
|
||||||
|
components: {
|
||||||
|
VehicleAttachmentTable,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
@@ -533,29 +525,14 @@ export default {
|
|||||||
receiverInvoiceOptions: [],
|
receiverInvoiceOptions: [],
|
||||||
contactOptions: [],
|
contactOptions: [],
|
||||||
departmentEmailOptions: [],
|
departmentEmailOptions: [],
|
||||||
|
invoiceItems: [],
|
||||||
|
transportTypeOptions: [],
|
||||||
invoiceTypeOptions: api.invoiceTypeOptions,
|
invoiceTypeOptions: api.invoiceTypeOptions,
|
||||||
detailColumns: invoiceApplicationDetailColumns,
|
detailBaseColumns: invoiceApplicationDetailColumns.filter(
|
||||||
goodsCategoryOptions: ['交通运输服务', '生产生活服务', '其他现代服务'],
|
column => !['feeItemsJson', 'freightAmount'].includes(column.prop)
|
||||||
goodsNameOptions: ['运费', '装卸搬运服务', '过路费', '仓储服务', '代理服务费'],
|
),
|
||||||
|
feeItemColumns: [],
|
||||||
unitOptions: ['吨', '车', '次', '项'],
|
unitOptions: ['吨', '车', '次', '项'],
|
||||||
taxRateOptions: [0, 1, 3, 6, 9, 13],
|
|
||||||
attachmentFileTypes: [
|
|
||||||
'pdf',
|
|
||||||
'bmp',
|
|
||||||
'jpeg',
|
|
||||||
'png',
|
|
||||||
'jpg',
|
|
||||||
'doc',
|
|
||||||
'docx',
|
|
||||||
'ppt',
|
|
||||||
'pptx',
|
|
||||||
'xlsx',
|
|
||||||
'xls',
|
|
||||||
'eml',
|
|
||||||
'msg',
|
|
||||||
'zip',
|
|
||||||
'rar',
|
|
||||||
],
|
|
||||||
settlementDialog: {
|
settlementDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -566,9 +543,7 @@ export default {
|
|||||||
rules: {
|
rules: {
|
||||||
settlementIds: [{ validator: this.validateSettlements, trigger: 'change' }],
|
settlementIds: [{ validator: this.validateSettlements, trigger: 'change' }],
|
||||||
invoiceType: [{ required: true, message: '请选择发票类型', trigger: 'change' }],
|
invoiceType: [{ required: true, message: '请选择发票类型', trigger: 'change' }],
|
||||||
departmentEmails: [
|
departmentEmails: [{ validator: this.validateDepartmentEmails, trigger: 'change' }],
|
||||||
{ type: 'array', required: true, min: 1, message: '请选择部门邮箱', trigger: 'change' },
|
|
||||||
],
|
|
||||||
receiverInvoiceInfoId: [{ required: true, message: '请选择受票方单位', trigger: 'change' }],
|
receiverInvoiceInfoId: [{ required: true, message: '请选择受票方单位', trigger: 'change' }],
|
||||||
contactPhone: [{ pattern: /^\d{11}$/, message: '联系电话必须为11位数字', trigger: 'blur' }],
|
contactPhone: [{ pattern: /^\d{11}$/, message: '联系电话必须为11位数字', trigger: 'blur' }],
|
||||||
taxpayerNo: [{ validator: this.validateInvoiceInfoField, trigger: 'change' }],
|
taxpayerNo: [{ validator: this.validateInvoiceInfoField, trigger: 'change' }],
|
||||||
@@ -614,11 +589,19 @@ export default {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
allocatedTotal() {
|
detailColumns() {
|
||||||
return this.form.settlements.reduce(
|
return [...this.detailBaseColumns, ...this.feeItemColumns];
|
||||||
(total, item) => total + Number(item.allocatedInvoiceAmount || 0),
|
},
|
||||||
0
|
invoiceItemCategories() {
|
||||||
);
|
return [...new Set(this.invoiceItems.map(item => item.categoryName).filter(Boolean))];
|
||||||
|
},
|
||||||
|
taxRateOptions() {
|
||||||
|
const rates = new Set([0, 1, 3, 6, 9, 13]);
|
||||||
|
this.invoiceItems.forEach(item => {
|
||||||
|
const rate = Number(item.defaultTaxRate);
|
||||||
|
if (Number.isFinite(rate) && rate >= 0 && rate <= 100) rates.add(rate);
|
||||||
|
});
|
||||||
|
return [...rates].sort((left, right) => left - right);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -641,22 +624,25 @@ export default {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
parseEmails(value) {
|
||||||
|
const values = Array.isArray(value) ? value : [value];
|
||||||
|
return values
|
||||||
|
.flatMap(item => String(item || '').split(/[;,,;]/))
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
await Promise.all([this.loadInvoiceItems(), this.loadTransportTypeOptions()]);
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
const userEmail = userInfo.email || '';
|
|
||||||
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.form.applicantName = userInfo.realName || userInfo.userName || '';
|
this.form.applicantName = userInfo.realName || userInfo.userName || '';
|
||||||
if (userEmail) this.form.departmentEmails = [userEmail];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||||
this.form = {
|
this.form = {
|
||||||
...emptyForm(),
|
...emptyForm(),
|
||||||
...data,
|
...data,
|
||||||
departmentEmails: String(data.departmentEmails || '')
|
departmentEmails: this.parseEmails(data.departmentEmails),
|
||||||
.split(';')
|
|
||||||
.filter(Boolean),
|
|
||||||
settlements: (data.settlements || []).map(item => ({
|
settlements: (data.settlements || []).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
settlementId: item.formalSettlementId,
|
settlementId: item.formalSettlementId,
|
||||||
@@ -669,12 +655,76 @@ export default {
|
|||||||
attachments: this.parse(data.attachmentsJson),
|
attachments: this.parse(data.attachmentsJson),
|
||||||
};
|
};
|
||||||
if (!this.form.sheets.length) this.form.sheets = [emptySheet()];
|
if (!this.form.sheets.length) this.form.sheets = [emptySheet()];
|
||||||
|
this.form.sheets.forEach(sheet =>
|
||||||
|
sheet.lines.forEach(line => this.syncInvoiceItemLine(line, false))
|
||||||
|
);
|
||||||
this.selectedDetailIds = (data.details || []).map(item => item.formalSettlementDetailId);
|
this.selectedDetailIds = (data.details || []).map(item => item.formalSettlementDetailId);
|
||||||
if (this.settlementIds.length) {
|
if (this.settlementIds.length) {
|
||||||
await Promise.all([this.loadReceiverInformation(), this.loadSettlementDetails(false)]);
|
await Promise.all([this.loadReceiverInformation(), this.loadSettlementDetails(false)]);
|
||||||
}
|
}
|
||||||
|
if (this.readonly) this.$nextTick(() => this.$refs.formRef?.clearValidate('departmentEmails'));
|
||||||
this.$nextTick(this.restoreDetailSelection);
|
this.$nextTick(this.restoreDetailSelection);
|
||||||
},
|
},
|
||||||
|
async loadInvoiceItems() {
|
||||||
|
const data = this.unwrapData(await getInvoiceItems());
|
||||||
|
this.invoiceItems = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
this.form.sheets.forEach(sheet =>
|
||||||
|
sheet.lines.forEach(line => this.syncInvoiceItemLine(line, false))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async loadTransportTypeOptions() {
|
||||||
|
const data = this.unwrapData(await getDictionary({ code: 'transport_type' }));
|
||||||
|
const records = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
this.transportTypeOptions = records.map(item => ({
|
||||||
|
label: item.dictValue || item.label || item.name,
|
||||||
|
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
transportTypeLabel(value) {
|
||||||
|
if (!value) return '-';
|
||||||
|
return (
|
||||||
|
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
value
|
||||||
|
);
|
||||||
|
},
|
||||||
|
invoiceItemNames(categoryName) {
|
||||||
|
return this.invoiceItems.filter(item => item.categoryName === categoryName);
|
||||||
|
},
|
||||||
|
findInvoiceItem(line) {
|
||||||
|
return this.invoiceItems.find(
|
||||||
|
item => item.categoryName === line.goodsCategory && item.shortName === line.goodsName
|
||||||
|
);
|
||||||
|
},
|
||||||
|
syncInvoiceItemLine(line, clearInvalid = true) {
|
||||||
|
const categoryExists = this.invoiceItemCategories.includes(line.goodsCategory);
|
||||||
|
if (!categoryExists && clearInvalid) line.goodsCategory = '';
|
||||||
|
const item = this.findInvoiceItem(line);
|
||||||
|
if (item) {
|
||||||
|
line.taxRate = Number(item.defaultTaxRate || 0);
|
||||||
|
this.recalculateLine(line);
|
||||||
|
} else if (clearInvalid) {
|
||||||
|
line.goodsName = '';
|
||||||
|
line.taxRate = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGoodsCategoryChange(line) {
|
||||||
|
const names = this.invoiceItemNames(line.goodsCategory);
|
||||||
|
if (!names.some(item => item.shortName === line.goodsName)) line.goodsName = '';
|
||||||
|
line.taxRate = 0;
|
||||||
|
if (names.length === 1) {
|
||||||
|
line.goodsName = names[0].shortName;
|
||||||
|
this.handleGoodsNameChange(line);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGoodsNameChange(line) {
|
||||||
|
const item = this.findInvoiceItem(line);
|
||||||
|
if (!item) {
|
||||||
|
line.taxRate = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line.taxRate = Number(item.defaultTaxRate || 0);
|
||||||
|
this.recalculateLine(line);
|
||||||
|
},
|
||||||
validateSettlements(rule, value, callback) {
|
validateSettlements(rule, value, callback) {
|
||||||
if (!this.form.settlements.length) {
|
if (!this.form.settlements.length) {
|
||||||
callback(new Error('请选择正式结算单'));
|
callback(new Error('请选择正式结算单'));
|
||||||
@@ -715,6 +765,27 @@ export default {
|
|||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
|
validateDepartmentEmails(rule, value, callback) {
|
||||||
|
if (this.readonly) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emails = this.parseEmails(value);
|
||||||
|
if (!emails.length) {
|
||||||
|
callback(new Error('请选择部门邮箱'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (emails.length > 3) {
|
||||||
|
callback(new Error('部门邮箱最多选择3个'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const options = new Set(this.departmentEmailOptions.map(item => item.toLowerCase()));
|
||||||
|
if (emails.some(email => !options.has(email.toLowerCase()))) {
|
||||||
|
callback(new Error('部门邮箱必须选择当前客商开票信息中的邮箱'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
handleInvoiceTypeChange() {
|
handleInvoiceTypeChange() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.formRef?.validateField([
|
this.$refs.formRef?.validateField([
|
||||||
@@ -746,6 +817,12 @@ export default {
|
|||||||
this.$message.warning('请至少选择一张应付正式结算单');
|
this.$message.warning('请至少选择一张应付正式结算单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await this.applySettlements(rows);
|
||||||
|
},
|
||||||
|
async selectSettlement(row) {
|
||||||
|
await this.applySettlements([row]);
|
||||||
|
},
|
||||||
|
async applySettlements(rows) {
|
||||||
const first = rows[0];
|
const first = rows[0];
|
||||||
const incompatible = rows.some(
|
const incompatible = rows.some(
|
||||||
item =>
|
item =>
|
||||||
@@ -775,23 +852,84 @@ export default {
|
|||||||
async loadSettlementDetails(resetSelection = true) {
|
async loadSettlementDetails(resetSelection = true) {
|
||||||
this.detailRows =
|
this.detailRows =
|
||||||
this.unwrapData(await api.getSettlementDetails(this.settlementIds.join(','))) || [];
|
this.unwrapData(await api.getSettlementDetails(this.settlementIds.join(','))) || [];
|
||||||
|
this.buildFeeItemColumns();
|
||||||
if (resetSelection) this.selectedDetailIds = this.detailRows.map(item => item.id);
|
if (resetSelection) this.selectedDetailIds = this.detailRows.map(item => item.id);
|
||||||
this.$nextTick(this.restoreDetailSelection);
|
this.$nextTick(this.restoreDetailSelection);
|
||||||
},
|
},
|
||||||
|
parseFeeItems(value) {
|
||||||
|
if (!value) return {};
|
||||||
|
if (typeof value === 'object' && !Array.isArray(value)) return value;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildFeeItemColumns() {
|
||||||
|
const groupedNames = new Map();
|
||||||
|
this.detailRows.forEach(row => {
|
||||||
|
Object.keys(this.parseFeeItems(row.feeItemsJson || row.feeItems)).forEach(name => {
|
||||||
|
const normalizedName = this.normalizeFeeItemName(name);
|
||||||
|
if (!normalizedName) return;
|
||||||
|
const key = this.feeItemNameKey(name);
|
||||||
|
const group = groupedNames.get(key) || { label: normalizedName, keys: [] };
|
||||||
|
if (!group.keys.includes(name)) group.keys.push(name);
|
||||||
|
groupedNames.set(key, group);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.feeItemColumns = [...groupedNames.values()].map((group, index) => ({
|
||||||
|
prop: `feeItem_${index}`,
|
||||||
|
label: group.label,
|
||||||
|
minWidth: 130,
|
||||||
|
money: true,
|
||||||
|
feeItemKeys: group.keys,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
normalizeFeeItemName(name) {
|
||||||
|
return String(name || '')
|
||||||
|
.replace(/[\s\u200b-\u200d\ufeff]/g, '')
|
||||||
|
.trim();
|
||||||
|
},
|
||||||
|
feeItemNameKey(name) {
|
||||||
|
return this.normalizeFeeItemName(name).toLowerCase();
|
||||||
|
},
|
||||||
|
getFeeItemValue(row, names) {
|
||||||
|
const items = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
||||||
|
return names.reduce((total, name) => total + Number(items[name] || 0), 0);
|
||||||
|
},
|
||||||
async loadReceiverInformation() {
|
async loadReceiverInformation() {
|
||||||
const data = this.unwrapData(await api.getReceiverInformation(this.settlementIds.join(',')));
|
const data = this.unwrapData(await api.getReceiverInformation(this.settlementIds.join(',')));
|
||||||
this.receiverInvoiceOptions = data.invoiceInfos || [];
|
const invoiceInfos = data.invoices || data.invoiceInfos || data.customer?.invoices || [];
|
||||||
|
this.receiverInvoiceOptions = Array.isArray(invoiceInfos) ? invoiceInfos : [];
|
||||||
this.contactOptions = data.contacts || [];
|
this.contactOptions = data.contacts || [];
|
||||||
|
const invoiceInfoEmails = [
|
||||||
|
...this.receiverInvoiceOptions.flatMap(item => this.parseEmails(item.email)),
|
||||||
|
];
|
||||||
|
this.departmentEmailOptions = invoiceInfoEmails
|
||||||
|
.filter(Boolean)
|
||||||
|
.reduce((emails, email) => {
|
||||||
|
if (!emails.some(item => item.toLowerCase() === email.toLowerCase())) emails.push(email);
|
||||||
|
return emails;
|
||||||
|
}, []);
|
||||||
|
this.form.departmentEmails = this.form.departmentEmails
|
||||||
|
.filter(email =>
|
||||||
|
this.departmentEmailOptions.some(option => option.toLowerCase() === email.toLowerCase())
|
||||||
|
)
|
||||||
|
.slice(0, 3);
|
||||||
this.form.issuerName = data.issuerName || this.form.issuerName;
|
this.form.issuerName = data.issuerName || this.form.issuerName;
|
||||||
if (!this.form.receiverInvoiceInfoId) {
|
let invoiceInfo = this.receiverInvoiceOptions.find(
|
||||||
const invoiceInfo =
|
item => String(item.id) === String(this.form.receiverInvoiceInfoId)
|
||||||
|
);
|
||||||
|
if (!invoiceInfo) {
|
||||||
|
invoiceInfo =
|
||||||
this.receiverInvoiceOptions.find(item => item.isDefault === 1) ||
|
this.receiverInvoiceOptions.find(item => item.isDefault === 1) ||
|
||||||
this.receiverInvoiceOptions[0];
|
this.receiverInvoiceOptions[0];
|
||||||
|
}
|
||||||
if (invoiceInfo) {
|
if (invoiceInfo) {
|
||||||
this.form.receiverInvoiceInfoId = invoiceInfo.id;
|
this.form.receiverInvoiceInfoId = invoiceInfo.id;
|
||||||
this.handleReceiverChange(invoiceInfo.id);
|
this.handleReceiverChange(invoiceInfo.id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!this.form.contactName) {
|
if (!this.form.contactName) {
|
||||||
const contact =
|
const contact =
|
||||||
this.contactOptions.find(item => item.isDefault === 1) || this.contactOptions[0];
|
this.contactOptions.find(item => item.isDefault === 1) || this.contactOptions[0];
|
||||||
@@ -846,23 +984,9 @@ export default {
|
|||||||
const rate = Number(row.taxRate || 0) / 100;
|
const rate = Number(row.taxRate || 0) / 100;
|
||||||
row.taxAmount = rate ? Number((amount - amount / (1 + rate)).toFixed(2)) : 0;
|
row.taxAmount = rate ? Number((amount - amount / (1 + rate)).toFixed(2)) : 0;
|
||||||
},
|
},
|
||||||
normalizeAttachments(files) {
|
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
|
||||||
const userName = userInfo.realName || userInfo.userName || '';
|
|
||||||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
this.form.attachments = (files || []).map(file => ({
|
|
||||||
...file,
|
|
||||||
description: file.description || '',
|
|
||||||
uploadUserName: file.uploadUserName || userName,
|
|
||||||
uploadTime: file.uploadTime || time,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
validateBusiness() {
|
validateBusiness() {
|
||||||
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
||||||
if (this.invoiceAmount <= 0) throw new Error('本次开票金额必须大于0');
|
if (this.invoiceAmount <= 0) throw new Error('本次开票金额必须大于0');
|
||||||
if (Math.abs(this.invoiceAmount - this.allocatedTotal) > 0.001) {
|
|
||||||
throw new Error('结算单分摊金额合计必须等于本次开票金额');
|
|
||||||
}
|
|
||||||
for (const sheet of this.form.sheets) {
|
for (const sheet of this.form.sheets) {
|
||||||
if (!sheet.lines.length) throw new Error('每张发票至少需要一条商品行');
|
if (!sheet.lines.length) throw new Error('每张发票至少需要一条商品行');
|
||||||
const hasFreight = sheet.lines.some(line => line.goodsName === '运费');
|
const hasFreight = sheet.lines.some(line => line.goodsName === '运费');
|
||||||
@@ -953,6 +1077,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.invoice-form-page__form {
|
||||||
|
padding-bottom: 72px;
|
||||||
|
}
|
||||||
.invoice-form-page__form :deep(.el-select),
|
.invoice-form-page__form :deep(.el-select),
|
||||||
.invoice-form-page__form :deep(.el-input-number) {
|
.invoice-form-page__form :deep(.el-input-number) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -968,11 +1095,16 @@ export default {
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.invoice-sheet__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8px 12px 0;
|
||||||
|
}
|
||||||
.invoice-form-page__links {
|
.invoice-form-page__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
.invoice-form-page__remark :deep(.el-form-item__content) {
|
.invoice-form-page__remark :deep(.el-form-item__content) {
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
@@ -997,17 +1129,6 @@ export default {
|
|||||||
:global(.avue-layout--horizontal .invoice-form-page__actions) {
|
:global(.avue-layout--horizontal .invoice-form-page__actions) {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
.invoice-form-page__attachment-upload {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
.invoice-form-page__attachment-upload .vehicle-attachment-upload {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.invoice-form-page__attachment-upload .el-upload {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
.invoice-form-page__dialog-search {
|
.invoice-form-page__dialog-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -47,8 +47,7 @@
|
|||||||
|
|
||||||
<section class="invoice-page__table-panel">
|
<section class="invoice-page__table-panel">
|
||||||
<div class="invoice-page__toolbar">
|
<div class="invoice-page__toolbar">
|
||||||
<div class="invoice-page__table-title">开票申请单列表</div>
|
<div class="invoice-page__toolbar-left">
|
||||||
<div class="invoice-page__toolbar-actions">
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="hasPermission('invoice_application_sync')"
|
v-if="hasPermission('invoice_application_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -69,6 +68,8 @@
|
|||||||
@click="handleExport"
|
@click="handleExport"
|
||||||
>导出</el-button
|
>导出</el-button
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
<div class="invoice-page__toolbar-right">
|
||||||
<el-tooltip content="刷新" placement="top">
|
<el-tooltip content="刷新" placement="top">
|
||||||
<el-button text :icon="Refresh" @click="loadTable" />
|
<el-button text :icon="Refresh" @click="loadTable" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -83,10 +84,21 @@
|
|||||||
:key="column.prop"
|
:key="column.prop"
|
||||||
v-bind="column"
|
v-bind="column"
|
||||||
align="center"
|
align="center"
|
||||||
show-overflow-tooltip
|
:show-overflow-tooltip="column.prop !== 'settlementNos'"
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link v-if="column.link && row[column.prop]" type="primary" @click="openView(row)">
|
<div v-if="column.prop === 'settlementNos'" class="invoice-page__settlement-nos">
|
||||||
|
<span
|
||||||
|
v-for="settlementNo in settlementNumberList(row.settlementNos)"
|
||||||
|
:key="settlementNo"
|
||||||
|
class="invoice-page__settlement-no"
|
||||||
|
@click.stop="openSettlementDetail(settlementNo)"
|
||||||
|
>
|
||||||
|
{{ settlementNo }}
|
||||||
|
</span>
|
||||||
|
<span v-if="!settlementNumberList(row.settlementNos).length">-</span>
|
||||||
|
</div>
|
||||||
|
<el-link v-else-if="column.link && row[column.prop]" type="primary" @click="openView(row)">
|
||||||
{{ row[column.prop] }}
|
{{ row[column.prop] }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">
|
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">
|
||||||
@@ -220,6 +232,7 @@ import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import * as api from '@/api/payment/invoiceApplication';
|
import * as api from '@/api/payment/invoiceApplication';
|
||||||
|
import * as formalSettlementApi from '@/api/settlement/formalSettlement';
|
||||||
import { invoiceApplicationTableColumns } from '@/option/payment/invoiceApplication';
|
import { invoiceApplicationTableColumns } from '@/option/payment/invoiceApplication';
|
||||||
|
|
||||||
const emptyQuery = () => ({
|
const emptyQuery = () => ({
|
||||||
@@ -302,6 +315,34 @@ export default {
|
|||||||
query: { mode: 'view', id: row.id },
|
query: { mode: 'view', id: row.id },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
settlementNumberList(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.split(/[、,,]/)
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
async openSettlementDetail(settlementNo) {
|
||||||
|
const normalizedNo = String(settlementNo || '').trim();
|
||||||
|
if (!normalizedNo) return;
|
||||||
|
try {
|
||||||
|
const data = this.unwrapData(
|
||||||
|
await formalSettlementApi.getList(1, 10, { formalSettlementNo: normalizedNo })
|
||||||
|
);
|
||||||
|
const row = (data.records || []).find(
|
||||||
|
item => String(item.formalSettlementNo || '').trim() === normalizedNo
|
||||||
|
);
|
||||||
|
if (!row?.id) {
|
||||||
|
this.$message.warning('未找到对应的正式结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: '/settlement/formal-settlement/form',
|
||||||
|
query: { mode: 'view', id: row.id, name: '查看正式结算' },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('正式结算单详情加载失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
async openFlow(row) {
|
async openFlow(row) {
|
||||||
this.flowDialog.row = { ...row };
|
this.flowDialog.row = { ...row };
|
||||||
this.flowDialog.records = [];
|
this.flowDialog.records = [];
|
||||||
@@ -442,20 +483,33 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
.invoice-page__toolbar-actions,
|
.invoice-page__toolbar-left,
|
||||||
|
.invoice-page__toolbar-right,
|
||||||
.invoice-page__links {
|
.invoice-page__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.invoice-page__table-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.invoice-page__links {
|
.invoice-page__links {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.invoice-page__settlement-nos {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px 8px;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
.invoice-page__settlement-no {
|
||||||
|
color: #409eff;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.invoice-page__settlement-no:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.invoice-page__pagination {
|
.invoice-page__pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -83,66 +83,6 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="开户行">
|
|
||||||
<el-input v-model="form.bankName" disabled placeholder="从金蝶票据池带出" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="开户账号">
|
|
||||||
<el-input v-model="form.bankAccount" disabled placeholder="从金蝶票据池带出" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="开票行">
|
|
||||||
<el-input v-model="form.issuingBank" disabled placeholder="从金蝶票据池带出" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="电话">
|
|
||||||
<el-input v-model="form.phone" :disabled="readonly" maxlength="50" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="客户邮箱" prop="customerEmails">
|
|
||||||
<el-select
|
|
||||||
v-model="form.customerEmails"
|
|
||||||
:disabled="readonly"
|
|
||||||
multiple
|
|
||||||
filterable
|
|
||||||
allow-create
|
|
||||||
default-first-option
|
|
||||||
:multiple-limit="3"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="email in customerEmailOptions"
|
|
||||||
:key="email"
|
|
||||||
:label="email"
|
|
||||||
:value="email"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="部门邮箱" prop="departmentEmails">
|
|
||||||
<el-select
|
|
||||||
v-model="form.departmentEmails"
|
|
||||||
:disabled="readonly"
|
|
||||||
multiple
|
|
||||||
filterable
|
|
||||||
allow-create
|
|
||||||
default-first-option
|
|
||||||
:multiple-limit="3"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="email in departmentEmailOptions"
|
|
||||||
:key="email"
|
|
||||||
:label="email"
|
|
||||||
:value="email"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
@@ -178,52 +118,7 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息">
|
<section-card title="附件信息">
|
||||||
<template #extra>
|
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
||||||
<el-button
|
|
||||||
v-if="form.attachments.length"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
:icon="Download"
|
|
||||||
@click="downloadAttachments"
|
|
||||||
>批量下载</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<el-table :data="form.attachments" border>
|
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
|
||||||
<el-table-column label="文件名" min-width="220">
|
|
||||||
<template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="附件描述" min-width="240">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input v-model="row.description" :disabled="readonly" maxlength="200" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="size" label="文件大小" width="120" />
|
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
|
||||||
<el-table-column label="操作" width="120" align="center">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<div class="receipt-form-page__links">
|
|
||||||
<el-link type="primary" :href="row.url || row.link" target="_blank">查看</el-link>
|
|
||||||
<el-link v-if="!readonly" type="danger" @click="form.attachments.splice($index, 1)"
|
|
||||||
>删除</el-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<vehicle-attachment-upload
|
|
||||||
v-model="form.attachments"
|
|
||||||
:readonly="readonly"
|
|
||||||
:multiple="true"
|
|
||||||
:limit="20"
|
|
||||||
:max-size="500"
|
|
||||||
:file-types="attachmentFileTypes"
|
|
||||||
:show-file-list="false"
|
|
||||||
button-text="上传附件"
|
|
||||||
class="receipt-form-page__uploader"
|
|
||||||
@change="normalizeAttachments"
|
|
||||||
/>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="备注">
|
<section-card title="备注">
|
||||||
@@ -242,17 +137,15 @@
|
|||||||
<div class="receipt-form-page__actions">
|
<div class="receipt-form-page__actions">
|
||||||
<el-button @click="goBack">取消</el-button>
|
<el-button @click="goBack">取消</el-button>
|
||||||
<el-button v-if="!readonly" type="primary" plain @click="syncReferenceData">同步</el-button>
|
<el-button v-if="!readonly" type="primary" plain @click="syncReferenceData">同步</el-button>
|
||||||
<el-button
|
<el-button v-if="!readonly && canSave" type="primary" plain @click="saveDraft"
|
||||||
v-if="!readonly && canSave"
|
>保存</el-button
|
||||||
type="primary"
|
>
|
||||||
plain
|
|
||||||
@click="saveDraft"
|
|
||||||
>保存</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!readonly && canSave && hasPermission('invoice_receipt_submit')"
|
v-if="!readonly && canSave && hasPermission('invoice_receipt_submit')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="submitForm"
|
@click="submitForm"
|
||||||
>提交</el-button>
|
>提交</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@@ -289,6 +182,11 @@
|
|||||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
|
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="confirmInvoice(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
||||||
@@ -331,6 +229,11 @@
|
|||||||
<el-table-column label="已收票金额" min-width="140" align="right">
|
<el-table-column label="已收票金额" min-width="140" align="right">
|
||||||
<template #default="{ row }">{{ formatMoney(row.receivedInvoiceAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.receivedInvoiceAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="selectSettlement(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||||
@@ -341,9 +244,103 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Download, Search } from '@element-plus/icons-vue';
|
import { Search } from '@element-plus/icons-vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/invoiceReceipt';
|
import * as api from '@/api/payment/invoiceReceipt';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
|
const invoicePoolMockTemplates = [
|
||||||
|
{
|
||||||
|
id: 90000001,
|
||||||
|
invoiceNo: '25312000000000000101',
|
||||||
|
invoiceDate: '2026-08-25',
|
||||||
|
invoiceType: '增值税专用发票',
|
||||||
|
taxRate: 9,
|
||||||
|
invoiceAmount: 109000,
|
||||||
|
taxAmount: 9000,
|
||||||
|
receiverName: '华东供应链管理有限公司',
|
||||||
|
issuerName: '上海安捷运输有限公司',
|
||||||
|
bankName: '中国工商银行上海分行',
|
||||||
|
bankAccount: '1001000100000000019',
|
||||||
|
issuingBank: '中国工商银行上海分行营业部',
|
||||||
|
phone: '021-66880001',
|
||||||
|
customerEmails: 'finance@huadong.example.com',
|
||||||
|
departmentEmails: 'transport@huadong.example.com',
|
||||||
|
attachmentsJson: '[]',
|
||||||
|
kingdeeBillNo: 'KD-FP-20260825001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 90000002,
|
||||||
|
invoiceNo: '25312000000000000102',
|
||||||
|
invoiceDate: '2026-08-26',
|
||||||
|
invoiceType: '增值税普通发票',
|
||||||
|
taxRate: 6,
|
||||||
|
invoiceAmount: 53000,
|
||||||
|
taxAmount: 3000,
|
||||||
|
receiverName: '华东供应链管理有限公司',
|
||||||
|
issuerName: '江苏联运物流有限公司',
|
||||||
|
bankName: '中国建设银行南京分行',
|
||||||
|
bankAccount: '3201000100000000026',
|
||||||
|
issuingBank: '中国建设银行南京城南支行',
|
||||||
|
phone: '025-66880002',
|
||||||
|
customerEmails: 'finance@huadong.example.com',
|
||||||
|
departmentEmails: 'logistics@huadong.example.com',
|
||||||
|
attachmentsJson: '[]',
|
||||||
|
kingdeeBillNo: 'KD-FP-20260826002',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 90000003,
|
||||||
|
invoiceNo: '25312000000000000103',
|
||||||
|
invoiceDate: '2026-08-27',
|
||||||
|
invoiceType: '增值税专用发票',
|
||||||
|
taxRate: 3,
|
||||||
|
invoiceAmount: 20600,
|
||||||
|
taxAmount: 600,
|
||||||
|
receiverName: '华东供应链管理有限公司',
|
||||||
|
issuerName: '浙江通达物流有限公司',
|
||||||
|
bankName: '招商银行杭州分行',
|
||||||
|
bankAccount: '5719000100000000033',
|
||||||
|
issuingBank: '招商银行杭州高新支行',
|
||||||
|
phone: '0571-66880003',
|
||||||
|
customerEmails: 'finance@huadong.example.com',
|
||||||
|
departmentEmails: 'settlement@huadong.example.com',
|
||||||
|
attachmentsJson: '[]',
|
||||||
|
kingdeeBillNo: 'KD-FP-20260827003',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const randomDigits = length =>
|
||||||
|
Array.from({ length }, () => Math.floor(Math.random() * 10)).join('');
|
||||||
|
|
||||||
|
const formatMockDate = date => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInvoicePoolMockRows = () =>
|
||||||
|
invoicePoolMockTemplates.map((template, index) => {
|
||||||
|
const invoiceDateValue = new Date();
|
||||||
|
invoiceDateValue.setDate(invoiceDateValue.getDate() - Math.floor(Math.random() * 15));
|
||||||
|
const invoiceDate = formatMockDate(invoiceDateValue);
|
||||||
|
const invoiceAmount = Math.floor(10000 + Math.random() * 190000);
|
||||||
|
const taxAmount = Number(
|
||||||
|
(
|
||||||
|
(invoiceAmount * Number(template.taxRate || 0)) /
|
||||||
|
(100 + Number(template.taxRate || 0))
|
||||||
|
).toFixed(2)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...template,
|
||||||
|
id: Date.now() + index * 100 + Math.floor(Math.random() * 100),
|
||||||
|
invoiceNo: `25${randomDigits(18)}`,
|
||||||
|
invoiceDate,
|
||||||
|
invoiceAmount,
|
||||||
|
taxAmount,
|
||||||
|
kingdeeBillNo: `KD-FP-${invoiceDate.replaceAll('-', '')}${randomDigits(3)}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -359,6 +356,8 @@ const emptyForm = () => ({
|
|||||||
bankName: '',
|
bankName: '',
|
||||||
bankAccount: '',
|
bankAccount: '',
|
||||||
issuingBank: '',
|
issuingBank: '',
|
||||||
|
kingdeeBillNo: '',
|
||||||
|
kingdeeStatus: 'unsynced',
|
||||||
phone: '',
|
phone: '',
|
||||||
customerEmails: [],
|
customerEmails: [],
|
||||||
departmentEmails: [],
|
departmentEmails: [],
|
||||||
@@ -373,29 +372,16 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InvoiceReceiptForm',
|
name: 'InvoiceReceiptForm',
|
||||||
|
components: {
|
||||||
|
VehicleAttachmentTable,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Download,
|
|
||||||
Search,
|
Search,
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
|
invoicePoolMockRows: [],
|
||||||
customerEmailOptions: [],
|
customerEmailOptions: [],
|
||||||
departmentEmailOptions: [],
|
departmentEmailOptions: [],
|
||||||
attachmentFileTypes: [
|
|
||||||
'pdf',
|
|
||||||
'bmp',
|
|
||||||
'jpeg',
|
|
||||||
'png',
|
|
||||||
'jpg',
|
|
||||||
'doc',
|
|
||||||
'docx',
|
|
||||||
'ppt',
|
|
||||||
'pptx',
|
|
||||||
'xlsx',
|
|
||||||
'xls',
|
|
||||||
'eml',
|
|
||||||
'msg',
|
|
||||||
'zip',
|
|
||||||
],
|
|
||||||
invoiceDialog: { visible: false, loading: false, keyword: '', rows: [], current: null },
|
invoiceDialog: { visible: false, loading: false, keyword: '', rows: [], current: null },
|
||||||
settlementDialog: {
|
settlementDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -433,12 +419,6 @@ export default {
|
|||||||
settlementIds() {
|
settlementIds() {
|
||||||
return this.form.settlements.map(item => item.formalSettlementId || item.settlementId);
|
return this.form.settlements.map(item => item.formalSettlementId || item.settlementId);
|
||||||
},
|
},
|
||||||
allocatedTotal() {
|
|
||||||
return this.form.settlements.reduce(
|
|
||||||
(total, item) => total + Number(item.allocatedInvoiceAmount || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
@@ -467,6 +447,7 @@ export default {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
this.invoicePoolMockRows = createInvoicePoolMockRows();
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
const userEmail = userInfo.email || '';
|
const userEmail = userInfo.email || '';
|
||||||
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
||||||
@@ -518,30 +499,43 @@ export default {
|
|||||||
async openInvoiceDialog() {
|
async openInvoiceDialog() {
|
||||||
this.invoiceDialog.visible = true;
|
this.invoiceDialog.visible = true;
|
||||||
this.invoiceDialog.keyword = this.form.invoiceNo || '';
|
this.invoiceDialog.keyword = this.form.invoiceNo || '';
|
||||||
|
if (!this.recordId) this.invoicePoolMockRows = createInvoicePoolMockRows();
|
||||||
await this.loadInvoicePool();
|
await this.loadInvoicePool();
|
||||||
},
|
},
|
||||||
async loadInvoicePool() {
|
async loadInvoicePool() {
|
||||||
this.invoiceDialog.loading = true;
|
this.invoiceDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
this.invoiceDialog.rows =
|
if (!this.recordId) {
|
||||||
this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword)) || [];
|
this.invoiceDialog.rows = this.filterInvoicePoolMockRows(this.invoiceDialog.keyword);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword));
|
||||||
|
this.invoiceDialog.rows = Array.isArray(data) ? data : data?.records || [];
|
||||||
} finally {
|
} finally {
|
||||||
this.invoiceDialog.loading = false;
|
this.invoiceDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
filterInvoicePoolMockRows(keyword) {
|
||||||
|
const normalizedKeyword = String(keyword || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (!normalizedKeyword) return this.invoicePoolMockRows.map(item => ({ ...item }));
|
||||||
|
return this.invoicePoolMockRows
|
||||||
|
.filter(item =>
|
||||||
|
[item.invoiceNo, item.issuerName, item.receiverName].some(value =>
|
||||||
|
String(value || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(normalizedKeyword)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(item => ({ ...item }));
|
||||||
|
},
|
||||||
confirmInvoice(row, options = {}) {
|
confirmInvoice(row, options = {}) {
|
||||||
const selected = row?.id ? row : this.invoiceDialog.current;
|
const selected = row?.id ? row : this.invoiceDialog.current;
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
this.$message.warning('请选择一张金蝶票据池发票');
|
this.$message.warning('请选择一张金蝶票据池发票');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.form.settlements.length) {
|
|
||||||
const first = this.form.settlements[0];
|
|
||||||
if (selected.receiverName !== first.payerName || selected.issuerName !== first.payeeName) {
|
|
||||||
this.$message.warning('金蝶发票的开票单位、受票单位与结算单收付款方不一致');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const preserveUserInput = options.preserveUserInput === true;
|
const preserveUserInput = options.preserveUserInput === true;
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
kingdeeInvoicePoolId: selected.id,
|
kingdeeInvoicePoolId: selected.id,
|
||||||
@@ -556,6 +550,8 @@ export default {
|
|||||||
bankName: selected.bankName,
|
bankName: selected.bankName,
|
||||||
bankAccount: selected.bankAccount,
|
bankAccount: selected.bankAccount,
|
||||||
issuingBank: selected.issuingBank,
|
issuingBank: selected.issuingBank,
|
||||||
|
kingdeeBillNo: selected.kingdeeBillNo || '',
|
||||||
|
kingdeeStatus: selected.kingdeeStatus || 'unsynced',
|
||||||
phone: preserveUserInput ? this.form.phone : selected.phone || '',
|
phone: preserveUserInput ? this.form.phone : selected.phone || '',
|
||||||
customerEmails: preserveUserInput
|
customerEmails: preserveUserInput
|
||||||
? this.form.customerEmails
|
? this.form.customerEmails
|
||||||
@@ -608,13 +604,6 @@ export default {
|
|||||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
this.form.kingdeeInvoicePoolId &&
|
|
||||||
(this.form.receiverName !== first.payerName || this.form.issuerName !== first.payeeName)
|
|
||||||
) {
|
|
||||||
this.$message.warning('金蝶发票的开票单位、受票单位与结算单收付款方不一致');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.form.settlements = rows.map(item => ({
|
this.form.settlements = rows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
formalSettlementId: item.id,
|
formalSettlementId: item.id,
|
||||||
@@ -629,6 +618,10 @@ export default {
|
|||||||
await this.loadReferenceInformation();
|
await this.loadReferenceInformation();
|
||||||
this.$refs.formRef?.validateField('settlementIds').catch(() => {});
|
this.$refs.formRef?.validateField('settlementIds').catch(() => {});
|
||||||
},
|
},
|
||||||
|
async selectSettlement(row) {
|
||||||
|
this.settlementDialog.selection = [row];
|
||||||
|
await this.confirmSettlements();
|
||||||
|
},
|
||||||
async loadReferenceInformation() {
|
async loadReferenceInformation() {
|
||||||
const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(',')));
|
const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(',')));
|
||||||
this.customerEmailOptions = data.customerEmails || [];
|
this.customerEmailOptions = data.customerEmails || [];
|
||||||
@@ -645,28 +638,6 @@ export default {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
normalizeAttachments(files) {
|
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
|
||||||
const userName = userInfo.realName || userInfo.userName || '';
|
|
||||||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
this.form.attachments = (files || []).map(file => ({
|
|
||||||
...file,
|
|
||||||
description: file.description || '',
|
|
||||||
uploadUserName: file.uploadUserName || userName,
|
|
||||||
uploadTime: file.uploadTime || time,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
downloadAttachments() {
|
|
||||||
this.form.attachments.forEach(file => {
|
|
||||||
const url = file.url || file.link;
|
|
||||||
if (!url) return;
|
|
||||||
const anchor = document.createElement('a');
|
|
||||||
anchor.href = url;
|
|
||||||
anchor.download = file.originalName || file.name || '';
|
|
||||||
anchor.target = '_blank';
|
|
||||||
anchor.click();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openSettlementDetail(row) {
|
openSettlementDetail(row) {
|
||||||
const id = row.formalSettlementId || row.settlementId || row.id;
|
const id = row.formalSettlementId || row.settlementId || row.id;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -678,9 +649,6 @@ export default {
|
|||||||
validateBusiness() {
|
validateBusiness() {
|
||||||
const invoiceAmount = Number(this.form.invoiceAmount || 0);
|
const invoiceAmount = Number(this.form.invoiceAmount || 0);
|
||||||
if (invoiceAmount <= 0) throw new Error('开票金额必须大于0');
|
if (invoiceAmount <= 0) throw new Error('开票金额必须大于0');
|
||||||
if (Math.abs(this.allocatedTotal - invoiceAmount) > 0.001) {
|
|
||||||
throw new Error('分摊发票金额总和必须等于开票金额');
|
|
||||||
}
|
|
||||||
for (const row of this.form.settlements) {
|
for (const row of this.form.settlements) {
|
||||||
const allocated = Number(row.allocatedInvoiceAmount);
|
const allocated = Number(row.allocatedInvoiceAmount);
|
||||||
if (!Number.isFinite(allocated) || allocated < 0) {
|
if (!Number.isFinite(allocated) || allocated < 0) {
|
||||||
@@ -695,6 +663,19 @@ export default {
|
|||||||
return {
|
return {
|
||||||
id: this.form.id,
|
id: this.form.id,
|
||||||
kingdeeInvoicePoolId: this.form.kingdeeInvoicePoolId,
|
kingdeeInvoicePoolId: this.form.kingdeeInvoicePoolId,
|
||||||
|
invoiceNo: this.form.invoiceNo,
|
||||||
|
invoiceDate: this.form.invoiceDate,
|
||||||
|
invoiceType: this.form.invoiceType,
|
||||||
|
taxRate: this.form.taxRate,
|
||||||
|
invoiceAmount: this.form.invoiceAmount,
|
||||||
|
taxAmount: this.form.taxAmount,
|
||||||
|
receiverName: this.form.receiverName,
|
||||||
|
issuerName: this.form.issuerName,
|
||||||
|
bankName: this.form.bankName,
|
||||||
|
bankAccount: this.form.bankAccount,
|
||||||
|
issuingBank: this.form.issuingBank,
|
||||||
|
kingdeeBillNo: this.form.kingdeeBillNo,
|
||||||
|
kingdeeStatus: this.form.kingdeeStatus,
|
||||||
phone: this.form.phone,
|
phone: this.form.phone,
|
||||||
customerEmails: this.form.customerEmails.join(';'),
|
customerEmails: this.form.customerEmails.join(';'),
|
||||||
departmentEmails: this.form.departmentEmails.join(';'),
|
departmentEmails: this.form.departmentEmails.join(';'),
|
||||||
@@ -731,7 +712,10 @@ export default {
|
|||||||
this.$message.warning('请先选择金蝶票据池发票');
|
this.$message.warning('请先选择金蝶票据池发票');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rows = this.unwrapData(await api.getInvoicePool(this.form.invoiceNo)) || [];
|
const data = this.recordId
|
||||||
|
? this.unwrapData(await api.getInvoicePool(this.form.invoiceNo))
|
||||||
|
: this.filterInvoicePoolMockRows(this.form.invoiceNo);
|
||||||
|
const rows = Array.isArray(data) ? data : data?.records || [];
|
||||||
const invoice = rows.find(item => item.invoiceNo === this.form.invoiceNo);
|
const invoice = rows.find(item => item.invoiceNo === this.form.invoiceNo);
|
||||||
if (!invoice) {
|
if (!invoice) {
|
||||||
this.$message.warning('金蝶票据池中未查询到该发票');
|
this.$message.warning('金蝶票据池中未查询到该发票');
|
||||||
@@ -755,6 +739,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.receipt-form-page__form {
|
||||||
|
padding-bottom: 72px;
|
||||||
|
}
|
||||||
.receipt-form-page__form :deep(.el-form-item) {
|
.receipt-form-page__form :deep(.el-form-item) {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
@@ -762,18 +749,9 @@ export default {
|
|||||||
.receipt-form-page__form :deep(.el-input-number) {
|
.receipt-form-page__form :deep(.el-input-number) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.receipt-form-page__links {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.receipt-form-page__remark :deep(.el-form-item__content) {
|
.receipt-form-page__remark :deep(.el-form-item__content) {
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
.receipt-form-page__uploader {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
.receipt-form-page__actions {
|
.receipt-form-page__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -56,8 +56,7 @@
|
|||||||
|
|
||||||
<section class="receipt-page__table-panel">
|
<section class="receipt-page__table-panel">
|
||||||
<div class="receipt-page__toolbar">
|
<div class="receipt-page__toolbar">
|
||||||
<div class="receipt-page__table-title">收票单列表</div>
|
<div class="receipt-page__toolbar-left">
|
||||||
<div class="receipt-page__toolbar-actions">
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="hasPermission('invoice_receipt_sync')"
|
v-if="hasPermission('invoice_receipt_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -75,6 +74,8 @@
|
|||||||
@click="handleExport"
|
@click="handleExport"
|
||||||
>导出</el-button
|
>导出</el-button
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
<div class="receipt-page__toolbar-right">
|
||||||
<el-tooltip content="刷新" placement="top">
|
<el-tooltip content="刷新" placement="top">
|
||||||
<el-button text :icon="Refresh" @click="loadTable" />
|
<el-button text :icon="Refresh" @click="loadTable" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -444,11 +445,8 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
.receipt-page__table-title {
|
.receipt-page__toolbar-left,
|
||||||
font-size: 16px;
|
.receipt-page__toolbar-right,
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.receipt-page__toolbar-actions,
|
|
||||||
.receipt-page__links {
|
.receipt-page__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -41,30 +41,61 @@
|
|||||||
></el-form-item
|
></el-form-item
|
||||||
></el-col
|
></el-col
|
||||||
>
|
>
|
||||||
<el-col :span="6"
|
<el-col :span="6">
|
||||||
><el-form-item label="所属项目"
|
<el-form-item label="所属项目">
|
||||||
><el-input v-model="form.projectName" disabled
|
<el-select
|
||||||
><template v-if="form.paymentType === 'project_advance'" #append
|
v-if="form.paymentType === 'project_advance'"
|
||||||
><el-button :disabled="readonly" @click="openReference">选择</el-button></template
|
v-model="form.projectId"
|
||||||
></el-input
|
:disabled="readonly"
|
||||||
></el-form-item
|
filterable
|
||||||
></el-col
|
clearable
|
||||||
|
:loading="projectLoading"
|
||||||
|
placeholder="请选择项目"
|
||||||
|
@change="handleProjectChange"
|
||||||
>
|
>
|
||||||
<el-col :span="6"
|
<el-option
|
||||||
><el-form-item label="合同名称"
|
v-for="item in projectOptions"
|
||||||
><el-input v-model="form.contractName" disabled /></el-form-item
|
:key="item.id"
|
||||||
></el-col>
|
:label="item.projectName || item.projectShortName || item.projectCode"
|
||||||
<el-col :span="6"
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input v-else v-model="form.projectName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="合同名称">
|
||||||
|
<el-select
|
||||||
|
v-if="form.paymentType === 'project_advance'"
|
||||||
|
v-model="form.contractId"
|
||||||
|
:disabled="readonly || !form.projectId"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
:loading="contractLoading"
|
||||||
|
placeholder="请先选择项目"
|
||||||
|
@change="handleAdvanceContractChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.contractName || item.contractNo"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input v-else v-model="form.contractName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="form.paymentType !== 'project_advance'" :span="6"
|
||||||
><el-form-item label="结算金额" prop="settlementAmount"
|
><el-form-item label="结算金额" prop="settlementAmount"
|
||||||
><el-input-number
|
><el-input-number
|
||||||
v-model="form.settlementAmount"
|
v-model="form.settlementAmount"
|
||||||
:disabled="readonly || form.paymentType !== 'project_advance'"
|
:disabled="readonly"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
style="width: 100%" /></el-form-item
|
style="width: 100%" /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col v-if="form.paymentType !== 'project_advance'" :span="6"
|
||||||
><el-form-item label="可付款金额"
|
><el-form-item label="可付款金额"
|
||||||
><el-input v-model="form.payableAmount" disabled /></el-form-item
|
><el-input v-model="form.payableAmount" disabled /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
@@ -73,7 +104,7 @@
|
|||||||
><el-input v-model="form.billType" disabled /></el-form-item
|
><el-input v-model="form.billType" disabled /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6"
|
||||||
><el-form-item label="付款比例" prop="paymentRatio"
|
><el-form-item label="付款比例(%)" prop="paymentRatio"
|
||||||
><div class="payment-form-page__percentage-input">
|
><div class="payment-form-page__percentage-input">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="form.paymentRatio"
|
v-model="form.paymentRatio"
|
||||||
@@ -108,7 +139,7 @@
|
|||||||
v-bind="item" /></el-select></el-form-item
|
v-bind="item" /></el-select></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col v-if="billPayment" :span="6"
|
<el-col v-if="billPayment" :span="6"
|
||||||
><el-form-item label="汇票票据" prop="billLedgerId"
|
><el-form-item label="汇票单号" prop="billLedgerId"
|
||||||
><el-select
|
><el-select
|
||||||
v-model="form.billLedgerId"
|
v-model="form.billLedgerId"
|
||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
@@ -184,7 +215,7 @@
|
|||||||
label="可付款金额"
|
label="可付款金额"
|
||||||
min-width="130" /><el-table-column
|
min-width="130" /><el-table-column
|
||||||
prop="paymentRatio"
|
prop="paymentRatio"
|
||||||
label="付款比例"
|
label="付款比例(%)"
|
||||||
min-width="110" /><el-table-column
|
min-width="110" /><el-table-column
|
||||||
prop="appliedAmount"
|
prop="appliedAmount"
|
||||||
label="付款金额"
|
label="付款金额"
|
||||||
@@ -395,8 +426,9 @@
|
|||||||
v-loading="formalPage.loading"
|
v-loading="formalPage.loading"
|
||||||
:data="formalRows"
|
:data="formalRows"
|
||||||
border
|
border
|
||||||
@row-click="selectFormal"
|
@selection-change="formalSelection = $event"
|
||||||
>
|
>
|
||||||
|
<el-table-column type="selection" width="50" />
|
||||||
<el-table-column prop="formalSettlementNo" label="结算单号" />
|
<el-table-column prop="formalSettlementNo" label="结算单号" />
|
||||||
<el-table-column prop="projectName" label="所属项目" />
|
<el-table-column prop="projectName" label="所属项目" />
|
||||||
<el-table-column prop="contractName" label="合同名称" />
|
<el-table-column prop="contractName" label="合同名称" />
|
||||||
@@ -407,6 +439,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div class="payment-form-page__reference-confirm">
|
||||||
|
<el-button type="primary" :disabled="!formalSelection.length" @click="confirmFormalSelection">确定</el-button>
|
||||||
|
</div>
|
||||||
<div class="payment-form-page__reference-pagination">
|
<div class="payment-form-page__reference-pagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="formalPage.current"
|
v-model:current-page="formalPage.current"
|
||||||
@@ -420,7 +455,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="预结算单" name="pre">
|
<el-tab-pane label="预结算单" name="pre">
|
||||||
<el-table v-loading="prePage.loading" :data="preRows" border @row-click="selectPre">
|
<el-table v-loading="prePage.loading" :data="preRows" border @selection-change="preSelection = $event">
|
||||||
|
<el-table-column type="selection" width="50" />
|
||||||
<el-table-column prop="preSettlementNo" label="结算单号" />
|
<el-table-column prop="preSettlementNo" label="结算单号" />
|
||||||
<el-table-column prop="projectName" label="所属项目" />
|
<el-table-column prop="projectName" label="所属项目" />
|
||||||
<el-table-column prop="contractName" label="合同名称" />
|
<el-table-column prop="contractName" label="合同名称" />
|
||||||
@@ -442,6 +478,9 @@
|
|||||||
@size-change="handlePreSizeChange"
|
@size-change="handlePreSizeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="payment-form-page__reference-confirm">
|
||||||
|
<el-button type="primary" :disabled="!preSelection.length" @click="confirmPreSelection">确定</el-button>
|
||||||
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs></div
|
</el-tabs></div
|
||||||
></el-dialog>
|
></el-dialog>
|
||||||
@@ -496,6 +535,7 @@ import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/paymentApplication';
|
import * as api from '@/api/payment/paymentApplication';
|
||||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||||
|
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||||
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
||||||
import * as formalApi from '@/api/settlement/formalSettlement';
|
import * as formalApi from '@/api/settlement/formalSettlement';
|
||||||
import * as preApi from '@/api/settlement/preSettlement';
|
import * as preApi from '@/api/settlement/preSettlement';
|
||||||
@@ -521,7 +561,7 @@ const ATTACHMENT_MATERIALS = {
|
|||||||
contract: {
|
contract: {
|
||||||
key: 'contract',
|
key: 'contract',
|
||||||
label: '合同签章文件',
|
label: '合同签章文件',
|
||||||
keywords: ['合同签章文件', '合同签章', '签章'],
|
keywords: ['合同签章文件', '合同签章', '签章', '合同'],
|
||||||
},
|
},
|
||||||
specialApproval: {
|
specialApproval: {
|
||||||
key: 'specialApproval',
|
key: 'specialApproval',
|
||||||
@@ -543,6 +583,7 @@ const emptyForm = () => ({
|
|||||||
paymentNo: '',
|
paymentNo: '',
|
||||||
paymentType: 'project_advance',
|
paymentType: 'project_advance',
|
||||||
settlementId: null,
|
settlementId: null,
|
||||||
|
settlementIds: [],
|
||||||
preSettlementId: null,
|
preSettlementId: null,
|
||||||
preSettlementIds: [],
|
preSettlementIds: [],
|
||||||
projectId: null,
|
projectId: null,
|
||||||
@@ -594,7 +635,9 @@ export default {
|
|||||||
contractName: '',
|
contractName: '',
|
||||||
},
|
},
|
||||||
formalRows: [],
|
formalRows: [],
|
||||||
|
formalSelection: [],
|
||||||
preRows: [],
|
preRows: [],
|
||||||
|
preSelection: [],
|
||||||
formalPage: {
|
formalPage: {
|
||||||
current: 1,
|
current: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
@@ -607,6 +650,10 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
},
|
},
|
||||||
|
projectOptions: [],
|
||||||
|
projectLoading: false,
|
||||||
|
contractOptions: [],
|
||||||
|
contractLoading: false,
|
||||||
contractRows: [],
|
contractRows: [],
|
||||||
billOptions: [],
|
billOptions: [],
|
||||||
billLoading: false,
|
billLoading: false,
|
||||||
@@ -674,13 +721,11 @@ export default {
|
|||||||
return this.hasPermission(code);
|
return this.hasPermission(code);
|
||||||
},
|
},
|
||||||
billPayment() {
|
billPayment() {
|
||||||
return ['bank_draft', 'commercial_draft'].includes(this.form.paymentMethod);
|
return this.form.paymentMethod !== 'bank_transfer';
|
||||||
},
|
},
|
||||||
paymentAmountBase() {
|
paymentAmountBase() {
|
||||||
const amount =
|
if (this.form.paymentType === 'project_advance') return 0;
|
||||||
this.form.paymentType === 'project_advance'
|
const amount = this.form.settlementAmount;
|
||||||
? this.form.payableAmount
|
|
||||||
: this.form.settlementAmount;
|
|
||||||
return Number(amount || 0);
|
return Number(amount || 0);
|
||||||
},
|
},
|
||||||
referenceLabel() {
|
referenceLabel() {
|
||||||
@@ -697,7 +742,10 @@ export default {
|
|||||||
return this.referenceRows.map(item => ({
|
return this.referenceRows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
paymentRatio: this.form.paymentRatio,
|
paymentRatio: this.form.paymentRatio,
|
||||||
appliedAmount: Number(
|
appliedAmount:
|
||||||
|
item.fixedAppliedAmount === true
|
||||||
|
? Number(item.appliedAmount || 0)
|
||||||
|
: Number(
|
||||||
(
|
(
|
||||||
(Number(item.settlementAmount || 0) * Number(this.form.paymentRatio || 0)) /
|
(Number(item.settlementAmount || 0) * Number(this.form.paymentRatio || 0)) /
|
||||||
100
|
100
|
||||||
@@ -809,6 +857,10 @@ export default {
|
|||||||
this.$nextTick(() => this.$refs.formRef?.validateField('referenceId').catch(() => {}));
|
this.$nextTick(() => this.$refs.formRef?.validateField('referenceId').catch(() => {}));
|
||||||
},
|
},
|
||||||
validateSettlementAmount(rule, value, callback) {
|
validateSettlementAmount(rule, value, callback) {
|
||||||
|
if (this.form.paymentType === 'project_advance') {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const amount = Number(value);
|
const amount = Number(value);
|
||||||
if (!Number.isFinite(amount) || amount < 0) {
|
if (!Number.isFinite(amount) || amount < 0) {
|
||||||
callback(new Error('结算金额不能小于0'));
|
callback(new Error('结算金额不能小于0'));
|
||||||
@@ -823,7 +875,11 @@ export default {
|
|||||||
callback(new Error('申请付款金额不能小于0'));
|
callback(new Error('申请付款金额不能小于0'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (payableAmount > 0 && amount > payableAmount) {
|
if (
|
||||||
|
this.form.paymentType !== 'project_advance' &&
|
||||||
|
payableAmount > 0 &&
|
||||||
|
amount > payableAmount
|
||||||
|
) {
|
||||||
callback(new Error('申请付款金额不能超过可付款金额'));
|
callback(new Error('申请付款金额不能超过可付款金额'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -835,7 +891,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!value) {
|
if (!value) {
|
||||||
callback(new Error('请选择可用汇票'));
|
callback(new Error('请选择汇票单号'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selected = this.billOptions.find(item => String(item.id) === String(value));
|
const selected = this.billOptions.find(item => String(item.id) === String(value));
|
||||||
@@ -911,6 +967,10 @@ export default {
|
|||||||
const referenceRow = this.createReferenceRow(data);
|
const referenceRow = this.createReferenceRow(data);
|
||||||
this.referenceRows = referenceRow.settlementNo ? [referenceRow] : [];
|
this.referenceRows = referenceRow.settlementNo ? [referenceRow] : [];
|
||||||
this.paymentRecords = data.paymentRecords || [];
|
this.paymentRecords = data.paymentRecords || [];
|
||||||
|
if (this.form.paymentType === 'project_advance') {
|
||||||
|
await this.loadProjectOptions();
|
||||||
|
await this.loadContractOptions(this.form.projectId);
|
||||||
|
}
|
||||||
await this.loadReceiptAccountOptions(false);
|
await this.loadReceiptAccountOptions(false);
|
||||||
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
|
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
|
||||||
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
||||||
@@ -921,8 +981,13 @@ export default {
|
|||||||
this.form.applyDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.applyDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
this.form.applicantName =
|
this.form.applicantName =
|
||||||
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
||||||
|
if (this.form.paymentType === 'project_advance') await this.loadProjectOptions();
|
||||||
|
if (this.transferPayload?.sourceFormalSettlements?.length) {
|
||||||
|
await this.loadTransferredFormalSettlements();
|
||||||
|
} else {
|
||||||
await this.loadTransferredPreSettlements();
|
await this.loadTransferredPreSettlements();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
initialAddPaymentType() {
|
initialAddPaymentType() {
|
||||||
const paymentType = String(this.$route.query.paymentType || '');
|
const paymentType = String(this.$route.query.paymentType || '');
|
||||||
@@ -938,7 +1003,12 @@ export default {
|
|||||||
.split(',')
|
.split(',')
|
||||||
.map(item => item.trim())
|
.map(item => item.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
return [...new Set([...payloadIds, ...queryIds].map(String))];
|
return [...new Set([...payloadIds, ...queryIds].map(String))].filter(id =>
|
||||||
|
this.isValidReferenceId(id)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isValidReferenceId(id) {
|
||||||
|
return id !== null && id !== undefined && String(id).trim() !== '-1';
|
||||||
},
|
},
|
||||||
async loadTransferredPreSettlements() {
|
async loadTransferredPreSettlements() {
|
||||||
const fallbackRows = this.transferPayload?.sourcePreSettlements || [];
|
const fallbackRows = this.transferPayload?.sourcePreSettlements || [];
|
||||||
@@ -973,6 +1043,97 @@ export default {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async loadTransferredFormalSettlements() {
|
||||||
|
const fallbackRows = this.transferPayload?.sourceFormalSettlements || [];
|
||||||
|
const rows = await Promise.all(
|
||||||
|
fallbackRows.map(async row => {
|
||||||
|
if (!this.isValidReferenceId(row.id)) return row;
|
||||||
|
try {
|
||||||
|
return { ...row, ...this.unwrapData(await formalApi.getDetail(row.id)) };
|
||||||
|
} catch (error) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.applyTransferredFormalSettlements(rows.filter(item => this.isValidReferenceId(item?.id)));
|
||||||
|
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData(rows)]);
|
||||||
|
},
|
||||||
|
applyTransferredFormalSettlements(rows = []) {
|
||||||
|
if (!rows.length) return;
|
||||||
|
const contractIds = new Set(rows.map(item => String(item.contractId || '')));
|
||||||
|
if (contractIds.size !== 1 || contractIds.has('')) {
|
||||||
|
this.$message.warning('所选正式结算单合同信息不一致,请返回后重新选择');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const first = rows[0];
|
||||||
|
const referenceRows = rows.map(item => ({
|
||||||
|
...this.createReferenceRow(item),
|
||||||
|
settlementNo: item.formalSettlementNo || item.settlementNo || '',
|
||||||
|
appliedAmount: Number(item.appliedAmount || 0),
|
||||||
|
fixedAppliedAmount: true,
|
||||||
|
}));
|
||||||
|
const settlementAmount = referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.settlementAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const payableAmount = referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.payableAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const appliedAmount = referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.appliedAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const paymentRatio = settlementAmount
|
||||||
|
? Number(((appliedAmount / settlementAmount) * 100).toFixed(2))
|
||||||
|
: 0;
|
||||||
|
const settlementIds = rows.map(item => item.settlementId || item.id).filter(Boolean);
|
||||||
|
this.amountSyncing = true;
|
||||||
|
this.referenceRows = referenceRows;
|
||||||
|
Object.assign(this.form, {
|
||||||
|
paymentType: 'settlement_payment',
|
||||||
|
settlementId: settlementIds[0] || null,
|
||||||
|
settlementIds,
|
||||||
|
preSettlementId: null,
|
||||||
|
preSettlementIds: [],
|
||||||
|
settlementNo: referenceRows
|
||||||
|
.map(item => item.settlementNo)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('、'),
|
||||||
|
preSettlementNo: '',
|
||||||
|
projectId: first.projectId,
|
||||||
|
projectName: first.projectName,
|
||||||
|
deptId: first.deptId,
|
||||||
|
deptName: first.deptName,
|
||||||
|
contractId: first.contractId,
|
||||||
|
contractNo: first.contractNo,
|
||||||
|
contractName: first.contractName,
|
||||||
|
payerName: first.payerName,
|
||||||
|
payeeName: first.payeeName,
|
||||||
|
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||||||
|
payableAmount: Number(payableAmount.toFixed(2)),
|
||||||
|
billType: '正式结算单',
|
||||||
|
paymentRatio,
|
||||||
|
appliedAmount: Number(appliedAmount.toFixed(2)),
|
||||||
|
invoices: rows.flatMap(item =>
|
||||||
|
this.createInvoiceRows(item.invoices || [], item.formalSettlementNo || item.settlementNo)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
this.paymentRecords = rows.flatMap(item =>
|
||||||
|
(item.paymentRecords || item.paymentApplications || []).map(record => ({
|
||||||
|
...record,
|
||||||
|
paidAmount: Number(record.paidAmount || 0),
|
||||||
|
paidDate: record.paidDate || record.paymentDate || record.createTime || '',
|
||||||
|
paymentNo: record.paymentNo || '',
|
||||||
|
voucherJson: record.voucherJson || '',
|
||||||
|
kingdeeBillNo: record.kingdeeBillNo || '',
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.amountSyncing = false;
|
||||||
|
this.$refs.formRef?.clearValidate();
|
||||||
|
});
|
||||||
|
},
|
||||||
createReferenceRow(row) {
|
createReferenceRow(row) {
|
||||||
const settlementAmount = Number(row.settlementAmount || 0);
|
const settlementAmount = Number(row.settlementAmount || 0);
|
||||||
const appliedAmount = Number(row.advanceAppliedAmount || row.appliedPaymentAmount || 0);
|
const appliedAmount = Number(row.advanceAppliedAmount || row.appliedPaymentAmount || 0);
|
||||||
@@ -1156,6 +1317,22 @@ export default {
|
|||||||
material.keywords.some(keyword => fileName.includes(keyword.toLocaleLowerCase())))
|
material.keywords.some(keyword => fileName.includes(keyword.toLocaleLowerCase())))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
resolveAttachmentTypeByFileName(file) {
|
||||||
|
let fileName = this.attachmentFileName(file) || this.attachmentFileUrl(file);
|
||||||
|
fileName = String(fileName || '').split(/[?#]/)[0].toLocaleLowerCase();
|
||||||
|
try {
|
||||||
|
fileName = decodeURIComponent(fileName);
|
||||||
|
} catch {}
|
||||||
|
const material = [...Object.values(ATTACHMENT_MATERIALS)]
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
Math.max(...b.keywords.map(String.length)) - Math.max(...a.keywords.map(String.length))
|
||||||
|
)
|
||||||
|
.find(item =>
|
||||||
|
item.keywords.some(keyword => fileName.includes(String(keyword).toLocaleLowerCase()))
|
||||||
|
);
|
||||||
|
return material ? MATERIAL_TYPE_MAP[material.key] : 'other';
|
||||||
|
},
|
||||||
hasAttachmentMaterial(material) {
|
hasAttachmentMaterial(material) {
|
||||||
const hasUploadedAttachment = (this.form.attachments || []).some(file =>
|
const hasUploadedAttachment = (this.form.attachments || []).some(file =>
|
||||||
this.attachmentMatchesMaterial(file, material)
|
this.attachmentMatchesMaterial(file, material)
|
||||||
@@ -1204,8 +1381,12 @@ export default {
|
|||||||
async loadCurrentSettlementDetails() {
|
async loadCurrentSettlementDetails() {
|
||||||
if (this.form.paymentType === 'project_advance') return [];
|
if (this.form.paymentType === 'project_advance') return [];
|
||||||
const requests = [];
|
const requests = [];
|
||||||
if (this.form.preSettlementId) requests.push(preApi.getDetail(this.form.preSettlementId));
|
if (this.isValidReferenceId(this.form.preSettlementId)) {
|
||||||
if (this.form.settlementId) requests.push(formalApi.getDetail(this.form.settlementId));
|
requests.push(preApi.getDetail(this.form.preSettlementId));
|
||||||
|
}
|
||||||
|
if (this.isValidReferenceId(this.form.settlementId)) {
|
||||||
|
requests.push(formalApi.getDetail(this.form.settlementId));
|
||||||
|
}
|
||||||
if (!requests.length) return [];
|
if (!requests.length) return [];
|
||||||
const responses = await Promise.all(requests);
|
const responses = await Promise.all(requests);
|
||||||
return responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
return responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
||||||
@@ -1279,15 +1460,32 @@ export default {
|
|||||||
handleTypeChange() {
|
handleTypeChange() {
|
||||||
this.form.invoices = [];
|
this.form.invoices = [];
|
||||||
this.paymentRecords = [];
|
this.paymentRecords = [];
|
||||||
if (this.form.paymentType === 'project_advance') {
|
Object.assign(this.form, {
|
||||||
this.form.settlementId = null;
|
settlementId: null,
|
||||||
this.form.preSettlementId = null;
|
settlementIds: [],
|
||||||
this.form.preSettlementIds = [];
|
preSettlementId: null,
|
||||||
this.form.settlementNo = '';
|
preSettlementIds: [],
|
||||||
this.form.preSettlementNo = '';
|
settlementNo: '',
|
||||||
|
preSettlementNo: '',
|
||||||
|
projectId: null,
|
||||||
|
projectName: '',
|
||||||
|
deptId: null,
|
||||||
|
deptName: '',
|
||||||
|
contractId: null,
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
payerName: '',
|
||||||
|
payeeName: '',
|
||||||
|
settlementAmount: 0,
|
||||||
|
payableAmount: 0,
|
||||||
|
appliedAmount: 0,
|
||||||
|
billType: '',
|
||||||
|
});
|
||||||
this.referenceRows = [];
|
this.referenceRows = [];
|
||||||
}
|
this.contractOptions = [];
|
||||||
|
this.clearReceiptAccount();
|
||||||
this.loadAttachmentRuleData([]);
|
this.loadAttachmentRuleData([]);
|
||||||
|
if (this.form.paymentType === 'project_advance') this.loadProjectOptions();
|
||||||
},
|
},
|
||||||
clearReceiptAccount() {
|
clearReceiptAccount() {
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
@@ -1300,6 +1498,108 @@ export default {
|
|||||||
customerRecords(response) {
|
customerRecords(response) {
|
||||||
return this.unwrapData(response)?.records || [];
|
return this.unwrapData(response)?.records || [];
|
||||||
},
|
},
|
||||||
|
async loadProjectOptions() {
|
||||||
|
if (this.projectLoading) return;
|
||||||
|
this.projectLoading = true;
|
||||||
|
try {
|
||||||
|
const response = await getProjectList(1, 9999, {});
|
||||||
|
this.projectOptions = this.customerRecords(response);
|
||||||
|
if (
|
||||||
|
this.form.projectId &&
|
||||||
|
this.form.projectName &&
|
||||||
|
!this.projectOptions.some(item => String(item.id) === String(this.form.projectId))
|
||||||
|
) {
|
||||||
|
this.projectOptions.unshift({
|
||||||
|
id: this.form.projectId,
|
||||||
|
projectName: this.form.projectName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.projectOptions = [];
|
||||||
|
this.$message.warning('项目列表加载失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
this.projectLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadContractOptions(projectId) {
|
||||||
|
if (!projectId) {
|
||||||
|
this.contractOptions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.contractLoading = true;
|
||||||
|
try {
|
||||||
|
const response = await formalApi.getContractOptions('', projectId);
|
||||||
|
this.contractOptions = (this.unwrapData(response) || []).filter(
|
||||||
|
item => String(item.projectId || '') === String(projectId)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
this.form.contractId &&
|
||||||
|
this.form.contractName &&
|
||||||
|
!this.contractOptions.some(item => String(item.id) === String(this.form.contractId))
|
||||||
|
) {
|
||||||
|
this.contractOptions.unshift({
|
||||||
|
id: this.form.contractId,
|
||||||
|
contractNo: this.form.contractNo,
|
||||||
|
contractName: this.form.contractName,
|
||||||
|
projectId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.contractOptions = [];
|
||||||
|
this.$message.warning('项目合同加载失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
this.contractLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleProjectChange(projectId) {
|
||||||
|
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||||||
|
this.form.projectName = project?.projectName || project?.projectShortName || '';
|
||||||
|
Object.assign(this.form, {
|
||||||
|
deptId: project?.undertakeDeptId || project?.businessDeptId || null,
|
||||||
|
deptName: project?.undertakeDeptName || project?.businessDeptName || '',
|
||||||
|
contractId: null,
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
payerName: '',
|
||||||
|
payeeName: '',
|
||||||
|
settlementAmount: 0,
|
||||||
|
payableAmount: 0,
|
||||||
|
appliedAmount: 0,
|
||||||
|
billType: '',
|
||||||
|
});
|
||||||
|
this.contractOptions = [];
|
||||||
|
this.clearReceiptAccount();
|
||||||
|
this.form.invoices = [];
|
||||||
|
this.paymentRecords = [];
|
||||||
|
await this.loadContractOptions(projectId);
|
||||||
|
await this.loadAttachmentRuleData([]);
|
||||||
|
},
|
||||||
|
async handleAdvanceContractChange(contractId) {
|
||||||
|
const contract = this.contractOptions.find(
|
||||||
|
item => String(item.id) === String(contractId)
|
||||||
|
);
|
||||||
|
if (!contract) {
|
||||||
|
Object.assign(this.form, {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
payerName: '',
|
||||||
|
payeeName: '',
|
||||||
|
});
|
||||||
|
this.clearReceiptAccount();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.assign(this.form, {
|
||||||
|
contractNo: contract.contractNo || '',
|
||||||
|
contractName: contract.contractName || '',
|
||||||
|
payerName: contract.payerName || contract.partyA || '',
|
||||||
|
payeeName: contract.payeeName || contract.partyB || '',
|
||||||
|
settlementAmount: 0,
|
||||||
|
payableAmount: 0,
|
||||||
|
billType: '',
|
||||||
|
});
|
||||||
|
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData([])]);
|
||||||
|
if (this.billPayment) this.loadBillOptions();
|
||||||
|
},
|
||||||
async loadReceiptAccountOptions(autoSelectDefault = true) {
|
async loadReceiptAccountOptions(autoSelectDefault = true) {
|
||||||
const payeeName = String(this.form.payeeName || '').trim();
|
const payeeName = String(this.form.payeeName || '').trim();
|
||||||
const preserveCurrentValue = autoSelectDefault === false;
|
const preserveCurrentValue = autoSelectDefault === false;
|
||||||
@@ -1483,12 +1783,10 @@ export default {
|
|||||||
this.loadPreReferences();
|
this.loadPreReferences();
|
||||||
},
|
},
|
||||||
async openReference() {
|
async openReference() {
|
||||||
|
if (this.form.paymentType === 'project_advance') return;
|
||||||
this.referenceVisible = true;
|
this.referenceVisible = true;
|
||||||
if (this.form.paymentType === 'project_advance') {
|
this.formalSelection = [];
|
||||||
const response = await formalApi.getContractOptions('');
|
this.preSelection = [];
|
||||||
this.contractRows = this.unwrapData(response) || [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.formalPage.current = 1;
|
this.formalPage.current = 1;
|
||||||
this.prePage.current = 1;
|
this.prePage.current = 1;
|
||||||
await this.loadSettlementReferences();
|
await this.loadSettlementReferences();
|
||||||
@@ -1498,7 +1796,6 @@ export default {
|
|||||||
this.form.invoices = [];
|
this.form.invoices = [];
|
||||||
this.paymentRecords = [];
|
this.paymentRecords = [];
|
||||||
this.clearReceiptAccount();
|
this.clearReceiptAccount();
|
||||||
const settlementType = row.settlementType || 'payable';
|
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
projectId: row.projectId,
|
projectId: row.projectId,
|
||||||
projectName: row.projectName,
|
projectName: row.projectName,
|
||||||
@@ -1507,21 +1804,24 @@ export default {
|
|||||||
contractId: row.id,
|
contractId: row.id,
|
||||||
contractNo: row.contractNo,
|
contractNo: row.contractNo,
|
||||||
contractName: row.contractName,
|
contractName: row.contractName,
|
||||||
payerName:
|
payerName: row.payerName || row.partyA || '',
|
||||||
row.payerName || (settlementType === 'receivable' ? row.partyB : row.partyA) || '',
|
payeeName: row.payeeName || row.partyB || '',
|
||||||
payeeName:
|
settlementAmount: 0,
|
||||||
row.payeeName || (settlementType === 'receivable' ? row.partyA : row.partyB) || '',
|
payableAmount: 0,
|
||||||
payableAmount: Number((Number(row.fundDemand || 0) * 10000).toFixed(2)),
|
appliedAmount: 0,
|
||||||
billType: row.settlementMode ? '项目预付' : '',
|
billType: '',
|
||||||
});
|
});
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData([])]);
|
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData([])]);
|
||||||
if (this.billPayment) this.loadBillOptions();
|
if (this.billPayment) this.loadBillOptions();
|
||||||
},
|
},
|
||||||
async selectFormal(row) {
|
async selectFormal(row) {
|
||||||
|
const validReferenceId = this.isValidReferenceId(row.id);
|
||||||
const [amountResponse, detailResponse] = await Promise.all([
|
const [amountResponse, detailResponse] = await Promise.all([
|
||||||
api.getReferenceAmount('settlement_payment', row.id, this.form.id),
|
validReferenceId
|
||||||
formalApi.getDetail(row.id),
|
? api.getReferenceAmount('settlement_payment', row.id, this.form.id)
|
||||||
|
: Promise.resolve(null),
|
||||||
|
validReferenceId ? formalApi.getDetail(row.id) : Promise.resolve(null),
|
||||||
]);
|
]);
|
||||||
const amount = this.unwrapData(amountResponse) || {};
|
const amount = this.unwrapData(amountResponse) || {};
|
||||||
const settlementAmount = Number(amount.settlementAmount ?? row.settlementAmount ?? 0);
|
const settlementAmount = Number(amount.settlementAmount ?? row.settlementAmount ?? 0);
|
||||||
@@ -1531,6 +1831,7 @@ export default {
|
|||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
paymentType: 'settlement_payment',
|
paymentType: 'settlement_payment',
|
||||||
settlementId: row.id,
|
settlementId: row.id,
|
||||||
|
settlementIds: [row.id],
|
||||||
preSettlementId: null,
|
preSettlementId: null,
|
||||||
preSettlementIds: [],
|
preSettlementIds: [],
|
||||||
settlementNo: row.formalSettlementNo,
|
settlementNo: row.formalSettlementNo,
|
||||||
@@ -1565,10 +1866,27 @@ export default {
|
|||||||
]);
|
]);
|
||||||
if (this.billPayment) this.loadBillOptions();
|
if (this.billPayment) this.loadBillOptions();
|
||||||
},
|
},
|
||||||
|
async confirmFormalSelection() {
|
||||||
|
const rows = this.formalSelection || [];
|
||||||
|
if (!rows.length) return;
|
||||||
|
if (new Set(rows.map(item => String(item.contractId || ''))).size !== 1) {
|
||||||
|
this.$message.warning('所选正式结算单必须属于同一合同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const validRows = rows.filter(row => this.isValidReferenceId(row.id));
|
||||||
|
const details = await Promise.all(validRows.map(row => formalApi.getDetail(row.id)));
|
||||||
|
this.applyTransferredFormalSettlements(
|
||||||
|
details.map((response, index) => ({ ...validRows[index], ...this.unwrapData(response) }))
|
||||||
|
);
|
||||||
|
this.referenceVisible = false;
|
||||||
|
},
|
||||||
async selectPre(row) {
|
async selectPre(row) {
|
||||||
|
const validReferenceId = this.isValidReferenceId(row.id);
|
||||||
const [amountResponse, detailResponse] = await Promise.all([
|
const [amountResponse, detailResponse] = await Promise.all([
|
||||||
api.getReferenceAmount('progress_advance', row.id, this.form.id),
|
validReferenceId
|
||||||
preApi.getDetail(row.id),
|
? api.getReferenceAmount('progress_advance', row.id, this.form.id)
|
||||||
|
: Promise.resolve(null),
|
||||||
|
validReferenceId ? preApi.getDetail(row.id) : Promise.resolve(null),
|
||||||
]);
|
]);
|
||||||
const amount = this.unwrapData(amountResponse) || {};
|
const amount = this.unwrapData(amountResponse) || {};
|
||||||
const settlementAmount = Number(amount.settlementAmount ?? row.settlementAmount ?? 0);
|
const settlementAmount = Number(amount.settlementAmount ?? row.settlementAmount ?? 0);
|
||||||
@@ -1582,6 +1900,7 @@ export default {
|
|||||||
preSettlementId: row.id,
|
preSettlementId: row.id,
|
||||||
preSettlementIds: [row.id],
|
preSettlementIds: [row.id],
|
||||||
settlementId: null,
|
settlementId: null,
|
||||||
|
settlementIds: [],
|
||||||
preSettlementNo: row.preSettlementNo,
|
preSettlementNo: row.preSettlementNo,
|
||||||
settlementNo: '',
|
settlementNo: '',
|
||||||
projectId: row.projectId,
|
projectId: row.projectId,
|
||||||
@@ -1610,13 +1929,33 @@ export default {
|
|||||||
]);
|
]);
|
||||||
if (this.billPayment) this.loadBillOptions();
|
if (this.billPayment) this.loadBillOptions();
|
||||||
},
|
},
|
||||||
|
async confirmPreSelection() {
|
||||||
|
const rows = this.preSelection || [];
|
||||||
|
if (!rows.length) return;
|
||||||
|
if (new Set(rows.map(item => String(item.contractId || ''))).size !== 1) {
|
||||||
|
this.$message.warning('所选预结算单必须属于同一合同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const validRows = rows.filter(row => this.isValidReferenceId(row.id));
|
||||||
|
const details = await Promise.all(validRows.map(row => preApi.getDetail(row.id)));
|
||||||
|
this.applyTransferredPreSettlements(
|
||||||
|
details.map((response, index) => ({ ...validRows[index], ...this.unwrapData(response) }))
|
||||||
|
);
|
||||||
|
this.referenceVisible = false;
|
||||||
|
},
|
||||||
normalizeAttachments(files) {
|
normalizeAttachments(files) {
|
||||||
this.form.attachments = (files || []).map(file => ({
|
this.form.attachments = (files || []).map(file => ({
|
||||||
...file,
|
...file,
|
||||||
attachmentType:
|
attachmentType: (() => {
|
||||||
file.attachmentType ||
|
const matchedType = this.resolveAttachmentTypeByFileName(file);
|
||||||
MATERIAL_TYPE_MAP[this.resolveAttachmentMaterial(file)?.key] ||
|
const existingType = [
|
||||||
|
...Object.values(MATERIAL_TYPE_MAP),
|
||||||
'other',
|
'other',
|
||||||
|
].includes(file.attachmentType)
|
||||||
|
? file.attachmentType
|
||||||
|
: 'other';
|
||||||
|
return matchedType !== 'other' ? matchedType : existingType;
|
||||||
|
})(),
|
||||||
description: file.description || '',
|
description: file.description || '',
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
@@ -1664,7 +2003,7 @@ export default {
|
|||||||
this.downloadableAttachments.forEach((file, index) => {
|
this.downloadableAttachments.forEach((file, index) => {
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
() => downloadFileByUrl(this.attachmentFileUrl(file), this.attachmentFileName(file)),
|
() => downloadFileByUrl(this.attachmentFileUrl(file), this.attachmentFileName(file)),
|
||||||
index * 300
|
index * 500
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1673,6 +2012,7 @@ export default {
|
|||||||
id,
|
id,
|
||||||
paymentType,
|
paymentType,
|
||||||
settlementId,
|
settlementId,
|
||||||
|
settlementIds,
|
||||||
preSettlementId,
|
preSettlementId,
|
||||||
preSettlementIds,
|
preSettlementIds,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -1704,6 +2044,7 @@ export default {
|
|||||||
id,
|
id,
|
||||||
paymentType,
|
paymentType,
|
||||||
settlementId,
|
settlementId,
|
||||||
|
settlementIds,
|
||||||
preSettlementId,
|
preSettlementId,
|
||||||
preSettlementIds,
|
preSettlementIds,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -96,51 +96,11 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息"><template #extra><el-button
|
<section-card title="附件信息">
|
||||||
v-if="form.attachments.length"
|
<vehicle-attachment-table
|
||||||
type="primary"
|
|
||||||
:icon="Download"
|
|
||||||
@click="downloadAttachments"
|
|
||||||
>批量下载</el-button
|
|
||||||
></template>
|
|
||||||
<el-table :data="form.attachments" border>
|
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
|
||||||
<el-table-column label="文件名" min-width="220">
|
|
||||||
<template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="description" label="附件描述" min-width="240" />
|
|
||||||
<el-table-column label="文件大小" width="120">
|
|
||||||
<template #default="{ row }">{{ displayFileSize(row.size) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
|
||||||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
|
||||||
<el-table-column label="操作" width="120" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div class="claim-record-form-page__links">
|
|
||||||
<el-link v-if="row.url || row.link" type="primary" @click="viewAttachment(row)">
|
|
||||||
查看
|
|
||||||
</el-link>
|
|
||||||
<el-link
|
|
||||||
v-if="form.claimStatus === 'claimed'"
|
|
||||||
type="danger"
|
|
||||||
@click="removeAttachment(row)"
|
|
||||||
>删除</el-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<vehicle-attachment-upload
|
|
||||||
v-if="form.claimStatus === 'claimed'"
|
|
||||||
v-model="form.attachments"
|
v-model="form.attachments"
|
||||||
:multiple="true"
|
:readonly="form.claimStatus !== 'claimed'"
|
||||||
:limit="20"
|
@update:model-value="handleAttachmentUpdate"
|
||||||
:max-size="500"
|
|
||||||
:file-types="attachmentFileTypes"
|
|
||||||
:show-file-list="false"
|
|
||||||
button-text="上传附件"
|
|
||||||
class="claim-record-form-page__uploader"
|
|
||||||
@change="handleAttachmentChange"
|
|
||||||
/>
|
/>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
@@ -156,8 +116,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Download } from '@element-plus/icons-vue';
|
|
||||||
import * as api from '@/api/payment/receiptClaimRecord';
|
import * as api from '@/api/payment/receiptClaimRecord';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
receiptNoticeNo: '',
|
receiptNoticeNo: '',
|
||||||
@@ -182,26 +142,10 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReceiptClaimRecordForm',
|
name: 'ReceiptClaimRecordForm',
|
||||||
|
components: { VehicleAttachmentTable },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Download,
|
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
attachmentFileTypes: [
|
|
||||||
'pdf',
|
|
||||||
'bmp',
|
|
||||||
'jpeg',
|
|
||||||
'png',
|
|
||||||
'jpg',
|
|
||||||
'doc',
|
|
||||||
'docx',
|
|
||||||
'ppt',
|
|
||||||
'pptx',
|
|
||||||
'xlsx',
|
|
||||||
'xls',
|
|
||||||
'eml',
|
|
||||||
'msg',
|
|
||||||
'zip',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -241,40 +185,9 @@ export default {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
downloadAttachments() {
|
async handleAttachmentUpdate(files) {
|
||||||
this.form.attachments.forEach(file => {
|
this.form.attachments = files || [];
|
||||||
const url = file.url || file.link;
|
await this.persistAttachments('附件更新成功');
|
||||||
if (!url) return;
|
|
||||||
const anchor = document.createElement('a');
|
|
||||||
anchor.href = url;
|
|
||||||
anchor.download = file.originalName || file.name || '';
|
|
||||||
anchor.target = '_blank';
|
|
||||||
anchor.click();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
viewAttachment(row) {
|
|
||||||
window.open(row.url || row.link, '_blank', 'noopener');
|
|
||||||
},
|
|
||||||
async handleAttachmentChange(files) {
|
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
|
||||||
const userName = userInfo.realName || userInfo.userName || '';
|
|
||||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
this.form.attachments = (files || []).map(file => ({
|
|
||||||
...file,
|
|
||||||
description: file.description || '',
|
|
||||||
uploadUserName: file.uploadUserName || userName,
|
|
||||||
uploadTime: file.uploadTime || uploadTime,
|
|
||||||
}));
|
|
||||||
await this.persistAttachments('附件上传成功');
|
|
||||||
},
|
|
||||||
async removeAttachment(row) {
|
|
||||||
await this.$confirm('确定删除该附件吗?', '删除附件', {
|
|
||||||
type: 'warning',
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
});
|
|
||||||
this.form.attachments = this.form.attachments.filter(item => item !== row);
|
|
||||||
await this.persistAttachments('附件删除成功');
|
|
||||||
},
|
},
|
||||||
async persistAttachments(message) {
|
async persistAttachments(message) {
|
||||||
await api.updateAttachments({
|
await api.updateAttachments({
|
||||||
@@ -296,15 +209,6 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
},
|
},
|
||||||
displayFileSize(size) {
|
|
||||||
if (size === null || size === undefined || size === '') return '-';
|
|
||||||
if (typeof size === 'string' && /[a-z]/i.test(size)) return size;
|
|
||||||
const bytes = Number(size);
|
|
||||||
if (!Number.isFinite(bytes)) return size;
|
|
||||||
if (bytes < 1024) return `${bytes}B`;
|
|
||||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
|
|
||||||
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -108,50 +108,8 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息"><template #extra><el-button
|
<section-card title="附件信息">
|
||||||
v-if="form.attachments.length"
|
<vehicle-attachment-table v-model="form.attachments" />
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
:icon="Download"
|
|
||||||
@click="downloadAttachments"
|
|
||||||
>批量下载</el-button
|
|
||||||
></template>
|
|
||||||
<el-table :data="form.attachments" border>
|
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
|
||||||
<el-table-column label="文件名" min-width="220"
|
|
||||||
><template #default="{ row }">{{
|
|
||||||
row.originalName || row.name || '-'
|
|
||||||
}}</template></el-table-column
|
|
||||||
>
|
|
||||||
<el-table-column label="附件描述" min-width="240"
|
|
||||||
><template #default="{ row }"
|
|
||||||
><el-input v-model="row.description" maxlength="200" /></template
|
|
||||||
></el-table-column>
|
|
||||||
<el-table-column prop="size" label="文件大小" width="120" /><el-table-column
|
|
||||||
prop="uploadUserName"
|
|
||||||
label="上传人"
|
|
||||||
width="140"
|
|
||||||
/><el-table-column prop="uploadTime" label="上传时间" width="170" />
|
|
||||||
<el-table-column label="操作" width="120" align="center"
|
|
||||||
><template #default="{ row, $index }"
|
|
||||||
><div class="receipt-claim-form-page__links">
|
|
||||||
<el-link type="primary" :href="row.url || row.link" target="_blank">查看</el-link
|
|
||||||
><el-link type="danger" @click="form.attachments.splice($index, 1)">删除</el-link>
|
|
||||||
</div></template
|
|
||||||
></el-table-column
|
|
||||||
>
|
|
||||||
</el-table>
|
|
||||||
<vehicle-attachment-upload
|
|
||||||
v-model="form.attachments"
|
|
||||||
:multiple="true"
|
|
||||||
:limit="20"
|
|
||||||
:max-size="500"
|
|
||||||
:file-types="attachmentFileTypes"
|
|
||||||
:show-file-list="false"
|
|
||||||
button-text="上传附件"
|
|
||||||
class="receipt-claim-form-page__uploader"
|
|
||||||
@change="normalizeAttachments"
|
|
||||||
/>
|
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="备注">
|
<section-card title="备注">
|
||||||
@@ -190,27 +148,27 @@
|
|||||||
border
|
border
|
||||||
@selection-change="settlementDialog.selection = $event"
|
@selection-change="settlementDialog.selection = $event"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="52" align="center" /><el-table-column
|
<el-table-column type="selection" width="52" align="center" />
|
||||||
prop="formalSettlementNo"
|
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
||||||
label="结算单号"
|
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
||||||
min-width="170"
|
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
||||||
/><el-table-column prop="projectName" label="所属项目" min-width="150" /><el-table-column
|
<el-table-column prop="payerName" label="付款方" min-width="150" />
|
||||||
prop="deptName"
|
<el-table-column prop="payeeName" label="收款方" min-width="150" />
|
||||||
label="所属组织"
|
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||||
min-width="150"
|
<template #default="{ row }">
|
||||||
/><el-table-column prop="payerName" label="付款方" min-width="150" /><el-table-column
|
{{ formatMoney(row.settlementAmount) }}
|
||||||
prop="payeeName"
|
</template>
|
||||||
label="收款方"
|
</el-table-column>
|
||||||
min-width="150"
|
<el-table-column label="已认领收款金额" min-width="150" align="right">
|
||||||
/><el-table-column label="结算总金额" min-width="140" align="right"
|
<template #default="{ row }">
|
||||||
><template #default="{ row }">{{
|
{{ formatMoney(row.claimedReceiptAmount) }}
|
||||||
formatMoney(row.settlementAmount)
|
</template>
|
||||||
}}</template></el-table-column
|
</el-table-column>
|
||||||
><el-table-column label="已认领收款金额" min-width="150" align="right"
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
><template #default="{ row }">{{
|
<template #default="{ row }">
|
||||||
formatMoney(row.claimedReceiptAmount)
|
<el-link type="primary" @click.stop="selectSettlement(row)">选择</el-link>
|
||||||
}}</template></el-table-column
|
</template>
|
||||||
>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<template #footer
|
<template #footer
|
||||||
><el-button @click="settlementDialog.visible = false">取消</el-button
|
><el-button @click="settlementDialog.visible = false">取消</el-button
|
||||||
@@ -221,8 +179,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Download, Search } from '@element-plus/icons-vue';
|
import { Search } from '@element-plus/icons-vue';
|
||||||
import * as api from '@/api/payment/receiptFlow';
|
import * as api from '@/api/payment/receiptFlow';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -247,28 +206,12 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReceiptFlowForm',
|
name: 'ReceiptFlowForm',
|
||||||
|
components: { VehicleAttachmentTable },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Download,
|
|
||||||
Search,
|
Search,
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
submitting: false,
|
submitting: false,
|
||||||
attachmentFileTypes: [
|
|
||||||
'pdf',
|
|
||||||
'bmp',
|
|
||||||
'jpeg',
|
|
||||||
'png',
|
|
||||||
'jpg',
|
|
||||||
'doc',
|
|
||||||
'docx',
|
|
||||||
'ppt',
|
|
||||||
'pptx',
|
|
||||||
'xlsx',
|
|
||||||
'xls',
|
|
||||||
'eml',
|
|
||||||
'msg',
|
|
||||||
'zip',
|
|
||||||
],
|
|
||||||
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
|
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
|
||||||
rules: {
|
rules: {
|
||||||
settlementIds: [
|
settlementIds: [
|
||||||
@@ -359,13 +302,6 @@ export default {
|
|||||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
this.form.counterpartyName &&
|
|
||||||
rows.some(item => item.payerName && item.payerName !== this.form.counterpartyName)
|
|
||||||
) {
|
|
||||||
this.$message.warning('对方户名与结算单付款方不一致');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.form.settlements = rows.map(item => ({
|
this.form.settlements = rows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
formalSettlementId: item.id,
|
formalSettlementId: item.id,
|
||||||
@@ -376,31 +312,13 @@ export default {
|
|||||||
this.settlementDialog.visible = false;
|
this.settlementDialog.visible = false;
|
||||||
this.$nextTick(() => this.$refs.formRef?.validateField('settlementIds'));
|
this.$nextTick(() => this.$refs.formRef?.validateField('settlementIds'));
|
||||||
},
|
},
|
||||||
|
selectSettlement(row) {
|
||||||
|
this.settlementDialog.selection = [row];
|
||||||
|
this.confirmSettlements();
|
||||||
|
},
|
||||||
remainingAmount(row) {
|
remainingAmount(row) {
|
||||||
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
|
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
|
||||||
},
|
},
|
||||||
normalizeAttachments(files) {
|
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
|
||||||
const userName = userInfo.realName || userInfo.userName || '';
|
|
||||||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
this.form.attachments = (files || []).map(file => ({
|
|
||||||
...file,
|
|
||||||
description: file.description || '',
|
|
||||||
uploadUserName: file.uploadUserName || userName,
|
|
||||||
uploadTime: file.uploadTime || time,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
downloadAttachments() {
|
|
||||||
this.form.attachments.forEach(file => {
|
|
||||||
const url = file.url || file.link;
|
|
||||||
if (!url) return;
|
|
||||||
const anchor = document.createElement('a');
|
|
||||||
anchor.href = url;
|
|
||||||
anchor.download = file.originalName || file.name || '';
|
|
||||||
anchor.target = '_blank';
|
|
||||||
anchor.click();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async submitClaim() {
|
async submitClaim() {
|
||||||
await this.$refs.formRef.validate();
|
await this.$refs.formRef.validate();
|
||||||
const amount = Number(this.form.receiptAmount || 0);
|
const amount = Number(this.form.receiptAmount || 0);
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
|
|
||||||
<section class="receipt-flow-page__table-panel">
|
<section class="receipt-flow-page__table-panel">
|
||||||
<div class="receipt-flow-page__toolbar">
|
<div class="receipt-flow-page__toolbar">
|
||||||
<div />
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="hasPermission('receipt_flow_sync')"
|
v-if="hasPermission('receipt_flow_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -76,7 +75,11 @@
|
|||||||
{{ displayValue(row[column.prop]) }}
|
{{ displayValue(row[column.prop]) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<span v-else-if="column.link">{{ displayValue(row[column.prop]) }}</span>
|
<span v-else-if="column.link">{{ displayValue(row[column.prop]) }}</span>
|
||||||
<el-tag v-else-if="column.status" :type="statusType(row.claimStatus)" class="status-text">
|
<el-tag
|
||||||
|
v-else-if="column.status"
|
||||||
|
:type="statusType(row.claimStatus)"
|
||||||
|
class="status-text"
|
||||||
|
>
|
||||||
{{ displayValue(row[column.prop]) }}
|
{{ displayValue(row[column.prop]) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
@@ -110,6 +113,61 @@ import { mapGetters } from 'vuex';
|
|||||||
import * as api from '@/api/payment/receiptFlow';
|
import * as api from '@/api/payment/receiptFlow';
|
||||||
import { receiptFlowTableColumns } from '@/option/payment/receiptFlow';
|
import { receiptFlowTableColumns } from '@/option/payment/receiptFlow';
|
||||||
|
|
||||||
|
const receiptFlowMockTemplates = [
|
||||||
|
{
|
||||||
|
receiptNoticeNo: 'SKTZ20260827001',
|
||||||
|
payerName: '华东供应链管理有限公司',
|
||||||
|
receiptAmount: 48000,
|
||||||
|
counterpartyName: '上海安捷运输有限公司',
|
||||||
|
counterpartyAccount: '310000000000001001',
|
||||||
|
counterpartyBank: '中国工商银行上海分行',
|
||||||
|
summary: '运输服务款',
|
||||||
|
transactionTime: '2026-08-27 09:30:00',
|
||||||
|
detailSerialNo: 'MOCK-RF-20260827-001',
|
||||||
|
sourceUpdatedTime: '2026-08-27 09:30:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
receiptNoticeNo: 'SKTZ20260827002',
|
||||||
|
payerName: '华东供应链管理有限公司',
|
||||||
|
receiptAmount: 26500,
|
||||||
|
counterpartyName: '江苏联运物流有限公司',
|
||||||
|
counterpartyAccount: '320100000000002002',
|
||||||
|
counterpartyBank: '中国建设银行南京分行',
|
||||||
|
summary: '物流服务款',
|
||||||
|
transactionTime: '2026-08-27 13:45:00',
|
||||||
|
detailSerialNo: 'MOCK-RF-20260827-002',
|
||||||
|
sourceUpdatedTime: '2026-08-27 13:45:00',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const randomDigits = length =>
|
||||||
|
Array.from({ length }, () => Math.floor(Math.random() * 10)).join('');
|
||||||
|
|
||||||
|
const formatMockDateTime = date => {
|
||||||
|
const pad = value => String(value).padStart(2, '0');
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
|
||||||
|
date.getHours()
|
||||||
|
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createReceiptFlowMockRows = () =>
|
||||||
|
receiptFlowMockTemplates.map((template, index) => {
|
||||||
|
const transactionDate = new Date();
|
||||||
|
transactionDate.setDate(transactionDate.getDate() - Math.floor(Math.random() * 7));
|
||||||
|
transactionDate.setHours(8 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 60), 0);
|
||||||
|
const transactionTime = formatMockDateTime(transactionDate);
|
||||||
|
const dateCode = transactionTime.slice(0, 10).replaceAll('-', '');
|
||||||
|
return {
|
||||||
|
...template,
|
||||||
|
receiptNoticeNo: `SKTZ${dateCode}${randomDigits(5)}`,
|
||||||
|
receiptAmount: Math.floor(10000 + Math.random() * 90000),
|
||||||
|
counterpartyAccount: `${template.counterpartyAccount.slice(0, -6)}${randomDigits(6)}`,
|
||||||
|
transactionTime,
|
||||||
|
detailSerialNo: `MOCK-RF-${dateCode}-${index + 1}${randomDigits(4)}`,
|
||||||
|
sourceUpdatedTime: transactionTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const emptyQuery = () => ({
|
const emptyQuery = () => ({
|
||||||
receiptNoticeNo: '',
|
receiptNoticeNo: '',
|
||||||
counterpartyName: '',
|
counterpartyName: '',
|
||||||
@@ -184,7 +242,8 @@ export default {
|
|||||||
async handleSync() {
|
async handleSync() {
|
||||||
this.syncing = true;
|
this.syncing = true;
|
||||||
try {
|
try {
|
||||||
const syncedCount = Number(this.unwrapData(await api.sync()) || 0);
|
const mockRows = createReceiptFlowMockRows();
|
||||||
|
const syncedCount = Number(this.unwrapData(await api.sync({ flows: mockRows })) || 0);
|
||||||
this.$message.success(`流水同步完成,共同步${syncedCount}条`);
|
this.$message.success(`流水同步完成,共同步${syncedCount}条`);
|
||||||
await this.loadTable();
|
await this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -246,7 +305,7 @@ export default {
|
|||||||
}
|
}
|
||||||
.receipt-flow-page__toolbar {
|
.receipt-flow-page__toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,7 @@
|
|||||||
v-bind="editorContainerProps"
|
v-bind="editorContainerProps"
|
||||||
@update:model-value="visible = $event"
|
@update:model-value="visible = $event"
|
||||||
>
|
>
|
||||||
<div
|
<div v-loading="loading" class="formal-editor" :class="{ 'formal-editor--page': pageMode }">
|
||||||
v-loading="loading"
|
|
||||||
class="formal-editor"
|
|
||||||
:class="{ 'formal-editor--page': pageMode }"
|
|
||||||
>
|
|
||||||
<section-card title="结算基本信息">
|
<section-card title="结算基本信息">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
@@ -227,6 +223,9 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'transportQuantity'">
|
||||||
|
{{ formatQuantity(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
<span v-else-if="column.prop === 'transportType'">
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
{{ transportTypeName(row[column.prop]) }}
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -417,7 +416,12 @@
|
|||||||
<el-table-column prop="paidAmount" label="已付款金额" min-width="130" align="center">
|
<el-table-column prop="paidAmount" label="已付款金额" min-width="130" align="center">
|
||||||
<template #default="{ row }">{{ formatMoney(row.paidAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.paidAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="approvalStatusName" label="单据状态" min-width="120" align="center">
|
<el-table-column
|
||||||
|
prop="approvalStatusName"
|
||||||
|
label="单据状态"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
<template #default="{ row }">{{ displayValue(row.approvalStatusName) }}</template>
|
<template #default="{ row }">{{ displayValue(row.approvalStatusName) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" align="center">
|
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" align="center">
|
||||||
@@ -439,7 +443,12 @@
|
|||||||
<el-table-column prop="adjustmentAmount" label="调整金额" min-width="130" align="center">
|
<el-table-column prop="adjustmentAmount" label="调整金额" min-width="130" align="center">
|
||||||
<template #default="{ row }">{{ formatMoney(row.adjustmentAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.adjustmentAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="approvalStatusName" label="单据状态" min-width="120" align="center">
|
<el-table-column
|
||||||
|
prop="approvalStatusName"
|
||||||
|
label="单据状态"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
<template #default="{ row }">{{ displayValue(row.approvalStatusName) }}</template>
|
<template #default="{ row }">{{ displayValue(row.approvalStatusName) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="remark" label="备注" min-width="220" align="center">
|
<el-table-column prop="remark" label="备注" min-width="220" align="center">
|
||||||
@@ -627,7 +636,7 @@
|
|||||||
><el-input-number
|
><el-input-number
|
||||||
v-model="row.transportQuantity"
|
v-model="row.transportQuantity"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="6"
|
:precision="2"
|
||||||
:controls="false" /></template
|
:controls="false" /></template
|
||||||
></el-table-column>
|
></el-table-column>
|
||||||
<el-table-column label="里程(KM)" min-width="130" align="center"
|
<el-table-column label="里程(KM)" min-width="130" align="center"
|
||||||
@@ -1458,9 +1467,7 @@ export default {
|
|||||||
remainingAmount = Number((remainingAmount - matchedAmount).toFixed(2));
|
remainingAmount = Number((remainingAmount - matchedAmount).toFixed(2));
|
||||||
return {
|
return {
|
||||||
invoiceNo,
|
invoiceNo,
|
||||||
invoiceDate: this.$dayjs()
|
invoiceDate: this.$dayjs().subtract(index, 'day').format('YYYY-MM-DD'),
|
||||||
.subtract(index, 'day')
|
|
||||||
.format('YYYY-MM-DD'),
|
|
||||||
invoiceType: index === 1 ? '增值税普通发票' : '增值税专用发票',
|
invoiceType: index === 1 ? '增值税普通发票' : '增值税专用发票',
|
||||||
taxRate: [3, 6, 9][index],
|
taxRate: [3, 6, 9][index],
|
||||||
invoiceAmount: invoiceAmounts[index],
|
invoiceAmount: invoiceAmounts[index],
|
||||||
@@ -1627,6 +1634,11 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return `${Number(value || 0).toFixed(2)} RMB`;
|
return `${Number(value || 0).toFixed(2)} RMB`;
|
||||||
},
|
},
|
||||||
|
formatQuantity(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '-';
|
||||||
|
const quantity = Number(value);
|
||||||
|
return Number.isFinite(quantity) ? quantity.toFixed(2) : '-';
|
||||||
|
},
|
||||||
async loadTransportTypeOptions() {
|
async loadTransportTypeOptions() {
|
||||||
const { data } = await getDictionary({ code: 'transport_type' });
|
const { data } = await getDictionary({ code: 'transport_type' });
|
||||||
this.transportTypeOptions = data?.data || [];
|
this.transportTypeOptions = data?.data || [];
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
v-if="hasPermission('formal_settlement_payment')"
|
v-if="hasPermission('formal_settlement_payment')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
disabled
|
:disabled="!selection.length"
|
||||||
@click="$emit('payment')"
|
@click="$emit('payment')"
|
||||||
>付款申请</el-button
|
>付款申请</el-button
|
||||||
>
|
>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
v-if="hasPermission('formal_settlement_sync')"
|
v-if="hasPermission('formal_settlement_sync')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
disabled
|
:disabled="selection.length !== 1"
|
||||||
@click="$emit('sync')"
|
@click="$emit('sync')"
|
||||||
>同步金蝶</el-button
|
>同步金蝶</el-button
|
||||||
>
|
>
|
||||||
@@ -46,13 +46,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
|
ref="tableRef"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="rows"
|
:data="rows"
|
||||||
border
|
border
|
||||||
highlight-current-row
|
highlight-current-row
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
|
<el-table-column type="selection" width="52" fixed="left" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
:key="column.prop"
|
:key="column.prop"
|
||||||
@@ -131,17 +134,40 @@ export default {
|
|||||||
page: { type: Object, required: true },
|
page: { type: Object, required: true },
|
||||||
hasPermission: { type: Function, required: true },
|
hasPermission: { type: Function, required: true },
|
||||||
},
|
},
|
||||||
emits: ['create', 'payment', 'sync', 'print', 'export', 'refresh', 'page-change', 'action'],
|
emits: [
|
||||||
|
'create',
|
||||||
|
'payment',
|
||||||
|
'sync',
|
||||||
|
'print',
|
||||||
|
'export',
|
||||||
|
'refresh',
|
||||||
|
'page-change',
|
||||||
|
'action',
|
||||||
|
'update:selection',
|
||||||
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Refresh,
|
Refresh,
|
||||||
selection: [],
|
selection: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
rows() {
|
||||||
|
this.selection = [];
|
||||||
|
this.$refs.tableRef?.clearSelection();
|
||||||
|
this.$emit('update:selection', this.selection);
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleCurrentChange(row) {
|
handleCurrentChange(row) {
|
||||||
this.selection = row ? [row] : [];
|
if (!row && this.selection.length === 1) {
|
||||||
|
this.selection = [];
|
||||||
this.$emit('update:selection', this.selection);
|
this.$emit('update:selection', this.selection);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSelectionChange(selection) {
|
||||||
|
this.selection = selection;
|
||||||
|
this.$emit('update:selection', selection);
|
||||||
},
|
},
|
||||||
emitAction(type, row) {
|
emitAction(type, row) {
|
||||||
this.$emit('action', { type, row });
|
this.$emit('action', { type, row });
|
||||||
|
|||||||
@@ -505,12 +505,13 @@
|
|||||||
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
||||||
<el-table-column label="运输总量" min-width="150" align="center">
|
<el-table-column label="运输总量" min-width="150" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
|
v-else
|
||||||
v-model="row.transportQuantity"
|
v-model="row.transportQuantity"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="6"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -570,7 +571,6 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.settlementAmountTax"
|
v-model="row.settlementAmountTax"
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:disabled="adjustDialog.readonly"
|
:disabled="adjustDialog.readonly"
|
||||||
@@ -1111,11 +1111,6 @@ export default {
|
|||||||
},
|
},
|
||||||
recalculateSummaryRow(row) {
|
recalculateSummaryRow(row) {
|
||||||
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
||||||
if (row.settlementAmount < 0) {
|
|
||||||
row.adjustAmount = -Number(row.originalAmount || 0);
|
|
||||||
row.settlementAmount = 0;
|
|
||||||
this.$message.warning('结算金额不能小于0');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
buildSummaryFeesFromDetails() {
|
buildSummaryFeesFromDetails() {
|
||||||
const summaryMap = new Map();
|
const summaryMap = new Map();
|
||||||
@@ -1176,7 +1171,9 @@ export default {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
||||||
if (residualAmount > 0) appendSummary('其他费用', residualAmount, row.feeType);
|
if (Math.abs(residualAmount) >= 0.005) {
|
||||||
|
appendSummary('其他费用', residualAmount, row.feeType);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const manualFees = this.summaryFees.filter(row => row.manualFlag === 1);
|
const manualFees = this.summaryFees.filter(row => row.manualFlag === 1);
|
||||||
@@ -1479,8 +1476,11 @@ export default {
|
|||||||
return `${amount.toFixed(2)} ${currency || 'RMB'}`;
|
return `${amount.toFixed(2)} ${currency || 'RMB'}`;
|
||||||
},
|
},
|
||||||
formatQuantity(row) {
|
formatQuantity(row) {
|
||||||
if (row.transportQuantity === undefined || row.transportQuantity === null) return '-';
|
const value = row.transportQuantity;
|
||||||
return `${row.transportQuantity}${row.quantityUnit ? ` ${row.quantityUnit}` : ''}`;
|
if (value === undefined || value === null || value === '') return '-';
|
||||||
|
const quantity = Number(value);
|
||||||
|
if (!Number.isFinite(quantity)) return '-';
|
||||||
|
return `${quantity.toFixed(2)}${row.quantityUnit ? ` ${row.quantityUnit}` : ''}`;
|
||||||
},
|
},
|
||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === undefined || value === null || value === '' ? '-' : value;
|
return value === undefined || value === null || value === '' ? '-' : value;
|
||||||
|
|||||||
@@ -38,7 +38,11 @@
|
|||||||
disabled
|
disabled
|
||||||
/><span v-else>{{
|
/><span v-else>{{
|
||||||
displayValue(
|
displayValue(
|
||||||
field.prop === 'settlementTypeName' ? settlementTypeName : form[field.prop]
|
field.prop === 'settlementTypeName'
|
||||||
|
? settlementTypeName
|
||||||
|
: field.prop === 'formalSettlementId'
|
||||||
|
? form.formalSettlementNo || form.formalSettlementId
|
||||||
|
: form[field.prop]
|
||||||
)
|
)
|
||||||
}}</span></el-form-item
|
}}</span></el-form-item
|
||||||
></el-col
|
></el-col
|
||||||
@@ -340,7 +344,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await api.getDetail(this.recordId);
|
const response = await api.getDetail(this.recordId);
|
||||||
|
const data = response?.data?.data || response?.data || response || {};
|
||||||
this.form = { ...createSettlementAdjustmentForm(), ...data };
|
this.form = { ...createSettlementAdjustmentForm(), ...data };
|
||||||
this.details = (data.details || []).map(item => ({
|
this.details = (data.details || []).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
|
|||||||
@@ -124,6 +124,22 @@
|
|||||||
class="status-text"
|
class="status-text"
|
||||||
>{{ updateName(row.updateResult) }}</el-tag
|
>{{ updateName(row.updateResult) }}</el-tag
|
||||||
>
|
>
|
||||||
|
<span v-else-if="column.prop === 'matchedExternalLineNo'">
|
||||||
|
{{ formatMatchedExternalLineNo(row.matchedExternalLineNo) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
|
{{ transportTypeLabel(row.transportType) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="column.prop === 'transportQuantity'">
|
||||||
|
{{ formatTransportQuantity(row.transportQuantity) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="column.prop === 'mileage'">{{ formatMileage(row.mileage) }}</span>
|
||||||
|
<span
|
||||||
|
v-else-if="
|
||||||
|
column.prop === 'actualDepartureTime' || column.prop === 'actualCompletionTime'
|
||||||
|
"
|
||||||
|
>{{ formatDate(row[column.prop]) }}</span
|
||||||
|
>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -160,7 +176,9 @@
|
|||||||
>下载货物明细对账模板</el-button
|
>下载货物明细对账模板</el-button
|
||||||
>
|
>
|
||||||
<el-button type="primary" plain @click="chooseImport">导入</el-button>
|
<el-button type="primary" plain @click="chooseImport">导入</el-button>
|
||||||
<el-button type="primary" @click="handleMatch">开始匹配内部账单</el-button>
|
<el-button type="primary" :disabled="!canStartMatch" @click="handleMatch"
|
||||||
|
>开始匹配内部账单</el-button
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
ref="fileInput"
|
ref="fileInput"
|
||||||
type="file"
|
type="file"
|
||||||
@@ -186,6 +204,19 @@
|
|||||||
<el-tag v-if="column.prop === 'matchStatus'" :type="matchTagType(row.matchStatus)" class="status-text">{{
|
<el-tag v-if="column.prop === 'matchStatus'" :type="matchTagType(row.matchStatus)" class="status-text">{{
|
||||||
matchName(row.matchStatus)
|
matchName(row.matchStatus)
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
|
{{ transportTypeLabel(row.transportType) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="column.prop === 'transportQuantity'">
|
||||||
|
{{ formatTransportQuantity(row.transportQuantity) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="column.prop === 'mileage'">{{ formatMileage(row.mileage) }}</span>
|
||||||
|
<span
|
||||||
|
v-else-if="
|
||||||
|
column.prop === 'actualDepartureTime' || column.prop === 'actualCompletionTime'
|
||||||
|
"
|
||||||
|
>{{ formatDate(row[column.prop]) }}</span
|
||||||
|
>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -202,27 +233,6 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="操作说明">
|
|
||||||
<el-collapse>
|
|
||||||
<el-collapse-item title="操作说明" name="help">
|
|
||||||
<div class="reconciliation-editor__help">
|
|
||||||
<p>1. 新增对账单时选择一张已审批通过的正式结算单,系统自动带出内部账单明细。</p>
|
|
||||||
<p>
|
|
||||||
2. 导入对方 Excel
|
|
||||||
账单后点击“开始匹配内部账单”,系统按车号、货物、地址、批次、发货时间、运输量和金额匹配。
|
|
||||||
</p>
|
|
||||||
<p>3. 外部账单存在重复候选时会标记为疑似重复,需要人工选择唯一明细匹配。</p>
|
|
||||||
<p>4. 只有所有内外部明细一一匹配且差异为 0,才允许按匹配结果更新账单或完成对账。</p>
|
|
||||||
<p>
|
|
||||||
5.
|
|
||||||
已付金额大于外部匹配金额时禁止更新;整车模式一车多货会跳过自动更新,可通过“调整”逐货物修改。
|
|
||||||
</p>
|
|
||||||
<p>6. 按匹配结果更新成功后生成变更记录;完成对账后单据不可再编辑和删除。</p>
|
|
||||||
</div>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</section-card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -263,6 +273,11 @@
|
|||||||
formatMoney(row.settlementAmount, row.currency)
|
formatMoney(row.settlementAmount, row.currency)
|
||||||
}}</template></el-table-column
|
}}</template></el-table-column
|
||||||
>
|
>
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="selectFormal(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="reconciliation-editor__pagination">
|
<div class="reconciliation-editor__pagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -288,6 +303,7 @@
|
|||||||
><el-input-number
|
><el-input-number
|
||||||
v-model="row.transportQuantity"
|
v-model="row.transportQuantity"
|
||||||
:min="0"
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
:controls="false" /></template
|
:controls="false" /></template
|
||||||
></el-table-column>
|
></el-table-column>
|
||||||
<el-table-column prop="unitPrice" label="运输单价" min-width="120"
|
<el-table-column prop="unitPrice" label="运输单价" min-width="120"
|
||||||
@@ -325,7 +341,11 @@
|
|||||||
<el-table-column prop="externalLineNo" label="外部行号" width="90" />
|
<el-table-column prop="externalLineNo" label="外部行号" width="90" />
|
||||||
<el-table-column prop="vehicleNo" label="车牌号" width="110" />
|
<el-table-column prop="vehicleNo" label="车牌号" width="110" />
|
||||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" />
|
<el-table-column prop="cargoName" label="货物名称" min-width="140" />
|
||||||
<el-table-column prop="transportQuantity" label="运输量" width="110" />
|
<el-table-column prop="transportQuantity" label="运输量" width="110"
|
||||||
|
><template #default="{ row }">{{
|
||||||
|
formatTransportQuantity(row.transportQuantity)
|
||||||
|
}}</template></el-table-column
|
||||||
|
>
|
||||||
<el-table-column prop="settlementAmount" label="结算金额" width="130"
|
<el-table-column prop="settlementAmount" label="结算金额" width="130"
|
||||||
><template #default="{ row }">{{
|
><template #default="{ row }">{{
|
||||||
formatMoney(row.settlementAmount)
|
formatMoney(row.settlementAmount)
|
||||||
@@ -346,7 +366,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/settlement/transportReconciliation';
|
import * as api from '@/api/settlement/transportReconciliation';
|
||||||
|
import * as formalSettlementApi from '@/api/settlement/formalSettlement';
|
||||||
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { transportReconciliationFormFields } from '@/option/settlement/transportReconciliationForm';
|
import { transportReconciliationFormFields } from '@/option/settlement/transportReconciliationForm';
|
||||||
import {
|
import {
|
||||||
@@ -373,6 +396,7 @@ export default {
|
|||||||
form: this.emptyForm(),
|
form: this.emptyForm(),
|
||||||
formFields: transportReconciliationFormFields,
|
formFields: transportReconciliationFormFields,
|
||||||
internalColumns,
|
internalColumns,
|
||||||
|
transportTypeOptions: [],
|
||||||
internalDetails: [],
|
internalDetails: [],
|
||||||
externalDetails: [],
|
externalDetails: [],
|
||||||
internalQuery: { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' },
|
internalQuery: { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' },
|
||||||
@@ -396,6 +420,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters(['userInfo']),
|
||||||
visible: {
|
visible: {
|
||||||
get() {
|
get() {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
@@ -435,6 +460,9 @@ export default {
|
|||||||
row => row.suspectedDuplicate || row.matchStatus === 'suspected_duplicate'
|
row => row.suspectedDuplicate || row.matchStatus === 'suspected_duplicate'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
canStartMatch() {
|
||||||
|
return Boolean(this.form.formalSettlementId && this.externalDetails.length);
|
||||||
|
},
|
||||||
visibleExternalRows() {
|
visibleExternalRows() {
|
||||||
return this.externalTab === 'duplicate' ? this.duplicateRows : this.externalDetails;
|
return this.externalTab === 'duplicate' ? this.duplicateRows : this.externalDetails;
|
||||||
},
|
},
|
||||||
@@ -479,6 +507,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
this.loadTransportTypeOptions();
|
||||||
this.currentId = this.recordId;
|
this.currentId = this.recordId;
|
||||||
this.form = this.emptyForm();
|
this.form = this.emptyForm();
|
||||||
this.internalDetails = [];
|
this.internalDetails = [];
|
||||||
@@ -487,7 +516,8 @@ export default {
|
|||||||
this.internalQuery = { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' };
|
this.internalQuery = { documentNo: '', vehicleNo: '', batchNo: '', cargoName: '' };
|
||||||
if (!this.currentId) {
|
if (!this.currentId) {
|
||||||
this.form.reconciliationMode = 'vehicle';
|
this.form.reconciliationMode = 'vehicle';
|
||||||
this.form.reconcilerName = '当前用户';
|
this.form.reconcilerName =
|
||||||
|
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || '当前用户';
|
||||||
this.form.reconciliationDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.reconciliationDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -499,10 +529,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadDetail() {
|
async loadDetail() {
|
||||||
const { data } = await api.getDetail(this.currentId);
|
const data = this.unwrapData(await api.getDetail(this.currentId)) || {};
|
||||||
this.form = { ...this.emptyForm(), ...data };
|
this.form = { ...this.emptyForm(), ...data };
|
||||||
this.internalDetails = data.internalDetails || [];
|
this.internalDetails =
|
||||||
this.externalDetails = data.externalDetails || [];
|
data.internalDetails || data.internalBillDetails || data.internals || [];
|
||||||
|
this.externalDetails =
|
||||||
|
data.externalDetails || data.externalBillDetails || data.externals || [];
|
||||||
},
|
},
|
||||||
openFormalDialog() {
|
openFormalDialog() {
|
||||||
if (!this.editable) return;
|
if (!this.editable) return;
|
||||||
@@ -513,21 +545,42 @@ export default {
|
|||||||
async loadFormalOptions() {
|
async loadFormalOptions() {
|
||||||
this.formalDialog.loading = true;
|
this.formalDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await api.getFormalOptions(
|
const params = {
|
||||||
|
settlementType: this.settlementType,
|
||||||
|
keyword: this.formalDialog.query.keyword,
|
||||||
|
};
|
||||||
|
const data = this.unwrapData(
|
||||||
|
await api.getFormalOptions(
|
||||||
this.formalDialog.page.current,
|
this.formalDialog.page.current,
|
||||||
this.formalDialog.page.size,
|
this.formalDialog.page.size,
|
||||||
{ settlementType: this.settlementType, keyword: this.formalDialog.query.keyword }
|
params
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this.formalDialog.rows = data.records || [];
|
let rows = data.records || [];
|
||||||
this.formalDialog.page.total = data.total || 0;
|
let total = data.total || 0;
|
||||||
|
if (!rows.length) {
|
||||||
|
const fallback = await formalSettlementApi.getList(
|
||||||
|
this.formalDialog.page.current,
|
||||||
|
this.formalDialog.page.size,
|
||||||
|
{ ...params, approvalStatus: 'approved' }
|
||||||
|
);
|
||||||
|
const fallbackData = this.unwrapData(fallback);
|
||||||
|
rows = fallbackData.records || [];
|
||||||
|
total = fallbackData.total || 0;
|
||||||
|
}
|
||||||
|
this.formalDialog.rows = rows;
|
||||||
|
this.formalDialog.page.total = total;
|
||||||
} finally {
|
} finally {
|
||||||
this.formalDialog.loading = false;
|
this.formalDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmFormal() {
|
async confirmFormal() {
|
||||||
if (this.formalDialog.selected.length !== 1)
|
if (this.formalDialog.selected.length !== 1)
|
||||||
return this.$message.warning('请选择一张正式结算单');
|
return this.$message.warning('请选择一张正式结算单');
|
||||||
const selected = this.formalDialog.selected[0];
|
await this.selectFormal(this.formalDialog.selected[0]);
|
||||||
|
},
|
||||||
|
async selectFormal(selected) {
|
||||||
|
const formalSettlementChanged = this.form.formalSettlementId !== selected.id;
|
||||||
this.form = {
|
this.form = {
|
||||||
...this.form,
|
...this.form,
|
||||||
...selected,
|
...selected,
|
||||||
@@ -535,33 +588,115 @@ export default {
|
|||||||
formalSettlementNo: selected.formalSettlementNo,
|
formalSettlementNo: selected.formalSettlementNo,
|
||||||
reconciliationNo: this.form.reconciliationNo,
|
reconciliationNo: this.form.reconciliationNo,
|
||||||
};
|
};
|
||||||
|
if (formalSettlementChanged) this.externalDetails = [];
|
||||||
|
await this.loadFormalInternalPreview(selected.id);
|
||||||
this.formalDialog.visible = false;
|
this.formalDialog.visible = false;
|
||||||
},
|
},
|
||||||
async handleSave() {
|
async loadFormalInternalPreview(formalSettlementId) {
|
||||||
|
const formal = this.unwrapData(await formalSettlementApi.getDetail(formalSettlementId));
|
||||||
|
const details = formal.details || [];
|
||||||
|
const buildInternalRow = (detail, overrides = {}, index = 0) => ({
|
||||||
|
...detail,
|
||||||
|
...overrides,
|
||||||
|
id: null,
|
||||||
|
lineNo: index + 1,
|
||||||
|
formalSettlementDetailId: detail.id,
|
||||||
|
settlementAmount: overrides.settlementAmount ?? detail.settlementAmountTax ?? 0,
|
||||||
|
matchResult: 'unmatched',
|
||||||
|
updateResult: 'not_updated',
|
||||||
|
});
|
||||||
|
if (this.form.reconciliationMode === 'cargo') {
|
||||||
|
const feeGroups = await Promise.all(
|
||||||
|
details.map(async detail => ({
|
||||||
|
detail,
|
||||||
|
fees: this.unwrapData(await formalSettlementApi.getDetailFees(detail.id)) || [],
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
this.internalDetails = feeGroups.flatMap(({ detail, fees }) => {
|
||||||
|
if (!fees.length) return [buildInternalRow(detail, {}, 0)];
|
||||||
|
return fees.map((fee, index) =>
|
||||||
|
buildInternalRow(
|
||||||
|
detail,
|
||||||
|
{
|
||||||
|
...fee,
|
||||||
|
settlementAmount: fee.settlementAmountTax ?? 0,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.internalDetails = details.map((detail, index) => buildInternalRow(detail, {}, index));
|
||||||
|
}
|
||||||
|
const internalQuantity = this.internalDetails.reduce(
|
||||||
|
(total, row) => total + Number(row.transportQuantity || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const internalAmount = this.internalDetails.reduce(
|
||||||
|
(total, row) => total + Number(row.settlementAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.form = {
|
||||||
|
...this.form,
|
||||||
|
internalBillCount: this.internalDetails.length,
|
||||||
|
internalQuantity,
|
||||||
|
internalAmount,
|
||||||
|
externalBillCount: 0,
|
||||||
|
externalQuantity: 0,
|
||||||
|
externalAmount: 0,
|
||||||
|
differenceCount: this.internalDetails.length,
|
||||||
|
differenceQuantity: internalQuantity,
|
||||||
|
differenceAmount: internalAmount,
|
||||||
|
matchedCount: 0,
|
||||||
|
unmatchedCount: this.internalDetails.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
unwrapData(response) {
|
||||||
|
const body = response?.data || response || {};
|
||||||
|
return body?.data || body;
|
||||||
|
},
|
||||||
|
async loadTransportTypeOptions() {
|
||||||
|
try {
|
||||||
|
const data = this.unwrapData(await getDictionary({ code: 'transport_type' }));
|
||||||
|
const records = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
this.transportTypeOptions = records.map(item => ({
|
||||||
|
label: item.dictValue || item.label || item.name,
|
||||||
|
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
this.transportTypeOptions = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleSave(silent = false) {
|
||||||
const valid = await this.$refs.formRef.validate().catch(() => false);
|
const valid = await this.$refs.formRef.validate().catch(() => false);
|
||||||
if (!valid || !this.form.formalSettlementId) return this.$message.warning('请选择正式结算单');
|
if (!valid || !this.form.formalSettlementId) {
|
||||||
|
this.$message.warning('请选择正式结算单');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await api.save({
|
const data = this.unwrapData(
|
||||||
|
await api.save({
|
||||||
id: this.currentId,
|
id: this.currentId,
|
||||||
formalSettlementId: this.form.formalSettlementId,
|
formalSettlementId: this.form.formalSettlementId,
|
||||||
reconciliationMode: this.form.reconciliationMode,
|
reconciliationMode: this.form.reconciliationMode,
|
||||||
reconciliationDate: this.form.reconciliationDate,
|
reconciliationDate: this.form.reconciliationDate,
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
this.currentId = data;
|
this.currentId = data;
|
||||||
await this.loadDetail();
|
await this.loadDetail();
|
||||||
|
if (!silent) {
|
||||||
this.$message.success('草稿保存成功');
|
this.$message.success('草稿保存成功');
|
||||||
this.$emit('success');
|
this.$emit('success');
|
||||||
|
}
|
||||||
|
return data;
|
||||||
} finally {
|
} finally {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleMatch() {
|
async handleMatch() {
|
||||||
if (!this.currentId) {
|
if (!this.currentId) return this.$message.warning('请先保存草稿后再匹配');
|
||||||
await this.handleSave();
|
|
||||||
if (!this.currentId) return;
|
|
||||||
}
|
|
||||||
this.actionLoading = true;
|
this.actionLoading = true;
|
||||||
try {
|
try {
|
||||||
await api.match(this.currentId);
|
await api.match(this.currentId);
|
||||||
@@ -572,10 +707,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleUpdate() {
|
async handleUpdate() {
|
||||||
if (!this.currentId) return this.$message.warning('请先保存对账单');
|
|
||||||
await this.$confirm('将以外部账单匹配金额更新内部结算明细,是否继续?', '更新账单', {
|
await this.$confirm('将以外部账单匹配金额更新内部结算明细,是否继续?', '更新账单', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
|
const savedId = await this.handleSave(true);
|
||||||
|
if (!savedId) return;
|
||||||
this.actionLoading = true;
|
this.actionLoading = true;
|
||||||
try {
|
try {
|
||||||
await api.updateByMatch(this.currentId);
|
await api.updateByMatch(this.currentId);
|
||||||
@@ -586,22 +722,30 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleComplete() {
|
async handleComplete() {
|
||||||
if (!this.currentId) return this.$message.warning('请先保存对账单');
|
if (
|
||||||
|
Number(this.form.differenceCount || 0) !== 0 ||
|
||||||
|
Number(this.form.differenceQuantity || 0) !== 0 ||
|
||||||
|
Number(this.form.differenceAmount || 0) !== 0
|
||||||
|
) {
|
||||||
|
return this.$message.warning('差异单数、差异货量和差异金额必须全部为0才可完成对账');
|
||||||
|
}
|
||||||
await this.$confirm('完成后对账单不可修改和删除,是否继续?', '完成对账', {
|
await this.$confirm('完成后对账单不可修改和删除,是否继续?', '完成对账', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
|
const savedId = await this.handleSave(true);
|
||||||
|
if (!savedId) return;
|
||||||
this.actionLoading = true;
|
this.actionLoading = true;
|
||||||
try {
|
try {
|
||||||
await api.complete(this.currentId);
|
await api.complete(this.currentId);
|
||||||
await this.loadDetail();
|
await this.loadDetail();
|
||||||
this.$message.success('对账完成');
|
this.$message.success('对账完成');
|
||||||
|
this.visible = false;
|
||||||
this.$emit('success');
|
this.$emit('success');
|
||||||
} finally {
|
} finally {
|
||||||
this.actionLoading = false;
|
this.actionLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
chooseImport() {
|
chooseImport() {
|
||||||
if (!this.currentId) return this.$message.warning('请先保存对账单');
|
|
||||||
this.$refs.fileInput?.click();
|
this.$refs.fileInput?.click();
|
||||||
},
|
},
|
||||||
async handleImport(event) {
|
async handleImport(event) {
|
||||||
@@ -609,6 +753,10 @@ export default {
|
|||||||
event.target.value = '';
|
event.target.value = '';
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
try {
|
try {
|
||||||
|
if (!this.currentId) {
|
||||||
|
const savedId = await this.handleSave(true);
|
||||||
|
if (!savedId) return;
|
||||||
|
}
|
||||||
const response =
|
const response =
|
||||||
this.form.reconciliationMode === 'cargo'
|
this.form.reconciliationMode === 'cargo'
|
||||||
? await api.importCargo(this.currentId, file)
|
? await api.importCargo(this.currentId, file)
|
||||||
@@ -731,6 +879,28 @@ export default {
|
|||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === null || value === undefined || value === '' ? '-' : value;
|
return value === null || value === undefined || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
|
formatMatchedExternalLineNo(value) {
|
||||||
|
return Number(value) < 0 ? '' : this.displayValue(value);
|
||||||
|
},
|
||||||
|
formatTransportQuantity(value) {
|
||||||
|
return value === null || value === undefined || value === ''
|
||||||
|
? '-'
|
||||||
|
: Number(value || 0).toFixed(2);
|
||||||
|
},
|
||||||
|
formatMileage(value) {
|
||||||
|
return Number(value) === -1 ? '' : this.displayValue(value);
|
||||||
|
},
|
||||||
|
formatDate(value) {
|
||||||
|
if (value === null || value === undefined || value === '') return '-';
|
||||||
|
const date = this.$dayjs(value);
|
||||||
|
return date.isValid() ? date.format('YYYY-MM-DD') : this.displayValue(value);
|
||||||
|
},
|
||||||
|
transportTypeLabel(value) {
|
||||||
|
if (value === null || value === undefined || value === '') return '-';
|
||||||
|
return (
|
||||||
|
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
||||||
|
);
|
||||||
|
},
|
||||||
formatMoney(value, currency = 'RMB') {
|
formatMoney(value, currency = 'RMB') {
|
||||||
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
|
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
>
|
>
|
||||||
<el-tab-pane label="应付" name="payable">
|
<el-tab-pane label="应付" name="payable">
|
||||||
<formal-settlement-table-panel
|
<formal-settlement-table-panel
|
||||||
|
:key="activeSettlementType"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="应收" name="receivable">
|
<el-tab-pane label="应收" name="receivable">
|
||||||
<formal-settlement-table-panel
|
<formal-settlement-table-panel
|
||||||
|
:key="activeSettlementType"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -96,7 +98,31 @@
|
|||||||
class="formal-payment-dialog"
|
class="formal-payment-dialog"
|
||||||
>
|
>
|
||||||
<section-card title="付款信息">
|
<section-card title="付款信息">
|
||||||
<el-form :model="paymentDialog.form" label-position="right" label-width="auto">
|
<template v-if="paymentDialog.rows.length > 1">
|
||||||
|
<div class="formal-payment-dialog__batch-tip">
|
||||||
|
已选择 {{ paymentDialog.rows.length }} 条同合同应付正式结算单,请确认各单申请金额。
|
||||||
|
</div>
|
||||||
|
<el-table :data="paymentDialog.rows" border>
|
||||||
|
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="160" />
|
||||||
|
<el-table-column label="剩余可申请金额" min-width="150" align="right">
|
||||||
|
<template #default="{ row }">{{
|
||||||
|
formatMoney(paymentRemaining(row), row.currency)
|
||||||
|
}}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="申请付款金额" min-width="150" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.appliedAmount"
|
||||||
|
:min="0.01"
|
||||||
|
:max="paymentRemaining(row)"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
<el-form v-else :model="paymentDialog.form" label-position="right" label-width="auto">
|
||||||
<el-form-item label="结算单号">{{
|
<el-form-item label="结算单号">{{
|
||||||
paymentDialog.row.formalSettlementNo || '-'
|
paymentDialog.row.formalSettlementNo || '-'
|
||||||
}}</el-form-item>
|
}}</el-form-item>
|
||||||
@@ -134,7 +160,9 @@
|
|||||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||||
import * as api from '@/api/settlement/formalSettlement';
|
import * as api from '@/api/settlement/formalSettlement';
|
||||||
|
import * as paymentApi from '@/api/payment/paymentApplication';
|
||||||
import {
|
import {
|
||||||
formalSettlementSearchFields,
|
formalSettlementSearchFields,
|
||||||
invoiceStatusOptions,
|
invoiceStatusOptions,
|
||||||
@@ -180,6 +208,7 @@ export default {
|
|||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
row: {},
|
row: {},
|
||||||
|
rows: [],
|
||||||
form: { appliedAmount: 0, remark: '' },
|
form: { appliedAmount: 0, remark: '' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -193,11 +222,7 @@ export default {
|
|||||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||||
},
|
},
|
||||||
paymentAvailable() {
|
paymentAvailable() {
|
||||||
return Math.max(
|
return this.paymentRemaining(this.paymentDialog.row);
|
||||||
0,
|
|
||||||
Number(this.paymentDialog.row.settlementAmount || 0) -
|
|
||||||
Number(this.paymentDialog.row.appliedPaymentAmount || 0)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -339,30 +364,93 @@ export default {
|
|||||||
this.$message.success(`同步成功,金蝶单据号:${data}`);
|
this.$message.success(`同步成功,金蝶单据号:${data}`);
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
},
|
},
|
||||||
openPaymentDialog() {
|
async openPaymentDialog() {
|
||||||
const row = this.selectedOne('付款申请');
|
if (!this.selection.length) return this.$message.warning('请至少选择一条正式结算单');
|
||||||
if (!row) return;
|
const rows = this.selection;
|
||||||
if (row.approvalStatus !== 'approved' || row.settlementType !== 'payable')
|
if (rows.some(row => row.approvalStatus !== 'approved' || row.settlementType !== 'payable'))
|
||||||
return this.$message.warning('仅审批通过的应付正式结算单允许发起付款申请');
|
return this.$message.warning('仅审批通过的应付正式结算单允许发起付款申请');
|
||||||
this.paymentDialog = {
|
const contractIds = new Set(rows.map(row => String(row.contractId || '')));
|
||||||
visible: true,
|
if (contractIds.size !== 1 || contractIds.has(''))
|
||||||
loading: false,
|
return this.$message.warning('付款申请必须选择同一个合同的正式结算单');
|
||||||
row,
|
const paymentRows = rows.map(row => ({ ...row, appliedAmount: this.paymentRemaining(row) }));
|
||||||
form: { appliedAmount: 0, remark: '' },
|
if (paymentRows.some(row => row.appliedAmount <= 0))
|
||||||
|
return this.$message.warning('所选正式结算单均须存在剩余可申请金额');
|
||||||
|
try {
|
||||||
|
const sourceFormalSettlements = await Promise.all(
|
||||||
|
paymentRows.map(async row => {
|
||||||
|
const [amountResponse, detailResponse] = await Promise.all([
|
||||||
|
paymentApi.getReferenceAmount('settlement_payment', row.id),
|
||||||
|
api.getDetail(row.id),
|
||||||
|
]);
|
||||||
|
const amount = this.unwrapData(amountResponse) || {};
|
||||||
|
const detail = this.unwrapData(detailResponse) || {};
|
||||||
|
const remainingAmount = Number(amount.payableAmount ?? this.paymentRemaining(row));
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
id: row.id,
|
||||||
|
settlementId: row.id,
|
||||||
|
projectId: detail.projectId ?? row.projectId,
|
||||||
|
projectName: detail.projectName || row.projectName,
|
||||||
|
deptId: detail.deptId ?? row.deptId,
|
||||||
|
deptName: detail.deptName || row.deptName,
|
||||||
|
contractId: detail.contractId ?? row.contractId,
|
||||||
|
contractNo: detail.contractNo || row.contractNo,
|
||||||
|
contractName: detail.contractName || row.contractName,
|
||||||
|
payerName: detail.payerName || row.payerName,
|
||||||
|
payeeName: detail.payeeName || row.payeeName,
|
||||||
|
formalSettlementNo: detail.formalSettlementNo || row.formalSettlementNo,
|
||||||
|
settlementAmount: Number(
|
||||||
|
amount.settlementAmount ?? detail.settlementAmount ?? row.settlementAmount ?? 0
|
||||||
|
),
|
||||||
|
payableAmount: remainingAmount,
|
||||||
|
appliedAmount: remainingAmount,
|
||||||
|
invoices: detail.invoices || [],
|
||||||
|
paymentRecords: detail.paymentRecords || detail.paymentApplications || [],
|
||||||
};
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (sourceFormalSettlements.some(row => row.appliedAmount <= 0)) {
|
||||||
|
this.$message.warning('所选正式结算单均须存在剩余可申请金额');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const transferToken = createSettlementTransfer({
|
||||||
|
sourceFormalSettlements,
|
||||||
|
settlementType: 'payable',
|
||||||
|
});
|
||||||
|
await this.$router.push({
|
||||||
|
path: '/payment/payment-application/form',
|
||||||
|
query: { mode: 'add', paymentType: 'settlement_payment', transferToken },
|
||||||
|
});
|
||||||
|
this.selection = [];
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('结算信息加载失败,请稍后重试');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async submitPayment() {
|
async submitPayment() {
|
||||||
if (Number(this.paymentDialog.form.appliedAmount || 0) <= 0)
|
const paymentRows = this.paymentDialog.rows;
|
||||||
|
if (paymentRows.length > 1) {
|
||||||
|
if (paymentRows.some(row => Number(row.appliedAmount || 0) <= 0))
|
||||||
|
return this.$message.warning('请输入各正式结算单的申请付款金额');
|
||||||
|
if (paymentRows.some(row => Number(row.appliedAmount) > this.paymentRemaining(row)))
|
||||||
|
return this.$message.warning('申请付款金额不能超过剩余可申请金额');
|
||||||
|
} else if (Number(this.paymentDialog.form.appliedAmount || 0) <= 0) {
|
||||||
return this.$message.warning('请输入申请付款金额');
|
return this.$message.warning('请输入申请付款金额');
|
||||||
|
}
|
||||||
this.paymentDialog.loading = true;
|
this.paymentDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
const response = await api.applyPayment({
|
const response =
|
||||||
id: this.paymentDialog.row.id,
|
paymentRows.length > 1
|
||||||
...this.paymentDialog.form,
|
? await api.applyPayments({
|
||||||
});
|
items: paymentRows.map(row => ({ id: row.id, appliedAmount: row.appliedAmount })),
|
||||||
|
remark: this.paymentDialog.form.remark,
|
||||||
|
})
|
||||||
|
: await api.applyPayment({ id: this.paymentDialog.row.id, ...this.paymentDialog.form });
|
||||||
const data = this.unwrapData(response);
|
const data = this.unwrapData(response);
|
||||||
this.$message.success(`付款申请已生成:${data}`);
|
this.$message.success(
|
||||||
|
Array.isArray(data) ? `已生成 ${data.length} 条付款申请` : `付款申请已生成:${data}`
|
||||||
|
);
|
||||||
this.paymentDialog.visible = false;
|
this.paymentDialog.visible = false;
|
||||||
|
this.selection = [];
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
} finally {
|
} finally {
|
||||||
this.paymentDialog.loading = false;
|
this.paymentDialog.loading = false;
|
||||||
@@ -474,6 +562,12 @@ export default {
|
|||||||
formatMoney(value, currency = 'RMB') {
|
formatMoney(value, currency = 'RMB') {
|
||||||
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
|
return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`;
|
||||||
},
|
},
|
||||||
|
paymentRemaining(row) {
|
||||||
|
const remaining =
|
||||||
|
row?.remainingPayableAmount ??
|
||||||
|
Number(row?.settlementAmount || 0) - Number(row?.appliedPaymentAmount || 0);
|
||||||
|
return Math.max(0, Number(remaining || 0));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -512,6 +606,10 @@ export default {
|
|||||||
.formal-page__table-panel {
|
.formal-page__table-panel {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
.formal-payment-dialog__batch-tip {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
.formal-page :deep(.el-table) {
|
.formal-page :deep(.el-table) {
|
||||||
--el-table-border-color: #eff1f7;
|
--el-table-border-color: #eff1f7;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,10 +138,11 @@ export default {
|
|||||||
async loadTable() {
|
async loadTable() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await api.getList(this.page.current, this.page.size, {
|
const response = await api.getList(this.page.current, this.page.size, {
|
||||||
...this.query,
|
...this.query,
|
||||||
settlementType: this.settlementType,
|
settlementType: this.settlementType,
|
||||||
});
|
});
|
||||||
|
const data = response?.data?.data || response?.data || response || {};
|
||||||
this.rows = data.records || [];
|
this.rows = data.records || [];
|
||||||
this.page.total = data.total || 0;
|
this.page.total = data.total || 0;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -202,10 +203,11 @@ export default {
|
|||||||
this.loadTable();
|
this.loadTable();
|
||||||
},
|
},
|
||||||
async handleExport() {
|
async handleExport() {
|
||||||
const { data } = await api.getList(1, 100000, {
|
const response = await api.getList(1, 100000, {
|
||||||
...this.query,
|
...this.query,
|
||||||
settlementType: this.settlementType,
|
settlementType: this.settlementType,
|
||||||
});
|
});
|
||||||
|
const data = response?.data?.data || response?.data || response || {};
|
||||||
const exportRows = (data.records || []).map(item => ({
|
const exportRows = (data.records || []).map(item => ({
|
||||||
对账单号: item.reconciliationNo,
|
对账单号: item.reconciliationNo,
|
||||||
付款方: item.payerName,
|
付款方: item.payerName,
|
||||||
|
|||||||
+13
-13
@@ -47,26 +47,26 @@ export default ({ mode, command }) => {
|
|||||||
__VUE_I18N_LEGACY_API__: true,
|
__VUE_I18N_LEGACY_API__: true,
|
||||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||||
},
|
},
|
||||||
// server: {
|
|
||||||
// port: 2888,
|
|
||||||
// proxy: {
|
|
||||||
// '/api': {
|
|
||||||
// target: 'http://localhost',
|
|
||||||
// //target: 'https://saber3.bladex.cn/api',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// rewrite: path => path.replace(/^\/api/, ''),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
server: {
|
server: {
|
||||||
port: 2889,
|
port: 2888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://172.16.203.228:8000',
|
target: 'http://localhost',
|
||||||
|
//target: 'https://saber3.bladex.cn/api',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
rewrite: path => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// server: {
|
||||||
|
// port: 2889,
|
||||||
|
// proxy: {
|
||||||
|
// '/api': {
|
||||||
|
// target: 'http://172.16.203.228:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': resolve(__dirname, './'),
|
'~': resolve(__dirname, './'),
|
||||||
|
|||||||
Reference in New Issue
Block a user