import assert from 'node:assert/strict' import test from 'node:test' import { buildAiApplicationPrecheck, buildAiApplicationPrecheckMessage, buildAiApplicationPrecheckThinkingEvents, buildAiApplicationSubmitConflictMessage, isAiApplicationPrecheckBlocking } from '../src/utils/aiApplicationPrecheckModel.js' const preview = { fields: { applicationType: '差旅费用申请', time: '2026-02-20 至 2026-02-23', location: '上海', reason: '辅助国网仿生产服务器部署', amount: '2,120元', days: '4天', transportMode: '火车' }, missingFields: [] } test('application precheck blocks application generation when existing application overlaps', () => { const precheck = buildAiApplicationPrecheck(preview, { currentUser: { name: '曹笑竹', departmentName: '技术部' }, claimsPayload: { items: [ { claim_no: 'AP-OVERLAP', document_type: 'expense_application', expense_type: 'travel_application', employee_name: '曹笑竹', status: 'submitted', risk_flags_json: [ { source: 'application_detail', application_detail: { business_time: '2026-02-21 至 2026-02-22', reason: '同时间段现场支持', location: '上海' } } ] } ] }, budgetSummary: { total_amount: 10000, reserved_amount: 8000, consumed_amount: 500, available_amount: 1500 } }) assert.equal(precheck.overlap.status, 'warning') assert.match(precheck.overlap.summary, /可能重叠/) assert.equal(precheck.overlap.matches[0].claimNo, 'AP-OVERLAP') assert.equal(precheck.budget.status, 'warning') assert.equal(precheck.budget.requiresBudgetReview, true) assert.match(precheck.budget.summary, /预算管理者审核/) const message = buildAiApplicationPrecheckMessage(preview, precheck) assert.match(message, /### 发现同时间段已有申请单/) assert.match(message, /时间重叠提醒/) assert.match(message, /AP-OVERLAP/) assert.match(message, /\| 单据编号 \| 申请时间 \| 状态 \| 事由 \| 操作 \|/) assert.match(message, /\| AP-OVERLAP \| 2026-02-21 至 2026-02-22 \| 审批中 \| 同时间段现场支持 \| \[查看\]\(#ai-open-application-detail:AP-OVERLAP\) \|/) assert.match(message, /2026-02-21 至 2026-02-22/) assert.match(message, /同时间段现场支持/) assert.match(message, /请先检查本次申请时间是否填写正确/) assert.doesNotMatch(message, /出差申请表草稿已生成/) }) test('application submit precheck blocks submit and keeps application detail action link', () => { const precheck = buildAiApplicationPrecheck(preview, { currentUser: { name: '曹笑竹', departmentName: '技术部' }, claimsPayload: { items: [ { claim_no: 'AP-OVERLAP', document_type: 'expense_application', expense_type: 'travel_application', employee_name: '曹笑竹', status: 'submitted', risk_flags_json: [ { source: 'application_detail', application_detail: { business_time: '2026-02-20 至 2026-02-23', reason: '辅助国网仿生产服务器部署', location: '上海' } } ] } ] } }) assert.equal(isAiApplicationPrecheckBlocking(precheck), true) const message = buildAiApplicationSubmitConflictMessage(preview, precheck) assert.match(message, /### 发现相同日期已有申请单/) assert.match(message, /当前不能继续提交/) assert.match(message, /请先核对申请时间是否填写正确/) assert.match(message, /\[查看\]\(#ai-open-application-detail:AP-OVERLAP\)/) assert.doesNotMatch(message, /生成新的出差申请表/) }) test('application precheck emits thinking events for overlap, budget, and form generation', () => { const precheck = buildAiApplicationPrecheck(preview, { currentUser: { name: '曹笑竹' }, claimsPayload: [], budgetSummary: { total_amount: 10000, reserved_amount: 1000, consumed_amount: 1000, available_amount: 8000 } }) const events = buildAiApplicationPrecheckThinkingEvents(precheck) assert.equal(events.length, 3) assert.deepEqual( events.map((event) => event.eventId), ['application-precheck-overlap', 'application-precheck-budget', 'application-precheck-form'] ) assert.match(events[1].content, /预算/) }) test('application precheck ignores application candidates without parseable business time', () => { const precheck = buildAiApplicationPrecheck(preview, { currentUser: { name: '曹笑竹' }, claimsPayload: { items: [ { claim_no: 'AP-NO-TIME', document_type: 'expense_application', expense_type: 'travel_application', employee_name: '曹笑竹', status: 'submitted' } ] }, budgetSummary: {} }) assert.equal(precheck.overlap.status, 'ok') assert.deepEqual(precheck.overlap.matches, []) })