Pub 946 Compliance
This commit is contained in:
@@ -29,6 +29,7 @@ const assetColumns = [
|
||||
'condition',
|
||||
'new_or_used',
|
||||
'listed_property',
|
||||
'passenger_auto',
|
||||
'business_use_percent',
|
||||
'investment_use_percent',
|
||||
'disposal_date',
|
||||
@@ -62,6 +63,7 @@ function assetFromRow(row) {
|
||||
return {
|
||||
...row,
|
||||
listed_property: Boolean(row.listed_property),
|
||||
passenger_auto: Boolean(row.passenger_auto),
|
||||
custom_fields: parseJson(row.custom_fields, {}),
|
||||
books: row.books ? parseJson(row.books, []) : undefined
|
||||
};
|
||||
@@ -213,6 +215,7 @@ function normalizeAssetInput(body) {
|
||||
input.investment_use_percent = Number(input.investment_use_percent || 0);
|
||||
input.custom_fields = json(input.custom_fields || {});
|
||||
input.listed_property = input.listed_property ? 1 : 0;
|
||||
input.passenger_auto = input.passenger_auto ? 1 : 0;
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -267,6 +270,7 @@ function listAssets(query = {}) {
|
||||
}
|
||||
|
||||
function createAsset(body, userId, companyId) {
|
||||
require('./midQuarter').clearCache(); // asset set changed → recompute the mid-quarter determination
|
||||
const created = now();
|
||||
const input = normalizeAssetInput(body);
|
||||
if (companyId) input.entity_id = companyId; // new assets belong to the active company
|
||||
@@ -289,6 +293,7 @@ function createAsset(body, userId, companyId) {
|
||||
}
|
||||
|
||||
function updateAsset(id, body, userId, companyId) {
|
||||
require('./midQuarter').clearCache();
|
||||
const before = hydrateAsset(id);
|
||||
if (!before) return null;
|
||||
if (companyId && before.entity_id !== companyId) return null; // belongs to another company
|
||||
@@ -323,6 +328,7 @@ function updateAsset(id, body, userId, companyId) {
|
||||
}
|
||||
|
||||
function deleteAsset(id, userId, companyId) {
|
||||
require('./midQuarter').clearCache();
|
||||
const before = hydrateAsset(id);
|
||||
if (!before) return false;
|
||||
if (companyId && before.entity_id !== companyId) return false; // belongs to another company
|
||||
@@ -332,6 +338,7 @@ function deleteAsset(id, userId, companyId) {
|
||||
}
|
||||
|
||||
function massUpdateAssets(ids, changes, userId, companyId) {
|
||||
require('./midQuarter').clearCache();
|
||||
const allowed = ['status', 'location', 'department', 'group_name', 'in_service_date', 'useful_life_months', 'condition', 'disposal_date'];
|
||||
const columns = allowed.filter((column) => Object.prototype.hasOwnProperty.call(changes, column));
|
||||
// Restrict to assets in the active company so a forged id list can't touch another company's data.
|
||||
|
||||
@@ -363,6 +363,7 @@ function buildLine(o) {
|
||||
|
||||
// Capitalize a Construction-in-progress asset: place it in service and post Dr asset / Cr CWIP.
|
||||
function capitalizeWip(assetId, body, userId, companyId) {
|
||||
require('./midQuarter').clearCache(); // status cip→in_service changes the placed-in-service set
|
||||
const glEntries = require('./glEntries');
|
||||
const { getDefaults } = require('./glAccounts');
|
||||
const asset = one('SELECT * FROM assets WHERE id = ? AND entity_id = ?', [assetId, companyId]);
|
||||
|
||||
@@ -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
|
||||
|
||||
68
server/services/midQuarter.js
Normal file
68
server/services/midQuarter.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const { all } = require('../db');
|
||||
|
||||
// IRS Pub 946 mid-quarter convention determination. The mid-quarter convention is MANDATORY for all of a
|
||||
// tax year's MACRS personal property when more than 40% of the aggregate depreciable basis of such
|
||||
// property is placed in service during the last three months (Q4) of the year. This is a portfolio- and
|
||||
// year-level fact, computed here once per (company, book, year) and consulted by the depreciation engine
|
||||
// (depreciation.conventionFor). Real property (mid-month) is excluded. Cached; invalidated on asset writes.
|
||||
|
||||
const cache = new Map(); // `${entityId}|${bookCode}|${year}` -> boolean
|
||||
|
||||
function clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
function bookObject(row, bookCode) {
|
||||
return {
|
||||
book_type: bookCode,
|
||||
cost: row.book_cost,
|
||||
depreciation_method: row.depreciation_method,
|
||||
convention: row.convention,
|
||||
useful_life_months: row.book_life,
|
||||
section_179_amount: row.section_179_amount,
|
||||
bonus_percent: row.bonus_percent,
|
||||
business_use_percent: row.book_bup,
|
||||
investment_use_percent: row.book_ivp,
|
||||
manual_depreciation: row.manual_depreciation
|
||||
};
|
||||
}
|
||||
|
||||
// True when the mid-quarter convention applies to (company, book) MACRS personal property placed in
|
||||
// service in `year`. No-op-friendly: returns false when the portfolio doesn't trip the 40% threshold.
|
||||
function applies(entityId, bookCode, year) {
|
||||
if (!entityId || !bookCode || !year) return false;
|
||||
const key = `${entityId}|${bookCode}|${Number(year)}`;
|
||||
if (cache.has(key)) return cache.get(key);
|
||||
|
||||
const dep = require('../depreciation');
|
||||
const { ruleSetForBook } = require('./ruleSets');
|
||||
const { assetFromRow } = require('./assets');
|
||||
const ruleSet = ruleSetForBook(bookCode);
|
||||
const rows = all(
|
||||
`SELECT a.*, b.cost AS book_cost, b.depreciation_method, b.convention, b.useful_life_months AS book_life,
|
||||
b.section_179_amount, b.bonus_percent, b.business_use_percent AS book_bup, b.investment_use_percent AS book_ivp, b.manual_depreciation
|
||||
FROM assets a JOIN asset_books b ON b.asset_id = a.id
|
||||
WHERE b.book_type = ? AND b.active = 1 AND a.entity_id = ? AND a.status != 'disposed'`,
|
||||
[bookCode, entityId]
|
||||
);
|
||||
|
||||
let total = 0;
|
||||
let lastQuarter = 0;
|
||||
for (const row of rows) {
|
||||
const pis = row.in_service_date || row.acquired_date;
|
||||
if (!pis || dep.yearFromDate(pis) !== Number(year)) continue;
|
||||
const book = bookObject(row, bookCode);
|
||||
const methodRule = dep.getMethodRule(ruleSet, book.depreciation_method);
|
||||
if (!dep.isMidQuarterEligible(book, methodRule)) continue;
|
||||
const basis = dep.midQuarterBasis(assetFromRow(row), book);
|
||||
if (basis <= 0) continue;
|
||||
total += basis;
|
||||
if (Math.ceil(dep.monthFromDate(pis) / 3) === 4) lastQuarter += basis;
|
||||
}
|
||||
|
||||
const result = total > 0 && lastQuarter / total > 0.4;
|
||||
cache.set(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { applies, clearCache };
|
||||
107
server/services/section179Limitation.js
Normal file
107
server/services/section179Limitation.js
Normal file
@@ -0,0 +1,107 @@
|
||||
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 };
|
||||
95
server/services/section280fLimits.js
Normal file
95
server/services/section280fLimits.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const { all, audit, now, one, run } = require('../db');
|
||||
|
||||
// §280F passenger-automobile annual depreciation caps, keyed by placed-in-service year (IRS Rev. Proc.,
|
||||
// indexed annually). Mirrors the §179-limits reference service: cached for the engine, CRUD for admins.
|
||||
|
||||
let cache = null;
|
||||
|
||||
function clearCache() {
|
||||
cache = null;
|
||||
}
|
||||
|
||||
function rows() {
|
||||
if (!cache) cache = all('SELECT * FROM section_280f_limits ORDER BY pis_year');
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Caps for a placed-in-service year: the most recent configured year at or before it, or null.
|
||||
function getLimit(year) {
|
||||
const y = Number(year);
|
||||
if (!y) return null;
|
||||
let match = null;
|
||||
for (const row of rows()) {
|
||||
if (row.pis_year <= y) match = row;
|
||||
else break;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
function fromRow(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
pis_year: row.pis_year,
|
||||
year1_no_bonus: row.year1_no_bonus,
|
||||
year1_bonus: row.year1_bonus,
|
||||
year2: row.year2,
|
||||
year3: row.year3,
|
||||
year4plus: row.year4plus
|
||||
};
|
||||
}
|
||||
|
||||
function list() {
|
||||
return all('SELECT * FROM section_280f_limits ORDER BY pis_year DESC').map(fromRow);
|
||||
}
|
||||
|
||||
function normalize(body) {
|
||||
return {
|
||||
pis_year: Math.trunc(Number(body.pis_year)) || 0,
|
||||
year1_no_bonus: Number(body.year1_no_bonus) || 0,
|
||||
year1_bonus: Number(body.year1_bonus) || 0,
|
||||
year2: Number(body.year2) || 0,
|
||||
year3: Number(body.year3) || 0,
|
||||
year4plus: Number(body.year4plus) || 0
|
||||
};
|
||||
}
|
||||
|
||||
function createLimit(body, userId) {
|
||||
const input = normalize(body);
|
||||
if (!input.pis_year) throw Object.assign(new Error('A valid placed-in-service year is required'), { status: 400 });
|
||||
if (one('SELECT id FROM section_280f_limits WHERE pis_year = ?', [input.pis_year])) {
|
||||
throw Object.assign(new Error('Caps for that year already exist'), { status: 400 });
|
||||
}
|
||||
const ts = now();
|
||||
run(
|
||||
`INSERT INTO section_280f_limits (pis_year, year1_no_bonus, year1_bonus, year2, year3, year4plus, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[input.pis_year, input.year1_no_bonus, input.year1_bonus, input.year2, input.year3, input.year4plus, ts, ts]
|
||||
);
|
||||
clearCache();
|
||||
audit(userId, 'create', 'section_280f_limit', String(input.pis_year), null, input);
|
||||
return fromRow(one('SELECT * FROM section_280f_limits WHERE pis_year = ?', [input.pis_year]));
|
||||
}
|
||||
|
||||
function updateLimit(year, body, userId) {
|
||||
const before = one('SELECT * FROM section_280f_limits WHERE pis_year = ?', [Number(year)]);
|
||||
if (!before) return null;
|
||||
const input = normalize({ ...fromRow(before), ...body, pis_year: before.pis_year });
|
||||
run(
|
||||
'UPDATE section_280f_limits SET year1_no_bonus = ?, year1_bonus = ?, year2 = ?, year3 = ?, year4plus = ?, updated_at = ? WHERE pis_year = ?',
|
||||
[input.year1_no_bonus, input.year1_bonus, input.year2, input.year3, input.year4plus, now(), before.pis_year]
|
||||
);
|
||||
clearCache();
|
||||
audit(userId, 'update', 'section_280f_limit', String(before.pis_year), fromRow(before), input);
|
||||
return fromRow(one('SELECT * FROM section_280f_limits WHERE pis_year = ?', [before.pis_year]));
|
||||
}
|
||||
|
||||
function deleteLimit(year, userId) {
|
||||
const before = one('SELECT * FROM section_280f_limits WHERE pis_year = ?', [Number(year)]);
|
||||
if (!before) return false;
|
||||
run('DELETE FROM section_280f_limits WHERE pis_year = ?', [before.pis_year]);
|
||||
clearCache();
|
||||
audit(userId, 'delete', 'section_280f_limit', String(before.pis_year), fromRow(before), null);
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = { clearCache, createLimit, deleteLimit, getLimit, list, updateLimit };
|
||||
Reference in New Issue
Block a user