Added Section 179 Limits
This commit is contained in:
39
server/db.js
39
server/db.js
@@ -652,6 +652,15 @@ function initialize() {
|
|||||||
updated_at TEXT NOT NULL
|
updated_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS section_179_limits (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
tax_year INTEGER NOT NULL UNIQUE,
|
||||||
|
base_limit REAL NOT NULL DEFAULT 0,
|
||||||
|
phaseout_threshold REAL NOT NULL DEFAULT 0,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
actor_id INTEGER REFERENCES users(id),
|
actor_id INTEGER REFERENCES users(id),
|
||||||
@@ -694,6 +703,8 @@ function migrate() {
|
|||||||
ensureColumn('assets', 'servicenow_sys_id', 'TEXT');
|
ensureColumn('assets', 'servicenow_sys_id', 'TEXT');
|
||||||
ensureColumn('assets', 'asset_class_number', 'TEXT');
|
ensureColumn('assets', 'asset_class_number', 'TEXT');
|
||||||
ensureColumn('assets', 'special_zone', 'TEXT');
|
ensureColumn('assets', 'special_zone', 'TEXT');
|
||||||
|
ensureColumn('depreciation_zones', 'section_179_increase', 'REAL NOT NULL DEFAULT 0');
|
||||||
|
ensureColumn('depreciation_zones', 'section_179_cost_factor', 'REAL NOT NULL DEFAULT 1');
|
||||||
dropUsersRoleCheck();
|
dropUsersRoleCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,6 +769,34 @@ function seed() {
|
|||||||
'30% special depreciation allowance for qualified NY Liberty Zone property; qualified leasehold improvements use a 5-year life; increased Section 179. Verify current tax law before filing.', 1, ?, ?)`,
|
'30% special depreciation allowance for qualified NY Liberty Zone property; qualified leasehold improvements use a 5-year life; increased Section 179. Verify current tax law before filing.', 1, ?, ?)`,
|
||||||
[createdAt, createdAt]
|
[createdAt, createdAt]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Enterprise / Empowerment Zone: raises the Section 179 limit by $35,000 (2002-2020) and counts
|
||||||
|
// only 50% of the property's cost toward the §179 investment-phaseout threshold.
|
||||||
|
run(
|
||||||
|
`INSERT OR IGNORE INTO depreciation_zones
|
||||||
|
(code, name, allowance_percent, pis_start, pis_end, real_property_end, max_recovery_years, leasehold_life_years,
|
||||||
|
section_179_increase, section_179_cost_factor, notes, enabled, created_at, updated_at)
|
||||||
|
VALUES ('enterprise_zone', 'Enterprise Zone (Empowerment Zone)', 0, '2002-01-01', '2020-12-31', NULL, NULL, NULL,
|
||||||
|
35000, 0.5,
|
||||||
|
'Section 179 limit increase for qualified empowerment/enterprise zone property: +$35,000 (2002-2020; +$20,000 for 1993-2001) and only 50% of cost counts toward the investment-phaseout threshold. Not available after 2020. Verify current tax law before filing.', 1, ?, ?)`,
|
||||||
|
[createdAt, createdAt]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Annual Section 179 deduction limits & investment-phaseout thresholds (IRS historical values).
|
||||||
|
// Seeded once, then user-managed in Admin → Application Configuration → Section 179 limits.
|
||||||
|
const section179Limits = [
|
||||||
|
[2014, 500000, 2000000], [2015, 500000, 2000000], [2016, 500000, 2010000], [2017, 510000, 2030000],
|
||||||
|
[2018, 1000000, 2500000], [2019, 1020000, 2550000], [2020, 1040000, 2590000], [2021, 1050000, 2620000],
|
||||||
|
[2022, 1080000, 2700000], [2023, 1160000, 2890000], [2024, 1220000, 3050000], [2025, 1250000, 3130000],
|
||||||
|
[2026, 1250000, 3130000]
|
||||||
|
];
|
||||||
|
for (const [year, limit, threshold] of section179Limits) {
|
||||||
|
run(
|
||||||
|
`INSERT OR IGNORE INTO section_179_limits (tax_year, base_limit, phaseout_threshold, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
[year, limit, threshold, createdAt, createdAt]
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!one('SELECT id FROM teams LIMIT 1')) {
|
if (!one('SELECT id FROM teams LIMIT 1')) {
|
||||||
run('INSERT INTO teams (name, description, created_at) VALUES (?, ?, ?)', [
|
run('INSERT INTO teams (name, description, created_at) VALUES (?, ?, ?)', [
|
||||||
'Finance Operations',
|
'Finance Operations',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const { parseJson } = require('./db');
|
const { parseJson } = require('./db');
|
||||||
const { buildDepreciationMethods } = require('./rule-catalog');
|
const { buildDepreciationMethods } = require('./rule-catalog');
|
||||||
const { getZone } = require('./services/zones');
|
const { getZone } = require('./services/zones');
|
||||||
|
const { getLimit: getSection179Limit } = require('./services/section179Limits');
|
||||||
|
|
||||||
// Built-in method catalog, used as a fallback so codes like MF200/DB200 always resolve
|
// Built-in method catalog, used as a fallback so codes like MF200/DB200 always resolve
|
||||||
// even if a particular rule set hasn't been re-expanded to include them.
|
// even if a particular rule set hasn't been re-expanded to include them.
|
||||||
@@ -47,7 +48,45 @@ function salvageValue(asset, book, methodRule) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function section179Amount(asset, book) {
|
function section179Amount(asset, book) {
|
||||||
return Math.min(grossBasis(asset, book), Number(book.section_179_amount || 0));
|
return Math.min(grossBasis(asset, book), Number(book.section_179_amount || 0), section179Limit(asset, book));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enterprise/empowerment-zone Section 179 treatment for the federal book: an increased dollar
|
||||||
|
// limit and a reduced (e.g. 50%) share of cost counting toward the phaseout threshold. Applies
|
||||||
|
// only when the asset is flagged for such a zone and placed in service within the date window.
|
||||||
|
function zoneSection179(asset, book) {
|
||||||
|
if (!asset.special_zone || String(book.book_type) !== 'FEDERAL') return null;
|
||||||
|
const zone = getZone(asset.special_zone);
|
||||||
|
if (!zone) return null;
|
||||||
|
const increase = Number(zone.section_179_increase || 0);
|
||||||
|
const costFactor = Number(zone.section_179_cost_factor || 0) || 1;
|
||||||
|
if (!increase && costFactor === 1) return null;
|
||||||
|
const pis = asset.in_service_date || asset.acquired_date || '';
|
||||||
|
if (pis && zone.pis_start && pis < zone.pis_start) return null;
|
||||||
|
if (pis && zone.pis_end && pis > zone.pis_end) return null;
|
||||||
|
return { increase, costFactor };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applicable §179 dollar cap for an asset's placed-in-service year: the configured base limit
|
||||||
|
// (plus any enterprise-zone increase) less the dollar-for-dollar investment phaseout. Returns
|
||||||
|
// Infinity when no limit is configured for the year, leaving the entered §179 amount uncapped.
|
||||||
|
function section179Limit(asset, book) {
|
||||||
|
const pisYear = yearFromDate(asset.in_service_date || asset.acquired_date);
|
||||||
|
const limitRow = getSection179Limit(pisYear);
|
||||||
|
if (!limitRow) return Infinity;
|
||||||
|
let limit = Number(limitRow.base_limit) || 0;
|
||||||
|
const threshold = Number(limitRow.phaseout_threshold) || 0;
|
||||||
|
let costFactor = 1;
|
||||||
|
const zone = zoneSection179(asset, book);
|
||||||
|
if (zone) {
|
||||||
|
limit += zone.increase;
|
||||||
|
costFactor = zone.costFactor;
|
||||||
|
}
|
||||||
|
if (threshold > 0) {
|
||||||
|
const investment = grossBasis(asset, book) * costFactor;
|
||||||
|
limit -= Math.max(0, investment - threshold);
|
||||||
|
}
|
||||||
|
return Math.max(0, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special-zone §168 allowance (e.g. New York Liberty Zone): applies to the federal tax book
|
// Special-zone §168 allowance (e.g. New York Liberty Zone): applies to the federal tax book
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const { requireCapability } = require('../middleware/auth');
|
|||||||
const { formatAssetId } = require('../services/assets');
|
const { formatAssetId } = require('../services/assets');
|
||||||
const assetClasses = require('../services/assetClasses');
|
const assetClasses = require('../services/assetClasses');
|
||||||
const zones = require('../services/zones');
|
const zones = require('../services/zones');
|
||||||
|
const section179Limits = require('../services/section179Limits');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -36,6 +37,35 @@ router.delete('/depreciation-zones/:code', requireCapability('config.manage'), (
|
|||||||
return res.status(204).end();
|
return res.status(204).end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Annual Section 179 deduction limits & phaseout thresholds. GET is reference data; mutations
|
||||||
|
// require config.manage. The engine caps each asset's §179 deduction at the applicable year's limit.
|
||||||
|
router.get('/section-179-limits', (req, res) => {
|
||||||
|
res.json({ limits: section179Limits.list() });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/section-179-limits', requireCapability('config.manage'), (req, res, next) => {
|
||||||
|
try {
|
||||||
|
res.status(201).json({ limit: section179Limits.createLimit(req.body, req.user.id) });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/section-179-limits/:year', requireCapability('config.manage'), (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const limit = section179Limits.updateLimit(req.params.year, req.body, req.user.id);
|
||||||
|
if (!limit) return res.status(404).json({ error: 'Limit not found' });
|
||||||
|
return res.json({ limit });
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/section-179-limits/:year', requireCapability('config.manage'), (req, res) => {
|
||||||
|
if (!section179Limits.deleteLimit(req.params.year, req.user.id)) return res.status(404).json({ error: 'Limit not found' });
|
||||||
|
return res.status(204).end();
|
||||||
|
});
|
||||||
|
|
||||||
// Asset class catalog (seeded from the IRS tables, then user-managed). GET is reference
|
// Asset class catalog (seeded from the IRS tables, then user-managed). GET is reference
|
||||||
// data used by the category picker; mutations require config.manage.
|
// data used by the category picker; mutations require config.manage.
|
||||||
router.get('/asset-classes', (req, res) => {
|
router.get('/asset-classes', (req, res) => {
|
||||||
|
|||||||
95
server/services/section179Limits.js
Normal file
95
server/services/section179Limits.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
const { all, audit, now, one, run } = require('../db');
|
||||||
|
|
||||||
|
let cache = null;
|
||||||
|
|
||||||
|
function clearCache() {
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorted ascending by tax_year, cached for the engine.
|
||||||
|
function rows() {
|
||||||
|
if (!cache) cache = all('SELECT * FROM section_179_limits ORDER BY tax_year');
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applicable limit for a placed-in-service year: the most recent year at or before it.
|
||||||
|
// Returns null when nothing is configured for/under the year, so the engine leaves §179 uncapped.
|
||||||
|
function getLimit(year) {
|
||||||
|
const y = Number(year);
|
||||||
|
if (!y) return null;
|
||||||
|
let match = null;
|
||||||
|
for (const row of rows()) {
|
||||||
|
if (row.tax_year <= y) match = row;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromRow(row) {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
tax_year: row.tax_year,
|
||||||
|
base_limit: row.base_limit,
|
||||||
|
phaseout_threshold: row.phaseout_threshold
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function list() {
|
||||||
|
return all('SELECT * FROM section_179_limits ORDER BY tax_year DESC').map(fromRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(body) {
|
||||||
|
return {
|
||||||
|
tax_year: Math.trunc(Number(body.tax_year)) || 0,
|
||||||
|
base_limit: Number(body.base_limit) || 0,
|
||||||
|
phaseout_threshold: Number(body.phaseout_threshold) || 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLimit(body, userId) {
|
||||||
|
const input = normalize(body);
|
||||||
|
if (!input.tax_year) throw Object.assign(new Error('A valid tax year is required'), { status: 400 });
|
||||||
|
if (one('SELECT id FROM section_179_limits WHERE tax_year = ?', [input.tax_year])) {
|
||||||
|
throw Object.assign(new Error('A limit for that tax year already exists'), { status: 400 });
|
||||||
|
}
|
||||||
|
const ts = now();
|
||||||
|
run(
|
||||||
|
`INSERT INTO section_179_limits (tax_year, base_limit, phaseout_threshold, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
[input.tax_year, input.base_limit, input.phaseout_threshold, ts, ts]
|
||||||
|
);
|
||||||
|
clearCache();
|
||||||
|
audit(userId, 'create', 'section_179_limit', String(input.tax_year), null, input);
|
||||||
|
return fromRow(one('SELECT * FROM section_179_limits WHERE tax_year = ?', [input.tax_year]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLimit(taxYear, body, userId) {
|
||||||
|
const before = one('SELECT * FROM section_179_limits WHERE tax_year = ?', [Number(taxYear)]);
|
||||||
|
if (!before) return null;
|
||||||
|
const input = normalize({ ...before, ...body, tax_year: before.tax_year });
|
||||||
|
run(
|
||||||
|
'UPDATE section_179_limits SET base_limit = ?, phaseout_threshold = ?, updated_at = ? WHERE tax_year = ?',
|
||||||
|
[input.base_limit, input.phaseout_threshold, now(), before.tax_year]
|
||||||
|
);
|
||||||
|
clearCache();
|
||||||
|
audit(userId, 'update', 'section_179_limit', String(before.tax_year), before, input);
|
||||||
|
return fromRow(one('SELECT * FROM section_179_limits WHERE tax_year = ?', [before.tax_year]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteLimit(taxYear, userId) {
|
||||||
|
const before = one('SELECT * FROM section_179_limits WHERE tax_year = ?', [Number(taxYear)]);
|
||||||
|
if (!before) return false;
|
||||||
|
run('DELETE FROM section_179_limits WHERE tax_year = ?', [before.tax_year]);
|
||||||
|
clearCache();
|
||||||
|
audit(userId, 'delete', 'section_179_limit', String(before.tax_year), before, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
clearCache,
|
||||||
|
createLimit,
|
||||||
|
deleteLimit,
|
||||||
|
getLimit,
|
||||||
|
list,
|
||||||
|
updateLimit
|
||||||
|
};
|
||||||
@@ -37,6 +37,8 @@ function fromRow(row) {
|
|||||||
real_property_end: row.real_property_end,
|
real_property_end: row.real_property_end,
|
||||||
max_recovery_years: row.max_recovery_years,
|
max_recovery_years: row.max_recovery_years,
|
||||||
leasehold_life_years: row.leasehold_life_years,
|
leasehold_life_years: row.leasehold_life_years,
|
||||||
|
section_179_increase: row.section_179_increase,
|
||||||
|
section_179_cost_factor: row.section_179_cost_factor,
|
||||||
notes: row.notes,
|
notes: row.notes,
|
||||||
enabled: Boolean(row.enabled),
|
enabled: Boolean(row.enabled),
|
||||||
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE special_zone = ?', [row.code]).c
|
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE special_zone = ?', [row.code]).c
|
||||||
@@ -57,6 +59,8 @@ function normalize(body) {
|
|||||||
real_property_end: body.real_property_end || null,
|
real_property_end: body.real_property_end || null,
|
||||||
max_recovery_years: num(body.max_recovery_years),
|
max_recovery_years: num(body.max_recovery_years),
|
||||||
leasehold_life_years: num(body.leasehold_life_years),
|
leasehold_life_years: num(body.leasehold_life_years),
|
||||||
|
section_179_increase: Number(body.section_179_increase) || 0,
|
||||||
|
section_179_cost_factor: body.section_179_cost_factor === '' || body.section_179_cost_factor === null || body.section_179_cost_factor === undefined ? 1 : Number(body.section_179_cost_factor),
|
||||||
notes: body.notes || null,
|
notes: body.notes || null,
|
||||||
enabled: body.enabled === false ? 0 : 1
|
enabled: body.enabled === false ? 0 : 1
|
||||||
};
|
};
|
||||||
@@ -72,9 +76,9 @@ function createZone(body, userId) {
|
|||||||
}
|
}
|
||||||
const ts = now();
|
const ts = now();
|
||||||
run(
|
run(
|
||||||
`INSERT INTO depreciation_zones (code, name, allowance_percent, pis_start, pis_end, real_property_end, max_recovery_years, leasehold_life_years, notes, enabled, created_at, updated_at)
|
`INSERT INTO depreciation_zones (code, name, allowance_percent, pis_start, pis_end, real_property_end, max_recovery_years, leasehold_life_years, section_179_increase, section_179_cost_factor, notes, enabled, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[code, input.name, input.allowance_percent, input.pis_start, input.pis_end, input.real_property_end, input.max_recovery_years, input.leasehold_life_years, input.notes, input.enabled, ts, ts]
|
[code, input.name, input.allowance_percent, input.pis_start, input.pis_end, input.real_property_end, input.max_recovery_years, input.leasehold_life_years, input.section_179_increase, input.section_179_cost_factor, input.notes, input.enabled, ts, ts]
|
||||||
);
|
);
|
||||||
clearCache();
|
clearCache();
|
||||||
audit(userId, 'create', 'depreciation_zone', code, null, input);
|
audit(userId, 'create', 'depreciation_zone', code, null, input);
|
||||||
@@ -87,8 +91,8 @@ function updateZone(code, body, userId) {
|
|||||||
const input = normalize({ ...before, ...body, enabled: body.enabled === undefined ? Boolean(before.enabled) : body.enabled });
|
const input = normalize({ ...before, ...body, enabled: body.enabled === undefined ? Boolean(before.enabled) : body.enabled });
|
||||||
run(
|
run(
|
||||||
`UPDATE depreciation_zones SET name = ?, allowance_percent = ?, pis_start = ?, pis_end = ?, real_property_end = ?,
|
`UPDATE depreciation_zones SET name = ?, allowance_percent = ?, pis_start = ?, pis_end = ?, real_property_end = ?,
|
||||||
max_recovery_years = ?, leasehold_life_years = ?, notes = ?, enabled = ?, updated_at = ? WHERE code = ?`,
|
max_recovery_years = ?, leasehold_life_years = ?, section_179_increase = ?, section_179_cost_factor = ?, notes = ?, enabled = ?, updated_at = ? WHERE code = ?`,
|
||||||
[input.name, input.allowance_percent, input.pis_start, input.pis_end, input.real_property_end, input.max_recovery_years, input.leasehold_life_years, input.notes, input.enabled, now(), code]
|
[input.name, input.allowance_percent, input.pis_start, input.pis_end, input.real_property_end, input.max_recovery_years, input.leasehold_life_years, input.section_179_increase, input.section_179_cost_factor, input.notes, input.enabled, now(), code]
|
||||||
);
|
);
|
||||||
clearCache();
|
clearCache();
|
||||||
audit(userId, 'update', 'depreciation_zone', code, before, input);
|
audit(userId, 'update', 'depreciation_zone', code, before, input);
|
||||||
|
|||||||
@@ -911,6 +911,44 @@ async function main() {
|
|||||||
.schedules.filter((r) => r.book === 'FEDERAL')[0].depreciation);
|
.schedules.filter((r) => r.book === 'FEDERAL')[0].depreciation);
|
||||||
if (outYear1 !== 2000) throw new Error(`Out-of-window asset should get plain MACRS year-1 (2000), got ${outYear1}`);
|
if (outYear1 !== 2000) throw new Error(`Out-of-window asset should get plain MACRS year-1 (2000), got ${outYear1}`);
|
||||||
|
|
||||||
|
// Section 179 limits + Enterprise Zone: the seeded 2015 limit ($500,000) caps a plain asset's
|
||||||
|
// §179, but an Enterprise Zone asset gets the +$35,000 increase, letting a larger §179 through.
|
||||||
|
const limitList = await request('/api/section-179-limits', { headers: auth });
|
||||||
|
if (!limitList.limits.some((l) => l.tax_year === 2015 && l.base_limit === 500000)) {
|
||||||
|
throw new Error('2015 §179 limit was not seeded');
|
||||||
|
}
|
||||||
|
const ezList = await request('/api/depreciation-zones', { headers: auth });
|
||||||
|
const ez = ezList.zones.find((z) => z.code === 'enterprise_zone');
|
||||||
|
if (!ez || ez.section_179_increase !== 35000 || ez.section_179_cost_factor !== 0.5) {
|
||||||
|
throw new Error('Enterprise Zone was not seeded with the §179 increase/cost factor');
|
||||||
|
}
|
||||||
|
// Plain asset (no zone): §179 entered 520k is capped at the 500k base limit. Straight-line, 5yr,
|
||||||
|
// half-year → basis 100k, year-1 SL 10k → year-1 = 500000 + 10000 = 510000.
|
||||||
|
const s179Plain = await request('/api/assets', {
|
||||||
|
method: 'POST', headers: auth,
|
||||||
|
body: JSON.stringify({
|
||||||
|
asset_id: `S179-${suffix}`, description: '§179 capped', category: 'Computer Equipment',
|
||||||
|
acquisition_cost: 600000, in_service_date: '2015-01-01', useful_life_months: 60,
|
||||||
|
books: [{ book_type: 'FEDERAL', active: true, depreciation_method: 'straight_line', convention: 'half_year', useful_life_months: 60, section_179_amount: 520000, cost: 600000 }]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const plainY1 = round((await request(`/api/assets/${s179Plain.asset.id}/depreciation?startYear=2015&endYear=2015`, { headers: auth }))
|
||||||
|
.schedules.filter((r) => r.book === 'FEDERAL')[0].depreciation);
|
||||||
|
if (plainY1 !== 510000) throw new Error(`§179 should cap at the 500k base limit (year-1 510000), got ${plainY1}`);
|
||||||
|
// Enterprise Zone asset: limit 500k + 35k = 535k, so the full 520k §179 applies. basis 80k,
|
||||||
|
// year-1 SL 8k → year-1 = 520000 + 8000 = 528000.
|
||||||
|
const s179Ez = await request('/api/assets', {
|
||||||
|
method: 'POST', headers: auth,
|
||||||
|
body: JSON.stringify({
|
||||||
|
asset_id: `S179EZ-${suffix}`, description: '§179 enterprise zone', category: 'Computer Equipment',
|
||||||
|
acquisition_cost: 600000, in_service_date: '2015-01-01', useful_life_months: 60, special_zone: 'enterprise_zone',
|
||||||
|
books: [{ book_type: 'FEDERAL', active: true, depreciation_method: 'straight_line', convention: 'half_year', useful_life_months: 60, section_179_amount: 520000, cost: 600000 }]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const ezY1 = round((await request(`/api/assets/${s179Ez.asset.id}/depreciation?startYear=2015&endYear=2015`, { headers: auth }))
|
||||||
|
.schedules.filter((r) => r.book === 'FEDERAL')[0].depreciation);
|
||||||
|
if (ezY1 !== 528000) throw new Error(`Enterprise Zone §179 increase should allow 520k (year-1 528000), got ${ezY1}`);
|
||||||
|
|
||||||
// Disposal: preview gain/loss, then record a full disposal.
|
// Disposal: preview gain/loss, then record a full disposal.
|
||||||
const disposalAsset = await request('/api/assets', {
|
const disposalAsset = await request('/api/assets', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -748,10 +748,18 @@ export default {
|
|||||||
if (!zone) return '';
|
if (!zone) return '';
|
||||||
const pis = this.localAsset.in_service_date || this.localAsset.acquired_date || '';
|
const pis = this.localAsset.in_service_date || this.localAsset.acquired_date || '';
|
||||||
const inWindow = (!zone.pis_start || !pis || pis >= zone.pis_start) && (!zone.pis_end || !pis || pis <= zone.pis_end);
|
const inWindow = (!zone.pis_start || !pis || pis >= zone.pis_start) && (!zone.pis_end || !pis || pis <= zone.pis_end);
|
||||||
const base = `${zone.name}: ${zone.allowance_percent}% §168 allowance applied to the FEDERAL book`
|
const benefits = [];
|
||||||
+ `${zone.pis_start || zone.pis_end ? ` for assets placed in service ${zone.pis_start || '…'} to ${zone.pis_end || '…'}` : ''}.`;
|
if (Number(zone.allowance_percent) > 0) benefits.push(`a ${zone.allowance_percent}% §168 special allowance`);
|
||||||
|
if (Number(zone.section_179_increase) > 0) {
|
||||||
|
const factor = Number(zone.section_179_cost_factor);
|
||||||
|
const phaseout = factor && factor !== 1 ? ` (only ${Math.round(factor * 100)}% of cost counts toward the §179 phaseout)` : '';
|
||||||
|
benefits.push(`an increased §179 limit of +$${Number(zone.section_179_increase).toLocaleString()}${phaseout}`);
|
||||||
|
}
|
||||||
|
if (!benefits.length) benefits.push('special depreciation treatment');
|
||||||
|
const window = zone.pis_start || zone.pis_end ? ` for assets placed in service ${zone.pis_start || '…'} to ${zone.pis_end || '…'}` : '';
|
||||||
|
const base = `${zone.name}: ${benefits.join(' and ')} on the FEDERAL book${window}.`;
|
||||||
const lease = zone.leasehold_life_years ? ` Qualified leasehold improvements use a ${zone.leasehold_life_years}-year life.` : '';
|
const lease = zone.leasehold_life_years ? ` Qualified leasehold improvements use a ${zone.leasehold_life_years}-year life.` : '';
|
||||||
if (pis && !inWindow) return `${base} This asset's placed-in-service date is OUTSIDE the window, so the allowance will not apply.${lease}`;
|
if (pis && !inWindow) return `${base} This asset's placed-in-service date is OUTSIDE the window, so it will not apply.${lease}`;
|
||||||
return `${base}${lease}`;
|
return `${base}${lease}`;
|
||||||
},
|
},
|
||||||
changedCriticalLabels() {
|
changedCriticalLabels() {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="section-title">Depreciation zones</h2>
|
<h2 class="section-title">Depreciation zones</h2>
|
||||||
<p class="quiet text-body-2">
|
<p class="quiet text-body-2">
|
||||||
Special-allowance zones (e.g. New York Liberty Zone). Assign one to an asset and the engine applies the zone’s §168
|
Special-allowance zones (e.g. New York Liberty Zone, Enterprise/Empowerment Zone). Assign one to an asset and the engine
|
||||||
allowance to the federal book for assets placed in service within the date window.
|
applies the zone’s §168 allowance and/or increased §179 limit to the federal book for assets placed in service within the
|
||||||
|
date window.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
<v-chip v-if="!row.enabled" size="x-small" variant="tonal" class="ml-2">Disabled</v-chip>
|
<v-chip v-if="!row.enabled" size="x-small" variant="tonal" class="ml-2">Disabled</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<template #cell-allowance_percent="{ row }">{{ row.allowance_percent }}%</template>
|
<template #cell-allowance_percent="{ row }">{{ row.allowance_percent }}%</template>
|
||||||
|
<template #cell-section_179_increase="{ row }">{{ row.section_179_increase ? `+$${Number(row.section_179_increase).toLocaleString()}` : '—' }}</template>
|
||||||
<template #cell-window="{ row }">{{ row.pis_start || '—' }} → {{ row.pis_end || '—' }}</template>
|
<template #cell-window="{ row }">{{ row.pis_start || '—' }} → {{ row.pis_end || '—' }}</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<v-btn icon="mdi-pencil" size="small" variant="text" @click="openEdit(row)" />
|
<v-btn icon="mdi-pencil" size="small" variant="text" @click="openEdit(row)" />
|
||||||
@@ -49,6 +51,8 @@
|
|||||||
<v-text-field v-model="form.real_property_end" type="date" label="Real-property PIS end (optional)" />
|
<v-text-field v-model="form.real_property_end" type="date" label="Real-property PIS end (optional)" />
|
||||||
<v-text-field v-model.number="form.max_recovery_years" type="number" label="Max recovery years (optional)" />
|
<v-text-field v-model.number="form.max_recovery_years" type="number" label="Max recovery years (optional)" />
|
||||||
<v-text-field v-model.number="form.leasehold_life_years" type="number" label="Leasehold improvement life (yrs, optional)" />
|
<v-text-field v-model.number="form.leasehold_life_years" type="number" label="Leasehold improvement life (yrs, optional)" />
|
||||||
|
<v-text-field v-model.number="form.section_179_increase" type="number" label="§179 limit increase ($)" prefix="$" hint="Enterprise/empowerment zones, e.g. 35000" persistent-hint />
|
||||||
|
<v-text-field v-model.number="form.section_179_cost_factor" type="number" step="0.01" label="§179 cost factor for phaseout" hint="Share of cost counted toward the threshold (e.g. 0.5)" persistent-hint />
|
||||||
</div>
|
</div>
|
||||||
<v-textarea v-model="form.notes" class="mt-2" label="Notes" rows="3" />
|
<v-textarea v-model="form.notes" class="mt-2" label="Notes" rows="3" />
|
||||||
<v-alert v-if="dialogError" type="error" density="compact" class="mt-2">{{ dialogError }}</v-alert>
|
<v-alert v-if="dialogError" type="error" density="compact" class="mt-2">{{ dialogError }}</v-alert>
|
||||||
@@ -87,7 +91,8 @@ import { apiRequest } from '../services/api';
|
|||||||
function blankForm() {
|
function blankForm() {
|
||||||
return {
|
return {
|
||||||
exists: false, code: '', name: '', allowance_percent: 0, enabled: true,
|
exists: false, code: '', name: '', allowance_percent: 0, enabled: true,
|
||||||
pis_start: '', pis_end: '', real_property_end: '', max_recovery_years: null, leasehold_life_years: null, notes: ''
|
pis_start: '', pis_end: '', real_property_end: '', max_recovery_years: null, leasehold_life_years: null,
|
||||||
|
section_179_increase: 0, section_179_cost_factor: 1, notes: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +114,8 @@ export default {
|
|||||||
dialogError: '',
|
dialogError: '',
|
||||||
columns: [
|
columns: [
|
||||||
{ key: 'name', label: 'Zone' },
|
{ key: 'name', label: 'Zone' },
|
||||||
{ key: 'allowance_percent', label: 'Allowance' },
|
{ key: 'allowance_percent', label: '§168 allowance' },
|
||||||
|
{ key: 'section_179_increase', label: '§179 increase', sortable: false },
|
||||||
{ key: 'window', label: 'Placed-in-service window', sortable: false },
|
{ key: 'window', label: 'Placed-in-service window', sortable: false },
|
||||||
{ key: 'asset_count', label: 'Assets', format: 'number' }
|
{ key: 'asset_count', label: 'Assets', format: 'number' }
|
||||||
]
|
]
|
||||||
@@ -140,7 +146,10 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
exists: true, code: row.code, name: row.name, allowance_percent: row.allowance_percent, enabled: row.enabled,
|
exists: true, code: row.code, name: row.name, allowance_percent: row.allowance_percent, enabled: row.enabled,
|
||||||
pis_start: row.pis_start || '', pis_end: row.pis_end || '', real_property_end: row.real_property_end || '',
|
pis_start: row.pis_start || '', pis_end: row.pis_end || '', real_property_end: row.real_property_end || '',
|
||||||
max_recovery_years: row.max_recovery_years, leasehold_life_years: row.leasehold_life_years, notes: row.notes || ''
|
max_recovery_years: row.max_recovery_years, leasehold_life_years: row.leasehold_life_years,
|
||||||
|
section_179_increase: row.section_179_increase || 0,
|
||||||
|
section_179_cost_factor: row.section_179_cost_factor === null || row.section_179_cost_factor === undefined ? 1 : row.section_179_cost_factor,
|
||||||
|
notes: row.notes || ''
|
||||||
};
|
};
|
||||||
this.dialogError = '';
|
this.dialogError = '';
|
||||||
this.dialog = true;
|
this.dialog = true;
|
||||||
|
|||||||
162
src/components/Section179LimitSettings.vue
Normal file
162
src/components/Section179LimitSettings.vue
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="span-12 panel-pad">
|
||||||
|
<div class="toolbar-row mb-3">
|
||||||
|
<div>
|
||||||
|
<h2 class="section-title">Section 179 limits</h2>
|
||||||
|
<p class="quiet text-body-2">
|
||||||
|
Annual §179 deduction limits and investment-phaseout thresholds. The engine caps each asset’s §179 deduction at the
|
||||||
|
applicable year’s limit (less any dollar-for-dollar phaseout). Enterprise/empowerment-zone assets get the increased limit
|
||||||
|
configured on their depreciation zone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn color="primary" prepend-icon="mdi-plus" @click="openNew">New year</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
:columns="columns"
|
||||||
|
:rows="limits"
|
||||||
|
row-key="tax_year"
|
||||||
|
:page-size="10"
|
||||||
|
has-actions
|
||||||
|
empty-text="No §179 limits configured — deductions are uncapped."
|
||||||
|
>
|
||||||
|
<template #cell-base_limit="{ row }">${{ Number(row.base_limit).toLocaleString() }}</template>
|
||||||
|
<template #cell-phaseout_threshold="{ row }">${{ Number(row.phaseout_threshold).toLocaleString() }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<v-btn icon="mdi-pencil" size="small" variant="text" @click="openEdit(row)" />
|
||||||
|
<v-btn icon="mdi-delete-outline" size="small" variant="text" color="error" @click="confirmDelete(row)" />
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
<v-alert v-if="error" type="error" density="compact" class="mt-3">{{ error }}</v-alert>
|
||||||
|
|
||||||
|
<v-dialog v-model="dialog" max-width="480">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>{{ form.exists ? `Edit ${form.tax_year}` : 'New §179 limit year' }}</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="form-grid">
|
||||||
|
<v-text-field v-model.number="form.tax_year" type="number" label="Tax year *" :disabled="form.exists" />
|
||||||
|
<v-text-field v-model.number="form.base_limit" type="number" label="Base deduction limit ($)" prefix="$" />
|
||||||
|
<v-text-field v-model.number="form.phaseout_threshold" type="number" label="Investment phaseout threshold ($)" prefix="$" />
|
||||||
|
</div>
|
||||||
|
<v-alert v-if="dialogError" type="error" density="compact" class="mt-2">{{ dialogError }}</v-alert>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn variant="text" @click="dialog = false">Cancel</v-btn>
|
||||||
|
<v-btn color="primary" prepend-icon="mdi-content-save" :loading="saving" :disabled="!form.tax_year" @click="save">Save</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-dialog v-model="deleteDialog" max-width="440">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-error">Delete limit</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p>Remove the §179 limit for <strong>{{ deleteTarget?.tax_year }}</strong>? Assets placed in service that year fall back to
|
||||||
|
the nearest earlier configured year (or become uncapped).</p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn variant="text" @click="deleteDialog = false">Cancel</v-btn>
|
||||||
|
<v-btn color="error" prepend-icon="mdi-delete-outline" @click="performDelete">Delete</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DataTable from './DataTable.vue';
|
||||||
|
import { apiRequest } from '../services/api';
|
||||||
|
|
||||||
|
function blankForm() {
|
||||||
|
return { exists: false, tax_year: new Date().getFullYear(), base_limit: 0, phaseout_threshold: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { DataTable },
|
||||||
|
props: {
|
||||||
|
token: { type: String, required: true }
|
||||||
|
},
|
||||||
|
emits: ['updated'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
limits: [],
|
||||||
|
dialog: false,
|
||||||
|
deleteDialog: false,
|
||||||
|
deleteTarget: null,
|
||||||
|
form: blankForm(),
|
||||||
|
saving: false,
|
||||||
|
error: '',
|
||||||
|
dialogError: '',
|
||||||
|
columns: [
|
||||||
|
{ key: 'tax_year', label: 'Tax year' },
|
||||||
|
{ key: 'base_limit', label: 'Base deduction limit' },
|
||||||
|
{ key: 'phaseout_threshold', label: 'Phaseout threshold' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async api(path, options = {}) {
|
||||||
|
return apiRequest(path, this.token, options);
|
||||||
|
},
|
||||||
|
async load() {
|
||||||
|
this.error = '';
|
||||||
|
try {
|
||||||
|
const data = await (await this.api('/api/section-179-limits')).json();
|
||||||
|
this.limits = data.limits;
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error.message;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openNew() {
|
||||||
|
this.form = blankForm();
|
||||||
|
this.dialogError = '';
|
||||||
|
this.dialog = true;
|
||||||
|
},
|
||||||
|
openEdit(row) {
|
||||||
|
this.form = { exists: true, tax_year: row.tax_year, base_limit: row.base_limit, phaseout_threshold: row.phaseout_threshold };
|
||||||
|
this.dialogError = '';
|
||||||
|
this.dialog = true;
|
||||||
|
},
|
||||||
|
async save() {
|
||||||
|
if (!this.form.tax_year) return;
|
||||||
|
this.saving = true;
|
||||||
|
this.dialogError = '';
|
||||||
|
try {
|
||||||
|
const method = this.form.exists ? 'PUT' : 'POST';
|
||||||
|
const url = this.form.exists ? `/api/section-179-limits/${this.form.tax_year}` : '/api/section-179-limits';
|
||||||
|
await this.api(url, { method, body: JSON.stringify(this.form) });
|
||||||
|
this.dialog = false;
|
||||||
|
await this.load();
|
||||||
|
this.$emit('updated');
|
||||||
|
} catch (error) {
|
||||||
|
this.dialogError = error.message;
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmDelete(row) {
|
||||||
|
this.deleteTarget = row;
|
||||||
|
this.deleteDialog = true;
|
||||||
|
},
|
||||||
|
async performDelete() {
|
||||||
|
const target = this.deleteTarget;
|
||||||
|
this.deleteDialog = false;
|
||||||
|
this.deleteTarget = null;
|
||||||
|
if (!target) return;
|
||||||
|
try {
|
||||||
|
await this.api(`/api/section-179-limits/${target.tax_year}`, { method: 'DELETE' });
|
||||||
|
await this.load();
|
||||||
|
this.$emit('updated');
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -291,6 +291,17 @@
|
|||||||
and any leasehold-improvement life. Zones are managed in <strong>Admin → Application Configuration → Depreciation zones</strong>
|
and any leasehold-improvement life. Zones are managed in <strong>Admin → Application Configuration → Depreciation zones</strong>
|
||||||
(create your own, set the allowance %, date window, and notes).
|
(create your own, set the allowance %, date window, and notes).
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
A zone can instead (or also) carry an <strong>increased §179 limit</strong> — this is how
|
||||||
|
<strong>Enterprise / Empowerment Zone</strong> property is handled. Set the zone’s <em>§179 limit increase</em>
|
||||||
|
(e.g. +$35,000 for 2002–2020) and its <em>§179 cost factor</em> (e.g. 0.5, meaning only 50% of the property’s cost counts
|
||||||
|
toward the investment-phaseout threshold). When such a zone is assigned to an asset placed in service within the window,
|
||||||
|
the federal book’s §179 deduction may exceed the standard annual limit by that increase. The base annual §179 deduction
|
||||||
|
limits and phaseout thresholds themselves are maintained in
|
||||||
|
<strong>Admin → Application Configuration → Section 179 limits</strong>; the engine caps each asset’s §179 deduction at the
|
||||||
|
applicable year’s limit (less any dollar-for-dollar phaseout). If no limit is configured for an asset’s placed-in-service
|
||||||
|
year, the entered §179 amount is left uncapped.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3>The PM (maintenance) book</h3>
|
<h3>The PM (maintenance) book</h3>
|
||||||
<p>
|
<p>
|
||||||
@@ -620,7 +631,7 @@
|
|||||||
<h2>Administration</h2>
|
<h2>Administration</h2>
|
||||||
<p>
|
<p>
|
||||||
<strong>Admin</strong> (admin role) is where the application is configured. It is split into three sub-screens in the
|
<strong>Admin</strong> (admin role) is where the application is configured. It is split into three sub-screens in the
|
||||||
navigation rail: <strong>Users & Teams</strong>, <strong>Application Configuration</strong> (application settings, asset classes, depreciation zones, asset ID templates, asset categories,
|
navigation rail: <strong>Users & Teams</strong>, <strong>Application Configuration</strong> (application settings, asset classes, depreciation zones, Section 179 limits, asset ID templates, asset categories,
|
||||||
asset departments, PM categories, parts categories, maintenance defaults, tax rule sets), and <strong>Integrations</strong> (email, webhook, ServiceNow, Workday). Each is described below.
|
asset departments, PM categories, parts categories, maintenance defaults, tax rule sets), and <strong>Integrations</strong> (email, webhook, ServiceNow, Workday). Each is described below.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,8 @@
|
|||||||
|
|
||||||
<DepreciationZoneSettings :token="token" />
|
<DepreciationZoneSettings :token="token" />
|
||||||
|
|
||||||
|
<Section179LimitSettings :token="token" />
|
||||||
|
|
||||||
<AssetIdTemplateSettings :token="token" />
|
<AssetIdTemplateSettings :token="token" />
|
||||||
|
|
||||||
<CategorySettings :token="token" @updated="$emit('categories-updated')" />
|
<CategorySettings :token="token" @updated="$emit('categories-updated')" />
|
||||||
@@ -280,6 +282,7 @@ import CategorySettings from '../components/CategorySettings.vue';
|
|||||||
import DataTable from '../components/DataTable.vue';
|
import DataTable from '../components/DataTable.vue';
|
||||||
import DepartmentSettings from '../components/DepartmentSettings.vue';
|
import DepartmentSettings from '../components/DepartmentSettings.vue';
|
||||||
import DepreciationZoneSettings from '../components/DepreciationZoneSettings.vue';
|
import DepreciationZoneSettings from '../components/DepreciationZoneSettings.vue';
|
||||||
|
import Section179LimitSettings from '../components/Section179LimitSettings.vue';
|
||||||
import NotificationSettings from '../components/NotificationSettings.vue';
|
import NotificationSettings from '../components/NotificationSettings.vue';
|
||||||
import PartCategorySettings from '../components/PartCategorySettings.vue';
|
import PartCategorySettings from '../components/PartCategorySettings.vue';
|
||||||
import PmCategorySettings from '../components/PmCategorySettings.vue';
|
import PmCategorySettings from '../components/PmCategorySettings.vue';
|
||||||
@@ -288,7 +291,7 @@ import ServiceNowSettings from '../components/ServiceNowSettings.vue';
|
|||||||
import WebhookSettings from '../components/WebhookSettings.vue';
|
import WebhookSettings from '../components/WebhookSettings.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AssetClassSettings, AssetIdTemplateSettings, CategorySettings, DataTable, DepartmentSettings, DepreciationZoneSettings, NotificationSettings, PartCategorySettings, PmCategorySettings, PmSettings, ServiceNowSettings, WebhookSettings },
|
components: { AssetClassSettings, AssetIdTemplateSettings, CategorySettings, DataTable, DepartmentSettings, DepreciationZoneSettings, NotificationSettings, PartCategorySettings, PmCategorySettings, PmSettings, Section179LimitSettings, ServiceNowSettings, WebhookSettings },
|
||||||
props: {
|
props: {
|
||||||
token: { type: String, default: '' },
|
token: { type: String, default: '' },
|
||||||
section: { type: String, default: 'admin-users' },
|
section: { type: String, default: 'admin-users' },
|
||||||
|
|||||||
Reference in New Issue
Block a user