feat: 完善审批退回流程与报销申请关联

后端优化报销单访问策略和常量定义,增强退回原因和审批状态
流转,前端完善退回对话框和审批交互组件,新增报销申请关联
模型,优化文档中心行数据和审批收件箱工具函数,增强引导
流程和会话模型,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-27 14:35:17 +08:00
parent 7d32eae74e
commit cbb98f4469
30 changed files with 1794 additions and 250 deletions

View File

@@ -426,6 +426,29 @@ function resolveDisplayName(...values) {
return ''
}
function resolveApplicationApproverName(claim) {
return resolveDisplayName(
claim?.manager_name,
claim?.managerName,
claim?.profile_manager,
claim?.profileManager,
claim?.direct_manager_name,
claim?.directManagerName
) || '直属领导'
}
function resolveProgressDisplayLabel(label, documentTypeCode, claim, approvalMeta) {
if (
documentTypeCode === DOCUMENT_TYPE_APPLICATION
&& approvalMeta.key !== 'completed'
&& normalizeText(label) === '直属领导审批'
) {
return `等待 ${resolveApplicationApproverName(claim)} 批复`
}
return label
}
function getRiskFlags(claim) {
return Array.isArray(claim?.risk_flags_json) ? claim.risk_flags_json : []
}
@@ -488,6 +511,25 @@ function findLatestReturnEvent(claim) {
)
}
function findLatestApplicationReturnEvent(claim) {
return getLatestEvent(
getRiskFlags(claim).filter((flag) => {
if (!flag || typeof flag !== 'object' || normalizeText(flag.source) !== 'manual_return') {
return false
}
const eventType = normalizeText(flag.event_type || flag.eventType)
const returnStage = normalizeText(flag.return_stage || flag.returnStage || flag.previous_approval_stage)
const stageKey = normalizeText(flag.return_stage_key || flag.returnStageKey)
return (
eventType === 'expense_application_return'
|| stageKey === 'direct_manager'
|| returnStage.includes('直属领导')
|| returnStage.includes('领导审批')
)
})
)
}
function buildProgressStepMeta(time, detail = '', title = '') {
return {
time,
@@ -532,6 +574,28 @@ function buildCompletedStepMeta(claim, label) {
const updatedAt = formatDateTime(claim?.updated_at)
return buildProgressStepMeta('财务通过', updatedAt, `财务审批通过 ${updatedAt}`.trim())
}
if (stepLabel === '直属领导审批') {
const returnEvent = findLatestApplicationReturnEvent(claim)
if (returnEvent) {
const handledAt = formatDateTime(returnEvent.created_at || returnEvent.createdAt)
return buildProgressStepMeta('已处理', handledAt, `直属领导已处理 ${handledAt}`.trim())
}
}
}
if (stepLabel === '退回') {
const returnEvent = findLatestApplicationReturnEvent(claim) || findLatestReturnEvent(claim)
if (returnEvent) {
const operator = resolveDisplayName(
returnEvent.operator,
returnEvent.operator_name,
returnEvent.operatorName,
claim?.manager_name
) || '直属领导'
const returnedAt = formatDateTime(returnEvent.created_at || returnEvent.createdAt)
return buildProgressStepMeta(`${operator}退回`, returnedAt, `${operator}退回 ${returnedAt}`.trim())
}
}
if (stepLabel === '归档入账') {
@@ -574,13 +638,22 @@ function resolveCurrentStepStartedAt(claim, label) {
function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}) {
const documentTypeCode = String(options.documentTypeCode || '').trim()
const hasApplicationReturnStep = (
documentTypeCode === DOCUMENT_TYPE_APPLICATION
&& Boolean(findLatestApplicationReturnEvent(claim))
&& approvalMeta.key === 'supplement'
)
const progressLabels =
documentTypeCode === DOCUMENT_TYPE_APPLICATION
? APPLICATION_PROGRESS_LABELS
? hasApplicationReturnStep
? ['创建申请', '直属领导审批', '退回', '待提交']
: APPLICATION_PROGRESS_LABELS
: REIMBURSEMENT_PROGRESS_LABELS
const currentIndex =
documentTypeCode === DOCUMENT_TYPE_APPLICATION
? resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode)
? hasApplicationReturnStep
? 3
: resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode)
: resolveProgressCurrentIndex(approvalMeta, workflowNode)
const currentTime =
approvalMeta.key === 'completed'
@@ -592,11 +665,13 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
: '进行中'
return progressLabels.map((label, index) => {
const displayLabel = resolveProgressDisplayLabel(label, documentTypeCode, claim, approvalMeta)
if (approvalMeta.key === 'completed') {
const stepMeta = buildCompletedStepMeta(claim, label)
return {
index: index + 1,
label,
label: displayLabel,
rawLabel: label,
time: stepMeta.time,
detail: stepMeta.detail,
title: stepMeta.title,
@@ -610,7 +685,8 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
const stepMeta = buildCompletedStepMeta(claim, label)
return {
index: index + 1,
label,
label: displayLabel,
rawLabel: label,
time: stepMeta.time,
detail: stepMeta.detail,
title: stepMeta.title,
@@ -624,10 +700,11 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
const stayDuration = formatDurationFrom(resolveCurrentStepStartedAt(claim, label))
return {
index: index + 1,
label,
label: displayLabel,
rawLabel: label,
time: stayDuration ? `停留 ${stayDuration}` : currentTime,
detail: '',
title: stayDuration ? `当前${label}已停留 ${stayDuration}` : currentTime,
title: stayDuration ? `当前${displayLabel}已停留 ${stayDuration}` : currentTime,
done: false,
active: true,
current: true
@@ -636,7 +713,8 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
return {
index: index + 1,
label,
label: displayLabel,
rawLabel: label,
time: '待处理',
detail: '',
title: '待处理',
@@ -758,9 +836,13 @@ export function mapExpenseClaimToRequest(claim) {
approvalTone: approvalMeta.tone,
secondaryStatusLabel: isApplicationDocument ? '申请材料' : (typeCode === 'travel' ? '行程状态' : '票据状态'),
secondaryStatusValue: isApplicationDocument
? '已进入审批流程'
? approvalMeta.key === 'supplement'
? '领导已退回,待重新提交'
: '已进入审批流程'
: (invoiceCount > 0 ? `已关联 ${invoiceCount} 张票据` : '待上传票据'),
secondaryStatusTone: isApplicationDocument ? 'success' : (invoiceCount > 0 ? 'success' : 'warning'),
secondaryStatusTone: isApplicationDocument
? approvalMeta.key === 'supplement' ? 'warning' : 'success'
: (invoiceCount > 0 ? 'success' : 'warning'),
riskSummary,
attachmentSummary: isApplicationDocument ? '申请单' : (invoiceCount > 0 ? `${invoiceCount} 张票据` : '无'),
expenseTableSummary: isApplicationDocument