Real Estate Cash Flow Projection Model

Real Estate Cash Flow Projection Model

Property & Loan Setup

Calculated Investment Details:

Calculated Down Payment: $0.00

Calculated Closing Costs: $0.00

Calculated Loan Amount: $0.00

Total Cash Needed at Closing: $0.00

Estimated Monthly P&I: $0.00

Pre-Tax Cash Flow (Year 1): ${formatCurrency(firstYearCashFlow)}

Capitalization Rate (Cap Rate): ${capRate.toFixed(2)}%

Cash-on-Cash Return (Year 1): ${firstYearCoC.toFixed(2)}%

`; getEl('resultsLoading').style.display = 'none'; } // --- PDF Generation --- function generatePdf() { if (projectionDataStore.length === 0) { alert("Please generate projections first before downloading PDF."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'letter' }); const propertyIdentifier = getEl('propertyIdentifier').value || "N/A"; const purchasePrice = getNumVal('purchasePrice'); const totalCashNeeded = parseFloat(getEl('displayTotalCashNeeded').textContent.replace(/[^0-9.-]+/g,"")); const loanAmount = parseFloat(getEl('displayLoanAmount').textContent.replace(/[^0-9.-]+/g,"")); const loanInterestRate = getNumVal('loanInterestRate'); const loanTermYears = getNumVal('loanTermYears'); const monthlyPandI = parseFloat(getEl('displayMonthlyPandI').textContent.replace(/[^0-9.-]+/g,"")); const grossMonthlyRent = getNumVal('grossMonthlyRent'); const otherMonthlyIncome = getNumVal('otherMonthlyIncome'); const propertyTaxesAnnual = getNumVal('propertyTaxesAnnual'); const propertyInsuranceAnnual = getNumVal('propertyInsuranceAnnual'); // ... (add other expense inputs as needed for the summary) const projectionLengthYears = getNumVal('projectionLengthYears'); const incomeGrowth = getNumVal('annualIncomeGrowthRate'); const expenseGrowth = getNumVal('annualExpenseGrowthRate'); const appreciation = getNumVal('annualPropertyValueAppreciationRate'); let yPos = 40; doc.setFontSize(18); doc.text(`Real Estate Cash Flow Projection: ${propertyIdentifier}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 30; doc.setFontSize(12); doc.text("Input Summary:", 40, yPos); yPos += 20; const inputSummary = [ ["Property & Loan:", ""], [" Purchase Price:", formatCurrency(purchasePrice)], [" Total Cash Needed:", formatCurrency(totalCashNeeded)], [" Loan Amount:", formatCurrency(loanAmount)], [" Interest Rate:", `${loanInterestRate.toFixed(2)}%`], [" Loan Term:", `${loanTermYears} Years`], [" Est. Monthly P&I:", formatCurrency(monthlyPandI)], ["Income (Initial):", ""], [" Gross Monthly Rent:", formatCurrency(grossMonthlyRent)], [" Other Monthly Income:", formatCurrency(otherMonthlyIncome)], ["Key Annual Expenses (Initial):", ""], [" Property Taxes:", formatCurrency(propertyTaxesAnnual)], [" Property Insurance:", formatCurrency(propertyInsuranceAnnual)], ["Projection Settings:", ""], [" Projection Length:", `${projectionLengthYears} Years`], [" Income Growth:", `${incomeGrowth.toFixed(2)}%`], [" Expense Growth:", `${expenseGrowth.toFixed(2)}%`], [" Value Appreciation:", `${appreciation.toFixed(2)}%`], ]; doc.autoTable({ startY: yPos, head: [['Parameter', 'Value']], body: inputSummary, theme: 'striped', headStyles: { fillColor: [59, 130, 246] }, // Tailwind blue-500 margin: { left: 40, right: 40 }, tableWidth: 'wrap', // Let table determine its width columnStyles: { 0: { cellWidth: 200 }, 1: { cellWidth: 'auto'} } }); yPos = doc.lastAutoTable.finalY + 20; doc.setFontSize(14); doc.text("Annual Projections:", 40, yPos); yPos += 5; const tableHeaders = [ "Yr", "Gross Rent", "Vacancy", "Other Inc.", "EGI", "Taxes", "Insurance", "HOA", "Repairs", "Mgmt", "CapEx", "Other Exp", "Total OpEx", "NOI", "Debt Svc", "Cash Flow", "CoC (%)", "Prop. Val", "Loan Bal." ]; const tableBody = projectionDataStore.map(d => [ d.year, formatCurrency(d.annualGrossScheduledRent, true), formatCurrency(d.annualVacancyLoss, true), formatCurrency(d.annualOtherIncome, true), formatCurrency(d.effectiveGrossIncome, true), formatCurrency(d.propertyTaxes, true), formatCurrency(d.insurance, true), formatCurrency(d.hoa, true), formatCurrency(d.repairs, true), formatCurrency(d.propMgmt, true), formatCurrency(d.capEx, true), formatCurrency(d.otherExp, true), formatCurrency(d.totalOperatingExpenses, true), formatCurrency(d.netOperatingIncome, true), formatCurrency(d.annualDebtService, true), formatCurrency(d.preTaxCashFlow, true), d.cashOnCashReturn.toFixed(2), formatCurrency(d.currentPropertyValue, true), formatCurrency(d.yearEndLoanBalance, true) ]); doc.autoTable({ startY: yPos, head: [tableHeaders], body: tableBody, theme: 'grid', // 'striped' or 'grid' headStyles: { fillColor: [59, 130, 246], fontSize: 7 }, // Tailwind blue-500 styles: { fontSize: 7, cellPadding: 2, overflow: 'linebreak' }, columnStyles: { // Adjust column widths as needed 0: { cellWidth: 25 }, // Year // ... other columns, can use 'auto' or fixed widths 16: { halign: 'right' }, // CoC }, margin: { left: 40, right: 40 } }); const safeFilename = (propertyIdentifier.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'real_estate') + '_cash_flow.pdf'; doc.save(safeFilename); } // --- Event Listeners & Initialization --- function setupEventListeners() { // Tab 1 inputs ['purchasePrice', 'downPaymentValue', 'closingCostsValue', 'loanInterestRate', 'loanTermYears'].forEach(id => { const el = getEl(id); if (el) el.addEventListener('input', calculateInvestmentDetails); }); ['downPaymentType', 'closingCostsType'].forEach(id => { const el = getEl(id); if (el) el.addEventListener('change', calculateInvestmentDetails); }); // Tab 2 inputs - these don't auto-trigger full projection, but good for future if we add intermediate calcs const tab2Inputs = [ 'grossMonthlyRent', 'otherMonthlyIncome', 'propertyTaxesAnnual', 'propertyInsuranceAnnual', 'hoaFeesMonthly', 'vacancyRatePercent', 'repairsMaintenanceValue', 'propertyManagementValue', 'capitalExpendituresValue', 'otherExpensesMonthly' ]; tab2Inputs.forEach(id => { const el = getEl(id); // if (el) el.addEventListener('input', () => { /* placeholder for potential Tab 2 summary */ }); }); ['repairsMaintenanceType', 'propertyManagementType', 'capitalExpendituresType'].forEach(id => { const el = getEl(id); // if (el) el.addEventListener('change', () => { /* placeholder */ }); }); // Tab 3 inputs - trigger projection generation ['projectionLengthYears', 'annualIncomeGrowthRate', 'annualExpenseGrowthRate', 'annualPropertyValueAppreciationRate'].forEach(id => { const el = getEl(id); if (el) el.addEventListener('input', () => { if (currentTab === 2) { // Only auto-calc if on the results tab if (validateTab1() && validateTab2() && validateTab3Settings()) { generateProjections(); } else { getEl('projectionsError').textContent = "Please correct errors in input fields before calculating projections."; } } }); }); const downloadButton = getEl('downloadPdfButton'); if (downloadButton) { downloadButton.addEventListener('click', generatePdf); } else { console.error("Download PDF button not found."); } } document.addEventListener('DOMContentLoaded', () => { const tabContentDivs = getEl('tabContents').children; for (let i = 0; i < tabContentDivs.length; i++) { if (tabContentDivs[i].classList.contains('tab-content')) { tabs.push(tabContentDivs[i]); } } const navButtons = document.querySelectorAll('.tab-button'); navButtons.forEach(btn => tabButtons.push(btn)); if (tabs.length > 0 && tabButtons.length > 0) { showTab(0); // Show the first tab initially calculateInvestmentDetails(); // Calculate initial investment details on load } else { console.error("Tabs or tab buttons not found. UI initialization failed."); } setupEventListeners(); });
Scroll to Top