mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
Merge pull request #595 from bigcapitalhq/add-comparators-to-amount-bank-rule
feat: Add amount comparators to amount bank rule field
This commit is contained in:
@@ -33,7 +33,16 @@ export class BankingRulesController extends BaseController {
|
||||
body('conditions.*.field').exists().isIn(['description', 'amount']),
|
||||
body('conditions.*.comparator')
|
||||
.exists()
|
||||
.isIn(['equals', 'contains', 'not_contain'])
|
||||
.isIn([
|
||||
'equals',
|
||||
'equal',
|
||||
'contains',
|
||||
'not_contain',
|
||||
'bigger',
|
||||
'bigger_or_equal',
|
||||
'smaller',
|
||||
'smaller_or_equal',
|
||||
])
|
||||
.default('contain')
|
||||
.trim(),
|
||||
body('conditions.*.value').exists().trim(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||
import { get } from 'lodash';
|
||||
import { Button, Classes, Intent, Radio, Tag } from '@blueprintjs/core';
|
||||
import * as R from 'ramda';
|
||||
import { CreateRuleFormSchema } from './RuleFormContentForm.schema';
|
||||
@@ -17,11 +18,12 @@ import {
|
||||
} from '@/components';
|
||||
import { useCreateBankRule, useEditBankRule } from '@/hooks/query/bank-rules';
|
||||
import {
|
||||
FieldCondition,
|
||||
Fields,
|
||||
RuleFormValues,
|
||||
TransactionTypeOptions,
|
||||
getAccountRootFromMoneyCategory,
|
||||
getDefaultFieldConditionByFieldKey,
|
||||
getFieldConditionsByFieldKey,
|
||||
initialValues,
|
||||
} from './_utils';
|
||||
import { useRuleFormDialogBoot } from './RuleFormBoot';
|
||||
@@ -175,6 +177,13 @@ function RuleFormConditions() {
|
||||
setFieldValue('conditions', _conditions);
|
||||
};
|
||||
|
||||
const handleConditionFieldChange = R.curry((index, item) => {
|
||||
const defaultComparator = getDefaultFieldConditionByFieldKey(item.value);
|
||||
|
||||
setFieldValue(`conditions[${index}].field`, item.value);
|
||||
setFieldValue(`conditions[${index}].comparator`, defaultComparator);
|
||||
});
|
||||
|
||||
return (
|
||||
<Box style={{ marginBottom: 15 }}>
|
||||
<Stack spacing={15}>
|
||||
@@ -190,6 +199,7 @@ function RuleFormConditions() {
|
||||
name={`conditions[${index}].field`}
|
||||
items={Fields}
|
||||
popoverProps={{ minimal: true, inline: false }}
|
||||
onItemChange={handleConditionFieldChange(index)}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
@@ -202,8 +212,13 @@ function RuleFormConditions() {
|
||||
>
|
||||
<FSelect
|
||||
name={`conditions[${index}].comparator`}
|
||||
items={FieldCondition}
|
||||
items={getFieldConditionsByFieldKey(
|
||||
get(values, `conditions[${index}].field`),
|
||||
)}
|
||||
popoverProps={{ minimal: true, inline: false }}
|
||||
shouldUpdateDeps={{
|
||||
fieldKey: get(values, `conditions[${index}].field`),
|
||||
}}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
@@ -42,11 +42,25 @@ export const Fields = [
|
||||
{ value: 'amount', text: 'Amount' },
|
||||
{ value: 'payee', text: 'Payee' },
|
||||
];
|
||||
export const FieldCondition = [
|
||||
|
||||
export const TextFieldConditions = [
|
||||
{ value: 'contains', text: 'Contains' },
|
||||
{ value: 'equals', text: 'Equals' },
|
||||
{ value: 'not_contains', text: 'Not Contains' },
|
||||
];
|
||||
export const NumberFieldConditions = [
|
||||
{ value: 'equal', text: 'Equal' },
|
||||
{ value: 'bigger', text: 'Bigger' },
|
||||
{ value: 'bigger_or_equal', text: 'Bigger or Equal' },
|
||||
{ value: 'smaller', text: 'Smaller' },
|
||||
{ value: 'smaller_or_equal', text: 'Smaller or Equal' },
|
||||
];
|
||||
|
||||
export const FieldCondition = [
|
||||
...TextFieldConditions,
|
||||
...NumberFieldConditions,
|
||||
];
|
||||
|
||||
export const AssignTransactionTypeOptions = [
|
||||
{ value: 'expense', text: 'Expense' },
|
||||
];
|
||||
@@ -56,3 +70,21 @@ export const getAccountRootFromMoneyCategory = (category: string): string[] => {
|
||||
|
||||
return get(MoneyCategoryPerCreditAccountRootType, _category) || [];
|
||||
};
|
||||
|
||||
export const getFieldConditionsByFieldKey = (fieldKey?: string) => {
|
||||
switch (fieldKey) {
|
||||
case 'amount':
|
||||
return NumberFieldConditions;
|
||||
default:
|
||||
return TextFieldConditions;
|
||||
}
|
||||
};
|
||||
|
||||
export const getDefaultFieldConditionByFieldKey = (fieldKey?: string) => {
|
||||
switch (fieldKey) {
|
||||
case 'amount':
|
||||
return 'bigger_or_equal';
|
||||
default:
|
||||
return 'contains';
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user