feat: 完善预算中心图表与确认对话框交互
后端预算服务增加汇总查询和辅助计算,前端预算中心优化趋 势图组件和数据展示,增强确认对话框通用性和样式,完善预 算编辑对话框布局,补充预算端点单元测试。
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user