✨ 完善付款申请多结算行、结算导入编辑与计费规则展示
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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('请选择所属项目');
|
||||
|
||||
Reference in New Issue
Block a user