2026-06-22 11:58:53 +08:00
|
|
|
|
import {
|
|
|
|
|
|
buildApplicationTemplatePreview,
|
|
|
|
|
|
buildLocalApplicationPreview,
|
|
|
|
|
|
normalizeApplicationPreview
|
|
|
|
|
|
} from '../../utils/expenseApplicationPreview.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
buildAiApplicationPrecheckThinkingEvents,
|
|
|
|
|
|
isAiApplicationPrecheckBlocking
|
|
|
|
|
|
} from '../../utils/aiApplicationPrecheckModel.js'
|
|
|
|
|
|
import { extractExpenseClaimItems } from '../../services/reimbursements.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
AI_APPLICATION_ACTION_SAVE_DRAFT,
|
|
|
|
|
|
AI_APPLICATION_ACTION_SUBMIT
|
|
|
|
|
|
} from '../../services/aiApplicationPreviewActions.js'
|
2026-06-23 11:21:18 +08:00
|
|
|
|
import { INLINE_APPLICATION_STATUS_LABELS } from '../../constants/documentProtocol.js'
|
|
|
|
|
|
import { resolveInlineApplicationPreviewTextAction } from './workbenchAiApplicationGateModel.js'
|
2026-06-22 11:58:53 +08:00
|
|
|
|
|
|
|
|
|
|
function normalizeInlineApplicationResultTableCell(value, fallback = '-') {
|
|
|
|
|
|
const text = String(value || '')
|
|
|
|
|
|
.replace(/\s*\n+\s*/g, ' ')
|
|
|
|
|
|
.replace(/\|/g, '|')
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
return text || fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeInlineApplicationStatusLabel(value, fallback = '') {
|
|
|
|
|
|
const text = String(value || '').trim()
|
|
|
|
|
|
if (!text) {
|
|
|
|
|
|
return fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
return INLINE_APPLICATION_STATUS_LABELS[text.toLowerCase()] || text
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function resolveInlineApplicationActionDocumentInfo(draftPayload = {}) {
|
|
|
|
|
|
const source = draftPayload && typeof draftPayload === 'object' ? draftPayload : {}
|
|
|
|
|
|
const body = String(source.body || source.markdown || '').trim()
|
|
|
|
|
|
const resolveBodyField = (labels = []) => {
|
|
|
|
|
|
for (const label of labels) {
|
|
|
|
|
|
const pattern = new RegExp(`${label}\\s*[::]\\s*([^\\n|]+)`, 'u')
|
|
|
|
|
|
const match = body.match(pattern)
|
|
|
|
|
|
if (match?.[1]) {
|
|
|
|
|
|
return String(match[1]).replace(/\*\*/g, '').trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
const startDate = String(source.start_date || source.startDate || source.trip_start_date || source.tripStartDate || '').trim()
|
|
|
|
|
|
const endDate = String(source.end_date || source.endDate || source.trip_end_date || source.tripEndDate || '').trim()
|
|
|
|
|
|
const dateText = String(
|
|
|
|
|
|
source.business_time ||
|
|
|
|
|
|
source.businessTime ||
|
|
|
|
|
|
source.time ||
|
|
|
|
|
|
source.occurred_at ||
|
|
|
|
|
|
source.occurredAt ||
|
|
|
|
|
|
source.apply_time ||
|
|
|
|
|
|
source.applyTime ||
|
|
|
|
|
|
''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
const rangeText = startDate && endDate && startDate !== endDate
|
|
|
|
|
|
? `${startDate} 至 ${endDate}`
|
|
|
|
|
|
: startDate || endDate
|
|
|
|
|
|
return {
|
|
|
|
|
|
claimNo: String(source.claim_no || source.claimNo || source.document_no || source.documentNo || '').trim(),
|
|
|
|
|
|
claimId: String(source.claim_id || source.claimId || source.id || '').trim(),
|
|
|
|
|
|
statusLabel: normalizeInlineApplicationStatusLabel(source.status_label || source.statusLabel || source.status),
|
|
|
|
|
|
approvalStage: String(source.approval_stage || source.approvalStage || '').trim(),
|
|
|
|
|
|
dateLabel: rangeText || dateText || resolveBodyField(['时间', '日期', '申请时间']) || '待补充',
|
|
|
|
|
|
locationLabel: String(
|
|
|
|
|
|
source.location ||
|
|
|
|
|
|
source.application_location ||
|
|
|
|
|
|
source.applicationLocation ||
|
|
|
|
|
|
source.destination ||
|
|
|
|
|
|
source.destination_city ||
|
|
|
|
|
|
source.destinationCity ||
|
|
|
|
|
|
''
|
|
|
|
|
|
).trim() || resolveBodyField(['地点', '目的地']) || '待补充',
|
|
|
|
|
|
reasonLabel: String(
|
|
|
|
|
|
source.reason ||
|
|
|
|
|
|
source.business_reason ||
|
|
|
|
|
|
source.businessReason ||
|
|
|
|
|
|
source.description ||
|
|
|
|
|
|
source.title ||
|
|
|
|
|
|
''
|
|
|
|
|
|
).trim() || resolveBodyField(['事由', '事件', '申请事由']) || '待补充',
|
|
|
|
|
|
amountLabel: String(
|
|
|
|
|
|
source.amount ||
|
|
|
|
|
|
source.application_amount ||
|
|
|
|
|
|
source.applicationAmount ||
|
|
|
|
|
|
source.estimated_amount ||
|
|
|
|
|
|
source.estimatedAmount ||
|
|
|
|
|
|
''
|
|
|
|
|
|
).trim() || resolveBodyField(['金额', '预计金额', '申请金额']) || '-',
|
|
|
|
|
|
documentTypeLabel: String(
|
|
|
|
|
|
source.document_type_label ||
|
|
|
|
|
|
source.documentTypeLabel ||
|
|
|
|
|
|
source.application_type_label ||
|
|
|
|
|
|
source.applicationTypeLabel ||
|
|
|
|
|
|
source.expense_type_label ||
|
|
|
|
|
|
source.expenseTypeLabel ||
|
|
|
|
|
|
''
|
|
|
|
|
|
).trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInlineApplicationResultTable(draftPayload = {}, options = {}) {
|
|
|
|
|
|
const info = resolveInlineApplicationActionDocumentInfo(draftPayload)
|
|
|
|
|
|
const reference = info.claimNo || info.claimId
|
|
|
|
|
|
const statusLabel = normalizeInlineApplicationStatusLabel(info.statusLabel, options.statusLabel)
|
|
|
|
|
|
return [
|
2026-06-24 10:42:50 +08:00
|
|
|
|
'| 单据类型 | 单据编号 | 单据状态 | 当前节点 | 日期 | 地点 | 事由 | 金额 |',
|
|
|
|
|
|
'| --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
|
|
|
|
`| ${normalizeInlineApplicationResultTableCell(info.documentTypeLabel || options.documentTypeLabel, '出差申请')} | ${normalizeInlineApplicationResultTableCell(reference)} | ${normalizeInlineApplicationResultTableCell(statusLabel)} | ${normalizeInlineApplicationResultTableCell(info.approvalStage || options.stageLabel)} | ${normalizeInlineApplicationResultTableCell(info.dateLabel)} | ${normalizeInlineApplicationResultTableCell(info.locationLabel)} | ${normalizeInlineApplicationResultTableCell(info.reasonLabel)} | ${normalizeInlineApplicationResultTableCell(info.amountLabel, '-')} |`
|
2026-06-22 11:58:53 +08:00
|
|
|
|
].join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function extractInlineApplicationDraftPayload(payload = {}) {
|
|
|
|
|
|
const result = payload?.result && typeof payload.result === 'object' ? payload.result : {}
|
|
|
|
|
|
return result.draft_payload && typeof result.draft_payload === 'object'
|
|
|
|
|
|
? result.draft_payload
|
|
|
|
|
|
: payload?.draft_payload && typeof payload.draft_payload === 'object'
|
|
|
|
|
|
? payload.draft_payload
|
|
|
|
|
|
: null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInlineApplicationPreviewActionResultText(actionType, payload = {}) {
|
|
|
|
|
|
const draftPayload = extractInlineApplicationDraftPayload(payload) || {}
|
|
|
|
|
|
const claimNo = String(draftPayload.claim_no || draftPayload.claimNo || '').trim()
|
|
|
|
|
|
const approvalStage = String(draftPayload.approval_stage || draftPayload.approvalStage || '').trim()
|
|
|
|
|
|
if (actionType === AI_APPLICATION_ACTION_SUBMIT) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'### 申请单据已生成,并已进入审批流程',
|
|
|
|
|
|
approvalStage ? `系统已推送到 **${approvalStage}**,当前节点:${approvalStage}。` : '系统已推送到审批流程,当前节点:审批中。',
|
|
|
|
|
|
buildInlineApplicationResultTable(draftPayload, {
|
|
|
|
|
|
statusLabel: '审批中',
|
|
|
|
|
|
stageLabel: approvalStage || '直属领导审批',
|
|
|
|
|
|
documentTypeLabel: '出差申请'
|
2026-06-24 10:42:50 +08:00
|
|
|
|
})
|
2026-06-22 11:58:53 +08:00
|
|
|
|
].filter(Boolean).join('\n\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
|
|
|
'### 申请草稿已保存',
|
|
|
|
|
|
claimNo ? `系统已保存当前申请草稿,草稿单号:**${claimNo}**。` : '系统已保存当前申请草稿。',
|
|
|
|
|
|
buildInlineApplicationResultTable(draftPayload, {
|
|
|
|
|
|
statusLabel: '草稿',
|
|
|
|
|
|
stageLabel: '待提交',
|
|
|
|
|
|
documentTypeLabel: '出差申请'
|
2026-06-24 10:42:50 +08:00
|
|
|
|
})
|
2026-06-22 11:58:53 +08:00
|
|
|
|
].filter(Boolean).join('\n\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInlineApplicationDetailAction(draftPayload = {}) {
|
|
|
|
|
|
const claimNo = String(draftPayload?.claim_no || draftPayload?.claimNo || '').trim()
|
|
|
|
|
|
if (!claimNo) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
return [{
|
|
|
|
|
|
label: '查看单据详情',
|
|
|
|
|
|
description: '打开刚生成的申请单详情。',
|
|
|
|
|
|
icon: 'mdi mdi-open-in-new',
|
|
|
|
|
|
action_type: 'open_application_detail',
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
claim_no: claimNo,
|
|
|
|
|
|
claim_id: String(draftPayload.claim_id || draftPayload.claimId || '').trim(),
|
|
|
|
|
|
document_type: 'application'
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function resolveInlineApplicationPreviewActionFromText(text = '') {
|
2026-06-23 11:21:18 +08:00
|
|
|
|
return resolveInlineApplicationPreviewTextAction(text)
|
2026-06-22 11:58:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeInlineApplicationTypeLabel(expenseTypeLabel, fallback = '费用申请') {
|
|
|
|
|
|
const label = String(expenseTypeLabel || '').trim()
|
|
|
|
|
|
if (!label) {
|
|
|
|
|
|
return fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
if (label.endsWith('费用申请') || label.endsWith('申请')) {
|
|
|
|
|
|
return label
|
|
|
|
|
|
}
|
|
|
|
|
|
if (label.endsWith('费用')) {
|
|
|
|
|
|
return `${label}申请`
|
|
|
|
|
|
}
|
|
|
|
|
|
if (label.endsWith('费')) {
|
|
|
|
|
|
return `${label.slice(0, -1)}费用申请`
|
|
|
|
|
|
}
|
|
|
|
|
|
return `${label}申请`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInlineApplicationPreview(expenseTypeLabel, sourceText = '', currentUser = {}) {
|
|
|
|
|
|
const rawText = String(sourceText || '').trim()
|
|
|
|
|
|
const preview = rawText
|
|
|
|
|
|
? buildLocalApplicationPreview(rawText, currentUser)
|
|
|
|
|
|
: buildApplicationTemplatePreview(currentUser)
|
|
|
|
|
|
const normalized = normalizeApplicationPreview(preview)
|
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
|
...normalized,
|
|
|
|
|
|
fields: {
|
|
|
|
|
|
...(normalized.fields || {}),
|
|
|
|
|
|
applicationType: normalizeInlineApplicationTypeLabel(
|
|
|
|
|
|
expenseTypeLabel,
|
|
|
|
|
|
normalized.fields?.applicationType || '费用申请'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function resolveInlineApplicationDraftIdentity(payload = {}) {
|
|
|
|
|
|
const source = payload && typeof payload === 'object' ? payload : {}
|
|
|
|
|
|
return {
|
|
|
|
|
|
claimId: String(source.claim_id || source.claimId || source.id || '').trim(),
|
|
|
|
|
|
claimNo: String(source.claim_no || source.claimNo || source.document_no || source.documentNo || '').trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function isSameInlineApplicationDraftClaim(claim = {}, draftPayload = {}) {
|
|
|
|
|
|
const draftIdentity = resolveInlineApplicationDraftIdentity(draftPayload)
|
|
|
|
|
|
if (!draftIdentity.claimId && !draftIdentity.claimNo) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
const claimIdentity = resolveInlineApplicationDraftIdentity(claim)
|
|
|
|
|
|
return Boolean(
|
|
|
|
|
|
(draftIdentity.claimId && claimIdentity.claimId && draftIdentity.claimId === claimIdentity.claimId) ||
|
|
|
|
|
|
(draftIdentity.claimNo && claimIdentity.claimNo && draftIdentity.claimNo === claimIdentity.claimNo)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInlineApplicationSubmitPrecheckPayload(claimsPayload, draftPayload = {}) {
|
|
|
|
|
|
const items = extractExpenseClaimItems(claimsPayload)
|
|
|
|
|
|
.filter((claim) => !isSameInlineApplicationDraftClaim(claim, draftPayload))
|
|
|
|
|
|
return { items }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function completeInlineThinkingEvents(events = []) {
|
|
|
|
|
|
return events.map((event) => ({
|
|
|
|
|
|
...event,
|
|
|
|
|
|
status: event.status === 'failed' ? 'failed' : 'completed'
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInitialInlineApplicationSubmitThinkingEvents() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-precheck-overlap',
|
|
|
|
|
|
title: '核查同时间段申请单',
|
2026-06-24 10:42:50 +08:00
|
|
|
|
content: '正在查询您名下可见申请单,检查是否存在相同或重叠日期。',
|
2026-06-22 11:58:53 +08:00
|
|
|
|
status: 'running'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-precheck-budget',
|
|
|
|
|
|
title: '评估预算与审批影响',
|
|
|
|
|
|
content: '等待单据重叠核查完成后,继续评估预算占用和审批影响。',
|
|
|
|
|
|
status: 'pending'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-submit',
|
|
|
|
|
|
title: '提交申请单据',
|
|
|
|
|
|
content: '等待提交前核查完成。',
|
|
|
|
|
|
status: 'pending'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildInlineApplicationSubmitThinkingEvents(precheck = {}) {
|
|
|
|
|
|
const blocked = isAiApplicationPrecheckBlocking(precheck)
|
|
|
|
|
|
return buildAiApplicationPrecheckThinkingEvents(precheck).map((event) => {
|
|
|
|
|
|
if (event.eventId !== 'application-precheck-form') {
|
|
|
|
|
|
return event
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
eventId: 'application-submit',
|
|
|
|
|
|
title: blocked ? '暂停提交申请' : '提交申请单据',
|
|
|
|
|
|
content: blocked
|
|
|
|
|
|
? '发现相同或重叠日期已有申请单,已暂停本次提交。'
|
|
|
|
|
|
: '提交前核查通过,正在生成申请单据并推送审批流程。',
|
|
|
|
|
|
status: blocked ? 'completed' : 'running'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildFailedInlineApplicationSubmitThinkingEvents(error) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-precheck-overlap',
|
|
|
|
|
|
title: '核查同时间段申请单',
|
|
|
|
|
|
content: `查询已有申请单失败:${String(error?.message || error || '未知错误')}`,
|
|
|
|
|
|
status: 'failed'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
eventId: 'application-submit',
|
|
|
|
|
|
title: '暂停提交申请',
|
|
|
|
|
|
content: '因为未能完成提交前重复日期核查,系统没有提交本次申请。',
|
|
|
|
|
|
status: 'failed'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|