2
0
mirror of https://github.com/frappe/books.git synced 2025-01-11 02:36:14 +00:00

Merge pull request #36 from netchampfaris/profit-and-loss

Profit and loss
This commit is contained in:
Faris Ansari 2018-04-24 13:38:26 +05:30 committed by GitHub
commit 8adb52c57c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 8418 additions and 1313 deletions

View File

@ -6,10 +6,8 @@ module.exports = {
return { return {
route: ['report', 'general-ledger'], route: ['report', 'general-ledger'],
params: { params: {
filters: { referenceType: form.doc.doctype,
referenceType: form.doc.doctype, referenceName: form.doc.name
referenceName: form.doc.name
}
} }
}; };
} }

View File

@ -1,22 +1,14 @@
const GeneralLedgerView = require('../reports/generalLedger/GeneralLedgerView');
const frappe = require('frappejs'); const frappe = require('frappejs');
const { registerReportRoutes } = require('../reports');
module.exports = { module.exports = {
start() { start() {
// require modules // require modules
frappe.registerModels(require('../models'), 'client'); frappe.registerModels(require('../models'), 'client');
frappe.registerView('List', 'ToDo', require('frappejs/models/doctype/ToDo/ToDoList.js'));
frappe.registerView('Form', 'FilterSelector', require('frappejs/models/doctype/FilterSelector/FilterSelectorForm.js'));
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');
@ -28,6 +20,9 @@ module.exports = {
frappe.desk.menu.addItem('Address', "#list/Address"); frappe.desk.menu.addItem('Address', "#list/Address");
frappe.desk.menu.addItem('Contact', "#list/Contact"); frappe.desk.menu.addItem('Contact', "#list/Contact");
frappe.desk.menu.addItem('Settings', () => frappe.desk.showFormModal('SystemSettings')); frappe.desk.menu.addItem('Settings', () => frappe.desk.showFormModal('SystemSettings'));
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.router.default = '#tree/Account'; frappe.router.default = '#tree/Account';

View File

@ -1,10 +1,12 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
const path = require('path'); const path = require('path');
const electron = require('frappejs/client/electron'); const electron = require('frappejs/client/electron');
const { writeFile } = require('frappejs/server/utils');
const appClient = require('../client'); const appClient = require('../client');
const SetupWizard = require('../setup'); const SetupWizard = require('../setup');
const { getPDFForElectron } = require('frappejs/server/pdf'); const { getPDFForElectron } = require('frappejs/server/pdf');
const { getSettings, saveSettings } = require('./settings');
const { postStart } = require('../server');
const { slug } = require('frappejs/utils');
const fs = require('fs'); const fs = require('fs');
@ -13,79 +15,74 @@ require.extensions['.html'] = function (module, filename) {
}; };
(async () => { (async () => {
const configFilePath = path.join(require('os').homedir(), '.config', 'frappe-accounting', 'settings.json'); let electronSettings = getSettings();
let firstRun = false, setupWizardValues = null;
let settings, dbPath; if (!electronSettings.dbPath) {
try { const values = await runSetupWizard();
settings = require(configFilePath); const dbPath = path.join(values.file[0].path, slug(values.companyName) + '.db');
} catch(e) { const config = {
settings = {} directory: path.dirname(dbPath),
dbPath: dbPath
};
await saveSettings(config);
firstRun = true;
electronSettings = config;
setupWizardValues = values;
} }
frappe.electronConfig = settings; await electron.start({
dbPath: electronSettings.dbPath,
models: require('../models')
});
await postStart();
if (firstRun) {
await saveSetupWizardValues(setupWizardValues);
await bootstrapChartOfAccounts();
}
frappe.getPDF = getPDFForElectron; frappe.getPDF = getPDFForElectron;
frappe.electronSettings = electronSettings;
if (settings.dbPath) { appClient.start();
dbPath = settings.dbPath;
electron.start({
dbPath,
models: require('../models')
}).then(() => {
frappe.syncDoc(require('../fixtures/invoicePrint'));
appClient.start();
});
} else {
const setup = new SetupWizard();
window.setup = setup;
const values = await setup.start();
const {
companyName,
file,
country,
name,
email,
abbreviation,
bankName
} = values;
dbPath = path.join(file[0].path, companyName + '.db');
electron.start({
dbPath,
models: require('../models')
}).then(async () => {
const config = {
directory: path.dirname(dbPath),
dbPath: dbPath
};
await writeFile(configFilePath, JSON.stringify(config));
frappe.electronConfig = config;
const doc = await frappe.getDoc('AccountingSettings');
await doc.set('companyName', companyName);
await doc.set('country', country);
await doc.set('fullname', name);
await doc.set('email', email);
await doc.set('bankName', bankName);
await doc.update();
// bootstrap Chart of Accounts
const importCOA = require('../models/doctype/account/importCOA');
const chart = require('../fixtures/standardCOA');
await importCOA(chart);
frappe.syncDoc(require('../fixtures/invoicePrint'));
appClient.start();
})
}
})(); })();
async function runSetupWizard() {
const setup = new SetupWizard();
const values = await setup.start();
return values;
}
async function saveSetupWizardValues(values) {
const {
companyName,
country,
name,
email,
abbreviation,
bankName,
fiscalYearStart,
fiscalYearEnd
} = values;
const doc = await frappe.getDoc('AccountingSettings');
await doc.set('companyName', companyName);
await doc.set('country', country);
await doc.set('fullname', name);
await doc.set('email', email);
await doc.set('bankName', bankName);
await doc.set('fiscalYearStart', fiscalYearStart);
await doc.set('fiscalYearEnd', fiscalYearEnd);
await doc.update();
}
async function bootstrapChartOfAccounts() {
const importCOA = require('../models/doctype/account/importCOA');
const chart = require('../fixtures/standardCOA');
await importCOA(chart);
}

View File

@ -7,6 +7,6 @@
<link href="../www/dist/css/style.css" rel="stylesheet"> <link href="../www/dist/css/style.css" rel="stylesheet">
</head> </head>
<body> <body>
<script src="./client.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

5
electron/index.js Normal file
View File

@ -0,0 +1,5 @@
global.rootRequire = function(name) {
return require(process.cwd() + '/' + name);
}
require('./client');

26
electron/settings.js Normal file
View File

@ -0,0 +1,26 @@
const os = require('os');
const path = require('path');
const { writeFile } = require('frappejs/server/utils');
const homedir = os.homedir();
const configFilePath = path.join(homedir, '.config', 'frappe-accounting', 'settings.json');
function getSettings() {
let settings;
try {
settings = require(configFilePath);
} catch (e) {
settings = {}
}
return settings;
}
async function saveSettings(settings) {
await writeFile(configFilePath, JSON.stringify(settings));
}
module.exports = {
getSettings,
saveSettings
}

View File

@ -17,7 +17,7 @@ function createWindow () {
// and load the index.html of the app. // and load the index.html of the app.
mainWindow.loadURL(url.format({ mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'electron/index.html'), pathname: path.join(__dirname, 'electron', 'index.html'),
protocol: 'file:', protocol: 'file:',
slashes: true slashes: true
})) }))

View File

@ -3,6 +3,7 @@ module.exports = {
"doctype": "DocType", "doctype": "DocType",
"documentClass": require("./AccountDocument.js"), "documentClass": require("./AccountDocument.js"),
"isSingle": 0, "isSingle": 0,
"isTree": 1,
"keywordFields": [ "keywordFields": [
"name", "name",
"rootType", "rootType",

View File

@ -49,7 +49,7 @@ module.exports = {
fieldname: "againstAccount", fieldname: "againstAccount",
label: "Against Account", label: "Against Account",
fieldtype: "Text", fieldtype: "Text",
required: 1 required: 0
}, },
{ {
fieldname: "referenceType", fieldname: "referenceType",

View File

@ -50,7 +50,21 @@ module.exports = {
"label": "Bank Name", "label": "Bank Name",
"fieldtype": "Data", "fieldtype": "Data",
"required": 1 "required": 1
} },
{
"fieldname": "fiscalYearStart",
"label": "Fiscal Year Start Date",
"fieldtype": "Date",
"required": 1
},
{
"fieldname": "fiscalYearEnd",
"label": "Fiscal Year End Date",
"fieldtype": "Date",
"required": 1
},
] ]
} }

