Added Section 179 Limits
This commit is contained in:
@@ -748,10 +748,18 @@ export default {
|
||||
if (!zone) return '';
|
||||
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 base = `${zone.name}: ${zone.allowance_percent}% §168 allowance applied to the FEDERAL book`
|
||||
+ `${zone.pis_start || zone.pis_end ? ` for assets placed in service ${zone.pis_start || '…'} to ${zone.pis_end || '…'}` : ''}.`;
|
||||
const benefits = [];
|
||||
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.` : '';
|
||||
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}`;
|
||||
},
|
||||
changedCriticalLabels() {
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
<div>
|
||||
<h2 class="section-title">Depreciation zones</h2>
|
||||
<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
|
||||
allowance to the federal book for assets placed in service within the date window.
|
||||
Special-allowance zones (e.g. New York Liberty Zone, Enterprise/Empowerment Zone). Assign one to an asset and the engine
|
||||
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>
|
||||
</div>
|
||||
<v-spacer />
|
||||
@@ -27,6 +28,7 @@
|
||||
<v-chip v-if="!row.enabled" size="x-small" variant="tonal" class="ml-2">Disabled</v-chip>
|
||||
</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 #actions="{ 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.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.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>
|
||||
<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>
|
||||
@@ -87,7 +91,8 @@ import { apiRequest } from '../services/api';
|
||||
function blankForm() {
|
||||
return {
|
||||
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: '',
|
||||
columns: [
|
||||
{ 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: 'asset_count', label: 'Assets', format: 'number' }
|
||||
]
|
||||
@@ -140,7 +146,10 @@ export default {
|
||||
this.form = {
|
||||
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 || '',
|
||||
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.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>
|
||||
(create your own, set the allowance %, date window, and notes).
|
||||
</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>
|
||||
<p>
|
||||
@@ -620,7 +631,7 @@
|
||||
<h2>Administration</h2>
|
||||
<p>
|
||||
<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.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
|
||||
<DepreciationZoneSettings :token="token" />
|
||||
|
||||
<Section179LimitSettings :token="token" />
|
||||
|
||||
<AssetIdTemplateSettings :token="token" />
|
||||
|
||||
<CategorySettings :token="token" @updated="$emit('categories-updated')" />
|
||||
@@ -280,6 +282,7 @@ import CategorySettings from '../components/CategorySettings.vue';
|
||||
import DataTable from '../components/DataTable.vue';
|
||||
import DepartmentSettings from '../components/DepartmentSettings.vue';
|
||||
import DepreciationZoneSettings from '../components/DepreciationZoneSettings.vue';
|
||||
import Section179LimitSettings from '../components/Section179LimitSettings.vue';
|
||||
import NotificationSettings from '../components/NotificationSettings.vue';
|
||||
import PartCategorySettings from '../components/PartCategorySettings.vue';
|
||||
import PmCategorySettings from '../components/PmCategorySettings.vue';
|
||||
@@ -288,7 +291,7 @@ import ServiceNowSettings from '../components/ServiceNowSettings.vue';
|
||||
import WebhookSettings from '../components/WebhookSettings.vue';
|
||||
|
||||
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: {
|
||||
token: { type: String, default: '' },
|
||||
section: { type: String, default: 'admin-users' },
|
||||
|
||||
Reference in New Issue
Block a user