Cutomisable Cash Flow Reports (#12969)

* add child doctype - Cash Flow Mapping Account

* adds new doctype - Cash Flow Mapping

* adds new doctype - Cash Flow Mapper

* adds new doctype Cash Flow Mapping Template

* adds new doctype Cash Flow Mapping Template

* adds adjustments to Cash Flow Mapper:
- remove fields from Cash Flow Mapping Template Details
- update in Cash FLow Mapper

* get cash_flow_accouts from Cash Flow Mapping

* change `tmp` to `mappers` and make sure `mappers` is sorted by its `position` field

* changes description from 'Net Profit/Loss' to 'Profit for the year'

* set `net_profit_loss` `parent_account` properly

* modify `get_account_type_based_data`:
- changed signature such that `account_type` parameter is now `account_name`
- where clause in query is now based on `name`

* remove zero rows

* de-duplicates row, summing similar accounts in the process

* makes gl sum calculation use `parent_account` as a condition

* add the `section_leader` immediately after adding net profit, sorts `accounts` by `is_working_capital` field

* adds `is_working_capital` to "account_types" so that we can use this to determine when to add "Changes in working capital" for operating activities

* add "Movement in working capital" subheader

* refactor code for readability

* adds new fields to `Cash Flow Mapping`:
- `is_interest_paid` to allow me recognise accounts for 'Interest Paid'
- `is_income_tax_paid` to allow me recognise accounts for 'Income Taxes Paid'

* allow `Cash Flow Mapping` to be renamable

* adds new field - `section_subtotal` useful for only Operating Activities

* changes `Cash Flow Mapping` doctype fields:
- remove `is_income_tax`_field
- add `is_income_tax_liability` field to identify tax payable accounts
- add `is_income_tax_expense` field to identify tax expense accounts in P or L

* calculates and shows tax paid adjustment in cash flow statement

* renames `is_interest_paid` to `is_finance_cost`

* - adds finance costs calculation
- correctly sets opening balance dates

* prevents users from selecting extra options in Cash Flow Mapping

* adds validation to prevent selecting multiple options

* adds new fields to Cash Flow Mapping

* calculate non cash p or l items (2nd pass)

* separates default cash flow generation from custom

* adds new setting to Accounts Settings:
- allow user elect to use customised cash flow report

* clean up

* removes mandatory constraint from accounts field

* allow rename, disallow create and delete

* adds patch to add default Cash Flow Mappers

* refactors custom_cashflow

* add article to explain configuration

* refactor

* further refactor

* final clean up (hopefully)

* clean up for codacy

* more codacy fixes

* more codacy fixes

* fix broken patch

* rename article to .md

* create default mappers after install

* PEP 8

* create the tables in `after_install` call
This commit is contained in:
tundebabzy 2018-02-22 06:38:36 +01:00 committed by Nabin Hait
parent 27d558c9ef
commit cad22dbe39
57 changed files with 1931 additions and 7 deletions

View File

@ -514,6 +514,68 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "report_settings_sb",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Report Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"description": "Only select if you have setup Cash Flow Mapper documents",
"fieldname": "use_custom_cash_flow",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Use Custom Cash Flow Format",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,

View File

@ -0,0 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow Mapper', {
});

View File

@ -0,0 +1,275 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:section_name",
"beta": 0,
"creation": "2018-02-08 10:00:14.066519",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Section Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_header",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Header",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "e.g Adjustments for:",
"fieldname": "section_leader",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Leader",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_subtotal",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Subtotal",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_footer",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Footer",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "accounts",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Accounts",
"length": 0,
"no_copy": 0,
"options": "Cash Flow Mapping Template Details",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "position",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Position",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-15 18:28:55.034933",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cash Flow Mapper",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "name",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class CashFlowMapper(Document):
pass

View File

