Files
X-Financial/web/src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js

297 lines
11 KiB
JavaScript
Raw Normal View History

import {
buildApplicationTemplatePreview,
buildLocalApplicationPreview,
normalizeApplicationPreview
} from '../../utils/expenseApplicationPreview.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'
import { INLINE_APPLICATION_STATUS_LABELS } from '../../constants/documentProtocol.js'
import { resolveInlineApplicationPreviewTextAction } from './workbenchAiApplicationGateModel.js'
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 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 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, '-')} |`
].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 = '') {
return resolveInlineApplicationPreviewTextAction(text)
}
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'
}
]
}