Files
X-Financial/web/src/views/scripts/travelRequestDetailAdviceModel.js
caoxiaozhu ca691f3ee0 feat: 优化差旅报销预审流程与个人工作台 UI 体系
- 完善 user_agent_application 申请差旅报销预审槽位与消息组装
- 增强预算助理报告与风险建议卡片交互
- 重构登录页视觉样式与移动端响应式适配
- 优化个人工作台、文档中心、政策中心、员工管理等页面布局
- 拆分 travelRequestDetailPreReviewModel 为 advice/submit 模型
- 补充报销草稿、风险复核、Item Sync 与模板执行器测试覆盖
2026-06-02 14:01:51 +08:00

120 lines
4.1 KiB
JavaScript

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)
}