Files
skr/ts/skr.api.ts
Juergen Kunz 8a9056e767
Some checks failed
Default (tags) / security (push) Successful in 44s
Default (tags) / test (push) Failing after 4m4s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
feat(core): initial release of SKR03/SKR04 German accounting standards implementation
- Complete implementation of German standard charts of accounts
- SKR03 (Process Structure Principle) for trading/service companies
- SKR04 (Financial Classification Principle) for manufacturing companies
- Double-entry bookkeeping with MongoDB persistence
- Comprehensive reporting suite with DATEV export
- Full TypeScript support and type safety
2025-08-09 12:00:40 +00:00

534 lines
14 KiB
TypeScript

import * as plugins from './plugins.js';
import { ChartOfAccounts } from './skr.classes.chartofaccounts.js';
import { Ledger } from './skr.classes.ledger.js';
import { Reports } from './skr.classes.reports.js';
import { Account } from './skr.classes.account.js';
import { Transaction } from './skr.classes.transaction.js';
import { JournalEntry } from './skr.classes.journalentry.js';
import type {
IDatabaseConfig,
TSKRType,
IAccountData,
IAccountFilter,
ITransactionData,
ITransactionFilter,
IJournalEntry,
IReportParams,
ITrialBalanceReport,
IIncomeStatement,
IBalanceSheet,
} from './skr.types.js';
/**
* Main API class for SKR accounting operations
*/
export class SkrApi {
private chartOfAccounts: ChartOfAccounts;
private ledger: Ledger | null = null;
private reports: Reports | null = null;
private logger: plugins.smartlog.Smartlog;
private initialized: boolean = false;
private currentSKRType: TSKRType | null = null;
constructor(private config: IDatabaseConfig) {
this.chartOfAccounts = new ChartOfAccounts(config);
this.logger = new plugins.smartlog.Smartlog({
logContext: {
company: 'fin.cx',
companyunit: 'skr',
containerName: 'SkrApi',
environment: 'local',
runtime: 'node',
zone: 'local',
},
});
}
/**
* Initialize the API with specified SKR type
*/
public async initialize(skrType: TSKRType): Promise<void> {
this.logger.log('info', `Initializing SKR API with ${skrType}`);
// Initialize chart of accounts
if (skrType === 'SKR03') {
await this.chartOfAccounts.initializeSKR03();
} else if (skrType === 'SKR04') {
await this.chartOfAccounts.initializeSKR04();
} else {
throw new Error(`Invalid SKR type: ${skrType}`);
}
this.currentSKRType = skrType;
this.ledger = new Ledger(skrType);
this.reports = new Reports(skrType);
this.initialized = true;
this.logger.log('info', 'SKR API initialized successfully');
}
/**
* Ensure API is initialized
*/
private ensureInitialized(): void {
if (!this.initialized || !this.currentSKRType) {
throw new Error('API not initialized. Call initialize() first.');
}
}
// ========== Account Management ==========
/**
* Create a new account
*/
public async createAccount(
accountData: Partial<IAccountData>,
): Promise<Account> {
this.ensureInitialized();
return await this.chartOfAccounts.createCustomAccount(accountData);
}
/**
* Get account by number
*/
public async getAccount(accountNumber: string): Promise<Account | null> {
this.ensureInitialized();
return await this.chartOfAccounts.getAccountByNumber(accountNumber);
}
/**
* Update an account
*/
public async updateAccount(
accountNumber: string,
updates: Partial<IAccountData>,
): Promise<Account> {
this.ensureInitialized();
return await this.chartOfAccounts.updateAccount(accountNumber, updates);
}
/**
* Delete an account
*/
public async deleteAccount(accountNumber: string): Promise<void> {
this.ensureInitialized();
await this.chartOfAccounts.deleteAccount(accountNumber);
}
/**
* List accounts with optional filter
*/
public async listAccounts(filter?: IAccountFilter): Promise<Account[]> {
this.ensureInitialized();
return await this.chartOfAccounts.getAllAccounts(filter);
}
/**
* Search accounts by term
*/
public async searchAccounts(searchTerm: string): Promise<Account[]> {
this.ensureInitialized();
return await this.chartOfAccounts.searchAccounts(searchTerm);
}
/**
* Get accounts by class
*/
public async getAccountsByClass(accountClass: number): Promise<Account[]> {
this.ensureInitialized();
return await this.chartOfAccounts.getAccountsByClass(accountClass);
}
/**
* Get accounts by type
*/
public async getAccountsByType(
accountType: IAccountData['accountType'],
): Promise<Account[]> {
this.ensureInitialized();
return await this.chartOfAccounts.getAccountsByType(accountType);
}
// ========== Transaction Management ==========
/**
* Post a simple transaction
*/
public async postTransaction(
transactionData: ITransactionData,
): Promise<Transaction> {
this.ensureInitialized();
return await this.chartOfAccounts.postTransaction(transactionData);
}
/**
* Post a journal entry
*/
public async postJournalEntry(
journalData: IJournalEntry,
): Promise<JournalEntry> {
this.ensureInitialized();
return await this.chartOfAccounts.postJournalEntry(journalData);
}
/**
* Get transaction by ID
*/
public async getTransaction(
transactionId: string,
): Promise<Transaction | null> {
this.ensureInitialized();
return await Transaction.getTransactionById(transactionId);
}
/**
* List transactions with optional filter
*/
public async listTransactions(
filter?: ITransactionFilter,
): Promise<Transaction[]> {
this.ensureInitialized();
return await this.chartOfAccounts.getTransactions(filter);
}
/**
* Get transactions for specific account
*/
public async getAccountTransactions(
accountNumber: string,
): Promise<Transaction[]> {
this.ensureInitialized();
return await this.chartOfAccounts.getAccountTransactions(accountNumber);
}
/**
* Reverse a transaction
*/
public async reverseTransaction(transactionId: string): Promise<Transaction> {
this.ensureInitialized();
return await this.chartOfAccounts.reverseTransaction(transactionId);
}
/**
* Reverse a journal entry
*/
public async reverseJournalEntry(journalId: string): Promise<JournalEntry> {
this.ensureInitialized();
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.reverseJournalEntry(journalId);
}
// ========== Reporting ==========
/**
* Generate trial balance
*/
public async generateTrialBalance(
params?: IReportParams,
): Promise<ITrialBalanceReport> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.getTrialBalance(params);
}
/**
* Generate income statement
*/
public async generateIncomeStatement(
params?: IReportParams,
): Promise<IIncomeStatement> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.getIncomeStatement(params);
}
/**
* Generate balance sheet
*/
public async generateBalanceSheet(
params?: IReportParams,
): Promise<IBalanceSheet> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.getBalanceSheet(params);
}
/**
* Generate general ledger
*/
public async generateGeneralLedger(params?: IReportParams): Promise<any> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.getGeneralLedger(params);
}
/**
* Generate cash flow statement
*/
public async generateCashFlowStatement(params?: IReportParams): Promise<any> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.getCashFlowStatement(params);
}
/**
* Export report to CSV
*/
public async exportReportToCSV(
reportType: 'trial_balance' | 'income_statement' | 'balance_sheet',
params?: IReportParams,
): Promise<string> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.exportToCSV(reportType, params);
}
/**
* Export to DATEV format
*/
public async exportToDATEV(params?: IReportParams): Promise<string> {
this.ensureInitialized();
if (!this.reports) throw new Error('Reports not initialized');
return await this.reports.exportToDATEV(params);
}
// ========== Period Management ==========
/**
* Close accounting period
*/
public async closePeriod(
period: string,
closingAccountNumber?: string,
): Promise<JournalEntry[]> {
this.ensureInitialized();
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.closeAccountingPeriod(
period,
closingAccountNumber,
);
}
/**
* Get account balance
*/
public async getAccountBalance(
accountNumber: string,
asOfDate?: Date,
): Promise<any> {
this.ensureInitialized();
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.getAccountBalance(accountNumber, asOfDate);
}
/**
* Recalculate all account balances
*/
public async recalculateBalances(): Promise<void> {
this.ensureInitialized();
if (!this.ledger) throw new Error('Ledger not initialized');
await this.ledger.recalculateAllBalances();
}
// ========== Import/Export ==========
/**
* Import accounts from CSV
*/
public async importAccountsFromCSV(csvContent: string): Promise<number> {
this.ensureInitialized();
return await this.chartOfAccounts.importAccountsFromCSV(csvContent);
}
/**
* Export accounts to CSV
*/
public async exportAccountsToCSV(): Promise<string> {
this.ensureInitialized();
return await this.chartOfAccounts.exportAccountsToCSV();
}
// ========== Utility Methods ==========
/**
* Get current SKR type
*/
public getSKRType(): TSKRType | null {
return this.currentSKRType;
}
/**
* Get account class description
*/
public getAccountClassDescription(accountClass: number): string {
this.ensureInitialized();
return this.chartOfAccounts.getAccountClassDescription(accountClass);
}
/**
* Validate double-entry rules
*/
public validateDoubleEntry(
debitAmount: number,
creditAmount: number,
): boolean {
if (!this.ledger) throw new Error('Ledger not initialized');
return this.ledger.validateDoubleEntry(debitAmount, creditAmount);
}
/**
* Get unbalanced transactions (for audit)
*/
public async getUnbalancedTransactions(): Promise<Transaction[]> {
this.ensureInitialized();
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.getUnbalancedTransactions();
}
/**
* Close the API and database connection
*/
public async close(): Promise<void> {
await this.chartOfAccounts.close();
this.initialized = false;
this.currentSKRType = null;
this.ledger = null;
this.reports = null;
this.logger.log('info', 'SKR API closed');
}
// ========== Batch Operations ==========
/**
* Post multiple transactions
*/
public async postBatchTransactions(
transactions: ITransactionData[],
): Promise<Transaction[]> {
this.ensureInitialized();
const results: Transaction[] = [];
const errors: Array<{ index: number; error: string }> = [];
for (let i = 0; i < transactions.length; i++) {
try {
const transaction = await this.postTransaction(transactions[i]);
results.push(transaction);
} catch (error) {
errors.push({ index: i, error: error.message });
}
}
if (errors.length > 0) {
this.logger.log(
'warn',
`Batch transaction posting completed with ${errors.length} errors`,
);
throw new Error(
`Batch posting failed for ${errors.length} transactions: ${JSON.stringify(errors)}`,
);
}
return results;
}
/**
* Create multiple accounts
*/
public async createBatchAccounts(
accounts: IAccountData[],
): Promise<Account[]> {
this.ensureInitialized();
const results: Account[] = [];
const errors: Array<{ index: number; error: string }> = [];
for (let i = 0; i < accounts.length; i++) {
try {
const account = await this.createAccount(accounts[i]);
results.push(account);
} catch (error) {
errors.push({ index: i, error: error.message });
}
}
if (errors.length > 0) {
this.logger.log(
'warn',
`Batch account creation completed with ${errors.length} errors`,
);
throw new Error(
`Batch creation failed for ${errors.length} accounts: ${JSON.stringify(errors)}`,
);
}
return results;
}
// ========== Pagination Support ==========
/**
* Get paginated accounts
*/
public async getAccountsPaginated(
page: number = 1,
pageSize: number = 50,
filter?: IAccountFilter,
): Promise<{
data: Account[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}> {
this.ensureInitialized();
const allAccounts = await this.listAccounts(filter);
const total = allAccounts.length;
const totalPages = Math.ceil(total / pageSize);
const start = (page - 1) * pageSize;
const end = start + pageSize;
const data = allAccounts.slice(start, end);
return {
data,
total,
page,
pageSize,
totalPages,
};
}
/**
* Get paginated transactions
*/
public async getTransactionsPaginated(
page: number = 1,
pageSize: number = 50,
filter?: ITransactionFilter,
): Promise<{
data: Transaction[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}> {
this.ensureInitialized();
const allTransactions = await this.listTransactions(filter);
const total = allTransactions.length;
const totalPages = Math.ceil(total / pageSize);
const start = (page - 1) * pageSize;
const end = start + pageSize;
const data = allTransactions.slice(start, end);
return {
data,
total,
page,
pageSize,
totalPages,
};
}
}