feat: 完善文档中心与报销申请交互及侧边栏重构

后端优化编排器报销查询和本体检测精度,增强报销单草稿保
存和附件回填逻辑,前端重构侧边栏组件支持折叠和图标导
航,完善文档中心状态筛选和详情提示,报销创建和审批详情
页优化会话管理和费用明细交互,新增助手应用服务和预设动
作工具函数,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-25 13:35:39 +08:00
parent 50b1c3f9a9
commit d0e946cf47
59 changed files with 5117 additions and 416 deletions

View File

@@ -2883,6 +2883,133 @@ def test_direct_manager_can_approve_subordinate_claim_to_finance_review() -> Non
)
def test_application_submit_skips_ai_review_and_receipt_requirements(monkeypatch: pytest.MonkeyPatch) -> None:
current_user = CurrentUserContext(
username="application-owner@example.com",
name="张三",
role_codes=["employee"],
is_admin=True,
)
with build_session() as db:
claim = ExpenseClaim(
claim_no="APP-20260525-SUBMIT",
employee_name="张三",
department_name="交付部",
project_code="PRJ-A",
expense_type="travel_application",
reason="支撑国网服务器上线部署",
location="上海",
amount=Decimal("12000.00"),
currency="CNY",
invoice_count=0,
occurred_at=datetime(2026, 5, 25, 9, 0, tzinfo=UTC),
submitted_at=None,
status="draft",
approval_stage="待提交",
risk_flags_json=[
{
"source": "submission_review",
"severity": "medium",
"message": "旧 AI 预审提示不应保留到申请单提交结果。",
}
],
)
db.add(claim)
db.commit()
claim_id = claim.id
service = ExpenseClaimService(db)
def fail_ai_review(_claim: ExpenseClaim) -> dict[str, object]:
raise AssertionError("费用申请提交不应进入 AI 预审")
monkeypatch.setattr(service, "_run_ai_submission_review", fail_ai_review)
submitted = service.submit_claim(claim_id, current_user)
assert submitted is not None
assert submitted.status == "submitted"
assert submitted.approval_stage == "直属领导审批"
assert submitted.invoice_count == 0
assert submitted.items == []
assert not any(
isinstance(flag, dict) and flag.get("source") == "submission_review"
for flag in submitted.risk_flags_json
)
assert any(
isinstance(flag, dict)
and flag.get("source") == "application_submission"
and flag.get("event_type") == "expense_application_submission"
and flag.get("next_approval_stage") == "直属领导审批"
for flag in submitted.risk_flags_json
)
def test_direct_manager_can_approve_application_claim_to_completed_stage() -> None:
current_user = CurrentUserContext(
username="manager-application-approve@example.com",
name="李经理",
role_codes=["manager"],
is_admin=False,
)
with build_session() as db:
manager = Employee(
employee_no="E8112",
name="李经理",
email="manager-application-approve@example.com",
)
employee = Employee(
employee_no="E8113",
name="张三",
email="zhangsan-application-approve@example.com",
manager=manager,
)
db.add_all([manager, employee])
db.flush()
claim = ExpenseClaim(
claim_no="APP-20260525-APPROVE",
employee_id=employee.id,
employee_name="张三",
department_name="交付部",
project_code="PRJ-A",
expense_type="travel_application",
reason="支撑国网服务器上线部署",
location="上海",
amount=Decimal("12000.00"),
currency="CNY",
invoice_count=0,
occurred_at=datetime(2026, 5, 25, 9, 0, tzinfo=UTC),
submitted_at=datetime(2026, 5, 25, 10, 0, tzinfo=UTC),
status="submitted",
approval_stage="直属领导审批",
risk_flags_json=[],
)
db.add(claim)
db.commit()
claim_id = claim.id
approved = ExpenseClaimService(db).approve_claim(
claim_id,
current_user,
opinion="业务必要,同意申请。",
)
assert approved is not None
assert approved.status == "approved"
assert approved.approval_stage == "审批完成"
assert any(
isinstance(flag, dict)
and flag.get("source") == "manual_approval"
and flag.get("event_type") == "expense_application_approval"
and flag.get("opinion") == "业务必要,同意申请。"
and flag.get("previous_approval_stage") == "直属领导审批"
and flag.get("next_status") == "approved"
and flag.get("next_approval_stage") == "审批完成"
for flag in approved.risk_flags_json
)
def test_finance_can_approve_claim_to_archive_stage() -> None:
current_user = CurrentUserContext(
username="finance-approve@example.com",