feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造

- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制
- 引入费用审批动态路由、平台风险分级、预审与风险阶段管理
- 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板
- 新增 Hermes 风险线索收集器、Agent 链路追踪中心
- 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估
- 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-01 17:07:14 +08:00
parent 7989f3a159
commit 92444e7eae
285 changed files with 25075 additions and 2986 deletions

View File

@@ -105,6 +105,10 @@ function parseYear(rawText) {
return match ? Number(match[1]) : 2026
}
function hasExplicitYear(rawText) {
return /(20\d{2})/.test(String(rawText || ''))
}
function resolvePreviousPeriod(year, quarter) {
if (quarter > 1) {
return { year, quarter: quarter - 1 }
@@ -117,35 +121,52 @@ export function shouldUseBudgetCompileReport(rawText, options = {}) {
return false
}
const text = normalizeBudgetText(rawText)
const hasTargetPeriod = parseQuarter(rawText) || hasExplicitYear(rawText)
return Boolean(
text &&
/(预算|budget)/.test(text) &&
/(编制|制定|测算|生成|规划|预算一下|compile|create|plan)/.test(text) &&
parseQuarter(rawText)
hasTargetPeriod
)
}
export function buildBudgetCompileReport(rawText, user = {}) {
const targetYear = parseYear(rawText)
const targetQuarter = parseQuarter(rawText) || 3
const previous = resolvePreviousPeriod(targetYear, targetQuarter)
const totalSpend = PREVIOUS_QUARTER_SPEND.reduce((sum, item) => sum + item.value, 0)
const totalBudget = 1320000
const recommendedTotal = PREVIOUS_QUARTER_SPEND.reduce((sum, item) => sum + item.recommendedBudget, 0)
const parsedQuarter = parseQuarter(rawText)
const isAnnualBudget = !parsedQuarter
const targetQuarter = parsedQuarter || 1
const previous = isAnnualBudget
? { year: targetYear - 1, quarter: 0 }
: resolvePreviousPeriod(targetYear, targetQuarter)
const periodMultiplier = isAnnualBudget ? 4 : 1
const totalSpend = PREVIOUS_QUARTER_SPEND.reduce((sum, item) => sum + item.value * periodMultiplier, 0)
const totalBudget = 1320000 * periodMultiplier
const recommendedTotal = PREVIOUS_QUARTER_SPEND.reduce((sum, item) => sum + item.recommendedBudget * periodMultiplier, 0)
const departmentName = String(user.departmentName || user.department || '').trim() || '当前部门'
const items = PREVIOUS_QUARTER_SPEND.map((item) => {
const value = item.value * periodMultiplier
const previousValue = item.previousValue * periodMultiplier
const recommendedBudget = item.recommendedBudget * periodMultiplier
const trendValue = item.previousValue
? ((item.value - item.previousValue) / item.previousValue) * 100
? ((value - previousValue) / previousValue) * 100
: 0
return {
...item,
amountDisplay: compactCurrency(item.value),
display: percent(item.value, totalSpend),
share: percent(item.value, totalSpend),
value,
previousValue,
recommendedBudget,
amountDisplay: compactCurrency(value),
display: percent(value, totalSpend),
share: percent(value, totalSpend),
trend: `${trendValue >= 0 ? '+' : ''}${trendValue.toFixed(1)}%`,
trendTone: trendValue >= 10 ? 'risk' : trendValue >= 0 ? 'warn' : 'stable',
recommendedDisplay: compactCurrency(item.recommendedBudget)
recommendedDisplay: compactCurrency(recommendedBudget),
editableBudget: recommendedBudget,
reminderThreshold: item.key === 'communication' || item.key === 'office' ? 60 : 70,
alertThreshold: item.key === 'communication' || item.key === 'office' ? 70 : 80,
riskThreshold: item.key === 'communication' || item.key === 'office' ? 80 : 90,
editNote: item.suggestion
}
})
@@ -158,13 +179,18 @@ export function buildBudgetCompileReport(rawText, user = {}) {
return {
type: 'budget_compile_analysis',
title: `${targetYear}${targetQuarter}季度预算编制前置分析报告`,
subtitle: `基于${previous.year}${previous.quarter}度预算执行模拟数据`,
title: isAnnualBudget
? `${targetYear}度预算编制前置分析报告`
: `${targetYear}${targetQuarter}季度预算编制前置分析报告`,
subtitle: isAnnualBudget
? `基于${previous.year}年度预算执行模拟数据`
: `基于${previous.year}${previous.quarter}季度预算执行模拟数据`,
departmentName,
targetPeriod: `${targetYear}${QUARTER_NAME_MAP[targetQuarter]}`,
basePeriod: `${previous.year}${QUARTER_NAME_MAP[previous.quarter]}`,
targetPeriod: isAnnualBudget ? `${targetYear}年度` : `${targetYear}${QUARTER_NAME_MAP[targetQuarter]}`,
basePeriod: isAnnualBudget ? `${previous.year}年度` : `${previous.year}${QUARTER_NAME_MAP[previous.quarter]}`,
periodType: isAnnualBudget ? '年度预算' : '季度预算',
centerValue: compactCurrency(totalSpend),
centerLabel: '上季度开销',
centerLabel: isAnnualBudget ? '去年开销' : '上季度开销',
summary: {
totalBudget: compactCurrency(totalBudget),
totalSpend: compactCurrency(totalSpend),
@@ -172,13 +198,25 @@ export function buildBudgetCompileReport(rawText, user = {}) {
recommendedTotal: compactCurrency(recommendedTotal)
},
macroInsights: [
`${previous.year}${previous.quarter}季度实际开销 ${compactCurrency(totalSpend)},预算使用率 ${percent(totalSpend, totalBudget)},整体仍在可控区间。`,
`${topItem.name}是最大开销项,占 ${topItem.share},建议作为${targetYear}${targetQuarter}季度预算编制的第一优先级。`,
`${isAnnualBudget ? `${previous.year}年度` : `${previous.year}${previous.quarter}季度`}实际开销 ${compactCurrency(totalSpend)},预算使用率 ${percent(totalSpend, totalBudget)},整体仍在可控区间。`,
`${topItem.name}是最大开销项,占 ${topItem.share},建议作为${isAnnualBudget ? `${targetYear}年度` : `${targetYear}${targetQuarter}季度`}预算编制的第一优先级。`,
`${growthItem.name}环比增长 ${growthItem.trend},需要在预算说明中提前解释业务驱动,避免后续报销阶段反复补充材料。`
],
items,
editableDraft: {
status: 'editing',
rows: items.map((item) => ({
key: item.key,
name: item.name,
budgetAmount: item.editableBudget,
reminderThreshold: item.reminderThreshold,
alertThreshold: item.alertThreshold,
riskThreshold: item.riskThreshold,
note: item.editNote
}))
},
recommendations: [
`建议${targetYear}${targetQuarter}季度总预算先按 ${compactCurrency(recommendedTotal)} 编制,再预留 5%-8% 部门机动池。`,
`建议${isAnnualBudget ? `${targetYear}年度` : `${targetYear}${targetQuarter}季度`}总预算先按 ${compactCurrency(recommendedTotal)} 编制,再预留 5%-8% 部门机动池。`,
'差旅和招待费采用更早的提醒阈值,通信和办公用品保持稳定额度,避免把预算过度分散到低波动项目。',
'正式编制时建议把重点项目、客户活动和集中采购计划写入预算说明,后续费用控制会更容易解释。'
],