fix: 修复匹配详情提示及公积金可选路径问题

修复匹配详情中“用户自填未经核验”等来源说明,清理未参与项的橙色注意提示,修正贷款统计口径文案;补充公积金完整可选路径提取逻辑,确保明确无额外时长要求的公积金分支可正确识别并通过。
This commit is contained in:
hujun
2026-10-19 16:11:59 +08:00
parent c95482a0f3
commit e5406c23a6
75 changed files with 31813 additions and 107 deletions
+36 -14
View File
@@ -2,6 +2,7 @@ import { revisionParams } from './records.uts'
import { requestJson } from '@/services/api.uts'
import { API_BASE_URL } from '@/services/config.uts'
import { yuanToWan } from '@/services/money.uts'
import { provinceOptions } from '@/services/cities.uts'
export type ProductMaterial = { name: string, note: string, required: boolean, objects: string }
export type ProductView = { logo: string, materialList: ProductMaterial[], consultPhone: string, repayment: string, channel: string, approval: string, id: string, version: number, name: string, bank: string, type: string, demo: boolean, amount: string, rate: string, term: string, objects: string, regions: string, valid: string, materials: string, source: string, sourceRef: string, checkedOn: string, conditions: string[] }
@@ -12,16 +13,23 @@ export type MatchPage = { assessmentId: string, revisionId: string, resultVersio
function text(data: UTSJSONObject, key: string): string { return data.getString(key) ?? '' }
export function validRecordId(value: string): boolean { return /^[1-9][0-9]{0,9}$/.test(value) }
export function eligibilityLabel(value: string): string { return value == 'eligible' ? '符合已知条件' : (value == 'ineligible' ? '条件不符' : '待确认') }
export function eligibilityLabel(value: string): string { return value == 'eligible' ? '符合已知条件' : (value == 'ineligible' ? '条件不符' : '待核实') }
export function hasProductValue(value: string): boolean { return value.trim().length > 0 && !['未提供', '待核实', '—'].includes(value.trim()) }
export function matchPercentage(state: string, score: number | null): string { return state == 'eligible' && score != null ? score.toString() + '%' : '' }
const fieldNames: UTSJSONObject = { requestedAmountYuan: '融资金额', requestedTermMonths: '融资期限', applicantType: '申请主体', purposeCode: '资金用途', cityCode: '所在城市', monthlyIncome: '月收入', annualRevenue: '年营收', operatingMonths: '经营月数', totalDebtBalance: '贷款余额', monthlyLoanPayment: '每月还款额', creditLoanCount: '贷款笔数', creditCardCount: '信用卡数量', creditCardTotalLimit: '信用卡总额度', creditCardUsedLimit: '信用卡已用额度', socialSecurity: '社保情况', housingFund: '公积金情况', house: '房产情况', car: '车辆情况', establishedMonths: '企业成立月数', annualInvoiceAmount: '年开票金额', taxPaymentMonths: '连续纳税月数', taxpayerGrade: '纳税等级', enterpriseCreditStatus: '企业信用状态', annualRevenueGrowthPercent: '年营收增长率', debtRatioPercent: '负债率', cashFlowStability: '经营流水稳定性', industryCategory: '行业类别' }
export function matchPercentage(state: string, score: number | null): string { return ['eligible','unknown','ineligible'].includes(state) && score != null ? score.toString() + '%' : '' }
const fieldNames: UTSJSONObject = { age: '年龄', occupation: '职业类型', employer: '单位名称', requestedAmountYuan: '融资金额', requestedTermMonths: '融资期限', applicantType: '申请主体', purposeCode: '资金用途', cityCode: '所在城市', provinceCode: '所在省份', monthlyIncome: '月收入', annualRevenue: '年营收', operatingMonths: '经营月数', totalDebtBalance: '贷款余额', monthlyLoanPayment: '每月还款额', creditLoanCount: '贷款笔数', creditCardCount: '信用卡数量', creditCardTotalLimit: '信用卡总额度', creditCardUsedLimit: '信用卡已用额度', socialSecurity: '社保情况', housingFund: '公积金情况', house: '房产情况', car: '车辆情况', establishedMonths: '企业成立月数', annualInvoiceAmount: '年开票金额', taxPaymentMonths: '连续纳税月数', taxpayerGrade: '纳税等级', enterpriseCreditStatus: '企业信用状态', annualRevenueGrowthPercent: '年营收增长率', debtRatioPercent: '负债率', cashFlowStability: '经营流水稳定性', industryCategory: '行业类别' }
const labels: UTSJSONObject = { personal: '个人', enterprise: '企业', daily_consumption: '日常消费', education: '教育进修', home_improvement: '家居装修', personal_business: '个体经营周转', working_capital: '经营周转', inventory_purchase: '采购备货', equipment_purchase: '设备购置', business_expansion: '经营扩展', other: '其他用途', none: '无', under_six_months: '不足六个月', six_months_plus: '六个月及以上', yes: '有', no: '无', normal: '正常', abnormal: '异常', stable: '稳定', unstable: '不稳定', manufacturing: '制造业', trade: '贸易', services: '服务业', construction: '建筑业', agriculture: '农业', finance: '金融业', real_estate: '房地产业', '440300': '深圳市', '440100': '广州市', '110100': '北京市', '310100': '上海市', '330100': '杭州市' }
const moneyFields = ['requestedAmountYuan','monthlyIncome','annualRevenue','totalDebtBalance','monthlyLoanPayment','creditCardTotalLimit','creditCardUsedLimit','annualInvoiceAmount']
const advisoryReasonCodes = ['AI_CONDITION_NOT_ASSESSED', 'PROFILE_DETAIL_NOT_ASSESSED']
function displayReasonText(value: string): string {
return value.replace(/((?:根据自填资料判断|资料尚需核验|用户自填[,、]未经核验|自填信息未经核验|产品快照))/g, '').replace(/自填信息未经核验;本页面不承诺额度、放款或获批。/g, '结果仅供参考,不代表审批结果。').trim()
}
function valueText(value: any | null, field: string): string {
if (value == null) return '未提供'
const raw = value.toString()
if (field == 'provinceCode') return provinceOptions.find((item): boolean => item.code == raw)?.name ?? '待核实'
if (field == 'age') return raw + ' 岁'
if (moneyFields.includes(field)) return yuanToWan(raw) + ' 万元'
if (field == 'requestedTermMonths' || field == 'operatingMonths' || field == 'establishedMonths' || field == 'taxPaymentMonths') return raw + ' 个月'
if (field == 'debtRatioPercent' || field == 'annualRevenueGrowthPercent') return raw + '%'
@@ -81,24 +89,37 @@ function matchView(data: UTSJSONObject): MatchView {
const evidence = data['evidence'] as UTSJSONObject[]
const ai = data['aiExplanation'] as UTSJSONObject
const explanations = (ai['reasonExplanations'] as UTSJSONObject[] | null) ?? []
const reasons = (key: string): MatchReason[] => (data[key] as UTSJSONObject[]).map((r: UTSJSONObject): MatchReason => {
const hiddenAdvisory = (reason: UTSJSONObject): boolean => {
const code = text(reason, 'code')
if (!advisoryReasonCodes.includes(code)) return false
const refs = (reason['evidenceRefs'] as string[] | null) ?? []
return refs.length > 0 && refs.every((ref: string): boolean => {
const item = evidence.find((entry: UTSJSONObject): boolean => text(entry, 'evidenceId') == ref)
if (item == null || text(item, 'reasonCode') != code || text(item, 'outcome') != 'unknown') return false
const source = text(item, 'source')
return code == 'AI_CONDITION_NOT_ASSESSED' ? source == 'ai_self_reported' : ['self_reported', 'product_snapshot'].includes(source)
})
}
// 仅隐藏证据一致的未参与判断提示;异常记录与原始快照保持可追溯。
const reasons = (key: string): MatchReason[] => (data[key] as UTSJSONObject[]).filter((r: UTSJSONObject): boolean => key != 'cautions' || !hiddenAdvisory(r)).map((r: UTSJSONObject): MatchReason => {
const reasonFields: string[] = []
const reasonText = ['AI_CONDITION_PASS', 'AI_CONDITION_FAIL'].includes(text(r, 'code')) ? text(r, 'text').replace('(根据自填资料判断)', '') : text(r, 'text')
const lines = (r['evidenceRefs'] as string[]).map((ref: string): string => {
const reasonText = displayReasonText(text(r, 'text'))
const lines = ((r['evidenceRefs'] as string[] | null) ?? []).map((ref: string): string => {
const e = evidence.find((item: UTSJSONObject): boolean => text(item, 'evidenceId') == ref)
if (e == null) return '证据暂不可读取'
const fields = e['fieldRefs'] as string[]; const field = fields.length > 0 ? fields[0] : ''
fields.forEach((name: string) => { if (!reasonFields.includes(name)) reasonFields.push(name) })
if (text(e, 'source') == 'ai_self_reported') {
if (e['actual'] != null && fieldNames.getString(field) != null) return fieldNames.getString(field)! + ':' + valueText(e['actual'], field)
const names = fields.map((name: string): string => fieldNames.getString(name) ?? '').filter((name: string): boolean => name.length > 0)
if (e['actual'] != null && (field == 'age' || fieldNames.getString(field) != null)) return (field == 'age' ? '年龄' : fieldNames.getString(field)!) + ':' + valueText(e['actual'], field)
const names = fields.map((name: string): string => name == 'age' ? '年龄' : (fieldNames.getString(name) ?? '')).filter((name: string): boolean => name.length > 0)
return names.length > 0 ? '关联资料:' + names.join('、') : reasonText
}
const source = text(e, 'source') == 'verified' ? '外部核验' : (text(e, 'source') == 'product_snapshot' ? '产品快照' : (text(e, 'source') == 'self_reported' ? '用户自填,未经核验' : '来源未知'))
return (fieldNames.getString(field) ?? field) + ':' + valueText(e['actual'], field) + ' · ' + source
const source = text(e, 'source')
const sourceNote = ['self_reported', 'product_snapshot'].includes(source) ? '' : ' · ' + (source == 'verified' ? '外部核验' : '来源未知')
return (fieldNames.getString(field) ?? field) + ':' + valueText(e['actual'], field) + sourceNote
})
const explanation = explanations.find((item: UTSJSONObject): boolean => text(item, 'reasonId') == text(r, 'reasonId'))
return { id: text(r, 'reasonId'), text: reasonText, evidence: lines, fields: reasonFields, explanation: text(ai, 'status') == 'completed' && explanation != null ? text(explanation, 'text') : '' }
return { id: text(r, 'reasonId'), text: reasonText, evidence: lines, fields: reasonFields, explanation: text(ai, 'status') == 'completed' && explanation != null ? displayReasonText(text(explanation, 'text')) : '' }
})
const availability = text(data, 'currentAvailability')
const score = data.getNumber('score')
@@ -108,7 +129,7 @@ function matchView(data: UTSJSONObject): MatchView {
return { score, id: text(data, 'productId'), state: text(data, 'eligibility'), stateLabel: missingData ? '资料不足' : eligibilityLabel(text(data, 'eligibility')), availability,
availabilityLabel: availability == 'available' ? '当前产品可查看' : (availability == 'unavailable' ? '当前产品已下架' : '当前产品状态暂无法确认'),
product: productView(data['productSnapshot'] as UTSJSONObject, false), positive: reasons('positiveReasons'), blocking: reasons('blockingReasons'), cautions: reasons('cautions'),
aiText: text(ai, 'status') == 'completed' ? text(ai, 'text') : (text(ai, 'status') == 'failed' ? 'AI解释暂不可用,已保留全部规则依据。' : '当前展示规则依据,尚未生成单产品AI解释。'),
aiText: text(ai, 'status') == 'completed' ? displayReasonText(text(ai, 'text')) : (text(ai, 'status') == 'failed' ? 'AI解释暂不可用,已保留全部规则依据。' : '当前展示规则依据,尚未生成单产品AI解释。'),
aiStatus: text(ai, 'status'), aiCanRetry: ai.getBoolean('canRetry') ?? false, aiCanRequest: data.getBoolean('canRequestExplanation') ?? false, aiGeneratedAt: text(ai, 'generatedAt') }
}
export async function requestProductExplanation(id: string, productId: string, requestId: string, revisionId: string = ''): Promise<MatchView> {
@@ -116,6 +137,7 @@ export async function requestProductExplanation(id: string, productId: string, r
return detailRecord(data, id, productId, revisionId).item
}
export async function fetchMatchPage(id: string, group: string, cursor: string, revisionId: string = ''): Promise<MatchPage> {
if (!['all','eligible','unknown','ineligible'].includes(group)) throw new Error('匹配分组无效,请重新选择。')
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches', 'GET', { ...revisionParams(revisionId), eligibility: group, limit: '3', cursor })
const matching = data['matching'] as UTSJSONObject; const status = text(matching, 'status')
const resolvedRevision = text(data, 'revisionId'); const version = data.getNumber('resultVersion') ?? 0
@@ -123,7 +145,7 @@ export async function fetchMatchPage(id: string, group: string, cursor: string,
const items = (data['items'] as UTSJSONObject[]).map((v: UTSJSONObject): MatchView => matchView(v))
const hasMore = data.getBoolean('hasMore') ?? false; const nextCursor = text(data, 'nextCursor')
const ids: string[] = []
items.forEach((item: MatchView) => { if (item.state != group || ids.includes(item.id)) throw new Error('匹配分组或产品编号不一致,请重新加载。'); ids.push(item.id) })
items.forEach((item: MatchView) => { if ((group != 'all' && item.state != group) || ids.includes(item.id)) throw new Error('匹配分组或产品编号不一致,请重新加载。'); ids.push(item.id) })
if (!['completed','unavailable'].includes(status) || items.length > 3 || (hasMore && (items.length == 0 || nextCursor.length == 0)) || (!hasMore && nextCursor.length > 0) || (status == 'unavailable' && (items.length > 0 || hasMore))) throw new Error('匹配分页状态不完整,请重新加载。')
if (status == 'completed') {
if (text(matching, 'catalogSnapshotId').length == 0 || text(matching, 'ruleVersion').length == 0 || text(matching, 'orderingPolicyVersion').length == 0) throw new Error('匹配快照版本不完整,请重新加载。')
@@ -132,7 +154,7 @@ export async function fetchMatchPage(id: string, group: string, cursor: string,
}
const reason = text(matching, 'reasonCode')
const count = (key: string): string => { const v = matching.getNumber(key); return v == null ? '—' : v.toString() }
const message = status == 'completed' ? (text(matching, 'orderingPolicyVersion') == 'product-score-descending-v1' ? '按本次条件匹配度排序,匹配度不代表获批概率。' : '按本次评估时的产品条件判断,展示顺序不代表优劣。') : (reason == 'LEGACY_INPUT' ? '旧版报告未执行产品匹配,可重新评估获取新的结果。' : (reason == 'MATCH_RULES_UNPUBLISHED' ? '本次评估尚未启用产品匹配规则。' : '本次评估没有可用产品目录,未执行产品匹配。'))
const message = status == 'completed' ? (group == 'all' ? '优先展示符合条件的产品,其次为待核实、条件不符的产品。' : (text(matching, 'orderingPolicyVersion') == 'product-score-descending-v1' ? '按本次条件匹配度排序,匹配度不代表获批概率。' : '按本次评估时的产品条件判断,展示顺序不代表优劣。')) : (reason == 'LEGACY_INPUT' ? '旧版报告未执行产品匹配,可重新评估获取新的结果。' : (reason == 'MATCH_RULES_UNPUBLISHED' ? '本次评估尚未启用产品匹配规则。' : '本次评估没有可用产品目录,未执行产品匹配。'))
return { assessmentId: id, revisionId: resolvedRevision, resultVersion: version, snapshotId: text(matching, 'catalogSnapshotId'), ruleVersion: text(matching, 'ruleVersion'), orderingVersion: text(matching, 'orderingPolicyVersion'), status, message, eligible: count('candidateCount'), ineligible: count('ineligibleCount'), unknown: count('unknownCount'), items, hasMore, cursor: nextCursor }
}
export async function fetchMatchDetail(id: string, productId: string, revisionId: string = ''): Promise<MatchView> {
+42 -11
View File
@@ -3,27 +3,51 @@ import { assessmentApiPayload, draftText } from '@/services/assessment.uts'
import { assessmentRequestKey } from '@/services/assessment-flow.uts'
import { contributionLabels, contributionCodes, contributionLabel } from '@/services/contribution.uts'
import { yuanToWan } from '@/services/money.uts'
import { provinceCodeForName } from '@/services/cities.uts'
import { FinancingGradeView, financingGradeFrom } from '@/packages/finance/services/financing-grade.uts'
export type AssessmentRecord = { id: string, type: string, status: string, statusLabel: string, message: string, city: string, submittedAt: string, taskId: string, stage: string, canRetry: boolean, revisionId: string, revisionNo: number, hasReport: boolean, publishedRevisionId: string }
export type AssessmentPage = { items: AssessmentRecord[], hasMore: boolean, nextCursor: string }
export type AssessmentField = { label: string, value: string }
export type ArchiveSection = { id: string, icon: string, title: string, note: string, fields: AssessmentField[], provided: number }
export type AssessmentDetail = { record: AssessmentRecord, fields: AssessmentField[], sections: ArchiveSection[] }
export type ArchiveCompleteness = { percent: number | null, description: string }
export type ArchiveField = { fieldKey: string, label: string, value: string, isMissing: boolean }
export type ArchiveSection = { id: string, icon: string, title: string, note: string, fields: ArchiveField[], provided: number }
export type AssessmentDetail = { record: AssessmentRecord, fields: ArchiveField[], sections: ArchiveSection[] }
export type ArchiveCompleteness = { assessmentId: string, revisionId: string, percent: number | null, description: string, notApplicableFields: string[] }
const archiveAmountKeys = ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']
// 档案只消费报告中已保存的完整度,不用当前表单规则重算旧评估,也不把缺失当作0%。
export async function fetchArchiveCompleteness(id: string, revisionId: string = ''): Promise<ArchiveCompleteness> {
const report = await requestJson('/assessments/' + encodeURIComponent(id) + '/report', 'GET', revisionParams(revisionId))
if (report.getString('assessmentId') != id) throw new Error('报告与当前档案不一致')
if (revisionId.length > 0 && report.getString('revisionId') != revisionId) throw new Error('报告版本不一致,请重新选择。')
if (report.getNumber('resultVersion') == 1) return { percent: null, description: '这份旧版报告未计算完整度,已提交资料仍可查看。' }
const result: ArchiveCompleteness = { assessmentId: id, revisionId: draftText(report, 'revisionId'), percent: null, description: '这份报告尚未评定资料完整度。', notApplicableFields: [] }
if (report.getNumber('resultVersion') == 1) { result.description = '这份旧版报告未计算完整度,已提交资料仍可查看。'; return result }
if (report.getNumber('resultVersion') != 2) throw new Error('报告格式暂不支持')
const completeness = report['completeness'] as UTSJSONObject | null
if (completeness == null || completeness.getString('status') != 'evaluated') return { percent: null, description: '这份报告尚未评定资料完整度。' }
if (completeness == null || completeness.getString('status') != 'evaluated') return result
const percent = completeness.getNumber('percent')
if (percent == null || !Number.isInteger(percent) || percent < 0 || percent > 100) throw new Error('完整度数据暂不可读取')
return { percent, description: '反映本次评估时的资料填写情况,不代表信用评分或审批结果。' }
result.percent = percent; result.description = '反映本次评估时的资料填写情况,不代表信用评分或审批结果。'
const items = completeness['items'] as UTSJSONObject[] | null
if (items != null && Array.isArray(items)) archiveAmountKeys.forEach((key: string) => {
const matching = items.filter((item: UTSJSONObject): boolean => item != null && item['fieldKey'] == key)
if (matching.length == 1 && matching[0]['state'] == 'not_applicable') result.notApplicableFields.push(key)
})
return result
}
// 只投影同版报告已经保存的不适用项,原档案与编辑回填继续保留原值。
export function archiveSections(detail: AssessmentDetail, completeness: ArchiveCompleteness | null): ArchiveSection[] {
if (completeness == null || completeness.assessmentId != detail.record.id
|| completeness.revisionId.length == 0 || completeness.revisionId != detail.record.revisionId) return detail.sections
return detail.sections.map((section: ArchiveSection): ArchiveSection => ({
id: section.id, icon: section.icon, title: section.title, note: section.note, provided: section.provided,
fields: section.fields.map((field: ArchiveField): ArchiveField => {
if (!field.isMissing || !archiveAmountKeys.includes(field.fieldKey) || !completeness!.notApplicableFields.includes(field.fieldKey)) return field
return { fieldKey: field.fieldKey, label: field.label, value: '不适用', isMissing: field.isMissing }
})
}))
}
function validAssessmentId(value: string): boolean { return /^[1-9][0-9]{0,9}$/.test(value) }
@@ -112,6 +136,7 @@ export async function fetchAnalysisReport(id: string, revisionId: string = ''):
let value = raw == null ? '未填写' : (typeof raw == 'number' ? (raw as number).toString() : draftText(fact, 'value'))
if (fact['value'] != null && fact['unit'] == 'yuan') value = yuanToWan(value) + ' 万元'
if (fact['value'] != null && fact['unit'] == 'month') value += ' 个月'
if (fact['value'] != null && fact['unit'] == 'age') value += ' 岁'
if (fact['state'] == 'invalid') value = '格式需核实'
const enumLabels: UTSJSONObject = { six_months_plus: '连续缴纳6个月以上', under_six_months: '缴纳不足6个月', none: '无' }
if (['socialSecurity', 'housingFund'].includes(draftText(fact, 'fieldKey'))) value = contributionLabel(value)
@@ -194,15 +219,18 @@ export async function createAssessment(payload: UTSJSONObject): Promise<string>
if (value.length > 0 && !/^[0-9]{1,4}$/.test(value)) throw new Error('数量与经营月数须为0至9999的整数')
form[key] = value.length == 0 ? null : parseInt(value)
})
if (draftText(values, 'loanCountScope') == 'all_loans') form['loanCountScope'] = 'all_loans'
form['maritalStatus'] = enumValue(draftText(values, 'maritalStatus'), ['已婚', '未婚', '离异', '丧偶'], ['married', 'single', 'divorced', 'widowed'])
const socialKeys = ['socialSecurity', 'housingFund']
socialKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), contributionLabels, contributionCodes) })
const ownershipKeys = ['house', 'car']
ownershipKeys.forEach((key: string) => { form[key] = enumValue(draftText(values, key), ['有', '无'], ['yes', 'no']) })
const provinceCode = provinceCodeForName(draftText(values, 'city').trim())
const cityNames = ['深圳市', '广州市', '北京市', '上海市', '杭州市']
const cityCodes = ['440300', '440100', '110100', '310100', '330100']
const cityIndex = cityNames.indexOf(draftText(values, 'city'))
form['cityCode'] = cityIndex < 0 ? null : cityCodes[cityIndex]
form['cityCode'] = provinceCode != null || cityIndex < 0 ? null : cityCodes[cityIndex]
if (provinceCode != null) form['provinceCode'] = provinceCode
// 卡片资料单独保存,确认页明示暂不绑定评估;只发送自填信息,绝不把本机路径或附件送入分析。
const body: UTSJSONObject = { inputVersion: 3, type: values['type'], moneyUnit: 'yuan', form, documents: [] as UTSJSONObject[], consentIds: [] as string[] }
if (editId.length > 0) body['baseRevisionId'] = baseRevision
@@ -252,7 +280,7 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
const record = recordFrom(result, id, revisionId)
const form = result['form'] as UTSJSONObject | null
if (form == null) throw new Error('评估详情不完整')
const fields: AssessmentField[] = []
const fields: ArchiveField[] = []
const enterprise = result['type'] == 'enterprise'
const sections: ArchiveSection[] = [
{ id: 'basic', icon: 'file-text', title: '基本信息', note: enterprise ? '企业与联系人信息' : '身份与联系方式', fields: [], provided: 0 },
@@ -260,14 +288,16 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
{ id: 'assets', icon: 'asset', title: '资产与负债', note: '贷款、信用卡额度与房车情况', fields: [], provided: 0 },
{ id: 'authorization', icon: 'authorization', title: '资料与授权', note: '私有资料与外部查询授权', fields: [], provided: 0 }
]
const addField = (section: number, label: string, value: string, provided: boolean) => {
const field: AssessmentField = { label, value }
const addField = (section: number, fieldKey: string, label: string, value: string, provided: boolean, isMissing: boolean) => {
const field: ArchiveField = { fieldKey, label, value, isMissing }
fields.push(field); sections[section].fields.push(field)
if (provided) sections[section].provided++
}
const keys = ['name', 'phone', 'city', 'idCard', 'enterpriseName', 'businessCode', 'occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'maritalStatus', 'socialSecurity', 'housingFund', 'house', 'car', 'socialSecurityBase', 'housingFundBase']
const labels = ['姓名 / 联系人', '手机号', '城市', '身份证号', '企业名称', '信用代码', '职业 / 行业', '工作单位', '经营月数', '月收入', '年营收', '信用贷款笔数', '欠款总余额', '贷款月还款金额', '信用卡张数', '信用卡总额度', '信用卡已用额度', '婚姻状况', '社保缴纳', '公积金缴纳', '是否有房', '是否有车', '社保月缴存基数', '公积金月缴存基数']
labels[11] = draftText(form, 'loanCountScope') == 'all_loans' ? '贷款笔数' : '信用贷款笔数(旧口径)'
const moneyKeys = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
if (form.getString('provinceCode') != null) labels[2] = '所在省份'
const enumLabels: UTSJSONObject = { married: '已婚', single: '未婚', divorced: '离异', widowed: '丧偶', yes: '有', no: '无', six_months_plus: '连续缴纳6个月以上', under_six_months: '缴纳不足6个月', none: '未缴纳' }
keys.forEach((key: string, index: number) => {
const raw = form[key]
@@ -282,7 +312,7 @@ export async function fetchAssessmentDetail(id: string, revisionId: string = '')
const incomeKeys = ['occupation', 'company', 'operatingMonths', 'monthlyIncome', 'annualRevenue', 'socialSecurity', 'housingFund', 'socialSecurityBase', 'housingFundBase']
const assetKeys = ['creditLoanCount', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardCount', 'creditCardTotalLimit', 'creditCardUsedLimit', 'house', 'car']
// “无”及数值0都是已填写,不能用真值判断遗漏零负债、零经营月数。
addField(incomeKeys.includes(key) ? 1 : (assetKeys.includes(key) ? 2 : 0), labels[index], value, raw != null && value.length > 0)
addField(incomeKeys.includes(key) ? 1 : (assetKeys.includes(key) ? 2 : 0), key, labels[index], value, raw != null && value.length > 0, raw == null)
})
return { record, fields, sections }
}
@@ -300,6 +330,7 @@ export async function fetchAssessmentEdit(id: string): Promise<UTSJSONObject> {
const form = result['form'] as UTSJSONObject | null
if (form == null || Array.isArray(form) || typeof form != 'object' || result.getString('moneyUnit') != 'yuan') throw new Error('修改资料不完整,请返回档案重新读取。')
const payload: UTSJSONObject = { type: result['type'], editAssessmentId: id, baseRevisionId: result['revisionId'], step: 0 }
payload['loanCountScope'] = draftText(form, 'loanCountScope') == 'all_loans' ? 'all_loans' : ''
const money = ['monthlyIncome', 'annualRevenue', 'totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit', 'socialSecurityBase', 'housingFundBase']
const labels: UTSJSONObject = { married: '已婚', single: '未婚', divorced: '离异', widowed: '丧偶', yes: '有', no: '无', six_months_plus: '连续缴纳 6 个月以上', under_six_months: '缴纳不足 6 个月', none: '未缴纳' }
const enums = ['maritalStatus', 'house', 'car', 'socialSecurity', 'housingFund']