From 3da2d428f3b49fd24dc3c027a3658ee6fad5b1f7 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 25 Aug 2026 22:35:50 +0800 Subject: [PATCH] fix bug --- src/api/settlement/formalSettlement.js | 3 +- .../settlement/formalSettlementTable.js | 6 +- .../settlement/receivable-payable-detail.js | 7 +- .../settlement/settlementAdjustmentForm.js | 1 + .../components/formal-settlement-editor.vue | 333 ++++++++++++-- .../formal-settlement-table-panel.vue | 37 +- .../components/pre-settlement-editor.vue | 14 +- .../settlement-adjustment-editor.vue | 157 ++++++- src/views/settlement/formal-settlement.vue | 19 + src/views/settlement/pre-settlement.vue | 24 +- .../settlement/receivable-payable-detail.vue | 412 ++++++++++++++---- 11 files changed, 883 insertions(+), 130 deletions(-) diff --git a/src/api/settlement/formalSettlement.js b/src/api/settlement/formalSettlement.js index 42e47f3..cfb5267 100644 --- a/src/api/settlement/formalSettlement.js +++ b/src/api/settlement/formalSettlement.js @@ -13,7 +13,8 @@ export const getCandidates = (current, size, params) => }); export const getContractOptions = (keyword, projectId) => request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword, projectId } }); -export const getNextNo = () => request({ url: `${baseUrl}/next-no`, method: 'get' }); +export const getNextNo = settlementType => + request({ url: `${baseUrl}/next-no`, method: 'get', params: { settlementType } }); export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' }); export const getCandidateDetails = (current, size, params) => request({ diff --git a/src/option/settlement/formalSettlementTable.js b/src/option/settlement/formalSettlementTable.js index dc8c79e..c3562c2 100644 --- a/src/option/settlement/formalSettlementTable.js +++ b/src/option/settlement/formalSettlementTable.js @@ -1,6 +1,5 @@ export const formalSettlementTableColumns = [ { label: '结算单号', prop: 'formalSettlementNo', minWidth: 160, link: true, fixed: 'left' }, - { label: '预结算单号', prop: 'preSettlementNos', minWidth: 190 }, { label: '来源', prop: 'sourceType', minWidth: 110 }, { label: '付款方', prop: 'payerName', minWidth: 150 }, { label: '收款方', prop: 'payeeName', minWidth: 150 }, @@ -12,13 +11,16 @@ export const formalSettlementTableColumns = [ { label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true }, { label: '结算汇率', prop: 'exchangeRate', minWidth: 110 }, { label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true }, - { label: '已收/已付合计', prop: 'paidAmount', minWidth: 145, money: true }, + { label: '已收/已付合计(含预付)', prop: 'paidAmount', minWidth: 200, money: true }, + { label: '剩余可付金额', prop: 'remainingPayableAmount', minWidth: 145, money: true }, + { label: '发票金额', prop: 'invoiceAmount', minWidth: 130, money: true }, { label: '发票状态', prop: 'invoiceStatusName', minWidth: 120 }, { label: '收付款状态', prop: 'paymentStatusName', minWidth: 120 }, { label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true }, { label: '当前节点', prop: 'currentNode', minWidth: 130 }, { label: '当前处理人', prop: 'currentProcessor', minWidth: 130 }, { label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 }, + { label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 }, { label: '创建人', prop: 'createUserName', minWidth: 110 }, { label: '创建时间', prop: 'createTime', minWidth: 170 }, ]; diff --git a/src/option/settlement/receivable-payable-detail.js b/src/option/settlement/receivable-payable-detail.js index 30b8557..9a5c212 100644 --- a/src/option/settlement/receivable-payable-detail.js +++ b/src/option/settlement/receivable-payable-detail.js @@ -57,14 +57,13 @@ export const feeDetailBaseColumns = [ { label: '货物类型', prop: 'cargoType', minWidth: 130 }, { label: '规格', prop: 'specification', minWidth: 120 }, { label: '型号', prop: 'model', minWidth: 120 }, - { label: '运费计费要素', prop: 'billingFactor', minWidth: 130 }, - { label: '运费计费类型', prop: 'billingType', minWidth: 130 }, + { label: '计费要素', prop: 'billingFactor', minWidth: 130 }, + { label: '计费类型', prop: 'billingType', minWidth: 130 }, { label: '运输量', prop: 'transportQuantityText', minWidth: 120 }, { label: '运费计算单位', prop: 'priceUnit', minWidth: 130 }, { label: '运输单价', prop: 'unitPrice', minWidth: 120 }, { label: '里程(KM)', prop: 'mileage', minWidth: 120 }, - { label: '运输费', prop: 'freightAmount', minWidth: 180 } - + { label: '运输费', prop: 'freightAmount', minWidth: 180 }, ]; export const feeDetailTailColumns = [ diff --git a/src/option/settlement/settlementAdjustmentForm.js b/src/option/settlement/settlementAdjustmentForm.js index 3755185..30cd4bf 100644 --- a/src/option/settlement/settlementAdjustmentForm.js +++ b/src/option/settlement/settlementAdjustmentForm.js @@ -24,5 +24,6 @@ export const createSettlementAdjustmentForm = () => ({ adjustmentAmount: 0, originalSettlementAmount: 0, adjustedSettlementAmount: 0, + attachmentsJson: '[]', remark: '', }); diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 1fa376a..ffebc64 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -136,6 +136,9 @@ - + {{ formatMoney(row[column.prop]) }} {{ feeCategoryName(row.feeType) }} @@ -214,6 +223,9 @@ > @@ -232,15 +244,79 @@ - - + + + + + + + + + + + + + + + + + + + + + + + +
+ +
@@ -313,10 +389,13 @@ v-bind="column" align="center" > - +
@@ -493,6 +572,8 @@ import { sourceColumns, summaryColumns, } from '@/option/settlement/formalSettlementTable'; +import { getDictionary } from '@/api/system/dictbiz'; +import { mapGetters } from 'vuex'; import * as XLSX from 'xlsx'; export default { @@ -518,6 +599,8 @@ export default { summaryFees: [], payments: [], attachments: [], + attachmentUploadFiles: [], + attachmentTypeOptions: [], attachmentFileTypes: [ 'pdf', 'bmp', @@ -539,6 +622,7 @@ export default { allContracts: [], projects: [], feeOptions: [], + transportTypeOptions: [], fields: formalSettlementFormFields, sourceTableColumns: sourceColumns, summaryTableColumns: summaryColumns, @@ -592,6 +676,7 @@ export default { }; }, computed: { + ...mapGetters(['userInfo']), visible: { get() { return this.modelValue; @@ -635,6 +720,10 @@ export default { }) ); }, + missingAttachmentTypeText() { + const missing = this.getMissingAttachmentTypes(); + return missing.length ? `未上传:${missing.join('、')}` : ''; + }, }, watch: { modelValue: { @@ -663,14 +752,19 @@ export default { this.summaryFees = []; this.payments = []; this.attachments = []; + this.attachmentUploadFiles = []; this.detailCollapsed = false; this.resetDetailQuery(false); - await Promise.all([this.loadAllContracts(), this.loadFeeOptions()]); + await Promise.all([ + this.loadAllContracts(), + this.loadFeeOptions(), + this.loadAttachmentTypeOptions(), + this.loadTransportTypeOptions(), + ]); if (!this.recordId) { this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD'); - const response = await getNextNo(); - this.form.formalSettlementNo = this.unwrapData(response) || ''; if (this.initialData) await this.applyInitialData(); + await this.refreshFormalSettlementNo(); return; } this.loading = true; @@ -690,6 +784,7 @@ export default { this.summaryFees = data.summaryFees || []; this.payments = data.payments || []; this.attachments = this.parseAttachments(data.attachmentsJson); + this.sortAttachments(); this.contracts = this.allContracts.filter( item => String(item.projectId) === String(this.form.projectId) ); @@ -697,6 +792,14 @@ export default { this.loading = false; } }, + async refreshFormalSettlementNo() { + if (this.form.id || !this.form.settlementType) { + if (!this.form.id) this.form.formalSettlementNo = ''; + return; + } + const response = await getNextNo(this.form.settlementType); + this.form.formalSettlementNo = this.unwrapData(response) || ''; + }, async applyInitialData() { const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements) ? this.initialData.sourcePreSettlements @@ -739,7 +842,7 @@ export default { settlementType, }); } - this.handleContractChange(contractId); + this.handleContractChange(contractId, false); Object.assign(this.form, { contractId, contractNo: first.contractNo || this.form.contractNo, @@ -781,7 +884,7 @@ export default { settlementType, }); } - this.handleContractChange(contractId); + this.handleContractChange(contractId, false); this.sources = rows.map(row => ({ ...row, preSettlementId: row.preSettlementId || row.id, @@ -869,7 +972,7 @@ export default { this.form.sourceDetailIds = []; this.loadContracts(); }, - handleContractChange(id) { + handleContractChange(id, refreshSettlementNo = true) { const contract = this.contracts.find(item => String(item.id) === String(id)); if (!contract) return; const formalSettlementId = this.form.id; @@ -889,6 +992,7 @@ export default { this.summaryFees = []; this.form.sourcePreSettlementIds = []; this.form.sourceDetailIds = []; + if (refreshSettlementNo) this.refreshFormalSettlementNo(); }, openCandidateDialog() { this.candidate.visible = true; @@ -933,6 +1037,7 @@ export default { contractId: this.sources[0].contractId, formalSettlementNo: this.form.formalSettlementNo, }); + this.refreshFormalSettlementNo(); } this.form.settlementAmount = this.sources.reduce( (sum, item) => sum + Number(item.settlementAmount || 0), @@ -1187,6 +1292,7 @@ export default { if (invalidManualFee) { return this.$message.warning('请完善手工费用的费用类型和费用项'); } + if (!this.validateAttachments()) return; this.saving = true; try { await save({ @@ -1213,7 +1319,7 @@ export default { remark: row.remark, manualFlag: row.manualFlag || 0, })), - attachmentsJson: JSON.stringify(this.attachments || []), + attachmentsJson: this.stringifyAttachments(this.attachments), remark: this.form.remark, }); this.$message.success('保存成功'); @@ -1233,6 +1339,142 @@ export default { formatMoney(value) { return `${Number(value || 0).toFixed(2)} RMB`; }, + async loadTransportTypeOptions() { + const { data } = await getDictionary({ code: 'transport_type' }); + this.transportTypeOptions = data?.data || []; + }, + transportTypeName(value) { + if (value === undefined || value === null || value === '') return ''; + const normalizedValue = String(value).trim().toLocaleLowerCase(); + const option = this.transportTypeOptions.find( + item => + String(item.dictKey ?? '') + .trim() + .toLocaleLowerCase() === normalizedValue || + String(item.dictValue ?? '') + .trim() + .toLocaleLowerCase() === normalizedValue + ); + return option?.dictValue || value; + }, + async loadAttachmentTypeOptions() { + const response = await getDictionary({ code: 'settle_attachment_types' }); + const data = response?.data?.data || []; + this.attachmentTypeOptions = data.map(item => ({ + label: item.dictValue, + value: item.dictValue, + })); + this.sortAttachments(); + }, + resolveAttachmentType(fileName) { + const normalizedName = String(fileName || '').toLocaleLowerCase(); + const matchedType = [...this.attachmentTypeOptions] + .filter(item => item?.label || item?.value) + .sort( + (a, b) => + String(b.label || b.value || '').length - String(a.label || a.value || '').length + ) + .find(item => { + const typeText = String(item.label || item.value || '').toLocaleLowerCase(); + return typeText && normalizedName.includes(typeText); + }); + if (matchedType?.value) return matchedType.value; + const otherType = this.attachmentTypeOptions.find(item => + String(item.label || item.value || '').includes('其他') + ); + if (otherType?.value) return otherType.value; + this.attachmentTypeOptions.push({ label: '其他附件', value: '其他附件' }); + return '其他附件'; + }, + getAttachmentTypeOrder(type) { + const normalizedType = String(type || '').trim(); + const index = this.attachmentTypeOptions.findIndex( + item => + String(item.value || '').trim() === normalizedType || + String(item.label || '').trim() === normalizedType + ); + return index === -1 ? Number.MAX_SAFE_INTEGER : index; + }, + sortAttachments() { + this.attachments = (this.attachments || []) + .map((file, index) => ({ file, index })) + .sort((a, b) => { + const orderDifference = + this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type); + return orderDifference || a.index - b.index; + }) + .map(item => item.file); + }, + getMissingAttachmentTypes(files = this.attachments) { + const uploadedTypes = new Set( + (files || []) + .filter(item => this.getAttachmentUrl(item)) + .map(item => String(item.type || '').trim()) + .filter(Boolean) + ); + return this.attachmentTypeOptions + .filter(item => String(item.label || item.value || '').trim() !== '其他附件') + .filter(item => !uploadedTypes.has(String(item.value || item.label || '').trim())) + .map(item => item.label || item.value); + }, + validateAttachments(files = this.attachments) { + const missing = this.getMissingAttachmentTypes(files); + if (!missing.length) return true; + this.$message.warning(`请先上传附件:${missing.join('、')}`); + return false; + }, + handleAttachmentUploadSuccess(file) { + const originalName = file.originalName || file.name || '附件'; + this.attachments.push({ + type: this.resolveAttachmentType(originalName), + originalName, + name: originalName, + uploadUserName: this.userInfo.realName || this.userInfo.userName || '', + uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'), + size: this.formatAttachmentSize(file.size), + url: file.url || file.link || '', + }); + this.sortAttachments(); + }, + removeAttachment(index) { + this.attachments.splice(index, 1); + }, + getAttachmentUrl(file = {}) { + return file.url || file.link || file.src || file.domain || ''; + }, + previewAttachment(file) { + const url = this.getAttachmentUrl(file); + if (!url) return this.$message.warning('附件地址为空,无法预览'); + window.open(url, '_blank'); + }, + attachmentTypeName(type) { + const option = this.attachmentTypeOptions.find( + item => String(item.value) === String(type) || String(item.label) === String(type) + ); + return this.displayValue(option?.label || type); + }, + formatAttachmentSize(size) { + if (!size) return ''; + const byteSize = Number(size); + if (!Number.isFinite(byteSize)) return String(size); + const mb = byteSize / 1024 / 1024; + if (mb >= 1) return `${mb.toFixed(2)} MB`; + return `${(byteSize / 1024).toFixed(2)} KB`; + }, + stringifyAttachments(list) { + const files = (list || []) + .filter(item => this.getAttachmentUrl(item)) + .map(item => ({ + type: String(item.type || '').trim(), + originalName: String(item.originalName || item.name || '').trim(), + name: String(item.originalName || item.name || '').trim(), + uploadUserName: String(item.uploadUserName || '').trim(), + uploadTime: String(item.uploadTime || '').trim(), + size: String(item.size || '').trim(), + url: String(this.getAttachmentUrl(item)).trim(), + })); + return files.length ? JSON.stringify(files) : ''; + }, parseFeeItems(value) { if (!value) return {}; if (typeof value === 'object') return value; @@ -1244,12 +1486,38 @@ export default { }, parseAttachments(value) { if (!value) return []; - if (Array.isArray(value)) return value; + let attachments = value; + if (!Array.isArray(value)) { + try { + attachments = JSON.parse(value); + } catch { + return []; + } + } + if (!Array.isArray(attachments)) return []; + return attachments.map(item => { + const url = this.getAttachmentUrl(item); + const originalName = + item.originalName || item.name || this.getAttachmentFileName(url) || '附件'; + return { + ...item, + type: item.type || this.resolveAttachmentType(originalName), + originalName, + name: originalName, + uploadUserName: item.uploadUserName || item.createUserName || item.uploadUser || '', + uploadTime: item.uploadTime || item.createTime || '', + size: this.formatAttachmentSize(item.size || item.attachSize), + url, + }; + }); + }, + getAttachmentFileName(url) { + if (!url) return ''; + const path = String(url).split('?')[0]; try { - const parsed = JSON.parse(value); - return Array.isArray(parsed) ? parsed : []; + return decodeURIComponent(path.substring(path.lastIndexOf('/') + 1)); } catch { - return []; + return path.substring(path.lastIndexOf('/') + 1); } }, }, @@ -1305,6 +1573,21 @@ export default { .formal-editor__adjust-reason { margin-top: 16px; } +.formal-editor__attachment-missing { + margin-left: 12px; + color: var(--el-color-danger); + font-size: 13px; + font-weight: 400; +} +.formal-editor__attachment-upload { + margin-top: 12px; +} +.formal-editor__negative-amount { + color: var(--el-color-danger); +} +.formal-editor :deep(.formal-editor__negative-amount .el-input__inner) { + color: var(--el-color-danger); +} /* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */ .formal-editor__candidate-search, .formal-editor__detail-search { diff --git a/src/views/settlement/components/formal-settlement-table-panel.vue b/src/views/settlement/components/formal-settlement-table-panel.vue index f685981..44f8a4c 100644 --- a/src/views/settlement/components/formal-settlement-table-panel.vue +++ b/src/views/settlement/components/formal-settlement-table-panel.vue @@ -49,9 +49,9 @@ v-loading="loading" :data="rows" border - @selection-change="handleSelectionChange" + highlight-current-row + @current-change="handleCurrentChange" > - - + - {{ formatMoney(row.adjustmentAmountNoTax) }} ;调整后结算金额:{{ formatMoney(form.adjustedSettlementAmount) }}
+ + + + + + + + + + + + + + + + + + + + +
+ +
+