Password Policy/App rename/PM Scheduling Improvements

This commit is contained in:
2026-06-06 02:32:47 -05:00
parent dfd999b6a9
commit f024286b5e
59 changed files with 3814 additions and 299 deletions

View File

@@ -4,8 +4,8 @@ const { DatabaseSync } = require('node:sqlite');
const bcrypt = require('bcryptjs');
const { expandRuleSet } = require('./rule-catalog');
const DATA_DIR = process.env.MIXEDASSETS_DATA_DIR || path.join(process.cwd(), 'data');
const DB_PATH = process.env.MIXEDASSETS_DB_PATH || path.join(DATA_DIR, 'mixedassets.sqlite');
const DATA_DIR = process.env.DEPRECORE_DATA_DIR || path.join(process.cwd(), 'data');
const DB_PATH = process.env.DEPRECORE_DB_PATH || path.join(DATA_DIR, 'deprecore.sqlite');
fs.mkdirSync(DATA_DIR, { recursive: true });
@@ -493,6 +493,39 @@ function initialize() {
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS pm_plan_meters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plan_id INTEGER NOT NULL REFERENCES pm_plans(id) ON DELETE CASCADE,
sort_order INTEGER NOT NULL DEFAULT 0,
label TEXT NOT NULL,
unit TEXT,
usage_interval REAL NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS asset_pm_meters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
asset_pm_id INTEGER NOT NULL REFERENCES asset_pm_plans(id) ON DELETE CASCADE,
sort_order INTEGER NOT NULL DEFAULT 0,
label TEXT NOT NULL,
unit TEXT,
usage_interval REAL NOT NULL DEFAULT 0,
baseline_reading REAL NOT NULL DEFAULT 0,
due_at_reading REAL,
last_reading REAL,
last_reading_date TEXT
);
CREATE TABLE IF NOT EXISTS pm_meter_readings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
asset_pm_meter_id INTEGER NOT NULL REFERENCES asset_pm_meters(id) ON DELETE CASCADE,
reading REAL NOT NULL,
reading_date TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'manual',
completion_id INTEGER REFERENCES pm_completions(id) ON DELETE SET NULL,
created_by INTEGER REFERENCES users(id),
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL DEFAULT 'other',
@@ -671,6 +704,13 @@ function initialize() {
after_json TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS password_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL
);
`);
migrate();
@@ -705,6 +745,19 @@ function migrate() {
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');
ensureColumn('depreciation_zones', 'section_179_threshold_increase', 'REAL NOT NULL DEFAULT 0');
ensureColumn('depreciation_zones', 'section_179_pis_start', 'TEXT');
ensureColumn('depreciation_zones', 'section_179_pis_end', 'TEXT');
// Dynamic PM scheduling: usage-meter & lifespan-curve signals layered on the calendar interval.
ensureColumn('pm_plans', 'lifespan_adjust', 'INTEGER NOT NULL DEFAULT 0');
ensureColumn('asset_pm_plans', 'lifespan_adjust', 'INTEGER NOT NULL DEFAULT 0');
ensureColumn('asset_pm_plans', 'schedule_mode', "TEXT NOT NULL DEFAULT 'earliest'");
// Password access control & account-lockout columns.
ensureColumn('users', 'password_changed_at', 'TEXT');
ensureColumn('users', 'failed_attempts', 'INTEGER NOT NULL DEFAULT 0');
ensureColumn('users', 'locked_until', 'TEXT');
ensureColumn('users', 'last_login_at', 'TEXT');
ensureColumn('users', 'must_change_password', 'INTEGER NOT NULL DEFAULT 0');
dropUsersRoleCheck();
}
@@ -782,9 +835,42 @@ function seed() {
[createdAt, createdAt]
);
// Qualified Gulf Opportunity (GO) Zone property: 50% §168 special allowance (placed in service
// 2005-08-28 through 2011-03-31, real property through 2010-12-31) AND an increased §179 limit of
// +$100,000 (2005-08-28 through 2008-12-31), with the investment-phaseout threshold raised by the
// lesser of $600,000 or the property's cost.
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, section_179_threshold_increase, section_179_pis_start, section_179_pis_end,
notes, enabled, created_at, updated_at)
VALUES ('go_zone', 'Qualified Gulf Opportunity (GO) Zone', 50, '2005-08-28', '2011-03-31', '2010-12-31', 20, 9,
100000, 1, 600000, '2005-08-28', '2008-12-31',
'50% special depreciation allowance for qualified GO Zone property (placed in service 2005-08-28 to 2011-03-31; real property to 2010-12-31). Increased §179 limit of +$100,000 (2005-08-28 to 2008-12-31), with the phaseout threshold raised by the lesser of $600,000 or the property''s cost. Qualified leasehold improvements use a 9-year life. Verify current tax law before filing.', 1, ?, ?)`,
[createdAt, createdAt]
);
// Qualified Recovery Assistance Property (Kansas Disaster Zone): 50% §168 special allowance (placed
// in service after 2007-05-04 through 2008-12-31; real property through 2009-12-31) AND an increased
// §179 limit of +$100,000 (2007-2008), with the phaseout threshold raised by the lesser of $600,000
// or the property's cost. The §168 and §179 windows coincide, so no separate §179 window is needed.
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, section_179_threshold_increase, section_179_pis_start, section_179_pis_end,
notes, enabled, created_at, updated_at)
VALUES ('kansas_disaster', 'Qualified Recovery Assistance (Kansas Disaster Zone)', 50, '2007-05-05', '2008-12-31', '2009-12-31', 20, NULL,
100000, 1, 600000, NULL, NULL,
'50% special depreciation allowance for qualified recovery assistance (Kansas Disaster Zone) property (placed in service 2007-05-05 to 2008-12-31; real property to 2009-12-31). Increased §179 limit of +$100,000 (2007-2008), with the phaseout threshold raised by the lesser of $600,000 or the property''s cost. 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 = [
[2003, 100000, 400000], [2004, 102000, 410000], [2005, 105000, 420000], [2006, 108000, 430000],
[2007, 125000, 500000], [2008, 250000, 800000], [2009, 250000, 800000], [2010, 500000, 2000000],
[2011, 500000, 2000000], [2012, 500000, 2000000], [2013, 500000, 2000000],
[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],
@@ -800,7 +886,7 @@ function seed() {
if (!one('SELECT id FROM teams LIMIT 1')) {
run('INSERT INTO teams (name, description, created_at) VALUES (?, ?, ?)', [
'Finance Operations',
'Default MixedAssets team',
'Default DepreCore team',
createdAt
]);
}
@@ -808,14 +894,15 @@ function seed() {
const team = one('SELECT id FROM teams WHERE name = ?', ['Finance Operations']);
if (!one('SELECT id FROM users LIMIT 1')) {
run(
'INSERT INTO users (team_id, name, email, password_hash, role, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
'INSERT INTO users (team_id, name, email, password_hash, role, password_changed_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[
team.id,
'MixedAssets Admin',
'admin@mixedassets.local',
bcrypt.hashSync(process.env.MIXEDASSETS_ADMIN_PASSWORD || 'ChangeMe123!', 12),
'DepreCore Admin',
'admin@deprecore.local',
bcrypt.hashSync(process.env.DEPRECORE_ADMIN_PASSWORD || 'ChangeMe123!', 12),
'admin',
createdAt,
createdAt,
createdAt
]
);
@@ -858,7 +945,7 @@ function seed() {
`INSERT OR IGNORE INTO employees (
employee_number, name, email, department, location, manager_name, status, source, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
['E-1001', 'Jordan Avery', 'jordan.avery@example.com', 'Operations', 'Headquarters', 'MixedAssets Admin', 'active', 'seed', createdAt, createdAt]
['E-1001', 'Jordan Avery', 'jordan.avery@example.com', 'Operations', 'Headquarters', 'DepreCore Admin', 'active', 'seed', createdAt, createdAt]
);
run(
@@ -946,8 +1033,8 @@ function seed() {
);
const settings = {
server_fqdn: process.env.MIXEDASSETS_SERVER_FQDN || 'localhost',
cors_allowed_domains: process.env.MIXEDASSETS_CORS_ORIGINS || 'http://localhost:5173',
server_fqdn: process.env.DEPRECORE_SERVER_FQDN || 'localhost',
cors_allowed_domains: process.env.DEPRECORE_CORS_ORIGINS || 'http://localhost:5173',
database_driver: 'node:sqlite',
database_path: DB_PATH,
default_books: 'GAAP,FEDERAL,STATE,AMT,ACE,USER',
@@ -959,7 +1046,7 @@ function seed() {
smtp_secure: 'false',
smtp_user: '',
smtp_password: '',
smtp_from: 'MixedAssets <no-reply@mixedassets.local>',
smtp_from: 'DepreCore <no-reply@deprecore.local>',
webhook_enabled: 'false',
webhook_url: '',
webhook_secret: '',