feat: 增强风险规则生成引擎与预算中心页面

后端拆分风险规则生成为解释器、语义分析、本体对齐等子模块,
优化模板执行和流程图生成,完善员工种子数据和导入逻辑,增强
报销单权限策略和草稿持久化,前端新增预算中心视图和趋势图
组件,重构审计页面和风险规则测试对话框交互,完善文档中心
和报销创建页面细节,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-26 09:15:14 +08:00
parent d0e946cf47
commit 0e861d8fa6
150 changed files with 14953 additions and 4099 deletions

View File

@@ -27,6 +27,10 @@ import {
resolveRiskRuleFields,
resolveRiskRuleFlow,
resolveRiskRuleFlowDiagramSvg,
resolveRiskRuleScore,
resolveRiskRuleScoreDetail,
resolveRiskRuleScoreLabel,
resolveRiskRuleScoreLevel,
resolveRiskRuleSeverity,
resolveRiskRuleSeverityLabel
} from './auditViewRiskRuleModel.js'
@@ -327,6 +331,14 @@ export function readScenarioItems(source) {
export function resolveRiskRuleCategory(source) {
const configJson = readConfigJson(source)
const expenseCategoryLabel =
normalizeText(configJson.expense_category_label) ||
normalizeText(configJson.metadata?.expense_category_label) ||
normalizeText(source?.expense_category_label)
if (expenseCategoryLabel) {
return expenseCategoryLabel
}
const explicit = normalizeRiskScenarioCategory(configJson.risk_category)
if (explicit) {
return explicit
@@ -442,16 +454,24 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) {
normalizeText(apiPayload?.description) ||
normalizeText(target.riskRuleDescription)
const riskCategory =
normalizeText(metadata.expense_category_label) ||
normalizeText(apiConfig.expense_category_label) ||
normalizeText(rulePayload.risk_category) ||
resolveRiskRuleCategory({ ...target, risk_category: rulePayload.risk_category, config_json: rulePayload })
const riskRuleFields = resolveRiskRuleFields(rulePayload)
const riskRuleCreatedAt = resolveRiskRuleCreatedAt(rulePayload, target.createdAt || target.updatedAt)
const riskRuleScoreLevel = resolveRiskRuleScoreLevel(rulePayload, apiConfig)
const statusValue = apiPayload?.status || target.statusValue || 'draft'
const isOnlineLabel = statusValue === 'active' ? '是' : '否'
const isEnabledValue = resolveRiskRuleEnabled(target, rulePayload)
const publisher = apiPayload?.created_by || target.publisher || (apiPayload?.recent_versions && apiPayload.recent_versions[0]?.created_by) || '系统管理员'
const publisher =
target.creator ||
normalizeText(apiPayload?.owner) ||
normalizeText(metadata.created_by) ||
normalizeText(apiPayload?.recent_versions?.[0]?.created_by) ||
'未知'
let publishedAt = target.publishedAt || '-'
if (apiPayload?.recent_versions) {
@@ -470,15 +490,23 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) {
riskCategory,
scope: riskCategory,
riskRuleSourceRef: resolveRiskRuleSourceRef(rulePayload),
riskRuleSeverity: resolveRiskRuleSeverity(rulePayload),
riskRuleSeverityLabel: resolveRiskRuleSeverityLabel(rulePayload),
riskRuleSeverity: riskRuleScoreLevel || resolveRiskRuleSeverity(rulePayload),
riskRuleSeverityLabel: riskRuleScoreLevel
? resolveRiskRuleScoreLabel(rulePayload, apiConfig)
: resolveRiskRuleSeverityLabel(rulePayload),
riskRuleScore: resolveRiskRuleScore(rulePayload, apiConfig),
riskRuleScoreLabel: resolveRiskRuleScoreLabel(rulePayload, apiConfig),
riskRuleScoreLevel: riskRuleScoreLevel || resolveRiskRuleSeverity(rulePayload),
riskRuleScoreDetail: resolveRiskRuleScoreDetail(rulePayload, apiConfig),
riskRuleCreatedAt: formatDateTime(riskRuleCreatedAt),
riskRuleAgeLabel: formatRiskRuleAge(riskRuleCreatedAt),
riskRuleFields,
riskRuleFieldSummary: buildRiskRuleFieldSummary(riskRuleFields),
riskRuleFlow: resolveRiskRuleFlow(rulePayload, riskRuleFields),
riskRuleFlowDiagramSvg:
normalizeText(apiPayload?.flow_diagram_svg) || resolveRiskRuleFlowDiagramSvg(rulePayload),
riskRuleFlowDiagramSvg: resolveRiskRuleFlowDiagramSvg({
...rulePayload,
flow_diagram_svg: normalizeText(apiPayload?.flow_diagram_svg) || rulePayload?.flow_diagram_svg
}),
riskRuleRequiresAttachment: Boolean(
rulePayload.requires_attachment ||
metadata.requires_attachment ||
@@ -860,12 +888,13 @@ export function buildListItem(asset) {
const isOnlineValue = asset.status === 'active'
const isEnabledValue = usesJsonRiskRule ? resolveRiskRuleEnabled(asset) : true
const reviewer = normalizeText(asset.reviewer) || '待分配'
const publisher = isRiskRule
? isOnlineValue
? normalizeText(asset.published_by) || reviewer || modifiedBy || '系统管理员'
: '-'
: ''
const publishedAt = isRiskRule && isOnlineValue ? formatDateTime(asset.published_at || asset.updated_at) : '-'
const creator =
normalizeText(asset.owner) ||
normalizeText(asset.config_json?.generation_request?.actor) ||
modifiedBy ||
'未知'
const publisher = isRiskRule ? creator : ''
const riskRuleCreatedAt = formatDateTime(asset.created_at || asset.updated_at)
return {
id: asset.id,
@@ -895,8 +924,9 @@ export function buildListItem(asset) {
statusValue: asset.status,
statusTone: statusMeta.tone,
hitRate: isRiskRule ? publisher : buildRowMetric({ ...asset, modified_by: modifiedBy }, typeKey),
creator,
publisher,
publishedAt,
publishedAt: isOnlineValue ? formatDateTime(asset.published_at || asset.updated_at) : '-',
isOnlineValue,
isOnlineLabel: isOnlineValue ? '是' : '否',
isOnlineTone: isOnlineValue ? 'success' : 'disabled',
@@ -905,7 +935,7 @@ export function buildListItem(asset) {
isEnabledTone: isEnabledValue ? 'success' : 'disabled',
modifiedBy,
changeCount,
updatedAt: isRiskRule ? publishedAt : formatDateTime(asset.updated_at),
updatedAt: isRiskRule ? riskRuleCreatedAt : formatDateTime(asset.updated_at),
badgeTone: tabMeta.badgeTone,
domainValue: asset.domain
}
@@ -1283,6 +1313,13 @@ export function buildDetailViewModel(detail, runs) {
const runtimeKind = normalizeText(configJson.runtime_kind || previewRuntimeRule.kind) || 'policy_rule_draft'
const ruleScenarioCategory = typeKey === 'rules' ? resolveRuleScenarioCategory(detail, tabId) : ''
const isEnabledValue = usesJsonRiskRule ? resolveRiskRuleEnabled(detail) : true
const generationStatus = normalizeText(configJson.generation_status || detail.status)
const riskRuleGenerationFailed = usesJsonRiskRule && (detail.status === 'failed' || generationStatus === 'failed')
const riskRuleGenerationBusy = usesJsonRiskRule && (detail.status === 'generating' || generationStatus === 'generating')
const riskRuleCreator =
normalizeText(detail.owner) ||
normalizeText(detail.recent_versions?.[0]?.created_by) ||
'未知'
return {
id: detail.id,
@@ -1321,6 +1358,10 @@ export function buildDetailViewModel(detail, runs) {
riskRuleSourceRef: '',
riskRuleSeverity: 'medium',
riskRuleSeverityLabel: '中风险',
riskRuleScore: null,
riskRuleScoreLabel: '待计算',
riskRuleScoreLevel: 'medium',
riskRuleScoreDetail: null,
riskRuleCreatedAt: formatDateTime(detail.created_at),
riskRuleAgeLabel: formatRiskRuleAge(detail.created_at),
isOnlineLabel: detail.status === 'active' ? '是' : '否',
@@ -1334,7 +1375,8 @@ export function buildDetailViewModel(detail, runs) {
detail.reviewer ||
(detail.recent_versions && detail.recent_versions[0]?.created_by) ||
'系统管理员'
: '-',
: riskRuleCreator,
creator: riskRuleCreator,
publishedAt:
history.find((item) => item.isPublished || item.lifecycleState === 'published')?.time ||
(detail.published_at ? formatDateTime(detail.published_at) : '') ||
@@ -1344,6 +1386,10 @@ export function buildDetailViewModel(detail, runs) {
riskRuleFlow: resolveRiskRuleFlow({}, []),
riskRuleFlowDiagramSvg: normalizeText(configJson.flow_diagram_svg),
riskRuleRequiresAttachment: Boolean(configJson.requires_attachment),
riskRuleGenerationStatus: generationStatus,
riskRuleGenerationFailed,
riskRuleGenerationBusy,
riskRuleGenerationError: normalizeText(configJson.generation_error),
latestTestSummary: detail.latest_test_summary || detail.latestTestSummary || null,
riskCategory: typeKey === 'rules' ? ruleScenarioCategory : '',
ruleDocument,