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:
10
ts/index.ts
Normal file
10
ts/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export * from './skr.types.js';
|
||||
export * from './skr.classes.account.js';
|
||||
export * from './skr.classes.transaction.js';
|
||||
export * from './skr.classes.journalentry.js';
|
||||
export * from './skr.classes.chartofaccounts.js';
|
||||
export * from './skr.classes.ledger.js';
|
||||
export * from './skr.classes.reports.js';
|
||||
export * from './skr.api.js';
|
||||
export * from './skr03.data.js';
|
||||
export * from './skr04.data.js';
|
7
ts/plugins.ts
Normal file
7
ts/plugins.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// @push.rocks scope
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
import * as smarttime from '@push.rocks/smarttime';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
|
||||
export { smartdata, smartunique, smarttime, smartlog };
|
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,
|
||||
};
|
||||
}
|
||||
}
|
238
ts/skr.classes.account.ts
Normal file
238
ts/skr.classes.account.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { getDb, getDbSync } from './skr.database.js';
|
||||
import type { TAccountType, TSKRType, IAccountData } from './skr.types.js';
|
||||
|
||||
const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata;
|
||||
|
||||
@plugins.smartdata.Collection(() => getDbSync())
|
||||
export class Account extends SmartDataDbDoc<Account, Account> {
|
||||
@unI()
|
||||
public id: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public accountNumber: string;
|
||||
|
||||
@svDb()
|
||||
@searchable()
|
||||
public accountName: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public accountClass: number;
|
||||
|
||||
@svDb()
|
||||
public accountGroup: number;
|
||||
|
||||
@svDb()
|
||||
public accountSubgroup: number;
|
||||
|
||||
@svDb()
|
||||
public accountType: TAccountType;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public skrType: TSKRType;
|
||||
|
||||
@svDb()
|
||||
@searchable()
|
||||
public description: string;
|
||||
|
||||
@svDb()
|
||||
public vatRate: number;
|
||||
|
||||
@svDb()
|
||||
public balance: number;
|
||||
|
||||
@svDb()
|
||||
public debitTotal: number;
|
||||
|
||||
@svDb()
|
||||
public creditTotal: number;
|
||||
|
||||
@svDb()
|
||||
public isActive: boolean;
|
||||
|
||||
@svDb()
|
||||
public isSystemAccount: boolean;
|
||||
|
||||
@svDb()
|
||||
public createdAt: Date;
|
||||
|
||||
@svDb()
|
||||
public updatedAt: Date;
|
||||
|
||||
constructor(data?: Partial<IAccountData>) {
|
||||
super();
|
||||
|
||||
if (data) {
|
||||
this.id = plugins.smartunique.shortId();
|
||||
this.accountNumber = data.accountNumber || '';
|
||||
this.accountName = data.accountName || '';
|
||||
this.accountClass = data.accountClass || 0;
|
||||
this.accountType = data.accountType || 'asset';
|
||||
this.skrType = data.skrType || 'SKR03';
|
||||
this.description = data.description || '';
|
||||
this.vatRate = data.vatRate || 0;
|
||||
this.isActive = data.isActive !== undefined ? data.isActive : true;
|
||||
|
||||
// Parse account structure from number
|
||||
if (this.accountNumber && this.accountNumber.length === 4) {
|
||||
this.accountClass = parseInt(this.accountNumber[0]);
|
||||
this.accountGroup = parseInt(this.accountNumber[1]);
|
||||
this.accountSubgroup = parseInt(this.accountNumber[2]);
|
||||
} else {
|
||||
this.accountGroup = 0;
|
||||
this.accountSubgroup = 0;
|
||||
}
|
||||
|
||||
this.balance = 0;
|
||||
this.debitTotal = 0;
|
||||
this.creditTotal = 0;
|
||||
this.isSystemAccount = true;
|
||||
this.createdAt = new Date();
|
||||
this.updatedAt = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
public static async createAccount(data: IAccountData): Promise<Account> {
|
||||
const account = new Account(data);
|
||||
await account.save();
|
||||
return account;
|
||||
}
|
||||
|
||||
public static async getAccountByNumber(
|
||||
accountNumber: string,
|
||||
skrType: TSKRType,
|
||||
): Promise<Account | null> {
|
||||
const account = await Account.getInstance({
|
||||
accountNumber,
|
||||
skrType,
|
||||
});
|
||||
return account;
|
||||
}
|
||||
|
||||
public static async getAccountsByClass(
|
||||
accountClass: number,
|
||||
skrType: TSKRType,
|
||||
): Promise<Account[]> {
|
||||
const accounts = await Account.getInstances({
|
||||
accountClass,
|
||||
skrType,
|
||||
isActive: true,
|
||||
});
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public static async getAccountsByType(
|
||||
accountType: TAccountType,
|
||||
skrType: TSKRType,
|
||||
): Promise<Account[]> {
|
||||
const accounts = await Account.getInstances({
|
||||
accountType,
|
||||
skrType,
|
||||
isActive: true,
|
||||
});
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public static async searchAccounts(
|
||||
searchTerm: string,
|
||||
skrType?: TSKRType,
|
||||
): Promise<Account[]> {
|
||||
const query: any = {};
|
||||
if (skrType) {
|
||||
query.skrType = skrType;
|
||||
}
|
||||
|
||||
const accounts = await Account.getInstances(query);
|
||||
|
||||
// Filter by search term
|
||||
const lowerSearchTerm = searchTerm.toLowerCase();
|
||||
return accounts.filter(
|
||||
(account) =>
|
||||
account.accountNumber.includes(searchTerm) ||
|
||||
account.accountName.toLowerCase().includes(lowerSearchTerm) ||
|
||||
account.description.toLowerCase().includes(lowerSearchTerm),
|
||||
);
|
||||
}
|
||||
|
||||
public async updateBalance(
|
||||
debitAmount: number = 0,
|
||||
creditAmount: number = 0,
|
||||
): Promise<void> {
|
||||
this.debitTotal += debitAmount;
|
||||
this.creditTotal += creditAmount;
|
||||
|
||||
// Calculate balance based on account type
|
||||
switch (this.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
// Normal debit accounts
|
||||
this.balance = this.debitTotal - this.creditTotal;
|
||||
break;
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
// Normal credit accounts
|
||||
this.balance = this.creditTotal - this.debitTotal;
|
||||
break;
|
||||
}
|
||||
|
||||
this.updatedAt = new Date();
|
||||
await this.save();
|
||||
}
|
||||
|
||||
public async deactivate(): Promise<void> {
|
||||
this.isActive = false;
|
||||
this.updatedAt = new Date();
|
||||
await this.save();
|
||||
}
|
||||
|
||||
public async activate(): Promise<void> {
|
||||
this.isActive = true;
|
||||
this.updatedAt = new Date();
|
||||
await this.save();
|
||||
}
|
||||
|
||||
public getNormalBalance(): 'debit' | 'credit' {
|
||||
switch (this.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
return 'debit';
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
return 'credit';
|
||||
}
|
||||
}
|
||||
|
||||
public async beforeSave(): Promise<void> {
|
||||
// Validate account number format
|
||||
if (!this.accountNumber || this.accountNumber.length !== 4) {
|
||||
throw new Error(
|
||||
`Invalid account number format: ${this.accountNumber}. Must be 4 digits.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Validate account number is numeric
|
||||
if (!/^\d{4}$/.test(this.accountNumber)) {
|
||||
throw new Error(
|
||||
`Account number must contain only digits: ${this.accountNumber}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Validate account class matches first digit
|
||||
const firstDigit = parseInt(this.accountNumber[0]);
|
||||
if (this.accountClass !== firstDigit) {
|
||||
throw new Error(
|
||||
`Account class ${this.accountClass} does not match account number ${this.accountNumber}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Validate SKR type
|
||||
if (this.skrType !== 'SKR03' && this.skrType !== 'SKR04') {
|
||||
throw new Error(`Invalid SKR type: ${this.skrType}`);
|
||||
}
|
||||
}
|
||||
}
|
508
ts/skr.classes.chartofaccounts.ts
Normal file
508
ts/skr.classes.chartofaccounts.ts
Normal file
@@ -0,0 +1,508 @@
|
||||
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';
|
||||
import type {
|
||||
IDatabaseConfig,
|
||||
TSKRType,
|
||||
IAccountData,
|
||||
IAccountFilter,
|
||||
ITransactionFilter,
|
||||
ITransactionData,
|
||||
IJournalEntry,
|
||||
} from './skr.types.js';
|
||||
|
||||
export class ChartOfAccounts {
|
||||
private logger: plugins.smartlog.Smartlog;
|
||||
private initialized: boolean = false;
|
||||
private skrType: TSKRType | null = null;
|
||||
|
||||
constructor(private config?: IDatabaseConfig) {
|
||||
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
|
||||
*/
|
||||
public async init(): Promise<void> {
|
||||
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
|
||||
*/
|
||||
public async initializeSKR03(): Promise<void> {
|
||||
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: Account[] = [];
|
||||
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
|
||||
*/
|
||||
public async initializeSKR04(): Promise<void> {
|
||||
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: Account[] = [];
|
||||
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
|
||||
*/
|
||||
public getSKRType(): TSKRType | null {
|
||||
return this.skrType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active SKR type
|
||||
*/
|
||||
public setSKRType(skrType: TSKRType): void {
|
||||
this.skrType = skrType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account by number
|
||||
*/
|
||||
public async getAccountByNumber(
|
||||
accountNumber: string,
|
||||
): Promise<Account | null> {
|
||||
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
|
||||
*/
|
||||
public async getAccountsByClass(accountClass: number): Promise<Account[]> {
|
||||
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
|
||||
*/
|
||||
public async getAccountsByType(
|
||||
accountType: IAccountData['accountType'],
|
||||
): Promise<Account[]> {
|
||||
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
|
||||
*/
|
||||
public async createCustomAccount(
|
||||
accountData: Partial<IAccountData>,
|
||||
): Promise<Account> {
|
||||
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: IAccountData = {
|
||||
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
|
||||
*/
|
||||
public async updateAccount(
|
||||
accountNumber: string,
|
||||
updates: Partial<IAccountData>,
|
||||
): Promise<Account> {
|
||||
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)
|
||||
*/
|
||||
public async deleteAccount(accountNumber: string): Promise<void> {
|
||||
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
|
||||
*/
|
||||
public async searchAccounts(searchTerm: string): Promise<Account[]> {
|
||||
return await Account.searchAccounts(searchTerm, this.skrType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all accounts
|
||||
*/
|
||||
public async getAllAccounts(filter?: IAccountFilter): Promise<Account[]> {
|
||||
const query: any = {};
|
||||
|
||||
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
|
||||
*/
|
||||
public async postTransaction(
|
||||
transactionData: ITransactionData,
|
||||
): Promise<Transaction> {
|
||||
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: ITransactionData = {
|
||||
...transactionData,
|
||||
skrType: this.skrType,
|
||||
};
|
||||
|
||||
return await Transaction.createTransaction(fullTransactionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a journal entry
|
||||
*/
|
||||
public async postJournalEntry(
|
||||
journalData: IJournalEntry,
|
||||
): Promise<JournalEntry> {
|
||||
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: IJournalEntry = {
|
||||
...journalData,
|
||||
skrType: this.skrType,
|
||||
};
|
||||
|
||||
const journalEntry = await JournalEntry.createJournalEntry(fullJournalData);
|
||||
await journalEntry.post();
|
||||
|
||||
return journalEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transactions for an account
|
||||
*/
|
||||
public async getAccountTransactions(
|
||||
accountNumber: string,
|
||||
): Promise<Transaction[]> {
|
||||
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
|
||||
*/
|
||||
public async getTransactions(
|
||||
filter?: ITransactionFilter,
|
||||
): Promise<Transaction[]> {
|
||||
if (!this.skrType) {
|
||||
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
|
||||
}
|
||||
|
||||
const query: any = {
|
||||
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
|
||||
*/
|
||||
public async reverseTransaction(transactionId: string): Promise<Transaction> {
|
||||
const transaction = await Transaction.getTransactionById(transactionId);
|
||||
if (!transaction) {
|
||||
throw new Error(`Transaction ${transactionId} not found`);
|
||||
}
|
||||
|
||||
return await transaction.reverseTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account class description
|
||||
*/
|
||||
public getAccountClassDescription(accountClass: number): string {
|
||||
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 as keyof typeof classes] || `Class ${accountClass}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import accounts from CSV
|
||||
*/
|
||||
public async importAccountsFromCSV(csvContent: string): Promise<number> {
|
||||
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: IAccountData = {
|
||||
accountNumber: parts[0],
|
||||
accountName: parts[1],
|
||||
accountClass: parseInt(parts[0][0]),
|
||||
accountType: parts[3] as IAccountData['accountType'],
|
||||
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
|
||||
*/
|
||||
public async exportAccountsToCSV(): Promise<string> {
|
||||
const accounts = await this.getAllAccounts();
|
||||
|
||||
const csvLines: string[] = [];
|
||||
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
|
||||
*/
|
||||
public async close(): Promise<void> {
|
||||
await closeDb();
|
||||
this.initialized = false;
|
||||
this.logger.log('info', 'ChartOfAccounts closed');
|
||||
}
|
||||
}
|
318
ts/skr.classes.journalentry.ts
Normal file
318
ts/skr.classes.journalentry.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { getDbSync } from './skr.database.js';
|
||||
import { Account } from './skr.classes.account.js';
|
||||
import { Transaction } from './skr.classes.transaction.js';
|
||||
import type {
|
||||
TSKRType,
|
||||
IJournalEntry,
|
||||
IJournalEntryLine,
|
||||
} from './skr.types.js';
|
||||
|
||||
const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata;
|
||||
|
||||
@plugins.smartdata.Collection(() => getDbSync())
|
||||
export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
|
||||
@unI()
|
||||
public id: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public journalNumber: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public date: Date;
|
||||
|
||||
@svDb()
|
||||
@searchable()
|
||||
public description: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public reference: string;
|
||||
|
||||
@svDb()
|
||||
public lines: IJournalEntryLine[];
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public skrType: TSKRType;
|
||||
|
||||
@svDb()
|
||||
public totalDebits: number;
|
||||
|
||||
@svDb()
|
||||
public totalCredits: number;
|
||||
|
||||
@svDb()
|
||||
public isBalanced: boolean;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public status: 'draft' | 'posted' | 'reversed';
|
||||
|
||||
@svDb()
|
||||
public transactionIds: string[];
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public period: string;
|
||||
|
||||
@svDb()
|
||||
public fiscalYear: number;
|
||||
|
||||
@svDb()
|
||||
public createdAt: Date;
|
||||
|
||||
@svDb()
|
||||
public postedAt: Date;
|
||||
|
||||
@svDb()
|
||||
public createdBy: string;
|
||||
|
||||
constructor(data?: Partial<IJournalEntry>) {
|
||||
super();
|
||||
|
||||
if (data) {
|
||||
this.id = plugins.smartunique.shortId();
|
||||
this.journalNumber = this.generateJournalNumber();
|
||||
this.date = data.date || new Date();
|
||||
this.description = data.description || '';
|
||||
this.reference = data.reference || '';
|
||||
this.lines = data.lines || [];
|
||||
this.skrType = data.skrType || 'SKR03';
|
||||
this.totalDebits = 0;
|
||||
this.totalCredits = 0;
|
||||
this.isBalanced = false;
|
||||
this.status = 'draft';
|
||||
this.transactionIds = [];
|
||||
|
||||
// Set period and fiscal year
|
||||
const entryDate = new Date(this.date);
|
||||
this.period = `${entryDate.getFullYear()}-${String(entryDate.getMonth() + 1).padStart(2, '0')}`;
|
||||
this.fiscalYear = entryDate.getFullYear();
|
||||
|
||||
this.createdAt = new Date();
|
||||
this.postedAt = null;
|
||||
this.createdBy = 'system';
|
||||
|
||||
// Calculate totals
|
||||
this.calculateTotals();
|
||||
}
|
||||
}
|
||||
|
||||
private generateJournalNumber(): string {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000);
|
||||
return `JE-${timestamp}-${random}`;
|
||||
}
|
||||
|
||||
private calculateTotals(): void {
|
||||
this.totalDebits = 0;
|
||||
this.totalCredits = 0;
|
||||
|
||||
for (const line of this.lines) {
|
||||
if (line.debit) {
|
||||
this.totalDebits += line.debit;
|
||||
}
|
||||
if (line.credit) {
|
||||
this.totalCredits += line.credit;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if balanced (allowing for small rounding differences)
|
||||
const difference = Math.abs(this.totalDebits - this.totalCredits);
|
||||
this.isBalanced = difference < 0.01;
|
||||
}
|
||||
|
||||
public static async createJournalEntry(
|
||||
data: IJournalEntry,
|
||||
): Promise<JournalEntry> {
|
||||
const journalEntry = new JournalEntry(data);
|
||||
await journalEntry.validate();
|
||||
await journalEntry.save();
|
||||
return journalEntry;
|
||||
}
|
||||
|
||||
public addLine(line: IJournalEntryLine): void {
|
||||
// Validate line
|
||||
if (!line.accountNumber) {
|
||||
throw new Error('Account number is required for journal entry line');
|
||||
}
|
||||
|
||||
if (!line.debit && !line.credit) {
|
||||
throw new Error('Either debit or credit amount is required');
|
||||
}
|
||||
|
||||
if (line.debit && line.credit) {
|
||||
throw new Error('A line cannot have both debit and credit amounts');
|
||||
}
|
||||
|
||||
if (line.debit && line.debit < 0) {
|
||||
throw new Error('Debit amount must be positive');
|
||||
}
|
||||
|
||||
if (line.credit && line.credit < 0) {
|
||||
throw new Error('Credit amount must be positive');
|
||||
}
|
||||
|
||||
this.lines.push(line);
|
||||
this.calculateTotals();
|
||||
}
|
||||
|
||||
public removeLine(index: number): void {
|
||||
if (index >= 0 && index < this.lines.length) {
|
||||
this.lines.splice(index, 1);
|
||||
this.calculateTotals();
|
||||
}
|
||||
}
|
||||
|
||||
public async validate(): Promise<void> {
|
||||
// Check if entry is balanced
|
||||
if (!this.isBalanced) {
|
||||
throw new Error(
|
||||
`Journal entry is not balanced. Debits: ${this.totalDebits}, Credits: ${this.totalCredits}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check minimum lines
|
||||
if (this.lines.length < 2) {
|
||||
throw new Error('Journal entry must have at least 2 lines');
|
||||
}
|
||||
|
||||
// Validate all accounts exist and are active
|
||||
for (const line of this.lines) {
|
||||
const account = await Account.getAccountByNumber(
|
||||
line.accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
`Account ${line.accountNumber} not found for ${this.skrType}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!account.isActive) {
|
||||
throw new Error(`Account ${line.accountNumber} is not active`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async post(): Promise<void> {
|
||||
if (this.status === 'posted') {
|
||||
throw new Error('Journal entry is already posted');
|
||||
}
|
||||
|
||||
// Validate before posting
|
||||
await this.validate();
|
||||
|
||||
// Create individual transactions for each debit-credit pair
|
||||
const transactions: Transaction[] = [];
|
||||
|
||||
// Simple posting logic: match debits with credits
|
||||
// For complex entries, this could be enhanced with specific pairing logic
|
||||
const debitLines = this.lines.filter((l) => l.debit);
|
||||
const creditLines = this.lines.filter((l) => l.credit);
|
||||
|
||||
if (debitLines.length === 1 && creditLines.length === 1) {
|
||||
// Simple entry: one debit, one credit
|
||||
const transaction = await Transaction.createTransaction({
|
||||
date: this.date,
|
||||
debitAccount: debitLines[0].accountNumber,
|
||||
creditAccount: creditLines[0].accountNumber,
|
||||
amount: debitLines[0].debit,
|
||||
description: this.description,
|
||||
reference: this.reference,
|
||||
skrType: this.skrType,
|
||||
costCenter: debitLines[0].costCenter,
|
||||
});
|
||||
transactions.push(transaction);
|
||||
} else {
|
||||
// Complex entry: multiple debits and/or credits
|
||||
// Create transactions to balance the entry
|
||||
for (const debitLine of debitLines) {
|
||||
for (const creditLine of creditLines) {
|
||||
const amount = Math.min(debitLine.debit || 0, creditLine.credit || 0);
|
||||
|
||||
if (amount > 0) {
|
||||
const transaction = await Transaction.createTransaction({
|
||||
date: this.date,
|
||||
debitAccount: debitLine.accountNumber,
|
||||
creditAccount: creditLine.accountNumber,
|
||||
amount: amount,
|
||||
description: `${this.description} - ${debitLine.description || creditLine.description || ''}`,
|
||||
reference: this.reference,
|
||||
skrType: this.skrType,
|
||||
costCenter: debitLine.costCenter || creditLine.costCenter,
|
||||
});
|
||||
transactions.push(transaction);
|
||||
|
||||
// Reduce amounts for tracking
|
||||
if (debitLine.debit) debitLine.debit -= amount;
|
||||
if (creditLine.credit) creditLine.credit -= amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store transaction IDs
|
||||
this.transactionIds = transactions.map((t) => t.id);
|
||||
|
||||
// Update status
|
||||
this.status = 'posted';
|
||||
this.postedAt = new Date();
|
||||
|
||||
await this.save();
|
||||
}
|
||||
|
||||
public async reverse(): Promise<JournalEntry> {
|
||||
if (this.status !== 'posted') {
|
||||
throw new Error('Can only reverse posted journal entries');
|
||||
}
|
||||
|
||||
// Create reversal entry with swapped debits and credits
|
||||
const reversalLines: IJournalEntryLine[] = this.lines.map((line) => ({
|
||||
accountNumber: line.accountNumber,
|
||||
debit: line.credit, // Swap
|
||||
credit: line.debit, // Swap
|
||||
description: `Reversal: ${line.description || ''}`,
|
||||
costCenter: line.costCenter,
|
||||
}));
|
||||
|
||||
const reversalEntry = new JournalEntry({
|
||||
date: new Date(),
|
||||
description: `Reversal of ${this.journalNumber}: ${this.description}`,
|
||||
reference: `REV-${this.journalNumber}`,
|
||||
lines: reversalLines,
|
||||
skrType: this.skrType,
|
||||
});
|
||||
|
||||
await reversalEntry.validate();
|
||||
await reversalEntry.post();
|
||||
|
||||
// Update original entry status
|
||||
this.status = 'reversed';
|
||||
await this.save();
|
||||
|
||||
return reversalEntry;
|
||||
}
|
||||
|
||||
public async beforeSave(): Promise<void> {
|
||||
// Recalculate totals before saving
|
||||
this.calculateTotals();
|
||||
|
||||
// Validate required fields
|
||||
if (!this.date) {
|
||||
throw new Error('Journal entry date is required');
|
||||
}
|
||||
|
||||
if (!this.description) {
|
||||
throw new Error('Journal entry description is required');
|
||||
}
|
||||
|
||||
if (this.lines.length === 0) {
|
||||
throw new Error('Journal entry must have at least one line');
|
||||
}
|
||||
}
|
||||
}
|
528
ts/skr.classes.ledger.ts
Normal file
528
ts/skr.classes.ledger.ts
Normal file
@@ -0,0 +1,528 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { Account } from './skr.classes.account.js';
|
||||
import { Transaction } from './skr.classes.transaction.js';
|
||||
import { JournalEntry } from './skr.classes.journalentry.js';
|
||||
import type {
|
||||
TSKRType,
|
||||
ITransactionData,
|
||||
IJournalEntry,
|
||||
IJournalEntryLine,
|
||||
IAccountBalance,
|
||||
} from './skr.types.js';
|
||||
|
||||
export class Ledger {
|
||||
private logger: plugins.smartlog.Smartlog;
|
||||
|
||||
constructor(private skrType: TSKRType) {
|
||||
this.logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: 'fin.cx',
|
||||
companyunit: 'skr',
|
||||
containerName: 'Ledger',
|
||||
environment: 'local',
|
||||
runtime: 'node',
|
||||
zone: 'local',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a transaction with validation
|
||||
*/
|
||||
public async postTransaction(
|
||||
transactionData: ITransactionData,
|
||||
): Promise<Transaction> {
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Posting transaction: ${transactionData.description}`,
|
||||
);
|
||||
|
||||
// Ensure SKR type matches
|
||||
const fullTransactionData: ITransactionData = {
|
||||
...transactionData,
|
||||
skrType: this.skrType,
|
||||
};
|
||||
|
||||
// Validate accounts exist
|
||||
await this.validateAccounts([
|
||||
transactionData.debitAccount,
|
||||
transactionData.creditAccount,
|
||||
]);
|
||||
|
||||
// Create and post transaction
|
||||
const transaction =
|
||||
await Transaction.createTransaction(fullTransactionData);
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Transaction ${transaction.transactionNumber} posted successfully`,
|
||||
);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a journal entry with validation
|
||||
*/
|
||||
public async postJournalEntry(
|
||||
journalData: IJournalEntry,
|
||||
): Promise<JournalEntry> {
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Posting journal entry: ${journalData.description}`,
|
||||
);
|
||||
|
||||
// Ensure SKR type matches
|
||||
const fullJournalData: IJournalEntry = {
|
||||
...journalData,
|
||||
skrType: this.skrType,
|
||||
};
|
||||
|
||||
// Validate all accounts exist
|
||||
const accountNumbers = journalData.lines.map((line) => line.accountNumber);
|
||||
await this.validateAccounts(accountNumbers);
|
||||
|
||||
// Validate journal entry is balanced
|
||||
this.validateJournalBalance(journalData.lines);
|
||||
|
||||
// Create and post journal entry
|
||||
const journalEntry = await JournalEntry.createJournalEntry(fullJournalData);
|
||||
await journalEntry.post();
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Journal entry ${journalEntry.journalNumber} posted successfully`,
|
||||
);
|
||||
return journalEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that accounts exist and are active
|
||||
*/
|
||||
private async validateAccounts(accountNumbers: string[]): Promise<void> {
|
||||
const uniqueAccountNumbers = [...new Set(accountNumbers)];
|
||||
|
||||
for (const accountNumber of uniqueAccountNumbers) {
|
||||
const account = await Account.getAccountByNumber(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
`Account ${accountNumber} not found for ${this.skrType}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!account.isActive) {
|
||||
throw new Error(`Account ${accountNumber} is not active`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate journal entry balance
|
||||
*/
|
||||
private validateJournalBalance(lines: IJournalEntryLine[]): void {
|
||||
let totalDebits = 0;
|
||||
let totalCredits = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.debit) totalDebits += line.debit;
|
||||
if (line.credit) totalCredits += line.credit;
|
||||
}
|
||||
|
||||
const difference = Math.abs(totalDebits - totalCredits);
|
||||
if (difference >= 0.01) {
|
||||
throw new Error(
|
||||
`Journal entry is not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a transaction
|
||||
*/
|
||||
public async reverseTransaction(transactionId: string): Promise<Transaction> {
|
||||
this.logger.log('info', `Reversing transaction: ${transactionId}`);
|
||||
|
||||
const transaction = await Transaction.getTransactionById(transactionId);
|
||||
if (!transaction) {
|
||||
throw new Error(`Transaction ${transactionId} not found`);
|
||||
}
|
||||
|
||||
if (transaction.skrType !== this.skrType) {
|
||||
throw new Error(
|
||||
`Transaction ${transactionId} belongs to different SKR type`,
|
||||
);
|
||||
}
|
||||
|
||||
const reversalTransaction = await transaction.reverseTransaction();
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Transaction reversed: ${reversalTransaction.transactionNumber}`,
|
||||
);
|
||||
return reversalTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a journal entry
|
||||
*/
|
||||
public async reverseJournalEntry(journalId: string): Promise<JournalEntry> {
|
||||
this.logger.log('info', `Reversing journal entry: ${journalId}`);
|
||||
|
||||
const journalEntry = await JournalEntry.getInstance({ id: journalId });
|
||||
if (!journalEntry) {
|
||||
throw new Error(`Journal entry ${journalId} not found`);
|
||||
}
|
||||
|
||||
if (journalEntry.skrType !== this.skrType) {
|
||||
throw new Error(
|
||||
`Journal entry ${journalId} belongs to different SKR type`,
|
||||
);
|
||||
}
|
||||
|
||||
const reversalEntry = await journalEntry.reverse();
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Journal entry reversed: ${reversalEntry.journalNumber}`,
|
||||
);
|
||||
return reversalEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account history (all transactions for an account)
|
||||
*/
|
||||
public async getAccountHistory(
|
||||
accountNumber: string,
|
||||
dateFrom?: Date,
|
||||
dateTo?: Date,
|
||||
): Promise<Transaction[]> {
|
||||
const account = await Account.getAccountByNumber(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
if (!account) {
|
||||
throw new Error(`Account ${accountNumber} not found`);
|
||||
}
|
||||
|
||||
let transactions = await Transaction.getTransactionsByAccount(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Apply date filter if provided
|
||||
if (dateFrom || dateTo) {
|
||||
transactions = transactions.filter((transaction) => {
|
||||
if (dateFrom && transaction.date < dateFrom) return false;
|
||||
if (dateTo && transaction.date > dateTo) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by date
|
||||
transactions.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account balance at a specific date
|
||||
*/
|
||||
public async getAccountBalance(
|
||||
accountNumber: string,
|
||||
asOfDate?: Date,
|
||||
): Promise<IAccountBalance> {
|
||||
const account = await Account.getAccountByNumber(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
if (!account) {
|
||||
throw new Error(`Account ${accountNumber} not found`);
|
||||
}
|
||||
|
||||
let transactions = await Transaction.getTransactionsByAccount(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Filter transactions up to the specified date
|
||||
if (asOfDate) {
|
||||
transactions = transactions.filter((t) => t.date <= asOfDate);
|
||||
}
|
||||
|
||||
// Calculate balance
|
||||
let debitTotal = 0;
|
||||
let creditTotal = 0;
|
||||
|
||||
for (const transaction of transactions) {
|
||||
if (transaction.debitAccount === accountNumber) {
|
||||
debitTotal += transaction.amount;
|
||||
}
|
||||
if (transaction.creditAccount === accountNumber) {
|
||||
creditTotal += transaction.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate net balance based on account type
|
||||
let balance: number;
|
||||
switch (account.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
// Normal debit accounts
|
||||
balance = debitTotal - creditTotal;
|
||||
break;
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
// Normal credit accounts
|
||||
balance = creditTotal - debitTotal;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
accountNumber,
|
||||
debitTotal,
|
||||
creditTotal,
|
||||
balance,
|
||||
lastUpdated: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Close accounting period (create closing entries)
|
||||
*/
|
||||
public async closeAccountingPeriod(
|
||||
period: string, // Format: YYYY-MM
|
||||
closingAccountNumber: string = '9400', // Default P&L account
|
||||
): Promise<JournalEntry[]> {
|
||||
this.logger.log('info', `Closing accounting period: ${period}`);
|
||||
|
||||
const closingEntries: JournalEntry[] = [];
|
||||
|
||||
// Get all revenue and expense accounts
|
||||
const revenueAccounts = await Account.getAccountsByType(
|
||||
'revenue',
|
||||
this.skrType,
|
||||
);
|
||||
const expenseAccounts = await Account.getAccountsByType(
|
||||
'expense',
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Calculate totals for each account in the period
|
||||
const periodTransactions = await Transaction.getTransactionsByPeriod(
|
||||
period,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Create closing entry for revenue accounts
|
||||
const revenueLines: IJournalEntryLine[] = [];
|
||||
let totalRevenue = 0;
|
||||
|
||||
for (const account of revenueAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(
|
||||
account.accountNumber,
|
||||
periodTransactions,
|
||||
);
|
||||
|
||||
if (balance !== 0) {
|
||||
// Revenue accounts have credit balance, so debit to close
|
||||
revenueLines.push({
|
||||
accountNumber: account.accountNumber,
|
||||
debit: Math.abs(balance),
|
||||
description: `Closing ${account.accountName}`,
|
||||
});
|
||||
totalRevenue += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalRevenue > 0) {
|
||||
// Credit the closing account
|
||||
revenueLines.push({
|
||||
accountNumber: closingAccountNumber,
|
||||
credit: totalRevenue,
|
||||
description: 'Revenue closing to P&L',
|
||||
});
|
||||
|
||||
const revenueClosingEntry = await this.postJournalEntry({
|
||||
date: new Date(),
|
||||
description: `Closing revenue accounts for period ${period}`,
|
||||
reference: `CLOSE-REV-${period}`,
|
||||
lines: revenueLines,
|
||||
skrType: this.skrType,
|
||||
});
|
||||
|
||||
closingEntries.push(revenueClosingEntry);
|
||||
}
|
||||
|
||||
// Create closing entry for expense accounts
|
||||
const expenseLines: IJournalEntryLine[] = [];
|
||||
let totalExpense = 0;
|
||||
|
||||
for (const account of expenseAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(
|
||||
account.accountNumber,
|
||||
periodTransactions,
|
||||
);
|
||||
|
||||
if (balance !== 0) {
|
||||
// Expense accounts have debit balance, so credit to close
|
||||
expenseLines.push({
|
||||
accountNumber: account.accountNumber,
|
||||
credit: Math.abs(balance),
|
||||
description: `Closing ${account.accountName}`,
|
||||
});
|
||||
totalExpense += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalExpense > 0) {
|
||||
// Debit the closing account
|
||||
expenseLines.push({
|
||||
accountNumber: closingAccountNumber,
|
||||
debit: totalExpense,
|
||||
description: 'Expense closing to P&L',
|
||||
});
|
||||
|
||||
const expenseClosingEntry = await this.postJournalEntry({
|
||||
date: new Date(),
|
||||
description: `Closing expense accounts for period ${period}`,
|
||||
reference: `CLOSE-EXP-${period}`,
|
||||
lines: expenseLines,
|
||||
skrType: this.skrType,
|
||||
});
|
||||
|
||||
closingEntries.push(expenseClosingEntry);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Period ${period} closed with ${closingEntries.length} entries`,
|
||||
);
|
||||
return closingEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate account balance for a specific set of transactions
|
||||
*/
|
||||
private async getAccountBalanceForPeriod(
|
||||
accountNumber: string,
|
||||
transactions: Transaction[],
|
||||
): Promise<number> {
|
||||
const account = await Account.getAccountByNumber(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
if (!account) return 0;
|
||||
|
||||
let debitTotal = 0;
|
||||
let creditTotal = 0;
|
||||
|
||||
for (const transaction of transactions) {
|
||||
if (transaction.debitAccount === accountNumber) {
|
||||
debitTotal += transaction.amount;
|
||||
}
|
||||
if (transaction.creditAccount === accountNumber) {
|
||||
creditTotal += transaction.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate net balance based on account type
|
||||
switch (account.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
return debitTotal - creditTotal;
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
return creditTotal - debitTotal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate double-entry rules
|
||||
*/
|
||||
public validateDoubleEntry(
|
||||
debitAmount: number,
|
||||
creditAmount: number,
|
||||
): boolean {
|
||||
return Math.abs(debitAmount - creditAmount) < 0.01;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unbalanced transactions (for audit)
|
||||
*/
|
||||
public async getUnbalancedTransactions(): Promise<Transaction[]> {
|
||||
// In a proper double-entry system, all posted transactions should be balanced
|
||||
// This method is mainly for audit purposes
|
||||
const allTransactions = await Transaction.getInstances({
|
||||
skrType: this.skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
|
||||
// Group transactions by journal entry if they have one
|
||||
const unbalanced: Transaction[] = [];
|
||||
|
||||
// Since our system ensures balance at posting time,
|
||||
// this should typically return an empty array
|
||||
// But we include it for completeness and audit purposes
|
||||
|
||||
return unbalanced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all account balances
|
||||
*/
|
||||
public async recalculateAllBalances(): Promise<void> {
|
||||
this.logger.log('info', 'Recalculating all account balances');
|
||||
|
||||
// Get all accounts
|
||||
const accounts = await Account.getInstances({ skrType: this.skrType });
|
||||
|
||||
for (const account of accounts) {
|
||||
// Reset balances
|
||||
account.debitTotal = 0;
|
||||
account.creditTotal = 0;
|
||||
account.balance = 0;
|
||||
|
||||
// Get all transactions for this account
|
||||
const transactions = await Transaction.getTransactionsByAccount(
|
||||
account.accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Recalculate totals
|
||||
for (const transaction of transactions) {
|
||||
if (transaction.debitAccount === account.accountNumber) {
|
||||
account.debitTotal += transaction.amount;
|
||||
}
|
||||
if (transaction.creditAccount === account.accountNumber) {
|
||||
account.creditTotal += transaction.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate balance based on account type
|
||||
switch (account.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
account.balance = account.debitTotal - account.creditTotal;
|
||||
break;
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
account.balance = account.creditTotal - account.debitTotal;
|
||||
break;
|
||||
}
|
||||
|
||||
account.updatedAt = new Date();
|
||||
await account.save();
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Recalculated balances for ${accounts.length} accounts`,
|
||||
);
|
||||
}
|
||||
}
|
721
ts/skr.classes.reports.ts
Normal file
721
ts/skr.classes.reports.ts
Normal file
@@ -0,0 +1,721 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { Account } from './skr.classes.account.js';
|
||||
import { Transaction } from './skr.classes.transaction.js';
|
||||
import { Ledger } from './skr.classes.ledger.js';
|
||||
import type {
|
||||
TSKRType,
|
||||
ITrialBalanceReport,
|
||||
ITrialBalanceEntry,
|
||||
IIncomeStatement,
|
||||
IIncomeStatementEntry,
|
||||
IBalanceSheet,
|
||||
IBalanceSheetEntry,
|
||||
IReportParams,
|
||||
} from './skr.types.js';
|
||||
|
||||
export class Reports {
|
||||
private logger: plugins.smartlog.Smartlog;
|
||||
private ledger: Ledger;
|
||||
|
||||
constructor(private skrType: TSKRType) {
|
||||
this.logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: 'fin.cx',
|
||||
companyunit: 'skr',
|
||||
containerName: 'Reports',
|
||||
environment: 'local',
|
||||
runtime: 'node',
|
||||
zone: 'local',
|
||||
},
|
||||
});
|
||||
this.ledger = new Ledger(skrType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Trial Balance
|
||||
*/
|
||||
public async getTrialBalance(
|
||||
params?: IReportParams,
|
||||
): Promise<ITrialBalanceReport> {
|
||||
this.logger.log('info', 'Generating trial balance');
|
||||
|
||||
const accounts = await Account.getInstances({
|
||||
skrType: this.skrType,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const entries: ITrialBalanceEntry[] = [];
|
||||
let totalDebits = 0;
|
||||
let totalCredits = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
// Get balance for the period if specified
|
||||
const balance = params?.dateTo
|
||||
? await this.ledger.getAccountBalance(
|
||||
account.accountNumber,
|
||||
params.dateTo,
|
||||
)
|
||||
: await this.ledger.getAccountBalance(account.accountNumber);
|
||||
|
||||
if (balance.debitTotal !== 0 || balance.creditTotal !== 0) {
|
||||
const entry: ITrialBalanceEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
debitBalance: balance.debitTotal,
|
||||
creditBalance: balance.creditTotal,
|
||||
netBalance: balance.balance,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
totalDebits += balance.debitTotal;
|
||||
totalCredits += balance.creditTotal;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort entries by account number
|
||||
entries.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
|
||||
|
||||
const report: ITrialBalanceReport = {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
entries,
|
||||
totalDebits,
|
||||
totalCredits,
|
||||
isBalanced: Math.abs(totalDebits - totalCredits) < 0.01,
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Trial balance generated with ${entries.length} accounts`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Income Statement (P&L)
|
||||
*/
|
||||
public async getIncomeStatement(
|
||||
params?: IReportParams,
|
||||
): Promise<IIncomeStatement> {
|
||||
this.logger.log('info', 'Generating income statement');
|
||||
|
||||
// Get revenue accounts
|
||||
const revenueAccounts = await Account.getAccountsByType(
|
||||
'revenue',
|
||||
this.skrType,
|
||||
);
|
||||
const expenseAccounts = await Account.getAccountsByType(
|
||||
'expense',
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
const revenueEntries: IIncomeStatementEntry[] = [];
|
||||
const expenseEntries: IIncomeStatementEntry[] = [];
|
||||
let totalRevenue = 0;
|
||||
let totalExpenses = 0;
|
||||
|
||||
// Process revenue accounts
|
||||
for (const account of revenueAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IIncomeStatementEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
revenueEntries.push(entry);
|
||||
totalRevenue += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Process expense accounts
|
||||
for (const account of expenseAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IIncomeStatementEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
expenseEntries.push(entry);
|
||||
totalExpenses += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
revenueEntries.forEach((entry) => {
|
||||
entry.percentage =
|
||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
||||
});
|
||||
|
||||
expenseEntries.forEach((entry) => {
|
||||
entry.percentage =
|
||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
||||
});
|
||||
|
||||
// Sort entries by account number
|
||||
revenueEntries.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
expenseEntries.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
|
||||
const report: IIncomeStatement = {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
revenue: revenueEntries,
|
||||
expenses: expenseEntries,
|
||||
totalRevenue,
|
||||
totalExpenses,
|
||||
netIncome: totalRevenue - totalExpenses,
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Income statement generated: Revenue ${totalRevenue}, Expenses ${totalExpenses}`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Balance Sheet
|
||||
*/
|
||||
public async getBalanceSheet(params?: IReportParams): Promise<IBalanceSheet> {
|
||||
this.logger.log('info', 'Generating balance sheet');
|
||||
|
||||
// Get accounts by type
|
||||
const assetAccounts = await Account.getAccountsByType(
|
||||
'asset',
|
||||
this.skrType,
|
||||
);
|
||||
const liabilityAccounts = await Account.getAccountsByType(
|
||||
'liability',
|
||||
this.skrType,
|
||||
);
|
||||
const equityAccounts = await Account.getAccountsByType(
|
||||
'equity',
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Process assets
|
||||
const currentAssets: IBalanceSheetEntry[] = [];
|
||||
const fixedAssets: IBalanceSheetEntry[] = [];
|
||||
let totalAssets = 0;
|
||||
|
||||
for (const account of assetAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
// Classify as current or fixed based on account class
|
||||
if (account.accountClass === 1) {
|
||||
currentAssets.push(entry);
|
||||
} else {
|
||||
fixedAssets.push(entry);
|
||||
}
|
||||
|
||||
totalAssets += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Process liabilities
|
||||
const currentLiabilities: IBalanceSheetEntry[] = [];
|
||||
const longTermLiabilities: IBalanceSheetEntry[] = [];
|
||||
let totalLiabilities = 0;
|
||||
|
||||
for (const account of liabilityAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
// Classify as current or long-term based on account number
|
||||
if (
|
||||
account.accountNumber.startsWith('16') ||
|
||||
account.accountNumber.startsWith('17')
|
||||
) {
|
||||
currentLiabilities.push(entry);
|
||||
} else {
|
||||
longTermLiabilities.push(entry);
|
||||
}
|
||||
|
||||
totalLiabilities += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Process equity
|
||||
const equityEntries: IBalanceSheetEntry[] = [];
|
||||
let totalEquity = 0;
|
||||
|
||||
for (const account of equityAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
equityEntries.push(entry);
|
||||
totalEquity += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Add current year profit/loss
|
||||
const incomeStatement = await this.getIncomeStatement(params);
|
||||
if (incomeStatement.netIncome !== 0) {
|
||||
equityEntries.push({
|
||||
accountNumber: '9999',
|
||||
accountName: 'Current Year Profit/Loss',
|
||||
amount: Math.abs(incomeStatement.netIncome),
|
||||
});
|
||||
totalEquity += Math.abs(incomeStatement.netIncome);
|
||||
}
|
||||
|
||||
// Sort entries
|
||||
currentAssets.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
fixedAssets.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
|
||||
currentLiabilities.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
longTermLiabilities.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
equityEntries.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
|
||||
const report: IBalanceSheet = {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
assets: {
|
||||
current: currentAssets,
|
||||
fixed: fixedAssets,
|
||||
totalAssets,
|
||||
},
|
||||
liabilities: {
|
||||
current: currentLiabilities,
|
||||
longTerm: longTermLiabilities,
|
||||
totalLiabilities,
|
||||
},
|
||||
equity: {
|
||||
entries: equityEntries,
|
||||
totalEquity,
|
||||
},
|
||||
isBalanced:
|
||||
Math.abs(totalAssets - (totalLiabilities + totalEquity)) < 0.01,
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Balance sheet generated: Assets ${totalAssets}, Liabilities ${totalLiabilities}, Equity ${totalEquity}`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account balance for a specific period
|
||||
*/
|
||||
private async getAccountBalanceForPeriod(
|
||||
account: Account,
|
||||
params?: IReportParams,
|
||||
): Promise<number> {
|
||||
let transactions = await Transaction.getTransactionsByAccount(
|
||||
account.accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Apply date filter if provided
|
||||
if (params?.dateFrom || params?.dateTo) {
|
||||
transactions = transactions.filter((transaction) => {
|
||||
if (params.dateFrom && transaction.date < params.dateFrom) return false;
|
||||
if (params.dateTo && transaction.date > params.dateTo) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
let debitTotal = 0;
|
||||
let creditTotal = 0;
|
||||
|
||||
for (const transaction of transactions) {
|
||||
if (transaction.debitAccount === account.accountNumber) {
|
||||
debitTotal += transaction.amount;
|
||||
}
|
||||
if (transaction.creditAccount === account.accountNumber) {
|
||||
creditTotal += transaction.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate net balance based on account type
|
||||
switch (account.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
return debitTotal - creditTotal;
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
return creditTotal - debitTotal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate General Ledger report
|
||||
*/
|
||||
public async getGeneralLedger(params?: IReportParams): Promise<any> {
|
||||
this.logger.log('info', 'Generating general ledger');
|
||||
|
||||
const accounts = await Account.getInstances({
|
||||
skrType: this.skrType,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const ledgerEntries = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const transactions = await this.getAccountTransactions(
|
||||
account.accountNumber,
|
||||
params,
|
||||
);
|
||||
|
||||
if (transactions.length > 0) {
|
||||
let runningBalance = 0;
|
||||
const accountEntries = [];
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const isDebit = transaction.debitAccount === account.accountNumber;
|
||||
const amount = transaction.amount;
|
||||
|
||||
// Update running balance based on account type
|
||||
if (
|
||||
account.accountType === 'asset' ||
|
||||
account.accountType === 'expense'
|
||||
) {
|
||||
runningBalance += isDebit ? amount : -amount;
|
||||
} else {
|
||||
runningBalance += isDebit ? -amount : amount;
|
||||
}
|
||||
|
||||
accountEntries.push({
|
||||
date: transaction.date,
|
||||
reference: transaction.reference,
|
||||
description: transaction.description,
|
||||
debit: isDebit ? amount : 0,
|
||||
credit: !isDebit ? amount : 0,
|
||||
balance: runningBalance,
|
||||
});
|
||||
}
|
||||
|
||||
ledgerEntries.push({
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
accountType: account.accountType,
|
||||
entries: accountEntries,
|
||||
finalBalance: runningBalance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
accounts: ledgerEntries,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account transactions for reporting
|
||||
*/
|
||||
private async getAccountTransactions(
|
||||
accountNumber: string,
|
||||
params?: IReportParams,
|
||||
): Promise<Transaction[]> {
|
||||
let transactions = await Transaction.getTransactionsByAccount(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Apply date filter
|
||||
if (params?.dateFrom || params?.dateTo) {
|
||||
transactions = transactions.filter((transaction) => {
|
||||
if (params.dateFrom && transaction.date < params.dateFrom) return false;
|
||||
if (params.dateTo && transaction.date > params.dateTo) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by date
|
||||
transactions.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Cash Flow Statement
|
||||
*/
|
||||
public async getCashFlowStatement(params?: IReportParams): Promise<any> {
|
||||
this.logger.log('info', 'Generating cash flow statement');
|
||||
|
||||
// Get cash and bank accounts
|
||||
const cashAccounts = ['1000', '1100', '1200', '1210']; // Standard cash/bank accounts
|
||||
|
||||
let operatingCashFlow = 0;
|
||||
let investingCashFlow = 0;
|
||||
let financingCashFlow = 0;
|
||||
|
||||
for (const accountNumber of cashAccounts) {
|
||||
const account = await Account.getAccountByNumber(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
if (!account) continue;
|
||||
|
||||
const transactions = await this.getAccountTransactions(
|
||||
accountNumber,
|
||||
params,
|
||||
);
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const otherAccount =
|
||||
transaction.debitAccount === accountNumber
|
||||
? transaction.creditAccount
|
||||
: transaction.debitAccount;
|
||||
|
||||
const otherAccountObj = await Account.getAccountByNumber(
|
||||
otherAccount,
|
||||
this.skrType,
|
||||
);
|
||||
if (!otherAccountObj) continue;
|
||||
|
||||
const amount =
|
||||
transaction.debitAccount === accountNumber
|
||||
? transaction.amount
|
||||
: -transaction.amount;
|
||||
|
||||
// Classify cash flow
|
||||
if (
|
||||
otherAccountObj.accountType === 'revenue' ||
|
||||
otherAccountObj.accountType === 'expense'
|
||||
) {
|
||||
operatingCashFlow += amount;
|
||||
} else if (otherAccountObj.accountClass === 0) {
|
||||
// Fixed assets
|
||||
investingCashFlow += amount;
|
||||
} else if (
|
||||
otherAccountObj.accountType === 'liability' ||
|
||||
otherAccountObj.accountType === 'equity'
|
||||
) {
|
||||
financingCashFlow += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
operatingActivities: operatingCashFlow,
|
||||
investingActivities: investingCashFlow,
|
||||
financingActivities: financingCashFlow,
|
||||
netCashFlow: operatingCashFlow + investingCashFlow + financingCashFlow,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export report to CSV format
|
||||
*/
|
||||
public async exportToCSV(
|
||||
reportType: 'trial_balance' | 'income_statement' | 'balance_sheet',
|
||||
params?: IReportParams,
|
||||
): Promise<string> {
|
||||
let csvContent = '';
|
||||
|
||||
switch (reportType) {
|
||||
case 'trial_balance':
|
||||
const trialBalance = await this.getTrialBalance(params);
|
||||
csvContent = this.trialBalanceToCSV(trialBalance);
|
||||
break;
|
||||
|
||||
case 'income_statement':
|
||||
const incomeStatement = await this.getIncomeStatement(params);
|
||||
csvContent = this.incomeStatementToCSV(incomeStatement);
|
||||
break;
|
||||
|
||||
case 'balance_sheet':
|
||||
const balanceSheet = await this.getBalanceSheet(params);
|
||||
csvContent = this.balanceSheetToCSV(balanceSheet);
|
||||
break;
|
||||
}
|
||||
|
||||
return csvContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert trial balance to CSV
|
||||
*/
|
||||
private trialBalanceToCSV(report: ITrialBalanceReport): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('"Account Number";"Account Name";"Debit";"Credit";"Balance"');
|
||||
|
||||
for (const entry of report.entries) {
|
||||
lines.push(
|
||||
`"${entry.accountNumber}";"${entry.accountName}";${entry.debitBalance};${entry.creditBalance};${entry.netBalance}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`"TOTAL";"";"${report.totalDebits}";"${report.totalCredits}";"""`,
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert income statement to CSV
|
||||
*/
|
||||
private incomeStatementToCSV(report: IIncomeStatement): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('"Type";"Account Number";"Account Name";"Amount";"Percentage"');
|
||||
|
||||
lines.push('"REVENUE";"";"";"";""');
|
||||
for (const entry of report.revenue) {
|
||||
lines.push(
|
||||
`"Revenue";"${entry.accountNumber}";"${entry.accountName}";${entry.amount};${entry.percentage?.toFixed(2)}%`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Revenue";"";"";"${report.totalRevenue}";"""`);
|
||||
lines.push('"";"";"";"";""');
|
||||
|
||||
lines.push('"EXPENSES";"";"";"";""');
|
||||
for (const entry of report.expenses) {
|
||||
lines.push(
|
||||
`"Expense";"${entry.accountNumber}";"${entry.accountName}";${entry.amount};${entry.percentage?.toFixed(2)}%`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Expenses";"";"";"${report.totalExpenses}";"""`);
|
||||
lines.push('"";"";"";"";""');
|
||||
lines.push(`"NET INCOME";"";"";"${report.netIncome}";"""`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert balance sheet to CSV
|
||||
*/
|
||||
private balanceSheetToCSV(report: IBalanceSheet): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('"Category";"Account Number";"Account Name";"Amount"');
|
||||
|
||||
lines.push('"ASSETS";"";"";"";');
|
||||
lines.push('"Current Assets";"";"";"";');
|
||||
for (const entry of report.assets.current) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push('"Fixed Assets";"";"";"";');
|
||||
for (const entry of report.assets.fixed) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Assets";"";"";"${report.assets.totalAssets}"`);
|
||||
lines.push('"";"";"";"";');
|
||||
|
||||
lines.push('"LIABILITIES";"";"";"";');
|
||||
lines.push('"Current Liabilities";"";"";"";');
|
||||
for (const entry of report.liabilities.current) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push('"Long-term Liabilities";"";"";"";');
|
||||
for (const entry of report.liabilities.longTerm) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`"Total Liabilities";"";"";"${report.liabilities.totalLiabilities}"`,
|
||||
);
|
||||
lines.push('"";"";"";"";');
|
||||
|
||||
lines.push('"EQUITY";"";"";"";');
|
||||
for (const entry of report.equity.entries) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Equity";"";"";"${report.equity.totalEquity}"`);
|
||||
lines.push('"";"";"";"";');
|
||||
lines.push(
|
||||
`"Total Liabilities + Equity";"";"";"${report.liabilities.totalLiabilities + report.equity.totalEquity}"`,
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export to DATEV format
|
||||
*/
|
||||
public async exportToDATEV(params?: IReportParams): Promise<string> {
|
||||
// DATEV format is specific to German accounting software
|
||||
// This is a simplified implementation
|
||||
const transactions = await Transaction.getInstances({
|
||||
skrType: this.skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
// DATEV header
|
||||
lines.push('EXTF;510;21;"Buchungsstapel";1;;;;;;;;;;;;;;');
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const date = transaction.date
|
||||
.toISOString()
|
||||
.split('T')[0]
|
||||
.replace(/-/g, '');
|
||||
const line = [
|
||||
transaction.amount.toFixed(2).replace('.', ','),
|
||||
'S',
|
||||
'EUR',
|
||||
'',
|
||||
'',
|
||||
transaction.debitAccount,
|
||||
transaction.creditAccount,
|
||||
'',
|
||||
date,
|
||||
'',
|
||||
transaction.description.substring(0, 60),
|
||||
'',
|
||||
].join(';');
|
||||
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
300
ts/skr.classes.transaction.ts
Normal file
300
ts/skr.classes.transaction.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { getDbSync } from './skr.database.js';
|
||||
import { Account } from './skr.classes.account.js';
|
||||
import type {
|
||||
TSKRType,
|
||||
TTransactionStatus,
|
||||
ITransactionData,
|
||||
} from './skr.types.js';
|
||||
|
||||
const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata;
|
||||
|
||||
@plugins.smartdata.Collection(() => getDbSync())
|
||||
export class Transaction extends SmartDataDbDoc<Transaction, Transaction> {
|
||||
@unI()
|
||||
public id: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public transactionNumber: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public date: Date;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public debitAccount: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public creditAccount: string;
|
||||
|
||||
@svDb()
|
||||
public amount: number;
|
||||
|
||||
@svDb()
|
||||
@searchable()
|
||||
public description: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public reference: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public skrType: TSKRType;
|
||||
|
||||
@svDb()
|
||||
public vatAmount: number;
|
||||
|
||||
@svDb()
|
||||
public costCenter: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public status: TTransactionStatus;
|
||||
|
||||
@svDb()
|
||||
public reversalOf: string;
|
||||
|
||||
@svDb()
|
||||
public reversedBy: string;
|
||||
|
||||
@svDb()
|
||||
@index()
|
||||
public period: string; // Format: YYYY-MM
|
||||
|
||||
@svDb()
|
||||
public fiscalYear: number;
|
||||
|
||||
@svDb()
|
||||
public createdAt: Date;
|
||||
|
||||
@svDb()
|
||||
public postedAt: Date;
|
||||
|
||||
@svDb()
|
||||
public createdBy: string;
|
||||
|
||||
constructor(data?: Partial<ITransactionData>) {
|
||||
super();
|
||||
|
||||
if (data) {
|
||||
this.id = plugins.smartunique.shortId();
|
||||
this.transactionNumber = this.generateTransactionNumber();
|
||||
this.date = data.date || new Date();
|
||||
this.debitAccount = data.debitAccount || '';
|
||||
this.creditAccount = data.creditAccount || '';
|
||||
this.amount = data.amount || 0;
|
||||
this.description = data.description || '';
|
||||
this.reference = data.reference || '';
|
||||
this.skrType = data.skrType || 'SKR03';
|
||||
this.vatAmount = data.vatAmount || 0;
|
||||
this.costCenter = data.costCenter || '';
|
||||
this.status = 'pending';
|
||||
this.reversalOf = '';
|
||||
this.reversedBy = '';
|
||||
|
||||
// Set period and fiscal year
|
||||
const transDate = new Date(this.date);
|
||||
this.period = `${transDate.getFullYear()}-${String(transDate.getMonth() + 1).padStart(2, '0')}`;
|
||||
this.fiscalYear = transDate.getFullYear();
|
||||
|
||||
this.createdAt = new Date();
|
||||
this.postedAt = null;
|
||||
this.createdBy = 'system';
|
||||
}
|
||||
}
|
||||
|
||||
private generateTransactionNumber(): string {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000);
|
||||
return `TXN-${timestamp}-${random}`;
|
||||
}
|
||||
|
||||
public static async createTransaction(
|
||||
data: ITransactionData,
|
||||
): Promise<Transaction> {
|
||||
const transaction = new Transaction(data);
|
||||
await transaction.validateAndPost();
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public static async getTransactionById(
|
||||
id: string,
|
||||
): Promise<Transaction | null> {
|
||||
const transaction = await Transaction.getInstance({ id });
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public static async getTransactionsByAccount(
|
||||
accountNumber: string,
|
||||
skrType: TSKRType,
|
||||
): Promise<Transaction[]> {
|
||||
const transactionsDebit = await Transaction.getInstances({
|
||||
debitAccount: accountNumber,
|
||||
skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
const transactionsCredit = await Transaction.getInstances({
|
||||
creditAccount: accountNumber,
|
||||
skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
const transactions = [...transactionsDebit, ...transactionsCredit];
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public static async getTransactionsByPeriod(
|
||||
period: string,
|
||||
skrType: TSKRType,
|
||||
): Promise<Transaction[]> {
|
||||
const transactions = await Transaction.getInstances({
|
||||
period,
|
||||
skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public static async getTransactionsByDateRange(
|
||||
dateFrom: Date,
|
||||
dateTo: Date,
|
||||
skrType: TSKRType,
|
||||
): Promise<Transaction[]> {
|
||||
const allTransactions = await Transaction.getInstances({
|
||||
skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
const transactions = allTransactions.filter(
|
||||
(t) => t.date >= dateFrom && t.date <= dateTo,
|
||||
);
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public async validateAndPost(): Promise<void> {
|
||||
// Validate transaction
|
||||
await this.validateTransaction();
|
||||
|
||||
// Update account balances
|
||||
await this.updateAccountBalances();
|
||||
|
||||
// Mark as posted
|
||||
this.status = 'posted';
|
||||
this.postedAt = new Date();
|
||||
|
||||
await this.save();
|
||||
}
|
||||
|
||||
private async validateTransaction(): Promise<void> {
|
||||
// Check if accounts exist
|
||||
const debitAccount = await Account.getAccountByNumber(
|
||||
this.debitAccount,
|
||||
this.skrType,
|
||||
);
|
||||
const creditAccount = await Account.getAccountByNumber(
|
||||
this.creditAccount,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
if (!debitAccount) {
|
||||
throw new Error(
|
||||
`Debit account ${this.debitAccount} not found for ${this.skrType}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!creditAccount) {
|
||||
throw new Error(
|
||||
`Credit account ${this.creditAccount} not found for ${this.skrType}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if accounts are active
|
||||
if (!debitAccount.isActive) {
|
||||
throw new Error(`Debit account ${this.debitAccount} is not active`);
|
||||
}
|
||||
|
||||
if (!creditAccount.isActive) {
|
||||
throw new Error(`Credit account ${this.creditAccount} is not active`);
|
||||
}
|
||||
|
||||
// Validate amount
|
||||
if (this.amount <= 0) {
|
||||
throw new Error('Transaction amount must be greater than zero');
|
||||
}
|
||||
|
||||
// Check for same account
|
||||
if (this.debitAccount === this.creditAccount) {
|
||||
throw new Error('Debit and credit accounts cannot be the same');
|
||||
}
|
||||
}
|
||||
|
||||
private async updateAccountBalances(): Promise<void> {
|
||||
const debitAccount = await Account.getAccountByNumber(
|
||||
this.debitAccount,
|
||||
this.skrType,
|
||||
);
|
||||
const creditAccount = await Account.getAccountByNumber(
|
||||
this.creditAccount,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
if (debitAccount) {
|
||||
await debitAccount.updateBalance(this.amount, 0);
|
||||
}
|
||||
|
||||
if (creditAccount) {
|
||||
await creditAccount.updateBalance(0, this.amount);
|
||||
}
|
||||
}
|
||||
|
||||
public async reverseTransaction(): Promise<Transaction> {
|
||||
if (this.status !== 'posted') {
|
||||
throw new Error('Can only reverse posted transactions');
|
||||
}
|
||||
|
||||
if (this.reversedBy) {
|
||||
throw new Error('Transaction has already been reversed');
|
||||
}
|
||||
|
||||
// Create reversal transaction
|
||||
const reversalData: ITransactionData = {
|
||||
date: new Date(),
|
||||
debitAccount: this.creditAccount, // Swap accounts
|
||||
creditAccount: this.debitAccount, // Swap accounts
|
||||
amount: this.amount,
|
||||
description: `Reversal of ${this.transactionNumber}: ${this.description}`,
|
||||
reference: `REV-${this.transactionNumber}`,
|
||||
skrType: this.skrType,
|
||||
vatAmount: this.vatAmount,
|
||||
costCenter: this.costCenter,
|
||||
};
|
||||
|
||||
const reversalTransaction = new Transaction(reversalData);
|
||||
reversalTransaction.reversalOf = this.id;
|
||||
await reversalTransaction.validateAndPost();
|
||||
|
||||
// Update original transaction
|
||||
this.reversedBy = reversalTransaction.id;
|
||||
this.status = 'reversed';
|
||||
await this.save();
|
||||
|
||||
return reversalTransaction;
|
||||
}
|
||||
|
||||
public async beforeSave(): Promise<void> {
|
||||
// Additional validation before saving
|
||||
if (!this.debitAccount || !this.creditAccount) {
|
||||
throw new Error('Both debit and credit accounts are required');
|
||||
}
|
||||
|
||||
if (!this.date) {
|
||||
throw new Error('Transaction date is required');
|
||||
}
|
||||
|
||||
if (!this.description) {
|
||||
throw new Error('Transaction description is required');
|
||||
}
|
||||
}
|
||||
}
|
39
ts/skr.database.ts
Normal file
39
ts/skr.database.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import type { IDatabaseConfig } from './skr.types.js';
|
||||
|
||||
let dbInstance: plugins.smartdata.SmartdataDb | null = null;
|
||||
|
||||
export const getDb = async (
|
||||
config?: IDatabaseConfig,
|
||||
): Promise<plugins.smartdata.SmartdataDb> => {
|
||||
if (!dbInstance) {
|
||||
if (!config) {
|
||||
throw new Error(
|
||||
'Database configuration required for first initialization',
|
||||
);
|
||||
}
|
||||
|
||||
dbInstance = new plugins.smartdata.SmartdataDb({
|
||||
mongoDbUrl: config.mongoDbUrl,
|
||||
mongoDbName: config.dbName || 'skr_accounting',
|
||||
});
|
||||
|
||||
await dbInstance.init();
|
||||
}
|
||||
|
||||
return dbInstance;
|
||||
};
|
||||
|
||||
export const getDbSync = (): plugins.smartdata.SmartdataDb => {
|
||||
if (!dbInstance) {
|
||||
throw new Error('Database not initialized. Call getDb() first.');
|
||||
}
|
||||
return dbInstance;
|
||||
};
|
||||
|
||||
export const closeDb = async (): Promise<void> => {
|
||||
if (dbInstance) {
|
||||
await dbInstance.close();
|
||||
dbInstance = null;
|
||||
}
|
||||
};
|
154
ts/skr.types.ts
Normal file
154
ts/skr.types.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
export type TAccountType =
|
||||
| 'asset'
|
||||
| 'liability'
|
||||
| 'equity'
|
||||
| 'revenue'
|
||||
| 'expense';
|
||||
|
||||
export type TSKRType = 'SKR03' | 'SKR04';
|
||||
|
||||
export type TTransactionStatus = 'pending' | 'posted' | 'reversed';
|
||||
|
||||
export type TReportType =
|
||||
| 'trial_balance'
|
||||
| 'income_statement'
|
||||
| 'balance_sheet'
|
||||
| 'general_ledger'
|
||||
| 'cash_flow';
|
||||
|
||||
export interface IAccountData {
|
||||
accountNumber: string;
|
||||
accountName: string;
|
||||
accountClass: number;
|
||||
accountType: TAccountType;
|
||||
skrType: TSKRType;
|
||||
description?: string;
|
||||
vatRate?: number;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface ITransactionData {
|
||||
date: Date;
|
||||
debitAccount: string;
|
||||
creditAccount: string;
|
||||
amount: number;
|
||||
description: string;
|
||||
reference?: string;
|
||||
skrType: TSKRType;
|
||||
vatAmount?: number;
|
||||
costCenter?: string;
|
||||
}
|
||||
|
||||
export interface IJournalEntry {
|
||||
date: Date;
|
||||
description: string;
|
||||
reference?: string;
|
||||
lines: IJournalEntryLine[];
|
||||
skrType: TSKRType;
|
||||
}
|
||||
|
||||
export interface IJournalEntryLine {
|
||||
accountNumber: string;
|
||||
debit?: number;
|
||||
credit?: number;
|
||||
description?: string;
|
||||
costCenter?: string;
|
||||
}
|
||||
|
||||
export interface ITrialBalanceEntry {
|
||||
accountNumber: string;
|
||||
accountName: string;
|
||||
debitBalance: number;
|
||||
creditBalance: number;
|
||||
netBalance: number;
|
||||
}
|
||||
|
||||
export interface ITrialBalanceReport {
|
||||
date: Date;
|
||||
skrType: TSKRType;
|
||||
entries: ITrialBalanceEntry[];
|
||||
totalDebits: number;
|
||||
totalCredits: number;
|
||||
isBalanced: boolean;
|
||||
}
|
||||
|
||||
export interface IIncomeStatementEntry {
|
||||
accountNumber: string;
|
||||
accountName: string;
|
||||
amount: number;
|
||||
percentage?: number;
|
||||
}
|
||||
|
||||
export interface IIncomeStatement {
|
||||
date: Date;
|
||||
skrType: TSKRType;
|
||||
revenue: IIncomeStatementEntry[];
|
||||
expenses: IIncomeStatementEntry[];
|
||||
totalRevenue: number;
|
||||
totalExpenses: number;
|
||||
netIncome: number;
|
||||
}
|
||||
|
||||
export interface IBalanceSheetEntry {
|
||||
accountNumber: string;
|
||||
accountName: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface IBalanceSheet {
|
||||
date: Date;
|
||||
skrType: TSKRType;
|
||||
assets: {
|
||||
current: IBalanceSheetEntry[];
|
||||
fixed: IBalanceSheetEntry[];
|
||||
totalAssets: number;
|
||||
};
|
||||
liabilities: {
|
||||
current: IBalanceSheetEntry[];
|
||||
longTerm: IBalanceSheetEntry[];
|
||||
totalLiabilities: number;
|
||||
};
|
||||
equity: {
|
||||
entries: IBalanceSheetEntry[];
|
||||
totalEquity: number;
|
||||
};
|
||||
isBalanced: boolean;
|
||||
}
|
||||
|
||||
export interface IAccountFilter {
|
||||
skrType?: TSKRType;
|
||||
accountClass?: number;
|
||||
accountType?: TAccountType;
|
||||
isActive?: boolean;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
export interface ITransactionFilter {
|
||||
skrType?: TSKRType;
|
||||
dateFrom?: Date;
|
||||
dateTo?: Date;
|
||||
accountNumber?: string;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
export interface IDatabaseConfig {
|
||||
mongoDbUrl: string;
|
||||
dbName?: string;
|
||||
}
|
||||
|
||||
export interface IReportParams {
|
||||
dateFrom?: Date;
|
||||
dateTo?: Date;
|
||||
skrType: TSKRType;
|
||||
format?: 'json' | 'csv' | 'datev';
|
||||
}
|
||||
|
||||
export interface IAccountBalance {
|
||||
accountNumber: string;
|
||||
debitTotal: number;
|
||||
creditTotal: number;
|
||||
balance: number;
|
||||
lastUpdated: Date;
|
||||
}
|
901
ts/skr03.data.ts
Normal file
901
ts/skr03.data.ts
Normal file
@@ -0,0 +1,901 @@
|
||||
import type { IAccountData } from './skr.types.js';
|
||||
|
||||
/**
|
||||
* SKR03 - Process Structure Principle (Prozessgliederungsprinzip)
|
||||
* Organized by business process flow
|
||||
*/
|
||||
export const SKR03_ACCOUNTS: IAccountData[] = [
|
||||
// Class 0: Capital Accounts (Anlagekonten)
|
||||
{
|
||||
accountNumber: '0001',
|
||||
accountName: 'Aufwendungen für Ingangsetzung',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Start-up expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '0010',
|
||||
accountName: 'Konzessionen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Concessions',
|
||||
},
|
||||
{
|
||||
accountNumber: '0020',
|
||||
accountName: 'Patente',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Patents',
|
||||
},
|
||||
{
|
||||
accountNumber: '0030',
|
||||
accountName: 'Lizenzen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Licenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '0050',
|
||||
accountName: 'Firmenwert',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Goodwill',
|
||||
},
|
||||
{
|
||||
accountNumber: '0100',
|
||||
accountName: 'EDV-Software',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'IT Software',
|
||||
},
|
||||
{
|
||||
accountNumber: '0200',
|
||||
accountName: 'Grundstücke',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Land and property',
|
||||
},
|
||||
{
|
||||
accountNumber: '0210',
|
||||
accountName: 'Gebäude',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Buildings',
|
||||
},
|
||||
{
|
||||
accountNumber: '0300',
|
||||
accountName: 'Maschinen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Machinery',
|
||||
},
|
||||
{
|
||||
accountNumber: '0400',
|
||||
accountName: 'Fuhrpark',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Vehicles',
|
||||
},
|
||||
{
|
||||
accountNumber: '0500',
|
||||
accountName: 'Betriebs- und Geschäftsausstattung',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Office equipment',
|
||||
},
|
||||
{
|
||||
accountNumber: '0600',
|
||||
accountName: 'Geleistete Anzahlungen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Prepayments on fixed assets',
|
||||
},
|
||||
{
|
||||
accountNumber: '0800',
|
||||
accountName: 'Finanzanlagen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Financial assets',
|
||||
},
|
||||
|
||||
// Class 1: Current Assets (Umlaufvermögen)
|
||||
{
|
||||
accountNumber: '1000',
|
||||
accountName: 'Kasse',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Cash on hand',
|
||||
},
|
||||
{
|
||||
accountNumber: '1100',
|
||||
accountName: 'Postbank',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Postal bank account',
|
||||
},
|
||||
{
|
||||
accountNumber: '1200',
|
||||
accountName: 'Bank',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Bank account',
|
||||
},
|
||||
{
|
||||
accountNumber: '1210',
|
||||
accountName: 'Sparkasse',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Savings bank',
|
||||
},
|
||||
{
|
||||
accountNumber: '1300',
|
||||
accountName: 'Wertpapiere',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Securities',
|
||||
},
|
||||
{
|
||||
accountNumber: '1400',
|
||||
accountName: 'Forderungen aus Lieferungen und Leistungen',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Trade receivables',
|
||||
},
|
||||
{
|
||||
accountNumber: '1500',
|
||||
accountName: 'Sonstige Vermögensgegenstände',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other assets',
|
||||
},
|
||||
{
|
||||
accountNumber: '1520',
|
||||
accountName: 'Abziehbare Vorsteuer',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Input VAT',
|
||||
},
|
||||
{
|
||||
accountNumber: '1570',
|
||||
accountName: 'Vorsteuer 7%',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Input VAT 7%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1571',
|
||||
accountName: 'Vorsteuer 19%',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Input VAT 19%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1600',
|
||||
accountName: 'Verbindlichkeiten aus Lieferungen und Leistungen',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Trade payables',
|
||||
},
|
||||
{
|
||||
accountNumber: '1700',
|
||||
accountName: 'Sonstige Verbindlichkeiten',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other liabilities',
|
||||
},
|
||||
{
|
||||
accountNumber: '1770',
|
||||
accountName: 'Umsatzsteuer 7%',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'VAT payable 7%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1771',
|
||||
accountName: 'Umsatzsteuer 19%',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'VAT payable 19%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1800',
|
||||
accountName: 'Privatentnahmen',
|
||||
accountClass: 1,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Private withdrawals',
|
||||
},
|
||||
{
|
||||
accountNumber: '1810',
|
||||
accountName: 'Privateinlagen',
|
||||
accountClass: 1,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Private deposits',
|
||||
},
|
||||
{
|
||||
accountNumber: '1900',
|
||||
accountName: 'Verrechnungskonto',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR03',
|
||||
description: 'Clearing account',
|
||||
},
|
||||
|
||||
// Class 2: Equity (Eigenkapital)
|
||||
{
|
||||
accountNumber: '2000',
|
||||
accountName: 'Eigenkapital',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Equity capital',
|
||||
},
|
||||
{
|
||||
accountNumber: '2100',
|
||||
accountName: 'Gezeichnetes Kapital',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Subscribed capital',
|
||||
},
|
||||
{
|
||||
accountNumber: '2200',
|
||||
accountName: 'Kapitalrücklage',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Capital reserves',
|
||||
},
|
||||
{
|
||||
accountNumber: '2300',
|
||||
accountName: 'Gewinnrücklagen',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Revenue reserves',
|
||||
},
|
||||
{
|
||||
accountNumber: '2400',
|
||||
accountName: 'Gewinnvortrag',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Profit carried forward',
|
||||
},
|
||||
{
|
||||
accountNumber: '2500',
|
||||
accountName: 'Verlustvortrag',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Loss carried forward',
|
||||
},
|
||||
{
|
||||
accountNumber: '2600',
|
||||
accountName: 'Jahresüberschuss',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Annual surplus',
|
||||
},
|
||||
{
|
||||
accountNumber: '2700',
|
||||
accountName: 'Jahresfehlbetrag',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Annual deficit',
|
||||
},
|
||||
{
|
||||
accountNumber: '2900',
|
||||
accountName: 'Sonderposten mit Rücklageanteil',
|
||||
accountClass: 2,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Special items with reserve portion',
|
||||
},
|
||||
|
||||
// Class 3: Provisions and Liabilities (Rückstellungen und Verbindlichkeiten)
|
||||
{
|
||||
accountNumber: '3000',
|
||||
accountName: 'Rückstellungen für Pensionen',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Pension provisions',
|
||||
},
|
||||
{
|
||||
accountNumber: '3100',
|
||||
accountName: 'Steuerrückstellungen',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Tax provisions',
|
||||
},
|
||||
{
|
||||
accountNumber: '3200',
|
||||
accountName: 'Sonstige Rückstellungen',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other provisions',
|
||||
},
|
||||
{
|
||||
accountNumber: '3300',
|
||||
accountName: 'Verbindlichkeiten gegenüber Kreditinstituten',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Bank loans',
|
||||
},
|
||||
{
|
||||
accountNumber: '3400',
|
||||
accountName: 'Erhaltene Anzahlungen',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Advance payments received',
|
||||
},
|
||||
{
|
||||
accountNumber: '3500',
|
||||
accountName: 'Verbindlichkeiten aus Steuern',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Tax liabilities',
|
||||
},
|
||||
{
|
||||
accountNumber: '3600',
|
||||
accountName: 'Verbindlichkeiten im Rahmen der sozialen Sicherheit',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Social security liabilities',
|
||||
},
|
||||
{
|
||||
accountNumber: '3700',
|
||||
accountName: 'Sonstige Verbindlichkeiten',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other liabilities',
|
||||
},
|
||||
{
|
||||
accountNumber: '3900',
|
||||
accountName: 'Passive Rechnungsabgrenzung',
|
||||
accountClass: 3,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR03',
|
||||
description: 'Deferred income',
|
||||
},
|
||||
|
||||
// Class 4: Operating Income (Betriebliche Erträge)
|
||||
{
|
||||
accountNumber: '4000',
|
||||
accountName: 'Umsatzerlöse',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Sales revenue',
|
||||
vatRate: 19,
|
||||
},
|
||||
{
|
||||
accountNumber: '4100',
|
||||
accountName: 'steuerfreie Umsätze',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Tax-free sales',
|
||||
},
|
||||
{
|
||||
accountNumber: '4200',
|
||||
accountName: 'Erlöse 7% USt',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Revenue 7% VAT',
|
||||
vatRate: 7,
|
||||
},
|
||||
{
|
||||
accountNumber: '4300',
|
||||
accountName: 'Erlöse 19% USt',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Revenue 19% VAT',
|
||||
vatRate: 19,
|
||||
},
|
||||
{
|
||||
accountNumber: '4400',
|
||||
accountName: 'Erlöse innergemeinschaftliche Lieferungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'EU sales',
|
||||
},
|
||||
{
|
||||
accountNumber: '4500',
|
||||
accountName: 'Erlöse Export',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Export sales',
|
||||
},
|
||||
{
|
||||
accountNumber: '4600',
|
||||
accountName: 'Bestandsveränderungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Inventory changes',
|
||||
},
|
||||
{
|
||||
accountNumber: '4700',
|
||||
accountName: 'Aktivierte Eigenleistungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Capitalized own work',
|
||||
},
|
||||
{
|
||||
accountNumber: '4800',
|
||||
accountName: 'Sonstige betriebliche Erträge',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other operating income',
|
||||
},
|
||||
{
|
||||
accountNumber: '4900',
|
||||
accountName: 'Erträge aus Beteiligungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Investment income',
|
||||
},
|
||||
|
||||
// Class 5: Material Costs (Materialkosten)
|
||||
{
|
||||
accountNumber: '5000',
|
||||
accountName: 'Aufwendungen für Roh-, Hilfs- und Betriebsstoffe',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Raw materials and supplies',
|
||||
},
|
||||
{
|
||||
accountNumber: '5100',
|
||||
accountName: 'Einkauf Waren',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Purchase of goods',
|
||||
},
|
||||
{
|
||||
accountNumber: '5200',
|
||||
accountName: 'Wareneingang 7% Vorsteuer',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Goods receipt 7% input tax',
|
||||
vatRate: 7,
|
||||
},
|
||||
{
|
||||
accountNumber: '5400',
|
||||
accountName: 'Wareneingang 19% Vorsteuer',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Goods receipt 19% input tax',
|
||||
vatRate: 19,
|
||||
},
|
||||
{
|
||||
accountNumber: '5500',
|
||||
accountName: 'Aufwendungen für bezogene Leistungen',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Purchased services',
|
||||
},
|
||||
{
|
||||
accountNumber: '5600',
|
||||
accountName: 'Aufwendungen für Energie',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Energy costs',
|
||||
},
|
||||
{
|
||||
accountNumber: '5700',
|
||||
accountName: 'Reisekosten',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Travel expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '5800',
|
||||
accountName: 'Bewirtungskosten',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Entertainment expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '5900',
|
||||
accountName: 'Fremdleistungen',
|
||||
accountClass: 5,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'External services',
|
||||
},
|
||||
|
||||
// Class 6: Personnel Costs (Personalkosten)
|
||||
{
|
||||
accountNumber: '6000',
|
||||
accountName: 'Löhne und Gehälter',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Wages and salaries',
|
||||
},
|
||||
{
|
||||
accountNumber: '6100',
|
||||
accountName: 'Soziale Abgaben',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Social security contributions',
|
||||
},
|
||||
{
|
||||
accountNumber: '6200',
|
||||
accountName: 'Aufwendungen für Altersversorgung',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Pension expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '6300',
|
||||
accountName: 'Sonstige soziale Aufwendungen',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other social expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '6400',
|
||||
accountName: 'Versicherungen',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Insurance',
|
||||
},
|
||||
{
|
||||
accountNumber: '6500',
|
||||
accountName: 'Berufsgenossenschaft',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Occupational insurance',
|
||||
},
|
||||
{
|
||||
accountNumber: '6600',
|
||||
accountName: 'Vermögenswirksame Leistungen',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Employee savings schemes',
|
||||
},
|
||||
{
|
||||
accountNumber: '6700',
|
||||
accountName: 'Aufwendungen für Fortbildung',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Training expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '6800',
|
||||
accountName: 'Aushilfslöhne',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Temporary staff wages',
|
||||
},
|
||||
{
|
||||
accountNumber: '6900',
|
||||
accountName: 'Aufwendungen für freie Mitarbeiter',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Freelancer expenses',
|
||||
},
|
||||
|
||||
// Class 7: Other Operating Expenses (Sonstige betriebliche Aufwendungen)
|
||||
{
|
||||
accountNumber: '7000',
|
||||
accountName: 'Abschreibungen',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Depreciation',
|
||||
},
|
||||
{
|
||||
accountNumber: '7100',
|
||||
accountName: 'Raumkosten',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Rent and lease',
|
||||
},
|
||||
{
|
||||
accountNumber: '7200',
|
||||
accountName: 'Instandhaltung',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Maintenance',
|
||||
},
|
||||
{
|
||||
accountNumber: '7300',
|
||||
accountName: 'Versicherungen',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Insurance',
|
||||
},
|
||||
{
|
||||
accountNumber: '7400',
|
||||
accountName: 'Fahrzeugkosten',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Vehicle expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '7500',
|
||||
accountName: 'Werbe- und Reisekosten',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Marketing and travel',
|
||||
},
|
||||
{
|
||||
accountNumber: '7600',
|
||||
accountName: 'Kosten der Warenabgabe',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Distribution costs',
|
||||
},
|
||||
{
|
||||
accountNumber: '7700',
|
||||
accountName: 'Verschiedene betriebliche Kosten',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Miscellaneous operating costs',
|
||||
},
|
||||
{
|
||||
accountNumber: '7800',
|
||||
accountName: 'Steuern vom Einkommen und Ertrag',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Income taxes',
|
||||
},
|
||||
{
|
||||
accountNumber: '7900',
|
||||
accountName: 'Sonstige Steuern',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other taxes',
|
||||
},
|
||||
|
||||
// Class 8: Financial Accounts (Finanzkonten)
|
||||
{
|
||||
accountNumber: '8000',
|
||||
accountName: 'Erlöse aus Anlagenabgängen',
|
||||
accountClass: 8,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Gains from asset disposals',
|
||||
},
|
||||
{
|
||||
accountNumber: '8100',
|
||||
accountName: 'Sonstige Zinsen und ähnliche Erträge',
|
||||
accountClass: 8,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Interest income',
|
||||
},
|
||||
{
|
||||
accountNumber: '8200',
|
||||
accountName: 'Erträge aus Beteiligungen',
|
||||
accountClass: 8,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Investment income',
|
||||
},
|
||||
{
|
||||
accountNumber: '8300',
|
||||
accountName: 'Zinsen und ähnliche Aufwendungen',
|
||||
accountClass: 8,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Interest expense',
|
||||
},
|
||||
{
|
||||
accountNumber: '8400',
|
||||
accountName: 'Sonstige Erträge',
|
||||
accountClass: 8,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other income',
|
||||
},
|
||||
{
|
||||
accountNumber: '8500',
|
||||
accountName: 'Sonstige Aufwendungen',
|
||||
accountClass: 8,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Other expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '8600',
|
||||
accountName: 'Außerordentliche Erträge',
|
||||
accountClass: 8,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Extraordinary income',
|
||||
},
|
||||
{
|
||||
accountNumber: '8700',
|
||||
accountName: 'Außerordentliche Aufwendungen',
|
||||
accountClass: 8,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Extraordinary expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '8800',
|
||||
accountName: 'Erträge aus Verlustübernahme',
|
||||
accountClass: 8,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR03',
|
||||
description: 'Income from loss absorption',
|
||||
},
|
||||
{
|
||||
accountNumber: '8900',
|
||||
accountName: 'Aufgrund von Gewinnabführung abgeführte Gewinne',
|
||||
accountClass: 8,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR03',
|
||||
description: 'Profits transferred',
|
||||
},
|
||||
|
||||
// Class 9: Closing Accounts (Abschlusskonten)
|
||||
{
|
||||
accountNumber: '9000',
|
||||
accountName: 'Saldenvorträge',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Opening balances',
|
||||
},
|
||||
{
|
||||
accountNumber: '9100',
|
||||
accountName: 'Summenvortrag',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Total carried forward',
|
||||
},
|
||||
{
|
||||
accountNumber: '9200',
|
||||
accountName: 'Eröffnungsbilanzkonto',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Opening balance sheet account',
|
||||
},
|
||||
{
|
||||
accountNumber: '9300',
|
||||
accountName: 'Schlussbilanzkonto',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Closing balance sheet account',
|
||||
},
|
||||
{
|
||||
accountNumber: '9400',
|
||||
accountName: 'Gewinn- und Verlustkonto',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Profit and loss account',
|
||||
},
|
||||
{
|
||||
accountNumber: '9500',
|
||||
accountName: 'Kapitalkonto',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Capital account',
|
||||
},
|
||||
{
|
||||
accountNumber: '9600',
|
||||
accountName: 'Privatkonto',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Private account',
|
||||
},
|
||||
{
|
||||
accountNumber: '9700',
|
||||
accountName: 'Eigenverbrauch',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Personal consumption',
|
||||
},
|
||||
{
|
||||
accountNumber: '9800',
|
||||
accountName: 'Statistische Konten',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Statistical accounts',
|
||||
},
|
||||
{
|
||||
accountNumber: '9900',
|
||||
accountName: 'Verrechnungskonten',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR03',
|
||||
description: 'Clearing accounts',
|
||||
},
|
||||
];
|
||||
|
||||
export const SKR03_ACCOUNT_CLASSES = {
|
||||
0: 'Anlagekonten (Fixed Assets)',
|
||||
1: 'Umlaufvermögen (Current Assets)',
|
||||
2: 'Eigenkapital (Equity)',
|
||||
3: 'Rückstellungen und Verbindlichkeiten (Provisions and Liabilities)',
|
||||
4: 'Betriebliche Erträge (Operating Income)',
|
||||
5: 'Materialkosten (Material Costs)',
|
||||
6: 'Personalkosten (Personnel Costs)',
|
||||
7: 'Sonstige betriebliche Aufwendungen (Other Operating Expenses)',
|
||||
8: 'Finanzkonten (Financial Accounts)',
|
||||
9: 'Abschlusskonten (Closing Accounts)',
|
||||
};
|
923
ts/skr04.data.ts
Normal file
923
ts/skr04.data.ts
Normal file
@@ -0,0 +1,923 @@
|
||||
import type { IAccountData } from './skr.types.js';
|
||||
|
||||
/**
|
||||
* SKR04 - Financial Classification Principle (Abschlussgliederungsprinzip)
|
||||
* Organized by financial statement structure
|
||||
*/
|
||||
export const SKR04_ACCOUNTS: IAccountData[] = [
|
||||
// Class 0: Capital Accounts (Anlagekonten)
|
||||
{
|
||||
accountNumber: '0001',
|
||||
accountName: 'Aufwendungen für Ingangsetzung',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Start-up expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '0010',
|
||||
accountName: 'Konzessionen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Concessions',
|
||||
},
|
||||
{
|
||||
accountNumber: '0020',
|
||||
accountName: 'Patente',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Patents',
|
||||
},
|
||||
{
|
||||
accountNumber: '0030',
|
||||
accountName: 'Lizenzen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Licenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '0050',
|
||||
accountName: 'Firmenwert',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Goodwill',
|
||||
},
|
||||
{
|
||||
accountNumber: '0100',
|
||||
accountName: 'EDV-Software',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'IT Software',
|
||||
},
|
||||
{
|
||||
accountNumber: '0200',
|
||||
accountName: 'Grundstücke',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Land and property',
|
||||
},
|
||||
{
|
||||
accountNumber: '0210',
|
||||
accountName: 'Gebäude',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Buildings',
|
||||
},
|
||||
{
|
||||
accountNumber: '0300',
|
||||
accountName: 'Maschinen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Machinery',
|
||||
},
|
||||
{
|
||||
accountNumber: '0400',
|
||||
accountName: 'Fuhrpark',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Vehicles',
|
||||
},
|
||||
{
|
||||
accountNumber: '0500',
|
||||
accountName: 'Betriebs- und Geschäftsausstattung',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Office equipment',
|
||||
},
|
||||
{
|
||||
accountNumber: '0600',
|
||||
accountName: 'Geleistete Anzahlungen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Prepayments on fixed assets',
|
||||
},
|
||||
{
|
||||
accountNumber: '0800',
|
||||
accountName: 'Finanzanlagen',
|
||||
accountClass: 0,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Financial assets',
|
||||
},
|
||||
|
||||
// Class 1: Financial and Current Assets (Finanz- und Umlaufvermögen)
|
||||
{
|
||||
accountNumber: '1000',
|
||||
accountName: 'Kasse',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Cash on hand',
|
||||
},
|
||||
{
|
||||
accountNumber: '1100',
|
||||
accountName: 'Postbank',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Postal bank account',
|
||||
},
|
||||
{
|
||||
accountNumber: '1200',
|
||||
accountName: 'Bank',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Bank account',
|
||||
},
|
||||
{
|
||||
accountNumber: '1210',
|
||||
accountName: 'Sparkasse',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Savings bank',
|
||||
},
|
||||
{
|
||||
accountNumber: '1300',
|
||||
accountName: 'Wertpapiere',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Securities',
|
||||
},
|
||||
{
|
||||
accountNumber: '1400',
|
||||
accountName: 'Forderungen aus Lieferungen und Leistungen',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Trade receivables',
|
||||
},
|
||||
{
|
||||
accountNumber: '1500',
|
||||
accountName: 'Sonstige Vermögensgegenstände',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other assets',
|
||||
},
|
||||
{
|
||||
accountNumber: '1520',
|
||||
accountName: 'Abziehbare Vorsteuer',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Input VAT',
|
||||
},
|
||||
{
|
||||
accountNumber: '1570',
|
||||
accountName: 'Vorsteuer 7%',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Input VAT 7%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1571',
|
||||
accountName: 'Vorsteuer 19%',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Input VAT 19%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1600',
|
||||
accountName: 'Verbindlichkeiten aus Lieferungen und Leistungen',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'Trade payables',
|
||||
},
|
||||
{
|
||||
accountNumber: '1700',
|
||||
accountName: 'Sonstige Verbindlichkeiten',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other liabilities',
|
||||
},
|
||||
{
|
||||
accountNumber: '1770',
|
||||
accountName: 'Umsatzsteuer 7%',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'VAT payable 7%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1771',
|
||||
accountName: 'Umsatzsteuer 19%',
|
||||
accountClass: 1,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'VAT payable 19%',
|
||||
},
|
||||
{
|
||||
accountNumber: '1800',
|
||||
accountName: 'Privatentnahmen',
|
||||
accountClass: 1,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Private withdrawals',
|
||||
},
|
||||
{
|
||||
accountNumber: '1810',
|
||||
accountName: 'Privateinlagen',
|
||||
accountClass: 1,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Private deposits',
|
||||
},
|
||||
{
|
||||
accountNumber: '1900',
|
||||
accountName: 'Verrechnungskonto',
|
||||
accountClass: 1,
|
||||
accountType: 'asset',
|
||||
skrType: 'SKR04',
|
||||
description: 'Clearing account',
|
||||
},
|
||||
|
||||
// Class 2: Expenses (Aufwendungen) - Part 1
|
||||
{
|
||||
accountNumber: '2000',
|
||||
accountName: 'Roh-, Hilfs- und Betriebsstoffe',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Raw materials and supplies',
|
||||
},
|
||||
{
|
||||
accountNumber: '2100',
|
||||
accountName: 'Bezogene Waren',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Purchased goods',
|
||||
},
|
||||
{
|
||||
accountNumber: '2200',
|
||||
accountName: 'Bezogene Leistungen',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Purchased services',
|
||||
},
|
||||
{
|
||||
accountNumber: '2300',
|
||||
accountName: 'Löhne',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Wages',
|
||||
},
|
||||
{
|
||||
accountNumber: '2400',
|
||||
accountName: 'Gehälter',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Salaries',
|
||||
},
|
||||
{
|
||||
accountNumber: '2500',
|
||||
accountName: 'Soziale Abgaben',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Social security contributions',
|
||||
},
|
||||
{
|
||||
accountNumber: '2600',
|
||||
accountName: 'Aufwendungen für Altersversorgung',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Pension expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '2700',
|
||||
accountName: 'Abschreibungen auf immaterielle Vermögensgegenstände',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Depreciation on intangible assets',
|
||||
},
|
||||
{
|
||||
accountNumber: '2800',
|
||||
accountName: 'Abschreibungen auf Sachanlagen',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Depreciation on fixed assets',
|
||||
},
|
||||
{
|
||||
accountNumber: '2900',
|
||||
accountName: 'Abschreibungen auf Finanzanlagen',
|
||||
accountClass: 2,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Depreciation on financial assets',
|
||||
},
|
||||
|
||||
// Class 3: Expenses (Aufwendungen) - Part 2
|
||||
{
|
||||
accountNumber: '3000',
|
||||
accountName: 'Raumkosten',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Rent and lease',
|
||||
},
|
||||
{
|
||||
accountNumber: '3100',
|
||||
accountName: 'Sonstige Raumkosten',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other occupancy costs',
|
||||
},
|
||||
{
|
||||
accountNumber: '3200',
|
||||
accountName: 'Instandhaltung',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Maintenance',
|
||||
},
|
||||
{
|
||||
accountNumber: '3300',
|
||||
accountName: 'Fahrzeugkosten',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Vehicle expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '3400',
|
||||
accountName: 'Werbe- und Reisekosten',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Marketing and travel',
|
||||
},
|
||||
{
|
||||
accountNumber: '3500',
|
||||
accountName: 'Bewirtungskosten',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Entertainment expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '3600',
|
||||
accountName: 'Versicherungen',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Insurance',
|
||||
},
|
||||
{
|
||||
accountNumber: '3700',
|
||||
accountName: 'Beiträge und Gebühren',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Fees and subscriptions',
|
||||
},
|
||||
{
|
||||
accountNumber: '3800',
|
||||
accountName: 'Büromaterial',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Office supplies',
|
||||
},
|
||||
{
|
||||
accountNumber: '3900',
|
||||
accountName: 'Sonstige Aufwendungen',
|
||||
accountClass: 3,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other expenses',
|
||||
},
|
||||
|
||||
// Class 4: Revenues (Erträge) - Part 1
|
||||
{
|
||||
accountNumber: '4000',
|
||||
accountName: 'Umsatzerlöse',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Sales revenue',
|
||||
vatRate: 19,
|
||||
},
|
||||
{
|
||||
accountNumber: '4100',
|
||||
accountName: 'steuerfreie Umsätze',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Tax-free sales',
|
||||
},
|
||||
{
|
||||
accountNumber: '4200',
|
||||
accountName: 'Erlöse 7% USt',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Revenue 7% VAT',
|
||||
vatRate: 7,
|
||||
},
|
||||
{
|
||||
accountNumber: '4300',
|
||||
accountName: 'Erlöse 19% USt',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Revenue 19% VAT',
|
||||
vatRate: 19,
|
||||
},
|
||||
{
|
||||
accountNumber: '4400',
|
||||
accountName: 'Erlöse innergemeinschaftliche Lieferungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'EU sales',
|
||||
},
|
||||
{
|
||||
accountNumber: '4500',
|
||||
accountName: 'Erlöse Export',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Export sales',
|
||||
},
|
||||
{
|
||||
accountNumber: '4600',
|
||||
accountName: 'Bestandsveränderungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Inventory changes',
|
||||
},
|
||||
{
|
||||
accountNumber: '4700',
|
||||
accountName: 'Aktivierte Eigenleistungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Capitalized own work',
|
||||
},
|
||||
{
|
||||
accountNumber: '4800',
|
||||
accountName: 'Sonstige betriebliche Erträge',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other operating income',
|
||||
},
|
||||
{
|
||||
accountNumber: '4900',
|
||||
accountName: 'Erträge aus Beteiligungen',
|
||||
accountClass: 4,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Investment income',
|
||||
},
|
||||
|
||||
// Class 5: Revenues (Erträge) - Part 2
|
||||
{
|
||||
accountNumber: '5000',
|
||||
accountName: 'Zinserträge',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Interest income',
|
||||
},
|
||||
{
|
||||
accountNumber: '5100',
|
||||
accountName: 'Erträge aus Wertpapieren',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Securities income',
|
||||
},
|
||||
{
|
||||
accountNumber: '5200',
|
||||
accountName: 'Erträge aus Anlagenabgängen',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Gains from asset disposals',
|
||||
},
|
||||
{
|
||||
accountNumber: '5300',
|
||||
accountName: 'Währungsgewinne',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Currency gains',
|
||||
},
|
||||
{
|
||||
accountNumber: '5400',
|
||||
accountName: 'Erträge aus der Auflösung von Rückstellungen',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Income from provision releases',
|
||||
},
|
||||
{
|
||||
accountNumber: '5500',
|
||||
accountName: 'Periodenfremde Erträge',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Prior period income',
|
||||
},
|
||||
{
|
||||
accountNumber: '5600',
|
||||
accountName: 'Außerordentliche Erträge',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Extraordinary income',
|
||||
},
|
||||
{
|
||||
accountNumber: '5700',
|
||||
accountName: 'Verwendung von Rücklagen',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Use of reserves',
|
||||
},
|
||||
{
|
||||
accountNumber: '5800',
|
||||
accountName: 'Gewinne aus Unternehmensverträgen',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Profits from company agreements',
|
||||
},
|
||||
{
|
||||
accountNumber: '5900',
|
||||
accountName: 'Sonstige Erträge',
|
||||
accountClass: 5,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other income',
|
||||
},
|
||||
|
||||
// Class 6: Special Accounts (Sonderkonten)
|
||||
{
|
||||
accountNumber: '6000',
|
||||
accountName: 'Betriebssteuern',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Operating taxes',
|
||||
},
|
||||
{
|
||||
accountNumber: '6100',
|
||||
accountName: 'Vermögensteuer',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Wealth tax',
|
||||
},
|
||||
{
|
||||
accountNumber: '6200',
|
||||
accountName: 'Körperschaftsteuer',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Corporate tax',
|
||||
},
|
||||
{
|
||||
accountNumber: '6300',
|
||||
accountName: 'Einkommensteuer',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Income tax',
|
||||
},
|
||||
{
|
||||
accountNumber: '6400',
|
||||
accountName: 'Gewerbesteuer',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Trade tax',
|
||||
},
|
||||
{
|
||||
accountNumber: '6500',
|
||||
accountName: 'Sonstige Steuern',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Other taxes',
|
||||
},
|
||||
{
|
||||
accountNumber: '6600',
|
||||
accountName: 'Zinsaufwendungen',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Interest expense',
|
||||
},
|
||||
{
|
||||
accountNumber: '6700',
|
||||
accountName: 'Währungsverluste',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Currency losses',
|
||||
},
|
||||
{
|
||||
accountNumber: '6800',
|
||||
accountName: 'Außerordentliche Aufwendungen',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Extraordinary expenses',
|
||||
},
|
||||
{
|
||||
accountNumber: '6900',
|
||||
accountName: 'Verluste aus Unternehmensverträgen',
|
||||
accountClass: 6,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Losses from company agreements',
|
||||
},
|
||||
|
||||
// Class 7: Cost Accounting (Kosten- und Leistungsrechnung)
|
||||
{
|
||||
accountNumber: '7000',
|
||||
accountName: 'Kostenstellenrechnung',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Cost center accounting',
|
||||
},
|
||||
{
|
||||
accountNumber: '7100',
|
||||
accountName: 'Kostenträgerrechnung',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Cost object accounting',
|
||||
},
|
||||
{
|
||||
accountNumber: '7200',
|
||||
accountName: 'Kostenartenrechnung',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Cost type accounting',
|
||||
},
|
||||
{
|
||||
accountNumber: '7300',
|
||||
accountName: 'Kalkulatorische Kosten',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Imputed costs',
|
||||
},
|
||||
{
|
||||
accountNumber: '7400',
|
||||
accountName: 'Kalkulatorische Abschreibungen',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Imputed depreciation',
|
||||
},
|
||||
{
|
||||
accountNumber: '7500',
|
||||
accountName: 'Kalkulatorische Zinsen',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Imputed interest',
|
||||
},
|
||||
{
|
||||
accountNumber: '7600',
|
||||
accountName: 'Kalkulatorischer Unternehmerlohn',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Imputed entrepreneur salary',
|
||||
},
|
||||
{
|
||||
accountNumber: '7700',
|
||||
accountName: 'Kalkulatorische Miete',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Imputed rent',
|
||||
},
|
||||
{
|
||||
accountNumber: '7800',
|
||||
accountName: 'Verrechnete Kosten',
|
||||
accountClass: 7,
|
||||
accountType: 'expense',
|
||||
skrType: 'SKR04',
|
||||
description: 'Allocated costs',
|
||||
},
|
||||
{
|
||||
accountNumber: '7900',
|
||||
accountName: 'Verrechnete Leistungen',
|
||||
accountClass: 7,
|
||||
accountType: 'revenue',
|
||||
skrType: 'SKR04',
|
||||
description: 'Allocated services',
|
||||
},
|
||||
|
||||
// Class 8: Free for Use (Zur freien Verfügung)
|
||||
{
|
||||
accountNumber: '8000',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8100',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8200',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8300',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8400',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8500',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8600',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8700',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8800',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
{
|
||||
accountNumber: '8900',
|
||||
accountName: 'frei',
|
||||
accountClass: 8,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Available for custom use',
|
||||
},
|
||||
|
||||
// Class 9: Equity and Closing Accounts (Eigenkapital und Abschlusskonten)
|
||||
{
|
||||
accountNumber: '9000',
|
||||
accountName: 'Eigenkapital',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Equity capital',
|
||||
},
|
||||
{
|
||||
accountNumber: '9100',
|
||||
accountName: 'Gezeichnetes Kapital',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Subscribed capital',
|
||||
},
|
||||
{
|
||||
accountNumber: '9200',
|
||||
accountName: 'Kapitalrücklage',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Capital reserves',
|
||||
},
|
||||
{
|
||||
accountNumber: '9300',
|
||||
accountName: 'Gewinnrücklagen',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Revenue reserves',
|
||||
},
|
||||
{
|
||||
accountNumber: '9400',
|
||||
accountName: 'Gewinnvortrag/Verlustvortrag',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Profit/loss carried forward',
|
||||
},
|
||||
{
|
||||
accountNumber: '9500',
|
||||
accountName: 'Jahresüberschuss/Jahresfehlbetrag',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Annual profit/loss',
|
||||
},
|
||||
{
|
||||
accountNumber: '9600',
|
||||
accountName: 'Rückstellungen',
|
||||
accountClass: 9,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'Provisions',
|
||||
},
|
||||
{
|
||||
accountNumber: '9700',
|
||||
accountName: 'Verbindlichkeiten',
|
||||
accountClass: 9,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'Liabilities',
|
||||
},
|
||||
{
|
||||
accountNumber: '9800',
|
||||
accountName: 'Rechnungsabgrenzungsposten',
|
||||
accountClass: 9,
|
||||
accountType: 'liability',
|
||||
skrType: 'SKR04',
|
||||
description: 'Accruals and deferrals',
|
||||
},
|
||||
{
|
||||
accountNumber: '9900',
|
||||
accountName: 'Statistische Konten',
|
||||
accountClass: 9,
|
||||
accountType: 'equity',
|
||||
skrType: 'SKR04',
|
||||
description: 'Statistical accounts',
|
||||
},
|
||||
];
|
||||
|
||||
export const SKR04_ACCOUNT_CLASSES = {
|
||||
0: 'Anlagekonten (Fixed Assets)',
|
||||
1: 'Finanz- und Umlaufvermögen (Financial and Current Assets)',
|
||||
2: 'Aufwendungen Teil 1 (Expenses Part 1)',
|
||||
3: 'Aufwendungen Teil 2 (Expenses Part 2)',
|
||||
4: 'Erträge Teil 1 (Revenues Part 1)',
|
||||
5: 'Erträge Teil 2 (Revenues Part 2)',
|
||||
6: 'Sonderkonten (Special Accounts)',
|
||||
7: 'Kosten- und Leistungsrechnung (Cost Accounting)',
|
||||
8: 'Zur freien Verfügung (Free for Use)',
|
||||
9: 'Eigenkapital und Abschlusskonten (Equity and Closing Accounts)',
|
||||
};
|
Reference in New Issue
Block a user