feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造
- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
@@ -165,6 +165,10 @@ export function generateRiskRuleAsset(payload, options = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchRiskRuleTemplates() {
|
||||
return apiRequest('/agent-assets/risk-rules/templates')
|
||||
}
|
||||
|
||||
export function updateRiskRuleDraft(assetId, payload, options = {}) {
|
||||
return apiRequest(`/agent-assets/${assetId}/risk-rules/draft`, {
|
||||
method: 'PATCH',
|
||||
@@ -181,6 +185,28 @@ export function createRiskRuleRevision(assetId, payload, options = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
export function regenerateRiskRuleAsset(assetId, payload = {}, options = {}) {
|
||||
return apiRequest(`/agent-assets/${assetId}/risk-rules/regenerate`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload || {}),
|
||||
headers: buildWriteHeaders(options),
|
||||
timeoutMs: options.timeoutMs || 60000,
|
||||
timeoutMessage: '风险规则重新生成时间较长,请稍后查看最新结果。'
|
||||
})
|
||||
}
|
||||
|
||||
export function submitRiskRuleFeedback(assetId, payload, options = {}) {
|
||||
return apiRequest(`/agent-assets/${assetId}/risk-rules/feedback`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload || {}),
|
||||
headers: buildWriteHeaders(options)
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchRiskRuleFeedback(assetId, params = {}) {
|
||||
return apiRequest(`/agent-assets/${assetId}/risk-rules/feedback${buildQuery(params)}`)
|
||||
}
|
||||
|
||||
export function fetchRiskRuleLatestTest(assetId) {
|
||||
return apiRequest(`/agent-assets/${assetId}/risk-rule-tests/latest`)
|
||||
}
|
||||
|
||||
39
web/src/services/agentTraces.js
Normal file
39
web/src/services/agentTraces.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { apiRequest } from './api.js'
|
||||
|
||||
function buildQuery(params = {}) {
|
||||
const search = new URLSearchParams()
|
||||
|
||||
if (params.agent) {
|
||||
search.set('agent', params.agent)
|
||||
}
|
||||
if (params.status) {
|
||||
search.set('status', params.status)
|
||||
}
|
||||
if (params.source) {
|
||||
search.set('source', params.source)
|
||||
}
|
||||
if (params.conversationId || params.conversation_id) {
|
||||
search.set('conversation_id', params.conversationId || params.conversation_id)
|
||||
}
|
||||
if (params.keyword) {
|
||||
search.set('keyword', params.keyword)
|
||||
}
|
||||
if (params.limit) {
|
||||
search.set('limit', String(params.limit))
|
||||
}
|
||||
|
||||
const query = search.toString()
|
||||
return query ? `?${query}` : ''
|
||||
}
|
||||
|
||||
export function fetchAgentTraces(params = {}) {
|
||||
return apiRequest(`/agent-traces${buildQuery(params)}`)
|
||||
}
|
||||
|
||||
export function fetchAgentTraceDetail(runId) {
|
||||
return apiRequest(`/agent-traces/${encodeURIComponent(String(runId || '').trim())}`)
|
||||
}
|
||||
|
||||
export function fetchConversationTrace(conversationId) {
|
||||
return apiRequest(`/agent-traces/conversations/${encodeURIComponent(String(conversationId || '').trim())}`)
|
||||
}
|
||||
@@ -25,6 +25,15 @@ const FINANCE_DASHBOARD_FALLBACK = {
|
||||
hasRealData: false
|
||||
}
|
||||
|
||||
const DIGITAL_EMPLOYEE_DASHBOARD_FALLBACK = {
|
||||
totals: null,
|
||||
dailyWork: [],
|
||||
taskDistribution: [],
|
||||
categoryDistribution: [],
|
||||
recentRuns: [],
|
||||
hasRealData: false
|
||||
}
|
||||
|
||||
function normalizeSystemDashboardPayload(payload = {}) {
|
||||
return {
|
||||
...SYSTEM_DASHBOARD_FALLBACK,
|
||||
@@ -62,6 +71,20 @@ function normalizeFinanceDashboardPayload(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeDigitalEmployeeDashboardPayload(payload = {}) {
|
||||
return {
|
||||
...DIGITAL_EMPLOYEE_DASHBOARD_FALLBACK,
|
||||
windowDays: Number(payload.window_days || payload.windowDays || 7),
|
||||
generatedAt: payload.generated_at || payload.generatedAt || '',
|
||||
hasRealData: Boolean(payload.has_real_data ?? payload.hasRealData),
|
||||
totals: payload.totals || null,
|
||||
dailyWork: payload.daily_work || payload.dailyWork || [],
|
||||
taskDistribution: payload.task_distribution || payload.taskDistribution || [],
|
||||
categoryDistribution: payload.category_distribution || payload.categoryDistribution || [],
|
||||
recentRuns: payload.recent_runs || payload.recentRuns || []
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchSystemDashboard(options = {}) {
|
||||
const days = Number(options.days || 7)
|
||||
const search = new URLSearchParams()
|
||||
@@ -75,6 +98,21 @@ export async function fetchSystemDashboard(options = {}) {
|
||||
return normalizeSystemDashboardPayload(payload)
|
||||
}
|
||||
|
||||
export async function fetchDigitalEmployeeDashboard(options = {}) {
|
||||
const days = Number(options.days || 7)
|
||||
const limit = Number(options.limit || 300)
|
||||
const search = new URLSearchParams()
|
||||
search.set('days', String(Math.max(1, Math.min(days, 30))))
|
||||
search.set('limit', String(Math.max(1, Math.min(limit, 1000))))
|
||||
|
||||
const payload = await apiRequest(`/analytics/digital-employee-dashboard?${search.toString()}`, {
|
||||
timeoutMs: Number(options.timeoutMs || 3500),
|
||||
timeoutMessage: '数字员工看板真实数据加载超时,请稍后重试。'
|
||||
})
|
||||
|
||||
return normalizeDigitalEmployeeDashboardPayload(payload)
|
||||
}
|
||||
|
||||
export async function fetchFinanceDashboard(options = {}) {
|
||||
const search = new URLSearchParams()
|
||||
search.set('range_key', String(options.rangeKey || options.range || '近10日'))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeAuthUserSnapshot } from '../utils/authUser.js'
|
||||
|
||||
const API_BASE_STORAGE_KEY = 'x-financial-api-base-url'
|
||||
const AUTH_USER_STORAGE_KEY = 'x-financial-auth-user'
|
||||
|
||||
@@ -43,16 +45,25 @@ function readCurrentUserHeaders() {
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(raw)
|
||||
const username = String(payload?.username || '').trim()
|
||||
const name = String(payload?.name || username).trim()
|
||||
const roleCodes = Array.isArray(payload?.roleCodes) ? payload.roleCodes.filter(Boolean) : []
|
||||
const user = normalizeAuthUserSnapshot(payload)
|
||||
const username = user.username
|
||||
const name = user.name || username
|
||||
const roleCodes = user.roleCodes
|
||||
const isAdmin = resolveStoredUserAdminFlag(payload, roleCodes)
|
||||
const department = String(payload?.department || payload?.departmentName || '').trim()
|
||||
const costCenter = String(payload?.costCenter || payload?.cost_center || '').trim()
|
||||
const department = user.department || user.departmentName
|
||||
const costCenter = user.costCenter
|
||||
const position = user.position
|
||||
const grade = user.grade
|
||||
const employeeNo = user.employeeNo
|
||||
const managerName = user.managerName
|
||||
const safeUsername = pickSafeHeaderValue(username)
|
||||
const safeName = pickSafeHeaderValue(name)
|
||||
const safeDepartment = pickSafeHeaderValue(department)
|
||||
const safeCostCenter = pickSafeHeaderValue(costCenter)
|
||||
const safePosition = pickSafeHeaderValue(position)
|
||||
const safeGrade = pickSafeHeaderValue(grade)
|
||||
const safeEmployeeNo = pickSafeHeaderValue(employeeNo)
|
||||
const safeManagerName = pickSafeHeaderValue(managerName)
|
||||
|
||||
if (!safeUsername && !safeName) {
|
||||
return {}
|
||||
@@ -79,6 +90,22 @@ function readCurrentUserHeaders() {
|
||||
headers['x-auth-cost-center'] = safeCostCenter
|
||||
}
|
||||
|
||||
if (safePosition) {
|
||||
headers['x-auth-position'] = safePosition
|
||||
}
|
||||
|
||||
if (safeGrade) {
|
||||
headers['x-auth-grade'] = safeGrade
|
||||
}
|
||||
|
||||
if (safeEmployeeNo) {
|
||||
headers['x-auth-employee-no'] = safeEmployeeNo
|
||||
}
|
||||
|
||||
if (safeManagerName) {
|
||||
headers['x-auth-manager-name'] = safeManagerName
|
||||
}
|
||||
|
||||
return headers
|
||||
} catch {
|
||||
return {}
|
||||
|
||||
@@ -7,6 +7,10 @@ export function login(payload) {
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchCurrentAuthUser() {
|
||||
return apiRequest('/auth/me')
|
||||
}
|
||||
|
||||
export function finishSession(sessionId, payload) {
|
||||
return apiRequest(`/auth/sessions/${encodeURIComponent(sessionId)}/finish`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -157,6 +157,13 @@ export function submitExpenseClaim(claimId) {
|
||||
})
|
||||
}
|
||||
|
||||
export function preReviewExpenseClaim(claimId) {
|
||||
return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/pre-review`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
}
|
||||
|
||||
export function returnExpenseClaim(claimId, payload = {}) {
|
||||
return apiRequest(`/reimbursements/claims/${encodeURIComponent(String(claimId || '').trim())}/return`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -53,9 +53,13 @@ export function normalizeRiskObservationDashboard(payload = {}) {
|
||||
windowDays: toNumber(payload.window_days ?? payload.windowDays, 30),
|
||||
totalObservations: toNumber(payload.total_observations ?? payload.totalObservations),
|
||||
pendingCount: toNumber(payload.pending_count ?? payload.pendingCount),
|
||||
riskClueCount: toNumber(payload.risk_clue_count ?? payload.riskClueCount),
|
||||
highOrAboveCount: toNumber(payload.high_or_above_count ?? payload.highOrAboveCount),
|
||||
confirmedCount: toNumber(payload.confirmed_count ?? payload.confirmedCount),
|
||||
falsePositiveCount: toNumber(payload.false_positive_count ?? payload.falsePositiveCount),
|
||||
feedbackSampleCount: toNumber(
|
||||
payload.feedback_sample_count ?? payload.feedbackSampleCount
|
||||
),
|
||||
totalAmount: toNumber(payload.total_amount ?? payload.totalAmount),
|
||||
averageScore: toNumber(payload.average_score ?? payload.averageScore),
|
||||
confirmationRate: toNumber(payload.confirmation_rate ?? payload.confirmationRate),
|
||||
|
||||
Reference in New Issue
Block a user