mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
Merge branch 'develop' into filter-uncategorized-bank-transactions
This commit is contained in:
@@ -52,6 +52,9 @@ export class GetBankAccountSummary {
|
||||
q.withGraphJoined('matchedBankTransactions');
|
||||
q.whereNull('matchedBankTransactions.id');
|
||||
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
@@ -65,16 +68,32 @@ export class GetBankAccountSummary {
|
||||
q.withGraphJoined('recognizedTransaction');
|
||||
q.whereNotNull('recognizedTransaction.id');
|
||||
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
|
||||
// Retrieves excluded transactions count.
|
||||
const excludedTransactionsCount =
|
||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||
q.where('accountId', bankAccountId);
|
||||
q.modify('excluded');
|
||||
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
// Retrieves the pending transactions count.
|
||||
const pendingTransactionsCount =
|
||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||
q.where('accountId', bankAccountId);
|
||||
q.modify('pending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
@@ -83,14 +102,15 @@ export class GetBankAccountSummary {
|
||||
const totalUncategorizedTransactions =
|
||||
uncategorizedTranasctionsCount?.total || 0;
|
||||
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
|
||||
|
||||
const totalExcludedTransactions = excludedTransactionsCount?.total || 0;
|
||||
const totalPendingTransactions = pendingTransactionsCount?.total || 0;
|
||||
|
||||
return {
|
||||
name: bankAccount.name,
|
||||
totalUncategorizedTransactions,
|
||||
totalRecognizedTransactions,
|
||||
totalExcludedTransactions,
|
||||
totalPendingTransactions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { initialize } from 'objection';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import { IAccountEventDeletePayload } from '@/interfaces';
|
||||
import { DeleteBankRulesService } from '../../Rules/DeleteBankRules';
|
||||
import { RevertRecognizedTransactions } from '../../RegonizeTranasctions/RevertRecognizedTransactions';
|
||||
|
||||
@Service()
|
||||
export class DeleteUncategorizedTransactionsOnAccountDeleting {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private deleteBankRules: DeleteBankRulesService;
|
||||
|
||||
@Inject()
|
||||
private revertRecognizedTransactins: RevertRecognizedTransactions;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.accounts.onDelete,
|
||||
this.handleDeleteBankRulesOnAccountDeleting.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles revert the recognized transactions and delete all the bank rules
|
||||
* associated to the deleted bank account.
|
||||
* @param {IAccountEventDeletePayload}
|
||||
*/
|
||||
private async handleDeleteBankRulesOnAccountDeleting({
|
||||
tenantId,
|
||||
oldAccount,
|
||||
trx,
|
||||
}: IAccountEventDeletePayload) {
|
||||
const knex = this.tenancy.knex(tenantId);
|
||||
const {
|
||||
BankRule,
|
||||
UncategorizedCashflowTransaction,
|
||||
MatchedBankTransaction,
|
||||
RecognizedBankTransaction,
|
||||
} = this.tenancy.models(tenantId);
|
||||
|
||||
const foundAssociatedRules = await BankRule.query(trx).where(
|
||||
'applyIfAccountId',
|
||||
oldAccount.id
|
||||
);
|
||||
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
|
||||
|
||||
await initialize(knex, [
|
||||
UncategorizedCashflowTransaction,
|
||||
RecognizedBankTransaction,
|
||||
MatchedBankTransaction,
|
||||
]);
|
||||
// Revert the recognized transactions of the given bank rules.
|
||||
await this.revertRecognizedTransactins.revertRecognizedTransactions(
|
||||
tenantId,
|
||||
foundAssociatedRulesIds,
|
||||
null,
|
||||
trx
|
||||
);
|
||||
// Delete the associated uncategorized transactions.
|
||||
await UncategorizedCashflowTransaction.query(trx)
|
||||
.where('accountId', oldAccount.id)
|
||||
.delete();
|
||||
|
||||
// Delete the given bank rules.
|
||||
await this.deleteBankRules.deleteBankRules(
|
||||
tenantId,
|
||||
foundAssociatedRulesIds,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ export class DisconnectPlaidItemOnAccountDeleted {
|
||||
.findOne('plaidItemId', oldAccount.plaidItemId)
|
||||
.delete();
|
||||
|
||||
// Remove Plaid item once the transaction resolve.
|
||||
if (oldPlaidItem) {
|
||||
const plaidInstance = PlaidClientWrapper.getClient();
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export class GetMatchedTransactions {
|
||||
.whereIn('id', uncategorizedTransactionIds)
|
||||
.throwIfNotFound();
|
||||
|
||||
const totalPending = Math.abs(sumBy(uncategorizedTransactions, 'amount'));
|
||||
const totalPending = sumBy(uncategorizedTransactions, 'amount');
|
||||
|
||||
const filtered = filter.transactionType
|
||||
? this.registered.filter((item) => item.type === filter.transactionType)
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { initialize } from 'objection';
|
||||
import { Knex } from 'knex';
|
||||
import { first } from 'lodash';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
||||
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
GetMatchedTransactionsFilter,
|
||||
IMatchTransactionDTO,
|
||||
MatchedTransactionPOJO,
|
||||
} from './types';
|
||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||
import { CreateBillPayment } from '@/services/Purchases/BillPayments/CreateBillPayment';
|
||||
import { IBillPaymentDTO } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
private createPaymentMadeService: CreateBillPayment;
|
||||
|
||||
/**
|
||||
* Retrieves the matched transactions.
|
||||
@@ -71,4 +78,62 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
|
||||
new GetMatchedTransactionBillsTransformer()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the common matched transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {Array<number>} uncategorizedTransactionIds
|
||||
* @param {IMatchTransactionDTO} matchTransactionDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public async createMatchedTransaction(
|
||||
tenantId: number,
|
||||
uncategorizedTransactionIds: Array<number>,
|
||||
matchTransactionDTO: IMatchTransactionDTO,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
await super.createMatchedTransaction(
|
||||
tenantId,
|
||||
uncategorizedTransactionIds,
|
||||
matchTransactionDTO,
|
||||
trx
|
||||
);
|
||||
const { Bill, UncategorizedCashflowTransaction, MatchedBankTransaction } =
|
||||
this.tenancy.models(tenantId);
|
||||
|
||||
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
|
||||
const uncategorizedTransaction =
|
||||
await UncategorizedCashflowTransaction.query(trx)
|
||||
.findById(uncategorizedTransactionId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const bill = await Bill.query(trx)
|
||||
.findById(matchTransactionDTO.referenceId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const createPaymentMadeDTO: IBillPaymentDTO = {
|
||||
vendorId: bill.vendorId,
|
||||
paymentAccountId: uncategorizedTransaction.accountId,
|
||||
paymentDate: uncategorizedTransaction.date,
|
||||
exchangeRate: 1,
|
||||
entries: [
|
||||
{
|
||||
paymentAmount: bill.dueAmount,
|
||||
billId: bill.id,
|
||||
},
|
||||
],
|
||||
branchId: bill.branchId,
|
||||
};
|
||||
// Create a new bill payment associated to the matched bill.
|
||||
const billPayment = await this.createPaymentMadeService.createBillPayment(
|
||||
tenantId,
|
||||
createPaymentMadeDTO,
|
||||
trx
|
||||
);
|
||||
// Link the create bill payment with matched transaction.
|
||||
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
|
||||
referenceType: 'BillPayment',
|
||||
referenceId: billPayment.id,
|
||||
}, trx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { initialize } from 'objection';
|
||||
import { Knex } from 'knex';
|
||||
import { first } from 'lodash';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
||||
import {
|
||||
GetMatchedTransactionsFilter,
|
||||
IMatchTransactionDTO,
|
||||
MatchedTransactionPOJO,
|
||||
MatchedTransactionsPOJO,
|
||||
} from './types';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||
import { CreatePaymentReceived } from '@/services/Sales/PaymentReceived/CreatePaymentReceived';
|
||||
import { IPaymentReceivedCreateDTO } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
|
||||
@Inject()
|
||||
protected tenancy: HasTenancyService;
|
||||
protected transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
protected transformer: TransformerInjectable;
|
||||
protected createPaymentReceivedService: CreatePaymentReceived;
|
||||
|
||||
/**
|
||||
* Retrieves the matched transactions.
|
||||
@@ -78,4 +82,64 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
|
||||
new GetMatchedTransactionInvoicesTransformer()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the common matched transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {Array<number>} uncategorizedTransactionIds
|
||||
* @param {IMatchTransactionDTO} matchTransactionDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public async createMatchedTransaction(
|
||||
tenantId: number,
|
||||
uncategorizedTransactionIds: Array<number>,
|
||||
matchTransactionDTO: IMatchTransactionDTO,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
await super.createMatchedTransaction(
|
||||
tenantId,
|
||||
uncategorizedTransactionIds,
|
||||
matchTransactionDTO,
|
||||
trx
|
||||
);
|
||||
const { SaleInvoice, UncategorizedCashflowTransaction, MatchedBankTransaction } =
|
||||
this.tenancy.models(tenantId);
|
||||
|
||||
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
|
||||
const uncategorizedTransaction =
|
||||
await UncategorizedCashflowTransaction.query(trx)
|
||||
.findById(uncategorizedTransactionId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const invoice = await SaleInvoice.query(trx)
|
||||
.findById(matchTransactionDTO.referenceId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const createPaymentReceivedDTO: IPaymentReceivedCreateDTO = {
|
||||
customerId: invoice.customerId,
|
||||
paymentDate: uncategorizedTransaction.date,
|
||||
amount: invoice.dueAmount,
|
||||
depositAccountId: uncategorizedTransaction.accountId,
|
||||
entries: [
|
||||
{
|
||||
index: 1,
|
||||
invoiceId: invoice.id,
|
||||
paymentAmount: invoice.dueAmount,
|
||||
},
|
||||
],
|
||||
branchId: invoice.branchId,
|
||||
};
|
||||
// Create a payment received associated to the matched invoice.
|
||||
const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
|
||||
tenantId,
|
||||
createPaymentReceivedDTO,
|
||||
{},
|
||||
trx
|
||||
);
|
||||
// Link the create payment received with matched invoice transaction.
|
||||
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
|
||||
referenceType: 'PaymentReceive',
|
||||
referenceId: paymentReceived.id,
|
||||
}, trx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IBillPaymentEventDeletedPayload,
|
||||
IPaymentReceiveDeletedPayload,
|
||||
IPaymentReceivedDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
||||
import events from '@/subscribers/events';
|
||||
@@ -23,7 +23,7 @@ export class ValidateMatchingOnPaymentMadeDelete {
|
||||
|
||||
/**
|
||||
* Validates the payment made transaction whether matched with bank transaction on deleting.
|
||||
* @param {IPaymentReceiveDeletedPayload}
|
||||
* @param {IPaymentReceivedDeletedPayload}
|
||||
*/
|
||||
public async validateMatchingOnPaymentMadeDeleting({
|
||||
tenantId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IPaymentReceiveDeletedPayload } from '@/interfaces';
|
||||
import { IPaymentReceivedDeletedPayload } from '@/interfaces';
|
||||
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@@ -20,13 +20,13 @@ export class ValidateMatchingOnPaymentReceivedDelete {
|
||||
|
||||
/**
|
||||
* Validates the payment received transaction whether matched with bank transaction on deleting.
|
||||
* @param {IPaymentReceiveDeletedPayload}
|
||||
* @param {IPaymentReceivedDeletedPayload}
|
||||
*/
|
||||
public async validateMatchingOnPaymentReceivedDeleting({
|
||||
tenantId,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceiveDeletedPayload) {
|
||||
}: IPaymentReceivedDeletedPayload) {
|
||||
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
|
||||
tenantId,
|
||||
'PaymentReceive',
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Knex } from 'knex';
|
||||
import uniqid from 'uniqid';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { RemovePendingUncategorizedTransaction } from '@/services/Cashflow/RemovePendingUncategorizedTransaction';
|
||||
|
||||
const CONCURRENCY_ASYNC = 10;
|
||||
|
||||
@@ -40,7 +41,7 @@ export class PlaidSyncDb {
|
||||
private cashflowApp: CashflowApplication;
|
||||
|
||||
@Inject()
|
||||
private deleteCashflowTransactionService: DeleteCashflowTransaction;
|
||||
private removePendingTransaction: RemovePendingUncategorizedTransaction;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
@@ -185,21 +186,22 @@ export class PlaidSyncDb {
|
||||
plaidTransactionsIds: string[],
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
const { CashflowTransaction } = this.tenancy.models(tenantId);
|
||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const cashflowTransactions = await CashflowTransaction.query(trx).whereIn(
|
||||
'plaidTransactionId',
|
||||
plaidTransactionsIds
|
||||
);
|
||||
const cashflowTransactionsIds = cashflowTransactions.map(
|
||||
const uncategorizedTransactions =
|
||||
await UncategorizedCashflowTransaction.query(trx).whereIn(
|
||||
'plaidTransactionId',
|
||||
plaidTransactionsIds
|
||||
);
|
||||
const uncategorizedTransactionsIds = uncategorizedTransactions.map(
|
||||
(trans) => trans.id
|
||||
);
|
||||
await bluebird.map(
|
||||
cashflowTransactionsIds,
|
||||
(transactionId: number) =>
|
||||
this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
||||
uncategorizedTransactionsIds,
|
||||
(uncategorizedTransactionId: number) =>
|
||||
this.removePendingTransaction.removePendingTransaction(
|
||||
tenantId,
|
||||
transactionId,
|
||||
uncategorizedTransactionId,
|
||||
trx
|
||||
),
|
||||
{ concurrency: CONCURRENCY_ASYNC }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
||||
import { PlaidSyncDb } from './PlaidSyncDB';
|
||||
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
@Service()
|
||||
export class PlaidUpdateTransactions {
|
||||
@@ -19,9 +19,9 @@ export class PlaidUpdateTransactions {
|
||||
|
||||
/**
|
||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||
* @param {number} tenantId
|
||||
* @param {number} plaidItemId
|
||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} plaidItemId - Plaid item id.
|
||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||
*/
|
||||
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
||||
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
||||
@@ -73,6 +73,12 @@ export class PlaidUpdateTransactions {
|
||||
item,
|
||||
trx
|
||||
);
|
||||
// Sync removed transactions.
|
||||
await this.plaidSync.syncRemoveTransactions(
|
||||
tenantId,
|
||||
removed?.map((r) => r.transaction_id),
|
||||
trx
|
||||
);
|
||||
// Sync bank account transactions.
|
||||
await this.plaidSync.syncAccountsTransactions(
|
||||
tenantId,
|
||||
|
||||
@@ -3,11 +3,11 @@ import {
|
||||
Item as PlaidItem,
|
||||
Institution as PlaidInstitution,
|
||||
AccountBase as PlaidAccount,
|
||||
TransactionBase as PlaidTransactionBase,
|
||||
} from 'plaid';
|
||||
import {
|
||||
CreateUncategorizedTransactionDTO,
|
||||
IAccountCreateDTO,
|
||||
PlaidTransaction,
|
||||
} from '@/interfaces';
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ export const transformPlaidAccountToCreateAccount = R.curry(
|
||||
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
||||
(
|
||||
cashflowAccountId: number,
|
||||
plaidTranasction: PlaidTransaction
|
||||
plaidTranasction: PlaidTransactionBase
|
||||
): CreateUncategorizedTransactionDTO => {
|
||||
return {
|
||||
date: plaidTranasction.date,
|
||||
@@ -64,6 +64,8 @@ export const transformPlaidTrxsToCashflowCreate = R.curry(
|
||||
accountId: cashflowAccountId,
|
||||
referenceNo: plaidTranasction.payment_meta?.reference_number,
|
||||
plaidTransactionId: plaidTranasction.transaction_id,
|
||||
pending: plaidTranasction.pending,
|
||||
pendingPlaidTransactionId: plaidTranasction.pending_transaction_id,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -33,17 +33,29 @@ const matchNumberCondition = (
|
||||
transaction: UncategorizedCashflowTransaction,
|
||||
condition: IBankRuleCondition
|
||||
) => {
|
||||
const conditionValue = parseFloat(condition.value);
|
||||
const transactionAmount =
|
||||
condition.field === 'amount'
|
||||
? Math.abs(transaction[condition.field])
|
||||
: (transaction[condition.field] as unknown as number);
|
||||
|
||||
switch (condition.comparator) {
|
||||
case BankRuleConditionComparator.Equals:
|
||||
return transaction[condition.field] === condition.value;
|
||||
case BankRuleConditionComparator.Contains:
|
||||
return transaction[condition.field]
|
||||
?.toString()
|
||||
.includes(condition.value.toString());
|
||||
case BankRuleConditionComparator.NotContain:
|
||||
return !transaction[condition.field]
|
||||
?.toString()
|
||||
.includes(condition.value.toString());
|
||||
case BankRuleConditionComparator.Equal:
|
||||
return transactionAmount === conditionValue;
|
||||
|
||||
case BankRuleConditionComparator.BiggerOrEqual:
|
||||
return transactionAmount >= conditionValue;
|
||||
|
||||
case BankRuleConditionComparator.Bigger:
|
||||
return transactionAmount > conditionValue;
|
||||
|
||||
case BankRuleConditionComparator.Smaller:
|
||||
return transactionAmount < conditionValue;
|
||||
|
||||
case BankRuleConditionComparator.SmallerOrEqual:
|
||||
return transactionAmount <= conditionValue;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -53,18 +65,19 @@ const matchTextCondition = (
|
||||
transaction: UncategorizedCashflowTransaction,
|
||||
condition: IBankRuleCondition
|
||||
): boolean => {
|
||||
const transactionValue = transaction[condition.field] as string;
|
||||
|
||||
switch (condition.comparator) {
|
||||
case BankRuleConditionComparator.Equals:
|
||||
return transaction[condition.field] === condition.value;
|
||||
case BankRuleConditionComparator.Equal:
|
||||
return transactionValue === condition.value;
|
||||
case BankRuleConditionComparator.Contains:
|
||||
const fieldValue = lowerCase(transaction[condition.field]);
|
||||
const fieldValue = lowerCase(transactionValue);
|
||||
const conditionValue = lowerCase(condition.value);
|
||||
|
||||
return fieldValue.includes(conditionValue);
|
||||
case BankRuleConditionComparator.NotContain:
|
||||
return !transaction[condition.field]?.includes(
|
||||
condition.value.toString()
|
||||
);
|
||||
return !transactionValue?.includes(condition.value.toString());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -101,8 +114,8 @@ const determineFieldType = (field: string): string => {
|
||||
case 'amount':
|
||||
return 'number';
|
||||
case 'description':
|
||||
return 'text';
|
||||
case 'payee':
|
||||
default:
|
||||
return 'unknown';
|
||||
return 'text';
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { isEqual, omit } from 'lodash';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IBankRuleEventCreatedPayload,
|
||||
@@ -55,10 +56,22 @@ export class TriggerRecognizedTransactions {
|
||||
private async recognizedTransactionsOnRuleEdited({
|
||||
tenantId,
|
||||
editRuleDTO,
|
||||
oldBankRule,
|
||||
bankRule,
|
||||
ruleId,
|
||||
}: IBankRuleEventEditedPayload) {
|
||||
const payload = { tenantId, ruleId };
|
||||
|
||||
// Cannot continue if the new and old bank rule values are the same,
|
||||
// after excluding `createdAt` and `updatedAt` dates.
|
||||
if (
|
||||
isEqual(
|
||||
omit(bankRule, ['createdAt', 'updatedAt']),
|
||||
omit(oldBankRule, ['createdAt', 'updatedAt'])
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.agenda.now(
|
||||
'rerecognize-uncategorized-transactions-job',
|
||||
payload
|
||||
|
||||
@@ -26,31 +26,39 @@ export class DeleteBankRuleSerivce {
|
||||
* @param {number} ruleId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async deleteBankRule(tenantId: number, ruleId: number): Promise<void> {
|
||||
public async deleteBankRule(
|
||||
tenantId: number,
|
||||
ruleId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldBankRule = await BankRule.query()
|
||||
.findById(ruleId)
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBankRuleDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
|
||||
tenantId,
|
||||
oldBankRule,
|
||||
ruleId,
|
||||
trx,
|
||||
} as IBankRuleEventDeletingPayload);
|
||||
return this.uow.withTransaction(
|
||||
tenantId,
|
||||
async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBankRuleDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
|
||||
tenantId,
|
||||
oldBankRule,
|
||||
ruleId,
|
||||
trx,
|
||||
} as IBankRuleEventDeletingPayload);
|
||||
|
||||
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete();
|
||||
await BankRule.query(trx).findById(ruleId).delete();
|
||||
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete()
|
||||
await BankRule.query(trx).findById(ruleId).delete();
|
||||
|
||||
// Triggers `onBankRuleDeleted` event.
|
||||
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
|
||||
tenantId,
|
||||
ruleId,
|
||||
trx,
|
||||
} as IBankRuleEventDeletedPayload);
|
||||
});
|
||||
// Triggers `onBankRuleDeleted` event.
|
||||
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
|
||||
tenantId,
|
||||
ruleId,
|
||||
trx,
|
||||
} as IBankRuleEventDeletedPayload);
|
||||
},
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import { castArray, uniq } from 'lodash';
|
||||
import { DeleteBankRuleSerivce } from './DeleteBankRule';
|
||||
|
||||
@Service()
|
||||
export class DeleteBankRulesService {
|
||||
@Inject()
|
||||
private deleteBankRuleService: DeleteBankRuleSerivce;
|
||||
|
||||
/**
|
||||
* Delete bank rules.
|
||||
* @param {number} tenantId
|
||||
* @param {number | Array<number>} bankRuleId
|
||||
*/
|
||||
async deleteBankRules(
|
||||
tenantId: number,
|
||||
bankRuleId: number | Array<number>,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
const bankRulesIds = uniq(castArray(bankRuleId));
|
||||
|
||||
const results = await PromisePool.withConcurrency(1)
|
||||
.for(bankRulesIds)
|
||||
.process(async (bankRuleId: number) => {
|
||||
await this.deleteBankRuleService.deleteBankRule(
|
||||
tenantId,
|
||||
bankRuleId,
|
||||
trx
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ export class EditBankRuleService {
|
||||
|
||||
const oldBankRule = await BankRule.query()
|
||||
.findById(ruleId)
|
||||
.withGraphFetched('conditions')
|
||||
.throwIfNotFound();
|
||||
|
||||
const tranformDTO = this.transformDTO(editRuleDTO);
|
||||
@@ -64,15 +65,15 @@ export class EditBankRuleService {
|
||||
} as IBankRuleEventEditingPayload);
|
||||
|
||||
// Updates the given bank rule.
|
||||
await BankRule.query(trx).upsertGraphAndFetch({
|
||||
const bankRule = await BankRule.query(trx).upsertGraphAndFetch({
|
||||
...tranformDTO,
|
||||
id: ruleId,
|
||||
});
|
||||
|
||||
// Triggers `onBankRuleEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.bankRules.onEdited, {
|
||||
tenantId,
|
||||
oldBankRule,
|
||||
bankRule,
|
||||
ruleId,
|
||||
editRuleDTO,
|
||||
trx,
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export enum BankRuleConditionField {
|
||||
Amount = 'Amount',
|
||||
Description = 'Description',
|
||||
Payee = 'Payee',
|
||||
Amount = 'amount',
|
||||
Description = 'description',
|
||||
Payee = 'payee',
|
||||
}
|
||||
|
||||
export enum BankRuleConditionComparator {
|
||||
Contains = 'contains',
|
||||
Equals = 'equals',
|
||||
Equal = 'equal',
|
||||
NotContain = 'not_contain',
|
||||
Bigger = 'bigger',
|
||||
BiggerOrEqual = 'bigger_or_equal',
|
||||
Smaller = 'smaller',
|
||||
SmallerOrEqual = 'smaller_or_equal',
|
||||
}
|
||||
|
||||
export interface IBankRuleCondition {
|
||||
@@ -56,7 +61,15 @@ export enum BankRuleAssignCategory {
|
||||
export interface IBankRuleConditionDTO {
|
||||
id?: number;
|
||||
field: string;
|
||||
comparator: string;
|
||||
comparator:
|
||||
| 'contains'
|
||||
| 'equals'
|
||||
| 'not_contains'
|
||||
| 'equal'
|
||||
| 'bigger'
|
||||
| 'bigger_or_equal'
|
||||
| 'smaller'
|
||||
| 'smaller_or_equal';
|
||||
value: number;
|
||||
}
|
||||
|
||||
@@ -99,6 +112,8 @@ export interface IBankRuleEventEditingPayload {
|
||||
export interface IBankRuleEventEditedPayload {
|
||||
tenantId: number;
|
||||
ruleId: number;
|
||||
oldBankRule: IBankRule;
|
||||
bankRule: IBankRule;
|
||||
editRuleDTO: IEditBankRuleDTO;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user