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

View File

@@ -23,16 +23,13 @@ export default class JournalPoster implements IJournalPoster {
balancesChange: IAccountsChange = {}; balancesChange: IAccountsChange = {};
accountsDepGraph: IAccountsChange; accountsDepGraph: IAccountsChange;
accountsBalanceTable: { [key: number]: number; } = {}; accountsBalanceTable: { [key: number]: number } = {};
/** /**
* Journal poster constructor. * Journal poster constructor.
* @param {number} tenantId - * @param {number} tenantId -
*/ */
constructor( constructor(tenantId: number, accountsGraph?: any) {
tenantId: number,
accountsGraph?: any,
) {
this.initTenancy(); this.initTenancy();
this.tenantId = tenantId; this.tenantId = tenantId;
@@ -126,7 +123,9 @@ export default class JournalPoster implements IJournalPoster {
accountChange: IAccountChange accountChange: IAccountChange
) { ) {
this.balancesChange = this.accountBalanceChangeReducer( this.balancesChange = this.accountBalanceChangeReducer(
this.balancesChange, accountId, accountChange, this.balancesChange,
accountId,
accountChange
); );
} }
@@ -140,7 +139,7 @@ export default class JournalPoster implements IJournalPoster {
private accountBalanceChangeReducer( private accountBalanceChangeReducer(
balancesChange: IAccountsChange, balancesChange: IAccountsChange,
accountId: number, accountId: number,
accountChange: IAccountChange, accountChange: IAccountChange
) { ) {
const change = { ...balancesChange }; const change = { ...balancesChange };
@@ -164,26 +163,32 @@ export default class JournalPoster implements IJournalPoster {
*/ */
private async convertBalanceChangesToArr( private async convertBalanceChangesToArr(
accountsChange: IAccountsChange accountsChange: IAccountsChange
) : Promise<{ account: number, change: number }[]>{ ): Promise<{ account: number; change: number }[]> {
const mappedList: { account: number, change: number }[] = []; const mappedList: { account: number; change: number }[] = [];
const accountsIds: number[] = Object.keys(accountsChange).map(id => parseInt(id, 10)); const accountsIds: number[] = Object.keys(accountsChange).map((id) =>
parseInt(id, 10)
);
await Promise.all( await Promise.all(
accountsIds.map(async (account: number) => { accountsIds.map(async (account: number) => {
const accountChange = accountsChange[account]; const accountChange = accountsChange[account];
const accountNode = this.accountsDepGraph.getNodeData(account); const accountNode = this.accountsDepGraph.getNodeData(account);
const normal = accountNode.accountNormal;
const { normal }: { normal: TEntryType } = accountNode.accountNormal;
let change = 0; let change = 0;
if (accountChange.credit) { if (accountChange.credit) {
change = (normal === 'credit') ? accountChange.credit : -1 * accountChange.credit; change =
normal === 'credit'
? accountChange.credit
: -1 * accountChange.credit;
} }
if (accountChange.debit) { if (accountChange.debit) {
change = (normal === 'debit') ? accountChange.debit : -1 * accountChange.debit; change =
normal === 'debit' ? accountChange.debit : -1 * accountChange.debit;
} }
mappedList.push({ account, change }); mappedList.push({ account, change });
}), })
); );
return mappedList; return mappedList;
} }
@@ -198,20 +203,26 @@ export default class JournalPoster implements IJournalPoster {
const { Account } = this.models; const { Account } = this.models;
const accountsChange = this.balanceChangeWithDepends(this.balancesChange); const accountsChange = this.balanceChangeWithDepends(this.balancesChange);
const balancesList = await this.convertBalanceChangesToArr(accountsChange); 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. // 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 }); .patch({ amount: 0 });
const balanceUpdateOpers: Promise<void>[] = []; const balanceUpdateOpers: Promise<void>[] = [];
balancesList.forEach((balance: { account: number, change: number }) => { balancesList.forEach((balance: { account: number; change: number }) => {
const method: string = (balance.change < 0) ? 'decrement' : 'increment'; const method: string = balance.change < 0 ? 'decrement' : 'increment';
this.logger.info('[journal_poster] increment/decrement account balance.', { this.logger.info(
balance, tenantId: this.tenantId, '[journal_poster] increment/decrement account balance.',
}) {
balance,
tenantId: this.tenantId,
}
);
const query = Account.query() const query = Account.query()
[method]('amount', Math.abs(balance.change)) [method]('amount', Math.abs(balance.change))
.where('id', balance.account); .where('id', balance.account);
@@ -228,16 +239,22 @@ export default class JournalPoster implements IJournalPoster {
* @param {IAccountsChange} accountsChange * @param {IAccountsChange} accountsChange
* @returns {IAccountsChange} * @returns {IAccountsChange}
*/ */
private balanceChangeWithDepends(accountsChange: IAccountsChange): IAccountsChange { private balanceChangeWithDepends(
accountsChange: IAccountsChange
): IAccountsChange {
const accountsIds = Object.keys(accountsChange); const accountsIds = Object.keys(accountsChange);
let changes: IAccountsChange = {}; let changes: IAccountsChange = {};
accountsIds.forEach((accountId) => { accountsIds.forEach((accountId) => {
const accountChange = accountsChange[accountId]; const accountChange = accountsChange[accountId];
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId); const depAccountsIds = this.accountsDepGraph.dependantsOf(accountId);
[accountId, ...depAccountsIds].forEach((account) => { [accountId, ...depAccountsIds].forEach((account) => {
changes = this.accountBalanceChangeReducer(changes, account, accountChange); changes = this.accountBalanceChangeReducer(
changes,
account,
accountChange
);
}); });
}); });
return changes; return changes;
@@ -260,11 +277,10 @@ export default class JournalPoster implements IJournalPoster {
const saveOperations: Promise<void>[] = []; const saveOperations: Promise<void>[] = [];
this.entries.forEach((entry) => { this.entries.forEach((entry) => {
const oper = transactionsRepository const oper = transactionsRepository.create({
.create({ accountId: entry.account,
accountId: entry.account, ...omit(entry, ['account']),
...omit(entry, ['account']), });
});
saveOperations.push(oper); saveOperations.push(oper);
}); });
await Promise.all(saveOperations); await Promise.all(saveOperations);
@@ -368,7 +384,7 @@ export default class JournalPoster implements IJournalPoster {
*/ */
getClosingBalance( getClosingBalance(
accountId: number, accountId: number,
closingDate: Date|string, closingDate: Date | string,
dateType: string = 'day' dateType: string = 'day'
): number { ): number {
let closingBalance = 0; let closingBalance = 0;
@@ -399,11 +415,16 @@ export default class JournalPoster implements IJournalPoster {
* @param {String} dateType - * @param {String} dateType -
* @return {Number} * @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 accountNode = this.accountsDepGraph.getNodeData(accountId);
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId); const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
const depAccounts = depAccountsIds const depAccounts = depAccountsIds.map((id) =>
.map((id) => this.accountsDepGraph.getNodeData(id)); this.accountsDepGraph.getNodeData(id)
);
let balance: number = 0; let balance: number = 0;
@@ -459,7 +480,11 @@ export default class JournalPoster implements IJournalPoster {
* @return {Number} * @return {Number}
*/ */
getTrialBalanceWithDepands(accountId: number, closingDate: Date, dateType: string) { getTrialBalanceWithDepands(
accountId: number,
closingDate: Date,
dateType: string
) {
const accountNode = this.accountsDepGraph.getNodeData(accountId); const accountNode = this.accountsDepGraph.getNodeData(accountId);
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId); const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
const depAccounts = depAccountsIds.map((id) => const depAccounts = depAccountsIds.map((id) =>
@@ -485,8 +510,8 @@ export default class JournalPoster implements IJournalPoster {
accountId: number, accountId: number,
contactId: number, contactId: number,
contactType: string, contactType: string,
closingDate?: Date|string, closingDate?: Date | string,
openingDate?: Date|string, openingDate?: Date | string
) { ) {
const momentClosingDate = moment(closingDate); const momentClosingDate = moment(closingDate);
const momentOpeningDate = moment(openingDate); const momentOpeningDate = moment(openingDate);
@@ -534,7 +559,7 @@ export default class JournalPoster implements IJournalPoster {
contactId: number, contactId: number,
contactType: string, contactType: string,
closingDate: Date, closingDate: Date,
openingDate: Date, openingDate: Date
) { ) {
const momentClosingDate = moment(closingDate); const momentClosingDate = moment(closingDate);
let balance = 0; let balance = 0;

View File

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