Files
X-Financial/web/src/views/scripts/digitalEmployeeWorkRecordsModel.js
caoxiaozhu 15006a05a7 feat: 数字员工财务报告体系与定时提醒及看板快照调度
- 新增数字员工财务报告生成、邮件投递与渲染调度器
- 引入员工画像扫描调度与定时提醒任务
- 完善财务看板快照、排行口径与部门人员占比计算
- 优化数字员工工作看板仪表盘与技能目录
- 增强前端总览页图表、工作台摘要与顶部导航栏交互
- 新增差旅申请规划推动提醒与报销创建会话状态管理
- 补充财务报告、看板调度、数字员工工作记录测试覆盖
2026-06-03 09:25:23 +08:00

321 lines
9.4 KiB
JavaScript

import {
formatAgentRunElapsed,
formatAgentRunProgress,
resolveAgentRunHeartbeat,
resolveAgentRunStatus
} from '../../utils/agentRunMonitor.js'
const SOURCE_LABELS = {
schedule: '定时任务',
system_event: '系统事件',
user_message: '用户触发'
}
const KNOWLEDGE_JOB_TYPES = new Set([
'knowledge_index_sync',
'llm_wiki_sync',
'llm_wiki_rule_formation',
'finance_policy_knowledge_organize'
])
export const VISIBLE_DIGITAL_EMPLOYEE_WORK_TASK_TYPES = new Set([
'finance_dashboard_snapshot',
'digital_employee_reminder_scan',
'employee_behavior_profile_scan',
'department_expense_baseline_accumulate',
'budget_overrun_precontrol_evaluate',
'multi_evidence_consistency_evaluate',
'travel_spatiotemporal_consistency_evaluate',
'global_risk_scan',
'finance_policy_knowledge_organize'
])
const DAILY_COMPACT_TASK_TYPES = new Set([
'finance_dashboard_snapshot'
])
const TASK_TYPE_LABELS = {
finance_dashboard_snapshot: '财务经营快照沉淀',
digital_employee_reminder_scan: '定时提醒与待办扫描',
global_risk_scan: '财务风险图谱巡检',
employee_behavior_profile_scan: '员工行为画像巡检',
department_expense_baseline_accumulate: '部门费用基线沉淀',
budget_overrun_precontrol_evaluate: '预算占用与超标预警',
multi_evidence_consistency_evaluate: '单据多凭证一致性评估',
travel_spatiotemporal_consistency_evaluate: '差旅时空一致性评估',
risk_clue_collect: '风险线索归集',
finance_policy_knowledge_organize: '知识制度整理',
knowledge_index_sync: '知识制度整理',
llm_wiki_sync: '知识制度整理',
llm_wiki_rule_formation: '知识制度整理'
}
const TASK_CODE_TO_TYPE = {
'task.hermes.finance_dashboard_snapshot': 'finance_dashboard_snapshot',
'task.hermes.digital_employee_reminder_scan': 'digital_employee_reminder_scan',
'task.hermes.global_risk_scan': 'global_risk_scan',
'task.hermes.employee_behavior_profile_scan': 'employee_behavior_profile_scan',
'task.hermes.department_expense_baseline_accumulate': 'department_expense_baseline_accumulate',
'task.hermes.budget_overrun_precontrol_evaluate': 'budget_overrun_precontrol_evaluate',
'task.hermes.multi_evidence_consistency_evaluate': 'multi_evidence_consistency_evaluate',
'task.hermes.travel_spatiotemporal_consistency_evaluate': 'travel_spatiotemporal_consistency_evaluate',
'task.hermes.finance_policy_knowledge_organize': 'finance_policy_knowledge_organize',
'task.hermes.risk_rule_discovery': 'risk_clue_collect'
}
function toObject(value) {
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
}
function normalizeTaskType(value) {
const normalized = String(value || '').trim()
if (!normalized) {
return ''
}
return TASK_CODE_TO_TYPE[normalized] || normalized
}
function resolveTaskTypeFromToolName(value) {
const name = String(value || '').trim()
if (name.includes('financial_risk_graph')) {
return 'global_risk_scan'
}
if (name.includes('finance_dashboard_snapshot') || name.includes('finance_dashboard')) {
return 'finance_dashboard_snapshot'
}
if (name.includes('digital_employee_reminder') || name.includes('reminder')) {
return 'digital_employee_reminder_scan'
}
if (name.includes('employee_behavior_profile')) {
return 'employee_behavior_profile_scan'
}
if (name.includes('finance_policy_knowledge')) {
return 'finance_policy_knowledge_organize'
}
if (name.includes('risk_clue')) {
return 'risk_clue_collect'
}
return ''
}
export function formatWorkRecordDateTime(value) {
if (!value) {
return '未结束'
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return String(value)
}
return date.toLocaleString('zh-CN', { hour12: false })
}
export function formatWorkRecordSummary(summary) {
const text = String(summary || '').trim()
if (!text) {
return '暂无摘要。'
}
if (text.length <= 64) {
return text
}
return `${text.slice(0, 64)}...`
}
export function resolveWorkRecordSourceLabel(source) {
return SOURCE_LABELS[source] || source || '未标记'
}
export function resolveWorkRecordTaskType(run) {
const routeJson = toObject(run?.route_json)
const routeCandidates = [
routeJson.job_type,
routeJson.task_type,
routeJson.report_type,
routeJson.task_code
].map(normalizeTaskType)
for (const candidate of routeCandidates) {
if (candidate) {
return candidate
}
}
for (const toolCall of run?.tool_calls || []) {
const requestJson = toObject(toolCall?.request_json)
const responseJson = toObject(toolCall?.response_json)
const candidates = [
requestJson.task_type,
requestJson.job_type,
responseJson.report_type,
responseJson.task_type,
responseJson.job_type,
resolveTaskTypeFromToolName(toolCall?.tool_name)
].map(normalizeTaskType)
const matched = candidates.find(Boolean)
if (matched) {
return matched
}
}
return ''
}
export function isVisibleDigitalEmployeeWorkRecord(run) {
const taskType = resolveWorkRecordTaskType(run)
return VISIBLE_DIGITAL_EMPLOYEE_WORK_TASK_TYPES.has(taskType)
}
function resolveWorkRecordDayKey(run) {
const date = new Date(run?.started_at || run?.finished_at || '')
if (Number.isNaN(date.getTime())) {
return 'unknown'
}
return date.toISOString().slice(0, 10)
}
export function compactDigitalEmployeeWorkRecords(items = []) {
const rows = []
const compactedKeys = new Set()
for (const run of items) {
const taskType = resolveWorkRecordTaskType(run)
if (!VISIBLE_DIGITAL_EMPLOYEE_WORK_TASK_TYPES.has(taskType)) {
continue
}
if (DAILY_COMPACT_TASK_TYPES.has(taskType)) {
const key = `${taskType}:${resolveWorkRecordDayKey(run)}`
if (compactedKeys.has(key)) {
continue
}
compactedKeys.add(key)
}
rows.push(run)
}
return rows
}
export function resolveWorkRecordTaskLabel(run) {
const taskType = resolveWorkRecordTaskType(run)
return TASK_TYPE_LABELS[taskType] || ''
}
export function resolveWorkRecordProductKind(run) {
const taskType = resolveWorkRecordTaskType(run)
if (taskType === 'finance_dashboard_snapshot') {
return 'finance_snapshot'
}
if (taskType === 'digital_employee_reminder_scan') {
return 'reminder_scan'
}
if (taskType === 'global_risk_scan') {
return 'risk_graph'
}
if (taskType === 'employee_behavior_profile_scan') {
return 'employee_profile'
}
if (taskType === 'risk_clue_collect') {
return 'risk_clue'
}
if (KNOWLEDGE_JOB_TYPES.has(taskType)) {
return 'knowledge'
}
return ''
}
export function extractWorkRecordToolSummary(run) {
const taskType = resolveWorkRecordTaskType(run)
const toolCalls = Array.isArray(run?.tool_calls) ? run.tool_calls : []
const matchedCall = toolCalls.find((toolCall) => {
const requestJson = toObject(toolCall?.request_json)
const responseJson = toObject(toolCall?.response_json)
const candidates = [
requestJson.task_type,
requestJson.job_type,
responseJson.report_type,
responseJson.task_type,
responseJson.job_type,
resolveTaskTypeFromToolName(toolCall?.tool_name)
].map(normalizeTaskType)
return candidates.includes(taskType)
}) || toolCalls[0]
const responseJson = toObject(matchedCall?.response_json)
const nestedSummary = toObject(responseJson.summary)
return Object.keys(nestedSummary).length ? nestedSummary : responseJson
}
export function resolveWorkRecordModuleLabel(run) {
const routeJson = run?.route_json || {}
const taskLabel = resolveWorkRecordTaskLabel(run)
if (taskLabel) {
return taskLabel
}
if (KNOWLEDGE_JOB_TYPES.has(routeJson.job_type)) {
return '知识制度整理'
}
if (routeJson.selected_agent) {
return '数字员工'
}
if (routeJson.folder) {
return String(routeJson.folder)
}
return resolveWorkRecordSourceLabel(run?.source)
}
export function resolveWorkRecordTitle(run) {
const routeJson = run?.route_json || {}
const taskLabel = resolveWorkRecordTaskLabel(run)
if (taskLabel) {
const suffix = String(routeJson.task_name || routeJson.folder || '本次运行').trim()
return suffix && suffix !== taskLabel ? `${taskLabel} · ${suffix}` : taskLabel
}
if (KNOWLEDGE_JOB_TYPES.has(routeJson.job_type)) {
return `知识制度整理 · ${routeJson.folder || '未指定目录'}`
}
return `数字员工调用 · ${resolveWorkRecordModuleLabel(run)}`
}
export function resolveWorkRecordStatusLabel(run) {
return resolveAgentRunStatus(run).label
}
export function resolveWorkRecordStatusTone(run) {
return resolveAgentRunStatus(run).tone
}
export function resolveWorkRecordStatusNote(run) {
const statusInfo = resolveAgentRunStatus(run)
if (statusInfo.note) {
return statusInfo.note
}
const heartbeat = resolveAgentRunHeartbeat(run)
if (heartbeat.at !== null) {
return `最后心跳 ${formatWorkRecordDateTime(heartbeat.at)}`
}
return '暂无额外状态'
}
export function resolveWorkRecordSummaryMeta(run) {
const statusInfo = resolveAgentRunStatus(run)
const progressText = formatAgentRunProgress(run)
const elapsedLabel = run?.status === 'running' ? '已运行' : '耗时'
const elapsedText = formatAgentRunElapsed(run)
const parts = [`阶段 ${statusInfo.phaseLabel}`]
if (progressText) {
parts.push(progressText)
}
if (elapsedText !== '—') {
parts.push(`${elapsedLabel} ${elapsedText}`)
}
return parts.join(' · ')
}