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:
parent
1c1f72281e
commit
3da7b7169a
@ -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');
|
||||
|
@ -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",
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
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 { 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',
|
||||
|
@ -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
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 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();
|
||||
}
|
||||
}
|
||||
|
10
yarn.lock
10
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user