HEL-342: 管理总览 KPI 绑定真实 dashboard 接口

去掉写死的演示金额与账期进度,改由 /api/admin/dashboard 的 totals 与 period_progress 填充;无数据走空态。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
总工
2026-09-01 19:32:59 +08:00
co-authored by Cursor multica-agent
parent b905cba52c
commit b2bcd47e62
12 changed files with 285 additions and 34 deletions
+112 -1
View File
@@ -8,6 +8,7 @@ as null so the UI shows an em dash rather than a guessed label.
from __future__ import annotations
from calendar import monthrange
from datetime import datetime, timedelta, timezone
from decimal import Decimal, ROUND_HALF_UP
import sqlite3
@@ -22,6 +23,12 @@ def today_shanghai() -> str:
return datetime.now(timezone(timedelta(hours=8))).date().isoformat()
def _month_bounds(year_month: str) -> tuple[str, str]:
year, month = (int(part) for part in year_month.split("-"))
last = monthrange(year, month)[1]
return f"{year_month}-01", f"{year_month}-{last:02d}"
def _q2(value: Decimal) -> str:
return str(value.quantize(TWOPLACES, rounding=ROUND_HALF_UP))
@@ -209,6 +216,108 @@ def company_summaries(
return items, totals
def period_progress(
connection: sqlite3.Connection, *, year_month: str
) -> dict[str, object]:
"""Per-company coverage of the cutoff month: done / in progress / unsubmitted.
Only companies with at least one active bank account are counted — the same
rule as monthly-close submission checks. Covered = confirmed source rows in
the month, or an approved no-business attestation that spans the month.
"""
start, end = _month_bounds(year_month)
enabled_rows = connection.execute(
"SELECT DISTINCT company_id FROM bank_accounts WHERE status = 'active'"
).fetchall()
enabled_ids = {int(row["company_id"]) for row in enabled_rows}
empty = {
"year_month": year_month,
"enabled_count": 0,
"done": 0,
"in_progress": 0,
"unsubmitted": 0,
"percent": 0,
}
if not enabled_ids:
return empty
submitted = {
int(row["company_id"])
for row in connection.execute(
"""
SELECT DISTINCT b.company_id AS company_id
FROM source_rows r
JOIN sheet_batches s ON s.id = r.sheet_batch_id
JOIN import_batches b ON b.id = s.import_batch_id
JOIN sheet_reviews rv ON rv.sheet_batch_id = s.id AND rv.review_status = 'confirmed'
WHERE date(r.transaction_at) >= date(?) AND date(r.transaction_at) <= date(?)
""",
(start, end),
).fetchall()
}
attested = {
int(row["company_id"])
for row in connection.execute(
"""
SELECT DISTINCT a.company_id AS company_id
FROM no_business_attestations a
WHERE a.status = 'approved'
AND date(a.gap_start) <= date(?)
AND date(a.gap_end) >= date(?)
""",
(end, start),
).fetchall()
}
covered = (submitted | attested) & enabled_ids
pending: set[int] = set()
for row in connection.execute(
"""
SELECT DISTINCT p.company_id AS company_id
FROM current_transfer_decisions c
JOIN transfer_match_decisions d ON d.id = c.decision_id
JOIN canonical_transfer_events e ON e.id = c.event_id
JOIN transfer_decision_participants p ON p.decision_id = d.id
WHERE e.lifecycle = 'active'
AND d.classification IN ('unresolved', 'needs_review')
AND date(d.effective_at) >= date(?) AND date(d.effective_at) <= date(?)
""",
(start, end),
):
pending.add(int(row["company_id"]))
for row in connection.execute(
"""
SELECT DISTINCT m.company_id AS company_id
FROM manual_records m
JOIN current_manual_record_decisions c ON c.record_id = m.id
JOIN manual_record_decisions d ON d.id = c.decision_id
WHERE d.state = 'pending'
AND date(m.occurred_at) >= date(?) AND date(m.occurred_at) <= date(?)
""",
(start, end),
):
pending.add(int(row["company_id"]))
for row in connection.execute(
"SELECT DISTINCT company_id FROM bank_accounts WHERE status = 'pending'"
):
pending.add(int(row["company_id"]))
pending_enabled = pending & enabled_ids
done = covered - pending_enabled
in_progress = pending_enabled
unsubmitted = enabled_ids - covered - pending_enabled
enabled_count = len(enabled_ids)
percent = round(100 * len(done) / enabled_count) if enabled_count else 0
return {
"year_month": year_month,
"enabled_count": enabled_count,
"done": len(done),
"in_progress": len(in_progress),
"unsubmitted": len(unsubmitted),
"percent": percent,
}
def company_peer_groups(
connection: sqlite3.Connection,
company_id: int,
@@ -378,13 +487,15 @@ def build_dashboard(
companies, totals = company_summaries(
connection, from_date=from_date, cutoff=cutoff_date
)
period_label = f"{period.year}-{period.month:02d}"
return {
"from_date": from_date,
"cutoff": cutoff_date,
"period_month": period_month,
"period_label": f"{period.year}-{period.month:02d}",
"period_label": period_label,
"audit": audit_counts(connection),
"totals": totals,
"period_progress": period_progress(connection, year_month=period_label),
"companies": companies,
"weekly_flow": weekly_flow(connection, cutoff=cutoff_date),
"opening_status": "unavailable",