refactoring: balance sheet report.

refactoring: trial balance sheet report.
refactoring: general ledger report.
refactoring: journal report.
refactoring: P&L report.
This commit is contained in:
Ahmed Bouhuolia
2020-12-10 13:04:49 +02:00
parent e8f329e29e
commit d49992a6d7
71 changed files with 3203 additions and 1571 deletions

View File

@@ -0,0 +1,150 @@
import { pick } from 'lodash';
import {
IGeneralLedgerSheetQuery,
IGeneralLedgerSheetAccount,
IGeneralLedgerSheetAccountBalance,
IGeneralLedgerSheetAccountTransaction,
IAccount,
IJournalPoster,
IAccountType,
IJournalEntry
} from 'interfaces';
import FinancialSheet from "../FinancialSheet";
export default class GeneralLedgerSheet extends FinancialSheet {
tenantId: number;
accounts: IAccount[];
query: IGeneralLedgerSheetQuery;
openingBalancesJournal: IJournalPoster;
closingBalancesJournal: IJournalPoster;
transactions: IJournalPoster;
baseCurrency: string;
/**
* Constructor method.
* @param {number} tenantId -
* @param {IAccount[]} accounts -
* @param {IJournalPoster} transactions -
* @param {IJournalPoster} openingBalancesJournal -
* @param {IJournalPoster} closingBalancesJournal -
*/
constructor(
tenantId: number,
query: IGeneralLedgerSheetQuery,
accounts: IAccount[],
transactions: IJournalPoster,
openingBalancesJournal: IJournalPoster,
closingBalancesJournal: IJournalPoster,
baseCurrency: string,
) {
super();
this.tenantId = tenantId;
this.query = query;
this.numberFormat = this.query.numberFormat;
this.accounts = accounts;
this.transactions = transactions;
this.openingBalancesJournal = openingBalancesJournal;
this.closingBalancesJournal = closingBalancesJournal;
this.baseCurrency = baseCurrency;
}
/**
* Mapping the account transactions to general ledger transactions of the given account.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountTransaction[]}
*/
private accountTransactionsMapper(
account: IAccount & { type: IAccountType }
): IGeneralLedgerSheetAccountTransaction[] {
const entries = this.transactions.getAccountEntries(account.id);
return entries.map((transaction: IJournalEntry): IGeneralLedgerSheetAccountTransaction => {
let amount = 0;
if (account.type.normal === 'credit') {
amount += transaction.credit - transaction.debit;
} else if (account.type.normal === 'debit') {
amount += transaction.debit - transaction.credit;
}
const formattedAmount = this.formatNumber(amount);
return {
...pick(transaction, ['id', 'note', 'transactionType', 'referenceType',
'referenceId', 'date']),
amount,
formattedAmount,
currencyCode: this.baseCurrency,
};
});
}
/**
* Retrieve account opening balance.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountBalance}
*/
private accountOpeningBalance(account: IAccount): IGeneralLedgerSheetAccountBalance {
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
const formattedAmount = this.formatNumber(amount);
const currencyCode = this.baseCurrency;
const date = this.query.fromDate;
return { amount, formattedAmount, currencyCode, date };
}
/**
* Retrieve account closing balance.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountBalance}
*/
private accountClosingBalance(account: IAccount): IGeneralLedgerSheetAccountBalance {
const amount = this.closingBalancesJournal.getAccountBalance(account.id);
const formattedAmount = this.formatNumber(amount);
const currencyCode = this.baseCurrency;
const date = this.query.toDate;
return { amount, formattedAmount, currencyCode, date };
}
/**
* Retreive general ledger accounts sections.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccount}
*/
private accountMapper(
account: IAccount & { type: IAccountType },
): IGeneralLedgerSheetAccount {
return {
...pick(account, ['id', 'name', 'code', 'index', 'parentAccountId']),
opening: this.accountOpeningBalance(account),
transactions: this.accountTransactionsMapper(account),
closing: this.accountClosingBalance(account),
}
}
/**
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
* @param {IAccount[]} accounts -
* @return {IGeneralLedgerSheetAccount[]}
*/
private accountsWalker(
accounts: IAccount & { type: IAccountType }[]
): IGeneralLedgerSheetAccount[] {
return accounts
.map((account: IAccount & { type: IAccountType }) => this.accountMapper(account))
// Filter general ledger accounts that have no transactions when `noneTransactions` is on.
.filter((generalLedgerAccount: IGeneralLedgerSheetAccount) => (
!(generalLedgerAccount.transactions.length === 0 && this.query.noneTransactions)
));
}
/**
* Retrieve general ledger report data.
* @return {IGeneralLedgerSheetAccount[]}
*/
public reportData(): IGeneralLedgerSheetAccount[] {
return this.accountsWalker(this.accounts);
}
}

