fix: account balance change with credit/debit entries.

This commit is contained in:
a.bouhuolia
2021-02-28 17:09:03 +02:00
parent 4d751f772e
commit b98d18f189
3 changed files with 95 additions and 70 deletions

View File

@@ -2,7 +2,6 @@ import { get } from 'lodash';
import { ACCOUNT_TYPES } from 'data/AccountTypes';
export default class AccountTypesUtils {
/**
* Retrieve account types list.
*/
@@ -11,15 +10,16 @@ export default class AccountTypesUtils {
}
/**
*
* @param {string} rootType
* Retrieve accounts types by the given root type.
* @param {string} rootType -
* @return {string}
*/
static getTypesByRootType(rootType: string) {
return ACCOUNT_TYPES.filter((type) => type.rootType === rootType);
}
/**
*
* Retrieve account type by the given account type key.
* @param {string} key
* @param {string} accessor
*/
@@ -33,7 +33,7 @@ export default class AccountTypesUtils {
}
/**
*
* Retrieve accounts types by the parent account type.
* @param {string} parentType
*/
static getTypesByParentType(parentType: string) {
@@ -41,20 +41,19 @@ export default class AccountTypesUtils {
}
/**
*
* Retrieve accounts types by the given account normal.
* @param {string} normal
*/
static getTypesByNormal(normal: string) {
return ACCOUNT_TYPES.filter((type) => type.normal === normal);
}
/**
*
* Detarmines whether the root type equals the account type.
* @param {string} key
* @param {string} rootType
*/
static isRootTypeEqualsKey(key: string, rootType: string) {
static isRootTypeEqualsKey(key: string, rootType: string): boolean {
return ACCOUNT_TYPES.some((type) => {
const isType = type.key === key;
const isRootType = type.rootType === rootType;
@@ -64,11 +63,11 @@ export default class AccountTypesUtils {
}
/**
*
* @param {string} key
* @param {string} parentType
* Detarmines whether the parent account type equals the account type key.
* @param {string} key - Account type key.
* @param {string} parentType - Account parent type.
*/
static isParentTypeEqualsKey(key: string, parentType: string) {
static isParentTypeEqualsKey(key: string, parentType: string): boolean {
return ACCOUNT_TYPES.some((type) => {
const isType = type.key === key;
const isParentType = type.parentType === parentType;
@@ -78,10 +77,11 @@ export default class AccountTypesUtils {
}
/**
* Detarmines whether account type has balance sheet.
* @param {string} key - Account type key.
*
* @param {string} key
*/
static isTypeBalanceSheet(key: string) {
static isTypeBalanceSheet(key: string): boolean {
return ACCOUNT_TYPES.some((type) => {
const isType = type.key === key;
return isType && type.balanceSheet;
@@ -89,10 +89,10 @@ export default class AccountTypesUtils {
}
/**
*
* @param {string} key
* Detarmines whether account type has profit/loss sheet.
* @param {string} key - Account type key.
*/
static isTypePLSheet(key: string) {
static isTypePLSheet(key: string): boolean {
return ACCOUNT_TYPES.some((type) => {
const isType = type.key === key;
return isType && type.incomeSheet;

View File

@@ -23,16 +23,13 @@ export default class JournalPoster implements IJournalPoster {
balancesChange: IAccountsChange = {};
accountsDepGraph: IAccountsChange;
accountsBalanceTable: { [key: number]: number; } = {};
accountsBalanceTable: { [key: number]: number } = {};
/**
* Journal poster constructor.
* @param {number} tenantId -
* @param {number} tenantId -
*/
constructor(
tenantId: number,
accountsGraph?: any,
) {
constructor(tenantId: number, accountsGraph?: any) {
this.initTenancy();
this.tenantId = tenantId;
@@ -59,7 +56,7 @@ export default class JournalPoster implements IJournalPoster {
/**
* Async initialize acccounts dependency graph.
* @private
* @private
* @returns {Promise<void>}
*/
public async initAccountsDepGraph(): Promise<void> {
@@ -72,7 +69,7 @@ export default class JournalPoster implements IJournalPoster {
}
/**
*
*
*/
public isEmpty() {
return this.entries.length === 0;
@@ -118,7 +115,7 @@ export default class JournalPoster implements IJournalPoster {
/**
* Sets account balance change.
* @private
* @param {number} accountId -
* @param {number} accountId -
* @param {IAccountChange} accountChange
*/
private _setAccountBalanceChange(
@@ -126,21 +123,23 @@ export default class JournalPoster implements IJournalPoster {
accountChange: IAccountChange
) {
this.balancesChange = this.accountBalanceChangeReducer(
this.balancesChange, accountId, accountChange,
this.balancesChange,
accountId,
accountChange
);
}
/**
* Accounts balance change reducer.
* @param {IAccountsChange} balancesChange
* @param {number} accountId
* @param {IAccountsChange} balancesChange
* @param {number} accountId
* @param {IAccountChange} accountChange
* @return {IAccountChange}
* @return {IAccountChange}
*/
private accountBalanceChangeReducer(
balancesChange: IAccountsChange,
accountId: number,
accountChange: IAccountChange,
accountChange: IAccountChange
) {
const change = { ...balancesChange };
@@ -159,31 +158,37 @@ export default class JournalPoster implements IJournalPoster {
/**
* Converts balance changes to array.
* @private
* @param {IAccountsChange} accountsChange -
* @param {IAccountsChange} accountsChange -
* @return {Promise<{ account: number, change: number }>}
*/
private async convertBalanceChangesToArr(
accountsChange: IAccountsChange
) : Promise<{ account: number, change: number }[]>{
const mappedList: { account: number, change: number }[] = [];
const accountsIds: number[] = Object.keys(accountsChange).map(id => parseInt(id, 10));
): Promise<{ account: number; change: number }[]> {
const mappedList: { account: number; change: number }[] = [];
const accountsIds: number[] = Object.keys(accountsChange).map((id) =>
parseInt(id, 10)
);
await Promise.all(
accountsIds.map(async (account: number) => {
const accountChange = accountsChange[account];
const accountNode = this.accountsDepGraph.getNodeData(account);
const { normal }: { normal: TEntryType } = accountNode.accountNormal;
const normal = accountNode.accountNormal;
let change = 0;
if (accountChange.credit) {
change = (normal === 'credit') ? accountChange.credit : -1 * accountChange.credit;
change =
normal === 'credit'
? accountChange.credit
: -1 * accountChange.credit;
}
if (accountChange.debit) {
change = (normal === 'debit') ? accountChange.debit : -1 * accountChange.debit;
change =
normal === 'debit' ? accountChange.debit : -1 * accountChange.debit;
}
mappedList.push({ account, change });
}),
})
);
return mappedList;
}
@@ -198,20 +203,26 @@ export default class JournalPoster implements IJournalPoster {
const { Account } = this.models;
const accountsChange = this.balanceChangeWithDepends(this.balancesChange);
const balancesList = await this.convertBalanceChangesToArr(accountsChange);
const balancesAccounts = balancesList.map(b => b.account);
const balancesAccounts = balancesList.map((b) => b.account);
// Ensure the accounts has atleast zero in amount.
await Account.query().where('amount', null).whereIn('id', balancesAccounts)
await Account.query()
.where('amount', null)
.whereIn('id', balancesAccounts)
.patch({ amount: 0 });
const balanceUpdateOpers: Promise<void>[] = [];
balancesList.forEach((balance: { account: number, change: number }) => {
const method: string = (balance.change < 0) ? 'decrement' : 'increment';
balancesList.forEach((balance: { account: number; change: number }) => {
const method: string = balance.change < 0 ? 'decrement' : 'increment';
this.logger.info('[journal_poster] increment/decrement account balance.', {
balance, tenantId: this.tenantId,
})
this.logger.info(
'[journal_poster] increment/decrement account balance.',
{
balance,
tenantId: this.tenantId,
}
);
const query = Account.query()
[method]('amount', Math.abs(balance.change))
.where('id', balance.account);
@@ -225,19 +236,25 @@ export default class JournalPoster implements IJournalPoster {
/**
* Changes all accounts that dependencies of changed accounts.
* @param {IAccountsChange} accountsChange
* @param {IAccountsChange} accountsChange
* @returns {IAccountsChange}
*/
private balanceChangeWithDepends(accountsChange: IAccountsChange): IAccountsChange {
private balanceChangeWithDepends(
accountsChange: IAccountsChange
): IAccountsChange {
const accountsIds = Object.keys(accountsChange);
let changes: IAccountsChange = {};
accountsIds.forEach((accountId) => {
const accountChange = accountsChange[accountId];
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
const depAccountsIds = this.accountsDepGraph.dependantsOf(accountId);
[accountId, ...depAccountsIds].forEach((account) => {
changes = this.accountBalanceChangeReducer(changes, account, accountChange);
changes = this.accountBalanceChangeReducer(
changes,
account,
accountChange
);
});
});
return changes;
@@ -260,11 +277,10 @@ export default class JournalPoster implements IJournalPoster {
const saveOperations: Promise<void>[] = [];
this.entries.forEach((entry) => {
const oper = transactionsRepository
.create({
accountId: entry.account,
...omit(entry, ['account']),
});
const oper = transactionsRepository.create({
accountId: entry.account,
...omit(entry, ['account']),
});
saveOperations.push(oper);
});
await Promise.all(saveOperations);
@@ -361,14 +377,14 @@ export default class JournalPoster implements IJournalPoster {
/**
* Retrieve the closing balance for the given account and closing date.
* @param {Number} accountId -
* @param {Date} closingDate -
* @param {string} dataType? -
* @param {Number} accountId -
* @param {Date} closingDate -
* @param {string} dataType? -
* @return {number}
*/
getClosingBalance(
accountId: number,
closingDate: Date|string,
closingDate: Date | string,
dateType: string = 'day'
): number {
let closingBalance = 0;
@@ -399,11 +415,16 @@ export default class JournalPoster implements IJournalPoster {
* @param {String} dateType -
* @return {Number}
*/
getAccountBalance(accountId: number, closingDate: Date|string, dateType: string) {
getAccountBalance(
accountId: number,
closingDate: Date | string,
dateType: string
) {
const accountNode = this.accountsDepGraph.getNodeData(accountId);
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
const depAccounts = depAccountsIds
.map((id) => this.accountsDepGraph.getNodeData(id));
const depAccounts = depAccountsIds.map((id) =>
this.accountsDepGraph.getNodeData(id)
);
let balance: number = 0;
@@ -459,7 +480,11 @@ export default class JournalPoster implements IJournalPoster {
* @return {Number}
*/
getTrialBalanceWithDepands(accountId: number, closingDate: Date, dateType: string) {
getTrialBalanceWithDepands(
accountId: number,
closingDate: Date,
dateType: string
) {
const accountNode = this.accountsDepGraph.getNodeData(accountId);
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
const depAccounts = depAccountsIds.map((id) =>
@@ -485,8 +510,8 @@ export default class JournalPoster implements IJournalPoster {
accountId: number,
contactId: number,
contactType: string,
closingDate?: Date|string,
openingDate?: Date|string,
closingDate?: Date | string,
openingDate?: Date | string
) {
const momentClosingDate = moment(closingDate);
const momentOpeningDate = moment(openingDate);
@@ -534,7 +559,7 @@ export default class JournalPoster implements IJournalPoster {
contactId: number,
contactType: string,
closingDate: Date,
openingDate: Date,
openingDate: Date
) {
const momentClosingDate = moment(closingDate);
let balance = 0;

View File

@@ -197,7 +197,7 @@ export default class ManualJournalsService implements IManualJournalsService {
}
/**
*
* Validate accounts with contact type.
* @param {number} tenantId
* @param {IManualJournalDTO} manualJournalDTO
* @param {string} accountBySlug