feat: 增加差旅报销标准测算和财务终审流程

新增差旅报销测算接口及 Spreadsheet 规则解析,审批流程拆分
直属领导审批与财务终审两阶段并细分权限,修复 PDF 文本层
缺失时自动回退 OCR,提交后清理关联会话,前端适配审批流
交互并补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-21 09:28:33 +08:00
parent 002bf4f756
commit 8f65661809
43 changed files with 4366 additions and 410 deletions

View File

@@ -21,7 +21,7 @@ const LOCATION_REQUIRED_EXPENSE_TYPES = new Set([
])
const REIMBURSEMENT_PROGRESS_LABELS = [
'保存草稿',
'创建单据',
'待提交',
'AI预审',
'直属领导审批',
@@ -270,6 +270,21 @@ function normalizeText(value) {
return String(value || '').trim()
}
function isEmailLike(value) {
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(normalizeText(value))
}
function resolveDisplayName(...values) {
for (const value of values) {
const normalized = normalizeText(value)
if (normalized && !isEmailLike(normalized)) {
return normalized
}
}
return ''
}
function getRiskFlags(claim) {
return Array.isArray(claim?.risk_flags_json) ? claim.risk_flags_json : []
}
@@ -344,7 +359,7 @@ function buildCompletedStepMeta(claim, label) {
const stepLabel = normalizeText(label)
const employeeName = normalizeText(claim?.employee_name) || '申请人'
if (stepLabel === '保存草稿') {
if (stepLabel === '创建单据') {
const createdAt = formatDateTime(claim?.created_at)
return buildProgressStepMeta(`${employeeName}创建`, createdAt)
}
@@ -362,7 +377,12 @@ function buildCompletedStepMeta(claim, label) {
if (stepLabel === '直属领导审批' || stepLabel === '财务审批') {
const approvalEvent = findApprovalEventForStep(claim, stepLabel)
if (approvalEvent) {
const operator = normalizeText(approvalEvent.operator) || (stepLabel === '财务审批' ? '财务' : '审批人')
const operator = resolveDisplayName(
approvalEvent.operator,
approvalEvent.operator_name,
approvalEvent.operatorName,
stepLabel === '直属领导审批' ? claim?.manager_name : ''
) || (stepLabel === '财务审批' ? '财务' : '直属领导')
const approvedAt = formatDateTime(approvalEvent.created_at || approvalEvent.createdAt)
return buildProgressStepMeta(`${operator}通过`, approvedAt, `${operator}审批通过 ${approvedAt}`.trim())
}
@@ -383,7 +403,7 @@ function buildCompletedStepMeta(claim, label) {
function resolveCurrentStepStartedAt(claim, label) {
const stepLabel = normalizeText(label)
if (stepLabel === '保存草稿') {
if (stepLabel === '创建单据') {
return claim?.created_at
}
if (stepLabel === '待提交') {
@@ -539,7 +559,7 @@ export function mapExpenseClaimToRequest(claim) {
employeeName: String(claim?.employee_name || '').trim() || '待补充',
employeePosition: String(claim?.employee_position || '').trim(),
employeeGrade: String(claim?.employee_grade || '').trim(),
managerName: String(claim?.manager_name || '').trim(),
managerName: resolveDisplayName(claim?.manager_name),
roleLabels: Array.isArray(claim?.role_labels) ? claim.role_labels.filter(Boolean) : [],
entity: '',
typeCode,