feat: 新增员工行为画像算法与费用风险标签体系

后端新增员工行为画像算法模块,支持标签规则引擎和评分计算,
完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流
和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费
用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台
样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-28 12:09:49 +08:00
parent 04cd6d0f81
commit 8a4a777be7
96 changed files with 9835 additions and 704 deletions

View File

@@ -2680,6 +2680,23 @@ def test_list_archived_claims_returns_company_archived_records_for_finance() ->
approval_stage="财务审批",
risk_flags_json=[],
),
ExpenseClaim(
claim_no="EXP-ARCH-PAID",
employee_name="",
department_name="C部",
project_code="PRJ-C",
expense_type="office",
reason="C 报销",
location="深圳",
amount=Decimal("180.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 11, 14, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 11, 15, 0, tzinfo=UTC),
status="paid",
approval_stage="已付款",
risk_flags_json=[],
),
ExpenseClaim(
claim_no="AP-20260525120000-ABCDEFGH",
employee_name="",
@@ -2722,6 +2739,7 @@ def test_list_archived_claims_returns_company_archived_records_for_finance() ->
assert {claim.claim_no for claim in claims} == {
"EXP-ARCH-101",
"EXP-ARCH-PAID",
"AP-20260525120000-ABCDEFGH",
}
@@ -3894,7 +3912,7 @@ def test_finance_cannot_operate_own_claim_in_finance_stage() -> None:
assert claim.risk_flags_json == []
def test_finance_can_approve_claim_to_archive_stage() -> None:
def test_finance_can_approve_claim_to_pending_payment_stage() -> None:
current_user = CurrentUserContext(
username="finance-approve@example.com",
name="财务复核",
@@ -3931,19 +3949,65 @@ def test_finance_can_approve_claim_to_archive_stage() -> None:
)
assert approved is not None
assert approved.status == "approved"
assert approved.approval_stage == "归档入账"
assert approved.status == "pending_payment"
assert approved.approval_stage == "待付款"
assert any(
isinstance(flag, dict)
and flag.get("source") == "finance_approval"
and flag.get("event_type") == "expense_claim_finance_approval"
and flag.get("opinion") == "票据与明细一致,同意入账。"
and flag.get("previous_approval_stage") == "财务审批"
and flag.get("next_approval_stage") == "归档入账"
and flag.get("next_status") == "pending_payment"
and flag.get("next_approval_stage") == "待付款"
for flag in approved.risk_flags_json
)
def test_finance_can_mark_pending_payment_claim_as_paid() -> None:
current_user = CurrentUserContext(
username="finance-pay@example.com",
name="财务付款",
role_codes=["finance"],
is_admin=False,
)
with build_session() as db:
claim = ExpenseClaim(
claim_no="EXP-FIN-PAY-201",
employee_name="张三",
department_name="市场部",
project_code="PRJ-A",
expense_type="transport",
reason="交通报销",
location="上海",
amount=Decimal("66.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 12, 9, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 12, 10, 0, tzinfo=UTC),
status="pending_payment",
approval_stage="待付款",
risk_flags_json=[],
)
db.add(claim)
db.commit()
paid = ExpenseClaimService(db).mark_claim_paid(claim.id, current_user)
assert paid is not None
assert paid.status == "paid"
assert paid.approval_stage == "已付款"
assert any(
isinstance(flag, dict)
and flag.get("source") == "payment"
and flag.get("event_type") == "expense_claim_payment_completed"
and flag.get("previous_status") == "pending_payment"
and flag.get("next_status") == "paid"
and flag.get("next_approval_stage") == "已付款"
for flag in paid.risk_flags_json
)
def test_return_claim_rejects_already_returned_claim_without_adding_event() -> None:
current_user = CurrentUserContext(
username="finance-returned@example.com",