' +
card('
月度签约趋势
2026', 'p-4') +
card('
月度确收与毛利
2026', 'p-4') +
diff --git a/static/modules/plan.js b/static/modules/plan.js
index e93831e..3c29218 100644
--- a/static/modules/plan.js
+++ b/static/modules/plan.js
@@ -294,15 +294,15 @@ function renderPlan() {
{ key: 'weight', label: '加权' },
{ key: 'project_type', label: '项目类型' },
{ key: 'sign_amount', label: '签约金额' },
- { key: 'rev', label: '已确收' },
+ { key: 'rev', label: '确收' },
{ key: 'gross', label: '毛利' },
- { key: 'cost', label: '成本' },
- { key: 'profit', label: '项目利润' },
- { key: 'payment', label: '已回款' },
- { key: 'paid', label: '已付' },
+ { key: 'gross_rate', label: '毛利率' },
+ { key: 'payment', label: '回款' },
{ key: 'cashflow', label: '现金流' },
- { key: 'exe_rate', label: '执行率' },
{ key: 'pay_rate', label: '回款率' },
+ { key: 'cost', label: '成本' },
+ { key: 'paid', label: '付款' },
+ { key: 'paid_rate', label: '付款率' },
];
const UNSIGNED_COLS = [
{ key: 'client_name', label: '客户' },
@@ -337,18 +337,14 @@ function renderPlan() {
const fmtNoUnit = (v) => v ? `
${Number(v||0).toLocaleString('zh-CN')}` : '
—';
const tdRow = (pf, rev, payment, cost, paid, gross) => {
const cashflow = payment - paid;
- const exe = rev && pf.sign_amount ? Math.round(rev / pf.sign_amount * 100) + '%' : '
—';
const payRVal = rev && payment ? Math.round(payment / rev * 100) : null;
const payRCls = payRVal === null ? '' : payRVal < 30 ? 'text-red-600' : payRVal < 80 ? 'text-amber-600' : 'text-green-600';
const payR = payRVal !== null ? '
' + payRVal + '%' : '
—';
const paidR = cost && paid ? Math.round(paid / cost * 100) + '%' : '
—';
const grossR = rev && gross ? Math.round(gross / rev * 100) + '%' : '
—';
- const cf = cashflow ? money(cashflow) : '
—';
+ const cf = cashflow ? money(cashflow) : '
—';
const cfCls = cashflow >= 0 ? 'text-green-600' : 'text-red-600';
- const profit = gross;
- const pfCls = profit >= 0 ? 'text-green-600' : 'text-red-600';
- const pfVal = profit ? money(profit) : '
—';
- return `
| ${esc(pf.client_name || "")} | ${esc(pf.customer_name)} | ${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])} | ${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])} | ${money(pf.sign_amount)} | ${fmtNoUnit(rev)} | ${fmtNoUnit(gross)} | ${fmtNoUnit(cost)} | ${pfVal} | ${fmtNoUnit(payment)} | ${fmtNoUnit(paid)} | ${cf} | ${exe} | ${payR} |
`;
+ return `
| ${esc(pf.client_name || "")} | ${esc(pf.customer_name)} | ${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])} | ${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])} | ${money(pf.sign_amount)} | ${fmtNoUnit(rev)} | ${fmtNoUnit(gross)} | ${grossR} | ${fmtNoUnit(payment)} | ${cf} | ${payR} | ${fmtNoUnit(cost)} | ${fmtNoUnit(paid)} | ${paidR} |
`;
};
// 待签约简版行
const tdRowUnsigned = (pf, cost, gross) => {
@@ -1034,8 +1030,77 @@ function renderPlanOverview() {
tbody += rowHtml + '';
}
+ // 部门经营卡片:毛利、平台费用、利润
+ var planExp = state.data.planExpense || [];
+ var expMonthly = [0,0,0,0,0,0,0,0,0,0,0,0];
+ for (var ei = 0; ei < planExp.length; ei++) {
+ var em = (planExp[ei].expense_month || '').substring(0, 7);
+ var mi = parseInt(em.substring(5)) - 1;
+ if (mi >= 0 && mi < 12) expMonthly[mi] += parseFloat(planExp[ei].amount || 0);
+ }
+ var expAnnual = 0; for (var ei = 0; ei < 12; ei++) expAnnual += expMonthly[ei];
+ var grossAnnual = 0; for (var ei = 0; ei < 12; ei++) grossAnnual += (fm[ei] ? fm[ei].gross : 0);
+ var deptThead = '
| 指标 | 年度累计 | ';
+ for (var dmi = 0; dmi < 12; dmi++) { deptThead += '' + mLbl[dmi] + ' | '; }
+ deptThead += '
|---|
';
+ var deptRows = [
+ { label: '毛利', vals: fm.map(function(x) { return x ? x.gross : 0; }), annual: grossAnnual },
+ { label: '平台费用', vals: expMonthly, annual: expAnnual },
+ { label: '利润', vals: fm.map(function(x, i) { return (x ? x.gross : 0) - expMonthly[i]; }), annual: grossAnnual - expAnnual }
+ ];
+ var deptTbody = '';
+ for (var dri = 0; dri < 3; dri++) {
+ var dr = deptRows[dri];
+ var rowCls = dri === 2 ? 'font-semibold' : '';
+ var valCls = dri === 2 ? 'text-green-600' : 'text-slate-700';
+ var rowHtml = '
| ' + dr.label + ' | ';
+ rowHtml += '' + moneyIntL(dr.annual) + ' | ';
+ for (var dmi = 0; dmi < 12; dmi++) {
+ rowHtml += '' + moneyIntL(dr.vals[dmi]) + ' | ';
+ }
+ deptTbody += rowHtml + '
';
+ }
+ var deptCard = card('
业务线经营情况
毛利 - 平台费用 = 利润
' + deptThead + '' + deptTbody + '
', 'p-4');
+
+
+ // 业务线现金流卡片
+ var cfMonthly = [0,0,0,0,0,0,0,0,0,0,0,0];
+ for (var cfi = 0; cfi < 12; cfi++) { cfMonthly[cfi] = (fm[cfi] ? (fm[cfi].payment - fm[cfi].paid) : 0); }
+ var cfAnnual = 0; for (var cfi = 0; cfi < 12; cfi++) cfAnnual += cfMonthly[cfi];
+ var expPaidMonthly = [0,0,0,0,0,0,0,0,0,0,0,0];
+ for (var epi = 0; epi < planExp.length; epi++) {
+ var epm = (planExp[epi].expense_month || '').substring(0, 7);
+ var epmi = parseInt(epm.substring(5)) - 1;
+ if (epmi >= 0 && epmi < 12) expPaidMonthly[epmi] += parseFloat(planExp[epi].incurred_amount || 0);
+ }
+ var expPaidAnnual = 0; for (var epi = 0; epi < 12; epi++) expPaidAnnual += expPaidMonthly[epi];
+ var blCfMonthly = cfMonthly.map(function(v, i) { return v - expPaidMonthly[i]; });
+ var blCfAnnual = cfAnnual - expPaidAnnual;
+ var cfRows = [
+ { label: '项目现金流', vals: cfMonthly, annual: cfAnnual },
+ { label: '平台费用(已付)', vals: expPaidMonthly, annual: expPaidAnnual },
+ { label: '业务线现金流', vals: blCfMonthly, annual: blCfAnnual }
+ ];
+ var cfTbody = '';
+ for (var cfri = 0; cfri < 3; cfri++) {
+ var cfdr = cfRows[cfri];
+ var cfRowCls = cfri === 2 ? 'font-semibold' : '';
+ var cfValCls = cfri === 2 ? (cfdr.annual >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700';
+ var cfRowHtml = '
| ' + cfdr.label + ' | ';
+ cfRowHtml += '' + moneyIntL(cfdr.annual) + ' | ';
+ for (var cfdmi = 0; cfdmi < 12; cfdmi++) {
+ var cellCls = cfValCls;
+ if (cfri === 2) cellCls = blCfMonthly[cfdmi] >= 0 ? 'text-green-600' : 'text-red-600';
+ cfRowHtml += '' + moneyIntL(cfdr.vals[cfdmi]) + ' | ';
+ }
+ cfTbody += cfRowHtml + '
';
+ }
+ var cfCard = card('
业务线现金流
项目现金流 - 平台费用(已付)= 业务线现金流
' + deptThead + '' + cfTbody + '
', 'p-4');
+
document.querySelector("#planOverview").innerHTML = '
' +
- card('
业务(项目)财务概览
合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流
' + thead + '' + tbody + '
', 'p-4') +
+ card('
业务(项目)财务概览
合同 → 确收 → 毛利 → 成本 → 已付
' + thead + '' + tbody + '
', 'p-4') +
+ deptCard +
+ cfCard +
'
' +
card('
月度签约趋势
2026', 'p-4') +
card('
月度确收与毛利
2026', 'p-4') +
diff --git a/templates/index.html b/templates/index.html
index 17d9acf..f394156 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -80,7 +80,7 @@
-
OPC Manager v1.0.0.30
+
OPC Manager v1.0.0.31