feat(web): 统一平台管理员判定与 AI 工作台申请预览动作接入
- authUser 抽出 resolveAuthUserAdminFlag,统一 isAdmin 解析(含 superadmin、role_codes、中英文角色名),accessControl 复用同一逻辑 - 登录态、应用外壳路由、系统状态接入统一管理员判定,LoginView 与相关 composable 配套调整 - AI 工作台申请提交改为调用新的 /application-preview-action 接口,草稿保存仍走 orchestrator;预审模型补充重叠冲突提示与阻断判断 - 同步更新 accessControl/api-request/ai 预览动作等前端测试
This commit is contained in:
@@ -1,127 +1,90 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import {
|
||||
AI_APPLICATION_ACTION_SAVE_DRAFT,
|
||||
AI_APPLICATION_ACTION_SUBMIT,
|
||||
buildAiApplicationPreviewActionPayload
|
||||
runAiApplicationPreviewAction
|
||||
} from '../src/services/aiApplicationPreviewActions.js'
|
||||
import {
|
||||
applyApplicationPolicyEstimateResult,
|
||||
buildApplicationPolicyEstimateRequest,
|
||||
buildLocalApplicationPreview
|
||||
} from '../src/utils/expenseApplicationPreview.js'
|
||||
|
||||
const applicationPreview = {
|
||||
fields: {
|
||||
applicationType: '差旅费用申请',
|
||||
applicant: '曹笑竹',
|
||||
grade: 'P5',
|
||||
department: '技术部',
|
||||
position: '财务智能化产品经理',
|
||||
managerName: '向万红',
|
||||
time: '2026-02-20 至 2026-02-23',
|
||||
location: '上海',
|
||||
reason: '辅助国网仿生产服务器部署',
|
||||
days: '4天',
|
||||
transportMode: '火车',
|
||||
lodgingDailyCap: '250元/天',
|
||||
subsidyDailyCap: '100元/天',
|
||||
transportPolicy: '按交通费用预估表暂估',
|
||||
policyEstimate: '交通 720元 + 住宿 1,000元 + 补贴 400元 = 2,120元(4天)',
|
||||
amount: '2,120元'
|
||||
}
|
||||
}
|
||||
async function testSubmitActionUsesFastPreviewEndpoint() {
|
||||
let capturedUrl = ''
|
||||
let capturedOptions = null
|
||||
|
||||
const currentUser = {
|
||||
username: 'caoxiaozhu@xf.com',
|
||||
name: '曹笑竹',
|
||||
departmentName: '技术部',
|
||||
position: '财务智能化产品经理',
|
||||
grade: 'P5',
|
||||
managerName: '向万红',
|
||||
roleCodes: ['employee']
|
||||
}
|
||||
|
||||
test('save application preview payload uses save draft action without submit wording', () => {
|
||||
const payload = buildAiApplicationPreviewActionPayload({
|
||||
actionType: AI_APPLICATION_ACTION_SAVE_DRAFT,
|
||||
applicationPreview,
|
||||
currentUser,
|
||||
conversationId: 'inline-1'
|
||||
})
|
||||
|
||||
assert.equal(payload.user_id, 'caoxiaozhu@xf.com')
|
||||
assert.equal(payload.conversation_id, 'inline-1')
|
||||
assert.equal(payload.context_json.session_type, 'application')
|
||||
assert.equal(payload.context_json.review_action, undefined)
|
||||
assert.equal(payload.context_json.application_action, 'save_draft')
|
||||
assert.equal(payload.context_json.application_preview.fields.transportMode, '火车')
|
||||
assert.match(payload.message, /费用申请保存草稿/)
|
||||
assert.match(payload.message, /保存草稿/)
|
||||
assert.doesNotMatch(payload.message, /确认提交/)
|
||||
})
|
||||
|
||||
test('submit application preview payload keeps existing draft id for resubmission', () => {
|
||||
const payload = buildAiApplicationPreviewActionPayload({
|
||||
actionType: AI_APPLICATION_ACTION_SUBMIT,
|
||||
applicationPreview,
|
||||
currentUser,
|
||||
conversationId: 'inline-1',
|
||||
draftPayload: {
|
||||
claim_id: 'draft-001',
|
||||
claim_no: 'AP-202602200001'
|
||||
global.fetch = async (url, options) => {
|
||||
capturedUrl = String(url)
|
||||
capturedOptions = options
|
||||
return {
|
||||
ok: true,
|
||||
async json() {
|
||||
return {
|
||||
status: 'succeeded',
|
||||
result: {
|
||||
draft_payload: {
|
||||
claim_id: 'claim-fast-submit',
|
||||
claim_no: 'AP-20260620-FAST',
|
||||
status: 'submitted',
|
||||
approval_stage: '直属领导审批'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await runAiApplicationPreviewAction({
|
||||
actionType: AI_APPLICATION_ACTION_SUBMIT,
|
||||
applicationPreview: {
|
||||
fields: {
|
||||
applicationType: '差旅费用申请',
|
||||
time: '2026-07-01 至 2026-07-03',
|
||||
location: '北京',
|
||||
reason: '项目实施',
|
||||
days: '3天',
|
||||
transportMode: '火车',
|
||||
amount: '1000元'
|
||||
}
|
||||
},
|
||||
currentUser: { username: 'zhangsan@example.com', name: '张三' },
|
||||
conversationId: 'conversation-fast-submit'
|
||||
})
|
||||
|
||||
assert.equal(payload.context_json.review_action, undefined)
|
||||
assert.equal(payload.context_json.application_edit_claim_id, 'draft-001')
|
||||
assert.equal(payload.context_json.draft_claim_id, 'draft-001')
|
||||
assert.match(payload.message, /费用申请确认提交/)
|
||||
assert.match(payload.message, /确认提交/)
|
||||
})
|
||||
assert.equal(capturedUrl, '/api/v1/reimbursements/application-preview-action')
|
||||
assert.equal(capturedOptions.method, 'POST')
|
||||
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.context_json.application_preview.fields.transportMode, '火车')
|
||||
}
|
||||
|
||||
test('travel application preview calculates base standards before transport mode is selected', () => {
|
||||
const preview = buildLocalApplicationPreview(
|
||||
'2月20-23日去上海出差,辅助国网仿生产服务器部署',
|
||||
{ name: '曹笑竹', grade: 'P5', location: '武汉' },
|
||||
{ today: '2026-06-20' }
|
||||
)
|
||||
const request = buildApplicationPolicyEstimateRequest(preview, { grade: 'P5', location: '武汉' })
|
||||
async function testSaveDraftActionKeepsOrchestratorPath() {
|
||||
let capturedUrl = ''
|
||||
|
||||
assert.equal(request.canCalculate, true)
|
||||
assert.deepEqual(request.payload, {
|
||||
days: 4,
|
||||
location: '上海',
|
||||
grade: 'P5',
|
||||
transport_mode: null,
|
||||
origin_location: '武汉',
|
||||
travel_date: '2026-02-20'
|
||||
global.fetch = async (url) => {
|
||||
capturedUrl = String(url)
|
||||
return {
|
||||
ok: true,
|
||||
async json() {
|
||||
return { status: 'succeeded', result: {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await runAiApplicationPreviewAction({
|
||||
actionType: AI_APPLICATION_ACTION_SAVE_DRAFT,
|
||||
applicationPreview: { fields: { reason: '项目实施' } },
|
||||
currentUser: { username: 'zhangsan@example.com', name: '张三' }
|
||||
})
|
||||
|
||||
const estimatedPreview = applyApplicationPolicyEstimateResult(preview, {
|
||||
days: 4,
|
||||
location: '上海',
|
||||
matched_city: '上海',
|
||||
grade: 'P5',
|
||||
hotel_rate: 450,
|
||||
hotel_amount: 1800,
|
||||
total_allowance_rate: 100,
|
||||
allowance_amount: 400,
|
||||
transport_mode: '火车',
|
||||
transport_origin: '武汉',
|
||||
transport_destination: '上海',
|
||||
transport_estimated_amount: 720,
|
||||
total_amount: 2200,
|
||||
rule_name: '公司差旅费报销规则',
|
||||
rule_version: 'v1.0.0'
|
||||
}, { grade: 'P5', location: '武汉' })
|
||||
assert.equal(capturedUrl, '/api/v1/orchestrator/run')
|
||||
}
|
||||
|
||||
assert.equal(estimatedPreview.fields.transportMode, '')
|
||||
assert.equal(estimatedPreview.missingFields.includes('出行方式'), true)
|
||||
assert.equal(estimatedPreview.fields.lodgingDailyCap, '450元/天')
|
||||
assert.equal(estimatedPreview.fields.subsidyDailyCap, '100元/天')
|
||||
assert.equal(estimatedPreview.fields.transportPolicy, '选择火车、飞机或轮船后自动预估交通费用')
|
||||
assert.equal(estimatedPreview.fields.policyEstimate, '交通待补充 + 住宿 1,800元 + 补贴 400元 = 2,200元(4天,不含交通)')
|
||||
assert.equal(estimatedPreview.fields.amount, '2,200元(不含交通)')
|
||||
async function run() {
|
||||
await testSubmitActionUsesFastPreviewEndpoint()
|
||||
await testSaveDraftActionKeepsOrchestratorPath()
|
||||
console.log('ai-application-preview-actions tests passed')
|
||||
}
|
||||
|
||||
run().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user