mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
import { sumBy } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
ITrialBalanceSheetQuery,
|
||||
ITrialBalanceAccount,
|
||||
IAccount,
|
||||
ITrialBalanceTotal,
|
||||
ITrialBalanceSheetData,
|
||||
IAccountType,
|
||||
} from '@/interfaces';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import { allPassedConditionsPass, flatToNestedArray } from 'utils';
|
||||
|
||||
export default class TrialBalanceSheet extends FinancialSheet {
|
||||
tenantId: number;
|
||||
query: ITrialBalanceSheetQuery;
|
||||
accounts: IAccount & { type: IAccountType }[];
|
||||
journalFinancial: any;
|
||||
baseCurrency: string;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId
|
||||
* @param {ITrialBalanceSheetQuery} query
|
||||
* @param {IAccount[]} accounts
|
||||
* @param journalFinancial
|
||||
*/
|
||||
constructor(
|
||||
tenantId: number,
|
||||
query: ITrialBalanceSheetQuery,
|
||||
accounts: IAccount & { type: IAccountType }[],
|
||||
journalFinancial: any,
|
||||
baseCurrency: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.tenantId = tenantId;
|
||||
this.query = query;
|
||||
this.accounts = accounts;
|
||||
this.journalFinancial = journalFinancial;
|
||||
this.numberFormat = this.query.numberFormat;
|
||||
this.baseCurrency = baseCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account mapper.
|
||||
* @param {IAccount} account
|
||||
* @return {ITrialBalanceAccount}
|
||||
*/
|
||||
private accountTransformer = (
|
||||
account: IAccount & { type: IAccountType }
|
||||
): ITrialBalanceAccount => {
|
||||
const trial = this.journalFinancial.getTrialBalanceWithDepands(account.id);
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
parentAccountId: account.parentAccountId,
|
||||
name: account.name,
|
||||
code: account.code,
|
||||
accountNormal: account.accountNormal,
|
||||
|
||||
credit: trial.credit,
|
||||
debit: trial.debit,
|
||||
balance: trial.balance,
|
||||
currencyCode: this.baseCurrency,
|
||||
|
||||
formattedCredit: this.formatNumber(trial.credit),
|
||||
formattedDebit: this.formatNumber(trial.debit),
|
||||
formattedBalance: this.formatNumber(trial.balance),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters trial balance sheet accounts nodes based on the given report query.
|
||||
* @param {ITrialBalanceAccount} accountNode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private accountFilter = (accountNode: ITrialBalanceAccount): boolean => {
|
||||
const { noneTransactions, noneZero, onlyActive } = this.query;
|
||||
|
||||
// Conditions pair filter detarminer.
|
||||
const condsPairFilters = [
|
||||
[noneTransactions, this.filterNoneTransactions],
|
||||
[noneZero, this.filterNoneZero],
|
||||
[onlyActive, this.filterActiveOnly],
|
||||
];
|
||||
return allPassedConditionsPass(condsPairFilters)(accountNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fitlers the accounts nodes.
|
||||
* @param {ITrialBalanceAccount[]} accountsNodes
|
||||
* @returns {ITrialBalanceAccount[]}
|
||||
*/
|
||||
private accountsFilter = (
|
||||
accountsNodes: ITrialBalanceAccount[]
|
||||
): ITrialBalanceAccount[] => {
|
||||
return accountsNodes.filter(this.accountFilter);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mappes the given account object to trial balance account node.
|
||||
* @param {IAccount[]} accountsNodes
|
||||
* @returns {ITrialBalanceAccount[]}
|
||||
*/
|
||||
private accountsMapper = (
|
||||
accountsNodes: IAccount[]
|
||||
): ITrialBalanceAccount[] => {
|
||||
return accountsNodes.map(this.accountTransformer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the given account node is not none transactions.
|
||||
* @param {ITrialBalanceAccount} accountNode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterNoneTransactions = (
|
||||
accountNode: ITrialBalanceAccount
|
||||
): boolean => {
|
||||
const entries = this.journalFinancial.getAccountEntriesWithDepents(
|
||||
accountNode.id
|
||||
);
|
||||
return entries.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the given account none zero.
|
||||
* @param {ITrialBalanceAccount} accountNode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterNoneZero = (accountNode: ITrialBalanceAccount): boolean => {
|
||||
return accountNode.balance !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the given account is active.
|
||||
* @param {ITrialBalanceAccount} accountNode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterActiveOnly = (accountNode: ITrialBalanceAccount): boolean => {
|
||||
return accountNode.credit !== 0 || accountNode.debit !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the flatten nodes to nested nodes.
|
||||
* @param {ITrialBalanceAccount[]} flattenAccounts
|
||||
* @returns {ITrialBalanceAccount[]}
|
||||
*/
|
||||
private nestedAccountsNode = (
|
||||
flattenAccounts: ITrialBalanceAccount[]
|
||||
): ITrialBalanceAccount[] => {
|
||||
return flatToNestedArray(flattenAccounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve trial balance total section.
|
||||
* @param {ITrialBalanceAccount[]} accountsBalances
|
||||
* @return {ITrialBalanceTotal}
|
||||
*/
|
||||
private tatalSection(
|
||||
accountsBalances: ITrialBalanceAccount[]
|
||||
): ITrialBalanceTotal {
|
||||
const credit = sumBy(accountsBalances, 'credit');
|
||||
const debit = sumBy(accountsBalances, 'debit');
|
||||
const balance = sumBy(accountsBalances, 'balance');
|
||||
const currencyCode = this.baseCurrency;
|
||||
|
||||
return {
|
||||
credit,
|
||||
debit,
|
||||
balance,
|
||||
currencyCode,
|
||||
formattedCredit: this.formatTotalNumber(credit),
|
||||
formattedDebit: this.formatTotalNumber(debit),
|
||||
formattedBalance: this.formatTotalNumber(balance),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts section of trial balance report.
|
||||
* @param {IAccount[]} accounts
|
||||
* @returns {ITrialBalanceAccount[]}
|
||||
*/
|
||||
private accountsSection(accounts: IAccount & { type: IAccountType }[]) {
|
||||
return R.compose(
|
||||
this.nestedAccountsNode,
|
||||
this.accountsFilter,
|
||||
this.accountsMapper
|
||||
)(accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve trial balance sheet statement data.
|
||||
* Note: Retruns null in case there is no transactions between the given date periods.
|
||||
*
|
||||
* @return {ITrialBalanceSheetData}
|
||||
*/
|
||||
public reportData(): ITrialBalanceSheetData {
|
||||
// Don't return noting if the journal has no transactions.
|
||||
if (this.journalFinancial.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// Retrieve accounts nodes.
|
||||
const accounts = this.accountsSection(this.accounts);
|
||||
|
||||
// Retrieve account node.
|
||||
const total = this.tatalSection(accounts);
|
||||
|
||||
return { accounts, total };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Journal from '@/services/Accounting/JournalPoster';
|
||||
import { ITrialBalanceSheetMeta, ITrialBalanceSheetQuery, ITrialBalanceStatement } from '@/interfaces';
|
||||
import TrialBalanceSheet from './TrialBalanceSheet';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import { parseBoolean } from 'utils';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export default class TrialBalanceSheetService extends FinancialSheet {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
inventoryService: InventoryService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Defaults trial balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
*/
|
||||
get defaultQuery(): ITrialBalanceSheetQuery {
|
||||
return {
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
numberFormat: {
|
||||
divideOn1000: false,
|
||||
negativeFormat: 'mines',
|
||||
showZero: false,
|
||||
formatMoney: 'total',
|
||||
precision: 2,
|
||||
},
|
||||
basis: 'accural',
|
||||
noneZero: false,
|
||||
noneTransactions: true,
|
||||
onlyActive: false,
|
||||
accountIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the trial balance sheet meta.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @returns {ITrialBalanceSheetMeta}
|
||||
*/
|
||||
reportMetadata(tenantId: number): ITrialBalanceSheetMeta {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
const isCostComputeRunning = this.inventoryService.isItemsCostComputeRunning(
|
||||
tenantId
|
||||
);
|
||||
const organizationName = settings.get({
|
||||
group: 'organization',
|
||||
key: 'name',
|
||||
});
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
return {
|
||||
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||
organizationName,
|
||||
baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve trial balance sheet statement.
|
||||
* -------------
|
||||
* @param {number} tenantId
|
||||
* @param {IBalanceSheetQuery} query
|
||||
*
|
||||
* @return {IBalanceSheetStatement}
|
||||
*/
|
||||
public async trialBalanceSheet(
|
||||
tenantId: number,
|
||||
query: ITrialBalanceSheetQuery
|
||||
): Promise<ITrialBalanceStatement> {
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
const {
|
||||
accountRepository,
|
||||
transactionsRepository,
|
||||
} = this.tenancy.repositories(tenantId);
|
||||
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
this.logger.info('[trial_balance_sheet] trying to calcualte the report.', {
|
||||
tenantId,
|
||||
filter,
|
||||
});
|
||||
// Retrieve all accounts on the storage.
|
||||
const accounts = await accountRepository.all();
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Retrieve all journal transactions based on the given query.
|
||||
const transactions = await transactionsRepository.journal({
|
||||
fromDate: query.fromDate,
|
||||
toDate: query.toDate,
|
||||
sumationCreditDebit: true,
|
||||
branchesIds: query.branchesIds
|
||||
});
|
||||
// Transform transactions array to journal collection.
|
||||
const transactionsJournal = Journal.fromTransactions(
|
||||
transactions,
|
||||
tenantId,
|
||||
accountsGraph
|
||||
);
|
||||
// Trial balance report instance.
|
||||
const trialBalanceInstance = new TrialBalanceSheet(
|
||||
tenantId,
|
||||
filter,
|
||||
accounts,
|
||||
transactionsJournal,
|
||||
tenant.metadata.baseCurrency,
|
||||
);
|
||||
// Trial balance sheet data.
|
||||
const trialBalanceSheetData = trialBalanceInstance.reportData();
|
||||
|
||||
return {
|
||||
data: trialBalanceSheetData,
|
||||
query: filter,
|
||||
meta: this.reportMetadata(tenantId),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user