Added Multi-Company Support
This commit is contained in:
@@ -19,35 +19,39 @@ function bookFromRow(row) {
|
||||
};
|
||||
}
|
||||
|
||||
function listBooks() {
|
||||
function listBooks(companyId) {
|
||||
const books = all(
|
||||
`SELECT b.*, t.name AS tax_rule_set_name, t.version AS tax_rule_set_version,
|
||||
(SELECT COUNT(*) FROM asset_books ab WHERE ab.book_type = b.code AND ab.active = 1) AS asset_count
|
||||
(SELECT COUNT(*) FROM asset_books ab JOIN assets a ON a.id = ab.asset_id
|
||||
WHERE ab.book_type = b.code AND ab.active = 1 AND a.entity_id = b.entity_id) AS asset_count
|
||||
FROM books b
|
||||
LEFT JOIN tax_rule_sets t ON t.id = b.tax_rule_set_id
|
||||
ORDER BY b.sort_order, b.id`
|
||||
WHERE (? IS NULL OR b.entity_id = ?)
|
||||
ORDER BY b.sort_order, b.id`,
|
||||
[companyId || null, companyId || null]
|
||||
).map(bookFromRow);
|
||||
const ruleSets = all('SELECT id, name, jurisdiction, version, active FROM tax_rule_sets ORDER BY active DESC, name')
|
||||
.map((row) => ({ ...row, active: Boolean(row.active) }));
|
||||
return { books, ruleSets };
|
||||
}
|
||||
|
||||
function getBook(code) {
|
||||
return bookFromRow(one('SELECT * FROM books WHERE code = ? OR id = ?', [code, code]) || null) || null;
|
||||
function getBook(code, companyId) {
|
||||
return bookFromRow(one('SELECT * FROM books WHERE (code = ? OR id = ?) AND (? IS NULL OR entity_id = ?)', [code, code, companyId || null, companyId || null]) || null) || null;
|
||||
}
|
||||
|
||||
function createBook(body, userId) {
|
||||
function createBook(body, userId, companyId) {
|
||||
const code = String(body.code || '').trim().toUpperCase().replace(/[^A-Z0-9_]/g, '').slice(0, 20);
|
||||
if (!code) throw Object.assign(new Error('Book code is required'), { status: 400 });
|
||||
if (one('SELECT id FROM books WHERE code = ?', [code])) {
|
||||
if (one('SELECT id FROM books WHERE code = ? AND entity_id = ?', [code, companyId])) {
|
||||
throw Object.assign(new Error(`A book with code "${code}" already exists`), { status: 400 });
|
||||
}
|
||||
const ts = now();
|
||||
const next = one('SELECT MAX(sort_order) AS max FROM books');
|
||||
const next = one('SELECT MAX(sort_order) AS max FROM books WHERE entity_id = ?', [companyId]);
|
||||
const result = run(
|
||||
`INSERT INTO books (code, name, description, book_type, enabled, is_primary, tax_rule_set_id, default_method, default_convention, fiscal_year_end_month, sort_order, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
`INSERT INTO books (entity_id, code, name, description, book_type, enabled, is_primary, tax_rule_set_id, default_method, default_convention, fiscal_year_end_month, sort_order, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
companyId,
|
||||
code,
|
||||
body.name || code,
|
||||
body.description || null,
|
||||
@@ -65,20 +69,20 @@ function createBook(body, userId) {
|
||||
return bookFromRow(one('SELECT * FROM books WHERE id = ?', [result.lastInsertRowid]));
|
||||
}
|
||||
|
||||
function deleteBook(id, userId) {
|
||||
const before = one('SELECT * FROM books WHERE id = ?', [id]);
|
||||
function deleteBook(id, userId, companyId) {
|
||||
const before = one('SELECT * FROM books WHERE id = ? AND (? IS NULL OR entity_id = ?)', [id, companyId || null, companyId || null]);
|
||||
if (!before) return false;
|
||||
if (before.is_primary) throw Object.assign(new Error('The primary book cannot be deleted'), { status: 400 });
|
||||
if (before.book_type === 'maintenance') throw Object.assign(new Error('The PM book cannot be deleted'), { status: 400 });
|
||||
const used = one('SELECT COUNT(*) AS count FROM asset_books WHERE book_type = ?', [before.code]).count;
|
||||
const used = one('SELECT COUNT(*) AS count FROM asset_books ab JOIN assets a ON a.id = ab.asset_id WHERE ab.book_type = ? AND a.entity_id = ?', [before.code, before.entity_id]).count;
|
||||
if (used) throw Object.assign(new Error(`This book is used by ${used} asset record(s); disable it instead`), { status: 400 });
|
||||
run('DELETE FROM books WHERE id = ?', [id]);
|
||||
audit(userId, 'delete', 'book', id, before, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateBook(id, body, userId) {
|
||||
const before = one('SELECT * FROM books WHERE id = ?', [id]);
|
||||
function updateBook(id, body, userId, companyId) {
|
||||
const before = one('SELECT * FROM books WHERE id = ? AND (? IS NULL OR entity_id = ?)', [id, companyId || null, companyId || null]);
|
||||
if (!before) return null;
|
||||
run(
|
||||
`UPDATE books SET
|
||||
@@ -104,11 +108,11 @@ function updateBook(id, body, userId) {
|
||||
|
||||
// General-ledger view of a book: per-asset basis, accumulated depreciation and
|
||||
// current-year expense rolled up to GL accounts, plus an asset-level detail.
|
||||
function bookLedger(code, year) {
|
||||
const book = getBook(code);
|
||||
function bookLedger(code, year, companyId) {
|
||||
const book = getBook(code, companyId);
|
||||
if (!book) return null;
|
||||
if (book.book_type === 'maintenance') {
|
||||
return { ...pmBookLedger(year), book };
|
||||
return { ...pmBookLedger(year, companyId), book };
|
||||
}
|
||||
const rules = ruleSetForBook(code);
|
||||
const rows = all(
|
||||
@@ -116,9 +120,9 @@ function bookLedger(code, year) {
|
||||
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
|
||||
WHERE b.book_type = ? AND b.active = 1 AND (? IS NULL OR a.entity_id = ?)
|
||||
ORDER BY a.asset_id`,
|
||||
[code]
|
||||
[code, companyId || null, companyId || null]
|
||||
);
|
||||
|
||||
const accountMap = new Map();
|
||||
@@ -195,8 +199,8 @@ function bookLedger(code, year) {
|
||||
}
|
||||
|
||||
// Shape the ledger account rollup as a generic report for PDF/XLSX/CSV export.
|
||||
function ledgerReport(code, year) {
|
||||
const ledger = bookLedger(code, year);
|
||||
function ledgerReport(code, year, companyId) {
|
||||
const ledger = bookLedger(code, year, companyId);
|
||||
if (!ledger) return null;
|
||||
return {
|
||||
title: `${ledger.book.name} general ledger — ${year}`,
|
||||
|
||||
Reference in New Issue
Block a user