feat: 本体字段治理与风险规则模板执行器重构

- 新增本体字段注册表与字段治理审计脚本
- 重构风险规则模板执行器、DSL 验证与清单分类器
- 完善票据夹服务与差旅请求详情页交互
- 优化趋势图表与总览页数据展示
- 增强报销平台风险分级与模拟公司筛选
- 补充本体字段、风险规则生成与票据夹服务测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 15:46:56 +08:00
parent e12b140508
commit 34457f9c3e
81 changed files with 4858 additions and 1073 deletions

View File

@@ -1,7 +1,7 @@
import { computed, reactive, ref } from 'vue'
import { fetchExpenseClaims } from '../services/reimbursements.js'
import { filterActionableRiskFlags } from '../utils/riskFlags.js'
import { filterActionableRiskFlags, normalizeRiskFlagTone } from '../utils/riskFlags.js'
const EXPENSE_TYPE_LABELS = {
travel: '差旅费',
@@ -429,13 +429,47 @@ function stringifyRiskFlag(value) {
return ''
}
function buildRiskSummary(riskFlags) {
const RISK_TONE_LABELS = {
high: '高风险',
medium: '中风险',
low: '低风险'
}
function resolveHighestRiskTone(flags) {
const tones = flags.map((item) => normalizeRiskFlagTone(item)).filter(Boolean)
if (tones.includes('high')) {
return 'high'
}
if (tones.includes('medium')) {
return 'medium'
}
if (tones.includes('low')) {
return 'low'
}
return 'low'
}
function buildRiskMeta(riskFlags) {
if (!Array.isArray(riskFlags) || !riskFlags.length) {
return '无'
return { summary: '无', tone: 'low', label: '无' }
}
const items = filterActionableRiskFlags(riskFlags).map((item) => stringifyRiskFlag(item)).filter(Boolean)
return items.length ? items.join('') : '无'
const actionableFlags = filterActionableRiskFlags(riskFlags)
const items = actionableFlags.map((item) => stringifyRiskFlag(item)).filter(Boolean)
if (!items.length) {
return { summary: '无', tone: 'low', label: '无' }
}
const tone = resolveHighestRiskTone(actionableFlags)
return {
summary: items.join(''),
tone,
label: RISK_TONE_LABELS[tone] || '待关注'
}
}
function buildRiskSummary(riskFlags) {
return buildRiskMeta(riskFlags).summary
}
function buildOccurredDisplay(claim) {
@@ -1218,11 +1252,19 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
})
}
function buildExpenseItems(claim, riskSummary) {
function buildExpenseItems(claim, riskMeta) {
if (!Array.isArray(claim?.items)) {
return []
}
const normalizedRiskMeta = typeof riskMeta === 'string'
? { summary: riskMeta, tone: riskMeta === '无' ? 'low' : 'medium', label: riskMeta === '无' ? '无' : '待关注' }
: {
summary: String(riskMeta?.summary || '无').trim() || '无',
tone: String(riskMeta?.tone || 'low').trim() || 'low',
label: String(riskMeta?.label || '').trim() || (String(riskMeta?.summary || '').trim() === '无' ? '无' : '待关注')
}
const visibleItems = filterVisibleExpenseRawItems(claim.items, claim)
const sortedItems = [...visibleItems].sort((left, right) => {
const leftType = normalizeExpenseType(left?.item_type)
@@ -1241,6 +1283,7 @@ function buildExpenseItems(claim, riskSummary) {
const itemTypeLabel = resolveTypeLabel(itemType)
const itemLocation = String(item?.item_location || '').trim()
const itemReason = String(item?.item_reason || '').trim()
const itemNote = String(item?.item_note || item?.itemNote || '').trim()
const itemAmount = parseNumber(item?.item_amount)
const itemAmountDisplay = itemAmount > 0 ? formatAmount(itemAmount) : '待补充'
@@ -1252,6 +1295,7 @@ function buildExpenseItems(claim, riskSummary) {
itemType,
itemReason,
itemLocation,
itemNote,
itemAmount,
invoiceId,
isSystemGenerated,
@@ -1273,9 +1317,9 @@ function buildExpenseItems(claim, riskSummary) {
attachmentHint: isSystemGenerated ? '根据出差天数与职级自动测算' : attachments.length ? attachments[0] : '仅支持上传 1 张 JPG、PNG、PDF 单据',
attachmentTone: isSystemGenerated ? 'system' : attachments.length ? 'ok' : 'missing',
attachments,
riskLabel: riskSummary === '无' ? '无' : '待关注',
riskText: riskSummary === '无' ? '' : riskSummary,
riskTone: riskSummary === '无' ? 'low' : 'medium'
riskLabel: normalizedRiskMeta.summary === '无' ? '无' : normalizedRiskMeta.label,
riskText: normalizedRiskMeta.summary === '无' ? '' : normalizedRiskMeta.summary,
riskTone: normalizedRiskMeta.summary === '无' ? 'low' : normalizedRiskMeta.tone
}
})
}
@@ -1288,9 +1332,10 @@ export function mapExpenseClaimToRequest(claim) {
const approvalMeta = resolveApprovalMeta(claim?.status)
const workflowNode = resolveWorkflowNode(claim, approvalMeta, isApplicationDocument)
const invoiceCount = Math.max(0, parseNumber(claim?.invoice_count))
const riskSummary = buildRiskSummary(claim?.risk_flags_json)
const riskMeta = buildRiskMeta(claim?.risk_flags_json)
const riskSummary = riskMeta.summary
const relatedApplication = isApplicationDocument ? null : resolveRelatedApplicationInfo(claim, typeLabel)
const expenseItems = buildExpenseItems(claim, riskSummary)
const expenseItems = buildExpenseItems(claim, riskMeta)
const visibleExpenseAmount = expenseItems.reduce((sum, item) => sum + parseNumber(item.itemAmount), 0)
const amountValue = relatedApplication
? expenseItems.length
@@ -1340,6 +1385,8 @@ export function mapExpenseClaimToRequest(claim) {
updatedAt: claim?.updated_at || '',
amount: amountValue,
riskFlags: Array.isArray(claim?.risk_flags_json) ? claim.risk_flags_json : [],
riskTone: riskMeta.tone,
riskLabel: riskMeta.label,
invoiceCount,
workflowNode,
approvalKey: approvalMeta.key,