Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5dd152ca4 | ||
|
|
2c20900e75 |
@@ -108,11 +108,12 @@ function renderExpense() {
|
||||
}
|
||||
|
||||
// 模态框 monthOpts for form (with current value preselected)
|
||||
var curMonth = thisYear + '-' + String(now.getMonth() + 1).padStart(2, '0');
|
||||
var modalMonthOpts = '<option value="">请选择</option>';
|
||||
for (var yr2 = thisYear - 1; yr2 <= thisYear + 1; yr2++)
|
||||
for (var m2 = 1; m2 <= 12; m2++) {
|
||||
var mv2 = yr2 + '-' + String(m2).padStart(2, '0');
|
||||
modalMonthOpts += '<option value="' + mv2 + '">' + mv2 + '</option>';
|
||||
modalMonthOpts += '<option value="' + mv2 + '"' + (mv2 === curMonth ? ' selected' : '') + '>' + mv2 + '</option>';
|
||||
}
|
||||
var statusOpts = STATUS_OPTIONS.map(function(s) { return '<option value="' + s + '">' + s + '</option>'; }).join('');
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ function renderFinance() {
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<label class="block"><span class="fin-label">签约金额(元) <span class="text-red-500">*</span></span><input name="sign_amount" class="form-ctrl" placeholder="必须大于 0"></label>
|
||||
<label class="block"><span class="fin-label">签约月份 <span class="text-red-500">*</span></span><select name="sign_month" required class="form-ctrl bg-white"><option value="">选择</option>${monthOptions('')}</select></label>
|
||||
<label class="block"><span class="fin-label">签约月份 <span class="text-red-500">*</span></span><select name="sign_month" required class="form-ctrl bg-white"><option value="">选择</option>${monthOptions(new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'))}</select></label>
|
||||
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
@@ -517,7 +517,7 @@ window.openFinanceModal = () => {
|
||||
modal.classList.remove("hidden");
|
||||
};
|
||||
|
||||
window.addRevpayRow = (month = '', rev = '', gross = '', payment = '', rev_note = '') => {
|
||||
window.addRevpayRow = (month = new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), rev = '', gross = '', payment = '', rev_note = '') => {
|
||||
const tbody = document.querySelector("#revpayTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
@@ -531,7 +531,7 @@ window.addRevpayRow = (month = '', rev = '', gross = '', payment = '', rev_note
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
};
|
||||
|
||||
window.addCostRow = (month = '', expense_type = '', cost = '', paid = '', exp_note = '') => {
|
||||
window.addCostRow = (month = new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), expense_type = '', cost = '', paid = '', exp_note = '') => {
|
||||
const tbody = document.querySelector("#costTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
@@ -1027,8 +1027,8 @@ function renderFinanceOverview() {
|
||||
for (var dmi = 0; dmi < 12; dmi++) { deptThead += '<th class="py-1.5 text-right font-semibold text-slate-600 whitespace-nowrap">' + mLbl[dmi] + '</th>'; }
|
||||
deptThead += '</tr>';
|
||||
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) { 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 = '';
|
||||
@@ -1043,7 +1043,7 @@ function renderFinanceOverview() {
|
||||
}
|
||||
deptTbody += rowHtml + '</tr>';
|
||||
}
|
||||
var deptCard = card('<h3 class="text-sm font-bold text-slate-700">业务线经营情况</h3><p class="text-xs text-slate-400 mb-3">毛利 - 平台费用 = 利润</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4');
|
||||
var deptCard = card('<h3 class="text-base font-bold text-slate-700 mb-3">部门损益情况 <span class="text-sm text-slate-500 font-normal ml-3">部门利润 = 所有项目产生的毛利 - 部门 1 级产生的人力成本,研发成本,差旅和其他支出</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4');
|
||||
|
||||
// 业务线现金流卡片
|
||||
var cfMonthly = [0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
@@ -1061,8 +1061,8 @@ function renderFinanceOverview() {
|
||||
var blCfAnnual = cfAnnual - expPaidAnnual;
|
||||
var cfRows = [
|
||||
{ label: '项目现金流', vals: cfMonthly, annual: cfAnnual },
|
||||
{ label: '平台费用(支出)', vals: expPaidMonthly, annual: expPaidAnnual },
|
||||
{ label: '业务线现金流', vals: blCfMonthly, annual: blCfAnnual }
|
||||
{ label: '部门现金流', vals: expPaidMonthly, annual: expPaidAnnual },
|
||||
{ label: '经营现金流', vals: blCfMonthly, annual: blCfAnnual }
|
||||
];
|
||||
var cfTbody = '';
|
||||
for (var cfri = 0; cfri < 3; cfri++) {
|
||||
@@ -1078,12 +1078,12 @@ function renderFinanceOverview() {
|
||||
}
|
||||
cfTbody += cfRowHtml + '</tr>';
|
||||
}
|
||||
var cfCard = card('<h3 class="text-sm font-bold text-slate-700">业务线现金流</h3><p class="text-xs text-slate-400 mb-3">项目现金流 - 平台费用(支出)= 业务线现金流</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4');
|
||||
var cfCard = card('<h3 class="text-base font-bold text-slate-700 mb-3">部门现金流 <span class="text-sm text-slate-500 font-normal ml-3">部门现金流=所有项目产生的现金流 - 部门一级因费用产生的现金流出或流入</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4');
|
||||
|
||||
document.querySelector("#financeOverview").innerHTML = '<div class="grid gap-2.5">' +
|
||||
deptCard +
|
||||
cfCard +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务(项目)财务月度概览</h3><p class="text-xs text-slate-400 mb-3">合同 → 确收 → 毛利 → 成本 → 支出</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">项目经营详情 <span class="text-sm text-slate-500 font-normal ml-3">所有项目产生的签单,确收,毛利,回款,当期成本,当期流出,现金流</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
'<div class="grid grid-cols-3 gap-2.5">' +
|
||||
card('<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">月度签约趋势</h2><span class="text-xs text-slate-400">2026</span></div><div style="position:relative;height:200px"><canvas id="finChartSign"></canvas></div>', 'p-4') +
|
||||
card('<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">月度确收与毛利</h2><span class="text-xs text-slate-400">2026</span></div><div style="position:relative;height:200px"><canvas id="finChartRev"></canvas></div>', 'p-4') +
|
||||
@@ -1159,11 +1159,11 @@ function renderFinanceQuarterlyOverview() {
|
||||
var grossQ = [qData[2][0], qData[2][1], qData[2][2], qData[2][3]];
|
||||
var grossAnnual = m.gross_annual || 0;
|
||||
var deptThead = '<tr class="border-b border-slate-200"><th class="py-1.5 text-left text-slate-500 whitespace-nowrap">指标</th><th class="py-1.5 text-right font-semibold text-slate-700 whitespace-nowrap">年度累计</th>';
|
||||
for (var qi = 0; qi < 4; qi++) { deptThead += '<th class="py-1.5 text-right font-semibold text-slate-600 whitespace-nowrap">Q' + (qi+1) + '</th>'; }
|
||||
for (var qi = 0; qi < 4; qi++) { deptThead += '<th class="py-1.5 text-right font-semibold text-slate-600 whitespace-nowrap">Q' + (qi+1) + '</th>'; if (qi > 0) deptThead += '<th class="py-1.5 text-right font-semibold text-slate-400 whitespace-nowrap">环比</th>'; }
|
||||
deptThead += '</tr>';
|
||||
var deptRows = [
|
||||
{ label: '毛利', vals: grossQ, annual: grossAnnual },
|
||||
{ label: '平台费用', vals: expQ, annual: expAnnual },
|
||||
{ label: '项目毛利', vals: grossQ, annual: grossAnnual },
|
||||
{ label: '部门费用', vals: expQ, annual: expAnnual },
|
||||
{ label: '利润', vals: grossQ.map(function(v, i) { return v - expQ[i]; }), annual: grossAnnual - expAnnual }
|
||||
];
|
||||
var deptTbody = '';
|
||||
@@ -1176,6 +1176,7 @@ function renderFinanceQuarterlyOverview() {
|
||||
for (var qi = 0; qi < 4; qi++) {
|
||||
var cellCls = dri === 2 ? (dr.vals[qi] >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700';
|
||||
rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap ' + cellCls + '">' + moneyIntL(dr.vals[qi]) + '</td>';
|
||||
if (qi > 0) rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap">' + qoq(dr.vals[qi], dr.vals[qi - 1]) + '</td>';
|
||||
}
|
||||
deptTbody += rowHtml + '</tr>';
|
||||
}
|
||||
@@ -1195,8 +1196,8 @@ function renderFinanceQuarterlyOverview() {
|
||||
var blCfAnnual = cfAnnual - expPaidAnnual;
|
||||
var cfRows = [
|
||||
{ label: '项目现金流', vals: cfQ, annual: cfAnnual },
|
||||
{ label: '平台费用(已付)', vals: expPaidQ, annual: expPaidAnnual },
|
||||
{ label: '业务线现金流', vals: blCfQ, annual: blCfAnnual }
|
||||
{ label: '部门现金流', vals: expPaidQ, annual: expPaidAnnual },
|
||||
{ label: '经营现金流', vals: blCfQ, annual: blCfAnnual }
|
||||
];
|
||||
var cfTbody = '';
|
||||
for (var cfri = 0; cfri < 3; cfri++) {
|
||||
@@ -1208,14 +1209,15 @@ function renderFinanceQuarterlyOverview() {
|
||||
for (var qi = 0; qi < 4; qi++) {
|
||||
var cellCls = cfri === 2 ? (blCfQ[qi] >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700';
|
||||
rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap ' + cellCls + '">' + moneyIntL(cfdr.vals[qi]) + '</td>';
|
||||
if (qi > 0) rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap">' + qoq(cfdr.vals[qi], cfdr.vals[qi - 1]) + '</td>';
|
||||
}
|
||||
cfTbody += rowHtml + '</tr>';
|
||||
}
|
||||
|
||||
document.querySelector("#financeQuarterlyOverview").innerHTML = '<div class="grid gap-2.5">' +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务(项目)财务季度概览</h3><p class="text-xs text-slate-400 mb-3">合同 → 确收 → 毛利 → 成本 → 支出 → 回款 → 项目现金流</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务线经营情况</h3><p class="text-xs text-slate-400 mb-3">毛利 - 平台费用 = 利润</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务线现金流</h3><p class="text-xs text-slate-400 mb-3">项目现金流 - 平台费用(已付)= 业务线现金流</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">项目经营详情 <span class="text-sm text-slate-500 font-normal ml-3">所有项目产生的签单,确收,毛利,回款,当期成本,当期流出,现金流</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">部门损益情况 <span class="text-sm text-slate-500 font-normal ml-3">部门利润 = 所有项目产生的毛利 - 部门 1 级产生的人力成本,研发成本,差旅和其他支出</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">部门现金流 <span class="text-sm text-slate-500 font-normal ml-3">部门现金流=所有项目产生的现金流 - 部门一级因费用产生的现金流出或流入</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4') +
|
||||
'</div>';
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ function renderPlan() {
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<label class="block"><span class="fin-label">签约金额(元) <span class="text-red-500">*</span></span><input name="sign_amount" class="form-ctrl" placeholder="必须大于 0"></label>
|
||||
<label class="block"><span class="fin-label">签约月份 <span class="text-red-500">*</span></span><select name="sign_month" required class="form-ctrl bg-white"><option value="">选择</option>${monthOptions('')}</select></label>
|
||||
<label class="block"><span class="fin-label">签约月份 <span class="text-red-500">*</span></span><select name="sign_month" required class="form-ctrl bg-white"><option value="">选择</option>${monthOptions(new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'))}</select></label>
|
||||
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
@@ -562,7 +562,7 @@ window.openPlanModal = () => {
|
||||
modal.classList.remove("hidden");
|
||||
};
|
||||
|
||||
window.planAddRevpayRow = (month = '', rev = '', gross = '', payment = '', rev_note = '') => {
|
||||
window.planAddRevpayRow = (month = new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), rev = '', gross = '', payment = '', rev_note = '') => {
|
||||
const tbody = document.querySelector("#plan_revpayTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
@@ -576,7 +576,7 @@ window.planAddRevpayRow = (month = '', rev = '', gross = '', payment = '', rev_n
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
};
|
||||
|
||||
window.planAddCostRow = (month = '', expense_type = '', cost = '', paid = '', exp_note = '') => {
|
||||
window.planAddCostRow = (month = new Date().getFullYear() + '-' + String(new Date().getMonth() + 1).padStart(2, '0'), expense_type = '', cost = '', paid = '', exp_note = '') => {
|
||||
const tbody = document.querySelector("#plan_costTbody");
|
||||
if (!tbody) return;
|
||||
const row = document.createElement("tr");
|
||||
@@ -1116,8 +1116,8 @@ function renderPlanOverview() {
|
||||
for (var dmi = 0; dmi < 12; dmi++) { deptThead += '<th class="py-1.5 text-right font-semibold text-slate-600 whitespace-nowrap">' + mLbl[dmi] + '</th>'; }
|
||||
deptThead += '</tr>';
|
||||
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) { 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 = '';
|
||||
@@ -1133,7 +1133,7 @@ function renderPlanOverview() {
|
||||
}
|
||||
deptTbody += rowHtml + '</tr>';
|
||||
}
|
||||
var deptCard = card('<h3 class="text-sm font-bold text-slate-700">业务线经营情况</h3><p class="text-xs text-slate-400 mb-3">毛利 - 平台费用 = 利润</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4');
|
||||
var deptCard = card('<h3 class="text-base font-bold text-slate-700 mb-3">部门损益情况 <span class="text-sm text-slate-500 font-normal ml-3">部门利润 = 所有项目产生的毛利 - 部门 1 级产生的人力成本,研发成本,差旅和其他支出</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4');
|
||||
|
||||
|
||||
// 业务线现金流卡片
|
||||
@@ -1151,8 +1151,8 @@ function renderPlanOverview() {
|
||||
var blCfAnnual = cfAnnual - expPaidAnnual;
|
||||
var cfRows = [
|
||||
{ label: '项目现金流', vals: cfMonthly, annual: cfAnnual },
|
||||
{ label: '平台费用(已付)', vals: expPaidMonthly, annual: expPaidAnnual },
|
||||
{ label: '业务线现金流', vals: blCfMonthly, annual: blCfAnnual }
|
||||
{ label: '部门现金流', vals: expPaidMonthly, annual: expPaidAnnual },
|
||||
{ label: '经营现金流', vals: blCfMonthly, annual: blCfAnnual }
|
||||
];
|
||||
var cfTbody = '';
|
||||
for (var cfri = 0; cfri < 3; cfri++) {
|
||||
@@ -1168,12 +1168,12 @@ function renderPlanOverview() {
|
||||
}
|
||||
cfTbody += cfRowHtml + '</tr>';
|
||||
}
|
||||
var cfCard = card('<h3 class="text-sm font-bold text-slate-700">业务线现金流</h3><p class="text-xs text-slate-400 mb-3">项目现金流 - 平台费用(已付)= 业务线现金流</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4');
|
||||
var cfCard = card('<h3 class="text-base font-bold text-slate-700 mb-3">部门现金流 <span class="text-sm text-slate-500 font-normal ml-3">部门现金流=所有项目产生的现金流 - 部门一级因费用产生的现金流出或流入</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4');
|
||||
|
||||
document.querySelector("#planOverview").innerHTML = '<div class="grid gap-2.5">' +
|
||||
deptCard +
|
||||
cfCard +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务(项目)财务概览</h3><p class="text-xs text-slate-400 mb-3">合同 → 确收 → 毛利 → 成本 → 已付</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">项目经营详情 <span class="text-sm text-slate-500 font-normal ml-3">所有项目产生的签单,确收,毛利,回款,当期成本,当期流出,现金流</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
'<div class="grid grid-cols-3 gap-2.5">' +
|
||||
card('<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">月度签约趋势</h2><span class="text-xs text-slate-400">2026</span></div><div style="position:relative;height:200px"><canvas id="planChartSign"></canvas></div>', 'p-4') +
|
||||
card('<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">月度确收与毛利</h2><span class="text-xs text-slate-400">2026</span></div><div style="position:relative;height:200px"><canvas id="planChartRev"></canvas></div>', 'p-4') +
|
||||
@@ -1253,11 +1253,11 @@ function renderPlanQuarterlyOverview() {
|
||||
var grossQ = [qData[2][0], qData[2][1], qData[2][2], qData[2][3]];
|
||||
var grossAnnual = annualSums2.gross;
|
||||
var deptThead = '<tr class="border-b border-slate-200"><th class="py-1.5 text-left text-slate-500 whitespace-nowrap">指标</th><th class="py-1.5 text-right font-semibold text-slate-700 whitespace-nowrap">年度累计</th>';
|
||||
for (var qi = 0; qi < 4; qi++) { deptThead += '<th class="py-1.5 text-right font-semibold text-slate-600 whitespace-nowrap">Q' + (qi+1) + '</th>'; }
|
||||
for (var qi = 0; qi < 4; qi++) { deptThead += '<th class="py-1.5 text-right font-semibold text-slate-600 whitespace-nowrap">Q' + (qi+1) + '</th>'; if (qi > 0) deptThead += '<th class="py-1.5 text-right font-semibold text-slate-400 whitespace-nowrap">环比</th>'; }
|
||||
deptThead += '</tr>';
|
||||
var deptRows = [
|
||||
{ label: '毛利', vals: grossQ, annual: grossAnnual },
|
||||
{ label: '平台费用', vals: expQ, annual: expAnnual },
|
||||
{ label: '项目毛利', vals: grossQ, annual: grossAnnual },
|
||||
{ label: '部门费用', vals: expQ, annual: expAnnual },
|
||||
{ label: '利润', vals: grossQ.map(function(v, i) { return v - expQ[i]; }), annual: grossAnnual - expAnnual }
|
||||
];
|
||||
var deptTbody = '';
|
||||
@@ -1270,6 +1270,7 @@ function renderPlanQuarterlyOverview() {
|
||||
for (var qi = 0; qi < 4; qi++) {
|
||||
var cellCls = dri === 2 ? (dr.vals[qi] >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700';
|
||||
rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap ' + cellCls + '">' + moneyIntL(dr.vals[qi]) + '</td>';
|
||||
if (qi > 0) rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap">' + qoq(dr.vals[qi], dr.vals[qi - 1]) + '</td>';
|
||||
}
|
||||
deptTbody += rowHtml + '</tr>';
|
||||
}
|
||||
@@ -1288,8 +1289,8 @@ function renderPlanQuarterlyOverview() {
|
||||
var blCfAnnual = cfAnnual - expPaidAnnual;
|
||||
var cfRows = [
|
||||
{ label: '项目现金流', vals: cfQ, annual: cfAnnual },
|
||||
{ label: '平台费用(已付)', vals: expPaidQ, annual: expPaidAnnual },
|
||||
{ label: '业务线现金流', vals: blCfQ, annual: blCfAnnual }
|
||||
{ label: '部门现金流', vals: expPaidQ, annual: expPaidAnnual },
|
||||
{ label: '经营现金流', vals: blCfQ, annual: blCfAnnual }
|
||||
];
|
||||
var cfTbody = '';
|
||||
for (var cfri = 0; cfri < 3; cfri++) {
|
||||
@@ -1301,14 +1302,15 @@ function renderPlanQuarterlyOverview() {
|
||||
for (var qi = 0; qi < 4; qi++) {
|
||||
var cellCls = cfri === 2 ? (blCfQ[qi] >= 0 ? 'text-green-600' : 'text-red-600') : 'text-slate-700';
|
||||
rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap ' + cellCls + '">' + moneyIntL(cfdr.vals[qi]) + '</td>';
|
||||
if (qi > 0) rowHtml += '<td class="py-1.5 text-right text-sm whitespace-nowrap">' + qoq(cfdr.vals[qi], cfdr.vals[qi - 1]) + '</td>';
|
||||
}
|
||||
cfTbody += rowHtml + '</tr>';
|
||||
}
|
||||
|
||||
document.querySelector("#planQuarterlyOverview").innerHTML = '<div class="grid gap-2.5">' +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务线经营情况</h3><p class="text-xs text-slate-400 mb-3">毛利 - 平台费用 = 利润</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务线现金流</h3><p class="text-xs text-slate-400 mb-3">项目现金流 - 平台费用(已付)= 业务线现金流</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-sm font-bold text-slate-700">业务(项目)财务季度概览</h3><p class="text-xs text-slate-400 mb-3">合同 → 确收 → 毛利 → 成本 → 支出 → 回款 → 项目现金流</p><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">部门损益情况 <span class="text-sm text-slate-500 font-normal ml-3">部门利润 = 所有项目产生的毛利 - 部门 1 级产生的人力成本,研发成本,差旅和其他支出</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + deptTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">部门现金流 <span class="text-sm text-slate-500 font-normal ml-3">部门现金流=所有项目产生的现金流 - 部门一级因费用产生的现金流出或流入</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + deptThead + '</thead><tbody>' + cfTbody + '</tbody></table></div>', 'p-4') +
|
||||
card('<h3 class="text-base font-bold text-slate-700 mb-3">项目经营详情 <span class="text-sm text-slate-500 font-normal ml-3">所有项目产生的签单,确收,毛利,回款,当期成本,当期流出,现金流</span></h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>', 'p-4') +
|
||||
'</div>';
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
}
|
||||
|
||||
@@ -108,11 +108,12 @@ function renderPlanExpense() {
|
||||
}
|
||||
|
||||
// 模态框 monthOpts for form (with current value preselected)
|
||||
var curMonth = thisYear + '-' + String(now.getMonth() + 1).padStart(2, '0');
|
||||
var modalMonthOpts = '<option value="">请选择</option>';
|
||||
for (var yr2 = thisYear - 1; yr2 <= thisYear + 1; yr2++)
|
||||
for (var m2 = 1; m2 <= 12; m2++) {
|
||||
var mv2 = yr2 + '-' + String(m2).padStart(2, '0');
|
||||
modalMonthOpts += '<option value="' + mv2 + '">' + mv2 + '</option>';
|
||||
modalMonthOpts += '<option value="' + mv2 + '"' + (mv2 === curMonth ? ' selected' : '') + '>' + mv2 + '</option>';
|
||||
}
|
||||
var statusOpts = STATUS_OPTIONS.map(function(s) { return '<option value="' + s + '">' + s + '</option>'; }).join('');
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ const state = {
|
||||
data: null,
|
||||
tenant: localStorage.getItem("opc-active-tenant") || "科普·无界",
|
||||
opFilter: "all",
|
||||
finFilter: "projects",
|
||||
finFilter: "overview",
|
||||
selectedProject: null,
|
||||
taskQuery: "",
|
||||
taskView: localStorage.getItem("opc-task-view") || "detail",
|
||||
finView: "overview",
|
||||
finSort: null, // { key: 'col', asc: true }
|
||||
planFilter: "projects",
|
||||
planFilter: "overview",
|
||||
planYear: new Date().getFullYear(),
|
||||
finYear: new Date().getFullYear(),
|
||||
planStatusFilter: "all",
|
||||
@@ -390,9 +390,10 @@ window.switchTenant = async (tenant) => {
|
||||
localStorage.setItem("opc-active-tenant", tenant);
|
||||
document.querySelector("#workspaceTitle").textContent = tenant.replace("·无界", "") + " OPC 工作台";
|
||||
if (typeof updateTenantLabel === "function") updateTenantLabel();
|
||||
state.planFilter = 'overview';
|
||||
state.finFilter = 'overview';
|
||||
updateSidebarTabs();
|
||||
if (tenant === "总览") { state.planFilter = 'overview'; state.finFilter = 'overview'; switchTab("home"); }
|
||||
else if (state.active === 'home') { state.active = 'plan'; localStorage.setItem("opc-active-tab", "plan"); document.querySelectorAll(".sidebar-tab").forEach((btn) => btn.classList.toggle("active", btn.dataset.tab === "plan")); document.querySelectorAll(".panel").forEach((panel) => panel.classList.toggle("active", panel.id === "plan")); }
|
||||
if (state.active === 'home') { state.active = 'plan'; localStorage.setItem("opc-active-tab", "plan"); document.querySelectorAll(".sidebar-tab").forEach((btn) => btn.classList.toggle("active", btn.dataset.tab === "plan")); document.querySelectorAll(".panel").forEach((panel) => panel.classList.toggle("active", panel.id === "plan")); }
|
||||
await load();
|
||||
};
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<header class="topbar border-b border-slate-200 bg-white px-8 py-5">
|
||||
<div class="flex items-center gap-3 w-full">
|
||||
<div class="flex-1">
|
||||
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.2.0.20</span></p>
|
||||
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.2.0.22</span></p>
|
||||
<div class="flex items-center gap-4 mt-1">
|
||||
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
|
||||
<div class="hidden sm:flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-full px-4 py-1.5">
|
||||
|
||||
Reference in New Issue
Block a user