@ -0,0 +1,25 @@
DEFAULT_MAPPERS = [
{
'doctype': 'Cash Flow Mapper',
'section_footer': 'Net cash generated by operating activities',
'section_header': 'Cash flows from operating activities',
'section_leader': 'Adjustments for',
'section_name': 'Operating Activities',
'position': 0,
'section_subtotal': 'Cash generated from operations',
},
{
'doctype': 'Cash Flow Mapper',
'position': 1,
'section_footer': 'Net cash used in investing activities',
'section_header': 'Cash flows from investing activities',
'section_name': 'Investing Activities'
},
{
'doctype': 'Cash Flow Mapper',
'position': 2,
'section_footer': 'Net cash used in financing activites',
'section_header': 'Cash flows from financing activities',
'section_name': 'Financing Activities',
}
]

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapper", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapper
() => frappe.tests.make('Cash Flow Mapper', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestCashFlowMapper(unittest.TestCase):
pass

View File

@ -0,0 +1,43 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow Mapping', {
refresh: function(frm) {
frm.events.disable_unchecked_fields(frm);
},
reset_check_fields: function(frm) {
frm.fields.filter(field => field.df.fieldtype === 'Check')
.map(field => frm.set_df_property(field.df.fieldname, 'read_only', 0));
},
has_checked_field(frm) {
const val = frm.fields.filter(field => field.value === 1);
return val.length ? 1 : 0;
},
_disable_unchecked_fields: function(frm) {
// get value of clicked field
frm.fields.filter(field => field.value === 0)
.map(field => frm.set_df_property(field.df.fieldname, 'read_only', 1));
},
disable_unchecked_fields: function(frm) {
frm.events.reset_check_fields(frm);
const checked = frm.events.has_checked_field(frm);
if (checked) {
frm.events._disable_unchecked_fields(frm);
}
},
is_working_capital: function(frm) {
frm.events.disable_unchecked_fields(frm);
},
is_finance_cost: function(frm) {
frm.events.disable_unchecked_fields(frm);
},
is_income_tax_liability: function(frm) {
frm.events.disable_unchecked_fields(frm);
},
is_income_tax_expense: function(frm) {
frm.events.disable_unchecked_fields(frm);
},
is_finance_cost_adjustment: function(frm) {
frm.events.disable_unchecked_fields(frm);
}
});

View File

@ -0,0 +1,359 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:mapping_name",
"beta": 0,
"creation": "2018-02-08 09:28:44.678364",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapping_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Label",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "accounts",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Accounts",
"length": 0,
"no_copy": 0,
"options": "Cash Flow Mapping Accounts",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_1",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Select Maximum Of 1",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "is_finance_cost",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Finance Cost",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "is_working_capital",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Working Capital",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "is_finance_cost_adjustment",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Finance Cost Adjustment",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "is_income_tax_liability",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Income Tax Liability",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "is_income_tax_expense",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Income Tax Expense",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-15 08:25:18.693533",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cash Flow Mapping",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "name",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class CashFlowMapping(Document):
def validate(self):
self.validate_checked_options()
def validate_checked_options(self):
checked_fields = [d for d in self.meta.fields if d.fieldtype == 'Check' and self.get(d.fieldname) == 1]
if len(checked_fields) > 1:
frappe.throw(
frappe._('You can only select a maximum of one option from the list of check boxes.'),
title='Error'
)

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapping", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapping
() => frappe.tests.make('Cash Flow Mapping', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestCashFlowMapping(unittest.TestCase):
def setUp(self):
if frappe.db.exists("Cash Flow Mapping", "Test Mapping"):
frappe.delete_doc('Cash Flow Mappping', 'Test Mapping')
def tearDown(self):
frappe.delete_doc('Cash Flow Mapping', 'Test Mapping')
def test_multiple_selections_not_allowed(self):
doc = frappe.new_doc('Cash Flow Mapping')
doc.mapping_name = 'Test Mapping'
doc.label = 'Test label'
doc.append(
'accounts',
{'account': 'Accounts Receivable - _TC'}
)
doc.is_working_capital = 1
doc.is_finance_cost = 1
self.assertRaises(frappe.ValidationError, doc.insert)
doc.is_finance_cost = 0
doc.insert()

View File

@ -0,0 +1,73 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:account",
"beta": 0,
"creation": "2018-02-08 09:25:34.353995",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-02-08 09:25:34.353995",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cash Flow Mapping Accounts",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class CashFlowMappingAccounts(Document):
pass

View File

@ -0,0 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow Mapping Template', {
});

