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] =?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) {