调整结算模块
This commit is contained in:
@@ -116,7 +116,7 @@
|
||||
<el-option
|
||||
v-for="item in feeOptions"
|
||||
:key="item.feeType"
|
||||
:label="item.feeTypeName || item.feeType"
|
||||
:label="feeCategoryName(item.feeType)"
|
||||
:value="item.feeType"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -773,17 +773,30 @@
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateAdjustRow(row, 'transportQuantity')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="里程(KM)" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.mileage" :min="0" :precision="2" :controls="false" />
|
||||
<el-input-number
|
||||
v-model="row.mileage"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateAdjustRow(row, 'mileage')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运输单价" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.unitPrice" :min="0" :precision="2" :controls="false" />
|
||||
<el-input-number
|
||||
v-model="row.unitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateAdjustRow(row, 'unitPrice')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -915,6 +928,7 @@ import {
|
||||
getDetail as getPreSettlementDetail,
|
||||
getDetailFees as getPreSettlementDetailFees,
|
||||
} from '@/api/settlement/preSettlement';
|
||||
import { calculateAdjustedFee, getFeeDetail } from '@/api/settlement/receivable-payable-detail';
|
||||
import {
|
||||
createFormalSettlementForm,
|
||||
formalSettlementFormFields,
|
||||
@@ -995,6 +1009,7 @@ export default {
|
||||
allContracts: [],
|
||||
projects: [],
|
||||
feeOptions: [],
|
||||
feeCategoryOptions: [],
|
||||
transportTypeOptions: [],
|
||||
fields: formalSettlementFormFields,
|
||||
sourceTableColumns: sourceColumns,
|
||||
@@ -1029,10 +1044,14 @@ export default {
|
||||
loading: false,
|
||||
saving: false,
|
||||
detailId: null,
|
||||
sourceDetailId: null,
|
||||
feeKey: '',
|
||||
reason: '',
|
||||
rows: [],
|
||||
targetRow: null,
|
||||
},
|
||||
detailFeeSnapshots: {},
|
||||
pendingAdjustments: {},
|
||||
billingRuleDialog: {
|
||||
visible: false,
|
||||
rule: null,
|
||||
@@ -1148,6 +1167,9 @@ export default {
|
||||
this.sources = data.sources || [];
|
||||
this.details = data.details || [];
|
||||
this.summaryFees = data.summaryFees || [];
|
||||
// 服务端数据已刷新,费用快照与暂存调整全部作废。
|
||||
this.detailFeeSnapshots = {};
|
||||
this.pendingAdjustments = {};
|
||||
this.paymentApplications = data.paymentApplications || [];
|
||||
if (this.readonly && this.form.settlementType === 'receivable') {
|
||||
this.receiptClaims = this.unwrapData(await getReceiptClaims(id)) || [];
|
||||
@@ -1174,6 +1196,8 @@ export default {
|
||||
this.sources = [];
|
||||
this.details = [];
|
||||
this.summaryFees = [];
|
||||
this.detailFeeSnapshots = {};
|
||||
this.pendingAdjustments = {};
|
||||
this.paymentApplications = [];
|
||||
this.receiptClaims = [];
|
||||
this.adjustments = [];
|
||||
@@ -1195,6 +1219,7 @@ export default {
|
||||
await Promise.all([
|
||||
this.loadAllContracts(),
|
||||
this.loadFeeOptions(),
|
||||
this.loadFeeCategoryOptions(),
|
||||
this.loadAttachmentTypeOptions(),
|
||||
this.loadTransportTypeOptions(),
|
||||
]);
|
||||
@@ -1284,7 +1309,7 @@ export default {
|
||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||
}));
|
||||
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
||||
this.buildSummaryFeesFromDetails();
|
||||
await this.refreshSummaryFees();
|
||||
},
|
||||
async applyInitialSources(rows) {
|
||||
const first = rows[0];
|
||||
@@ -1347,7 +1372,7 @@ export default {
|
||||
summaryMap.set(key, current);
|
||||
});
|
||||
this.summaryFees = [...summaryMap.values()];
|
||||
if (!this.summaryFees.length) this.buildSummaryFeesFromDetails();
|
||||
if (!this.summaryFees.length) await this.refreshSummaryFees();
|
||||
},
|
||||
async loadAllContracts() {
|
||||
const response = await getContractOptions('');
|
||||
@@ -1375,6 +1400,10 @@ export default {
|
||||
const response = await getFeeOptions();
|
||||
this.feeOptions = this.unwrapData(response) || [];
|
||||
},
|
||||
async loadFeeCategoryOptions() {
|
||||
const { data } = await getDictionary({ code: 'fee_category' });
|
||||
this.feeCategoryOptions = data?.data || [];
|
||||
},
|
||||
handleProjectChange(id) {
|
||||
const project = this.projects.find(item => String(item.id) === String(id));
|
||||
this.form.projectName = project?.name || '';
|
||||
@@ -1505,7 +1534,7 @@ export default {
|
||||
this.detailCandidate.page.current = 1;
|
||||
this.loadDetailCandidates();
|
||||
},
|
||||
confirmDetailCandidates() {
|
||||
async confirmDetailCandidates() {
|
||||
if (!this.detailCandidate.selected.length) {
|
||||
return this.$message.warning('请选择结算明细');
|
||||
}
|
||||
@@ -1526,7 +1555,12 @@ export default {
|
||||
...existing.values(),
|
||||
];
|
||||
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
||||
this.buildSummaryFeesFromDetails();
|
||||
this.detailCandidate.loading = true;
|
||||
try {
|
||||
await this.refreshSummaryFees();
|
||||
} finally {
|
||||
this.detailCandidate.loading = false;
|
||||
}
|
||||
this.detailCandidate.visible = false;
|
||||
},
|
||||
async removeDetail(row) {
|
||||
@@ -1537,7 +1571,7 @@ export default {
|
||||
this.form.sourceDetailIds = this.details
|
||||
.filter(item => !item.sourcePreSettlementId)
|
||||
.map(item => item.sourceDetailId);
|
||||
this.buildSummaryFeesFromDetails();
|
||||
await this.refreshSummaryFees();
|
||||
},
|
||||
addSummaryFee() {
|
||||
this.summaryFees.push({
|
||||
@@ -1559,7 +1593,13 @@ export default {
|
||||
manualFeeItems(feeType) {
|
||||
return this.feeOptions.find(item => item.feeType === feeType)?.feeItems || [];
|
||||
},
|
||||
// fee_category 业务字典:dictKey 为字典键值(落库值),dictValue 为字典名称(展示值)。
|
||||
feeCategoryName(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
const option = this.feeCategoryOptions.find(
|
||||
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
|
||||
);
|
||||
if (option) return option.dictValue ?? value;
|
||||
return (
|
||||
this.feeOptions.find(item => String(item.feeType) === String(value))?.feeTypeName || value
|
||||
);
|
||||
@@ -1589,16 +1629,25 @@ export default {
|
||||
return totalText;
|
||||
});
|
||||
},
|
||||
async refreshSummaryFees() {
|
||||
await this.ensureDetailFeeSnapshots();
|
||||
this.buildSummaryFeesFromDetails();
|
||||
},
|
||||
// 结算合计按费用项归集:原金额取明细进入结算单时的费用金额,结算金额取调整后的金额,调整金额为两者之差。
|
||||
buildSummaryFeesFromDetails() {
|
||||
const generatedMap = new Map();
|
||||
const existingGenerated = new Map(
|
||||
this.summaryFees
|
||||
.filter(row => row.manualFlag !== 1)
|
||||
.filter(row => Number(row.manualFlag) !== 1)
|
||||
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
||||
);
|
||||
const append = (feeItem, value, fallbackFeeType = '') => {
|
||||
const amount = Number(value || 0);
|
||||
if (!feeItem || !Number.isFinite(amount) || Math.abs(amount) < 0.005) return;
|
||||
const append = (feeItem, originalValue, settlementValue, fallbackFeeType = '') => {
|
||||
const originalAmount = Number(originalValue || 0);
|
||||
const settlementAmount = Number(settlementValue || 0);
|
||||
if (!feeItem || !Number.isFinite(originalAmount) || !Number.isFinite(settlementAmount)) {
|
||||
return;
|
||||
}
|
||||
if (Math.abs(originalAmount) < 0.005 && Math.abs(settlementAmount) < 0.005) return;
|
||||
const option = this.feeOptions.find(item =>
|
||||
(item.feeItems || []).some(name => String(name) === String(feeItem))
|
||||
);
|
||||
@@ -1613,35 +1662,212 @@ export default {
|
||||
feeType,
|
||||
feeItem,
|
||||
originalAmount: 0,
|
||||
adjustAmount: Number(old?.adjustAmount || 0),
|
||||
adjustAmount: 0,
|
||||
settlementAmount: 0,
|
||||
remark: old?.remark || '',
|
||||
manualFlag: 0,
|
||||
};
|
||||
current.originalAmount = Number((current.originalAmount + amount).toFixed(2));
|
||||
current.settlementAmount = Number(
|
||||
(current.originalAmount + Number(current.adjustAmount || 0)).toFixed(2)
|
||||
current.originalAmount = Number((current.originalAmount + originalAmount).toFixed(2));
|
||||
current.settlementAmount = Number((current.settlementAmount + settlementAmount).toFixed(2));
|
||||
current.adjustAmount = Number(
|
||||
(current.settlementAmount - current.originalAmount).toFixed(2)
|
||||
);
|
||||
generatedMap.set(key, current);
|
||||
};
|
||||
this.details.forEach(row => {
|
||||
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
||||
const entries = Object.entries(feeItems);
|
||||
entries.forEach(([feeItem, amount]) => append(feeItem, amount, row.feeType));
|
||||
let knownAmount = entries.reduce((total, [, amount]) => total + Number(amount || 0), 0);
|
||||
if (!entries.some(([feeItem]) => this.isFreightFeeItem(feeItem))) {
|
||||
append('运输费', row.freightAmount, '物流配送');
|
||||
knownAmount += Number(row.freightAmount || 0);
|
||||
}
|
||||
const totalAmount = Number(
|
||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0
|
||||
this.details.forEach(detail => {
|
||||
this.resolveDetailFeePairs(detail).forEach(([baseRow, currentRow]) =>
|
||||
this.appendFeeRowSummary(baseRow, currentRow, detail, append)
|
||||
);
|
||||
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
||||
if (Math.abs(residualAmount) >= 0.005) append('其他费用', residualAmount, '其他费用');
|
||||
});
|
||||
const manualRows = this.summaryFees.filter(row => row.manualFlag === 1);
|
||||
const manualRows = this.summaryFees.filter(row => Number(row.manualFlag) === 1);
|
||||
this.summaryFees = [...generatedMap.values(), ...manualRows];
|
||||
},
|
||||
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少费用快照时退化为明细自身的汇总行。
|
||||
resolveDetailFeePairs(detail) {
|
||||
const feeKey = this.detailFeeKey(detail);
|
||||
const baseRows = this.detailFeeSnapshots[feeKey] || [];
|
||||
if (!baseRows.length) return [this.buildDetailFeePair(detail)];
|
||||
const pendingRows = this.pendingAdjustments[feeKey]?.rows || [];
|
||||
return baseRows.map((baseRow, index) => {
|
||||
const matched =
|
||||
pendingRows.find(row => baseRow.id && String(row.id) === String(baseRow.id)) ||
|
||||
pendingRows[index];
|
||||
return [baseRow, matched || baseRow];
|
||||
});
|
||||
},
|
||||
buildDetailFeePair(detail) {
|
||||
const baseRow = {
|
||||
feeItems: this.parseFeeItems(detail.feeItemsJson || detail.feeItems),
|
||||
feeType: detail.feeType,
|
||||
freightAmount: Number(detail.freightAmount || 0),
|
||||
settlementAmountTax: Number(
|
||||
detail.originalAmount ?? this.detailSettlementAmount(detail)
|
||||
),
|
||||
};
|
||||
return [baseRow, { ...baseRow, settlementAmountTax: this.detailSettlementAmount(detail) }];
|
||||
},
|
||||
detailSettlementAmount(detail) {
|
||||
return Number(
|
||||
detail?.settlementAmountTax ??
|
||||
detail?.totalAmount ??
|
||||
detail?.afterAmount ??
|
||||
detail?.settlementAmount ??
|
||||
0
|
||||
);
|
||||
},
|
||||
appendFeeRowSummary(baseRow, currentRow, detail, append) {
|
||||
const fallbackFeeType = currentRow.feeType || detail.feeType || '';
|
||||
const feeItemNames = Array.from(
|
||||
new Set([...Object.keys(baseRow.feeItems || {}), ...Object.keys(currentRow.feeItems || {})])
|
||||
);
|
||||
feeItemNames.forEach(name =>
|
||||
append(name, baseRow.feeItems?.[name], currentRow.feeItems?.[name], fallbackFeeType)
|
||||
);
|
||||
const hasFreightItem = feeItemNames.some(name => this.isFreightFeeItem(name));
|
||||
if (!hasFreightItem) {
|
||||
append('运输费', baseRow.freightAmount, currentRow.freightAmount, '物流配送');
|
||||
}
|
||||
const knownAmount = row =>
|
||||
this.sumFeeItems(row.feeItems) + (hasFreightItem ? 0 : Number(row.freightAmount || 0));
|
||||
const baseResidual = Number(
|
||||
(Number(baseRow.settlementAmountTax || 0) - knownAmount(baseRow)).toFixed(2)
|
||||
);
|
||||
const currentResidual = Number(
|
||||
(Number(currentRow.settlementAmountTax || 0) - knownAmount(currentRow)).toFixed(2)
|
||||
);
|
||||
if (Math.abs(baseResidual) < 0.005 && Math.abs(currentResidual) < 0.005) return;
|
||||
// 结算金额被手工改写且无法拆分到具体费用项时,计入金额占比最大的费用项,否则归入其他费用。
|
||||
append(
|
||||
this.residualFeeItem(baseRow, currentRow),
|
||||
baseResidual,
|
||||
currentResidual,
|
||||
fallbackFeeType
|
||||
);
|
||||
},
|
||||
residualFeeItem(baseRow, currentRow) {
|
||||
const feeItems = { ...(baseRow.feeItems || {}), ...(currentRow.feeItems || {}) };
|
||||
const dominant = Object.entries(feeItems)
|
||||
.filter(([, amount]) => Math.abs(Number(amount || 0)) >= 0.005)
|
||||
.sort((a, b) => Math.abs(Number(b[1] || 0)) - Math.abs(Number(a[1] || 0)))[0];
|
||||
if (dominant) return dominant[0];
|
||||
return (
|
||||
currentRow.billingRule?.feeItem ||
|
||||
currentRow.feeItem ||
|
||||
baseRow.billingRule?.feeItem ||
|
||||
baseRow.feeItem ||
|
||||
'其他费用'
|
||||
);
|
||||
},
|
||||
sumFeeItems(feeItems) {
|
||||
return Number(
|
||||
Object.values(feeItems || {})
|
||||
.reduce((total, value) => total + Number(value || 0), 0)
|
||||
.toFixed(2)
|
||||
);
|
||||
},
|
||||
detailFeeKey(detail) {
|
||||
if (detail.formalSettlementId && detail.id) return `formal:${detail.id}`;
|
||||
if (detail.sourcePreSettlementId && detail.id) return `pre:${detail.id}`;
|
||||
const sourceDetailId = detail.sourceDetailId || detail.id;
|
||||
return sourceDetailId ? `src:${sourceDetailId}` : '';
|
||||
},
|
||||
async loadDetailFeeRows(detail) {
|
||||
if (detail.formalSettlementId && detail.id) {
|
||||
const rows = this.unwrapData(await getDetailFees(detail.id)) || [];
|
||||
return rows.map(item => this.normalizeAdjustRow(item));
|
||||
}
|
||||
if (detail.sourcePreSettlementId && detail.id) {
|
||||
const rows = this.unwrapData(await getPreSettlementDetailFees(detail.id)) || [];
|
||||
return rows.map(item => this.normalizeAdjustRow(item));
|
||||
}
|
||||
const sourceDetailId = detail.sourceDetailId || detail.id;
|
||||
if (!sourceDetailId) return [];
|
||||
const data = this.unwrapData(await getFeeDetail(sourceDetailId)) || {};
|
||||
return (data.records || []).map(item =>
|
||||
this.normalizeAdjustRow(item, data.feeItemNames || [])
|
||||
);
|
||||
},
|
||||
// 明细列表只有汇总金额,费用项级别的原始金额需要按明细单独拉取并缓存为调整基准。
|
||||
async ensureDetailFeeSnapshots() {
|
||||
const targets = this.details.filter(detail => {
|
||||
const feeKey = this.detailFeeKey(detail);
|
||||
return feeKey && !this.detailFeeSnapshots[feeKey];
|
||||
});
|
||||
if (!targets.length) return;
|
||||
await Promise.all(
|
||||
targets.map(async detail => {
|
||||
const feeKey = this.detailFeeKey(detail);
|
||||
try {
|
||||
const feeRows = await this.loadDetailFeeRows(detail);
|
||||
if (!feeRows.length) return;
|
||||
this.detailFeeSnapshots[feeKey] = feeRows;
|
||||
this.applySnapshotToDetail(detail, feeRows);
|
||||
} catch (error) {
|
||||
// 取不到费用明细时退回按明细汇总金额归集,后续操作会再次尝试拉取。
|
||||
delete this.detailFeeSnapshots[feeKey];
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
applySnapshotToDetail(detail, feeRows) {
|
||||
if (!feeRows.length || detail.pendingDetailAdjustment) return;
|
||||
const feeItems = {};
|
||||
feeRows.forEach(row =>
|
||||
Object.entries(row.feeItems || {}).forEach(([name, amount]) => {
|
||||
feeItems[name] = Number((Number(feeItems[name] || 0) + Number(amount || 0)).toFixed(2));
|
||||
})
|
||||
);
|
||||
const settlementAmount = feeRows.reduce(
|
||||
(total, row) => total + Number(row.settlementAmountTax || 0),
|
||||
0
|
||||
);
|
||||
const freightAmount = feeRows.reduce(
|
||||
(total, row) => total + Number(row.freightAmount || 0),
|
||||
0
|
||||
);
|
||||
Object.assign(detail, {
|
||||
feeItemsJson: JSON.stringify(feeItems),
|
||||
freightAmount: Number(freightAmount.toFixed(2)),
|
||||
originalAmount: Number(settlementAmount.toFixed(2)),
|
||||
adjustAmount: 0,
|
||||
settlementAmountTax: Number(settlementAmount.toFixed(2)),
|
||||
});
|
||||
},
|
||||
normalizeAdjustRow(item, feeItemNames = []) {
|
||||
const rawFeeItems =
|
||||
item.feeItems && typeof item.feeItems === 'object'
|
||||
? item.feeItems
|
||||
: this.parseFeeItems(item.feeItemsJson);
|
||||
const names = feeItemNames.length ? feeItemNames : Object.keys(rawFeeItems);
|
||||
return {
|
||||
...item,
|
||||
transportQuantity: Number(item.transportQuantity || 0),
|
||||
mileage:
|
||||
item.mileage === null || item.mileage === undefined || Number(item.mileage) === -1
|
||||
? null
|
||||
: Number(item.mileage),
|
||||
freightAmount: Number(item.freightAmount || 0),
|
||||
feeItems: Object.fromEntries(names.map(name => [name, Number(rawFeeItems[name] || 0)])),
|
||||
settlementAmountTax: Number(
|
||||
item.settlementAmountTax ?? item.totalAmount ?? item.afterAmount ?? item.settlementAmount ?? 0
|
||||
),
|
||||
billingRule: this.normalizeBillingRule(item),
|
||||
calculating: false,
|
||||
calculateError: '',
|
||||
adjustCalculateTimer: null,
|
||||
adjustCalculateVersion: 0,
|
||||
};
|
||||
},
|
||||
cloneAdjustRows(rows) {
|
||||
return (rows || []).map(row => ({
|
||||
...row,
|
||||
feeItems: { ...row.feeItems },
|
||||
calculating: false,
|
||||
calculateError: '',
|
||||
adjustCalculateTimer: null,
|
||||
adjustCalculateVersion: 0,
|
||||
}));
|
||||
},
|
||||
isFreightFeeItem(name) {
|
||||
return name && (name.includes('运费') || name.includes('运输费'));
|
||||
},
|
||||
@@ -1768,76 +1994,100 @@ export default {
|
||||
row.settlementAmountTax = Number(
|
||||
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
|
||||
);
|
||||
if (changedField && this.adjust.sourceDetailId && (row.sourceFeeId || row.id)) {
|
||||
this.scheduleSourceAdjustCalculation(row);
|
||||
}
|
||||
},
|
||||
scheduleSourceAdjustCalculation(row) {
|
||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||
row.adjustCalculateVersion = Number(row.adjustCalculateVersion || 0) + 1;
|
||||
row.calculating = true;
|
||||
const version = row.adjustCalculateVersion;
|
||||
row.adjustCalculateTimer = setTimeout(async () => {
|
||||
row.adjustCalculateTimer = null;
|
||||
try {
|
||||
const response = await calculateAdjustedFee({
|
||||
detailId: this.adjust.sourceDetailId,
|
||||
feeId: row.sourceFeeId || row.id,
|
||||
transportQuantity: Number(row.transportQuantity || 0),
|
||||
mileage: Number(row.mileage || 0),
|
||||
freightAmount: Number(row.freightAmount || 0),
|
||||
feeItems: Object.fromEntries(
|
||||
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||
),
|
||||
});
|
||||
if (version !== row.adjustCalculateVersion || !this.adjust.visible) return;
|
||||
const data = response.data?.data || response.data || {};
|
||||
row.freightAmount = Number(data.freightAmount || 0);
|
||||
row.feeItems = Object.fromEntries(
|
||||
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||
);
|
||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
||||
row.settlementAmountTax = Number(data.afterAmount || 0);
|
||||
} catch (error) {
|
||||
row.calculateError = error.message || '费用试算失败';
|
||||
} finally {
|
||||
if (version === row.adjustCalculateVersion) row.calculating = false;
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
async openAdjustDialog(row) {
|
||||
if (!row.formalSettlementId) {
|
||||
this.adjust = {
|
||||
visible: true,
|
||||
loading: true,
|
||||
saving: false,
|
||||
detailId: null,
|
||||
reason: '',
|
||||
rows: [],
|
||||
targetRow: row,
|
||||
};
|
||||
try {
|
||||
let feeRows = [];
|
||||
if (row.sourcePreSettlementId && row.id) {
|
||||
const response = await getPreSettlementDetailFees(row.id);
|
||||
feeRows = this.unwrapData(response) || [];
|
||||
}
|
||||
this.adjust.rows = (feeRows.length ? feeRows : [row]).map(item => ({
|
||||
...item,
|
||||
feeItems: this.parseFeeItems(item.feeItemsJson || item.feeItems),
|
||||
billingRule: this.normalizeBillingRule(item),
|
||||
settlementAmountTax:
|
||||
item.settlementAmountTax ??
|
||||
item.totalAmount ??
|
||||
item.afterAmount ??
|
||||
item.settlementAmount ??
|
||||
0,
|
||||
}));
|
||||
} finally {
|
||||
this.adjust.loading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const detailRow = row;
|
||||
const feeKey = this.detailFeeKey(row);
|
||||
const pending = feeKey ? this.pendingAdjustments[feeKey] : null;
|
||||
this.adjust = {
|
||||
visible: true,
|
||||
loading: true,
|
||||
saving: false,
|
||||
detailId: detailRow.id,
|
||||
reason: '',
|
||||
detailId: row.formalSettlementId ? row.id : null,
|
||||
sourceDetailId: row.sourceDetailId || null,
|
||||
feeKey,
|
||||
reason: pending?.reason || '',
|
||||
rows: [],
|
||||
targetRow: null,
|
||||
targetRow: row.formalSettlementId ? null : row,
|
||||
};
|
||||
try {
|
||||
const response = await getDetailFees(detailRow.id);
|
||||
const data = this.unwrapData(response);
|
||||
this.adjust.rows = (data || []).map(item => ({
|
||||
...item,
|
||||
feeItems: this.parseFeeItems(item.feeItemsJson),
|
||||
billingRule: this.normalizeBillingRule(item),
|
||||
}));
|
||||
// 已暂存的调整结果优先回显,避免重新打开时又回到调整前的数据。
|
||||
if (pending?.rows?.length) {
|
||||
this.adjust.rows = this.cloneAdjustRows(pending.rows);
|
||||
return;
|
||||
}
|
||||
if (feeKey && !this.detailFeeSnapshots[feeKey]) {
|
||||
const feeRows = await this.loadDetailFeeRows(row);
|
||||
if (feeRows.length) this.detailFeeSnapshots[feeKey] = feeRows;
|
||||
}
|
||||
const snapshot = feeKey ? this.detailFeeSnapshots[feeKey] : null;
|
||||
this.adjust.rows = snapshot?.length
|
||||
? this.cloneAdjustRows(snapshot)
|
||||
: [this.normalizeAdjustRow(row)];
|
||||
} finally {
|
||||
this.adjust.loading = false;
|
||||
}
|
||||
},
|
||||
async saveAdjustment() {
|
||||
if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因');
|
||||
if (this.adjust.rows.some(row => row.calculating)) {
|
||||
return this.$message.warning('费用正在重新计算,请稍候');
|
||||
}
|
||||
if (!this.adjust.detailId) {
|
||||
const target = this.adjust.targetRow;
|
||||
const edited = this.adjust.rows[0];
|
||||
if (!target || !edited) return;
|
||||
const originalAmount = Number(
|
||||
target.originalAmount ??
|
||||
target.totalAmount ??
|
||||
target.afterAmount ??
|
||||
target.settlementAmountTax ??
|
||||
target.settlementAmount ??
|
||||
0
|
||||
);
|
||||
// 原金额以明细进入结算单时的费用快照为基准,调整金额 = 结算金额 - 原金额。
|
||||
const baseRows = this.detailFeeSnapshots[this.adjust.feeKey] || [];
|
||||
const originalAmount = baseRows.length
|
||||
? Number(
|
||||
baseRows
|
||||
.reduce((total, row) => total + Number(row.settlementAmountTax || 0), 0)
|
||||
.toFixed(2)
|
||||
)
|
||||
: Number(
|
||||
target.originalAmount ??
|
||||
target.totalAmount ??
|
||||
target.afterAmount ??
|
||||
target.settlementAmountTax ??
|
||||
target.settlementAmount ??
|
||||
0
|
||||
);
|
||||
const settlementAmount = Number(
|
||||
this.adjust.rows
|
||||
.reduce((total, item) => total + Number(item.settlementAmountTax || 0), 0)
|
||||
@@ -1870,9 +2120,15 @@ export default {
|
||||
remark: edited.remark,
|
||||
pendingDetailAdjustment: true,
|
||||
});
|
||||
this.buildSummaryFeesFromDetails();
|
||||
if (this.adjust.feeKey) {
|
||||
this.pendingAdjustments[this.adjust.feeKey] = {
|
||||
reason: this.adjust.reason,
|
||||
rows: this.cloneAdjustRows(this.adjust.rows),
|
||||
};
|
||||
}
|
||||
await this.refreshSummaryFees();
|
||||
this.adjust.visible = false;
|
||||
this.$message.success('调整已暂存,请保存正式结算单提交');
|
||||
this.$message.success('调整已暂存,保存正式结算单后生效');
|
||||
return;
|
||||
}
|
||||
this.adjust.saving = true;
|
||||
|
||||
Reference in New Issue
Block a user