feat(tests): integrate qenv for dynamic configuration and enhance SKR API tests
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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;
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user