feat: 新增总览工作台(仅管理员)
- 新增总览工作台,聚合学术/科普/科研/医患/MCN数据 - 4个聚合API: 预算/发生月报季报 - 新增预算总览/发生总览侧边栏tab - 月报/季报视图切换 - 总览仅管理员可见 - 版本号 v1.2.0.24
This commit is contained in:
@@ -14,7 +14,7 @@ from helpers import add_file_index, attach_common, monthly_finance
|
||||
|
||||
bp = Blueprint("api", __name__)
|
||||
|
||||
ALL_TENANTS = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学术·无界", "测试·无界"]
|
||||
ALL_TENANTS = ["总览", "科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学术·无界", "测试·无界"]
|
||||
|
||||
TABLES = {
|
||||
"sales": ("sales_leads", ["target_customer", "priority", "status", "tenant"]),
|
||||
@@ -75,7 +75,7 @@ def auth_login():
|
||||
session["display_name"] = user["display_name"]
|
||||
session["role"] = user["role"]
|
||||
if user["role"] == "admin":
|
||||
session["tenants"] = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学术·无界", "测试·无界"]
|
||||
session["tenants"] = ["总览", "科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学术·无界", "测试·无界"]
|
||||
else:
|
||||
ut = rows(conn, "SELECT tenant FROM user_tenants WHERE user_id=?", (user["id"],))
|
||||
session["tenants"] = [x["tenant"] for x in ut]
|
||||
@@ -128,7 +128,6 @@ def auth_me():
|
||||
tenants = session.get("tenants", [])
|
||||
tenants = [t if t != "总工作台" else "科普·无界" for t in tenants]
|
||||
tenants = [t if t != "学会·无界" else "学术·无界" for t in tenants]
|
||||
tenants = [t for t in tenants if t != "总览" and t != "总工作台"]
|
||||
return jsonify({
|
||||
"logged_in": True,
|
||||
"user": {"id": session["user_id"], "username": session["username"], "display_name": session["display_name"], "role": session["role"]},
|
||||
@@ -264,7 +263,6 @@ def bootstrap():
|
||||
allowed = session.get("tenants", [])
|
||||
allowed = [t if t != "总工作台" else "科普·无界" for t in allowed]
|
||||
allowed = [t if t != "学会·无界" else "学术·无界" for t in allowed]
|
||||
allowed = [t for t in allowed if t != "总览" and t != "总工作台"]
|
||||
if tenant not in allowed:
|
||||
tenant = allowed[0] if allowed else ""
|
||||
conn = db()
|
||||
@@ -731,3 +729,177 @@ def delete_file(file_id):
|
||||
@bp.route("/api/health")
|
||||
def health():
|
||||
return jsonify({"ok": True, "service": "opc-manager"})
|
||||
|
||||
# ---------- 总览聚合 API ----------
|
||||
|
||||
OVERVIEW_TENANTS = ["学术·无界", "科普·无界", "科研·无界", "医患·无界", "MCN·无界"]
|
||||
|
||||
|
||||
def _aggregate_monthly_from_json(conn, table, tenants):
|
||||
"""聚合多个 tenant 的表 (含 budget_data/expense_data JSON) 到 12 个月数组"""
|
||||
_year = date.today().year
|
||||
months = [f"{_year}-{m:02d}" for m in range(1, 13)]
|
||||
rev, gross, payment, cost, paid = [0]*12, [0]*12, [0]*12, [0]*12, [0]*12
|
||||
sign_total = 0
|
||||
sign_cnt = 0
|
||||
|
||||
for tenant in tenants:
|
||||
pfs = rows(conn, f"SELECT sign_amount, sign_month, budget_data, expense_data FROM {table} WHERE tenant=?", [tenant])
|
||||
for pf in pfs:
|
||||
sm = pf.get("sign_month") or ""
|
||||
if sm.startswith(str(_year)):
|
||||
sign_total += float(pf.get("sign_amount") or 0)
|
||||
sign_cnt += 1
|
||||
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)
|
||||
rev[i] += float(b.get("rev") or 0)
|
||||
gross[i] += float(b.get("gross") or 0)
|
||||
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)
|
||||
cost[i] += float(e.get("cost") or 0)
|
||||
paid[i] += float(e.get("paid") or 0)
|
||||
|
||||
return {
|
||||
"months": months,
|
||||
"rev": rev, "gross": gross, "payment": payment, "cost": cost, "paid": paid,
|
||||
"cashflow": [payment[i] - paid[i] for i in range(12)],
|
||||
"sign_total": sign_total, "sign_cnt": sign_cnt,
|
||||
}
|
||||
|
||||
|
||||
def _aggregate_expenses(conn, table, tenants):
|
||||
"""聚合多个 tenant 的平台费用到 12 个月"""
|
||||
_year = date.today().year
|
||||
months = [f"{_year}-{m:02d}" for m in range(1, 13)]
|
||||
amount = [0]*12
|
||||
incurred = [0]*12
|
||||
for tenant in tenants:
|
||||
exps = rows(conn, f"SELECT expense_month, amount, incurred_amount FROM {table} WHERE tenant=? AND expense_month LIKE ?", [tenant, f"{_year}-%"])
|
||||
for e in exps:
|
||||
m = (e.get("expense_month") or "")[:7]
|
||||
if m in months:
|
||||
i = months.index(m)
|
||||
amount[i] += float(e.get("amount") or 0)
|
||||
incurred[i] += float(e.get("incurred_amount") or 0)
|
||||
return amount, incurred
|
||||
|
||||
|
||||
def _build_overview_result(agg, exp_amount, exp_incurred):
|
||||
"""根据聚合数据构建前端使用的 overview JSON"""
|
||||
months = agg["months"]
|
||||
rev = agg["rev"]
|
||||
gross = agg["gross"]
|
||||
payment = agg["payment"]
|
||||
cost = agg["cost"]
|
||||
paid = agg["paid"]
|
||||
cf = agg["cashflow"]
|
||||
|
||||
# 部门损益情况
|
||||
dept_profit = [gross[i] - exp_amount[i] for i in range(12)]
|
||||
dept = {
|
||||
"project_gross": gross,
|
||||
"dept_expense": exp_amount,
|
||||
"profit": dept_profit,
|
||||
"annual": [sum(gross), sum(exp_amount), sum(dept_profit)],
|
||||
}
|
||||
|
||||
# 部门现金流
|
||||
bl_cf = [cf[i] - exp_incurred[i] for i in range(12)]
|
||||
dept_cf = {
|
||||
"project_cf": cf,
|
||||
"dept_cf_item": exp_incurred,
|
||||
"bl_cf": bl_cf,
|
||||
"annual": [sum(cf), sum(exp_incurred), sum(bl_cf)],
|
||||
}
|
||||
|
||||
# 项目经营详情
|
||||
project = {
|
||||
"sign": agg["sign_total"],
|
||||
"revenue": rev,
|
||||
"gross": gross,
|
||||
"cost": cost,
|
||||
"paid": paid,
|
||||
"payment": payment,
|
||||
"cashflow": cf,
|
||||
"annual": [agg["sign_total"], sum(rev), sum(gross), sum(cost), sum(paid), sum(payment), sum(cf)],
|
||||
}
|
||||
|
||||
return {"dept": dept, "dept_cf": dept_cf, "project": project, "months": months}
|
||||
|
||||
|
||||
def _quarterly_from_monthly(arr12):
|
||||
"""将 12 个月数组转换为 4 个季度"""
|
||||
q_ranges = [(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 q_ranges]
|
||||
|
||||
|
||||
@bp.route("/api/overview/plan-monthly")
|
||||
@login_required
|
||||
def overview_plan_monthly():
|
||||
conn = db()
|
||||
agg = _aggregate_monthly_from_json(conn, "plan_finances", OVERVIEW_TENANTS)
|
||||
exp_amount, exp_incurred = _aggregate_expenses(conn, "plan_expense_records", OVERVIEW_TENANTS)
|
||||
conn.close()
|
||||
return jsonify(_build_overview_result(agg, exp_amount, exp_incurred))
|
||||
|
||||
|
||||
@bp.route("/api/overview/plan-quarterly")
|
||||
@login_required
|
||||
def overview_plan_quarterly():
|
||||
conn = db()
|
||||
agg = _aggregate_monthly_from_json(conn, "plan_finances", OVERVIEW_TENANTS)
|
||||
exp_amount, exp_incurred = _aggregate_expenses(conn, "plan_expense_records", OVERVIEW_TENANTS)
|
||||
conn.close()
|
||||
# Convert monthly to quarterly
|
||||
result = _build_overview_result(agg, exp_amount, exp_incurred)
|
||||
for key in ["revenue","gross","payment","cost","paid","cashflow"]:
|
||||
result["project"][key] = _quarterly_from_monthly(result["project"][key])
|
||||
result["dept"]["project_gross"] = _quarterly_from_monthly(result["dept"]["project_gross"])
|
||||
result["dept"]["dept_expense"] = _quarterly_from_monthly(result["dept"]["dept_expense"])
|
||||
result["dept"]["profit"] = _quarterly_from_monthly(result["dept"]["profit"])
|
||||
result["dept_cf"]["project_cf"] = _quarterly_from_monthly(result["dept_cf"]["project_cf"])
|
||||
result["dept_cf"]["dept_cf_item"] = _quarterly_from_monthly(result["dept_cf"]["dept_cf_item"])
|
||||
result["dept_cf"]["bl_cf"] = _quarterly_from_monthly(result["dept_cf"]["bl_cf"])
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@bp.route("/api/overview/finance-monthly")
|
||||
@login_required
|
||||
def overview_finance_monthly():
|
||||
conn = db()
|
||||
agg = _aggregate_monthly_from_json(conn, "project_finances", OVERVIEW_TENANTS)
|
||||
exp_amount, exp_incurred = _aggregate_expenses(conn, "expense_records", OVERVIEW_TENANTS)
|
||||
conn.close()
|
||||
return jsonify(_build_overview_result(agg, exp_amount, exp_incurred))
|
||||
|
||||
|
||||
@bp.route("/api/overview/finance-quarterly")
|
||||
@login_required
|
||||
def overview_finance_quarterly():
|
||||
conn = db()
|
||||
agg = _aggregate_monthly_from_json(conn, "project_finances", OVERVIEW_TENANTS)
|
||||
exp_amount, exp_incurred = _aggregate_expenses(conn, "expense_records", OVERVIEW_TENANTS)
|
||||
conn.close()
|
||||
result = _build_overview_result(agg, exp_amount, exp_incurred)
|
||||
for key in ["revenue","gross","payment","cost","paid","cashflow"]:
|
||||
result["project"][key] = _quarterly_from_monthly(result["project"][key])
|
||||
result["dept"]["project_gross"] = _quarterly_from_monthly(result["dept"]["project_gross"])
|
||||
result["dept"]["dept_expense"] = _quarterly_from_monthly(result["dept"]["dept_expense"])
|
||||
result["dept"]["profit"] = _quarterly_from_monthly(result["dept"]["profit"])
|
||||
result["dept_cf"]["project_cf"] = _quarterly_from_monthly(result["dept_cf"]["project_cf"])
|
||||
result["dept_cf"]["dept_cf_item"] = _quarterly_from_monthly(result["dept_cf"]["dept_cf_item"])
|
||||
result["dept_cf"]["bl_cf"] = _quarterly_from_monthly(result["dept_cf"]["bl_cf"])
|
||||
return jsonify(result)
|
||||
|
||||
Reference in New Issue
Block a user