feat: 优化差旅报销预审流程与个人工作台 UI 体系
- 完善 user_agent_application 申请差旅报销预审槽位与消息组装 - 增强预算助理报告与风险建议卡片交互 - 重构登录页视觉样式与移动端响应式适配 - 优化个人工作台、文档中心、政策中心、员工管理等页面布局 - 拆分 travelRequestDetailPreReviewModel 为 advice/submit 模型 - 补充报销草稿、风险复核、Item Sync 与模板执行器测试覆盖
This commit is contained in:
119
web/src/views/scripts/travelRequestDetailAdviceModel.js
Normal file
119
web/src/views/scripts/travelRequestDetailAdviceModel.js
Normal file
@@ -0,0 +1,119 @@
|
||||
function normalizeText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
|
||||
function uniqueTexts(values) {
|
||||
return [...new Set(values.map((item) => normalizeText(item)).filter(Boolean))]
|
||||
}
|
||||
|
||||
function isPlaceholderValue(value) {
|
||||
const text = normalizeText(value)
|
||||
if (!text) {
|
||||
return true
|
||||
}
|
||||
return ['待补充', '暂无', '无', '未知', '处理中'].includes(text.replace(/\s+/g, ''))
|
||||
}
|
||||
|
||||
function isApplicationDocumentRequest(requestModel) {
|
||||
const documentType = normalizeText(
|
||||
requestModel?.documentTypeCode
|
||||
|| requestModel?.document_type_code
|
||||
|| requestModel?.documentType
|
||||
|| requestModel?.document_type
|
||||
).toLowerCase()
|
||||
const claimNo = normalizeText(requestModel?.claimNo || requestModel?.claim_no || requestModel?.documentNo).toUpperCase()
|
||||
return documentType === 'application' || claimNo.startsWith('AP-') || claimNo.startsWith('APP-')
|
||||
}
|
||||
|
||||
function isHotelExpenseItem(item) {
|
||||
const text = [
|
||||
item?.itemType,
|
||||
item?.typeCode,
|
||||
item?.name,
|
||||
item?.category,
|
||||
item?.desc,
|
||||
item?.itemReason
|
||||
].map((value) => normalizeText(value)).join(' ')
|
||||
return /hotel_ticket|hotel|住宿|酒店|水单/.test(text)
|
||||
}
|
||||
|
||||
export function buildTravelReceiptMaterialPrompts(requestModel, items) {
|
||||
if (isApplicationDocumentRequest(requestModel)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const normalizedItems = Array.isArray(items) ? items : []
|
||||
const missingHotelItems = normalizedItems.filter(
|
||||
(item) => !item?.isSystemGenerated && isHotelExpenseItem(item) && isPlaceholderValue(item.invoiceId)
|
||||
)
|
||||
if (!missingHotelItems.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
`当前包含 ${missingHotelItems.length} 条住宿费用明细,但暂未关联住宿发票或酒店水单。请补充住宿材料,避免后续被退回补件。`
|
||||
]
|
||||
}
|
||||
|
||||
function profileMetric(profile, key) {
|
||||
const profiles = Array.isArray(profile?.profiles) ? profile.profiles : []
|
||||
for (const item of profiles) {
|
||||
const metrics = item?.metrics && typeof item.metrics === 'object' ? item.metrics : {}
|
||||
const value = Number(metrics[key])
|
||||
if (Number.isFinite(value) && value > 0) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function profileReviewSuggestionTexts(profile) {
|
||||
const suggestions = Array.isArray(profile?.review_suggestions)
|
||||
? profile.review_suggestions
|
||||
: Array.isArray(profile?.reviewSuggestions)
|
||||
? profile.reviewSuggestions
|
||||
: []
|
||||
return suggestions
|
||||
.map((item) => normalizeText(item?.message || item?.title || item?.label))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function profileRiskTagTexts(profile) {
|
||||
const tags = Array.isArray(profile?.profile_tags)
|
||||
? profile.profile_tags
|
||||
: Array.isArray(profile?.profileTags)
|
||||
? profile.profileTags
|
||||
: []
|
||||
return tags
|
||||
.filter((tag) => normalizeText(tag?.polarity) === 'risk')
|
||||
.map((tag) => normalizeText(tag?.reason || tag?.display_label || tag?.label))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function buildEmployeeProfileAdviceItems(profile) {
|
||||
if (!profile || typeof profile !== 'object') {
|
||||
return []
|
||||
}
|
||||
|
||||
const returnCount = profileMetric(profile, 'return_count')
|
||||
const missingAttachmentCount = profileMetric(profile, 'missing_attachment_count')
|
||||
const invoiceMismatchCount = profileMetric(profile, 'invoice_mismatch_count')
|
||||
const missingContextCount = profileMetric(profile, 'missing_business_context_count')
|
||||
const items = []
|
||||
|
||||
if (returnCount > 0) {
|
||||
items.push(`历史退单建议:近 90 天存在 ${returnCount} 次退单或退回记录,提交前重点复核退回原因对应的票据、事由和说明,避免重复被退。`)
|
||||
}
|
||||
if (missingAttachmentCount > 0 || missingContextCount > 0) {
|
||||
items.push(`材料完整性建议:历史材料或业务上下文缺失累计 ${missingAttachmentCount + missingContextCount} 项,本次提交前请重点核对附件、事由、地点和补充说明。`)
|
||||
}
|
||||
if (invoiceMismatchCount > 0) {
|
||||
items.push(`票据一致性建议:历史存在 ${invoiceMismatchCount} 次票据不一致记录,本次请重点核对票据日期、城市、金额和费用明细。`)
|
||||
}
|
||||
|
||||
return uniqueTexts([
|
||||
...items,
|
||||
...profileReviewSuggestionTexts(profile),
|
||||
...profileRiskTagTexts(profile)
|
||||
]).slice(0, 4)
|
||||
}
|
||||
Reference in New Issue
Block a user