✨ 结算付款体验优化与合同/运单导入校验
付款申请汇票筛选与提示去重、结算菜单拆分与金蝶同步弹框、应收应付详情高度、合同计费与半月结调整,以及导入运单批量删除校验。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -459,7 +459,7 @@
|
||||
</section-card>
|
||||
|
||||
<section-card v-if="form.id && readonly" title="变更记录">
|
||||
<el-table :data="adjustChangeRecords" border>
|
||||
<el-table :data="changeRecords" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in changeTableColumns"
|
||||
@@ -753,20 +753,6 @@
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
|
||||
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
||||
<el-table-column label="计费规则" min-width="190" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="pre-settlement-editor__billing-rule-cell">
|
||||
<span>{{ displayValue(billingRuleName(row)) }}</span>
|
||||
<el-icon
|
||||
class="pre-settlement-editor__billing-rule-icon"
|
||||
title="查看计费规则详情"
|
||||
@click="openBillingRuleInfo(row)"
|
||||
>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运输总量" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
|
||||
@@ -809,11 +795,33 @@
|
||||
v-for="feeItem in adjustFeeItemNames"
|
||||
:key="feeItem"
|
||||
:label="feeItem"
|
||||
min-width="140"
|
||||
min-width="160"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
v-if="isFreightFeeItem(feeItem)"
|
||||
class="pre-settlement-editor__billing-rule-cell"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="adjustDialog.readonly"
|
||||
@change="recalculateAdjustRow(row, feeItem)"
|
||||
/>
|
||||
<el-icon
|
||||
v-if="hasBillingRule(row)"
|
||||
class="pre-settlement-editor__billing-rule-icon"
|
||||
title="查看计费规则详情"
|
||||
@click="openBillingRuleInfo(row)"
|
||||
>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row.feeItems[feeItem]"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
@@ -833,11 +841,6 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-form
|
||||
v-if="!adjustDialog.readonly"
|
||||
@@ -846,7 +849,7 @@
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__adjust-reason"
|
||||
>
|
||||
<el-form-item label="调整原因">
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="adjustDialog.reason"
|
||||
type="textarea"
|
||||
@@ -885,12 +888,6 @@
|
||||
<template v-if="billingRuleDialog.rule">
|
||||
<div class="dialog-section-title">计费规则</div>
|
||||
<el-descriptions :column="2" border class="pre-settlement-editor__billing-rule-detail">
|
||||
<el-descriptions-item label="规则名称">
|
||||
{{ displayValue(billingRuleDialog.rule.name) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="费用类型">
|
||||
{{ displayValue(billingRuleDialog.rule.feeType) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="费用项">
|
||||
{{ displayValue(billingRuleDialog.rule.feeItem) }}
|
||||
</el-descriptions-item>
|
||||
@@ -926,8 +923,8 @@
|
||||
<el-descriptions-item label="目的地">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.destination) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运输方式">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.transportMode) }}
|
||||
<el-descriptions-item label="货物名称">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoName) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="货物类型">
|
||||
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoType) }}
|
||||
@@ -1202,8 +1199,8 @@ export default {
|
||||
return this.editable && this.hasPermission('pre_settlement_adjust');
|
||||
},
|
||||
dialogTitle() {
|
||||
if (this.readonly) return '查看预结算单';
|
||||
return this.form.id ? '编辑预结算单' : '新增预结算单';
|
||||
if (this.readonly) return '查看预付结算单';
|
||||
return this.form.id ? '编辑预付结算单' : '新增预付结算单';
|
||||
},
|
||||
settlementTypeName() {
|
||||
return this.form.settlementType === 'receivable' ? '应收' : '应付';
|
||||
@@ -1285,13 +1282,6 @@ export default {
|
||||
});
|
||||
return Array.from(names);
|
||||
},
|
||||
adjustChangeRecords() {
|
||||
return this.changeRecords.filter(
|
||||
row =>
|
||||
row.changeType === '结算明细项' &&
|
||||
String(row.lineNo ?? '') === String(this.adjustDialog.detailLineNo ?? '')
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
@@ -1865,7 +1855,25 @@ export default {
|
||||
});
|
||||
|
||||
const manualFees = this.summaryFees.filter(row => Number(row.manualFlag) === 1);
|
||||
this.summaryFees = [...summaryMap.values(), ...manualFees];
|
||||
const nextFeeMap = new Map();
|
||||
summaryMap.forEach((row, key) => nextFeeMap.set(`0\u0000${key}`, row));
|
||||
manualFees.forEach(row => {
|
||||
const key = `1\u0000${row.feeType}\u0000${row.feeItem}`;
|
||||
nextFeeMap.set(key, row);
|
||||
});
|
||||
// 按编辑前顺序回填,新增费用项追加到末尾,避免手工费用被统一挤到最后。
|
||||
const orderedFees = [];
|
||||
const previousKeys = this.summaryFees.map(
|
||||
row => `${Number(row.manualFlag) === 1 ? 1 : 0}\u0000${row.feeType}\u0000${row.feeItem}`
|
||||
);
|
||||
previousKeys.forEach(key => {
|
||||
const row = nextFeeMap.get(key);
|
||||
if (!row) return;
|
||||
orderedFees.push(row);
|
||||
nextFeeMap.delete(key);
|
||||
});
|
||||
nextFeeMap.forEach(row => orderedFees.push(row));
|
||||
this.summaryFees = orderedFees;
|
||||
this.recalculateLocalAmount();
|
||||
},
|
||||
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少来源费用快照时退化为明细自身的汇总行。
|
||||
@@ -2053,7 +2061,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async removeDetailRow(row) {
|
||||
await this.$confirm('确认将该明细踢出预结算单?', '提示', { type: 'warning' });
|
||||
await this.$confirm('确认将该明细踢出预付结算单?', '提示', { type: 'warning' });
|
||||
if (!this.form.id || !row.id || row.id === row.sourceDetailId) {
|
||||
this.details = this.details.filter(item => item !== row);
|
||||
this.buildSummaryFeesFromDetails();
|
||||
@@ -2228,7 +2236,11 @@ export default {
|
||||
: '';
|
||||
const sourceDetailId = String(this.adjustDialog.sourceDetailId || '');
|
||||
const pending = sourceDetailId ? this.pendingAdjustments[sourceDetailId] : null;
|
||||
this.adjustDialog.reason = pending?.reason || '';
|
||||
// 底部备注回显结算明细备注;暂存过的调整优先取暂存备注
|
||||
this.adjustDialog.reason =
|
||||
pending?.reason !== undefined && pending?.reason !== null
|
||||
? pending.reason
|
||||
: detailRow.remark || '';
|
||||
try {
|
||||
// 已暂存的调整结果优先回显,避免重新打开时又回到调整前的数据。
|
||||
if (pending?.rows?.length) {
|
||||
@@ -2295,8 +2307,19 @@ export default {
|
||||
this.adjustChangeRecordDetailRows = this.buildAdjustChangeRecordRows(row);
|
||||
this.adjustChangeRecordVisible = true;
|
||||
},
|
||||
billingRuleName(row) {
|
||||
return this.normalizeBillingRule(row).name;
|
||||
hasBillingRule(row) {
|
||||
const rule = this.normalizeBillingRule(row);
|
||||
return Boolean(
|
||||
rule.feeItem ||
|
||||
rule.billingElement ||
|
||||
rule.billingType ||
|
||||
rule.unitPrice ||
|
||||
rule.rules?.length ||
|
||||
rule.matchCondition?.origin ||
|
||||
rule.matchCondition?.destination ||
|
||||
rule.matchCondition?.cargoName ||
|
||||
rule.matchCondition?.cargoType
|
||||
);
|
||||
},
|
||||
openBillingRuleInfo(row) {
|
||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
||||
@@ -2323,27 +2346,8 @@ export default {
|
||||
const matchCondition = this.parseObject(
|
||||
source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition
|
||||
);
|
||||
const ruleNames = matchedRules
|
||||
.map(item => item.ruleName || item.name || item.billingRuleName || item.feeItem)
|
||||
.filter(Boolean);
|
||||
const name =
|
||||
source.billingRuleName ||
|
||||
source.feeRuleName ||
|
||||
source.ruleName ||
|
||||
source.billingPlanRuleName ||
|
||||
source.billingPlanName ||
|
||||
rule.name ||
|
||||
rule.ruleName ||
|
||||
rule.billingRuleName ||
|
||||
rule.planName ||
|
||||
rule.billingPlanName ||
|
||||
ruleNames.join('、') ||
|
||||
rule.feeItem ||
|
||||
'';
|
||||
return {
|
||||
...rule,
|
||||
name,
|
||||
feeType: source.billingFeeType || source.feeType || rule.feeType,
|
||||
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||
billingElement:
|
||||
source.billingElement ||
|
||||
@@ -2361,7 +2365,11 @@ export default {
|
||||
...matchCondition,
|
||||
origin: matchCondition.origin || matchCondition.originName,
|
||||
destination: matchCondition.destination || matchCondition.destinationName,
|
||||
transportMode: matchCondition.transportMode || matchCondition.transportType,
|
||||
cargoName:
|
||||
matchCondition.cargoName ||
|
||||
matchCondition.cargoNameLabel ||
|
||||
source.cargoName ||
|
||||
rule.cargoName,
|
||||
cargoType: matchCondition.cargoType || matchCondition.cargoTypeName,
|
||||
},
|
||||
};
|
||||
@@ -2451,9 +2459,13 @@ export default {
|
||||
reason: this.adjustDialog.reason,
|
||||
rows: this.adjustRows.map(row => ({ ...row, feeItems: { ...row.feeItems } })),
|
||||
};
|
||||
this.applyLocalAdjustment(this.adjustDialog.sourceDetailId, this.adjustRows);
|
||||
this.applyLocalAdjustment(
|
||||
this.adjustDialog.sourceDetailId,
|
||||
this.adjustRows,
|
||||
this.adjustDialog.reason
|
||||
);
|
||||
this.adjustDialog.visible = false;
|
||||
this.$message.success('结算明细调整已暂存,保存预结算单后生效');
|
||||
this.$message.success('结算明细调整已暂存,保存预付结算单后生效');
|
||||
return;
|
||||
}
|
||||
await adjustDetail({
|
||||
@@ -2468,7 +2480,6 @@ export default {
|
||||
feeItems: row.feeItems,
|
||||
settlementAmountTax: row.settlementAmountTax,
|
||||
settlementAmountNoTax: row.settlementAmountNoTax,
|
||||
remark: row.remark,
|
||||
})),
|
||||
});
|
||||
this.adjustDialog.visible = false;
|
||||
@@ -2479,12 +2490,12 @@ export default {
|
||||
this.adjustDialog.saving = false;
|
||||
}
|
||||
},
|
||||
applyLocalAdjustment(sourceDetailId, rows) {
|
||||
applyLocalAdjustment(sourceDetailId, rows, detailRemark) {
|
||||
const detail = this.details.find(
|
||||
item => String(item.sourceDetailId || item.id) === String(sourceDetailId)
|
||||
);
|
||||
if (!detail) return;
|
||||
// 原金额以进入预结算单时的费用快照为基准,调整金额 = 结算金额 - 原金额。
|
||||
// 原金额以进入预付结算单时的费用快照为基准,调整金额 = 结算金额 - 原金额。
|
||||
const baseRows = this.sourceFeeSnapshots[String(sourceDetailId)] || [];
|
||||
const originalAmount = baseRows.length
|
||||
? baseRows.reduce((sum, row) => sum + Number(row.settlementAmountTax || 0), 0)
|
||||
@@ -2493,20 +2504,48 @@ export default {
|
||||
(sum, row) => sum + Number(row.settlementAmountTax || 0),
|
||||
0
|
||||
);
|
||||
const settlementAmountNoTax = rows.reduce(
|
||||
(sum, row) => sum + Number(row.settlementAmountNoTax || 0),
|
||||
0
|
||||
);
|
||||
const hasSettlementAmountNoTax = rows.some(
|
||||
row => row.settlementAmountNoTax !== undefined && row.settlementAmountNoTax !== null
|
||||
);
|
||||
const freightAmount = rows.reduce((sum, row) => sum + Number(row.freightAmount || 0), 0);
|
||||
const transportQuantity = rows.reduce(
|
||||
(sum, row) => sum + Number(row.transportQuantity || 0),
|
||||
0
|
||||
);
|
||||
const mileage = rows.reduce((sum, row) => sum + Number(row.mileage || 0), 0);
|
||||
const feeItems = {};
|
||||
rows.forEach(row =>
|
||||
Object.entries(row.feeItems || {}).forEach(([name, amount]) => {
|
||||
feeItems[name] = Number((Number(feeItems[name] || 0) + Number(amount || 0)).toFixed(2));
|
||||
})
|
||||
);
|
||||
const firstRow = rows[0] || {};
|
||||
Object.assign(detail, {
|
||||
transportQuantity: Number(transportQuantity.toFixed(2)),
|
||||
mileage: Number(mileage.toFixed(2)),
|
||||
unitPrice: Number(firstRow.unitPrice || 0),
|
||||
originalAmount: Number(originalAmount.toFixed(2)),
|
||||
adjustAmount: Number((settlementAmountTax - originalAmount).toFixed(2)),
|
||||
settlementAmountTax: Number(settlementAmountTax.toFixed(2)),
|
||||
settlementAmountNoTax: hasSettlementAmountNoTax
|
||||
? Number(settlementAmountNoTax.toFixed(2))
|
||||
: detail.settlementAmountNoTax,
|
||||
freightAmount: Number(freightAmount.toFixed(2)),
|
||||
feeItemsJson: JSON.stringify(feeItems),
|
||||
// 底部备注写入结算明细备注
|
||||
remark: detailRemark ?? '',
|
||||
});
|
||||
// 暂存调整后同步费用缓存,保证货物名称/类型拼接与列表展示一致。
|
||||
if (detail.id) {
|
||||
this.detailFeeCache[detail.id] = rows.map(row => ({
|
||||
...row,
|
||||
feeItems: { ...row.feeItems },
|
||||
}));
|
||||
}
|
||||
this.buildSummaryFeesFromDetails();
|
||||
},
|
||||
async persistPendingAdjustments() {
|
||||
@@ -2534,11 +2573,14 @@ export default {
|
||||
feeItems: pendingRow.feeItems,
|
||||
settlementAmountTax: pendingRow.settlementAmountTax,
|
||||
settlementAmountNoTax: pendingRow.settlementAmountNoTax,
|
||||
remark: pendingRow.remark,
|
||||
};
|
||||
});
|
||||
if (rows.length) {
|
||||
await adjustDetail({ detailId: detail.id, changeReason: pending.reason, rows });
|
||||
await adjustDetail({
|
||||
detailId: detail.id,
|
||||
changeReason: pending.reason,
|
||||
rows,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.pendingAdjustments = {};
|
||||
@@ -2610,7 +2652,7 @@ export default {
|
||||
XLSX.utils.book_append_sheet(workbook, sheet, '结算明细');
|
||||
XLSX.writeFile(
|
||||
workbook,
|
||||
`${this.form.preSettlementNo || '预结算单'}结算明细${this.$dayjs().format(
|
||||
`${this.form.preSettlementNo || '预付结算单'}结算明细${this.$dayjs().format(
|
||||
'YYYY-MM-DD HH-mm-ss'
|
||||
)}.xlsx`
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user