Files
skr/ts/skr.api.ts
Juergen Kunz 73b46f7857
Some checks failed
Default (tags) / security (push) Successful in 48s
Default (tags) / test (push) Failing after 4m3s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
feat(invoice): add e-invoice support with XRechnung/ZUGFeRD and advanced export features
2025-08-12 12:37:01 +00:00

1021 lines
29 KiB
TypeScript

import * as plugins from './plugins.js';
import * as path from 'path';
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 { SkrExport, type IExportOptions } from './skr.export.js';
import { LedgerExporter } from './skr.export.ledger.js';
import { AccountsExporter } from './skr.export.accounts.js';
import { BalancesExporter } from './skr.export.balances.js';
import { PdfReportGenerator, type IPdfReportOptions } from './skr.export.pdf.js';
import { SecurityManager, type ISigningOptions } from './skr.security.js';
import { InvoiceAdapter } from './skr.invoice.adapter.js';
import { InvoiceStorage } from './skr.invoice.storage.js';
import { InvoiceBookingEngine, type IBookingOptions, type IBookingResult } from './skr.invoice.booking.js';
import type {
IInvoice,
IInvoiceFilter,
IInvoiceImportOptions,
IInvoiceExportOptions,
IBookingRules,
TInvoiceDirection,
} from './skr.invoice.entity.js';
import type {
IDatabaseConfig,
TSKRType,
IAccountData,
IAccountFilter,
ITransactionData,
ITransactionFilter,
IJournalEntry,
IReportParams,
ITrialBalanceReport,
IIncomeStatement,
IBalanceSheet,
IAccountBalance,
} 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;
private invoiceAdapter: InvoiceAdapter | null = null;
private invoiceStorage: InvoiceStorage | null = null;
private invoiceBookingEngine: InvoiceBookingEngine | 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);
// Initialize invoice components
this.invoiceAdapter = new InvoiceAdapter();
const invoicePath = this.config.invoiceExportPath || path.resolve(process.cwd(), 'exports', 'invoices');
this.invoiceStorage = new InvoiceStorage(invoicePath);
this.invoiceBookingEngine = new InvoiceBookingEngine(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();
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.postTransaction(transactionData);
}
/**
* Post a journal entry
*/
public async postJournalEntry(
journalData: IJournalEntry,
): Promise<JournalEntry> {
this.ensureInitialized();
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.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();
}
/**
* Export Jahresabschluss in GoBD-compliant BagIt format
* Creates a revision-safe export for 10-year archival
*/
public async exportJahresabschluss(options: IExportOptions): Promise<string> {
this.ensureInitialized();
if (!this.ledger || !this.reports || !this.currentSKRType) {
throw new Error('API not fully initialized');
}
this.logger.log('info', `Starting Jahresabschluss export for fiscal year ${options.fiscalYear}`);
// Create export instance
const exporter = new SkrExport(options);
// Create BagIt structure
await exporter.createBagItStructure();
await exporter.createExportMetadata(this.currentSKRType);
await exporter.createSchemas();
// Export accounting data
await this.exportLedgerData(exporter, options);
await this.exportAccountData(exporter, options);
await this.exportBalanceData(exporter, options);
// Generate PDF reports if requested
if (options.generatePdfReports) {
await this.generatePdfReports(exporter, options);
}
// Sign export if requested
if (options.signExport) {
await this.signExport(exporter, options);
}
// Create manifests and validate
await exporter.writeManifests();
const merkleRoot = await exporter.createMerkleTree();
const isValid = await exporter.validateBagIt();
if (!isValid) {
throw new Error('BagIt validation failed');
}
this.logger.log('ok', `Jahresabschluss export completed. Merkle root: ${merkleRoot}`);
return options.exportPath;
}
/**
* Export ledger data in NDJSON format
*/
private async exportLedgerData(exporter: SkrExport, options: IExportOptions): Promise<void> {
if (!this.ledger) throw new Error('Ledger not initialized');
const ledgerExporter = new LedgerExporter(options.exportPath);
await ledgerExporter.initialize();
// Get all transactions for the period
const transactions = await this.chartOfAccounts.getTransactions({
dateFrom: options.dateFrom,
dateTo: options.dateTo
});
// Export each transaction
for (const transaction of transactions) {
const transactionData = transaction;
await ledgerExporter.exportTransaction(transactionData as any);
}
// Get all journal entries for the period
// Use MongoDB query syntax for date range
const journalEntries = await JournalEntry.getInstances({
date: {
$gte: options.dateFrom,
$lte: options.dateTo
} as any, // SmartData supports MongoDB query operators
skrType: this.currentSKRType
});
// Export each journal entry
for (const entry of journalEntries) {
const entryData = entry;
await ledgerExporter.exportJournalEntry(entryData as any);
}
const entryCount = await ledgerExporter.close();
this.logger.log('info', `Exported ${entryCount} ledger entries`);
}
/**
* Export account data in CSV format
*/
private async exportAccountData(exporter: SkrExport, options: IExportOptions): Promise<void> {
const accountsExporter = new AccountsExporter(options.exportPath);
// Get all accounts
const accounts = await this.chartOfAccounts.getAllAccounts();
// Add each account to export
for (const account of accounts) {
const accountData = account;
accountsExporter.addAccount(accountData as any);
}
// Export to CSV and JSON
await accountsExporter.exportToCSV();
await accountsExporter.exportToJSON();
this.logger.log('info', `Exported ${accountsExporter.getAccountCount()} accounts`);
}
/**
* Export balance data in CSV format
*/
private async exportBalanceData(exporter: SkrExport, options: IExportOptions): Promise<void> {
if (!this.ledger) throw new Error('Ledger not initialized');
const balancesExporter = new BalancesExporter(
options.exportPath,
options.fiscalYear
);
// Get all accounts with balances
const accounts = await this.chartOfAccounts.getAllAccounts();
for (const account of accounts) {
const balance = await this.ledger.getAccountBalance(
account.accountNumber,
options.dateTo
);
if (balance) {
balancesExporter.addBalance(
account.accountNumber,
account.accountName,
balance as IAccountBalance,
`${options.fiscalYear}`
);
}
}
// Export balance reports
await balancesExporter.exportToCSV();
await balancesExporter.exportTrialBalance();
await balancesExporter.exportClassSummary();
this.logger.log('info', `Exported ${balancesExporter.getBalanceCount()} account balances`);
}
/**
* Generate PDF reports for the export
*/
private async generatePdfReports(exporter: SkrExport, options: IExportOptions): Promise<void> {
if (!this.reports) throw new Error('Reports not initialized');
const pdfOptions: IPdfReportOptions = {
companyName: options.companyInfo?.name || 'Unternehmen',
companyAddress: options.companyInfo?.address,
taxId: options.companyInfo?.taxId,
registrationNumber: options.companyInfo?.registrationNumber,
fiscalYear: options.fiscalYear,
dateFrom: options.dateFrom,
dateTo: options.dateTo,
preparedDate: new Date()
};
const pdfGenerator = new PdfReportGenerator(options.exportPath, pdfOptions);
await pdfGenerator.initialize();
try {
// Generate reports
const trialBalance = await this.reports.getTrialBalance({
dateFrom: options.dateFrom,
dateTo: options.dateTo,
skrType: this.currentSKRType
});
const incomeStatement = await this.reports.getIncomeStatement({
dateFrom: options.dateFrom,
dateTo: options.dateTo,
skrType: this.currentSKRType
});
const balanceSheet = await this.reports.getBalanceSheet({
dateFrom: options.dateFrom,
dateTo: options.dateTo,
skrType: this.currentSKRType
});
// Generate PDFs
const jahresabschlussPdf = await pdfGenerator.generateJahresabschlussPdf(
trialBalance,
incomeStatement,
balanceSheet
);
// Save PDFs
await pdfGenerator.savePdfReport('jahresabschluss.pdf', jahresabschlussPdf);
// Store in BagIt structure
await exporter.storeDocument(jahresabschlussPdf, 'jahresabschluss.pdf');
this.logger.log('info', 'PDF reports generated successfully');
} finally {
await pdfGenerator.close();
}
}
/**
* Sign the export with CAdES signature
*/
private async signExport(exporter: SkrExport, options: IExportOptions): Promise<void> {
const signingOptions: ISigningOptions = {
certificatePem: options.signExport ? undefined : undefined, // Use provided cert or generate
privateKeyPem: options.signExport ? undefined : undefined,
includeTimestamp: options.timestampExport !== false
};
const security = new SecurityManager(signingOptions);
// Generate self-signed certificate if none provided
let cert: string, key: string;
if (!signingOptions.certificatePem) {
const generated = await security.generateSelfSignedCertificate(
options.companyInfo?.name || 'SKR Export System'
);
cert = generated.certificate;
key = generated.privateKey;
} else {
cert = signingOptions.certificatePem;
key = signingOptions.privateKeyPem!;
}
// Sign the manifest
const manifestPath = path.resolve(
options.exportPath,
`jahresabschluss_${options.fiscalYear}`,
'manifest-sha256.txt'
);
await security.createDetachedSignature(
manifestPath,
path.resolve(
options.exportPath,
`jahresabschluss_${options.fiscalYear}`,
'data',
'metadata',
'signatures',
'manifest.cades'
)
);
this.logger.log('info', 'Export signed with CAdES signature');
}
// ========== 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,
};
}
// ========== Invoice Management ==========
/**
* Import an invoice from file or buffer
* Parses, validates, and optionally books the invoice
*/
public async importInvoice(
file: Buffer | string,
direction: TInvoiceDirection,
options?: IInvoiceImportOptions
): Promise<IInvoice> {
this.ensureInitialized();
if (!this.invoiceAdapter || !this.invoiceStorage || !this.invoiceBookingEngine) {
throw new Error('Invoice components not initialized');
}
this.logger.log('info', `Importing ${direction} invoice`);
// Parse and validate invoice
const invoice = await this.invoiceAdapter.parseInvoice(file, direction);
// Store invoice
await this.invoiceStorage.initialize();
const contentHash = await this.invoiceStorage.storeInvoice(invoice);
invoice.contentHash = contentHash;
// Auto-book if requested
if (options?.autoBook) {
const bookingResult = await this.bookInvoice(
invoice,
options.bookingRules,
{
autoBook: true,
confidenceThreshold: options.confidenceThreshold || 80,
skipValidation: options.validateOnly
}
);
if (bookingResult.success && bookingResult.bookingInfo) {
invoice.bookingInfo = bookingResult.bookingInfo;
invoice.status = 'posted';
// Update stored metadata with booking information
await this.invoiceStorage.updateMetadata(invoice.contentHash, {
journalEntryId: bookingResult.bookingInfo.journalEntryId,
transactionIds: bookingResult.bookingInfo.transactionIds
});
}
}
this.logger.log('info', `Invoice imported successfully: ${invoice.invoiceNumber}`);
return invoice;
}
/**
* Book an invoice to the ledger
*/
public async bookInvoice(
invoice: IInvoice,
bookingRules?: Partial<IBookingRules>,
options?: IBookingOptions
): Promise<IBookingResult> {
this.ensureInitialized();
if (!this.invoiceBookingEngine) {
throw new Error('Invoice booking engine not initialized');
}
this.logger.log('info', `Booking invoice ${invoice.invoiceNumber}`);
const result = await this.invoiceBookingEngine.bookInvoice(
invoice,
bookingRules,
options
);
if (result.success) {
this.logger.log('info', `Invoice booked successfully with confidence ${result.confidence}%`);
// Update stored metadata if invoice has a content hash
if (invoice.contentHash && result.bookingInfo && this.invoiceStorage) {
await this.invoiceStorage.updateMetadata(invoice.contentHash, {
journalEntryId: result.bookingInfo.journalEntryId,
transactionIds: result.bookingInfo.transactionIds
});
}
} else {
this.logger.log('error', `Invoice booking failed: ${result.errors?.join(', ')}`);
}
return result;
}
/**
* Export an invoice in a different format
*/
public async exportInvoice(
invoice: IInvoice,
options: IInvoiceExportOptions
): Promise<{ xml: string; pdf?: Buffer }> {
this.ensureInitialized();
if (!this.invoiceAdapter) {
throw new Error('Invoice adapter not initialized');
}
this.logger.log('info', `Exporting invoice ${invoice.invoiceNumber} to ${options.format}`);
// Convert format if needed
const xml = await this.invoiceAdapter.convertFormat(invoice, options.format);
// Generate PDF if requested
let pdf: Buffer | undefined;
if (options.embedInPdf) {
const result = await this.invoiceAdapter.generateInvoice(invoice, options.format);
pdf = result.pdf;
}
return { xml, pdf };
}
/**
* Search invoices by filter
*/
public async searchInvoices(filter: IInvoiceFilter): Promise<IInvoice[]> {
this.ensureInitialized();
if (!this.invoiceStorage) {
throw new Error('Invoice storage not initialized');
}
await this.invoiceStorage.initialize();
const metadata = await this.invoiceStorage.searchInvoices(filter);
const invoices: IInvoice[] = [];
for (const meta of metadata) {
const invoice = await this.invoiceStorage.retrieveInvoice(meta.contentHash);
if (invoice) {
invoices.push(invoice);
}
}
return invoices;
}
/**
* Get invoice by content hash
*/
public async getInvoice(contentHash: string): Promise<IInvoice | null> {
this.ensureInitialized();
if (!this.invoiceStorage) {
throw new Error('Invoice storage not initialized');
}
await this.invoiceStorage.initialize();
return await this.invoiceStorage.retrieveInvoice(contentHash);
}
/**
* Get invoice storage statistics
*/
public async getInvoiceStatistics(): Promise<any> {
this.ensureInitialized();
if (!this.invoiceStorage) {
throw new Error('Invoice storage not initialized');
}
await this.invoiceStorage.initialize();
return await this.invoiceStorage.getStatistics();
}
/**
* Create EN16931 compliance report for invoices
*/
public async createInvoiceComplianceReport(): Promise<void> {
this.ensureInitialized();
if (!this.invoiceStorage) {
throw new Error('Invoice storage not initialized');
}
await this.invoiceStorage.initialize();
await this.invoiceStorage.createComplianceReport();
this.logger.log('info', 'Invoice compliance report created');
}
/**
* Generate an invoice from internal data
*/
public async generateInvoice(
invoiceData: Partial<IInvoice>,
format: IInvoiceExportOptions['format']
): Promise<{ xml: string; pdf?: Buffer }> {
this.ensureInitialized();
if (!this.invoiceAdapter) {
throw new Error('Invoice adapter not initialized');
}
this.logger.log('info', `Generating invoice in ${format} format`);
return await this.invoiceAdapter.generateInvoice(invoiceData, format);
}
}