diff --git a/static/modules/finance.js b/static/modules/finance.js
index 51b4224..f056205 100644
--- a/static/modules/finance.js
+++ b/static/modules/finance.js
@@ -286,14 +286,14 @@ function renderFinance() {
return h;
};
const SIGNED_COLS = [
- { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
+ { key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
{ key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'cost', label: '成本' },
{ key: 'profit', 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: '付款率' },
];
const UNSIGNED_COLS = [
- { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
+ { key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
{ key: 'sign_month', label: '签约月份' }, { key: 'gross', label: '毛利' }, { key: 'cost', label: '成本' },
{ key: 'profit', label: '项目利润' },
];
@@ -305,7 +305,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 ? 7 : 14;
const tbody = rows.length ? `
${rows.join("")}` : `| ${emptyText} |
`;
const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-6';
diff --git a/static/modules/plan.js b/static/modules/plan.js
index ec64d5c..999c639 100644
--- a/static/modules/plan.js
+++ b/static/modules/plan.js
@@ -965,11 +965,35 @@ window.deletePlanItem = async () => {
}
}
+
+// 从 planFinances 计算月度数据
+function computePlanMonthly(planFinances) {
+ var pfs = planFinances || [];
+ var months = [];
+ var year = new Date().getFullYear();
+ for (var mi = 1; mi <= 12; mi++) { months.push(year + '-' + String(mi).padStart(2, '0')); }
+ var data = [];
+ for (var i = 0; i < 12; i++) {
+ var month = months[i];
+ var sign = 0, revenue = 0, gross = 0, payment = 0, cost = 0, paid = 0;
+ for (var j = 0; j < pfs.length; j++) {
+ var pf = pfs[j];
+ if ((pf.sign_month || '') === month) sign += parseFloat(pf.sign_amount || 0);
+ var bd = []; try { bd = JSON.parse(pf.budget_data || '[]'); } catch(e) {}
+ var b = bd.find(function(x) { return (x.month || '') === month; });
+ if (b) { revenue += parseFloat(b.rev || 0); gross += parseFloat(b.gross || 0); payment += parseFloat(b.payment || 0); }
+ var ed = []; try { ed = JSON.parse(pf.expense_data || '[]'); } catch(e) {}
+ var e = ed.find(function(x) { return (x.month || '') === month; });
+ if (e) { cost += parseFloat(e.cost || 0); paid += parseFloat(e.paid || 0); }
+ }
+ data.push({ month: month, sign: Math.round(sign), revenue: Math.round(revenue), gross: Math.round(gross), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid) });
+ }
+ return data;
+}
// 总览渲染
function renderPlanOverview() {
- const { summary, financeMonthly } = state.data;
- if (!summary) return;
- const m = summary.metrics;
+ const planFinances = state.data.planFinances || [];
+ const fm = computePlanMonthly(planFinances);
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
const moneyWan = (v) => {
const n = Number(v || 0);
@@ -977,20 +1001,20 @@ function renderPlanOverview() {
};
const card = (body, cls) => '' + body + '
';
- // 月度数据 (financeMonthly 有12个月)
- const fm = financeMonthly || [];
+ // 从 planFinances 计算年度累计
+ var annualSums = { sign: 0, revenue: 0, gross: 0, cost: 0, payment: 0, paid: 0 };
+ for (var i = 0; i < fm.length; i++) {
+ annualSums.sign += fm[i].sign; annualSums.revenue += fm[i].revenue;
+ annualSums.gross += fm[i].gross; annualSums.cost += fm[i].cost;
+ 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 = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流'];
// 每行的年度累计 + 12个月值
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,
+ annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost,
+ annualSums.gross - annualSums.cost, annualSums.payment, annualSums.paid,
+ annualSums.payment - annualSums.paid,
];
// 字段映射: sign, revenue, gross, cost, profit(=gross-cost), payment, paid, cashflow(=payment-paid)
const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null];
@@ -1027,23 +1051,27 @@ function renderPlanOverview() {
if (window.lucide) window.lucide.createIcons();
// 渲染图表(复用 home.js 的 moneyTick / chartOptions / monthLabels)
- if (financeMonthly && window.Chart) {
- renderPlanCharts(financeMonthly);
+ if (fm && window.Chart) {
+ renderPlanCharts(fm);
}
}
function renderPlanQuarterlyOverview() {
- const { summary, financeMonthly } = state.data;
- if (!summary) return;
- const m = summary.metrics;
+ const planFinances = state.data.planFinances || [];
+ const fm = computePlanMonthly(planFinances);
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
const card = (body, cls) => '' + body + '
';
- const fm = financeMonthly || [];
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];
+ var annualSums2 = { sign: 0, revenue: 0, gross: 0, cost: 0, payment: 0, paid: 0 };
+ for (var i2 = 0; i2 < fm.length; i2++) {
+ annualSums2.sign += fm[i2].sign; annualSums2.revenue += fm[i2].revenue;
+ 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];
// Calculate quarterly sums
diff --git a/templates/index.html b/templates/index.html
index 81029ec..121c3ad 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -80,7 +80,7 @@
-
OPC Manager v1.0.0.27
+
OPC Manager v1.0.0.28