@fin.cx/skr
Version:
SKR03 and SKR04 German accounting standards for double-entry bookkeeping
377 lines • 29 kB
JavaScript
import * as plugins from './plugins.js';
import { getDb, closeDb } from './skr.database.js';
import { Account } from './skr.classes.account.js';
import { Transaction } from './skr.classes.transaction.js';
import { JournalEntry } from './skr.classes.journalentry.js';
import { SKR03_ACCOUNTS, SKR03_ACCOUNT_CLASSES } from './skr03.data.js';
import { SKR04_ACCOUNTS, SKR04_ACCOUNT_CLASSES } from './skr04.data.js';
export class ChartOfAccounts {
constructor(config) {
this.config = config;
this.initialized = false;
this.skrType = null;
this.logger = new plugins.smartlog.Smartlog({
logContext: {
company: 'fin.cx',
companyunit: 'skr',
containerName: 'ChartOfAccounts',
environment: 'local',
runtime: 'node',
zone: 'local',
},
});
this.logger.enableConsole();
}
/**
* Initialize the database connection
*/
async init() {
if (this.initialized) {
this.logger.log('info', 'ChartOfAccounts already initialized');
return;
}
if (!this.config) {
throw new Error('Database configuration required for initialization');
}
await getDb(this.config);
this.initialized = true;
this.logger.log('info', 'ChartOfAccounts initialized successfully');
}
/**
* Initialize SKR03 chart of accounts
*/
async initializeSKR03() {
await this.init();
this.logger.log('info', 'Initializing SKR03 chart of accounts');
// Check if SKR03 accounts already exist
const existingAccounts = await Account.getInstances({ skrType: 'SKR03' });
if (existingAccounts.length > 0) {
this.logger.log('info', `SKR03 already initialized with ${existingAccounts.length} accounts`);
this.skrType = 'SKR03';
return;
}
// Create all SKR03 accounts
const accounts = [];
for (const accountData of SKR03_ACCOUNTS) {
const account = await Account.createAccount(accountData);
accounts.push(account);
}
this.skrType = 'SKR03';
this.logger.log('info', `Successfully initialized SKR03 with ${accounts.length} accounts`);
}
/**
* Initialize SKR04 chart of accounts
*/
async initializeSKR04() {
await this.init();
this.logger.log('info', 'Initializing SKR04 chart of accounts');
// Check if SKR04 accounts already exist
const existingAccounts = await Account.getInstances({ skrType: 'SKR04' });
if (existingAccounts.length > 0) {
this.logger.log('info', `SKR04 already initialized with ${existingAccounts.length} accounts`);
this.skrType = 'SKR04';
return;
}
// Create all SKR04 accounts
const accounts = [];
for (const accountData of SKR04_ACCOUNTS) {
const account = await Account.createAccount(accountData);
accounts.push(account);
}
this.skrType = 'SKR04';
this.logger.log('info', `Successfully initialized SKR04 with ${accounts.length} accounts`);
}
/**
* Get the current SKR type
*/
getSKRType() {
return this.skrType;
}
/**
* Set the active SKR type
*/
setSKRType(skrType) {
this.skrType = skrType;
}
/**
* Get account by number
*/
async getAccountByNumber(accountNumber) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
return await Account.getAccountByNumber(accountNumber, this.skrType);
}
/**
* Get accounts by class
*/
async getAccountsByClass(accountClass) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
return await Account.getAccountsByClass(accountClass, this.skrType);
}
/**
* Get accounts by type
*/
async getAccountsByType(accountType) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
return await Account.getAccountsByType(accountType, this.skrType);
}
/**
* Create a custom account
*/
async createCustomAccount(accountData) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
// Ensure the account uses the current SKR type
const fullAccountData = {
accountNumber: accountData.accountNumber || '',
accountName: accountData.accountName || '',
accountClass: accountData.accountClass || 0,
accountType: accountData.accountType || 'asset',
skrType: this.skrType,
description: accountData.description,
vatRate: accountData.vatRate,
isActive: accountData.isActive !== undefined ? accountData.isActive : true,
};
// Validate account number doesn't already exist
const existing = await this.getAccountByNumber(fullAccountData.accountNumber);
if (existing) {
throw new Error(`Account ${fullAccountData.accountNumber} already exists`);
}
return await Account.createAccount(fullAccountData);
}
/**
* Update an existing account
*/
async updateAccount(accountNumber, updates) {
const account = await this.getAccountByNumber(accountNumber);
if (!account) {
throw new Error(`Account ${accountNumber} not found`);
}
// Apply updates
if (updates.accountName !== undefined)
account.accountName = updates.accountName;
if (updates.description !== undefined)
account.description = updates.description;
if (updates.vatRate !== undefined)
account.vatRate = updates.vatRate;
if (updates.isActive !== undefined)
account.isActive = updates.isActive;
account.updatedAt = new Date();
await account.save();
return account;
}
/**
* Delete a custom account (only non-system accounts)
*/
async deleteAccount(accountNumber) {
const account = await this.getAccountByNumber(accountNumber);
if (!account) {
throw new Error(`Account ${accountNumber} not found`);
}
if (account.isSystemAccount) {
throw new Error(`Cannot delete system account ${accountNumber}`);
}
// Check if account has transactions
const transactions = await Transaction.getTransactionsByAccount(accountNumber, account.skrType);
if (transactions.length > 0) {
throw new Error(`Cannot delete account ${accountNumber} with existing transactions`);
}
await account.delete();
}
/**
* Search accounts
*/
async searchAccounts(searchTerm) {
return await Account.searchAccounts(searchTerm, this.skrType);
}
/**
* Get all accounts
*/
async getAllAccounts(filter) {
const query = {};
if (this.skrType) {
query.skrType = this.skrType;
}
if (filter) {
if (filter.accountClass !== undefined)
query.accountClass = filter.accountClass;
if (filter.accountType !== undefined)
query.accountType = filter.accountType;
if (filter.isActive !== undefined)
query.isActive = filter.isActive;
}
const accounts = await Account.getInstances(query);
// Apply text search if provided
if (filter?.searchTerm) {
const lowerSearchTerm = filter.searchTerm.toLowerCase();
return accounts.filter((account) => account.accountNumber.includes(filter.searchTerm) ||
account.accountName.toLowerCase().includes(lowerSearchTerm) ||
account.description.toLowerCase().includes(lowerSearchTerm));
}
return accounts;
}
/**
* Post a simple transaction
*/
async postTransaction(transactionData) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
// Ensure the transaction uses the current SKR type
const fullTransactionData = {
...transactionData,
skrType: this.skrType,
};
return await Transaction.createTransaction(fullTransactionData);
}
/**
* Post a journal entry
*/
async postJournalEntry(journalData) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
// Ensure the journal entry uses the current SKR type
const fullJournalData = {
...journalData,
skrType: this.skrType,
};
const journalEntry = await JournalEntry.createJournalEntry(fullJournalData);
await journalEntry.post();
return journalEntry;
}
/**
* Get transactions for an account
*/
async getAccountTransactions(accountNumber) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
return await Transaction.getTransactionsByAccount(accountNumber, this.skrType);
}
/**
* Get transactions by filter
*/
async getTransactions(filter) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
const query = {
skrType: this.skrType,
status: 'posted',
};
if (filter) {
if (filter.dateFrom || filter.dateTo) {
query.date = {};
if (filter.dateFrom)
query.date.$gte = filter.dateFrom;
if (filter.dateTo)
query.date.$lte = filter.dateTo;
}
if (filter.accountNumber) {
query.$or = [
{ debitAccount: filter.accountNumber },
{ creditAccount: filter.accountNumber },
];
}
if (filter.minAmount || filter.maxAmount) {
query.amount = {};
if (filter.minAmount)
query.amount.$gte = filter.minAmount;
if (filter.maxAmount)
query.amount.$lte = filter.maxAmount;
}
}
const transactions = await Transaction.getInstances(query);
// Apply text search if provided
if (filter?.searchTerm) {
const lowerSearchTerm = filter.searchTerm.toLowerCase();
return transactions.filter((transaction) => transaction.description.toLowerCase().includes(lowerSearchTerm) ||
transaction.reference.toLowerCase().includes(lowerSearchTerm));
}
return transactions;
}
/**
* Reverse a transaction
*/
async reverseTransaction(transactionId) {
const transaction = await Transaction.getTransactionById(transactionId);
if (!transaction) {
throw new Error(`Transaction ${transactionId} not found`);
}
return await transaction.reverseTransaction();
}
/**
* Get account class description
*/
getAccountClassDescription(accountClass) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
const classes = this.skrType === 'SKR03' ? SKR03_ACCOUNT_CLASSES : SKR04_ACCOUNT_CLASSES;
return (classes[accountClass] || `Class ${accountClass}`);
}
/**
* Import accounts from CSV
*/
async importAccountsFromCSV(csvContent) {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
const lines = csvContent.split('\n').filter((line) => line.trim());
let importedCount = 0;
for (const line of lines) {
// Parse CSV line (expecting format: "account";"name";"description";"type";"active")
const parts = line
.split(';')
.map((part) => part.replace(/"/g, '').trim());
if (parts.length >= 5) {
const accountData = {
accountNumber: parts[0],
accountName: parts[1],
accountClass: parseInt(parts[0][0]),
accountType: parts[3],
skrType: this.skrType,
description: parts[2],
isActive: parts[4].toLowerCase() === 'standard' ||
parts[4].toLowerCase() === 'active',
};
try {
await this.createCustomAccount(accountData);
importedCount++;
}
catch (error) {
this.logger.log('warn', `Failed to import account ${parts[0]}: ${error.message}`);
}
}
}
return importedCount;
}
/**
* Export accounts to CSV
*/
async exportAccountsToCSV() {
const accounts = await this.getAllAccounts();
const csvLines = [];
csvLines.push('"Account";"Name";"Description";"Type";"Active"');
for (const account of accounts) {
csvLines.push(`"${account.accountNumber}";"${account.accountName}";"${account.description}";"${account.accountType}";"${account.isActive ? 'Active' : 'Inactive'}"`);
}
return csvLines.join('\n');
}
/**
* Close the database connection
*/
async close() {
await closeDb();
this.initialized = false;
this.logger.log('info', 'ChartOfAccounts closed');
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tyLmNsYXNzZXMuY2hhcnRvZmFjY291bnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc2tyLmNsYXNzZXMuY2hhcnRvZmFjY291bnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDbkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDN0QsT0FBTyxFQUFFLGNBQWMsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQVd4RSxNQUFNLE9BQU8sZUFBZTtJQUsxQixZQUFvQixNQUF3QjtRQUF4QixXQUFNLEdBQU4sTUFBTSxDQUFrQjtRQUhwQyxnQkFBVyxHQUFZLEtBQUssQ0FBQztRQUM3QixZQUFPLEdBQW9CLElBQUksQ0FBQztRQUd0QyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUM7WUFDMUMsVUFBVSxFQUFFO2dCQUNWLE9BQU8sRUFBRSxRQUFRO2dCQUNqQixXQUFXLEVBQUUsS0FBSztnQkFDbEIsYUFBYSxFQUFFLGlCQUFpQjtnQkFDaEMsV0FBVyxFQUFFLE9BQU87Z0JBQ3BCLE9BQU8sRUFBRSxNQUFNO2dCQUNmLElBQUksRUFBRSxPQUFPO2FBQ2Q7U0FDRixDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQzlCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxJQUFJ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