mirror of
https://github.com/frappe/books.git
synced 2025-02-02 12:08:27 +00:00
Profit and Loss Statement first cut
This commit is contained in:
parent
1c1f72281e
commit
3da7b7169a
@ -1,5 +1,5 @@
|
|||||||
const GeneralLedgerView = require('../reports/generalLedger/GeneralLedgerView');
|
|
||||||
const frappe = require('frappejs');
|
const frappe = require('frappejs');
|
||||||
|
const { registerReportRoutes } = require('../reports');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
start() {
|
start() {
|
||||||
@ -8,12 +8,7 @@ module.exports = {
|
|||||||
|
|
||||||
frappe.registerView('List', 'Customer', require('../models/doctype/Party/CustomerList.js'));
|
frappe.registerView('List', 'Customer', require('../models/doctype/Party/CustomerList.js'));
|
||||||
|
|
||||||
frappe.router.add('report/general-ledger', async (params) => {
|
registerReportRoutes();
|
||||||
if (!frappe.views.generalLedger) {
|
|
||||||
frappe.views.generalLedger = new GeneralLedgerView();
|
|
||||||
}
|
|
||||||
await frappe.views.generalLedger.show(params);
|
|
||||||
})
|
|
||||||
|
|
||||||
frappe.desk.menu.addItem('ToDo', '#list/ToDo');
|
frappe.desk.menu.addItem('ToDo', '#list/ToDo');
|
||||||
frappe.desk.menu.addItem('Chart of Accounts', '#tree/Account');
|
frappe.desk.menu.addItem('Chart of Accounts', '#tree/Account');
|
||||||
|
@ -31,7 +31,13 @@ module.exports = {
|
|||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"label": "Account",
|
"label": "Account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "Account"
|
"target": "Account",
|
||||||
|
getFilters: (query, control) => {
|
||||||
|
return {
|
||||||
|
keywords: ["like", query],
|
||||||
|
isGroup: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "items",
|
"fieldname": "items",
|
||||||
|
@ -2,26 +2,25 @@ const frappe = require('frappejs');
|
|||||||
const { getData } = require('../financialStatements');
|
const { getData } = require('../financialStatements');
|
||||||
|
|
||||||
class ProfitAndLoss {
|
class ProfitAndLoss {
|
||||||
async run(params) {
|
async run({ fromDate, toDate, periodicity }) {
|
||||||
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];
|
|
||||||
|
|
||||||
let income = await getData({
|
let income = await getData({
|
||||||
rootType: 'Income',
|
rootType: 'Income',
|
||||||
balanceMustBe: 'Credit'
|
balanceMustBe: 'Credit',
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
periodicity
|
||||||
});
|
});
|
||||||
|
|
||||||
let expense = await getData({
|
let expense = await getData({
|
||||||
rootType: 'Expense',
|
rootType: 'Expense',
|
||||||
balanceMustBe: 'Credit'
|
balanceMustBe: 'Debit',
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
periodicity
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return { income, expense };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
93
reports/ProfitAndLoss/ProfitAndLossView.js
Normal file
93
reports/ProfitAndLoss/ProfitAndLossView.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,80 @@
|
|||||||
const frappe = require('frappejs');
|
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);
|
const accounts = await getAccounts(rootType);
|
||||||
if (!accounts || accounts.length === 0) return [];
|
if (!accounts || accounts.length === 0) return [];
|
||||||
|
|
||||||
const ledgerEntries = await frappe.db.getAll({
|
const ledgerEntries = await frappe.db.getAll({
|
||||||
doctype: 'AccountingLedgerEntry',
|
doctype: 'AccountingLedgerEntry',
|
||||||
fields: ['account', 'debit', 'credit'],
|
fields: ['account', 'debit', 'credit', 'date'],
|
||||||
filters: {
|
filters: {
|
||||||
account: ['in', accounts]
|
account: ['in', accounts],
|
||||||
|
date: ['>=', fromDate, '<=', toDate]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let data = {};
|
let data = {};
|
||||||
|
|
||||||
for (let entry of ledgerEntries) {
|
for (let entry of ledgerEntries) {
|
||||||
|
let periodKey = getPeriodKey(entry.date, periodicity);
|
||||||
|
|
||||||
if (!data[entry.account]) {
|
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;
|
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) {
|
async function getAccounts(rootType) {
|
||||||
return (await frappe.db.getAll({
|
return (await frappe.db.getAll({
|
||||||
doctype: 'Account',
|
doctype: 'Account',
|
||||||
|
@ -24,6 +24,8 @@ module.exports = class GeneralLedgerView extends ReportPage {
|
|||||||
return [
|
return [
|
||||||
{label: 'Date', fieldtype: 'Date'},
|
{label: 'Date', fieldtype: 'Date'},
|
||||||
{label: 'Account', fieldtype: 'Link'},
|
{label: 'Account', fieldtype: 'Link'},
|
||||||
|
{label: 'Reference Type', fieldtype: 'Data'},
|
||||||
|
{label: 'Reference Name', fieldtype: 'Data'},
|
||||||
{label: 'Party', fieldtype: 'Link'},
|
{label: 'Party', fieldtype: 'Link'},
|
||||||
{label: 'Description', fieldtype: 'Data'},
|
{label: 'Description', fieldtype: 'Data'},
|
||||||
{label: 'Debit', fieldtype: 'Currency'},
|
{label: 'Debit', fieldtype: 'Currency'},
|
||||||
|
42
reports/index.js
Normal file
42
reports/index.js
Normal 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
|
||||||
|
}
|
@ -4,8 +4,8 @@ global.rootRequire = function(name) {
|
|||||||
|
|
||||||
const server = require('frappejs/server');
|
const server = require('frappejs/server');
|
||||||
const frappe = require('frappejs');
|
const frappe = require('frappejs');
|
||||||
const GeneralLedger = require('../reports/generalLedger/GeneralLedger');
|
|
||||||
const naming = require('frappejs/model/naming');
|
const naming = require('frappejs/model/naming');
|
||||||
|
const { registerReportMethods } = require('../reports');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async start() {
|
async start() {
|
||||||
@ -35,9 +35,6 @@ module.exports = {
|
|||||||
await naming.createNumberSeries('JV-', 'JournalEntrySettings');
|
await naming.createNumberSeries('JV-', 'JournalEntrySettings');
|
||||||
await naming.createNumberSeries('QTN-', 'QuotationSettings');
|
await naming.createNumberSeries('QTN-', 'QuotationSettings');
|
||||||
|
|
||||||
frappe.registerMethod({
|
registerReportMethods();
|
||||||
method: 'general-ledger',
|
|
||||||
handler: args => GeneralLedger(args)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -1260,6 +1260,10 @@ deep-is@~0.1.3:
|
|||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
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:
|
define-property@^0.2.5:
|
||||||
version "0.2.5"
|
version "0.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
|
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"
|
clusterize.js "^0.18.0"
|
||||||
codemirror "^5.35.0"
|
codemirror "^5.35.0"
|
||||||
commander "^2.13.0"
|
commander "^2.13.0"
|
||||||
|
deepmerge "^2.1.0"
|
||||||
eslint "^4.19.1"
|
eslint "^4.19.1"
|
||||||
express "^4.16.2"
|
express "^4.16.2"
|
||||||
flatpickr "^4.3.2"
|
flatpickr "^4.3.2"
|
||||||
frappe-datatable frappe/datatable
|
frappe-datatable frappe/datatable
|
||||||
frappejs "../frappejs"
|
frappejs "../frappejs"
|
||||||
jquery "^3.3.1"
|
jquery "^3.3.1"
|
||||||
|
luxon "^1.0.0"
|
||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
mocha "^4.1.0"
|
mocha "^4.1.0"
|
||||||
moment "^2.20.1"
|
moment "^2.20.1"
|
||||||
@ -3151,6 +3157,10 @@ lru-cache@^4.0.1:
|
|||||||
pseudomap "^1.0.2"
|
pseudomap "^1.0.2"
|
||||||
yallist "^2.1.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:
|
macaddress@^0.2.8:
|
||||||
version "0.2.8"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user