Redesign finance dashboard with period summaries
This commit is contained in:
@@ -861,6 +861,156 @@ def test_save_summary_if_absent_skips_existing(monkeypatch):
|
||||
db.close()
|
||||
|
||||
|
||||
def _insert_finance_summary_row(db, stat_date, summary):
|
||||
from app.models.finance_daily_summary import FinanceDailySummary
|
||||
|
||||
row = FinanceDailySummary(
|
||||
stat_date=stat_date,
|
||||
summary_json=json.dumps(summary, ensure_ascii=False),
|
||||
success=summary.get("success", False),
|
||||
)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
return row
|
||||
|
||||
|
||||
def test_week_summary_aggregates_stored_snapshots_only(monkeypatch):
|
||||
"""Week aggregation reads existing DB snapshots without computing missing days."""
|
||||
from app.services.finance_service import get_finance_summary
|
||||
|
||||
db = _make_inmemory_db()
|
||||
_insert_finance_summary_row(db, date(2026, 6, 30), {
|
||||
"date": "2026-06-30",
|
||||
"total_revenue": 100.0,
|
||||
"total_cost": 30.0,
|
||||
"net_income": 70.0,
|
||||
"margin_percent": 70.0,
|
||||
"website_items": [
|
||||
{"id": 1, "name": "Site A", "upstream_type": "sub2api", "amount": 100.0, "status": "success", "error": None},
|
||||
],
|
||||
"upstream_items": [
|
||||
{"id": 10, "name": "Up A", "upstream_type": "new_api", "amount": 30.0, "status": "success", "error": None},
|
||||
],
|
||||
"failed_count": 0,
|
||||
"success": True,
|
||||
})
|
||||
_insert_finance_summary_row(db, date(2026, 7, 2), {
|
||||
"date": "2026-07-02",
|
||||
"total_revenue": 50.0,
|
||||
"total_cost": 0.0,
|
||||
"net_income": 50.0,
|
||||
"margin_percent": 100.0,
|
||||
"website_items": [
|
||||
{"id": 1, "name": "Site A", "upstream_type": "sub2api", "amount": 50.0, "status": "success", "error": None},
|
||||
],
|
||||
"upstream_items": [
|
||||
{"id": 10, "name": "Up A", "upstream_type": "new_api", "amount": 0.0, "status": "failed", "error": "timeout"},
|
||||
],
|
||||
"failed_count": 1,
|
||||
"success": False,
|
||||
})
|
||||
|
||||
def _should_not_compute(*_args, **_kwargs):
|
||||
raise AssertionError("week/month summary must not compute daily summaries")
|
||||
|
||||
monkeypatch.setattr("app.services.finance_service.compute_daily_summary", _should_not_compute)
|
||||
result = get_finance_summary(db, "week", date(2026, 7, 2))
|
||||
|
||||
assert result["start_date"] == "2026-06-26"
|
||||
assert result["end_date"] == "2026-07-02"
|
||||
assert result["included_days"] == 2
|
||||
assert result["partial"] is True
|
||||
assert len(result["missing_dates"]) == 5
|
||||
assert result["total_revenue"] == 150.0
|
||||
assert result["total_cost"] == 30.0
|
||||
assert result["net_income"] == 120.0
|
||||
assert result["chart"]["labels"] == [
|
||||
"2026-06-26", "2026-06-27", "2026-06-28", "2026-06-29",
|
||||
"2026-06-30", "2026-07-01", "2026-07-02",
|
||||
]
|
||||
assert result["chart"]["revenue"] == [0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 50.0]
|
||||
assert result["website_items"][0]["amount"] == 150.0
|
||||
assert result["upstream_items"][0]["status"] == "failed"
|
||||
assert result["upstream_items"][0]["amount"] == 30.0
|
||||
assert result["upstream_items"][0]["error"] == "2026-07-02: timeout"
|
||||
db.close()
|
||||
|
||||
|
||||
def test_month_summary_uses_natural_month_and_reports_missing_dates():
|
||||
"""Month aggregation spans the selected date's calendar month."""
|
||||
from app.services.finance_service import get_finance_summary
|
||||
|
||||
db = _make_inmemory_db()
|
||||
_insert_finance_summary_row(db, date(2026, 6, 1), {
|
||||
"date": "2026-06-01",
|
||||
"total_revenue": 10.0,
|
||||
"total_cost": 4.0,
|
||||
"net_income": 6.0,
|
||||
"margin_percent": 60.0,
|
||||
"website_items": [],
|
||||
"upstream_items": [],
|
||||
"failed_count": 0,
|
||||
"success": True,
|
||||
})
|
||||
_insert_finance_summary_row(db, date(2026, 6, 15), {
|
||||
"date": "2026-06-15",
|
||||
"total_revenue": 20.0,
|
||||
"total_cost": 7.0,
|
||||
"net_income": 13.0,
|
||||
"margin_percent": 65.0,
|
||||
"website_items": [],
|
||||
"upstream_items": [],
|
||||
"failed_count": 0,
|
||||
"success": True,
|
||||
})
|
||||
|
||||
result = get_finance_summary(db, "month", date(2026, 6, 15))
|
||||
|
||||
assert result["start_date"] == "2026-06-01"
|
||||
assert result["end_date"] == "2026-06-30"
|
||||
assert result["included_days"] == 2
|
||||
assert result["partial"] is True
|
||||
assert len(result["missing_dates"]) == 28
|
||||
assert result["total_revenue"] == 30.0
|
||||
assert result["total_cost"] == 11.0
|
||||
assert result["net_income"] == 19.0
|
||||
assert len(result["chart"]["labels"]) == 30
|
||||
db.close()
|
||||
|
||||
|
||||
def test_day_summary_adds_seven_day_snapshot_chart(monkeypatch):
|
||||
"""Day summary keeps existing compute-on-miss behavior and adds daily trend data."""
|
||||
from app.services.finance_service import get_finance_summary
|
||||
|
||||
db = _make_inmemory_db()
|
||||
_add_minimal_test_data(db)
|
||||
_insert_finance_summary_row(db, date(2026, 6, 30), {
|
||||
"date": "2026-06-30",
|
||||
"total_revenue": 80.0,
|
||||
"total_cost": 20.0,
|
||||
"net_income": 60.0,
|
||||
"margin_percent": 75.0,
|
||||
"website_items": [],
|
||||
"upstream_items": [],
|
||||
"failed_count": 0,
|
||||
"success": True,
|
||||
})
|
||||
monkeypatch.setattr("app.services.finance_service.fetch_website_revenue", lambda *_: (100.0, None))
|
||||
monkeypatch.setattr("app.services.finance_service.fetch_upstream_cost_sub2api", lambda *_: (40.0, None))
|
||||
|
||||
result = get_finance_summary(db, "day", date(2026, 7, 2))
|
||||
|
||||
assert result["total_revenue"] == 100.0
|
||||
assert result["included_days"] == 1
|
||||
assert result["partial"] is False
|
||||
assert result["chart"]["labels"] == [
|
||||
"2026-06-26", "2026-06-27", "2026-06-28", "2026-06-29",
|
||||
"2026-06-30", "2026-07-01", "2026-07-02",
|
||||
]
|
||||
assert result["chart"]["revenue"] == [0.0, 0.0, 0.0, 0.0, 80.0, 0.0, 100.0]
|
||||
db.close()
|
||||
|
||||
|
||||
def _make_test_summary(website_items, upstream_items=None):
|
||||
"""Return a summary dict with deep-copied items to prevent cross-contamination."""
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user