Pub 946 Compliance
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
const { all, audit, now, one, run, tx } = require('../db');
|
||||
const { annualSchedule, money } = require('../depreciation');
|
||||
const { annualSchedule, money, section280fRecapture } = require('../depreciation');
|
||||
const { ruleSetForBook } = require('./ruleSets');
|
||||
const { hydrateAsset } = require('./assets');
|
||||
|
||||
@@ -30,6 +30,44 @@ function bookFor(asset, bookType) {
|
||||
return asset.books.find((book) => book.book_type === bookType) || asset.books[0] || { book_type: bookType };
|
||||
}
|
||||
|
||||
// Real property (§1250) vs personal property (§1245), by recovery period: 27.5-year residential rental
|
||||
// and 39-year nonresidential real property are §1250; everything else is §1245.
|
||||
function isRealProperty(asset, book) {
|
||||
const months = Number(book.useful_life_months || asset.useful_life_months || 0);
|
||||
const pt = String(asset.property_type || '').toLowerCase();
|
||||
return months >= 330 || /real|realty|1250|building/.test(pt);
|
||||
}
|
||||
|
||||
// Characterize the gain on disposal into ordinary-income recapture vs. §1231 (capital) gain (Form 4797).
|
||||
// §1245: ordinary recapture = lesser of gain or total depreciation taken (incl. §179/bonus). §1250:
|
||||
// ordinary recapture = lesser of gain or "additional depreciation" (accelerated over straight-line); the
|
||||
// remaining gain up to straight-line taken is unrecaptured §1250 gain (a 25% capital bucket).
|
||||
function characterizeRecapture(asset, book, rules, year, proportion, gainLoss, accumulated) {
|
||||
const section = isRealProperty(asset, book) ? '1250' : '1245';
|
||||
const gain = Math.max(0, gainLoss);
|
||||
let ordinary = 0;
|
||||
let unrecaptured1250 = 0;
|
||||
if (gain > 0) {
|
||||
if (section === '1245') {
|
||||
ordinary = Math.min(gain, accumulated);
|
||||
} else {
|
||||
// Straight-line accumulated for the same period (additional depreciation = accelerated − SL).
|
||||
const slBook = { ...book, depreciation_method: 'straight_line', section_179_amount: 0, bonus_percent: 0 };
|
||||
const slRow = annualSchedule(asset, slBook, rules, { startYear: year, endYear: year })[0] || {};
|
||||
const slAccum = money(Number(slRow.accumulated || 0) * proportion);
|
||||
const additional = Math.max(0, money(accumulated - slAccum));
|
||||
ordinary = Math.min(gain, additional);
|
||||
unrecaptured1250 = Math.min(money(gain - ordinary), slAccum);
|
||||
}
|
||||
}
|
||||
return {
|
||||
recapture_section: section,
|
||||
ordinary_recapture: money(ordinary),
|
||||
unrecaptured_1250_gain: money(unrecaptured1250),
|
||||
section_1231_gain: money(gainLoss - ordinary) // negative for a §1231 loss
|
||||
};
|
||||
}
|
||||
|
||||
function computeDisposal(asset, input = {}) {
|
||||
const bookType = input.book_type || 'GAAP';
|
||||
const book = bookFor(asset, bookType);
|
||||
@@ -55,6 +93,10 @@ function computeDisposal(asset, input = {}) {
|
||||
const realized = money(price - expense);
|
||||
const gainLoss = money(realized - netBookValue);
|
||||
|
||||
// Recapture characterization is computed on the disposed book's depreciation (use a tax book — FEDERAL —
|
||||
// for tax recapture). "Depreciation taken" includes the impairment write-down already reflected in NBV.
|
||||
const recapture = characterizeRecapture(asset, book, rules, year, proportion, gainLoss, money(accumulated + impairment));
|
||||
|
||||
return {
|
||||
book_type: bookType,
|
||||
disposal_date: disposalDate,
|
||||
@@ -69,7 +111,8 @@ function computeDisposal(asset, input = {}) {
|
||||
impairment,
|
||||
net_book_value: netBookValue,
|
||||
realized,
|
||||
gain_loss: gainLoss
|
||||
gain_loss: gainLoss,
|
||||
...recapture
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,7 +122,18 @@ function previewDisposal(assetId, input) {
|
||||
return computeDisposal(asset, input);
|
||||
}
|
||||
|
||||
// §280F recapture preview for a listed/passenger asset converting to ≤50% business use in a given year.
|
||||
function previewSection280fRecapture(assetId, input = {}) {
|
||||
const asset = hydrateAsset(assetId);
|
||||
if (!asset) return null;
|
||||
const bookType = input.book || 'FEDERAL';
|
||||
const book = bookFor(asset, bookType);
|
||||
const year = Number(input.year) || new Date().getFullYear();
|
||||
return section280fRecapture(asset, book, ruleSetForBook(bookType), year);
|
||||
}
|
||||
|
||||
function recordDisposal(assetId, input, userId) {
|
||||
require('./midQuarter').clearCache();
|
||||
const asset = hydrateAsset(assetId);
|
||||
if (!asset) return null;
|
||||
const result = computeDisposal(asset, input);
|
||||
@@ -91,13 +145,15 @@ function recordDisposal(assetId, input, userId) {
|
||||
`INSERT INTO disposals (
|
||||
asset_id, disposal_date, method, property_type, partial, disposed_cost,
|
||||
disposal_price, disposal_expense, accumulated_depreciation, net_book_value,
|
||||
gain_loss, book_type, notes, created_by, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
gain_loss, book_type, recapture_section, ordinary_recapture, section_1231_gain,
|
||||
unrecaptured_1250_gain, notes, created_by, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
assetId, result.disposal_date, result.method, result.property_type, result.partial,
|
||||
result.disposed_cost, result.disposal_price, result.disposal_expense,
|
||||
result.accumulated_depreciation, result.net_book_value, result.gain_loss,
|
||||
result.book_type, input.notes || null, userId, timestamp
|
||||
result.book_type, result.recapture_section, result.ordinary_recapture, result.section_1231_gain,
|
||||
result.unrecaptured_1250_gain, input.notes || null, userId, timestamp
|
||||
]
|
||||
);
|
||||
|
||||
@@ -127,6 +183,7 @@ function recordDisposal(assetId, input, userId) {
|
||||
}
|
||||
|
||||
function reverseDisposal(assetId, disposalId, userId) {
|
||||
require('./midQuarter').clearCache();
|
||||
const disposal = one('SELECT * FROM disposals WHERE id = ? AND asset_id = ?', [disposalId, assetId]);
|
||||
if (!disposal) return null;
|
||||
guardSubledger(assetId, disposal.disposal_date);
|
||||
@@ -191,6 +248,7 @@ module.exports = {
|
||||
deleteAdjustment,
|
||||
netAdjustments,
|
||||
previewDisposal,
|
||||
previewSection280fRecapture,
|
||||
recordAdjustment,
|
||||
recordDisposal,
|
||||
reverseDisposal
|
||||
|
||||
Reference in New Issue
Block a user