✨ 完善付款申请多结算行、结算导入编辑与计费规则展示

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-19 12:06:51 +08:00
parent 1ac741f785
commit aee661772b
8 changed files with 884 additions and 217 deletions
+223 -21
View File
@@ -121,7 +121,7 @@
><div class="payment-form-page__percentage-input">
<el-input-number
v-model="form.paymentRatio"
:disabled="readonly"
:disabled="readonly || multipleSettlementRows"
:min="0"
:max="100"
:precision="2"
@@ -134,7 +134,7 @@
><el-form-item label="申请付款金额" prop="appliedAmount"
><el-input-number
v-model="form.appliedAmount"
:disabled="readonly"
:disabled="readonly || multipleSettlementRows"
:min="0"
:precision="2"
:step="0.01"
@@ -241,15 +241,39 @@
<el-table-column prop="payableAmount" label="可付款金额" min-width="130">
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
</el-table-column>
<el-table-column prop="paymentRatio" label="付款比例(%)" min-width="110">
<el-table-column prop="paymentRatio" label="付款比例(%)" min-width="160">
<template #default="{ row }">
<span :class="{ 'payment-form-page__ratio-exceeded': row.ratioExceeded }">
<el-input-number
v-if="multipleSettlementRows && !readonly"
:model-value="row.paymentRatio"
:min="0"
:max="rowPaymentRatioMax(row)"
:precision="2"
:controls="false"
:value-on-clear="null"
style="width: 100%"
@update:model-value="value => handleRowPaymentRatioChange(row, value)"
/>
<span v-else :class="{ 'payment-form-page__ratio-exceeded': row.ratioExceeded }">
{{ formatMoney(row.paymentRatio) }}
</span>
</template>
</el-table-column>
<el-table-column prop="appliedAmount" label="付款金额" min-width="130">
<template #default="{ row }">{{ formatMoney(row.appliedAmount) }}</template>
<el-table-column prop="appliedAmount" label="付款金额" min-width="160">
<template #default="{ row }">
<el-input-number
v-if="multipleSettlementRows && !readonly"
:model-value="row.appliedAmount"
:min="0"
:max="Number(row.payableAmount || 0)"
:precision="2"
:controls="false"
:value-on-clear="null"
style="width: 100%"
@update:model-value="value => handleRowAppliedAmountChange(row, value)"
/>
<span v-else>{{ formatMoney(row.appliedAmount) }}</span>
</template>
</el-table-column>
<el-table-column prop="ratioRemark" label="备注" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
@@ -988,12 +1012,27 @@ export default {
referenceSettlementCount() {
return this.referenceSettlementNos.length;
},
multipleSettlementRows() {
return this.referenceRows.length > 1;
},
// 结算单号展示:单张显示单号;多张仅提示数量,明细在下方「结算信息」表,悬停可看全部单号
referenceDisplayText() {
if (this.referenceSettlementCount > 1) return `已选 ${this.referenceSettlementCount} 张结算单`;
return this.referenceLabel;
},
settlementRows() {
if (this.referenceRows.length > 1) {
return this.referenceRows.map(item => {
const paymentRatio = item.paymentRatio ?? this.form.paymentRatio;
const appliedAmount = Number(item.appliedAmount || 0);
return {
...item,
paymentRatio,
appliedAmount,
...this.buildSettlementRatioHint({ ...item, paymentRatio }),
};
});
}
if (this.referenceRows.length) {
return this.referenceRows.map(item => {
const appliedAmount =
@@ -1057,7 +1096,7 @@ export default {
},
'form.paymentRatio'(value) {
this.checkContractPaymentRatio(value);
if (this.amountSyncing || !this.paymentAmountBase) return;
if (this.amountSyncing || this.multipleSettlementRows || !this.paymentAmountBase) return;
this.amountSyncing = true;
this.form.appliedAmount = Number(
((this.paymentAmountBase * Number(value || 0)) / 100).toFixed(2)
@@ -1067,7 +1106,7 @@ export default {
});
},
'form.appliedAmount'(value) {
if (this.amountSyncing || !this.paymentAmountBase) return;
if (this.amountSyncing || this.multipleSettlementRows || !this.paymentAmountBase) return;
this.amountSyncing = true;
this.form.paymentRatio = Number(
((Number(value || 0) / this.paymentAmountBase) * 100).toFixed(2)
@@ -1080,6 +1119,7 @@ export default {
if (
this.amountSyncing ||
this.form.paymentType === 'project_advance' ||
this.multipleSettlementRows ||
!this.paymentAmountBase
) {
return;
@@ -1292,6 +1332,7 @@ export default {
}
const referenceRow = this.createReferenceRow(data);
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
if (this.referenceRows.length > 1) this.syncHeaderPaymentFromRows();
this.paymentRecords = data.paymentRecords || [];
},
async initialize() {
@@ -1315,6 +1356,7 @@ export default {
};
const referenceRow = this.createReferenceRow(data);
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
this.clearFormalReferenceWhenPreOnly();
this.paymentRecords = data.paymentRecords || [];
if (this.form.paymentType === 'project_advance') {
await this.loadProjectOptions();
@@ -1322,6 +1364,7 @@ export default {
}
await this.fillBillTypeFromContract(this.form.contractId);
await this.refreshSettlementPaymentSequences();
if (this.referenceRows.length > 1) this.syncHeaderPaymentFromRows();
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
} else {
@@ -1489,6 +1532,7 @@ export default {
fixedAppliedAmount: true,
};
});
this.seedMultipleRowPayments(referenceRows);
const settlementAmount = referenceRows.reduce(
(total, item) => total + Number(item.settlementAmount || 0),
0
@@ -1553,20 +1597,48 @@ export default {
this.$refs.formRef?.clearValidate();
});
},
clearFormalReferenceWhenPreOnly() {
const progressAdvance = this.form.paymentType === 'progress_advance';
const onlyPre =
progressAdvance ||
(this.referenceRows.length > 0 &&
this.referenceRows.every(item => item.referenceType === 'pre'));
if (!onlyPre) return;
if (!this.form.preSettlementId) {
const preId =
this.referenceRows.find(item => item.referenceType === 'pre')?.id ||
(progressAdvance ? this.form.settlementId : null);
if (preId) {
this.form.preSettlementId = preId;
this.form.preSettlementIds = [preId];
}
}
this.form.settlementId = null;
this.form.settlementIds = [];
this.form.settlementNo = '';
},
restoreReferenceRows(data, fallbackRow) {
const relations = Array.isArray(data?.settlements) ? data.settlements : [];
const rows = relations
.map(item => {
const isPre = item.preSettlementId !== undefined && item.preSettlementId !== null && item.preSettlementId !== '';
const isPre = Boolean(item.preSettlementId || item.preSettlementNo);
const settlementNo = item.preSettlementNo || item.formalSettlementNo || '';
if (!settlementNo) return null;
return {
id: isPre ? item.preSettlementId : item.formalSettlementId,
id: isPre ? item.preSettlementId || item.formalSettlementId : item.formalSettlementId,
settlementNo,
referenceType: isPre ? 'pre' : 'formal',
settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)),
payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)),
appliedAmount: Number(Number(item.appliedAmount || 0).toFixed(2)),
paymentRatio: Number(item.settlementAmount || 0)
? Number(
(
(Number(item.appliedAmount || 0) / Number(item.settlementAmount || 0)) *
100
).toFixed(2)
)
: null,
fixedAppliedAmount: this.readonly,
};
})
@@ -1582,7 +1654,12 @@ export default {
},
createReferenceRow(row) {
const settlementAmount = Number(row.settlementAmount || 0);
const isPre = Boolean(row.preSettlementNo || row.preSettlementId || row.referenceType === 'pre');
const isPre = Boolean(
row.preSettlementNo ||
row.preSettlementId ||
row.referenceType === 'pre' ||
row.paymentType === 'progress_advance'
);
const paymentSequence = Number(row.paymentSequence);
// 可付款金额必须来自 referenceAmount 接口(已扣减占用中的付款申请);缺失时置 0,避免误用未扣减结算金额
const payableAmount =
@@ -1590,7 +1667,7 @@ export default {
? 0
: Number(row.payableAmount || 0);
return {
id: row.preSettlementId || row.settlementId || row.id,
id: row.preSettlementId || (isPre ? row.settlementId : null) || row.settlementId || row.id,
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
referenceType: isPre ? 'pre' : 'formal',
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
@@ -1604,7 +1681,11 @@ export default {
*/
buildSettlementRatioHint(row = {}) {
const sequence = Number(row.paymentSequence || 1);
const paymentRatio = Number(this.form.paymentRatio);
const rawRatio =
row.paymentRatio !== undefined && row.paymentRatio !== null && row.paymentRatio !== ''
? row.paymentRatio
: this.form.paymentRatio;
const paymentRatio = Number(rawRatio);
const ratioConfig = this.contractPaymentRatioRows[sequence - 1];
const ratioLimitValue = ratioConfig?.ratioLimit;
const ratioLimit = Number(ratioLimitValue);
@@ -1614,9 +1695,9 @@ export default {
String(ratioLimitValue).trim() !== '' &&
Number.isFinite(ratioLimit);
const hasPaymentRatio =
this.form.paymentRatio !== null &&
this.form.paymentRatio !== undefined &&
String(this.form.paymentRatio).trim() !== '' &&
rawRatio !== null &&
rawRatio !== undefined &&
String(rawRatio).trim() !== '' &&
Number.isFinite(paymentRatio);
const ratioExceeded = hasLimit && hasPaymentRatio && paymentRatio > ratioLimit;
return {
@@ -1737,6 +1818,7 @@ export default {
this.$message.error(error?.message || '可付款金额获取失败,请重试');
return;
}
this.seedMultipleRowPayments(this.referenceRows);
const settlementAmount = this.referenceRows.reduce(
(total, item) => total + Number(item.settlementAmount || 0),
0
@@ -1745,7 +1827,19 @@ export default {
(total, item) => total + Number(item.payableAmount || 0),
0
);
const paymentRatio = Number(this.form.paymentRatio || 0);
const appliedAmount = this.referenceRows.reduce(
(total, item) => total + Number(item.appliedAmount || 0),
0
);
const multiple = this.referenceRows.length > 1;
const paymentRatio = multiple
? settlementAmount
? Number(((appliedAmount / settlementAmount) * 100).toFixed(2))
: 0
: Number(this.form.paymentRatio || 0);
const headerAppliedAmount = multiple
? Number(appliedAmount.toFixed(2))
: Number(((payableAmount * paymentRatio) / 100).toFixed(2));
// 尾款付款场景下选择预付结算单时保留尾款付款类型,不强制改为进度预付
this.amountSyncing = true;
Object.assign(this.form, {
@@ -1771,8 +1865,8 @@ export default {
settlementAmount: Number(settlementAmount.toFixed(2)),
payableAmount: Number(payableAmount.toFixed(2)),
billType: '',
// 申请金额按可付款金额×比例,避免未扣减已申请额度
appliedAmount: Number(((payableAmount * paymentRatio) / 100).toFixed(2)),
paymentRatio,
appliedAmount: headerAppliedAmount,
});
this.paymentRecords = [];
this.clearReceiptAccount();
@@ -2914,6 +3008,94 @@ export default {
);
});
},
rowPaymentRatioMax(row) {
const settlementAmount = Number(row.settlementAmount || 0);
const payableAmount = Number(row.payableAmount || 0);
if (settlementAmount <= 0) return 100;
return Number(Math.min(100, (payableAmount / settlementAmount) * 100).toFixed(2));
},
seedMultipleRowPayments(rows = []) {
if (rows.length <= 1) return;
const ratio = Number(this.form.paymentRatio || 0);
rows.forEach(item => {
const settlementAmount = Number(item.settlementAmount || 0);
const payableAmount = Number(item.payableAmount || 0);
const hasRatio =
item.paymentRatio !== null && item.paymentRatio !== undefined && item.paymentRatio !== '';
if (hasRatio) return;
let applied = Number(item.appliedAmount || 0);
if (applied <= 0 && ratio > 0) {
applied = Number(((settlementAmount * ratio) / 100).toFixed(2));
}
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
item.appliedAmount = Number(applied.toFixed(2));
item.paymentRatio = settlementAmount
? Number(((item.appliedAmount / settlementAmount) * 100).toFixed(2))
: 0;
});
},
findReferenceRow(row) {
return this.referenceRows.find(
item =>
item.id === row.id &&
item.referenceType === row.referenceType &&
item.settlementNo === row.settlementNo
);
},
handleRowPaymentRatioChange(row, value) {
const target = this.findReferenceRow(row);
if (!target) return;
const settlementAmount = Number(target.settlementAmount || 0);
const payableAmount = Number(target.payableAmount || 0);
const ratio = value === null || value === undefined || value === '' ? 0 : Number(value);
let applied = Number(((settlementAmount * ratio) / 100).toFixed(2));
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
target.appliedAmount = applied;
target.paymentRatio = settlementAmount
? Number(((applied / settlementAmount) * 100).toFixed(2))
: Number(ratio.toFixed(2));
this.syncHeaderPaymentFromRows();
},
handleRowAppliedAmountChange(row, value) {
const target = this.findReferenceRow(row);
if (!target) return;
const settlementAmount = Number(target.settlementAmount || 0);
const payableAmount = Number(target.payableAmount || 0);
let applied = value === null || value === undefined || value === '' ? 0 : Number(value);
if (!Number.isFinite(applied) || applied < 0) applied = 0;
if (applied > payableAmount) applied = payableAmount;
target.appliedAmount = Number(applied.toFixed(2));
target.paymentRatio = settlementAmount
? Number(((target.appliedAmount / settlementAmount) * 100).toFixed(2))
: 0;
this.syncHeaderPaymentFromRows();
},
syncHeaderPaymentFromRows() {
if (this.referenceRows.length <= 1) return;
const settlementAmount = this.referenceRows.reduce(
(total, item) => total + Number(item.settlementAmount || 0),
0
);
const payableAmount = this.referenceRows.reduce(
(total, item) => total + Number(item.payableAmount || 0),
0
);
const appliedAmount = this.referenceRows.reduce(
(total, item) => total + Number(item.appliedAmount || 0),
0
);
this.amountSyncing = true;
this.form.settlementAmount = Number(settlementAmount.toFixed(2));
this.form.payableAmount = Number(payableAmount.toFixed(2));
this.form.appliedAmount = Number(appliedAmount.toFixed(2));
this.form.paymentRatio = settlementAmount
? Number(((appliedAmount / settlementAmount) * 100).toFixed(2))
: 0;
this.$nextTick(() => {
this.amountSyncing = false;
this.$refs.formRef?.validateField?.(['settlementAmount', 'paymentRatio', 'appliedAmount']);
});
},
payload() {
const {
id,
@@ -2947,11 +3129,15 @@ export default {
remark,
invoices,
} = this.form;
const usePreSettlementOnly =
paymentType === 'progress_advance' ||
(this.referenceRows.length > 0 &&
this.referenceRows.every(item => item.referenceType === 'pre'));
return {
id,
paymentType,
settlementId,
settlementIds,
settlementId: usePreSettlementOnly ? null : settlementId,
settlementIds: usePreSettlementOnly ? [] : settlementIds,
preSettlementId,
preSettlementIds,
projectId,
@@ -2968,6 +3154,12 @@ export default {
billType,
paymentRatio,
appliedAmount,
settlementLines: (this.referenceRows || []).map(item => ({
formalSettlementId: item.referenceType === 'formal' ? item.id : null,
preSettlementId: item.referenceType === 'pre' ? item.id : null,
paymentRatio: item.paymentRatio,
appliedAmount: item.appliedAmount,
})),
paymentMethod,
billLedgerId,
billNo,
@@ -3027,6 +3219,16 @@ export default {
ElMessage.warning('申请付款金额必填且必须大于0');
return false;
}
if (this.multipleSettlementRows) {
const invalidRow = this.referenceRows.find(
item => Number(item.appliedAmount || 0) > Number(item.payableAmount || 0)
);
if (invalidRow) {
this.clearMessages();
ElMessage.warning(`${invalidRow.settlementNo || '结算单'}付款金额不能超过可付款金额`);
return false;
}
}
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
this.clearMessages();
ElMessage.warning('请选择所属项目');