✨ 评分减分累计与最高扣分、用户部门标签联动树,并修复对账导入与结算调整等

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-23 04:28:22 +08:00
parent 8d9fe998c0
commit 2a5eca8393
17 changed files with 691 additions and 307 deletions
@@ -101,6 +101,17 @@
style="width: 500px"
/>
</el-form-item>
<el-form-item label="减分最高扣分">
<el-input
:model-value="configForm.maxMinusScore"
placeholder="所有减分项合计不得超过该分值"
clearable
style="width: 500px"
@input="handleMaxMinusScoreInput"
>
<template #append>分</template>
</el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="configForm.remark"
@@ -628,6 +639,7 @@ export default {
name: '',
remark: '',
standardDescription: '',
maxMinusScore: '',
status: 3,
categories: this.defaultCategories(),
standards: [],
@@ -684,6 +696,7 @@ export default {
});
return {
...detail,
maxMinusScore: this.normalizePlainDecimal(detail.maxMinusScore),
categories: categories.map(category => ({
...(map[category.categoryCode] || category),
categoryName: category.categoryName,
@@ -691,6 +704,14 @@ export default {
standards: (detail.standards || []).map(standard => this.normalizeStandard(standard)),
};
},
handleMaxMinusScoreInput(value) {
const raw = String(value ?? '')
.replace(/[^\d.]/g, '')
.replace(/^\./, '')
.replace(/(\..*)\./g, '$1')
.replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
this.configForm.maxMinusScore = raw;
},
openConfig(row, readonly = false) {
this.readonly = readonly;
if (row && row.id) {
@@ -816,6 +837,9 @@ export default {
},
prepareSubmitConfig(config) {
const payload = JSON.parse(JSON.stringify(config || {}));
const maxMinus = this.normalizePlainDecimal(payload.maxMinusScore);
payload.maxMinusScore =
maxMinus === '' || maxMinus == null ? null : this.toScoreValue(maxMinus);
(payload.categories || []).forEach(category => {
(category.items || []).forEach(item => {
item.score = this.toScoreValue(item.score);
@@ -1072,6 +1096,14 @@ export default {
this.$message.warning('请输入评定表名称');
return false;
}
if (
this.configForm.maxMinusScore !== '' &&
this.configForm.maxMinusScore != null &&
!this.isNonNegativeDecimal(this.configForm.maxMinusScore)
) {
this.$message.warning('减分最高扣分须为不小于0的数字,最多两位小数');
return false;
}
const basic = this.configForm.categories.find(item => item.categoryCode === 'basic');
if (!basic || basic.items.length === 0) {
this.$message.warning('至少配置1个基础得分项目');
+138 -38
View File
@@ -592,18 +592,27 @@
<span>{{ row.reviewStatus }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<el-table-column label="操作" min-width="220" fixed="right" align="center">
<template #default="{ row }">
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
详情
</el-link>
<el-link
v-if="showScoreDelete"
type="danger"
@click="deleteScore(row.__rawIndex)"
>
删除
</el-link>
<div class="score-list-table__actions">
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
详情
</el-link>
<el-link
v-if="canReviewScore(row)"
type="primary"
@click="startReviewScore(row.__raw, row.__rawIndex)"
>
复评
</el-link>
<el-link
v-if="showScoreDelete"
type="danger"
@click="deleteScore(row.__rawIndex)"
>
删除
</el-link>
</div>
</template>
</el-table-column>
</el-table>
@@ -2231,7 +2240,8 @@ export default {
showScoreDelete() {
if (this.readonly) return false;
const status = this.archiveForm.approvalStatus || 'draft';
return status === 'draft' || status === 'rejected';
// 草稿/驳回可删;审批通过后也允许删除并重建评分表(旧评分逻辑删除保留,新评分审核通过前仍用旧额度)
return status === 'draft' || status === 'rejected' || status === 'approved';
},
isNewScore() {
return this.scoreRecordIndex < 0 && !this.reviewMode;
@@ -2281,10 +2291,17 @@ export default {
{ categoryCode: 'plus', categoryName: '加分项目' },
{ categoryCode: 'minus', categoryName: '减分项目' },
];
const maxMinus = Number(this.currentScore?.maxMinusScore);
const hasMaxMinus = Number.isFinite(maxMinus) && maxMinus >= 0;
const rows = categoryMap.map((category, index) => {
const details = this.getScoreCategoryDetails(category.categoryCode);
const selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
const reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
let selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
let reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
// 减分项目合计受量化表「最高扣分」限制
if (category.categoryCode === 'minus' && hasMaxMinus) {
selfScore = Math.min(selfScore, maxMinus);
reviewScore = Math.min(reviewScore, maxMinus);
}
return {
...category,
index: index + 1,
@@ -4199,6 +4216,7 @@ export default {
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
maxCreditLimit: '',
maxMinusScore: '',
creditLevel: '',
standards: [],
tempApplyCreditLimit: '',
@@ -4218,6 +4236,10 @@ export default {
: score.applyCreditLimit
),
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
maxMinusScore:
score.maxMinusScore === undefined || score.maxMinusScore === null
? ''
: score.maxMinusScore,
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
details: (score.details || []).map(detail => ({
@@ -4256,7 +4278,14 @@ export default {
String(item.score.creditLevel || '').trim()
)[0]?.score;
},
/** 曾审核通过后:删除/重建评分在再次审核通过前,主表等级与额度保持旧值暂时生效 */
shouldFreezeEffectiveCredit(archive = this.archiveForm) {
return Boolean(archive?.approvedTime);
},
applyArchiveCreditFromScores(archive) {
if (this.shouldFreezeEffectiveCredit(archive)) {
return archive;
}
const latestScore = this.getLatestCreditScore(archive.scores || []);
archive.customerLevel = latestScore?.creditLevel || archive.customerLevel || '';
archive.maxCreditLimit = latestScore?.maxCreditLimit || archive.maxCreditLimit || '';
@@ -4588,9 +4617,31 @@ export default {
}
this.$nextTick(() => this.openScore(scores[0], 0));
},
/** 提交后详情页评分表:审批中且未完成复评时可复评 */
canReviewScore(row = {}) {
return (
this.hasPermission('customer_type_re_cert') &&
this.archiveForm.approvalStatus === 'reviewing' &&
row.reviewStatus !== '已完成'
);
},
startReviewScore(row, index = 0) {
if (!row) {
this.$message.warning('暂无评分记录,无法复评');
return;
}
// 详情只读进入时也要能改复评得分并确认
this.reviewMode = true;
this.openScore(row, index);
},
addScore() {
if ((this.archiveForm.scores || []).length) {
this.$message.warning('请勿重复添加');
const existingScores = this.archiveForm.scores || [];
if (existingScores.length) {
this.$message.warning(
this.shouldFreezeEffectiveCredit()
? '请先删除原评分记录后再添加新评分表'
: '请勿重复添加'
);
return;
}
this.scoreRecordIndex = -1;
@@ -4611,9 +4662,9 @@ export default {
const loadOptions = this.scoreQuantificationOptions.length
? Promise.resolve()
: this.initScoreQuantificationOptions();
loadOptions.then(() =>
this.ensureScoreQuantificationOption(this.currentScore.quantificationId)
);
loadOptions
.then(() => this.ensureScoreQuantificationOption(this.currentScore.quantificationId))
.then(() => this.applyMaxMinusScoreFromQuantification(this.currentScore.quantificationId));
},
resetScoreDialog() {
this.scoreRecordIndex = -1;
@@ -4648,6 +4699,11 @@ export default {
applyCreditLimit: this.currentScore.applyCreditLimit,
maxCreditLimit: '',
creditLevel: '',
maxMinusScore:
quantificationData.maxMinusScore ??
scoreTemplate.maxMinusScore ??
this.currentScore.maxMinusScore ??
'',
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
tempCreditStartDate: this.currentScore.tempCreditStartDate,
tempCreditEndDate: this.currentScore.tempCreditEndDate,
@@ -4664,6 +4720,17 @@ export default {
this.calculateScore(this.currentScore);
});
},
applyMaxMinusScoreFromQuantification(quantificationId) {
if (!quantificationId) return Promise.resolve();
return getCreditScoreQuantificationDetail(quantificationId).then(res => {
const data = res?.data?.data || {};
this.currentScore.maxMinusScore =
data.maxMinusScore === undefined || data.maxMinusScore === null
? ''
: data.maxMinusScore;
this.calculateScore(this.currentScore);
});
},
normalizeScoreTemplateDetails(template = {}) {
const categoryDetails = (template.categories || []).flatMap(category =>
(category.items || []).map(item => ({
@@ -4808,13 +4875,24 @@ export default {
if (!valid) return null;
const distance = increase ? input - base : base - input;
const changeValue = Number(rule.changeValue);
const stepSize = Number.isFinite(changeValue) && changeValue > 0 ? changeValue : 1;
const steps = Math.floor(distance / stepSize + 1e-9);
const stepScore = Math.abs(Number(rule.score || 0));
if (!Number.isFinite(stepScore)) return null;
if (!Number.isFinite(stepScore) || steps < 0) return null;
const projectScore = this.getScoreItemFullScore(detail);
// 减分项:按阶梯累计扣分,存正数,总分计算时再减;不做项目满分截断
if (this.isMinusScoreDetail(detail)) {
return Number((steps * stepScore).toFixed(2));
}
// 基础/加分项:从项目分值起步,按阶梯加减,限制在 [0, 项目满分]
const baseScore = Number(detail.score || 0);
const signedStep = rule.scoreType === 'subtract' ? -stepScore : stepScore;
const calculatedScore = Number(detail.score || 0) + distance * signedStep;
return Number(Math.min(Math.max(calculatedScore, 0), projectScore).toFixed(2));
const calculatedScore = baseScore + steps * signedStep;
const projectScore = this.getScoreItemFullScore(detail);
const upper = Math.max(projectScore, baseScore, 0);
return Number(Math.min(Math.max(calculatedScore, 0), upper).toFixed(2));
},
handleScoreValueInput(detail, value) {
const raw = String(value ?? '')
@@ -4995,14 +5073,17 @@ export default {
const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus');
const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus');
const sumScore = (list, prop) => list.reduce((sum, item) => sum + Number(item[prop] || 0), 0);
const rawMinusSelf = sumScore(minusDetails, 'selfScore');
const rawMinusReview = sumScore(minusDetails, 'reviewScore');
// 量化表「减分最高扣分」:所有减分项合计不得超过该上限
const maxMinus = Number(score.maxMinusScore);
const hasMaxMinus = Number.isFinite(maxMinus) && maxMinus >= 0;
const minusSelf = hasMaxMinus ? Math.min(rawMinusSelf, maxMinus) : rawMinusSelf;
const minusReview = hasMaxMinus ? Math.min(rawMinusReview, maxMinus) : rawMinusReview;
const selfScore =
sumScore(basicDetails, 'selfScore') +
sumScore(plusDetails, 'selfScore') -
sumScore(minusDetails, 'selfScore');
sumScore(basicDetails, 'selfScore') + sumScore(plusDetails, 'selfScore') - minusSelf;
const reviewScore =
sumScore(basicDetails, 'reviewScore') +
sumScore(plusDetails, 'reviewScore') -
sumScore(minusDetails, 'reviewScore');
sumScore(basicDetails, 'reviewScore') + sumScore(plusDetails, 'reviewScore') - minusReview;
const hasReviewScore = details.some(
item => !this.isBlankScore(item.reviewScore)
);
@@ -5076,7 +5157,11 @@ export default {
return pending;
},
deleteScore(index) {
this.$confirm('确定删除该评分记录?', '提示', {
const freezeCredit = this.shouldFreezeEffectiveCredit();
const confirmText = freezeCredit
? '确定删除该评分记录?删除后可重新添加评分表;原评分数据会保留,新评分再次审核通过前,客商等级与额度仍按原评分暂时生效。'
: '确定删除该评分记录?';
this.$confirm(confirmText, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
@@ -5089,16 +5174,23 @@ export default {
} else if (this.scoreRecordIndex > index) {
this.scoreRecordIndex -= 1;
}
const latestScore = this.getLatestCreditScore(this.archiveForm.scores);
if (latestScore) {
this.syncArchiveCreditFromScores();
} else {
this.archiveForm.customerLevel = '';
this.archiveForm.maxCreditLimit = '';
this.archiveForm.applyCreditLimit = '';
if (!freezeCredit) {
const latestScore = this.getLatestCreditScore(this.archiveForm.scores);
if (latestScore) {
this.syncArchiveCreditFromScores();
} else {
this.archiveForm.customerLevel = '';
this.archiveForm.maxCreditLimit = '';
this.archiveForm.applyCreditLimit = '';
}
}
this.handleScoreCurrentChange(this.scorePage.currentPage);
this.saveScoreArchive('评分记录已删除', '评分记录已删除,请保存客商档案后生效');
this.saveScoreArchive(
freezeCredit
? '评分记录已删除,原等级与额度暂时继续生效'
: '评分记录已删除',
'评分记录已删除,请保存客商档案后生效'
);
})
.catch(() => {});
},
@@ -5795,6 +5887,14 @@ export default {
}
}
.score-list-table__actions {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 8px;
}
.contact-form,
.receipt-form,
.invoice-form {