feat: 优化差旅报销预审流程与个人工作台 UI 体系
- 完善 user_agent_application 申请差旅报销预审槽位与消息组装 - 增强预算助理报告与风险建议卡片交互 - 重构登录页视觉样式与移动端响应式适配 - 优化个人工作台、文档中心、政策中心、员工管理等页面布局 - 拆分 travelRequestDetailPreReviewModel 为 advice/submit 模型 - 补充报销草稿、风险复核、Item Sync 与模板执行器测试覆盖
This commit is contained in:
@@ -27,6 +27,7 @@ export const LOCATION_REQUIRED_EXPENSE_TYPES = new Set([
|
||||
])
|
||||
|
||||
export const SYSTEM_GENERATED_EXPENSE_TYPES = new Set(['travel_allowance'])
|
||||
export const OPTIONAL_ATTACHMENT_EXPENSE_TYPES = new Set(['ride_ticket', 'travel_allowance'])
|
||||
export const LONG_DISTANCE_TRAVEL_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket'])
|
||||
export const ROUTE_DESCRIPTION_EXPENSE_TYPES = new Set(['train_ticket', 'flight_ticket', 'ship_ticket', 'ferry_ticket', 'ride_ticket'])
|
||||
export const HOTEL_DESCRIPTION_EXPENSE_TYPES = new Set(['hotel_ticket'])
|
||||
@@ -88,6 +89,11 @@ export function isSystemGeneratedExpenseItemSource(source) {
|
||||
return Boolean(source?.isSystemGenerated || source?.is_system_generated || SYSTEM_GENERATED_EXPENSE_TYPES.has(itemType))
|
||||
}
|
||||
|
||||
export function isAttachmentRequiredExpenseItem(source) {
|
||||
const itemType = normalizeExpenseType(source?.itemType || source?.item_type)
|
||||
return !isSystemGeneratedExpenseItemSource({ ...source, itemType }) && !OPTIONAL_ATTACHMENT_EXPENSE_TYPES.has(itemType)
|
||||
}
|
||||
|
||||
export function isLocationRequiredExpenseType(value) {
|
||||
return LOCATION_REQUIRED_EXPENSE_TYPES.has(normalizeExpenseType(value))
|
||||
}
|
||||
@@ -200,12 +206,11 @@ export function buildFallbackProgressSteps(requestModel = {}) {
|
||||
return [
|
||||
{ index: 1, label: '关联单据', time: hasRelatedApplication ? '已关联' : '待核对', done: hasRelatedApplication, active: true, current: !hasRelatedApplication },
|
||||
{ index: 2, label: '待提交', time: hasRelatedApplication ? '进行中' : '待处理', active: hasRelatedApplication, current: hasRelatedApplication },
|
||||
{ index: 3, label: 'AI预审', time: '待处理' },
|
||||
{ index: 4, label: '直属领导审批', time: '待处理' },
|
||||
{ index: 5, label: '财务审批', time: '待处理' },
|
||||
{ index: 6, label: '待付款', time: pendingPayment ? '进行中' : completed ? '已完成' : '待处理', done: completed, active: pendingPayment || completed, current: pendingPayment },
|
||||
{ index: 7, label: '已付款', time: paid || completed ? '已完成' : '待处理', done: paid || completed, active: paid || completed, current: false },
|
||||
{ index: 8, label: '已归档', time: paid || completed ? '已完成' : '待处理', done: paid || completed, active: paid || completed, current: false }
|
||||
{ index: 3, label: '直属领导审批', time: '待处理' },
|
||||
{ index: 4, label: '财务审批', time: '待处理' },
|
||||
{ index: 5, label: '待付款', time: pendingPayment ? '进行中' : completed ? '已完成' : '待处理', done: completed, active: pendingPayment || completed, current: pendingPayment },
|
||||
{ index: 6, label: '已付款', time: paid || completed ? '已完成' : '待处理', done: paid || completed, active: paid || completed, current: false },
|
||||
{ index: 7, label: '已归档', time: paid || completed ? '已完成' : '待处理', done: paid || completed, active: paid || completed, current: false }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -476,59 +481,13 @@ export function buildExpenseDraftIssues(item) {
|
||||
if (!Number.isFinite(Number(item.itemAmount)) || Number(item.itemAmount) <= 0) {
|
||||
issues.push('缺少金额')
|
||||
}
|
||||
if (isPlaceholderValue(item.invoiceId)) {
|
||||
if (isAttachmentRequiredExpenseItem(item) && isPlaceholderValue(item.invoiceId)) {
|
||||
issues.push('缺少票据标识')
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
export function buildOptionalTravelReceiptRiskCards(requestModel, items) {
|
||||
if (isApplicationDocumentRequest(requestModel)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const normalizedItems = Array.isArray(items) ? items : []
|
||||
const isTravelContext =
|
||||
requestModel?.detailVariant === 'travel' ||
|
||||
requestModel?.typeCode === 'travel' ||
|
||||
normalizedItems.some((item) => ['train_ticket', 'flight_ticket', 'hotel_ticket', 'ride_ticket', 'travel_allowance'].includes(item.itemType))
|
||||
if (!isTravelContext) {
|
||||
return []
|
||||
}
|
||||
|
||||
const hasUploadedType = (itemType) =>
|
||||
normalizedItems.some((item) => item.itemType === itemType && !item.isSystemGenerated && !isPlaceholderValue(item.invoiceId))
|
||||
const cards = []
|
||||
if (!hasUploadedType('hotel_ticket')) {
|
||||
cards.push({
|
||||
id: 'travel-optional-hotel-ticket',
|
||||
businessStage: 'reimbursement',
|
||||
tone: 'low',
|
||||
label: '低风险',
|
||||
title: '住宿票据提醒',
|
||||
risk: '当前差旅单暂未上传住宿票据;如果本次出差发生住宿费用,请不要忘记补充酒店住宿票据。',
|
||||
summary: '住宿票据缺失不阻断当前提交,但会影响住宿费用报销完整性。',
|
||||
ruleBasis: ['差旅费可以包含交通、住宿和补贴等明细;住宿费用需要住宿票据支撑。'],
|
||||
suggestion: '如有住宿费用,请新增住宿票明细并上传酒店发票或住宿清单;如未住宿,可忽略该提醒。'
|
||||
})
|
||||
}
|
||||
if (!hasUploadedType('ride_ticket')) {
|
||||
cards.push({
|
||||
id: 'travel-optional-ride-ticket',
|
||||
businessStage: 'reimbursement',
|
||||
tone: 'low',
|
||||
label: '低风险',
|
||||
title: '乘车票据提醒',
|
||||
risk: '当前差旅单暂未上传市内乘车票据;如果发生打车或市内交通费用,可以继续补充票据报销。',
|
||||
summary: '市内交通票据缺失不阻断当前提交,但可能遗漏可报销费用。',
|
||||
ruleBasis: ['差旅费可以补充市内交通/乘车票据;该类票据通常作为差旅费用的可选补充材料。'],
|
||||
suggestion: '如有打车、网约车或市内交通费用,请新增乘车明细并上传对应票据。'
|
||||
})
|
||||
}
|
||||
return cards
|
||||
}
|
||||
|
||||
export function buildDraftBlockingIssues(request, expenseItems) {
|
||||
const issues = []
|
||||
const locationRequired = isLocationRequiredExpenseType(request.typeCode)
|
||||
|
||||
Reference in New Issue
Block a user