import assert from 'node:assert/strict' import test from 'node:test' import { buildDetailAlerts, hasMissingAttachment, hasPendingInfo } from '../src/utils/detailAlerts.js' test('detail topbar ignores system allowance rows when checking missing tickets', () => { const request = { node: '直属领导审批', approvalKey: 'in_progress', typeCode: 'travel', typeLabel: '差旅费', reason: '上海项目出差', location: '上海', city: '上海', occurredDisplay: '2026-05-13 至 2026-05-15', amountValue: 1008, profilePosition: '待补充', profileGrade: '待补充', profileManager: '待补充', expenseItems: [ { id: 'outbound-train', itemType: 'train_ticket', itemReason: '广州南-上海虹桥', itemLocation: '上海', itemDate: '2026-05-13', itemAmount: 354, invoiceId: 'outbound.png' }, { id: 'hotel', itemType: 'hotel_ticket', itemReason: '上海中心酒店', itemLocation: '上海', itemDate: '2026-05-14', itemAmount: 354, invoiceId: 'hotel.png' }, { id: 'allowance', itemType: 'travel_allowance', itemReason: '系统自动计算出差补贴', itemLocation: '上海', itemDate: '2026-05-15', itemAmount: 300, invoiceId: '', isSystemGenerated: true } ] } const alerts = buildDetailAlerts(request).map((item) => item.label) assert.equal(hasMissingAttachment(request), false) assert.equal(hasPendingInfo(request), false) assert.deepEqual(alerts, ['SLA 催单次数 0']) }) test('detail topbar still flags real manual rows without required ticket info', () => { const request = { node: '待提交', approvalKey: 'draft', typeCode: 'travel', typeLabel: '差旅费', reason: '待补充', location: '待补充', city: '待补充', occurredDisplay: '待补充', amountValue: 0, expenseItems: [ { id: 'manual-train', itemType: 'train_ticket', itemReason: '', itemLocation: '', itemDate: '', itemAmount: 0, invoiceId: '' }, { id: 'allowance', itemType: 'travel_allowance', itemReason: '系统自动计算出差补贴', itemAmount: 300, invoiceId: '', isSystemGenerated: true } ] } const alerts = buildDetailAlerts(request).map((item) => item.label) assert.equal(hasMissingAttachment(request), true) assert.equal(hasPendingInfo(request), true) assert.deepEqual(alerts, ['SLA 催单次数 0', '缺少票据', '待补信息']) }) test('application detail topbar does not ask for receipt attachments', () => { const request = { id: 'AP-20260525103045-ABCDEFGH', claimNo: 'AP-20260525103045-ABCDEFGH', documentTypeCode: 'application', node: '直属领导审批', approvalKey: 'in_progress', typeCode: 'travel_application', typeLabel: '差旅费用申请', reason: '支撑国网服务器上线部署', location: '上海', city: '上海', occurredDisplay: '2026-05-25 ~ 2026-05-28', amountValue: 12000, attachmentSummary: '申请单', secondaryStatusValue: '已进入审批流程', expenseItems: [] } const alerts = buildDetailAlerts(request).map((item) => item.label) assert.equal(hasMissingAttachment(request), false) assert.equal(alerts.includes('缺少票据'), false) assert.deepEqual(alerts, ['SLA 催单次数 0']) }) test('detail topbar surfaces stored medium and high risk flags first', () => { const highAlerts = buildDetailAlerts({ node: '待提交', approvalKey: 'draft', riskFlags: [ { source: 'submission_review', hit_source: 'rule_center', severity: 'high', message: '票据日期超出申报差旅行程。' }, { source: 'submission_review', hit_source: 'rule_center', severity: 'medium', message: '票据城市需要人工核对。' } ], expenseItems: [] }) const mediumAlerts = buildDetailAlerts({ node: '待提交', approvalKey: 'draft', riskFlags: [ { source: 'submission_review', hit_source: 'rule_center', severity: 'medium', message: '票据城市需要人工核对。' } ], expenseItems: [] }) assert.equal(highAlerts[0].label, '高风险 1 项') assert.equal(highAlerts[0].tone, 'danger') assert.equal(mediumAlerts[0].label, '中风险 1 项') assert.equal(mediumAlerts[0].tone, 'warning') }) test('detail topbar does not treat handoff or SLA events as risk flags', () => { const alerts = buildDetailAlerts({ node: '待提交', approvalKey: 'draft', typeCode: 'travel', typeLabel: '差旅费', reason: '上海项目出差', location: '上海', city: '上海', occurredDisplay: '2026-05-13', amountValue: 300, riskFlags: [ { source: 'application_handoff', event_type: 'expense_application_to_reimbursement_draft', severity: 'info', message: '费用申请已生成报销草稿。' }, { source: 'sla_reminder', message: '下属已催单' } ], expenseItems: [ { id: 'train', itemType: 'train_ticket', itemReason: '武汉-上海', itemLocation: '上海', itemDate: '2026-05-13', itemAmount: 300, invoiceId: 'ticket.pdf' } ] }) assert.deepEqual(alerts.map((item) => item.label), ['SLA 催单次数 1']) }) test('detail topbar shows SLA reminder count from direct fields and reminder events', () => { const directAlerts = buildDetailAlerts({ node: '直属领导审批', approvalKey: 'in_progress', slaReminderCount: 2, expenseItems: [] }) const eventAlerts = buildDetailAlerts({ node: '直属领导审批', approvalKey: 'in_progress', riskFlags: [ { source: 'sla_reminder', message: '下属已催单' }, { event_type: 'urge', message: '再次催单' } ], expenseItems: [] }) assert.equal(directAlerts[0].label, 'SLA 催单次数 2') assert.equal(directAlerts[0].tone, 'warning') assert.equal(directAlerts[0].icon, 'mdi mdi-bell-ring-outline') assert.equal(eventAlerts[0].label, 'SLA 催单次数 2') })