Total Income
$0
0 transactions
Total Expenses
$0
0 transactions
Net Profit
$0
0% margin
This Month
$0
net income

Monthly Income vs Expenses

Income Expenses

Income by Property

📊

Record your first transaction to see income broken down by property. Start by logging this month's rent payments.

Recent Transactions

DateDescriptionCategoryPropertyTypeAmount
💰

No transactions yet. Start by logging this month's rent payments.

`); w.document.close(); } // ═════════════════════════════════════════════════════════ // 1099 / TAX // ═════════════════════════════════════════════════════════ let vendorTINs = {}; function loadTINs() { try { vendorTINs = JSON.parse(localStorage.getItem('rv_vendor_tins') || '{}'); } catch(e){ vendorTINs={}; } } function saveTINs() { localStorage.setItem('rv_vendor_tins', JSON.stringify(vendorTINs)); } function render1099() { loadTINs(); const year = parseInt(document.getElementById('taxYear').value); // Only expense transactions paid to vendors/contractors const yearTx = transactions.filter(t => t.type==='expense' && t.date.startsWith(String(year))); // Group by party name const byParty = {}; yearTx.forEach(t => { const key = (t.party||'Unknown Vendor').trim(); if (!byParty[key]) byParty[key] = { total:0, txCount:0, categories:new Set() }; byParty[key].total += t.amount; byParty[key].txCount++; byParty[key].categories.add(t.category); }); const parties = Object.entries(byParty).sort((a,b)=>b[1].total-a[1].total); const necParties = parties.filter(([,v])=>v.total >= 600); // Update stats document.getElementById('taxTotalVendors').textContent = parties.length; document.getElementById('taxNECCount').textContent = necParties.length; document.getElementById('taxTotalPaid').textContent = fmt(yearTx.reduce((s,t)=>s+t.amount,0)); // Render table const tbody = document.getElementById('necTbody'); if (!parties.length) { tbody.innerHTML = 'No expense transactions in ' + year + '.'; return; } tbody.innerHTML = parties.map(([party, data]) => { const needs1099 = data.total >= 600; const tin = vendorTINs[party] || ''; const type = vendorTINs[party+'_type'] || 'Individual'; return ` ${esc(party)}
${[...data.categories].map(c=>esc(c)).join(', ')} ${fmt(data.total)} ${needs1099?'⚠️ YES — File 1099-NEC':'No'} `; }).join(''); // Show print preview if there are NEC vendors if (necParties.length) renderNECPreview(necParties, year); } function saveTIN(party, val) { loadTINs(); vendorTINs[party] = val; saveTINs(); } function saveTINType(party, val) { loadTINs(); vendorTINs[party+'_type'] = val; saveTINs(); } function preview1099(party, total, year) { const tin = vendorTINs[party] || '___-__-____'; const html = `
FieldValue
Payer (Box — Filer Info)3120Life LLC
Daniel Hart · contact@3120life.com
Recipient Name${esc(party)}
Recipient TIN/EIN${esc(tin)}
Box 1 — Nonemployee Compensation${fmt(total)}
Tax Year${year}
Filing DeadlineJanuary 31, ${year+1}
This is a summary only. File official 1099-NEC forms through IRS e-file or a licensed tax preparer.
`; const wrapper = document.getElementById('nec-preview-wrapper'); document.getElementById('necPrintArea').innerHTML = html; wrapper.style.display = 'block'; wrapper.scrollIntoView({ behavior:'smooth' }); } function renderNECPreview(necParties, year) { const rows = necParties.map(([party, data]) => { const tin = vendorTINs[party] || 'TIN NEEDED'; return `${esc(party)}${esc(tin)}${fmt(data.total)}`; }).join(''); document.getElementById('necPrintArea').innerHTML = ` ${rows}
VendorTIN/EINBox 1 Amount
File 1099-NEC for each vendor above by January 31, ${year+1}. Verify TINs before filing.
`; document.getElementById('nec-preview-wrapper').style.display = 'block'; } function export1099CSV() { loadTINs(); const year = parseInt(document.getElementById('taxYear').value); const yearTx = transactions.filter(t=>t.type==='expense' && t.date.startsWith(String(year))); const byParty = {}; yearTx.forEach(t => { const key = (t.party||'Unknown').trim(); byParty[key] = (byParty[key]||0) + t.amount; }); const rows = Object.entries(byParty).sort((a,b)=>b[1]-a[1]).map(([party, total]) => [ `"${party}"`, vendorTINs[party]||'', vendorTINs[party+'_type']||'Individual', fmt(total).replace('$',''), total>=600?'YES':'NO', year ].join(',')); const csv = ['Vendor,TIN/EIN,Type,Total Paid,1099-NEC Required,Tax Year', ...rows].join('\n'); const blob = new Blob([csv],{type:'text/csv'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href=url; a.download=`1099-NEC-list-${year}.csv`; a.click(); URL.revokeObjectURL(url); toast('📥 1099 CSV exported!'); } // init advanced sections on load document.addEventListener('DOMContentLoaded', () => { // Recon: set default date to today const today = new Date().toISOString().slice(0,10); document.getElementById('reconDate').value = today; // Disbursements: set default month const now = new Date(); document.getElementById('disbMonth').value = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0'); loadOwners(); renderDisbursements(); // 1099: populate year dropdown const taxSel = document.getElementById('taxYear'); const curYear = now.getFullYear(); for (let y=curYear; y>=curYear-5; y--) { const opt = document.createElement('option'); opt.value=y; opt.textContent=y; taxSel.appendChild(opt); } render1099(); }); // ───────────────────────────────────────────────────────── // CSV EXPORT // ───────────────────────────────────────────────────────── function exportCSV() { const headers = ['Date','Type','Category','Property','Party','Method','Notes','Amount']; const rows = transactions.map(t => [ t.date, t.type, t.category, `"${t.property}"`, `"${t.party||''}"`, t.method, `"${t.notes||''}"`, t.amount ].join(',')); const csv = [headers.join(','), ...rows].join('\n'); const blob = new Blob([csv], { type:'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href=url; a.download=`rentverified-accounting-${new Date().toISOString().slice(0,10)}.csv`; a.click(); URL.revokeObjectURL(url); toast('📥 CSV exported!'); } // ───────────────────────────────────────────────────────── // HELPERS // ───────────────────────────────────────────────────────── function fmt(n) { return '$' + Math.abs(n).toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}); } function toast(msg) { const el = document.getElementById('toast'); el.textContent = msg; el.classList.add('show'); setTimeout(()=>el.classList.remove('show'), 3000); } // ───────────────────────────────────────────────────────── // CHART OF ACCOUNTS // ───────────────────────────────────────────────────────── const DEFAULT_COA = [ // Assets { code:'1000', name:'Cash - Operating', type:'asset' }, { code:'1010', name:'Cash - Petty Cash', type:'asset' }, { code:'1100', name:'Accounts Receivable', type:'asset' }, { code:'1200', name:'Prepaid Expenses', type:'asset' }, { code:'1300', name:'Equipment', type:'asset' }, { code:'1400', name:'Security Deposits Held', type:'asset' }, // Liabilities { code:'2000', name:'Accounts Payable', type:'liability' }, { code:'2100', name:'Credit Card Payable', type:'liability' }, { code:'2200', name:'Mortgage / Loans Payable', type:'liability' }, { code:'2300', name:'Accrued Expenses', type:'liability' }, { code:'2400', name:'Security Deposits Liability',type:'liability' }, { code:'2500', name:'Sales Tax Payable', type:'liability' }, // Equity { code:'3000', name:'Owner Equity', type:'equity' }, { code:'3100', name:'Retained Earnings', type:'equity' }, { code:'3200', name:'Owner Draws', type:'equity' }, // Revenue { code:'4000', name:'Service Revenue', type:'revenue' }, { code:'4010', name:'Rent Income', type:'revenue' }, { code:'4020', name:'Consulting Revenue', type:'revenue' }, { code:'4030', name:'Late Fees', type:'revenue' }, { code:'4040', name:'Other Income', type:'revenue' }, // Expenses { code:'5000', name:'Cost of Goods Sold', type:'expense' }, { code:'5100', name:'Subcontractor Expense', type:'expense' }, { code:'5200', name:'Materials & Supplies', type:'expense' }, { code:'6000', name:'Rent Expense', type:'expense' }, { code:'6010', name:'Utilities', type:'expense' }, { code:'6020', name:'Insurance', type:'expense' }, { code:'6030', name:'Repairs & Maintenance', type:'expense' }, { code:'6040', name:'Office Supplies', type:'expense' }, { code:'6050', name:'Advertising & Marketing', type:'expense' }, { code:'6060', name:'Professional Fees', type:'expense' }, { code:'6070', name:'Travel & Transportation', type:'expense' }, { code:'6080', name:'Payroll / Wages', type:'expense' }, { code:'6090', name:'Depreciation', type:'expense' }, { code:'6100', name:'Bank Fees & Interest', type:'expense' }, { code:'6999', name:'Miscellaneous Expense', type:'expense' } ]; let chartOfAccounts = []; function loadCOA() { try { chartOfAccounts = JSON.parse(localStorage.getItem('rv_chart_of_accounts') || '[]'); } catch(e) { chartOfAccounts = []; } if (chartOfAccounts.length === 0) { // Seed defaults chartOfAccounts = DEFAULT_COA.map(function(a) { return { id: uid(), code: a.code, name: a.name, type: a.type, is_active: true, is_trust: false }; }); saveCOA(); } } function saveCOA() { localStorage.setItem('rv_chart_of_accounts', JSON.stringify(chartOfAccounts)); } function toggleAddAccount() { var form = document.getElementById('addAccountForm'); form.style.display = form.style.display === 'none' ? '' : 'none'; } function saveAccount() { var code = document.getElementById('coaCode').value.trim(); var name = document.getElementById('coaName').value.trim(); var type = document.getElementById('coaType').value; var trust = document.getElementById('coaTrust').value === 'true'; if (!code || !name) { toast('⚠️ Code and name are required'); return; } if (chartOfAccounts.find(function(a) { return a.code === code; })) { toast('⚠️ Account code already exists'); return; } chartOfAccounts.push({ id: uid(), code: code, name: name, type: type, is_active: true, is_trust: trust }); chartOfAccounts.sort(function(a, b) { return a.code.localeCompare(b.code); }); saveCOA(); renderCOA(); toggleAddAccount(); document.getElementById('coaCode').value = ''; document.getElementById('coaName').value = ''; toast('✅ Account added'); } function toggleAccountActive(code) { var acct = chartOfAccounts.find(function(a) { return a.code === code; }); if (acct) { acct.is_active = !acct.is_active; saveCOA(); renderCOA(); } } function renderCOA() { var container = document.getElementById('coaContainer'); if (!container) return; var hasTrust = chartOfAccounts.some(function(a) { return a.is_trust; }); document.getElementById('trustWarning').style.display = hasTrust ? '' : 'none'; var typeOrder = ['asset','liability','equity','revenue','expense']; var typeLabels = { asset:'Assets', liability:'Liabilities', equity:'Equity', revenue:'Revenue', expense:'Expenses' }; var typeColors = { asset:'#3b82f6', liability:'#ef4444', equity:'#8b5cf6', revenue:'#10b981', expense:'#f59e0b' }; var html = ''; // Trust accounts first if any if (hasTrust) { var trustAccts = chartOfAccounts.filter(function(a) { return a.is_trust; }); html += '
' + '

⚠️ Client Trust (IOLTA)

' + '
' + '' + ''; trustAccts.forEach(function(a) { html += '' + '' + ''; }); html += '
CodeAccount NameTypeStatus
' + a.code + '' + a.name + '' + a.type + '' + (a.is_active ? 'Active' : 'Inactive') + '
'; } // Regular accounts by type typeOrder.forEach(function(type) { var accts = chartOfAccounts.filter(function(a) { return a.type === type && !a.is_trust; }); if (accts.length === 0) return; html += '
' + '

' + typeLabels[type] + ' (' + accts.length + ' accounts)

' + '
' + '' + ''; accts.forEach(function(a) { html += '' + '' + '' + ''; }); html += '
CodeAccount NameStatus
' + a.code + '' + a.name + '' + (a.is_active ? 'Active' : 'Inactive') + '
'; }); container.innerHTML = html; // Also update the category dropdown in Add Transaction to use COA updateCategoryDropdownFromCOA(); } function updateCategoryDropdownFromCOA() { // If COA is loaded, add account codes as category options var catSelect = document.getElementById('txCategory'); if (!catSelect) return; // Keep existing categories but add COA header var existingOptions = catSelect.innerHTML; if (existingOptions.indexOf('— Chart of Accounts —') >= 0) return; // already added var coaGroup = ''; chartOfAccounts.filter(function(a) { return a.is_active; }).forEach(function(a) { coaGroup += ''; }); coaGroup += ''; catSelect.innerHTML += coaGroup; } // ───────────────────────────────────────────────────────── // FINANCIAL EXPORTS // ───────────────────────────────────────────────────────── function getDateDefaults() { var now = new Date(); var yearStart = now.getFullYear() + '-01-01'; var today = now.toISOString().slice(0, 10); return { start: yearStart, end: today }; } function buildPLData(startDate, endDate) { var filtered = transactions.filter(function(t) { return t.date >= startDate && t.date <= endDate; }); var revenue = {}; var expenses = {}; var totalRevenue = 0; var totalExpenses = 0; filtered.forEach(function(t) { var cat = t.category || 'Uncategorized'; if (t.type === 'income') { revenue[cat] = (revenue[cat] || 0) + (parseFloat(t.amount) || 0); totalRevenue += parseFloat(t.amount) || 0; } else { expenses[cat] = (expenses[cat] || 0) + (parseFloat(t.amount) || 0); totalExpenses += parseFloat(t.amount) || 0; } }); return { revenue: revenue, expenses: expenses, totalRevenue: totalRevenue, totalExpenses: totalExpenses, netIncome: totalRevenue - totalExpenses }; } function exportPL() { var start = document.getElementById('plStart').value || getDateDefaults().start; var end = document.getElementById('plEnd').value || getDateDefaults().end; var data = buildPLData(start, end); var csv = 'Profit & Loss Statement\nPeriod: ' + start + ' to ' + end + '\n\n'; csv += 'Category,Amount\n'; csv += '--- REVENUE ---,\n'; Object.keys(data.revenue).forEach(function(k) { csv += '"' + k + '",' + data.revenue[k].toFixed(2) + '\n'; }); csv += 'TOTAL REVENUE,' + data.totalRevenue.toFixed(2) + '\n\n'; csv += '--- EXPENSES ---,\n'; Object.keys(data.expenses).forEach(function(k) { csv += '"' + k + '",' + data.expenses[k].toFixed(2) + '\n'; }); csv += 'TOTAL EXPENSES,' + data.totalExpenses.toFixed(2) + '\n\n'; csv += 'NET INCOME,' + data.netIncome.toFixed(2) + '\n'; downloadCSV(csv, 'profit-loss-' + start + '-to-' + end + '.csv'); toast('📊 P&L exported'); } function previewPL() { var start = document.getElementById('plStart').value || getDateDefaults().start; var end = document.getElementById('plEnd').value || getDateDefaults().end; var data = buildPLData(start, end); var el = document.getElementById('plPreview'); var html = '

P&L: ' + start + ' to ' + end + '

'; html += '
Revenue'; Object.keys(data.revenue).forEach(function(k) { html += '
' + k + '' + fmt(data.revenue[k]) + '
'; }); html += '
Total Revenue' + fmt(data.totalRevenue) + '
'; html += '
Expenses'; Object.keys(data.expenses).forEach(function(k) { html += '
' + k + '' + fmt(data.expenses[k]) + '
'; }); html += '
Total Expenses' + fmt(data.totalExpenses) + '
'; html += '
Net Income' + fmt(data.netIncome) + '
'; el.innerHTML = html; el.style.display = ''; } function buildTrialBalanceData(asOfDate) { var filtered = transactions.filter(function(t) { return t.date <= asOfDate; }); var balances = {}; // Initialize from COA chartOfAccounts.filter(function(a) { return a.is_active; }).forEach(function(a) { balances[a.code] = { code: a.code, name: a.name, type: a.type, debit: 0, credit: 0 }; }); // Map transactions to accounts filtered.forEach(function(t) { var cat = t.category || ''; var amt = parseFloat(t.amount) || 0; // Check if category is a COA reference if (cat.startsWith('COA:')) { var code = cat.replace('COA:', ''); if (balances[code]) { if (t.type === 'income') balances[code].credit += amt; else balances[code].debit += amt; return; } } // Default mapping: income → 4000, expense → 6999 if (t.type === 'income') { if (!balances['4000']) balances['4000'] = { code:'4000', name:'Service Revenue', type:'revenue', debit:0, credit:0 }; balances['4000'].credit += amt; } else { if (!balances['6999']) balances['6999'] = { code:'6999', name:'Miscellaneous Expense', type:'expense', debit:0, credit:0 }; balances['6999'].debit += amt; } }); return Object.values(balances).filter(function(b) { return b.debit > 0 || b.credit > 0; }) .sort(function(a, b) { return a.code.localeCompare(b.code); }); } function exportTrialBalance() { var asOf = document.getElementById('tbDate').value || getDateDefaults().end; var data = buildTrialBalanceData(asOf); var totalDebit = 0, totalCredit = 0; var csv = 'Trial Balance\nAs of: ' + asOf + '\n\n'; csv += 'Code,Account Name,Type,Debit,Credit\n'; data.forEach(function(b) { csv += b.code + ',"' + b.name + '",' + b.type + ',' + b.debit.toFixed(2) + ',' + b.credit.toFixed(2) + '\n'; totalDebit += b.debit; totalCredit += b.credit; }); csv += '\nTOTAL,,,' + totalDebit.toFixed(2) + ',' + totalCredit.toFixed(2) + '\n'; downloadCSV(csv, 'trial-balance-' + asOf + '.csv'); toast('📋 Trial Balance exported'); } function previewTrialBalance() { var asOf = document.getElementById('tbDate').value || getDateDefaults().end; var data = buildTrialBalanceData(asOf); var el = document.getElementById('tbPreview'); var totalDebit = 0, totalCredit = 0; var html = '

Trial Balance as of ' + asOf + '

'; html += ''; html += ''; data.forEach(function(b) { totalDebit += b.debit; totalCredit += b.credit; html += '' + '' + ''; }); html += '' + ''; html += '
CodeAccountDebitCredit
' + b.code + '' + b.name + '' + (b.debit ? fmt(b.debit) : '') + '' + (b.credit ? fmt(b.credit) : '') + '
TOTAL' + fmt(totalDebit) + '' + fmt(totalCredit) + '
'; var diff = Math.abs(totalDebit - totalCredit); if (diff < 0.01) { html += '
✅ Balanced — Debits equal Credits
'; } else { html += '
⚠️ Out of balance by ' + fmt(diff) + '
'; } el.innerHTML = html; el.style.display = ''; } function exportQuick(type) { var now = new Date(); var y = now.getFullYear(); if (type === 'ytd-pl') { document.getElementById('plStart').value = y + '-01-01'; document.getElementById('plEnd').value = now.toISOString().slice(0,10); exportPL(); } else if (type === 'ytd-tb') { document.getElementById('tbDate').value = now.toISOString().slice(0,10); exportTrialBalance(); } else if (type === 'last-year-pl') { document.getElementById('plStart').value = (y-1) + '-01-01'; document.getElementById('plEnd').value = (y-1) + '-12-31'; exportPL(); } } function downloadCSV(csvContent, filename) { var blob = new Blob([csvContent], { type: 'text/csv' }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); URL.revokeObjectURL(a.href); } // ───────────────────────────────────────────────────────── // MY CLIENTS (Accountant vertical) // ───────────────────────────────────────────────────────── function detectAccountantMode() { try { var config = JSON.parse(localStorage.getItem('rv_workspace_config') || '{}'); if (config.industry === 'accountant' || config.industry === 'cpa') { // Show clients sidebar var header = document.getElementById('clientsSidebarHeader'); var link = document.getElementById('clientsSidebarLink'); if (header) header.style.display = ''; if (link) link.style.display = ''; loadClients(); } } catch(e) {} } function loadClients() { // In localStorage mode: look for client workspaces // In Supabase mode: query workspace_members where user is contractor var clients = JSON.parse(localStorage.getItem('rv_accountant_clients') || '[]'); // Demo data if none exist in accountant mode if (clients.length === 0) { var config = JSON.parse(localStorage.getItem('rv_workspace_config') || '{}'); if (config.industry === 'accountant' || config.industry === 'cpa') { clients = [ { id: 'demo_c1', name: 'Smith Property Management', industry: 'property_management', status: 'active', last_activity: '2026-03-18' }, { id: 'demo_c2', name: 'Hart Construction LLC', industry: 'construction_trades', status: 'active', last_activity: '2026-03-15' }, { id: 'demo_c3', name: 'Garcia Consulting', industry: 'general', status: 'active', last_activity: '2026-03-10' } ]; } } renderClients(clients); } function renderClients(clients) { var grid = document.getElementById('clientsGrid'); if (!grid || clients.length === 0) return; var industryIcons = { property_management: '🏠', construction_trades: '🏗️', contractor: '🔧', accountant: '📊', attorney: '⚖️', general: '💼', insurance: '🛡️' }; grid.innerHTML = clients.map(function(c) { var icon = industryIcons[c.industry] || '💼'; return '
' + '
' + '
' + icon + '
' + '
' + '
' + esc(c.name) + '
' + '
' + esc((c.industry || '').replace(/_/g, ' ')) + '
' + '
Last active: ' + esc(c.last_activity) + '
' + '
' + '
View →
' + '
' + '
'; }).join(''); } function switchToClient(clientId) { // In full Supabase mode this would call Workspace.switchTo(clientId) // For now, show a toast explaining the concept toast('🔄 Switching to client workspace… (requires Supabase)'); } // ───────────────────────────────────────────────────────── // INIT EXTENSIONS // ───────────────────────────────────────────────────────── (function initExtensions() { loadCOA(); renderCOA(); detectAccountantMode(); // Set default dates for exports var defaults = getDateDefaults(); var plStart = document.getElementById('plStart'); var plEnd = document.getElementById('plEnd'); var tbDate = document.getElementById('tbDate'); if (plStart) plStart.value = defaults.start; if (plEnd) plEnd.value = defaults.end; if (tbDate) tbDate.value = defaults.end; })();