完善收付款管理
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 },
|
||||
});
|
||||
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 = [
|
||||
{ label: '未认领', value: 'unclaimed' },
|
||||
|
||||
@@ -30,5 +30,4 @@ export const invoiceApplicationDetailColumns = [
|
||||
{ prop: 'mileage', label: '里程(KM)', minWidth: 120 },
|
||||
{ prop: 'batchNo', label: '批次号', minWidth: 120 },
|
||||
{ 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 title="附件信息">
|
||||
<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 }">
|
||||
<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"
|
||||
/>
|
||||
<vehicle-attachment-table v-model="form.attachments" />
|
||||
</section-card>
|
||||
|
||||
<section-card title="备注">
|
||||
@@ -279,6 +238,7 @@ import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/billLedger';
|
||||
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -302,6 +262,7 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'BillLedgerForm',
|
||||
components: { VehicleAttachmentTable },
|
||||
data() {
|
||||
return {
|
||||
form: emptyForm(),
|
||||
|
||||
@@ -75,51 +75,7 @@
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件信息">
|
||||
<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 }">
|
||||
<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"
|
||||
/>
|
||||
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
||||
</section-card>
|
||||
|
||||
<div class="bill-payment-form-page__actions">
|
||||
@@ -163,6 +119,11 @@
|
||||
<template #default="{ row }">{{ formatMoney(row.availableBalance) }}</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
<template #footer>
|
||||
<el-button @click="billDialog.visible = false">取消</el-button>
|
||||
@@ -177,6 +138,7 @@ import { Search } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/billPayment';
|
||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -194,6 +156,7 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'BillPaymentForm',
|
||||
components: { VehicleAttachmentTable },
|
||||
data() {
|
||||
return {
|
||||
Search,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<section class="bill-ledger-table">
|
||||
<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>
|
||||
@@ -94,13 +93,9 @@ export default {
|
||||
.bill-ledger-table__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.bill-ledger-table__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.bill-ledger-table__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
:disabled="readonly"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
:multiple-limit="3"
|
||||
placeholder="请选择客商开票信息邮箱"
|
||||
no-data-text="当前客商开票信息未配置邮箱"
|
||||
>
|
||||
<el-option
|
||||
v-for="email in departmentEmailOptions"
|
||||
@@ -203,9 +203,14 @@
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="商品和服务分类" min-width="160">
|
||||
<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
|
||||
v-for="item in goodsCategoryOptions"
|
||||
v-for="item in invoiceItemCategories"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
@@ -215,12 +220,17 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="货物或服务简称" min-width="170">
|
||||
<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
|
||||
v-for="item in goodsNameOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
v-for="item in invoiceItemNames(row.goodsCategory)"
|
||||
:key="item.shortName"
|
||||
:label="item.shortName"
|
||||
:value="item.shortName"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -358,7 +368,15 @@
|
||||
<el-link v-if="column.link && row[column.prop]" type="primary">
|
||||
{{ row[column.prop] }}
|
||||
</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.prop === 'transportType'">{{ transportTypeLabel(row.transportType) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -366,41 +384,7 @@
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件信息">
|
||||
<el-table :data="form.attachments" border>
|
||||
<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>
|
||||
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
||||
</section-card>
|
||||
|
||||
<section-card title="备注">
|
||||
@@ -469,6 +453,11 @@
|
||||
<el-table-column label="可开票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.availableInvoiceAmount) }}</template>
|
||||
</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>
|
||||
<template #footer>
|
||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||
@@ -481,13 +470,16 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
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 VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
let localId = 0;
|
||||
const emptyLine = () => ({
|
||||
localId: ++localId,
|
||||
goodsCategory: '交通运输服务',
|
||||
goodsName: '运费',
|
||||
goodsCategory: '',
|
||||
goodsName: '',
|
||||
unit: '吨',
|
||||
quantity: 0,
|
||||
unitPriceNoTax: 0,
|
||||
@@ -525,6 +517,9 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'InvoiceApplicationForm',
|
||||
components: {
|
||||
VehicleAttachmentTable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: emptyForm(),
|
||||
@@ -533,29 +528,14 @@ export default {
|
||||
receiverInvoiceOptions: [],
|
||||
contactOptions: [],
|
||||
departmentEmailOptions: [],
|
||||
invoiceItems: [],
|
||||
transportTypeOptions: [],
|
||||
invoiceTypeOptions: api.invoiceTypeOptions,
|
||||
detailColumns: invoiceApplicationDetailColumns,
|
||||
goodsCategoryOptions: ['交通运输服务', '生产生活服务', '其他现代服务'],
|
||||
goodsNameOptions: ['运费', '装卸搬运服务', '过路费', '仓储服务', '代理服务费'],
|
||||
detailBaseColumns: invoiceApplicationDetailColumns.filter(
|
||||
column => !['feeItemsJson', 'freightAmount'].includes(column.prop)
|
||||
),
|
||||
feeItemColumns: [],
|
||||
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: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
@@ -566,9 +546,7 @@ export default {
|
||||
rules: {
|
||||
settlementIds: [{ validator: this.validateSettlements, trigger: 'change' }],
|
||||
invoiceType: [{ required: true, message: '请选择发票类型', trigger: 'change' }],
|
||||
departmentEmails: [
|
||||
{ type: 'array', required: true, min: 1, message: '请选择部门邮箱', trigger: 'change' },
|
||||
],
|
||||
departmentEmails: [{ validator: this.validateDepartmentEmails, trigger: 'change' }],
|
||||
receiverInvoiceInfoId: [{ required: true, message: '请选择受票方单位', trigger: 'change' }],
|
||||
contactPhone: [{ pattern: /^\d{11}$/, message: '联系电话必须为11位数字', trigger: 'blur' }],
|
||||
taxpayerNo: [{ validator: this.validateInvoiceInfoField, trigger: 'change' }],
|
||||
@@ -614,11 +592,19 @@ export default {
|
||||
0
|
||||
);
|
||||
},
|
||||
allocatedTotal() {
|
||||
return this.form.settlements.reduce(
|
||||
(total, item) => total + Number(item.allocatedInvoiceAmount || 0),
|
||||
0
|
||||
);
|
||||
detailColumns() {
|
||||
return [...this.detailBaseColumns, ...this.feeItemColumns];
|
||||
},
|
||||
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() {
|
||||
@@ -641,22 +627,25 @@ export default {
|
||||
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() {
|
||||
await Promise.all([this.loadInvoiceItems(), this.loadTransportTypeOptions()]);
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
const userEmail = userInfo.email || '';
|
||||
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
||||
if (!this.recordId) {
|
||||
this.form.applicantName = userInfo.realName || userInfo.userName || '';
|
||||
if (userEmail) this.form.departmentEmails = [userEmail];
|
||||
return;
|
||||
}
|
||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
departmentEmails: String(data.departmentEmails || '')
|
||||
.split(';')
|
||||
.filter(Boolean),
|
||||
departmentEmails: this.parseEmails(data.departmentEmails),
|
||||
settlements: (data.settlements || []).map(item => ({
|
||||
...item,
|
||||
settlementId: item.formalSettlementId,
|
||||
@@ -669,12 +658,76 @@ export default {
|
||||
attachments: this.parse(data.attachmentsJson),
|
||||
};
|
||||
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);
|
||||
if (this.settlementIds.length) {
|
||||
await Promise.all([this.loadReceiverInformation(), this.loadSettlementDetails(false)]);
|
||||
}
|
||||
if (this.readonly) this.$nextTick(() => this.$refs.formRef?.clearValidate('departmentEmails'));
|
||||
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) {
|
||||
if (!this.form.settlements.length) {
|
||||
callback(new Error('请选择正式结算单'));
|
||||
@@ -715,6 +768,27 @@ export default {
|
||||
}
|
||||
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() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef?.validateField([
|
||||
@@ -746,6 +820,12 @@ export default {
|
||||
this.$message.warning('请至少选择一张应付正式结算单');
|
||||
return;
|
||||
}
|
||||
await this.applySettlements(rows);
|
||||
},
|
||||
async selectSettlement(row) {
|
||||
await this.applySettlements([row]);
|
||||
},
|
||||
async applySettlements(rows) {
|
||||
const first = rows[0];
|
||||
const incompatible = rows.some(
|
||||
item =>
|
||||
@@ -775,23 +855,84 @@ export default {
|
||||
async loadSettlementDetails(resetSelection = true) {
|
||||
this.detailRows =
|
||||
this.unwrapData(await api.getSettlementDetails(this.settlementIds.join(','))) || [];
|
||||
this.buildFeeItemColumns();
|
||||
if (resetSelection) this.selectedDetailIds = this.detailRows.map(item => item.id);
|
||||
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() {
|
||||
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 || [];
|
||||
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;
|
||||
if (!this.form.receiverInvoiceInfoId) {
|
||||
const invoiceInfo =
|
||||
let invoiceInfo = this.receiverInvoiceOptions.find(
|
||||
item => String(item.id) === String(this.form.receiverInvoiceInfoId)
|
||||
);
|
||||
if (!invoiceInfo) {
|
||||
invoiceInfo =
|
||||
this.receiverInvoiceOptions.find(item => item.isDefault === 1) ||
|
||||
this.receiverInvoiceOptions[0];
|
||||
}
|
||||
if (invoiceInfo) {
|
||||
this.form.receiverInvoiceInfoId = invoiceInfo.id;
|
||||
this.handleReceiverChange(invoiceInfo.id);
|
||||
}
|
||||
}
|
||||
if (!this.form.contactName) {
|
||||
const contact =
|
||||
this.contactOptions.find(item => item.isDefault === 1) || this.contactOptions[0];
|
||||
@@ -846,23 +987,9 @@ export default {
|
||||
const rate = Number(row.taxRate || 0) / 100;
|
||||
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() {
|
||||
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
||||
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) {
|
||||
if (!sheet.lines.length) throw new Error('每张发票至少需要一条商品行');
|
||||
const hasFreight = sheet.lines.some(line => line.goodsName === '运费');
|
||||
@@ -968,12 +1095,6 @@ export default {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
}
|
||||
.invoice-form-page__links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.invoice-form-page__remark :deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
@@ -997,17 +1118,6 @@ export default {
|
||||
:global(.avue-layout--horizontal .invoice-form-page__actions) {
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -47,8 +47,7 @@
|
||||
|
||||
<section class="invoice-page__table-panel">
|
||||
<div class="invoice-page__toolbar">
|
||||
<div class="invoice-page__table-title">开票申请单列表</div>
|
||||
<div class="invoice-page__toolbar-actions">
|
||||
<div class="invoice-page__toolbar-left">
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_application_sync')"
|
||||
type="primary"
|
||||
@@ -69,6 +68,8 @@
|
||||
@click="handleExport"
|
||||
>导出</el-button
|
||||
>
|
||||
</div>
|
||||
<div class="invoice-page__toolbar-right">
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button text :icon="Refresh" @click="loadTable" />
|
||||
</el-tooltip>
|
||||
@@ -442,16 +443,13 @@ export default {
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.invoice-page__toolbar-actions,
|
||||
.invoice-page__toolbar-left,
|
||||
.invoice-page__toolbar-right,
|
||||
.invoice-page__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.invoice-page__table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.invoice-page__links {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
@@ -83,66 +83,6 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</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>
|
||||
</section-card>
|
||||
|
||||
@@ -178,52 +118,7 @@
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件信息">
|
||||
<template #extra>
|
||||
<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"
|
||||
/>
|
||||
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
||||
</section-card>
|
||||
|
||||
<section-card title="备注">
|
||||
@@ -289,6 +184,11 @@
|
||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
<template #footer>
|
||||
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
||||
@@ -331,6 +231,11 @@
|
||||
<el-table-column label="已收票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.receivedInvoiceAmount) }}</template>
|
||||
</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>
|
||||
<template #footer>
|
||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||
@@ -341,9 +246,70 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Download, Search } from '@element-plus/icons-vue';
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/invoiceReceipt';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
const invoicePoolMockRows = [
|
||||
{
|
||||
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 emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -359,6 +325,8 @@ const emptyForm = () => ({
|
||||
bankName: '',
|
||||
bankAccount: '',
|
||||
issuingBank: '',
|
||||
kingdeeBillNo: '',
|
||||
kingdeeStatus: 'unsynced',
|
||||
phone: '',
|
||||
customerEmails: [],
|
||||
departmentEmails: [],
|
||||
@@ -373,29 +341,15 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'InvoiceReceiptForm',
|
||||
components: {
|
||||
VehicleAttachmentTable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Download,
|
||||
Search,
|
||||
form: emptyForm(),
|
||||
customerEmailOptions: [],
|
||||
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 },
|
||||
settlementDialog: {
|
||||
visible: false,
|
||||
@@ -433,12 +387,6 @@ export default {
|
||||
settlementIds() {
|
||||
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() {
|
||||
this.initialize();
|
||||
@@ -523,25 +471,37 @@ export default {
|
||||
async loadInvoicePool() {
|
||||
this.invoiceDialog.loading = true;
|
||||
try {
|
||||
this.invoiceDialog.rows =
|
||||
this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword)) || [];
|
||||
if (!this.recordId) {
|
||||
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 {
|
||||
this.invoiceDialog.loading = false;
|
||||
}
|
||||
},
|
||||
filterInvoicePoolMockRows(keyword) {
|
||||
const normalizedKeyword = String(keyword || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!normalizedKeyword) return invoicePoolMockRows.map(item => ({ ...item }));
|
||||
return invoicePoolMockRows
|
||||
.filter(item =>
|
||||
[item.invoiceNo, item.issuerName, item.receiverName].some(value =>
|
||||
String(value || '')
|
||||
.toLowerCase()
|
||||
.includes(normalizedKeyword)
|
||||
)
|
||||
)
|
||||
.map(item => ({ ...item }));
|
||||
},
|
||||
confirmInvoice(row, options = {}) {
|
||||
const selected = row?.id ? row : this.invoiceDialog.current;
|
||||
if (!selected) {
|
||||
this.$message.warning('请选择一张金蝶票据池发票');
|
||||
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;
|
||||
Object.assign(this.form, {
|
||||
kingdeeInvoicePoolId: selected.id,
|
||||
@@ -556,6 +516,8 @@ export default {
|
||||
bankName: selected.bankName,
|
||||
bankAccount: selected.bankAccount,
|
||||
issuingBank: selected.issuingBank,
|
||||
kingdeeBillNo: selected.kingdeeBillNo || '',
|
||||
kingdeeStatus: selected.kingdeeStatus || 'unsynced',
|
||||
phone: preserveUserInput ? this.form.phone : selected.phone || '',
|
||||
customerEmails: preserveUserInput
|
||||
? this.form.customerEmails
|
||||
@@ -608,13 +570,6 @@ export default {
|
||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||
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 => ({
|
||||
...item,
|
||||
formalSettlementId: item.id,
|
||||
@@ -629,6 +584,10 @@ export default {
|
||||
await this.loadReferenceInformation();
|
||||
this.$refs.formRef?.validateField('settlementIds').catch(() => {});
|
||||
},
|
||||
async selectSettlement(row) {
|
||||
this.settlementDialog.selection = [row];
|
||||
await this.confirmSettlements();
|
||||
},
|
||||
async loadReferenceInformation() {
|
||||
const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(',')));
|
||||
this.customerEmailOptions = data.customerEmails || [];
|
||||
@@ -645,28 +604,6 @@ export default {
|
||||
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) {
|
||||
const id = row.formalSettlementId || row.settlementId || row.id;
|
||||
if (!id) return;
|
||||
@@ -678,9 +615,6 @@ export default {
|
||||
validateBusiness() {
|
||||
const invoiceAmount = Number(this.form.invoiceAmount || 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) {
|
||||
const allocated = Number(row.allocatedInvoiceAmount);
|
||||
if (!Number.isFinite(allocated) || allocated < 0) {
|
||||
@@ -695,6 +629,19 @@ export default {
|
||||
return {
|
||||
id: this.form.id,
|
||||
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,
|
||||
customerEmails: this.form.customerEmails.join(';'),
|
||||
departmentEmails: this.form.departmentEmails.join(';'),
|
||||
@@ -731,7 +678,10 @@ export default {
|
||||
this.$message.warning('请先选择金蝶票据池发票');
|
||||
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);
|
||||
if (!invoice) {
|
||||
this.$message.warning('金蝶票据池中未查询到该发票');
|
||||
@@ -762,18 +712,9 @@ export default {
|
||||
.receipt-form-page__form :deep(.el-input-number) {
|
||||
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) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.receipt-form-page__uploader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.receipt-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -56,8 +56,7 @@
|
||||
|
||||
<section class="receipt-page__table-panel">
|
||||
<div class="receipt-page__toolbar">
|
||||
<div class="receipt-page__table-title">收票单列表</div>
|
||||
<div class="receipt-page__toolbar-actions">
|
||||
<div class="receipt-page__toolbar-left">
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_receipt_sync')"
|
||||
type="primary"
|
||||
@@ -75,6 +74,8 @@
|
||||
@click="handleExport"
|
||||
>导出</el-button
|
||||
>
|
||||
</div>
|
||||
<div class="receipt-page__toolbar-right">
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button text :icon="Refresh" @click="loadTable" />
|
||||
</el-tooltip>
|
||||
@@ -444,11 +445,8 @@ export default {
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.receipt-page__table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.receipt-page__toolbar-actions,
|
||||
.receipt-page__toolbar-left,
|
||||
.receipt-page__toolbar-right,
|
||||
.receipt-page__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -96,51 +96,11 @@
|
||||
</el-table>
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件信息"><template #extra><el-button
|
||||
v-if="form.attachments.length"
|
||||
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'"
|
||||
<section-card title="附件信息">
|
||||
<vehicle-attachment-table
|
||||
v-model="form.attachments"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传附件"
|
||||
class="claim-record-form-page__uploader"
|
||||
@change="handleAttachmentChange"
|
||||
:readonly="form.claimStatus !== 'claimed'"
|
||||
@update:model-value="handleAttachmentUpdate"
|
||||
/>
|
||||
</section-card>
|
||||
|
||||
@@ -156,8 +116,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import * as api from '@/api/payment/receiptClaimRecord';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
receiptNoticeNo: '',
|
||||
@@ -182,26 +142,10 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'ReceiptClaimRecordForm',
|
||||
components: { VehicleAttachmentTable },
|
||||
data() {
|
||||
return {
|
||||
Download,
|
||||
form: emptyForm(),
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
'jpeg',
|
||||
'png',
|
||||
'jpg',
|
||||
'doc',
|
||||
'docx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'xlsx',
|
||||
'xls',
|
||||
'eml',
|
||||
'msg',
|
||||
'zip',
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -241,40 +185,9 @@ export default {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
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();
|
||||
});
|
||||
},
|
||||
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 handleAttachmentUpdate(files) {
|
||||
this.form.attachments = files || [];
|
||||
await this.persistAttachments('附件更新成功');
|
||||
},
|
||||
async persistAttachments(message) {
|
||||
await api.updateAttachments({
|
||||
@@ -296,15 +209,6 @@ export default {
|
||||
formatMoney(value) {
|
||||
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>
|
||||
|
||||
@@ -108,50 +108,8 @@
|
||||
</el-table>
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件信息"><template #extra><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" 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 title="附件信息">
|
||||
<vehicle-attachment-table v-model="form.attachments" />
|
||||
</section-card>
|
||||
|
||||
<section-card title="备注">
|
||||
@@ -190,27 +148,27 @@
|
||||
border
|
||||
@selection-change="settlementDialog.selection = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="52" align="center" /><el-table-column
|
||||
prop="formalSettlementNo"
|
||||
label="结算单号"
|
||||
min-width="170"
|
||||
/><el-table-column prop="projectName" label="所属项目" min-width="150" /><el-table-column
|
||||
prop="deptName"
|
||||
label="所属组织"
|
||||
min-width="150"
|
||||
/><el-table-column prop="payerName" label="付款方" min-width="150" /><el-table-column
|
||||
prop="payeeName"
|
||||
label="收款方"
|
||||
min-width="150"
|
||||
/><el-table-column label="结算总金额" min-width="140" align="right"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.settlementAmount)
|
||||
}}</template></el-table-column
|
||||
><el-table-column label="已认领收款金额" min-width="150" align="right"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.claimedReceiptAmount)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column type="selection" width="52" align="center" />
|
||||
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
||||
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
||||
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
||||
<el-table-column prop="payerName" label="付款方" min-width="150" />
|
||||
<el-table-column prop="payeeName" label="收款方" min-width="150" />
|
||||
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatMoney(row.settlementAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已认领收款金额" min-width="150" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatMoney(row.claimedReceiptAmount) }}
|
||||
</template>
|
||||
</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>
|
||||
<template #footer
|
||||
><el-button @click="settlementDialog.visible = false">取消</el-button
|
||||
@@ -221,8 +179,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Download, Search } from '@element-plus/icons-vue';
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import * as api from '@/api/payment/receiptFlow';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -247,28 +206,12 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'ReceiptFlowForm',
|
||||
components: { VehicleAttachmentTable },
|
||||
data() {
|
||||
return {
|
||||
Download,
|
||||
Search,
|
||||
form: emptyForm(),
|
||||
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: [] },
|
||||
rules: {
|
||||
settlementIds: [
|
||||
@@ -359,13 +302,6 @@ export default {
|
||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||
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 => ({
|
||||
...item,
|
||||
formalSettlementId: item.id,
|
||||
@@ -376,31 +312,13 @@ export default {
|
||||
this.settlementDialog.visible = false;
|
||||
this.$nextTick(() => this.$refs.formRef?.validateField('settlementIds'));
|
||||
},
|
||||
selectSettlement(row) {
|
||||
this.settlementDialog.selection = [row];
|
||||
this.confirmSettlements();
|
||||
},
|
||||
remainingAmount(row) {
|
||||
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() {
|
||||
await this.$refs.formRef.validate();
|
||||
const amount = Number(this.form.receiptAmount || 0);
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
|
||||
<section class="receipt-flow-page__table-panel">
|
||||
<div class="receipt-flow-page__toolbar">
|
||||
<div />
|
||||
<el-button
|
||||
v-if="hasPermission('receipt_flow_sync')"
|
||||
type="primary"
|
||||
@@ -110,6 +109,33 @@ import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/receiptFlow';
|
||||
import { receiptFlowTableColumns } from '@/option/payment/receiptFlow';
|
||||
|
||||
const receiptFlowMockRows = [
|
||||
{
|
||||
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 emptyQuery = () => ({
|
||||
receiptNoticeNo: '',
|
||||
counterpartyName: '',
|
||||
@@ -184,7 +210,9 @@ export default {
|
||||
async handleSync() {
|
||||
this.syncing = true;
|
||||
try {
|
||||
const syncedCount = Number(this.unwrapData(await api.sync()) || 0);
|
||||
const syncedCount = Number(
|
||||
this.unwrapData(await api.sync({ flows: receiptFlowMockRows })) || 0
|
||||
);
|
||||
this.$message.success(`流水同步完成,共同步${syncedCount}条`);
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
@@ -246,7 +274,7 @@ export default {
|
||||
}
|
||||
.receipt-flow-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user