View File

@ -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",

View File

@ -1,6 +1,6 @@
const Invoice = require('./InvoiceDocument'); const Invoice = require('./InvoiceDocument');
const frappe = require('frappejs'); const frappe = require('frappejs');
const LedgerPosting = require.main.require('./accounting/ledgerPosting'); const LedgerPosting = rootRequire('accounting/ledgerPosting');
module.exports = class InvoiceServer extends Invoice { module.exports = class InvoiceServer extends Invoice {
getPosting() { getPosting() {

View File

@ -70,7 +70,7 @@ module.exports = {
// section 2 // section 2
{ fields: ["accounts"]}, { fields: ["accounts"]},
// section 3 // section 3
{ {
columns: [ columns: [
{ fields: [ "referenceNumber"] }, { fields: [ "referenceNumber"] },
{ fields: [ "referenceDate"] } { fields: [ "referenceDate"] }

View File

@ -1,10 +1,10 @@
const BaseDocument = require('frappejs/model/document'); const JournalEntry = require('./JournalEntry');
const frappe = require('frappejs'); const frappe = require('frappejs');
const LedgerPosting = require.main.require('./accounting/ledgerPosting'); const LedgerPosting = rootRequire('accounting/ledgerPosting');
module.exports = class PaymentServer extends BaseDocument { module.exports = class JournalEntryServer extends BaseDocument {
/** /**
getPosting() { getPosting() {
let entries = new LedgerPosting({reference: this, party: this.party}); let entries = new LedgerPosting({reference: this, party: this.party});
entries.debit(this.paymentAccount, this.amount); entries.debit(this.paymentAccount, this.amount);
@ -24,6 +24,6 @@ module.exports = class PaymentServer extends BaseDocument {
async afterRevert() { async afterRevert() {
await this.getPosting().postReverse(); await this.getPosting().postReverse();
} }
**/ **/
} }

View File

@ -1,6 +1,6 @@
const BaseDocument = require('frappejs/model/document'); const BaseDocument = require('frappejs/model/document');
const frappe = require('frappejs'); const frappe = require('frappejs');
const LedgerPosting = require.main.require('./accounting/ledgerPosting'); const LedgerPosting = rootRequire('accounting/ledgerPosting');
module.exports = class PaymentServer extends BaseDocument { module.exports = class PaymentServer extends BaseDocument {
getPosting() { getPosting() {

View File

@ -1,8 +1,10 @@
const deepmerge = require('deepmerge');
const Invoice = require('../Invoice/Invoice'); const Invoice = require('../Invoice/Invoice');
const Quotation = Invoice;
Quotation.name = "Quotation"; const Quotation = deepmerge(Invoice, {
Quotation.label = "Quotation"; name: "Quotation",
Quotation.settings = "QuotationSettings"; label: "Quotation",
settings: "QuotationSettings"
});
module.exports = Quotation; module.exports = Quotation;

View File

@ -1,8 +1,11 @@
const deepmerge = require('deepmerge');
const InvoiceSettings = require('../InvoiceSettings/InvoiceSettings'); const InvoiceSettings = require('../InvoiceSettings/InvoiceSettings');
const QuotationSettings = InvoiceSettings; const QuotationSettings = deepmerge(InvoiceSettings, {
QuotationSettings.name = "QuotationSettings"; "name": "QuotationSettings",
QuotationSettings.label = "Quotation Settings"; "label": "Quotation Settings",
QuotationSettings.fields.find((field)=>{ "fields": {
if (field.fieldname == "numberSeries") field.default = "QTN"; "default": "INV"
}); }
})
module.exports = QuotationSettings; module.exports = QuotationSettings;

View File

@ -16,11 +16,12 @@
"test": "mocha tests", "test": "mocha tests",
"start": "nodemon server.js", "start": "nodemon server.js",
"watch": "rollup -c --watch", "watch": "rollup -c --watch",
"electron": "electron main.js", "electron": "EIO_WS_ENGINE=ws electron main.js",
"electron-pack": "electron-packager . --overwrite", "electron-pack": "electron-packager . --overwrite",
"postinstall": "electron-builder install-app-deps" "postinstall": "electron-builder install-app-deps"
}, },
"dependencies": { "dependencies": {
"frappe-datatable": "../datatable",
"frappejs": "../frappejs" "frappejs": "../frappejs"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,53 @@
const frappe = require('frappejs');
const { unique } = require('frappejs/utils');
const { getData } = require('../FinancialStatements/FinancialStatements');
class BalanceSheet {
async run({ fromDate, toDate, periodicity }) {
let asset = await getData({
rootType: 'Asset',
balanceMustBe: 'Debit',
fromDate,
toDate,
periodicity,
accumulateValues: true
});
let liability = await getData({
rootType: 'Liability',
balanceMustBe: 'Credit',
fromDate,
toDate,
periodicity,
accumulateValues: true
});
let equity = await getData({
rootType: 'Equity',
balanceMustBe: 'Credit',
fromDate,
toDate,
periodicity,
accumulateValues: true
});
const rows = [
...asset.accounts, asset.totalRow, [],
...liability.accounts, liability.totalRow, [],
...equity.accounts, equity.totalRow, []
];
const columns = unique([
...asset.periodList,
...liability.periodList,
...equity.periodList
]);
return { rows, columns };
}
}
module.exports = function execute(params) {
return new BalanceSheet().run(params);
}

View File

@ -0,0 +1,16 @@
const frappe = require('frappejs');
const FinancialStatementsView = require('../FinancialStatements/FinancialStatementsView');
module.exports = class BalanceSheetView extends FinancialStatementsView {
constructor() {
super({
title: frappe._('Balance Sheet'),
method: 'balance-sheet',
filterFields: [
{fieldtype: 'Date', label: 'To Date', required: 1},
{fieldtype: 'Select', options: ['Monthly', 'Quarterly', 'Half Yearly', 'Yearly'],
label: 'Periodicity', fieldname: 'periodicity', default: 'Monthly'}
]
});
}
}

View File

@ -0,0 +1,214 @@
const frappe = require('frappejs');
const { DateTime } = require('luxon');
const { unique } = require('frappejs/utils');
async function getData({
rootType,
balanceMustBe = 'Debit',
fromDate,
toDate,
periodicity = 'Monthly',
accumulateValues = false
}) {
let accounts = await getAccounts(rootType);
let fiscalYear = await getFiscalYear();
let ledgerEntries = await getLedgerEntries(fromDate, toDate, accounts);
let periodList = getPeriodList(fromDate, toDate, periodicity, fiscalYear);
for (let account of accounts) {
const entries = ledgerEntries.filter(entry => entry.account === account.name);
for (let entry of entries) {
let periodKey = getPeriodKey(entry.date, periodicity);
if (!account[periodKey]) {
account[periodKey] = 0.0;
}
const multiplier = balanceMustBe === 'Debit' ? 1 : -1;
const value = (entry.debit - entry.credit) * multiplier
account[periodKey] += value;
}
}
if (accumulateValues) {
periodList.forEach((periodKey, i) => {
if (i === 0) return;
const previousPeriodKey = periodList[i - 1];
for (let account of accounts) {
if (!account[periodKey]) {
account[periodKey] = 0.0;
}
account[periodKey] += account[previousPeriodKey] || 0.0;
}
});
}
// calculate totalRow
let totalRow = {
account: `Total ${rootType} (${balanceMustBe})`
};
periodList.forEach((periodKey) => {
if (!totalRow[periodKey]) {
totalRow[periodKey] = 0.0;
}
for (let account of accounts) {
totalRow[periodKey] += account[periodKey] || 0.0;
}
});
return { accounts, totalRow, periodList };
}
function getPeriodList(fromDate, toDate, periodicity, fiscalYear) {
if (!fromDate) {
fromDate = fiscalYear.start;
}
let monthsToAdd = {
'Monthly': 1,
'Quarterly': 3,
'Half Yearly': 6,
'Yearly': 12
}[periodicity];
let startDate = DateTime.fromISO(fromDate).startOf('month');
let endDate = DateTime.fromISO(toDate).endOf('month');
let curDate = startDate;
let out = [];
while (curDate <= endDate) {
out.push(getPeriodKey(curDate, periodicity));
curDate = curDate.plus({ months: monthsToAdd });
}
return out;
}
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();
}
function setIndentLevel(accounts, parentAccount, level) {
if (!parentAccount) {
// root
parentAccount = null;
level = 0;
}
accounts.forEach(account => {
if (account.parentAccount === parentAccount && account.indent === undefined) {
account.indent = level;
setIndentLevel(accounts, account.name, level + 1);
}
});
return accounts;
}
function sortAccounts(accounts) {
let out = [];
let pushed = {};
pushToOut(null);
function pushToOut(parentAccount) {
accounts.forEach(account => {
if (account.parentAccount === parentAccount && !pushed[account.name]) {
out.push(account);
pushed[account.name] = 1;
pushToOut(account.name);
}
})
}
return out;
}
async function getLedgerEntries(fromDate, toDate, accounts) {
const dateFilter = () => {
const before = ['<=', toDate];
const after = ['>=', fromDate];
if (fromDate) {
return [...after, ...before];
}
return before;
}
const ledgerEntries = await frappe.db.getAll({
doctype: 'AccountingLedgerEntry',
fields: ['account', 'debit', 'credit', 'date'],
filters: {
account: ['in', accounts.map(d => d.name)],
date: dateFilter()
}
});
return ledgerEntries;
}
async function getAccounts(rootType) {
let accounts = await frappe.db.getAll({
doctype: 'Account',
fields: ['name', 'parentAccount'],
filters: {
rootType
}
});
accounts = setIndentLevel(accounts);
accounts = sortAccounts(accounts);
accounts.forEach(account => {
account.account = account.name;
});
return accounts;
}
async function getFiscalYear() {
let { fiscalYearStart, fiscalYearEnd } = await frappe.getSingle('AccountingSettings');
return {
start: fiscalYearStart,
end: fiscalYearEnd
};
}
module.exports = {
getData
}

View File

@ -0,0 +1,41 @@
const ReportPage = require('frappejs/client/desk/reportpage');
const frappe = require('frappejs');
const { unique } = require('frappejs/utils');
module.exports = class FinancialStatementsView extends ReportPage {
constructor(opts) {
super({
title: opts.title,
filterFields: opts.filterFields
});
this.method = opts.method;
this.datatableOptions = {
treeView: true,
layout: 'fixed'
}
}
getRowsForDataTable(data) {
return data.rows || [];
}
getColumns(data) {
const columns = [
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 340 }
];
if (data && data.columns) {
const currencyColumns = data.columns;
const columnDefs = currencyColumns.map(name => ({
label: name,
fieldname: name,
fieldtype: 'Currency'
}));
columns.push(...columnDefs);
}
return columns;
}
}

View File

@ -0,0 +1,47 @@
const frappe = require('frappejs');
const { unique } = require('frappejs/utils');
const { getData } = require('../FinancialStatements/FinancialStatements');
class ProfitAndLoss {
async run({ fromDate, toDate, periodicity }) {
let income = await getData({
rootType: 'Income',
balanceMustBe: 'Credit',
fromDate,
toDate,
periodicity
});
let expense = await getData({
rootType: 'Expense',
balanceMustBe: 'Debit',
fromDate,
toDate,
periodicity
});
const rows = [
...income.accounts, income.totalRow, [],
...expense.accounts, expense.totalRow, []
];
const columns = unique([...income.periodList, ...expense.periodList])
let profitRow = {
account: 'Total Profit'
}
for (let column of columns) {
profitRow[column] = (income.totalRow[column] || 0.0) - (expense.totalRow[column] || 0.0);
}
rows.push(profitRow);
return { rows, columns };
}
}
module.exports = function execute(params) {
return new ProfitAndLoss().run(params);
}

View File

@ -0,0 +1,17 @@
const frappe = require('frappejs');
const FinancialStatementsView = require('../FinancialStatements/FinancialStatementsView');
module.exports = class ProfitAndLossView extends FinancialStatementsView {
constructor() {
super({
title: frappe._('Profit and Loss'),
method: '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', default: 'Monthly'}
]
});
}
}

View File

@ -1,9 +1,5 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
module.exports = function execute(params) {
return new GeneralLedger().run(params);
}
class GeneralLedger { class GeneralLedger {
async run(params) { async run(params) {
const filters = {}; const filters = {};
@ -22,4 +18,8 @@ class GeneralLedger {
return data; return data;
} }
} }
module.exports = function execute(params) {
return new GeneralLedger().run(params);
}

View File

@ -24,11 +24,13 @@ 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: 'Party', fieldtype: 'Link'},
{label: 'Description', fieldtype: 'Data'},
{label: 'Debit', fieldtype: 'Currency'}, {label: 'Debit', fieldtype: 'Currency'},
{label: 'Credit', fieldtype: 'Currency'}, {label: 'Credit', fieldtype: 'Currency'},
{label: 'Balance', fieldtype: 'Currency'} {label: 'Balance', fieldtype: 'Currency'},
{label: 'Reference Type', fieldtype: 'Data'},
{label: 'Reference Name', fieldtype: 'Data'},
{label: 'Party', fieldtype: 'Link'},
{label: 'Description', fieldtype: 'Data'},
] ]
} }
} }

57
reports/index.js Normal file
View File

@ -0,0 +1,57 @@
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');
const BalanceSheet = require('./BalanceSheet/BalanceSheet');
const BalanceSheetView = require('./BalanceSheet/BalanceSheetView');
// 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)
});
frappe.registerMethod({
method: 'balance-sheet',
handler: args => BalanceSheet(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);
});
frappe.router.add('report/balance-sheet', async (params) => {
if (!frappe.views.BalanceSheet) {
frappe.views.BalanceSheet = new BalanceSheetView();
}
await frappe.views.BalanceSheet.show(params);
});
}
module.exports = {
registerReportMethods,
registerReportRoutes
}

