产品迭代表格化 + 财务总视图/月度视图 + 总工作台

产品迭代:
- 卡片改表格(10列),5个日期内联编辑,后端日期校验
- 表头排序,新增未开始状态,详情页耗时统计
- 删除 owner/platform/feature_list 字段

财务:
- 新增总视图和月度视图,去除确收/毛利和回款/应付视图
- 月度流水加已付列,费用改应付
- 月份选择器,表格居中对齐
- 去除流程项目/流程金额卡片

总工作台:
- 聚合所有工作台首页数据
- 只显示首页tab,隐藏4个模块卡片
This commit is contained in:
mac
2026-07-02 17:55:40 +08:00
parent fbff2e5f24
commit 34786ba9e5
6 changed files with 197 additions and 27 deletions

View File

@@ -67,7 +67,7 @@ def admin_required(f):
return decorated
ALL_TENANTS = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"]
ALL_TENANTS = ["总工作台", "科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"]
@app.route("/login")
def login_page():
@@ -90,7 +90,7 @@ def auth_login():
session["role"] = user["role"]
# 管理员可看所有工作台OPC负责人看分配的工作台
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]
@@ -113,10 +113,14 @@ def auth_logout():
def auth_me():
if "user_id" not in session:
return jsonify({"logged_in": False})
tenants = session.get("tenants", [])
# 确保总工作台始终在列表最前
if "总工作台" not in tenants:
tenants = ["总工作台"] + tenants
return jsonify({
"logged_in": True,
"user": {"id": session["user_id"], "username": session["username"], "display_name": session["display_name"], "role": session["role"]},
"tenants": session.get("tenants", []),
"tenants": tenants,
})
@@ -748,10 +752,88 @@ def bootstrap():
tenant = request.args.get("tenant", session.get("tenants", ["科普·无界"])[0])
# 验证用户是否有权限访问该 workbench
allowed = session.get("tenants", [])
if "总工作台" not in allowed:
allowed = ["总工作台"] + allowed
if tenant not in allowed:
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_proposals = attach_common(conn, "proposals", rows(conn, "SELECT * FROM business_proposals WHERE tenant=? ORDER BY id DESC", [t]))
t_signed_pfs = [x for x in t_pfs if x["status"] == "已签约"]
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"2026_{m:02d}")
if b:
total += float(b.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"2026-{m:02d}" for m in _q_range]
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 if x["status"] == "已签约"),
"signed_annual": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约"),
"signed_q2": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约" and (x.get("sign_month") or "")[:7] in _q_months),
"signed_month": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约" and (x.get("sign_month") or "")[:7] == f"2026-{_now_month:02d}"),
"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]),
"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]),
"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]),
"cost_annual": t_sum_budget("cost", range(1, 13)),
"cost_q2": t_sum_budget("cost", _q_range),
"cost_month": t_sum_budget("cost", [_now_month]),
})
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]))
# 合并 metrics
agg = {}
for key in ["total_projects","total_proposals","total_products","upcoming_products","signed_amount","signed_annual","signed_q2","signed_month","revenue_annual","revenue_q2","monthly_revenue","gross_annual","gross_q2","monthly_net_profit","payment_annual","payment_q2","payment_month","cost_annual","cost_q2","cost_month"]:
agg[key] = sum(m.get(key, 0) for m in all_metrics)
# 合并 monthly finance按月累加
merged_monthly = []
for i in range(12):
m = {"month": all_monthly[0][i]["month"] if all_monthly and len(all_monthly[0]) > i else f"2026-{i+1:02d}"}
for field in ["revenue","gross","payment","cost","sign"]:
m[field] = sum(tl[i][field] if i < len(tl) else 0 for tl in all_monthly)
merged_monthly.append(m)
summary = {
"project_name": "总工作台",
"metrics": agg,
"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})
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))
@@ -859,7 +941,7 @@ TABLES = {
"products": ("product_versions", ["product_name", "version", "version_goal", "priority", "start_date", "plan_date", "dev_done_date", "test_date", "launch_date", "status", "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", "priority", "tenant"]),
"projectFinances": ("project_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "owner", "total_rev", "total_gross", "budget_data"]),
"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"]),
}

View File

@@ -67,6 +67,14 @@ def migrate_add_columns():
_add_column_if_missing(conn, "project_finances", col,
f"ALTER TABLE project_finances ADD COLUMN {col} DOUBLE NOT NULL DEFAULT 0")
# project_finances 总视图字段:已回款 / 应付 / 已付
_add_column_if_missing(conn, "project_finances", "total_payment",
"ALTER TABLE project_finances ADD COLUMN total_payment DOUBLE NOT NULL DEFAULT 0")
_add_column_if_missing(conn, "project_finances", "total_cost",
"ALTER TABLE project_finances ADD COLUMN total_cost DOUBLE NOT NULL DEFAULT 0")
_add_column_if_missing(conn, "project_finances", "total_paid",
"ALTER TABLE project_finances ADD COLUMN total_paid DOUBLE NOT NULL DEFAULT 0")
conn.commit()
print("[migrate] 加列迁移完成")
finally: