const REQUEST_TYPE_META = { travel: { label: '差旅费', detailVariant: 'travel', tone: 'travel', secondaryStatusLabel: '行程状态' }, entertainment: { label: '业务招待费', detailVariant: 'general', tone: 'entertainment', secondaryStatusLabel: '票据状态' }, hotel: { label: '住宿费', detailVariant: 'general', tone: 'travel', secondaryStatusLabel: '票据状态' }, transport: { label: '交通费', detailVariant: 'general', tone: 'travel', secondaryStatusLabel: '票据状态' }, meal: { label: '餐费', detailVariant: 'general', tone: 'meeting', secondaryStatusLabel: '票据状态' }, office: { label: '办公费', detailVariant: 'general', tone: 'office', secondaryStatusLabel: '票据状态' }, meeting: { label: '会务费', detailVariant: 'general', tone: 'meeting', secondaryStatusLabel: '票据状态' }, training: { label: '培训费', detailVariant: 'general', tone: 'training', secondaryStatusLabel: '票据状态' }, other: { label: '其他费用', detailVariant: 'general', tone: 'other', secondaryStatusLabel: '票据状态' } } const APPROVAL_META = { draft: { label: '草稿', tone: 'draft' }, in_progress: { label: '审批中', tone: 'info' }, supplement: { label: '待补充', tone: 'warning' }, completed: { label: '已完成', tone: 'success' }, rejected: { label: '已退回', tone: 'danger' } } const BACKEND_STATUS_META = { draft: { key: 'draft', label: '草稿', tone: 'draft' }, submitted: { key: 'in_progress', label: '审批中', tone: 'info' }, pending: { key: 'in_progress', label: '审批中', tone: 'info' }, reviewing: { key: 'in_progress', label: '审批中', tone: 'info' }, in_review: { key: 'in_progress', label: '审批中', tone: 'info' }, in_progress: { key: 'in_progress', label: '审批中', tone: 'info' }, approved: { key: 'completed', label: '已完成', tone: 'success' }, paid: { key: 'completed', label: '已完成', tone: 'success' }, completed: { key: 'completed', label: '已完成', tone: 'success' }, supplement: { key: 'supplement', label: '待补充', tone: 'warning' }, returned: { key: 'supplement', label: '待提交', tone: 'warning' }, rejected: { key: 'rejected', label: '已退回', tone: 'danger' }, cancelled: { key: 'rejected', label: '已退回', tone: 'danger' } } function parseRequestDateFromId(id) { const match = String(id || '').match(/(\d{4})[-]?(\d{2})(\d{2})/) if (!match) { return '' } const [, year, month, day] = match return `${year}-${month}-${day}` } function parseAmount(value) { if (typeof value === 'number' && Number.isFinite(value)) { return value } const normalized = String(value || '') .replace(/[,,\s]/g, '') .replace(/[¥¥]/g, '') .replace(/元/g, '') .trim() if (!normalized || !/^-?\d+(?:\.\d+)?$/.test(normalized)) { return null } const amount = Number(normalized) return Number.isFinite(amount) ? amount : null } export function formatRequestCurrency(value) { const amount = parseAmount(value) if (amount === null) { return String(value || '').trim() || '待补充' } return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY', minimumFractionDigits: 0, maximumFractionDigits: Number.isInteger(amount) ? 0 : 2 }).format(amount) } function resolveApprovalState(request) { const normalizedStatus = String(request?.status || '').trim().toLowerCase() if (normalizedStatus && BACKEND_STATUS_META[normalizedStatus]) { const meta = BACKEND_STATUS_META[normalizedStatus] return { key: meta.key, label: request.approvalStatus || request.approval || meta.label, tone: request.approvalTone || meta.tone } } if (request?.approvalKey && APPROVAL_META[request.approvalKey]) { return { key: request.approvalKey, label: request.approvalStatus || request.approval || APPROVAL_META[request.approvalKey].label, tone: request.approvalTone || APPROVAL_META[request.approvalKey].tone } } if (typeof request?.approval === 'string' && request.approval.trim()) { return { key: request.approvalKey || 'in_progress', label: request.approval, tone: request.approvalTone || 'info' } } if (typeof request?.status === 'string') { if (request.status === 'success') return { key: 'completed', label: '已完成', tone: 'success' } if (request.status === 'danger') return { key: 'supplement', label: '待补充', tone: 'warning' } } return { key: 'in_progress', label: '审批中', tone: 'info' } } function resolveTypeMeta(request) { const typeCode = String(request?.typeCode || request?.expense_type || '').trim() || 'other' const typeMeta = REQUEST_TYPE_META[typeCode] || REQUEST_TYPE_META.other return { typeCode, typeLabel: String(request?.typeLabel || request?.category || '').trim() || typeMeta.label, detailVariant: String(request?.detailVariant || '').trim() || typeMeta.detailVariant, typeTone: typeMeta.tone, secondaryStatusLabel: request?.secondaryStatusLabel || typeMeta.secondaryStatusLabel } } function normalizeRoleLabels(value) { if (Array.isArray(value)) { return value.map((item) => String(item || '').trim()).filter(Boolean) } const text = String(value || '').trim() return text ? [text] : [] } export function normalizeRequestForUi(request) { if (!request) { return null } const { typeCode, typeLabel, detailVariant, typeTone, secondaryStatusLabel } = resolveTypeMeta(request) const approvalState = resolveApprovalState(request) const amountValue = parseAmount(request.amount) const amountDisplay = formatRequestCurrency(amountValue ?? request.amount) const title = String(request.title || request.reason || '').trim() || `${typeLabel}报销` const sceneTarget = String(request.sceneTarget || request.location || request.city || request.entity || '').trim() || '待补充' const occurredDisplay = String(request.occurredDisplay || request.period || request.occurredAt || '').trim() || '待补充' const applyTime = String(request.applyTime || parseRequestDateFromId(request.id) || '').trim() || '待补充' const workflowNode = String( request.workflowNode || request.node || request.approval_stage || request.approvalStage || '' ).trim() || '待提交' const secondaryStatusValue = String(request.secondaryStatusValue || request.travel || '').trim() || (detailVariant === 'travel' ? '待安排行程' : '待补充票据') const secondaryStatusTone = String(request.secondaryStatusTone || request.travelTone || '').trim() || 'neutral' const roleLabels = normalizeRoleLabels(request.roleLabels || request.role_labels) const profileIdentity = String(request.profileIdentity || request.employeeIdentity || request.identity || '').trim() || roleLabels.join(' / ') || '员工' return { ...request, claimId: String(request.claimId || request.claim_id || '').trim(), documentNo: String(request.documentNo || request.claimNo || request.claim_no || request.id || '').trim(), typeCode, typeLabel, detailVariant, typeTone, title, reason: title, sceneLabel: String(request.sceneLabel || '').trim() || typeLabel, sceneTarget, city: sceneTarget, location: String(request.location || '').trim() || sceneTarget, relatedCustomer: String(request.relatedCustomer || '').trim() || '待补充', occurredDisplay, period: occurredDisplay, applyTime, amountValue, amountDisplay, amount: amountDisplay, workflowNode, node: workflowNode, approvalKey: approvalState.key, approvalStatus: approvalState.label, approval: approvalState.label, approvalTone: approvalState.tone, secondaryStatusLabel, secondaryStatusValue, secondaryStatusTone, travel: secondaryStatusValue, travelTone: secondaryStatusTone, riskSummary: String(request.riskSummary || request.risk || '').trim() || '暂无异常', attachmentSummary: String(request.attachmentSummary || '').trim() || '待补充', rangeBucket: String(request.rangeBucket || request.range || '').trim() || '本周', detailTitle: String(request.detailTitle || '').trim() || title, note: String(request.note || '').trim(), profileName: String(request.person || request.applicant || request.employeeName || '').trim() || '当前申请人', profileDepartment: String(request.dept || request.department || request.departmentName || '').trim() || '所属部门', profileIdentity, profilePosition: String(request.profilePosition || request.employeePosition || request.employee_position || request.position || '').trim() || '待补充', profileGrade: String(request.profileGrade || request.employeeGrade || request.employee_grade || request.grade || '').trim() || '待补充', profileManager: String(request.profileManager || request.managerName || request.manager_name || request.manager || '').trim() || '待补充', roleLabels, profileAvatar: String(request.person || request.applicant || request.employeeName || '申').trim().slice(0, 1) || '申' } }