View File

@ -1,21 +1,29 @@
global.rootRequire = function(name) {
return require(process.cwd() + '/' + 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() {
await server.start({ await server.start({
backend: 'sqlite', backend: 'sqlite',
connectionParams: {dbPath: 'test.db'}, connectionParams: { dbPath: 'test.db' },
staticPath: './www', staticPath: './www',
models: require('../models') models: require('../models')
}) })
await this.postStart();
},
async postStart() {
// set server-side modules // set server-side modules
frappe.models.Invoice.documentClass = require('../models/doctype/Invoice/InvoiceServer.js'); frappe.models.Invoice.documentClass = require('../models/doctype/Invoice/InvoiceServer.js');
frappe.models.Payment.documentClass = require('../models/doctype/Payment/PaymentServer.js'); frappe.models.Payment.documentClass = require('../models/doctype/Payment/PaymentServer.js');
frappe.models.JournalEntry.documentClass = require('../models/doctype/JournalEntry/JournalEntryServer.js'); // frappe.models.JournalEntry.documentClass = require('../models/doctype/JournalEntry/JournalEntryServer.js');
frappe.metaCache = {}; frappe.metaCache = {};
@ -27,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)
});
} }
} }

View File

@ -46,7 +46,21 @@ module.exports = {
"label": "Bank Name", "label": "Bank Name",
"fieldtype": "Data", "fieldtype": "Data",
"required": 1 "required": 1
} },
{
"fieldname": "fiscalYearStart",
"label": "Fiscal Year Start Date",
"fieldtype": "Date",
"required": 1
},
{
"fieldname": "fiscalYearEnd",
"label": "Fiscal Year End Date",
"fieldtype": "Date",
"required": 1
},
], ],
layout: [ layout: [
@ -67,7 +81,7 @@ module.exports = {
{ {
title: 'Add your Company', title: 'Add your Company',
fields: ['companyName', 'bankName'] fields: ['companyName', 'bankName', 'fiscalYearStart', 'fiscalYearEnd']
} }
] ]
} }

