mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
add server to monorepo.
This commit is contained in:
759
packages/server/src/services/Accounting/JournalPoster.ts
Normal file
759
packages/server/src/services/Accounting/JournalPoster.ts
Normal file
@@ -0,0 +1,759 @@
|
||||
import { omit, get, chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Container } from 'typedi';
|
||||
import async from 'async';
|
||||
import JournalEntry from '@/services/Accounting/JournalEntry';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
IJournalEntry,
|
||||
IJournalPoster,
|
||||
IAccountChange,
|
||||
IAccountsChange,
|
||||
TEntryType,
|
||||
} from '@/interfaces';
|
||||
import Knex from 'knex';
|
||||
|
||||
const CONTACTS_CONFIG = [
|
||||
{
|
||||
accountBySlug: 'accounts-receivable',
|
||||
contactService: 'customer',
|
||||
assignRequired: true,
|
||||
},
|
||||
{
|
||||
accountBySlug: 'accounts-payable',
|
||||
contactService: 'vendor',
|
||||
assignRequired: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default class JournalPoster implements IJournalPoster {
|
||||
tenantId: number;
|
||||
tenancy: TenancyService;
|
||||
logger: any;
|
||||
models: any;
|
||||
repositories: any;
|
||||
|
||||
deletedEntriesIds: number[] = [];
|
||||
entries: IJournalEntry[] = [];
|
||||
balancesChange: IAccountsChange = {};
|
||||
accountsDepGraph: IAccountsChange;
|
||||
|
||||
accountsBalanceTable: { [key: number]: number } = {};
|
||||
contactsBalanceTable: {
|
||||
[key: number]: { credit: number; debit: number }[];
|
||||
} = {};
|
||||
saveContactBalanceQueue: any;
|
||||
|
||||
/**
|
||||
* Journal poster constructor.
|
||||
* @param {number} tenantId -
|
||||
*/
|
||||
constructor(tenantId: number, accountsGraph?: any, trx?: Knex.Transaction) {
|
||||
this.initTenancy();
|
||||
|
||||
this.tenantId = tenantId;
|
||||
this.models = this.tenancy.models(tenantId);
|
||||
this.repositories = this.tenancy.repositories(tenantId);
|
||||
|
||||
if (accountsGraph) {
|
||||
this.accountsDepGraph = accountsGraph;
|
||||
}
|
||||
this.trx = trx;
|
||||
this.saveContactBalanceQueue = async.queue(
|
||||
this.saveContactBalanceChangeTask.bind(this),
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial tenancy.
|
||||
* @private
|
||||
*/
|
||||
private initTenancy() {
|
||||
try {
|
||||
this.tenancy = Container.get(TenancyService);
|
||||
this.logger = Container.get('logger');
|
||||
} catch (exception) {
|
||||
throw new Error('Should execute this class inside tenancy area.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async initialize acccounts dependency graph.
|
||||
* @private
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async initAccountsDepGraph(): Promise<void> {
|
||||
const { accountRepository } = this.repositories;
|
||||
|
||||
if (!this.accountsDepGraph) {
|
||||
const accountsDepGraph = await accountRepository.getDependencyGraph();
|
||||
this.accountsDepGraph = accountsDepGraph;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the ledger is empty.
|
||||
*/
|
||||
public isEmpty() {
|
||||
return this.entries.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the credit entry for the given account.
|
||||
* @param {IJournalEntry} entry -
|
||||
*/
|
||||
public credit(entryModel: IJournalEntry): void {
|
||||
if (entryModel instanceof JournalEntry === false) {
|
||||
throw new Error('The entry is not instance of JournalEntry.');
|
||||
}
|
||||
this.entries.push(entryModel.entry);
|
||||
this.setAccountBalanceChange(entryModel.entry);
|
||||
this.setContactBalanceChange(entryModel.entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the debit entry for the given account.
|
||||
* @param {JournalEntry} entry -
|
||||
*/
|
||||
public debit(entryModel: IJournalEntry): void {
|
||||
if (entryModel instanceof JournalEntry === false) {
|
||||
throw new Error('The entry is not instance of JournalEntry.');
|
||||
}
|
||||
this.entries.push(entryModel.entry);
|
||||
this.setAccountBalanceChange(entryModel.entry);
|
||||
this.setContactBalanceChange(entryModel.entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the contact balance change.
|
||||
*/
|
||||
private setContactBalanceChange(entry) {
|
||||
if (!entry.contactId) {
|
||||
return;
|
||||
}
|
||||
const change = {
|
||||
debit: entry.debit || 0,
|
||||
credit: entry.credit || 0,
|
||||
account: entry.account,
|
||||
};
|
||||
if (!this.contactsBalanceTable[entry.contactId]) {
|
||||
this.contactsBalanceTable[entry.contactId] = [];
|
||||
}
|
||||
this.contactsBalanceTable[entry.contactId].push(change);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save contacts balance change.
|
||||
*/
|
||||
async saveContactsBalance() {
|
||||
await this.initAccountsDepGraph();
|
||||
|
||||
const balanceChanges = Object.entries(this.contactsBalanceTable).map(
|
||||
([contactId, entries]) => ({
|
||||
contactId,
|
||||
entries: entries.filter((entry) => {
|
||||
const account = this.accountsDepGraph.getNodeData(entry.account);
|
||||
|
||||
return (
|
||||
account &&
|
||||
CONTACTS_CONFIG.some((config) => {
|
||||
return config.accountBySlug === account.slug;
|
||||
})
|
||||
);
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const balanceEntries = chain(balanceChanges)
|
||||
.map((change) =>
|
||||
change.entries.map((entry) => ({
|
||||
...entry,
|
||||
contactId: change.contactId,
|
||||
}))
|
||||
)
|
||||
.flatten()
|
||||
.value();
|
||||
|
||||
return this.saveContactBalanceQueue.pushAsync(balanceEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves contact balance change task.
|
||||
* @param {number} contactId - Contact id.
|
||||
* @param {number} credit - Credit amount.
|
||||
* @param {number} debit - Debit amount.
|
||||
*/
|
||||
async saveContactBalanceChangeTask({ contactId, credit, debit }) {
|
||||
const { contactRepository } = this.repositories;
|
||||
|
||||
const contact = await contactRepository.findOneById(contactId);
|
||||
let balanceChange = 0;
|
||||
|
||||
if (contact.contactNormal === 'credit') {
|
||||
balanceChange += credit - debit;
|
||||
} else {
|
||||
balanceChange += debit - credit;
|
||||
}
|
||||
// Contact change balance.
|
||||
await contactRepository.changeNumber(
|
||||
{ id: contactId },
|
||||
'balance',
|
||||
balanceChange,
|
||||
this.trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets account balance change.
|
||||
* @param {JournalEntry} entry
|
||||
* @param {String} type
|
||||
*/
|
||||
private setAccountBalanceChange(entry: IJournalEntry): void {
|
||||
const accountChange: IAccountChange = {
|
||||
debit: entry.debit,
|
||||
credit: entry.credit,
|
||||
};
|
||||
this._setAccountBalanceChange(entry.account, accountChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets account balance change.
|
||||
* @private
|
||||
* @param {number} accountId -
|
||||
* @param {IAccountChange} accountChange
|
||||
*/
|
||||
private _setAccountBalanceChange(
|
||||
accountId: number,
|
||||
accountChange: IAccountChange
|
||||
) {
|
||||
this.balancesChange = this.accountBalanceChangeReducer(
|
||||
this.balancesChange,
|
||||
accountId,
|
||||
accountChange
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accounts balance change reducer.
|
||||
* @param {IAccountsChange} balancesChange
|
||||
* @param {number} accountId
|
||||
* @param {IAccountChange} accountChange
|
||||
* @return {IAccountChange}
|
||||
*/
|
||||
private accountBalanceChangeReducer(
|
||||
balancesChange: IAccountsChange,
|
||||
accountId: number,
|
||||
accountChange: IAccountChange
|
||||
) {
|
||||
const change = { ...balancesChange };
|
||||
|
||||
if (!change[accountId]) {
|
||||
change[accountId] = { credit: 0, debit: 0 };
|
||||
}
|
||||
if (accountChange.credit) {
|
||||
change[accountId].credit += accountChange.credit;
|
||||
}
|
||||
if (accountChange.debit) {
|
||||
change[accountId].debit += accountChange.debit;
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts balance changes to array.
|
||||
* @private
|
||||
* @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)
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
accountsIds.map(async (account: number) => {
|
||||
const accountChange = accountsChange[account];
|
||||
const accountNode = this.accountsDepGraph.getNodeData(account);
|
||||
const normal = accountNode.accountNormal;
|
||||
|
||||
let change = 0;
|
||||
|
||||
if (accountChange.credit) {
|
||||
change +=
|
||||
normal === 'credit'
|
||||
? accountChange.credit
|
||||
: -1 * accountChange.credit;
|
||||
}
|
||||
if (accountChange.debit) {
|
||||
change +=
|
||||
normal === 'debit' ? accountChange.debit : -1 * accountChange.debit;
|
||||
}
|
||||
mappedList.push({ account, change });
|
||||
})
|
||||
);
|
||||
return mappedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the balance change of journal entries.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async saveBalance() {
|
||||
await this.initAccountsDepGraph();
|
||||
|
||||
const { Account } = this.models;
|
||||
const accountsChange = this.balanceChangeWithDepends(this.balancesChange);
|
||||
const balancesList = await this.convertBalanceChangesToArr(accountsChange);
|
||||
const balancesAccounts = balancesList.map((b) => b.account);
|
||||
|
||||
// Ensure the accounts has atleast zero in amount.
|
||||
await Account.query(this.trx)
|
||||
.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';
|
||||
|
||||
this.logger.info(
|
||||
'[journal_poster] increment/decrement account balance.',
|
||||
{
|
||||
balance,
|
||||
tenantId: this.tenantId,
|
||||
}
|
||||
);
|
||||
const query = Account.query(this.trx)
|
||||
[method]('amount', Math.abs(balance.change))
|
||||
.where('id', balance.account);
|
||||
|
||||
balanceUpdateOpers.push(query);
|
||||
});
|
||||
|
||||
await Promise.all(balanceUpdateOpers);
|
||||
this.resetAccountsBalanceChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes all accounts that dependencies of changed accounts.
|
||||
* @param {IAccountsChange} accountsChange
|
||||
* @returns {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.dependantsOf(accountId);
|
||||
|
||||
[accountId, ...depAccountsIds].forEach((account) => {
|
||||
changes = this.accountBalanceChangeReducer(
|
||||
changes,
|
||||
account,
|
||||
accountChange
|
||||
);
|
||||
});
|
||||
});
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets accounts balance change.
|
||||
* @private
|
||||
*/
|
||||
private resetAccountsBalanceChange() {
|
||||
this.balancesChange = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the stacked journal entries to the storage.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async saveEntries() {
|
||||
const { transactionsRepository } = this.repositories;
|
||||
const saveOperations: Promise<void>[] = [];
|
||||
|
||||
this.logger.info('[journal] trying to insert accounts transactions.');
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
const oper = transactionsRepository.create(
|
||||
{
|
||||
accountId: entry.account,
|
||||
...omit(entry, ['account']),
|
||||
},
|
||||
this.trx
|
||||
);
|
||||
saveOperations.push(oper);
|
||||
});
|
||||
await Promise.all(saveOperations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the stacked journal entries.
|
||||
*/
|
||||
public reverseEntries() {
|
||||
const reverseEntries: IJournalEntry[] = [];
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
const reverseEntry = { ...entry };
|
||||
|
||||
if (entry.credit) {
|
||||
reverseEntry.debit = entry.credit;
|
||||
}
|
||||
if (entry.debit) {
|
||||
reverseEntry.credit = entry.debit;
|
||||
}
|
||||
reverseEntries.push(reverseEntry);
|
||||
});
|
||||
this.entries = reverseEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all stored entries or by the given in ids.
|
||||
* @param {Array} ids -
|
||||
*/
|
||||
removeEntries(ids: number[] = []) {
|
||||
const targetIds = ids.length <= 0 ? this.entries.map((e) => e.id) : ids;
|
||||
const removeEntries = this.entries.filter(
|
||||
(e) => targetIds.indexOf(e.id) !== -1
|
||||
);
|
||||
this.entries = this.entries.filter((e) => targetIds.indexOf(e.id) === -1);
|
||||
|
||||
removeEntries.forEach((entry) => {
|
||||
entry.credit = -1 * entry.credit;
|
||||
entry.debit = -1 * entry.debit;
|
||||
|
||||
this.setAccountBalanceChange(entry);
|
||||
this.setContactBalanceChange(entry);
|
||||
});
|
||||
this.deletedEntriesIds.push(...removeEntries.map((entry) => entry.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the stacked entries.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deleteEntries() {
|
||||
const { transactionsRepository } = this.repositories;
|
||||
|
||||
if (this.deletedEntriesIds.length > 0) {
|
||||
await transactionsRepository.deleteWhereIdIn(
|
||||
this.deletedEntriesIds,
|
||||
this.trx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load fetched accounts journal entries.
|
||||
* @param {IJournalEntry[]} entries -
|
||||
*/
|
||||
fromTransactions(transactions) {
|
||||
transactions.forEach((transaction) => {
|
||||
this.entries.push({
|
||||
...transaction,
|
||||
referenceTypeFormatted: transaction.referenceTypeFormatted,
|
||||
account: transaction.accountId,
|
||||
accountNormal: get(transaction, 'account.accountNormal'),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the entries balance change.
|
||||
* @public
|
||||
*/
|
||||
public calculateEntriesBalanceChange() {
|
||||
this.entries.forEach((entry) => {
|
||||
if (entry.credit) {
|
||||
this.setAccountBalanceChange(entry, 'credit');
|
||||
}
|
||||
if (entry.debit) {
|
||||
this.setAccountBalanceChange(entry, 'debit');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static fromTransactions(entries, ...args: [number, ...any]) {
|
||||
const journal = new this(...args);
|
||||
journal.fromTransactions(entries);
|
||||
|
||||
return journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the closing balance for the given account and closing date.
|
||||
* @param {Number} accountId -
|
||||
* @param {Date} closingDate -
|
||||
* @param {string} dataType? -
|
||||
* @return {number}
|
||||
*/
|
||||
getClosingBalance(
|
||||
accountId: number,
|
||||
closingDate: Date | string,
|
||||
dateType: string = 'day'
|
||||
): number {
|
||||
let closingBalance = 0;
|
||||
const momentClosingDate = moment(closingDate);
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
// Can not continue if not before or event same closing date.
|
||||
if (
|
||||
(!momentClosingDate.isAfter(entry.date, dateType) &&
|
||||
!momentClosingDate.isSame(entry.date, dateType)) ||
|
||||
(entry.account !== accountId && accountId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (entry.accountNormal === 'credit') {
|
||||
closingBalance += entry.credit ? entry.credit : -1 * entry.debit;
|
||||
} else if (entry.accountNormal === 'debit') {
|
||||
closingBalance += entry.debit ? entry.debit : -1 * entry.credit;
|
||||
}
|
||||
});
|
||||
return closingBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given account balance with dependencies accounts.
|
||||
* @param {Number} accountId -
|
||||
* @param {Date} closingDate -
|
||||
* @param {String} dateType -
|
||||
* @return {Number}
|
||||
*/
|
||||
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)
|
||||
);
|
||||
|
||||
let balance: number = 0;
|
||||
|
||||
[...depAccounts, accountNode].forEach((account) => {
|
||||
const closingBalance = this.getClosingBalance(
|
||||
account.id,
|
||||
closingDate,
|
||||
dateType
|
||||
);
|
||||
this.accountsBalanceTable[account.id] = closingBalance;
|
||||
balance += this.accountsBalanceTable[account.id];
|
||||
});
|
||||
return balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the credit/debit sumation for the given account and date.
|
||||
* @param {Number} account -
|
||||
* @param {Date|String} closingDate -
|
||||
*/
|
||||
getTrialBalance(accountId, closingDate) {
|
||||
const momentClosingDate = moment(closingDate);
|
||||
const result = {
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
balance: 0,
|
||||
};
|
||||
this.entries.forEach((entry) => {
|
||||
if (
|
||||
(!momentClosingDate.isAfter(entry.date, 'day') &&
|
||||
!momentClosingDate.isSame(entry.date, 'day')) ||
|
||||
(entry.account !== accountId && accountId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
result.credit += entry.credit;
|
||||
result.debit += entry.debit;
|
||||
|
||||
if (entry.accountNormal === 'credit') {
|
||||
result.balance += entry.credit - entry.debit;
|
||||
} else if (entry.accountNormal === 'debit') {
|
||||
result.balance += entry.debit - entry.credit;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve trial balance of the given account with depends.
|
||||
* @param {Number} accountId
|
||||
* @param {Date} closingDate
|
||||
* @param {String} dateType
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
getTrialBalanceWithDepands(
|
||||
accountId: number,
|
||||
closingDate: Date,
|
||||
dateType: string
|
||||
) {
|
||||
const accountNode = this.accountsDepGraph.getNodeData(accountId);
|
||||
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
|
||||
const depAccounts = depAccountsIds.map((id) =>
|
||||
this.accountsDepGraph.getNodeData(id)
|
||||
);
|
||||
const trialBalance = { credit: 0, debit: 0, balance: 0 };
|
||||
|
||||
[...depAccounts, accountNode].forEach((account) => {
|
||||
const _trialBalance = this.getTrialBalance(
|
||||
account.id,
|
||||
closingDate,
|
||||
dateType
|
||||
);
|
||||
|
||||
trialBalance.credit += _trialBalance.credit;
|
||||
trialBalance.debit += _trialBalance.debit;
|
||||
trialBalance.balance += _trialBalance.balance;
|
||||
});
|
||||
return trialBalance;
|
||||
}
|
||||
|
||||
getContactTrialBalance(
|
||||
accountId: number,
|
||||
contactId: number,
|
||||
contactType: string,
|
||||
closingDate?: Date | string,
|
||||
openingDate?: Date | string
|
||||
) {
|
||||
const momentClosingDate = moment(closingDate);
|
||||
const momentOpeningDate = moment(openingDate);
|
||||
const trial = {
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
balance: 0,
|
||||
};
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
if (
|
||||
(closingDate &&
|
||||
!momentClosingDate.isAfter(entry.date, 'day') &&
|
||||
!momentClosingDate.isSame(entry.date, 'day')) ||
|
||||
(openingDate &&
|
||||
!momentOpeningDate.isBefore(entry.date, 'day') &&
|
||||
!momentOpeningDate.isSame(entry.date)) ||
|
||||
(accountId && entry.account !== accountId) ||
|
||||
(contactId && entry.contactId !== contactId) ||
|
||||
entry.contactType !== contactType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (entry.credit) {
|
||||
trial.balance -= entry.credit;
|
||||
trial.credit += entry.credit;
|
||||
}
|
||||
if (entry.debit) {
|
||||
trial.balance += entry.debit;
|
||||
trial.debit += entry.debit;
|
||||
}
|
||||
});
|
||||
return trial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve total balnace of the given customer/vendor contact.
|
||||
* @param {Number} accountId
|
||||
* @param {Number} contactId
|
||||
* @param {String} contactType
|
||||
* @param {Date} closingDate
|
||||
*/
|
||||
getContactBalance(
|
||||
accountId: number,
|
||||
contactId: number,
|
||||
contactType: string,
|
||||
closingDate: Date,
|
||||
openingDate: Date
|
||||
) {
|
||||
const momentClosingDate = moment(closingDate);
|
||||
let balance = 0;
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
if (
|
||||
(closingDate &&
|
||||
!momentClosingDate.isAfter(entry.date, 'day') &&
|
||||
!momentClosingDate.isSame(entry.date, 'day')) ||
|
||||
(entry.account !== accountId && accountId) ||
|
||||
(contactId && entry.contactId !== contactId) ||
|
||||
entry.contactType !== contactType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (entry.credit) {
|
||||
balance -= entry.credit;
|
||||
}
|
||||
if (entry.debit) {
|
||||
balance += entry.debit;
|
||||
}
|
||||
});
|
||||
return balance;
|
||||
}
|
||||
|
||||
getAccountEntries(accountId: number) {
|
||||
return this.entries.filter((entry) => entry.account === accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account entries with depents accounts.
|
||||
* @param {number} accountId -
|
||||
*/
|
||||
getAccountEntriesWithDepents(accountId: number) {
|
||||
const depAccountsIds = this.accountsDepGraph.dependenciesOf(accountId);
|
||||
const accountsIds = [accountId, ...depAccountsIds];
|
||||
|
||||
return this.entries.filter(
|
||||
(entry) => accountsIds.indexOf(entry.account) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve total balnace of the given customer/vendor contact.
|
||||
* @param {Number} accountId
|
||||
* @param {Number} contactId
|
||||
* @param {String} contactType
|
||||
* @param {Date} closingDate
|
||||
*/
|
||||
getEntriesBalance(entries) {
|
||||
let balance = 0;
|
||||
|
||||
entries.forEach((entry) => {
|
||||
if (entry.credit) {
|
||||
balance -= entry.credit;
|
||||
}
|
||||
if (entry.debit) {
|
||||
balance += entry.debit;
|
||||
}
|
||||
});
|
||||
return balance;
|
||||
}
|
||||
|
||||
getContactEntries(contactId: number, openingDate: Date, closingDate?: Date) {
|
||||
const momentClosingDate = moment(closingDate);
|
||||
const momentOpeningDate = moment(openingDate);
|
||||
|
||||
return this.entries.filter((entry) => {
|
||||
if (
|
||||
(closingDate &&
|
||||
!momentClosingDate.isAfter(entry.date, 'day') &&
|
||||
!momentClosingDate.isSame(entry.date, 'day')) ||
|
||||
(openingDate &&
|
||||
!momentOpeningDate.isBefore(entry.date, 'day') &&
|
||||
!momentOpeningDate.isSame(entry.date)) ||
|
||||
entry.contactId === contactId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user