feat: 新增预算费控模型与报销审批流引擎

后端新增预算费控服务和报销单审批流模块,引入申请人费用画像
算法,优化知识库 RAG 运行时和同步逻辑,完善报销单工作流常
量和明细同步,更新差旅报销规则电子表格,前端新增预算分析
组件和数字员工模型,完善审批对话框和洞察面板交互,优化侧
边栏和顶栏样式,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-27 17:31:27 +08:00
parent cbb98f4469
commit d4d5d40569
75 changed files with 5393 additions and 686 deletions

View File

@@ -5,10 +5,12 @@ import { mapExpenseClaimToRequest } from '../src/composables/useRequests.js'
const CREATE_APPLICATION = '\u521b\u5efa\u7533\u8bf7'
const DIRECT_MANAGER_APPROVAL = '\u76f4\u5c5e\u9886\u5bfc\u5ba1\u6279'
const BUDGET_MANAGER_APPROVAL = '\u9884\u7b97\u7ba1\u7406\u8005\u5ba1\u6279'
const APPROVAL_COMPLETED = '\u5ba1\u6279\u5b8c\u6210'
const RETURNED = '\u9000\u56de'
const WAIT_SUBMIT = '\u5f85\u63d0\u4ea4'
const WAIT_LEADER_LI_APPROVAL = '\u7b49\u5f85 Leader Li \u6279\u590d'
const WAIT_BUDGET_ZHAO_APPROVAL = '\u7b49\u5f85 \u8d75\u9884\u7b97 \u6279\u590d'
const LEADER_RETURNED_STATUS = '\u9886\u5bfc\u5df2\u9000\u56de\uff0c\u5f85\u91cd\u65b0\u63d0\u4ea4'
test('application claims are mapped as application documents', () => {
@@ -41,7 +43,7 @@ test('application claims are mapped as application documents', () => {
assert.equal(request.expenseTableSummary, '预计金额已随申请提交')
assert.deepEqual(
request.progressSteps.map((step) => step.label),
[CREATE_APPLICATION, WAIT_LEADER_LI_APPROVAL, APPROVAL_COMPLETED]
[CREATE_APPLICATION, WAIT_LEADER_LI_APPROVAL, BUDGET_MANAGER_APPROVAL, APPROVAL_COMPLETED]
)
assert.equal(request.progressSteps.some((step) => step.label === 'AI预审'), false)
assert.equal(request.progressSteps.some((step) => step.label === '财务审批'), false)
@@ -50,6 +52,47 @@ test('application claims are mapped as application documents', () => {
assert.equal(request.progressSteps.find((step) => step.label === WAIT_LEADER_LI_APPROVAL)?.current, true)
})
test('application claims wait for department P8 budget monitor after leader approval', () => {
const request = mapExpenseClaimToRequest({
id: 'claim-application-budget',
claim_no: 'AP-20260525103145-BUDGET',
employee_name: '张三',
department_name: '交付部',
manager_name: 'Leader Li',
expense_type: 'travel_application',
reason: '支撑国网服务器上线部署',
location: '上海',
amount: 12000,
invoice_count: 0,
occurred_at: '2026-05-25T00:00:00.000Z',
submitted_at: '2026-05-25T02:00:00.000Z',
created_at: '2026-05-25T01:30:00.000Z',
updated_at: '2026-05-25T03:00:00.000Z',
status: 'submitted',
approval_stage: BUDGET_MANAGER_APPROVAL,
risk_flags_json: [
{
source: 'manual_approval',
event_type: 'expense_application_approval',
operator: 'Leader Li',
previous_approval_stage: DIRECT_MANAGER_APPROVAL,
next_approval_stage: BUDGET_MANAGER_APPROVAL,
next_approver_name: '赵预算',
next_approver_grade: 'P8',
created_at: '2026-05-25T03:00:00.000Z'
}
],
items: []
})
assert.deepEqual(
request.progressSteps.map((step) => step.label),
[CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, WAIT_BUDGET_ZHAO_APPROVAL, APPROVAL_COMPLETED]
)
assert.equal(request.progressSteps.find((step) => step.label === WAIT_BUDGET_ZHAO_APPROVAL)?.current, true)
assert.equal(request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)?.time, 'Leader Li通过')
})
test('returned application claims include leader return node and supplement status', () => {
const request = mapExpenseClaimToRequest({
id: 'claim-application-returned',
@@ -86,7 +129,7 @@ test('returned application claims include leader return node and supplement stat
assert.deepEqual(
request.progressSteps.map((step) => step.label),
[CREATE_APPLICATION, WAIT_LEADER_LI_APPROVAL, RETURNED, WAIT_SUBMIT]
[CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, RETURNED, WAIT_SUBMIT]
)
assert.equal(request.progressSteps.find((step) => step.label === RETURNED)?.time, 'Leader Li\u9000\u56de')
assert.match(request.progressSteps.find((step) => step.label === RETURNED)?.detail, /2026-05-25/)
@@ -96,7 +139,7 @@ test('returned application claims include leader return node and supplement stat
assert.equal(request.progressSteps.some((step) => step.label === APPROVAL_COMPLETED), false)
})
test('approved application claims complete after direct manager approval only', () => {
test('approved application claims complete after budget approval', () => {
const request = mapExpenseClaimToRequest({
id: 'claim-application-approved',
claim_no: 'AP-20260525113045-HGFEDCBA',
@@ -120,6 +163,16 @@ test('approved application claims complete after direct manager approval only',
event_type: 'expense_application_approval',
operator: '李经理',
previous_approval_stage: '直属领导审批',
next_approval_stage: '预算管理者审批',
next_approver_name: '赵预算',
next_approver_grade: 'P8',
created_at: '2026-05-25T03:00:00.000Z'
},
{
source: 'budget_approval',
event_type: 'expense_application_budget_approval',
operator: '赵预算',
previous_approval_stage: '预算管理者审批',
next_approval_stage: '审批完成',
created_at: '2026-05-25T03:00:00.000Z'
}
@@ -131,10 +184,11 @@ test('approved application claims complete after direct manager approval only',
assert.equal(request.workflowNode, '审批完成')
assert.deepEqual(
request.progressSteps.map((step) => step.label),
['创建申请', '直属领导审批', '审批完成']
['创建申请', '直属领导审批', '预算管理者审批', '审批完成']
)
assert.equal(request.progressSteps.every((step) => step.done), true)
assert.equal(request.progressSteps.find((step) => step.label === '直属领导审批')?.time, '李经理通过')
assert.equal(request.progressSteps.find((step) => step.label === '预算管理者审批')?.time, '赵预算通过')
})
test('progress steps show approval operator time and current stay duration', () => {