View File

@ -0,0 +1,123 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-02-08 10:20:18.316801",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "template_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Template Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapping",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Cash Flow Mapping",
"length": 0,
"no_copy": 0,
"options": "Cash Flow Mapping Template Details",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-08 10:20:18.316801",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cash Flow Mapping Template",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class CashFlowMappingTemplate(Document):
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapping Template", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapping Template
() => frappe.tests.make('Cash Flow Mapping Template', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestCashFlowMappingTemplate(unittest.TestCase):
pass

View File

@ -0,0 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow Mapping Template Details', {
});

View File

@ -0,0 +1,94 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:mapping",
"beta": 0,
"creation": "2018-02-08 10:18:48.513608",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mapping",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Mapping",
"length": 0,
"no_copy": 0,
"options": "Cash Flow Mapping",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-08 10:33:39.413930",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cash Flow Mapping Template Details",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class CashFlowMappingTemplateDetails(Document):
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapping Template Details", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapping Template Details
() => frappe.tests.make('Cash Flow Mapping Template Details', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestCashFlowMappingTemplateDetails(unittest.TestCase):
pass

View File

@ -4,12 +4,17 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
from erpnext.accounts.utils import get_fiscal_year
def execute(filters=None):
if cint(frappe.db.get_single_value('Accounts Settings', 'use_custom_cash_flow')):
from erpnext.accounts.report.cash_flow.custom_cash_flow import execute as execute_custom
return execute_custom(filters=filters)
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.periodicity, filters.accumulated_values, filters.company)
@ -44,10 +49,7 @@ def execute(filters=None):
}
# combine all cash flow accounts for iteration
cash_flow_accounts = []
cash_flow_accounts.append(operation_accounts)
cash_flow_accounts.append(investing_accounts)
cash_flow_accounts.append(financing_accounts)
cash_flow_accounts = [operation_accounts, investing_accounts, financing_accounts]
# compute net profit / loss
income = get_data(filters.company, "Income", "Credit", period_list,
@ -100,6 +102,7 @@ def execute(filters=None):
return columns, data
def get_account_type_based_data(company, account_type, period_list, accumulated_values):
data = {}
total = 0
@ -127,6 +130,7 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
data["total"] = total
return data
def get_start_date(period, accumulated_values, company):
start_date = period["year_start_date"]
if accumulated_values:
@ -134,6 +138,7 @@ def get_start_date(period, accumulated_values, company):
return start_date
def add_total_row_account(out, data, label, period_list, currency):
total_row = {
"account_name": "'" + _("{0}").format(label) + "'",

View File

@ -0,0 +1,443 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import add_to_date
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
def get_mapper_for(mappers, position):
mapper_list = filter(lambda x: x['position'] == position, mappers)
return mapper_list[0] if mapper_list else []
def get_mappers_from_db():
return frappe.get_all(
'Cash Flow Mapper',
fields=[
'section_name', 'section_header', 'section_leader', 'section_subtotal',
'section_footer', 'name', 'position'],
order_by='position'
)
def get_accounts_in_mappers(mapping_names):
return frappe.db.sql(
'select cfma.name, cfm.label, cfm.is_working_capital, cfm.is_income_tax_liability, '
'cfm.is_income_tax_expense, cfm.is_finance_cost, cfm.is_finance_cost_adjustment '
'from `tabCash Flow Mapping Accounts` cfma '
'join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name '
'where cfma.parent in %s '
'order by cfm.is_working_capital',
(mapping_names,)
)
def setup_mappers(mappers):
cash_flow_accounts = []
for mapping in mappers:
mapping['account_types'] = []
mapping['tax_liabilities'] = []
mapping['tax_expenses'] = []
mapping['finance_costs'] = []
mapping['finance_costs_adjustments'] = []
doc = frappe.get_doc('Cash Flow Mapper', mapping['name'])
mapping_names = [item.name for item in doc.accounts]
if not mapping_names:
continue
accounts = get_accounts_in_mappers(mapping_names)
account_types = [
dict(
name=account[0], label=account[1], is_working_capital=account[2],
is_income_tax_liability=account[3], is_income_tax_expense=account[4]
) for account in accounts if not account[3]]
finance_costs_adjustments = [
dict(
name=account[0], label=account[1], is_finance_cost=account[5],
is_finance_cost_adjustment=account[6]
) for account in accounts if account[6]]
tax_liabilities = [
dict(
name=account[0], label=account[1], is_income_tax_liability=account[3],
is_income_tax_expense=account[4]
) for account in accounts if account[3]]
tax_expenses = [
dict(
name=account[0], label=account[1], is_income_tax_liability=account[3],
is_income_tax_expense=account[4]
) for account in accounts if account[4]]
finance_costs = [
dict(
name=account[0], label=account[1], is_finance_cost=account[5])
for account in accounts if account[5]]
account_types_labels = sorted(
set(
[(d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense'])
for d in account_types]
),
key=lambda x: x[1]
)
fc_adjustment_labels = sorted(
set(
[(d['label'], d['is_finance_cost'], d['is_finance_cost_adjustment'])
for d in finance_costs_adjustments if d['is_finance_cost_adjustment']]
),
key=lambda x: x[2]
)
unique_liability_labels = sorted(
set(
[(d['label'], d['is_income_tax_liability'], d['is_income_tax_expense'])
for d in tax_liabilities]
),
key=lambda x: x[0]
)
unique_expense_labels = sorted(
set(
[(d['label'], d['is_income_tax_liability'], d['is_income_tax_expense'])
for d in tax_expenses]
),
key=lambda x: x[0]
)
unique_finance_costs_labels = sorted(
set(
[(d['label'], d['is_finance_cost']) for d in finance_costs]
),
key=lambda x: x[0]
)
for label in account_types_labels:
names = [d['name'] for d in account_types if d['label'] == label[0]]
m = dict(label=label[0], names=names, is_working_capital=label[1])
mapping['account_types'].append(m)
for label in fc_adjustment_labels:
names = [d['name'] for d in finance_costs_adjustments if d['label'] == label[0]]
m = dict(label=label[0], names=names)
mapping['finance_costs_adjustments'].append(m)
for label in unique_liability_labels:
names = [d['name'] for d in tax_liabilities if d['label'] == label[0]]
m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
mapping['tax_liabilities'].append(m)
for label in unique_expense_labels:
names = [d['name'] for d in tax_expenses if d['label'] == label[0]]
m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
mapping['tax_expenses'].append(m)
for label in unique_finance_costs_labels:
names = [d['name'] for d in finance_costs if d['label'] == label[0]]
m = dict(label=label[0], names=names, is_finance_cost=label[1])
mapping['finance_costs'].append(m)
cash_flow_accounts.append(mapping)
return cash_flow_accounts
def add_data_for_operating_activities(
filters, company_currency, profit_data, period_list, light_mappers, mapper, data):
has_added_working_capital_header = False
section_data = []
data.append({
"account_name": mapper['section_header'],
"parent_account": None,
"indent": 0.0,
"account": mapper['section_header']
})
if profit_data:
profit_data.update({
"indent": 1,
"parent_account": get_mapper_for(light_mappers, position=0)['section_header']
})
data.append(profit_data)
section_data.append(profit_data)
data.append({
"account_name": mapper["section_leader"],
"parent_account": None,
"indent": 1.0,
"account": mapper["section_leader"]
})
for account in mapper['account_types']:
if account['is_working_capital'] and not has_added_working_capital_header:
data.append({
"account_name": 'Movement in working capital',
"parent_account": None,
"indent": 1.0,
"account": ""
})
has_added_working_capital_header = True
account_data = _get_account_type_based_data(
filters, account['names'], period_list, filters.accumulated_values)
if not account['is_working_capital']:
for key in account_data:
if key != 'total':
account_data[key] *= -1
if account_data['total'] != 0:
account_data.update({
"account_name": account['label'],
"account": account['names'],
"indent": 1.0,
"parent_account": mapper['section_header'],
"currency": company_currency
})
data.append(account_data)
section_data.append(account_data)
_add_total_row_account(
data, section_data, mapper['section_subtotal'], period_list, company_currency, indent=1)
# calculate adjustment for tax paid and add to data
if not mapper['tax_liabilities']:
mapper['tax_liabilities'] = [
dict(label='Income tax paid', names=[''], tax_liability=1, tax_expense=0)]
for account in mapper['tax_liabilities']:
tax_paid = calculate_adjustment(
filters, mapper['tax_liabilities'], mapper['tax_expenses'],
filters.accumulated_values, period_list)
if tax_paid:
tax_paid.update({
'parent_account': mapper['section_header'],
'currency': company_currency,
'account_name': account['label'],
'indent': 1.0
})
data.append(tax_paid)
section_data.append(tax_paid)
if not mapper['finance_costs_adjustments']:
mapper['finance_costs_adjustments'] = [dict(label='Interest Paid', names=[''])]
for account in mapper['finance_costs_adjustments']:
interest_paid = calculate_adjustment(
filters, mapper['finance_costs_adjustments'], mapper['finance_costs'],
filters.accumulated_values, period_list
)
if interest_paid:
interest_paid.update({
'parent_account': mapper['section_header'],
'currency': company_currency,
'account_name': account['label'],
'indent': 1.0
})
data.append(interest_paid)
section_data.append(interest_paid)
_add_total_row_account(
data, section_data, mapper['section_footer'], period_list, company_currency)
def calculate_adjustment(filters, non_expense_mapper, expense_mapper, use_accumulated_values, period_list):
liability_accounts = [d['names'] for d in non_expense_mapper]
expense_accounts = [d['names'] for d in expense_mapper]
non_expense_closing = _get_account_type_based_data(
filters, liability_accounts, period_list, 0)
non_expense_opening = _get_account_type_based_data(
filters, liability_accounts, period_list, use_accumulated_values, opening_balances=1)
expense_data = _get_account_type_based_data(
filters, expense_accounts, period_list, use_accumulated_values)
data = _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data)
return data
def _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data):
account_data = {}
for month in non_expense_opening.keys():
if non_expense_opening[month] and non_expense_closing[month]:
account_data[month] = non_expense_opening[month] - expense_data[month] + non_expense_closing[month]
elif expense_data[month]:
account_data[month] = expense_data[month]
return account_data
def add_data_for_other_activities(
filters, company_currency, profit_data, period_list, light_mappers, mapper_list, data):
for mapper in mapper_list:
section_data = []
data.append({
"account_name": mapper['section_header'],
"parent_account": None,
"indent": 0.0,
"account": mapper['section_header']
})
for account in mapper['account_types']:
account_data = _get_account_type_based_data(filters,
account['names'], period_list, filters.accumulated_values)
if account_data['total'] != 0:
account_data.update({
"account_name": account['label'],
"account": account['names'],
"indent": 1,
"parent_account": mapper['section_header'],
"currency": company_currency
})
data.append(account_data)
section_data.append(account_data)
_add_total_row_account(data, section_data, mapper['section_footer'],
period_list, company_currency)
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
data = []
operating_activities_mapper = get_mapper_for(light_mappers, position=0)
other_mappers = [
get_mapper_for(light_mappers, position=1),
get_mapper_for(light_mappers, position=2)
]
if operating_activities_mapper:
add_data_for_operating_activities(
filters, company_currency, profit_data, period_list, light_mappers,
operating_activities_mapper, data
)
if all(other_mappers):
add_data_for_other_activities(
filters, company_currency, profit_data, period_list, light_mappers, other_mappers, data
)
return data
def execute(filters=None):
period_list = get_period_list(
filters.from_fiscal_year, filters.to_fiscal_year, filters.periodicity,
filters.accumulated_values, filters.company
)
mappers = get_mappers_from_db()
cash_flow_accounts = setup_mappers(mappers)
# compute net profit / loss
income = get_data(
filters.company, "Income", "Credit", period_list,
accumulated_values=filters.accumulated_values, ignore_closing_entries=True,
ignore_accumulated_values_for_fy=True
)
expense = get_data(
filters.company, "Expense", "Debit", period_list,
accumulated_values=filters.accumulated_values, ignore_closing_entries=True,
ignore_accumulated_values_for_fy=True
)
net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company)
company_currency = frappe.db.get_value("Company", filters.company, "default_currency")
data = compute_data(filters, company_currency, net_profit_loss, period_list, mappers, cash_flow_accounts)
_add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency)
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
return columns, data
def _get_account_type_based_data(filters, account_names, period_list, accumulated_values, opening_balances=0):
from erpnext.accounts.report.cash_flow.cash_flow import get_start_date
company = filters.company
data = {}
total = 0
for period in period_list:
start_date = get_start_date(period, accumulated_values, company)
if opening_balances:
date_info = dict(date=start_date)
months_map = {'Monthly': -1, 'Quarterly': -3, 'Half-Yearly': -6}
years_map = {'Yearly': -1}
if months_map.get(filters.periodicity):
date_info.update(months=months_map[filters.periodicity])
else:
date_info.update(years=years_map[filters.periodicity])
if accumulated_values:
start, end = add_to_date(start_date, years=-1), add_to_date(period['to_date'], years=-1)
else:
start, end = add_to_date(**date_info), add_to_date(**date_info)
gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit)
from `tabGL Entry`
where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE name IN %s
OR parent_account IN %s)
""", (company, start, end, account_names, account_names))
else:
gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit)
from `tabGL Entry`
where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE name IN %s
OR parent_account IN %s)
""", (company, start_date if accumulated_values else period['from_date'],
period['to_date'], account_names, account_names))
if gl_sum and gl_sum[0]:
amount = gl_sum[0]
else:
amount = 0
total += amount
data.setdefault(period["key"], amount)
data["total"] = total
return data
def _add_total_row_account(out, data, label, period_list, currency, indent=0.0):
total_row = {
"indent": indent,
"account_name": "'" + _("{0}").format(label) + "'",
"account": "'" + _("{0}").format(label) + "'",
"currency": currency
}
for row in data:
if row.get("parent_account"):
for period in period_list:
total_row.setdefault(period.key, 0.0)
total_row[period.key] += row.get(period.key, 0.0)
total_row.setdefault("total", 0.0)
total_row["total"] += row["total"]
out.append(total_row)
out.append({})

