From a75ac1cfa44b8b4e8f944dca2e17e9405ec8e52b Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Mon, 31 Aug 2026 10:38:21 +0800 Subject: [PATCH] fix --- src/api/business/waybill-manage.js | 6 + src/api/settlement/settlementAdjustment.js | 2 + src/option/settlement/formalSettlementForm.js | 6 +- .../settlement/formalSettlementSearch.js | 16 +- .../settlement/settlementAdjustmentForm.js | 6 +- .../settlement/settlementAdjustmentTable.js | 2 - .../components/business-crud-page.vue | 137 ++++++++ .../components/formal-settlement-editor.vue | 207 ++++++++++-- .../formal-settlement-table-panel.vue | 24 +- .../components/pre-settlement-editor.vue | 50 ++- .../settlement-adjustment-editor.vue | 243 ++++++++----- src/views/settlement/formal-settlement.vue | 8 +- .../settlement/receivable-payable-detail.vue | 318 ++++++++++++++++-- 13 files changed, 864 insertions(+), 161 deletions(-) diff --git a/src/api/business/waybill-manage.js b/src/api/business/waybill-manage.js index 220dd24..6b1645c 100644 --- a/src/api/business/waybill-manage.js +++ b/src/api/business/waybill-manage.js @@ -19,6 +19,12 @@ export const changeRoute = data => method: 'post', data, }); +export const maintainMileage = data => + request({ + url: `${baseUrl}/maintain-mileage`, + method: 'post', + data, + }); export const roadLoading = ids => request({ url: `${baseUrl}/road-loading`, diff --git a/src/api/settlement/settlementAdjustment.js b/src/api/settlement/settlementAdjustment.js index 3316e4f..d809bf6 100644 --- a/src/api/settlement/settlementAdjustment.js +++ b/src/api/settlement/settlementAdjustment.js @@ -8,6 +8,8 @@ export const getFormalSettlements = keyword => request({ url: `${baseUrl}/candidate-formal-settlements`, method: 'get', params: { keyword } }); export const getFormalDetails = formalSettlementId => request({ url: `${baseUrl}/formal-details`, method: 'get', params: { formalSettlementId } }); +export const getFeeOptions = () => + request({ url: `${baseUrl}/fee-options`, method: 'get' }); export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const submit = id => request({ url: `${baseUrl}/submit`, method: 'post', data: { id } }); diff --git a/src/option/settlement/formalSettlementForm.js b/src/option/settlement/formalSettlementForm.js index 99674a2..4b51186 100644 --- a/src/option/settlement/formalSettlementForm.js +++ b/src/option/settlement/formalSettlementForm.js @@ -11,8 +11,8 @@ export const formalSettlementFormFields = [ { label: '汇率日期', prop: 'exchangeRateDate', type: 'date', required: true }, { label: '结算汇率', prop: 'exchangeRate', type: 'number', required: true }, { label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true }, - { label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', readonly: true, money: true }, - { label: '已收/已付合计', prop: 'paidAmount', readonly: true, money: true }, + { label: '创建人', prop: 'createUserName', readonly: true }, + { label: '创建时间', prop: 'createTime', readonly: true }, ]; export const createFormalSettlementForm = () => ({ @@ -27,6 +27,8 @@ export const createFormalSettlementForm = () => ({ sourceDetailIds: [], exchangeRateDate: '', exchangeRate: 1, + createUserName: '', + createTime: '', attachmentsJson: '[]', remark: '', }); diff --git a/src/option/settlement/formalSettlementSearch.js b/src/option/settlement/formalSettlementSearch.js index c5766f5..d516fa6 100644 --- a/src/option/settlement/formalSettlementSearch.js +++ b/src/option/settlement/formalSettlementSearch.js @@ -7,11 +7,21 @@ export const approvalStatusOptions = [ ]; export const invoiceStatusOptions = [ - { label: '未收/开票', value: 'unreceived' }, - { label: '部分收/开票', value: 'partial' }, - { label: '已收/开票', value: 'completed' }, + { label: '未开/收票', value: 'unreceived' }, + { label: '部分开/收票', value: 'partial' }, + { label: '已开/收票', value: 'completed' }, ]; +export const invoiceStatusName = (value, settlementType) => { + const payable = settlementType === 'payable'; + const labels = { + unreceived: payable ? '未收票' : '未开票', + partial: payable ? '部分收票' : '部分开票', + completed: payable ? '已收票' : '已开票', + }; + return labels[value] || value || '-'; +}; + export const paymentStatusOptions = [ { label: '未收/付款', value: 'unpaid' }, { label: '部分收/付款', value: 'partial' }, diff --git a/src/option/settlement/settlementAdjustmentForm.js b/src/option/settlement/settlementAdjustmentForm.js index 30cd4bf..157358b 100644 --- a/src/option/settlement/settlementAdjustmentForm.js +++ b/src/option/settlement/settlementAdjustmentForm.js @@ -1,13 +1,15 @@ export const settlementAdjustmentFormFields = [ { label: '单据号', prop: 'adjustmentNo' }, { label: '关联结算单号', prop: 'formalSettlementId' }, - { label: '客户/客商名称', prop: 'customerName' }, + { label: '客户', prop: 'customerName' }, { label: '结算单类型', prop: 'settlementTypeName' }, { label: '关联合同名称', prop: 'contractName' }, { label: '关联项目', prop: 'projectName' }, { label: '原结算金额', prop: 'originalSettlementAmount', money: true }, { label: '调整金额', prop: 'adjustmentAmount', money: true }, { label: '调整后结算金额', prop: 'adjustedSettlementAmount', money: true }, + { label: '创建日期', prop: 'createTime' }, + { label: '创建人', prop: 'createUserName' }, ]; export const createSettlementAdjustmentForm = () => ({ @@ -24,6 +26,8 @@ export const createSettlementAdjustmentForm = () => ({ adjustmentAmount: 0, originalSettlementAmount: 0, adjustedSettlementAmount: 0, + createTime: '', + createUserName: '', attachmentsJson: '[]', remark: '', }); diff --git a/src/option/settlement/settlementAdjustmentTable.js b/src/option/settlement/settlementAdjustmentTable.js index d411d5b..aaaa9fc 100644 --- a/src/option/settlement/settlementAdjustmentTable.js +++ b/src/option/settlement/settlementAdjustmentTable.js @@ -8,8 +8,6 @@ export const settlementAdjustmentTableColumns = [ { label: '合同编号', prop: 'contractNo', minWidth: 150 }, { label: '合同名称', prop: 'contractName', minWidth: 170 }, { label: '调整金额', prop: 'adjustmentAmount', minWidth: 130, money: true }, - { label: '原结算金额', prop: 'originalSettlementAmount', minWidth: 140, money: true }, - { label: '调整后结算金额', prop: 'adjustedSettlementAmount', minWidth: 160, money: true }, { label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true }, { label: '当前节点', prop: 'currentNode', minWidth: 120 }, { label: '当前处理人', prop: 'currentProcessor', minWidth: 130 }, diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue index a4d84c1..c640079 100644 --- a/src/views/business/components/business-crud-page.vue +++ b/src/views/business/components/business-crud-page.vue @@ -2143,6 +2143,9 @@ {{ completeActionLabel }} + + 维护里程 + + + + + + + + + + + + + + + + this.$refs.mileageFormRef?.clearValidate()); + }, + handleMaintainMileageInput(value) { + this.mileageForm.mileage = String(value || '') + .replace(/\D/g, '') + .slice(0, 10); + }, + async submitMileageMaintenance() { + const valid = await this.$refs.mileageFormRef?.validate().catch(() => false); + if (!valid || this.mileageDialog.submitting) return; + this.mileageDialog.submitting = true; + try { + await this.api.maintainMileage({ + id: this.mileageForm.id, + mileage: Number(this.mileageForm.mileage), + mileageRemark: String(this.mileageForm.mileageRemark || '').trim(), + }); + this.$message.success('里程维护成功'); + this.mileageDialog.visible = false; + this.onLoad(this.page, this.query); + } finally { + this.mileageDialog.submitting = false; + } + }, + resetMileageDialog() { + this.mileageDialog.row = null; + this.mileageForm = { id: '', mileage: '', mileageRemark: '' }; + this.$refs.mileageFormRef?.clearValidate(); + }, handleAction(action, row) { const actionName = { enable: '启用', @@ -15592,6 +15717,18 @@ export default { top: 92px !important; left: 0 !important; } +:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form) { + padding: 4px 20px 0; +} + +:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form .el-form-item) { + margin-bottom: 16px; +} + +:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form .el-input), +:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form .el-textarea) { + width: 100%; +} .el-input__icon { color: #1e90ff !important; } diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 7491a0e..776ae94 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -60,7 +60,7 @@ v-else-if="field.type === 'number' && editable" v-model="form[field.prop]" :min="0.000001" - :precision="6" + :precision="2" :controls="false" /> {{ formatMoney(form[field.prop]) }} @@ -153,8 +153,7 @@ {{ formatMoney(row[column.prop]) }} @@ -224,7 +223,10 @@ - +
+ + 批量下载 + +
+ + @@ -626,6 +630,39 @@ > + + + + {{ formatMoney(detailAmountAdjust.originalAmount) }} + + + + + + {{ formatMoney(detailAmountAdjustSettlementAmount) }} + + + + + @@ -671,14 +708,6 @@ :precision="2" :controls="false" /> - @@ -723,6 +752,7 @@ import { summaryColumns, } from '@/option/settlement/formalSettlementTable'; import { getDictionary } from '@/api/system/dictbiz'; +import { h } from 'vue'; import { mapGetters } from 'vuex'; import { downloadFileByUrl } from '@/utils/util'; import * as XLSX from 'xlsx'; @@ -752,6 +782,7 @@ export default { adjustments: [], changeRecords: [], attachments: [], + selectedAttachmentRows: [], invoices: [], invoiceClaimNo: '', invoiceClaimLoading: false, @@ -815,6 +846,12 @@ export default { reason: '', rows: [], }, + detailAmountAdjust: { + visible: false, + row: null, + originalAmount: 0, + adjustAmount: 0, + }, detailCollapsed: false, detailQuery: { documentNo: '', @@ -865,6 +902,14 @@ export default { summaryTotal() { return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0); }, + detailAmountAdjustSettlementAmount() { + return Number( + ( + Number(this.detailAmountAdjust.originalAmount || 0) + + Number(this.detailAmountAdjust.adjustAmount || 0) + ).toFixed(2) + ); + }, filteredDetails() { return this.details.filter(row => Object.entries(this.appliedDetailQuery).every(([field, keyword]) => { @@ -901,7 +946,14 @@ export default { }, methods: { async initialize() { + const newRecordAudit = this.recordId + ? null + : { + createUserName: this.userInfo?.realName || this.userInfo?.userName || '', + createTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'), + }; this.form = createFormalSettlementForm(); + if (newRecordAudit) Object.assign(this.form, newRecordAudit); this.sources = []; this.details = []; this.summaryFees = []; @@ -909,9 +961,16 @@ export default { this.adjustments = []; this.changeRecords = []; this.attachments = []; + this.selectedAttachmentRows = []; this.invoices = []; this.invoiceClaimNo = ''; this.attachmentUploadFiles = []; + this.detailAmountAdjust = { + visible: false, + row: null, + originalAmount: 0, + adjustAmount: 0, + }; this.detailCollapsed = false; this.resetDetailQuery(false); await Promise.all([ @@ -923,6 +982,7 @@ export default { if (!this.recordId) { this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD'); if (this.initialData) await this.applyInitialData(); + Object.assign(this.form, newRecordAudit); await this.refreshFormalSettlementNo(); return; } @@ -945,6 +1005,7 @@ export default { this.adjustments = data.adjustments || []; this.changeRecords = data.changeRecords || []; this.attachments = this.parseAttachments(data.attachmentsJson); + this.selectedAttachmentRows = []; this.invoices = data.invoices || []; this.sortAttachments(); this.contracts = this.allContracts.filter( @@ -1272,7 +1333,8 @@ export default { this.buildSummaryFeesFromDetails(); this.detailCandidate.visible = false; }, - removeDetail(row) { + async removeDetail(row) { + await this.$confirm('确认将该明细踢出正式结算单?', '提示', { type: 'warning' }); const index = this.details.indexOf(row); if (index < 0) return; this.details.splice(index, 1); @@ -1309,16 +1371,26 @@ export default { isSummaryMoney(prop) { return ['originalAmount', 'adjustAmount', 'settlementAmount'].includes(prop); }, + isNegativeAdjustedAmount(row, prop) { + return ( + Number(row.adjustAmount || 0) < 0 && ['adjustAmount', 'settlementAmount'].includes(prop) + ); + }, recalculateSummaryRow(row) { row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0); }, getSummarySums({ columns, data }) { const sumProps = ['originalAmount', 'adjustAmount', 'settlementAmount']; + const hasNegativeAdjustment = data.some(row => Number(row.adjustAmount || 0) < 0); return columns.map((column, index) => { if (index === 0) return '合计'; if (!sumProps.includes(column.property)) return ''; const total = data.reduce((sum, row) => sum + Number(row[column.property] || 0), 0); - return this.formatMoney(total); + const totalText = this.formatMoney(total); + if (column.property === 'settlementAmount' && hasNegativeAdjustment) { + return h('span', { style: { color: 'var(--el-color-danger)' } }, totalText); + } + return totalText; }); }, buildSummaryFeesFromDetails() { @@ -1406,6 +1478,20 @@ export default { ); }, async openAdjustDialog(row) { + if (!row.formalSettlementId) { + const originalAmount = Number( + row.originalAmount ?? + row.totalAmount ?? + Number(row.settlementAmountTax || 0) - Number(row.adjustAmount || 0) + ); + this.detailAmountAdjust = { + visible: true, + row, + originalAmount: Number(originalAmount.toFixed(2)), + adjustAmount: Number(row.adjustAmount || 0), + }; + return; + } this.adjust = { visible: true, loading: true, @@ -1425,6 +1511,20 @@ export default { this.adjust.loading = false; } }, + confirmDetailAmountAdjustment() { + const settlementAmount = this.detailAmountAdjustSettlementAmount; + if (!Number.isFinite(settlementAmount) || settlementAmount < 0) { + return this.$message.warning('调整后的结算金额不能小于0'); + } + const row = this.detailAmountAdjust.row; + if (!row) return; + row.originalAmount = this.detailAmountAdjust.originalAmount; + row.adjustAmount = Number(this.detailAmountAdjust.adjustAmount || 0); + row.settlementAmountTax = settlementAmount; + row.pendingDetailAdjustment = true; + this.buildSummaryFeesFromDetails(); + this.detailAmountAdjust.visible = false; + }, async saveAdjustment() { if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因'); this.adjust.saving = true; @@ -1586,6 +1686,17 @@ export default { settlementType: this.form.settlementType, sourcePreSettlementIds: this.form.sourcePreSettlementIds, sourceDetailIds: this.form.sourceDetailIds, + detailAdjustments: this.details + .filter(row => row.pendingDetailAdjustment) + .map(row => ({ + sourcePreSettlementDetailId: + row.sourcePreSettlementDetailId || + (row.sourcePreSettlementId ? row.id : undefined), + sourceDetailId: row.sourcePreSettlementId + ? undefined + : row.sourceDetailId || row.id, + adjustAmount: Number(row.adjustAmount || 0), + })), exchangeRateDate: this.form.exchangeRateDate, exchangeRate: this.form.exchangeRate, summaryFees: this.summaryFees @@ -1639,6 +1750,12 @@ export default { const quantity = Number(value); return Number.isFinite(quantity) ? quantity.toFixed(2) : '-'; }, + formatDetailTransportQuantity(value) { + return this.readonly && Number(value) === -1 ? '-' : this.formatQuantity(value); + }, + formatDetailMileage(value) { + return this.readonly && Number(value) === -1 ? '-' : this.displayValue(value); + }, async loadTransportTypeOptions() { const { data } = await getDictionary({ code: 'transport_type' }); this.transportTypeOptions = data?.data || []; @@ -1736,12 +1853,46 @@ export default { }); this.sortAttachments(); }, + handleAttachmentSelectionChange(rows) { + this.selectedAttachmentRows = rows || []; + }, removeAttachment(index) { this.attachments.splice(index, 1); + this.selectedAttachmentRows = this.selectedAttachmentRows.filter(file => + this.attachments.includes(file) + ); }, getAttachmentUrl(file = {}) { return file.url || file.link || file.src || file.domain || ''; }, + getAttachmentName(file = {}) { + return ( + file.originalName || file.name || this.getAttachmentFileName(this.getAttachmentUrl(file)) + ); + }, + downloadAttachment(file) { + const url = this.getAttachmentUrl(file); + if (!url) { + this.$message.warning('附件地址为空,无法下载'); + return; + } + downloadFileByUrl(url, this.getAttachmentName(file) || '附件'); + }, + downloadAttachments() { + const files = this.selectedAttachmentRows.length + ? this.selectedAttachmentRows + : this.attachments; + const downloadableFiles = files.filter(file => this.getAttachmentUrl(file)); + if (!downloadableFiles.length) { + this.$message.warning( + this.selectedAttachmentRows.length ? '所选附件无法下载' : '暂无可下载附件' + ); + return; + } + downloadableFiles.forEach((file, index) => { + window.setTimeout(() => this.downloadAttachment(file), index * 200); + }); + }, previewAttachment(file) { const url = this.getAttachmentUrl(file); if (!url) return this.$message.warning('附件地址为空,无法预览'); @@ -1844,7 +1995,7 @@ export default { } .formal-editor__detail-filter-actions { grid-column: 1 / -1; - justify-self: start; + justify-self: end; } .formal-editor__page-actions { display: flex; @@ -1892,12 +2043,20 @@ export default { .formal-editor__adjust-reason { margin-top: 16px; } +.formal-editor__amount-adjust :deep(.el-input-number) { + width: 100%; +} .formal-editor__attachment-missing { margin-left: 12px; color: var(--el-color-danger); font-size: 13px; font-weight: 400; } +.formal-editor__attachment-actions { + display: flex; + justify-content: flex-end; + margin-bottom: 12px; +} .formal-editor__attachment-upload { margin-top: 12px; } diff --git a/src/views/settlement/components/formal-settlement-table-panel.vue b/src/views/settlement/components/formal-settlement-table-panel.vue index 71adabf..63a5859 100644 --- a/src/views/settlement/components/formal-settlement-table-panel.vue +++ b/src/views/settlement/components/formal-settlement-table-panel.vue @@ -77,9 +77,10 @@ >{{ displayValue(row[column.prop]) }} {{ formatMoney(row[column.prop], row.currency) }} - {{ - invoiceName(row.invoiceStatus) - }} + {{ invoiceName(row.invoiceStatus, row.settlementType) }} {{ paymentName(row.paymentStatus) }} @@ -120,7 +121,7 @@