Merge remote-tracking branch 'origin/master' into feature/hcs-api-p0-web

# Conflicts:
#	src/views/business/contract-manage-change.vue
This commit is contained in:
刘泉佳
2026-10-23 19:20:12 +08:00
27 changed files with 1924 additions and 572 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个基础得分项目');
+341 -168
View File
@@ -398,47 +398,6 @@
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="经营范围" prop="businessScope">
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
<el-option
v-for="item in businessScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="营业期限"
prop="businessEndDate"
:required="archiveForm.businessTermType === '固定期限'"
:rules="businessEndDateRules"
>
<div class="business-term-field">
<el-date-picker
v-model="archiveForm.businessEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="archiveForm.businessTermType === '长期'"
@change="handleBusinessEndDateChange"
/>
<el-checkbox
:model-value="archiveForm.businessTermType === '长期'"
@change="handleNoFixedTermChange"
>
无固定期限
</el-checkbox>
</div>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="助记码" prop="mnemonicCode">
<el-input v-model="archiveForm.mnemonicCode" maxlength="15" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客户简称" prop="shortName">
<el-input
@@ -499,38 +458,38 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
</section-card>
<section-card title="联系信息">
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="客商负责人" prop="principal">
<el-input v-model="archiveForm.principal" maxlength="10" show-word-limit />
<el-col :span="12">
<el-form-item
label="营业期限"
prop="businessEndDate"
:required="archiveForm.businessTermType === '固定期限'"
:rules="businessEndDateRules"
>
<div class="business-term-field">
<el-date-picker
v-model="archiveForm.businessStartDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="开始日期"
/>
<span class="business-term-separator">至</span>
<el-date-picker
v-model="archiveForm.businessEndDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="截止日期"
:disabled="archiveForm.businessTermType === '长期'"
@change="handleBusinessEndDateChange"
/>
<el-checkbox
:model-value="archiveForm.businessTermType === '长期'"
@change="handleNoFixedTermChange"
>
无固定期限
</el-checkbox>
</div>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="archiveForm.contactPhone" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属组织" prop="deptName">
<el-cascader
v-model="deptCascaderValue"
:options="deptTree"
:props="deptCascaderProps"
disabled
placeholder="广西现代物流集团有限公司"
/>
</el-form-item>
</el-col>
<el-col :span="6"></el-col>
</el-row>
</section-card>
<section-card title="财务/信用信息">
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="注册资金(万元)" prop="registeredCapital">
<el-input
@@ -542,11 +501,12 @@
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="开票税点(%)" prop="invoiceTaxRate">
<el-form-item label="实缴资本(万元)" prop="paidInCapital">
<el-input
v-model="archiveForm.invoiceTaxRate"
maxlength="5"
@input="value => handleDecimalInput('invoiceTaxRate', value, 13)"
v-model="archiveForm.paidInCapital"
maxlength="18"
:disabled="kingdeeLocked"
@input="value => handleDecimalInput('paidInCapital', value)"
/>
</el-form-item>
</el-col>
@@ -570,6 +530,29 @@
<el-input v-model="archiveForm.applyCreditLimit" maxlength="18" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="已使用额度(万元)" prop="usedCreditLimit">
<el-input
v-model="archiveForm.usedCreditLimit"
maxlength="18"
:disabled="readonly || Boolean(archiveForm.id)"
placeholder="新建可录历史已使用额度"
@input="value => handleDecimalInput('usedCreditLimit', value)"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="经营范围" prop="businessScope" class="archive-form__remark">
<el-input
v-model="archiveForm.businessScope"
type="textarea"
:rows="4"
maxlength="2000"
show-word-limit
placeholder="可通过营业执照 OCR 自动识别,也可手动填写"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark" class="archive-form__remark">
<el-input
@@ -644,18 +627,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>
@@ -1155,7 +1147,14 @@
/>
</el-form-item>
<el-form-item label="开户行名称" prop="bankName">
<el-input v-model="invoiceForm.bankName" maxlength="100" />
<el-autocomplete
v-model="invoiceForm.bankName"
:fetch-suggestions="queryBankNameSuggestions"
clearable
maxlength="100"
placeholder="请选择或输入开户行名称"
class="invoice-bank-autocomplete"
/>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -1656,8 +1655,10 @@ export default {
const nonNegativeLabelMap = {
invoiceTaxRate: '开票税点',
registeredCapital: '注册资金',
paidInCapital: '实缴资本',
maxCreditLimit: '最大资金使用额度',
applyCreditLimit: '申请总资金使用额度',
usedCreditLimit: '已使用额度',
};
const validateNonNegative = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
@@ -1668,15 +1669,6 @@ export default {
callback();
}
};
const validateInvoiceTaxRate = (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback();
} else if (Number(value) < 0 || Number(value) > 13) {
callback(new Error('开票税点必须在0到13之间'));
} else {
callback();
}
};
const validateUnifiedCreditCode = (rule, value, callback) => {
const code = String(value || '')
.trim()
@@ -1806,7 +1798,7 @@ export default {
{ label: '是', value: 1 },
{ label: '否', value: 0 },
],
businessScopeOptions: [],
bankListOptions: [],
qualificationTypeOptions: [],
creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({
label: `${item}级`,
@@ -1845,15 +1837,12 @@ export default {
],
customerType: [{ required: true, message: '请选择客商分类', trigger: 'change' }],
legalPerson: [{ required: true, message: '请输入企业法人', trigger: 'blur' }],
contactPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1\d{10}$/, message: '联系电话必须为11位手机号', trigger: 'blur' },
],
deptName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
invoiceTaxRate: [{ validator: validateInvoiceTaxRate, trigger: 'blur' }],
registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
paidInCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
usedCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
businessScope: [{ max: 2000, message: '经营范围最多2000个字符', trigger: 'blur' }],
registeredRegionName: [{ required: true, message: '请选择省市区', trigger: 'change' }],
registeredDetailAddress: [
{ required: true, message: '请输入详细地址', trigger: 'blur' },
@@ -2296,7 +2285,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;
@@ -2346,10 +2336,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,
@@ -2406,7 +2403,6 @@ export default {
ensurePublicViewOptions(archive = {}) {
this.mergeSelectOptions('customerNatureOptions', archive.customerNature);
this.mergeSelectOptions('customerTypeOptions', archive.customerType);
this.mergeSelectOptions('businessScopeOptions', archive.businessScope);
this.mergeSelectOptions(
'qualificationTypeOptions',
(this.qualificationFiles || []).map(item => item.type)
@@ -2464,9 +2460,11 @@ export default {
form.fullName = '';
form.legalPerson = '';
form.registeredCapital = '';
form.paidInCapital = '';
form.registeredDetailAddress = '';
form.businessScope = [];
form.businessScope = '';
form.businessTermType = '固定期限';
form.businessStartDate = '';
form.businessEndDate = '';
},
// 新增态下统一社会信用代码录入完成后自动查询金蝶(A902-1),防抖 500ms;
@@ -2525,14 +2523,7 @@ export default {
}
if (customer.businessterm) this.applyBusinessTermRecognition(customer.businessterm);
if (customer.businessscope) {
const matchedScopes = this.businessScopeOptions
.filter(
item =>
customer.businessscope.includes(item.label) ||
customer.businessscope.includes(item.value)
)
.map(item => item.value);
if (matchedScopes.length) form.businessScope = matchedScopes;
form.businessScope = String(customer.businessscope).trim();
}
if (!form.shortName && customer.simplename) form.shortName = customer.simplename;
this.$nextTick(() => {
@@ -2541,6 +2532,8 @@ export default {
'fullName',
'legalPerson',
'registeredCapital',
'paidInCapital',
'businessStartDate',
'businessEndDate',
'businessScope',
'registeredDetailAddress',
@@ -2563,8 +2556,13 @@ export default {
deptIds: '',
deptName: '',
customerType: [],
businessScope: [],
businessScope: '',
businessTermType: '固定期限',
businessStartDate: '',
businessEndDate: '',
registeredCapital: '',
paidInCapital: '',
usedCreditLimit: '',
networkFreightPlatform: null,
guangxiTop100: null,
registeredRegionPath: [],
@@ -2699,6 +2697,12 @@ export default {
approvedTime: '审核通过时间',
status: '状态',
businessTermType: '营业期限类型',
businessStartDate: '营业期限开始日',
businessEndDate: '营业期限截止日',
registeredCapital: '注册资金(万元)',
paidInCapital: '实缴资本(万元)',
usedCreditLimit: '已使用额度(万元)',
businessScope: '经营范围',
networkFreightPlatform: '网络货运平台',
registeredRegionName: '注册地址',
registeredDetailAddress: '详细地址',
@@ -3147,11 +3151,21 @@ export default {
const customerTypeColumn = this.findColumn(this.option.column, 'customerType');
customerTypeColumn.dicData = [{ label: '全部', value: '' }, ...options];
});
this.loadDictOptions('scope_of_business', 'businessScopeOptions');
this.loadDictOptions('bank_list', 'bankListOptions');
this.loadDictOptions('customer_attachment_types', 'qualificationTypeOptions', [], () => {
this.sortQualificationFiles();
});
},
queryBankNameSuggestions(queryString, callback) {
const query = String(queryString || '')
.trim()
.toLowerCase();
const options = Array.isArray(this.bankListOptions) ? this.bankListOptions : [];
const matched = query
? options.filter(item => String(item.value || '').toLowerCase().includes(query))
: options;
callback(matched.map(item => ({ value: item.value })));
},
initMkEnabled() {
this._mkEnabledResolved = false;
loadMkEnabled()
@@ -3405,8 +3419,15 @@ export default {
});
},
formatBusinessLicenseValidPeriod(row = {}) {
if (row.businessTermType === '长期') return '无固定期限';
return row.businessEndDate || '';
if (row.businessTermType === '长期') {
return row.businessStartDate
? `${row.businessStartDate} 至 无固定期限`
: '无固定期限';
}
if (row.businessStartDate && row.businessEndDate) {
return `${row.businessStartDate} 至 ${row.businessEndDate}`;
}
return row.businessEndDate || row.businessStartDate || '';
},
formatFundUseRate(row = {}) {
if (row.fundUseRate === undefined || row.fundUseRate === null || row.fundUseRate === '') {
@@ -3939,8 +3960,13 @@ export default {
const legalPerson = this.getBusinessLicenseOcrValue(result, ['法定代表人', '法人']);
const address = this.getBusinessLicenseOcrValue(result, ['地址', '住所']);
const capital = this.getBusinessLicenseOcrValue(result, ['注册资本']);
const paidInCapital = this.getBusinessLicenseOcrValue(result, ['实缴资本', '实收资本']);
const term = this.getBusinessLicenseOcrValue(result, ['营业期限', '有效期']);
const termStartDate = this.getBusinessLicenseOcrValue(result, ['有效期起始日期']);
const termStartDate = this.getBusinessLicenseOcrValue(result, [
'有效期起始日期',
'成立日期',
'核准日期',
]);
const scope = this.getBusinessLicenseOcrValue(result, ['经营范围']);
if (code) this.archiveForm.unifiedCreditCode = code.replace(/\s/g, '').toUpperCase();
if (name) this.archiveForm.fullName = name;
@@ -3955,15 +3981,16 @@ export default {
const normalizedCapital = capital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
if (normalizedCapital) this.archiveForm.registeredCapital = normalizedCapital[0];
}
this.applyBusinessTermRecognition(term);
if (paidInCapital) {
const normalizedPaidIn = paidInCapital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
if (normalizedPaidIn) this.archiveForm.paidInCapital = normalizedPaidIn[0];
}
this.applyBusinessTermRecognition(term, termStartDate);
if (this.isNoFixedBusinessTerm(term) || this.isNoFixedBusinessTerm(termStartDate)) {
this.handleNoFixedTermChange(true);
}
if (scope) {
const matchedScopes = this.businessScopeOptions
.filter(item => scope.includes(item.label) || scope.includes(item.value))
.map(item => item.value);
if (matchedScopes.length) this.archiveForm.businessScope = matchedScopes;
this.archiveForm.businessScope = scope;
}
this.$nextTick(() => {
[
@@ -3971,27 +3998,49 @@ export default {
'fullName',
'legalPerson',
'registeredCapital',
'paidInCapital',
'businessStartDate',
'businessEndDate',
'businessScope',
'registeredDetailAddress',
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
});
},
applyBusinessTermRecognition(term) {
normalizeOcrDate(value) {
if (!value) return '';
const match = String(value).match(/(\d{4})[年./-](\d{1,2})[月./-](\d{1,2})/);
if (!match) return '';
const month = String(match[2]).padStart(2, '0');
const day = String(match[3]).padStart(2, '0');
return `${match[1]}-${month}-${day}`;
},
applyBusinessTermRecognition(term, termStartDate = '') {
const startFromField = this.normalizeOcrDate(termStartDate);
if (startFromField) {
this.archiveForm.businessStartDate = startFromField;
}
if (!term) return;
if (/长期|永久/.test(term)) {
if (/长期|永久|无固定/.test(term) || this.isNoFixedBusinessTerm(term)) {
this.archiveForm.businessTermType = '长期';
this.archiveForm.businessEndDate = '';
return;
}
const match = term.match(/(\d{4}[年./-]\d{1,2}[月./-]\d{1,2})/);
if (!match) return;
const date = match[1]
.replace(/[年./]/g, '-')
.replace('月', '-')
.replace('日', '');
this.archiveForm.businessTermType = '固定期限';
this.archiveForm.businessEndDate = date;
const dateMatches = String(term).match(/\d{4}[年./-]\d{1,2}[月./-]\d{1,2}/g) || [];
const startDate = this.normalizeOcrDate(dateMatches[0]) || startFromField;
const endDate = this.normalizeOcrDate(dateMatches[1]);
if (startDate) {
this.archiveForm.businessStartDate = startDate;
}
if (endDate) {
this.archiveForm.businessTermType = '固定期限';
this.archiveForm.businessEndDate = endDate;
return;
}
if (startDate && dateMatches.length === 1 && !/至|到|-|—|~/.test(term)) {
// 仅识别到一个日期且无区间分隔时,按截止日处理(兼容旧 OCR 结果)
this.archiveForm.businessTermType = '固定期限';
this.archiveForm.businessEndDate = startDate;
}
},
getQualificationFileUrl(file = {}) {
return file.url || file.link || file.domain || '';
@@ -4159,7 +4208,6 @@ export default {
return value;
},
normalizeDetail(detail) {
const businessScope = this.splitValue(detail.businessScope);
const registeredDetailAddress =
detail.registeredDetailAddress ||
(detail.registeredRegionName ? '' : detail.registeredAddress) ||
@@ -4172,10 +4220,14 @@ export default {
customerKind: detail.customerKind || 'external',
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
paidInCapital: this.normalizeOptionalAmount(detail.paidInCapital),
maxCreditLimit: this.normalizeOptionalAmount(detail.maxCreditLimit),
applyCreditLimit: this.normalizeOptionalAmount(detail.applyCreditLimit),
businessScope,
usedCreditLimit: this.normalizeOptionalAmount(detail.usedCreditLimit),
businessScope: detail.businessScope || '',
businessTermType: detail.businessTermType || '固定期限',
businessStartDate: detail.businessStartDate || '',
businessEndDate: detail.businessEndDate || '',
networkFreightPlatform:
detail.networkFreightPlatform === 0 || detail.networkFreightPlatform === 1
? detail.networkFreightPlatform
@@ -4211,6 +4263,7 @@ export default {
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
maxCreditLimit: '',
maxMinusScore: '',
creditLevel: '',
standards: [],
tempApplyCreditLimit: '',
@@ -4230,6 +4283,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 => ({
@@ -4268,7 +4325,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 || '';
@@ -4397,16 +4461,21 @@ export default {
if (archive.businessTermType === '长期') {
archive.businessEndDate = null;
}
if (!archive.businessStartDate) {
archive.businessStartDate = null;
}
const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
const paidInCapital = this.normalizeOptionalAmount(archive.paidInCapital);
const usedCreditLimit = this.normalizeOptionalAmount(archive.usedCreditLimit);
archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
archive.paidInCapital = paidInCapital === '' ? null : paidInCapital;
archive.usedCreditLimit = usedCreditLimit === '' ? null : usedCreditLimit;
archive.customerType = Array.isArray(archive.customerType)
? archive.customerType.join(',')
: archive.customerType;
archive.businessScope = Array.isArray(archive.businessScope)
? archive.businessScope.join(',')
: archive.businessScope;
archive.businessScope = String(archive.businessScope || '').trim();
archive.networkFreightPlatform =
archive.networkFreightPlatform === 0 || archive.networkFreightPlatform === 1
? archive.networkFreightPlatform
@@ -4437,6 +4506,11 @@ export default {
return contact;
})
.filter(item => item.contactName || item.contactPhone);
// 主表联系电话改由联系人维护,取第一条有效手机号回填
const firstContactPhone = (archive.contacts || [])
.map(item => String(item.contactPhone || '').trim())
.find(Boolean);
archive.contactPhone = firstContactPhone || null;
archive.receiptAccounts = (archive.receiptAccounts || [])
.map(item => this.normalizeReceipt(item))
.filter(item => item.accountName || item.accountHolderName || item.bankAccount || item.cnapsCode);
@@ -4475,16 +4549,29 @@ export default {
archive.applyCreditLimit = applyCreditLimit === '' ? null : applyCreditLimit;
return archive;
},
validateContactsRequired(archive = this.archiveForm) {
const contacts = (archive.contacts || []).filter(
item => String(item.contactName || '').trim() || String(item.contactPhone || '').trim()
);
if (!contacts.length) {
this.$message.warning('请至少维护一条联系人');
this.activeTab = 'contacts';
return false;
}
return true;
},
saveArchive() {
if (this.kingdeeBlocked) {
this.$message.warning('金蝶审核中,请稍后重新添加');
return;
}
this.applyFixedOwnerDept();
this.$refs.archiveForm.validate(valid => {
if (!valid) {
this.$message.warning('请先完整填写必填项');
return;
}
if (!this.validateContactsRequired()) return;
submit(this.normalizeArchive()).then(() => {
this.closeArchive();
this.onLoad(this.page);
@@ -4497,11 +4584,13 @@ export default {
this.$message.warning('金蝶审核中,请稍后重新添加');
return;
}
this.applyFixedOwnerDept();
this.$refs.archiveForm.validate(valid => {
if (!valid) {
this.$message.warning('请先完整填写必填项');
return;
}
if (!this.validateContactsRequired()) return;
const archive = this.normalizeArchive();
// 仅提交时落变更记录;保存不记录
archive.recordChange = true;
@@ -4577,9 +4666,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;
@@ -4600,9 +4711,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;
@@ -4637,6 +4748,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,
@@ -4653,6 +4769,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 => ({
@@ -4797,13 +4924,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 ?? '')
@@ -4984,14 +5122,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)
);
@@ -5065,7 +5206,11 @@ export default {
return pending;
},
deleteScore(index) {
this.$confirm('确定删除该评分记录?', '提示', {
const freezeCredit = this.shouldFreezeEffectiveCredit();
const confirmText = freezeCredit
? '确定删除该评分记录?删除后可重新添加评分表;原评分数据会保留,新评分再次审核通过前,客商等级与额度仍按原评分暂时生效。'
: '确定删除该评分记录?';
this.$confirm(confirmText, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
@@ -5078,16 +5223,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(() => {});
},
@@ -5223,6 +5375,7 @@ export default {
getDetail(row.id).then(res => {
const archive = this.normalizeDetail(res.data.data);
archive.qualificationAttachments = archive.qualificationAttachments || '';
if (!this.validateContactsRequired(archive)) return;
if (!this.validateScoreForApproval(archive)) return;
this.$confirm('是否提交客商准入审批?', '提示', {
confirmButtonText: '确定',
@@ -5604,7 +5757,8 @@ export default {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
flex-wrap: wrap;
width: 100%;
:deep(.el-date-editor) {
flex: 0 0 140px;
@@ -5612,6 +5766,11 @@ export default {
min-width: 0;
}
.business-term-separator {
flex: 0 0 auto;
color: #606266;
}
:deep(.el-checkbox) {
flex: 0 0 auto;
margin-right: 0;
@@ -5619,6 +5778,10 @@ export default {
}
}
.invoice-bank-autocomplete {
width: 100%;
}
.archive-form__address {
:deep(.el-cascader) {
flex: 0 0 240px;
@@ -5827,6 +5990,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 {
@@ -5846,11 +6017,13 @@ export default {
:deep(.el-input),
:deep(.el-cascader),
:deep(.el-select) {
:deep(.el-select),
:deep(.el-autocomplete) {
width: 100%;
}
:deep(.el-cascader .el-input) {
:deep(.el-cascader .el-input),
:deep(.el-autocomplete .el-input) {
width: 100%;
}
}