feat: 完善文档中心与报销申请交互及侧边栏重构
后端优化编排器报销查询和本体检测精度,增强报销单草稿保 存和附件回填逻辑,前端重构侧边栏组件支持折叠和图标导 航,完善文档中心状态筛选和详情提示,报销创建和审批详情 页优化会话管理和费用明细交互,新增助手应用服务和预设动 作工具函数,补充单元测试覆盖。
This commit is contained in:
@@ -5,6 +5,10 @@ import { filterActionableRiskFlags } from '../utils/riskFlags.js'
|
||||
|
||||
const EXPENSE_TYPE_LABELS = {
|
||||
travel: '差旅费',
|
||||
travel_application: '差旅费用申请',
|
||||
expense_application: '费用申请',
|
||||
purchase_application: '采购费用申请',
|
||||
meeting_application: '会务费用申请',
|
||||
train_ticket: '火车票',
|
||||
flight_ticket: '机票',
|
||||
ship_ticket: '轮船票',
|
||||
@@ -36,6 +40,8 @@ const SYSTEM_GENERATED_EXPENSE_TYPES = new Set(['travel_allowance'])
|
||||
const LONG_DISTANCE_TRAVEL_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket'])
|
||||
const ROUTE_DESCRIPTION_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket', 'ship_ticket', 'ferry_ticket', 'ride_ticket'])
|
||||
const HOTEL_DESCRIPTION_EXPENSE_TYPES = new Set(['hotel_ticket'])
|
||||
const DOCUMENT_TYPE_APPLICATION = 'application'
|
||||
const DOCUMENT_TYPE_REIMBURSEMENT = 'reimbursement'
|
||||
|
||||
const REIMBURSEMENT_PROGRESS_LABELS = [
|
||||
'创建单据',
|
||||
@@ -46,6 +52,12 @@ const REIMBURSEMENT_PROGRESS_LABELS = [
|
||||
'归档入账'
|
||||
]
|
||||
|
||||
const APPLICATION_PROGRESS_LABELS = [
|
||||
'创建申请',
|
||||
'直属领导审批',
|
||||
'审批完成'
|
||||
]
|
||||
|
||||
function parseNumber(value) {
|
||||
const nextValue = Number(value)
|
||||
return Number.isFinite(nextValue) ? nextValue : 0
|
||||
@@ -123,6 +135,28 @@ function resolveTypeLabel(typeCode) {
|
||||
return EXPENSE_TYPE_LABELS[String(typeCode || '').trim()] || EXPENSE_TYPE_LABELS.other
|
||||
}
|
||||
|
||||
function resolveDocumentTypeMeta(claim, typeCode) {
|
||||
const explicitType = String(
|
||||
claim?.document_type_code
|
||||
|| claim?.documentTypeCode
|
||||
|| claim?.document_type
|
||||
|| claim?.documentType
|
||||
|| ''
|
||||
).trim()
|
||||
const claimNo = String(claim?.claim_no || claim?.claimNo || '').trim().toUpperCase()
|
||||
const normalizedType = String(typeCode || '').trim()
|
||||
const isApplication =
|
||||
explicitType === DOCUMENT_TYPE_APPLICATION
|
||||
|| explicitType === 'expense_application'
|
||||
|| claimNo.startsWith('APP-')
|
||||
|| normalizedType === 'application'
|
||||
|| normalizedType.endsWith('_application')
|
||||
|
||||
return isApplication
|
||||
? { documentTypeCode: DOCUMENT_TYPE_APPLICATION, documentTypeLabel: '申请单' }
|
||||
: { documentTypeCode: DOCUMENT_TYPE_REIMBURSEMENT, documentTypeLabel: '报销单' }
|
||||
}
|
||||
|
||||
function normalizeExpenseType(typeCode) {
|
||||
return String(typeCode || '').trim() || 'other'
|
||||
}
|
||||
@@ -237,7 +271,7 @@ function resolveApprovalMeta(status) {
|
||||
return { key: 'in_progress', label: '审批中', tone: 'info' }
|
||||
}
|
||||
|
||||
function resolveWorkflowNode(claim, approvalMeta) {
|
||||
function resolveWorkflowNode(claim, approvalMeta, isApplicationDocument = false) {
|
||||
if (String(claim?.status || '').trim().toLowerCase() === 'returned') {
|
||||
return '待提交'
|
||||
}
|
||||
@@ -259,10 +293,10 @@ function resolveWorkflowNode(claim, approvalMeta) {
|
||||
}
|
||||
|
||||
if (approvalMeta.key === 'completed') {
|
||||
return '归档入账'
|
||||
return isApplicationDocument ? '审批完成' : '归档入账'
|
||||
}
|
||||
|
||||
return 'AI预审'
|
||||
return isApplicationDocument ? '直属领导审批' : 'AI预审'
|
||||
}
|
||||
|
||||
function stringifyRiskFlag(value) {
|
||||
@@ -345,6 +379,31 @@ function resolveProgressCurrentIndex(approvalMeta, workflowNode) {
|
||||
return 2
|
||||
}
|
||||
|
||||
function resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode) {
|
||||
const normalizedNode = String(workflowNode || '').trim()
|
||||
|
||||
if (approvalMeta.key === 'completed') {
|
||||
return 2
|
||||
}
|
||||
|
||||
if (normalizedNode.includes('审批完成') || normalizedNode.includes('申请完成')) {
|
||||
return 2
|
||||
}
|
||||
if (
|
||||
normalizedNode.includes('直属领导')
|
||||
|| normalizedNode.includes('领导审批')
|
||||
|| normalizedNode.includes('部门负责人')
|
||||
|| normalizedNode.includes('负责人审批')
|
||||
) {
|
||||
return 1
|
||||
}
|
||||
if (approvalMeta.key === 'draft' || approvalMeta.key === 'supplement' || approvalMeta.key === 'rejected') {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
@@ -438,9 +497,9 @@ function buildCompletedStepMeta(claim, label) {
|
||||
const stepLabel = normalizeText(label)
|
||||
const employeeName = normalizeText(claim?.employee_name) || '申请人'
|
||||
|
||||
if (stepLabel === '创建单据') {
|
||||
if (stepLabel === '创建单据' || stepLabel === '创建申请') {
|
||||
const createdAt = formatDateTime(claim?.created_at)
|
||||
return buildProgressStepMeta(`${employeeName}创建`, createdAt)
|
||||
return buildProgressStepMeta(stepLabel === '创建申请' ? `${employeeName}发起申请` : `${employeeName}创建`, createdAt)
|
||||
}
|
||||
|
||||
if (stepLabel === '待提交') {
|
||||
@@ -477,12 +536,17 @@ function buildCompletedStepMeta(claim, label) {
|
||||
return buildProgressStepMeta('归档入账', archivedAt)
|
||||
}
|
||||
|
||||
if (stepLabel === '审批完成') {
|
||||
const completedAt = formatDateTime(claim?.updated_at)
|
||||
return buildProgressStepMeta('审批完成', completedAt)
|
||||
}
|
||||
|
||||
return buildProgressStepMeta('已完成')
|
||||
}
|
||||
|
||||
function resolveCurrentStepStartedAt(claim, label) {
|
||||
const stepLabel = normalizeText(label)
|
||||
if (stepLabel === '创建单据') {
|
||||
if (stepLabel === '创建单据' || stepLabel === '创建申请') {
|
||||
return claim?.created_at
|
||||
}
|
||||
if (stepLabel === '待提交') {
|
||||
@@ -499,14 +563,22 @@ function resolveCurrentStepStartedAt(claim, label) {
|
||||
const leaderApprovalEvent = findApprovalEventForStep(claim, '直属领导审批')
|
||||
return leaderApprovalEvent?.created_at || leaderApprovalEvent?.createdAt || claim?.updated_at || claim?.submitted_at
|
||||
}
|
||||
if (stepLabel === '归档入账') {
|
||||
if (stepLabel === '归档入账' || stepLabel === '审批完成') {
|
||||
return claim?.updated_at || claim?.submitted_at
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function buildProgressSteps(approvalMeta, workflowNode, claim = {}) {
|
||||
const currentIndex = resolveProgressCurrentIndex(approvalMeta, workflowNode)
|
||||
function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}) {
|
||||
const documentTypeCode = String(options.documentTypeCode || '').trim()
|
||||
const progressLabels =
|
||||
documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
||||
? APPLICATION_PROGRESS_LABELS
|
||||
: REIMBURSEMENT_PROGRESS_LABELS
|
||||
const currentIndex =
|
||||
documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
||||
? resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode)
|
||||
: resolveProgressCurrentIndex(approvalMeta, workflowNode)
|
||||
const currentTime =
|
||||
approvalMeta.key === 'completed'
|
||||
? '已完成'
|
||||
@@ -516,7 +588,7 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}) {
|
||||
? '已退回'
|
||||
: '进行中'
|
||||
|
||||
return REIMBURSEMENT_PROGRESS_LABELS.map((label, index) => {
|
||||
return progressLabels.map((label, index) => {
|
||||
if (approvalMeta.key === 'completed') {
|
||||
const stepMeta = buildCompletedStepMeta(claim, label)
|
||||
return {
|
||||
@@ -636,8 +708,10 @@ function buildExpenseItems(claim, riskSummary) {
|
||||
export function mapExpenseClaimToRequest(claim) {
|
||||
const typeCode = String(claim?.expense_type || '').trim() || 'other'
|
||||
const typeLabel = resolveTypeLabel(typeCode)
|
||||
const documentTypeMeta = resolveDocumentTypeMeta(claim, typeCode)
|
||||
const isApplicationDocument = documentTypeMeta.documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
||||
const approvalMeta = resolveApprovalMeta(claim?.status)
|
||||
const workflowNode = resolveWorkflowNode(claim, approvalMeta)
|
||||
const workflowNode = resolveWorkflowNode(claim, approvalMeta, isApplicationDocument)
|
||||
const invoiceCount = Math.max(0, parseNumber(claim?.invoice_count))
|
||||
const riskSummary = buildRiskSummary(claim?.risk_flags_json)
|
||||
const expenseItems = buildExpenseItems(claim, riskSummary)
|
||||
@@ -659,8 +733,9 @@ export function mapExpenseClaimToRequest(claim) {
|
||||
entity: '',
|
||||
typeCode,
|
||||
typeLabel,
|
||||
detailVariant: typeCode === 'travel' ? 'travel' : 'general',
|
||||
title: String(claim?.reason || '').trim() || `${typeLabel}报销`,
|
||||
...documentTypeMeta,
|
||||
detailVariant: typeCode === 'travel' || typeCode === 'travel_application' ? 'travel' : 'general',
|
||||
title: String(claim?.reason || '').trim() || (isApplicationDocument ? typeLabel : `${typeLabel}报销`),
|
||||
sceneLabel: typeLabel,
|
||||
sceneTarget: String(claim?.location || '').trim() || '待补充',
|
||||
location: String(claim?.location || '').trim() || '待补充',
|
||||
@@ -678,18 +753,24 @@ export function mapExpenseClaimToRequest(claim) {
|
||||
approvalKey: approvalMeta.key,
|
||||
approvalStatus: approvalMeta.label,
|
||||
approvalTone: approvalMeta.tone,
|
||||
secondaryStatusLabel: typeCode === 'travel' ? '行程状态' : '票据状态',
|
||||
secondaryStatusValue: invoiceCount > 0 ? `已关联 ${invoiceCount} 张票据` : '待上传票据',
|
||||
secondaryStatusTone: invoiceCount > 0 ? 'success' : 'warning',
|
||||
secondaryStatusLabel: isApplicationDocument ? '申请材料' : (typeCode === 'travel' ? '行程状态' : '票据状态'),
|
||||
secondaryStatusValue: isApplicationDocument
|
||||
? '已进入审批流程'
|
||||
: (invoiceCount > 0 ? `已关联 ${invoiceCount} 张票据` : '待上传票据'),
|
||||
secondaryStatusTone: isApplicationDocument ? 'success' : (invoiceCount > 0 ? 'success' : 'warning'),
|
||||
riskSummary,
|
||||
attachmentSummary: invoiceCount > 0 ? `${invoiceCount} 张票据` : '无',
|
||||
expenseTableSummary: expenseItems.length
|
||||
attachmentSummary: isApplicationDocument ? '申请单' : (invoiceCount > 0 ? `${invoiceCount} 张票据` : '无'),
|
||||
expenseTableSummary: isApplicationDocument
|
||||
? '预计金额已纳入预算管理口径'
|
||||
: expenseItems.length
|
||||
? (invoiceCount > 0
|
||||
? `共 ${expenseItems.length} 条费用明细,已关联 ${invoiceCount} 张票据`
|
||||
: `共 ${expenseItems.length} 条费用明细,待补充票据`)
|
||||
: '暂无费用明细',
|
||||
note: String(claim?.reason || '').trim(),
|
||||
progressSteps: buildProgressSteps(approvalMeta, workflowNode, claim),
|
||||
progressSteps: buildProgressSteps(approvalMeta, workflowNode, claim, {
|
||||
documentTypeCode: documentTypeMeta.documentTypeCode
|
||||
}),
|
||||
expenseItems
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user