283 lines
7.4 KiB
TypeScript
283 lines
7.4 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as skr from '../ts/index.js';
|
|
import { getTestConfig } from './helpers/setup.js';
|
|
|
|
let api: skr.SkrApi;
|
|
let testConfig: Awaited<ReturnType<typeof getTestConfig>>;
|
|
|
|
tap.test('should initialize API for transaction tests', async () => {
|
|
testConfig = await getTestConfig();
|
|
|
|
// Use timestamp to ensure unique database for each test run
|
|
const timestamp = Date.now();
|
|
api = new skr.SkrApi({
|
|
mongoDbUrl: testConfig.mongoDbUrl,
|
|
dbName: `${testConfig.mongoDbName}_transactions_${timestamp}`,
|
|
});
|
|
|
|
await api.initialize('SKR03');
|
|
expect(api.getSKRType()).toEqual('SKR03');
|
|
});
|
|
|
|
tap.test('should enforce double-entry bookkeeping rules', async () => {
|
|
let errorThrown = false;
|
|
|
|
try {
|
|
// Try to post unbalanced journal entry
|
|
await api.postJournalEntry({
|
|
date: new Date(),
|
|
description: 'Unbalanced entry',
|
|
reference: 'TEST-001',
|
|
lines: [
|
|
{ accountNumber: '1000', debit: 100 },
|
|
{ accountNumber: '4000', credit: 50 }, // Unbalanced!
|
|
],
|
|
skrType: 'SKR03',
|
|
});
|
|
} catch (error) {
|
|
errorThrown = true;
|
|
expect(error.message).toInclude('not balanced');
|
|
}
|
|
|
|
expect(errorThrown).toBeTrue();
|
|
});
|
|
|
|
tap.test('should prevent posting to same account', async () => {
|
|
let errorThrown = false;
|
|
|
|
try {
|
|
await api.postTransaction({
|
|
date: new Date(),
|
|
debitAccount: '1000',
|
|
creditAccount: '1000', // Same account!
|
|
amount: 100,
|
|
description: 'Invalid transaction',
|
|
skrType: 'SKR03',
|
|
});
|
|
} catch (error) {
|
|
errorThrown = true;
|
|
expect(error.message).toInclude('cannot be the same');
|
|
}
|
|
|
|
expect(errorThrown).toBeTrue();
|
|
});
|
|
|
|
tap.test('should prevent posting to inactive account', async () => {
|
|
// First create and deactivate an account
|
|
const customAccount = await api.createAccount({
|
|
accountNumber: '9998',
|
|
accountName: 'Inactive Test Account',
|
|
accountClass: 9,
|
|
accountType: 'equity',
|
|
isActive: false,
|
|
});
|
|
|
|
let errorThrown = false;
|
|
|
|
try {
|
|
await api.postTransaction({
|
|
date: new Date(),
|
|
debitAccount: '9998', // Inactive account
|
|
creditAccount: '1000',
|
|
amount: 100,
|
|
description: 'Transaction to inactive account',
|
|
skrType: 'SKR03',
|
|
});
|
|
} catch (error) {
|
|
errorThrown = true;
|
|
expect(error.message).toInclude('not active');
|
|
}
|
|
|
|
expect(errorThrown).toBeTrue();
|
|
});
|
|
|
|
tap.test(
|
|
'should handle complex journal entry with multiple lines',
|
|
async () => {
|
|
const journalEntry = await api.postJournalEntry({
|
|
date: new Date(),
|
|
description: 'Complex distribution',
|
|
reference: 'COMPLEX-001',
|
|
lines: [
|
|
{ accountNumber: '5000', debit: 500, description: 'Materials' },
|
|
{ accountNumber: '6000', debit: 300, description: 'Wages' },
|
|
{ accountNumber: '7100', debit: 200, description: 'Rent' },
|
|
{ accountNumber: '1200', credit: 1000, description: 'Bank payment' },
|
|
],
|
|
skrType: 'SKR03',
|
|
});
|
|
|
|
expect(journalEntry.lines.length).toEqual(4);
|
|
expect(journalEntry.totalDebits).toEqual(1000);
|
|
expect(journalEntry.totalCredits).toEqual(1000);
|
|
expect(journalEntry.isBalanced).toBeTrue();
|
|
},
|
|
);
|
|
|
|
tap.test('should track transaction history for account', async () => {
|
|
// Post multiple transactions
|
|
await api.postTransaction({
|
|
date: new Date('2024-01-01'),
|
|
debitAccount: '1000',
|
|
creditAccount: '4000',
|
|
amount: 100,
|
|
description: 'Sale 1',
|
|
skrType: 'SKR03',
|
|
});
|
|
|
|
await api.postTransaction({
|
|
date: new Date('2024-01-02'),
|
|
debitAccount: '1000',
|
|
creditAccount: '4000',
|
|
amount: 200,
|
|
description: 'Sale 2',
|
|
skrType: 'SKR03',
|
|
});
|
|
|
|
await api.postTransaction({
|
|
date: new Date('2024-01-03'),
|
|
debitAccount: '5000',
|
|
creditAccount: '1000',
|
|
amount: 50,
|
|
description: 'Purchase',
|
|
skrType: 'SKR03',
|
|
});
|
|
|
|
// Get transaction history for cash account
|
|
const history = await api.getAccountTransactions('1000');
|
|
|
|
expect(history.length).toBeGreaterThanOrEqual(3);
|
|
|
|
// Check balance calculation
|
|
const balance = await api.getAccountBalance('1000');
|
|
expect(balance.debitTotal).toBeGreaterThanOrEqual(300);
|
|
expect(balance.creditTotal).toBeGreaterThanOrEqual(50);
|
|
});
|
|
|
|
tap.test('should filter transactions by date range', async () => {
|
|
const startDate = new Date('2024-01-01');
|
|
const endDate = new Date('2024-01-31');
|
|
|
|
const transactions = await api.listTransactions({
|
|
dateFrom: startDate,
|
|
dateTo: endDate,
|
|
});
|
|
|
|
for (const transaction of transactions) {
|
|
expect(transaction.date.getTime()).toBeGreaterThanOrEqual(
|
|
startDate.getTime(),
|
|
);
|
|
expect(transaction.date.getTime()).toBeLessThanOrEqual(endDate.getTime());
|
|
}
|
|
});
|
|
|
|
tap.test('should filter transactions by amount range', async () => {
|
|
const transactions = await api.listTransactions({
|
|
minAmount: 100,
|
|
maxAmount: 500,
|
|
});
|
|
|
|
for (const transaction of transactions) {
|
|
expect(transaction.amount).toBeGreaterThanOrEqual(100);
|
|
expect(transaction.amount).toBeLessThanOrEqual(500);
|
|
}
|
|
});
|
|
|
|
tap.test('should handle batch transaction posting', async () => {
|
|
const batchTransactions = [
|
|
{
|
|
date: new Date(),
|
|
debitAccount: '1200',
|
|
creditAccount: '4000',
|
|
amount: 100,
|
|
description: 'Batch sale 1',
|
|
skrType: 'SKR03' as skr.TSKRType,
|
|
},
|
|
{
|
|
date: new Date(),
|
|
debitAccount: '1200',
|
|
creditAccount: '4000',
|
|
amount: 200,
|
|
description: 'Batch sale 2',
|
|
skrType: 'SKR03' as skr.TSKRType,
|
|
},
|
|
{
|
|
date: new Date(),
|
|
debitAccount: '1200',
|
|
creditAccount: '4000',
|
|
amount: 300,
|
|
description: 'Batch sale 3',
|
|
skrType: 'SKR03' as skr.TSKRType,
|
|
},
|
|
];
|
|
|
|
const results = await api.postBatchTransactions(batchTransactions);
|
|
|
|
expect(results.length).toEqual(3);
|
|
expect(results[0].amount).toEqual(100);
|
|
expect(results[1].amount).toEqual(200);
|
|
expect(results[2].amount).toEqual(300);
|
|
});
|
|
|
|
tap.test('should handle transaction with VAT', async () => {
|
|
const transaction = await api.postTransaction({
|
|
date: new Date(),
|
|
debitAccount: '5400', // Goods with 19% VAT
|
|
creditAccount: '1600', // Trade payables
|
|
amount: 119,
|
|
description: 'Purchase including VAT',
|
|
skrType: 'SKR03',
|
|
vatAmount: 19,
|
|
reference: 'VAT-001',
|
|
});
|
|
|
|
expect(transaction.vatAmount).toEqual(19);
|
|
expect(transaction.amount).toEqual(119);
|
|
});
|
|
|
|
tap.test('should handle transaction with cost center', async () => {
|
|
const transaction = await api.postTransaction({
|
|
date: new Date(),
|
|
debitAccount: '6000', // Wages
|
|
creditAccount: '1200', // Bank
|
|
amount: 1000,
|
|
description: 'Salary for marketing department',
|
|
skrType: 'SKR03',
|
|
costCenter: 'MARKETING',
|
|
});
|
|
|
|
expect(transaction.costCenter).toEqual('MARKETING');
|
|
});
|
|
|
|
tap.test('should validate account numbers are 4 digits', async () => {
|
|
let errorThrown = false;
|
|
|
|
try {
|
|
await api.createAccount({
|
|
accountNumber: '123', // Only 3 digits!
|
|
accountName: 'Invalid Account',
|
|
accountClass: 1,
|
|
accountType: 'asset',
|
|
});
|
|
} catch (error) {
|
|
errorThrown = true;
|
|
expect(error.message).toInclude('4 digits');
|
|
}
|
|
|
|
expect(errorThrown).toBeTrue();
|
|
});
|
|
|
|
tap.test('should recalculate all balances', async () => {
|
|
await api.recalculateBalances();
|
|
|
|
// Verify balances are consistent
|
|
const trialBalance = await api.generateTrialBalance();
|
|
expect(trialBalance.isBalanced).toBeTrue();
|
|
});
|
|
|
|
tap.test('should close API connection', async () => {
|
|
await api.close();
|
|
});
|
|
|
|
export default tap.start();
|