187 lines
7.0 KiB
JavaScript
187 lines
7.0 KiB
JavaScript
|
|
import { fetchExpenseClaims } from '../../services/reimbursements.js'
|
||
|
|
import {
|
||
|
|
applyAiExpenseAnswer,
|
||
|
|
buildAiExpenseStepPrompt,
|
||
|
|
buildAiExpenseSummary,
|
||
|
|
createAiExpenseDraft,
|
||
|
|
isAiExpenseDraftComplete
|
||
|
|
} from '../../utils/aiExpenseDraftModel.js'
|
||
|
|
import {
|
||
|
|
buildExpenseSceneSelectionMessage,
|
||
|
|
SESSION_TYPE_EXPENSE
|
||
|
|
} from '../../views/scripts/travelReimbursementConversationModel.js'
|
||
|
|
import { buildExpenseSceneSelectionActions } from '../../utils/expenseAssistantActions.js'
|
||
|
|
import {
|
||
|
|
buildRequiredApplicationActions,
|
||
|
|
buildRequiredApplicationMissingText,
|
||
|
|
buildRequiredApplicationSelectionText,
|
||
|
|
filterRequiredApplicationCandidates
|
||
|
|
} from '../../views/scripts/travelReimbursementApplicationLinkModel.js'
|
||
|
|
|
||
|
|
export { SESSION_TYPE_EXPENSE }
|
||
|
|
|
||
|
|
export function useWorkbenchAiExpenseFlow({
|
||
|
|
activateInlineConversation,
|
||
|
|
aiExpenseDraft,
|
||
|
|
assistantDraft,
|
||
|
|
clearAiModeFiles,
|
||
|
|
closeWorkbenchDatePicker,
|
||
|
|
conversationMessages,
|
||
|
|
conversationStarted,
|
||
|
|
createInlineMessage,
|
||
|
|
currentUser,
|
||
|
|
persistCurrentConversation,
|
||
|
|
pushInlineUserMessage,
|
||
|
|
removeWorkbenchDateTag,
|
||
|
|
resolveLatestInlineUserPrompt,
|
||
|
|
scrollInlineConversationToBottom,
|
||
|
|
startAiApplicationPreview
|
||
|
|
}) {
|
||
|
|
function pushInlineExpenseSceneSelectionPrompt(originalMessage, selectedLabel = '') {
|
||
|
|
const sourceText = String(originalMessage || '我要报销').trim()
|
||
|
|
if (!conversationStarted.value) {
|
||
|
|
activateInlineConversation({
|
||
|
|
title: String(selectedLabel || sourceText || '报销').trim().slice(0, 18) || '报销'
|
||
|
|
})
|
||
|
|
}
|
||
|
|
assistantDraft.value = ''
|
||
|
|
removeWorkbenchDateTag()
|
||
|
|
closeWorkbenchDatePicker()
|
||
|
|
conversationMessages.value.push(createInlineMessage('user', String(selectedLabel || sourceText).trim()))
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', buildExpenseSceneSelectionMessage(sourceText), {
|
||
|
|
suggestedActions: buildExpenseSceneSelectionActions(sourceText)
|
||
|
|
}))
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
}
|
||
|
|
|
||
|
|
function startAiApplicationPreviewFromAction(payload = {}, fallbackLabel = '') {
|
||
|
|
const expenseType = String(payload.expense_type || '').trim()
|
||
|
|
const expenseTypeLabel = String(payload.expense_type_label || fallbackLabel || '').trim()
|
||
|
|
return startAiApplicationPreview(
|
||
|
|
expenseType,
|
||
|
|
expenseTypeLabel,
|
||
|
|
payload.carry_text || resolveLatestInlineUserPrompt()
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function startAiExpenseDraft(expenseType, expenseTypeLabel, requiresApplicationBeforeReimbursement) {
|
||
|
|
if (!conversationStarted.value) {
|
||
|
|
activateInlineConversation({ title: String(expenseTypeLabel || '报销').trim().slice(0, 18) || '报销' })
|
||
|
|
}
|
||
|
|
assistantDraft.value = ''
|
||
|
|
removeWorkbenchDateTag()
|
||
|
|
closeWorkbenchDatePicker()
|
||
|
|
clearAiModeFiles()
|
||
|
|
pushInlineUserMessage(`选择${expenseTypeLabel || expenseType || '报销'}`)
|
||
|
|
|
||
|
|
if (requiresApplicationBeforeReimbursement) {
|
||
|
|
void resolveAiExpenseApplicationLink(expenseType, expenseTypeLabel)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const draft = createAiExpenseDraft(expenseType, expenseTypeLabel)
|
||
|
|
aiExpenseDraft.value = draft
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', buildAiExpenseStepPrompt(draft)))
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
}
|
||
|
|
|
||
|
|
function advanceAiExpenseDraft(answer, files = []) {
|
||
|
|
const fileNames = Array.from(files || [])
|
||
|
|
pushInlineUserMessage(answer || (fileNames.length ? `上传 ${fileNames.length} 份附件` : ''))
|
||
|
|
assistantDraft.value = ''
|
||
|
|
clearAiModeFiles()
|
||
|
|
|
||
|
|
const next = applyAiExpenseAnswer(aiExpenseDraft.value, answer, fileNames)
|
||
|
|
aiExpenseDraft.value = next
|
||
|
|
|
||
|
|
if (isAiExpenseDraftComplete(next)) {
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', `${buildAiExpenseSummary(next)}\n\n如果哪一项需要修改,直接告诉我;确认无误后我再帮你生成报销草稿。`))
|
||
|
|
aiExpenseDraft.value = null
|
||
|
|
} else {
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', buildAiExpenseStepPrompt(next)))
|
||
|
|
}
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
}
|
||
|
|
|
||
|
|
async function resolveAiExpenseApplicationLink(expenseType, expenseTypeLabel) {
|
||
|
|
let claims = null
|
||
|
|
try {
|
||
|
|
claims = await fetchExpenseClaims()
|
||
|
|
} catch {
|
||
|
|
aiExpenseDraft.value = null
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', '查询可关联申请单时出现异常,请稍后再试,我先暂停这次报销流程。'))
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const candidates = filterRequiredApplicationCandidates(claims, expenseType, currentUser.value || {})
|
||
|
|
aiExpenseDraft.value = createAiExpenseDraft(expenseType, expenseTypeLabel)
|
||
|
|
|
||
|
|
if (!candidates.length) {
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', buildRequiredApplicationMissingText(expenseType), {
|
||
|
|
suggestedActions: [{
|
||
|
|
label: '确认发起出差申请',
|
||
|
|
description: '生成完整申请表,并预填已识别的时间、地点和事由',
|
||
|
|
icon: 'mdi mdi-file-plus-outline',
|
||
|
|
action_type: 'ai_application_start_inline',
|
||
|
|
payload: {
|
||
|
|
expense_type: expenseType,
|
||
|
|
expense_type_label: expenseTypeLabel
|
||
|
|
}
|
||
|
|
}]
|
||
|
|
}))
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', buildRequiredApplicationSelectionText(expenseType, candidates), {
|
||
|
|
suggestedActions: buildRequiredApplicationActions(candidates, 'select_required_application')
|
||
|
|
}))
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
}
|
||
|
|
|
||
|
|
function linkAiExpenseApplication(application = {}) {
|
||
|
|
const draft = aiExpenseDraft.value
|
||
|
|
if (!draft) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
const claimNo = String(application.application_claim_no || '').trim()
|
||
|
|
pushInlineUserMessage(`关联申请单 ${claimNo}`.trim())
|
||
|
|
|
||
|
|
const linked = {
|
||
|
|
...draft,
|
||
|
|
applicationClaim: application,
|
||
|
|
values: {
|
||
|
|
...draft.values,
|
||
|
|
reason: String(application.application_reason || '').trim(),
|
||
|
|
location: String(application.application_location || '').trim(),
|
||
|
|
time_range: String(application.application_business_time || '').trim(),
|
||
|
|
amount: String(application.application_amount_label || application.application_amount || '').trim()
|
||
|
|
},
|
||
|
|
stepKey: 'attachments'
|
||
|
|
}
|
||
|
|
aiExpenseDraft.value = linked
|
||
|
|
conversationMessages.value.push(createInlineMessage('assistant', [
|
||
|
|
`已关联申请单${claimNo ? ` ${claimNo}` : ''},事由、时间、地点、金额我先用申请单的内容预填了。`,
|
||
|
|
'',
|
||
|
|
'再确认一下票据:可以现在上传,或回复“稍后上传”。'
|
||
|
|
].join('\n')))
|
||
|
|
persistCurrentConversation()
|
||
|
|
scrollInlineConversationToBottom()
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
advanceAiExpenseDraft,
|
||
|
|
linkAiExpenseApplication,
|
||
|
|
pushInlineExpenseSceneSelectionPrompt,
|
||
|
|
startAiApplicationPreviewFromAction,
|
||
|
|
startAiExpenseDraft
|
||
|
|
}
|
||
|
|
}
|