2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +00:00

Profit and Loss Statement first cut

This commit is contained in:
Faris Ansari 2018-04-19 20:01:35 +05:30
parent 1c1f72281e
commit 3da7b7169a
9 changed files with 223 additions and 29 deletions

View File

@ -1,5 +1,5 @@
const GeneralLedgerView = require('../reports/generalLedger/GeneralLedgerView');
const frappe = require('frappejs');
const { registerReportRoutes } = require('../reports');
module.exports = {
start() {
@ -8,12 +8,7 @@ module.exports = {
frappe.registerView('List', 'Customer', require('../models/doctype/Party/CustomerList.js'));
frappe.router.add('report/general-ledger', async (params) => {
if (!frappe.views.generalLedger) {
frappe.views.generalLedger = new GeneralLedgerView();
}
await frappe.views.generalLedger.show(params);
})
registerReportRoutes();
frappe.desk.menu.addItem('ToDo', '#list/ToDo');
frappe.desk.menu.addItem('Chart of Accounts', '#tree/Account');

View File

@ -31,7 +31,13 @@ module.exports = {
"fieldname": "account",
"label": "Account",
"fieldtype": "Link",
"target": "Account"
"target": "Account",
getFilters: (query, control) => {
return {
keywords: ["like", query],
isGroup: 0
}
}
},
{
"fieldname": "items",

View File

@ -2,26 +2,25 @@ const frappe = require('frappejs');
const { getData } = require('../financialStatements');
class ProfitAndLoss {
async run(params) {
const filters = {};
if (params.account) filters.account = params.account;
if (params.party) filters.party = params.party;
if (params.referenceType) filters.referenceType = params.referenceType;
if (params.referenceName) filters.referenceName = params.referenceName;
if (params.fromDate) filters.date = ['>=', params.fromDate];
if (params.toDate) filters.date = ['<=', params.toDate];
async run({ fromDate, toDate, periodicity }) {
let income = await getData({
rootType: 'Income',
balanceMustBe: 'Credit'
balanceMustBe: 'Credit',
fromDate,
toDate,
periodicity
});
let expense = await getData({
rootType: 'Expense',
balanceMustBe: 'Credit'
balanceMustBe: 'Debit',
fromDate,
toDate,
periodicity
});
return data;
return { income, expense };
}
}

View File

@ -0,0 +1,93 @@
const ReportPage = require('frappejs/client/desk/reportpage');
const frappe = require('frappejs');
const { unique } = require('frappejs/utils');
module.exports = class ProfitAndLossView extends ReportPage {
constructor() {
super({
title: frappe._('Profit and Loss'),
filterFields: [
{fieldtype: 'Date', label: 'From Date', required: 1},
{fieldtype: 'Date', label: 'To Date', required: 1},
{fieldtype: 'Select', options: ['Monthly', 'Quarterly', 'Half Yearly', 'Yearly'],
label: 'Periodicity', fieldname: 'periodicity'}
]
});
this.method = 'profit-and-loss';
this.datatableOptions = {
treeView: true
}
}
getRowsForDataTable(data) {
const { expense, income } = data;
const rows = [];
rows.push({
account: 'Income',
indent: 0
});
for (let account of Object.keys(income)) {
const row = {
account,
indent: 1
};
for (let periodKey of Object.keys(income[account] || {})) {
row[periodKey] = income[account][periodKey];
}
rows.push(row);
}
rows.push({
account: 'Expense',
indent: 0
});
for (let account of Object.keys(expense)) {
const row = {
account,
indent: 1
};
for (let periodKey of Object.keys(expense[account] || {})) {
row[periodKey] = expense[account][periodKey];
}
rows.push(row);
}
return rows;
}
getColumns(data) {
debugger
const columns = [
{ label: 'Account', fieldtype: 'Data' }
];
if (data) {
const { income, expense } = data;
let currencyColumns = [];
for (let account of Object.keys(income)) {
const periods = Object.keys(income[account] || {});
currencyColumns.push(...periods);
}
for (let account of Object.keys(expense)) {
const periods = Object.keys(expense[account] || {});
currencyColumns.push(...periods);
}
currencyColumns = unique(currencyColumns);
const columnDefs = currencyColumns.map(name => ({
label: name,
fieldname: name,
fieldtype: 'Currency'
}));
columns.push(...columnDefs);
}
return columns;
}
}

View File

@ -1,30 +1,80 @@
const frappe = require('frappejs');
const { DateTime } = require('luxon');
async function getData({
rootType,
balanceMustBe = 'Debit',
fromDate,
toDate,
periodicity = 'Monthly'
}) {
async function getData({ rootType, balanceMustBe }) {
const accounts = await getAccounts(rootType);
if (!accounts || accounts.length === 0) return [];
const ledgerEntries = await frappe.db.getAll({
doctype: 'AccountingLedgerEntry',
fields: ['account', 'debit', 'credit'],
fields: ['account', 'debit', 'credit', 'date'],
filters: {
account: ['in', accounts]
account: ['in', accounts],
date: ['>=', fromDate, '<=', toDate]
}
});
let data = {};
for (let entry of ledgerEntries) {
let periodKey = getPeriodKey(entry.date, periodicity);
if (!data[entry.account]) {
data[entry.account] = 0.0;
data[entry.account] = {};
}
data[entry.account] += entry.debit - entry.credit;
if (!data[entry.account][periodKey]) {
data[entry.account][periodKey] = 0.0;
}
const multiplier = balanceMustBe === 'Debit' ? 1 : -1;
data[entry.account][periodKey] += (entry.debit - entry.credit) * multiplier;
}
return data;
}
function getPeriodKey(date, periodicity) {
let key;
let dateObj = DateTime.fromISO(date);
let year = dateObj.year;
let quarter = dateObj.quarter;
let month = dateObj.month;
let getKey = {
'Monthly': () => `${dateObj.monthShort} ${year}`,
'Quarterly': () => {
return {
1: `Jan ${year} - Mar ${year}`,
2: `Apr ${year} - Jun ${year}`,
3: `Jun ${year} - Sep ${year}`,
4: `Oct ${year} - Dec ${year}`
}[quarter]
},
'Half Yearly': () => {
return {
1: `Apr ${year} - Sep ${year}`,
2: `Oct ${year} - Mar ${year}`
}[[2, 3].includes(quarter) ? 1 : 2]
},
'Yearly': () => {
if (month > 3) {
return `${year} - ${year + 1}`
}
return `${year - 1} - ${year}`
}
}[periodicity];
return getKey();
}
async function getAccounts(rootType) {
return (await frappe.db.getAll({
doctype: 'Account',

View File

@ -24,6 +24,8 @@ module.exports = class GeneralLedgerView extends ReportPage {
return [
{label: 'Date', fieldtype: 'Date'},
{label: 'Account', fieldtype: 'Link'},
{label: 'Reference Type', fieldtype: 'Data'},
{label: 'Reference Name', fieldtype: 'Data'},
{label: 'Party', fieldtype: 'Link'},
{label: 'Description', fieldtype: 'Data'},
{label: 'Debit', fieldtype: 'Currency'},

42
reports/index.js Normal file
View File

@ -0,0 +1,42 @@
const frappe = require('frappejs');
const GeneralLedger = require('./GeneralLedger/GeneralLedger');
const GeneralLedgerView = require('../reports/generalLedger/GeneralLedgerView');
const ProfitAndLoss = require('./ProfitAndLoss/ProfitAndLoss');
const ProfitAndLossView = require('./ProfitAndLoss/ProfitAndLossView');
// called on server side
function registerReportMethods() {
frappe.registerMethod({
method: 'general-ledger',
handler: args => GeneralLedger(args)
});
frappe.registerMethod({
method: 'profit-and-loss',
handler: args => ProfitAndLoss(args)
});
}
// called on client side
function registerReportRoutes() {
frappe.router.add('report/general-ledger', async (params) => {
if (!frappe.views.GeneralLedger) {
frappe.views.GeneralLedger = new GeneralLedgerView();
}
await frappe.views.GeneralLedger.show(params);
});
frappe.router.add('report/profit-and-loss', async (params) => {
if (!frappe.views.ProfitAndLoss) {
frappe.views.ProfitAndLoss = new ProfitAndLossView();
}
await frappe.views.ProfitAndLoss.show(params);
});
}
module.exports = {
registerReportMethods,
registerReportRoutes
}

View File

@ -4,8 +4,8 @@ global.rootRequire = function(name) {
const server = require('frappejs/server');
const frappe = require('frappejs');
const GeneralLedger = require('../reports/generalLedger/GeneralLedger');
const naming = require('frappejs/model/naming');
const { registerReportMethods } = require('../reports');
module.exports = {
async start() {
@ -35,9 +35,6 @@ module.exports = {
await naming.createNumberSeries('JV-', 'JournalEntrySettings');
await naming.createNumberSeries('QTN-', 'QuotationSettings');
frappe.registerMethod({
method: 'general-ledger',
handler: args => GeneralLedger(args)
});
registerReportMethods();
}
}

View File

@ -1260,6 +1260,10 @@ deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
deepmerge@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.0.tgz#511a54fff405fc346f0240bb270a3e9533a31102"
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@ -2053,12 +2057,14 @@ frappejs@../frappejs:
clusterize.js "^0.18.0"
codemirror "^5.35.0"
commander "^2.13.0"
deepmerge "^2.1.0"
eslint "^4.19.1"
express "^4.16.2"
flatpickr "^4.3.2"
frappe-datatable frappe/datatable
frappejs "../frappejs"
jquery "^3.3.1"
luxon "^1.0.0"
mkdirp "^0.5.1"
mocha "^4.1.0"
moment "^2.20.1"
@ -3151,6 +3157,10 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
luxon@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.0.0.tgz#ec1cba8cf53be14d2375c2f17e3468eb195c20bb"
macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"