diff --git a/backend/routes.py b/backend/routes.py index 1acf410..89b0ba2 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -257,6 +257,7 @@ def index(): @bp.route("/api/bootstrap") def bootstrap(): + """轻量级 bootstrap:只返回 summary + financeMonthly(首页用),其他模块按需加载""" if "user_id" not in session: return jsonify({"error": "未登录"}), 401 _year = date.today().year @@ -270,154 +271,20 @@ def bootstrap(): tenant = allowed[0] conn = db() try: - # 总工作台:聚合所有工作台的首页数据 if tenant == "总览": real_tenants = [t for t in allowed if t != "总览"] all_metrics = [] all_monthly = [] all_recent = [] for t in real_tenants: - t_pfs = rows(conn, "SELECT * FROM project_finances WHERE tenant=? ORDER BY id DESC", [t]) t_ops = attach_common(conn, "operations", rows(conn, "SELECT * FROM operation_projects WHERE tenant=? ORDER BY id ASC", [t])) t_sales = attach_common(conn, "sales", rows(conn, "SELECT * FROM sales_leads WHERE tenant=? ORDER BY id DESC", [t])) t_products = attach_common(conn, "products", rows(conn, "SELECT * FROM product_versions WHERE tenant=? ORDER BY id DESC", [t])) - t_expense = rows(conn, "SELECT * FROM expense_records WHERE tenant=?", [t]) t_proposals = attach_common(conn, "proposals", rows(conn, "SELECT * FROM business_proposals WHERE tenant=? ORDER BY id DESC", [t])) - t_signed_pfs = t_pfs - def t_parse_budget(pf): - try: - budget = json.loads(pf.get("budget_data") or "[]") - except (json.JSONDecodeError, TypeError): - budget = [] - return {(b.get("month") or "").replace("-", "_"): b for b in budget} - t_bm = {pf["id"]: t_parse_budget(pf) for pf in t_pfs} - def t_sum_budget(field, months_range): - total = 0 - for pf in t_pfs: - bm = t_bm.get(pf["id"], {}) - for m in months_range: - b = bm.get(f"{_year}_{m:02d}") - if b: - total += float(b.get(field) or 0) - return total - - def t_parse_expense(pf): - try: - ed = json.loads(pf.get("expense_data") or "[]") - except (json.JSONDecodeError, TypeError): - ed = [] - return {(e.get("month") or "").replace("-", "_"): e for e in ed} - - t_em = {pf["id"]: t_parse_expense(pf) for pf in t_pfs} - - def t_sum_expense(field, months_range): - total = 0 - for pf in t_pfs: - em = t_em.get(pf["id"], {}) - for m in months_range: - e = em.get(f"{_year}_{m:02d}") - if e: - total += float(e.get(field) or 0) - return total - - _now_month = date.today().month - _q_start = ((_now_month - 1) // 3) * 3 + 1 - _q_range = range(_q_start, _q_start + 3) - _q_months = [f"{_year}-{m:02d}" for m in _q_range] - _prev_q_start = ((_now_month - 4) // 3) * 3 + 1 - _prev_q_range = range(max(_prev_q_start, 1), _prev_q_start + 3) - _prev_q_months = [f"{_year}-{m:02d}" for m in _prev_q_range] - _prev_month = _now_month - 1 if _now_month > 1 else None - all_metrics.append({ - "total_projects": len(t_signed_pfs), - "total_proposals": len(t_ops), - "total_products": len(t_proposals), - "upcoming_products": len(t_products), - "signed_amount": sum(x["sign_amount"] or 0 for x in t_pfs), - "signed_annual": sum(x["sign_amount"] or 0 for x in t_pfs), - "signed_q2": sum(x["sign_amount"] or 0 for x in t_pfs if (x.get("sign_month") or "")[:7] in _q_months), - "signed_month": sum(x["sign_amount"] or 0 for x in t_pfs if (x.get("sign_month") or "")[:7] == f"{_year}-{_now_month:02d}"), - "signed_prev_q": sum(x["sign_amount"] or 0 for x in t_pfs if (x.get("sign_month") or "")[:7] in _prev_q_months), - "signed_prev_month": sum(x["sign_amount"] or 0 for x in t_pfs if (x.get("sign_month") or "")[:7] == f"{_year}-{_prev_month:02d}") if _prev_month else 0, - "revenue_annual": t_sum_budget("rev", range(1, 13)), - "revenue_q2": t_sum_budget("rev", _q_range), - "monthly_revenue": t_sum_budget("rev", [_now_month]), - "revenue_prev_q": t_sum_budget("rev", _prev_q_range), - "revenue_prev_month": t_sum_budget("rev", [_prev_month]) if _prev_month else 0, - "gross_annual": t_sum_budget("gross", range(1, 13)), - "gross_q2": t_sum_budget("gross", _q_range), - "monthly_net_profit": t_sum_budget("gross", [_now_month]), - "gross_prev_q": t_sum_budget("gross", _prev_q_range), - "gross_prev_month": t_sum_budget("gross", [_prev_month]) if _prev_month else 0, - "payment_annual": t_sum_budget("payment", range(1, 13)), - "payment_q2": t_sum_budget("payment", _q_range), - "payment_month": t_sum_budget("payment", [_now_month]), - "payment_prev_q": t_sum_budget("payment", _prev_q_range), - "payment_prev_month": t_sum_budget("payment", [_prev_month]) if _prev_month else 0, - "cost_annual": t_sum_expense("cost", range(1, 13)), - "cost_q2": t_sum_expense("cost", _q_range), - "cost_month": t_sum_expense("cost", [_now_month]), - "cost_prev_q": t_sum_expense("cost", _prev_q_range), - "cost_prev_month": t_sum_expense("cost", [_prev_month]) if _prev_month else 0, - }) - # 费用汇总(从 expense_records 表计算) - def t_expense_sum(m_range): - total = 0 - for r in t_expense: - if (r.get("status") or "") == "暂不计入": continue - m = (r.get("expense_month") or "").strip() - if not m or "-" not in m: continue - try: - if int(m.split("-")[1]) in m_range: - total += float(r.get("amount") or 0) - except: pass - return total - all_metrics[-1]["expense_annual"] = t_expense_sum(range(1, 13)) - all_metrics[-1]["expense_q2"] = t_expense_sum(_q_range) - all_metrics[-1]["expense_month"] = t_expense_sum([_now_month]) - all_metrics[-1]["expense_prev_q"] = t_expense_sum(_prev_q_range) - all_metrics[-1]["expense_prev_month"] = t_expense_sum([_prev_month]) if _prev_month else 0 - all_metrics[-1]["paid_annual"] = t_sum_expense("paid", range(1, 13)) - all_metrics[-1]["paid_q2"] = t_sum_expense("paid", _q_range) - all_metrics[-1]["paid_month"] = t_sum_expense("paid", [_now_month]) - all_metrics[-1]["paid_prev_q"] = t_sum_expense("paid", _prev_q_range) - all_metrics[-1]["paid_prev_month"] = t_sum_expense("paid", [_prev_month]) if _prev_month else 0 - all_metrics[-1]["proj_expense_annual"] = t_sum_budget("expense", range(1, 13)) - all_metrics[-1]["proj_expense_q2"] = t_sum_budget("expense", _q_range) - all_metrics[-1]["proj_expense_month"] = t_sum_budget("expense", [_now_month]) - all_metrics[-1]["proj_expense_prev_q"] = t_sum_budget("expense", _prev_q_range) - all_metrics[-1]["proj_expense_prev_month"] = t_sum_budget("expense", [_prev_month]) if _prev_month else 0 - all_metrics[-1]["cashflow_annual"] = all_metrics[-1]["payment_annual"] - all_metrics[-1]["paid_annual"] - all_metrics[-1]["cashflow_q2"] = all_metrics[-1]["payment_q2"] - all_metrics[-1]["paid_q2"] - all_metrics[-1]["cashflow_month"] = all_metrics[-1]["payment_month"] - all_metrics[-1]["paid_month"] - all_metrics[-1]["cashflow_prev_q"] = all_metrics[-1]["payment_prev_q"] - all_metrics[-1]["paid_prev_q"] - all_metrics[-1]["cashflow_prev_month"] = all_metrics[-1]["payment_prev_month"] - all_metrics[-1]["paid_prev_month"] - def t_expense_paid_sum(m_range): - total = 0 - for r in t_expense: - if (r.get("status") or "") == "暂不计入": continue - m = (r.get("expense_month") or "").strip() - if not m or "-" not in m: continue - try: - if int(m.split("-")[1]) in m_range: - total += float(r.get("incurred_amount") or 0) - except: pass - return total - all_metrics[-1]["expense_paid_annual"] = t_expense_paid_sum(range(1, 13)) - all_metrics[-1]["expense_paid_q2"] = t_expense_paid_sum(_q_range) - all_metrics[-1]["expense_paid_month"] = t_expense_paid_sum([_now_month]) - all_metrics[-1]["expense_paid_prev_q"] = t_expense_paid_sum(_prev_q_range) - all_metrics[-1]["expense_paid_prev_month"] = t_expense_paid_sum([_prev_month]) if _prev_month else 0 - all_metrics[-1]["dept_cf_annual"] = all_metrics[-1]["cashflow_annual"] - all_metrics[-1]["expense_paid_annual"] - all_metrics[-1]["dept_cf_q2"] = all_metrics[-1]["cashflow_q2"] - all_metrics[-1]["expense_paid_q2"] - all_metrics[-1]["dept_cf_month"] = all_metrics[-1]["cashflow_month"] - all_metrics[-1]["expense_paid_month"] - all_metrics[-1]["dept_cf_prev_q"] = all_metrics[-1]["cashflow_prev_q"] - all_metrics[-1]["expense_paid_prev_q"] - all_metrics[-1]["dept_cf_prev_month"] = all_metrics[-1]["cashflow_prev_month"] - all_metrics[-1]["expense_paid_prev_month"] - all_metrics[-1]["profit_annual"] = all_metrics[-1]["gross_annual"] - all_metrics[-1]["proj_expense_annual"] - all_metrics[-1]["profit_q2"] = all_metrics[-1]["gross_q2"] - all_metrics[-1]["proj_expense_q2"] - all_metrics[-1]["profit_month"] = all_metrics[-1]["monthly_net_profit"] - all_metrics[-1]["proj_expense_month"] - all_metrics[-1]["profit_prev_q"] = all_metrics[-1]["gross_prev_q"] - all_metrics[-1]["proj_expense_prev_q"] - all_metrics[-1]["profit_prev_month"] = all_metrics[-1]["gross_prev_month"] - all_metrics[-1]["proj_expense_prev_month"] + t_pfs = rows(conn, "SELECT * FROM project_finances WHERE tenant=? ORDER BY id DESC", [t]) + t_expense = rows(conn, "SELECT * FROM expense_records WHERE tenant=? ORDER BY id DESC", [t]) + t_metrics = compute_metrics(t_pfs, t_ops, t_sales, t_products, t_proposals, t_expense, _year, conn, t) + all_metrics.append(t_metrics) all_monthly.append(monthly_finance(conn, t)) all_recent.extend(rows(conn, "SELECT * FROM follow_up_records WHERE tenant=? ORDER BY id DESC LIMIT 4", [t])) agg = {} @@ -435,20 +302,15 @@ def bootstrap(): "recent": sorted(all_recent, key=lambda x: x.get("id", 0), reverse=True)[:8], "risks": [], } - return jsonify({"summary": summary, "sales": [], "proposals": [], "operations": [], "products": [], "finance": [], "projectFinances": [], "financeMonthly": merged_monthly, "tasks": [], "expense": [], "planFinances": [], "planExpense": [], "tenant": tenant, "tenants": allowed}) + return jsonify({"summary": summary, "financeMonthly": merged_monthly, "tenant": tenant, "tenants": allowed}) - def q(sql, *args): - return rows(conn, sql, args) - sales = attach_common(conn, "sales", q("SELECT * FROM sales_leads WHERE tenant=? ORDER BY id DESC", tenant)) - proposals = attach_common(conn, "proposals", q("SELECT * FROM business_proposals WHERE tenant=? ORDER BY id DESC", tenant)) - operations = attach_common(conn, "operations", q("SELECT * FROM operation_projects WHERE tenant=? ORDER BY id ASC", tenant)) - products = attach_common(conn, "products", q("SELECT * FROM product_versions WHERE tenant=? ORDER BY id DESC", tenant)) - finance = q("SELECT * FROM finance_records WHERE tenant=? ORDER BY month DESC, id DESC", tenant) - tasks = q("SELECT * FROM project_tasks WHERE tenant=? ORDER BY phase, sort_order, id", tenant) - pfs = q("SELECT * FROM project_finances WHERE tenant=? ORDER BY id DESC", tenant) - expense = q("SELECT * FROM expense_records WHERE tenant=? ORDER BY id DESC", tenant) - planPfs = q("SELECT * FROM plan_finances WHERE tenant=? ORDER BY id DESC", tenant) - planExpense = q("SELECT * FROM plan_expense_records WHERE tenant=? ORDER BY id DESC", tenant) + # 普通工作台:只查 summary 所需的数据(project_finances + operations + sales + products + proposals + expense) + pfs = rows(conn, "SELECT * FROM project_finances WHERE tenant=? ORDER BY id DESC", [tenant]) + operations = attach_common(conn, "operations", rows(conn, "SELECT * FROM operation_projects WHERE tenant=? ORDER BY id ASC", [tenant])) + sales = attach_common(conn, "sales", rows(conn, "SELECT * FROM sales_leads WHERE tenant=? ORDER BY id DESC", [tenant])) + products = attach_common(conn, "products", rows(conn, "SELECT * FROM product_versions WHERE tenant=? ORDER BY id DESC", [tenant])) + proposals = attach_common(conn, "proposals", rows(conn, "SELECT * FROM business_proposals WHERE tenant=? ORDER BY id DESC", [tenant])) + expense = rows(conn, "SELECT * FROM expense_records WHERE tenant=? ORDER BY id DESC", [tenant]) signed_pfs = pfs def parse_budget(pf): @@ -581,7 +443,7 @@ def bootstrap(): pipeline_amount = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_status"] not in ["已签约","已丢单","已归档","已完成"]) signed_not_executed = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_type"] == "execution" and x["execution_progress"] < 100) summary = { - "project_name": "科普(慰心斋)", + "project_name": tenant, "metrics": { "p0_customers": len([x for x in sales if x["priority"] == "P0"]), "active_sales": len([x for x in sales if x["status"] in ["待跟进", "跟进中", "方案中", "商务谈判"]]), @@ -656,10 +518,10 @@ def bootstrap(): "proj_expense_prev_month": proj_expense_prev_month, "signed_not_executed": signed_not_executed, }, - "recent": q("SELECT * FROM follow_up_records WHERE tenant=? ORDER BY id DESC LIMIT 8", tenant), + "recent": rows(conn, "SELECT * FROM follow_up_records WHERE tenant=? ORDER BY id DESC LIMIT 8", [tenant]), "risks": [{"title": "执行提醒", "content": x["next_action"]} for x in operations if x["next_action"]][:5], } - return jsonify({"summary": summary, "sales": sales, "proposals": proposals, "operations": operations, "products": products, "finance": finance, "projectFinances": pfs, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "expense": expense, "planFinances": planPfs, "planExpense": planExpense, "tenant": tenant, "tenants": allowed}) + return jsonify({"summary": summary, "financeMonthly": monthly_finance(conn, tenant), "tenant": tenant, "tenants": allowed}) finally: conn.close() diff --git a/static/modules/finance.js b/static/modules/finance.js index 2766e98..5d30463 100644 --- a/static/modules/finance.js +++ b/static/modules/finance.js @@ -110,11 +110,12 @@ function renderFinance() { const nowD = new Date(); const activeQ = state.finQuarter !== undefined ? state.finQuarter : Math.floor(nowD.getMonth() / 3); const activeMonths = qMonths[activeQ]; - const finHeaderBase = `
| 月份 | 确收 | 毛利 | 回款 | 成本 | 支出 | 现金流 |
|---|
OPC Manager v1.2.0.3
+OPC Manager v1.2.0.4