Projection table will appear here after calculation.
";
return;
}
const y1 = rycfp_resultsData.year1;
const inputs = rycfp_resultsData.inputs;
keyMetricsDiv.innerHTML = `
Key Financial Metrics (Year 1)
Total Investment Cost: ${rycfp_formatCurrency(y1.totalInvestmentCost)}
Initial Cash Invested: ${rycfp_formatCurrency(y1.initialCashInvested)}
Gross Rental Yield: ${rycfp_formatPercent(y1.grossRentalYield)}
Net Rental Yield (on Total Cost): ${rycfp_formatPercent(y1.netRentalYield)}
Cash on Cash Return (Pre-Tax): ${rycfp_formatPercent(y1.cashOnCashReturn)}
`;
cashFlowDiv.innerHTML = `
Cash Flow Summary (Year 1)
Effective Gross Income (EGI): ${rycfp_formatCurrency(y1.effectiveGrossIncome)}
Total Operating Expenses: ${rycfp_formatCurrency(y1.totalOperatingExpenses)}
Net Operating Income (NOI): ${rycfp_formatCurrency(y1.netOperatingIncome)}
${inputs.useFinancing ? `
Annual Mortgage Payment (P&I): ${rycfp_formatCurrency(y1.annualMortgagePayment)}
` : ''}
Annual Pre-Tax Cash Flow: ${rycfp_formatCurrency(y1.preTaxCashFlow)}
Monthly Pre-Tax Cash Flow: ${rycfp_formatCurrency(y1.preTaxCashFlow / 12)}
`;
let tableHTML = `
| Year | Gross Rent | EGI | Op. Exp. | NOI |
${inputs.useFinancing ? 'Debt Serv. | Principal | Interest | ' : ''}
Pre-Tax CF | Cumul. CF |
${inputs.useFinancing ? 'Loan Bal. | ' : ''}
Prop. Value |
`;
rycfp_resultsData.projections.forEach(p => {
tableHTML += `
| ${p.year} |
${rycfp_formatCurrency(p.projectedGrossRent,0)} |
${rycfp_formatCurrency(p.projectedEGI,0)} |
${rycfp_formatCurrency(p.projectedOpEx,0)} |
${rycfp_formatCurrency(p.projectedNOI,0)} |
${inputs.useFinancing ? `${rycfp_formatCurrency(p.debtService,0)} | ${rycfp_formatCurrency(p.principalPaid,0)} | ${rycfp_formatCurrency(p.interestPaid,0)} | ` : ''}
${rycfp_formatCurrency(p.preTaxCashFlow,0)} |
${rycfp_formatCurrency(p.cumulativeCashFlow,0)} |
${inputs.useFinancing ? `${rycfp_formatCurrency(p.endOfYearLoanBalance,0)} | ` : ''}
${rycfp_formatCurrency(p.projectedPropertyValue,0)} |
`;
});
tableHTML += `
`;
projectionTableContainer.innerHTML = tableHTML;
const pdfBtn = document.getElementById('rycfp_downloadPdfButton');
if(pdfBtn) pdfBtn.style.display = 'block';
}
// --- PDF Generation ---
function rycfp_downloadPDF() {
if (!rycfp_resultsData) { alert("Please calculate results first."); return; }
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('Core PDF library (jsPDF) is not loaded. Cannot generate PDF.');
console.error('jsPDF library not found.');
return;
}
const { jsPDF: JSPDF } = window.jspdf;
const doc = new JSPDF();
if (typeof doc.autoTable !== 'function') {
alert('PDF Table plugin (jsPDF-AutoTable) seems not to be loaded correctly. Tables in PDF might be missing or the PDF may fail to generate.\nPlease ensure your internet connection is active and no browser extensions are blocking scripts from cdnjs.cloudflare.com.');
console.error('doc.autoTable is not a function. jsPDF-AutoTable plugin error.');
return;
}
let y = 15; const m = 15;
const inputs = rycfp_resultsData.inputs;
const y1 = rycfp_resultsData.year1;
doc.setFontSize(16);
doc.text("Rental Yield & Cash Flow Projection Report", m, y); y += 10;
doc.setFontSize(12); doc.text("Input Summary:", m, y); y += 6;
doc.setFontSize(9);
let inputSummary = [
["Property Price:", rycfp_formatCurrency(inputs.propertyPrice), "Monthly Rent:", rycfp_formatCurrency(inputs.monthlyRent)],
["Acquisition Costs:", rycfp_formatCurrency(inputs.acquisitionCosts), "Vacancy Rate:", rycfp_formatPercent(inputs.vacancyRate)],
["Initial Renovations:", rycfp_formatCurrency(inputs.initialRenovations), "Other Monthly Income:", rycfp_formatCurrency(inputs.otherMonthlyIncome)],
["Property Taxes (Ann.):", rycfp_formatCurrency(inputs.propertyTaxesAnnual), "Maint. (% Rent):", rycfp_formatPercent(inputs.maintenancePercent)],
["Insurance (Ann.):", rycfp_formatCurrency(inputs.propertyInsuranceAnnual), "Mgmt. (% Rent):", rycfp_formatPercent(inputs.managementPercent)],
["HOA (Ann.):", rycfp_formatCurrency(inputs.hoaFeesAnnual), "Utilities (Owner, Ann.):", rycfp_formatCurrency(inputs.utilitiesOwnerAnnual)],
["Other Exp. (Ann.):", rycfp_formatCurrency(inputs.otherExpensesAnnual), "", "" ]
];
if(inputs.useFinancing){
inputSummary.push(["Financing Enabled:", "Yes", "Down Payment:", `${rycfp_formatPercent(inputs.downPaymentPercent)} (${rycfp_formatCurrency(inputs.propertyPrice * inputs.downPaymentPercent/100)})`]);
inputSummary.push(["Loan Amount:", rycfp_formatCurrency(inputs.loanAmount), "Interest Rate:", rycfp_formatPercent(inputs.interestRateAnnual)]);
inputSummary.push(["Loan Term:", `${inputs.loanTermYears} years`, "Monthly P&I:", rycfp_formatCurrency(inputs.monthlyMortgagePayment)]);
} else {
inputSummary.push(["Financing Enabled:", "No", "", ""]);
}
inputSummary.push(["Projection Period:", `${inputs.projectionPeriod} years`, "Income Growth:", rycfp_formatPercent(inputs.incomeGrowth)]);
inputSummary.push(["Expense Growth:", rycfp_formatPercent(inputs.expensesGrowth), "Value Growth:", rycfp_formatPercent(inputs.propertyValueGrowth)]);
doc.autoTable({ startY: y, body: inputSummary, theme: 'plain', styles: {fontSize: 8.5, cellPadding: 1}, columnStyles: {0:{fontStyle:'bold'}, 2:{fontStyle:'bold'}}});
y = doc.lastAutoTable.finalY + 7;
doc.setFontSize(12); doc.text("Key Metrics & Cash Flow (Year 1):", m, y); y += 6;
let metricsSummary = [
["Total Investment Cost:", rycfp_formatCurrency(y1.totalInvestmentCost), "Gross Rental Yield:", rycfp_formatPercent(y1.grossRentalYield)],
["Initial Cash Invested:", rycfp_formatCurrency(y1.initialCashInvested), "Net Rental Yield (on Total Cost):", rycfp_formatPercent(y1.netRentalYield)],
["Effective Gross Income (EGI):", rycfp_formatCurrency(y1.effectiveGrossIncome), "Cash on Cash Return (Pre-Tax):", rycfp_formatPercent(y1.cashOnCashReturn)],
["Total Operating Expenses:", rycfp_formatCurrency(y1.totalOperatingExpenses), "Net Operating Income (NOI):", rycfp_formatCurrency(y1.netOperatingIncome)],
inputs.useFinancing ? ["Annual Mortgage (P&I):", rycfp_formatCurrency(y1.annualMortgagePayment)] : ["Annual Mortgage (P&I):", "N/A"],
inputs.useFinancing ? ["Annual Pre-Tax Cash Flow:", rycfp_formatCurrency(y1.preTaxCashFlow)] : ["Annual Pre-Tax Cash Flow (NOI):", rycfp_formatCurrency(y1.netOperatingIncome)]
];
doc.autoTable({ startY: y, body: metricsSummary, theme: 'plain', styles: {fontSize: 8.5, cellPadding: 1}, columnStyles: {0:{fontStyle:'bold'}, 2:{fontStyle:'bold'}}});
y = doc.lastAutoTable.finalY + 7;
doc.setFontSize(12); doc.text(`Projections (${inputs.projectionPeriod} Years):`, m, y); y+=2;
let head = [['Year', 'Rent', 'EGI', 'Op.Exp', 'NOI']];
if (inputs.useFinancing) head[0].push('DebtSrv', 'Principal', 'Interest');
head[0].push('PreTaxCF', 'Cumul.CF');
if (inputs.useFinancing) head[0].push('LoanBal');
head[0].push('Prop.Val');
let body = rycfp_resultsData.projections.map(p => {
let row = [
p.year, p.projectedGrossRent.toFixed(0), p.projectedEGI.toFixed(0), p.projectedOpEx.toFixed(0), p.projectedNOI.toFixed(0)
];
if (inputs.useFinancing) row.push(p.debtService.toFixed(0), p.principalPaid.toFixed(0), p.interestPaid.toFixed(0));
row.push(p.preTaxCashFlow.toFixed(0), p.cumulativeCashFlow.toFixed(0));
if (inputs.useFinancing) row.push(p.endOfYearLoanBalance.toFixed(0));
row.push(p.projectedPropertyValue.toFixed(0));
return row;
});
doc.autoTable({
startY: y, head: head, body: body, theme: 'grid',
headStyles: { fillColor: [30, 64, 175], textColor: 255, fontSize: 7.5, cellPadding: 1.2 }, // Dark blue header
styles: { fontSize: 7, cellPadding: 1.2, halign: 'right' },
columnStyles: { 0: { halign: 'center', fontStyle: 'bold' } }
});
doc.save(`Rental_Projection_${rycfp_getInputValue('rycfp_propertyPrice') || 'Property'}.pdf`);
}