diff --git a/static/modules/finance.js b/static/modules/finance.js
index 0f459c1..dede43b 100644
--- a/static/modules/finance.js
+++ b/static/modules/finance.js
@@ -287,10 +287,9 @@ function renderFinance() {
};
const SIGNED_COLS = [
{ key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
- { key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'cost', label: '成本' },
- { key: 'payment', label: '已回款' }, { key: 'paid', label: '已付' },
- { key: 'cashflow', label: '现金流' }, { key: 'exe_rate', label: '执行率' }, { key: 'gross_rate', label: '毛利率' },
- { key: 'pay_rate', label: '回款率' }, { key: 'paid_rate', label: '付款率' },
+ { key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'gross_rate', label: '毛利率' },
+ { key: 'payment', label: '回款' }, { key: 'cashflow', label: '现金流' }, { key: 'pay_rate', label: '回款率' },
+ { key: 'cost', label: '成本' }, { key: 'paid', label: '已付' }, { key: 'paid_rate', label: '付款率' },
];
const UNSIGNED_COLS = [
{ key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
@@ -305,7 +304,7 @@ function renderFinance() {
return typeof v === 'object' ? [c.label, v.val, v.cls] : [c.label, v, c.color];
});
const thead = isUnsigned ? buildThead(UNSIGNED_COLS, 'uns') : buildThead(SIGNED_COLS, 'sig');
- const theadCols = isUnsigned ? 6 : 13;
+ const theadCols = isUnsigned ? 6 : 12;
const tbody = rows.length ? `
' +
card('
业务(项目)财务月度概览
合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流
' + thead + '' + tbody + '
', 'p-4') +
- '
' +
+ '
' +
card('
月度签约趋势
2026', 'p-4') +
card('
月度确收与毛利
2026', 'p-4') +
card('
月度回款与已付
2026', 'p-4') +
- card('
月度项目利润
2026', 'p-4') +
'
';
if (window.lucide) window.lucide.createIcons();
@@ -985,21 +979,19 @@ function renderFinanceQuarterlyOverview() {
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
const card = (body, cls) => '
' + body + '
';
const fm = financeMonthly || [];
- const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流'];
+ const LABELS = ['合同金额','确收金额','确收毛利','成本','已付'];
const qLbl = ['Q1','Q2','Q3','Q4'];
const qRanges = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]];
- const annualVals = [m.signed_annual||m.signed_amount||0, m.revenue_annual||0, m.gross_annual||0, m.cost_annual||0, m.profit_annual||0, m.payment_annual||0, m.paid_annual||0, m.cashflow_annual||0];
- const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null];
- var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
+ const annualVals = [m.signed_annual||m.signed_amount||0, m.revenue_annual||0, m.gross_annual||0, m.cost_annual||0, m.paid_annual||0];
+ const fmFields = ['sign','revenue','gross','cost','paid'];
+ var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
for (var qi = 0; qi < 4; qi++) {
- for (var ri = 0; ri < 8; ri++) {
+ for (var ri = 0; ri < 5; ri++) {
var sum = 0;
for (var mi = 0; mi < 3; mi++) {
var idx = qRanges[qi][mi];
if (fm[idx]) {
- if (ri === 4) { sum += (fm[idx].gross||0) - (fm[idx].cost||0); }
- else if (ri === 7) { sum += (fm[idx].payment||0) - (fm[idx].paid||0); }
- else { var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0; }
+ var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0;
}
}
qData[ri][qi] = sum;
@@ -1013,14 +1005,12 @@ function renderFinanceQuarterlyOverview() {
thead += '';
var qoq = function(cur, prev) { if (!prev) return '
—'; var pct = Math.round((cur - prev) / prev * 100); var cls = pct >= 0 ? 'text-green-600' : 'text-red-600'; var sign = pct >= 0 ? '+' : ''; return '
' + sign + pct + '%'; };
var tbody = '';
- for (var ri = 0; ri < 8; ri++) {
+ for (var ri = 0; ri < 5; ri++) {
var rowHtml = '
| ' + LABELS[ri] + ' | ';
rowHtml += '' + moneyIntL(annualVals[ri]) + ' | ';
for (var qi = 0; qi < 4; qi++) {
var val = qData[ri][qi];
var cls = 'text-slate-700';
- if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
- if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
rowHtml += '' + moneyIntL(val) + ' | ';
if (qi > 0) { rowHtml += '' + qoq(val, qData[ri][qi - 1]) + ' | '; }
}
diff --git a/static/modules/plan.js b/static/modules/plan.js
index 999c639..e93831e 100644
--- a/static/modules/plan.js
+++ b/static/modules/plan.js
@@ -1009,32 +1009,26 @@ function renderPlanOverview() {
annualSums.payment += fm[i].payment; annualSums.paid += fm[i].paid;
}
const mLbl = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
- const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流'];
+ const LABELS = ['合同金额','确收金额','确收毛利','成本','已付'];
// 每行的年度累计 + 12个月值
const annualVals = [
- annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost,
- annualSums.gross - annualSums.cost, annualSums.payment, annualSums.paid,
- annualSums.payment - annualSums.paid,
+ annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost, annualSums.paid,
];
// 字段映射: sign, revenue, gross, cost, profit(=gross-cost), payment, paid, cashflow(=payment-paid)
- const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null];
+ const fmFields = ['sign','revenue','gross','cost','paid'];
var thead = '
| 指标 | 年度累计 | ';
for (var mi = 0; mi < 12; mi++) { thead += '' + mLbl[mi] + ' | '; }
thead += '
|---|
';
var tbody = '';
- for (var ri = 0; ri < 8; ri++) {
+ for (var ri = 0; ri < 5; ri++) {
var rowHtml = '
| ' + LABELS[ri] + ' | ';
rowHtml += '' + moneyIntL(annualVals[ri]) + ' | ';
for (var mi = 0; mi < 12; mi++) {
var val = 0;
if (fm[mi]) {
- if (ri === 4) { val = (fm[mi].gross || 0) - (fm[mi].cost || 0); }
- else if (ri === 7) { val = (fm[mi].payment || 0) - (fm[mi].paid || 0); }
- else { var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0; }
+ var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0;
}
var cls = 'text-slate-700';
- if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
- if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
rowHtml += '' + moneyIntL(val) + ' | ';
}
tbody += rowHtml + '
';
@@ -1042,11 +1036,10 @@ function renderPlanOverview() {
document.querySelector("#planOverview").innerHTML = '
' +
card('
业务(项目)财务概览
合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流
' + thead + '' + tbody + '
', 'p-4') +
- '
' +
+ '
' +
card('
月度签约趋势
2026', 'p-4') +
card('
月度确收与毛利
2026', 'p-4') +
card('
月度回款与已付
2026', 'p-4') +
- card('
月度项目利润
2026', 'p-4') +
'
';
if (window.lucide) window.lucide.createIcons();
@@ -1062,7 +1055,7 @@ function renderPlanQuarterlyOverview() {
const fm = computePlanMonthly(planFinances);
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
const card = (body, cls) => '
' + body + '
';
- const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流'];
+ const LABELS = ['合同金额','确收金额','确收毛利','成本','已付'];
const qLbl = ['Q1','Q2','Q3','Q4'];
const qRanges = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]];
var annualSums2 = { sign: 0, revenue: 0, gross: 0, cost: 0, payment: 0, paid: 0 };
@@ -1071,20 +1064,18 @@ function renderPlanQuarterlyOverview() {
annualSums2.gross += fm[i2].gross; annualSums2.cost += fm[i2].cost;
annualSums2.payment += fm[i2].payment; annualSums2.paid += fm[i2].paid;
}
- const annualVals = [annualSums2.sign, annualSums2.revenue, annualSums2.gross, annualSums2.cost, annualSums2.gross - annualSums2.cost, annualSums2.payment, annualSums2.paid, annualSums2.payment - annualSums2.paid];
- const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null];
+ const annualVals = [annualSums2.sign, annualSums2.revenue, annualSums2.gross, annualSums2.cost, annualSums2.paid];
+ const fmFields = ['sign','revenue','gross','cost','paid'];
// Calculate quarterly sums
- var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
+ var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
for (var qi = 0; qi < 4; qi++) {
- for (var ri = 0; ri < 8; ri++) {
+ for (var ri = 0; ri < 5; ri++) {
var sum = 0;
for (var mi = 0; mi < 3; mi++) {
var idx = qRanges[qi][mi];
if (fm[idx]) {
- if (ri === 4) { sum += (fm[idx].gross||0) - (fm[idx].cost||0); }
- else if (ri === 7) { sum += (fm[idx].payment||0) - (fm[idx].paid||0); }
- else { var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0; }
+ var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0;
}
}
qData[ri][qi] = sum;
@@ -1101,14 +1092,12 @@ function renderPlanQuarterlyOverview() {
var qoq = function(cur, prev) { if (!prev) return '
—'; var pct = Math.round((cur - prev) / prev * 100); var cls = pct >= 0 ? 'text-green-600' : 'text-red-600'; var sign = pct >= 0 ? '+' : ''; return '
' + sign + pct + '%'; };
var tbody = '';
- for (var ri = 0; ri < 8; ri++) {
+ for (var ri = 0; ri < 5; ri++) {
var rowHtml = '
| ' + LABELS[ri] + ' | ';
rowHtml += '' + moneyIntL(annualVals[ri]) + ' | ';
for (var qi = 0; qi < 4; qi++) {
var val = qData[ri][qi];
var cls = 'text-slate-700';
- if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
- if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600';
rowHtml += '' + moneyIntL(val) + ' | ';
if (qi > 0) {
rowHtml += '' + qoq(val, qData[ri][qi - 1]) + ' | ';
@@ -1144,11 +1133,6 @@ function renderPlanCharts(data) {
if (state.planChart3) state.planChart3.destroy();
state.planChart3 = new Chart(c3, Object.assign({}, iconf, { data: { labels: labels, datasets: [{ label: "回款", data: data.map(function(x) { return x.payment || 0; }), borderColor: "#d97706", backgroundColor: "rgba(217,119,6,0.06)", fill: true, tension: 0.3 }, { label: "已付", data: data.map(function(x) { return x.cost || 0; }), borderColor: "#7c3aed", backgroundColor: "rgba(124,58,237,0.06)", fill: true, tension: 0.3 }] } }));
}
- var c4 = document.querySelector("#planChartProfit");
- if (c4 && window.Chart) {
- if (state.planChart4) state.planChart4.destroy();
- state.planChart4 = new Chart(c4, Object.assign({}, iconf, { data: { labels: labels, datasets: [{ label: "利润", data: data.map(function(x) { return x.gross || 0; }), borderColor: "#6366f1", backgroundColor: "rgba(99,102,241,0.06)", fill: true, tension: 0.3 }] } }));
- }
};
function planInlineSelect(name, pfId, field, currentVal, options) {
diff --git a/templates/index.html b/templates/index.html
index e3e50c2..17d9acf 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -80,7 +80,7 @@
-
OPC Manager v1.0.0.29
+
OPC Manager v1.0.0.30