feat: 财务看板口径重构与半年模拟数据及报销状态注册表
- 重构 finance_dashboard 口径计算,新增模拟公司画像数据生成与筛选 - 引入 expense_claim_status_registry 统一报销状态流转 - 完善报销草稿流程、Item Sync 与本体解析器 - 优化总览页趋势图、分页组件与请求进度步骤 - 增强报销申请快速预览、本体工具与详情展示 - 新增半年报销模拟数据种子脚本与状态审计工具 - 补充财务看板、报销状态注册与模拟数据测试覆盖
This commit is contained in:
@@ -31,11 +31,11 @@ export const APPLICATION_PREVIEW_FIELD_DEFINITIONS = [
|
||||
export const APPLICATION_TRANSPORT_MODE_OPTIONS = ['火车', '飞机', '轮船']
|
||||
|
||||
const APPLICATION_POLICY_PENDING_TEXT = '填写地点和天数后自动测算'
|
||||
const APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT = '选择火车、飞机或轮船后自动生成交通参考票价,报销阶段按真实票据复核'
|
||||
const APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT = '选择火车、飞机或轮船后自动预估交通费用'
|
||||
|
||||
export function resolveApplicationTimeLabel(applicationType = '') {
|
||||
const label = String(applicationType || '').trim()
|
||||
if (/差旅|出差/.test(label)) return '行程时间'
|
||||
if (/差旅|出差/.test(label)) return '出发时间'
|
||||
if (/招待|宴请|餐饮/.test(label)) return '招待时间'
|
||||
return '申请时间'
|
||||
}
|
||||
@@ -47,10 +47,36 @@ function resolveApplicationFieldLabel(item, fields = {}) {
|
||||
return item.label
|
||||
}
|
||||
|
||||
function isTravelApplicationType(applicationType = '') {
|
||||
return /差旅|出差/.test(String(applicationType || '').trim())
|
||||
}
|
||||
|
||||
function resolveApplicationTripDateParts(fields = {}) {
|
||||
const timeText = String(fields.time || '').trim()
|
||||
const matchedDates = timeText.match(/20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/g) || []
|
||||
const startDate = normalizeDateText(matchedDates[0] || timeText)
|
||||
const explicitEndDate = normalizeDateText(matchedDates[matchedDates.length - 1] || '')
|
||||
const inferredEndDate = explicitEndDate && explicitEndDate !== startDate
|
||||
? explicitEndDate
|
||||
: buildEndDateFromDays(startDate, fields.days)
|
||||
|
||||
return {
|
||||
startDate,
|
||||
endDate: inferredEndDate || explicitEndDate || startDate
|
||||
}
|
||||
}
|
||||
|
||||
function compactText(value) {
|
||||
return String(value || '').replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
function looksLikeStructuredTravelApplication(text) {
|
||||
const source = String(text || '')
|
||||
return /(?:发生时间|业务发生时间|申请时间|时间)\s*[::]/.test(source)
|
||||
&& /(?:地点|业务地点|发生地点|目的地)\s*[::]/.test(source)
|
||||
&& /(?:天数|出差天数|申请天数)\s*[::]?\s*(?:\d+|[一二两三四五六七八九十]{1,3})\s*天/.test(source)
|
||||
}
|
||||
|
||||
function resolveFirstMatch(text, patterns = []) {
|
||||
for (const pattern of patterns) {
|
||||
const match = text.match(pattern)
|
||||
@@ -106,6 +132,7 @@ function resolvePreviewToday(options = {}) {
|
||||
|
||||
function resolveApplicationType(text) {
|
||||
const compact = compactText(text)
|
||||
if (looksLikeStructuredTravelApplication(text)) return '差旅费用申请'
|
||||
if (/差旅|出差|高铁|动车|火车|飞机|机票|航班|酒店|住宿/.test(compact)) return '差旅费用申请'
|
||||
if (/交通|出租车|的士|网约车|打车|通勤/.test(compact)) return '交通费用申请'
|
||||
if (/住宿|酒店/.test(compact)) return '住宿费用申请'
|
||||
@@ -224,7 +251,7 @@ function buildTransportPolicyText(transportMode, location = '', transportEstimat
|
||||
if (!mode) return APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT
|
||||
const estimate = transportEstimate || buildMockApplicationTransportEstimate({ transportMode: mode, location, time })
|
||||
if (!estimate) return APPLICATION_TRANSPORT_REIMBURSEMENT_TEXT
|
||||
return `${estimate.basisText},报销阶段按真实票据复核`
|
||||
return estimate.basisText
|
||||
}
|
||||
|
||||
function ensureApplicationPolicyFields(fields = {}) {
|
||||
@@ -437,9 +464,8 @@ export function applyApplicationPolicyEstimateResult(preview = {}, result = {},
|
||||
allowanceAmount: result?.allowance_amount
|
||||
})
|
||||
const transportEstimate = systemEstimate.transportEstimate
|
||||
const queryLabel = transportEstimate?.queryDate || '出行日期待确认'
|
||||
const transportText = transportEstimate
|
||||
? `交通 ${systemEstimate.transportAmountDisplay}元(按 ${queryLabel} 参考票价) + `
|
||||
? `交通 ${systemEstimate.transportAmountDisplay}元 + `
|
||||
: ''
|
||||
const totalAmount = systemEstimate.totalAmountDisplay
|
||||
const amount = totalAmount ? `${totalAmount}元` : fields.amount
|
||||
@@ -499,7 +525,6 @@ export function refreshApplicationPreviewTransportEstimate(preview = {}) {
|
||||
const hotelAmount = formatPolicyMoney(hotelAmountSource)
|
||||
const allowanceAmount = formatPolicyMoney(allowanceAmountSource)
|
||||
const hasPolicyAmounts = parseMoneyNumber(hotelAmountSource) > 0 || parseMoneyNumber(allowanceAmountSource) > 0
|
||||
const queryLabel = transportEstimate.queryDate || '出行日期待确认'
|
||||
const nextFields = {
|
||||
...fields,
|
||||
transportPolicy: buildTransportPolicyText(fields.transportMode, location, transportEstimate, fields.time),
|
||||
@@ -513,7 +538,7 @@ export function refreshApplicationPreviewTransportEstimate(preview = {}) {
|
||||
if (hasPolicyAmounts) {
|
||||
const days = Number(policyResult.days) || parseApplicationDaysValue(fields.days) || 1
|
||||
const totalAmount = systemEstimate.totalAmountDisplay
|
||||
nextFields.policyEstimate = `交通 ${systemEstimate.transportAmountDisplay}元(按 ${queryLabel} 参考票价) + 住宿 ${hotelAmount}元 + 补贴 ${allowanceAmount}元 = ${totalAmount}元(${days}天)`
|
||||
nextFields.policyEstimate = `交通 ${systemEstimate.transportAmountDisplay}元 + 住宿 ${hotelAmount}元 + 补贴 ${allowanceAmount}元 = ${totalAmount}元(${days}天)`
|
||||
nextFields.amount = totalAmount ? `${totalAmount}元` : nextFields.amount
|
||||
nextFields.policyTotalAmount = totalAmount ? `${totalAmount}元` : ''
|
||||
}
|
||||
@@ -639,17 +664,41 @@ export function buildModelRefinedApplicationPreview(localPreview = {}, ontology
|
||||
export function buildApplicationPreviewRows(preview = {}) {
|
||||
const normalized = normalizeApplicationPreview(preview)
|
||||
const fields = normalized.fields || {}
|
||||
return APPLICATION_PREVIEW_FIELD_DEFINITIONS.map((item) => {
|
||||
return APPLICATION_PREVIEW_FIELD_DEFINITIONS.flatMap((item) => {
|
||||
if (item.key === 'time' && isTravelApplicationType(fields.applicationType)) {
|
||||
const tripDates = resolveApplicationTripDateParts(fields)
|
||||
const rawValue = fields[item.key]
|
||||
const missing = item.required !== false && !isApplicationPreviewValueProvided(rawValue)
|
||||
return [
|
||||
{
|
||||
...item,
|
||||
label: '出发时间',
|
||||
value: tripDates.startDate || '待补充',
|
||||
editable: item.editable !== false,
|
||||
highlight: Boolean(item.highlight),
|
||||
missing
|
||||
},
|
||||
{
|
||||
key: 'time_return',
|
||||
label: '返回时间',
|
||||
value: tripDates.endDate || '待补充',
|
||||
editable: false,
|
||||
highlight: Boolean(item.highlight),
|
||||
missing
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const rawValue = fields[item.key]
|
||||
const value = String(rawValue || '').trim() || '待补充'
|
||||
return {
|
||||
return [{
|
||||
...item,
|
||||
label: resolveApplicationFieldLabel(item, fields),
|
||||
value,
|
||||
editable: item.editable !== false,
|
||||
highlight: Boolean(item.highlight),
|
||||
missing: item.required !== false && !isApplicationPreviewValueProvided(rawValue)
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user