feat(server): categorize the synced bank transactions

This commit is contained in:
Ahmed Bouhuolia
2024-03-01 17:12:56 +02:00
parent ea8c5458ff
commit 5b4ddadcf6
6 changed files with 82 additions and 14 deletions

View File

@@ -1,5 +1,5 @@
import { Service, Inject } from 'typedi';
import { check } from 'express-validator';
import { check, oneOf } from 'express-validator';
import { Router, Request, Response, NextFunction } from 'express';
import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions';
@@ -75,14 +75,16 @@ export default class NewCashflowTransactionController extends BaseController {
public get categorizeCashflowTransactionValidationSchema() {
return [
check('date').exists().isISO8601().toDate(),
check('to_account_id').exists().isInt().toInt(),
check('from_account_id').exists().isInt().toInt(),
oneOf([
check('to_account_id').exists().isInt().toInt(),
check('from_account_id').exists().isInt().toInt(),
]),
check('transaction_number').optional(),
check('transaction_type').exists(),
check('reference_no').optional(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('description').optional(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
@@ -125,7 +127,7 @@ export default class NewCashflowTransactionController extends BaseController {
const ownerContributionDTO = this.matchedBodyData(req);
try {
const { cashflowTransaction } =
const cashflowTransaction =
await this.newCashflowTranscationService.newCashflowTransaction(
tenantId,
ownerContributionDTO,
@@ -301,6 +303,16 @@ export default class NewCashflowTransactionController extends BaseController {
],
});
}
if (error.errorType === 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID') {
return res.boom.badRequest(null, {
errors: [
{
type: 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
code: 4100,
}
]
});
}
}
next(error);
}

View File

@@ -5,6 +5,7 @@ exports.up = function (knex) {
table.increments('id');
table.date('date').index();
table.decimal('amount');
table.string('currency_code');
table.string('reference_no').index();
table
.integer('account_id')

View File

@@ -22,23 +22,42 @@ export default class UncategorizedCashflowTransaction extends TenantModel {
* Retrieves the withdrawal amount.
* @returns {number}
*/
public withdrawal() {
return this.amount > 0 ? Math.abs(this.amount) : 0;
public get withdrawal() {
return this.amount < 0 ? Math.abs(this.amount) : 0;
}
/**
* Retrieves the deposit amount.
* @returns {number}
*/
public deposit() {
return this.amount < 0 ? Math.abs(this.amount) : 0;
public get deposit(): number {
return this.amount > 0 ? Math.abs(this.amount) : 0;
}
/**
* Detarmines whether the transaction is deposit transaction.
*/
public get isDepositTransaction(): boolean {
return 0 < this.deposit;
}
/**
* Detarmines whether the transaction is withdrawal transaction.
*/
public get isWithdrawalTransaction(): boolean {
return 0 < this.withdrawal;
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['withdrawal', 'deposit'];
return [
'withdrawal',
'deposit',
'isDepositTransaction',
'isWithdrawalTransaction',
];
}
/**

View File

@@ -12,6 +12,7 @@ import { Knex } from 'knex';
import { transformCategorizeTransToCashflow } from './utils';
import { CommandCashflowValidator } from './CommandCasflowValidator';
import NewCashflowTransactionService from './NewCashflowTransactionService';
import { TransferAuthorizationGuaranteeDecision } from 'plaid';
@Service()
export class CategorizeCashflowTransaction {
@@ -50,6 +51,12 @@ export class CategorizeCashflowTransaction {
// Validates the transaction shouldn't be categorized before.
this.commandValidators.validateTransactionShouldNotCategorized(transaction);
// Validate the uncateogirzed transaction if it's deposit the transaction direction
// should `IN` and the same thing if it's withdrawal the direction should be OUT.
this.commandValidators.validateUncategorizeTransactionType(
transaction,
categorizeDTO.transactionType
);
// Edits the cashflow transaction under UOW env.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onTransactionCategorizing` event.

View File

@@ -1,9 +1,13 @@
import { Service } from 'typedi';
import { includes, camelCase, upperFirst } from 'lodash';
import { IAccount } from '@/interfaces';
import { IAccount, IUncategorizedCashflowTransaction } from '@/interfaces';
import { getCashflowTransactionType } from './utils';
import { ServiceError } from '@/exceptions';
import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants';
import {
CASHFLOW_DIRECTION,
CASHFLOW_TRANSACTION_TYPE,
ERRORS,
} from './constants';
import CashflowTransaction from '@/models/CashflowTransaction';
@Service()
@@ -71,4 +75,28 @@ export class CommandCashflowValidator {
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_CATEGORIZED);
}
}
/**
*
* @param {uncategorizeTransaction}
* @param {string} transactionType
* @throws {ServiceError(ERRORS.UNCATEGORIZED_TRANSACTION_TYPE_INVALID)}
*/
public validateUncategorizeTransactionType(
uncategorizeTransaction: IUncategorizedCashflowTransaction,
transactionType: string
) {
const type = getCashflowTransactionType(
upperFirst(camelCase(transactionType)) as CASHFLOW_TRANSACTION_TYPE
);
if (
(type.direction === CASHFLOW_DIRECTION.IN &&
uncategorizeTransaction.isDepositTransaction) ||
(type.direction === CASHFLOW_DIRECTION.OUT &&
uncategorizeTransaction.isWithdrawalTransaction)
) {
return;
}
throw new ServiceError(ERRORS.UNCATEGORIZED_TRANSACTION_TYPE_INVALID);
}
}

View File

@@ -10,7 +10,8 @@ export const ERRORS = {
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
TRANSACTION_ALREADY_CATEGORIZED: 'TRANSACTION_ALREADY_CATEGORIZED',
TRANSACTION_ALREADY_UNCATEGORIZED: 'TRANSACTION_ALREADY_UNCATEGORIZED'
TRANSACTION_ALREADY_UNCATEGORIZED: 'TRANSACTION_ALREADY_UNCATEGORIZED',
UNCATEGORIZED_TRANSACTION_TYPE_INVALID: 'UNCATEGORIZED_TRANSACTION_TYPE_INVALID'
};
export enum CASHFLOW_DIRECTION {