View File

@ -36,8 +36,8 @@ def execute(filters=None):
def get_net_profit_loss(income, expense, period_list, company):
total = 0
net_profit_loss = {
"account_name": "'" + _("Net Profit / Loss") + "'",
"account": "'" + _("Net Profit / Loss") + "'",
"account_name": "'" + _("Profit for the year") + "'",
"account": "'" + _("Profit for the year") + "'",
"warn_if_negative": True,
"currency": frappe.db.get_value("Company", company, "default_currency")
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,156 @@
# How To Customise Cash Flow Report
As your chart of accounts begins to get more complex and reporting standards change and evolve, the default cash flow
report might no longer suffice. This is because ERPNext might not be able to accurately guess the classification and
purpose of all accounts in the charts of accounts. Another gripe you might have is the inability to adjust the report
format to fit your needs.
This will no longer be a problem because ERPNext now allows users to customise the cash flow report.
## Technical Overview
Customisation is made possible by the introduction of two new doctypes - Cash Flow Mapper and Cash Flow Mapping. Both
doctypes contain the information required to generate a cash flow report.
Cash Flow Mapping shows how accounts in your charts of accounts map to a line item in your cash flow report while
Cash Flow Mapper gets all the Cash Flow Mappings that relate to the three sections of a cash flow statement.
With this, you generate detailed cash flow reports to your requirements. This might not make a lot of sense but it will
after we go through an example.
## Example
### Background information
Let's assume we have a fictitious company for which we want to generate a cash flow report.
This is what the cash flow report looks like at the moment:
<img alt="Default cash flow report" class="screenshot" src="{{docs_base_url}}/assets/img/articles/default-cash-flow-report.png">
We don't like the report for the following reasons:
- The reporting format is too scant.
- The 'Net Cash From Operations' figure is wrong
### Customisation Process
We wants the Cash Flow Report to look something similar to the format in the images below:
<img alt="cash flow format 1" class="screenshot" src="{{docs_base_url}}/assets/img/articles/format-1.png">
<img alt="cash flow format 1" class="screenshot" src="{{docs_base_url}}/assets/img/articles/format-2.png">
#### Activate Customised Cash Flow Report
Do this in Accounts Settings by checking the 'Use Custom Cash Flow Format' checkbox. This will cause ERPNext to only
use your custom format for cash flow reports.
After doing that, your cash flow report should look like this:
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/no-mappers.png">
Move to the next section to build the report.
#### Create Cash Flow Mappings
For each line, we need to create a Cash Flow Mapping document to represent it.
<img alt="new cash flow mapping form" class="screenshot" src="{{docs_base_url}}/assets/img/articles/new-cash-flow-mapping.png">
You can think of the Cash Flow Mapping as a representation of each line in the cash flow report. A Cash Flow Mapping
is a child of a Cash Flow Mapper which will be explained later.
Let's start by creating Cash Flow Mappings that will represent the add back of non cash expenses already recodgnised in
the Profit or Loss statement. We want them to appear on the cash statement as:
- Income taxes recognised in profit or loss
- Finance costs recognised in profit or loss
- Depreciation of non-current assets
Start by opening a new Cash Flow Mapping form.
The fields in the Cash Flow Mapping doctype are:
- **Name**: This something to identify this document. Name it something related to the label
- **Label**: This is what will show in the cash flow statement
- **Accounts**: This table contains all the accounts which this line relates to.
With this information, let's go ahead and create the Cash Flow Mapping Document for the line 'Income taxes recognised in profit or loss'
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-1.png">
I have named it 'Income Tax Charge' and given it a label 'Income taxes recognised in profit or loss'. We want this
line to reflect income tax charges from our profit or loss statement. The account where this happens in our chart
of account is named 'Income Taxes' (an expense) so I have added 'Income Taxes' into the accounts table. If you have
more accounts representing income tax expenses, you should add all of them here.
Because Income Tax expense needs to be adjusted further in the cash flow statement, check the 'Is Income Tax Expense'
checkbox. This is what will help ERPNext properly calculate the adjustments to be made.
*For best results, let parent accounts have child accounts that have the same treatment for cash flow reporting
purposes because ERPNext will calculate net change of all children accounts in a situation where the selected account
is a parent account.*
In the same way, I have created for the remaining two mappings.
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-2.png">
Finance costs also need to be adjusted so make sure to check the 'Is Finance Cost' checkbox.
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-3.png">
Next let's add Cash Flow Mapping for items that show changes in working capital:
- Increase/(decrease) in other liabilities
- (Increase)/decrease in trade and other receivables
- Increase/(decrease) in trade and other payables
- VAT payable
- (Increase)/decrease in inventory
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-4.png">
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-5.png">
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-6.png">
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-7.png">
<img alt="custom cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-8.png">
Don't forget to tell ERPNext that these mappings represent changes in working capital by checking the 'Is Working
Capital' checkbox.
At this point we have created all the mappings necessary for the Operating Activities section of our cash flow
statement. However, ERPNext doesn't know that yet until we create Cash Flow Mapper documents. We'll create Cash Flow
Mapper documents next.
#### Create Cash Flow Mappers
Cash Flow Mappers represents the sections of the cash flow statement. A standard cash flow statement has only three
sections so when you view the Cash Flow Mapper list, you will that three have been created for you named:
- Operating Activities
- Financing Activities
- Investing Activities
You will not be able to add or remove any of them but they are editable and can be renamed.
<img alt="cash flow mapper list" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapper-2.png">
Open the Operating Activities Cash Flow Mapper so we can add the Cash Flow Mappings we have created.
- **Section Name**: This is the heading of the section.
- **Section Leader**: This is the first sub-header immediately after the profit figure. Relates only to Operating
Activities Cash Flow Mapper
- **Section Subtotal**: This is the label for subtotal in the cash flow statement section. Relates only to Operating
Activities Cash Flow Mapper
- **Section Footer**: This is the label for the total in the cash flow statement section.
- **Mapping**: This table contains all the Cash Flow Mappings related to the Cash Flow Mapper.
Now add all the Cash Flow Mappings you have created and Save. You should have something like this:
<img alt="cash flow mapper for operating activities" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapper-4.png">
Refresh the cash flow statement and view the changes.
<img alt="updated cash flow report" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapper-3.png">
Looks close to our requirements but we are not done yet. Create new mappings for 'Investing Activities' and 'Financing
Activities' sections of the cash flow statement.
<img alt="cash flow mapping" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-9.png">
<img alt="cash flow mapping" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapping-10.png">
<img alt="cash flow mapper for operating activities" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapper-5.png">
<img alt="cash flow mapper for operating activities" class="screenshot" src="{{docs_base_url}}/assets/img/articles/cash-flow-mapper-6.png">
Here's what our cash flow statement now looks like:
<img alt="final cash flow statement" class="screenshot" src="{{docs_base_url}}/assets/img/articles/final-cash-flow.png">

View File

@ -502,5 +502,6 @@ erpnext.patches.v10_0.update_translatable_fields
erpnext.patches.v10_0.rename_offer_letter_to_job_offer
execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True)
erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_group
erpnext.patches.v10_0.add_default_cash_flow_mappers
erpnext.patches.v11_0.make_quality_inspection_template
erpnext.patches.v10_0.update_territory_and_customer_group

