From 6a6d10dc7939dfd3148c01b92568891a8b29aa71 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sat, 5 Sep 2026 23:26:37 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=B8=9A=E5=8A=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/settlement/formalSettlement.js | 2 + .../components/waybill-import-dialog.vue | 12 +- .../components/waybill-manage-page.vue | 28 +- .../components/formal-settlement-editor.vue | 167 +++++++- .../components/pre-settlement-editor.vue | 362 ++++++++++++------ src/views/settlement/formal-settlement.vue | 4 +- 6 files changed, 437 insertions(+), 138 deletions(-) diff --git a/src/api/settlement/formalSettlement.js b/src/api/settlement/formalSettlement.js index 78aa78b..9c7992a 100644 --- a/src/api/settlement/formalSettlement.js +++ b/src/api/settlement/formalSettlement.js @@ -44,3 +44,5 @@ export const applyPayment = data => request({ url: `${baseUrl}/apply-payment`, method: 'post', data }); export const applyPayments = data => request({ url: `${baseUrl}/apply-payments`, method: 'post', data }); +export const claimInvoices = data => + request({ url: `${baseUrl}/claim-invoices`, method: 'post', data }); diff --git a/src/views/business/components/waybill-import-dialog.vue b/src/views/business/components/waybill-import-dialog.vue index c85786b..e2034a5 100644 --- a/src/views/business/components/waybill-import-dialog.vue +++ b/src/views/business/components/waybill-import-dialog.vue @@ -571,17 +571,17 @@ const buildCarrierContractOptions = contractRows => { item => String(item.contractCategory || '').trim() === '承运商合同' ); const nameCounts = validContracts.reduce((result, item) => { - const name = String(item.partyB || '').trim(); + const name = String(item.partyA || item.partyAName || '').trim(); if (name) result[name] = (result[name] || 0) + 1; return result; }, {}); return validContracts .map(contract => { - const carrierName = String(contract.partyB || '').trim(); + const carrierName = String(contract.partyA || contract.partyAName || '').trim(); if (!contract.id || !carrierName) return null; const identifier = contract.contractName || contract.contractNo || contract.id; return { - id: contract.partyBId || contract.customerId || '', + id: contract.partyAId || contract.partyAUserId || contract.customerId || '', carrierContractId: contract.id, carrierName, label: nameCounts[carrierName] > 1 ? `${carrierName}(${identifier})` : carrierName, @@ -607,7 +607,11 @@ const syncCarrierOptions = () => { carrierOptions.value = []; return; } - const carrierId = customerContract?.partyBId || customerContract?.customerId || ''; + const carrierId = + customerContract?.partyBId || + customerContract?.partyBUserId || + customerContract?.customerId || + ''; carrierOptions.value = [{ id: carrierId, carrierName, label: carrierName, value: carrierName }]; form.carrierId = carrierId; form.carrierIds = carrierId ? [carrierId] : []; diff --git a/src/views/business/components/waybill-manage-page.vue b/src/views/business/components/waybill-manage-page.vue index b04dcae..71faec1 100644 --- a/src/views/business/components/waybill-manage-page.vue +++ b/src/views/business/components/waybill-manage-page.vue @@ -5413,8 +5413,8 @@ export default { } }, fillSelfOperatedCarrierFromContract(contract = {}) { - if (this.form.carrierType !== '自运' || this.waybillHasCarrierContracts === true) return; - const carrierName = String(contract.partyB || '').trim(); + if (this.form.carrierType !== '自运') return; + const carrierName = String(contract.partyB || contract.partyBName || '').trim(); if (!carrierName) return; const exists = this.taskCarrierOptions.some( item => String(item.value || item.carrierName || '') === carrierName @@ -5462,17 +5462,18 @@ export default { }, getWaybillCarrierContractOptions(contracts = []) { const carrierNameCounts = contracts.reduce((counts, contract) => { - const carrierName = String(contract.partyB || '').trim(); + const carrierName = String(contract.partyA || contract.partyAName || '').trim(); if (carrierName) counts[carrierName] = (counts[carrierName] || 0) + 1; return counts; }, {}); return contracts .map(contract => { - const carrierName = String(contract.partyB || '').trim(); + const carrierName = String(contract.partyA || contract.partyAName || '').trim(); if (!carrierName || !contract.id) return null; const contractIdentifier = contract.contractName || contract.contractNo || contract.id; return { id: contract.id, + carrierId: contract.partyAId || contract.partyAUserId || contract.customerId || '', value: contract.id, label: carrierNameCounts[carrierName] > 1 @@ -5581,28 +5582,15 @@ export default { return Promise.all([contractPromise, detailPromise]) .then(([carrierContractOptions, options]) => { if (requestId !== this.taskCarrierRequestId) return this.taskCarrierOptions; - if (carrierTypeAtRequest === '自运' && carrierContractOptions?.length) { - const selfCarrierOptions = carrierContractOptions.map(item => ({ - ...item, - value: item.carrierName, - })); - this.taskCarrierOptions = selfCarrierOptions; - const hasCurrentCarrier = selfCarrierOptions.some( - item => String(item.value || '') === String(this.form.carrierName || '') - ); - if (!hasCurrentCarrier) { - this.form.carrierName = selfCarrierOptions[0].carrierName; - this.form.carrierId = ''; - } - return selfCarrierOptions; - } - this.taskCarrierOptions = options || []; if (carrierTypeAtRequest === '自运') { + this.taskCarrierOptions = []; const customerContract = this.contractOptions.find( item => String(item.id) === String(this.form.contractId) ); this.fillSelfOperatedCarrierFromContract(customerContract); + return this.taskCarrierOptions; } + this.taskCarrierOptions = options || []; return this.taskCarrierOptions; }) .finally(() => { diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 21e8427..dd3e17c 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -337,11 +337,15 @@ clearable maxlength="32" placeholder="请输入发票号码" - @keyup.enter="claimInvoices" + @keyup.enter="openInvoiceClaimDialog" /> - + 认领查询 @@ -407,6 +411,67 @@ + + + + + + + + + + + {{ formatMoney(row.invoiceAmount) }} + + + {{ formatMoney(row.matchedAmount) }} + + + + + + + 取消 + 确认认领 + + + {{ displayValue(row.changeType) }} - {{ displayValue(row.lineNo) }} + {{ changeRecordLineNo(row) }} {{ displayValue(row.operationType) }} @@ -843,6 +908,7 @@ import { getFeeOptions, getNextNo, getReceiptClaims, + claimInvoices as claimInvoicesApi, save, } from '@/api/settlement/formalSettlement'; import { @@ -898,6 +964,14 @@ export default { invoices: [], invoiceClaimNo: '', invoiceClaimLoading: false, + invoiceClaimDialog: { + visible: false, + loading: false, + confirming: false, + rows: [], + selected: [], + page: { current: 1, size: 10, total: 0 }, + }, attachmentUploadFiles: [], attachmentTypeOptions: [], attachmentFileTypes: [ @@ -1034,6 +1108,11 @@ export default { }); return Array.from(names); }, + invoiceClaimPagedRows() { + const { current, size } = this.invoiceClaimDialog.page; + const start = (current - 1) * size; + return this.invoiceClaimDialog.rows.slice(start, start + size); + }, }, watch: { modelValue: { @@ -1103,6 +1182,9 @@ export default { this.selectedAttachmentRows = []; this.invoices = []; this.invoiceClaimNo = ''; + this.invoiceClaimDialog.visible = false; + this.invoiceClaimDialog.rows = []; + this.invoiceClaimDialog.selected = []; this.attachmentUploadFiles = []; this.billingRuleDialog = { visible: false, @@ -1864,6 +1946,78 @@ export default { this.invoiceClaimLoading = false; } }, + openInvoiceClaimDialog() { + const stamp = Date.now(); + this.invoiceClaimDialog.loading = true; + this.invoiceClaimDialog.rows = Array.from({ length: 80 }, (_, index) => { + const amount = Math.floor(1000 + Math.random() * 49000); + return { + invoiceNo: `MOCK${stamp}${String(index + 1).padStart(3, '0')}`.slice(0, 32), + invoiceDate: this.$dayjs() + .subtract(Math.floor(Math.random() * 90), 'day') + .format('YYYY-MM-DD'), + invoiceType: index % 2 ? '增值税普通发票' : '增值税专用发票', + issuerName: this.form.payerName || 'Mock开票单位', + receiverName: this.form.payeeName || 'Mock受票单位', + invoiceAmount: amount, + availableInvoiceAmount: amount, + matchedAmount: amount, + taxRate: [3, 6, 9, 13][index % 4], + attachmentJson: '', + mockData: true, + }; + }); + this.invoiceClaimDialog.selected = []; + this.invoiceClaimDialog.page.current = 1; + this.invoiceClaimDialog.page.total = 80; + this.invoiceClaimDialog.visible = true; + this.invoiceClaimDialog.loading = false; + }, + async confirmInvoiceClaims() { + if (!this.invoiceClaimDialog.selected.length) { + this.$message.warning('请至少选择一条发票'); + return; + } + this.invoiceClaimDialog.confirming = true; + try { + const existingNumbers = new Set(this.invoices.map(item => String(item.invoiceNo))); + let remaining = Math.max( + Number(this.form.settlementAmount || 0) - + this.invoices.reduce((total, item) => total + Number(item.matchedAmount || 0), 0), + 0 + ); + const invoices = this.invoiceClaimDialog.selected + .filter(row => !existingNumbers.has(String(row.invoiceNo))) + .map(row => { + const sourceAmount = Number(row.matchedAmount || 0); + const matchedAmount = + Number(this.form.settlementAmount || 0) > 0 + ? Math.min(sourceAmount, remaining) + : sourceAmount; + if (Number(this.form.settlementAmount || 0) > 0) { + remaining = Math.max(0, remaining - matchedAmount); + } + return { ...row, matchedAmount }; + }) + .filter(row => row.matchedAmount > 0); + if (!invoices.length) { + this.$message.warning('当前正式结算单没有可认领的剩余金额'); + return; + } + if (!this.form.id) { + this.invoices = [...this.invoices, ...invoices]; + this.invoiceClaimDialog.visible = false; + this.$message.success(`已选择${invoices.length}条发票,保存正式结算单后生效`); + return; + } + await claimInvoicesApi({ formalSettlementId: this.form.id, invoices }); + await this.loadFormalDetail(this.form.id); + this.invoiceClaimDialog.visible = false; + this.$message.success('发票认领成功,已同步正式结算单及关联付款单据'); + } finally { + this.invoiceClaimDialog.confirming = false; + } + }, validateInvoices() { const invoiceNumbers = new Set(); let matchedTotal = 0; @@ -2000,7 +2154,8 @@ export default { return this.$message.warning('请完善手工费用的费用类型和费用项'); } if (!this.validateInvoices()) return; - if (!this.validateAttachments()) return; + // 附件类型仅做提示,不阻断正式结算单新增/保存提交。 + this.validateAttachments(); this.saving = true; try { await save(this.buildSavePayload()); @@ -2014,6 +2169,10 @@ export default { displayValue(value) { return value === null || value === undefined || value === '' ? '-' : value; }, + changeRecordLineNo(row) { + const lineNo = row?.lineNo; + return lineNo === -1 || String(lineNo) === '-1' ? '' : this.displayValue(lineNo); + }, paymentMethodName(value) { const labels = { bank_transfer: '银行转账', diff --git a/src/views/settlement/components/pre-settlement-editor.vue b/src/views/settlement/components/pre-settlement-editor.vue index 8d1b40a..d624e8a 100644 --- a/src/views/settlement/components/pre-settlement-editor.vue +++ b/src/views/settlement/components/pre-settlement-editor.vue @@ -82,12 +82,7 @@ 添加费用 - + {{ formatDetailMileage(row[column.prop]) }} - + {{ transportTypeName(row[column.prop]) }} {{ displayValue(row[column.prop]) }} @@ -374,6 +369,12 @@ {{ advanceStatusName(row.billStatus) }} + + {{ advanceCreateUserName(row) }} + + + {{ advanceCreateTime(row) }} + {{ displayValue(row[column.prop]) }} @@ -381,7 +382,7 @@ - + - - - - - - - - {{ displayValue(billingRuleName(row)) }} - - - - - - - - - {{ formatQuantity(row) }} - + + + + + + + + + {{ displayValue(billingRuleName(row)) }} + + + + + + + + + {{ formatQuantity(row) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + 已生效 + + + + 查看详情 + + + + + + 取消 + + + 变更日期:{{ adjustChangeRecord.changeTime || '-' }} + 经办人:{{ adjustChangeRecord.operatorName || '-' }} + 变更类型:{{ adjustChangeRecord.changeType || '-' }} + 状态:已生效 + + + + + + + + + 关闭 + + + column.prop !== 'settlementAmountNoTax'); }, detailFeeItemNames() { @@ -1132,6 +1196,13 @@ export default { }); return Array.from(names); }, + adjustChangeRecords() { + return this.changeRecords.filter( + row => + row.changeType === '结算明细项' && + String(row.lineNo ?? '') === String(this.adjustDialog.detailLineNo ?? '') + ); + }, }, watch: { modelValue: { @@ -1229,6 +1300,12 @@ export default { this.attachments = []; this.selectedAttachmentRows = []; this.adjustRows = []; + this.adjustDialog.activeTab = 'adjust'; + this.adjustDialog.detailId = ''; + this.adjustDialog.detailLineNo = ''; + this.adjustChangeRecordVisible = false; + this.adjustChangeRecord = null; + this.adjustChangeRecordDetailRows = []; this.billingRuleDialog.visible = false; this.billingRuleDialog.rule = null; this.contractOptions = []; @@ -1247,7 +1324,11 @@ export default { this.form = { ...emptyPreSettlementForm(), ...detail }; this.summaryFees = (detail.summaryFees || []).map(row => ({ ...row })); this.details = (detail.details || []).map(row => ({ ...row })); - this.advances = detail.advances || []; + this.advances = (detail.advances || []).map(row => ({ + ...row, + createUserName: row.createUserName || detail.createUserName, + createTime: row.createTime || detail.createTime, + })); this.changeRecords = detail.changeRecords || []; this.attachments = this.parseAttachments(detail.attachmentsJson); this.selectedAttachmentRows = []; @@ -1360,7 +1441,11 @@ export default { }, async loadTransportTypeOptions() { const { data } = await getDictionary({ code: 'transport_type' }); - this.transportTypeOptions = data?.data || []; + const records = Array.isArray(data?.data) ? data.data : data?.data?.records || []; + this.transportTypeOptions = records.map(item => ({ + dictKey: item.dictKey ?? item.value, + dictValue: item.dictValue ?? item.label ?? item.name, + })); }, handleContractChange(id) { const contract = this.contractOptions.find(item => String(item.id) === String(id)); @@ -1720,7 +1805,9 @@ export default { this.adjustDialog.visible = true; this.adjustDialog.loading = true; this.adjustDialog.readonly = readonly; + this.adjustDialog.activeTab = 'adjust'; this.adjustDialog.detailId = detailRow.id; + this.adjustDialog.detailLineNo = detailRow.lineNo; this.adjustDialog.reason = ''; try { const { data } = await getDetailFees(detailRow.id); @@ -1733,6 +1820,42 @@ export default { this.adjustDialog.loading = false; } }, + parseChangeRecordData(value) { + if (!value) return {}; + if (typeof value === 'object') return value; + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}; + } catch (error) { + return {}; + } + }, + formatAdjustChangeValue(value) { + if (value === undefined || value === null || value === '') return '空'; + if (typeof value === 'object') return JSON.stringify(value); + return String(value); + }, + buildAdjustChangeRecordRows(row = {}) { + const before = this.parseChangeRecordData(row.beforeData); + const after = this.parseChangeRecordData(row.afterData); + const fields = [...new Set([...Object.keys(before), ...Object.keys(after)])]; + const rows = fields + .filter(field => JSON.stringify(before[field]) !== JSON.stringify(after[field])) + .map(field => ({ + field, + before: this.formatAdjustChangeValue(before[field]), + after: this.formatAdjustChangeValue(after[field]), + })); + if (row.changeContent) + rows.unshift({ field: '变更内容', before: '空', after: row.changeContent }); + if (row.changeReason) rows.push({ field: '变更原因', before: '空', after: row.changeReason }); + return rows; + }, + openAdjustChangeRecord(row) { + this.adjustChangeRecord = row; + this.adjustChangeRecordDetailRows = this.buildAdjustChangeRecordRows(row); + this.adjustChangeRecordVisible = true; + }, billingRuleName(row) { return this.normalizeBillingRule(row).name; }, @@ -2035,6 +2158,14 @@ export default { }[status] || this.displayValue(status) ); }, + advanceCreateUserName(row) { + return this.displayValue( + row?.createUserName || row?.advanceCreateUserName || row?.applyUserName + ); + }, + advanceCreateTime(row) { + return this.displayValue(row?.createTime || row?.advanceCreateTime || row?.applyTime); + }, changeRecordLineNo(row) { if (row.changeType !== '结算明细项') return ''; if (row.lineNo !== undefined && row.lineNo !== null && row.lineNo !== '') return row.lineNo; @@ -2225,6 +2356,23 @@ export default { } } +.pre-settlement-change-record-detail-meta { + display: flex; + flex-wrap: wrap; + gap: 8px 32px; + margin-bottom: 16px; + color: #606266; +} + +:deep(.pre-settlement-change-record-detail-dialog .el-dialog__body) { + padding-top: 12px; +} + +:deep(.pre-settlement-change-record-detail-dialog .el-table .cell) { + white-space: pre-wrap; + word-break: break-all; +} + :deep(.pre-settlement-editor .el-dialog__body) { padding: 12px 16px; } diff --git a/src/views/settlement/formal-settlement.vue b/src/views/settlement/formal-settlement.vue index e37461c..14366d2 100644 --- a/src/views/settlement/formal-settlement.vue +++ b/src/views/settlement/formal-settlement.vue @@ -577,9 +577,7 @@ export default { return `${Number(value || 0).toFixed(2)} ${currency || 'RMB'}`; }, paymentRemaining(row) { - const remaining = - row?.remainingPayableAmount ?? - Number(row?.settlementAmount || 0) - Number(row?.appliedPaymentAmount || 0); + const remaining = Number(row?.settlementAmount || 0) - Number(row?.paidAmount || 0); return Math.max(0, Number(remaining || 0)); }, },