Compare commits

..
3 Commits
Author SHA1 Message Date
刘泉佳 aa0aec3704 feat: 批量转结算弹窗新增单据号和合同编码查询条件 2026-09-23 15:39:32 +08:00
刘泉佳 622705325b feat: 选票弹窗新增剩余可用金额列并支持游标翻页与上一页缓存回显
- 下一页回传上页nextCursor续扫,新查询/换页容量/重新打开自动重置游标与页缓存
- 上一页直接回显已浏览页缓存,不再发起请求
- 查询表单删除单据使用状态条件(后端固定查未用),新增剩余可用金额展示列
2026-09-23 15:06:33 +08:00
刘泉佳 4657fc29bd ♻️ 收票管理仅保留推送金蝶收票单并统一命名为批量同步 2026-09-23 11:05:19 +08:00
6 changed files with 56 additions and 62 deletions
+5 -6
View File
@@ -25,8 +25,6 @@ export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post'
export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data }); export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data }); export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const syncKingdee = id =>
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
export const pushKingdee = ids => export const pushKingdee = ids =>
request({ url: `${baseUrl}/push-kingdee`, method: 'post', data: ids }); request({ url: `${baseUrl}/push-kingdee`, method: 'post', data: ids });
@@ -38,8 +36,9 @@ export const approvalStatusOptions = [
{ label: '已作废', value: 'voided' }, { label: '已作废', value: 'voided' },
]; ];
export const kingdeeStatusOptions = [ export const kingdeePushStatusOptions = [
{ label: '未同步', value: 'unsynced' }, { label: '未推送', value: 'unsynced' },
{ label: '已同步', value: 'synced' }, { label: '已保存收票单', value: 'pushed' },
{ label: '同步失败', value: 'failed' }, { label: '已指定应付', value: 'assigned' },
{ label: '推送失败', value: 'failed' },
]; ];
@@ -1,6 +1,6 @@
<template> <template>
<el-dialog v-model="visible" :title="title" width="92%" append-to-body destroy-on-close @open="handleOpen"> <el-dialog v-model="visible" :title="title" width="92%" append-to-body destroy-on-close @open="handleOpen">
<el-form :model="query" inline label-width="90px" @submit.prevent> <el-form :model="query" inline label-width="130px" @submit.prevent>
<el-form-item label="发票号码"> <el-form-item label="发票号码">
<el-input v-model="query.invoiceNo" clearable @keyup.enter="handleQuery" /> <el-input v-model="query.invoiceNo" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@@ -14,7 +14,7 @@
end-placeholder="结束日期" end-placeholder="结束日期"
/> />
</el-form-item> </el-form-item>
<el-form-item label="销方税号"> <el-form-item label="销方(开票方税号)">
<el-input <el-input
v-model="query.salerTaxNo" v-model="query.salerTaxNo"
clearable clearable
@@ -22,7 +22,7 @@
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="购方税号"> <el-form-item label="购方(收票方税号)">
<el-input <el-input
v-model="query.buyerTaxNo" v-model="query.buyerTaxNo"
clearable clearable
@@ -46,16 +46,6 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="单据使用状态">
<el-select v-model="query.expenseStatus" clearable placeholder="不限" style="width: 130px">
<el-option
v-for="option in expenseStatusOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item> <el-form-item>
<el-button :disabled="loading" @click="resetQuery">重置</el-button> <el-button :disabled="loading" @click="resetQuery">重置</el-button>
<el-button type="primary" :disabled="loading" @click="handleQuery">查询</el-button> <el-button type="primary" :disabled="loading" @click="handleQuery">查询</el-button>
@@ -98,6 +88,9 @@
<el-table-column label="税额" min-width="120" align="right"> <el-table-column label="税额" min-width="120" align="right">
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template> <template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="剩余可用金额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.availableTaxAmount) }}</template>
</el-table-column>
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="210" show-overflow-tooltip /> <el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="210" show-overflow-tooltip />
<el-table-column label="认领状态" min-width="100" align="center"> <el-table-column label="认领状态" min-width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
@@ -153,16 +146,16 @@ export default {
{ value: '26', label: '数电发票(普通发票)' }, { value: '26', label: '数电发票(普通发票)' },
{ value: '27', label: '数电发票(增值税专用发票)' }, { value: '27', label: '数电发票(增值税专用发票)' },
], ],
expenseStatusOptions: [
{ value: '1', label: '未用' },
{ value: '30', label: '在用' },
{ value: '60', label: '已用' },
{ value: '65', label: '已入账' },
],
rows: [], rows: [],
selected: [], selected: [],
current: null, current: null,
page: { current: 1, size: 10, hasMore: false }, page: { current: 1, size: 10, hasMore: false },
// 续扫游标(服务端过滤补页后翻页须按游标对齐,防漏行/重行)
nextCursor: null,
// 下一页请求待回传的游标:nextPage 时暂存,load 成功或失败后清空
pendingCursor: null,
// 已浏览页缓存:上一页直接回显不再请求(0 远程调用)
pageCache: {},
query: this.emptyQuery(), query: this.emptyQuery(),
}; };
}, },
@@ -185,8 +178,6 @@ export default {
salerTaxNo: '', salerTaxNo: '',
buyerTaxNo: '', buyerTaxNo: '',
invoiceType: '', invoiceType: '',
// 单据使用状态默认"未用"
expenseStatus: '1',
}; };
}, },
// 远程实时查询的票未入本地镜像,id 恒空,用发票号码兜底作为行主键 // 远程实时查询的票未入本地镜像,id 恒空,用发票号码兜底作为行主键
@@ -200,21 +191,34 @@ export default {
this.selected = []; this.selected = [];
this.$refs.poolTable?.clearSelection(); this.$refs.poolTable?.clearSelection();
this.page.current = 1; this.page.current = 1;
this.resetScanState();
this.load(); this.load();
}, },
handleQuery() { handleQuery() {
this.page.current = 1; this.page.current = 1;
this.resetScanState();
this.load(); this.load();
}, },
handleSizeChange() { handleSizeChange() {
this.page.current = 1; this.page.current = 1;
this.resetScanState();
this.load(); this.load();
}, },
prevPage() { prevPage() {
// 已浏览页直接回显缓存,不再请求
const cached = this.pageCache[this.page.current - 1];
if (cached) {
this.page.current -= 1;
this.rows = cached.rows;
this.page.hasMore = cached.hasMore;
this.nextCursor = cached.nextCursor;
return;
}
this.page.current -= 1; this.page.current -= 1;
this.load(); this.load();
}, },
nextPage() { nextPage() {
this.pendingCursor = this.nextCursor;
this.page.current += 1; this.page.current += 1;
this.load(); this.load();
}, },
@@ -222,6 +226,12 @@ export default {
this.query = this.emptyQuery(); this.query = this.emptyQuery();
this.handleQuery(); this.handleQuery();
}, },
// 新查询/换页容量/重新打开时清空游标与已浏览页缓存,重新锚定扫描
resetScanState() {
this.nextCursor = null;
this.pendingCursor = null;
this.pageCache = {};
},
unwrap(response) { unwrap(response) {
const body = response?.data || response || {}; const body = response?.data || response || {};
return body?.data || body; return body?.data || body;
@@ -230,23 +240,27 @@ export default {
this.loading = true; this.loading = true;
// 请求前快照页码:prev/next 会先改写 current,远程失败时回滚避免停留在错页 // 请求前快照页码:prev/next 会先改写 current,远程失败时回滚避免停留在错页
const lastRequested = this.page.current; const lastRequested = this.page.current;
const cursor = this.pendingCursor;
this.pendingCursor = null;
try { try {
const range = this.query.invoiceDateRange || []; const range = this.query.invoiceDateRange || [];
const data = this.unwrap( const data = this.unwrap(
await this.queryApi({ await this.queryApi({
current: this.page.current, current: this.page.current,
size: this.page.size, size: this.page.size,
cursor: cursor || undefined,
invoiceNo: this.query.invoiceNo || undefined, invoiceNo: this.query.invoiceNo || undefined,
invoiceDateStart: range[0] || undefined, invoiceDateStart: range[0] || undefined,
invoiceDateEnd: range[1] || undefined, invoiceDateEnd: range[1] || undefined,
salerTaxNo: this.query.salerTaxNo || undefined, salerTaxNo: this.query.salerTaxNo || undefined,
buyerTaxNo: this.query.buyerTaxNo || undefined, buyerTaxNo: this.query.buyerTaxNo || undefined,
invoiceType: this.query.invoiceType || undefined, invoiceType: this.query.invoiceType || undefined,
expenseStatus: this.query.expenseStatus || undefined,
}) })
); );
this.rows = data.records || []; this.rows = data.records || [];
this.page.hasMore = !!data.hasMore; this.page.hasMore = !!data.hasMore;
this.nextCursor = data.nextCursor || null;
this.pageCache[this.page.current] = { rows: this.rows, hasMore: this.page.hasMore, nextCursor: this.nextCursor };
} catch (error) { } catch (error) {
if (this.page.current !== lastRequested) { if (this.page.current !== lastRequested) {
this.page.current = lastRequested; this.page.current = lastRequested;
+3 -2
View File
@@ -11,6 +11,7 @@ export const invoiceReceiptTableColumns = [
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 130 }, { prop: 'currentProcessor', label: '当前处理人', minWidth: 130 },
{ prop: 'createUserName', label: '创建人', minWidth: 110 }, { prop: 'createUserName', label: '创建人', minWidth: 110 },
{ prop: 'createTime', label: '创建时间', minWidth: 170 }, { prop: 'createTime', label: '创建时间', minWidth: 170 },
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 150 }, { prop: 'kingdeeReceivedInvoiceNo', label: '金蝶单据号', minWidth: 150 },
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 140 }, { prop: 'kingdeePushStatusName', label: '金蝶状态', minWidth: 130 },
{ prop: 'kingdeePushMsg', label: '失败原因', minWidth: 160 },
]; ];
@@ -88,6 +88,8 @@ export const updateFeeFormFields = [
]; ];
export const transferSearchFields = [ export const transferSearchFields = [
{ label: '单据号', prop: 'documentNo', type: 'input' },
{ label: '合同编码', prop: 'contractNo', type: 'input' },
{ label: '合同名称', prop: 'contractName', type: 'input' }, { label: '合同名称', prop: 'contractName', type: 'input' },
{ label: '批次号', prop: 'batchNo', type: 'input' }, { label: '批次号', prop: 'batchNo', type: 'input' },
{ label: '生成日期', prop: 'generateDateRange', type: 'daterange' }, { label: '生成日期', prop: 'generateDateRange', type: 'daterange' },
@@ -226,8 +226,6 @@ const emptyForm = () => ({
bankName: '', bankName: '',
bankAccount: '', bankAccount: '',
issuingBank: '', issuingBank: '',
kingdeeBillNo: '',
kingdeeStatus: 'unsynced',
phone: '', phone: '',
customerEmails: [], customerEmails: [],
departmentEmails: [], departmentEmails: [],
@@ -395,8 +393,6 @@ 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
@@ -518,8 +514,6 @@ export default {
bankName: this.form.bankName, bankName: this.form.bankName,
bankAccount: this.form.bankAccount, bankAccount: this.form.bankAccount,
issuingBank: this.form.issuingBank, 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(';'),
+10 -26
View File
@@ -40,10 +40,10 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="searchExpanded" label="金蝶单据状态"> <el-form-item v-if="searchExpanded" label="金蝶状态">
<el-select v-model="query.kingdeeStatus" clearable placeholder="请选择"> <el-select v-model="query.kingdeePushStatus" clearable placeholder="请选择">
<el-option <el-option
v-for="item in kingdeeStatusOptions" v-for="item in kingdeePushStatusOptions"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
@@ -66,19 +66,12 @@
<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__toolbar-left"> <div class="receipt-page__toolbar-left">
<el-button
v-if="hasPermission('invoice_receipt_sync')"
type="primary"
plain
@click="handleBatchSync"
>批量同步</el-button
>
<el-button <el-button
v-if="hasPermission('invoice_receipt_sync')" v-if="hasPermission('invoice_receipt_sync')"
type="primary" type="primary"
plain plain
@click="handlePushKingdee" @click="handlePushKingdee"
>推送金蝶收票单</el-button >批量同步</el-button
> >
<el-button v-if="hasPermission('invoice_receipt_add')" type="primary" @click="openCreate" <el-button v-if="hasPermission('invoice_receipt_add')" type="primary" @click="openCreate"
>新增</el-button >新增</el-button
@@ -251,7 +244,7 @@ const emptyQuery = () => ({
deptName: '', deptName: '',
deptPath: [], deptPath: [],
approvalStatus: '', approvalStatus: '',
kingdeeStatus: '', kingdeePushStatus: '',
}); });
export default { export default {
@@ -265,7 +258,7 @@ export default {
query: emptyQuery(), query: emptyQuery(),
searchExpanded: false, searchExpanded: false,
approvalStatusOptions: api.approvalStatusOptions, approvalStatusOptions: api.approvalStatusOptions,
kingdeeStatusOptions: api.kingdeeStatusOptions, kingdeePushStatusOptions: api.kingdeePushStatusOptions,
columns: invoiceReceiptTableColumns, columns: invoiceReceiptTableColumns,
rows: [], rows: [],
selection: [], selection: [],
@@ -370,23 +363,13 @@ export default {
this.$message.success('作废成功'); this.$message.success('作废成功');
this.loadTable(); this.loadTable();
}, },
async handleBatchSync() {
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
if (!rows.length) {
this.$message.warning('请选择至少一条审批通过的收票登记');
return;
}
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
this.$message.success(`已同步${rows.length}条收票登记`);
this.loadTable();
},
async handlePushKingdee() { async handlePushKingdee() {
const rows = this.selection.filter(row => row.approvalStatus === 'approved'); const rows = this.selection.filter(row => row.approvalStatus === 'approved');
if (!rows.length) { if (!rows.length) {
this.$message.warning('请选择至少一条审批通过的收票登记'); this.$message.warning('请选择至少一条审批通过的收票登记');
return; return;
} }
await this.$confirm(`确认将选中的 ${rows.length} 条收票登记推送金蝶收票单?`, '提示', { await this.$confirm(`确认将选中的 ${rows.length} 条收票登记同步至金蝶?`, '提示', {
type: 'warning', type: 'warning',
}); });
const summary = this.unwrapData(await api.pushKingdee(rows.map(row => row.id))); const summary = this.unwrapData(await api.pushKingdee(rows.map(row => row.id)));
@@ -408,8 +391,9 @@ export default {
当前处理人: item.currentProcessor, 当前处理人: item.currentProcessor,
创建人: item.createUserName, 创建人: item.createUserName,
创建时间: item.createTime, 创建时间: item.createTime,
金蝶单据号: item.kingdeeBillNo, 金蝶单据号: item.kingdeeReceivedInvoiceNo,
金蝶单据状态: item.kingdeeStatusName, 金蝶状态: item.kingdeePushStatusName,
失败原因: item.kingdeePushMsg,
})); }));
const workbook = XLSX.utils.book_new(); const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '收票登记'); XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '收票登记');