|
|
|
|
@@ -54,6 +54,8 @@ const DOCUMENT_BACKED_EXPENSE_TYPES = new Set([
|
|
|
|
|
const DOCUMENT_TYPE_APPLICATION = 'application'
|
|
|
|
|
const DOCUMENT_TYPE_REIMBURSEMENT = 'reimbursement'
|
|
|
|
|
const RELATED_APPLICATION_STEP_LABEL = '关联单据'
|
|
|
|
|
const APPLICATION_LINK_STATUS_STEP_LABEL = '关联单据状态'
|
|
|
|
|
const APPLICATION_ARCHIVE_STAGE_LABEL = '申请归档'
|
|
|
|
|
const ARCHIVED_STEP_LABEL = '已归档'
|
|
|
|
|
|
|
|
|
|
const REIMBURSEMENT_PROGRESS_LABELS = [
|
|
|
|
|
@@ -70,13 +72,17 @@ const APPLICATION_PROGRESS_LABELS = [
|
|
|
|
|
'创建申请',
|
|
|
|
|
'直属领导审批',
|
|
|
|
|
'预算管理者审批',
|
|
|
|
|
'审批完成'
|
|
|
|
|
'审批完成',
|
|
|
|
|
APPLICATION_LINK_STATUS_STEP_LABEL,
|
|
|
|
|
ARCHIVED_STEP_LABEL
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const APPLICATION_PROGRESS_LABELS_WITHOUT_BUDGET = [
|
|
|
|
|
'创建申请',
|
|
|
|
|
'直属领导审批',
|
|
|
|
|
'审批完成'
|
|
|
|
|
'审批完成',
|
|
|
|
|
APPLICATION_LINK_STATUS_STEP_LABEL,
|
|
|
|
|
ARCHIVED_STEP_LABEL
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
function parseNumber(value) {
|
|
|
|
|
@@ -425,6 +431,17 @@ function resolveWorkflowNode(claim, approvalMeta, isApplicationDocument = false)
|
|
|
|
|
const rawNode = String(claim?.approval_stage || '').trim()
|
|
|
|
|
|
|
|
|
|
if (rawNode) {
|
|
|
|
|
if (
|
|
|
|
|
isApplicationDocument
|
|
|
|
|
&& approvalMeta.key === 'completed'
|
|
|
|
|
&& (
|
|
|
|
|
rawNode === '审批完成'
|
|
|
|
|
|| rawNode.includes('审批完成')
|
|
|
|
|
|| rawNode.includes('申请完成')
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return APPLICATION_LINK_STATUS_STEP_LABEL
|
|
|
|
|
}
|
|
|
|
|
if (rawNode === '审批流转' || rawNode.includes('AI预审') || rawNode.includes('AI验审')) {
|
|
|
|
|
return approvalMeta.key === 'draft' || approvalMeta.key === 'supplement' ? '待提交' : '直属领导审批'
|
|
|
|
|
}
|
|
|
|
|
@@ -444,7 +461,7 @@ function resolveWorkflowNode(claim, approvalMeta, isApplicationDocument = false)
|
|
|
|
|
|
|
|
|
|
if (approvalMeta.key === 'completed') {
|
|
|
|
|
const normalizedStatus = String(claim?.status || '').trim().toLowerCase()
|
|
|
|
|
return isApplicationDocument ? '审批完成' : normalizedStatus === 'paid' ? '已付款' : '归档入账'
|
|
|
|
|
return isApplicationDocument ? APPLICATION_LINK_STATUS_STEP_LABEL : normalizedStatus === 'paid' ? '已付款' : '归档入账'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return '直属领导审批'
|
|
|
|
|
@@ -578,9 +595,15 @@ function resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode) {
|
|
|
|
|
const normalizedNode = String(workflowNode || '').trim()
|
|
|
|
|
|
|
|
|
|
if (approvalMeta.key === 'completed') {
|
|
|
|
|
return 3
|
|
|
|
|
return normalizedNode.includes(APPLICATION_ARCHIVE_STAGE_LABEL) ? 4 : 3
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (normalizedNode.includes(APPLICATION_ARCHIVE_STAGE_LABEL) || normalizedNode.includes(ARCHIVED_STEP_LABEL)) {
|
|
|
|
|
return 4
|
|
|
|
|
}
|
|
|
|
|
if (normalizedNode.includes(APPLICATION_LINK_STATUS_STEP_LABEL)) {
|
|
|
|
|
return 3
|
|
|
|
|
}
|
|
|
|
|
if (normalizedNode.includes('审批完成') || normalizedNode.includes('申请完成')) {
|
|
|
|
|
return 3
|
|
|
|
|
}
|
|
|
|
|
@@ -602,6 +625,44 @@ function resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode) {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isApplicationArchivedWorkflow(claim, workflowNode) {
|
|
|
|
|
const normalizedNode = normalizeText(workflowNode || claim?.approval_stage || claim?.workflowNode)
|
|
|
|
|
if (normalizedNode.includes(APPLICATION_ARCHIVE_STAGE_LABEL) || normalizedNode.includes(ARCHIVED_STEP_LABEL)) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return getRiskFlags(claim).some((flag) => (
|
|
|
|
|
flag
|
|
|
|
|
&& typeof flag === 'object'
|
|
|
|
|
&& normalizeText(flag.source) === 'application_archive_sync'
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveApplicationLinkedReimbursementNo(claim) {
|
|
|
|
|
for (const flag of [...getRiskFlags(claim)].reverse()) {
|
|
|
|
|
if (!flag || typeof flag !== 'object') {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
const generatedNo = normalizeText(
|
|
|
|
|
flag.generated_draft_claim_no
|
|
|
|
|
|| flag.generatedDraftClaimNo
|
|
|
|
|
|| flag.reimbursement_claim_no
|
|
|
|
|
|| flag.reimbursementClaimNo
|
|
|
|
|
)
|
|
|
|
|
if (generatedNo) {
|
|
|
|
|
return generatedNo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildApplicationLinkStatusStepMeta(claim) {
|
|
|
|
|
const reimbursementNo = resolveApplicationLinkedReimbursementNo(claim)
|
|
|
|
|
const updatedAt = formatDateTime(claim?.updated_at)
|
|
|
|
|
return reimbursementNo
|
|
|
|
|
? buildProgressStepMeta(`关联中 ${reimbursementNo}`, updatedAt)
|
|
|
|
|
: buildProgressStepMeta('未关联', updatedAt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeText(value) {
|
|
|
|
|
return String(value || '').trim()
|
|
|
|
|
}
|
|
|
|
|
@@ -1069,6 +1130,10 @@ function buildCompletedStepMeta(claim, label) {
|
|
|
|
|
return buildProgressStepMeta('待核对关联单据', createdAt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stepLabel === APPLICATION_LINK_STATUS_STEP_LABEL) {
|
|
|
|
|
return buildApplicationLinkStatusStepMeta(claim)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stepLabel === '创建单据' || stepLabel === '创建申请') {
|
|
|
|
|
const createdAt = formatDateTime(claim?.created_at)
|
|
|
|
|
return buildProgressStepMeta(stepLabel === '创建申请' ? `${employeeName}发起申请` : `${employeeName}创建`, createdAt)
|
|
|
|
|
@@ -1201,24 +1266,32 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
|
|
|
|
|
&& !hasMergedApplicationBudgetApproval
|
|
|
|
|
&& applicationRequiresBudgetReviewStep(claim, workflowNode)
|
|
|
|
|
)
|
|
|
|
|
const isApplicationDocument = documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
|
|
|
|
const applicationArchived = isApplicationDocument && isApplicationArchivedWorkflow(claim, workflowNode)
|
|
|
|
|
const progressLabels =
|
|
|
|
|
documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
|
|
|
|
isApplicationDocument
|
|
|
|
|
? hasApplicationReturnStep
|
|
|
|
|
? ['创建申请', '直属领导审批', '退回', '待提交']
|
|
|
|
|
: hasMergedApplicationBudgetApproval
|
|
|
|
|
? ['创建申请', '直属领导审批', '审批完成']
|
|
|
|
|
? ['创建申请', '直属领导审批', '审批完成', APPLICATION_LINK_STATUS_STEP_LABEL, ARCHIVED_STEP_LABEL]
|
|
|
|
|
: shouldShowApplicationBudgetStep
|
|
|
|
|
? APPLICATION_PROGRESS_LABELS
|
|
|
|
|
: APPLICATION_PROGRESS_LABELS_WITHOUT_BUDGET
|
|
|
|
|
: REIMBURSEMENT_PROGRESS_LABELS
|
|
|
|
|
const applicationLinkIndex = progressLabels.indexOf(APPLICATION_LINK_STATUS_STEP_LABEL)
|
|
|
|
|
const applicationArchiveIndex = progressLabels.indexOf(ARCHIVED_STEP_LABEL)
|
|
|
|
|
const currentIndex =
|
|
|
|
|
documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
|
|
|
|
isApplicationDocument
|
|
|
|
|
? hasApplicationReturnStep
|
|
|
|
|
? 3
|
|
|
|
|
: Math.min(
|
|
|
|
|
resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode),
|
|
|
|
|
Math.max(0, progressLabels.length - 1)
|
|
|
|
|
)
|
|
|
|
|
: applicationArchived && applicationArchiveIndex >= 0
|
|
|
|
|
? applicationArchiveIndex
|
|
|
|
|
: approvalMeta.key === 'completed' && applicationLinkIndex >= 0
|
|
|
|
|
? applicationLinkIndex
|
|
|
|
|
: Math.min(
|
|
|
|
|
resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode),
|
|
|
|
|
Math.max(0, progressLabels.length - 1)
|
|
|
|
|
)
|
|
|
|
|
: resolveProgressCurrentIndex(approvalMeta, workflowNode)
|
|
|
|
|
const currentTime =
|
|
|
|
|
approvalMeta.key === 'completed'
|
|
|
|
|
@@ -1233,7 +1306,7 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
|
|
|
|
|
|
|
|
|
|
return progressLabels.map((label, index) => {
|
|
|
|
|
const displayLabel = resolveProgressDisplayLabel(label, documentTypeCode, claim, approvalMeta)
|
|
|
|
|
if (approvalMeta.key === 'completed') {
|
|
|
|
|
if (approvalMeta.key === 'completed' && (!isApplicationDocument || applicationArchived)) {
|
|
|
|
|
const stepMeta = buildCompletedStepMeta(claim, label)
|
|
|
|
|
return {
|
|
|
|
|
index: index + 1,
|
|
|
|
|
@@ -1264,6 +1337,20 @@ function buildProgressSteps(approvalMeta, workflowNode, claim = {}, options = {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index === currentIndex) {
|
|
|
|
|
if (isApplicationDocument && label === APPLICATION_LINK_STATUS_STEP_LABEL) {
|
|
|
|
|
const stepMeta = buildApplicationLinkStatusStepMeta(claim)
|
|
|
|
|
return {
|
|
|
|
|
index: index + 1,
|
|
|
|
|
label: displayLabel,
|
|
|
|
|
rawLabel: label,
|
|
|
|
|
time: stepMeta.time,
|
|
|
|
|
detail: stepMeta.detail,
|
|
|
|
|
title: stepMeta.title,
|
|
|
|
|
done: false,
|
|
|
|
|
active: true,
|
|
|
|
|
current: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const stayDuration = formatDurationFrom(resolveCurrentStepStartedAt(claim, label))
|
|
|
|
|
return {
|
|
|
|
|
index: index + 1,
|
|
|
|
|
@@ -1385,6 +1472,11 @@ export function mapExpenseClaimToRequest(claim) {
|
|
|
|
|
const isApplicationDocument = documentTypeMeta.documentTypeCode === DOCUMENT_TYPE_APPLICATION
|
|
|
|
|
const approvalMeta = resolveApprovalMeta(claim?.status)
|
|
|
|
|
const workflowNode = resolveWorkflowNode(claim, approvalMeta, isApplicationDocument)
|
|
|
|
|
const applicationArchived = isApplicationDocument && isApplicationArchivedWorkflow(claim, workflowNode)
|
|
|
|
|
const applicationLinkedReimbursementNo = isApplicationDocument ? resolveApplicationLinkedReimbursementNo(claim) : ''
|
|
|
|
|
const applicationLinkStatusText = applicationLinkedReimbursementNo
|
|
|
|
|
? `关联中 ${applicationLinkedReimbursementNo}`
|
|
|
|
|
: '未关联'
|
|
|
|
|
const invoiceCount = Math.max(0, parseNumber(claim?.invoice_count))
|
|
|
|
|
const riskMeta = buildRiskMeta(claim?.risk_flags_json)
|
|
|
|
|
const riskSummary = riskMeta.summary
|
|
|
|
|
@@ -1453,10 +1545,18 @@ export function mapExpenseClaimToRequest(claim) {
|
|
|
|
|
secondaryStatusValue: isApplicationDocument
|
|
|
|
|
? approvalMeta.key === 'supplement'
|
|
|
|
|
? '领导已退回,待重新提交'
|
|
|
|
|
: '已进入审批流程'
|
|
|
|
|
: applicationArchived
|
|
|
|
|
? '已归档'
|
|
|
|
|
: approvalMeta.key === 'completed'
|
|
|
|
|
? applicationLinkStatusText
|
|
|
|
|
: '已进入审批流程'
|
|
|
|
|
: (invoiceCount > 0 ? `已关联 ${invoiceCount} 张票据` : '待上传票据'),
|
|
|
|
|
secondaryStatusTone: isApplicationDocument
|
|
|
|
|
? approvalMeta.key === 'supplement' ? 'warning' : 'success'
|
|
|
|
|
? approvalMeta.key === 'supplement'
|
|
|
|
|
? 'warning'
|
|
|
|
|
: approvalMeta.key === 'completed' && !applicationArchived && !applicationLinkedReimbursementNo
|
|
|
|
|
? 'warning'
|
|
|
|
|
: 'success'
|
|
|
|
|
: (invoiceCount > 0 ? 'success' : 'warning'),
|
|
|
|
|
riskSummary,
|
|
|
|
|
attachmentSummary: isApplicationDocument ? '申请单' : (invoiceCount > 0 ? `${invoiceCount} 张票据` : '无'),
|
|
|
|
|
|