产品迭代表格化 + 财务总视图/月度视图 + 总工作台
产品迭代: - 卡片改表格(10列),5个日期内联编辑,后端日期校验 - 表头排序,新增未开始状态,详情页耗时统计 - 删除 owner/platform/feature_list 字段 财务: - 新增总视图和月度视图,去除确收/毛利和回款/应付视图 - 月度流水加已付列,费用改应付 - 月份选择器,表格居中对齐 - 去除流程项目/流程金额卡片 总工作台: - 聚合所有工作台首页数据 - 只显示首页tab,隐藏4个模块卡片
This commit is contained in:
@@ -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"]),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user