Updated GL system allows changing account defaults and add/updating journal entries

This commit is contained in:
2026-06-08 11:43:02 -05:00
parent 582720bf01
commit 15e93c1e45
16 changed files with 980 additions and 495 deletions

View File

@@ -438,6 +438,31 @@ function initialize() {
CREATE INDEX IF NOT EXISTS idx_gl_entries_scope ON gl_entries (entity_id, book_code, fiscal_year);
CREATE INDEX IF NOT EXISTS idx_gl_entries_extid ON gl_entries (entity_id, book_code, external_id);
CREATE TABLE IF NOT EXISTS gl_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
entity_id INTEGER REFERENCES entities(id),
code TEXT NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'asset',
normal_balance TEXT NOT NULL DEFAULT 'debit',
description TEXT,
active INTEGER NOT NULL DEFAULT 1,
created_by INTEGER REFERENCES users(id),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(entity_id, code)
);
CREATE TABLE IF NOT EXISTS gl_account_defaults (
entity_id INTEGER PRIMARY KEY REFERENCES entities(id) ON DELETE CASCADE,
asset_account TEXT,
accumulated_account TEXT,
expense_account TEXT,
pm_expense_account TEXT,
pm_clearing_account TEXT,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
@@ -957,6 +982,8 @@ function backfillCompanyScope() {
if (!one('SELECT 1 AS x FROM user_companies LIMIT 1')) {
run('INSERT OR IGNORE INTO user_companies (user_id, entity_id) SELECT u.id, e.id FROM users u CROSS JOIN entities e');
}
// Seed a starter chart of accounts + GL posting defaults for any existing company that lacks one.
for (const company of all('SELECT id FROM entities')) seedGlAccountsForCompany(company.id);
}
// Custom roles require the legacy CHECK(role IN (...)) constraint on `users` to be removed.
@@ -1041,6 +1068,33 @@ function seedReferenceValuesForCompany(entityId) {
}
}
// Seed a starter chart of accounts and the GL posting defaults for a company (idempotent per company).
// The defaults are the fallback accounts the depreciation/PM ledgers post to when an asset doesn't
// carry its own. Called during initial seed and when a new company is created.
function seedGlAccountsForCompany(entityId) {
if (!entityId || one('SELECT id FROM gl_accounts WHERE entity_id = ? LIMIT 1', [entityId])) return;
const ts = now();
const accounts = [
['1500', 'Fixed Assets', 'asset', 'debit', 'Asset cost / basis'],
['1590', 'Accumulated Depreciation', 'contra_asset', 'credit', 'Accumulated depreciation (contra-asset)'],
['2000', 'Maintenance Clearing', 'liability', 'credit', 'PM/maintenance clearing'],
['6400', 'Depreciation Expense', 'expense', 'debit', 'Periodic depreciation expense'],
['6450', 'Maintenance Expense', 'expense', 'debit', 'Preventative-maintenance spend']
];
for (const [code, name, type, normal, description] of accounts) {
run(
`INSERT OR IGNORE INTO gl_accounts (entity_id, code, name, type, normal_balance, description, active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)`,
[entityId, code, name, type, normal, description, ts, ts]
);
}
run(
`INSERT OR IGNORE INTO gl_account_defaults (entity_id, asset_account, accumulated_account, expense_account, pm_expense_account, pm_clearing_account, updated_at)
VALUES (?, '1500', '1590', '6400', '6450', '2000', ?)`,
[entityId, ts]
);
}
function seed() {
const createdAt = now();
@@ -1170,7 +1224,10 @@ function seed() {
}
const refEntity = one('SELECT id FROM entities ORDER BY id LIMIT 1');
if (refEntity) seedReferenceValuesForCompany(refEntity.id);
if (refEntity) {
seedReferenceValuesForCompany(refEntity.id);
seedGlAccountsForCompany(refEntity.id);
}
run(
`INSERT OR IGNORE INTO employees (
@@ -1301,6 +1358,7 @@ module.exports = {
parseJson,
run,
seedBooksForCompany,
seedGlAccountsForCompany,
seedReferenceValuesForCompany,
tx
};