feat(ai): issue verified application preview decisions
This commit is contained in:
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict'
|
||||
import {
|
||||
AI_APPLICATION_ACTION_SAVE_DRAFT,
|
||||
AI_APPLICATION_ACTION_SUBMIT,
|
||||
registerAiApplicationPreviewDecision,
|
||||
runAiApplicationPreviewAction
|
||||
} from '../src/services/aiApplicationPreviewActions.js'
|
||||
|
||||
@@ -34,6 +35,7 @@ async function testSubmitActionUsesFastPreviewEndpoint() {
|
||||
await runAiApplicationPreviewAction({
|
||||
actionType: AI_APPLICATION_ACTION_SUBMIT,
|
||||
applicationPreview: {
|
||||
decisionId: 'decision-fast-submit',
|
||||
fields: {
|
||||
applicationType: '差旅费用申请',
|
||||
time: '2026-07-01 至 2026-07-03',
|
||||
@@ -55,7 +57,8 @@ async function testSubmitActionUsesFastPreviewEndpoint() {
|
||||
}
|
||||
},
|
||||
currentUser: { username: 'zhangsan@example.com', name: '张三' },
|
||||
conversationId: 'conversation-fast-submit'
|
||||
conversationId: 'conversation-fast-submit',
|
||||
requestId: 'action-fast-submit'
|
||||
})
|
||||
|
||||
assert.equal(capturedUrl, '/api/v1/reimbursements/application-preview-action')
|
||||
@@ -63,6 +66,9 @@ async function testSubmitActionUsesFastPreviewEndpoint() {
|
||||
const body = JSON.parse(capturedOptions.body)
|
||||
assert.equal(body.context_json.session_type, 'application')
|
||||
assert.equal(body.context_json.application_stage, 'expense_application')
|
||||
assert.equal(body.action_type, 'submit')
|
||||
assert.equal(body.decision_id, 'decision-fast-submit')
|
||||
assert.equal(body.request_id, 'action-fast-submit')
|
||||
assert.equal(body.context_json.application_preview.fields.transportMode, '火车')
|
||||
assert.deepEqual(body.context_json.application_preview.aiDecisionFeedback.changedFields, [
|
||||
{
|
||||
@@ -110,6 +116,59 @@ async function testSaveDraftActionUsesFastPreviewEndpoint() {
|
||||
assert.equal(body.context_json.application_action, 'save_draft')
|
||||
assert.equal(body.context_json.application_save_mode, true)
|
||||
assert.equal(body.context_json.application_stage, 'expense_application')
|
||||
assert.equal(body.action_type, 'save_draft')
|
||||
assert.equal('decision_id' in body, false)
|
||||
assert.equal('request_id' in body, false)
|
||||
}
|
||||
|
||||
async function testRegistrationUsesServerCanonicalPreview() {
|
||||
let capturedUrl = ''
|
||||
let capturedOptions = null
|
||||
global.fetch = async (url, options) => {
|
||||
capturedUrl = String(url)
|
||||
capturedOptions = options
|
||||
return {
|
||||
ok: true,
|
||||
async json() {
|
||||
return {
|
||||
decision_id: 'decision-issued-1',
|
||||
decision_source: 'rule',
|
||||
expires_at: '2026-07-14T10:30:00Z',
|
||||
application_preview: {
|
||||
fields: {
|
||||
reason: '服务端规范事由',
|
||||
amount: '1800元'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const preview = await registerAiApplicationPreviewDecision({
|
||||
applicationPreview: {
|
||||
fields: {
|
||||
reason: '客户端事由',
|
||||
location: '上海'
|
||||
}
|
||||
},
|
||||
conversationId: 'conversation-issued-1',
|
||||
message: '去上海做客户现场实施',
|
||||
requestId: 'preview-request-1'
|
||||
})
|
||||
|
||||
assert.equal(capturedUrl, '/api/v1/reimbursements/application-previews')
|
||||
assert.equal(capturedOptions.method, 'POST')
|
||||
assert.deepEqual(JSON.parse(capturedOptions.body), {
|
||||
message: '去上海做客户现场实施',
|
||||
conversation_id: 'conversation-issued-1',
|
||||
request_id: 'preview-request-1'
|
||||
})
|
||||
assert.equal(preview.decisionId, 'decision-issued-1')
|
||||
assert.equal(preview.decisionSource, 'rule')
|
||||
assert.equal(preview.fields.reason, '服务端规范事由')
|
||||
assert.equal(preview.fields.location, '上海')
|
||||
assert.equal(preview.decisionTrackingStatus, 'registered')
|
||||
}
|
||||
|
||||
async function testEditDraftActionCarriesClaimAndEditableFields() {
|
||||
@@ -167,6 +226,7 @@ async function testEditDraftActionCarriesClaimAndEditableFields() {
|
||||
async function run() {
|
||||
await testSubmitActionUsesFastPreviewEndpoint()
|
||||
await testSaveDraftActionUsesFastPreviewEndpoint()
|
||||
await testRegistrationUsesServerCanonicalPreview()
|
||||
await testEditDraftActionCarriesClaimAndEditableFields()
|
||||
console.log('ai-application-preview-actions tests passed')
|
||||
}
|
||||
|
||||
@@ -101,3 +101,45 @@ test('AI workbench conversation store preserves stewardRemainingTasks on message
|
||||
const persistedMessage = conversation.messages.find((m) => m.id === 'a1')
|
||||
assert.deepEqual(persistedMessage.stewardRemainingTasks, remainingTasks)
|
||||
})
|
||||
|
||||
test('AI workbench conversation store preserves preview decision and retry context', () => {
|
||||
installLocalStorageMock()
|
||||
const user = { username: 'preview-user' }
|
||||
const applicationPreview = {
|
||||
decisionId: 'decision-preview-1',
|
||||
decisionActionRequestId: 'action-preview-1',
|
||||
fields: { reason: '客户现场实施' },
|
||||
aiDecisionFeedback: {
|
||||
schemaVersion: 1,
|
||||
revision: 1,
|
||||
changedFields: [
|
||||
{ fieldKey: 'reason', suggestedValue: '客户拜访', finalValue: '客户现场实施' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
saveAiWorkbenchConversation(user, {
|
||||
id: 'conv-preview-decision',
|
||||
updatedAt: Date.now(),
|
||||
messages: [
|
||||
{ id: 'u1', role: 'user', content: '发起出差申请' },
|
||||
{
|
||||
id: 'a1',
|
||||
role: 'assistant',
|
||||
content: '申请核对表',
|
||||
text: '申请核对表',
|
||||
applicationPreview,
|
||||
draftPayload: { claim_id: 'claim-preview-1' },
|
||||
requestedSubmit: true,
|
||||
submitRequiresConfirmation: true
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const history = loadAiWorkbenchConversationHistory(user)
|
||||
const restored = history[0].messages.find((message) => message.id === 'a1')
|
||||
assert.deepEqual(restored.applicationPreview, applicationPreview)
|
||||
assert.equal(restored.draftPayload.claim_id, 'claim-preview-1')
|
||||
assert.equal(restored.requestedSubmit, true)
|
||||
assert.equal(restored.submitRequiresConfirmation, true)
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { trackApplicationPreviewFieldEdit } from '../src/utils/expenseApplicatio
|
||||
|
||||
function testTracksFirstExplicitEdit() {
|
||||
const previous = {
|
||||
decisionId: 'decision-feedback-1',
|
||||
fields: {
|
||||
reason: '客户拜访',
|
||||
location: '上海'
|
||||
@@ -27,12 +28,13 @@ function testTracksFirstExplicitEdit() {
|
||||
}
|
||||
])
|
||||
assert.equal(tracked.aiDecisionFeedback.revision, 1)
|
||||
assert.equal(tracked.decisionId, 'decision-feedback-1')
|
||||
}
|
||||
|
||||
function testKeepsOriginalSuggestionAcrossRepeatedEdits() {
|
||||
const first = trackApplicationPreviewFieldEdit(
|
||||
{ fields: { transportMode: '火车' } },
|
||||
{ fields: { transportMode: '飞机' } },
|
||||
{ decisionId: 'decision-feedback-2', fields: { transportMode: '火车' } },
|
||||
{ decisionId: 'decision-feedback-2', fields: { transportMode: '飞机' } },
|
||||
'transportMode'
|
||||
)
|
||||
const second = trackApplicationPreviewFieldEdit(
|
||||
@@ -48,6 +50,7 @@ function testKeepsOriginalSuggestionAcrossRepeatedEdits() {
|
||||
finalValue: '自驾'
|
||||
}
|
||||
])
|
||||
assert.equal(second.decisionId, 'decision-feedback-2')
|
||||
}
|
||||
|
||||
function testRemovesDiffWhenUserRestoresSuggestion() {
|
||||
|
||||
Reference in New Issue
Block a user