feat: 完善差旅票据行程提取与费用明细回填逻辑
增强文档智能识别的票据场景关键词和字段提取能力,优化 会话关联草稿报销单的解析路径,修复费用明细合并和票据 去重边界问题,前端改进报销创建和审批详情交互,补充单 元测试覆盖。
This commit is contained in:
@@ -101,6 +101,29 @@ test('AI advice card splits every attachment risk point with basis and suggestio
|
||||
assert.ok(advice.riskCards.every((card) => card.suggestion.includes('费用项目调整为交通费')))
|
||||
})
|
||||
|
||||
test('AI advice splits claim attachment risk flags into specific points', () => {
|
||||
const riskCards = buildAttachmentRiskCards({
|
||||
claimRiskFlags: [
|
||||
{
|
||||
source: 'attachment_analysis',
|
||||
severity: 'medium',
|
||||
label: '中风险',
|
||||
message: '费用明细第 2 条:日期字段:未识别到开票日期。',
|
||||
summary: '当前附件可见部分内容,但金额、用途、日期或附件类型仍有缺失或不一致。',
|
||||
points: [
|
||||
'日期字段:未识别到开票日期或业务发生日期。',
|
||||
'金额字段:附件识别金额 300.00 元与报销金额 88.00 元不一致。'
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert.equal(riskCards.length, 2)
|
||||
assert.equal(riskCards[0].risk, '日期字段:未识别到开票日期或业务发生日期。')
|
||||
assert.equal(riskCards[1].risk, '金额字段:附件识别金额 300.00 元与报销金额 88.00 元不一致。')
|
||||
assert.ok(riskCards[0].ruleBasis.some((item) => item.includes('风险汇总')))
|
||||
})
|
||||
|
||||
test('AI advice view model exposes grouped completion and risk sections', () => {
|
||||
const advice = buildAiAdviceViewModel({
|
||||
completionItems: ['补充业务地点', '补充报销金额'],
|
||||
@@ -298,6 +321,54 @@ test('expense item upload patches OCR amount into the visible detail row', () =>
|
||||
assert.match(detailViewScript, /expenseEditor\.itemAmount = String\(recognizedItemAmount\)/)
|
||||
})
|
||||
|
||||
test('expense detail save is blocked while attachment recognition is running', () => {
|
||||
assert.match(detailViewScript, /const uploadingExpenseId = ref\(''\)/)
|
||||
assert.match(detailViewScript, /const actionBusy = computed\(\(\) =>[\s\S]*Boolean\(uploadingExpenseId\.value\)/)
|
||||
assert.match(
|
||||
detailViewTemplate,
|
||||
/@click="saveExpenseEdit\(item\)"[\s\S]*:disabled="actionBusy"/
|
||||
)
|
||||
assert.match(
|
||||
detailViewScript,
|
||||
/if \(actionBusy\.value\) \{[\s\S]*toast\(uploadingExpenseId\.value \? '附件识别中,请等待识别完成后再保存。' : '当前操作处理中,请稍后再保存。'\)[\s\S]*return/
|
||||
)
|
||||
})
|
||||
|
||||
test('transport ticket descriptions use route format and invalid format becomes risk advice', () => {
|
||||
assert.match(detailViewScript, /const ROUTE_DESCRIPTION_EXPENSE_TYPES = new Set\(\['train_ticket', 'flight_ticket', 'ride_ticket'\]\)/)
|
||||
assert.match(detailViewScript, /const ROUTE_DESCRIPTION_PATTERN = \/\^\[A-Za-z0-9\\u4e00-\\u9fa5/)
|
||||
assert.match(detailViewScript, /return isRouteDescriptionExpenseType\(itemType\) \? '始发地-目的地,例如:广州南-北京南' : '输入费用说明'/)
|
||||
assert.match(detailViewScript, /return isRouteDescriptionExpenseType\(itemType\) \? '始发地-目的地' : '业务报销说明'/)
|
||||
assert.match(
|
||||
detailViewScript,
|
||||
/isRouteDescriptionExpenseType\(item\.itemType\) && !isValidRouteDescription\(item\.itemReason\)[\s\S]*issues\.push\('行程说明格式错误'\)/
|
||||
)
|
||||
assert.match(
|
||||
detailViewScript,
|
||||
/isRouteDescriptionExpenseType\(expenseEditor\.itemType\)[\s\S]*!isValidRouteDescription\(expenseEditor\.itemReason\)[\s\S]*return '行程说明格式应为“始发地-目的地”,例如:广州南-北京南。'/
|
||||
)
|
||||
assert.match(
|
||||
detailViewScript,
|
||||
/fieldText === '行程说明格式错误'[\s\S]*return `\$\{labelPrefix\}的行程说明,格式应为“始发地-目的地”。`/
|
||||
)
|
||||
})
|
||||
|
||||
test('transport ticket items no longer generate business location completion advice', () => {
|
||||
const locationRequiredBlock = detailViewScript.match(/const LOCATION_REQUIRED_EXPENSE_TYPES = new Set\(\[[\s\S]*?\]\)/)?.[0] || ''
|
||||
|
||||
assert.match(locationRequiredBlock, /'travel'/)
|
||||
assert.match(locationRequiredBlock, /'meeting'/)
|
||||
assert.match(locationRequiredBlock, /'entertainment'/)
|
||||
assert.doesNotMatch(locationRequiredBlock, /'train_ticket'/)
|
||||
assert.doesNotMatch(locationRequiredBlock, /'flight_ticket'/)
|
||||
assert.doesNotMatch(locationRequiredBlock, /'ride_ticket'/)
|
||||
assert.match(
|
||||
detailViewScript,
|
||||
/const locationRequired = isLocationRequiredExpenseType\(item\.itemType\)[\s\S]*if \(locationRequired && isPlaceholderValue\(item\.itemLocation\)\) \{[\s\S]*issues\.push\('缺少地点'\)/
|
||||
)
|
||||
assert.doesNotMatch(detailViewScript, /完善第 1 条费用明细的业务地点/)
|
||||
})
|
||||
|
||||
test('return reason dialog is wired into approval and detail return actions', () => {
|
||||
assert.match(returnReasonDialog, /missing_attachment/)
|
||||
assert.match(returnReasonDialog, /invoice_mismatch/)
|
||||
|
||||
Reference in New Issue
Block a user