feat: 完善预算中心图表与确认对话框交互

后端预算服务增加汇总查询和辅助计算,前端预算中心优化趋
势图组件和数据展示,增强确认对话框通用性和样式,完善预
算编辑对话框布局,补充预算端点单元测试。
This commit is contained in:
caoxiaozhu
2026-05-26 20:07:56 +08:00
parent e7bef0883d
commit df49103f23
10 changed files with 716 additions and 153 deletions

View File

@@ -12,7 +12,7 @@ from sqlalchemy.pool import StaticPool
from app.api.deps import get_db
from app.db.base import Base
from app.main import create_app
from app.models.budget import BudgetAllocation
from app.models.budget import BudgetAllocation, BudgetTransaction
def build_session_factory() -> sessionmaker[Session]:
@@ -123,6 +123,90 @@ def test_admin_can_view_all_budget_allocations_without_is_admin_header() -> None
payload = response.json()
assert {item["department_name"] for item in payload["allocations"]} == {"市场部", "财务部"}
assert {item["subject_code"] for item in payload["allocations"]} == {"travel", "office"}
assert payload["warnings"] == []
assert [item["period_key"] for item in payload["trend"]] == ["2026Q2"]
assert Decimal(payload["trend"][0]["total_amount"]) == Decimal("80000.00")
def test_budget_summary_returns_real_trend_and_warnings() -> None:
client, session_factory = build_client()
now = datetime.now(UTC)
with session_factory() as db:
seed_budget_allocations(db)
db.add(
BudgetAllocation(
id="budget-market-travel-q1",
budget_no="BUD-MARKET-TRAVEL-Q1",
fiscal_year=2026,
period_type="quarter",
period_key="2026Q1",
department_id="dept-market",
department_name="市场部",
cost_center="CC-4100",
project_code=None,
subject_code="travel",
subject_name="差旅费",
original_amount=Decimal("40000.00"),
adjusted_amount=Decimal("0.00"),
status="active",
warning_threshold=Decimal("80.00"),
control_action="block",
created_at=now,
updated_at=now,
)
)
db.add_all(
[
BudgetTransaction(
transaction_no="BTX-MARKET-TRAVEL-Q1",
allocation_id="budget-market-travel-q1",
source_type="claim",
source_id="claim-q1",
source_no="CLM-Q1",
transaction_type="consume",
amount=Decimal("12000.00"),
before_available_amount=Decimal("40000.00"),
after_available_amount=Decimal("28000.00"),
operator="tester",
reason="Q1 真实发生额",
created_at=now,
),
BudgetTransaction(
transaction_no="BTX-MARKET-TRAVEL-Q2",
allocation_id="budget-market-travel",
source_type="claim",
source_id="claim-q2",
source_no="CLM-Q2",
transaction_type="consume",
amount=Decimal("41000.00"),
before_available_amount=Decimal("50000.00"),
after_available_amount=Decimal("9000.00"),
operator="tester",
reason="Q2 真实发生额",
created_at=now,
),
]
)
db.commit()
response = client.get(
"/api/v1/budgets/summary?year=2026&period=2026Q2&cost_center=CC-4100",
headers={"x-auth-username": "admin", "x-auth-role-codes": "manager"},
)
assert response.status_code == 200
payload = response.json()
assert [item["subject_code"] for item in payload["allocations"]] == ["travel"]
assert [item["period_key"] for item in payload["trend"]] == ["2026Q1", "2026Q2"]
assert Decimal(payload["trend"][0]["used_amount"]) == Decimal("12000.00")
assert Decimal(payload["trend"][1]["used_amount"]) == Decimal("41000.00")
assert payload["warning_count"] == 1
assert payload["over_budget_count"] == 0
assert len(payload["warnings"]) == 1
warning = payload["warnings"][0]
assert warning["subject_code"] == "travel"
assert warning["severity"] == "warn"
assert Decimal(warning["usage_rate"]) == Decimal("82.00")
def test_budget_monitor_is_limited_to_own_department_scope() -> None: