feat: 新增员工行为画像算法与费用风险标签体系
后端新增员工行为画像算法模块,支持标签规则引擎和评分计算, 完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流 和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费 用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台 样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
@@ -33,6 +33,18 @@ test('document center archived rows are detected from archive flag or request st
|
||||
}),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedDocumentRow({
|
||||
rawRequest: { status: 'pending_payment', approval_stage: '待付款' }
|
||||
}),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedDocumentRow({
|
||||
rawRequest: { status: 'paid', approval_stage: '已付款' }
|
||||
}),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedDocumentRow({
|
||||
rawRequest: { status: 'approved', approval_stage: '部门审批', approvalKey: 'completed' }
|
||||
@@ -45,10 +57,11 @@ test('document center all scope excludes archived rows from merged lists', () =>
|
||||
const rows = excludeArchivedDocumentRows([
|
||||
{ claimId: 'a', archived: true },
|
||||
{ claimId: 'b', rawRequest: { status: 'approved', approval_stage: '归档入账' } },
|
||||
{ claimId: 'c', rawRequest: { status: 'submitted', approval_stage: '部门审批' } }
|
||||
{ claimId: 'c', rawRequest: { status: 'submitted', approval_stage: '部门审批' } },
|
||||
{ claimId: 'd', rawRequest: { status: 'pending_payment', approval_stage: '待付款' } }
|
||||
])
|
||||
|
||||
assert.deepEqual(rows.map((row) => row.claimId), ['c'])
|
||||
assert.deepEqual(rows.map((row) => row.claimId), ['c', 'd'])
|
||||
})
|
||||
|
||||
test('application scope does not mark submitted approval application rows as new', () => {
|
||||
|
||||
@@ -201,7 +201,7 @@ test('documents center switches filter conditions by category tab', () => {
|
||||
)
|
||||
assert.match(
|
||||
documentsCenterView,
|
||||
/\[DOCUMENT_SCOPE_ARCHIVE\]: \{[\s\S]*dateLabel: '归档时间'[\s\S]*statusTitle: '归档状态'[\s\S]*statusTabs: \['全部', '已完成'\]/
|
||||
/\[DOCUMENT_SCOPE_ARCHIVE\]: \{[\s\S]*dateLabel: '归档时间'[\s\S]*statusTitle: '归档状态'[\s\S]*statusTabs: \['全部', '已付款', '已完成'\]/
|
||||
)
|
||||
assert.match(documentsCenterView, /v-if="showDocumentTypeFilter" class="document-filter"/)
|
||||
assert.match(documentsCenterView, /:placeholder="activeFilterConfig\.searchPlaceholder"/)
|
||||
|
||||
@@ -19,6 +19,10 @@ test('isArchivedExpenseClaim recognizes finance archive stage', () => {
|
||||
}),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedExpenseClaim({ status: 'paid', approval_stage: '已付款' }),
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('isArchivedExpenseClaim ignores in-progress claims', () => {
|
||||
@@ -26,6 +30,10 @@ test('isArchivedExpenseClaim ignores in-progress claims', () => {
|
||||
isArchivedExpenseClaim({ status: 'submitted', approval_stage: '财务审批' }),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedExpenseClaim({ status: 'pending_payment', approval_stage: '待付款' }),
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('archive data stays available through api client but archive center is removed from navigation', () => {
|
||||
|
||||
@@ -399,7 +399,7 @@ test('ticket description helper does not show the destination city as detail tex
|
||||
assert.equal(request.expenseItems.find((item) => item.id === 'ship')?.name, '轮船票')
|
||||
})
|
||||
|
||||
test('completed finance approval marks finance and archive progress steps', () => {
|
||||
test('finance approval moves reimbursement to pending payment step', () => {
|
||||
const request = mapExpenseClaimToRequest({
|
||||
id: 'claim-finance-completed',
|
||||
claim_no: 'EXP-202605-004',
|
||||
@@ -414,8 +414,8 @@ test('completed finance approval marks finance and archive progress steps', () =
|
||||
submitted_at: '2026-05-20T02:00:00.000Z',
|
||||
created_at: '2026-05-20T01:30:00.000Z',
|
||||
updated_at: '2026-05-20T04:00:00.000Z',
|
||||
status: 'approved',
|
||||
approval_stage: '归档入账',
|
||||
status: 'pending_payment',
|
||||
approval_stage: '待付款',
|
||||
risk_flags_json: [
|
||||
{
|
||||
source: 'manual_approval',
|
||||
@@ -428,7 +428,7 @@ test('completed finance approval marks finance and archive progress steps', () =
|
||||
source: 'finance_approval',
|
||||
operator: '财务复核',
|
||||
previous_approval_stage: '财务审批',
|
||||
next_approval_stage: '归档入账',
|
||||
next_approval_stage: '待付款',
|
||||
created_at: '2026-05-20T04:00:00.000Z'
|
||||
}
|
||||
],
|
||||
@@ -436,14 +436,62 @@ test('completed finance approval marks finance and archive progress steps', () =
|
||||
})
|
||||
|
||||
const financeStep = request.progressSteps.find((step) => step.label === '财务审批')
|
||||
const archiveStep = request.progressSteps.find((step) => step.label === '归档入账')
|
||||
const paymentStep = request.progressSteps.find((step) => step.label === '待付款')
|
||||
|
||||
assert.equal(request.riskSummary, '无')
|
||||
assert.equal(request.workflowNode, '归档入账')
|
||||
assert.equal(request.workflowNode, '待付款')
|
||||
assert.equal(request.approvalKey, 'pending_payment')
|
||||
assert.equal(financeStep.time, '财务复核通过')
|
||||
assert.match(financeStep.detail, /2026-05-20/)
|
||||
assert.equal(archiveStep.time, '归档入账')
|
||||
assert.equal(archiveStep.done, true)
|
||||
assert.equal(paymentStep.current, true)
|
||||
assert.equal(paymentStep.done, false)
|
||||
})
|
||||
|
||||
test('paid reimbursement marks payment progress step as complete', () => {
|
||||
const request = mapExpenseClaimToRequest({
|
||||
id: 'claim-finance-paid',
|
||||
claim_no: 'EXP-202605-005',
|
||||
employee_name: '张三',
|
||||
department_name: '市场部',
|
||||
expense_type: 'transport',
|
||||
reason: '交通报销',
|
||||
location: '上海',
|
||||
amount: 88,
|
||||
invoice_count: 1,
|
||||
occurred_at: '2026-05-20T01:00:00.000Z',
|
||||
submitted_at: '2026-05-20T02:00:00.000Z',
|
||||
created_at: '2026-05-20T01:30:00.000Z',
|
||||
updated_at: '2026-05-20T05:00:00.000Z',
|
||||
status: 'paid',
|
||||
approval_stage: '已付款',
|
||||
risk_flags_json: [
|
||||
{
|
||||
source: 'finance_approval',
|
||||
operator: '财务复核',
|
||||
previous_approval_stage: '财务审批',
|
||||
next_approval_stage: '待付款',
|
||||
created_at: '2026-05-20T04:00:00.000Z'
|
||||
},
|
||||
{
|
||||
source: 'payment',
|
||||
event_type: 'expense_claim_payment_completed',
|
||||
operator: '财务付款',
|
||||
previous_approval_stage: '待付款',
|
||||
next_approval_stage: '已付款',
|
||||
created_at: '2026-05-20T05:00:00.000Z'
|
||||
}
|
||||
],
|
||||
items: []
|
||||
})
|
||||
|
||||
const paymentStep = request.progressSteps.find((step) => step.label === '待付款')
|
||||
const paidStep = request.progressSteps.find((step) => step.label === '已付款')
|
||||
|
||||
assert.equal(request.workflowNode, '已付款')
|
||||
assert.equal(request.approvalStatus, '已付款')
|
||||
assert.equal(paymentStep.time, '待付款')
|
||||
assert.equal(paidStep.time, '已付款')
|
||||
assert.equal(paidStep.done, true)
|
||||
})
|
||||
|
||||
test('current direct manager step shows how long the claim has stayed there', () => {
|
||||
|
||||
@@ -63,6 +63,22 @@ test('detects archived claim view models for delete permission gating', () => {
|
||||
}),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedRequestView({
|
||||
status: 'pending_payment',
|
||||
approval_stage: '待付款',
|
||||
approvalKey: 'pending_payment'
|
||||
}),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedRequestView({
|
||||
status: 'paid',
|
||||
approval_stage: '已付款',
|
||||
approvalKey: 'completed'
|
||||
}),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
isArchivedRequestView({
|
||||
status: 'approved',
|
||||
@@ -84,3 +100,18 @@ test('detects archived claim view models for delete permission gating', () => {
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('normalizes pending payment backend claims', () => {
|
||||
const request = normalizeRequestForUi({
|
||||
id: 'EXP-202605-004',
|
||||
claim_id: 'claim-4',
|
||||
status: 'pending_payment',
|
||||
approval_stage: '待付款',
|
||||
expense_type: 'transport',
|
||||
amount: 88
|
||||
})
|
||||
|
||||
assert.equal(request.approvalKey, 'pending_payment')
|
||||
assert.equal(request.approvalStatus, '待付款')
|
||||
assert.equal(request.node, '待付款')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user