|
|
|
|
@@ -109,7 +109,7 @@ function renderPlan() {
|
|
|
|
|
const activeQ = state.planQuarter !== undefined ? state.planQuarter : Math.floor(nowD.getMonth() / 3);
|
|
|
|
|
const activeMonths = qMonths[activeQ];
|
|
|
|
|
const yearOpts = [2024,2025,2026,2027];
|
|
|
|
|
const finHeaderBase = `<div class="flex items-center gap-2"><button onclick="setPlanView('overview')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planView==='overview'?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">总视图</button><span class="text-slate-300 mx-2">|</span><span class="text-sm text-slate-500">年份:</span><select onchange="setPlanYear(this.value)" class="px-2 py-0.5 rounded text-sm font-medium border border-slate-200 bg-white">${yearOpts.map(y => `<option value="${y}" ${state.planYear===y?'selected':''}>${y}</option>`).join('')}</select><span class="text-slate-300 mx-2">|</span><span class="text-sm text-slate-500">季度:</span>${qLabels.map((q,i) => `<button onclick="setPlanQuarter(${i})" class="px-3 py-0.5 rounded text-sm font-medium ${state.planView==='quarterly'&&activeQ===i?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">${q}</button>`).join('')}<span class="text-slate-300 mx-2">|</span><span class="text-sm text-slate-500">月度:</span>${activeMonths.map(m => { const ms = `${state.planYear}-${String(m).padStart(2,'0')}`; return `<button onclick="setPlanMonth('${ms}')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planView==='monthly'&&state.planMonth===ms?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">${m}月</button>`; }).join('')}</div>`;
|
|
|
|
|
const finHeaderBase = `<div class="flex items-center gap-2"><button onclick="setPlanStatusFilter('all')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planStatusFilter==='all'?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">全部</button><button onclick="setPlanStatusFilter('unsigned')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planStatusFilter==='unsigned'?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">待签约</button><button onclick="setPlanStatusFilter('signed')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planStatusFilter==='signed'?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">已签约</button><button onclick="setPlanStatusFilter('paymentonly')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planStatusFilter==='paymentonly'?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">只付款</button><span class="text-slate-300 mx-2">|</span><span class="text-sm text-slate-500">年份:</span><select onchange="setPlanYear(this.value)" class="px-2 py-0.5 rounded text-sm font-medium border border-slate-200 bg-white">${yearOpts.map(y => `<option value="${y}" ${state.planYear===y?'selected':''}>${y}</option>`).join('')}</select><span class="text-slate-300 mx-2">|</span><span class="text-sm text-slate-500">季度:</span>${qLabels.map((q,i) => `<button onclick="setPlanQuarter(${i})" class="px-3 py-0.5 rounded text-sm font-medium ${state.planView==='quarterly'&&activeQ===i?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">${q}</button>`).join('')}<span class="text-slate-300 mx-2">|</span><span class="text-sm text-slate-500">月度:</span>${activeMonths.map(m => { const ms = `${state.planYear}-${String(m).padStart(2,'0')}`; return `<button onclick="setPlanMonth('${ms}')" class="px-3 py-0.5 rounded text-sm font-medium ${state.planView==='monthly'&&state.planMonth===ms?'bg-blue-600 text-white':'bg-slate-100 text-slate-600 hover:bg-slate-200'}">${m}月</button>`; }).join('')}</div>`;
|
|
|
|
|
const finAddBtn = `<button class="btn btn-primary btn-sm" onclick="openPlanModal()">新增财务项目</button>`;
|
|
|
|
|
|
|
|
|
|
const isOverviewMode = false;
|
|
|
|
|
@@ -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">
|
|
|
|
|
@@ -257,9 +257,9 @@ function renderPlan() {
|
|
|
|
|
{ label: '已确收', hideUnsigned: true, value: ctx => moneyWan(ctx.sumRev), color: 'text-blue-700' },
|
|
|
|
|
{ label: '待回款', hideUnsigned: true, value: ctx => { const v = ctx.sumRev - ctx.sumPay; return { val: v < 0 ? '超额回款 ' + moneyWan(Math.abs(v)) : moneyWan(v), cls: v > 0 ? 'text-red-600' : v < 0 ? 'text-green-600' : 'text-slate-400' }; }, color: null },
|
|
|
|
|
{ label: '毛利', value: ctx => moneyWan(ctx.sumGross), color: 'text-green-700' },
|
|
|
|
|
{ label: '成本(不含平台费用)', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' },
|
|
|
|
|
{ label: '当期成本(不含平台费用)', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' },
|
|
|
|
|
{ label: '回款', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPay), color: 'text-amber-700' },
|
|
|
|
|
{ label: '已付', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPaid), color: 'text-purple-700' },
|
|
|
|
|
{ label: '当期流出', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPaid), color: 'text-purple-700' },
|
|
|
|
|
{ label: '现金流(回款-支出)', hideUnsigned: true, value: ctx => { const v = ctx.sumPay - ctx.sumPaid; return { val: moneyWan(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null },
|
|
|
|
|
{ label: '毛利率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumGross ? Math.round(ctx.sumGross / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
|
|
|
|
|
{ label: '回款率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumPay ? Math.round(ctx.sumPay / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
|
|
|
|
|
@@ -307,9 +307,8 @@ function renderPlan() {
|
|
|
|
|
{ key: 'gross_rate', label: '毛利率' },
|
|
|
|
|
{ key: 'payment', label: '回款' },
|
|
|
|
|
{ key: 'pay_rate', label: '回款率' },
|
|
|
|
|
{ key: 'cost', label: '成本' },
|
|
|
|
|
{ key: 'paid', label: '付款' },
|
|
|
|
|
{ key: 'paid_rate', label: '付款率' },
|
|
|
|
|
{ key: 'cost', label: '当期成本' },
|
|
|
|
|
{ key: 'paid', label: '当期流出' },
|
|
|
|
|
{ key: 'cashflow', label: '现金流' },
|
|
|
|
|
{ key: 'pending', label: '待回款' },
|
|
|
|
|
];
|
|
|
|
|
@@ -321,7 +320,7 @@ function renderPlan() {
|
|
|
|
|
{ key: 'sign_amount', label: '签约金额' },
|
|
|
|
|
{ key: 'sign_month', label: '签约月份' },
|
|
|
|
|
{ key: 'gross', label: '毛利' },
|
|
|
|
|
{ key: 'cost', label: '成本' },
|
|
|
|
|
{ key: 'cost', label: '当期成本' },
|
|
|
|
|
{ key: 'profit', label: '项目利润' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
@@ -332,7 +331,7 @@ function renderPlan() {
|
|
|
|
|
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 ? 9 : 14;
|
|
|
|
|
const theadCols = isUnsigned ? 9 : 13;
|
|
|
|
|
const tbody = rows.length ? `<tbody style="line-height:1.37">${rows.join("")}</tbody>` : `<tr><td colspan="${theadCols}" class="p-6 text-center text-slate-400">${emptyText}</td></tr>`;
|
|
|
|
|
|
|
|
|
|
const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-5';
|
|
|
|
|
@@ -345,12 +344,26 @@ function renderPlan() {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fmtNoUnit = (v) => v ? `<span class="font-medium">${Number(v||0).toLocaleString('zh-CN')}</span>` : '<span class="text-slate-300">—</span>';
|
|
|
|
|
|
|
|
|
|
// 状态过滤函数
|
|
|
|
|
const filterByStatus = (items) => {
|
|
|
|
|
var sf = state.planStatusFilter || 'all';
|
|
|
|
|
if (sf === 'all') return items;
|
|
|
|
|
return items.filter(d => {
|
|
|
|
|
if (sf === 'unsigned') return (d.pf.project_type || '待签约') === '待签约';
|
|
|
|
|
if (sf === 'signed') return (d.pf.project_type || '待签约') === '已签约';
|
|
|
|
|
if (sf === 'paymentonly') {
|
|
|
|
|
return (!d.rev && !d.gross && !d.payment) && (d.cost || d.paid);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const tdRow = (pf, rev, payment, cost, paid, gross) => {
|
|
|
|
|
const cashflow = payment - paid;
|
|
|
|
|
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 ? '<span class="' + payRCls + ' font-semibold">' + payRVal + '%</span>' : '<span class="text-slate-300">—</span>';
|
|
|
|
|
const paidR = cost && paid ? Math.round(paid / cost * 100) + '%' : '<span class="text-slate-300">—</span>';
|
|
|
|
|
const grossR = rev && gross ? Math.round(gross / rev * 100) + '%' : '<span class="text-slate-300">—</span>';
|
|
|
|
|
const cf = cashflow ? money(cashflow) : '<span class="text-slate-300">—</span>';
|
|
|
|
|
const cfCls = cashflow >= 0 ? 'text-green-600' : 'text-red-600';
|
|
|
|
|
@@ -379,7 +392,7 @@ function renderPlan() {
|
|
|
|
|
var bCf = bPay - ePaid;
|
|
|
|
|
return '<tr class="bg-slate-50"><td class="px-2 py-px"></td><td class="px-2 py-px text-center text-xs text-slate-400">' + m + '</td><td class="px-2 py-px text-center text-xs text-blue-600">' + (bRev ? money(bRev) : '—') + '</td><td class="px-2 py-px text-center text-xs text-green-600">' + (bGross ? money(bGross) : '—') + '</td><td class="px-2 py-px"></td><td class="px-2 py-px text-center text-xs text-amber-600">' + (bPay ? money(bPay) : '—') + '</td><td class="px-2 py-px"></td><td class="px-2 py-px text-center text-xs text-rose-600">' + (eCost ? money(eCost) : '—') + '</td><td class="px-2 py-px text-center text-xs text-purple-600">' + (ePaid ? money(ePaid) : '—') + '</td><td class="px-2 py-px"></td><td class="px-2 py-px text-center text-xs ' + (bCf >= 0 ? 'text-green-600' : 'text-red-600') + '">' + (bCf ? money(bCf) : '—') + '</td><td class="px-2 py-px text-center text-xs ' + (bRev - bPay > 0 ? 'text-red-600' : bRev - bPay < 0 ? 'text-green-600' : 'text-slate-300') + '">' + (bRev - bPay ? money(bRev - bPay) : '—') + '</td></tr>';
|
|
|
|
|
}).join('');
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation();planToggleExpand(${pf.id})"><i data-lucide="chevron-right" style="width:14px;height:14px;color:#94a3b8" id="plan_expand_icon_${pf.id}"></i></td><td class="px-2 py-px text-center align-middle text-sm truncate" style="max-width:100px" title="${esc(pf.client_name || '')}">${esc(pf.client_name || "")}</td><td class="px-2 py-px text-sm font-medium text-center align-middle truncate" style="min-width:300px;max-width:300px;width:300px" title="${esc(pf.customer_name)}">${esc(pf.customer_name)}</td><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])}</td><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}</td><td class="px-2 py-px text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="px-2 py-px text-center text-blue-700 align-middle">${fmtNoUnit(rev)}</td><td class="px-2 py-px text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="px-2 py-px text-center align-middle text-sm">${grossR}</td><td class="px-2 py-px text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="px-2 py-px text-center align-middle text-sm">${payR}</td><td class="px-2 py-px text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="px-2 py-px text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="px-2 py-px text-center align-middle text-sm">${paidR}</td><td class="px-2 py-px text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="px-2 py-px text-center align-middle text-sm font-semibold ${pendingCls}">${pendingVal}</td></tr><tr class="hidden" id="plan_expand_${pf.id}"><td colspan="16"><table class="w-full"><thead><tr class="border-b border-slate-200"><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">月份</th><th class="px-2 py-1 text-center text-xs text-slate-400">确收</th><th class="px-2 py-1 text-center text-xs text-slate-400">毛利</th><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">回款</th><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">成本</th><th class="px-2 py-1 text-center text-xs text-slate-400">支出</th><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">现金流</th><th class="px-2 py-1 text-center text-xs text-slate-400">待回款</th></tr></thead><tbody>${detailRows}</tbody></table></td></tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation();planToggleExpand(${pf.id})"><i data-lucide="chevron-right" style="width:14px;height:14px;color:#94a3b8" id="plan_expand_icon_${pf.id}"></i></td><td class="px-2 py-px text-center align-middle text-sm truncate" style="max-width:100px" title="${esc(pf.client_name || '')}">${esc(pf.client_name || "")}</td><td class="px-2 py-px text-sm font-medium text-center align-middle truncate" style="min-width:300px;max-width:300px;width:300px" title="${esc(pf.customer_name)}">${esc(pf.customer_name)}</td><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('weight', pf.id, 'weight', pf.weight || '20%', ['20%','40%','60%','80%','100%'])}</td><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}</td><td class="px-2 py-px text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="px-2 py-px text-center text-blue-700 align-middle">${fmtNoUnit(rev)}</td><td class="px-2 py-px text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="px-2 py-px text-center align-middle text-sm">${grossR}</td><td class="px-2 py-px text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="px-2 py-px text-center align-middle text-sm">${payR}</td><td class="px-2 py-px text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="px-2 py-px text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="px-2 py-px text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="px-2 py-px text-center align-middle text-sm font-semibold ${pendingCls}">${pendingVal}</td></tr><tr class="hidden" id="plan_expand_${pf.id}"><td colspan="15"><table class="w-full"><thead><tr class="border-b border-slate-200"><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">月份</th><th class="px-2 py-1 text-center text-xs text-slate-400">确收</th><th class="px-2 py-1 text-center text-xs text-slate-400">毛利</th><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">回款</th><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">当期成本</th><th class="px-2 py-1 text-center text-xs text-slate-400">当期流出</th><th class="px-2 py-1"></th><th class="px-2 py-1 text-center text-xs text-slate-400">现金流</th><th class="px-2 py-1 text-center text-xs text-slate-400">待回款</th></tr></thead><tbody>${detailRows}</tbody></table></td></tr>`;
|
|
|
|
|
};
|
|
|
|
|
// 待签约简版行
|
|
|
|
|
const tdRowUnsigned = (pf, cost, gross) => {
|
|
|
|
|
@@ -422,7 +435,7 @@ function renderPlan() {
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
var sortK = state.planSort && state.planSort.key ? state.planSort.key.split('|')[1] : null;
|
|
|
|
|
dataItems = sortData(dataItems, sortK);
|
|
|
|
|
dataItems = filterByStatus(sortData(dataItems, sortK));
|
|
|
|
|
const rows = [];
|
|
|
|
|
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
|
|
|
|
dataItems.forEach(d => {
|
|
|
|
|
@@ -477,7 +490,7 @@ function renderPlan() {
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
var sortK = state.planSort && state.planSort.key ? state.planSort.key.split('|')[1] : null;
|
|
|
|
|
dataItems = sortData(dataItems, sortK);
|
|
|
|
|
dataItems = filterByStatus(sortData(dataItems, sortK));
|
|
|
|
|
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
|
|
|
|
const rows = [];
|
|
|
|
|
dataItems.forEach(d => {
|
|
|
|
|
@@ -518,7 +531,7 @@ function renderPlan() {
|
|
|
|
|
});
|
|
|
|
|
var sortK = null;
|
|
|
|
|
if (state.planSort && state.planSort.key && state.planSort.key.startsWith('sig|')) sortK = state.planSort.key.split('|')[1];
|
|
|
|
|
dataItems = sortData(dataItems, sortK);
|
|
|
|
|
dataItems = filterByStatus(sortData(dataItems, sortK));
|
|
|
|
|
let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0;
|
|
|
|
|
const rows = [];
|
|
|
|
|
dataItems.forEach(d => {
|
|
|
|
|
@@ -549,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");
|
|
|
|
|
@@ -563,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");
|
|
|
|
|
@@ -1120,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');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 业务线现金流卡片
|
|
|
|
|
@@ -1139,7 +1152,7 @@ function renderPlanOverview() {
|
|
|
|
|
var cfRows = [
|
|
|
|
|
{ label: '项目现金流', vals: cfMonthly, annual: cfAnnual },
|
|
|
|
|
{ label: '平台费用(已付)', vals: expPaidMonthly, annual: expPaidAnnual },
|
|
|
|
|
{ label: '业务线现金流', vals: blCfMonthly, annual: blCfAnnual }
|
|
|
|
|
{ label: '部门现金流', vals: blCfMonthly, annual: blCfAnnual }
|
|
|
|
|
];
|
|
|
|
|
var cfTbody = '';
|
|
|
|
|
for (var cfri = 0; cfri < 3; cfri++) {
|
|
|
|
|
@@ -1155,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') +
|
|
|
|
|
@@ -1276,7 +1289,7 @@ function renderPlanQuarterlyOverview() {
|
|
|
|
|
var cfRows = [
|
|
|
|
|
{ label: '项目现金流', vals: cfQ, annual: cfAnnual },
|
|
|
|
|
{ label: '平台费用(已付)', vals: expPaidQ, annual: expPaidAnnual },
|
|
|
|
|
{ label: '业务线现金流', vals: blCfQ, annual: blCfAnnual }
|
|
|
|
|
{ label: '部门现金流', vals: blCfQ, annual: blCfAnnual }
|
|
|
|
|
];
|
|
|
|
|
var cfTbody = '';
|
|
|
|
|
for (var cfri = 0; cfri < 3; cfri++) {
|
|
|
|
|
@@ -1293,9 +1306,9 @@ function renderPlanQuarterlyOverview() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|