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

@@ -224,14 +224,22 @@ export function resolveReviewMissingSlotCards(reviewPayload) {
export function resolveReviewExtraMissingLabels(reviewPayload) {
const labels = Array.isArray(reviewPayload?.missing_slots)
? reviewPayload.missing_slots.map((item) => String(item || '').trim()).filter(Boolean)
? reviewPayload.missing_slots
.map((item) => {
if (item && typeof item === 'object') {
return String(item.label || item.title || item.key || '').trim()
}
return String(item || '').trim()
})
.filter(Boolean)
: []
if (!labels.length) return []
const slotLabels = new Set(
(Array.isArray(reviewPayload?.slot_cards) ? reviewPayload.slot_cards : [])
.map((item) => String(item?.label || item?.key || '').trim())
.filter(Boolean)
(Array.isArray(reviewPayload?.slot_cards) ? reviewPayload.slot_cards : []).flatMap((item) => [
String(item?.label || '').trim(),
String(item?.key || '').trim()
]).filter(Boolean)
)
return labels.filter((label) => !slotLabels.has(label))
}
@@ -1239,23 +1247,66 @@ function buildReviewPlainFollowupItem(item, pendingMode) {
}
}
const REVIEW_PENDING_SUMMARY_TEMPLATES = [
({ issueSummary }) => `当前还有 ${issueSummary}。请核查对话中的文字说明;如果想先暂存,也可以点击对话文字中的“草稿”。`,
({ issueSummary }) => `我这边看到还有 ${issueSummary},建议先把下方内容核对一下;暂时不处理也没关系,可以点击“草稿”先保存。`,
({ issueSummary }) => `下方还有 ${issueSummary},需要你确认。信息没补齐前可以先核查说明,后续需要暂存时点“草稿”。`,
({ issueSummary }) => `这笔报销还有 ${issueSummary},尚未完全确认。请先看一下下面的补充项;需要中途保存时,可以点“草稿”。`,
({ issueSummary }) => `目前还有 ${issueSummary}。你可以先按下面的提示补充,也可以稍后再处理,点击“草稿”即可暂存当前信息。`,
({ issueSummary }) => `还有 ${issueSummary},建议先核对下面说明;如果票据或金额暂时不全,可以通过“草稿”保留当前进度。`,
({ issueSummary }) => `这次识别结果里还有 ${issueSummary}。请重点看下面几项,暂不提交时可以点“草稿”保存。`,
({ issueSummary }) => `我还需要你确认 ${issueSummary}。下面列出了具体内容;如果现在不方便补齐,可以先点“草稿”。`,
({ issueSummary }) => `当前还有 ${issueSummary},需要进一步处理。请根据下面提示核查,待补充完再继续;临时保存可点击“草稿”。`,
({ issueSummary }) => `本次报销还有 ${issueSummary},请先检查下面的补充项;想先留存当前识别结果时可以点“草稿”。`
]
function buildStableTemplateIndex(signature, total) {
const source = String(signature || '')
let hash = 0
for (let index = 0; index < source.length; index += 1) {
hash = ((hash << 5) - hash + source.charCodeAt(index)) >>> 0
}
return total ? hash % total : 0
}
function buildReviewPendingSummary(pendingCount, riskCount, signature = '') {
const issueParts = []
if (pendingCount) {
issueParts.push(`${pendingCount} 项信息待补充`)
}
if (riskCount) {
issueParts.push(`${riskCount} 条风险提醒`)
}
const issueSummary = issueParts.length ? issueParts.join('、') : '一些细节还需要进一步确认'
const templateIndex = buildStableTemplateIndex(signature || issueSummary, REVIEW_PENDING_SUMMARY_TEMPLATES.length)
return REVIEW_PENDING_SUMMARY_TEMPLATES[templateIndex]({ issueSummary })
}
export function buildReviewPlainFollowupCopy(reviewPayload) {
const todoItems = buildReviewTodoItems(reviewPayload)
const pendingCount = countReviewPendingItems(reviewPayload)
const riskBriefs = resolvePresentationRiskBriefs(reviewPayload)
const extraMissingCount = resolveReviewExtraMissingLabels(reviewPayload).length
if (pendingCount || resolveReviewExtraMissingLabels(reviewPayload).length) {
if (pendingCount || extraMissingCount) {
const summarySignature = [
pendingCount || extraMissingCount,
riskBriefs.length,
...todoItems.map((item) => `${item.key}:${item.title}:${item.status}`)
].join('|')
return {
lead: '我还需要你核查或补充下面这些信息:',
lead: '补充信息:',
tone: 'danger',
summary: buildReviewPendingSummary(pendingCount || extraMissingCount, riskBriefs.length, summarySignature),
items: todoItems.map((item) => buildReviewPlainFollowupItem(item, true)),
notes: riskBriefs.length
? [`另外还有 ${riskBriefs.length} 条风险提醒,提交前建议一起确认。`]
: []
notes: []
}
}
return {
lead: todoItems.length ? '我已整理出当前识别到的关键信息:' : '当前关键信息已基本整理完成。',
tone: 'neutral',
summary: '',
items: todoItems.map((item) => buildReviewPlainFollowupItem(item, false)),
notes: [
reviewPayload?.can_proceed ? '确认无误后,可以继续下一步。' : '',