317
www/dist/css/style.css vendored
View File

@ -7130,40 +7130,160 @@ div.CodeMirror-dragcursors {
/* Help users use markselection to safely style text background */ /* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { span.CodeMirror-selectedtext {
background: none; } background: none; }
/* This file is processed by postcss */ .datatable *, .datatable *::after, .datatable *::before {
/* variables */
.data-table {
/* styling */
position: relative;
overflow: auto; }
/* resets */
.data-table *, .data-table *::after, .data-table *::before {
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; } box-sizing: border-box; }
.data-table button, .data-table input { .datatable {
position: relative;
overflow: auto; }
.dt-header {
border-collapse: collapse;
border-bottom: 1px solid #d1d8dd;
position: absolute;
top: 0;
left: 0;
background-color: #fff; }
.dt-body {
border-collapse: collapse; }
.dt-scrollable {
max-height: 40vw;
overflow: auto;
border-bottom: 1px solid #d1d8dd; }
.dt-scrollable--highlight-all {
background-color: #fffce7; }
.dt-scrollable__no-data {
text-align: center;
padding: 16px;
padding: 1rem;
border-left: 1px solid #d1d8dd;
border-right: 1px solid #d1d8dd; }
.dt-row--highlight {
background-color: #fffce7; }
.dt-row--unhighlight {
background-color: #fff; }
.dt-row--hide {
display: none; }
.dt-cell {
border: 1px solid #d1d8dd;
position: relative;
outline: none;
padding: 0; }
.dt-cell__content {
padding: 8px;
padding: 0.5rem;
border: 2px solid transparent;
height: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden; }
.dt-cell__edit {
display: none;
padding: 8px;
padding: 0.5rem;
background-color: #fff;
border: 2px solid #ffa00a;
z-index: 1;
height: 100%; }
.dt-cell__resize-handle {
opacity: 0;
position: absolute;
right: -3px;
top: 0;
width: 5px;
height: 100%;
cursor: col-resize;
z-index: 1; }
.dt-cell--editing .dt-cell__content {
display: none; }
.dt-cell--editing .dt-cell__edit {
display: block; }
.dt-cell--focus .dt-cell__content {
border-color: #5292f7; }
.dt-cell--highlight {
background-color: #f5f7fa; }
.dt-cell--dragging {
background-color: #f5f7fa; }
.dt-cell--header .dt-cell__content {
padding-right: 16px;
padding-right: 1rem;
font-weight: bold; }
.dt-cell--header:hover .dt-dropdown__toggle {
opacity: 1; }
.dt-cell--tree-close .dt-tree-node__toggle:before {
content: '►'; }
.dt-dropdown {
position: absolute;
right: 10px;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
vertical-align: top;
text-align: left;
font-weight: normal;
cursor: pointer; }
.dt-dropdown__toggle {
opacity: 0; }
.dt-dropdown__list {
display: none;
position: absolute;
min-width: 128px;
min-width: 8rem;
top: 100%;
right: 0;
z-index: 1;
background-color: #fff;
border-radius: 3px;
padding: 8px 0;
padding: 0.5rem 0;
-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); }
.dt-dropdown__list-item {
padding: 8px 16px;
padding: 0.5rem 1rem; }
.dt-dropdown__list-item:hover {
background-color: #f5f7fa; }
.dt-dropdown--active .dt-dropdown__list {
display: block; }
.dt-tree-node {
display: inline-block;
position: relative; }
.dt-tree-node__toggle {
display: inline-block;
position: absolute;
font-size: 10px;
padding: 0 4px;
cursor: pointer; }
.dt-tree-node__toggle:before {
content: '▼'; }
.dt-toast {
position: absolute;
bottom: 16px;
bottom: 1rem;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%); }
.dt-toast__message {
display: inline-block;
background-color: rgba(0, 0, 0, 0.8);
color: #dfe2e5;
border-radius: 3px;
padding: 8px 16px;
padding: 0.5rem 1rem; }
.dt-input {
outline: none;
width: 100%;
border: none;
overflow: visible; overflow: visible;
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
line-height: inherit; line-height: inherit;
margin: 0; margin: 0;
padding: 0; } padding: 0; }
.data-table .input-style { .dt-freeze {
outline: none;
width: 100%;
border: none; }
.data-table *, .data-table *:focus {
outline: none;
border-radius: 0px;
-webkit-box-shadow: none;
box-shadow: none; }
.data-table table {
border-collapse: collapse; }
.data-table table td {
padding: 0;
border: 1px solid #d1d8dd; }
.data-table thead td {
border-bottom-width: 1px; }
.data-table .freeze-container {
display: -webkit-box; display: -webkit-box;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
@ -7180,150 +7300,15 @@ span.CodeMirror-selectedtext {
background-color: #f5f7fa; background-color: #f5f7fa;
opacity: 0.5; opacity: 0.5;
font-size: 2em; } font-size: 2em; }
.data-table .freeze-container span { .dt-freeze__message {
position: absolute; position: absolute;
top: 50%; top: 50%;
-webkit-transform: translateY(-50%); -webkit-transform: translateY(-50%);
transform: translateY(-50%); } transform: translateY(-50%); }
.data-table .hide { .dt-paste-target {
display: none; } position: fixed;
.data-table .toast-message { left: -999em; }
position: absolute; body.dt-resize {
bottom: 16px;
bottom: 1rem;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%); }
.data-table .toast-message span {
display: inline-block;
background-color: rgba(0, 0, 0, 0.8);
color: #dfe2e5;
border-radius: 3px;
padding: 8px 16px;
padding: 0.5rem 1rem; }
.body-scrollable {
max-height: 500px;
overflow: auto;
border-bottom: 1px solid #d1d8dd; }
.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
background-color: #f5f7fa; }
.body-scrollable .no-data td {
text-align: center;
padding: 8px;
padding: 0.5rem; }
.data-table-header {
position: absolute;
top: 0;
left: 0;
background-color: white;
font-weight: bold; }
.data-table-header .content span:not(.column-resizer) {
cursor: pointer; }
.data-table-header .column-resizer {
display: none;
position: absolute;
right: 0;
top: 0;
width: 4px;
width: 0.25rem;
height: 100%;
background-color: #5292f7;
cursor: col-resize; }
.data-table-header .data-table-dropdown {
position: absolute;
right: 10px;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
vertical-align: top;
text-align: left; }
.data-table-header .data-table-dropdown.is-active .data-table-dropdown-list {
display: block; }
.data-table-header .data-table-dropdown.is-active .data-table-dropdown-toggle {
display: block; }
.data-table-header .data-table-dropdown-toggle {
display: none;
background-color: transparent;
border: none; }
.data-table-header .data-table-dropdown-list {
display: none;
font-weight: normal;
position: absolute;
min-width: 128px;
min-width: 8rem;
top: 100%;
right: 0;
z-index: 1;
background-color: white;
border-radius: 3px;
-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
padding-bottom: 8px;
padding-bottom: 0.5rem;
padding-top: 8px;
padding-top: 0.5rem; }
.data-table-header .data-table-dropdown-list > div {
padding: 8px 16px;
padding: 0.5rem 1rem; }
.data-table-header .data-table-dropdown-list > div:hover {
background-color: #f5f7fa; }
.data-table-header .data-table-cell.remove-column {
background-color: #FD8B8B;
-webkit-transition: 300ms background-color ease-in-out;
transition: 300ms background-color ease-in-out; }
.data-table-header .data-table-cell.sortable-chosen {
background-color: #f5f7fa; }
.data-table-cell {
position: relative; }
.data-table-cell .content {
padding: 8px;
padding: 0.5rem;
border: 2px solid transparent; }
.data-table-cell .content.ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden; }
.data-table-cell .edit-cell {
display: none;
padding: 8px;
padding: 0.5rem;
background: #fff;
z-index: 1;
height: 100%; }
.data-table-cell.selected .content {
border: 2px solid #5292f7; }
.data-table-cell.editing .content {
display: none; }
.data-table-cell.editing .edit-cell {
border: 2px solid #5292f7;
display: block; }
.data-table-cell.highlight {
background-color: #f5f7fa; }
.data-table-cell:hover .column-resizer {
display: inline-block; }
.data-table-cell:hover .data-table-dropdown-toggle {
display: block; }
.data-table-cell .tree-node {
display: inline-block;
position: relative; }
.data-table-cell .toggle {
display: inline-block;
position: absolute;
padding: 0 4px;
cursor: pointer; }
.data-table-cell .toggle:before {
content: '▼'; }
.data-table-cell.tree-close .toggle:before {
content: '►'; }
.data-table-row.row-highlight {
background-color: #f5f7fa; }
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
body.data-table-resize {
cursor: col-resize; } cursor: col-resize; }
.indicator, .indicator-right { .indicator, .indicator-right {
background: none; background: none;

8635
www/dist/js/bundle.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -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"
@ -2035,9 +2039,8 @@ fragment-cache@^0.2.1:
dependencies: dependencies:
map-cache "^0.2.2" map-cache "^0.2.2"
frappe-datatable@frappe/datatable: frappe-datatable@../datatable:
version "0.0.3" version "0.0.5"
resolved "https://codeload.github.com/frappe/datatable/tar.gz/e5af37fb07ddaaa43cfb42dd84ac4f98d93816bc"
dependencies: dependencies:
clusterize.js "^0.18.0" clusterize.js "^0.18.0"
lodash "^4.17.5" lodash "^4.17.5"
@ -2053,12 +2056,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 "../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 +3156,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"