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
This commit is contained in:
533
ts/skr.api.ts
Normal file
533
ts/skr.api.ts
Normal file
@@ -0,0 +1,533 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user