diff --git a/src/api/payment/invoiceReceipt.js b/src/api/payment/invoiceReceipt.js index 8db2dfb..5ae7cbc 100644 --- a/src/api/payment/invoiceReceipt.js +++ b/src/api/payment/invoiceReceipt.js @@ -38,6 +38,15 @@ export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'p export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const pushKingdee = ids => request({ url: `${baseUrl}/push-kingdee`, method: 'post', timeout: 30000, data: ids }); +// 先票后款:发票明细查询(收票登记明细区块按票号带出发票明细行) +export const invoiceDetailQuery = invoiceNo => + request({ url: `${baseUrl}/invoice-detail-query`, method: 'get', params: { invoiceNo } }); +// 先票后款:结算单认领收票单候选分页(审批通过且未被有效应付结算单认领) +export const claimCandidates = params => + request({ url: `${baseUrl}/claim-candidates`, method: 'get', params }); +// 先票后款:结算单已认领收票登记列表(发票登记对话框回显已认领票) +export const claimedReceipts = formalSettlementId => + request({ url: `${baseUrl}/claimed-list`, method: 'get', params: { formalSettlementId } }); export const approvalStatusOptions = [ { label: '草稿', value: 'draft' }, diff --git a/src/api/payment/paymentApplication.js b/src/api/payment/paymentApplication.js index e464552..4a64416 100644 --- a/src/api/payment/paymentApplication.js +++ b/src/api/payment/paymentApplication.js @@ -27,13 +27,13 @@ export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'p export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); // 同步金蝶涉及多次金蝶远程接口调用(A103-2 预检 + A103-1/A103-3 推送),单笔可达 20s+,覆盖全局 10s 超时 export const syncKingdee = id => - request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: 30000, params: { id } }); + request({ url: `${baseUrl}/sync-kingdee`, method: 'post', timeout: APPROVE_TIMEOUT, params: { id } }); // 批量同步逐单单据级事务串行推送,耗时随单量线性增长 export const syncKingdeeBatch = ids => - request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', timeout: 30000, data: ids }); + request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', timeout: APPROVE_TIMEOUT, data: ids }); // 回写任务按 paymentNo 分批远程查询 A103-2/A103-4,同样可能超过全局超时 export const syncKingdeeResult = () => - request({ url: `${baseUrl}/sync-kingdee-result`, method: 'post', timeout: 30000 }); + request({ url: `${baseUrl}/sync-kingdee-result`, method: 'post', timeout: APPROVE_TIMEOUT }); export const paymentTypeOptions = [ { label: '项目预付', value: 'project_advance' }, diff --git a/src/api/settlement/formalSettlement.js b/src/api/settlement/formalSettlement.js index 2d980d4..8769744 100644 --- a/src/api/settlement/formalSettlement.js +++ b/src/api/settlement/formalSettlement.js @@ -58,5 +58,21 @@ 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 }, + }); +// 先票后款:查看页重推认领收票单(收票单A303-2+指定应付A302-6 一步式),涉及多次金蝶远程调用,覆盖全局超时 +export const repushClaimedInvoice = (id, invoiceNo, matchedAmount) => + request({ + url: `${baseUrl}/repush-claimed-invoice`, + method: 'post', + timeout: 30000, + params: { id, invoiceNo, matchedAmount }, + }); export const queryInvoicePool = params => request({ url: `${baseUrl}/invoice-pool`, method: 'get', params }); diff --git a/src/option/payment/paymentApplication.js b/src/option/payment/paymentApplication.js index 9a74a22..6e5559a 100644 --- a/src/option/payment/paymentApplication.js +++ b/src/option/payment/paymentApplication.js @@ -170,6 +170,7 @@ export const paymentApplicationOption = ({ { label: '当前处理人', prop: 'currentProcessor', minWidth: 120, formatter: textFormatter }, { label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 130, formatter: textFormatter }, { label: '金蝶单据状态', prop: 'kingdeeStatusName', slot: true, minWidth: 130 }, + { label: '已指定应付', prop: 'kingdeeAssignStatus', slot: true, minWidth: 120 }, { label: '金蝶锁状态', prop: 'kingdeeLockStatus', slot: true, minWidth: 150 }, { label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100, formatter: textFormatter }, ], diff --git a/src/option/settlement/formalSettlement.js b/src/option/settlement/formalSettlement.js index 6a0e213..6fdc26b 100644 --- a/src/option/settlement/formalSettlement.js +++ b/src/option/settlement/formalSettlement.js @@ -89,24 +89,28 @@ const COLUMN_EXTRA = { }, kingdeeLockStatus: { slot: true }, kingdeeWriteOffStatus: { slot: true }, + kingdeeAssignStatus: { slot: true }, createTime: { width: 180 }, }; -const buildTableColumns = () => - formalSettlementTableColumns.map(column => { - const extra = { ...(COLUMN_EXTRA[column.prop] || {}) }; - if (column.money) { - extra.formatter = moneyFormatter; - extra.align = 'right'; - } - // link / status 标记的列统一走单元格插槽 - if (column.link || column.status) extra.slot = true; - // 插槽列不挂兜底 formatter:avue 中 formatter 会覆盖单元格插槽渲染 - if (!extra.slot && !extra.formatter) extra.formatter = textFormatter; - return { ...column, ...extra }; - }); +// 已指定应付仅付款结算单(先票后款 A302-6)有,收款结算单不展示 +const buildTableColumns = settlementType => + formalSettlementTableColumns + .filter(column => !(settlementType === 'receivable' && column.prop === 'kingdeeAssignStatus')) + .map(column => { + const extra = { ...(COLUMN_EXTRA[column.prop] || {}) }; + if (column.money) { + extra.formatter = moneyFormatter; + extra.align = 'right'; + } + // link / status 标记的列统一走单元格插槽 + if (column.link || column.status) extra.slot = true; + // 插槽列不挂兜底 formatter:avue 中 formatter 会覆盖单元格插槽渲染 + if (!extra.slot && !extra.formatter) extra.formatter = textFormatter; + return { ...column, ...extra }; + }); -export const formalSettlementOption = () => ({ +export const formalSettlementOption = (settlementType = '') => ({ height: 'auto', calcHeight: 32, tip: false, @@ -168,7 +172,7 @@ export const formalSettlementOption = () => ({ type: 'daterange', }), // ---------- 表格列(部分兼搜索项) ---------- - ...buildTableColumns(), + ...buildTableColumns(settlementType), ], }); diff --git a/src/option/settlement/formalSettlementTable.js b/src/option/settlement/formalSettlementTable.js index 117ae86..9360d50 100644 --- a/src/option/settlement/formalSettlementTable.js +++ b/src/option/settlement/formalSettlementTable.js @@ -27,6 +27,7 @@ export const formalSettlementTableColumns = [ { label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 }, { label: '金蝶冲销状态', prop: 'kingdeeWriteOffStatus', minWidth: 135 }, { label: '金蝶锁状态', prop: 'kingdeeLockStatus', minWidth: 150 }, + { label: '已指定应付', prop: 'kingdeeAssignStatus', minWidth: 120 }, { label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100 }, { label: '创建人', prop: 'createUserName', minWidth: 110 }, { label: '创建时间', prop: 'createTime', minWidth: 170 }, diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 2dfa40b..ced8d78 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -1082,6 +1082,7 @@ import { defineAsyncComponent } from 'vue'; import * as api from '@/api/business/project-apply'; import { getList as getContractManageList } from '@/api/business/contract-manage'; import { + getRisk, loadHcsImmediateEnabled, repush as hcsRepush, repushBatch as hcsRepushBatch, diff --git a/src/views/payment/invoice-receipt-form.vue b/src/views/payment/invoice-receipt-form.vue index b798e98..b9e6538 100644 --- a/src/views/payment/invoice-receipt-form.vue +++ b/src/views/payment/invoice-receipt-form.vue @@ -53,15 +53,6 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + {{ formatQuantity(row.num) }} + + + {{ formatMoney(row.unitPrice) }} + + + {{ formatMoney(row.detailAmount) }} + + + {{ formatRateText(row.taxRate) }} + + + {{ formatMoney(row.taxAmount) }} + + + + + - + {{ formatMoney(row.receivedInvoiceAmount) }} - - - - + + {{ formatMoney(row.allocatedInvoiceAmount) }} @@ -216,71 +214,6 @@ :query-api="apiQueryInvoicePool" @confirm="handleInvoicePicked" /> - - - - - 查询 - - - - - - - - - - - {{ formatMoney(row.settlementAmount) }} - - - {{ formatMoney(row.receivedInvoiceAmount) }} - - - - 选择 - - - - - 取消 - - 确定{{ settlementDialog.selection.length ? `(${settlementDialog.selection.length})` : '' }} - - - @@ -288,11 +221,15 @@ import { Search } from '@element-plus/icons-vue'; import { mapGetters } from 'vuex'; import * as api from '@/api/payment/invoiceReceipt'; +import { getDeptTree } from '@/api/system/dept'; import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { getMkPublicDetail } from '@/api/mk-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; +// 提交成功后带标记返回,列表页(keep-alive)据此重新拉取 +const invoiceReceiptListRefreshKey = 'invoice-receipt-list-refresh'; + const emptyForm = () => ({ id: null, invoiceNo: '', @@ -310,6 +247,7 @@ const emptyForm = () => ({ customerEmails: [], departmentEmails: [], projectName: '', + deptId: null, deptName: '', payerName: '', payeeName: '', @@ -330,26 +268,24 @@ export default { form: emptyForm(), customerEmailOptions: [], departmentEmailOptions: [], + invoiceItems: [], + deptTreeOptions: [], + deptMap: new Map(), + // 结算组织=合同承办部门:部门树级联单选(取末级 id),后端沿部门树上溯平台公司取金蝶组织 + deptCascaderProps: { checkStrictly: true, emitPath: true }, invoiceDialog: { visible: false, }, - settlementDialog: { - visible: false, - loading: false, - keyword: '', - rows: [], - selection: [], - }, rules: { invoiceNo: [{ required: true, validator: this.validateInvoice, trigger: 'change' }], - settlementIds: [{ required: true, validator: this.validateSettlements, trigger: 'change' }], + deptId: [{ required: true, validator: this.validateDept, trigger: 'change' }], customerEmails: [{ validator: this.validateEmails, trigger: 'change' }], departmentEmails: [{ validator: this.validateEmails, trigger: 'change' }], }, }; }, computed: { - ...mapGetters(['permission']), + ...mapGetters(['permission', 'userInfo']), recordId() { return this.$route.query.id || ''; }, @@ -363,14 +299,11 @@ export default { const code = this.recordId ? 'invoice_receipt_edit' : 'invoice_receipt_add'; return this.hasPermission(code); }, - settlementLabel() { - return this.form.settlements - .map(item => item.formalSettlementNo) - .filter(Boolean) - .join(','); - }, - settlementIds() { - return this.form.settlements.map(item => item.formalSettlementId || item.settlementId); + deptCascaderValue() { + if (this.form.deptId === null || this.form.deptId === undefined || this.form.deptId === '') { + return []; + } + return [String(this.form.deptId)]; }, }, created() { @@ -406,6 +339,7 @@ export default { .filter(Boolean); }, async initialize() { + this.loadDepartments(); if (this.isPublicViewPage) { await this.loadPublicDetail(); return; @@ -419,14 +353,13 @@ export default { } const data = this.unwrapData(await api.getDetail(this.recordId)); this.applyDetail(data); - if (this.settlementIds.length) await this.loadReferenceInformation(); }, async loadPublicDetail() { if (!this.recordId) return; const data = this.unwrapData(await getMkPublicDetail('invoice-receipt', this.recordId)); this.applyDetail(data); }, - applyDetail(data) { + async applyDetail(data) { this.form = { ...emptyForm(), ...data, @@ -438,6 +371,7 @@ export default { })), attachments: this.parse(data.attachmentsJson), }; + await this.loadInvoiceDetail(); }, validateInvoice(rule, value, callback) { if (!value) { @@ -446,9 +380,9 @@ export default { } callback(); }, - validateSettlements(rule, value, callback) { - if (!this.form.settlements.length) { - callback(new Error('请选择应付正式结算单')); + validateDept(rule, value, callback) { + if (this.form.deptId === null || this.form.deptId === undefined || this.form.deptId === '') { + callback(new Error('请选择结算组织')); return; } callback(); @@ -469,7 +403,7 @@ export default { openInvoiceDialog() { this.invoiceDialog.visible = true; }, - confirmInvoice(row, options = {}) { + async confirmInvoice(row, options = {}) { const selected = row; if (!selected) { this.$message.warning('请选择一张金蝶票据池发票'); @@ -504,92 +438,63 @@ export default { ...new Set([...this.departmentEmailOptions, ...this.form.departmentEmails]), ]; this.invoiceDialog.visible = false; + await this.loadInvoiceDetail(); this.$refs.formRef?.validateField('invoiceNo').catch(() => {}); return true; }, - async openSettlementDialog() { - this.settlementDialog.visible = true; - this.settlementDialog.selection = []; - await this.loadSettlementCandidates(); - await this.$nextTick(); - const selectedIds = new Set(this.settlementIds.map(id => String(id))); - this.settlementDialog.rows.forEach(row => { - if (selectedIds.has(String(row.id))) { - this.$refs.settlementTable?.toggleRowSelection(row, true); - } - }); - }, - async loadSettlementCandidates() { - this.settlementDialog.loading = true; + // 发票明细区块:按票号带出金蝶发票明细行(A801-1 镜像,推送 A303-2 分录同源) + async loadInvoiceDetail() { + if (!this.form.invoiceNo) { + this.invoiceItems = []; + return; + } try { - this.settlementDialog.rows = - this.unwrapData( - await api.getSettlementCandidates(this.settlementDialog.keyword, this.form.id) - ) || []; - } finally { - this.settlementDialog.loading = false; + this.invoiceItems = this.unwrapData(await api.invoiceDetailQuery(this.form.invoiceNo)) || []; + } catch { + this.invoiceItems = []; } }, - async confirmSettlements() { - const rows = this.settlementDialog.selection; - if (!rows.length) { - this.$message.warning('请至少选择一张应付正式结算单'); - return; - } - const first = rows[0]; - const incompatible = rows.some( - item => - String(item.projectId) !== String(first.projectId) || - String(item.deptId) !== String(first.deptId) || - item.payerName !== first.payerName || - item.payeeName !== first.payeeName - ); - if (incompatible) { - this.$message.warning('关联结算单必须属于同一项目、组织及收付款方'); - return; - } - const previousAmount = new Map( - this.form.settlements.map(item => [ - String(item.formalSettlementId || item.settlementId), - item.allocatedInvoiceAmount, - ]) - ); - this.form.settlements = rows.map(item => ({ - ...item, - formalSettlementId: item.id, - settlementId: item.id, - allocatedInvoiceAmount: previousAmount.has(String(item.id)) - ? Number(previousAmount.get(String(item.id)) || 0) - : rows.length === 1 - ? Number(item.settlementAmount || 0) - : 0, + loadDepartments() { + // 结算组织取租户部门树(过滤外部组织),选中取末级部门 id + getDeptTree(this.userInfo?.tenantId) + .then(response => { + const rows = this.excludeExternalOrganization(this.unwrapData(response) || []); + this.deptTreeOptions = this.toDeptCascaderOptions(rows); + this.deptMap = new Map(); + this.flattenDepartments(rows).forEach(item => + this.deptMap.set(String(item.id), item.title || item.deptName || item.name) + ); + }) + .catch(() => {}); + }, + excludeExternalOrganization(list = []) { + return list.reduce((result, item) => { + const deptName = item.title || item.deptName || item.name; + if (String(deptName || '').trim() === '外部组织') return result; + result.push({ + ...item, + children: item.children?.length + ? this.excludeExternalOrganization(item.children) + : item.children, + }); + return result; + }, []); + }, + toDeptCascaderOptions(list = []) { + return list.map(item => ({ + label: item.title || item.deptName || item.name, + value: item.id == null || item.id === '' ? '' : String(item.id), + children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined, })); - this.form.projectName = first.projectName; - this.form.deptName = first.deptName; - this.form.payerName = first.payerName; - this.form.payeeName = first.payeeName; - this.settlementDialog.visible = false; - await this.loadReferenceInformation(); - this.$refs.formRef?.validateField('settlementIds').catch(() => {}); }, - selectSettlement(row) { - this.$refs.settlementTable?.toggleRowSelection(row); + flattenDepartments(rows) { + return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]); }, - async loadReferenceInformation() { - const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(','))); - this.customerEmailOptions = data.customerEmails || []; - this.departmentEmailOptions = [ - ...new Set([...this.departmentEmailOptions, ...(data.departmentEmails || [])]), - ]; - if (!this.form.customerEmails.length && this.customerEmailOptions.length) { - this.form.customerEmails = [this.customerEmailOptions[0]]; - } - }, - remainingAmount(row) { - return Math.max( - Number(row.settlementAmount || 0) - Number(row.receivedInvoiceAmount || 0), - 0 - ); + handleDeptChange(value) { + const deptId = Array.isArray(value) ? value.at(-1) : value; + this.form.deptId = deptId || null; + this.form.deptName = deptId ? this.deptMap.get(String(deptId)) || '' : ''; + this.$refs.formRef?.validateField('deptId').catch(() => {}); }, openSettlementDetail(row) { const id = row.formalSettlementId || row.settlementId || row.id; @@ -602,15 +507,6 @@ export default { validateBusiness() { const invoiceAmount = Number(this.form.invoiceAmount || 0); if (invoiceAmount <= 0) throw new Error('开票金额必须大于0'); - for (const row of this.form.settlements) { - const allocated = Number(row.allocatedInvoiceAmount); - if (!Number.isFinite(allocated) || allocated < 0) { - throw new Error('分摊发票金额必须大于或等于0'); - } - if (allocated - this.remainingAmount(row) > 0.001) { - throw new Error(`结算单${row.formalSettlementNo}的累计收票金额不能超过结算总金额`); - } - } }, payload() { return { @@ -631,10 +527,9 @@ export default { departmentEmails: this.form.departmentEmails.join(';'), attachmentsJson: JSON.stringify(this.form.attachments || []), remark: this.form.remark, - settlements: this.form.settlements.map(item => ({ - settlementId: item.formalSettlementId || item.settlementId, - allocatedInvoiceAmount: item.allocatedInvoiceAmount, - })), + // 结算组织=页面手选部门(先票后款不挂结算单,认领关系由结算单侧维护,不再传 settlements) + deptId: this.form.deptId, + deptName: this.form.deptName, }; }, async saveDraft() { @@ -663,6 +558,7 @@ export default { mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); + sessionStorage.setItem(invoiceReceiptListRefreshKey, '1'); this.goBack(); }, async syncReferenceData() { @@ -678,8 +574,7 @@ export default { this.$message.warning('金蝶票池未查询到该发票'); return; } - if (!this.confirmInvoice(invoice, { preserveUserInput: true })) return; - if (this.settlementIds.length) await this.loadReferenceInformation(); + if (!(await this.confirmInvoice(invoice, { preserveUserInput: true }))) return; this.$message.success('同步成功'); }, goBack() { @@ -688,8 +583,13 @@ export default { formatMoney(value) { return Number(value || 0).toFixed(2); }, - formatRate(value) { - return value === null || value === undefined || value === '' ? '' : `${Number(value)}%`; + formatQuantity(value) { + const number = Number(value); + return Number.isFinite(number) ? String(number) : '-'; + }, + formatRateText(value) { + if (value === null || value === undefined || value === '') return '-'; + return `${value}%`; }, }, }; @@ -738,15 +638,6 @@ export default { :global(.avue-layout--horizontal .receipt-form-page__actions) { left: 0; } -.receipt-form-page__dialog-search { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-bottom: 12px; -} -.receipt-form-page__dialog-search .el-input { - width: 360px; -} .receipt-form-page :deep(.el-table) { --el-table-border-color: #eff1f7; } diff --git a/src/views/payment/invoice-receipt.vue b/src/views/payment/invoice-receipt.vue index aed1115..0a15cd8 100644 --- a/src/views/payment/invoice-receipt.vue +++ b/src/views/payment/invoice-receipt.vue @@ -223,6 +223,10 @@ import organizationSearch from '@/mixins/organization-search'; import { getMKApprovalUrl } from '@/api/system/business-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; +// 表单提交成功后带标记返回,keep-alive 缓存的列表页据此重新拉取 +const invoiceReceiptListRefreshKey = 'invoice-receipt-list-refresh'; +const invoiceReceiptListRoute = '/payment/invoice-receipt'; + const emptyQuery = () => ({ invoiceNo: '', invoiceDate: '', @@ -281,7 +285,28 @@ export default { this.mkEnabled = false; }); }, + activated() { + this.consumeListRefreshFlag(); + }, + watch: { + $route(to, from) { + if ( + to.path === invoiceReceiptListRoute && + from?.path?.includes('/invoice-receipt/form') + ) { + this.consumeListRefreshFlag(); + } + }, + }, methods: { + /** 表单提交成功后带标记返回时,重新拉取列表 */ + consumeListRefreshFlag() { + if (!sessionStorage.getItem(invoiceReceiptListRefreshKey)) { + return; + } + sessionStorage.removeItem(invoiceReceiptListRefreshKey); + this.loadTable(); + }, hasPermission(code) { return this.permission?.[code] !== false; }, diff --git a/src/views/payment/payment-application.vue b/src/views/payment/payment-application.vue index 7135c3a..a66fb04 100644 --- a/src/views/payment/payment-application.vue +++ b/src/views/payment/payment-application.vue @@ -80,6 +80,23 @@ + + + + {{ { assigned: '已指定应付', failed: '指定失败' }[row.kingdeeAssignStatus] || '未指定' }} + + - + + + row.id))) || []; const failedList = data.filter(item => String(item).includes('同步失败')); @@ -425,6 +448,7 @@ export default { this.warnAttachmentNotUploaded(rows.map(row => row.id)); } finally { this.syncLoading = false; + loading.close(); } }, // 附件联带同步提示:同步成功但附件未全部上传时追加 warning,重推即自动补传 diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 7455abb..bf279bb 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -440,8 +440,15 @@ /> - - 认领查询 + + + 选择收票单 + 认领查询 @@ -452,11 +459,6 @@ {{ displayValue(row.invoiceType) }} - - - {{ row.taxRate === null || row.taxRate === undefined ? '-' : `${row.taxRate}%` }} - - + {{ formatMoney(row.matchedAmount) }} + + + + + {{ + { assigned: '已指定应付', failed: '指定失败' }[row.kingdeePushStatus] || + '未指定' + }} + + + + @@ -506,6 +535,14 @@ @click="downloadInvoiceAttachment(row)" >下载 + + 重新推送 删除 @@ -546,6 +583,11 @@ @confirm="handleInvoiceClaimConfirm" /> + + ({ + ...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); }, @@ -2765,10 +2891,7 @@ export default { } matchedTotal += matchedAmount; } - if (matchedTotal > this.resolveInvoiceClaimSettlementAmount()) { - this.$message.warning('发票匹配结算单金额合计不能超过结算金额'); - return false; - } + // 先票后款全额认领:匹配金额=发票全额,可超过结算金额,不再校验合计上限(收票状态按覆盖判定) return true; }, removeInvoice(index) { diff --git a/src/views/settlement/components/formal-settlement-table-panel.vue b/src/views/settlement/components/formal-settlement-table-panel.vue index 4f2d32c..4616077 100644 --- a/src/views/settlement/components/formal-settlement-table-panel.vue +++ b/src/views/settlement/components/formal-settlement-table-panel.vue @@ -23,6 +23,15 @@ @click="$emit('payment')" >付款申请 + + 发票登记 - + + + + {{ { assigned: '已指定应付', failed: '指定失败' }[row.kingdeeAssignStatus] || '未指定' }} + + - + + + + + + + 查询 + + + + + + {{ row.invoiceDate || '-' }} + + + + + + {{ formatMoney(row.invoiceAmount) }} + + + + + + {{ pushStatusName(row.kingdeePushStatus) }} + + + + + + + + + + 仅列出审批通过且未被其他付款结算单认领的收票单 + + + 取消 + + 确定{{ selection.length ? `(${selection.length})` : '' }} + + + + + + + + diff --git a/src/views/settlement/formal-settlement.vue b/src/views/settlement/formal-settlement.vue index b25d8e5..f4b9dd4 100644 --- a/src/views/settlement/formal-settlement.vue +++ b/src/views/settlement/formal-settlement.vue @@ -98,6 +98,125 @@ > + + + + + + + {{ invoiceRegisterDialog.row.formalSettlementNo || '-' }} + + + {{ invoiceRegisterDialog.row.projectName || '-' }} + + + {{ invoiceRegisterDialog.row.deptName || '-' }} + + + {{ invoiceRegisterDialog.row.contractNo || '-' }} + + + {{ invoiceRegisterDialog.row.payerName || '-' }} + + + {{ invoiceRegisterDialog.row.payeeName || '-' }} + + + {{ settlementTypeName(invoiceRegisterDialog.row.settlementType) }} + + + {{ invoiceRegisterDialog.row.businessModelName || '-' }} + + + {{ formatMoney(invoiceRegisterDialog.row.settlementAmount, invoiceRegisterDialog.row.currency) }} + + + {{ formatMoney(invoiceRegisterDialog.row.invoiceAmount, invoiceRegisterDialog.row.currency) }} + + + {{ invoiceRegisterDialog.row.createUserName || '-' }} + + + {{ invoiceRegisterDialog.row.createTime || '-' }} + + + {{ kingdeeSyncName(invoiceRegisterDialog.row.kingdeeSyncStatus) }} + + + {{ invoiceRegisterDialog.row.remark || '-' }} + + + + + + 选择收票单 + + + + + + + + + {{ formatMoney(row.invoiceAmount) }} + + + + + + {{ formatMoney(row.matchedAmount || row.invoiceAmount) }} + + + + + + 已认领 + 移除 + + + + + + 取消 + 保存认领 + + + + @@ -113,16 +232,18 @@ 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 * as invoiceReceiptApi from '@/api/payment/invoiceReceipt'; +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=收款结算单 */ @@ -134,7 +255,6 @@ export default { }, data() { return { - option: formalSettlementOption(), query: {}, activeSettlementType: 'payable', rows: [], @@ -151,6 +271,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, @@ -158,6 +288,10 @@ export default { }, computed: { ...mapGetters(['permission', 'userInfo']), + // 列随结算类型切换重建:收款结算单不展示「已指定应付」 + option() { + return formalSettlementOption(this.activeSettlementType); + }, isAdmin() { return isAdministrator(this.userInfo); }, @@ -297,6 +431,7 @@ export default { return: () => this.handleReturn(row), void: () => this.handleVoid(row), print: () => this.handlePrint(row), + invoiceRegister: () => this.openInvoiceRegisterDialog(row), }; (handlers[type] || (() => {}))(); }, @@ -596,6 +731,68 @@ export default { this.paymentDialog.loading = false; } }, + // 先票后款路径B:发票登记——认领收票单(claim-invoices 走增量认领并同步收票侧关系) + // 打开时回显本单已认领收票单(claimed 标记禁止移除),已认领过票的可直接保存推送补指定应付 + async openInvoiceRegisterDialog(row) { + if ((row?.approvalStatus || '') !== 'approved') { + this.$message.warning('仅审批通过的付款结算单允许发票登记'); + return; + } + this.invoiceRegisterDialog.row = row || {}; + this.invoiceRegisterDialog.candidates = []; + this.invoiceRegisterDialog.visible = true; + try { + const claimed = this.unwrapData(await invoiceReceiptApi.claimedReceipts(row.id)) || []; + this.invoiceRegisterDialog.candidates = claimed.map(item => ({ ...item, claimed: true })); + } catch (error) { + this.invoiceRegisterDialog.candidates = []; + } + }, + 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.matchedAmount || row.invoiceAmount || 0), + })); + try { + // 先确认后保存:确定才认领并推送指定应付,取消不保存任何数据 + await this.$confirm('确认认领并推送金蝶指定应付?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + }); + } catch { + return; + } + this.invoiceRegisterDialog.saving = true; + try { + await api.claimInvoices({ formalSettlementId: settlement.id, invoices }); + const data = this.unwrapData(await api.invoiceAssignKingdee(settlement.id)); + this.$message.success(data || '发票登记推送金蝶成功'); + this.invoiceRegisterDialog.visible = false; + this.onLoad(this.page); + } finally { + this.invoiceRegisterDialog.saving = false; + } + }, handlePrint(row) { if (!row) { row = this.selectedOne('打印'); @@ -645,6 +842,9 @@ export default { params.settlementType = this.lockedSettlementType || this.activeSettlementType; return params; }, + settlementTypeName(value) { + return { payable: '付款', receivable: '收款' }[value] || '-'; + }, unwrapData(response) { const body = response?.data || response || {}; return body?.data || body; @@ -685,6 +885,11 @@ export default { margin-bottom: 12px; color: #606266; } +.invoice-register-dialog__actions { + display: flex; + justify-content: flex-end; + margin-bottom: 8px; +} :deep(.formal-page.basic-container .basic-container__card > .el-card__body) { padding: 0; }