// overview_plan.js — 预算总览模块(5 个工作台数据聚合) function renderOverviewPlan() { var view = state.overviewView || "monthly"; var container = document.querySelector("#overview_plan"); if (!container) return; container.innerHTML = '
加载中...
'; if (view === 'matrix') { renderDeptMatrix(); return; } if (view === 'cf_matrix') { renderCfMatrix(); return; } if (view === 'project_matrix') { renderProjectMatrix(); return; } var api = view === "quarterly" ? "/api/overview/plan-quarterly" : "/api/overview/plan-monthly"; fetch(api).then(r => r.json()).then(data => { if (state.overviewView !== view) return; renderOverviewPlanData(data, view); }).catch(() => { if (state.overviewView !== view) return; container.innerHTML = '
加载失败
'; }); } function renderProjectMatrix() { var c = document.querySelector("#overview_plan"); if (!c) return; c.innerHTML = '
加载项目经营详情矩阵...
'; fetch('/api/overview/dept-project-matrix').then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }).then(function(data) { renderProjectMatrixData(data, c); }).catch(function(e) { c.innerHTML = '
加载失败: ' + (e.message || '未知错误') + '
'; }); } function renderProjectMatrixData(data, container) { try { var mf = function(v) { return Math.round(Number(v || 0)).toLocaleString("zh-CN"); }; var tenants = data.tenants; var sn = tenants.map(function(t) { return t.replace('·无界', ''); }); var mL = ['01月','02月','03月','04月','05月','06月','07月','08月','09月','10月','11月','12月']; var keys = ["sign","revenue","gross","cost","paid","payment","cashflow"]; var labels = ["合同金额","确收金额","确收毛利","当期成本","当期流出","回款","项目现金流"]; var vBtns = btnBar('project_matrix'); var cards = ''; for (var mi = 11; mi >= 0; mi--) { var th = '指标月度合计'; for (var ti = 0; ti < tenants.length; ti++) { th += '' + sn[ti] + ''; } th += ''; if (isAllZero(data, tenants, keys.filter(function(k) { return k !== 'sign'; }), mi)) continue; var th = '指标月度合计'; for (var ti = 0; ti < tenants.length; ti++) { th += '' + sn[ti] + ''; } th += ''; var tb = projectMatrixTbody(data, tenants, mi, keys, labels, mf); cards += '
' + mL[mi] + '
' + th + '' + tb + '
'; } container.innerHTML = '
' + vBtns + '
' + cards + '
'; if (window.lucide) window.lucide.createIcons(); } catch(e) { container.innerHTML = '
渲染失败: ' + (e.message || '未知错误') + '
'; } } function projectMatrixTbody(data, tenants, mi, keys, labels, mf) { var tb = ''; for (var ri = 0; ri < keys.length; ri++) { var rowCls = ri === keys.length - 1 ? 'font-semibold' : ''; var ts = 0; var vals = []; for (var ti = 0; ti < tenants.length; ti++) { var v = data.data[tenants[ti]][keys[ri]][mi] || 0; ts += v; vals.push(v); } var tCls = 'text-slate-700'; if (ri === 2) tCls = 'text-green-600'; if (ri === keys.length - 1) tCls = ts >= 0 ? 'text-green-600' : 'text-red-600'; var rh = '' + labels[ri] + '' + mf(ts) + ''; for (var ti = 0; ti < tenants.length; ti++) { var cls = 'text-slate-700'; if (ri === 2) cls = 'text-green-600'; if (ri === keys.length - 1) cls = vals[ti] >= 0 ? 'text-green-600' : 'text-red-600'; rh += '' + mf(vals[ti]) + ''; } rh += ''; tb += rh; } return tb; } function renderOverviewPlanData(data, view) { const container = document.querySelector("#overview_plan"); if (!container) return; const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN"); const card = (body, cls) => '
' + body + '
'; const isQuarterly = view === "quarterly"; const headers = isQuarterly ? ['Q1','Q2','Q3','Q4'] : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']; const cols = isQuarterly ? 4 : 12; const d = data.dept; const cf = data.dept_cf; const p = data.project; // 视图切换按钮 var btns = []; btns.push(''); btns.push(''); btns.push(''); btns.push(''); btns.push(''); var viewBtns = '
' + btns.join('') + '
'; // ====== 部门损益情况 ====== var deptThead = '指标年度累计'; for (var hi = 0; hi < cols; hi++) { deptThead += '' + headers[hi] + ''; } deptThead += ''; var deptRows = [ { label: '项目毛利', vals: d.project_gross, annual: d.annual[0] }, { label: '部门费用', vals: d.dept_expense, annual: d.annual[1] }, { label: '利润', vals: d.profit, annual: d.annual[2] } ]; var deptTbody = ''; for (var dri = 0; dri < 3; dri++) { var dr = deptRows[dri]; var rowCls = dri === 2 ? 'font-semibold' : ''; var rowHtml = '' + dr.label + ''; var annualCls = dri === 2 ? (dr.annual >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700'; rowHtml += '' + moneyIntL(dr.annual) + ''; for (var i = 0; i < cols; i++) { var cellCls = dri === 2 ? (dr.vals[i] >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700'; rowHtml += '' + moneyIntL(dr.vals[i]) + ''; } deptTbody += rowHtml + ''; } var deptCard = card('

部门损益情况 部门利润 = 所有项目产生的毛利 - 部门 1 级产生的人力成本,研发成本,差旅和其他支出

' + deptThead + '' + deptTbody + '
', 'p-4'); // ====== 部门现金流 ====== var cfRows = [ { label: '项目现金流', vals: cf.project_cf, annual: cf.annual[0] }, { label: '部门现金流', vals: cf.dept_cf_item, annual: cf.annual[1] }, { label: '经营现金流', vals: cf.bl_cf, annual: cf.annual[2] } ]; var cfTbody = ''; for (var cfri = 0; cfri < 3; cfri++) { var cfr = cfRows[cfri]; var cfRowCls = cfri === 2 ? 'font-semibold' : ''; var rowHtml = '' + cfr.label + ''; var cAnnualCls = cfri === 2 ? (cfr.annual >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700'; rowHtml += '' + moneyIntL(cfr.annual) + ''; for (var i = 0; i < cols; i++) { var cCellCls = cfri === 2 ? (cfr.vals[i] >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700'; rowHtml += '' + moneyIntL(cfr.vals[i]) + ''; } cfTbody += rowHtml + ''; } var cfCard = card('

部门现金流 部门现金流=所有项目产生的现金流 - 部门一级因费用产生的现金流出或流入

' + deptThead + '' + cfTbody + '
', 'p-4'); // ====== 项目经营详情 ====== var LABELS = ['合同金额','确收金额','确收毛利','成本','支出','回款','项目现金流']; var projVals = [p.sign_monthly || [], p.revenue, p.gross, p.cost, p.paid, p.payment, p.cashflow]; var projAnnual = p.annual; var projThead = '指标年度累计'; for (var phi = 0; phi < cols; phi++) { projThead += '' + headers[phi] + ''; } projThead += ''; var projTbody = ''; for (var ri = 0; ri < 7; ri++) { var rowHtml = '' + LABELS[ri] + ''; rowHtml += '' + moneyIntL(projAnnual[ri]) + ''; for (var i = 0; i < cols; i++) { var val = projVals[ri] ? (projVals[ri][i] || 0) : 0; var cls = 'text-slate-700'; if (ri === 2) cls = 'text-green-600'; if (ri === 6) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; rowHtml += '' + moneyIntL(val) + ''; } projTbody += rowHtml + ''; } var projCard = card('

项目经营详情 所有项目产生的签单,确收,毛利,回款,当期成本,当期流出,现金流

' + projThead + '' + projTbody + '
', 'p-4'); container.innerHTML = '
' + viewBtns + deptCard + cfCard + projCard + '
'; if (window.lucide) window.lucide.createIcons(); } window.setOverviewView = (v) => { state.overviewView = v; renderOverviewPlan(); }; // ====== 损益报表 ====== function renderDeptMatrix() { var c = document.querySelector("#overview_plan"); if (!c) return; c.innerHTML = '
加载损益矩阵...
'; fetch('/api/overview/dept-matrix').then(r => r.json()).then(function(data) { renderDeptMatrixData(data, c); }) .catch(function() { c.innerHTML = '
加载失败
'; }); } function renderDeptMatrixData(data, container) { var mf = function(v) { return Math.round(Number(v || 0)).toLocaleString("zh-CN"); }; var tenants = data.tenants; var sn = tenants.map(function(t) { return t.replace('·无界', ''); }); var mL = ['01月','02月','03月','04月','05月','06月','07月','08月','09月','10月','11月','12月']; var vBtns = btnBar('matrix'); var cards = ''; for (var mi = 11; mi >= 0; mi--) { if (isAllZero(data, tenants, ['project_gross','dept_expense','profit'], mi)) continue; var th = '指标月度合计'; for (var ti = 0; ti < tenants.length; ti++) { th += '' + sn[ti] + ''; } th += ''; var tb = matrixTbody(data, tenants, mi, ['project_gross','dept_expense','profit'], ['项目毛利','部门费用','利润'], mf); cards += '
' + mL[mi] + '
' + th + '' + tb + '
'; } container.innerHTML = '
' + vBtns + '
' + cards + '
'; if (window.lucide) window.lucide.createIcons(); } // ====== 现金流报表 ====== function renderCfMatrix() { var c = document.querySelector("#overview_plan"); if (!c) return; c.innerHTML = '
加载现金流矩阵...
'; fetch('/api/overview/dept-cf-matrix').then(r => r.json()).then(function(data) { renderCfMatrixData(data, c); }) .catch(function() { c.innerHTML = '
加载失败
'; }); } function renderCfMatrixData(data, container) { var mf = function(v) { return Math.round(Number(v || 0)).toLocaleString("zh-CN"); }; var tenants = data.tenants; var sn = tenants.map(function(t) { return t.replace('·无界', ''); }); var mL = ['01月','02月','03月','04月','05月','06月','07月','08月','09月','10月','11月','12月']; var vBtns = btnBar('cf_matrix'); var cards = ''; for (var mi = 11; mi >= 0; mi--) { var th = '指标月度合计'; for (var ti = 0; ti < tenants.length; ti++) { th += '' + sn[ti] + ''; } th += ''; if (isAllZero(data, tenants, ['project_cf','dept_cf','bl_cf'], mi)) continue; var th = '指标月度合计'; for (var ti = 0; ti < tenants.length; ti++) { th += '' + sn[ti] + ''; } th += ''; var tb = matrixTbody(data, tenants, mi, ['project_cf','dept_cf','bl_cf'], ['项目现金流','部门现金流','经营现金流'], mf); cards += '
' + mL[mi] + '
' + th + '' + tb + '
'; } container.innerHTML = '
' + vBtns + '
' + cards + '
'; if (window.lucide) window.lucide.createIcons(); } // ====== 共用工具 ====== function btnBar(active) { var btns = []; btns.push(''); btns.push(''); btns.push(''); btns.push(''); btns.push(''); return '
' + btns.join('') + '
'; } function matrixTbody(data, tenants, mi, keys, labels, mf) { var tb = ''; for (var ri = 0; ri < keys.length; ri++) { var rowCls = ri === keys.length - 1 ? 'font-semibold' : ''; var ts = 0; var vals = []; for (var ti = 0; ti < tenants.length; ti++) { var v = data.data[tenants[ti]][keys[ri]][mi] || 0; ts += v; vals.push(v); } var tCls = ts >= 0 ? 'text-green-600' : 'text-red-600'; var rh = '' + labels[ri] + '' + mf(ts) + ''; for (var ti = 0; ti < tenants.length; ti++) { var cls = vals[ti] >= 0 ? 'text-green-600' : 'text-red-600'; rh += '' + mf(vals[ti]) + ''; } rh += ''; tb += rh; } return tb; function isAllZero(data, tenants, keys, mi) { for (var ti = 0; ti < tenants.length; ti++) { for (var ki = 0; ki < keys.length; ki++) { if ((data.data[tenants[ti]][keys[ki]][mi] || 0) !== 0) return false; } } return true; } // ====== 项目经营详情报表 ====== }