diff --git a/backend/flask_app.py b/backend/flask_app.py index d54c061..8795808 100644 --- a/backend/flask_app.py +++ b/backend/flask_app.py @@ -328,17 +328,20 @@ def monthly_finance(conn, tenant="科普·无界"): months.append(m.strftime("%Y-%m")) data = [] for month in months: - def s(cat): - return one(conn, "SELECT COALESCE(SUM(amount),0) AS v FROM finance_records WHERE month=? AND category=? AND tenant=?", (month, cat, tenant))["v"] - revenue = s("确认收入") + s("签单") - labor = s("人力成本") - expense = s("费用") - purchase = s("外部采购") - net = revenue - labor - expense - purchase + col_month = month.replace("-", "_") + col_rev = f"rev_{col_month}" + col_gross = f"gross_{col_month}" + # Only project_finances has columns for 2026-06 through 2026-09 + if month in ["2026-06", "2026-07", "2026-08", "2026-09"]: + revenue = one(conn, f"SELECT COALESCE(SUM({col_rev}),0) AS v FROM project_finances WHERE tenant=?", (tenant,))["v"] + gross = one(conn, f"SELECT COALESCE(SUM({col_gross}),0) AS v FROM project_finances WHERE tenant=?", (tenant,))["v"] + else: + revenue = 0 + gross = 0 data.append({ "month": month, "revenue": revenue, - "labor": labor, "expense": expense, "purchase": purchase, - "net_profit": net, + "labor": 0, "expense": 0, "purchase": 0, + "net_profit": gross, }) return data @@ -361,24 +364,17 @@ def bootstrap(): 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) current_month = "2026-06" - # Finance aggregates - def sum_cat(months, cat): - return sum(x["amount"] for x in finance if x["month"] in months and x.get("category") == cat) - months_2026 = [f"2026-{m:02d}" for m in range(1,7)] - months_q2 = ["2026-04","2026-05","2026-06"] - rev_annual = sum_cat(months_2026, "确认收入") + sum_cat(months_2026, "签单") - labor_annual = sum_cat(months_2026, "人力成本") - expense_annual = sum_cat(months_2026, "费用") - purchase_annual = sum_cat(months_2026, "外部采购") - rev_q2 = sum_cat(months_q2, "确认收入") + sum_cat(months_q2, "签单") - labor_q2 = sum_cat(months_q2, "人力成本") - expense_q2 = sum_cat(months_q2, "费用") - purchase_q2 = sum_cat(months_q2, "外部采购") - rev_month = sum_cat([current_month], "确认收入") + sum_cat([current_month], "签单") - labor_month = sum_cat([current_month], "人力成本") - expense_month = sum_cat([current_month], "费用") - purchase_month = sum_cat([current_month], "外部采购") + # Finance aggregates — from project_finances (project-based) + def pf_sum(field): + return sum(x[field] or 0 for x in pfs) + rev_month = pf_sum("rev_2026_06") + gross_month = pf_sum("gross_2026_06") + rev_q2 = pf_sum("rev_2026_06") + gross_q2 = pf_sum("gross_2026_06") + rev_annual = rev_q2 + gross_annual = gross_q2 # Contract aggregates — time-based signed_amount = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_status"] == "已签约") from datetime import date @@ -402,8 +398,8 @@ def bootstrap(): "execution_projects": len([x for x in operations if x["project_type"] == "execution"]), "risk_projects": len([x for x in operations if x["project_status"] == "有风险" or x["risks"]]), "monthly_revenue": rev_month, - "monthly_net_profit": rev_month - labor_month - expense_month - purchase_month, - "monthly_gross": rev_month - labor_month - expense_month - purchase_month, + "monthly_net_profit": gross_month, + "monthly_gross": gross_month, "upcoming_products": len([x for x in products if x["status"] in ["规划中", "设计中", "开发中", "测试中"]]), "total_projects": len(operations), "total_proposals": len(proposals), @@ -416,14 +412,14 @@ def bootstrap(): "pipeline_amount": pipeline_amount, "revenue_annual": rev_annual, "revenue_q2": rev_q2, - "gross_annual": rev_annual - labor_annual - expense_annual - purchase_annual, - "gross_q2": rev_q2 - labor_q2 - expense_q2 - purchase_q2, + "gross_annual": gross_annual, + "gross_q2": gross_q2, "signed_not_executed": signed_not_executed, }, "recent": q("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, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "tenant": tenant, "tenants": ["科普·无界","科研·无界","医患·无界"]}) + return jsonify({"summary": summary, "sales": sales, "proposals": proposals, "operations": operations, "products": products, "finance": finance, "projectFinances": pfs, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "tenant": tenant, "tenants": ["科普·无界","科研·无界","医患·无界"]}) finally: conn.close() @@ -435,6 +431,7 @@ TABLES = { "products": ("product_versions", ["product_name", "version", "version_goal", "feature_list", "launch_date", "status", "platform", "notes", "tenant"]), "finance": ("finance_records", ["month", "project_name", "record_type", "category", "amount", "occurred_date", "notes", "tenant"]), "tasks": ("project_tasks", ["project_id", "phase", "milestone", "task", "owner", "due_date", "blockers", "notes", "status", "sort_order", "tenant"]), + "projectFinances": ("project_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "rev_2026_06", "rev_2026_07", "rev_2026_08", "rev_2026_09", "gross_2026_06", "gross_2026_07", "gross_2026_08", "gross_2026_09"]), } diff --git a/static/app.js b/static/app.js index aae6b33..3584e1f 100644 --- a/static/app.js +++ b/static/app.js @@ -232,14 +232,18 @@ window.submitTaskForm = async (event, projectId) => { }; window.createFinance = async (event) => { event.preventDefault(); - const data = Object.fromEntries(new FormData(event.currentTarget).entries()); - // Map form: month(date)→month(YYYY-MM), category→category, amount, tenant - data.month = data.month.substring(0, 7); - data.project_name = state.tenant; + const form = event.currentTarget; + const data = Object.fromEntries(new FormData(form).entries()); data.tenant = state.tenant; - data.record_type = "revenue"; // default, will be adjusted by category + data.sign_amount = parseFloat(data.sign_amount) || 0; + for (const m of ["2026-06","2026-07","2026-08","2026-09"]) { + const k = m.replace("-","_"); + data["rev_"+k] = parseFloat(data["rev_"+k]) || 0; + data["gross_"+k] = parseFloat(data["gross_"+k]) || 0; + } try { - await api("/api/finance", { method: "POST", body: JSON.stringify({ data }) }); + await api("/api/projectFinances", { method: "POST", body: JSON.stringify({ data }) }); + form.reset(); await load(); } catch (error) { alert("新增失败:" + error.message); @@ -421,16 +425,35 @@ function renderProducts() { } function renderFinance() { - const rows = state.data.finance.map((x) => [x.month, x.category, money(x.amount), text(x.notes)]); + const pfs = state.data.projectFinances || []; + const ops = state.data.operations || []; + const months = ["2026-06","2026-07","2026-08","2026-09"]; + const monthLabels = ["6月","7月","8月","9月"]; + + // Aggregates + const signed = pfs.filter(x => x.status === "已签单"); + const pending = pfs.filter(x => x.status !== "已签单"); + const sumSign = signed.reduce((s,x) => s + (x.sign_amount||0), 0); + const sumPending = pending.reduce((s,x) => s + (x.sign_amount||0), 0); + const monthRev = months.map(m => pfs.reduce((s,x) => s + (x["rev_"+m.replace("-","_")]||0), 0)); + const monthGross = months.map(m => pfs.reduce((s,x) => s + (x["gross_"+m.replace("-","_")]||0), 0)); + + const renderPfRow = (pf) => { + const mCols = months.map(m => { + const rev = pf["rev_"+m.replace("-","_")] || 0; + const gross = pf["gross_"+m.replace("-","_")] || 0; + return `
${l}
${v}
| 客户 | 类型 | 状态 | 签约金额 | ${monthLabels.map(l => `${l} 确收/毛利 | `).join("")}销售 |
|---|