Net Cash Flow Projection Tool
Projection Setup & Initial Balance
Projection Parameters
Initial Cash Balance
$
Income Projection
Add Recurring Income Source
$
%
Recurring Income List:
Add One-Time / Irregular Income
$
One-Time Income List:
Expense Projection
Add Recurring Expense
$
%
Recurring Expense List:
Add One-Time / Irregular Expense
$
One-Time Expense List:
Net Cash Flow Projection
| Month/Year | Total Income ($) | Total Expenses ($) | Net Cash Flow ($) | Cumulative Balance ($) |
|---|
Please complete setup and input income/expenses, then click "Generate Projection".
Total Projected Income: ${ncfp_formatCurrency(totalProjectedIncome)}
Total Projected Expenses: ${ncfp_formatCurrency(totalProjectedExpenses)}
Overall Net Cash Flow: ${ncfp_formatCurrency(totalProjectedIncome - totalProjectedExpenses)}
Average Monthly Net Cash Flow: ${ncfp_formatCurrency((totalProjectedIncome - totalProjectedExpenses) / projectionMonths)}
Ending Cumulative Balance: ${ncfp_formatCurrency(cumulativeBalance)}
Number of Months with Negative Net Cash Flow: ${negativeFlowMonths}
`; ncfp_projectionDataForPdf = { params: { startMonthYear: startMonthYearStr, projectionMonths, initialBalance: ncfp_getInputNum('ncfp-initialCashBalance') }, recurringIncome: JSON.parse(JSON.stringify(ncfp_recurringIncome)), oneTimeIncome: JSON.parse(JSON.stringify(ncfp_oneTimeIncome)), recurringExpenses: JSON.parse(JSON.stringify(ncfp_recurringExpenses)), oneTimeExpenses: JSON.parse(JSON.stringify(ncfp_oneTimeExpenses)), projectionTable: projectionResults, summaryMetrics: { totalProjectedIncome, totalProjectedExpenses, overallNetCashFlow: totalProjectedIncome - totalProjectedExpenses, avgMonthlyNetCashFlow: (totalProjectedIncome - totalProjectedExpenses) / projectionMonths, endingBalance: cumulativeBalance, negativeFlowMonths } }; } catch (error) { console.error("Error generating projection:", error.stack); alert("Error generating projection: " + error.message); ncfp_projectionDataForPdf = null; } } function ncfp_downloadPDF() { if (!ncfp_projectionDataForPdf) { alert("Please generate a projection first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('PDF library (jsPDF) not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'pt'); if (typeof doc.autoTable !== 'function') { alert('PDF Table plugin (jsPDF-AutoTable) not loaded.'); return; } const data = ncfp_projectionDataForPdf; const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--ncfp-primary-color').trim(); let yPos = 40; doc.setFontSize(18); doc.setTextColor(primaryColor); doc.text("Net Cash Flow Projection Report", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 20; doc.setFontSize(10); doc.setTextColor(50); doc.text(`Projection Period: ${ncfp_formatMonthYear(data.params.startMonthYear)} for ${data.params.projectionMonths} months`, 14, yPos); yPos+=15; doc.text(`Initial Cash Balance: ${ncfp_formatCurrency(data.params.initialBalance)}`, 14, yPos); yPos += 20; function drawListToPdf(title, items, itemAmountKey = 'amount') { if (items.length > 0) { if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = 40; } doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text(title, 14, yPos); yPos += 15; const headers = [["Name/Description", "Details", "Amount ($)"]]; const body = items.map(item => { let details = ""; if (item.growthRate !== undefined) details += `${item.type === 'income' ? 'Growth' : 'Inflation'}: ${item.growthRate}% p.a. `; if (item.startDate) details += `Start: ${ncfp_formatMonthYear(item.startDate)} `; if (item.endDate) details += `End: ${ncfp_formatMonthYear(item.endDate)} `; if (item.category) details += `Category: ${item.category} `; if (item.date) details += `Date: ${ncfp_formatMonthYear(item.date)}`; return [item.name || item.description, details.trim(), Number(item[itemAmountKey]).toFixed(2)]; }); doc.autoTable({ startY: yPos, head: headers, body: body, theme: 'striped', headStyles:{fillColor:primaryColor, textColor:[255,255,255]}, styles:{fontSize:8, cellPadding:2}, columnStyles:{2:{halign:'right'}}}); yPos = doc.lastAutoTable.finalY + 15; } } drawListToPdf("Recurring Income Sources", data.recurringIncome); drawListToPdf("One-Time Income", data.oneTimeIncome); drawListToPdf("Recurring Expenses", data.recurringExpenses); drawListToPdf("One-Time Expenses", data.oneTimeExpenses); if (yPos > doc.internal.pageSize.getHeight() - 100 && data.projectionTable.length > 0) { doc.addPage(); yPos = 40; } doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Cash Flow Projection Table", 14, yPos); yPos += 15; const projHeaders = ["Month/Year", "Income ($)", "Expenses ($)", "Net Flow ($)", "Cum. Balance ($)"]; const projBody = data.projectionTable.map(p => [ p.monthYear, p.income.toFixed(2), p.expenses.toFixed(2), p.netFlow.toFixed(2), p.cumulative.toFixed(2) ]); doc.autoTable({ startY: yPos, head: [projHeaders], body: projBody, theme: 'grid', headStyles: { fillColor: primaryColor, textColor: [255,255,255], fontStyle: 'bold' }, styles: { fontSize: 8, cellPadding: 2 }, columnStyles: { 1:{halign:'right'}, 2:{halign:'right'}, 3:{halign:'right'}, 4:{halign:'right'} }, didDrawCell: (dataHook) => { if (dataHook.column.index === 3 && dataHook.cell.section === 'body') { if (parseFloat(dataHook.cell.raw) < 0) dataHook.cell.styles.textColor = getComputedStyle(document.documentElement).getPropertyValue('--ncfp-accent-negative').trim(); } if (dataHook.column.index === 4 && dataHook.cell.section === 'body') { if (parseFloat(dataHook.cell.raw) < 0) dataHook.cell.styles.textColor = getComputedStyle(document.documentElement).getPropertyValue('--ncfp-accent-negative').trim(); } } }); yPos = doc.lastAutoTable.finalY + 15; if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = 40; } doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Summary Metrics", 14, yPos); yPos += 15; const summaryPdfData = [ ["Total Projected Income:", ncfp_formatCurrency(data.summaryMetrics.totalProjectedIncome)], ["Total Projected Expenses:", ncfp_formatCurrency(data.summaryMetrics.totalProjectedExpenses)], ["Overall Net Cash Flow:", ncfp_formatCurrency(data.summaryMetrics.overallNetCashFlow)], ["Average Monthly Net Cash Flow:", ncfp_formatCurrency(data.summaryMetrics.avgMonthlyNetCashFlow)], ["Ending Cumulative Balance:", ncfp_formatCurrency(data.summaryMetrics.endingBalance)], ["Months with Negative Net Cash Flow:", `${data.summaryMetrics.negativeFlowMonths}`] ]; doc.autoTable({startY: yPos, body: summaryPdfData, theme:'plain', styles:{fontSize:9}, columnStyles:{0:{fontStyle:'bold', cellWidth:200}}}); const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(8); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount}`, doc.internal.pageSize.getWidth() / 2, doc.internal.pageSize.getHeight() - 10, { align: 'center' }); doc.text(`Generated on: ${new Date().toLocaleDateString('en-US', {year:'numeric', month:'short', day:'numeric'})}`, 14, doc.internal.pageSize.getHeight() - 10); doc.text(`Net Cash Flow Projection`, doc.internal.pageSize.getWidth() - 14, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); } doc.save("Net_Cash_Flow_Projection.pdf"); } document.addEventListener('DOMContentLoaded', () => { if (ncfp_tabs.length > 0 && ncfp_tabs[0]) ncfp_openTab(null, ncfp_tabs[0].id); ncfp_updateNavButtons(); const today = new Date(); document.getElementById('ncfp-startMonthYear').value = ncfp_toYYYYMM(today); ncfp_renderRecurringList('income'); ncfp_renderOneTimeList('income'); ncfp_renderRecurringList('expense'); ncfp_renderOneTimeList('expense'); const primaryColorVal = getComputedStyle(document.documentElement).getPropertyValue('--ncfp-primary-color').trim(); if (primaryColorVal.startsWith('#')) { const r = parseInt(primaryColorVal.slice(1, 3), 16); const g = parseInt(primaryColorVal.slice(3, 5), 16); const b = parseInt(primaryColorVal.slice(5, 7), 16); document.documentElement.style.setProperty('--ncfp-primary-color-rgb', `${r},${g},${b}`); } });