feat(tests): integrate qenv for dynamic configuration and enhance SKR API tests

This commit is contained in:
2025-08-10 19:52:23 +00:00
parent f42c8539a6
commit 10ca6f2992
10 changed files with 693 additions and 27 deletions

View File

@@ -158,7 +158,8 @@ export class SkrApi {
transactionData: ITransactionData,
): Promise<Transaction> {
this.ensureInitialized();
return await this.chartOfAccounts.postTransaction(transactionData);
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.postTransaction(transactionData);
}
/**
@@ -168,7 +169,8 @@ export class SkrApi {
journalData: IJournalEntry,
): Promise<JournalEntry> {
this.ensureInitialized();
return await this.chartOfAccounts.postJournalEntry(journalData);
if (!this.ledger) throw new Error('Ledger not initialized');
return await this.ledger.postJournalEntry(journalData);
}
/**

View File

@@ -96,6 +96,8 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
this.postedAt = null;
this.createdBy = 'system';
// Normalize any negative amounts to the correct side
this.sanitizeLines();
// Calculate totals
this.calculateTotals();
}
@@ -107,6 +109,36 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
return `JE-${timestamp}-${random}`;
}
private sanitizeLines(): void {
for (const line of this.lines) {
// Check if both debit and credit are set (not allowed)
if (line.debit !== undefined && line.debit !== 0 &&
line.credit !== undefined && line.credit !== 0) {
throw new Error('A line cannot have both debit and credit amounts');
}
// Handle negative debit - convert to positive credit
if (line.debit !== undefined && line.debit < 0) {
line.credit = Math.abs(line.debit);
delete (line as any).debit;
}
// Handle negative credit - convert to positive debit
if (line.credit !== undefined && line.credit < 0) {
line.debit = Math.abs(line.credit);
delete (line as any).credit;
}
// Check that at least one side has a positive value
const hasDebit = line.debit !== undefined && line.debit > 0;
const hasCredit = line.credit !== undefined && line.credit > 0;
if (!hasDebit && !hasCredit) {
throw new Error('Either debit or credit must be a positive number');
}
}
}
private calculateTotals(): void {
this.totalDebits = 0;
this.totalCredits = 0;
@@ -204,6 +236,8 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
throw new Error('Journal entry is already posted');
}
// Normalize any negative amounts to the correct side
this.sanitizeLines();
// Validate before posting
await this.validate();
@@ -230,28 +264,41 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
transactions.push(transaction);
} else {
// Complex entry: multiple debits and/or credits
// Create transactions to balance the entry
for (const debitLine of debitLines) {
for (const creditLine of creditLines) {
const amount = Math.min(debitLine.debit || 0, creditLine.credit || 0);
// Build working queues with remaining amounts (don't mutate original lines)
const debitQueue = debitLines.map(l => ({
line: l,
remaining: l.debit || 0
}));
const creditQueue = creditLines.map(l => ({
line: l,
remaining: l.credit || 0
}));
if (amount > 0) {
// Create transactions to balance the entry
for (const d of debitQueue) {
for (const c of creditQueue) {
const amount = Math.min(d.remaining, c.remaining);
if (amount > 0.0000001) { // small epsilon to avoid float artifacts
const transaction = await Transaction.createTransaction({
date: this.date,
debitAccount: debitLine.accountNumber,
creditAccount: creditLine.accountNumber,
amount: amount,
description: `${this.description} - ${debitLine.description || creditLine.description || ''}`,
debitAccount: d.line.accountNumber,
creditAccount: c.line.accountNumber,
amount: Math.round(amount * 100) / 100, // round to 2 decimals
description: `${this.description} - ${d.line.description || c.line.description || ''}`,
reference: this.reference,
skrType: this.skrType,
costCenter: debitLine.costCenter || creditLine.costCenter,
costCenter: d.line.costCenter || c.line.costCenter,
});
transactions.push(transaction);
// Reduce amounts for tracking
if (debitLine.debit) debitLine.debit -= amount;
if (creditLine.credit) creditLine.credit -= amount;
// Reduce remaining amounts in working copies (not original lines)
d.remaining -= amount;
c.remaining -= amount;
}
if (d.remaining <= 0.0000001) break;
}
}
}
@@ -299,6 +346,8 @@ export class JournalEntry extends SmartDataDbDoc<JournalEntry, JournalEntry> {
}
public async beforeSave(): Promise<void> {
// Normalize any negative amounts to the correct side
this.sanitizeLines();
// Recalculate totals before saving
this.calculateTotals();

View File

@@ -344,9 +344,28 @@ export class Reports {
// Apply date filter if provided
if (params?.dateFrom || params?.dateTo) {
// Normalize dates for inclusive comparison
const dateFrom = params.dateFrom ? new Date(params.dateFrom) : null;
const dateTo = params.dateTo ? new Date(params.dateTo) : null;
// Set dateFrom to start of day (00:00:00.000)
if (dateFrom) {
dateFrom.setHours(0, 0, 0, 0);
}
// Set dateTo to end of day (23:59:59.999) for inclusive comparison
if (dateTo) {
dateTo.setHours(23, 59, 59, 999);
}
transactions = transactions.filter((transaction) => {
if (params.dateFrom && transaction.date < params.dateFrom) return false;
if (params.dateTo && transaction.date > params.dateTo) return false;
const txDate = transaction.date instanceof Date
? transaction.date
: new Date(transaction.date);
const txTime = txDate.getTime();
if (dateFrom && txTime < dateFrom.getTime()) return false;
if (dateTo && txTime > dateTo.getTime()) return false;
return true;
});
}
@@ -453,9 +472,28 @@ export class Reports {
// Apply date filter
if (params?.dateFrom || params?.dateTo) {
// Normalize dates for inclusive comparison
const dateFrom = params.dateFrom ? new Date(params.dateFrom) : null;
const dateTo = params.dateTo ? new Date(params.dateTo) : null;
// Set dateFrom to start of day (00:00:00.000)
if (dateFrom) {
dateFrom.setHours(0, 0, 0, 0);
}
// Set dateTo to end of day (23:59:59.999) for inclusive comparison
if (dateTo) {
dateTo.setHours(23, 59, 59, 999);
}
transactions = transactions.filter((transaction) => {
if (params.dateFrom && transaction.date < params.dateFrom) return false;
if (params.dateTo && transaction.date > params.dateTo) return false;
const txDate = transaction.date instanceof Date
? transaction.date
: new Date(transaction.date);
const txTime = txDate.getTime();
if (dateFrom && txTime < dateFrom.getTime()) return false;
if (dateTo && txTime > dateTo.getTime()) return false;
return true;
});
}