|
|
|
|
@@ -96,7 +96,7 @@ function renderPlan() {
|
|
|
|
|
})();
|
|
|
|
|
const sm = pf.sign_month || "";
|
|
|
|
|
const signMonthCell = `<td class="p-2 text-center align-middle text-sm"><span class="pf-sm-text cursor-pointer hover:text-blue-600" id="pf-sm-${pf.id}" onclick="event.stopPropagation(); planEditSignMonth(event, ${pf.id})">${sm || '—'}</span></td>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-sm font-medium text-center align-middle">${esc(pf.customer_name)}</td>${signMonthCell}<td class="p-2 text-center align-middle text-sm">${money(pf.sign_amount)}</td>${mCols}${totalCol}</tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-center align-middle text-sm">${esc(pf.client_name || "")}</td><td class="p-2 text-sm font-medium text-center align-middle">${esc(pf.customer_name)}</td><td class="p-2 text-center align-middle text-sm">${esc(pf.weight || "20%")}</td><td class="p-2 text-center align-middle text-sm">${esc(pf.project_type || "待签约")}</td>${signMonthCell}<td class="p-2 text-center align-middle text-sm">${money(pf.sign_amount)}</td>${mCols}${totalCol}</tr>`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const now2 = new Date();
|
|
|
|
|
@@ -150,7 +150,7 @@ function renderPlan() {
|
|
|
|
|
</div>
|
|
|
|
|
<div class="grid grid-cols-3 gap-3">
|
|
|
|
|
<label class="block"><span class="fin-label">联系电话</span><input name="contact_phone" class="form-ctrl" placeholder="请输入联系电话"></label>
|
|
|
|
|
<label class="block"><span class="fin-label">其他</span><input name="other_info" class="form-ctrl" placeholder="备注信息"></label>
|
|
|
|
|
<label class="block"><span class="fin-label">其他</span><input name="other_info" class="form-ctrl" placeholder="备注信息"></label></div><div class="grid grid-cols-3 gap-3"><label class="block"><span class="fin-label">客户名称</span><input name="client_name" class="form-ctrl" placeholder="客户名称"></label><label class="block"><span class="fin-label">项目类型</span><select name="project_type" class="form-ctrl bg-white"><option>待签约</option><option>已签约</option></select></label><label class="block"><span class="fin-label">加权</span><select name="weight" class="form-ctrl bg-white"><option>20%</option><option>40%</option><option>60%</option><option>80%</option><option>100%</option></select></label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="block"><span class="fin-label">业务类型</span><div class="flex flex-wrap gap-2 mt-1" id="plan_businessTypeChecks">${fmTypes.map(t => `<label class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full border border-slate-200 cursor-pointer hover:border-blue-300 text-sm bt-chip" onclick="planToggleBtChip(this)"><input type="checkbox" name="business_type[]" value="${t}" class="hidden"><span>${t}</span></label>`).join("")}</div></div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -285,8 +285,10 @@ function renderPlan() {
|
|
|
|
|
return h;
|
|
|
|
|
};
|
|
|
|
|
const SIGNED_COLS = [
|
|
|
|
|
{ key: 'client_name', label: '客户' },
|
|
|
|
|
{ key: 'customer_name', label: '项目名称' },
|
|
|
|
|
,
|
|
|
|
|
{ key: 'weight', label: '加权' },
|
|
|
|
|
{ key: 'project_type', label: '项目类型' },
|
|
|
|
|
{ key: 'sign_amount', label: '签约金额' },
|
|
|
|
|
{ key: 'rev', label: '已确收' },
|
|
|
|
|
{ key: 'gross', label: '毛利' },
|
|
|
|
|
@@ -296,13 +298,13 @@ function renderPlan() {
|
|
|
|
|
{ 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: 'client_name', label: '客户' },
|
|
|
|
|
{ key: 'customer_name', label: '项目名称' },
|
|
|
|
|
,
|
|
|
|
|
{ key: 'weight', label: '加权' },
|
|
|
|
|
{ key: 'project_type', label: '项目类型' },
|
|
|
|
|
{ key: 'sign_amount', label: '签约金额' },
|
|
|
|
|
{ key: 'sign_month', label: '签约月份' },
|
|
|
|
|
{ key: 'gross', label: '毛利' },
|
|
|
|
|
@@ -317,13 +319,13 @@ 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 ? 6 : 13;
|
|
|
|
|
const theadCols = isUnsigned ? 9 : 14;
|
|
|
|
|
const tbody = rows.length ? `<tbody>${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-6';
|
|
|
|
|
const subtitle = isUnsigned
|
|
|
|
|
? '合同 → 毛利 → 成本 → 项目利润'
|
|
|
|
|
: '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流 → 执行率 → 毛利率 → 回款率 → 付款率';
|
|
|
|
|
: '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流';
|
|
|
|
|
return card(`<div class="grid ${cardGrid} gap-3 mb-3">${cards.map(([l, v, c]) => '<div class="metric-card p-3"><span class="flex items-center gap-2 text-sm text-slate-500">' + l + '</span><strong class="mt-2 block text-xl ' + c + '">' + v + '</strong></div>').join("")}</div>`, "p-4") +
|
|
|
|
|
card(`<h3 class="text-sm font-bold text-slate-700">项目财务明细</h3><p class="text-xs text-slate-400 mb-3">${subtitle}</p><div class="overflow-x-auto"><table class="w-full text-sm">${thead}${tbody}</table></div>`, "p-4");
|
|
|
|
|
};
|
|
|
|
|
@@ -342,14 +344,14 @@ function renderPlan() {
|
|
|
|
|
const profit = gross;
|
|
|
|
|
const pfCls = profit >= 0 ? 'text-green-600' : 'text-red-600';
|
|
|
|
|
const pfVal = profit ? money(profit) : '<span class="text-slate-300">—</span>';
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 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="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center text-blue-700 align-middle">${fmtNoUnit(rev)}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td><td class="p-2 text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="p-2 text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="p-2 text-center align-middle text-sm">${exe}</td><td class="p-2 text-center align-middle text-sm">${grossR}</td><td class="p-2 text-center align-middle text-sm">${payR}</td><td class="p-2 text-center align-middle text-sm">${paidR}</td></tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-center align-middle text-sm">${esc(pf.client_name || "")}</td><td class="p-2 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="p-2 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="p-2 text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}</td><td class="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center text-blue-700 align-middle">${fmtNoUnit(rev)}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td><td class="p-2 text-center text-amber-700 align-middle">${fmtNoUnit(payment)}</td><td class="p-2 text-center text-purple-700 align-middle">${fmtNoUnit(paid)}</td><td class="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td><td class="p-2 text-center align-middle text-sm">${exe}</td><td class="p-2 text-center align-middle text-sm">${payR}</td></tr>`;
|
|
|
|
|
};
|
|
|
|
|
// 待签约简版行
|
|
|
|
|
const tdRowUnsigned = (pf, cost, gross) => {
|
|
|
|
|
const profit = gross;
|
|
|
|
|
const pfCls = profit >= 0 ? 'text-green-600' : 'text-red-600';
|
|
|
|
|
const pfVal = profit ? money(profit) : '<span class="text-slate-300">—</span>';
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 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="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center align-middle text-sm">${pf.sign_month || '—'}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td></tr>`;
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="planOpenPfEditModal(${pf.id})"><td class="p-2 text-center align-middle text-sm">${esc(pf.client_name || "")}</td><td class="p-2 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="p-2 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="p-2 text-center align-middle text-sm" onclick="event.stopPropagation()">${planInlineSelect('ptype', pf.id, 'project_type', pf.project_type || '待签约', ['待签约','已签约'])}</td><td class="p-2 text-center align-middle text-sm font-medium">${money(pf.sign_amount)}</td><td class="p-2 text-center align-middle text-sm">${pf.sign_month || '—'}</td><td class="p-2 text-center align-middle text-sm font-medium">${fmtNoUnit(gross)}</td><td class="p-2 text-center text-rose-700 align-middle">${fmtNoUnit(cost)}</td><td class="p-2 text-center font-semibold align-middle ${pfCls}">${pfVal}</td></tr>`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (state.planView === 'monthly') {
|
|
|
|
|
@@ -784,7 +786,7 @@ window.planOpenPfEditModal = (pfId) => {
|
|
|
|
|
document.querySelector("#plan_pf-id-input").value = pf.id;
|
|
|
|
|
document.querySelector("#planModalTitle").textContent = "编辑项目财务";
|
|
|
|
|
document.querySelector("#planDeleteBtn").classList.remove("hidden");
|
|
|
|
|
const form = document.querySelector("#financeModal form");
|
|
|
|
|
const form = document.querySelector("#planModal form");
|
|
|
|
|
form.querySelector('[name="project_id"]').value = pf.project_id || "";
|
|
|
|
|
const deptDisplay = form.querySelector('.bg-slate-50 [disabled]');
|
|
|
|
|
if (deptDisplay) deptDisplay.value = pf.project_id || "";
|
|
|
|
|
@@ -820,6 +822,9 @@ window.planOpenPfEditModal = (pfId) => {
|
|
|
|
|
setVal("contact_name", pf.contact_name);
|
|
|
|
|
setVal("contact_phone", pf.contact_phone);
|
|
|
|
|
setVal("other_info", pf.other_info);
|
|
|
|
|
setVal("client_name", pf.client_name);
|
|
|
|
|
form.querySelector('[name="project_type"]').value = pf.project_type || "待签约";
|
|
|
|
|
form.querySelector('[name="weight"]').value = pf.weight || "20%";
|
|
|
|
|
let budgetData = [];
|
|
|
|
|
try { budgetData = JSON.parse(pf.budget_data || "[]"); } catch (e) { budgetData = []; }
|
|
|
|
|
planInitRevpayTable(budgetData.length ? budgetData : null);
|
|
|
|
|
@@ -961,46 +966,50 @@ function renderPlanOverview() {
|
|
|
|
|
const { summary, financeMonthly } = state.data;
|
|
|
|
|
if (!summary) return;
|
|
|
|
|
const m = summary.metrics;
|
|
|
|
|
const moneyIntL = (v) => `${Math.round(Number(v || 0)).toLocaleString("zh-CN")} 元`;
|
|
|
|
|
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
|
|
|
|
|
const moneyWan = (v) => {
|
|
|
|
|
const n = Number(v || 0);
|
|
|
|
|
return n >= 10000 ? (n / 10000).toFixed(0) + "万" : n.toLocaleString("zh-CN");
|
|
|
|
|
};
|
|
|
|
|
const card = (body, cls) => '<div class="card ' + (cls || 'p-4') + '">' + body + '</div>';
|
|
|
|
|
|
|
|
|
|
const rows1 = [["年度累计", moneyIntL(m.signed_annual || m.signed_amount)], ["本季度累计", moneyIntL(m.signed_q2 || 0)], ["本月累计", moneyIntL(m.signed_month || 0)], ["上本季度累计", moneyIntL(m.signed_prev_q || 0)], ["上月累计", moneyIntL(m.signed_prev_month || 0)]];
|
|
|
|
|
const rows2 = [["年度累计", moneyIntL(m.revenue_annual || 0)], ["本季度累计", moneyIntL(m.revenue_q2 || 0)], ["本月累计", moneyIntL(m.monthly_revenue || 0)], ["上本季度累计", moneyIntL(m.revenue_prev_q || 0)], ["上月累计", moneyIntL(m.revenue_prev_month || 0)]];
|
|
|
|
|
const rows3 = [["年度累计", moneyIntL(m.gross_annual || 0)], ["本季度累计", moneyIntL(m.gross_q2 || 0)], ["本月累计", moneyIntL(m.monthly_net_profit || 0)], ["上本季度累计", moneyIntL(m.gross_prev_q || 0)], ["上月累计", moneyIntL(m.gross_prev_month || 0)]];
|
|
|
|
|
const rows4 = [["年度累计", moneyIntL(m.cost_annual || 0)], ["本季度累计", moneyIntL(m.cost_q2 || 0)], ["本月累计", moneyIntL(m.cost_month || 0)], ["上本季度累计", moneyIntL(m.cost_prev_q || 0)], ["上月累计", moneyIntL(m.cost_prev_month || 0)]];
|
|
|
|
|
const rows5 = [["年度累计", moneyIntL(m.profit_annual || 0)], ["本季度累计", moneyIntL(m.profit_q2 || 0)], ["本月累计", moneyIntL(m.profit_month || 0)], ["上本季度累计", moneyIntL(m.profit_prev_q || 0)], ["上月累计", moneyIntL(m.profit_prev_month || 0)]];
|
|
|
|
|
const rows7 = [["年度累计", moneyIntL(m.payment_annual || 0)], ["本季度累计", moneyIntL(m.payment_q2 || 0)], ["本月累计", moneyIntL(m.payment_month || 0)], ["上本季度累计", moneyIntL(m.payment_prev_q || 0)], ["上月累计", moneyIntL(m.payment_prev_month || 0)]];
|
|
|
|
|
const rows8 = [["年度累计", moneyIntL(m.paid_annual || 0)], ["本季度累计", moneyIntL(m.paid_q2 || 0)], ["本月累计", moneyIntL(m.paid_month || 0)], ["上本季度累计", moneyIntL(m.paid_prev_q || 0)], ["上月累计", moneyIntL(m.paid_prev_month || 0)]];
|
|
|
|
|
const rows9 = [["年度累计", moneyIntL(m.cashflow_annual || 0)], ["本季度累计", moneyIntL(m.cashflow_q2 || 0)], ["本月累计", moneyIntL(m.cashflow_month || 0)], ["上本季度累计", moneyIntL(m.cashflow_prev_q || 0)], ["上月累计", moneyIntL(m.cashflow_prev_month || 0)]];
|
|
|
|
|
|
|
|
|
|
const qoq = (cur, prev) => { if (!prev) return '<span class="text-slate-300">—</span>'; const pct = Math.round((cur - prev) / prev * 100); const cls = pct >= 0 ? 'text-green-600' : 'text-red-600'; const sign = pct >= 0 ? '+' : ''; return '<span class="' + cls + '">' + sign + pct + '%</span>'; };
|
|
|
|
|
// 月度数据 (financeMonthly 有12个月)
|
|
|
|
|
const fm = financeMonthly || [];
|
|
|
|
|
const mLbl = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
|
|
|
|
|
const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流'];
|
|
|
|
|
const Q_FIELDS = [m.signed_q2||0, m.revenue_q2, m.gross_q2, m.cost_q2||0, m.profit_q2||0, m.payment_q2||0, m.paid_q2||0, m.cashflow_q2||0];
|
|
|
|
|
const Q_PREV = [m.signed_prev_q||0, m.revenue_prev_q||0, m.gross_prev_q||0, m.cost_prev_q||0, m.profit_prev_q||0, m.payment_prev_q||0, m.paid_prev_q||0, m.cashflow_prev_q||0];
|
|
|
|
|
const M_FIELDS = [m.signed_month||0, m.monthly_revenue, m.monthly_net_profit, m.cost_month||0, m.profit_month||0, m.payment_month||0, m.paid_month||0, m.cashflow_month||0];
|
|
|
|
|
const M_PREV = [m.signed_prev_month||0, m.revenue_prev_month||0, m.gross_prev_month||0, m.cost_prev_month||0, m.profit_prev_month||0, m.payment_prev_month||0, m.paid_prev_month||0, m.cashflow_prev_month||0];
|
|
|
|
|
const ROWS = [rows1, rows2, rows3, rows4, rows5, rows7, rows8, rows9];
|
|
|
|
|
const CF_RAW = [m.cashflow_annual||0, m.cashflow_q2||0, m.cashflow_month||0, m.cashflow_prev_q||0, m.cashflow_prev_month||0];
|
|
|
|
|
const PF_RAW = [m.profit_annual||0, m.profit_q2||0, m.profit_month||0, m.profit_prev_q||0, m.profit_prev_month||0];
|
|
|
|
|
|
|
|
|
|
var tdVal = function(ri, col, val) {
|
|
|
|
|
if (ri === 4) return '<td class="py-2 text-right font-semibold ' + (PF_RAW[col] >= 0 ? 'text-green-600' : 'text-red-600') + '">' + val + '</td>';
|
|
|
|
|
if (ri === 7) return '<td class="py-2 text-right font-semibold ' + (CF_RAW[col] >= 0 ? 'text-green-600' : 'text-red-600') + '">' + val + '</td>';
|
|
|
|
|
return '<td class="py-2 text-right font-semibold text-slate-800">' + val + '</td>';
|
|
|
|
|
};
|
|
|
|
|
var thead = '<tr class="border-b border-slate-200"><th class="py-2 text-left text-slate-500">指标</th><th class="py-2 text-right font-semibold text-slate-700">年度累计</th><th class="py-2 text-right font-semibold text-slate-700">上季度累计</th><th class="py-2 text-right font-semibold text-slate-700">上月累计</th><th class="py-2 text-right font-semibold text-slate-700">本季度累计</th><th class="py-2 text-right font-semibold text-slate-400">季环比</th><th class="py-2 text-right font-semibold text-slate-700">本月累计</th><th class="py-2 text-right font-semibold text-slate-400">月环比</th></tr>';
|
|
|
|
|
// 每行的年度累计 + 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,
|
|
|
|
|
];
|
|
|
|
|
// 字段映射: sign, revenue, gross, cost, profit(=gross-cost), payment, paid, cashflow(=payment-paid)
|
|
|
|
|
const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null];
|
|
|
|
|
var thead = '<tr class="border-b border-slate-200"><th class="py-2 text-left text-slate-500 whitespace-nowrap">指标</th><th class="py-2 text-right font-semibold text-slate-700 whitespace-nowrap">年度累计</th>';
|
|
|
|
|
for (var mi = 0; mi < 12; mi++) { thead += '<th class="py-2 text-right font-semibold text-slate-600 whitespace-nowrap">' + mLbl[mi] + '</th>'; }
|
|
|
|
|
thead += '</tr>';
|
|
|
|
|
var tbody = '';
|
|
|
|
|
for (var ri = 0; ri < 8; ri++) {
|
|
|
|
|
var vals = ROWS[ri];
|
|
|
|
|
tbody += '<tr class="border-b border-slate-100 last:border-0"><td class="py-2 text-left font-semibold text-slate-700">' + LABELS[ri] + '</td>' +
|
|
|
|
|
tdVal(ri, 0, vals[0][1]) + tdVal(ri, 3, vals[3][1]) + tdVal(ri, 4, vals[4][1]) + tdVal(ri, 1, vals[1][1]) +
|
|
|
|
|
'<td class="py-2 text-right">' + qoq(Q_FIELDS[ri], Q_PREV[ri]) + '</td>' +
|
|
|
|
|
tdVal(ri, 2, vals[2][1]) +
|
|
|
|
|
'<td class="py-2 text-right">' + qoq(M_FIELDS[ri], M_PREV[ri]) + '</td></tr>';
|
|
|
|
|
var rowHtml = '<tr class="border-b border-slate-100 last:border-0"><td class="py-2 text-left font-semibold text-slate-700 whitespace-nowrap">' + LABELS[ri] + '</td>';
|
|
|
|
|
rowHtml += '<td class="py-2 text-right font-semibold text-slate-800 whitespace-nowrap">' + moneyIntL(annualVals[ri]) + '</td>';
|
|
|
|
|
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 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 += '<td class="py-2 text-right text-sm whitespace-nowrap ' + cls + '">' + moneyIntL(val) + '</td>';
|
|
|
|
|
}
|
|
|
|
|
tbody += rowHtml + '</tr>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.querySelector("#planOverview").innerHTML = '<div class="grid gap-5">' +
|
|
|
|
|
@@ -1046,3 +1055,21 @@ function renderPlanCharts(data) {
|
|
|
|
|
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) {
|
|
|
|
|
var opts = '';
|
|
|
|
|
for (var i = 0; i < options.length; i++) {
|
|
|
|
|
opts += '<option value="' + options[i] + '"' + (options[i] === currentVal ? ' selected' : '') + '>' + options[i] + '</option>';
|
|
|
|
|
}
|
|
|
|
|
return '<select onchange="planInlineUpdate(' + pfId + ', \'' + field + '\'' + ', this.value)" class="text-sm bg-transparent border-0 cursor-pointer text-center" style="appearance:auto;-webkit-appearance:auto;vertical-align:middle">' + opts + '</select>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.planInlineUpdate = async (pfId, field, value) => {
|
|
|
|
|
try {
|
|
|
|
|
await api("/api/planFinances/" + pfId, { method: "PUT", body: JSON.stringify({ data: { [field]: value } }) });
|
|
|
|
|
await load();
|
|
|
|
|
toast("已更新", "success");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
toast("更新失败:" + error.message, "error");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|