feat: 付款结算单选收票单全额认领并新增发票登记推送指定应付
This commit is contained in:
@@ -58,5 +58,13 @@ export const applyPayments = data =>
|
||||
request({ url: `${baseUrl}/apply-payments`, method: 'post', data });
|
||||
export const claimInvoices = data =>
|
||||
request({ url: `${baseUrl}/claim-invoices`, method: 'post', data });
|
||||
// 先票后款:发票登记确认推送(认领收票单逐单 A302-6 指定应付),涉及多次金蝶远程调用,覆盖全局超时
|
||||
export const invoiceAssignKingdee = id =>
|
||||
request({
|
||||
url: `${baseUrl}/invoice-assign-kingdee`,
|
||||
method: 'post',
|
||||
timeout: 30000,
|
||||
params: { id },
|
||||
});
|
||||
export const queryInvoicePool = params =>
|
||||
request({ url: `${baseUrl}/invoice-pool`, method: 'get', params });
|
||||
|
||||
@@ -440,8 +440,15 @@
|
||||
/>
|
||||
|
||||
<section-card title="发票信息">
|
||||
<div class="formal-editor__invoice-claim">
|
||||
<el-button type="primary" @click="openInvoiceClaimDialog">认领查询</el-button>
|
||||
<div v-if="editable" class="formal-editor__invoice-claim">
|
||||
<!-- 先票后款:付款结算单选审批通过的收票单(全额认领);收款结算单维持原票池认领 -->
|
||||
<el-button
|
||||
v-if="form.settlementType !== 'receivable'"
|
||||
type="primary"
|
||||
@click="openInvoiceReceiptDialog"
|
||||
>选择收票单</el-button
|
||||
>
|
||||
<el-button v-else type="primary" @click="openInvoiceClaimDialog">认领查询</el-button>
|
||||
</div>
|
||||
<el-table :data="invoices" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
@@ -452,11 +459,6 @@
|
||||
<el-table-column prop="invoiceType" label="发票类型" min-width="150" align="center">
|
||||
<template #default="{ row }">{{ displayValue(row.invoiceType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taxRate" label="税率" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.taxRate === null || row.taxRate === undefined ? '-' : `${row.taxRate}%` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="invoiceAmount"
|
||||
label="发票金额(含税)"
|
||||
@@ -480,8 +482,9 @@
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<!-- 先票后款:收票单认领金额固定=发票金额(全额认领),不可改 -->
|
||||
<el-input-number
|
||||
v-if="editable"
|
||||
v-if="editable && form.settlementType === 'receivable'"
|
||||
v-model="row.matchedAmount"
|
||||
:min="0"
|
||||
:max="Number(row.availableInvoiceAmount || 0)"
|
||||
@@ -546,6 +549,11 @@
|
||||
@confirm="handleInvoiceClaimConfirm"
|
||||
/>
|
||||
|
||||
<invoice-receipt-candidates-dialog
|
||||
v-model="invoiceReceiptDialog.visible"
|
||||
@confirm="handleInvoiceReceiptConfirm"
|
||||
/>
|
||||
|
||||
<section-card
|
||||
v-if="readonly"
|
||||
:title="form.settlementType === 'receivable' ? '收款信息' : '付款信息'"
|
||||
@@ -1068,6 +1076,7 @@ import {
|
||||
settlementDetailPath,
|
||||
} from '@/utils/settlement-detail-link';
|
||||
import { getBfiExchangeRate } from '@/api/base/currency';
|
||||
import InvoiceReceiptCandidatesDialog from './invoice-receipt-candidates-dialog.vue';
|
||||
import {
|
||||
createFormalSettlementForm,
|
||||
formalSettlementFormFields,
|
||||
@@ -1110,6 +1119,7 @@ export default {
|
||||
components: {
|
||||
InfoFilled,
|
||||
KingdeeInvoicePoolDialog,
|
||||
InvoiceReceiptCandidatesDialog,
|
||||
ChangeRecordDetailDialog,
|
||||
ProjectAdvanceWriteOff,
|
||||
},
|
||||
@@ -1158,6 +1168,10 @@ export default {
|
||||
selected: [],
|
||||
confirming: false,
|
||||
},
|
||||
// 先票后款:付款结算单选收票单(审批通过+未认领)弹窗
|
||||
invoiceReceiptDialog: {
|
||||
visible: false,
|
||||
},
|
||||
attachmentUploadFiles: [],
|
||||
attachmentTypeOptions: [],
|
||||
attachmentFileTypes: [
|
||||
@@ -2651,6 +2665,39 @@ export default {
|
||||
openInvoiceClaimDialog() {
|
||||
this.invoiceClaimDialog.visible = true;
|
||||
},
|
||||
// 先票后款:选收票单(审批通过+未被认领),确认后按发票号全额认领
|
||||
openInvoiceReceiptDialog() {
|
||||
this.invoiceReceiptDialog.visible = true;
|
||||
},
|
||||
async handleInvoiceReceiptConfirm(rows) {
|
||||
// 收票单行 → 结算单发票行:匹配金额固定=发票金额(全额认领),附件契约归一为单数 attachmentJson
|
||||
const invoices = rows.map(row => ({
|
||||
...row,
|
||||
attachmentJson: row.attachmentJson || row.attachmentsJson,
|
||||
invoiceType: row.invoiceType || '',
|
||||
taxRate: row.taxRate ?? null,
|
||||
availableInvoiceAmount: Number(row.invoiceAmount || 0),
|
||||
matchedAmount: Number(row.invoiceAmount || 0),
|
||||
}));
|
||||
if (!this.form.id) {
|
||||
const existingNumbers = new Set(this.invoices.map(item => String(item.invoiceNo)));
|
||||
this.invoices = [
|
||||
...this.invoices,
|
||||
...invoices.filter(row => !existingNumbers.has(String(row.invoiceNo))),
|
||||
];
|
||||
this.$message.success(`已选择${invoices.length}条收票单,保存正式结算单后生效`);
|
||||
return;
|
||||
}
|
||||
const existingNumbers = new Set(this.invoices.map(item => String(item.invoiceNo)));
|
||||
const newInvoices = invoices.filter(row => !existingNumbers.has(String(row.invoiceNo)));
|
||||
if (!newInvoices.length) {
|
||||
this.$message.warning('所选收票单均已认领');
|
||||
return;
|
||||
}
|
||||
await claimInvoicesApi({ formalSettlementId: this.form.id, invoices: newInvoices });
|
||||
await this.loadFormalDetail(this.form.id);
|
||||
this.$message.success('收票单认领成功,已同步正式结算单及关联付款单据');
|
||||
},
|
||||
invoicePoolQueryApi(params) {
|
||||
return queryInvoicePool(params);
|
||||
},
|
||||
|
||||
@@ -189,6 +189,13 @@ export default {
|
||||
row.kingdeeSyncStatus !== 'synced'
|
||||
)
|
||||
actions.push({ type: 'void', label: '作废', danger: true });
|
||||
// 先票后款:付款结算单审批通过后可发票登记(关联收票单并推送指定应付)
|
||||
if (
|
||||
this.showPaymentAction &&
|
||||
this.hasPermission('invoice_register') &&
|
||||
row.approvalStatus === 'approved'
|
||||
)
|
||||
actions.push({ type: 'invoiceRegister', label: '发票登记' });
|
||||
return actions;
|
||||
},
|
||||
statusType(status) {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="选择收票单"
|
||||
width="86%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@open="handleOpen"
|
||||
>
|
||||
<div class="invoice-candidates-dialog__search">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="发票号 / 销方(开票单位) / 购方(受票单位)"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-button type="primary" :loading="loading" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="candidatesTable"
|
||||
v-loading="loading"
|
||||
:data="rows"
|
||||
row-key="id"
|
||||
border
|
||||
height="460"
|
||||
@selection-change="selection = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="52" fixed="left" align="center" reserve-selection />
|
||||
<el-table-column prop="invoiceNo" label="发票号码" min-width="180" />
|
||||
<el-table-column prop="invoiceDate" label="开票日期" min-width="110" align="center">
|
||||
<template #default="{ row }">{{ row.invoiceDate || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="invoiceType" label="发票类型" min-width="140" />
|
||||
<el-table-column prop="issuerName" label="开票单位(销方)" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="receiverName" label="受票单位(购方)" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="发票金额(含税)" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="推送状态" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="pushStatusType(row.kingdeePushStatus)" size="small">
|
||||
{{ pushStatusName(row.kingdeePushStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="invoice-candidates-dialog__tip">
|
||||
仅列出审批通过且未被其他付款结算单认领的收票单,最多 200 条
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">
|
||||
确定{{ selection.length ? `(${selection.length})` : '' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { claimCandidates } from '@/api/payment/invoiceReceipt';
|
||||
|
||||
export default {
|
||||
name: 'InvoiceReceiptCandidatesDialog',
|
||||
props: {
|
||||
modelValue: { type: Boolean, required: true },
|
||||
},
|
||||
emits: ['update:modelValue', 'confirm'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
keyword: '',
|
||||
rows: [],
|
||||
selection: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
unwrap(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
handleOpen() {
|
||||
this.keyword = '';
|
||||
this.selection = [];
|
||||
this.$refs.candidatesTable?.clearSelection?.();
|
||||
this.load();
|
||||
},
|
||||
handleQuery() {
|
||||
this.load();
|
||||
},
|
||||
async load() {
|
||||
this.loading = true;
|
||||
try {
|
||||
this.rows = this.unwrap(await claimCandidates(this.keyword)) || [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleConfirm() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.warning('请至少选择一条收票单');
|
||||
return;
|
||||
}
|
||||
this.$emit('confirm', this.selection.map(row => ({ ...row })));
|
||||
this.visible = false;
|
||||
},
|
||||
pushStatusName(value) {
|
||||
return (
|
||||
{ pushed: '已保存收票单', assigned: '已指定应付', failed: '推送失败', unsynced: '未推送' }[
|
||||
value
|
||||
] || '未推送'
|
||||
);
|
||||
},
|
||||
pushStatusType(value) {
|
||||
return { pushed: 'warning', assigned: 'success', failed: 'danger', unsynced: 'info' }[value] || 'info';
|
||||
},
|
||||
formatMoney(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number.toFixed(2) : '-';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invoice-candidates-dialog__search {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.el-input {
|
||||
width: 360px;
|
||||
}
|
||||
}
|
||||
.invoice-candidates-dialog__tip {
|
||||
margin-top: 8px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -98,6 +98,79 @@
|
||||
></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 先票后款路径B:结算单审批后发票登记(关联收票单 → 确认推送指定应付) -->
|
||||
<el-dialog
|
||||
v-model="invoiceRegisterDialog.visible"
|
||||
title="发票登记"
|
||||
width="86%"
|
||||
append-to-body
|
||||
class="invoice-register-dialog"
|
||||
>
|
||||
<section-card title="结算基本信息">
|
||||
<el-descriptions :column="4" border size="small">
|
||||
<el-descriptions-item label="结算单号">
|
||||
{{ invoiceRegisterDialog.row.formalSettlementNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结算总金额">
|
||||
{{ formatMoney(invoiceRegisterDialog.row.settlementAmount, invoiceRegisterDialog.row.currency) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="已匹配发票金额">
|
||||
{{ formatMoney(invoiceRegisterDialog.row.invoiceAmount, invoiceRegisterDialog.row.currency) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="金蝶同步状态">
|
||||
{{ kingdeeSyncName(invoiceRegisterDialog.row.kingdeeSyncStatus) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</section-card>
|
||||
<section-card title="认领收票单">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="invoiceRegisterDialog.saving"
|
||||
@click="candidatesDialog.visible = true"
|
||||
>选择收票单</el-button
|
||||
>
|
||||
<el-table :data="invoiceRegisterDialog.candidates" border class="invoice-register-dialog__table">
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column prop="invoiceNo" label="发票号" min-width="160" align="center" />
|
||||
<el-table-column prop="invoiceDate" label="开票日期" min-width="120" align="center" />
|
||||
<el-table-column prop="invoiceType" label="发票类型" min-width="140" align="center" />
|
||||
<el-table-column prop="issuerName" label="开票单位(销方)" min-width="170" show-overflow-tooltip />
|
||||
<el-table-column label="发票金额(含税)" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="匹配结算单金额" min-width="150" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" fixed="right" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-link
|
||||
type="danger"
|
||||
:disabled="invoiceRegisterDialog.saving"
|
||||
@click="invoiceRegisterDialog.candidates.splice($index, 1)"
|
||||
>移除</el-link
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section-card>
|
||||
<template #footer>
|
||||
<el-button @click="invoiceRegisterDialog.visible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="invoiceRegisterDialog.saving"
|
||||
:disabled="!invoiceRegisterDialog.candidates.length"
|
||||
@click="submitInvoiceRegister"
|
||||
>保存认领</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<invoice-receipt-candidates-dialog
|
||||
v-model="candidatesDialog.visible"
|
||||
@confirm="addInvoiceRegisterCandidates"
|
||||
/>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
@@ -113,16 +186,17 @@ import { isAdministrator } from '@/utils/auth';
|
||||
import { downloadFile } from '@/utils/util';
|
||||
import * as api from '@/api/settlement/formalSettlement';
|
||||
import * as paymentApi from '@/api/payment/paymentApplication';
|
||||
import { formalSettlementOption } from '@/option/settlement/formalSettlement';
|
||||
import { formalSettlementOption, kingdeeSyncName } from '@/option/settlement/formalSettlement';
|
||||
import organizationSearch from '@/mixins/organization-search';
|
||||
import FormalSettlementEditor from './components/formal-settlement-editor.vue';
|
||||
import FormalSettlementTablePanel from './components/formal-settlement-table-panel.vue';
|
||||
import InvoiceReceiptCandidatesDialog from './components/invoice-receipt-candidates-dialog.vue';
|
||||
import { submitMkApprovalFlow, loadMkEnabled } from '@/utils/mk-approval';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
|
||||
export default {
|
||||
name: 'FormalSettlement',
|
||||
components: { FormalSettlementEditor, FormalSettlementTablePanel },
|
||||
components: { FormalSettlementEditor, FormalSettlementTablePanel, InvoiceReceiptCandidatesDialog },
|
||||
mixins: [organizationSearch],
|
||||
props: {
|
||||
/** 菜单拆分后锁定结算类型:payable=付款结算单,receivable=收款结算单 */
|
||||
@@ -151,6 +225,16 @@ export default {
|
||||
rows: [],
|
||||
form: { appliedAmount: 0, remark: '' },
|
||||
},
|
||||
// 先票后款路径B:发票登记(关联收票单 + 确认推送指定应付)
|
||||
invoiceRegisterDialog: {
|
||||
visible: false,
|
||||
saving: false,
|
||||
row: {},
|
||||
candidates: [],
|
||||
},
|
||||
candidatesDialog: {
|
||||
visible: false,
|
||||
},
|
||||
mkEnabled: false,
|
||||
// keep-alive 多标签下仅激活实例响应 detailNo,避免缓存页重复弹窗
|
||||
isPageActive: true,
|
||||
@@ -297,6 +381,7 @@ export default {
|
||||
return: () => this.handleReturn(row),
|
||||
void: () => this.handleVoid(row),
|
||||
print: () => this.handlePrint(row),
|
||||
invoiceRegister: () => this.openInvoiceRegisterDialog(row),
|
||||
};
|
||||
(handlers[type] || (() => {}))();
|
||||
},
|
||||
@@ -596,6 +681,58 @@ export default {
|
||||
this.paymentDialog.loading = false;
|
||||
}
|
||||
},
|
||||
// 先票后款路径B:发票登记——认领收票单(claim-invoices 走增量认领并同步收票侧关系)
|
||||
openInvoiceRegisterDialog(row) {
|
||||
this.invoiceRegisterDialog.row = row || {};
|
||||
this.invoiceRegisterDialog.candidates = [];
|
||||
this.invoiceRegisterDialog.visible = true;
|
||||
},
|
||||
addInvoiceRegisterCandidates(rows) {
|
||||
const existing = new Set(
|
||||
this.invoiceRegisterDialog.candidates.map(item => String(item.invoiceNo))
|
||||
);
|
||||
const added = rows.filter(row => !existing.has(String(row.invoiceNo)));
|
||||
if (!added.length) {
|
||||
this.$message.warning('所选收票单均已登记');
|
||||
return;
|
||||
}
|
||||
// 全额认领:匹配金额固定=发票金额
|
||||
this.invoiceRegisterDialog.candidates.push(
|
||||
...added.map(row => ({ ...row, matchedAmount: Number(row.invoiceAmount || 0) }))
|
||||
);
|
||||
},
|
||||
async submitInvoiceRegister() {
|
||||
const settlement = this.invoiceRegisterDialog.row;
|
||||
const invoices = this.invoiceRegisterDialog.candidates.map(row => ({
|
||||
...row,
|
||||
attachmentJson: row.attachmentJson || row.attachmentsJson,
|
||||
invoiceType: row.invoiceType || '',
|
||||
taxRate: row.taxRate ?? null,
|
||||
availableInvoiceAmount: Number(row.invoiceAmount || 0),
|
||||
matchedAmount: Number(row.invoiceAmount || 0),
|
||||
}));
|
||||
this.invoiceRegisterDialog.saving = true;
|
||||
try {
|
||||
await api.claimInvoices({ formalSettlementId: settlement.id, invoices });
|
||||
this.invoiceRegisterDialog.visible = false;
|
||||
this.onLoad(this.page);
|
||||
try {
|
||||
await this.$confirm('是否将发票匹配数据推送金蝶?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
} catch {
|
||||
this.$message.info('已保存认领,未推送金蝶');
|
||||
return;
|
||||
}
|
||||
const data = this.unwrapData(await api.invoiceAssignKingdee(settlement.id));
|
||||
this.$message.success(data || '发票登记推送金蝶成功');
|
||||
this.onLoad(this.page);
|
||||
} finally {
|
||||
this.invoiceRegisterDialog.saving = false;
|
||||
}
|
||||
},
|
||||
handlePrint(row) {
|
||||
if (!row) {
|
||||
row = this.selectedOne('打印');
|
||||
|
||||
Reference in New Issue
Block a user