Refine travel reimbursement steward flow

Align planner, runtime rules, and policy assets so travel guidance
matches the updated reimbursement workflow.
This commit is contained in:
caoxiaozhu
2026-06-15 22:55:18 +08:00
parent 792741709a
commit 9f7b8b46a3
85 changed files with 9496 additions and 2555 deletions

View File

@@ -98,8 +98,15 @@ const FIELD_VALUE_DISPLAY_CONFIG = {
}
}
export function buildStewardPlanRequest({ rawText = '', files = [], currentUser = {} } = {}) {
export function buildStewardPlanRequest({
rawText = '',
files = [],
currentUser = {},
conversationId = '',
stewardState = null
} = {}) {
const safeFiles = Array.isArray(files) ? files : []
const normalizedConversationId = String(conversationId || '').trim()
return {
message: String(rawText || '').trim(),
user_id: String(currentUser.username || currentUser.name || 'anonymous').trim() || 'anonymous',
@@ -111,6 +118,8 @@ export function buildStewardPlanRequest({ rawText = '', files = [], currentUser
context_json: {
entry_source: 'workbench',
session_type: 'steward',
conversation_id: normalizedConversationId,
steward_state: stewardState && typeof stewardState === 'object' ? stewardState : null,
role_codes: Array.isArray(currentUser.roleCodes) ? currentUser.roleCodes : [],
username: currentUser.username || '',
name: currentUser.name || currentUser.username || '',
@@ -124,9 +133,13 @@ export function normalizeStewardPlan(rawPlan = {}, options = {}) {
const visibleThinkingEventCount = Number.isFinite(options.visibleThinkingEventCount)
? Number(options.visibleThinkingEventCount)
: Number(rawPlan.visibleThinkingEventCount || rawPlan.visible_thinking_event_count || 0)
const pendingFlowConfirmation = normalizePendingFlowConfirmation(rawPlan)
return {
planId: String(rawPlan.plan_id || rawPlan.planId || ''),
planStatus: String(rawPlan.plan_status || rawPlan.planStatus || ''),
nextAction: String(rawPlan.next_action || rawPlan.nextAction || ''),
conversationId: String(rawPlan.conversation_id || rawPlan.conversationId || ''),
stewardState: rawPlan.steward_state || rawPlan.stewardState || null,
summary: String(rawPlan.summary || ''),
visibleThinkingEventCount,
initialSummaryOnly: Boolean(rawPlan.initial_summary_only || rawPlan.initialSummaryOnly || options.initialSummaryOnly),
@@ -185,12 +198,17 @@ export function normalizeStewardPlan(rawPlan = {}, options = {}) {
: [],
confirmationGroups: Array.isArray(rawPlan.confirmation_groups)
? rawPlan.confirmation_groups
: []
: [],
pendingFlowConfirmation,
candidateFlows: pendingFlowConfirmation.candidateFlows
}
}
export function buildStewardPlanMessageText(plan) {
const normalized = normalizeStewardPlan(plan)
if (isPendingFlowConfirmationPlan(normalized)) {
return buildPendingFlowConfirmationMessageText(normalized)
}
const nextContext = resolveNextActionContext(normalized)
const orderedTasks = buildOrderedStewardTasks(normalized, nextContext?.task)
const taskLines = orderedTasks.map((task, index) =>
@@ -266,6 +284,28 @@ export function formatStewardOntologyFields(fields = {}, taskType = '') {
export function buildStewardSuggestedActions(plan) {
const normalized = normalizeStewardPlan(plan)
if (isPendingFlowConfirmationPlan(normalized)) {
return normalized.candidateFlows.map((flow) => ({
label: flow.label,
description: flow.reason || '选择后小财管家会继续整理对应流程材料。',
icon: flow.flowId === 'travel_application'
? 'mdi mdi-file-plus-outline'
: 'mdi mdi-receipt-text-plus-outline',
action_type: ASSISTANT_SCOPE_ACTION_SWITCH,
payload: {
steward_confirm_flow: true,
steward_plan_id: normalized.planId,
flow_id: flow.flowId,
session_type: flow.flowId === 'travel_application'
? SESSION_TYPE_APPLICATION
: SESSION_TYPE_EXPENSE,
selected_flow_label: flow.label,
carry_text: flow.label,
auto_submit: true,
steward_state: normalized.stewardState || null
}
}))
}
const nextContext = resolveNextActionContext(normalized)
if (!nextContext) {
return []
@@ -300,6 +340,70 @@ export function buildStewardSuggestedActions(plan) {
]
}
function normalizePendingFlowConfirmation(rawPlan = {}) {
const rawPending = rawPlan.pending_flow_confirmation || rawPlan.pendingFlowConfirmation || {}
const rawCandidates = Array.isArray(rawPlan.candidate_flows || rawPlan.candidateFlows)
? rawPlan.candidate_flows || rawPlan.candidateFlows
: rawPending?.candidate_flows || rawPending?.candidateFlows || []
const candidateFlows = Array.isArray(rawCandidates)
? rawCandidates
.map((item) => normalizeCandidateFlow(item))
.filter((item) => item.flowId)
: []
return {
status: String(rawPending?.status || '').trim(),
sourceMessage: String(rawPending?.source_message || rawPending?.sourceMessage || '').trim(),
reason: String(rawPending?.reason || '').trim(),
candidateFlows
}
}
function normalizeCandidateFlow(item = {}) {
const flowId = String(item.flow_id || item.flowId || '').trim()
if (!['travel_application', 'travel_reimbursement'].includes(flowId)) {
return { flowId: '' }
}
return {
flowId,
label: String(item.label || (flowId === 'travel_application' ? '补办出差申请' : '发起费用报销')).trim(),
confidence: Number(item.confidence || 0),
reason: String(item.reason || '').trim(),
ontologyFields: item.ontology_fields || item.ontologyFields || {},
missingFields: Array.isArray(item.missing_fields || item.missingFields)
? item.missing_fields || item.missingFields
: []
}
}
function isPendingFlowConfirmationPlan(normalized) {
return (
String(normalized?.nextAction || '').trim() === 'confirm_flow' ||
String(normalized?.planStatus || '').trim() === 'needs_flow_confirmation' ||
String(normalized?.pendingFlowConfirmation?.status || '').trim() === 'pending'
) && Array.isArray(normalized?.candidateFlows) && normalized.candidateFlows.length > 0
}
function buildPendingFlowConfirmationMessageText(normalized) {
const fields = normalized.candidateFlows[0]?.ontologyFields || {}
const knownParts = formatStewardOntologyFields(fields, 'expense_application')
const candidateLines = normalized.candidateFlows.map((flow, index) =>
`${index + 1}. **${flow.label}**${flow.reason ? `\n - ${flow.reason}` : ''}`
)
return [
'### 需要先确认流程方向',
'',
knownParts
? `我识别到这是一项财务事项,已提取到:**${knownParts}**。`
: '我识别到这是一项财务事项,但还需要确认你要进入哪个流程。',
'',
normalized.pendingFlowConfirmation.reason || normalized.summary || '当前还不能确定你要补办申请还是发起报销。',
'',
...candidateLines,
'',
'请先选择一个方向,我会继续整理对应材料。'
].filter((line, index, lines) => line || lines[index - 1]).join('\n')
}
function resolveNextActionContext(normalized) {
const applicationTask = normalized.tasks.find((task) => task.taskType === 'expense_application')
const applicationAction = applicationTask
@@ -508,6 +612,13 @@ function buildStewardCarryText(actionType, task, group, normalized = null) {
group?.attachmentNames?.length ? `相关附件:${group.attachmentNames.join('、')}` : '',
group?.excludedAttachmentNames?.length ? `暂不归集附件:${group.excludedAttachmentNames.join('、')}` : '',
missingFields ? `还需要补充:${missingFields}` : '',
actionType === 'confirm_create_application'
? missingFields
? '请先追问上述缺失信息,不要直接生成申请单核对表,也不要替用户默认填写。'
: '请直接生成申请单核对结果;信息足够时生成申请单,但在入库或提交审批前仍需让我确认。'
: missingFields
? '请先追问上述缺失信息,不要直接生成报销核对结果,也不要替用户默认填写。'
: '请直接生成报销核对结果;需要创建草稿、绑定附件或提交审批前仍需让我确认。'
]
const remainingTaskText = normalized ? buildRemainingTaskText(normalized, task.taskId) : ''
if (remainingTaskText) {