feat: close specific account.

This commit is contained in:
Ahmed Bouhuolia
2020-10-05 22:05:04 +02:00
parent 63c4567e08
commit 681fa0560e
3 changed files with 118 additions and 3 deletions

View File

@@ -43,6 +43,15 @@ export default class AccountsController extends BaseController{
asyncMiddleware(this.inactivateAccount.bind(this)),
this.catchServiceErrors,
);
router.post(
'/:id/close', [
...this.accountParamSchema,
...this.closingAccountSchema,
],
this.validationResult,
asyncMiddleware(this.closeAccount.bind(this)),
this.catchServiceErrors,
)
router.post(
'/:id', [
...this.accountDTOSchema,
@@ -150,6 +159,13 @@ export default class AccountsController extends BaseController{
];
}
get closingAccountSchema() {
return [
check('to_account_id').exists().isNumeric().toInt(),
check('delete_after_closing').exists().isBoolean(),
]
}
/**
* Creates a new account.
* @param {Request} req -
@@ -330,6 +346,30 @@ export default class AccountsController extends BaseController{
}
}
/**
* Closes the given account.
* @param {Request} req
* @param {Response} res
* @param next
*/
async closeAccount(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: accountId } = req.params;
const closeAccountQuery = this.matchedBodyData(req);
try {
await this.accountsService.closeAccount(
tenantId,
accountId,
closeAccountQuery.toAccountId,
closeAccountQuery.deleteAfterClosing
);
return res.status(200).send({ id: accountId });
} catch (error) {
next(error);
}
}
/**
* Transforms service errors to response.
* @param {Error}
@@ -411,6 +451,12 @@ export default class AccountsController extends BaseController{
{ errors: [{ type: 'ACCOUNTS_PREDEFINED', code: 1100 }] }
);
}
if (error.errorType === 'close_account_and_to_account_not_same_type') {
return res.boom.badRequest(
'The close account has different root type with to account.',
{ errors: [{ type: 'CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE', code: 1200 }] },
);
}
}
next(error)
}

View File

@@ -61,7 +61,7 @@ export default class AccountRepository extends TenantRepository {
* @param {number} id - Account id.
* @return {IAccount}
*/
getById(id: number): IAccount {
findById(id: number): IAccount {
const { Account } = this.models;
return this.cache.get(`accounts.id.${id}`, () => {
return Account.query().findById(id);
@@ -93,10 +93,12 @@ export default class AccountRepository extends TenantRepository {
* Inserts a new accounts to the storage.
* @param {IAccount} account
*/
async insert(account: IAccount): Promise<void> {
async insert(accountInput: IAccount): Promise<void> {
const { Account } = this.models;
await Account.query().insertAndFetch({ ...account });
const account = await Account.query().insertAndFetch({ ...accountInput });
this.flushCache();
return account;
}
/**
@@ -121,6 +123,20 @@ export default class AccountRepository extends TenantRepository {
this.flushCache();
}
/**
* Changes account balance.
* @param {number} accountId
* @param {number} amount
* @return {Promise<void>}
*/
async balanceChange(accountId: number, amount: number): Promise<void> {
const { Account } = this.models;
const method: string = (amount < 0) ? 'decrement' : 'increment';
await Account.query().where('id', accountId)[method]('amount', amount);
this.flushCache();
}
/**
* Flush repository cache.
*/

View File

@@ -10,6 +10,9 @@ import {
} from 'decorators/eventDispatcher';
import DynamicListingService from 'services/DynamicListing/DynamicListService';
import events from 'subscribers/events';
import JournalPoster from 'services/Accounting/JournalPoster';
import { Account } from 'models';
import AccountRepository from 'repositories/AccountRepository';
@Service()
export default class AccountsService {
@@ -475,4 +478,54 @@ export default class AccountsService {
filterMeta: dynamicList.getResponseMeta(),
};
}
/**
* Closes the given account.
* -----------
* Precedures.
* -----------
* - Transfer the given account transactions to another account
* with the same root type.
* - Delete the given account.
* -------
* @param {number} tenantId -
* @param {number} accountId -
* @param {number} toAccountId -
* @param {boolean} deleteAfterClosing -
*/
public async closeAccount(
tenantId: number,
accountId: number,
toAccountId: number,
deleteAfterClosing: boolean,
) {
this.logger.info('[account] trying to close account.', { tenantId, accountId, toAccountId, deleteAfterClosing });
const { AccountTransaction } = this.tenancy.models(tenantId);
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
const account = await this.getAccountOrThrowError(tenantId, accountId);
const toAccount = await this.getAccountOrThrowError(tenantId, toAccountId);
this.throwErrorIfAccountPredefined(account);
const accountType = await accountTypeRepository.getTypeMeta(account.accountTypeId);
const toAccountType = await accountTypeRepository.getTypeMeta(toAccount.accountTypeId);
if (accountType.rootType !== toAccountType.rootType) {
throw new ServiceError('close_account_and_to_account_not_same_type');
}
const updateAccountBalanceOper = await accountRepository.balanceChange(accountId, account.balance || 0);
// Move transactiosn operation.
const moveTransactionsOper = await AccountTransaction.query()
.where('account_id', accountId)
.patch({ accountId: toAccountId });
await Promise.all([ moveTransactionsOper, updateAccountBalanceOper ]);
if (deleteAfterClosing) {
await accountRepository.deleteById(accountId);
}
}
}