Compare commits

..

19 Commits
v2.0 ... v3.1.4

Author SHA1 Message Date
mac
4911f24d40 v3.1.4 — 修复pfs变量丢失导致加载失败 2026-06-17 13:13:27 +08:00
mac
8c24abd53e v3.1.3 — 财务弹窗UI优化:分区卡片布局+月网格+圆角+更大尺寸 2026-06-17 13:12:39 +08:00
mac
0a7f70757d v3.1.2 — 财务弹窗按工作台显示项目和业务类型 2026-06-17 13:11:06 +08:00
mac
29dc7e040e v3.1.1 — 科普·无界新增"全品类科普"类型 2026-06-17 13:07:36 +08:00
mac
c8387011cc v3.1.0 — 财务分类重构:三大工作台13种业务类型+66条重新分配 2026-06-17 13:05:58 +08:00
mac
5061de70f8 v3.0.4 — 点击项目行弹出编辑弹窗+PUT保存 2026-06-17 11:27:29 +08:00
mac
ea3ba25da5 v3.0.3 — 修复:新增财务项目改为弹窗按钮+finFilter+createFinance修正 2026-06-17 11:17:12 +08:00
mac
bd7125fab8 v3.0.2 — 新增财务项目改为弹窗按钮+月度趋势缩减为6个月 2026-06-16 17:11:02 +08:00
mac
94dd1fe677 v3.0.1 — 财务项目明细已签/待签分Tab展示 2026-06-16 16:57:13 +08:00
mac
fa6c9b1711 v3.0 — 财务重构为项目财务视图:汇总卡片+项目明细+月度确收/毛利 2026-06-16 16:43:44 +08:00
mac
f4eacfafe2 v2.0.9 — 业务方案删除按钮改为通用deleteDrawerItem支持所有资源 2026-06-16 16:03:33 +08:00
mac
f8c816dc38 v2.0.8 — 任务行添加拖拽手柄图标(grip-vertical) 2026-06-16 16:01:54 +08:00
mac
e2d9049e45 v2.0.7 — 标题随项目切换动态变化:科普/科研/医患 OPC 工作台 2026-06-16 15:57:18 +08:00
mac
1b0049e342 v2.0.6 — X轴12月(前9+当前+后2) + Y轴万元 + 净利口径=确认收入-人力-费用-采购 2026-06-16 15:56:21 +08:00
mac
87a5d4f81d v2.0.5 — 财务曲线改为5类对应+月度净利=确认收入-人力成本-费用-外部采购 2026-06-16 15:54:24 +08:00
mac
d6ec7b24ec v2.0.4 — 曲线图默认折叠可展开,明细列表默认展示 2026-06-16 15:51:09 +08:00
mac
194c91cf25 v2.0.3 — 财务增加费用说明输入框 + 明细表默认折叠可展开 2026-06-16 15:49:35 +08:00
mac
68797e4fb5 v2.0.2 — 财务类型改为5类:签单/确认收入/人力成本/费用/外部采购 2026-06-16 15:47:44 +08:00
mac
af4ae1cbc3 v2.0.1 — 财务表单简化:日月合并+去类型+分类含签单+日历控件 2026-06-16 15:47:04 +08:00
5 changed files with 176 additions and 41 deletions

View File

@@ -317,12 +317,32 @@ def attach_common(conn, resource, items):
def monthly_finance(conn, tenant="科普·无界"):
from datetime import date
today = date.today()
# 6 months: 3 before + current + 2 after
from dateutil.relativedelta import relativedelta
start = today + relativedelta(months=-3)
months = []
for i in range(6):
m = start + relativedelta(months=i)
months.append(m.strftime("%Y-%m"))
data = []
for item in rows(conn, "SELECT DISTINCT month FROM finance_records WHERE tenant=? ORDER BY month", (tenant,)):
month = item["month"]
revenue = one(conn, "SELECT COALESCE(SUM(amount),0) AS v FROM finance_records WHERE month=? AND record_type='revenue' AND tenant=?", (month, tenant))["v"]
cost = one(conn, "SELECT COALESCE(SUM(amount),0) AS v FROM finance_records WHERE month=? AND record_type='cost_expense' AND tenant=?", (month, tenant))["v"]
data.append({"month": month, "revenue": revenue, "gross_profit": revenue - cost, "cost_expense": cost, "net_profit": revenue - cost})
for month in months:
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": 0, "expense": 0, "purchase": 0,
"net_profit": gross,
})
return data
@@ -344,18 +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_finance(months, rtype):
return sum(x["amount"] for x in finance if x["month"] in months and x["record_type"] == rtype)
months_2026 = [f"2026-{m:02d}" for m in range(1,7)]
months_q2 = ["2026-04","2026-05","2026-06"]
revenue_annual = sum_finance(months_2026, "revenue")
cost_annual = sum_finance(months_2026, "cost_expense")
revenue_q2 = sum_finance(months_q2, "revenue")
cost_q2 = sum_finance(months_q2, "cost_expense")
revenue_month = sum_finance([current_month], "revenue")
cost_month = sum_finance([current_month], "cost_expense")
# 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
@@ -378,8 +397,9 @@ def bootstrap():
"active_sales": len([x for x in sales if x["status"] in ["待跟进", "跟进中", "方案中", "商务谈判"]]),
"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": revenue_month,
"monthly_net_profit": revenue_month - cost_month,
"monthly_revenue": rev_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),
@@ -390,16 +410,16 @@ def bootstrap():
"signed_q2": signed_q2,
"signed_month": signed_month,
"pipeline_amount": pipeline_amount,
"revenue_annual": revenue_annual,
"revenue_q2": revenue_q2,
"gross_annual": revenue_annual - cost_annual,
"gross_q2": revenue_q2 - cost_q2,
"revenue_annual": rev_annual,
"revenue_q2": rev_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()
@@ -411,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"]),
}

View File

@@ -1 +1,2 @@
Flask==3.0.3
python-dateutil==2.9.0

File diff suppressed because one or more lines are too long

View File

@@ -554,6 +554,9 @@ td {
.task-row:hover { background: #f8fafc; }
.task-dot { display: flex; color: #cbd5e1; flex-shrink: 0; cursor: pointer; }
.task-dot:hover { color: #6366f1; }
.task-grip { display: flex; color: #cbd5e1; flex-shrink: 0; cursor: grab; }
.task-grip:hover { color: #94a3b8; }
.task-grip:active { cursor: grabbing; }
.task-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.task-name { color: #1e293b; font-size: 13px; }
.task-desc { color: #94a3b8; font-size: 12px; word-wrap: break-word; overflow-wrap: break-word; }

View File

@@ -31,7 +31,7 @@
<div>
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager</p>
<div class="flex items-center gap-3 mt-1">
<h1 class="text-2xl font-semibold">无界 OPC 工作台</h1>
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
<select id="tenantSelect" class="rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 outline-none focus:border-blue-500" onchange="switchTenant(this.value)">
<option value="科普·无界">科普·无界</option>
<option value="科研·无界">科研·无界</option>