diff --git a/backend/routes.py b/backend/routes.py
index c8991b8..a767c8a 100644
--- a/backend/routes.py
+++ b/backend/routes.py
@@ -724,6 +724,359 @@ def delete_file(file_id):
conn.close()
+
+# ---------- 总览部门损益矩阵 API ----------
+
+@bp.route("/api/overview/dept-matrix")
+@login_required
+def overview_dept_matrix():
+ """返回各工作台的损益数据矩阵(按年/季/月筛选)"""
+ conn = db()
+ _year = int(request.args.get("year", date.today().year))
+ view = request.args.get("view", "monthly")
+
+ # 12个月数组
+ months = [f"{_year}-{m:02d}" for m in range(1, 13)]
+
+ tenants = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
+ result = {"tenants": tenants, "months": months, "data": {}, "total": {"project_gross": [0]*12, "dept_expense": [0]*12, "profit": [0]*12}}
+
+ for tenant in tenants:
+ # 项目毛利(从 budget_data JSON)
+ pfs = rows(conn, "SELECT budget_data FROM plan_finances WHERE tenant=?", [tenant])
+ gross = [0]*12
+ for pf in pfs:
+ try:
+ bd = json.loads(pf.get("budget_data") or "[]")
+ except:
+ bd = []
+ for b in bd:
+ m = (b.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ gross[i] += float(b.get("gross") or 0)
+
+ # 部门费用(从 plan_expense_records)
+ expense = [0]*12
+ exps = rows(conn, "SELECT expense_month, amount FROM plan_expense_records WHERE tenant=? AND expense_month LIKE %s", [tenant, f"{_year}-%"])
+ for e in exps:
+ m = (e.get("expense_month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ expense[i] += float(e.get("amount") or 0)
+
+ profit = [gross[i] - expense[i] for i in range(12)]
+
+ result["data"][tenant] = {
+ "project_gross": gross,
+ "dept_expense": expense,
+ "profit": profit
+ }
+
+ for i in range(12):
+ result["total"]["project_gross"][i] += gross[i]
+ result["total"]["dept_expense"][i] += expense[i]
+ result["total"]["profit"][i] += profit[i]
+
+ # 按视图聚合
+ if view == "quarterly":
+ def to_quarterly(arr12):
+ qr = [(0,1,2),(3,4,5),(6,7,8),(9,10,11)]
+ return [arr12[q[0]]+arr12[q[1]]+arr12[q[2]] for q in qr]
+ result["months"] = ["Q1","Q2","Q3","Q4"]
+ for t in tenants:
+ for key in ["project_gross","dept_expense","profit"]:
+ result["data"][t][key] = to_quarterly(result["data"][t][key])
+ for key in ["project_gross","dept_expense","profit"]:
+ result["total"][key] = to_quarterly(result["total"][key])
+ elif view == "monthly":
+ sel_month = request.args.get("month", "")
+ if sel_month and sel_month in months:
+ mi = months.index(sel_month)
+ result["months"] = [sel_month]
+ for t in tenants:
+ for key in ["project_gross","dept_expense","profit"]:
+ result["data"][t][key] = [result["data"][t][key][mi]]
+ for key in ["project_gross","dept_expense","profit"]:
+ result["total"][key] = [result["total"][key][mi]]
+ # else: yearly (default) — keep all 12 months
+
+ # Compute annual totals
+ result["annual"] = {}
+ for t in tenants:
+ d = result["data"][t]
+ result["annual"][t] = {
+ "project_gross": sum(d["project_gross"]),
+ "dept_expense": sum(d["dept_expense"]),
+ "profit": sum(d["profit"])
+ }
+ result["annual"]["total"] = {
+ "project_gross": sum(result["total"]["project_gross"]),
+ "dept_expense": sum(result["total"]["dept_expense"]),
+ "profit": sum(result["total"]["profit"])
+ }
+
+ conn.close()
+ return jsonify(result)
+
+@bp.route("/api/overview/finance-dept-matrix")
+@login_required
+def overview_finance_dept_matrix():
+ """返回各工作台已发生损益数据矩阵"""
+ conn = db()
+ _year = date.today().year
+ months = [f"{_year}-{m:02d}" for m in range(1, 13)]
+ tenants = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
+ result = {"tenants": tenants, "months": months, "data": {}, "total": {"project_gross": [0]*12, "dept_expense": [0]*12, "profit": [0]*12}}
+
+ for tenant in tenants:
+ pfs = rows(conn, "SELECT budget_data FROM project_finances WHERE tenant=?", [tenant])
+ gross = [0]*12
+ for pf in pfs:
+ try:
+ bd = json.loads(pf.get("budget_data") or "[]")
+ except:
+ bd = []
+ for b in bd:
+ m = (b.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ gross[i] += float(b.get("gross") or 0)
+
+ expense = [0]*12
+ exps = rows(conn, "SELECT expense_month, amount FROM expense_records WHERE tenant=? AND expense_month LIKE %s", [tenant, f"{_year}-%"])
+ for e in exps:
+ m = (e.get("expense_month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ expense[i] += float(e.get("amount") or 0)
+
+ profit = [gross[i] - expense[i] for i in range(12)]
+ result["data"][tenant] = {"project_gross": gross, "dept_expense": expense, "profit": profit}
+ for i in range(12):
+ result["total"]["project_gross"][i] += gross[i]
+ result["total"]["dept_expense"][i] += expense[i]
+ result["total"]["profit"][i] += profit[i]
+
+ result["annual"] = {}
+ for t in tenants:
+ d = result["data"][t]
+ result["annual"][t] = {"project_gross": sum(d["project_gross"]), "dept_expense": sum(d["dept_expense"]), "profit": sum(d["profit"])}
+ result["annual"]["total"] = {"project_gross": sum(result["total"]["project_gross"]), "dept_expense": sum(result["total"]["dept_expense"]), "profit": sum(result["total"]["profit"])}
+
+ conn.close()
+ return jsonify(result)
+
+
+@bp.route("/api/overview/dept-cf-matrix")
+@login_required
+def overview_dept_cf_matrix():
+ """返回各工作台预算现金流数据矩阵"""
+ conn = db()
+ _year = date.today().year
+ months = [f"{_year}-{m:02d}" for m in range(1, 13)]
+ tenants = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
+ result = {"tenants": tenants, "months": months, "data": {}, "total": {"project_cf": [0]*12, "dept_cf": [0]*12, "bl_cf": [0]*12}}
+
+ for tenant in tenants:
+ pfs = rows(conn, "SELECT budget_data, expense_data FROM plan_finances WHERE tenant=?", [tenant])
+ project_cf = [0]*12
+ for pf in pfs:
+ try: bd = json.loads(pf.get("budget_data") or "[]")
+ except: bd = []
+ try: ed = json.loads(pf.get("expense_data") or "[]")
+ except: ed = []
+ for b in bd:
+ m = (b.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ project_cf[i] += float(b.get("payment") or 0)
+ for e in ed:
+ m = (e.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ project_cf[i] -= float(e.get("paid") or 0)
+
+ dept_cf = [0]*12
+ exps = rows(conn, "SELECT expense_month, incurred_amount FROM plan_expense_records WHERE tenant=? AND expense_month LIKE %s", [tenant, f"{_year}-%"])
+ for e in exps:
+ m = (e.get("expense_month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ dept_cf[i] += float(e.get("incurred_amount") or 0)
+
+ bl_cf = [project_cf[i] - dept_cf[i] for i in range(12)]
+ result["data"][tenant] = {"project_cf": project_cf, "dept_cf": dept_cf, "bl_cf": bl_cf}
+ for i in range(12):
+ result["total"]["project_cf"][i] += project_cf[i]
+ result["total"]["dept_cf"][i] += dept_cf[i]
+ result["total"]["bl_cf"][i] += bl_cf[i]
+
+ result["annual"] = {}
+ for t in tenants:
+ d = result["data"][t]
+ result["annual"][t] = {"project_cf": sum(d["project_cf"]), "dept_cf": sum(d["dept_cf"]), "bl_cf": sum(d["bl_cf"])}
+ result["annual"]["total"] = {"project_cf": sum(result["total"]["project_cf"]), "dept_cf": sum(result["total"]["dept_cf"]), "bl_cf": sum(result["total"]["bl_cf"])}
+
+ conn.close()
+ return jsonify(result)
+
+
+@bp.route("/api/overview/finance-dept-cf-matrix")
+@login_required
+def overview_finance_dept_cf_matrix():
+ """返回各工作台已发生现金流数据矩阵"""
+ conn = db()
+ _year = date.today().year
+ months = [f"{_year}-{m:02d}" for m in range(1, 13)]
+ tenants = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
+ result = {"tenants": tenants, "months": months, "data": {}, "total": {"project_cf": [0]*12, "dept_cf": [0]*12, "bl_cf": [0]*12}}
+
+ for tenant in tenants:
+ pfs = rows(conn, "SELECT budget_data, expense_data FROM project_finances WHERE tenant=?", [tenant])
+ project_cf = [0]*12
+ for pf in pfs:
+ try: bd = json.loads(pf.get("budget_data") or "[]")
+ except: bd = []
+ try: ed = json.loads(pf.get("expense_data") or "[]")
+ except: ed = []
+ for b in bd:
+ m = (b.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ project_cf[i] += float(b.get("payment") or 0)
+ for e in ed:
+ m = (e.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ project_cf[i] -= float(e.get("paid") or 0)
+
+ dept_cf = [0]*12
+ exps = rows(conn, "SELECT expense_month, incurred_amount FROM expense_records WHERE tenant=? AND expense_month LIKE %s", [tenant, f"{_year}-%"])
+ for e in exps:
+ m = (e.get("expense_month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ dept_cf[i] += float(e.get("incurred_amount") or 0)
+
+ bl_cf = [project_cf[i] - dept_cf[i] for i in range(12)]
+ result["data"][tenant] = {"project_cf": project_cf, "dept_cf": dept_cf, "bl_cf": bl_cf}
+ for i in range(12):
+ result["total"]["project_cf"][i] += project_cf[i]
+ result["total"]["dept_cf"][i] += dept_cf[i]
+ result["total"]["bl_cf"][i] += bl_cf[i]
+
+ result["annual"] = {}
+ for t in tenants:
+ d = result["data"][t]
+ result["annual"][t] = {"project_cf": sum(d["project_cf"]), "dept_cf": sum(d["dept_cf"]), "bl_cf": sum(d["bl_cf"])}
+ result["annual"]["total"] = {"project_cf": sum(result["total"]["project_cf"]), "dept_cf": sum(result["total"]["dept_cf"]), "bl_cf": sum(result["total"]["bl_cf"])}
+
+ conn.close()
+ return jsonify(result)
+
+
+@bp.route("/api/overview/dept-project-matrix")
+@login_required
+def overview_dept_project_matrix():
+ """返回各工作台预算项目经营详情矩阵"""
+ conn = db()
+ _year = date.today().year
+ months = [f"{_year}-{m:02d}" for m in range(1, 13)]
+ tenants = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
+ keys = ["sign","revenue","gross","cost","paid","payment","cashflow"]
+ result = {"tenants": tenants, "months": months, "data": {}, "total": {k: [0]*12 for k in keys}}
+
+ for tenant in tenants:
+ tdata = {k: [0]*12 for k in keys}
+ pfs = rows(conn, "SELECT sign_amount, sign_month, budget_data, expense_data FROM plan_finances WHERE tenant=?", [tenant])
+ for pf in pfs:
+ sm = (pf.get("sign_month") or "")[:7]
+ if sm in months:
+ i = months.index(sm)
+ tdata["sign"][i] += float(pf.get("sign_amount") or 0)
+ try: bd = json.loads(pf.get("budget_data") or "[]")
+ except: bd = []
+ try: ed = json.loads(pf.get("expense_data") or "[]")
+ except: ed = []
+ for b in bd:
+ m = (b.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ tdata["revenue"][i] += float(b.get("rev") or 0)
+ tdata["gross"][i] += float(b.get("gross") or 0)
+ tdata["payment"][i] += float(b.get("payment") or 0)
+ for e in ed:
+ m = (e.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ tdata["cost"][i] += float(e.get("cost") or 0)
+ tdata["paid"][i] += float(e.get("paid") or 0)
+ for i in range(12):
+ tdata["cashflow"][i] = tdata["payment"][i] - tdata["paid"][i]
+ result["data"][tenant] = tdata
+ for k in keys:
+ for i in range(12):
+ result["total"][k][i] += tdata[k][i]
+
+ result["annual"] = {}
+ for t in tenants:
+ result["annual"][t] = {k: sum(result["data"][t][k]) for k in keys}
+ result["annual"]["total"] = {k: sum(result["total"][k]) for k in keys}
+ conn.close()
+ return jsonify(result)
+
+
+@bp.route("/api/overview/finance-dept-project-matrix")
+@login_required
+def overview_finance_dept_project_matrix():
+ """返回各工作台已发生项目经营详情矩阵"""
+ conn = db()
+ _year = date.today().year
+ months = [f"{_year}-{m:02d}" for m in range(1, 13)]
+ tenants = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
+ keys = ["sign","revenue","gross","cost","paid","payment","cashflow"]
+ result = {"tenants": tenants, "months": months, "data": {}, "total": {k: [0]*12 for k in keys}}
+
+ for tenant in tenants:
+ tdata = {k: [0]*12 for k in keys}
+ pfs = rows(conn, "SELECT sign_amount, sign_month, budget_data, expense_data FROM project_finances WHERE tenant=?", [tenant])
+ for pf in pfs:
+ sm = (pf.get("sign_month") or "")[:7]
+ if sm in months:
+ i = months.index(sm)
+ tdata["sign"][i] += float(pf.get("sign_amount") or 0)
+ try: bd = json.loads(pf.get("budget_data") or "[]")
+ except: bd = []
+ try: ed = json.loads(pf.get("expense_data") or "[]")
+ except: ed = []
+ for b in bd:
+ m = (b.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ tdata["revenue"][i] += float(b.get("rev") or 0)
+ tdata["gross"][i] += float(b.get("gross") or 0)
+ tdata["payment"][i] += float(b.get("payment") or 0)
+ for e in ed:
+ m = (e.get("month") or "")[:7]
+ if m in months:
+ i = months.index(m)
+ tdata["cost"][i] += float(e.get("cost") or 0)
+ tdata["paid"][i] += float(e.get("paid") or 0)
+ for i in range(12):
+ tdata["cashflow"][i] = tdata["payment"][i] - tdata["paid"][i]
+ result["data"][tenant] = tdata
+ for k in keys:
+ for i in range(12):
+ result["total"][k][i] += tdata[k][i]
+
+ result["annual"] = {}
+ for t in tenants:
+ result["annual"][t] = {k: sum(result["data"][t][k]) for k in keys}
+ result["annual"]["total"] = {k: sum(result["total"][k]) for k in keys}
+ conn.close()
+ return jsonify(result)
+
# ---------- health ----------
@bp.route("/api/health")
diff --git a/static/modules/overview_finance.js b/static/modules/overview_finance.js
index 3f21807..6c7030f 100644
--- a/static/modules/overview_finance.js
+++ b/static/modules/overview_finance.js
@@ -1,38 +1,105 @@
// overview_finance.js — 发生总览模块(5 个工作台数据聚合)
function renderOverviewFinance() {
- const view = state.overviewView || "monthly";
- const api = view === "quarterly" ? "/api/overview/finance-quarterly" : "/api/overview/finance-monthly";
- const container = document.querySelector("#overview_finance");
+ var view = state.overviewView || "monthly";
+ if (view === 'matrix') { renderFinanceDeptMatrix(); return; }
+ if (view === 'cf_matrix') { renderFinanceCfMatrix(); return; }
+ if (view === 'project_matrix') { renderFinanceProjectMatrix(); return; }
+
+ var api = view === "quarterly" ? "/api/overview/finance-quarterly" : "/api/overview/finance-monthly";
+ var container = document.querySelector("#overview_finance");
if (!container) return;
container.innerHTML = '