fix: match uncategorized bank transactions

This commit is contained in:
Ahmed Bouhuolia
2025-12-22 23:02:08 +02:00
parent a9a7cd8617
commit 37f0f4e227
3 changed files with 37 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
import * as moment from 'moment'; import * as moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import { isEmpty, sumBy } from 'lodash'; import { isEmpty, round, sumBy } from 'lodash';
import { ERRORS, MatchedTransactionPOJO } from './types'; import { ERRORS, MatchedTransactionPOJO } from './types';
import { ServiceError } from '../Items/ServiceError'; import { ServiceError } from '../Items/ServiceError';
@@ -22,18 +22,24 @@ export const sortClosestMatchTransactions = (
}; };
export const sumMatchTranasctions = (transactions: Array<any>) => { export const sumMatchTranasctions = (transactions: Array<any>) => {
return transactions.reduce( const total = transactions.reduce(
(total, item) => (sum, item) => {
total + const amount = parseFloat(item.amount) || 0;
(item.transactionNormal === 'debit' ? 1 : -1) * parseFloat(item.amount), const multiplier = item.transactionNormal === 'debit' ? 1 : -1;
return sum + multiplier * amount;
},
0 0
); );
// Round to 2 decimal places to avoid floating-point precision issues
return round(total, 2);
}; };
export const sumUncategorizedTransactions = ( export const sumUncategorizedTransactions = (
uncategorizedTransactions: Array<any> uncategorizedTransactions: Array<any>
) => { ) => {
return sumBy(uncategorizedTransactions, 'amount'); const total = sumBy(uncategorizedTransactions, 'amount');
// Round to 2 decimal places to avoid floating-point precision issues
return round(total, 2);
}; };
export const validateUncategorizedTransactionsNotMatched = ( export const validateUncategorizedTransactionsNotMatched = (

View File

@@ -34,7 +34,7 @@ export class MatchBankTransactions {
private readonly uncategorizedBankTransactionModel: TenantModelProxy< private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction typeof UncategorizedBankTransaction
>, >,
) {} ) { }
/** /**
* Validates the match bank transactions DTO. * Validates the match bank transactions DTO.
@@ -100,7 +100,10 @@ export class MatchBankTransactions {
); );
// Validates the total given matching transcations whether is not equal // Validates the total given matching transcations whether is not equal
// uncategorized transaction amount. // uncategorized transaction amount.
if (totalUncategorizedTransactions !== totalMatchedTranasctions) { // Use tolerance-based comparison to handle floating-point precision issues
const tolerance = 0.01; // Allow 0.01 difference for floating-point precision
const difference = Math.abs(totalUncategorizedTransactions - totalMatchedTranasctions);
if (difference > tolerance) {
throw new ServiceError(ERRORS.TOTAL_MATCHING_TRANSACTIONS_INVALID); throw new ServiceError(ERRORS.TOTAL_MATCHING_TRANSACTIONS_INVALID);
} }
} }

View File

@@ -12,24 +12,30 @@ export class MatchTransactionsTypes {
private static registry: MatchTransactionsTypesRegistry; private static registry: MatchTransactionsTypesRegistry;
/** /**
* Consttuctor method. * Constructor method.
*/ */
constructor() { constructor(
private readonly getMatchedInvoicesService: GetMatchedTransactionsByInvoices,
private readonly getMatchedBillsService: GetMatchedTransactionsByBills,
private readonly getMatchedExpensesService: GetMatchedTransactionsByExpenses,
private readonly getMatchedManualJournalsService: GetMatchedTransactionsByManualJournals,
private readonly getMatchedCashflowService: GetMatchedTransactionsByCashflow,
) {
this.boot(); this.boot();
} }
get registered() { get registered() {
return [ return [
{ type: 'SaleInvoice', service: GetMatchedTransactionsByInvoices }, { type: 'SaleInvoice', service: this.getMatchedInvoicesService },
{ type: 'Bill', service: GetMatchedTransactionsByBills }, { type: 'Bill', service: this.getMatchedBillsService },
{ type: 'Expense', service: GetMatchedTransactionsByExpenses }, { type: 'Expense', service: this.getMatchedExpensesService },
{ {
type: 'ManualJournal', type: 'ManualJournal',
service: GetMatchedTransactionsByManualJournals, service: this.getMatchedManualJournalsService,
}, },
{ {
type: 'CashflowTransaction', type: 'CashflowTransaction',
service: GetMatchedTransactionsByCashflow, service: this.getMatchedCashflowService,
}, },
]; ];
} }
@@ -50,14 +56,13 @@ export class MatchTransactionsTypes {
* Boots all the registered importables. * Boots all the registered importables.
*/ */
public boot() { public boot() {
if (!MatchTransactionsTypes.registry) { const instance = MatchTransactionsTypesRegistry.getInstance();
const instance = MatchTransactionsTypesRegistry.getInstance();
this.registered.forEach((registered) => { // Always register services to ensure they're available
// const serviceInstanace = Container.get(registered.service); this.registered.forEach((registered) => {
// instance.register(registered.type, serviceInstanace); instance.register(registered.type, registered.service);
}); });
MatchTransactionsTypes.registry = instance;
} MatchTransactionsTypes.registry = instance;
} }
} }