feat: Enhance journal entry and transaction handling with posting keys
- Added posting key support to SKR03 and SKR04 journal entries and transactions to ensure DATEV compliance. - Implemented validation for posting keys in journal entries, ensuring all lines have a posting key and that they are consistent across the entry. - Introduced automatic account checks to prevent posting to accounts that cannot be directly posted to (e.g., 1400, 1600). - Updated account validation to include checks for debtor and creditor ranges. - Enhanced invoice booking logic to include appropriate posting keys based on VAT rates and scenarios. - Created a new module for posting key definitions and validation rules, including functions for validating posting keys and suggesting appropriate keys based on transaction parameters. - Updated tests to cover new posting key functionality and ensure compliance with accounting rules.
This commit is contained in:
@@ -2,6 +2,11 @@ import * as plugins from './plugins.js';
|
||||
import { getDbSync } from './skr.database.js';
|
||||
import { Account } from './skr.classes.account.js';
|
||||
import { Transaction } from './skr.classes.transaction.js';
|
||||
import {
|
||||
validatePostingKey,
|
||||
validatePostingKeyConsistency,
|
||||
getPostingKeyDescription,
|
||||
} from './skr.postingkeys.js';
|
||||
import type {
|
||||
TSKRType,
|
||||
IJournalEntry,
|
||||
@@ -212,22 +217,84 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
|
||||
throw new Error('Journal entry must have at least 2 lines');
|
||||
}
|
||||
|
||||
// Validate all accounts exist and are active
|
||||
// Validate all accounts exist, are active, and can be posted to
|
||||
const validationErrors: string[] = [];
|
||||
const validationWarnings: string[] = [];
|
||||
|
||||
for (const line of this.lines) {
|
||||
// Validate posting key is present (REQUIRED)
|
||||
if (!line.postingKey) {
|
||||
validationErrors.push(
|
||||
`Line for account ${line.accountNumber} is missing required posting key (Buchungsschlüssel). ` +
|
||||
`Posting keys are mandatory for DATEV compliance.`
|
||||
);
|
||||
continue; // Skip further validation for this line
|
||||
}
|
||||
|
||||
// Validate account is not an automatic account (Automatikkonto)
|
||||
try {
|
||||
await Account.validateAccountForPosting(line.accountNumber, this.skrType);
|
||||
} catch (error) {
|
||||
validationErrors.push(error.message);
|
||||
continue; // Skip further validation for this line
|
||||
}
|
||||
|
||||
// Get account for posting key validation
|
||||
const account = await Account.getAccountByNumber(
|
||||
line.accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
validationErrors.push(
|
||||
`Account ${line.accountNumber} not found for ${this.skrType}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!account.isActive) {
|
||||
throw new Error(`Account ${line.accountNumber} is not active`);
|
||||
validationErrors.push(`Account ${line.accountNumber} is not active`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate posting key for this line
|
||||
const amount = line.debit || line.credit || 0;
|
||||
const postingKeyValidation = validatePostingKey(
|
||||
line.postingKey,
|
||||
line.accountNumber,
|
||||
amount
|
||||
);
|
||||
|
||||
if (!postingKeyValidation.isValid) {
|
||||
validationErrors.push(...postingKeyValidation.errors);
|
||||
}
|
||||
|
||||
if (postingKeyValidation.warnings.length > 0) {
|
||||
validationWarnings.push(...postingKeyValidation.warnings);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate posting key consistency across all lines
|
||||
const consistencyValidation = validatePostingKeyConsistency(this.lines);
|
||||
if (!consistencyValidation.isValid) {
|
||||
validationErrors.push(...consistencyValidation.errors);
|
||||
}
|
||||
if (consistencyValidation.warnings.length > 0) {
|
||||
validationWarnings.push(...consistencyValidation.warnings);
|
||||
}
|
||||
|
||||
// Log warnings but don't fail validation
|
||||
if (validationWarnings.length > 0) {
|
||||
console.warn('Journal entry validation warnings:');
|
||||
validationWarnings.forEach(warning => console.warn(` - ${warning}`));
|
||||
}
|
||||
|
||||
// Throw if any errors
|
||||
if (validationErrors.length > 0) {
|
||||
throw new Error(
|
||||
'Journal entry validation failed:\n' +
|
||||
validationErrors.map(e => ` - ${e}`).join('\n')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +392,7 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
|
||||
credit: line.debit, // Swap
|
||||
description: `Reversal: ${line.description || ''}`,
|
||||
costCenter: line.costCenter,
|
||||
postingKey: line.postingKey, // Keep same posting key for reversal
|
||||
}));
|
||||
|
||||
const reversalEntry = new JournalEntry({
|
||||
|
||||
Reference in New Issue
Block a user