feat: 新增归档中心页面并完善知识库与报销查询能力

新增前端归档中心视图及相关工具函数,扩充知识库文档分类和
提取器支持多种格式,增强编排器报销查询的多维度检索,优
化本体规则和用户代理审核消息,前端完善报销创建和审批详
情交互细节,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-22 16:00:19 +08:00
parent 1f15699013
commit 88ff04bef8
120 changed files with 6236 additions and 643 deletions

View File

@@ -1516,8 +1516,21 @@ def test_upload_hotel_attachment_flags_amount_over_travel_policy(monkeypatch, tm
analysis = uploaded_meta["analysis"]
assert analysis["severity"] == "high"
assert analysis["headline"] == "AI提示住宿金额超出报销标准"
assert "保留在单据中" in analysis["summary"]
assert "特殊情况" in analysis["summary"]
assert any("住宿标准" in point and "800.00 元" in point for point in analysis["points"])
assert any("住宿费按员工职级" in basis for basis in analysis["rule_basis"])
db.refresh(claim)
hotel_item = next(item for item in claim.items if str(item.invoice_id or "").strip())
assert hotel_item.item_amount == Decimal("800.00")
assert claim.invoice_count == 1
assert any(
isinstance(flag, dict)
and str(flag.get("source") or "").strip() == "attachment_analysis"
and flag.get("item_id") == hotel_item.id
and str(flag.get("severity") or "").strip() == "high"
for flag in list(claim.risk_flags_json or [])
)
def test_attachment_analysis_does_not_compare_business_purpose_with_ticket_scene() -> None:
@@ -2433,8 +2446,8 @@ def test_list_claims_allows_finance_to_view_all_records() -> None:
claims = ExpenseClaimService(db).list_claims(current_user)
assert len(claims) == 2
assert {claim.claim_no for claim in claims} == {"EXP-FIN-101", "EXP-FIN-102"}
assert len(claims) == 1
assert claims[0].claim_no == "EXP-FIN-101"
def test_list_claims_allows_executive_to_view_all_records() -> None:
@@ -2488,8 +2501,134 @@ def test_list_claims_allows_executive_to_view_all_records() -> None:
claims = ExpenseClaimService(db).list_claims(current_user)
assert len(claims) == 2
assert {claim.claim_no for claim in claims} == {"EXP-EXE-101", "EXP-EXE-102"}
assert len(claims) == 1
assert claims[0].claim_no == "EXP-EXE-101"
def test_list_claims_keeps_own_archived_claim_for_finance_applicant() -> None:
current_user = CurrentUserContext(
username="finance@example.com",
name="财务",
role_codes=["finance"],
is_admin=False,
)
with build_session() as db:
db.add(
ExpenseClaim(
claim_no="EXP-FIN-OWN-ARCH",
employee_name="财务",
department_name="财务部",
project_code="PRJ-FIN",
expense_type="meal",
reason="本人报销",
location="上海",
amount=Decimal("88.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="approved",
approval_stage="归档入账",
risk_flags_json=[],
)
)
db.commit()
claims = ExpenseClaimService(db).list_claims(current_user)
assert len(claims) == 1
assert claims[0].claim_no == "EXP-FIN-OWN-ARCH"
def test_list_archived_claims_returns_company_archived_records_for_finance() -> None:
current_user = CurrentUserContext(
username="finance@example.com",
name="财务",
role_codes=["finance"],
is_admin=False,
)
with build_session() as db:
db.add_all(
[
ExpenseClaim(
claim_no="EXP-ARCH-101",
employee_name="",
department_name="A部",
project_code="PRJ-A",
expense_type="travel",
reason="A 报销",
location="上海",
amount=Decimal("120.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 11, 9, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 11, 10, 0, tzinfo=UTC),
status="approved",
approval_stage="归档入账",
risk_flags_json=[],
),
ExpenseClaim(
claim_no="EXP-ARCH-102",
employee_name="",
department_name="B部",
project_code="PRJ-B",
expense_type="meal",
reason="B 报销",
location="杭州",
amount=Decimal("300.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 11, 12, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 11, 13, 0, tzinfo=UTC),
status="submitted",
approval_stage="财务审批",
risk_flags_json=[],
),
]
)
db.commit()
claims = ExpenseClaimService(db).list_archived_claims(current_user)
assert len(claims) == 1
assert claims[0].claim_no == "EXP-ARCH-101"
def test_list_archived_claims_is_empty_for_regular_employee() -> None:
current_user = CurrentUserContext(
username="zhangsan@example.com",
name="张三",
role_codes=["employee"],
is_admin=False,
)
with build_session() as db:
db.add(
ExpenseClaim(
claim_no="EXP-ARCH-EMP",
employee_name="张三",
department_name="研发部",
project_code="PRJ-EMP",
expense_type="travel",
reason="本人报销",
location="北京",
amount=Decimal("200.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 10, 9, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 10, 10, 0, tzinfo=UTC),
status="approved",
approval_stage="归档入账",
risk_flags_json=[],
)
)
db.commit()
claims = ExpenseClaimService(db).list_archived_claims(current_user)
assert claims == []
def test_finance_can_return_but_cannot_delete_submitted_claim() -> None: