const { all, audit, now, one, run } = require('../db'); const { money } = require('../depreciation'); const FREQUENCIES = { monthly: 12, quarterly: 4, semiannual: 2, annual: 1 }; function periodsPerYear(frequency) { return FREQUENCIES[frequency] || 12; } function addMonths(date, count) { const next = new Date(date.getTime()); next.setMonth(next.getMonth() + count); return next; } function listLeases(query = {}) { return all( `SELECT l.*, a.asset_id AS asset_code, a.description AS asset_description FROM leases l LEFT JOIN assets a ON a.id = l.asset_id WHERE (? IS NULL OR l.asset_id = ?) ORDER BY l.start_date DESC, l.id DESC`, [query.asset_id || null, query.asset_id || null] ); } function getLease(id) { return one('SELECT * FROM leases WHERE id = ?', [id]); } function createLease(input, userId) { const timestamp = now(); const result = run( `INSERT INTO leases (asset_id, lessor, contract_value, start_date, end_date, discount_rate, payment_frequency, notes, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ input.asset_id || null, input.lessor || null, Number(input.contract_value || 0), input.start_date || null, input.end_date || null, Number(input.discount_rate || 0), input.payment_frequency || 'monthly', input.notes || null, timestamp, timestamp ] ); audit(userId, 'create', 'lease', result.lastInsertRowid, null, input); return getLease(result.lastInsertRowid); } function updateLease(id, input, userId) { const before = getLease(id); if (!before) return null; run( `UPDATE leases SET lessor = ?, contract_value = ?, start_date = ?, end_date = ?, discount_rate = ?, payment_frequency = ?, notes = ?, updated_at = ? WHERE id = ?`, [ input.lessor ?? before.lessor, Number(input.contract_value ?? before.contract_value), input.start_date ?? before.start_date, input.end_date ?? before.end_date, Number(input.discount_rate ?? before.discount_rate), input.payment_frequency ?? before.payment_frequency, input.notes ?? before.notes, now(), id ] ); audit(userId, 'update', 'lease', id, before, input); return getLease(id); } function deleteLease(id, userId) { const before = getLease(id); if (!before) return false; run('DELETE FROM leases WHERE id = ?', [id]); audit(userId, 'delete', 'lease', id, before, null); return true; } // Straight-line ROU amortization with an ASC 842-style liability schedule. function leaseSchedule(lease) { if (!lease) return null; const ppy = periodsPerYear(lease.payment_frequency); const monthsPerPeriod = 12 / ppy; const start = lease.start_date ? new Date(`${lease.start_date}T00:00:00`) : new Date(); const end = lease.end_date ? new Date(`${lease.end_date}T00:00:00`) : addMonths(start, 12); const totalMonths = Math.max(monthsPerPeriod, (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth())); const periods = Math.max(1, Math.round(totalMonths / monthsPerPeriod)); const contractValue = Number(lease.contract_value || 0); const payment = contractValue / periods; const rate = Number(lease.discount_rate || 0) / 100 / ppy; const presentValue = rate > 0 ? payment * (1 - (1 + rate) ** -periods) / rate : contractValue; const rouAmortization = presentValue / periods; let liability = presentValue; let rouBalance = presentValue; let totalInterest = 0; const rows = []; for (let period = 1; period <= periods; period += 1) { const interest = liability * rate; const principal = payment - interest; liability = Math.max(0, liability - principal); rouBalance = Math.max(0, rouBalance - rouAmortization); totalInterest += interest; rows.push({ period, date: addMonths(start, period * monthsPerPeriod).toISOString().slice(0, 10), payment: money(payment), interest: money(interest), principal: money(principal), liability_balance: money(liability), rou_amortization: money(rouAmortization), rou_balance: money(rouBalance) }); } return { summary: { periods, frequency: lease.payment_frequency, periodic_payment: money(payment), present_value: money(presentValue), total_payments: money(payment * periods), total_interest: money(totalInterest) }, rows }; } module.exports = { FREQUENCIES, createLease, deleteLease, getLease, leaseSchedule, listLeases, updateLease };