|
|
|
|
@@ -290,6 +290,7 @@ function renderFinance() {
|
|
|
|
|
return h;
|
|
|
|
|
};
|
|
|
|
|
const SIGNED_COLS = [
|
|
|
|
|
{ key: '_expand', label: '' },
|
|
|
|
|
{ key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
|
|
|
|
|
{ key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'gross_rate', label: '毛利率' },
|
|
|
|
|
{ key: 'payment', label: '回款' }, { key: 'pay_rate', label: '回款率' },
|
|
|
|
|
@@ -331,7 +332,24 @@ function renderFinance() {
|
|
|
|
|
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';
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="openPfEditModal(${pf.id})"><td class="px-2 py-px text-center align-middle text-sm">${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 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="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td></tr>`;
|
|
|
|
|
var budgetArr = [];
|
|
|
|
|
var expenseArr = [];
|
|
|
|
|
try { budgetArr = JSON.parse(pf.budget_data || "[]"); } catch(e) {}
|
|
|
|
|
try { expenseArr = JSON.parse(pf.expense_data || "[]"); } catch(e) {}
|
|
|
|
|
var detailRows = budgetArr.filter(function(b) {
|
|
|
|
|
var exp = expenseArr.find(function(e) { return e.month === (b.month || ''); }) || {};
|
|
|
|
|
var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0);
|
|
|
|
|
var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0);
|
|
|
|
|
return bRev || bGross || bPay || eCost || ePaid;
|
|
|
|
|
}).map(function(b) {
|
|
|
|
|
var m = b.month || '';
|
|
|
|
|
var exp = expenseArr.find(function(e) { return e.month === m; }) || {};
|
|
|
|
|
var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0);
|
|
|
|
|
var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0);
|
|
|
|
|
var bCf = bPay - ePaid;
|
|
|
|
|
return '<tr class="bg-slate-50"><td class="px-2 py-px"></td><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></tr>';
|
|
|
|
|
}).join('');
|
|
|
|
|
return `<tr class="border-b border-slate-100 hover:bg-slate-50 cursor-pointer" onclick="openPfEditModal(${pf.id})"><td class="px-2 py-px text-center align-middle text-sm" onclick="event.stopPropagation();finToggleExpand(${pf.id})"><i data-lucide="chevron-right" style="width:14px;height:14px;color:#94a3b8" id="fin_expand_icon_${pf.id}"></i></td><td class="px-2 py-px text-center align-middle text-sm">${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 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="p-2 text-center font-semibold align-middle ${cfCls}">${cf}</td></tr><tr class="hidden" id="fin_expand_${pf.id}"><td colspan="13"><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"></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></tr></thead><tbody>${detailRows}</tbody></table></td></tr>`;
|
|
|
|
|
};
|
|
|
|
|
// 待签约简版行
|
|
|
|
|
const tdRowUnsigned = (pf, cost, gross) => {
|
|
|
|
|
@@ -752,6 +770,19 @@ window.deleteFinFollowup = async (event, followupId) => {
|
|
|
|
|
if (pfId) await loadFinFollowups(pfId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.finToggleExpand = (pfId) => {
|
|
|
|
|
var row = document.getElementById('fin_expand_' + pfId);
|
|
|
|
|
var icon = document.getElementById('fin_expand_icon_' + pfId);
|
|
|
|
|
if (!row) return;
|
|
|
|
|
if (row.classList.contains('hidden')) {
|
|
|
|
|
row.classList.remove('hidden');
|
|
|
|
|
if (icon) { icon.setAttribute('data-lucide', 'chevron-down'); if (window.lucide) window.lucide.createIcons(); }
|
|
|
|
|
} else {
|
|
|
|
|
row.classList.add('hidden');
|
|
|
|
|
if (icon) { icon.setAttribute('data-lucide', 'chevron-right'); if (window.lucide) window.lucide.createIcons(); }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.openPfEditModal = (pfId) => {
|
|
|
|
|
const pf = (state.data.projectFinances || []).find(x => x.id === pfId);
|
|
|
|
|
if (!pf) return;
|
|
|
|
|
|