feat(server): validate the max depth level of the parent account.

This commit is contained in:
a.bouhuolia
2023-02-14 23:47:24 +02:00
parent e3a072e267
commit 8a5fbfc041
6 changed files with 80 additions and 13 deletions

View File

@@ -9,6 +9,7 @@ import DynamicListingService from '@/services/DynamicListing/DynamicListService'
import { DATATYPES_LENGTH } from '@/data/DataTypes'; import { DATATYPES_LENGTH } from '@/data/DataTypes';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AccountsApplication } from '@/services/Accounts/AccountsApplication'; import { AccountsApplication } from '@/services/Accounts/AccountsApplication';
import { MAX_ACCOUNTS_CHART_DEPTH } from 'services/Accounts/constants';
@Service() @Service()
export default class AccountsController extends BaseController { export default class AccountsController extends BaseController {
@@ -494,6 +495,22 @@ export default class AccountsController extends BaseController {
} }
); );
} }
if (error.errorType === 'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL') {
return res.boom.badRequest(
'The parent account exceeded the depth level of accounts chart.',
{
errors: [
{
type: 'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL',
code: 1500,
data: {
maxDepth: MAX_ACCOUNTS_CHART_DEPTH,
},
},
],
}
);
}
} }
next(error); next(error);
} }

View File

@@ -3,7 +3,7 @@ import TenancyService from '@/services/Tenancy/TenancyService';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { IAccountDTO, IAccount, IAccountCreateDTO } from '@/interfaces'; import { IAccountDTO, IAccount, IAccountCreateDTO } from '@/interfaces';
import AccountTypesUtils from '@/lib/AccountTypes'; import AccountTypesUtils from '@/lib/AccountTypes';
import { ERRORS } from './constants'; import { ERRORS, MAX_ACCOUNTS_CHART_DEPTH } from './constants';
@Service() @Service()
export class CommandAccountValidators { export class CommandAccountValidators {
@@ -160,7 +160,7 @@ export class CommandAccountValidators {
public validateCurrentSameParentAccount = ( public validateCurrentSameParentAccount = (
accountDTO: IAccountCreateDTO, accountDTO: IAccountCreateDTO,
parentAccount: IAccount, parentAccount: IAccount,
baseCurrency: string, baseCurrency: string
) => { ) => {
// If the account DTO currency not assigned and the parent account has no base currency. // If the account DTO currency not assigned and the parent account has no base currency.
if ( if (
@@ -208,4 +208,24 @@ export class CommandAccountValidators {
} }
return account; return account;
} }
/**
* Validates the max depth level of accounts chart.
* @param {numebr} tenantId - Tenant id.
* @param {number} parentAccountId - Parent account id.
*/
public async validateMaxParentAccountDepthLevels(
tenantId: number,
parentAccountId: number
) {
const { accountRepository } = this.tenancy.repositories(tenantId);
const accountsGraph = await accountRepository.getDependencyGraph();
const parentDependantsIds = accountsGraph.dependantsOf(parentAccountId);
if (parentDependantsIds.length >= MAX_ACCOUNTS_CHART_DEPTH) {
throw new ServiceError(ERRORS.PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL);
}
}
} }

View File

@@ -70,6 +70,11 @@ export class CreateAccount {
parentAccount, parentAccount,
baseCurrency baseCurrency
); );
// Validates the max depth level of accounts chart.
await this.validator.validateMaxParentAccountDepthLevels(
tenantId,
accountDTO.parentAccountId
);
} }
// Validates the given account type supports the multi-currency. // Validates the given account type supports the multi-currency.
this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency); this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency);

View File

@@ -13,8 +13,12 @@ export const ERRORS = {
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE: CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE:
'close_account_and_to_account_not_same_type', 'close_account_and_to_account_not_same_type',
ACCOUNTS_NOT_FOUND: 'accounts_not_found', ACCOUNTS_NOT_FOUND: 'accounts_not_found',
ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY: 'ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY', ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY:
ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT: 'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT', 'ACCOUNT_TYPE_NOT_SUPPORTS_MULTI_CURRENCY',
ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT:
'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT',
PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL:
'PARENT_ACCOUNT_EXCEEDED_THE_DEPTH_LEVEL',
}; };
// Default views columns. // Default views columns.
@@ -27,6 +31,8 @@ export const DEFAULT_VIEW_COLUMNS = [
{ key: 'currencyCode', label: 'Currency' }, { key: 'currencyCode', label: 'Currency' },
]; ];
export const MAX_ACCOUNTS_CHART_DEPTH = 5;
// Accounts default views. // Accounts default views.
export const DEFAULT_VIEWS = [ export const DEFAULT_VIEWS = [
{ {
@@ -43,7 +49,12 @@ export const DEFAULT_VIEWS = [
slug: 'liabilities', slug: 'liabilities',
rolesLogicExpression: '1', rolesLogicExpression: '1',
roles: [ roles: [
{ fieldKey: 'root_type', index: 1, comparator: 'equals', value: 'liability' }, {
fieldKey: 'root_type',
index: 1,
comparator: 'equals',
value: 'liability',
},
], ],
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
@@ -52,7 +63,12 @@ export const DEFAULT_VIEWS = [
slug: 'equity', slug: 'equity',
rolesLogicExpression: '1', rolesLogicExpression: '1',
roles: [ roles: [
{ fieldKey: 'root_type', index: 1, comparator: 'equals', value: 'equity' }, {
fieldKey: 'root_type',
index: 1,
comparator: 'equals',
value: 'equity',
},
], ],
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
@@ -61,7 +77,12 @@ export const DEFAULT_VIEWS = [
slug: 'income', slug: 'income',
rolesLogicExpression: '1', rolesLogicExpression: '1',
roles: [ roles: [
{ fieldKey: 'root_type', index: 1, comparator: 'equals', value: 'income' }, {
fieldKey: 'root_type',
index: 1,
comparator: 'equals',
value: 'income',
},
], ],
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
@@ -70,7 +91,12 @@ export const DEFAULT_VIEWS = [
slug: 'expenses', slug: 'expenses',
rolesLogicExpression: '1', rolesLogicExpression: '1',
roles: [ roles: [
{ fieldKey: 'root_type', index: 1, comparator: 'equals', value: 'expense' }, {
fieldKey: 'root_type',
index: 1,
comparator: 'equals',
value: 'expense',
},
], ],
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },

View File

@@ -99,7 +99,7 @@
"yup": "^0.28.1" "yup": "^0.28.1"
}, },
"scripts": { "scripts": {
"dev": "craco start", "dev": "PORT=4000 craco start",
"build": "craco build", "build": "craco build",
"test": "node scripts/test.js", "test": "node scripts/test.js",
"storybook": "start-storybook -p 6006" "storybook": "start-storybook -p 6006"

View File

@@ -208,10 +208,9 @@
width: 100%; width: 100%;
} }
} }
&:focus { &:focus {
outline: rgba(0, 82, 204, 0.6) auto 1px; outline: 1px solid rgba(0, 82, 204, 0.7);
outline-offset: 1px; outline-offset: -1px;
} }
} }