import assert from 'node:assert/strict' import test from 'node:test' import { mapExpenseClaimToRequest } from '../src/composables/useRequests.js' test('application claims are mapped as application documents', () => { const request = mapExpenseClaimToRequest({ id: 'claim-application-1', claim_no: 'APP-20260525-ABC123', employee_name: '张三', department_name: '交付部', expense_type: 'travel_application', reason: '支撑国网服务器上线部署', location: '上海', amount: 12000, invoice_count: 0, occurred_at: '2026-05-25T00:00:00.000Z', submitted_at: '2026-05-25T02:00:00.000Z', created_at: '2026-05-25T01:30:00.000Z', updated_at: '2026-05-25T02:00:00.000Z', status: 'submitted', approval_stage: '直属领导审批', risk_flags_json: [], items: [] }) assert.equal(request.documentTypeCode, 'application') assert.equal(request.documentTypeLabel, '申请单') assert.equal(request.typeLabel, '差旅费用申请') assert.equal(request.secondaryStatusLabel, '申请材料') assert.equal(request.secondaryStatusValue, '已进入审批流程') assert.equal(request.expenseTableSummary, '预计金额已纳入预算管理口径') assert.deepEqual( request.progressSteps.map((step) => step.label), ['创建申请', '直属领导审批', '审批完成'] ) assert.equal(request.progressSteps.some((step) => step.label === 'AI预审'), false) assert.equal(request.progressSteps.some((step) => step.label === '财务审批'), false) assert.equal(request.progressSteps.find((step) => step.label === '直属领导审批')?.current, true) }) test('approved application claims complete after direct manager approval only', () => { const request = mapExpenseClaimToRequest({ id: 'claim-application-approved', claim_no: 'APP-20260525-DONE01', employee_name: '张三', department_name: '交付部', manager_name: '李经理', expense_type: 'travel_application', reason: '支撑国网服务器上线部署', location: '上海', amount: 12000, invoice_count: 0, occurred_at: '2026-05-25T00:00:00.000Z', submitted_at: '2026-05-25T02:00:00.000Z', created_at: '2026-05-25T01:30:00.000Z', updated_at: '2026-05-25T03:00:00.000Z', status: 'approved', approval_stage: '审批完成', risk_flags_json: [ { source: 'manual_approval', event_type: 'expense_application_approval', operator: '李经理', previous_approval_stage: '直属领导审批', next_approval_stage: '审批完成', created_at: '2026-05-25T03:00:00.000Z' } ], items: [] }) assert.equal(request.documentTypeCode, 'application') assert.equal(request.workflowNode, '审批完成') assert.deepEqual( request.progressSteps.map((step) => step.label), ['创建申请', '直属领导审批', '审批完成'] ) assert.equal(request.progressSteps.every((step) => step.done), true) assert.equal(request.progressSteps.find((step) => step.label === '直属领导审批')?.time, '李经理通过') }) test('progress steps show approval operator time and current stay duration', () => { const originalNow = Date.now Date.now = () => new Date('2026-05-20T05:00:00.000Z').getTime() try { const request = mapExpenseClaimToRequest({ id: 'claim-1', claim_no: 'EXP-202605-001', employee_name: '张三', department_name: '市场部', expense_type: 'transport', reason: '交通报销', location: '上海', amount: 88, invoice_count: 1, occurred_at: '2026-05-20T01:00:00.000Z', submitted_at: '2026-05-20T02:00:00.000Z', created_at: '2026-05-20T01:30:00.000Z', updated_at: '2026-05-20T03:30:00.000Z', status: 'submitted', approval_stage: '财务审批', risk_flags_json: [ { source: 'manual_approval', operator: '李经理', previous_approval_stage: '直属领导审批', next_approval_stage: '财务审批', created_at: '2026-05-20T03:30:00.000Z' } ], items: [] }) const leaderStep = request.progressSteps.find((step) => step.label === '直属领导审批') const financeStep = request.progressSteps.find((step) => step.label === '财务审批') const aiStep = request.progressSteps.find((step) => step.label === 'AI预审') const firstStep = request.progressSteps[0] assert.equal(request.riskSummary, '无') assert.equal(firstStep.label, '创建单据') assert.equal(leaderStep.time, '李经理通过') assert.match(leaderStep.detail, /2026-05-20/) assert.match(leaderStep.title, /李经理审批通过/) assert.equal(aiStep.time, 'AI预审通过') assert.match(aiStep.detail, /2026-05-20/) assert.equal(financeStep.current, true) assert.equal(financeStep.time, '停留 1小时30分钟') } finally { Date.now = originalNow } }) test('progress steps do not expose approver email when manager name is available', () => { const originalNow = Date.now Date.now = () => new Date('2026-05-20T05:00:00.000Z').getTime() try { const request = mapExpenseClaimToRequest({ id: 'claim-email-operator', claim_no: 'EXP-202605-003', employee_name: '张三', department_name: '市场部', manager_name: '李经理', expense_type: 'transport', reason: '交通报销', location: '上海', amount: 88, invoice_count: 1, occurred_at: '2026-05-20T01:00:00.000Z', submitted_at: '2026-05-20T02:00:00.000Z', created_at: '2026-05-20T01:30:00.000Z', updated_at: '2026-05-20T03:30:00.000Z', status: 'submitted', approval_stage: '财务审批', risk_flags_json: [ { source: 'manual_approval', operator: 'manager@example.com', operator_username: 'manager@example.com', previous_approval_stage: '直属领导审批', next_approval_stage: '财务审批', created_at: '2026-05-20T03:30:00.000Z' } ], items: [] }) const leaderStep = request.progressSteps.find((step) => step.label === '直属领导审批') assert.equal(leaderStep.time, '李经理通过') assert.ok(!leaderStep.title.includes('manager@example.com')) } finally { Date.now = originalNow } }) test('travel expense items describe departure return and lodging time below the date', () => { const request = mapExpenseClaimToRequest({ id: 'claim-travel-time-labels', claim_no: 'EXP-202605-TRAVEL', employee_name: '张三', department_name: '市场部', expense_type: 'travel', reason: '北京客户现场出差', location: '北京', amount: 1108, invoice_count: 3, occurred_at: '2026-05-13T01:00:00.000Z', created_at: '2026-05-13T01:30:00.000Z', updated_at: '2026-05-13T03:30:00.000Z', status: 'draft', approval_stage: '待提交', risk_flags_json: [], items: [ { id: 'outbound-train', item_type: 'train_ticket', item_reason: '广州南-北京南', item_location: '北京', item_date: '2026-05-13', item_amount: 354, invoice_id: 'outbound.png' }, { id: 'hotel', item_type: 'hotel_ticket', item_reason: '北京全季酒店', item_location: '北京', item_date: '2026-05-14', item_amount: 100, invoice_id: 'hotel.png' }, { id: 'return-train', item_type: 'train_ticket', item_reason: '北京南-广州南', item_location: '广州', item_date: '2026-05-15', item_amount: 354, invoice_id: 'return.png' }, { id: 'allowance', item_type: 'travel_allowance', item_reason: '系统自动计算出差补贴', item_location: '北京', item_date: '2026-05-15', item_amount: 300, invoice_id: '', is_system_generated: true } ] }) assert.equal(request.expenseItems.find((item) => item.id === 'outbound-train')?.dayLabel, '出发时间') assert.equal(request.expenseItems.find((item) => item.id === 'return-train')?.dayLabel, '返回时间') assert.equal(request.expenseItems.find((item) => item.id === 'hotel')?.dayLabel, '住宿时间') assert.equal(request.expenseItems.find((item) => item.id === 'outbound-train')?.detail, '起始地-目的地') assert.equal(request.expenseItems.find((item) => item.id === 'return-train')?.detail, '起始地-目的地') assert.equal(request.expenseItems.find((item) => item.id === 'hotel')?.detail, '目的地酒店') assert.equal(request.expenseItems.at(-1)?.id, 'allowance') assert.equal(request.expenseItems.at(-1)?.dayLabel, '系统自动计算') }) test('ticket description helper does not show the destination city as detail text', () => { const request = mapExpenseClaimToRequest({ id: 'claim-ticket-detail-helper', claim_no: 'EXP-202605-ROUTE', employee_name: '张三', department_name: '市场部', expense_type: 'travel', reason: '上海项目出差', location: '上海', amount: 520, invoice_count: 2, occurred_at: '2026-05-13T01:00:00.000Z', created_at: '2026-05-13T01:30:00.000Z', updated_at: '2026-05-13T03:30:00.000Z', status: 'draft', approval_stage: '待提交', risk_flags_json: [], items: [ { id: 'flight', item_type: 'flight_ticket', item_reason: '广州白云-上海虹桥', item_location: '上海', item_date: '2026-05-13', item_amount: 320, invoice_id: 'flight.png' }, { id: 'ship', item_type: 'ship_ticket', item_reason: '上海港-舟山港', item_location: '舟山', item_date: '2026-05-14', item_amount: 200, invoice_id: 'ship.png' } ] }) assert.equal(request.expenseItems.find((item) => item.id === 'flight')?.detail, '起始地-目的地') assert.equal(request.expenseItems.find((item) => item.id === 'ship')?.detail, '起始地-目的地') assert.equal(request.expenseItems.find((item) => item.id === 'ship')?.name, '轮船票') }) test('completed finance approval marks finance and archive progress steps', () => { const request = mapExpenseClaimToRequest({ id: 'claim-finance-completed', claim_no: 'EXP-202605-004', employee_name: '张三', department_name: '市场部', expense_type: 'transport', reason: '交通报销', location: '上海', amount: 88, invoice_count: 1, occurred_at: '2026-05-20T01:00:00.000Z', submitted_at: '2026-05-20T02:00:00.000Z', created_at: '2026-05-20T01:30:00.000Z', updated_at: '2026-05-20T04:00:00.000Z', status: 'approved', approval_stage: '归档入账', risk_flags_json: [ { source: 'manual_approval', operator: '李经理', previous_approval_stage: '直属领导审批', next_approval_stage: '财务审批', created_at: '2026-05-20T03:00:00.000Z' }, { source: 'finance_approval', operator: '财务复核', previous_approval_stage: '财务审批', next_approval_stage: '归档入账', created_at: '2026-05-20T04:00:00.000Z' } ], items: [] }) const financeStep = request.progressSteps.find((step) => step.label === '财务审批') const archiveStep = request.progressSteps.find((step) => step.label === '归档入账') assert.equal(request.riskSummary, '无') assert.equal(request.workflowNode, '归档入账') assert.equal(financeStep.time, '财务复核通过') assert.match(financeStep.detail, /2026-05-20/) assert.equal(archiveStep.time, '归档入账') assert.equal(archiveStep.done, true) }) test('current direct manager step shows how long the claim has stayed there', () => { const originalNow = Date.now Date.now = () => new Date('2026-05-20T05:15:00.000Z').getTime() try { const request = mapExpenseClaimToRequest({ id: 'claim-2', claim_no: 'EXP-202605-002', employee_name: '王五', department_name: '市场部', expense_type: 'office', reason: '办公用品', location: '上海', amount: 128, invoice_count: 1, occurred_at: '2026-05-20T01:00:00.000Z', submitted_at: '2026-05-20T02:00:00.000Z', created_at: '2026-05-20T01:30:00.000Z', updated_at: '2026-05-20T02:00:00.000Z', status: 'submitted', approval_stage: '直属领导审批', risk_flags_json: [], items: [] }) const leaderStep = request.progressSteps.find((step) => step.label === '直属领导审批') const submitStep = request.progressSteps.find((step) => step.label === '待提交') assert.equal(submitStep.time, '王五提交') assert.match(submitStep.detail, /2026-05-20/) assert.equal(leaderStep.current, true) assert.equal(leaderStep.time, '停留 3小时15分钟') } finally { Date.now = originalNow } })