diff --git a/client/index.js b/client/index.js index 6fb4d3f0..3970c41b 100644 --- a/client/index.js +++ b/client/index.js @@ -26,6 +26,7 @@ module.exports = { frappe.desk.menu.addItem('General Ledger', '#report/general-ledger'); frappe.desk.menu.addItem('Profit And Loss', '#report/profit-and-loss'); frappe.desk.menu.addItem('Balance Sheet', '#report/balance-sheet'); + frappe.desk.menu.addItem('Trial Balance', '#report/trial-balance'); frappe.desk.menu.addItem('Sales Register', '#report/sales-register'); frappe.desk.menu.addItem('Purchase Register', '#report/purchase-register'); diff --git a/reports/FinancialStatements/FinancialStatements.js b/reports/FinancialStatements/FinancialStatements.js index ccc6e36b..3d4c3f89 100644 --- a/reports/FinancialStatements/FinancialStatements.js +++ b/reports/FinancialStatements/FinancialStatements.js @@ -3,13 +3,13 @@ const { DateTime } = require('luxon'); const { unique } = require('frappejs/utils'); async function getData({ - rootType, - balanceMustBe = 'Debit', - fromDate, - toDate, - periodicity = 'Monthly', - accumulateValues = false - }) { + rootType, + balanceMustBe = 'Debit', + fromDate, + toDate, + periodicity = 'Monthly', + accumulateValues = false +}) { let accounts = await getAccounts(rootType); let fiscalYear = await getFiscalYear(); @@ -64,6 +64,42 @@ async function getData({ return { accounts, totalRow, periodList }; } +async function getTrialBalance({ rootType, fromDate, toDate }) { + let accounts = await getAccounts(rootType); + let ledgerEntries = await getLedgerEntries(null, toDate, accounts); + + for (let account of accounts) { + const accountEntries = ledgerEntries.filter(entry => entry.account === account.name); + // opening + const beforePeriodEntries = accountEntries.filter(entry => entry.date < fromDate); + account.opening = beforePeriodEntries.reduce((acc, entry) => { + return acc + (entry.debit - entry.credit); + }, 0); + + if (account.opening >= 0) { + account.openingDebit = account.opening; + } else { + account.openingCredit = -account.opening; + } + + // debit / credit + const periodEntries = accountEntries.filter(entry => entry.date >= fromDate); + account.debit = periodEntries.reduce((acc, entry) => acc + entry.debit, 0); + account.credit = periodEntries.reduce((acc, entry) => acc + entry.credit, 0); + + // closing + account.closing = account.opening + account.debit - account.credit; + + if (account.closing >= 0) { + account.closingDebit = account.closing; + } else { + account.closingCredit = -account.closing; + } + } + + return accounts; +} + function getPeriodList(fromDate, toDate, periodicity, fiscalYear) { if (!fromDate) { fromDate = fiscalYear.start; @@ -210,5 +246,6 @@ async function getFiscalYear() { } module.exports = { - getData + getData, + getTrialBalance } diff --git a/reports/TrialBalance/TrialBalance.js b/reports/TrialBalance/TrialBalance.js new file mode 100644 index 00000000..e545addc --- /dev/null +++ b/reports/TrialBalance/TrialBalance.js @@ -0,0 +1,18 @@ +const frappe = require('frappejs'); +const { getTrialBalance } = require('../FinancialStatements/FinancialStatements'); + +module.exports = class TrialBalance { + async run({ fromDate, toDate }) { + const promises = ['Asset', 'Expense', 'Income', 'Liability', 'Equity'].map(rootType => { + return getTrialBalance({ rootType, fromDate, toDate }); + }); + + const values = await Promise.all(promises); + + let rows = values.reduce((acc, curr) => { + return [...acc, ...curr]; + }, []); + + return { rows }; + } +} diff --git a/reports/TrialBalance/TrialBalanceView.js b/reports/TrialBalance/TrialBalanceView.js new file mode 100644 index 00000000..a3ca0e28 --- /dev/null +++ b/reports/TrialBalance/TrialBalanceView.js @@ -0,0 +1,46 @@ +const ReportPage = require('frappejs/client/desk/reportpage'); +const frappe = require('frappejs'); + +module.exports = class TrialBalanceView extends ReportPage { + constructor(opts) { + super({ + title: frappe._('Trial Balance'), + filterFields: [ + {fieldtype: 'Date', fieldname: 'fromDate', label: 'From Date', required: 1}, + {fieldtype: 'Date', fieldname: 'toDate', label: 'To Date', required: 1} + ] + }); + + this.method = 'trial-balance'; + this.datatableOptions = { + treeView: true, + layout: 'fixed' + } + } + + async setDefaultFilterValues() { + const accountingSettings = await frappe.getSingle('AccountingSettings'); + this.filters.setValue('fromDate', accountingSettings.fiscalYearStart); + this.filters.setValue('toDate', accountingSettings.fiscalYearEnd); + + this.run(); + } + + getRowsForDataTable(data) { + return data.rows || []; + } + + getColumns(data) { + const columns = [ + { label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 340 }, + { label: 'Opening (Dr)', fieldtype: 'Currency', fieldname: 'openingDebit' }, + { label: 'Opening (Cr)', fieldtype: 'Currency', fieldname: 'openingCredit' }, + { label: 'Debit', fieldtype: 'Currency', fieldname: 'debit' }, + { label: 'Credit', fieldtype: 'Currency', fieldname: 'credit' }, + { label: 'Closing (Dr)', fieldtype: 'Currency', fieldname: 'closingDebit' }, + { label: 'Closing (Cr)', fieldtype: 'Currency', fieldname: 'closingCredit' } + ]; + + return columns; + } +} diff --git a/reports/index.js b/reports/index.js index 3ab47d78..b55722ba 100644 --- a/reports/index.js +++ b/reports/index.js @@ -9,6 +9,9 @@ const ProfitAndLossView = require('./ProfitAndLoss/ProfitAndLossView'); const BalanceSheet = require('./BalanceSheet/BalanceSheet'); const BalanceSheetView = require('./BalanceSheet/BalanceSheetView'); +const TrialBalance = require('./TrialBalance/TrialBalance'); +const TrialBalanceView = require('./TrialBalance/TrialBalanceView'); + const SalesRegister = require('./SalesRegister/SalesRegister'); const SalesRegisterView = require('./SalesRegister/SalesRegisterView'); @@ -32,6 +35,11 @@ function registerReportMethods() { handler: getReportData(BalanceSheet) }); + frappe.registerMethod({ + method: 'trial-balance', + handler: getReportData(TrialBalance) + }); + frappe.registerMethod({ method: 'sales-register', handler: getReportData(SalesRegister) @@ -66,6 +74,13 @@ function registerReportRoutes() { await frappe.views.BalanceSheet.show(params); }); + frappe.router.add('report/trial-balance', async (params) => { + if (!frappe.views.TrialBalance) { + frappe.views.TrialBalance = new TrialBalanceView(); + } + await frappe.views.TrialBalance.show(params); + }); + frappe.router.add('report/sales-register', async (params) => { if (!frappe.views.SalesRegister) { frappe.views.SalesRegister = new SalesRegisterView();