const EXPENSE_TYPE_LABELS = { travel: '差旅费', hotel: '住宿费', transport: '交通费', meal: '业务招待费', entertainment: '业务招待费', meeting: '会务费', office: '办公用品费', training: '培训费', communication: '通讯费', welfare: '福利费', other: '其他费用' } const SLOT_LABELS = { expense_type: '费用场景', amount: '申请金额', time_range: '业务时间', reason: '申请事由', attachments: '附件说明', customer_name: '客户名称', participants: '参与人员' } const PRE_APPROVAL_TYPES = new Set(['travel', 'meeting', 'office', 'training']) const ATTACHMENT_REQUIRED_TYPES = new Set(['meeting', 'training']) export const APPLICATION_EXAMPLES = [ '申请下周去北京做客户现场验收,差旅预算18000元', '申请上海产品发布会会务费32000元,需要场地和物料', '申请部门集中采购办公用品4800元,用于新员工入职' ] export function buildExpenseApplicationOntologyContext(currentUser = {}) { return { document_type: 'expense_application', application_stage: 'pre_approval', conversation_scenario: 'expense', entry_source: 'documents_application', role_codes: Array.isArray(currentUser.roleCodes) ? currentUser.roleCodes : [], is_admin: Boolean(currentUser.isAdmin), name: currentUser.name || '', role: currentUser.role || '', department: currentUser.department || currentUser.departmentName || '', department_name: currentUser.department || currentUser.departmentName || '', position: currentUser.position || '', grade: currentUser.grade || '', employee_no: currentUser.employeeNo || currentUser.employee_no || '' } } export function resolveEntity(ontology, type) { const entities = Array.isArray(ontology?.entities) ? ontology.entities : [] return entities.find((item) => item?.type === type) || null } export function resolveConstraint(ontology, field) { const constraints = Array.isArray(ontology?.constraints) ? ontology.constraints : [] return constraints.find((item) => item?.field === field) || null } export function resolveExpenseTypeCode(ontology) { const entity = resolveEntity(ontology, 'expense_type') return String(entity?.normalized_value || entity?.value || 'other').trim() || 'other' } export function resolveExpenseTypeLabel(code) { return EXPENSE_TYPE_LABELS[String(code || '').trim()] || EXPENSE_TYPE_LABELS.other } export function resolveApplicationAmount(ontology) { const amountEntity = resolveEntity(ontology, 'amount') const amountConstraint = resolveConstraint(ontology, 'amount') const rawValue = amountEntity?.normalized_value || amountEntity?.value || amountConstraint?.value || '' const numericValue = Number(String(rawValue).replace(/[^\d.]/g, '')) return { raw: String(rawValue || '').trim(), value: Number.isFinite(numericValue) ? numericValue : 0 } } export function resolveTimeRangeText(ontology) { const range = ontology?.time_range || {} if (range.start_date && range.end_date) { return range.start_date === range.end_date ? range.start_date : `${range.start_date} 至 ${range.end_date}` } return String(range.raw || '').trim() } export function resolveAttachmentPolicy(expenseTypeCode, amount = 0) { const code = String(expenseTypeCode || '').trim() if (ATTACHMENT_REQUIRED_TYPES.has(code)) { return { level: 'required', label: '必须提交', description: code === 'meeting' ? '需补充会议通知、议程、参会范围或预算说明。' : '需补充培训通知、课程说明、报价或审批依据。' } } if (code === 'office' && amount >= 5000) { return { level: 'required', label: '必须提交', description: '办公采购金额较高,需补充采购清单、报价或预算说明。' } } if (code === 'travel') { return { level: 'optional', label: '说明可选', description: '可先提交出差目的、时间和预算;行程或邀请材料可作为补充说明。' } } return { level: 'none', label: '无需附件', description: '当前申请事项可先不提交附件,后续报销阶段再按票据要求补充。' } } export function buildApplicationFieldsFromOntology(ontology, prompt, currentUser = {}) { const expenseTypeCode = resolveExpenseTypeCode(ontology) const amount = resolveApplicationAmount(ontology) const locationEntity = resolveEntity(ontology, 'location') const documentTypeEntity = resolveEntity(ontology, 'document_type') const workflowStageEntity = resolveEntity(ontology, 'workflow_stage') const attachmentPolicy = resolveAttachmentPolicy(expenseTypeCode, amount.value) return { documentType: documentTypeEntity?.normalized_value || 'expense_application', documentTypeLabel: documentTypeEntity?.value || '费用申请', workflowStage: workflowStageEntity?.normalized_value || 'pre_approval', workflowStageLabel: workflowStageEntity?.value || '前置申请', expenseTypeCode, expenseTypeLabel: resolveExpenseTypeLabel(expenseTypeCode), amount: amount.value, amountDisplay: amount.value ? `¥${amount.value.toLocaleString('zh-CN')}` : '待补充', timeRange: resolveTimeRangeText(ontology) || '待补充', location: locationEntity?.normalized_value || locationEntity?.value || '待补充', reason: String(prompt || '').trim() || '待补充', applicant: currentUser.name || currentUser.username || '当前用户', department: currentUser.department || currentUser.departmentName || '待补充', preApprovalRequired: PRE_APPROVAL_TYPES.has(expenseTypeCode), attachmentPolicy, missingSlots: normalizeMissingSlots(ontology?.missing_slots || []) } } export function normalizeMissingSlots(slots = []) { const normalized = Array.isArray(slots) ? slots : [] return normalized.map((item) => ({ key: String(item || '').trim(), label: SLOT_LABELS[String(item || '').trim()] || String(item || '').trim() })).filter((item) => item.key) }