4 Commits

Author SHA1 Message Date
jkunz 8dc566a709 v1.3.0
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-16 20:39:11 +00:00
jkunz 0c20005db2 feat(deps): bump @fin.cx/einvoice to 5.2.0 2026-04-16 20:39:11 +00:00
jkunz b9df310aaf v1.2.2
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-16 19:41:55 +00:00
jkunz 40ffc2b355 fix(exports): stabilize published types and compatibility with updated dependencies 2026-04-16 19:41:55 +00:00
25 changed files with 4720 additions and 5549 deletions
+12 -6
View File
@@ -1,9 +1,5 @@
{ {
"npmci": { "@git.zone/cli": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"gitzone": {
"projectType": "npm", "projectType": "npm",
"module": { "module": {
"githost": "code.foss.global", "githost": "code.foss.global",
@@ -12,6 +8,16 @@
"description": "SKR03 and SKR04 German accounting standards for double-entry bookkeeping", "description": "SKR03 and SKR04 German accounting standards for double-entry bookkeeping",
"npmPackagename": "@fin.cx/skr", "npmPackagename": "@fin.cx/skr",
"license": "MIT" "license": "MIT"
} },
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"@ship.zone/szci": {
"npmGlobalTools": []
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"json.schemas": [ "json.schemas": [
{ {
"fileMatch": ["/npmextra.json"], "fileMatch": ["/.smartconfig.json"],
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
+13
View File
@@ -1,5 +1,18 @@
# Changelog # Changelog
## 2026-04-16 - 1.3.0 - feat(deps)
bump @fin.cx/einvoice to 5.2.0
- updates the @fin.cx/einvoice dependency from 5.1.4 to 5.2.0
## 2026-04-16 - 1.2.2 - fix(exports)
stabilize published types and compatibility with updated dependencies
- replace wildcard package exports with explicit runtime and type exports in the public entrypoint
- add a strict consumer type-check fixture and test script to verify published declaration files
- adapt filesystem, PDF, e-invoice, and signing integrations to updated dependency APIs
- harden error handling and initialization checks across API, chart of accounts, invoice, and journal workflows
## 2025-10-28 - 1.2.1 - fix(skr.classes.account) ## 2025-10-28 - 1.2.1 - fix(skr.classes.account)
Remove incorrect SKR04 automatic account 3300; improve VAT posting validation and test isolation; update readme hints and CI settings Remove incorrect SKR04 automatic account 3300; improve VAT posting validation and test isolation; update readme hints and CI settings
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Task Venture Capital GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+24 -20
View File
@@ -1,12 +1,13 @@
{ {
"name": "@fin.cx/skr", "name": "@fin.cx/skr",
"version": "1.2.1", "version": "1.3.0",
"description": "SKR03 and SKR04 German accounting standards for double-entry bookkeeping", "description": "SKR03 and SKR04 German accounting standards for double-entry bookkeeping",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "tstest test/ --verbose --logfile --timeout=60", "test": "tstest test/ --verbose --logfile --timeout=60 && pnpm run test:published-types",
"test:published-types": "pnpm build && pnpm exec tsc --pretty false -p test/fixtures/strict-consumer/tsconfig.json",
"build": "tsbuild --web --node", "build": "tsbuild --web --node",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
@@ -25,25 +26,27 @@
"license": "MIT", "license": "MIT",
"packageManager": "pnpm@10.11.0", "packageManager": "pnpm@10.11.0",
"dependencies": { "dependencies": {
"@e-invoice-eu/core": "^2.1.9", "@e-invoice-eu/core": "^3.1.0",
"@fin.cx/einvoice": "5.1.4", "@fin.cx/einvoice": "5.2.0",
"@push.rocks/smartdata": "^5.15.1", "@push.rocks/smartdata": "^7.1.7",
"@push.rocks/smartfile": "^11.0.22", "@push.rocks/smartfile": "^13.1.2",
"@push.rocks/smarthash": "^3.0.7", "@push.rocks/smartfs": "^1.5.0",
"@push.rocks/smartlog": "^3.1.8", "@push.rocks/smarthash": "^3.2.6",
"@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartlog": "^3.2.2",
"@push.rocks/smartpdf": "^3.1.5", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smarttime": "^4.1.1", "@push.rocks/smartpdf": "^4.2.0",
"@push.rocks/smarttime": "^4.2.3",
"@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartunique": "^3.0.9",
"merkletreejs": "^0.4.0", "merkletreejs": "^0.6.0",
"node-forge": "^1.3.1" "node-forge": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.6.4", "@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsrun": "^1.3.3", "@git.zone/tsrun": "^2.0.2",
"@git.zone/tstest": "^2.3.2", "@git.zone/tstest": "^3.6.3",
"@push.rocks/qenv": "^6.1.0", "@push.rocks/qenv": "^6.1.3",
"@types/node-forge": "^1.3.11" "@types/node": "^25.6.0",
"@types/node-forge": "^1.3.14"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -62,8 +65,9 @@
"dist_ts_web/**/*", "dist_ts_web/**/*",
"assets/**/*", "assets/**/*",
"cli.js", "cli.js",
"npmextra.json", ".smartconfig.json",
"readme.md" "readme.md",
"license.md"
], ],
"pnpm": { "pnpm": {
"overrides": {} "overrides": {}
+4041 -4691
View File
File diff suppressed because it is too large Load Diff
+237 -654
View File
@@ -1,738 +1,321 @@
# @fin.cx/skr 📊 # @fin.cx/skr
> **Enterprise-grade German accounting standards implementation for SKR03 and SKR04** `@fin.cx/skr` is a TypeScript library for German double-entry bookkeeping with built-in SKR03 and SKR04 chart initialization, MongoDB-backed persistence, reporting, DATEV export, GoBD-oriented Jahresabschluss export, and e-invoice workflows.
> Rock-solid double-entry bookkeeping with MongoDB persistence, e-invoice integration, and full TypeScript support
## 🚀 Why @fin.cx/skr? It is built for developers who need a programmable accounting core instead of a pile of CSV glue code: initialize a chart of accounts, post validated transactions and journal entries, generate reports, and archive year-end data in a structured export format.
Building compliant German accounting software? You've come to the right place! This module provides a **complete, type-safe implementation** of the German standard charts of accounts (Standardkontenrahmen) SKR03 and SKR04, the backbone of professional accounting in Germany. ## Issue Reporting and Security
### 🎯 What makes it awesome? For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
- **🏢 Enterprise-Ready**: Production-tested implementation following HGB/GoBD standards ## What This Library Does
- **⚡ Lightning Fast**: MongoDB-powered with optimized indexing and real-time balance updates
- **🔒 Type-Safe**: Full TypeScript support with comprehensive type definitions
- **🎮 Developer-Friendly**: Intuitive API that makes complex accounting operations simple
- **📈 Real-time Reporting**: Generate financial statements on-the-fly
- **🔄 Transaction Safety**: Built-in double-entry validation and automatic reversals
- **✅ Battle-Tested**: 65+ comprehensive tests covering all edge cases
- **🛡️ SKR Validation**: Automatic validation against official SKR standards
- **🧾 E-Invoice Support**: Full XRechnung/ZUGFeRD integration for modern invoice processing
- **🔐 Cryptographic Security**: Merkle tree and digital signature support for audit trails
- **📑 PDF Export**: Professional PDF report generation with customizable templates
## 📦 Installation - Initializes SKR03 or SKR04 account sets in MongoDB
- Enforces double-entry bookkeeping rules for transactions and journal entries
- Supports DATEV posting keys and VAT-aware journal lines
- Prevents direct posting to automatic accounts that require personal accounts
- Generates trial balance, income statement, balance sheet, general ledger, and cash flow reports
- Exports accounting data as CSV, DATEV, and GoBD-style Jahresabschluss packages
- Imports, stores, searches, books, and exports EN16931-style e-invoices
- Adds signing and timestamp helpers for audit-oriented export workflows
## Why It Is Useful
- You get a real accounting domain model, not just account lists
- SKR03 and SKR04 are both supported behind one API
- Tests cover initialization, posting, reversals, reports, pagination, DATEV export, and full year-end flows
- The package exports the lower-level classes too, so you can stay high-level with `SkrApi` or build around the primitives
## Requirements
- Node.js 20+ with ESM support
- A reachable MongoDB instance
- `pnpm`
The test setup reads MongoDB connection details from `.nogit/` via `@push.rocks/qenv`, but the runtime API only needs a `mongoDbUrl` and an optional `dbName`.
## Installation
```bash ```bash
# Using npm
npm install @fin.cx/skr
# Using pnpm (recommended)
pnpm add @fin.cx/skr pnpm add @fin.cx/skr
# Using yarn
yarn add @fin.cx/skr
``` ```
## 🎓 Quick Start ## Quick Start
### Basic Setup ```ts
```typescript
import { SkrApi } from '@fin.cx/skr'; import { SkrApi } from '@fin.cx/skr';
// Initialize the API
const api = new SkrApi({ const api = new SkrApi({
mongoDbUrl: 'mongodb://localhost:27017', mongoDbUrl: 'mongodb://localhost:27017',
dbName: 'accounting' // optional, defaults to 'skr_accounting' dbName: 'accounting_demo',
}); });
// Choose your SKR standard (SKR03 or SKR04)
await api.initialize('SKR03'); await api.initialize('SKR03');
```
### 💰 Posting Transactions await api.postTransaction({
```typescript
// Simple transaction posting
const transaction = await api.postTransaction({
date: new Date(), date: new Date(),
debitAccount: '1200', // Bank account debitAccount: '1200',
creditAccount: '8400', // Revenue account creditAccount: '4000',
amount: 1190.00, amount: 1000,
description: 'Invoice #2024-001 payment received', description: 'Test sale',
reference: 'INV-2024-001', reference: 'INV-001',
vatAmount: 190.00 skrType: 'SKR03',
}); });
// Complex journal entry with multiple lines const trialBalance = await api.generateTrialBalance();
const journalEntry = await api.postJournalEntry({
console.log(trialBalance.isBalanced);
await api.close();
```
## SKR03 vs SKR04
`initialize('SKR03')` loads the process-oriented chart.
- Class 4: operating income
- Class 5: material costs
- Class 6: personnel costs
- Class 7: other operating expenses
`initialize('SKR04')` loads the financial-statement-oriented chart.
- Class 2 and 3: expenses
- Class 4 and 5: revenues
- Class 8: reserved as `frei` for custom use
The test suite exercises both variants and includes full Jahresabschluss scenarios for each.
## Posting Model
Simple postings use `postTransaction()`.
```ts
await api.postTransaction({
date: new Date(), date: new Date(),
description: 'Monthly salary payments', debitAccount: '5400',
reference: 'SAL-2024-03', creditAccount: '70001',
lines: [ amount: 119,
{ accountNumber: '6000', debit: 5000.00, description: 'Gross salary' }, description: 'Purchase including VAT',
{ accountNumber: '6100', debit: 1000.00, description: 'Social security employer' }, skrType: 'SKR03',
{ accountNumber: '1800', credit: 1500.00, description: 'Tax withholding' }, vatAmount: 19,
{ accountNumber: '1200', credit: 4500.00, description: 'Net payment' } reference: 'VAT-001',
]
}); });
``` ```
### 🧾 E-Invoice Integration Complex bookings use `postJournalEntry()` with explicit DATEV posting keys.
```typescript ```ts
// Import electronic invoices (XRechnung/ZUGFeRD)
const invoiceData = await api.importInvoice(xmlContent, {
format: 'xrechnung',
validateSchema: true,
checkDuplicates: true
});
// Automatically book invoice to accounting
const booking = await api.bookInvoice(invoiceData.invoiceId, {
autoDetectAccounts: true,
splitVAT: true,
createPaymentSchedule: true
});
// Export invoice in various formats
const xRechnung = await api.exportInvoice(invoiceId, {
format: 'xrechnung',
version: '3.0',
includeAttachments: true
});
// Search and filter invoices
const invoices = await api.searchInvoices({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31'),
status: 'booked',
minAmount: 100,
customerVATId: 'DE123456789'
});
// Generate compliance reports
const complianceReport = await api.createInvoiceComplianceReport({
period: '2024-Q1',
includeValidation: true,
includeStatistics: true
});
```
### 📊 Generating Financial Reports
```typescript
// Trial Balance (Summen- und Saldenliste)
const trialBalance = await api.generateTrialBalance({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// Income Statement (GuV - Gewinn- und Verlustrechnung)
const incomeStatement = await api.generateIncomeStatement({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// Balance Sheet (Bilanz)
const balanceSheet = await api.generateBalanceSheet({
date: new Date('2024-12-31')
});
// General Ledger Export
const generalLedger = await api.generateGeneralLedger({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// Cash Flow Statement
const cashFlow = await api.generateCashFlowStatement({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
```
### 📑 Advanced Export Features
```typescript
// Export complete annual closing package (Jahresabschluss)
const jahresabschluss = await api.exportJahresabschluss({
year: 2024,
includeReports: ['balance_sheet', 'income_statement', 'cash_flow'],
format: 'structured', // 'structured' | 'pdf' | 'csv'
language: 'de',
signatureRequired: true
});
// Generate PDF reports with professional formatting
const pdfReports = await api.generatePdfReports({
reports: ['trial_balance', 'income_statement', 'balance_sheet'],
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31'),
companyInfo: {
name: 'Mustermann GmbH',
address: 'Hauptstraße 1, 10115 Berlin',
taxNumber: 'DE123456789',
registrationNumber: 'HRB 12345'
},
outputPath: './reports/',
template: 'professional' // Custom templates available
});
// Export with cryptographic signatures for audit trail
const signedExport = await api.signExport({
data: jahresabschluss,
privateKey: privateKeyPEM,
certificate: certificatePEM,
includeTimestamp: true,
hashAlgorithm: 'SHA256'
});
// Detailed account data export
const accountExport = await api.exportAccountData({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31'),
format: 'detailed', // 'summary' | 'detailed' | 'tree'
includeTransactions: true,
includeBalances: true
});
// Balance history export for analysis
const balanceHistory = await api.exportBalanceData({
accounts: ['1200', '1000', '8400'],
interval: 'monthly', // 'daily' | 'weekly' | 'monthly' | 'quarterly'
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31'),
includeRunningTotals: true
});
// Ledger export with filtering options
const ledgerExport = await api.exportLedgerData({
accounts: ['1000-1999'], // Range support
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31'),
includeReversals: false,
groupByAccount: true,
format: 'journal' // 'journal' | 'T-account' | 'chronological'
});
```
## 🏗️ Core Features
### Account Management
```typescript
// Create custom accounts
const account = await api.createAccount({
accountNumber: '1299',
accountName: 'PayPal Business',
accountClass: 1,
accountType: 'asset',
description: 'PayPal business account for online payments',
isActive: true
});
// Batch create multiple accounts for efficiency
const accounts = await api.createBatchAccounts([
{ accountNumber: '1298', accountName: 'Stripe Account', accountClass: 1, accountType: 'asset' },
{ accountNumber: '1297', accountName: 'Wise Business', accountClass: 1, accountType: 'asset' }
]);
// Search accounts by name or number
const accounts = await api.searchAccounts('bank');
// Get account with full details
const account = await api.getAccount('1200');
// Update account information
await api.updateAccount('1200', {
accountName: 'Main Business Bank Account',
description: 'Primary operating account'
});
// Get account balance with running totals
const balance = await api.getAccountBalance('1200');
console.log(`Balance: €${balance.balance}`);
console.log(`Total Debits: €${balance.debitTotal}`);
console.log(`Total Credits: €${balance.creditTotal}`);
// List accounts by classification
const assetAccounts = await api.getAccountsByType('asset');
const class4Accounts = await api.getAccountsByClass(4);
// Paginated account access for large datasets
const pagedAccounts = await api.getAccountsPaginated({
page: 1,
limit: 50,
sortBy: 'accountNumber',
sortOrder: 'asc'
});
```
### Transaction Management
```typescript
// Get transaction by ID
const transaction = await api.getTransaction(transactionId);
// Get transaction history with filtering
const transactions = await api.listTransactions({
accountNumber: '1200',
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31'),
minAmount: 100,
maxAmount: 10000
});
// Get all transactions for a specific account
const accountTransactions = await api.getAccountTransactions('1200', {
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// Reverse transactions (Storno)
const reversal = await api.reverseTransaction(transactionId);
// Reverse complex journal entries
const journalReversal = await api.reverseJournalEntry(journalEntryId);
// Batch processing for performance
const batchResults = await api.postBatchTransactions([
{ date: new Date(), debitAccount: '1200', creditAccount: '8400', amount: 100 },
{ date: new Date(), debitAccount: '1200', creditAccount: '8400', amount: 200 },
{ date: new Date(), debitAccount: '1200', creditAccount: '8400', amount: 300 }
]);
// Paginated access for large datasets
const pagedTransactions = await api.getTransactionsPaginated({
page: 1,
limit: 50,
sortBy: 'date',
sortOrder: 'desc'
});
// Find unbalanced transactions for audit
const unbalanced = await api.getUnbalancedTransactions();
```
## 📚 SKR03 vs SKR04: Which One to Choose?
### SKR03 - Process Structure Principle (Prozessgliederungsprinzip)
**Best for:** 🛍️ Trading companies, 💼 Service providers, 🏪 Retail businesses
- Accounts organized by **business process flow**
- Easier mapping to operational workflows
- Natural progression from purchasing → inventory → sales
- Popular with small to medium enterprises
### SKR04 - Financial Classification Principle (Abschlussgliederungsprinzip)
**Best for:** 🏭 Manufacturing companies, 🏗️ Large corporations, 📈 Public companies
- Accounts organized by **financial statement structure**
- Direct mapping to balance sheet and P&L positions
- Simplified financial reporting and analysis
- Preferred by auditors and financial institutions
## 🎯 Account Structure
Both SKR standards follow the same 4-digit hierarchical structure:
```
[0-9] → Account Class (Kontenklasse)
[0-9] → Account Group (Kontengruppe)
[0-9] → Account Subgroup (Kontenuntergruppe)
[0-9] → Individual Account (Einzelkonto)
```
### Account Classes Overview
| Class | SKR03 Description | SKR04 Description | Type |
|-------|------------------|-------------------|------|
| **0** | Fixed Assets (Anlagevermögen) | Fixed Assets | Asset |
| **1** | Current Assets (Umlaufvermögen) | Financial & Current Assets | Asset |
| **2** | Equity (Eigenkapital) | Expenses Part 1 | Equity/Expense |
| **3** | Liabilities (Fremdkapital) | Expenses Part 2 | Liability/Expense |
| **4** | Operating Income (Betriebliche Erträge) | Revenues Part 1 | Revenue |
| **5** | Material Costs (Materialaufwand) | Revenues Part 2 | Expense/Revenue |
| **6** | Operating Expenses (Betriebsaufwand) | Special Accounts | Expense |
| **7** | Other Costs (Weitere Aufwendungen) | Cost Accounting | Expense |
| **8** | Income (Erträge) | Free for Use (Custom) | Revenue |
| **9** | Closing Accounts (Abschlusskonten) | Equity & Closing | System |
## 🔧 Advanced Features
### Period Management
```typescript
// Close accounting period with automatic adjustments
await api.closePeriod('2024-01', {
performYearEndAdjustments: true,
generateReports: true
});
// Recalculate all account balances
await api.recalculateBalances();
```
### Data Import/Export
```typescript
// Import accounts from CSV
const importedCount = await api.importAccountsFromCSV(csvContent);
// Export accounts to CSV
const csvExport = await api.exportAccountsToCSV();
// Export to DATEV format (for tax advisors)
const datevExport = await api.exportToDATEV({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// Export reports to CSV
const reportCsv = await api.exportReportToCSV('income_statement', {
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
```
### Validation & Integrity
```typescript
// Find unbalanced transactions
const unbalanced = await api.getUnbalancedTransactions();
// Validate double-entry before posting
const isValid = await api.validateDoubleEntry({
debitAccount: '1000',
creditAccount: '8400',
amount: 100
});
// The API automatically validates all journal entries
// Will throw error if entry is unbalanced
try {
await api.postJournalEntry({ await api.postJournalEntry({
date: new Date(), date: new Date(),
description: 'Complex distribution',
reference: 'COMPLEX-001',
lines: [ lines: [
{ accountNumber: '1000', debit: 100 }, { accountNumber: '5000', debit: 500, description: 'Materials', postingKey: 40 },
{ accountNumber: '8400', credit: 99 } // Unbalanced! { accountNumber: '6000', debit: 300, description: 'Wages', postingKey: 40 },
] { accountNumber: '7100', debit: 200, description: 'Rent', postingKey: 40 },
{ accountNumber: '1200', credit: 1000, description: 'Bank payment', postingKey: 40 },
],
skrType: 'SKR03',
}); });
} catch (error) {
console.error('Journal entry is not balanced!');
}
``` ```
### Invoice Processing & Compliance Important behavior from the code and tests:
```typescript - debit and credit totals must balance
// Get invoice statistics and analytics - debit and credit account cannot be the same in a simple transaction
const stats = await api.getInvoiceStatistics({ - inactive accounts cannot be posted to
dateFrom: new Date('2024-01-01'), - automatic accounts such as debtor or creditor control accounts are meant to be replaced by personal accounts for direct postings
dateTo: new Date('2024-12-31'),
groupBy: 'month', ## Common Workflows
includeVATAnalysis: true
Create custom accounts:
```ts
await api.createAccount({
accountNumber: '4999',
accountName: 'Custom Revenue Account',
accountClass: 4,
accountType: 'revenue',
description: 'Test custom account',
}); });
```
// Generate invoices programmatically Batch operations:
const invoice = await api.generateInvoice({
invoiceNumber: 'INV-2024-001', ```ts
date: new Date(), await api.createBatchAccounts([
dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
seller: {
name: 'Your Company GmbH',
vatId: 'DE123456789',
address: 'Hauptstraße 1, 10115 Berlin'
},
buyer: {
name: 'Customer AG',
vatId: 'DE987654321',
address: 'Kundenweg 5, 80331 München'
},
lines: [
{ {
description: 'Consulting Services', accountNumber: '10001',
quantity: 10, accountName: 'Kunde Mustermann GmbH',
unitPrice: 100,
vatRate: 19
}
]
});
// Validate invoice compliance
const validation = await api.validateInvoice(invoice, {
standard: 'xrechnung',
checkBusinessRules: true,
checkVATRules: true
});
```
### Utility Functions
```typescript
// Get SKR type description for account classes
const classDesc = api.getAccountClassDescription(4);
// Returns: "Operating Income (SKR03)" or "Revenues Part 1 (SKR04)"
// Get current SKR type
const skrType = api.getSKRType(); // Returns: 'SKR03' or 'SKR04'
```
## 🛡️ Type Safety
Full TypeScript support with comprehensive type definitions:
```typescript
import type {
TSKRType,
IAccountData,
ITransactionData,
IJournalEntry,
IJournalEntryLine,
ITrialBalanceReport,
IIncomeStatement,
IBalanceSheet,
IAccountFilter,
ITransactionFilter,
IPaginationParams,
IAccountBalance,
ICashFlowStatement,
IGeneralLedger,
IInvoice,
IInvoiceLine,
IInvoiceParty,
IBookingRules,
IValidationResult
} from '@fin.cx/skr';
// All operations are fully typed
const account: IAccountData = {
accountNumber: '1200',
accountName: 'Bank Account',
accountClass: 1, accountClass: 1,
accountType: 'asset', accountType: 'asset',
skrType: 'SKR03', skrType: 'SKR03',
isActive: true },
}; {
accountNumber: '70001',
// TypeScript will catch errors at compile time accountName: 'Lieferant Test GmbH',
const filter: IAccountFilter = { accountClass: 7,
accountType: 'asset', accountType: 'liability',
isActive: true, skrType: 'SKR03',
accountClass: 1 },
}; ]);
// Journal entries are validated at type level
const journalEntry: IJournalEntry = {
date: new Date(),
description: 'Year-end closing',
lines: [
{ accountNumber: '8400', debit: 0, credit: 1000 },
{ accountNumber: '9000', debit: 1000, credit: 0 }
]
};
``` ```
## 🌟 Real-World Example: Complete Annual Closing Pagination:
Here's how to perform a complete Jahresabschluss (annual financial closing): ```ts
const page1 = await api.getAccountsPaginated(1, 10);
console.log(page1.total, page1.totalPages, page1.data.length);
```
```typescript Reversals and validation:
import { SkrApi } from '@fin.cx/skr';
async function performJahresabschluss() { ```ts
const api = new SkrApi({ const ok = api.validateDoubleEntry(100, 100);
mongoDbUrl: process.env.MONGODB_URL!, const reversed = await api.reverseTransaction(transactionId);
dbName: 'company_accounting' ```
});
await api.initialize('SKR04'); // Using SKR04 for better reporting structure ## Reports And Exports
// 1. Post year-end adjustments Available reporting methods on `SkrApi`:
const adjustments = await api.postJournalEntry({
date: new Date('2024-12-31'),
description: 'Jahresabschlussbuchungen',
reference: 'JA-2024',
lines: [
// Depreciation (AfA)
{ accountNumber: '3700', debit: 10000, description: 'AfA auf Anlagen' },
{ accountNumber: '0210', credit: 10000, description: 'Wertberichtigung Gebäude' },
// Provisions (Rückstellungen) - `generateTrialBalance()`
{ accountNumber: '3500', debit: 5000, description: 'Bildung Rückstellungen' }, - `generateIncomeStatement()`
{ accountNumber: '0800', credit: 5000, description: 'Sonstige Rückstellungen' }, - `generateBalanceSheet()`
- `generateGeneralLedger()`
- `generateCashFlowStatement()`
- `exportReportToCSV()`
- `exportToDATEV()`
// VAT clearing Year-end archival export:
{ accountNumber: '1771', debit: 19000, description: 'USt-Saldo' },
{ accountNumber: '1571', credit: 17000, description: 'Vorsteuer-Saldo' },
{ accountNumber: '1700', credit: 2000, description: 'USt-Zahllast' }
]
});
// 2. Generate comprehensive annual closing package ```ts
const jahresabschluss = await api.exportJahresabschluss({ const exportPath = await api.exportJahresabschluss({
year: 2024, exportPath: './exports',
includeReports: ['balance_sheet', 'income_statement', 'cash_flow', 'trial_balance'], fiscalYear: 2024,
format: 'pdf', dateFrom: new Date('2024-01-01'),
language: 'de', dateTo: new Date('2024-12-31'),
signatureRequired: true, includeDocuments: true,
generatePdfReports: true,
signExport: false,
timestampExport: false,
companyInfo: { companyInfo: {
name: 'Mustermann GmbH', name: 'Example GmbH',
address: 'Hauptstraße 1, 10115 Berlin', taxId: 'DE123456789',
taxNumber: 'DE123456789', registrationNumber: 'HRB 12345',
registrationNumber: 'HRB 12345' address: 'Example Street 1, 28195 Bremen',
} },
}); });
// 3. Generate individual reports for analysis console.log(exportPath);
const incomeStatement = await api.generateIncomeStatement({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
const balanceSheet = await api.generateBalanceSheet({
date: new Date('2024-12-31')
});
const cashFlow = await api.generateCashFlowStatement({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// 4. Export for tax advisor in DATEV format
const datevExport = await api.exportToDATEV({
dateFrom: new Date('2024-01-01'),
dateTo: new Date('2024-12-31')
});
// 5. Create signed export for audit trail
const signedExport = await api.signExport({
data: jahresabschluss,
privateKey: process.env.PRIVATE_KEY!,
certificate: process.env.CERTIFICATE!,
includeTimestamp: true
});
// 6. Close the period
await api.closePeriod('2024-12', {
performYearEndAdjustments: true,
generateReports: true
});
console.log('🎊 Jahresabschluss 2024 Complete!');
console.log(`📈 Umsatz: €${incomeStatement.totalRevenue.toLocaleString('de-DE')}`);
console.log(`💰 Aufwendungen: €${incomeStatement.totalExpenses.toLocaleString('de-DE')}`);
console.log(`📊 Jahresergebnis: €${incomeStatement.netIncome.toLocaleString('de-DE')}`);
console.log(`💼 Bilanzsumme: €${balanceSheet.assets.totalAssets.toLocaleString('de-DE')}`);
console.log(`💵 Cash Flow: €${cashFlow.netCashFlow.toLocaleString('de-DE')}`);
console.log(incomeStatement.netIncome > 0 ? '✅ Gewinn!' : '📉 Verlust');
await api.close();
}
performJahresabschluss().catch(console.error);
``` ```
## 🚦 API Reference The export code creates a BagIt-style folder structure with metadata, accounting data, report output, document storage, and manifest hashes.
### Main Classes ## E-Invoice Workflows
| Class | Description | The package includes invoice types and API helpers for importing, storing, booking, searching, exporting, and generating e-invoices.
|-------|-------------|
| **`SkrApi`** | Main API entry point for all operations |
| **`ChartOfAccounts`** | Account management and initialization |
| **`Ledger`** | General ledger and transaction posting with SKR validation |
| **`Reports`** | Financial reporting and exports |
| **`Account`** | Account model with balance tracking |
| **`Transaction`** | Double-entry transaction model |
| **`JournalEntry`** | Complex multi-line journal entries |
| **`InvoiceAdapter`** | XRechnung/ZUGFeRD invoice processing |
| **`InvoiceBookingEngine`** | Automatic invoice to accounting booking |
| **`InvoiceStorage`** | Invoice persistence and search |
### Key Methods Supported invoice directions:
| Method | Description | - `inbound`
|--------|-------------| - `outbound`
| `initialize(skrType)` | Initialize with SKR03 or SKR04 |
| `postTransaction(data)` | Post a simple two-line transaction |
| `postJournalEntry(data)` | Post complex multi-line journal entry |
| `postBatchTransactions(transactions)` | Post multiple transactions efficiently |
| `reverseTransaction(id)` | Create reversal (Storno) entry |
| `reverseJournalEntry(id)` | Reverse complex journal entries |
| `generateTrialBalance(params)` | Generate Summen- und Saldenliste |
| `generateIncomeStatement(params)` | Generate GuV (P&L) statement |
| `generateBalanceSheet(params)` | Generate Bilanz (balance sheet) |
| `generateCashFlowStatement(params)` | Generate cash flow statement |
| `generateGeneralLedger(params)` | Generate complete general ledger |
| `exportToDATEV(params)` | Export DATEV-compatible data |
| `exportJahresabschluss(params)` | Export complete annual closing package |
| `generatePdfReports(params)` | Generate professional PDF reports |
| `signExport(data)` | Create cryptographically signed exports |
| `importInvoice(data, options)` | Import XRechnung/ZUGFeRD invoices |
| `bookInvoice(invoiceId, rules)` | Book invoice to accounting |
| `exportInvoice(id, options)` | Export invoice in various formats |
| `searchInvoices(filter)` | Search and filter invoices |
| `closePeriod(period, options)` | Close accounting period |
| `recalculateBalances()` | Recalculate all account balances |
| `validateDoubleEntry(data)` | Validate transaction before posting |
| `getUnbalancedTransactions()` | Find integrity issues |
| `createBatchAccounts(accounts)` | Create multiple accounts at once |
## 🏆 Why Developers Love It Supported formats in the invoice model:
- **🎯 Zero Configuration**: Pre-configured SKR03/SKR04 accounts out of the box - `xrechnung`
- **🔄 Automatic Validation**: Never worry about unbalanced entries or wrong account types - `zugferd`
- **📊 Real-time Analytics**: Instant financial insights with live balance updates - `facturx`
- **🛡️ SKR Compliance**: Validates against official SKR standards automatically - `peppol`
- **🚀 High Performance**: Optimized MongoDB queries and batch operations - `ubl`
- **📚 German Compliance**: Full HGB/GoBD compliance built-in
- **🤝 Type Safety**: Complete TypeScript definitions prevent runtime errors
- **🔍 Smart Validation**: Warns about non-standard accounts and type mismatches
- **🧾 E-Invoice Ready**: Native XRechnung/ZUGFeRD support for modern workflows
- **🔐 Audit-Proof**: Cryptographic signatures and Merkle trees for tamper-proof records
- **📑 Professional Reports**: Generate PDF reports that impress auditors and stakeholders
## 📋 Requirements Example import and booking flow:
- **Node.js** >= 18.0.0 ```ts
- **MongoDB** >= 5.0 const invoice = await api.importInvoice('./fixtures/invoice.xml', 'inbound', {
- **TypeScript** >= 5.0 (for development) autoBook: true,
confidenceThreshold: 80,
});
## 🔬 Testing const hits = await api.searchInvoices({
invoiceNumber: invoice.invoiceNumber,
});
The module includes comprehensive test coverage with real-world scenarios: const exported = await api.exportInvoice(invoice, {
format: 'xrechnung',
embedInPdf: true,
});
```
The API also exposes:
- `bookInvoice()`
- `getInvoice()`
- `getInvoiceStatistics()`
- `createInvoiceComplianceReport()`
- `generateInvoice()`
## Public Exports
Top-level exports include:
- `SkrApi`
- `Account`
- `Transaction`
- `JournalEntry`
- `ChartOfAccounts`
- `Ledger`
- `Reports`
- `SkrExport`
- `LedgerExporter`
- `AccountsExporter`
- `BalancesExporter`
- `PdfReportGenerator`
- `SecurityManager`
- `SKR03_ACCOUNTS`, `SKR04_ACCOUNTS`
This makes the package usable as both an application-facing API and a toolkit for custom accounting workflows.
## Development
Build:
```bash ```bash
# Run all tests pnpm build
pnpm test
# Run specific test suites
pnpm test test/test.skr03.ts # SKR03 functionality
pnpm test test/test.skr04.ts # SKR04 functionality
pnpm test test/test.jahresabschluss.skr03.ts # Annual closing SKR03
pnpm test test/test.jahresabschluss.skr04.ts # Annual closing SKR04
pnpm test test/test.invoice.ts # Invoice processing
pnpm test test/test.export.ts # Export functionality
``` ```
Test:
```bash
pnpm test
```
Current project checks include:
- runtime tests for SKR03 and SKR04 flows
- transaction and journal validation
- report generation
- DATEV export
- published type consumption through `test/fixtures/strict-consumer`
## License and Legal Information ## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license.md) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks ### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH. This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
### Company Information ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc. For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
+1 -1
View File
@@ -18,7 +18,7 @@ TypeScript module implementing SKR03 and SKR04 German accounting standards for d
- [ ] @push.rocks/smartdata - [ ] @push.rocks/smartdata
- [ ] @git.zone/tstest (dev dependency) - [ ] @git.zone/tstest (dev dependency)
- [ ] Create tsconfig.json based on @push.rocks/smarthash pattern - [ ] Create tsconfig.json based on @push.rocks/smarthash pattern
- [ ] Create npmextra.json for additional configuration - [ ] Create .smartconfig.json for additional configuration
- [ ] Create .gitignore file - [ ] Create .gitignore file
- [ ] Create directory structure - [ ] Create directory structure
- [ ] ts/ directory for source code - [ ] ts/ directory for source code
+7
View File
@@ -0,0 +1,7 @@
import type { TPostingKey, TSKRType } from '@fin.cx/skr';
const skrType: TSKRType = 'SKR03';
const postingKey: TPostingKey = 40;
void skrType;
void postingKey;
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": false,
"noEmit": true,
"ignoreDeprecations": "6.0",
"types": ["node"],
"baseUrl": ".",
"paths": {
"@fin.cx/skr": ["../../../dist_ts/index.d.ts"]
}
},
"include": ["./index.ts"]
}
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@fin.cx/skr', name: '@fin.cx/skr',
version: '1.2.1', version: '1.3.0',
description: 'SKR03 and SKR04 German accounting standards for double-entry bookkeeping' description: 'SKR03 and SKR04 German accounting standards for double-entry bookkeeping'
} }
+43 -15
View File
@@ -1,16 +1,44 @@
export * from './skr.types.js'; export * from './skr.types.js';
export * from './skr.classes.account.js'; export { Account } from './skr.classes.account.js';
export * from './skr.classes.transaction.js'; export { Transaction } from './skr.classes.transaction.js';
export * from './skr.classes.journalentry.js'; export { JournalEntry } from './skr.classes.journalentry.js';
export * from './skr.classes.chartofaccounts.js'; export { ChartOfAccounts } from './skr.classes.chartofaccounts.js';
export * from './skr.classes.ledger.js'; export { Ledger } from './skr.classes.ledger.js';
export * from './skr.classes.reports.js'; export { Reports } from './skr.classes.reports.js';
export * from './skr.api.js'; export { SkrApi } from './skr.api.js';
export * from './skr03.data.js'; export { SKR03_ACCOUNTS, SKR03_ACCOUNT_CLASSES } from './skr03.data.js';
export * from './skr04.data.js'; export { SKR04_ACCOUNTS, SKR04_ACCOUNT_CLASSES } from './skr04.data.js';
export * from './skr.export.js'; export { SkrExport } from './skr.export.js';
export * from './skr.export.ledger.js'; export type {
export * from './skr.export.accounts.js'; IExportOptions,
export * from './skr.export.balances.js'; IExportMetadata,
export * from './skr.export.pdf.js'; IBagItManifest,
export * from './skr.security.js'; IDocumentIndex,
} from './skr.export.js';
export { LedgerExporter } from './skr.export.ledger.js';
export type {
ITransactionDataExport,
IJournalEntryExport,
IJournalEntryLineExport,
ILedgerEntry,
ILedgerLine,
IDocumentRef,
} from './skr.export.ledger.js';
export { AccountsExporter } from './skr.export.accounts.js';
export type {
IAccountDataExport,
IAccountExportRow,
} from './skr.export.accounts.js';
export { BalancesExporter } from './skr.export.balances.js';
export type {
IAccountBalanceExport,
IBalanceExportRow,
} from './skr.export.balances.js';
export { PdfReportGenerator } from './skr.export.pdf.js';
export type { IPdfReportOptions } from './skr.export.pdf.js';
export { SecurityManager } from './skr.security.js';
export type {
ISigningOptions,
ISignatureResult,
ITimestampResponse,
} from './skr.security.js';
+44 -7
View File
@@ -3,26 +3,63 @@ import * as smartdata from '@push.rocks/smartdata';
import * as smartunique from '@push.rocks/smartunique'; import * as smartunique from '@push.rocks/smartunique';
import * as smarttime from '@push.rocks/smarttime'; import * as smarttime from '@push.rocks/smarttime';
import * as smartlog from '@push.rocks/smartlog'; import * as smartlog from '@push.rocks/smartlog';
import * as smartfile from '@push.rocks/smartfile'; import * as smartfsModule from '@push.rocks/smartfs';
import * as smarthash from '@push.rocks/smarthash'; import * as smarthash from '@push.rocks/smarthash';
import * as smartpath from '@push.rocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
import * as smartpdf from '@push.rocks/smartpdf'; import * as path from 'path';
// third party // third party
import * as nodeForge from 'node-forge';
import { MerkleTree } from 'merkletreejs'; import { MerkleTree } from 'merkletreejs';
import * as einvoice from '@fin.cx/einvoice';
const smartfs = new smartfsModule.SmartFs(
new smartfsModule.SmartFsProviderNode(),
);
const smartfile = {
fs: {
ensureDir: async (dirPath: string): Promise<void> => {
await smartfs.directory(dirPath).create();
},
toBuffer: async (filePath: string): Promise<Buffer> => {
return (await smartfs.file(filePath).read()) as Buffer;
},
toStringSync: async (filePath: string): Promise<string> => {
return (await smartfs.file(filePath).encoding('utf8').read()) as string;
},
fileExists: async (filePath: string): Promise<boolean> => {
return await smartfs.file(filePath).exists();
},
listFileTree: async (dirPath: string, pattern: string): Promise<string[]> => {
const suffix = pattern.replace(/^\*\*\/\*/, '');
try {
const entries = await smartfs.directory(dirPath).recursive().list();
return entries
.filter((entry) => entry.isFile && entry.path.endsWith(suffix))
.map((entry) => path.relative(dirPath, entry.path));
} catch (error) {
if (error instanceof Error && error.message.includes('ENOENT')) {
return [];
}
throw error;
}
},
},
memory: {
toFs: async (content: string | Buffer, filePath: string): Promise<void> => {
await smartfs.directory(path.dirname(filePath)).create();
await smartfs.file(filePath).write(content);
},
},
};
export { export {
smartdata, smartdata,
smartunique, smartunique,
smarttime, smarttime,
smartlog, smartlog,
smartfs,
smartfile, smartfile,
smarthash, smarthash,
smartpath, smartpath,
smartpdf,
nodeForge,
MerkleTree, MerkleTree,
einvoice
}; };
+9 -5
View File
@@ -534,6 +534,10 @@ export class SkrApi {
*/ */
private async generatePdfReports(exporter: SkrExport, options: IExportOptions): Promise<void> { private async generatePdfReports(exporter: SkrExport, options: IExportOptions): Promise<void> {
if (!this.reports) throw new Error('Reports not initialized'); if (!this.reports) throw new Error('Reports not initialized');
const skrType = this.currentSKRType;
if (!skrType) {
throw new Error('API not initialized. Call initialize() first.');
}
const pdfOptions: IPdfReportOptions = { const pdfOptions: IPdfReportOptions = {
companyName: options.companyInfo?.name || 'Unternehmen', companyName: options.companyInfo?.name || 'Unternehmen',
@@ -554,19 +558,19 @@ export class SkrApi {
const trialBalance = await this.reports.getTrialBalance({ const trialBalance = await this.reports.getTrialBalance({
dateFrom: options.dateFrom, dateFrom: options.dateFrom,
dateTo: options.dateTo, dateTo: options.dateTo,
skrType: this.currentSKRType skrType,
}); });
const incomeStatement = await this.reports.getIncomeStatement({ const incomeStatement = await this.reports.getIncomeStatement({
dateFrom: options.dateFrom, dateFrom: options.dateFrom,
dateTo: options.dateTo, dateTo: options.dateTo,
skrType: this.currentSKRType skrType,
}); });
const balanceSheet = await this.reports.getBalanceSheet({ const balanceSheet = await this.reports.getBalanceSheet({
dateFrom: options.dateFrom, dateFrom: options.dateFrom,
dateTo: options.dateTo, dateTo: options.dateTo,
skrType: this.currentSKRType skrType,
}); });
// Generate PDFs // Generate PDFs
@@ -702,7 +706,7 @@ export class SkrApi {
const transaction = await this.postTransaction(transactions[i]); const transaction = await this.postTransaction(transactions[i]);
results.push(transaction); results.push(transaction);
} catch (error) { } catch (error) {
errors.push({ index: i, error: error.message }); errors.push({ index: i, error: error instanceof Error ? error.message : String(error) });
} }
} }
@@ -735,7 +739,7 @@ export class SkrApi {
const account = await this.createAccount(accounts[i]); const account = await this.createAccount(accounts[i]);
results.push(account); results.push(account);
} catch (error) { } catch (error) {
errors.push({ index: i, error: error.message }); errors.push({ index: i, error: error instanceof Error ? error.message : String(error) });
} }
} }
+39 -21
View File
@@ -2,68 +2,86 @@ import * as plugins from './plugins.js';
import { getDb, getDbSync } from './skr.database.js'; import { getDb, getDbSync } from './skr.database.js';
import type { TAccountType, TSKRType, IAccountData } from './skr.types.js'; import type { TAccountType, TSKRType, IAccountData } from './skr.types.js';
const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata; declare abstract class SmartDataDbDocBase {
public save(): Promise<void>;
public delete(): Promise<void>;
public static getInstance<T>(
this: new (...args: any[]) => T,
query: Record<string, any>,
): Promise<T | null>;
public static getInstances<T>(
this: new (...args: any[]) => T,
query: Record<string, any>,
): Promise<T[]>;
}
@plugins.smartdata.Collection(() => getDbSync()) const SmartDataDbDoc = plugins.smartdata.SmartDataDbDoc as unknown as typeof SmartDataDbDocBase;
export class Account extends SmartDataDbDoc<Account, Account> { const Collection = plugins.smartdata.Collection as any;
const svDb = plugins.smartdata.svDb as any;
const unI = plugins.smartdata.unI as any;
const index = plugins.smartdata.index as any;
const searchable = plugins.smartdata.searchable as any;
@Collection(() => getDbSync())
export class Account extends SmartDataDbDoc {
@unI() @unI()
public id: string; public id!: string;
@svDb() @svDb()
@index() @index()
public accountNumber: string; public accountNumber!: string;
@svDb() @svDb()
@searchable() @searchable()
public accountName: string; public accountName!: string;
@svDb() @svDb()
@index() @index()
public accountClass: number; public accountClass!: number;
@svDb() @svDb()
public accountGroup: number; public accountGroup!: number;
@svDb() @svDb()
public accountSubgroup: number; public accountSubgroup!: number;
@svDb() @svDb()
public accountType: TAccountType; public accountType!: TAccountType;
@svDb() @svDb()
@index() @index()
public skrType: TSKRType; public skrType!: TSKRType;
@svDb() @svDb()
@searchable() @searchable()
public description: string; public description!: string;
@svDb() @svDb()
public vatRate: number; public vatRate!: number;
@svDb() @svDb()
public balance: number; public balance!: number;
@svDb() @svDb()
public debitTotal: number; public debitTotal!: number;
@svDb() @svDb()
public creditTotal: number; public creditTotal!: number;
@svDb() @svDb()
public isActive: boolean; public isActive!: boolean;
@svDb() @svDb()
public isSystemAccount: boolean; public isSystemAccount!: boolean;
@svDb() @svDb()
public isAutomaticAccount: boolean; public isAutomaticAccount!: boolean;
@svDb() @svDb()
public createdAt: Date; public createdAt!: Date;
@svDb() @svDb()
public updatedAt: Date; public updatedAt!: Date;
constructor(data?: Partial<IAccountData>) { constructor(data?: Partial<IAccountData>) {
super(); super();
+8 -3
View File
@@ -262,6 +262,9 @@ export class ChartOfAccounts {
* Search accounts * Search accounts
*/ */
public async searchAccounts(searchTerm: string): Promise<Account[]> { public async searchAccounts(searchTerm: string): Promise<Account[]> {
if (!this.skrType) {
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
}
return await Account.searchAccounts(searchTerm, this.skrType); return await Account.searchAccounts(searchTerm, this.skrType);
} }
@@ -287,10 +290,11 @@ export class ChartOfAccounts {
// Apply text search if provided // Apply text search if provided
if (filter?.searchTerm) { if (filter?.searchTerm) {
const lowerSearchTerm = filter.searchTerm.toLowerCase(); const searchTerm = filter.searchTerm;
const lowerSearchTerm = searchTerm.toLowerCase();
return accounts.filter( return accounts.filter(
(account) => (account) =>
account.accountNumber.includes(filter.searchTerm) || account.accountNumber.includes(searchTerm) ||
account.accountName.toLowerCase().includes(lowerSearchTerm) || account.accountName.toLowerCase().includes(lowerSearchTerm) ||
account.description.toLowerCase().includes(lowerSearchTerm), account.description.toLowerCase().includes(lowerSearchTerm),
); );
@@ -468,9 +472,10 @@ export class ChartOfAccounts {
await this.createCustomAccount(accountData); await this.createCustomAccount(accountData);
importedCount++; importedCount++;
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.log( this.logger.log(
'warn', 'warn',
`Failed to import account ${parts[0]}: ${error.message}`, `Failed to import account ${parts[0]}: ${errorMessage}`,
); );
} }
} }
+40 -22
View File
@@ -13,67 +13,85 @@ import type {
IJournalEntryLine, IJournalEntryLine,
} from './skr.types.js'; } from './skr.types.js';
const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata; declare abstract class SmartDataDbDocBase {
public save(): Promise<void>;
public delete(): Promise<void>;
public static getInstance<T>(
this: new (...args: any[]) => T,
query: Record<string, any>,
): Promise<T | null>;
public static getInstances<T>(
this: new (...args: any[]) => T,
query: Record<string, any>,
): Promise<T[]>;
}
@plugins.smartdata.Collection(() => getDbSync()) const SmartDataDbDoc = plugins.smartdata.SmartDataDbDoc as unknown as typeof SmartDataDbDocBase;
export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> { const Collection = plugins.smartdata.Collection as any;
const svDb = plugins.smartdata.svDb as any;
const unI = plugins.smartdata.unI as any;
const index = plugins.smartdata.index as any;
const searchable = plugins.smartdata.searchable as any;
@Collection(() => getDbSync())
export class JournalEntry extends SmartDataDbDoc {
@unI() @unI()
public id: string; public id!: string;
@svDb() @svDb()
@index() @index()
public journalNumber: string; public journalNumber!: string;
@svDb() @svDb()
@index() @index()
public date: Date; public date!: Date;
@svDb() @svDb()
@searchable() @searchable()
public description: string; public description!: string;
@svDb() @svDb()
@index() @index()
public reference: string; public reference!: string;
@svDb() @svDb()
public lines: IJournalEntryLine[]; public lines!: IJournalEntryLine[];
@svDb() @svDb()
@index() @index()
public skrType: TSKRType; public skrType!: TSKRType;
@svDb() @svDb()
public totalDebits: number; public totalDebits!: number;
@svDb() @svDb()
public totalCredits: number; public totalCredits!: number;
@svDb() @svDb()
public isBalanced: boolean; public isBalanced!: boolean;
@svDb() @svDb()
@index() @index()
public status: 'draft' | 'posted' | 'reversed'; public status!: 'draft' | 'posted' | 'reversed';
@svDb() @svDb()
public transactionIds: string[]; public transactionIds!: string[];
@svDb() @svDb()
@index() @index()
public period: string; public period!: string;
@svDb() @svDb()
public fiscalYear: number; public fiscalYear!: number;
@svDb() @svDb()
public createdAt: Date; public createdAt!: Date;
@svDb() @svDb()
public postedAt: Date; public postedAt!: Date | null;
@svDb() @svDb()
public createdBy: string; public createdBy!: string;
constructor(data?: Partial<IJournalEntry>) { constructor(data?: Partial<IJournalEntry>) {
super(); super();
@@ -240,7 +258,7 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
try { try {
await Account.validateAccountForPosting(line.accountNumber, this.skrType); await Account.validateAccountForPosting(line.accountNumber, this.skrType);
} catch (error) { } catch (error) {
validationErrors.push(error.message); validationErrors.push(error instanceof Error ? error.message : String(error));
continue; // Skip further validation for this line continue; // Skip further validation for this line
} }
@@ -329,7 +347,7 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
date: this.date, date: this.date,
debitAccount: debitLines[0].accountNumber, debitAccount: debitLines[0].accountNumber,
creditAccount: creditLines[0].accountNumber, creditAccount: creditLines[0].accountNumber,
amount: debitLines[0].debit, amount: debitLines[0].debit || 0,
description: this.description, description: this.description,
reference: this.reference, reference: this.reference,
skrType: this.skrType, skrType: this.skrType,
+22 -2
View File
@@ -410,7 +410,20 @@ export class Reports {
isActive: true, isActive: true,
}); });
const ledgerEntries = []; const ledgerEntries: Array<{
accountNumber: string;
accountName: string;
accountType: string;
entries: Array<{
date: Date;
reference: string;
description: string;
debit: number;
credit: number;
balance: number;
}>;
finalBalance: number;
}> = [];
for (const account of accounts) { for (const account of accounts) {
const transactions = await this.getAccountTransactions( const transactions = await this.getAccountTransactions(
@@ -420,7 +433,14 @@ export class Reports {
if (transactions.length > 0) { if (transactions.length > 0) {
let runningBalance = 0; let runningBalance = 0;
const accountEntries = []; const accountEntries: Array<{
date: Date;
reference: string;
description: string;
debit: number;
credit: number;
balance: number;
}> = [];
for (const transaction of transactions) { for (const transaction of transactions) {
const isDebit = transaction.debitAccount === account.accountNumber; const isDebit = transaction.debitAccount === account.accountNumber;
+40 -22
View File
@@ -7,75 +7,93 @@ import type {
ITransactionData, ITransactionData,
} from './skr.types.js'; } from './skr.types.js';
const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata; declare abstract class SmartDataDbDocBase {
public save(): Promise<void>;
public delete(): Promise<void>;
public static getInstance<T>(
this: new (...args: any[]) => T,
query: Record<string, any>,
): Promise<T | null>;
public static getInstances<T>(
this: new (...args: any[]) => T,
query: Record<string, any>,
): Promise<T[]>;
}
@plugins.smartdata.Collection(() => getDbSync()) const SmartDataDbDoc = plugins.smartdata.SmartDataDbDoc as unknown as typeof SmartDataDbDocBase;
export class Transaction extends SmartDataDbDoc<Transaction, Transaction> { const Collection = plugins.smartdata.Collection as any;
const svDb = plugins.smartdata.svDb as any;
const unI = plugins.smartdata.unI as any;
const index = plugins.smartdata.index as any;
const searchable = plugins.smartdata.searchable as any;
@Collection(() => getDbSync())
export class Transaction extends SmartDataDbDoc {
@unI() @unI()
public id: string; public id!: string;
@svDb() @svDb()
@index() @index()
public transactionNumber: string; public transactionNumber!: string;
@svDb() @svDb()
@index() @index()
public date: Date; public date!: Date;
@svDb() @svDb()
@index() @index()
public debitAccount: string; public debitAccount!: string;
@svDb() @svDb()
@index() @index()
public creditAccount: string; public creditAccount!: string;
@svDb() @svDb()
public amount: number; public amount!: number;
@svDb() @svDb()
@searchable() @searchable()
public description: string; public description!: string;
@svDb() @svDb()
@index() @index()
public reference: string; public reference!: string;
@svDb() @svDb()
@index() @index()
public skrType: TSKRType; public skrType!: TSKRType;
@svDb() @svDb()
public vatAmount: number; public vatAmount!: number;
@svDb() @svDb()
public costCenter: string; public costCenter!: string;
@svDb() @svDb()
@index() @index()
public status: TTransactionStatus; public status!: TTransactionStatus;
@svDb() @svDb()
public reversalOf: string; public reversalOf!: string;
@svDb() @svDb()
public reversedBy: string; public reversedBy!: string;
@svDb() @svDb()
@index() @index()
public period: string; // Format: YYYY-MM public period!: string; // Format: YYYY-MM
@svDb() @svDb()
public fiscalYear: number; public fiscalYear!: number;
@svDb() @svDb()
public createdAt: Date; public createdAt!: Date;
@svDb() @svDb()
public postedAt: Date; public postedAt!: Date | null;
@svDb() @svDb()
public createdBy: string; public createdBy!: string;
constructor(data?: Partial<ITransactionData>) { constructor(data?: Partial<ITransactionData>) {
super(); super();
+3 -2
View File
@@ -1,5 +1,6 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import * as path from 'path'; import * as path from 'path';
import { SmartPdf } from '@push.rocks/smartpdf';
import type { ITrialBalanceReport, IIncomeStatement, IBalanceSheet } from './skr.types.js'; import type { ITrialBalanceReport, IIncomeStatement, IBalanceSheet } from './skr.types.js';
export interface IPdfReportOptions { export interface IPdfReportOptions {
@@ -17,7 +18,7 @@ export interface IPdfReportOptions {
export class PdfReportGenerator { export class PdfReportGenerator {
private exportPath: string; private exportPath: string;
private options: IPdfReportOptions; private options: IPdfReportOptions;
private pdfInstance: plugins.smartpdf.SmartPdf | null = null; private pdfInstance: SmartPdf | null = null;
constructor(exportPath: string, options: IPdfReportOptions) { constructor(exportPath: string, options: IPdfReportOptions) {
this.exportPath = exportPath; this.exportPath = exportPath;
@@ -28,7 +29,7 @@ export class PdfReportGenerator {
* Initializes the PDF generator * Initializes the PDF generator
*/ */
public async initialize(): Promise<void> { public async initialize(): Promise<void> {
this.pdfInstance = new plugins.smartpdf.SmartPdf(); this.pdfInstance = new SmartPdf();
await this.pdfInstance.start(); await this.pdfInstance.start();
} }
+30 -10
View File
@@ -18,11 +18,25 @@ import type {
*/ */
export class InvoiceAdapter { export class InvoiceAdapter {
private logger: plugins.smartlog.ConsoleLog; private logger: plugins.smartlog.ConsoleLog;
private readonly einvoiceModuleName = '@fin.cx/einvoice';
constructor() { constructor() {
this.logger = new plugins.smartlog.ConsoleLog(); this.logger = new plugins.smartlog.ConsoleLog();
} }
private async getEInvoiceClass(): Promise<{
new (): any;
fromXml(xmlString: string): Promise<any>;
}> {
const { EInvoice } = (await import(this.einvoiceModuleName)) as {
EInvoice: {
new (): any;
fromXml(xmlString: string): Promise<any>;
};
};
return EInvoice;
}
private readonly MAX_XML_SIZE = 10 * 1024 * 1024; // 10MB max private readonly MAX_XML_SIZE = 10 * 1024 * 1024; // 10MB max
private readonly MAX_PDF_SIZE = 50 * 1024 * 1024; // 50MB max private readonly MAX_PDF_SIZE = 50 * 1024 * 1024; // 50MB max
@@ -44,13 +58,14 @@ export class InvoiceAdapter {
} }
// Parse the invoice using @fin.cx/einvoice // Parse the invoice using @fin.cx/einvoice
let einvoice; const EInvoice = await this.getEInvoiceClass();
let einvoice: any;
if (typeof file === 'string') { if (typeof file === 'string') {
einvoice = await plugins.einvoice.EInvoice.fromXml(file); einvoice = await EInvoice.fromXml(file);
} else { } else {
// Convert buffer to string first // Convert buffer to string first
const xmlString = file.toString('utf-8'); const xmlString = file.toString('utf-8');
einvoice = await plugins.einvoice.EInvoice.fromXml(xmlString); einvoice = await EInvoice.fromXml(xmlString);
} }
// Get detected format // Get detected format
@@ -74,7 +89,7 @@ export class InvoiceAdapter {
invoice.xmlContent = einvoice.getXml(); invoice.xmlContent = einvoice.getXml();
// Calculate content hash // Calculate content hash
invoice.contentHash = await this.calculateContentHash(invoice.xmlContent); invoice.contentHash = await this.calculateContentHash(invoice.xmlContent!);
// Classify tax scenario // Classify tax scenario
invoice.taxScenario = this.classifyTaxScenario(invoice); invoice.taxScenario = this.classifyTaxScenario(invoice);
@@ -82,7 +97,8 @@ export class InvoiceAdapter {
return invoice; return invoice;
} catch (error) { } catch (error) {
this.logger.log('error', `Failed to parse invoice: ${error}`); this.logger.log('error', `Failed to parse invoice: ${error}`);
throw new Error(`Invoice parsing failed: ${error.message}`); const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Invoice parsing failed: ${errorMessage}`);
} }
} }
@@ -310,7 +326,7 @@ export class InvoiceAdapter {
* Get exemption reason for VAT category * Get exemption reason for VAT category
*/ */
private getExemptionReason(categoryCode: string): string | undefined { private getExemptionReason(categoryCode: string): string | undefined {
const exemptionReasons: Record<string, string> = { const exemptionReasons: Record<string, string | undefined> = {
'E': 'Tax exempt', 'E': 'Tax exempt',
'Z': 'Zero rated', 'Z': 'Zero rated',
'AE': 'Reverse charge (§13b UStG)', 'AE': 'Reverse charge (§13b UStG)',
@@ -516,7 +532,8 @@ export class InvoiceAdapter {
): Promise<string> { ): Promise<string> {
try { try {
// Load from existing XML // Load from existing XML
const einvoice = await plugins.einvoice.EInvoice.fromXml(invoice.xmlContent!); const EInvoice = await this.getEInvoiceClass();
const einvoice: any = await EInvoice.fromXml(invoice.xmlContent!);
// Convert to target format (takes ~0.6ms) // Convert to target format (takes ~0.6ms)
const convertedXml = await einvoice.exportXml(targetFormat as any); const convertedXml = await einvoice.exportXml(targetFormat as any);
@@ -524,7 +541,8 @@ export class InvoiceAdapter {
return convertedXml; return convertedXml;
} catch (error) { } catch (error) {
this.logger.log('error', `Failed to convert invoice format: ${error}`); this.logger.log('error', `Failed to convert invoice format: ${error}`);
throw new Error(`Format conversion failed: ${error.message}`); const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Format conversion failed: ${errorMessage}`);
} }
} }
@@ -537,7 +555,8 @@ export class InvoiceAdapter {
): Promise<{ xml: string; pdf?: Buffer }> { ): Promise<{ xml: string; pdf?: Buffer }> {
try { try {
// Create a new invoice instance // Create a new invoice instance
const einvoice = new plugins.einvoice.EInvoice(); const EInvoice = await this.getEInvoiceClass();
const einvoice: any = new EInvoice();
// Set invoice data // Set invoice data
const businessTerms = this.mapToBusinessTerms(invoiceData); const businessTerms = this.mapToBusinessTerms(invoiceData);
@@ -558,7 +577,8 @@ export class InvoiceAdapter {
return { xml, pdf }; return { xml, pdf };
} catch (error) { } catch (error) {
this.logger.log('error', `Failed to generate invoice: ${error}`); this.logger.log('error', `Failed to generate invoice: ${error}`);
throw new Error(`Invoice generation failed: ${error.message}`); const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Invoice generation failed: ${errorMessage}`);
} }
} }
+4 -2
View File
@@ -131,10 +131,11 @@ export class InvoiceBookingEngine {
}; };
} catch (error) { } catch (error) {
this.logger.log('error', `Failed to book invoice: ${error}`); this.logger.log('error', `Failed to book invoice: ${error}`);
const errorMessage = error instanceof Error ? error.message : String(error);
return { return {
success: false, success: false,
confidence: 0, confidence: 0,
errors: [`Booking failed: ${error.message}`] errors: [`Booking failed: ${errorMessage}`]
}; };
} }
} }
@@ -574,10 +575,11 @@ export class InvoiceBookingEngine {
}; };
} catch (error) { } catch (error) {
this.logger.log('error', `Failed to book payment: ${error}`); this.logger.log('error', `Failed to book payment: ${error}`);
const errorMessage = error instanceof Error ? error.message : String(error);
return { return {
success: false, success: false,
confidence: 0, confidence: 0,
errors: [`Payment booking failed: ${error.message}`] errors: [`Payment booking failed: ${errorMessage}`]
}; };
} }
} }
+2 -1
View File
@@ -224,7 +224,8 @@ export class InvoiceStorage {
return contentHash; return contentHash;
} catch (error) { } catch (error) {
this.logger.log('error', `Failed to store invoice: ${error}`); this.logger.log('error', `Failed to store invoice: ${error}`);
throw new Error(`Invoice storage failed: ${error.message}`); const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Invoice storage failed: ${errorMessage}`);
} }
} }
+22 -21
View File
@@ -2,6 +2,7 @@ import * as plugins from './plugins.js';
import * as path from 'path'; import * as path from 'path';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as https from 'https'; import * as https from 'https';
import * as nodeForge from 'node-forge';
export interface ISigningOptions { export interface ISigningOptions {
certificatePem?: string; certificatePem?: string;
@@ -57,19 +58,19 @@ export class SecurityManager {
try { try {
// Parse certificate and key // Parse certificate and key
const certificate = plugins.nodeForge.pki.certificateFromPem(cert); const certificate = nodeForge.pki.certificateFromPem(cert);
const privateKey = this.options.privateKeyPassphrase const privateKey = this.options.privateKeyPassphrase
? plugins.nodeForge.pki.decryptRsaPrivateKey(key, this.options.privateKeyPassphrase) ? nodeForge.pki.decryptRsaPrivateKey(key, this.options.privateKeyPassphrase)
: plugins.nodeForge.pki.privateKeyFromPem(key); : nodeForge.pki.privateKeyFromPem(key);
// Create PKCS#7 signed data (CMS) // Create PKCS#7 signed data (CMS)
const p7 = plugins.nodeForge.pkcs7.createSignedData(); const p7 = nodeForge.pkcs7.createSignedData();
// Add content // Add content
if (typeof data === 'string') { if (typeof data === 'string') {
p7.content = plugins.nodeForge.util.createBuffer(data, 'utf8'); p7.content = nodeForge.util.createBuffer(data, 'utf8');
} else { } else {
p7.content = plugins.nodeForge.util.createBuffer(data.toString('latin1')); p7.content = nodeForge.util.createBuffer(data.toString('latin1'));
} }
// Add certificate // Add certificate
@@ -79,17 +80,17 @@ export class SecurityManager {
p7.addSigner({ p7.addSigner({
key: privateKey, key: privateKey,
certificate: certificate, certificate: certificate,
digestAlgorithm: plugins.nodeForge.pki.oids.sha256, digestAlgorithm: nodeForge.pki.oids.sha256,
authenticatedAttributes: [ authenticatedAttributes: [
{ {
type: plugins.nodeForge.pki.oids.contentType, type: nodeForge.pki.oids.contentType,
value: plugins.nodeForge.pki.oids.data value: nodeForge.pki.oids.data
}, },
{ {
type: plugins.nodeForge.pki.oids.messageDigest type: nodeForge.pki.oids.messageDigest
}, },
{ {
type: plugins.nodeForge.pki.oids.signingTime, type: nodeForge.pki.oids.signingTime,
value: new Date().toISOString() value: new Date().toISOString()
} }
] ]
@@ -99,7 +100,7 @@ export class SecurityManager {
p7.sign({ detached: true }); p7.sign({ detached: true });
// Convert to PEM // Convert to PEM
const pem = plugins.nodeForge.pkcs7.messageToPem(p7); const pem = nodeForge.pkcs7.messageToPem(p7);
// Extract base64 signature // Extract base64 signature
const signature = pem const signature = pem
@@ -237,14 +238,14 @@ export class SecurityManager {
} }
// Parse the PKCS#7 message // Parse the PKCS#7 message
const p7 = plugins.nodeForge.pkcs7.messageFromPem(pemSignature); const p7 = nodeForge.pkcs7.messageFromPem(pemSignature);
// Prepare content for verification // Prepare content for verification
let content: plugins.nodeForge.util.ByteStringBuffer; let content: nodeForge.util.ByteStringBuffer;
if (typeof data === 'string') { if (typeof data === 'string') {
content = plugins.nodeForge.util.createBuffer(data, 'utf8'); content = nodeForge.util.createBuffer(data, 'utf8');
} else { } else {
content = plugins.nodeForge.util.createBuffer(data.toString('latin1')); content = nodeForge.util.createBuffer(data.toString('latin1'));
} }
// Verify the signature // Verify the signature
@@ -267,8 +268,8 @@ export class SecurityManager {
commonName: string = 'SKR Export System', commonName: string = 'SKR Export System',
validDays: number = 365 validDays: number = 365
): Promise<{ certificate: string; privateKey: string }> { ): Promise<{ certificate: string; privateKey: string }> {
const keys = plugins.nodeForge.pki.rsa.generateKeyPair(2048); const keys = nodeForge.pki.rsa.generateKeyPair(2048);
const cert = plugins.nodeForge.pki.createCertificate(); const cert = nodeForge.pki.createCertificate();
cert.publicKey = keys.publicKey; cert.publicKey = keys.publicKey;
cert.serialNumber = '01'; cert.serialNumber = '01';
@@ -326,11 +327,11 @@ export class SecurityManager {
]); ]);
// Self-sign certificate // Self-sign certificate
cert.sign(keys.privateKey, plugins.nodeForge.md.sha256.create()); cert.sign(keys.privateKey, nodeForge.md.sha256.create());
// Convert to PEM // Convert to PEM
const certificatePem = plugins.nodeForge.pki.certificateToPem(cert); const certificatePem = nodeForge.pki.certificateToPem(cert);
const privateKeyPem = plugins.nodeForge.pki.privateKeyToPem(keys.privateKey); const privateKeyPem = nodeForge.pki.privateKeyToPem(keys.privateKey);
return { return {
certificate: certificatePem, certificate: certificatePem,
+1 -5
View File
@@ -1,15 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": false,
"target": "ES2022", "target": "ES2022",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"baseUrl": ".", "types": ["node"]
"paths": {}
}, },
"exclude": ["dist_*/**/*.d.ts"] "exclude": ["dist_*/**/*.d.ts"]
} }