const { all, audit, now, one, run } = require('../db'); const { getLimit } = require('./section179Limits'); // §179 return-level taxable-income limitation and carryover (IRS Pub 946 / IRC §179(b)). The per-asset // engine already caps each asset's §179 at the dollar limit and phase-out; THIS layer aggregates the // §179 elected across a company's assets for a tax year, applies the single annual dollar limit (reduced // by the investment phase-out) AND the business taxable-income limitation, and carries any disallowed // amount forward to the next year. Stateful: the prior year's carryover feeds the next year's available // deduction. Per company + book + year. function yearOf(value) { const s = String(value || ''); return s ? Number(s.slice(0, 4)) : 0; } function round2(n) { return Math.round((Number(n) || 0) * 100) / 100; } // §179 elected per asset and the cost of §179 property placed in service, for a (company, book, year). function electedFor(companyId, bookCode, year) { const rows = all( `SELECT a.acquisition_cost, a.in_service_date, a.acquired_date, b.section_179_amount, b.cost FROM assets a JOIN asset_books b ON b.asset_id = a.id WHERE a.entity_id = ? AND b.book_type = ? AND b.active = 1 AND a.status != 'disposed'`, [companyId, bookCode] ); let elected = 0; let propertyCost = 0; let count = 0; for (const r of rows) { if (yearOf(r.in_service_date || r.acquired_date) !== Number(year)) continue; const amt = Number(r.section_179_amount || 0); if (amt <= 0) continue; elected += amt; propertyCost += Number(r.cost ?? r.acquisition_cost ?? 0); count += 1; } return { elected: round2(elected), propertyCost: round2(propertyCost), count }; } function savedRow(companyId, bookCode, year) { return one('SELECT * FROM section_179_carryover WHERE entity_id = ? AND book_code = ? AND tax_year = ?', [companyId, bookCode, Number(year)]); } // Compute the limitation. `taxableIncome` overrides the stored value when provided (null/undefined uses // the stored value, if any). The business-income limitation is only applied when an income figure exists. function summary(companyId, bookCode, year, taxableIncome) { const yr = Number(year); const limitRow = getLimit(yr); const { elected, propertyCost, count } = electedFor(companyId, bookCode, yr); const priorRow = savedRow(companyId, bookCode, yr - 1); const priorCarryover = round2(priorRow ? priorRow.carryover_to_next : 0); const baseLimit = limitRow ? Number(limitRow.base_limit) : Infinity; const phaseoutThreshold = limitRow ? Number(limitRow.phaseout_threshold) : Infinity; const phaseoutReduction = Number.isFinite(phaseoutThreshold) ? Math.max(0, propertyCost - phaseoutThreshold) : 0; const dollarLimit = Number.isFinite(baseLimit) ? Math.max(0, baseLimit - phaseoutReduction) : Infinity; const available = round2(elected + priorCarryover); const dollarAllowed = round2(Math.min(available, dollarLimit)); const stored = savedRow(companyId, bookCode, yr); const income = taxableIncome !== undefined && taxableIncome !== null ? Number(taxableIncome) : (stored && stored.taxable_income !== null ? Number(stored.taxable_income) : null); const incomeApplied = income !== null && !Number.isNaN(income); const allowed = round2(incomeApplied ? Math.min(dollarAllowed, Math.max(0, income)) : dollarAllowed); const carryover = round2(Math.max(0, available - allowed)); return { company_id: companyId, book_code: bookCode, tax_year: yr, asset_count: count, elected, property_cost: propertyCost, prior_carryover: priorCarryover, base_limit: Number.isFinite(baseLimit) ? baseLimit : null, phaseout_threshold: Number.isFinite(phaseoutThreshold) ? phaseoutThreshold : null, phaseout_reduction: round2(phaseoutReduction), dollar_limit: Number.isFinite(dollarLimit) ? round2(dollarLimit) : null, available, dollar_allowed: dollarAllowed, taxable_income: incomeApplied ? round2(income) : null, income_limited: incomeApplied && allowed < dollarAllowed, allowed_deduction: allowed, carryover_to_next: carryover }; } // Persist the entered taxable income and the resulting carryover (so the next year picks it up). Audited. function save(companyId, bookCode, year, taxableIncome, userId) { const result = summary(companyId, bookCode, year, taxableIncome); const income = taxableIncome === undefined || taxableIncome === null || taxableIncome === '' ? null : Number(taxableIncome); run( `INSERT INTO section_179_carryover (entity_id, book_code, tax_year, taxable_income, carryover_to_next, updated_by, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(entity_id, book_code, tax_year) DO UPDATE SET taxable_income = excluded.taxable_income, carryover_to_next = excluded.carryover_to_next, updated_by = excluded.updated_by, updated_at = excluded.updated_at`, [companyId, bookCode, Number(year), income, result.carryover_to_next, userId, now()] ); audit(userId, 'update', 'section_179_limitation', `${bookCode}:${year}`, null, { taxable_income: income, carryover: result.carryover_to_next }); return result; } module.exports = { summary, save };