refactor: enforce 800 line source limits
This commit is contained in:
271
web/src/views/scripts/travelReimbursementSubmitAttachmentFlow.js
Normal file
271
web/src/views/scripts/travelReimbursementSubmitAttachmentFlow.js
Normal file
@@ -0,0 +1,271 @@
|
||||
import { ATTACHMENT_ASSOCIATION_CONFIRM_HREF } from './travelReimbursementAttachmentModel.js'
|
||||
|
||||
export function createSubmitAttachmentAssociationFlow({
|
||||
activeReviewPayload,
|
||||
buildReviewFormContextFromPayload,
|
||||
createMessage,
|
||||
draftClaimId,
|
||||
emitDraftSaved,
|
||||
fetchReceiptFolderItems,
|
||||
isKnowledgeSession,
|
||||
messages,
|
||||
nextTick,
|
||||
persistSessionState,
|
||||
resetFlowRun,
|
||||
reviewInlineForm,
|
||||
scrollToBottom,
|
||||
sessionSwitchBusy,
|
||||
submitComposer,
|
||||
submitting,
|
||||
toast
|
||||
}) {
|
||||
const pendingAttachmentAssociations = new Map()
|
||||
|
||||
function createPendingAttachmentAssociationId() {
|
||||
return `attachment-association-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
}
|
||||
|
||||
function emitSavedDraftRefresh(draftPayload) {
|
||||
if (!emitDraftSaved || isKnowledgeSession.value || !draftPayload?.claim_no) {
|
||||
return
|
||||
}
|
||||
const draftType = String(draftPayload.draft_type || '').trim()
|
||||
emitDraftSaved({
|
||||
claimId: String(draftPayload.claim_id || draftPayload.claimId || '').trim(),
|
||||
claimNo: String(draftPayload.claim_no || draftPayload.claimNo || '').trim(),
|
||||
status: String(draftPayload.status || '').trim(),
|
||||
approvalStage: String(draftPayload.approval_stage || draftPayload.approvalStage || '').trim(),
|
||||
documentType: draftType === 'expense_application' ? 'application' : 'reimbursement'
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeRecognizedAttachmentData(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return null
|
||||
}
|
||||
const documents = Array.isArray(data.ocrDocuments) ? data.ocrDocuments : []
|
||||
if (!documents.length) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
ocrPayload: data.ocrPayload || null,
|
||||
ocrSummary: String(data.ocrSummary || '').trim(),
|
||||
ocrDocuments: documents,
|
||||
ocrFilePreviews: Array.isArray(data.ocrFilePreviews) ? data.ocrFilePreviews : []
|
||||
}
|
||||
}
|
||||
|
||||
function hasReceiptFolderSourceFile(files) {
|
||||
return files.some((file) => String(file?.receiptId || '').trim())
|
||||
}
|
||||
|
||||
async function promptUnlinkedReceiptFolderIfNeeded({
|
||||
detailScopedClaimId,
|
||||
files,
|
||||
fileNames,
|
||||
options,
|
||||
rawText,
|
||||
resolvedUploadDisposition,
|
||||
reviewAction,
|
||||
systemGenerated,
|
||||
userText
|
||||
}) {
|
||||
if (
|
||||
isKnowledgeSession.value ||
|
||||
systemGenerated ||
|
||||
!files.length ||
|
||||
detailScopedClaimId ||
|
||||
resolvedUploadDisposition ||
|
||||
options.skipReceiptFolderUnlinkedPrompt ||
|
||||
options.skipDraftAssociationPrompt ||
|
||||
reviewAction ||
|
||||
hasReceiptFolderSourceFile(files)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
let unlinkedReceipts = []
|
||||
try {
|
||||
unlinkedReceipts = await fetchReceiptFolderItems('unlinked')
|
||||
} catch (error) {
|
||||
console.warn('Failed to load unlinked receipt folder items before attachment upload:', error)
|
||||
return false
|
||||
}
|
||||
const count = Array.isArray(unlinkedReceipts) ? unlinkedReceipts.length : 0
|
||||
if (!count) {
|
||||
return false
|
||||
}
|
||||
|
||||
resetFlowRun()
|
||||
if (!options.skipUserMessage) {
|
||||
messages.value.push(createMessage('user', userText, fileNames))
|
||||
}
|
||||
messages.value.push(createMessage(
|
||||
'assistant',
|
||||
`票据夹中还有 ${count} 份未关联票据。建议先处理这些票据再上传新附件,避免重复保存或遗漏关联。`,
|
||||
[],
|
||||
{
|
||||
meta: ['票据夹待关联'],
|
||||
suggestedActions: [
|
||||
{
|
||||
action_type: 'open_receipt_folder',
|
||||
label: '去票据夹关联',
|
||||
icon: 'mdi mdi-folder-open-outline',
|
||||
payload: { target_view: 'receiptFolder' }
|
||||
},
|
||||
{
|
||||
action_type: 'continue_upload_with_unlinked_receipts',
|
||||
label: '继续上传新附件',
|
||||
icon: 'mdi mdi-upload-outline',
|
||||
payload: { raw_text: rawText }
|
||||
}
|
||||
]
|
||||
}
|
||||
))
|
||||
nextTick(scrollToBottom)
|
||||
persistSessionState()
|
||||
return true
|
||||
}
|
||||
|
||||
function buildConfirmedAssociationText(message) {
|
||||
return String(message?.text || '')
|
||||
.replace(`[确认](${ATTACHMENT_ASSOCIATION_CONFIRM_HREF})`, '已确认')
|
||||
.replace(`[确定](${ATTACHMENT_ASSOCIATION_CONFIRM_HREF})`, '已确定')
|
||||
}
|
||||
|
||||
function resolveReviewPanelScope({
|
||||
reviewPayload = null,
|
||||
reviewAction = '',
|
||||
fileCount = 0,
|
||||
rawText = ''
|
||||
} = {}) {
|
||||
if (!reviewPayload || typeof reviewPayload !== 'object') {
|
||||
return ''
|
||||
}
|
||||
|
||||
const normalizedAction = String(reviewAction || '').trim()
|
||||
const documentCount = Array.isArray(reviewPayload.document_cards) ? reviewPayload.document_cards.length : 0
|
||||
const riskCount = Array.isArray(reviewPayload.risk_briefs) ? reviewPayload.risk_briefs.length : 0
|
||||
const asksRisk = /风险|隐患|超标|异常|重复|待整改|风险项|高风险|中风险|低风险/.test(String(rawText || ''))
|
||||
|
||||
if (fileCount > 0 && documentCount > 0) {
|
||||
return 'documents'
|
||||
}
|
||||
if (riskCount > 0 && (asksRisk || ['next_step', 'submit', 'submit_claim'].includes(normalizedAction))) {
|
||||
return 'risk'
|
||||
}
|
||||
if (!normalizedAction && fileCount === 0) {
|
||||
return 'overview'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
async function confirmPendingAttachmentAssociation(message) {
|
||||
if (submitting.value || sessionSwitchBusy.value) return null
|
||||
|
||||
const pending = message?.pendingAttachmentAssociation && typeof message.pendingAttachmentAssociation === 'object'
|
||||
? message.pendingAttachmentAssociation
|
||||
: null
|
||||
const associationId = String(pending?.id || '').trim()
|
||||
if (!associationId || pending?.status === 'confirmed') {
|
||||
return null
|
||||
}
|
||||
|
||||
const runtime = pendingAttachmentAssociations.get(associationId)
|
||||
if (!runtime || !Array.isArray(runtime.files) || !runtime.files.length) {
|
||||
toast('当前会话里没有可归集的附件原件,请重新上传票据后再确认。')
|
||||
return null
|
||||
}
|
||||
|
||||
pending.status = 'confirmed'
|
||||
message.pendingAttachmentAssociation = pending
|
||||
message.text = buildConfirmedAssociationText(message)
|
||||
message.meta = ['已确认归集']
|
||||
persistSessionState()
|
||||
|
||||
if (pending.mode === 'save_then_associate') {
|
||||
const inheritedReviewContext = buildReviewFormContextFromPayload(
|
||||
activeReviewPayload.value,
|
||||
reviewInlineForm.value
|
||||
)
|
||||
const savePayload = await submitComposer({
|
||||
rawText: '请先把当前已识别的报销信息保存为草稿,随后继续归集本次上传的附件。',
|
||||
userText: '',
|
||||
files: [],
|
||||
skipUserMessage: true,
|
||||
pendingText: '正在先保存未保存单据...',
|
||||
systemGenerated: true,
|
||||
extraContext: {
|
||||
...runtime.extraContext,
|
||||
...inheritedReviewContext,
|
||||
review_action: 'save_draft'
|
||||
}
|
||||
})
|
||||
const savedClaimId = String(savePayload?.result?.draft_payload?.claim_id || draftClaimId.value || '').trim()
|
||||
const savedClaimNo = String(savePayload?.result?.draft_payload?.claim_no || '').trim()
|
||||
if (!savedClaimId) {
|
||||
toast('当前单据还没有保存成功,请稍后重试。')
|
||||
return savePayload
|
||||
}
|
||||
|
||||
return submitComposer({
|
||||
rawText: `确认将本次上传的 ${runtime.fileNames.length} 份票据归集到草稿 ${savedClaimNo || '当前草稿'}`,
|
||||
userText: `保存草稿并归集 ${runtime.fileNames.length} 份票据`,
|
||||
files: runtime.files,
|
||||
uploadDisposition: 'continue_existing',
|
||||
skipDraftAssociationPrompt: true,
|
||||
skipUserMessage: true,
|
||||
appendToCurrentFlow: true,
|
||||
systemGenerated: true,
|
||||
pendingText: savedClaimNo
|
||||
? `草稿 ${savedClaimNo} 已保存,正在识别并归集附件...`
|
||||
: '草稿已保存,正在识别并归集附件...',
|
||||
associationConfirmed: true,
|
||||
extraContext: {
|
||||
...runtime.extraContext,
|
||||
review_action: 'link_to_existing_draft',
|
||||
draft_claim_id: savedClaimId,
|
||||
selected_claim_id: savedClaimId,
|
||||
selected_claim_no: savedClaimNo,
|
||||
attachment_association_confirmed: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return submitComposer({
|
||||
rawText: `确认将本次上传的 ${runtime.fileNames.length} 份票据归集到草稿 ${runtime.claimNo || '当前草稿'}`,
|
||||
userText: `确认归集到草稿 ${runtime.claimNo || '当前草稿'}`,
|
||||
files: runtime.files,
|
||||
uploadDisposition: 'continue_existing',
|
||||
skipDraftAssociationPrompt: true,
|
||||
pendingText: runtime.claimNo
|
||||
? `正在将票据归集到草稿 ${runtime.claimNo}...`
|
||||
: '正在将票据归集到当前草稿...',
|
||||
associationConfirmed: true,
|
||||
recognizedAttachmentData: {
|
||||
ocrPayload: runtime.ocrPayload,
|
||||
ocrSummary: runtime.ocrSummary,
|
||||
ocrDocuments: runtime.ocrDocuments,
|
||||
ocrFilePreviews: runtime.ocrFilePreviews
|
||||
},
|
||||
extraContext: {
|
||||
...runtime.extraContext,
|
||||
review_action: 'link_to_existing_draft',
|
||||
draft_claim_id: runtime.claimId,
|
||||
selected_claim_id: runtime.claimId,
|
||||
selected_claim_no: runtime.claimNo,
|
||||
attachment_association_confirmed: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
confirmPendingAttachmentAssociation,
|
||||
createPendingAttachmentAssociationId,
|
||||
emitSavedDraftRefresh,
|
||||
normalizeRecognizedAttachmentData,
|
||||
pendingAttachmentAssociations,
|
||||
promptUnlinkedReceiptFolderIfNeeded,
|
||||
resolveReviewPanelScope
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user