Compare commits

..

6 Commits

7 changed files with 213 additions and 68 deletions
@@ -14,21 +14,53 @@
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.salerName" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="受票单位">
<el-input v-model="query.buyerName" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="开票单位(销方)">
<el-select
v-model="query.salerTaxNo" v-model="query.salerTaxNo"
clearable clearable
style="width: 180px" filterable
remote
reserve-keyword
:remote-method="keyword => searchCustomers(keyword, 'saler')"
:loading="customerLoading.saler"
placeholder="输入名称搜索或直接粘贴税号"
style="width: 220px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> >
<el-option
v-for="item in customerOptions.saler"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="购方(收票方税号)"> <el-form-item label="受票单位(购方)">
<el-input <el-select
v-model="query.buyerTaxNo" v-model="query.buyerTaxNo"
clearable clearable
style="width: 180px" filterable
remote
reserve-keyword
:remote-method="keyword => searchCustomers(keyword, 'buyer')"
:loading="customerLoading.buyer"
placeholder="输入名称搜索或直接粘贴税号"
style="width: 220px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> >
<el-option
v-for="item in customerOptions.buyer"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="发票类型"> <el-form-item label="发票类型">
<el-select <el-select
@@ -46,6 +78,18 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="认领状态">
<el-select v-model="query.claimed" clearable placeholder="不限" style="width: 120px">
<el-option label="未认领" :value="false" />
<el-option label="已认领" :value="true" />
</el-select>
</el-form-item>
<el-form-item label="登记状态">
<el-select v-model="query.registered" clearable placeholder="不限" style="width: 120px">
<el-option label="未登记" :value="false" />
<el-option label="已登记" :value="true" />
</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>
@@ -91,7 +135,7 @@
<el-table-column label="剩余可用金额" min-width="130" align="right"> <el-table-column label="剩余可用金额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.availableTaxAmount) }}</template> <template #default="{ row }">{{ formatMoney(row.availableTaxAmount) }}</template>
</el-table-column> </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 }">
<el-tag v-if="row.claimed" type="danger" size="small">已认领</el-tag> <el-tag v-if="row.claimed" type="danger" size="small">已认领</el-tag>
@@ -129,12 +173,17 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="kingdee-pool-dialog__pager"> <div class="kingdee-pool-dialog__pager">
<span class="kingdee-pool-dialog__pager-info">第 {{ page.current }} 页</span> <span class="kingdee-pool-dialog__pager-tip">按名称检索范围为已同步票,未找到请改用发票号码或税号精确查找</span>
<el-button :disabled="page.current <= 1 || loading" @click="prevPage">上一页</el-button> <el-pagination
<el-button :disabled="!page.hasMore || loading" @click="nextPage">下一页</el-button> v-model:current-page="page.current"
<el-select v-model="page.size" style="width: 110px" @change="handleSizeChange"> v-model:page-size="page.size"
<el-option v-for="n in [10, 20, 50, 100]" :key="n" :label="`${n}条/页`" :value="n" /> :total="page.total"
</el-select> :page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:disabled="loading"
@current-change="load"
@size-change="handleSizeChange"
/>
</div> </div>
<template #footer> <template #footer>
<el-button @click="visible = false">取消</el-button> <el-button @click="visible = false">取消</el-button>
@@ -144,6 +193,8 @@
</template> </template>
<script> <script>
import request from '@/axios';
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
import { import {
downloadKingdeeInvoiceFile, downloadKingdeeInvoiceFile,
isKingdeePathAttachment, isKingdeePathAttachment,
@@ -164,7 +215,7 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
// 与后端 BfiConstant.INVOICE_TYPE_NAMES 对齐:value 为发票类型编码,label 为中文名 // 与后端 BfiConstant.INVOICE_TYPE_NAMES 对齐:value 为发票类型编码(镜像按 invoice_type_code 精确匹配)
// 选票场景只开放数电票(普票/专票) // 选票场景只开放数电票(普票/专票)
invoiceTypeOptions: [ invoiceTypeOptions: [
{ value: '26', label: '数电发票(普通发票)' }, { value: '26', label: '数电发票(普通发票)' },
@@ -173,15 +224,12 @@ export default {
rows: [], rows: [],
selected: [], selected: [],
current: null, current: null,
page: { current: 1, size: 10, hasMore: false }, page: { current: 1, size: 10, total: 0 },
// 续扫游标(服务端过滤补页后翻页须按游标对齐,防漏行/重行)
nextCursor: null,
// 下一页请求待回传的游标:nextPage 时暂存,load 成功或失败后清空
pendingCursor: null,
// 已浏览页缓存:上一页直接回显不再请求(0 远程调用)
pageCache: {},
// 正在查看/下载附件的发票号(行级 loading 守卫) // 正在查看/下载附件的发票号(行级 loading 守卫)
attachmentBusy: null, attachmentBusy: null,
// 销/购方客商下拉:远程按名称搜客商档案,选中带出税号查询;手动输入的裸税号直接作为值过滤
customerOptions: { saler: [], buyer: [] },
customerLoading: { saler: false, buyer: false },
query: this.emptyQuery(), query: this.emptyQuery(),
}; };
}, },
@@ -201,14 +249,18 @@ export default {
invoiceNo: '', invoiceNo: '',
// 默认查近一个月,随请求传后端并在页面回显 // 默认查近一个月,随请求传后端并在页面回显
invoiceDateRange: [this.$dayjs().subtract(1, 'month').format('YYYY-MM-DD'), this.$dayjs().format('YYYY-MM-DD')], invoiceDateRange: [this.$dayjs().subtract(1, 'month').format('YYYY-MM-DD'), this.$dayjs().format('YYYY-MM-DD')],
salerName: '',
buyerName: '',
salerTaxNo: '', salerTaxNo: '',
buyerTaxNo: '', buyerTaxNo: '',
invoiceType: '', invoiceType: '',
claimed: null,
registered: null,
}; };
}, },
// 远程实时查询的票未入本地镜像,id 恒空,用发票号码兜底作为行主键 // 镜像行主键为 id,回源入库前的响应仍可能无 id,用发票号码兜底
rowKey(row) { rowKey(row) {
return row.invoiceNo || row.id; return row.id || row.invoiceNo;
}, },
// 票池行附件字段为复数 attachmentsJson,行内首项即发票文件({source,path,name}) // 票池行附件字段为复数 attachmentsJson,行内首项即发票文件({source,path,name})
rowAttachment(row = {}) { rowAttachment(row = {}) {
@@ -261,46 +313,52 @@ 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();
},
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.load();
},
nextPage() {
this.pendingCursor = this.nextCursor;
this.page.current += 1;
this.load(); this.load();
}, },
resetQuery() { resetQuery() {
this.query = this.emptyQuery(); this.query = this.emptyQuery();
this.handleQuery(); this.handleQuery();
}, },
// 新查询/换页容量/重新打开时清空游标与已浏览页缓存,重新锚定扫描 // 客商名称远程搜索:含 18 位社会信用代码特征(数字+大写字母)时按裸税号直查;
resetScanState() { // 否则搜客商档案,取客商发票信息(blade_customer_invoice_info)的抬头/税号作选项——票池税号是开票税号,非统一信用代码
this.nextCursor = null; async searchCustomers(keyword, side) {
this.pendingCursor = null; const text = String(keyword || '').trim();
this.pageCache = {}; if (!text) return;
if (/^[0-9A-Z]{15,20}$/.test(text)) {
this.customerOptions[side] = [{ label: `${text}(手输税号)`, value: text }];
return;
}
this.customerLoading[side] = true;
try {
const data = this.unwrap(await getCustomerList(1, 20, { fullName: text }));
const customers = (data.records || []).filter(item => item.id);
if (!customers.length) {
this.customerOptions[side] = [];
return;
}
// 列表接口不带发票信息,逐户取 detail(detail 组装 invoices,每户一张发票信息)
const options = await Promise.all(
customers.map(async customer => {
const detail = this.unwrap(await request({ url: '/blade-transport/customer-archive/detail', method: 'get', params: { id: customer.id } }));
return (detail?.invoices || [])
.filter(invoice => invoice.taxNo)
.map(invoice => ({ label: `${invoice.invoiceTitle || customer.fullName}(${invoice.taxNo})`, value: invoice.taxNo }));
})
);
this.customerOptions[side] = options.flat().filter(
(option, index, list) => list.findIndex(item => item.value === option.value) === index
);
} finally {
this.customerLoading[side] = false;
}
}, },
unwrap(response) { unwrap(response) {
const body = response?.data || response || {}; const body = response?.data || response || {};
@@ -308,34 +366,26 @@ export default {
}, },
async load() { async load() {
this.loading = true; this.loading = true;
// 请求前快照页码:prev/next 会先改写 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,
salerName: this.query.salerName || undefined,
buyerName: this.query.buyerName || 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,
claimed: this.query.claimed ?? undefined,
registered: this.query.registered ?? undefined,
}) })
); );
this.rows = data.records || []; this.rows = data.records || [];
this.page.hasMore = !!data.hasMore; this.page.total = Number(data.total || 0);
this.nextCursor = data.nextCursor || null;
this.pageCache[this.page.current] = { rows: this.rows, hasMore: this.page.hasMore, nextCursor: this.nextCursor };
} catch (error) {
if (this.page.current !== lastRequested) {
this.page.current = lastRequested;
}
throw error;
} finally { } finally {
this.loading = false; this.loading = false;
} }
@@ -373,8 +423,12 @@ export default {
.kingdee-pool-dialog__pager { .kingdee-pool-dialog__pager {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; justify-content: space-between;
justify-content: flex-end;
margin-top: 12px; margin-top: 12px;
gap: 16px;
}
.kingdee-pool-dialog__pager-tip {
color: #909399;
font-size: 12px;
} }
</style> </style>
+1
View File
@@ -13,6 +13,7 @@ export const invoiceApplicationTableColumns = [
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 130 }, { prop: 'currentProcessor', label: '当前处理人', minWidth: 130 },
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 150 }, { prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 150 },
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 140 }, { prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 140 },
{ prop: 'kingdeeLockStatus', label: '金蝶锁状态', minWidth: 150 },
{ prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 }, { prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 },
]; ];
+1
View File
@@ -17,5 +17,6 @@ export const paymentApplicationTableColumns = [
{ prop: 'currentProcessor', label: '当前处理人', minWidth: 120 }, { prop: 'currentProcessor', label: '当前处理人', minWidth: 120 },
{ prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 130 }, { prop: 'kingdeeBillNo', label: '金蝶单据号', minWidth: 130 },
{ prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 130 }, { prop: 'kingdeeStatusName', label: '金蝶单据状态', minWidth: 130 },
{ prop: 'kingdeeLockStatus', label: '金蝶锁状态', minWidth: 150 },
{ prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 }, { prop: 'attachmentSyncStatusName', label: '附件同步', minWidth: 100 },
]; ];
@@ -21,6 +21,7 @@ export const formalSettlementTableColumns = [
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 }, { label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 }, { label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 }, { label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 },
{ label: '金蝶锁状态', prop: 'kingdeeLockStatus', minWidth: 150 },
{ label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100 }, { label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100 },
{ label: '创建人', prop: 'createUserName', minWidth: 110 }, { label: '创建人', prop: 'createUserName', minWidth: 110 },
{ label: '创建时间', prop: 'createTime', minWidth: 170 }, { label: '创建时间', prop: 'createTime', minWidth: 170 },
+31 -1
View File
@@ -115,6 +115,12 @@
{{ 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>
<template v-else-if="column.prop === 'kingdeeLockStatus'">
<el-tooltip v-if="lockTag(row).show" :content="lockTag(row).tip" placement="top">
<el-tag :type="lockTag(row).type" class="status-text">{{ lockTag(row).label }}</el-tag>
</el-tooltip>
<span v-else>-</span>
</template>
<span v-else>{{ displayValue(row[column.prop]) }}</span> <span v-else>{{ displayValue(row[column.prop]) }}</span>
</template> </template>
</el-table-column> </el-table-column>
@@ -183,7 +189,9 @@
> >
<el-link <el-link
v-if=" v-if="
hasPermission('invoice_application_void') && row.approvalStatus === 'approved' hasPermission('invoice_application_void') &&
row.approvalStatus === 'approved' &&
row.kingdeeLockStatus !== 'locked'
" "
type="danger" type="danger"
@click="handleVoid(row)" @click="handleVoid(row)"
@@ -448,6 +456,28 @@ export default {
'' ''
); );
}, },
lockTag(row) {
if (row.kingdeeLockStatus === 'locked') {
return { show: true, type: 'danger', label: '金蝶锁定中', tip: '该单据已被金蝶锁定,不可编辑/作废' };
}
if (row.kingdeeLockStatus === 'failed') {
if ((row.kingdeeTryCount || 0) >= 5) {
return {
show: true,
type: 'danger',
label: '锁定失败·已达上限',
tip: row.kingdeeLockFailReason || '自动补偿次数已用完,请重推「同步金蝶」立即补锁',
};
}
return {
show: true,
type: 'warning',
label: '锁定失败·补偿中',
tip: row.kingdeeLockFailReason || '系统将自动补偿重锁',
};
}
return { show: false };
},
displayValue(value) { displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value; return value === null || value === undefined || value === '' ? '-' : value;
}, },
+31 -1
View File
@@ -138,6 +138,12 @@
>{{ row[column.prop] || '-' }}</el-tag >{{ row[column.prop] || '-' }}</el-tag
> >
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span> <span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
<template v-else-if="column.prop === 'kingdeeLockStatus'">
<el-tooltip v-if="lockTag(row).show" :content="lockTag(row).tip" placement="top">
<el-tag :type="lockTag(row).type" class="status-text">{{ lockTag(row).label }}</el-tag>
</el-tooltip>
<span v-else>-</span>
</template>
<span v-else>{{ displayValue(row[column.prop]) }}</span> <span v-else>{{ displayValue(row[column.prop]) }}</span>
</template> </template>
</el-table-column> </el-table-column>
@@ -200,7 +206,9 @@
> >
<el-link <el-link
v-if=" v-if="
hasPermission('payment_application_void') && row.approvalStatus === 'approved' hasPermission('payment_application_void') &&
row.approvalStatus === 'approved' &&
row.kingdeeLockStatus !== 'locked'
" "
type="danger" type="danger"
@click="handleVoid(row)" @click="handleVoid(row)"
@@ -496,6 +504,28 @@ export default {
'' ''
); );
}, },
lockTag(row) {
if (row.kingdeeLockStatus === 'locked') {
return { show: true, type: 'danger', label: '金蝶锁定中', tip: '该单据已被金蝶锁定,不可编辑/作废' };
}
if (row.kingdeeLockStatus === 'failed') {
if ((row.kingdeeTryCount || 0) >= 5) {
return {
show: true,
type: 'danger',
label: '锁定失败·已达上限',
tip: row.kingdeeLockFailReason || '自动补偿次数已用完,请重推「同步金蝶」立即补锁',
};
}
return {
show: true,
type: 'warning',
label: '锁定失败·补偿中',
tip: row.kingdeeLockFailReason || '系统将自动补偿重锁',
};
}
return { show: false };
},
displayValue(value) { displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value; return value === null || value === undefined || value === '' ? '-' : value;
}, },
@@ -88,6 +88,12 @@
<span v-else-if="column.prop === 'kingdeeSyncStatus'">{{ <span v-else-if="column.prop === 'kingdeeSyncStatus'">{{
kingdeeSyncName(row.kingdeeSyncStatus) kingdeeSyncName(row.kingdeeSyncStatus)
}}</span> }}</span>
<template v-else-if="column.prop === 'kingdeeLockStatus'">
<el-tooltip v-if="lockTag(row).show" :content="lockTag(row).tip" placement="top">
<el-tag :type="lockTag(row).type" class="status-text">{{ lockTag(row).label }}</el-tag>
</el-tooltip>
<span v-else>-</span>
</template>
<span v-else>{{ displayValue(row[column.prop]) }}</span> <span v-else>{{ displayValue(row[column.prop]) }}</span>
</template> </template>
</el-table-column> </el-table-column>
@@ -226,6 +232,28 @@ export default {
kingdeeSyncName(value) { kingdeeSyncName(value) {
return kingdeeSyncStatusOptions.find(item => item.value === value)?.label || value || '-'; return kingdeeSyncStatusOptions.find(item => item.value === value)?.label || value || '-';
}, },
lockTag(row) {
if (row.kingdeeLockStatus === 'locked') {
return { show: true, type: 'danger', label: '金蝶锁定中', tip: '该单据已被金蝶锁定,不可编辑/作废' };
}
if (row.kingdeeLockStatus === 'failed') {
if ((row.kingdeeTryCount || 0) >= 5) {
return {
show: true,
type: 'danger',
label: '锁定失败·已达上限',
tip: row.kingdeeLockFailReason || '自动补偿次数已用完,请重推「同步金蝶」立即补锁',
};
}
return {
show: true,
type: 'warning',
label: '锁定失败·补偿中',
tip: row.kingdeeLockFailReason || '系统将自动补偿重锁',
};
}
return { show: false };
},
displayValue(value) { displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value; return value === null || value === undefined || value === '' ? '-' : value;
}, },