From 441d62a874cb594e2d16437ed4ecc99ec8aee9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sat, 31 Oct 2026 16:07:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B6=E7=A5=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E5=85=88=E7=A5=A8=E5=90=8E=E6=AC=BE=E8=A1=A8=E5=8D=95=E6=94=B9?= =?UTF-8?q?=E9=80=A0=EF=BC=88=E6=89=8B=E9=80=89=E7=BB=93=E7=AE=97=E7=BB=84?= =?UTF-8?q?=E7=BB=87/=E5=8F=91=E7=A5=A8=E6=98=8E=E7=BB=86=E5=8C=BA?= =?UTF-8?q?=E5=9D=97/=E5=8E=BB=E5=85=B3=E8=81=94=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E5=8D=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/payment/invoiceReceipt.js | 6 + src/views/payment/invoice-receipt-form.vue | 343 ++++++++------------- 2 files changed, 127 insertions(+), 222 deletions(-) diff --git a/src/api/payment/invoiceReceipt.js b/src/api/payment/invoiceReceipt.js index 8db2dfb..79d2fab 100644 --- a/src/api/payment/invoiceReceipt.js +++ b/src/api/payment/invoiceReceipt.js @@ -38,6 +38,12 @@ 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 = keyword => + request({ url: `${baseUrl}/claim-candidates`, method: 'get', params: { keyword } }); export const approvalStatusOptions = [ { label: '草稿', value: 'draft' }, diff --git a/src/views/payment/invoice-receipt-form.vue b/src/views/payment/invoice-receipt-form.vue index b798e98..53ae453 100644 --- a/src/views/payment/invoice-receipt-form.vue +++ b/src/views/payment/invoice-receipt-form.vue @@ -109,34 +109,50 @@ - - -
- - - -
-
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + @@ -216,71 +223,6 @@ :query-api="apiQueryInvoicePool" @confirm="handleInvoicePicked" /> - - - - - - - - - - - - - - - - - - - - - - - @@ -288,6 +230,7 @@ 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'; @@ -310,6 +253,7 @@ const emptyForm = () => ({ customerEmails: [], departmentEmails: [], projectName: '', + deptId: null, deptName: '', payerName: '', payeeName: '', @@ -330,26 +274,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 +305,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 +345,7 @@ export default { .filter(Boolean); }, async initialize() { + this.loadDepartments(); if (this.isPublicViewPage) { await this.loadPublicDetail(); return; @@ -419,14 +359,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 +377,7 @@ export default { })), attachments: this.parse(data.attachmentsJson), }; + await this.loadInvoiceDetail(); }, validateInvoice(rule, value, callback) { if (!value) { @@ -446,9 +386,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 +409,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 +444,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 +513,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 +533,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() { @@ -678,8 +579,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,6 +588,14 @@ export default { formatMoney(value) { return Number(value || 0).toFixed(2); }, + formatQuantity(value) { + const number = Number(value); + return Number.isFinite(number) ? String(number) : '-'; + }, + formatRateText(value) { + if (value === null || value === undefined || value === '') return '-'; + return `${value}%`; + }, formatRate(value) { return value === null || value === undefined || value === '' ? '' : `${Number(value)}%`; }, @@ -738,15 +646,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; }