feat: 重构报销单服务并完善前端提交与审核交互

重构 expense_claims 服务模块结构并优化差旅票据审核逻辑,
增强用户代理服务的票据类型识别,前端报销创建页面拆分为
附件模型和会话模型模块,重构提交编排器和草稿关联确认流
程,更新知识库索引,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-22 08:58:59 +08:00
parent f6f787ff38
commit 5fe3b201d9
42 changed files with 13697 additions and 9496 deletions

View File

@@ -37,6 +37,7 @@ function resolveStatusTone(status) {
export const MAX_ATTACHMENTS = 10
export const MAX_OCR_DOCUMENTS = 10
export const VISIBLE_ATTACHMENT_CHIPS = 2
export const ATTACHMENT_ASSOCIATION_CONFIRM_HREF = '#confirm-attachment-association'
export function normalizeOcrDocuments(payload) {
const documents = Array.isArray(payload?.documents) ? payload.documents : []
@@ -85,6 +86,88 @@ export function buildOcrSummaryFromDocuments(documents) {
.join('')
}
function resolveAssociationDocumentTypeLabel(document) {
const explicitLabel = String(document?.document_type_label || '').trim()
if (explicitLabel) {
return explicitLabel
}
const sceneLabel = String(document?.scene_label || '').trim()
if (sceneLabel) {
return sceneLabel
}
const typeLabel = resolveDocumentTypeLabel(document?.document_type)
return String(typeLabel || '').trim() || '其他票据'
}
function buildAssociationDocumentContentLines(document) {
const fields = Array.isArray(document?.document_fields) ? document.document_fields : []
const fieldLines = fields
.map((field) => {
const label = String(field?.label || '').trim()
const value = String(field?.value || '').trim()
return label && value ? `- ${label}${value}` : ''
})
.filter(Boolean)
if (fieldLines.length) {
return fieldLines.slice(0, 8)
}
const summary = String(document?.summary || document?.text || '').trim()
if (summary) {
return [`- 识别内容:${summary}`]
}
return ['- 识别内容:暂未提取到结构化字段,请以票据原件为准。']
}
export function buildAttachmentAssociationConfirmationMessage({
claimNo = '',
claimTitle = '',
fileNames = [],
ocrDocuments = []
} = {}) {
const documents = Array.isArray(ocrDocuments) && ocrDocuments.length
? ocrDocuments
: (Array.isArray(fileNames) ? fileNames : [])
.map((filename) => ({ filename }))
.filter((item) => String(item.filename || '').trim())
const targetLines = [
claimNo ? `- 草稿单号:${claimNo}` : '',
claimTitle ? `- 单据说明:${claimTitle}` : '',
`- 本次待归集附件:${documents.length || fileNames.length || 0}`
].filter(Boolean)
const documentBlocks = documents.map((document, index) => {
const filename = String(document?.filename || '').trim() || `附件 ${index + 1}`
const typeLabel = resolveAssociationDocumentTypeLabel(document)
const contentLines = buildAssociationDocumentContentLines(document)
return [
`附件 ${index + 1}${filename}`,
'',
`附件类型:${typeLabel}`,
'',
...contentLines
].join('\n')
})
return [
'已识别附件信息:',
'',
documentBlocks.join('\n\n'),
'',
'请问是否确定将票据信息归集到单据:',
'',
targetLines.join('\n'),
'',
`如果 [确认](${ATTACHMENT_ASSOCIATION_CONFIRM_HREF}) 该信息,我将直接将票据进行归集。`
]
.filter((part) => String(part || '').trim())
.join('\n')
}
export function normalizeReviewDocumentFieldKey(label) {
const compact = String(label || '').replace(/\s+/g, '').toLowerCase()
if (!compact) return ''