From 8430c93b97a705f3b76f02e7257ee56ab0a018e4 Mon Sep 17 00:00:00 2001 From: mac Date: Mon, 6 Jul 2026 13:04:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20+=20=E8=B4=A2=E5=8A=A1=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E9=87=8D=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 采购模块: - 新增 purchase_records 表 + CRUD 路由 + bootstrap 数据加载 - 前端采购管理页面: 表格 + 弹窗新增/编辑 + 侧边栏入口 财务模块: - 表格新增签约金额、执行率、回款率、付款率、毛利、毛利率列 - 状态/日期筛选统一为无边框下拉框,移至页面顶部工具栏 - 删除 metric 卡片,新增 11 张汇总卡片(签约金额~现金流) - 表格合计行移至表头下方,后改为汇总卡片 - 所有单元格统一水平/垂直居中 - 修复季度视图 gross 变量缺失、合计行遗漏 --- backend/migrations/tables.py | 14 +++++ backend/routes.py | 6 +- static/modules/finance.js | 43 +++++++-------- static/modules/purchase.js | 103 +++++++++++++++++++++++++++++++++++ static/modules/utils.js | 1 + templates/index.html | 6 ++ 6 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 static/modules/purchase.js diff --git a/backend/migrations/tables.py b/backend/migrations/tables.py index 2bc68f5..df6e67e 100644 --- a/backend/migrations/tables.py +++ b/backend/migrations/tables.py @@ -146,6 +146,20 @@ def migrate_create_tables(): budget_data TEXT, created_at VARCHAR(30) NOT NULL DEFAULT '', updated_at VARCHAR(30) NOT NULL DEFAULT '' +)""", + """CREATE TABLE IF NOT EXISTS purchase_records ( + id INT AUTO_INCREMENT PRIMARY KEY, + tenant VARCHAR(100) NOT NULL DEFAULT '科普·无界', + project_name VARCHAR(200) NOT NULL DEFAULT '', + purchase_item VARCHAR(300) NOT NULL DEFAULT '', + supplier VARCHAR(200) NOT NULL DEFAULT '', + amount DOUBLE NOT NULL DEFAULT 0, + purchase_date VARCHAR(30) NOT NULL DEFAULT '', + status VARCHAR(50) NOT NULL DEFAULT '待审批', + category VARCHAR(100) NOT NULL DEFAULT '', + notes VARCHAR(1000) NOT NULL DEFAULT '', + created_at VARCHAR(30) NOT NULL DEFAULT '', + updated_at VARCHAR(30) NOT NULL DEFAULT '' )""", ] diff --git a/backend/routes.py b/backend/routes.py index 26ce6bd..4dc6f3f 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -24,6 +24,7 @@ TABLES = { "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", "priority", "tenant"]), "projectFinances": ("project_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "owner", "total_rev", "total_gross", "total_payment", "total_cost", "total_paid", "budget_data", "start_date", "end_date", "task_type", "task_count", "service_fee_standard", "project_manager", "task_data", "project_code", "contact_name", "contact_phone", "other_info"]), + "purchase": ("purchase_records", ["project_name", "purchase_item", "supplier", "amount", "purchase_date", "status", "category", "notes", "tenant"]), } # ---------- 鉴权装饰器 ---------- @@ -321,7 +322,7 @@ 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": [], "tenant": tenant, "tenants": allowed}) + return jsonify({"summary": summary, "sales": [], "proposals": [], "operations": [], "products": [], "finance": [], "projectFinances": [], "financeMonthly": merged_monthly, "tasks": [], "purchase": [], "tenant": tenant, "tenants": allowed}) def q(sql, *args): return rows(conn, sql, args) @@ -332,6 +333,7 @@ def bootstrap(): 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) + purchase = q("SELECT * FROM purchase_records WHERE tenant=? ORDER BY id DESC", tenant) signed_pfs = [x for x in pfs if x["status"] == "已签约"] def parse_budget(pf): @@ -434,7 +436,7 @@ def bootstrap(): "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, "projectFinances": pfs, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "tenant": tenant, "tenants": allowed}) + return jsonify({"summary": summary, "sales": sales, "proposals": proposals, "operations": operations, "products": products, "finance": finance, "projectFinances": pfs, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "purchase": purchase, "tenant": tenant, "tenants": allowed}) finally: conn.close() diff --git a/static/modules/finance.js b/static/modules/finance.js index 4881e3b..3fe5b28 100644 --- a/static/modules/finance.js +++ b/static/modules/finance.js @@ -99,20 +99,15 @@ function renderFinance() { } })(); const sm = pf.sign_month || ""; - const signMonthCell = `${sm || '—'}`; - return `${esc(pf.customer_name)}${signMonthCell}${money(pf.sign_amount)}${mCols}${totalCol}`; + const signMonthCell = `${sm || '—'}`; + return `${esc(pf.customer_name)}${signMonthCell}${money(pf.sign_amount)}${mCols}${totalCol}`; }; const finHeaderBase = `|筛选:状态:`; const finAddBtn = ``; document.querySelector("#finance").innerHTML = `
-
- ${[["已签项目","" + signed.length,"file-check-2"],["签约金额",moneyWan(sumSign),"coins"],["待签项目","" + pending.length,"file-question"],["待签金额",moneyWan(sumPending),"hourglass"]].map(([l,v,icon]) => `
${l}${v}
`).join("")} -
-
- ${[["本月确收",moneyInt(thisMonthRev),"trending-up"],["本月毛利",moneyInt(thisMonthGross),"percent"],["本月回款",moneyInt(monthPayment),"wallet"],["本月应付",moneyInt(monthCost),"receipt"],["本月现金流",moneyInt(monthCashflow),"repeat"]].map(([l,v,icon]) => `
${l}${v}
`).join("")} -
+
${finHeaderBase}
${finAddBtn}
@@ -114,6 +119,7 @@ +