From 1a143aa05b2278d27381aa4575f8e6e06b36dd53 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 01/32] =?UTF-8?q?fix:=20=E5=90=88=E5=90=8C=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=88=A0=E9=99=A4hcsRepush=E9=87=8D=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E4=BF=AE=E5=A4=8D=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/business/contract-manage.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue index d11d3ec..554dbfe 100644 --- a/src/views/business/contract-manage.vue +++ b/src/views/business/contract-manage.vue @@ -1122,7 +1122,6 @@ import { repush as hcsRepush, repushBatch as hcsRepushBatch, } from '@/api/business/hcs'; -import { repush as hcsRepush, repushBatch as hcsRepushBatch, getRisk } from '@/api/business/hcs'; import { isContractBlack, isRiskyTag, resolveRiskTagName } from '@/utils/risk-display'; import { Position } from '@element-plus/icons-vue'; import { getMkPublicDetail } from '@/api/mk-process'; 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 02/32] =?UTF-8?q?feat:=20=E6=94=B6=E7=A5=A8=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=E5=85=88=E7=A5=A8=E5=90=8E=E6=AC=BE=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E6=94=B9=E9=80=A0=EF=BC=88=E6=89=8B=E9=80=89=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E7=BB=84=E7=BB=87/=E5=8F=91=E7=A5=A8=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E5=8C=BA=E5=9D=97/=E5=8E=BB=E5=85=B3=E8=81=94=E7=BB=93?= =?UTF-8?q?=E7=AE=97=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; } From 46e7952e4943eb50339cbe99f7b5900ed31110e2 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:12 +0800 Subject: [PATCH 03/32] =?UTF-8?q?feat:=20=E4=BB=98=E6=AC=BE=E7=BB=93?= =?UTF-8?q?=E7=AE=97=E5=8D=95=E9=80=89=E6=94=B6=E7=A5=A8=E5=8D=95=E5=85=A8?= =?UTF-8?q?=E9=A2=9D=E8=AE=A4=E9=A2=86=E5=B9=B6=E6=96=B0=E5=A2=9E=E5=8F=91?= =?UTF-8?q?=E7=A5=A8=E7=99=BB=E8=AE=B0=E6=8E=A8=E9=80=81=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=BA=94=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/settlement/formalSettlement.js | 8 + .../components/formal-settlement-editor.vue | 63 +++++++- .../formal-settlement-table-panel.vue | 7 + .../invoice-receipt-candidates-dialog.vue | 150 ++++++++++++++++++ src/views/settlement/formal-settlement.vue | 141 +++++++++++++++- 5 files changed, 359 insertions(+), 10 deletions(-) create mode 100644 src/views/settlement/components/invoice-receipt-candidates-dialog.vue diff --git a/src/api/settlement/formalSettlement.js b/src/api/settlement/formalSettlement.js index 2d980d4..032e253 100644 --- a/src/api/settlement/formalSettlement.js +++ b/src/api/settlement/formalSettlement.js @@ -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 }); diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 7455abb..2589487 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 @@ - - - + + + + + + + {{ invoiceRegisterDialog.row.formalSettlementNo || '-' }} + + + {{ formatMoney(invoiceRegisterDialog.row.settlementAmount, invoiceRegisterDialog.row.currency) }} + + + {{ formatMoney(invoiceRegisterDialog.row.invoiceAmount, invoiceRegisterDialog.row.currency) }} + + + {{ kingdeeSyncName(invoiceRegisterDialog.row.kingdeeSyncStatus) }} + + + + + 选择收票单 + + + + + + + + + + + + + + + + + + + + + @@ -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('打印'); From 9908b543ea44966e695c50102f5296f388d7a94b 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:37:01 +0800 Subject: [PATCH 04/32] =?UTF-8?q?fix:=20=E9=A1=B9=E7=9B=AE=E7=AB=8B?= =?UTF-8?q?=E9=A1=B9=E8=A1=A5=E5=85=85getRisk=E5=AF=BC=E5=85=A5=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=A3=8E=E9=99=A9=E6=9F=A5=E8=AF=A2=E6=9C=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/business/project-apply.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 0242715..88e1ba9 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -985,6 +985,7 @@ import { exportBlob } from '@/api/common'; import { defineAsyncComponent } from 'vue'; import * as api from '@/api/business/project-apply'; import { + getRisk, loadHcsImmediateEnabled, repush as hcsRepush, repushBatch as hcsRepushBatch, From d78d1008248867683db30e4c7291c6bf8acaae70 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:52:22 +0800 Subject: [PATCH 05/32] =?UTF-8?q?feat:=20=E5=85=A8=E9=A2=9D=E8=AE=A4?= =?UTF-8?q?=E9=A2=86=E4=B8=8D=E5=86=8D=E6=A0=A1=E9=AA=8C=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E9=87=91=E9=A2=9D=E5=90=88=E8=AE=A1=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/settlement/components/formal-settlement-editor.vue | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 2589487..28e76e8 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -2812,10 +2812,7 @@ export default { } matchedTotal += matchedAmount; } - if (matchedTotal > this.resolveInvoiceClaimSettlementAmount()) { - this.$message.warning('发票匹配结算单金额合计不能超过结算金额'); - return false; - } + // 先票后款全额认领:匹配金额=发票全额,可超过结算金额,不再校验合计上限(收票状态按覆盖判定) return true; }, removeInvoice(index) { From ab7491e6d692a0b49f3a5cc9dd9344c3d2cd5834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sun, 1 Nov 2026 22:05:56 +0800 Subject: [PATCH 06/32] =?UTF-8?q?feat:=20=E6=94=B6=E7=A5=A8=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=E8=A1=A8=E5=8D=95=E5=8E=BB=E7=A8=8E=E7=8E=87=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/payment/invoice-receipt-form.vue | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/views/payment/invoice-receipt-form.vue b/src/views/payment/invoice-receipt-form.vue index 53ae453..fe6d8b8 100644 --- a/src/views/payment/invoice-receipt-form.vue +++ b/src/views/payment/invoice-receipt-form.vue @@ -53,15 +53,6 @@ - - - - - From eae50ab90987fb312694a1b8a6fdc71309a2b3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Mon, 2 Nov 2026 15:39:26 +0800 Subject: [PATCH 07/32] =?UTF-8?q?feat:=20=E5=8F=91=E7=A5=A8=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=E7=A7=BB=E8=87=B3=E5=B7=A5=E5=85=B7=E6=A0=8F=E5=B9=B6?= =?UTF-8?q?=E5=9B=9E=E6=98=BE=E5=B7=B2=E8=AE=A4=E9=A2=86=E6=94=B6=E7=A5=A8?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/payment/invoiceReceipt.js | 3 +++ .../formal-settlement-table-panel.vue | 16 +++++++++------- src/views/settlement/formal-settlement.vue | 19 +++++++++++++++++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/api/payment/invoiceReceipt.js b/src/api/payment/invoiceReceipt.js index 79d2fab..4fecc7a 100644 --- a/src/api/payment/invoiceReceipt.js +++ b/src/api/payment/invoiceReceipt.js @@ -44,6 +44,9 @@ export const invoiceDetailQuery = invoiceNo => // 先票后款:结算单认领收票单候选(审批通过且未被有效应付结算单认领) export const claimCandidates = keyword => request({ url: `${baseUrl}/claim-candidates`, method: 'get', params: { keyword } }); +// 先票后款:结算单已认领收票登记列表(发票登记对话框回显已认领票) +export const claimedReceipts = formalSettlementId => + request({ url: `${baseUrl}/claimed-list`, method: 'get', params: { formalSettlementId } }); export const approvalStatusOptions = [ { label: '草稿', value: 'draft' }, diff --git a/src/views/settlement/components/formal-settlement-table-panel.vue b/src/views/settlement/components/formal-settlement-table-panel.vue index 927e6fd..60fb4f7 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')" >付款申请 + + 发票登记 {{ formatMoney(row.invoiceAmount) }} - + + @@ -1301,6 +1321,19 @@ class="score-detail-form" > + + + + 线上评估 + 线下评估 + + 线下请上传评分表 Excel + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - @@ -1386,6 +1372,108 @@ + + +
+ + 下载模板 + + + + 上传 Excel + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1395,7 +1483,7 @@ sum + Number(item.selfScore || 0), 0); let reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0); - // 暂定:加分/减分合计最高按 15 分计 + // 暂定:加分/减分合计最高按 15 分计(分类汇总展示封顶后值) if (category.categoryCode === 'plus') { selfScore = Math.min(selfScore, HARDCODED_MAX_PLUS_SCORE); reviewScore = Math.min(reviewScore, HARDCODED_MAX_PLUS_SCORE); @@ -2406,6 +2509,16 @@ export default { }); return rows; }, + isOfflineScore() { + return String(this.currentScore?.evaluateMode || 'online') === 'offline'; + }, + expiryTagOptions() { + return [ + { label: '全部', value: 'all' }, + { label: '30天内到期', value: 'soon' }, + { label: '已过期', value: 'expired' }, + ]; + }, }, watch: { $route(to, from) { @@ -3518,13 +3631,22 @@ export default { return Number.isFinite(rate) ? `${rate.toFixed(2)}%` : '-'; }, isBusinessLicenseExpiringSoon(row = {}) { - if (row.businessTermType === '长期' || !row.businessEndDate) return false; - const endDate = this.$dayjs(row.businessEndDate); + return this.isDateExpiringSoon(row.businessEndDate, row.businessTermType === '长期'); + }, + /** 日期是否在 30 天内到期(含当天);skip=true 时不提示(如营业执照长期) */ + isDateExpiringSoon(dateValue, skip = false) { + if (skip || !dateValue) return false; + const endDate = this.$dayjs(dateValue); if (!endDate.isValid()) return false; const today = this.$dayjs().startOf('day'); const expireSoonDate = today.add(30, 'day'); return !endDate.isAfter(expireSoonDate, 'day'); }, + handleExpireChange(expireStatus) { + this.query.expireStatus = expireStatus || 'all'; + this.page.currentPage = 1; + this.onLoad(this.page, this.query); + }, handleBusinessEndDateChange(value) { if (value) { this.archiveForm.businessTermType = '固定期限'; @@ -4408,7 +4530,9 @@ export default { normalizeScore(score) { return { quantificationId: '', + evaluateMode: 'online', scoreDate: this.$dayjs().format('YYYY-MM-DD'), + nextEvaluateDate: '', applyCreditLimit: this.archiveForm.applyCreditLimit || '', maxCreditLimit: '', maxMinusScore: HARDCODED_MAX_MINUS_SCORE, @@ -4426,6 +4550,8 @@ export default { details: [], ...score, quantificationId: score.quantificationId ? String(score.quantificationId) : '', + evaluateMode: score.evaluateMode === 'offline' ? 'offline' : 'online', + nextEvaluateDate: score.nextEvaluateDate || '', applyCreditLimit: this.normalizeOptionalAmount( score.applyCreditLimit === undefined ? this.archiveForm.applyCreditLimit @@ -4588,19 +4714,38 @@ export default { this.invoiceForm = this.emptyInvoice(); this.$refs.archiveForm?.clearValidate(); }, - // 客商材料不做提交校验,仅在页面上提示未上传项。提交审批前必须完成自评,且评分记录只能有一条。 + // 仅客商分类含「客户」时强制评分;纯承运商不强制。评分记录至多一条。 validateScoreForApproval(archive) { const scores = archive.scores || []; if (scores.length > 1) { this.$message.warning('评分记录只能有一条'); return false; } + if (!this.requiresCustomerScore(archive.customerType)) { + return true; + } if (scores.length !== 1 || scores[0].selfStatus !== '已完成') { - this.$message.warning('提交审批前必须完成自评'); + this.$message.warning('客户提交审批前必须完成评分自评'); + return false; + } + if (!scores[0].nextEvaluateDate) { + this.$message.warning('请填写下次评定日期'); return false; } return true; }, + /** 客商分类是否包含客户(字典值「客户」或历史值 customer) */ + requiresCustomerScore(customerType) { + const values = Array.isArray(customerType) + ? customerType + : String(customerType || '') + .replace(/,/g, ',') + .split(','); + return values + .map(item => String(item || '').trim()) + .filter(Boolean) + .some(item => item === '客户' || item.toLowerCase() === 'customer'); + }, normalizeArchive() { // 提交前再次固定所属组织,防止被绕过 this.applyFixedOwnerDept(); @@ -4891,7 +5036,9 @@ export default { const template = this.normalizeScore({ ...scoreTemplate, quantificationId, + evaluateMode: this.currentScore.evaluateMode || 'online', scoreDate: this.currentScore.scoreDate, + nextEvaluateDate: this.currentScore.nextEvaluateDate, applyCreditLimit: this.currentScore.applyCreditLimit, maxCreditLimit: '', creditLevel: '', @@ -5145,7 +5292,8 @@ export default { .replace(/^\./, '') .replace(/(\..*)\./g, '$1') .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2'); - if (prop === 'reviewScore' && normalized !== '') { + // 减分项复评得分存正数扣分,允许填 0(不扣分);不做项目满分截断 + if (prop === 'reviewScore' && normalized !== '' && !this.isMinusScoreDetail(detail)) { const maxScore = this.getScoreItemFullScore(detail); if (Number(normalized) > maxScore) { detail[prop] = String(maxScore); @@ -5206,7 +5354,12 @@ export default { const optionScores = this.parseScoreOptions(detail.optionsJson) .map(item => Number(item.score)) .filter(score => Number.isFinite(score)); - return optionScores.length ? Math.max(...optionScores) : 0; + if (!optionScores.length) return 0; + // 减分项选项分常为负(如 -5),项目满分按绝对值取最大可扣分;避免 0 分被误判为高于“最高得分-5” + if (this.isMinusScoreDetail(detail)) { + return Math.max(...optionScores.map(score => Math.abs(score))); + } + return Math.max(...optionScores); }, getScoreFullMark(details = []) { return details.reduce((sum, item) => { @@ -5224,6 +5377,7 @@ export default { }); }, matchCreditStandard(score, scoreRate) { + // 闭开区间:≥lower 且 item && item.creditLevel) .sort((a, b) => Number(b.scoreRateLower || 0) - Number(a.scoreRateLower || 0)); @@ -5231,7 +5385,10 @@ export default { const lower = Number(standard.scoreRateLower); const upper = Number(standard.scoreRateUpper); if (!Number.isFinite(lower) || !Number.isFinite(upper)) return false; - return scoreRate >= lower && scoreRate <= upper; + if (upper >= 100) { + return scoreRate >= lower && scoreRate <= upper; + } + return scoreRate >= lower && scoreRate < upper; }); }, calculateCreditLimitByStandard(standard, scoreRate) { @@ -5267,25 +5424,32 @@ export default { const rawPlusReview = sumScore(plusDetails, 'reviewScore'); const rawMinusSelf = sumScore(minusDetails, 'selfScore'); const rawMinusReview = sumScore(minusDetails, 'reviewScore'); - // 暂定:加分/减分合计最高按 15 分计 + // 暂定:加分/减分合计最高按 15 分计(未超按实际,超 15 按 15/-15) const plusSelf = Math.min(rawPlusSelf, HARDCODED_MAX_PLUS_SCORE); const plusReview = Math.min(rawPlusReview, HARDCODED_MAX_PLUS_SCORE); const minusSelf = Math.min(rawMinusSelf, HARDCODED_MAX_MINUS_SCORE); const minusReview = Math.min(rawMinusReview, HARDCODED_MAX_MINUS_SCORE); - const selfScore = sumScore(basicDetails, 'selfScore') + plusSelf - minusSelf; + const computedSelfScore = sumScore(basicDetails, 'selfScore') + plusSelf - minusSelf; const reviewScore = sumScore(basicDetails, 'reviewScore') + plusReview - minusReview; const hasReviewScore = details.some( item => !this.isBlankScore(item.reviewScore) ); + // 线下模式允许手工改自评得分:有手工值时优先采用,否则回退明细合计 + const isOffline = String(score.evaluateMode || '') === 'offline'; + const manualSelfScore = Number(score.selfScore); + const selfScore = + isOffline && Number.isFinite(manualSelfScore) && String(score.selfScore ?? '').trim() !== '' + ? manualSelfScore + : computedSelfScore; const finalScore = hasReviewScore ? reviewScore : selfScore; const fullMark = this.getScoreFullMark(details); const scoreRate = fullMark > 0 ? Math.max(0, Math.min(100, (finalScore / fullMark) * 100)) : 0; const standard = this.matchCreditStandard(score, scoreRate); const hasStandards = (score.standards || []).some(item => item && item.creditLevel); - score.selfScore = Number(selfScore.toFixed(2)); + score.selfScore = Number(Number(selfScore).toFixed(2)); score.reviewScore = Number(reviewScore.toFixed(2)); - score.finalScore = Number(finalScore.toFixed(2)); + score.finalScore = Number(Number(finalScore).toFixed(2)); score.scoreRate = Number(scoreRate.toFixed(2)); if (hasStandards) { score.creditLevel = standard?.creditLevel || ''; @@ -5413,6 +5577,10 @@ export default { this.$message.warning('请选择评定日期'); return false; } + if (!this.currentScore.nextEvaluateDate) { + this.$message.warning('请选择下次评定日期'); + return false; + } if (!this.currentScore.applyCreditLimit) { this.$message.warning('请输入拟申请总资金使用额度'); return false; @@ -5426,7 +5594,11 @@ export default { return false; } if (!this.currentScore.details || !this.currentScore.details.length) { - this.$message.warning('当前评分量化表未配置评分项目'); + this.$message.warning( + this.isOfflineScore + ? '请先上传评分表 Excel 解析回填明细' + : '当前评分量化表未配置评分项目' + ); return false; } const invalidScoreInput = this.currentScore.details.find(item => { @@ -5440,7 +5612,9 @@ export default { ); return false; } + // 减分项允许填 0(不扣分),且按阶梯累计扣分不做项目满分截断 const overMaxReviewScore = this.currentScore.details.find(item => { + if (this.isMinusScoreDetail(item)) return false; if ( item.reviewScore === undefined || item.reviewScore === null || @@ -5635,13 +5809,15 @@ export default { }); }, searchReset() { - this.query = {}; - this.onLoad(this.page); + this.query = { expireStatus: this.query.expireStatus || 'all' }; + this.onLoad(this.page, this.query); }, searchChange(params, done) { // Avue 只回传非空筛选项,被清空的字段不会出现在 params 里, // 因此查询条件必须整体重建,不能与上一次的 this.query 合并,否则清空后仍会带上次的值 + const expireStatus = this.query.expireStatus || 'all'; this.query = this.buildQuery(params, true); + this.query.expireStatus = expireStatus; this.page.currentPage = 1; this.onLoad(this.page, this.query); done(); @@ -5780,8 +5956,145 @@ export default { if (status === 0 || status === '0' || status === -1 || status === '-1') { delete values.status; } + if (!values.expireStatus) { + values.expireStatus = this.query.expireStatus || 'all'; + } + if (values.expireStatus === 'all') { + delete values.expireStatus; + } return values; }, + handleEvaluateModeChange(mode) { + this.currentScore.evaluateMode = mode === 'offline' ? 'offline' : 'online'; + if (this.isOfflineScore) { + this.expandedScoreCategoryKeys = []; + } + }, + handleOfflineSelfScoreInput(value) { + const normalized = String(value || '') + .replace(/[^\d.-]/g, '') + .replace(/(?!^)-/g, '') + .replace(/^\./, '') + .replace(/(\..*)\./g, '$1') + .replace(/^(-?\d+)(\.\d{0,2})?.*$/, '$1$2'); + this.currentScore.selfScore = normalized; + }, + recalculateOfflineScore() { + if (!this.isOfflineScore) return; + const selfScore = Number(this.currentScore.selfScore); + if (!Number.isFinite(selfScore)) return; + this.currentScore.selfScore = Number(selfScore.toFixed(2)); + const hasReviewScore = (this.currentScore.details || []).some( + item => !this.isBlankScore(item.reviewScore) + ); + if (!hasReviewScore) { + this.currentScore.finalScore = this.currentScore.selfScore; + } + const fullMark = this.getScoreFullMark(this.currentScore.details || []); + const scoreRate = + fullMark > 0 + ? Math.max(0, Math.min(100, (Number(this.currentScore.finalScore) / fullMark) * 100)) + : 0; + this.currentScore.scoreRate = Number(scoreRate.toFixed(2)); + const standard = this.matchCreditStandard(this.currentScore, scoreRate); + const hasStandards = (this.currentScore.standards || []).some(item => item && item.creditLevel); + if (hasStandards) { + this.currentScore.creditLevel = standard?.creditLevel || ''; + this.currentScore.maxCreditLimit = standard + ? this.calculateCreditLimitByStandard(standard, scoreRate) + : ''; + } + }, + downloadScoreTemplate() { + if (!this.currentScore.quantificationId) { + this.$message.warning('请先选择评分量化表'); + return; + } + NProgress.start(); + const loading = ElLoading.service({ + lock: true, + text: '模板下载中', + background: 'rgba(255, 255, 255, 0.7)', + }); + downloadScoreExcelTemplate(this.currentScore.quantificationId) + .then(res => { + downloadXls( + res.data, + `客商评分明细模板${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx` + ); + }) + .finally(() => { + loading.close(); + NProgress.done(); + }); + }, + async uploadScoreExcel(option) { + const file = option?.file; + if (!file) return; + if (!this.currentScore.quantificationId) { + this.$message.warning('请先选择评分量化表'); + return; + } + const fileName = String(file.name || '').toLowerCase(); + if (!/\.(xls|xlsx)$/.test(fileName)) { + this.$message.error('请上传 .xls,.xlsx 标准格式文件'); + return; + } + this.scoreImporting = true; + NProgress.start(); + const loading = ElLoading.service({ + lock: true, + text: '导入中', + background: 'rgba(255, 255, 255, 0.7)', + }); + try { + const res = await importScoreExcel(this.currentScore.quantificationId, file); + if (isExcelResponse(res)) { + downloadXls( + res.data, + `客商评分导入失败明细${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx` + ); + this.$message.warning('部分数据导入失败,已下载失败明细'); + return; + } + const result = isBusinessJson(res?.data) ? res.data : await parseBlobJson(res?.data); + if (Number(result?.code) !== 200) { + this.$message.error(result?.msg || '导入失败'); + return; + } + const imported = result?.data || {}; + const keepFields = { + evaluateMode: 'offline', + scoreDate: this.currentScore.scoreDate, + nextEvaluateDate: this.currentScore.nextEvaluateDate, + applyCreditLimit: this.currentScore.applyCreditLimit, + tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit, + tempCreditStartDate: this.currentScore.tempCreditStartDate, + tempCreditEndDate: this.currentScore.tempCreditEndDate, + remark: this.currentScore.remark, + attachments: this.scoreAttachmentFiles, + quantificationId: this.currentScore.quantificationId, + }; + this.currentScore = this.normalizeScore({ + ...imported, + ...keepFields, + standards: imported.standards || this.currentScore.standards || [], + details: (imported.details || []).map(detail => ({ + ...detail, + scoreInput: this.normalizeScoreInputValue(detail.scoreInput), + })), + }); + this.calculateScore(this.currentScore); + this.$message.success('评分表解析成功,请在证明材料中上传评分表原件'); + } catch (error) { + const message = await resolveImportErrorMessage(error); + this.$message.error(message); + } finally { + this.scoreImporting = false; + loading.close(); + NProgress.done(); + } + }, }, }; @@ -5816,6 +6129,23 @@ export default { color: var(--el-color-danger); } +.customer-archive-page__expiry-tabs { + margin-bottom: 8px; +} + +.score-offline-tip { + margin-left: 12px; + color: #909399; + font-size: 12px; +} + +.score-offline-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + // 风险客商悬浮提示:popper 挂在 body 下,需用 :global 约束宽度 :global(.customer-risk-popper) { max-width: 360px; From 8f3da0e38649580c0766ca94bcab544fdcc92557 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 4 Nov 2026 23:47:00 +0800 Subject: [PATCH 25/32] =?UTF-8?q?:sparkles:=20=E9=A2=84=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E6=94=B6=E6=AC=BE=E6=96=B9=E4=B8=8E=E4=BB=98=E6=AC=BE=E6=96=B9?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E4=B8=8B=E6=8B=89=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- src/option/settlement/preSettlementForm.js | 18 +- .../components/pre-settlement-editor.vue | 213 ++++++++++++++++-- 2 files changed, 208 insertions(+), 23 deletions(-) diff --git a/src/option/settlement/preSettlementForm.js b/src/option/settlement/preSettlementForm.js index 64754ba..f5d00ec 100644 --- a/src/option/settlement/preSettlementForm.js +++ b/src/option/settlement/preSettlementForm.js @@ -8,8 +8,20 @@ export const preSettlementFormFields = [ { label: '合同名称', prop: 'contractId', type: 'contract', required: true }, { label: '合同编号', prop: 'contractNo', readonly: true }, { label: '项目名称', prop: 'projectName', readonly: true }, - { label: '收款方', prop: 'payeeName', readonly: true }, - { label: '付款方', prop: 'payerName', readonly: true }, + { + label: '收款方', + prop: 'payeeName', + type: 'select', + optionsKey: 'payeeOptions', + required: true, + }, + { + label: '付款方', + prop: 'payerName', + type: 'select', + optionsKey: 'payerOptions', + required: true, + }, { label: '结算类型', prop: 'settlementType', readonly: true }, { label: '结算金额', prop: 'settlementAmount', readonly: true, money: true }, { label: '业务模式', prop: 'businessModel', type: 'select', optionsKey: 'businessModelOptions' }, @@ -24,6 +36,8 @@ export const preSettlementFormFields = [ export const preSettlementFormRules = { contractId: [{ required: true, message: '请选择合同名称', trigger: 'change' }], + payeeName: [{ required: true, message: '请选择收款方', trigger: 'change' }], + payerName: [{ required: true, message: '请选择付款方', trigger: 'change' }], exchangeRateDate: [ { validator: (_rule, value, callback, source) => { diff --git a/src/views/settlement/components/pre-settlement-editor.vue b/src/views/settlement/components/pre-settlement-editor.vue index 5dfb517..f45102d 100644 --- a/src/views/settlement/components/pre-settlement-editor.vue +++ b/src/views/settlement/components/pre-settlement-editor.vue @@ -989,6 +989,7 @@ import { save, submit, } from '@/api/settlement/preSettlement'; +import { getDetail as getContractDetail } from '@/api/business/contract-manage'; import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail'; import { resolveReceivablePayableDetailId, @@ -1072,6 +1073,8 @@ export default { changeTableColumns: changeRecordColumns, candidateColumns: candidateDetailColumns, contractOptions: [], + payerOptions: [], + payeeOptions: [], contractCategoryOptions, signTypeOptions, effectiveTypeOptions, @@ -1391,18 +1394,34 @@ export default { const first = rows[0]; const settlementType = this.initialData.settlementType || first.settlementType || 'payable'; const contractId = first.contractId || ''; - let contractPartyA = first.payerName || ''; - let contractPartyB = first.payeeName || ''; - if (contractId && (!contractPartyA || !contractPartyB)) { + let contractPartyA = ''; + let contractPartyB = ''; + let contractPartyAJson = ''; + let contractPartyBJson = ''; + if (contractId) { try { - const { data } = await getContractOptions(first.contractNo || ''); - const matched = (data?.data || []).find(item => String(item.id) === String(contractId)); - contractPartyA = contractPartyA || matched?.partyA || ''; - contractPartyB = contractPartyB || matched?.partyB || ''; + const contractDetail = await this.fetchContractPartyDetail(contractId); + if (contractDetail) { + contractPartyA = contractDetail.partyA || ''; + contractPartyB = contractDetail.partyB || ''; + contractPartyAJson = contractDetail.partyAJson || ''; + contractPartyBJson = contractDetail.partyBJson || ''; + } } catch (error) { contractPartyA = ''; contractPartyB = ''; } + if (!contractPartyA && !contractPartyB) { + try { + const { data } = await getContractOptions(first.contractNo || ''); + const matched = (data?.data || []).find(item => String(item.id) === String(contractId)); + contractPartyA = matched?.partyA || ''; + contractPartyB = matched?.partyB || ''; + } catch (error) { + contractPartyA = first.payerName || ''; + contractPartyB = first.payeeName || ''; + } + } } if (!this.contractOptions.some(item => String(item.id) === String(contractId))) { this.contractOptions.push({ @@ -1416,6 +1435,8 @@ export default { settlementType, partyA: contractPartyA, partyB: contractPartyB, + partyAJson: contractPartyAJson, + partyBJson: contractPartyBJson, }); } this.handleContractChange(contractId); @@ -1427,12 +1448,12 @@ export default { projectName: first.projectName || this.form.projectName, deptId: first.deptId || this.form.deptId, deptName: first.deptName || this.form.deptName, - payerName: this.form.payerName || contractPartyA, - payeeName: this.form.payeeName || contractPartyB, settlementType, currency: first.currency || 'RMB', localCurrency: first.localCurrency || 'RMB', }); + // 明细带入的收付方若为单值且在选项中,优先保留;多方拼接值由下拉默认首项承接 + this.applyPartySelection(first.payerName, first.payeeName); this.details = rows.map(row => this.normalizeSourceDetail(row)); this.detailPage.current = 1; this.loading = true; @@ -1516,6 +1537,8 @@ export default { this.billingRuleDialog.visible = false; this.billingRuleDialog.rule = null; this.contractOptions = []; + this.payerOptions = []; + this.payeeOptions = []; this.contractDialog.visible = false; this.contractDialog.expanded = false; this.contractDialog.rows = []; @@ -1552,21 +1575,20 @@ export default { this.attachments = this.parseAttachments(detail.attachmentsJson); this.sortAttachments(); this.selectedAttachmentRows = []; - if ( - detail.contractId && - !this.contractOptions.some(item => item.id === detail.contractId) - ) { - this.contractOptions.push({ - id: detail.contractId, + if (detail.contractId) { + await this.ensureContractPartyOptions(detail.contractId, { contractNo: detail.contractNo, contractName: detail.contractName, projectId: detail.projectId, projectName: detail.projectName, deptId: detail.deptId, deptName: detail.deptName, - partyA: detail.payerName, - partyB: detail.payeeName, + settlementType: detail.settlementType, }); + this.applyPartySelection(detail.payerName, detail.payeeName); + } else { + this.payerOptions = []; + this.payeeOptions = []; } if (this.publicMode && detail.detailFees) { Object.entries(detail.detailFees).forEach(([detailId, feeRows]) => { @@ -1640,6 +1662,8 @@ export default { deptName: row.deptName || row.organizationName, partyA: row.partyA || row.payerName, partyB: row.partyB || row.payeeName, + partyAJson: row.partyAJson || '', + partyBJson: row.partyBJson || '', settlementType: row.settlementType || (row.contractCategory === '客户合同' ? 'receivable' : 'payable'), }; @@ -1649,7 +1673,7 @@ export default { this.handleContractChange(contract.id); this.contractDialog.visible = false; this.contractDialog.current = null; - this.$refs.formRef?.clearValidate('contractId'); + this.$refs.formRef?.clearValidate(['contractId', 'payerName', 'payeeName']); }, contractCategoryName(value) { return ( @@ -1714,6 +1738,8 @@ export default { this.form.deptName = ''; this.form.payerName = ''; this.form.payeeName = ''; + this.payerOptions = []; + this.payeeOptions = []; this.summaryFees = []; this.projectAdvanceWriteOffs = []; return; @@ -1726,9 +1752,8 @@ export default { this.form.deptId = contract.deptId; this.form.deptName = contract.deptName; this.form.settlementType = contract.settlementType || 'payable'; - // 部分候选合同只带 id/名称,缺甲方乙方;此时保留已带出的收付方,不要清成 undefined - if (contract.partyA) this.form.payerName = contract.partyA; - if (contract.partyB) this.form.payeeName = contract.partyB; + this.syncPartyOptionsFromContract(contract); + this.applyPartySelection('', ''); this.form.currency = contract.settlementCurrency || 'RMB'; this.summaryFees = []; this.projectAdvanceWriteOffs = []; @@ -1737,6 +1762,152 @@ export default { this.fetchBfiExchangeRate(); } }, + parsePartyNames(partyJson, fallbackName) { + try { + const parsed = partyJson ? JSON.parse(partyJson) : []; + if (Array.isArray(parsed) && parsed.length) { + const names = parsed + .map(item => String(item?.customerName || '').trim()) + .filter(Boolean); + if (names.length) return [...new Set(names)]; + } + } catch (error) { + // JSON 解析失败时回退到名称拆分 + } + return [ + ...new Set( + String(fallbackName || '') + .split(/[、,,]/) + .map(name => name.trim()) + .filter(Boolean) + ), + ]; + }, + buildPartySelectOptions(names) { + return (names || []).map(name => ({ dictKey: name, dictValue: name })); + }, + resolveContractPartyNames(contract) { + const settlementType = contract?.settlementType || this.form.settlementType || 'payable'; + const partyANames = this.parsePartyNames(contract?.partyAJson, contract?.partyA); + const partyBNames = this.parsePartyNames(contract?.partyBJson, contract?.partyB); + if (settlementType === 'receivable') { + return { payerNames: partyBNames, payeeNames: partyANames }; + } + return { payerNames: partyANames, payeeNames: partyBNames }; + }, + syncPartyOptionsFromContract(contract) { + const { payerNames, payeeNames } = this.resolveContractPartyNames(contract || {}); + this.payerOptions = this.buildPartySelectOptions(payerNames); + this.payeeOptions = this.buildPartySelectOptions(payeeNames); + }, + isJoinedPartyName(value) { + return /[、,,]/.test(String(value || '')); + }, + pickPartyValue(names, currentValue) { + const options = Array.isArray(names) ? names : []; + const current = String(currentValue || '').trim(); + if (!options.length) { + // 无线上选项时(如公开查看拉不到合同),保留已有单值,丢弃多方拼接脏值 + return this.isJoinedPartyName(current) ? '' : current; + } + if (current && !this.isJoinedPartyName(current) && options.includes(current)) { + return current; + } + return options[0] || ''; + }, + applyPartySelection(payerName, payeeName) { + const payerNames = this.payerOptions.map(item => item.dictKey); + const payeeNames = this.payeeOptions.map(item => item.dictKey); + this.form.payerName = this.pickPartyValue(payerNames, payerName); + this.form.payeeName = this.pickPartyValue(payeeNames, payeeName); + // 编辑历史单值不在合同多方中时,仍保留展示,避免下拉空白 + if ( + this.form.payerName && + !this.payerOptions.some(item => item.dictKey === this.form.payerName) + ) { + this.payerOptions = [ + ...this.payerOptions, + { dictKey: this.form.payerName, dictValue: this.form.payerName }, + ]; + } + if ( + this.form.payeeName && + !this.payeeOptions.some(item => item.dictKey === this.form.payeeName) + ) { + this.payeeOptions = [ + ...this.payeeOptions, + { dictKey: this.form.payeeName, dictValue: this.form.payeeName }, + ]; + } + }, + async fetchContractPartyDetail(contractId) { + if (!contractId) return null; + const { data } = await getContractDetail(contractId); + return data?.data || null; + }, + async ensureContractPartyOptions(contractId, fallback = {}) { + if (!contractId) { + this.payerOptions = []; + this.payeeOptions = []; + return null; + } + let contract = this.contractOptions.find(item => String(item.id) === String(contractId)); + const needDetail = + !contract || + !(contract.partyAJson || contract.partyBJson) || + this.isJoinedPartyName(contract.partyA) || + this.isJoinedPartyName(contract.partyB) || + (!contract.partyA && !contract.partyB); + if (needDetail) { + try { + const detail = await this.fetchContractPartyDetail(contractId); + if (detail) { + contract = { + ...(contract || {}), + id: contractId, + contractNo: detail.contractNo || fallback.contractNo || contract?.contractNo, + contractName: detail.contractName || fallback.contractName || contract?.contractName, + projectId: detail.projectId || fallback.projectId || contract?.projectId, + projectName: detail.projectName || fallback.projectName || contract?.projectName, + deptId: + detail.organizationId || + detail.deptId || + fallback.deptId || + contract?.deptId, + deptName: + detail.organizationName || + detail.deptName || + fallback.deptName || + contract?.deptName, + partyA: detail.partyA || contract?.partyA || '', + partyB: detail.partyB || contract?.partyB || '', + partyAJson: detail.partyAJson || contract?.partyAJson || '', + partyBJson: detail.partyBJson || contract?.partyBJson || '', + settlementType: + fallback.settlementType || + contract?.settlementType || + (detail.contractCategory === '客户合同' ? 'receivable' : 'payable'), + settlementCurrency: detail.settlementCurrency || contract?.settlementCurrency, + }; + } + } catch (error) { + // 合同详情失败时仍用现有快照拆分多方名称 + } + } + if (!contract) { + contract = { + id: contractId, + ...fallback, + partyA: fallback.partyA || '', + partyB: fallback.partyB || '', + }; + } + const index = this.contractOptions.findIndex(item => String(item.id) === String(contractId)); + if (index >= 0) this.contractOptions.splice(index, 1, contract); + else this.contractOptions.push(contract); + this.syncPartyOptionsFromContract(contract); + return contract; + }, async saveDraft(shouldSubmit) { await this.$refs.formRef?.validate(); if (shouldSubmit && !this.details.length) { From c525bffecad37a62d36c774826e064ebfceb3eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 5 Nov 2026 11:44:19 +0800 Subject: [PATCH 26/32] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BC=80?= =?UTF-8?q?=E7=A5=A8=E7=94=B3=E8=AF=B7=E6=8F=90=E4=BA=A4=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=A1=B5=E6=9C=AA=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/invoice-application-form.vue | 4 +++ src/views/payment/invoice-application.vue | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/views/payment/invoice-application-form.vue b/src/views/payment/invoice-application-form.vue index 9e91a2c..b4897ea 100644 --- a/src/views/payment/invoice-application-form.vue +++ b/src/views/payment/invoice-application-form.vue @@ -494,6 +494,9 @@ import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.v import { getMkPublicDetail } from '@/api/mk-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; +// 提交成功后带标记返回,列表页(keep-alive)据此重新拉取 +const invoiceApplicationListRefreshKey = 'invoice-application-list-refresh'; + let localId = 0; const emptyLine = () => ({ localId: ++localId, @@ -1227,6 +1230,7 @@ export default { mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); + sessionStorage.setItem(invoiceApplicationListRefreshKey, '1'); this.goBack(); }, async syncReferenceData() { diff --git a/src/views/payment/invoice-application.vue b/src/views/payment/invoice-application.vue index 27075a7..164d462 100644 --- a/src/views/payment/invoice-application.vue +++ b/src/views/payment/invoice-application.vue @@ -251,6 +251,10 @@ import { getMKApprovalUrl } from '@/api/system/business-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; import { kingdeeStatusType } from '@/utils/kingdee'; +// 表单提交成功后带标记返回,keep-alive 缓存的列表页据此重新拉取 +const invoiceApplicationListRefreshKey = 'invoice-application-list-refresh'; +const invoiceApplicationListRoute = '/payment/invoice-application'; + const emptyQuery = () => ({ applicationNo: '', projectName: '', @@ -308,7 +312,28 @@ export default { this.mkEnabled = false; }); }, + activated() { + this.consumeListRefreshFlag(); + }, + watch: { + $route(to, from) { + if ( + to.path === invoiceApplicationListRoute && + from?.path?.includes('/invoice-application/form') + ) { + this.consumeListRefreshFlag(); + } + }, + }, methods: { + /** 表单提交成功后带标记返回时,重新拉取列表 */ + consumeListRefreshFlag() { + if (!sessionStorage.getItem(invoiceApplicationListRefreshKey)) { + return; + } + sessionStorage.removeItem(invoiceApplicationListRefreshKey); + this.loadTable(); + }, hasPermission(code) { return this.permission?.[code] !== false; }, From 4dd63fc5fa307c477e3a8ec478876c43e70eb3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 5 Nov 2026 12:05:56 +0800 Subject: [PATCH 27/32] =?UTF-8?q?feat:=20=E6=94=B6=E7=A5=A8=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=E6=96=B0=E5=A2=9E=E6=89=80=E5=B1=9E=E5=90=88=E5=90=8C?= =?UTF-8?q?=E5=B8=A6=E5=87=BA=E6=89=BF=E5=8A=9E=E9=83=A8=E9=97=A8=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=E6=89=8B=E9=80=89=E7=BB=93=E7=AE=97=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/payment/invoice-receipt-form.vue | 183 +++++++++++++-------- 1 file changed, 114 insertions(+), 69 deletions(-) diff --git a/src/views/payment/invoice-receipt-form.vue b/src/views/payment/invoice-receipt-form.vue index b9e6538..50cbfb8 100644 --- a/src/views/payment/invoice-receipt-form.vue +++ b/src/views/payment/invoice-receipt-form.vue @@ -100,18 +100,28 @@ - - - + + +
+ + + +
+
+
+
+ + +
@@ -214,6 +224,48 @@ :query-api="apiQueryInvoicePool" @confirm="handleInvoicePicked" /> + + + + + + + + + + + 查询 + 重置 + + + + + + + + + + + + @@ -221,7 +273,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 { getList as getContractList } from '@/api/business/contract-manage'; 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'; @@ -247,6 +299,8 @@ const emptyForm = () => ({ customerEmails: [], departmentEmails: [], projectName: '', + contractNo: '', + contractName: '', deptId: null, deptName: '', payerName: '', @@ -269,16 +323,19 @@ export default { customerEmailOptions: [], departmentEmailOptions: [], invoiceItems: [], - deptTreeOptions: [], - deptMap: new Map(), - // 结算组织=合同承办部门:部门树级联单选(取末级 id),后端沿部门树上溯平台公司取金蝶组织 - deptCascaderProps: { checkStrictly: true, emitPath: true }, + contractDialog: { + visible: false, + loading: false, + rows: [], + page: { current: 1, size: 10, total: 0 }, + query: { contractNo: '', contractName: '' }, + }, invoiceDialog: { visible: false, }, rules: { invoiceNo: [{ required: true, validator: this.validateInvoice, trigger: 'change' }], - deptId: [{ required: true, validator: this.validateDept, trigger: 'change' }], + contractNo: [{ required: true, validator: this.validateContract, trigger: 'change' }], customerEmails: [{ validator: this.validateEmails, trigger: 'change' }], departmentEmails: [{ validator: this.validateEmails, trigger: 'change' }], }, @@ -299,12 +356,6 @@ export default { const code = this.recordId ? 'invoice_receipt_edit' : 'invoice_receipt_add'; return this.hasPermission(code); }, - deptCascaderValue() { - if (this.form.deptId === null || this.form.deptId === undefined || this.form.deptId === '') { - return []; - } - return [String(this.form.deptId)]; - }, }, created() { this.initialize(); @@ -339,7 +390,6 @@ export default { .filter(Boolean); }, async initialize() { - this.loadDepartments(); if (this.isPublicViewPage) { await this.loadPublicDetail(); return; @@ -380,9 +430,9 @@ export default { } callback(); }, - validateDept(rule, value, callback) { - if (this.form.deptId === null || this.form.deptId === undefined || this.form.deptId === '') { - callback(new Error('请选择结算组织')); + validateContract(rule, value, callback) { + if (!this.form.contractNo) { + callback(new Error('请选择所属合同')); return; } callback(); @@ -454,47 +504,43 @@ export default { this.invoiceItems = []; } }, - 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(() => {}); + // 所属合同=承运商合同:选择后带出承办部门(结算组织=承办部门对应平台公司金蝶编码,后端派生) + openContractDialog() { + this.contractDialog.visible = true; + this.searchContracts(); }, - 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, + searchContracts() { + this.contractDialog.page.current = 1; + this.loadContracts(); + }, + resetContractQuery() { + this.contractDialog.query = { contractNo: '', contractName: '' }; + this.searchContracts(); + }, + async loadContracts() { + this.contractDialog.loading = true; + try { + const res = await getContractList(this.contractDialog.page.current, this.contractDialog.page.size, { + ...this.contractDialog.query, + contractCategory: '承运商合同', + approvalStatuses: 'approved,change_approved', }); - return result; - }, []); + const data = this.unwrapData(res) || {}; + this.contractDialog.rows = data.records || []; + this.contractDialog.page.total = Number(data.total || 0); + } finally { + this.contractDialog.loading = false; + } }, - 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, - })); - }, - flattenDepartments(rows) { - return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]); - }, - 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(() => {}); + pickContract(row) { + if (!row) return; + Object.assign(this.form, { + contractNo: row.contractNo, + contractName: row.contractName, + deptName: row.organizationName, + }); + this.contractDialog.visible = false; + this.$refs.formRef?.validateField('contractNo').catch(() => {}); }, openSettlementDetail(row) { const id = row.formalSettlementId || row.settlementId || row.id; @@ -527,9 +573,8 @@ export default { departmentEmails: this.form.departmentEmails.join(';'), attachmentsJson: JSON.stringify(this.form.attachments || []), remark: this.form.remark, - // 结算组织=页面手选部门(先票后款不挂结算单,认领关系由结算单侧维护,不再传 settlements) - deptId: this.form.deptId, - deptName: this.form.deptName, + // 所属合同仅传编号,合同名称/承办部门由后端按编号带出(结算组织=合同承办部门) + contractNo: this.form.contractNo, }; }, async saveDraft() { From 22bbcbb7aa32be0e8faefb32a06782ec158752e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 5 Nov 2026 13:25:21 +0800 Subject: [PATCH 28/32] =?UTF-8?q?fix:=20=E5=BA=94=E6=94=B6=E8=BD=AC?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E4=B8=8E=E5=BC=80=E7=A5=A8=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E7=BB=93=E7=AE=97=E5=8D=95=E5=8F=B7=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E6=94=B6=E6=AC=BE=E7=BB=93=E7=AE=97=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/views/index.js | 34 ++++++++++++++++++- src/views/payment/invoice-application.vue | 13 +++++-- src/views/settlement/formal-settlement.vue | 6 ++-- .../settlement/receivable-payable-detail.vue | 4 ++- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/router/views/index.js b/src/router/views/index.js index e90da64..02c3516 100644 --- a/src/router/views/index.js +++ b/src/router/views/index.js @@ -129,7 +129,39 @@ export default [ name: '新增编辑结算单', meta: { keepAlive: false, - // 付款/收款结算单共用表单页,具体高亮菜单由页面按 settlementType 回写 + // 付款结算单表单入口,高亮付款结算单菜单 + activeMenu: '/settlement/payment-settlement', + }, + component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), + }, + ], + }, + { + // 收款结算单表单入口:与付款结算单共用表单组件,仅高亮菜单不同 + path: '/settlement/receipt-settlement/form', + component: Layout, + children: [ + { + path: '', + name: '新增编辑收款结算单', + meta: { + keepAlive: false, + activeMenu: '/settlement/receipt-settlement', + }, + component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), + }, + ], + }, + { + // 付款结算单表单入口:与 settlementListPath('payable') + '/form' 对应 + path: '/settlement/payment-settlement/form', + component: Layout, + children: [ + { + path: '', + name: '新增编辑付款结算单', + meta: { + keepAlive: false, activeMenu: '/settlement/payment-settlement', }, component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), diff --git a/src/views/payment/invoice-application.vue b/src/views/payment/invoice-application.vue index 164d462..39c40a6 100644 --- a/src/views/payment/invoice-application.vue +++ b/src/views/payment/invoice-application.vue @@ -250,6 +250,7 @@ import organizationSearch from '@/mixins/organization-search'; import { getMKApprovalUrl } from '@/api/system/business-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; import { kingdeeStatusType } from '@/utils/kingdee'; +import { settlementListPath } from '@/utils/settlement-menu'; // 表单提交成功后带标记返回,keep-alive 缓存的列表页据此重新拉取 const invoiceApplicationListRefreshKey = 'invoice-application-list-refresh'; @@ -397,12 +398,18 @@ export default { item => String(item.formalSettlementNo || '').trim() === normalizedNo ); if (!row?.id) { - this.$message.warning('未找到对应的付款结算单'); + this.$message.warning('未找到对应的结算单'); return; } + // 开票申请关联的多为收款结算单,按单据类型路由到对应表单入口,避免误落付款结算单 this.$router.push({ - path: '/settlement/formal-settlement/form', - query: { mode: 'view', id: row.id, name: '查看正式结算' }, + path: settlementListPath(row.settlementType) + '/form', + query: { + mode: 'view', + id: row.id, + settlementType: row.settlementType, + name: '查看正式结算', + }, }); } catch (error) { this.$message.error('正式结算单详情加载失败'); diff --git a/src/views/settlement/formal-settlement.vue b/src/views/settlement/formal-settlement.vue index f4b9dd4..b2e5905 100644 --- a/src/views/settlement/formal-settlement.vue +++ b/src/views/settlement/formal-settlement.vue @@ -436,8 +436,9 @@ export default { (handlers[type] || (() => {}))(); }, openCreate() { + // 按列表锁定的结算类型路由到对应表单入口,保证菜单高亮与关闭回落列表正确 this.$router.push({ - path: '/settlement/formal-settlement/form', + path: settlementListPath(this.activeSettlementType) + '/form', query: { mode: 'add', name: `新增${this.billLabel}`, @@ -448,8 +449,9 @@ export default { }, openEdit(row) { const settlementType = row.settlementType || this.activeSettlementType; + // 行类型可能与列表锁定类型不同,按行类型路由 this.$router.push({ - path: '/settlement/formal-settlement/form', + path: settlementListPath(settlementType) + '/form', query: { mode: 'edit', id: row.id, diff --git a/src/views/settlement/receivable-payable-detail.vue b/src/views/settlement/receivable-payable-detail.vue index a837842..00eb971 100644 --- a/src/views/settlement/receivable-payable-detail.vue +++ b/src/views/settlement/receivable-payable-detail.vue @@ -1060,6 +1060,7 @@ import { formatContractOptionLabel } from '@/utils/contract'; import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule'; import { downloadXls } from '@/utils/util'; import { createSettlementTransfer } from '@/utils/settlement-transfer'; +import { settlementListPath } from '@/utils/settlement-menu'; import { contractApprovalStatusOptions, contractCategoryOptions, @@ -2659,10 +2660,11 @@ export default { }); const isPreSettlement = settlementBillType === 'pre'; this.transferDialog.visible = false; + // 按结算类型路由到对应表单入口,保证收款/付款菜单各自高亮正确 await this.$router.push({ path: isPreSettlement ? '/settlement/pre-settlement/form' - : '/settlement/formal-settlement/form', + : settlementListPath(this.settlementType) + '/form', query: { mode: 'add', transferToken, From ffc2942d2b32d4a672d4df168bb11f07f418beff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 5 Nov 2026 13:25:26 +0800 Subject: [PATCH 29/32] =?UTF-8?q?fix:=20=E4=BB=98=E6=AC=BE=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E7=BB=93=E7=AE=97=E5=8D=95=E5=8F=B7=E8=AF=AF=E8=B7=B3?= =?UTF-8?q?=E9=A2=84=E4=BB=98=E7=BB=93=E7=AE=97=E5=8D=95=E8=BF=87=E6=BB=A4?= =?UTF-8?q?-1=E5=8D=A0=E4=BD=8Did?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/payment-application-form.vue | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue index 0a01f1c..f93c25f 100644 --- a/src/views/payment/payment-application-form.vue +++ b/src/views/payment/payment-application-form.vue @@ -1296,9 +1296,11 @@ export default { this.form = { ...emptyForm(), ...data, + // 后端可能返回 -1 占位预付 id,统一归一为 null,防止点击结算单号误跳预付 + preSettlementId: this.isValidReferenceId(data.preSettlementId) ? data.preSettlementId : null, preSettlementIds: data.preSettlementIds?.length ? data.preSettlementIds - : data.preSettlementId + : this.isValidReferenceId(data.preSettlementId) ? [data.preSettlementId] : [], attachments: this.parse(data.attachmentsJson), @@ -1344,9 +1346,13 @@ export default { this.form = { ...emptyForm(), ...data, + // 后端可能返回 -1 占位预付 id,统一归一为 null,防止点击结算单号误跳预付 + preSettlementId: this.isValidReferenceId(data.preSettlementId) + ? data.preSettlementId + : null, preSettlementIds: data.preSettlementIds?.length ? data.preSettlementIds - : data.preSettlementId + : this.isValidReferenceId(data.preSettlementId) ? [data.preSettlementId] : [], attachments: this.parse(data.attachmentsJson), @@ -1587,17 +1593,22 @@ export default { }); }, clearFormalReferenceWhenPreOnly() { + // 纯预付引用才收敛为预付;-1 占位 id 不得充当预付关联 const progressAdvance = this.form.paymentType === 'progress_advance'; const onlyPre = progressAdvance || (this.referenceRows.length > 0 && this.referenceRows.every(item => item.referenceType === 'pre')); if (!onlyPre) return; - if (!this.form.preSettlementId) { + if (!this.isValidReferenceId(this.form.preSettlementId)) { const preId = - this.referenceRows.find(item => item.referenceType === 'pre')?.id || - (progressAdvance ? this.form.settlementId : null); - if (preId) { + this.referenceRows.find( + item => item.referenceType === 'pre' && this.isValidReferenceId(item.id) + )?.id || + (progressAdvance && this.isValidReferenceId(this.form.settlementId) + ? this.form.settlementId + : null); + if (this.isValidReferenceId(preId)) { this.form.preSettlementId = preId; this.form.preSettlementIds = [preId]; } @@ -1608,13 +1619,16 @@ export default { }, restoreReferenceRows(data, fallbackRow) { const relations = Array.isArray(data?.settlements) ? data.settlements : []; + // 行级预付/正式类型以过滤占位 id 后的关联为准,决定点击结算单号的跳转目标 const rows = relations .map(item => { - const isPre = Boolean(item.preSettlementId || item.preSettlementNo); + // 后端用 -1 占位空 id,必须先过滤再判预付,否则正式结算行会被误标为预付 + const preId = this.isValidReferenceId(item.preSettlementId) ? item.preSettlementId : null; + const isPre = Boolean(preId || item.preSettlementNo); const settlementNo = item.preSettlementNo || item.formalSettlementNo || ''; if (!settlementNo) return null; return { - id: isPre ? item.preSettlementId || item.formalSettlementId : item.formalSettlementId, + id: isPre ? preId || item.formalSettlementId : item.formalSettlementId, settlementNo, referenceType: isPre ? 'pre' : 'formal', settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)), @@ -1642,9 +1656,10 @@ export default { }, createReferenceRow(row) { const settlementAmount = Number(row.settlementAmount || 0); + // 后端以 -1 占位空预付 id,必须先校验否则正式结算行会被误判为预付 const isPre = Boolean( row.preSettlementNo || - row.preSettlementId || + this.isValidReferenceId(row.preSettlementId) || row.referenceType === 'pre' || row.paymentType === 'progress_advance' ); @@ -2770,23 +2785,26 @@ export default { }, async openSettlementDetail(row = {}) { const settlementNo = String(row.settlementNo || '').trim(); + // 行上 referenceType 优先;发票行等只带单号的,按单号回查引用行;都缺省才回落表单级推断 + const matchedReference = this.referenceRows.find( + item => String(item.settlementNo || '').trim() === settlementNo + ); + const referenceType = row.referenceType || matchedReference?.referenceType; const isPre = - row.referenceType === 'pre' || - this.form.paymentType === 'progress_advance' || - Boolean(this.form.preSettlementId || this.form.preSettlementNo); + referenceType === 'pre' || + (!referenceType && + (this.form.paymentType === 'progress_advance' || + Boolean( + this.isValidReferenceId(this.form.preSettlementId) || this.form.preSettlementNo + ))); let id = row.id || row.preSettlementId || row.settlementId || + matchedReference?.id || (isPre ? this.form.preSettlementId || this.form.preSettlementIds?.[0] : this.form.settlementId || this.form.settlementIds?.[0]); - if (!id && settlementNo) { - const matched = this.referenceRows.find( - item => String(item.settlementNo || '').trim() === settlementNo - ); - id = matched?.id; - } if (!id && settlementNo) { try { const listApi = isPre ? preApi.getList : formalApi.getList; From 8057e860de9b3164846ce71b446b101aec083ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 5 Nov 2026 14:47:06 +0800 Subject: [PATCH 30/32] =?UTF-8?q?fix:=20=E6=89=B9=E9=87=8F=E8=BD=AC?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E5=BC=B9=E7=AA=97=E5=B1=85=E4=B8=AD=E9=99=90?= =?UTF-8?q?=E9=AB=98=E5=B9=B6=E5=9B=BA=E5=AE=9A=E5=8B=BE=E9=80=89=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/settlement/receivable-payable-detail.vue | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/views/settlement/receivable-payable-detail.vue b/src/views/settlement/receivable-payable-detail.vue index 00eb971..6f19b7f 100644 --- a/src/views/settlement/receivable-payable-detail.vue +++ b/src/views/settlement/receivable-payable-detail.vue @@ -507,7 +507,13 @@ - +
@@ -548,9 +554,10 @@ :data="transferRows" row-key="id" border + max-height="50vh" @selection-change="transferSelectionChange" > - + Date: Thu, 5 Nov 2026 15:01:28 +0800 Subject: [PATCH 31/32] =?UTF-8?q?fix:=20=E9=A2=84=E4=BB=98=E7=BB=93?= =?UTF-8?q?=E7=AE=97=E5=8D=95=E4=B8=8E=E7=BB=93=E7=AE=97=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=8D=95=E5=88=86=E9=A1=B5=E5=9B=BA=E5=AE=9A=E5=BA=95=E9=83=A8?= =?UTF-8?q?=EF=BC=8C=E6=90=9C=E7=B4=A2label=E4=B8=8D=E5=86=8D=E6=8D=A2?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/option/settlement/formalSettlement.js | 2 +- src/views/settlement/pre-settlement.vue | 32 ++++++++++++++++++- .../settlement/settlement-adjustment.vue | 30 +++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/option/settlement/formalSettlement.js b/src/option/settlement/formalSettlement.js index 6fdc26b..6e335c0 100644 --- a/src/option/settlement/formalSettlement.js +++ b/src/option/settlement/formalSettlement.js @@ -118,7 +118,7 @@ export const formalSettlementOption = (settlementType = '') => ({ emptyBtnText: '重置', searchShow: true, searchIcon: true, - searchLabelWidth: 88, + searchLabelWidth: 100, // 查询/重置紧跟首行 4 个条件(4×5 + 菜单 4 = 24),不再独占下一整行 searchMenuSpan: 4, searchIndex: 4, diff --git a/src/views/settlement/pre-settlement.vue b/src/views/settlement/pre-settlement.vue index 8648db4..a36e4b4 100644 --- a/src/views/settlement/pre-settlement.vue +++ b/src/views/settlement/pre-settlement.vue @@ -1,7 +1,7 @@