View File

@@ -0,0 +1,125 @@
import { Service, Inject } from "typedi";
import moment from 'moment';
import { ServiceError } from "exceptions";
import { difference } from 'lodash';
import { IGeneralLedgerSheetQuery } from 'interfaces';
import TenancyService from 'services/Tenancy/TenancyService';
import Journal from "services/Accounting/JournalPoster";
import GeneralLedgerSheet from 'services/FinancialStatements/GeneralLedger/GeneralLedger';
const ERRORS = {
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
};
@Service()
export default class GeneralLedgerService {
@Inject()
tenancy: TenancyService;
@Inject('logger')
logger: any;
/**
* Defaults general ledger report filter query.
* @return {IBalanceSheetQuery}
*/
get defaultQuery() {
return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
numberFormat: {
noCents: false,
divideOn1000: false,
},
noneZero: false,
accountsIds: [],
};
}
/**
* Validates accounts existance on the storage.
* @param {number} tenantId
* @param {number[]} accountsIds
*/
async validateAccountsExistance(tenantId: number, accountsIds: number[]) {
const { Account } = this.tenancy.models(tenantId);
const storedAccounts = await Account.query().whereIn('id', accountsIds);
const storedAccountsIds = storedAccounts.map((a) => a.id);
if (difference(accountsIds, storedAccountsIds).length > 0) {
throw new ServiceError(ERRORS.ACCOUNTS_NOT_FOUND)
}
}
/**
* Retrieve general ledger report statement.
* ----------
* @param {number} tenantId
* @param {IGeneralLedgerSheetQuery} query
* @return {IGeneralLedgerStatement}
*/
async generalLedger(tenantId: number, query: IGeneralLedgerSheetQuery):
Promise<{
data: any,
query: IGeneralLedgerSheetQuery,
}> {
const {
accountRepository,
transactionsRepository,
} = this.tenancy.repositories(tenantId);
const filter = {
...this.defaultQuery,
...query,
};
this.logger.info('[general_ledger] trying to calculate the report.', { tenantId, filter })
const settings = this.tenancy.settings(tenantId);
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
// Retrieve all accounts from the storage.
const accounts = await accountRepository.allAccounts('type');
const accountsGraph = await accountRepository.getDependencyGraph();
// Retreive journal transactions from/to the given date.
const transactions = await transactionsRepository.journal({
fromDate: filter.fromDate,
toDate: filter.toDate,
});
// Retreive opening balance credit/debit sumation.
const openingBalanceTrans = await transactionsRepository.journal({
toDate: filter.fromDate,
sumationCreditDebit: true,
});
// Retreive closing balance credit/debit sumation.
const closingBalanceTrans = await transactionsRepository.journal({
toDate: filter.toDate,
sumationCreditDebit: true,
});
// Transform array transactions to journal collection.
const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph);
const openingTransJournal = Journal.fromTransactions(openingBalanceTrans, tenantId, accountsGraph);
const closingTransJournal = Journal.fromTransactions(closingBalanceTrans, tenantId, accountsGraph);
// General ledger report instance.
const generalLedgerInstance = new GeneralLedgerSheet(
tenantId,
filter,
accounts,
transactionsJournal,
openingTransJournal,
closingTransJournal,
baseCurrency
);
// Retrieve general ledger report data.
const reportData = generalLedgerInstance.reportData();
return {
data: reportData,
query: filter,
}
}
}