View File

@ -0,0 +1,15 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.setup.install import create_default_cash_flow_mapper_templates
def execute():
frappe.reload_doc('accounts', 'doctype', frappe.scrub('Cash Flow Mapping'))
frappe.reload_doc('accounts', 'doctype', frappe.scrub('Cash Flow Mapper'))
frappe.reload_doc('accounts', 'doctype', frappe.scrub('Cash Flow Mapping Template Details'))
create_default_cash_flow_mapper_templates()

View File

@ -4,6 +4,7 @@
from __future__ import print_function, unicode_literals
import frappe
from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS
from frappe import _
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
@ -11,14 +12,17 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
default_mail_footer = """<div style="padding: 7px; text-align: right; color: #888"><small>Sent via
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
def after_install():
frappe.get_doc({'doctype': "Role", "role_name": "Analytics"}).insert()
set_single_defaults()
create_compact_item_print_custom_field()
create_print_zero_amount_taxes_custom_field()
add_all_roles_to("Administrator")
create_default_cash_flow_mapper_templates()
frappe.db.commit()
def check_setup_wizard_not_completed():
if frappe.db.get_default('desktop:home_page') == 'desktop':
print()
@ -27,6 +31,7 @@ def check_setup_wizard_not_completed():
print()
return False
def set_single_defaults():
for dt in ('Accounts Settings', 'Print Settings', 'HR Settings', 'Buying Settings',
'Selling Settings', 'Stock Settings'):
@ -45,6 +50,7 @@ def set_single_defaults():
frappe.db.set_default("date_format", "dd-mm-yyyy")
def create_compact_item_print_custom_field():
create_custom_field('Print Settings', {
'label': _('Compact Item Print'),
@ -54,6 +60,7 @@ def create_compact_item_print_custom_field():
'insert_after': 'with_letterhead'
})
def create_print_zero_amount_taxes_custom_field():
create_custom_field('Print Settings', {
'label': _('Print taxes with zero amount'),
@ -61,4 +68,13 @@ def create_print_zero_amount_taxes_custom_field():
'fieldtype': 'Check',
'default': 0,
'insert_after': 'allow_print_for_cancelled'
})
})
def create_default_cash_flow_mapper_templates():
mappers = DEFAULT_MAPPERS
for mapper in mappers:
if not frappe.db.exists('Cash Flow Mapper', mapper['section_name']):
doc = frappe.get_doc(mapper)
doc.insert(ignore_permissions=True)