✨ 结算单业务模式运输方式与编辑体验优化

预结算/收付款结算支持业务模式与运输方式展示导出;完善结算调整、对账导入与变更记录;组织合同初始流水号未填不再显示-1。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-21 16:42:02 +08:00
parent 6a21b4ac8d
commit e7c76c803f
11 changed files with 310 additions and 93 deletions
@@ -20,7 +20,7 @@
<el-form-item :label="field.label" :prop="field.prop">
<el-input
v-if="field.type === 'contract'"
:model-value="formatContractOptionLabel(form)"
:model-value="form.contractName"
readonly
:disabled="!editable || details.length > 0"
placeholder="请选择合同"
@@ -53,6 +53,23 @@
:disabled="!editable"
@change="recalculateLocalAmount"
/>
<el-select
v-else-if="field.type === 'select' && editable"
v-model="form[field.prop]"
filterable
clearable
placeholder="请选择"
>
<el-option
v-for="item in formSelectOptions(field)"
:key="item.dictKey"
:label="item.dictValue"
:value="item.dictKey"
/>
</el-select>
<span v-else-if="field.type === 'select'" class="form-readonly">
{{ formSelectLabel(field, form[field.prop]) }}
</span>
<el-input
v-else-if="field.type === 'textarea'"
v-model="form[field.prop]"
@@ -493,7 +510,7 @@
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<el-link type="primary" @click="openAdjustChangeRecord(row)">查看</el-link>
<el-link type="primary" @click="openAdjustChangeRecord(row)">详情</el-link>
</template>
</el-table-column>
</el-table>
@@ -999,8 +1016,8 @@ import {
settlementDetailColumns,
settlementSummaryColumns,
} from '@/option/settlement/preSettlementTable';
import { formatContractOptionLabel } from '@/utils/contract';
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
import { isApprovalSettlementChange } from '@/utils/settlement-change-record';
import { normalizeCurrencyCode } from '@/utils/currency';
import { settlementStatusName } from '@/utils/settlementStatus';
import { downloadFileByUrl } from '@/utils/util';
@@ -1095,6 +1112,7 @@ export default {
},
feeOptions: [],
feeCategoryOptions: [],
businessModelOptions: [],
transportTypeOptions: [],
summaryFees: [],
details: [],
@@ -1144,6 +1162,8 @@ export default {
readonly: false,
detailId: '',
sourceDetailId: '',
// 未保存明细(id 仍等于来源应收应付明细)时先暂存,保存预结算单后再生效
draftMode: false,
detailLineNo: '',
reason: '',
},
@@ -1330,7 +1350,6 @@ export default {
methods: {
isTierUnitPrice,
isUnitPriceAdjustable,
formatContractOptionLabel,
hasPermission(code) {
return this.isAdmin || this.validData(this.permission?.[code], false);
},
@@ -1344,6 +1363,7 @@ export default {
if (!this.publicMode) {
await this.loadFeeOptions();
await this.loadFeeCategoryOptions();
await this.loadBusinessModelOptions();
await this.loadTransportTypeOptions();
await this.loadAttachmentTypeOptions();
}
@@ -1485,6 +1505,7 @@ export default {
this.detailFeeCache = {};
this.adjustDialog.detailId = '';
this.adjustDialog.sourceDetailId = '';
this.adjustDialog.draftMode = false;
this.adjustDialog.detailLineNo = '';
this.adjustChangeRecordVisible = false;
this.adjustChangeRecord = null;
@@ -1523,7 +1544,7 @@ export default {
createTime: row.paymentCreateTime || row.createTime,
}));
this.changeRecords = (detail.changeRecords || []).filter(
row => row.changeContent !== '预结算审批通过'
row => !isApprovalSettlementChange(row.changeContent)
);
this.attachments = this.parseAttachments(detail.attachmentsJson);
this.sortAttachments();
@@ -1647,6 +1668,14 @@ export default {
const { data } = await getDictionary({ code: 'fee_category' });
this.feeCategoryOptions = data?.data || [];
},
async loadBusinessModelOptions() {
const { data } = await getDictionary({ code: 'business_model' });
const records = Array.isArray(data?.data) ? data.data : data?.data?.records || [];
this.businessModelOptions = records.map(item => ({
dictKey: item.dictKey ?? item.value,
dictValue: item.dictValue ?? item.label ?? item.name,
}));
},
async loadTransportTypeOptions() {
const { data } = await getDictionary({ code: 'transport_type' });
const records = Array.isArray(data?.data) ? data.data : data?.data?.records || [];
@@ -1655,6 +1684,21 @@ export default {
dictValue: item.dictValue ?? item.label ?? item.name,
}));
},
formSelectOptions(field) {
const key = field?.optionsKey;
const options = key ? this[key] : [];
return Array.isArray(options) ? options : [];
},
formSelectLabel(field, value) {
if (value === undefined || value === null || value === '') return '-';
const normalized = String(value).trim();
const option = this.formSelectOptions(field).find(
item =>
String(item.dictKey ?? '').trim() === normalized ||
String(item.dictValue ?? '').trim() === normalized
);
return option?.dictValue || normalized;
},
handleContractChange(id) {
const contract = this.contractOptions.find(item => String(item.id) === String(id));
if (!contract) {
@@ -1741,6 +1785,8 @@ export default {
return {
id: this.form.id || undefined,
contractId: this.form.contractId,
businessModel: this.form.businessModel || undefined,
transportType: this.form.transportType || undefined,
exchangeRateDate: this.form.exchangeRateDate || undefined,
exchangeRate: this.form.exchangeRate,
attachmentsJson: JSON.stringify(this.attachments || []),
@@ -2251,17 +2297,21 @@ export default {
},
async openAdjustDialog(row, readonly) {
const detailRow = row;
const sourceDetailId = detailRow.sourceDetailId || '';
// 未保存明细 id 仍等于来源应收应付明细 id,调整结果先暂存
const draftMode =
!this.form.id ||
!detailRow.id ||
String(detailRow.id) === String(detailRow.sourceDetailId);
this.adjustDialog.visible = true;
this.adjustDialog.loading = true;
this.adjustDialog.readonly = readonly;
this.adjustDialog.draftMode = draftMode;
this.adjustDialog.detailId = detailRow.id;
this.adjustDialog.detailLineNo = detailRow.lineNo;
this.adjustDialog.sourceDetailId =
detailRow.id && String(detailRow.id) === String(detailRow.sourceDetailId)
? detailRow.sourceDetailId
: '';
const sourceDetailId = String(this.adjustDialog.sourceDetailId || '');
const pending = sourceDetailId ? this.pendingAdjustments[sourceDetailId] : null;
this.adjustDialog.sourceDetailId = sourceDetailId;
const pendingKey = String(sourceDetailId || '');
const pending = pendingKey ? this.pendingAdjustments[pendingKey] : null;
// 底部备注回显结算明细备注;暂存过的调整优先取暂存备注
this.adjustDialog.reason =
pending?.reason !== undefined && pending?.reason !== null
@@ -2278,11 +2328,12 @@ export default {
this.adjustRows = cachedFees.map(item => this.normalizeAdjustRow(item));
return;
}
if (sourceDetailId) {
if (!this.sourceFeeSnapshots[sourceDetailId]) {
this.sourceFeeSnapshots[sourceDetailId] = await this.loadSourceFeeRows(sourceDetailId);
if (draftMode && sourceDetailId) {
if (!this.sourceFeeSnapshots[String(sourceDetailId)]) {
this.sourceFeeSnapshots[String(sourceDetailId)] =
await this.loadSourceFeeRows(sourceDetailId);
}
this.adjustRows = this.cloneAdjustRows(this.sourceFeeSnapshots[sourceDetailId]);
this.adjustRows = this.cloneAdjustRows(this.sourceFeeSnapshots[String(sourceDetailId)]);
return;
}
const cachedFees = this.detailFeeCache[detailRow.id];
@@ -2554,7 +2605,7 @@ export default {
if (
['transportQuantity', 'mileage', 'unitPrice'].includes(changedField) &&
this.adjustDialog.sourceDetailId &&
row.id &&
(row.sourceFeeId || row.id) &&
(changedField !== 'unitPrice' || isUnitPriceAdjustable(row))
) {
this.scheduleSourceAdjustCalculation(row, changedField);
@@ -2570,7 +2621,8 @@ export default {
try {
const response = await calculateAdjustedFee({
detailId: this.adjustDialog.sourceDetailId,
feeId: row.id,
// 已保存预结算费用行需用源应收应付费用ID试算
feeId: row.sourceFeeId || row.id,
transportQuantity: Number(row.transportQuantity || 0),
mileage: Number(row.mileage || 0),
unitPrice: row.unitPrice === null || row.unitPrice === '' ? null : Number(row.unitPrice),
@@ -2596,6 +2648,7 @@ export default {
row.settlementAmountTax = Number(data.afterAmount || 0);
} catch (error) {
row.calculateError = error.message || '费用试算失败';
this.$message.warning(row.calculateError);
} finally {
if (version === row.adjustCalculateVersion) row.calculating = false;
}
@@ -2611,7 +2664,7 @@ export default {
}
this.adjustDialog.saving = true;
try {
if (this.adjustDialog.sourceDetailId) {
if (this.adjustDialog.draftMode && this.adjustDialog.sourceDetailId) {
this.pendingAdjustments[this.adjustDialog.sourceDetailId] = {
reason: this.adjustDialog.reason,
rows: this.adjustRows.map(row => ({ ...row, feeItems: { ...row.feeItems } })),