feat: 完善差旅票据行程提取与费用明细回填逻辑
增强文档智能识别的票据场景关键词和字段提取能力,优化 会话关联草稿报销单的解析路径,修复费用明细合并和票据 去重边界问题,前端改进报销创建和审批详情交互,补充单 元测试覆盖。
This commit is contained in:
@@ -50,16 +50,14 @@ const EXPENSE_TYPE_OPTIONS = [
|
||||
|
||||
const LOCATION_REQUIRED_EXPENSE_TYPES = new Set([
|
||||
'travel',
|
||||
'train_ticket',
|
||||
'flight_ticket',
|
||||
'hotel_ticket',
|
||||
'ride_ticket',
|
||||
'meeting',
|
||||
'entertainment'
|
||||
])
|
||||
|
||||
const SYSTEM_GENERATED_EXPENSE_TYPES = new Set(['travel_allowance'])
|
||||
const LONG_DISTANCE_TRAVEL_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket'])
|
||||
const ROUTE_DESCRIPTION_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket', 'ride_ticket'])
|
||||
const ROUTE_DESCRIPTION_PATTERN = /^[A-Za-z0-9\u4e00-\u9fa5()()·]{2,40}\s*-\s*[A-Za-z0-9\u4e00-\u9fa5()()·]{2,40}$/
|
||||
|
||||
function parseCurrency(value) {
|
||||
return Number.parseFloat(String(value).replace(/[^\d.]/g, '')) || 0
|
||||
@@ -103,6 +101,23 @@ function resolveLocationDisplay(value, expenseType) {
|
||||
return isPlaceholderValue(value) ? '待补充' : value
|
||||
}
|
||||
|
||||
function isRouteDescriptionExpenseType(value) {
|
||||
return ROUTE_DESCRIPTION_EXPENSE_TYPES.has(normalizeExpenseType(value))
|
||||
}
|
||||
|
||||
function isValidRouteDescription(value) {
|
||||
const text = String(value || '').trim()
|
||||
return ROUTE_DESCRIPTION_PATTERN.test(text) && !/\d{4}[-/年.]\d{1,2}[-/月.]\d{1,2}/.test(text)
|
||||
}
|
||||
|
||||
function resolveExpenseReasonPlaceholder(itemType) {
|
||||
return isRouteDescriptionExpenseType(itemType) ? '始发地-目的地,例如:广州南-北京南' : '输入费用说明'
|
||||
}
|
||||
|
||||
function resolveExpenseReasonHelper(itemType) {
|
||||
return isRouteDescriptionExpenseType(itemType) ? '始发地-目的地' : '业务报销说明'
|
||||
}
|
||||
|
||||
function buildFallbackProgressSteps() {
|
||||
return [
|
||||
{ index: 1, label: '创建单据', time: '已完成', done: true, active: true },
|
||||
@@ -368,6 +383,8 @@ function buildExpenseDraftIssues(item) {
|
||||
}
|
||||
if (isPlaceholderValue(item.itemReason)) {
|
||||
issues.push('缺少说明')
|
||||
} else if (isRouteDescriptionExpenseType(item.itemType) && !isValidRouteDescription(item.itemReason)) {
|
||||
issues.push('行程说明格式错误')
|
||||
}
|
||||
if (locationRequired && isPlaceholderValue(item.itemLocation)) {
|
||||
issues.push('缺少地点')
|
||||
@@ -464,6 +481,9 @@ function mapIssueToAdvice(issue) {
|
||||
if (fieldText === '缺少说明') {
|
||||
return `${labelPrefix}的用途说明。`
|
||||
}
|
||||
if (fieldText === '行程说明格式错误') {
|
||||
return `${labelPrefix}的行程说明,格式应为“始发地-目的地”。`
|
||||
}
|
||||
if (fieldText === '缺少地点') {
|
||||
return `${labelPrefix}的业务地点。`
|
||||
}
|
||||
@@ -1042,6 +1062,12 @@ export default {
|
||||
if (isPlaceholderValue(expenseEditor.itemReason)) {
|
||||
return '请输入费用说明。'
|
||||
}
|
||||
if (
|
||||
isRouteDescriptionExpenseType(expenseEditor.itemType)
|
||||
&& !isValidRouteDescription(expenseEditor.itemReason)
|
||||
) {
|
||||
return '行程说明格式应为“始发地-目的地”,例如:广州南-北京南。'
|
||||
}
|
||||
|
||||
const amount = Number(expenseEditor.itemAmount)
|
||||
if (!Number.isFinite(amount) || amount <= 0) {
|
||||
@@ -1325,6 +1351,10 @@ export default {
|
||||
}
|
||||
|
||||
async function saveExpenseEdit(item) {
|
||||
if (actionBusy.value) {
|
||||
toast(uploadingExpenseId.value ? '附件识别中,请等待识别完成后再保存。' : '当前操作处理中,请稍后再保存。')
|
||||
return
|
||||
}
|
||||
if (!request.value.claimId) {
|
||||
toast('当前草稿缺少 claimId,暂时无法保存费用明细。')
|
||||
return
|
||||
@@ -1666,6 +1696,8 @@ export default {
|
||||
resolveAttachmentDisplayName,
|
||||
resolveAttachmentPreviewTitle,
|
||||
resolveAttachmentRecognition,
|
||||
resolveExpenseReasonHelper,
|
||||
resolveExpenseReasonPlaceholder,
|
||||
resolveExpenseRiskState,
|
||||
resolveExpenseIssues,
|
||||
returnBusy,
|
||||
|
||||
Reference in New Issue
Block a user