import { buildApplicationTemplatePreview, buildLocalApplicationPreview, normalizeApplicationPreview } from '../../utils/expenseApplicationPreview.js' import { AI_APPLICATION_DETAIL_HREF_PREFIX } from '../../utils/aiDocumentDetailReference.js' import { buildAiApplicationPrecheckThinkingEvents, isAiApplicationPrecheckBlocking } from '../../utils/aiApplicationPrecheckModel.js' import { extractExpenseClaimItems } from '../../services/reimbursements.js' import { AI_APPLICATION_ACTION_SAVE_DRAFT, AI_APPLICATION_ACTION_SUBMIT } from '../../services/aiApplicationPreviewActions.js' const INLINE_APPLICATION_STATUS_LABELS = { draft: '草稿', submitted: '审批中', pending: '待处理', approved: '已审批', completed: '已完成', archived: '已归档', returned: '已退回', rejected: '已驳回', pending_payment: '待付款', paid: '已付款' } function normalizeInlineApplicationResultTableCell(value, fallback = '-') { const text = String(value || '') .replace(/\s*\n+\s*/g, ' ') .replace(/\|/g, '|') .trim() return text || fallback } export function normalizeInlineApplicationStatusLabel(value, fallback = '') { const text = String(value || '').trim() if (!text) { return fallback } return INLINE_APPLICATION_STATUS_LABELS[text.toLowerCase()] || text } export function buildInlineApplicationActionDetailHref(reference = '') { const source = reference && typeof reference === 'object' ? reference : { reference } const claimId = String(source.claimId || source.claim_id || source.id || '').trim() const claimNo = String(source.claimNo || source.claim_no || source.documentNo || source.document_no || '').trim() const fallback = String(source.reference || '').trim() if (claimId || claimNo) { const params = new URLSearchParams() if (claimId) { params.set('claim_id', claimId) } if (claimNo) { params.set('claim_no', claimNo) } return `${AI_APPLICATION_DETAIL_HREF_PREFIX}${encodeURIComponent(params.toString())}` } return fallback ? `${AI_APPLICATION_DETAIL_HREF_PREFIX}${encodeURIComponent(fallback)}` : '' } export function resolveInlineApplicationActionDocumentInfo(draftPayload = {}) { const source = draftPayload && typeof draftPayload === 'object' ? draftPayload : {} const body = String(source.body || source.markdown || '').trim() const resolveBodyField = (labels = []) => { for (const label of labels) { const pattern = new RegExp(`${label}\\s*[::]\\s*([^\\n|]+)`, 'u') const match = body.match(pattern) if (match?.[1]) { return String(match[1]).replace(/\*\*/g, '').trim() } } return '' } const startDate = String(source.start_date || source.startDate || source.trip_start_date || source.tripStartDate || '').trim() const endDate = String(source.end_date || source.endDate || source.trip_end_date || source.tripEndDate || '').trim() const dateText = String( source.business_time || source.businessTime || source.time || source.occurred_at || source.occurredAt || source.apply_time || source.applyTime || '' ).trim() const rangeText = startDate && endDate && startDate !== endDate ? `${startDate} 至 ${endDate}` : startDate || endDate return { claimNo: String(source.claim_no || source.claimNo || source.document_no || source.documentNo || '').trim(), claimId: String(source.claim_id || source.claimId || source.id || '').trim(), statusLabel: normalizeInlineApplicationStatusLabel(source.status_label || source.statusLabel || source.status), approvalStage: String(source.approval_stage || source.approvalStage || '').trim(), dateLabel: rangeText || dateText || resolveBodyField(['时间', '日期', '申请时间']) || '待补充', locationLabel: String( source.location || source.application_location || source.applicationLocation || source.destination || source.destination_city || source.destinationCity || '' ).trim() || resolveBodyField(['地点', '目的地']) || '待补充', reasonLabel: String( source.reason || source.business_reason || source.businessReason || source.description || source.title || '' ).trim() || resolveBodyField(['事由', '事件', '申请事由']) || '待补充', amountLabel: String( source.amount || source.application_amount || source.applicationAmount || source.estimated_amount || source.estimatedAmount || '' ).trim() || resolveBodyField(['金额', '预计金额', '申请金额']) || '-', documentTypeLabel: String( source.document_type_label || source.documentTypeLabel || source.application_type_label || source.applicationTypeLabel || source.expense_type_label || source.expenseTypeLabel || '' ).trim() } } export function buildInlineApplicationResultTable(draftPayload = {}, options = {}) { const info = resolveInlineApplicationActionDocumentInfo(draftPayload) const reference = info.claimNo || info.claimId const href = buildInlineApplicationActionDetailHref(info) const actionText = href ? `[查看](${href})` : '-' const statusLabel = normalizeInlineApplicationStatusLabel(info.statusLabel, options.statusLabel) return [ '| 单据类型 | 单据编号 | 单据状态 | 当前节点 | 日期 | 地点 | 事由 | 金额 | 操作 |', '| --- | --- | --- | --- | --- | --- | --- | --- | --- |', `| ${normalizeInlineApplicationResultTableCell(info.documentTypeLabel || options.documentTypeLabel, '出差申请')} | ${normalizeInlineApplicationResultTableCell(reference)} | ${normalizeInlineApplicationResultTableCell(statusLabel)} | ${normalizeInlineApplicationResultTableCell(info.approvalStage || options.stageLabel)} | ${normalizeInlineApplicationResultTableCell(info.dateLabel)} | ${normalizeInlineApplicationResultTableCell(info.locationLabel)} | ${normalizeInlineApplicationResultTableCell(info.reasonLabel)} | ${normalizeInlineApplicationResultTableCell(info.amountLabel, '-')} | ${actionText} |` ].join('\n') } export function extractInlineApplicationDraftPayload(payload = {}) { const result = payload?.result && typeof payload.result === 'object' ? payload.result : {} return result.draft_payload && typeof result.draft_payload === 'object' ? result.draft_payload : payload?.draft_payload && typeof payload.draft_payload === 'object' ? payload.draft_payload : null } export function buildInlineApplicationPreviewActionResultText(actionType, payload = {}) { const draftPayload = extractInlineApplicationDraftPayload(payload) || {} const claimNo = String(draftPayload.claim_no || draftPayload.claimNo || '').trim() const approvalStage = String(draftPayload.approval_stage || draftPayload.approvalStage || '').trim() if (actionType === AI_APPLICATION_ACTION_SUBMIT) { return [ '### 申请单据已生成,并已进入审批流程', approvalStage ? `系统已推送到 **${approvalStage}**,当前节点:${approvalStage}。` : '系统已推送到审批流程,当前节点:审批中。', buildInlineApplicationResultTable(draftPayload, { statusLabel: '审批中', stageLabel: approvalStage || '直属领导审批', documentTypeLabel: '出差申请' }), '需要查看完整详情时,请点击卡片“操作”行的“查看”进入单据详情。' ].filter(Boolean).join('\n\n') } return [ '### 申请草稿已保存', claimNo ? `系统已保存当前申请草稿,草稿单号:**${claimNo}**。` : '系统已保存当前申请草稿。', buildInlineApplicationResultTable(draftPayload, { statusLabel: '草稿', stageLabel: '待提交', documentTypeLabel: '出差申请' }), '后续请点击卡片“操作”行的“查看”进入详情页继续核对。' ].filter(Boolean).join('\n\n') } export function buildInlineApplicationDetailAction(draftPayload = {}) { const claimNo = String(draftPayload?.claim_no || draftPayload?.claimNo || '').trim() if (!claimNo) { return [] } return [{ label: '查看单据详情', description: '打开刚生成的申请单详情。', icon: 'mdi mdi-open-in-new', action_type: 'open_application_detail', payload: { claim_no: claimNo, claim_id: String(draftPayload.claim_id || draftPayload.claimId || '').trim(), document_type: 'application' } }] } export function resolveInlineApplicationPreviewActionFromText(text = '') { const normalized = String(text || '').replace(/\s+/g, '').trim() if (!normalized) { return '' } if (/^(保存草稿|保存|存草稿|先保存)$/.test(normalized)) { return AI_APPLICATION_ACTION_SAVE_DRAFT } if (/^(提交|提交申请|确认提交|提交审批|直接提交)$/.test(normalized)) { return AI_APPLICATION_ACTION_SUBMIT } return '' } export function normalizeInlineApplicationTypeLabel(expenseTypeLabel, fallback = '费用申请') { const label = String(expenseTypeLabel || '').trim() if (!label) { return fallback } if (label.endsWith('费用申请') || label.endsWith('申请')) { return label } if (label.endsWith('费用')) { return `${label}申请` } if (label.endsWith('费')) { return `${label.slice(0, -1)}费用申请` } return `${label}申请` } export function buildInlineApplicationPreview(expenseTypeLabel, sourceText = '', currentUser = {}) { const rawText = String(sourceText || '').trim() const preview = rawText ? buildLocalApplicationPreview(rawText, currentUser) : buildApplicationTemplatePreview(currentUser) const normalized = normalizeApplicationPreview(preview) return normalizeApplicationPreview({ ...normalized, fields: { ...(normalized.fields || {}), applicationType: normalizeInlineApplicationTypeLabel( expenseTypeLabel, normalized.fields?.applicationType || '费用申请' ) } }) } export function resolveInlineApplicationDraftIdentity(payload = {}) { const source = payload && typeof payload === 'object' ? payload : {} return { claimId: String(source.claim_id || source.claimId || source.id || '').trim(), claimNo: String(source.claim_no || source.claimNo || source.document_no || source.documentNo || '').trim() } } export function isSameInlineApplicationDraftClaim(claim = {}, draftPayload = {}) { const draftIdentity = resolveInlineApplicationDraftIdentity(draftPayload) if (!draftIdentity.claimId && !draftIdentity.claimNo) { return false } const claimIdentity = resolveInlineApplicationDraftIdentity(claim) return Boolean( (draftIdentity.claimId && claimIdentity.claimId && draftIdentity.claimId === claimIdentity.claimId) || (draftIdentity.claimNo && claimIdentity.claimNo && draftIdentity.claimNo === claimIdentity.claimNo) ) } export function buildInlineApplicationSubmitPrecheckPayload(claimsPayload, draftPayload = {}) { const items = extractExpenseClaimItems(claimsPayload) .filter((claim) => !isSameInlineApplicationDraftClaim(claim, draftPayload)) return { items } } export function completeInlineThinkingEvents(events = []) { return events.map((event) => ({ ...event, status: event.status === 'failed' ? 'failed' : 'completed' })) } export function buildInitialInlineApplicationSubmitThinkingEvents() { return [ { eventId: 'application-precheck-overlap', title: '核查同时间段申请单', content: '正在查询你名下可见申请单,检查是否存在相同或重叠日期。', status: 'running' }, { eventId: 'application-precheck-budget', title: '评估预算与审批影响', content: '等待单据重叠核查完成后,继续评估预算占用和审批影响。', status: 'pending' }, { eventId: 'application-submit', title: '提交申请单据', content: '等待提交前核查完成。', status: 'pending' } ] } export function buildInlineApplicationSubmitThinkingEvents(precheck = {}) { const blocked = isAiApplicationPrecheckBlocking(precheck) return buildAiApplicationPrecheckThinkingEvents(precheck).map((event) => { if (event.eventId !== 'application-precheck-form') { return event } return { eventId: 'application-submit', title: blocked ? '暂停提交申请' : '提交申请单据', content: blocked ? '发现相同或重叠日期已有申请单,已暂停本次提交。' : '提交前核查通过,正在生成申请单据并推送审批流程。', status: blocked ? 'completed' : 'running' } }) } export function buildFailedInlineApplicationSubmitThinkingEvents(error) { return [ { eventId: 'application-precheck-overlap', title: '核查同时间段申请单', content: `查询已有申请单失败:${String(error?.message || error || '未知错误')}`, status: 'failed' }, { eventId: 'application-submit', title: '暂停提交申请', content: '因为未能完成提交前重复日期核查,系统没有提交本次申请。', status: 'failed' } ] }