feat: 本体字段治理与风险规则模板执行器重构
- 新增本体字段注册表与字段治理审计脚本 - 重构风险规则模板执行器、DSL 验证与清单分类器 - 完善票据夹服务与差旅请求详情页交互 - 优化趋势图表与总览页数据展示 - 增强报销平台风险分级与模拟公司筛选 - 补充本体字段、风险规则生成与票据夹服务测试覆盖
This commit is contained in:
@@ -51,6 +51,7 @@ const emptyFinanceTrend = {
|
||||
labels: [],
|
||||
claimCount: [],
|
||||
claimAmount: [],
|
||||
categoryAmountSeries: [],
|
||||
applications: [],
|
||||
approved: [],
|
||||
avgHours: []
|
||||
@@ -130,6 +131,9 @@ function resolveTopRangeKey(range, customRange = {}) {
|
||||
if (key === '\u672c\u5468' || key === '\u4eca\u65e5') {
|
||||
return `recent-${resolveTopRangeDays(key, customRange)}-days`
|
||||
}
|
||||
if (/\d+/.test(key)) {
|
||||
return `recent-${resolveTopRangeDays(key, customRange)}-days`
|
||||
}
|
||||
return key || DEFAULT_OVERVIEW_RANGE
|
||||
}
|
||||
|
||||
@@ -155,7 +159,9 @@ export function useOverviewView(options = {}) {
|
||||
const financeDashboardPayload = ref(null)
|
||||
const financeDashboardLoading = ref(false)
|
||||
const financeDashboardError = ref(null)
|
||||
const financeDashboardRenderKey = ref(0)
|
||||
const financeDashboardLoaded = computed(() => Boolean(financeDashboardPayload.value))
|
||||
let financeDashboardRequestSeq = 0
|
||||
const systemDashboardPayload = ref(null)
|
||||
const systemDashboardLoading = ref(false)
|
||||
const systemDashboardError = ref(null)
|
||||
@@ -226,16 +232,27 @@ export function useOverviewView(options = {}) {
|
||||
}
|
||||
|
||||
const loadFinanceDashboard = async () => {
|
||||
const requestSeq = ++financeDashboardRequestSeq
|
||||
financeDashboardLoading.value = true
|
||||
financeDashboardError.value = null
|
||||
|
||||
try {
|
||||
financeDashboardPayload.value = await fetchFinanceDashboard(getFinanceRangeParams())
|
||||
const payload = await fetchFinanceDashboard(getFinanceRangeParams())
|
||||
if (requestSeq !== financeDashboardRequestSeq) {
|
||||
return
|
||||
}
|
||||
financeDashboardPayload.value = payload
|
||||
financeDashboardRenderKey.value += 1
|
||||
} catch (error) {
|
||||
if (requestSeq !== financeDashboardRequestSeq) {
|
||||
return
|
||||
}
|
||||
financeDashboardPayload.value = null
|
||||
financeDashboardError.value = error
|
||||
} finally {
|
||||
financeDashboardLoading.value = false
|
||||
if (requestSeq === financeDashboardRequestSeq) {
|
||||
financeDashboardLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,6 +906,7 @@ export function useOverviewView(options = {}) {
|
||||
financeDashboardError,
|
||||
financeDashboardLoaded,
|
||||
financeDashboardLoading,
|
||||
financeDashboardRenderKey,
|
||||
formatCompact,
|
||||
formatCurrency,
|
||||
formatMetricValue,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user