Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c68fcaadcc | ||
|
|
2c199aae76 | ||
|
|
be6a7f5c38 |
@@ -352,8 +352,19 @@ def bootstrap():
|
|||||||
cost_q2 = sum_finance(months_q2, "cost_expense")
|
cost_q2 = sum_finance(months_q2, "cost_expense")
|
||||||
revenue_month = sum_finance([current_month], "revenue")
|
revenue_month = sum_finance([current_month], "revenue")
|
||||||
cost_month = sum_finance([current_month], "cost_expense")
|
cost_month = sum_finance([current_month], "cost_expense")
|
||||||
# Contract aggregates
|
# Contract aggregates — time-based
|
||||||
signed_amount = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_status"] == "已签约")
|
signed_amount = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_status"] == "已签约")
|
||||||
|
from datetime import date
|
||||||
|
today = date.today()
|
||||||
|
def contract_in_period(op, start, end):
|
||||||
|
if op["project_status"] != "已签约": return False
|
||||||
|
try:
|
||||||
|
d = date.fromisoformat(op["created_at"][:10])
|
||||||
|
return start <= d <= end
|
||||||
|
except: return False
|
||||||
|
signed_annual = sum(x["expected_contract_amount"] or 0 for x in operations if contract_in_period(x, date(2026,1,1), date(2026,12,31)))
|
||||||
|
signed_q2 = sum(x["expected_contract_amount"] or 0 for x in operations if contract_in_period(x, date(2026,4,1), date(2026,6,30)))
|
||||||
|
signed_month = sum(x["expected_contract_amount"] or 0 for x in operations if contract_in_period(x, date(2026,6,1), date(2026,6,30)))
|
||||||
pipeline_amount = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_status"] not in ["已签约","已丢单","已归档","已完成"])
|
pipeline_amount = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_status"] not in ["已签约","已丢单","已归档","已完成"])
|
||||||
signed_not_executed = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_type"] == "execution" and x["execution_progress"] < 100)
|
signed_not_executed = sum(x["expected_contract_amount"] or 0 for x in operations if x["project_type"] == "execution" and x["execution_progress"] < 100)
|
||||||
summary = {
|
summary = {
|
||||||
@@ -366,8 +377,14 @@ def bootstrap():
|
|||||||
"monthly_revenue": revenue_month,
|
"monthly_revenue": revenue_month,
|
||||||
"monthly_net_profit": revenue_month - cost_month,
|
"monthly_net_profit": revenue_month - cost_month,
|
||||||
"upcoming_products": len([x for x in products if x["status"] in ["规划中", "设计中", "开发中", "测试中"]]),
|
"upcoming_products": len([x for x in products if x["status"] in ["规划中", "设计中", "开发中", "测试中"]]),
|
||||||
|
"total_projects": len(operations),
|
||||||
|
"total_proposals": len(proposals),
|
||||||
|
"total_products": len(products),
|
||||||
# Extended finance metrics
|
# Extended finance metrics
|
||||||
"signed_amount": signed_amount,
|
"signed_amount": signed_amount,
|
||||||
|
"signed_annual": signed_annual,
|
||||||
|
"signed_q2": signed_q2,
|
||||||
|
"signed_month": signed_month,
|
||||||
"pipeline_amount": pipeline_amount,
|
"pipeline_amount": pipeline_amount,
|
||||||
"revenue_annual": revenue_annual,
|
"revenue_annual": revenue_annual,
|
||||||
"revenue_q2": revenue_q2,
|
"revenue_q2": revenue_q2,
|
||||||
|
|||||||
@@ -94,30 +94,38 @@ function render() {
|
|||||||
function renderHome() {
|
function renderHome() {
|
||||||
const { summary, financeMonthly } = state.data;
|
const { summary, financeMonthly } = state.data;
|
||||||
const m = summary.metrics;
|
const m = summary.metrics;
|
||||||
|
const rows1 = [
|
||||||
|
["年度累计签约", money(m.signed_annual || m.signed_amount)],
|
||||||
|
["Q2 累计签约", money(m.signed_q2 || 0)],
|
||||||
|
["本月新增签约", money(m.signed_month || 0)],
|
||||||
|
["合同流程中", money(m.pipeline_amount)],
|
||||||
|
];
|
||||||
|
const rows2 = [
|
||||||
|
["年度累计确收", money(m.revenue_annual)],
|
||||||
|
["Q2 累计确收", money(m.revenue_q2)],
|
||||||
|
["本月新增确收", money(m.monthly_revenue)],
|
||||||
|
["已签约未执行", money(m.signed_not_executed)],
|
||||||
|
];
|
||||||
|
const rows3 = [
|
||||||
|
["年度累计毛利", money(m.gross_annual)],
|
||||||
|
["Q2 累计毛利", money(m.gross_q2)],
|
||||||
|
["本月新增毛利", money(m.monthly_net_profit)],
|
||||||
|
["合同毛利率", m.revenue_annual ? Math.round(m.gross_annual / m.revenue_annual * 100) + "%" : "—"],
|
||||||
|
];
|
||||||
|
const tblCard = (title, rows) => card(`<h3 class="text-sm font-bold text-slate-700 mb-3">${title}</h3><table class="w-full text-sm"><tbody>${rows.map(([label, value]) => `<tr class="border-b border-slate-100 last:border-0"><td class="py-2 pr-4 text-slate-500">${label}</td><td class="py-2 text-right font-semibold text-slate-800">${value}</td></tr>`).join("")}</tbody></table>`, "p-4");
|
||||||
document.querySelector("#home").innerHTML = `
|
document.querySelector("#home").innerHTML = `
|
||||||
<div class="grid gap-5">
|
<div class="grid gap-5">
|
||||||
<div class="grid grid-cols-4 gap-3">
|
<div class="grid grid-cols-6 gap-3">
|
||||||
${[
|
${[
|
||||||
["P0 客户数", m.p0_customers, "projects"],
|
["重点项目", m.total_projects, "projects"],
|
||||||
["跟进中销售机会", m.active_sales, "projects"],
|
["业务方案", m.total_proposals, "proposals"],
|
||||||
["已签约执行项目", m.execution_projects, "projects"],
|
["产品版本", m.total_products, "products"],
|
||||||
["有风险项目", m.risk_projects, "projects"],
|
["本月确收", money(m.monthly_revenue), "finance"],
|
||||||
["本月收入", money(m.monthly_revenue), "finance"],
|
["本月毛利", money(m.monthly_gross || m.monthly_net_profit), "finance"],
|
||||||
["本月净利", money(m.monthly_net_profit), "finance"],
|
["本月净利", money(m.monthly_net_profit), "finance"],
|
||||||
["即将上线版本", m.upcoming_products, "products"],
|
|
||||||
["已签约未执行", money(m.signed_not_executed), "finance"],
|
|
||||||
].map(([label, value, tab]) => `<button class="metric-card" onclick="switchTab('${tab}')"><span class="flex items-center gap-2 text-xs text-slate-500"><i data-lucide="gauge"></i>${label}</span><strong class="mt-2 block text-2xl">${value}</strong></button>`).join("")}
|
].map(([label, value, tab]) => `<button class="metric-card" onclick="switchTab('${tab}')"><span class="flex items-center gap-2 text-xs text-slate-500"><i data-lucide="gauge"></i>${label}</span><strong class="mt-2 block text-2xl">${value}</strong></button>`).join("")}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-4 gap-3">
|
<div class="grid grid-cols-3 gap-5">${tblCard("合同金额", rows1)}${tblCard("确收金额", rows2)}${tblCard("确收毛利", rows3)}</div>
|
||||||
${[
|
|
||||||
["已签约合同总额", money(m.signed_amount), "projects"],
|
|
||||||
["合同流程中", money(m.pipeline_amount), "projects"],
|
|
||||||
["年度累计确收", money(m.revenue_annual), "finance"],
|
|
||||||
["Q2 累计确收", money(m.revenue_q2), "finance"],
|
|
||||||
["年度累计毛利", money(m.gross_annual), "finance"],
|
|
||||||
["Q2 累计毛利", money(m.gross_q2), "finance"],
|
|
||||||
].map(([label, value, tab]) => `<button class="metric-card" onclick="switchTab('${tab}')"><span class="flex items-center gap-2 text-xs text-slate-500"><i data-lucide="trending-up"></i>${label}</span><strong class="mt-2 block text-2xl">${value}</strong></button>`).join("")}
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-5">
|
<div class="grid grid-cols-2 gap-5">
|
||||||
${card(`<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">财务趋势</h2>${badge("YYYY-MM")}</div><div style="position:relative;height:140px"><canvas id="financeChart"></canvas></div>`, "p-4")}
|
${card(`<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">财务趋势</h2>${badge("YYYY-MM")}</div><div style="position:relative;height:140px"><canvas id="financeChart"></canvas></div>`, "p-4")}
|
||||||
${card(`<h2 class="text-lg font-bold">风险提醒</h2><div class="mt-3 grid gap-2">${(summary.risks.length ? summary.risks : [{ title: "暂无高风险", content: "当前无明确阻塞,按周更新即可。" }]).map((r) => `<div class="rounded-md border border-amber-200 bg-amber-50 p-3"><p class="font-bold text-amber-900">${r.title}</p><p class="mt-1 text-sm text-amber-800 break-words">${r.content}</p></div>`).join("")}</div>`, "p-5")}
|
${card(`<h2 class="text-lg font-bold">风险提醒</h2><div class="mt-3 grid gap-2">${(summary.risks.length ? summary.risks : [{ title: "暂无高风险", content: "当前无明确阻塞,按周更新即可。" }]).map((r) => `<div class="rounded-md border border-amber-200 bg-amber-50 p-3"><p class="font-bold text-amber-900">${r.title}</p><p class="mt-1 text-sm text-amber-800 break-words">${r.content}</p></div>`).join("")}</div>`, "p-5")}
|
||||||
|
|||||||
Reference in New Issue
Block a user