From c75a2b1eed612dcbb9b3b16007dcd2d3ac1e132f Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 31 Oct 2018 15:01:47 +0000 Subject: [PATCH 01/29] Plaid integration --- .../doctype/account_subtype/__init__.py | 0 .../account_subtype/account_subtype.js | 8 + .../account_subtype/account_subtype.json | 134 ++++ .../account_subtype/account_subtype.py | 10 + .../account_subtype/test_account_subtype.js | 23 + .../account_subtype/test_account_subtype.py | 10 + .../accounts/doctype/account_type/__init__.py | 0 .../doctype/account_type/account_type.js | 8 + .../doctype/account_type/account_type.json | 134 ++++ .../doctype/account_type/account_type.py | 10 + .../doctype/account_type/test_account_type.js | 23 + .../doctype/account_type/test_account_type.py | 10 + erpnext/accounts/doctype/bank/bank.js | 24 +- erpnext/accounts/doctype/bank/bank.json | 136 +++- .../doctype/bank_account/bank_account.json | 648 ++++++++++++------ .../doctype/bank_transaction/__init__.py | 0 .../bank_transaction/bank_transaction.js | 8 + .../bank_transaction/bank_transaction.json | 615 +++++++++++++++++ .../bank_transaction/bank_transaction.py | 10 + .../bank_transaction_upload.py | 70 ++ .../bank_transaction/test_bank_transaction.js | 23 + .../bank_transaction/test_bank_transaction.py | 10 + .../bank_transaction_mapping/__init__.py | 0 .../bank_transaction_mapping.json | 107 +++ .../bank_transaction_mapping.py | 10 + .../doctype/plaid_settings/__init__.py | 0 .../doctype/plaid_settings/plaid_connector.py | 83 +++ .../doctype/plaid_settings/plaid_settings.js | 12 + .../plaid_settings/plaid_settings.json | 192 ++++++ .../doctype/plaid_settings/plaid_settings.py | 184 +++++ .../plaid_settings/test_plaid_settings.js | 23 + .../plaid_settings/test_plaid_settings.py | 10 + erpnext/public/build.json | 107 +-- erpnext/public/js/reconciliation/Home.vue | 81 +++ erpnext/public/js/reconciliation/Sidebar.vue | 49 ++ .../reconciliation/components/AccountCard.vue | 78 +++ .../components/BankAccountsContainer.vue | 71 ++ .../reconciliation/components/EmptyState.vue | 45 ++ .../components/NewAccountCard.vue | 71 ++ .../reconciliation/components/PlaidLink.vue | 136 ++++ .../components/TransactionCard.vue | 41 ++ .../components/TransactionsContainer.vue | 72 ++ .../js/reconciliation/pages/Dashboard.vue | 25 + .../reconciliation/pages/Reconciliation.vue | 155 +++++ .../public/js/reconciliation/pages/Upload.vue | 115 ++++ .../reconciliation/reconciliation_factory.js | 29 + .../js/reconciliation/reconciliation_home.js | 100 +++ .../public/js/reconciliation/vue-plugins.js | 17 + erpnext/public/less/bankreconciliation.less | 71 ++ requirements.txt | 3 +- 50 files changed, 3550 insertions(+), 251 deletions(-) create mode 100644 erpnext/accounts/doctype/account_subtype/__init__.py create mode 100644 erpnext/accounts/doctype/account_subtype/account_subtype.js create mode 100644 erpnext/accounts/doctype/account_subtype/account_subtype.json create mode 100644 erpnext/accounts/doctype/account_subtype/account_subtype.py create mode 100644 erpnext/accounts/doctype/account_subtype/test_account_subtype.js create mode 100644 erpnext/accounts/doctype/account_subtype/test_account_subtype.py create mode 100644 erpnext/accounts/doctype/account_type/__init__.py create mode 100644 erpnext/accounts/doctype/account_type/account_type.js create mode 100644 erpnext/accounts/doctype/account_type/account_type.json create mode 100644 erpnext/accounts/doctype/account_type/account_type.py create mode 100644 erpnext/accounts/doctype/account_type/test_account_type.js create mode 100644 erpnext/accounts/doctype/account_type/test_account_type.py create mode 100644 erpnext/accounts/doctype/bank_transaction/__init__.py create mode 100644 erpnext/accounts/doctype/bank_transaction/bank_transaction.js create mode 100644 erpnext/accounts/doctype/bank_transaction/bank_transaction.json create mode 100644 erpnext/accounts/doctype/bank_transaction/bank_transaction.py create mode 100644 erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py create mode 100644 erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js create mode 100644 erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py create mode 100644 erpnext/accounts/doctype/bank_transaction_mapping/__init__.py create mode 100644 erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.json create mode 100644 erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js create mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py create mode 100644 erpnext/public/js/reconciliation/Home.vue create mode 100644 erpnext/public/js/reconciliation/Sidebar.vue create mode 100644 erpnext/public/js/reconciliation/components/AccountCard.vue create mode 100644 erpnext/public/js/reconciliation/components/BankAccountsContainer.vue create mode 100644 erpnext/public/js/reconciliation/components/EmptyState.vue create mode 100644 erpnext/public/js/reconciliation/components/NewAccountCard.vue create mode 100644 erpnext/public/js/reconciliation/components/PlaidLink.vue create mode 100644 erpnext/public/js/reconciliation/components/TransactionCard.vue create mode 100644 erpnext/public/js/reconciliation/components/TransactionsContainer.vue create mode 100644 erpnext/public/js/reconciliation/pages/Dashboard.vue create mode 100644 erpnext/public/js/reconciliation/pages/Reconciliation.vue create mode 100644 erpnext/public/js/reconciliation/pages/Upload.vue create mode 100644 erpnext/public/js/reconciliation/reconciliation_factory.js create mode 100644 erpnext/public/js/reconciliation/reconciliation_home.js create mode 100644 erpnext/public/js/reconciliation/vue-plugins.js create mode 100644 erpnext/public/less/bankreconciliation.less diff --git a/erpnext/accounts/doctype/account_subtype/__init__.py b/erpnext/accounts/doctype/account_subtype/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.js b/erpnext/accounts/doctype/account_subtype/account_subtype.js new file mode 100644 index 00000000000..bc542f50207 --- /dev/null +++ b/erpnext/accounts/doctype/account_subtype/account_subtype.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Account Subtype', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.json b/erpnext/accounts/doctype/account_subtype/account_subtype.json new file mode 100644 index 00000000000..6b1f2a2526e --- /dev/null +++ b/erpnext/accounts/doctype/account_subtype/account_subtype.json @@ -0,0 +1,134 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:account_subtype", + "beta": 0, + "creation": "2018-10-25 15:46:08.054586", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_subtype", + "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": "Account Subtype", + "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, + "translatable": 0, + "unique": 1 + } + ], + "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-10-25 15:47:03.841390", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Account Subtype", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 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, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 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": "Accounts User", + "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": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.py b/erpnext/accounts/doctype/account_subtype/account_subtype.py new file mode 100644 index 00000000000..27e61d81cbd --- /dev/null +++ b/erpnext/accounts/doctype/account_subtype/account_subtype.py @@ -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 +import frappe +from frappe.model.document import Document + +class AccountSubtype(Document): + pass diff --git a/erpnext/accounts/doctype/account_subtype/test_account_subtype.js b/erpnext/accounts/doctype/account_subtype/test_account_subtype.js new file mode 100644 index 00000000000..5646763bbd0 --- /dev/null +++ b/erpnext/accounts/doctype/account_subtype/test_account_subtype.js @@ -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: Account Subtype", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Account Subtype + () => frappe.tests.make('Account Subtype', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/account_subtype/test_account_subtype.py b/erpnext/accounts/doctype/account_subtype/test_account_subtype.py new file mode 100644 index 00000000000..92f29bdf03e --- /dev/null +++ b/erpnext/accounts/doctype/account_subtype/test_account_subtype.py @@ -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 frappe +import unittest + +class TestAccountSubtype(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/account_type/__init__.py b/erpnext/accounts/doctype/account_type/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/account_type/account_type.js b/erpnext/accounts/doctype/account_type/account_type.js new file mode 100644 index 00000000000..727634571cc --- /dev/null +++ b/erpnext/accounts/doctype/account_type/account_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Account Type', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/accounts/doctype/account_type/account_type.json b/erpnext/accounts/doctype/account_type/account_type.json new file mode 100644 index 00000000000..6b8f724b408 --- /dev/null +++ b/erpnext/accounts/doctype/account_type/account_type.json @@ -0,0 +1,134 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:account_type", + "beta": 0, + "creation": "2018-10-25 15:45:45.789963", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_type", + "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": "Account Type", + "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, + "translatable": 0, + "unique": 1 + } + ], + "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-10-25 15:46:51.042604", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Account Type", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 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, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 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": "Accounts User", + "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": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account_type/account_type.py b/erpnext/accounts/doctype/account_type/account_type.py new file mode 100644 index 00000000000..5d9d6a9472e --- /dev/null +++ b/erpnext/accounts/doctype/account_type/account_type.py @@ -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 +import frappe +from frappe.model.document import Document + +class AccountType(Document): + pass diff --git a/erpnext/accounts/doctype/account_type/test_account_type.js b/erpnext/accounts/doctype/account_type/test_account_type.js new file mode 100644 index 00000000000..76e434f4ab5 --- /dev/null +++ b/erpnext/accounts/doctype/account_type/test_account_type.js @@ -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: Account Type", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Account Type + () => frappe.tests.make('Account Type', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/account_type/test_account_type.py b/erpnext/accounts/doctype/account_type/test_account_type.py new file mode 100644 index 00000000000..3f7e25c67a8 --- /dev/null +++ b/erpnext/accounts/doctype/account_type/test_account_type.py @@ -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 frappe +import unittest + +class TestAccountType(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 1063a07b820..50334861b7b 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -2,7 +2,29 @@ // For license information, please see license.txt frappe.ui.form.on('Bank', { + onload: function(frm) { + add_fields_to_mapping_table(frm); + }, refresh: function(frm) { - + add_fields_to_mapping_table(frm); } }); + + +let add_fields_to_mapping_table = function (frm) { + let options = [] + + frappe.model.with_doctype("Bank Transaction", function() { + let meta = frappe.get_meta("Bank Transaction") + meta.fields.forEach(value => { + if (!["Section Break", "Column Break"].includes(value.fieldtype)) { + options.push(value.fieldname); + } + }) + }) + + frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field", + frm.doc.name).options = options; + + frm.fields_dict.bank_transaction_mapping.grid.refresh(); +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json index 0a24726f36a..b7e1c1f4933 100644 --- a/erpnext/accounts/doctype/bank/bank.json +++ b/erpnext/accounts/doctype/bank/bank.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -15,6 +16,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,6 +44,134 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "data_import_configuration_section", + "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": "Data Import Configuration", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bank_transaction_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": "Bank Transaction Mapping", + "length": 0, + "no_copy": 0, + "options": "Bank Transaction 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": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "plaid_access_token", + "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": "Plaid Access Token", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -55,7 +185,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-07 17:00:21.246202", + "modified": "2018-10-25 15:20:38.837772", "modified_by": "Administrator", "module": "Accounts", "name": "Bank", @@ -64,7 +194,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -90,5 +219,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 4897097b4ac..5a20cef094a 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 1, @@ -61,7 +62,7 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Account", + "label": "GL Account", "length": 0, "no_copy": 0, "options": "Account", @@ -111,6 +112,136 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_type", + "fieldtype": "Link", + "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": "Account Type", + "length": 0, + "no_copy": 0, + "options": "Account Type", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_subtype", + "fieldtype": "Link", + "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": "Account Subtype", + "length": 0, + "no_copy": 0, + "options": "Account Subtype", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column 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": "", + "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": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_default", + "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 the Default Account", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -177,198 +308,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column 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": "", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_default", - "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 Default", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_account_no", - "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": "Bank Account No", - "length": 30, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "iban", - "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": "IBAN", - "length": 25, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "branch_code", - "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": "Branch Code", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "swift_number", - "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": "SWIFT number", - "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, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -386,6 +325,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Party Details", "length": 0, "no_copy": 0, "permlevel": 0, @@ -498,6 +438,197 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_details_section", + "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": "Account Details", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "iban", + "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": "IBAN", + "length": 25, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_12", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bank_account_no", + "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": "Bank Account No", + "length": 30, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "branch_code", + "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": "Branch Code", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "swift_number", + "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": "SWIFT number", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -602,7 +733,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_12", + "fieldname": "column_break_13", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, @@ -657,6 +788,133 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "integration_details_section", + "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": "Integration Details", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "integration_id", + "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": "Integration ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_27", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mask", + "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": "Mask", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -669,7 +927,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-20 13:55:36.996465", + "modified": "2018-10-26 14:14:45.049414", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/accounts/doctype/bank_transaction/__init__.py b/erpnext/accounts/doctype/bank_transaction/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js new file mode 100644 index 00000000000..6960570be36 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Bank Transaction', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json new file mode 100644 index 00000000000..7c39087dfab --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -0,0 +1,615 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2018-10-22 18:19:02.784533", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "ACC-BTN-.YYYY.-", + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 1, + "options": "ACC-BTN-.YYYY.-", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "date", + "fieldtype": "Date", + "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": "Date", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Settled", + "fieldname": "status", + "fieldtype": "Select", + "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": "Status", + "length": 0, + "no_copy": 0, + "options": "\nPending\nSettled", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bank_account", + "fieldtype": "Link", + "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": "Bank Account", + "length": 0, + "no_copy": 0, + "options": "Bank 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": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "debit", + "fieldtype": "Currency", + "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": "Debit", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "credit", + "fieldtype": "Currency", + "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": "Credit", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "currency", + "fieldtype": "Link", + "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": "Currency", + "length": 0, + "no_copy": 0, + "options": "Currency", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_10", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "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": "Description", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_14", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_number", + "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": "Reference Number", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "transaction_id", + "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": "Transaction ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "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": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Bank Transaction", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-10-26 15:58:53.400200", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Transaction", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 1, + "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": 1, + "write": 1 + }, + { + "amend": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 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": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py new file mode 100644 index 00000000000..efa9093339b --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -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 +import frappe +from frappe.model.document import Document + +class BankTransaction(Document): + pass diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py new file mode 100644 index 00000000000..7e591e4a5c3 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -0,0 +1,70 @@ +# -*- 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 +import json +from frappe.utils import getdate + +@frappe.whitelist() +def upload_bank_statement(): + if getattr(frappe, "uploaded_file", None): + with open(frappe.uploaded_file, "rb") as upfile: + fcontent = upfile.read() + else: + from frappe.utils.file_manager import get_uploaded_content + fname, fcontent = get_uploaded_content() + + if frappe.safe_encode(fname).lower().endswith("csv"): + from frappe.utils.csvutils import read_csv_content + rows = read_csv_content(fcontent, False) + + elif frappe.safe_encode(fname).lower().endswith("xlsx"): + from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file + rows = read_xlsx_file_from_attached_file(fcontent=fcontent) + + columns = rows[0] + rows.pop(0) + data = rows + return {"columns": columns, "data": data} + + +@frappe.whitelist() +def create_bank_entries(columns, data, bank_account): + bank_account = json.loads(bank_account) + header_map = get_header_mapping(columns, bank_account) + + for d in json.loads(data): + fields = {} + for key, value in header_map.iteritems(): + fields.update({key: d[int(value)-1]}) + + + bank_transaction = frappe.get_doc({ + "doctype": "Bank Transaction" + }) + bank_transaction.update(fields) + bank_transaction.date = getdate(bank_transaction.date) + bank_transaction.bank_account = bank_account["name"] + bank_transaction.insert() + + return 'success' + +def get_header_mapping(columns, bank_account): + mapping = get_bank_mapping(bank_account) + + header_map = {} + for column in json.loads(columns): + if column["content"] in mapping: + header_map.update({mapping[column["content"]]: column["colIndex"]}) + + return header_map + +def get_bank_mapping(bank_account): + bank_name = frappe.db.get_value("Bank Account", bank_account["name"], "bank") + bank = frappe.get_doc("Bank", bank_name) + + mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping} + + return mapping \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js new file mode 100644 index 00000000000..305119e1370 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js @@ -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: Bank Transaction", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Bank Transaction + () => frappe.tests.make('Bank Transaction', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py new file mode 100644 index 00000000000..911cac24565 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -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 frappe +import unittest + +class TestBankTransaction(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/bank_transaction_mapping/__init__.py b/erpnext/accounts/doctype/bank_transaction_mapping/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.json b/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.json new file mode 100644 index 00000000000..ace554b2440 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.json @@ -0,0 +1,107 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-10-24 15:24:56.713277", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bank_transaction_field", + "fieldtype": "Select", + "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": "Field in Bank Transaction", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "file_field", + "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": "Column in Bank File", + "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, + "translatable": 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-10-24 15:24:56.713277", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Transaction Mapping", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py b/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py new file mode 100644 index 00000000000..70b7ed8f71e --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py @@ -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 +import frappe +from frappe.model.document import Document + +class BankTransactionMapping(Document): + pass diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/__init__.py b/erpnext/erpnext_integrations/doctype/plaid_settings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py new file mode 100644 index 00000000000..24bb2e9fd9e --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -0,0 +1,83 @@ +# -*- 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 import _ +import json +import requests +from plaid import Client +from plaid.errors import APIError, ItemError + +class PlaidConnector(): + def __init__(self, access_token=None): + + if not(frappe.conf.get("plaid_client_id") and frappe.conf.get("plaid_secret") and frappe.conf.get("plaid_public_key")): + frappe.throw(_("Please complete your Plaid API configuration before synchronizing your account")) + + self.config = { + "plaid_client_id": frappe.conf.get("plaid_client_id"), + "plaid_secret": frappe.conf.get("plaid_secret"), + "plaid_public_key": frappe.conf.get("plaid_public_key"), + "plaid_env": frappe.conf.get("plaid_env") + } + + self.client = Client(client_id=self.config["plaid_client_id"], + secret=self.config["plaid_secret"], + public_key=self.config["plaid_public_key"], + environment=self.config["plaid_env"] + ) + + self.access_token = access_token + + def get_access_token(self, public_token): + if public_token is None: + frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error")) + + response = self.client.Item.public_token.exchange(public_token) + access_token = response['access_token'] + + return access_token + + def auth(self): + try: + print(self.access_token) + self.client.Auth.get(self.access_token) + print("Authentication successful.....") + except ItemError as e: + if e.code == 'ITEM_LOGIN_REQUIRED': + pass + else: + pass + except APIError as e: + if e.code == 'PLANNED_MAINTENANCE': + pass + else: + pass + except requests.Timeout: + pass + except Exception as e: + print(e) + frappe.log_error(frappe.get_traceback(), _("Plaid authentication error")) + frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'}) + + def get_transactions(self, start_date, end_date, account_id=None): + try: + self.auth() + if account_id: + account_ids = [account_id] + + response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids) + + else: + response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date) + + transactions = response['transactions'] + + while len(transactions) < response['total_transactions']: + response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions)) + transactions.extend(response['transactions']) + return transactions + except Exception: + frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js new file mode 100644 index 00000000000..18519bb9b1a --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -0,0 +1,12 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Plaid Settings', { + refresh: function(frm) { + + }, + + connect_btn: function(frm) { + frappe.set_route('bankreconciliation/synchronization'); + } +}); \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json new file mode 100644 index 00000000000..570bd1b3723 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -0,0 +1,192 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-10-25 10:02:48.656165", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enabled", + "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": "Enabled", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.enabled==1", + "fieldname": "connect_btn", + "fieldtype": "Button", + "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": "Connect with plaid", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.enabled==1", + "fieldname": "section_break_3", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "last_sync_date", + "fieldtype": "Date", + "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": "Last Synchronization Date", + "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, + "translatable": 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": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2018-10-26 17:28:47.689735", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "Plaid Settings", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "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": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py new file mode 100644 index 00000000000..f823a0fc492 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -0,0 +1,184 @@ +# -*- 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 +import json +from frappe import _ +from frappe.model.document import Document +from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account +from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector +from frappe.utils import getdate, formatdate, today, add_months + +class PlaidSettings(Document): + pass + +@frappe.whitelist() +def plaid_configuration(): + return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site } + + +@frappe.whitelist() +def add_institution(token, response): + response = json.loads(response) + frappe.log_error(response) + + plaid = PlaidConnector() + access_token = plaid.get_access_token(token) + + if not frappe.db.exists("Bank", response["institution"]["name"]): + try: + bank = frappe.get_doc({ + "doctype": "Bank", + "bank_name": response["institution"]["name"], + "plaid_access_token": access_token + }) + bank.insert() + except Exception: + frappe.throw(frappe.get_traceback()) + + else: + bank = frappe.get_doc("Bank", response["institution"]["name"]) + bank.plaid_access_token = access_token + bank.save() + + return bank + +@frappe.whitelist() +def add_bank_accounts(response, bank): + response = json.loads(response) + bank = json.loads(bank) + company = "Dokos" + result = [] + default_gl_account = get_default_bank_cash_account(company, "Bank") + + for account in response["accounts"]: + acc_type = frappe.db.get_value("Account Type", account["type"]) + if not acc_type: + add_account_type(account["type"]) + + acc_subtype = frappe.db.get_value("Account Subtype", account["subtype"]) + if not acc_subtype: + add_account_subtype(account["subtype"]) + + if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])): + try: + new_account = frappe.get_doc({ + "doctype": "Bank Account", + "bank": bank["bank_name"], + "account": default_gl_account.account, + "account_name": account["name"], + "account_type": account["type"] or "", + "account_subtype": account["subtype"] or "", + "mask": account["mask"] or "", + "integration_id": account["id"], + "is_company_account": 1, + "company": company + }) + new_account.insert() + + result.append(new_account.name) + + except Exception: + frappe.throw(frappe.get_traceback()) + + else: + result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name")) + + return result + +def add_account_type(account_type): + try: + frappe.get_doc({ + "doctype": "Account Type", + "account_type": account_type + }).insert() + except: + frappe.throw(frappe.get_traceback()) + + +def add_account_subtype(account_subtype): + try: + frappe.get_doc({ + "doctype": "Account Subtype", + "account_subtype": account_subtype + }).insert() + except: + frappe.throw(frappe.get_traceback()) + +@frappe.whitelist() +def sync_transactions(bank, bank_account=None): + + last_sync_date = frappe.db.get_value("Plaid Settings", None, "last_sync_date") + if last_sync_date: + start_date = formatdate(last_sync_date, "YYYY-MM-dd") + else: + start_date = formatdate(add_months(today(), -12), "YYYY-MM-dd") + end_date = formatdate(today(), "YYYY-MM-dd") + + try: + transactions = get_transactions(bank=bank, bank_account=bank_account, start_date=start_date, end_date=end_date) + result = [] + if transactions: + for transaction in transactions: + result.append(new_bank_transaction(transaction)) + + frappe.db.set_value("Plaid Settings", None, "last_sync_date", getdate(end_date)) + + return result + except Exception: + frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) + + +def get_transactions(bank, bank_account=None, start_date=None, end_date=None): + access_token = None + + if bank_account: + related_bank = frappe.db.get_values("Bank Account", dict(account_name=bank_account), ["bank", "integration_id"], as_dict=True) + access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token") + account_id = related_bank[0].integration_id + + else: + access_token = frappe.db.get_value("Bank", bank, "plaid_access_token") + account_id = None + + plaid = PlaidConnector(access_token) + transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) + + return transactions + +def new_bank_transaction(transaction): + result = [] + + bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"])) + + if float(transaction["amount"]) >= 0: + debit = float(transaction["amount"]) + credit = 0 + else: + debit = 0 + credit = abs(float(transaction["amount"])) + + status = "Pending" if transaction["pending"] == "True" else "Settled" + + if not frappe.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])): + try: + new_transaction = frappe.get_doc({ + "doctype": "Bank Transaction", + "date": getdate(transaction["date"]), + "status": status, + "bank_account": bank_account, + "debit": debit, + "credit": credit, + "currency": transaction["iso_currency_code"], + "description": transaction["name"] + }) + new_transaction.insert() + + result.append(new_transaction.name) + + except Exception: + frappe.throw(frappe.get_traceback()) + + return result diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js new file mode 100644 index 00000000000..dc91347336d --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js @@ -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: Plaid Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Plaid Settings + () => frappe.tests.make('Plaid Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py new file mode 100644 index 00000000000..fdf285632ee --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -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 frappe +import unittest + +class TestPlaidSettings(unittest.TestCase): + pass diff --git a/erpnext/public/build.json b/erpnext/public/build.json index c34eef25080..aa059d4e91e 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,58 +1,63 @@ { - "css/erpnext.css": [ - "public/less/erpnext.less", - "public/less/hub.less" - ], - "css/marketplace.css": [ - "public/less/hub.less" - ], - "js/erpnext-web.min.js": [ - "public/js/website_utils.js", - "public/js/shopping_cart.js" - ], + "css/erpnext.css": [ + "public/less/erpnext.less", + "public/less/hub.less", + "public/less/bankreconciliation.less" + ], + "css/marketplace.css": [ + "public/less/hub.less" + ], + "js/erpnext-web.min.js": [ + "public/js/website_utils.js", + "public/js/shopping_cart.js" + ], "css/erpnext-web.css": [ "public/less/website.less" ], - "js/marketplace.min.js": [ - "public/js/hub/marketplace.js" - ], - "js/erpnext.min.js": [ - "public/js/conf.js", - "public/js/utils.js", - "public/js/queries.js", - "public/js/sms_manager.js", - "public/js/utils/party.js", - "public/js/templates/address_list.html", - "public/js/templates/contact_list.html", - "public/js/controllers/stock_controller.js", - "public/js/payment/payments.js", - "public/js/controllers/taxes_and_totals.js", - "public/js/controllers/transaction.js", - "public/js/pos/pos.html", - "public/js/pos/pos_bill_item.html", - "public/js/pos/pos_bill_item_new.html", - "public/js/pos/pos_selected_item.html", - "public/js/pos/pos_item.html", - "public/js/pos/pos_tax_row.html", - "public/js/pos/customer_toolbar.html", - "public/js/pos/pos_invoice_list.html", - "public/js/payment/pos_payment.html", - "public/js/payment/payment_details.html", - "public/js/templates/item_selector.html", + "js/marketplace.min.js": [ + "public/js/hub/marketplace.js" + ], + "js/bankreconciliation.min.js": [ + "public/js/reconciliation/reconciliation_home.js" + ], + "js/erpnext.min.js": [ + "public/js/conf.js", + "public/js/utils.js", + "public/js/queries.js", + "public/js/sms_manager.js", + "public/js/utils/party.js", + "public/js/templates/address_list.html", + "public/js/templates/contact_list.html", + "public/js/controllers/stock_controller.js", + "public/js/payment/payments.js", + "public/js/controllers/taxes_and_totals.js", + "public/js/controllers/transaction.js", + "public/js/pos/pos.html", + "public/js/pos/pos_bill_item.html", + "public/js/pos/pos_bill_item_new.html", + "public/js/pos/pos_selected_item.html", + "public/js/pos/pos_item.html", + "public/js/pos/pos_tax_row.html", + "public/js/pos/customer_toolbar.html", + "public/js/pos/pos_invoice_list.html", + "public/js/payment/pos_payment.html", + "public/js/payment/payment_details.html", + "public/js/templates/item_selector.html", "public/js/templates/employees_to_mark_attendance.html", - "public/js/utils/item_selector.js", - "public/js/help_links.js", - "public/js/agriculture/ternary_plot.js", - "public/js/templates/item_quick_entry.html", - "public/js/utils/item_quick_entry.js", + "public/js/utils/item_selector.js", + "public/js/help_links.js", + "public/js/agriculture/ternary_plot.js", + "public/js/templates/item_quick_entry.html", + "public/js/utils/item_quick_entry.js", "public/js/utils/customer_quick_entry.js", - "public/js/education/student_button.html", - "public/js/education/assessment_result_tool.html", - "public/js/hub/hub_factory.js" - ], - "js/item-dashboard.min.js": [ - "stock/dashboard/item_dashboard.html", - "stock/dashboard/item_dashboard_list.html", - "stock/dashboard/item_dashboard.js" - ] + "public/js/education/student_button.html", + "public/js/education/assessment_result_tool.html", + "public/js/hub/hub_factory.js", + "public/js/reconciliation/reconciliation_factory.js" + ], + "js/item-dashboard.min.js": [ + "stock/dashboard/item_dashboard.html", + "stock/dashboard/item_dashboard_list.html", + "stock/dashboard/item_dashboard.js" + ] } diff --git a/erpnext/public/js/reconciliation/Home.vue b/erpnext/public/js/reconciliation/Home.vue new file mode 100644 index 00000000000..57e6cefd694 --- /dev/null +++ b/erpnext/public/js/reconciliation/Home.vue @@ -0,0 +1,81 @@ + + + diff --git a/erpnext/public/js/reconciliation/Sidebar.vue b/erpnext/public/js/reconciliation/Sidebar.vue new file mode 100644 index 00000000000..e99bf978b2a --- /dev/null +++ b/erpnext/public/js/reconciliation/Sidebar.vue @@ -0,0 +1,49 @@ + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/AccountCard.vue b/erpnext/public/js/reconciliation/components/AccountCard.vue new file mode 100644 index 00000000000..99fda80626c --- /dev/null +++ b/erpnext/public/js/reconciliation/components/AccountCard.vue @@ -0,0 +1,78 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/BankAccountsContainer.vue b/erpnext/public/js/reconciliation/components/BankAccountsContainer.vue new file mode 100644 index 00000000000..099a73755cb --- /dev/null +++ b/erpnext/public/js/reconciliation/components/BankAccountsContainer.vue @@ -0,0 +1,71 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/EmptyState.vue b/erpnext/public/js/reconciliation/components/EmptyState.vue new file mode 100644 index 00000000000..490a67eb655 --- /dev/null +++ b/erpnext/public/js/reconciliation/components/EmptyState.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/NewAccountCard.vue b/erpnext/public/js/reconciliation/components/NewAccountCard.vue new file mode 100644 index 00000000000..bf311ac44a0 --- /dev/null +++ b/erpnext/public/js/reconciliation/components/NewAccountCard.vue @@ -0,0 +1,71 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/PlaidLink.vue b/erpnext/public/js/reconciliation/components/PlaidLink.vue new file mode 100644 index 00000000000..40f486c9d1d --- /dev/null +++ b/erpnext/public/js/reconciliation/components/PlaidLink.vue @@ -0,0 +1,136 @@ + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/TransactionCard.vue b/erpnext/public/js/reconciliation/components/TransactionCard.vue new file mode 100644 index 00000000000..96141116947 --- /dev/null +++ b/erpnext/public/js/reconciliation/components/TransactionCard.vue @@ -0,0 +1,41 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/TransactionsContainer.vue b/erpnext/public/js/reconciliation/components/TransactionsContainer.vue new file mode 100644 index 00000000000..8c25c146071 --- /dev/null +++ b/erpnext/public/js/reconciliation/components/TransactionsContainer.vue @@ -0,0 +1,72 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/pages/Dashboard.vue b/erpnext/public/js/reconciliation/pages/Dashboard.vue new file mode 100644 index 00000000000..f60a75ece5f --- /dev/null +++ b/erpnext/public/js/reconciliation/pages/Dashboard.vue @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/pages/Reconciliation.vue b/erpnext/public/js/reconciliation/pages/Reconciliation.vue new file mode 100644 index 00000000000..b946e1be580 --- /dev/null +++ b/erpnext/public/js/reconciliation/pages/Reconciliation.vue @@ -0,0 +1,155 @@ + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/pages/Upload.vue b/erpnext/public/js/reconciliation/pages/Upload.vue new file mode 100644 index 00000000000..f88b42929e6 --- /dev/null +++ b/erpnext/public/js/reconciliation/pages/Upload.vue @@ -0,0 +1,115 @@ + + + + \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/reconciliation_factory.js b/erpnext/public/js/reconciliation/reconciliation_factory.js new file mode 100644 index 00000000000..b76722f6ff8 --- /dev/null +++ b/erpnext/public/js/reconciliation/reconciliation_factory.js @@ -0,0 +1,29 @@ +frappe.provide('erpnext.bankreconciliation'); + +frappe.views.bankreconciliationFactory = class bankreconciliationFactory extends frappe.views.Factory { + show() { + if (frappe.pages.bankreconciliation) { + frappe.container.change_to('bankreconciliation'); + } else { + this.make('bankreconciliation'); + } + } + make(page_name) { + const assets = [ + '/assets/js/bankreconciliation.min.js' + ]; + frappe.require(assets, () => { + erpnext.bankreconciliation.home = new erpnext.bankreconciliation.Home({ + parent: this.make_page(true, page_name) + }); + }); + } +}; + +$(document).on('toolbar_setup', () => { + $('#toolbar-user .navbar-reload').after(` +
  • + ${__("Bank Reconciliation")} +
  • + `); +}); \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/reconciliation_home.js b/erpnext/public/js/reconciliation/reconciliation_home.js new file mode 100644 index 00000000000..fb7fe647149 --- /dev/null +++ b/erpnext/public/js/reconciliation/reconciliation_home.js @@ -0,0 +1,100 @@ +import Vue from 'vue/dist/vue.js'; +import './vue-plugins'; + +import Home from './Home.vue'; +import Sidebar from './Sidebar.vue'; + +import EventEmitter from '../hub/event_emitter'; + +frappe.provide('erpnext.bankreconciliation'); +frappe.provide('frappe.route'); +frappe.provide('frappe.upload'); + +$.extend(erpnext.bankreconciliation, EventEmitter.prototype); +$.extend(frappe.route, EventEmitter.prototype); + +erpnext.bankreconciliation.Home = class bankreconciliation { + constructor({ parent }) { + this.$parent = $(parent); + this.page = parent.page; + this.company = frappe.defaults.get_user_default("Company"); + this.setup_header(); + this.make_sidebar(); + this.make_body(); + this.setup_events(); + this.set_secondary_action(); + } + + make_sidebar() { + this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); + + new Vue({ + el: $('
    ').appendTo(this.$sidebar)[0], + render: h => h(Sidebar) + }); + } + + make_body() { + let me = this; + me.$body = me.$parent.find('.layout-main-section'); + me.$page_container = $('
    ').appendTo(this.$body); + + new Vue({ + el: me.$page_container[0], + render(h) { + return h(Home, {props: { initCompany: me.company}}) + } + }); + } + + setup_header() { + this.page.set_title(__('Bank Reconciliation')); + } + + setup_events() { + this.$parent.on('click', '[data-route]', (e) => { + const $target = $(e.currentTarget); + const route = $target.data().route; + frappe.set_route(route); + }); + + this.$parent.on('click', '[data-action]', e => { + const $target = $(e.currentTarget); + const action = $target.data().action; + + if (action && this[action]) { + this[action].apply(this, $target); + } + }) + } + + set_secondary_action() { + let me = this; + this.page.set_secondary_action(this.company, function () { + me.company_selection_dialog(); + }) + } + + company_selection_dialog() { + let me = this; + let dialog = new frappe.ui.Dialog({ + title: __('Select another company'), + fields: [ + { + "label": "Company", + "fieldname": "company", + "fieldtype": "Link", + "options": "Company" + } + ], + primary_action_label: __('Confirm'), + primary_action: function(v) { + me.company = v.company; + erpnext.bankreconciliation.trigger('company_changed', v.company); + me.set_secondary_action(); + dialog.hide(); + }, + }) + dialog.show(); + } +}; \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/vue-plugins.js b/erpnext/public/js/reconciliation/vue-plugins.js new file mode 100644 index 00000000000..708d179177d --- /dev/null +++ b/erpnext/public/js/reconciliation/vue-plugins.js @@ -0,0 +1,17 @@ +import Vue from 'vue/dist/vue.js'; + +Vue.prototype.__ = window.__; +Vue.prototype.frappe = window.frappe; + +Vue.directive('route', { + bind(el, binding) { + const route = binding.value; + if (!route) return; + el.classList.add('cursor-pointer'); + el.dataset.route = route; + el.addEventListener('click', () => frappe.set_route(route)); + }, + unbind(el) { + el.classList.remove('cursor-pointer'); + } +}); \ No newline at end of file diff --git a/erpnext/public/less/bankreconciliation.less b/erpnext/public/less/bankreconciliation.less new file mode 100644 index 00000000000..35c144f48e4 --- /dev/null +++ b/erpnext/public/less/bankreconciliation.less @@ -0,0 +1,71 @@ + +@import "variables.less"; +@import (reference) 'common.less'; + +body[data-route*="bankreconciliation"] { + + .layout-side-section { + padding-top: 25px; + padding-left: 5px; + padding-right: 25px; + } + + [data-route], [data-action] { + cursor: pointer; + } + + .layout-main-section { + border: none; + font-size: @text-medium; + padding-top: 25px; + + @media (max-width: @screen-xs) { + padding-left: 20px; + padding-right: 20px; + } + } + + input, textarea { + font-size: @text-medium; + } + + .bankreconciliation-sidebar { + padding-top: 25px; + padding-right: 15px; + } + + .bankreconciliation-sidebar-group { + margin-bottom: 10px; + } + + .bankreconciliation-sidebar-item { + padding: 5px 8px; + margin-bottom: 3px; + border-radius: 4px; + border: 1px solid transparent; + + &.active, &:hover:not(.is-title) { + border-color: @border-color; + } + } + + .form-container { + .frappe-control { + max-width: 100% !important; + } + } + + .upload-btn-container { + margin-top: 20px; + } + + .account-card { + .selected { + background-color: #e2f4d0; + } + } + + .table-container { + margin-top: 20px; + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0fee6c10847..28ba9f676ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ python-stdnum braintree gocardless_pro woocommerce -pandas \ No newline at end of file +pandas +plaid-python \ No newline at end of file From 09cad814cd707c56ca965c63cbde3269946714f1 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 16 Nov 2018 09:41:56 +0000 Subject: [PATCH 02/29] Reconciliation dashboard wip --- .../doctype/bank_account/bank_account.json | 34 +- .../PlaidLink.vue | 0 .../bank_reconciliation_dashboard/__init__.py | 0 .../bank_reconciliation_dashboard.js | 217 +++++++ .../bank_reconciliation_dashboard.json | 539 ++++++++++++++++++ .../bank_reconciliation_dashboard.py | 10 + .../test_bank_reconciliation_dashboard.js | 23 + .../test_bank_reconciliation_dashboard.py | 10 + .../plaid_settings/plaid_settings.json | 66 +-- .../doctype/plaid_settings/plaid_settings.py | 4 +- erpnext/public/build.json | 9 +- 11 files changed, 837 insertions(+), 75 deletions(-) rename erpnext/{public/js/reconciliation/components => accounts/doctype/bank_reconciliation_dashboard}/PlaidLink.vue (100%) create mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/__init__.py create mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js create mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json create mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py create mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js create mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 5a20cef094a..f2aa408b3b2 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -853,6 +853,38 @@ "translatable": 0, "unique": 1 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "last_integration_date", + "fieldtype": "Date", + "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": "Last Integration Date", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -927,7 +959,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-26 14:14:45.049414", + "modified": "2018-11-15 17:37:10.340070", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/public/js/reconciliation/components/PlaidLink.vue b/erpnext/accounts/doctype/bank_reconciliation_dashboard/PlaidLink.vue similarity index 100% rename from erpnext/public/js/reconciliation/components/PlaidLink.vue rename to erpnext/accounts/doctype/bank_reconciliation_dashboard/PlaidLink.vue diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/__init__.py b/erpnext/accounts/doctype/bank_reconciliation_dashboard/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js new file mode 100644 index 00000000000..69e91a8ab92 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js @@ -0,0 +1,217 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.provide("erpnext.accounts"); + + +frappe.ui.form.on('Bank Reconciliation Dashboard', { + refresh: function(frm) { + frm.disable_save(); + toggle_sidebar(frm); + frm.page.add_menu_item(__("Toggle Sidebar"), function() { + toggle_sidebar(frm); + }); + + new erpnext.accounts.newInstitution(frm); + }, + import_data: function(frm) { + new erpnext.accounts.bankTransactionUpload(frm); + }, + sync_data: function(frm) { + new erpnext.accounts.bankTransactionSync(frm); + }, + reconcile_data: function(frm) { + console.log("test") + }, + +}); + +let toggle_sidebar = function(frm) { + frm.sidebar.sidebar.toggle(); + frm.page.current_view.find('.layout-main-section-wrapper').toggleClass('col-md-10 col-md-12'); +} + +erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { + constructor(frm) { + this.frm = frm; + this.data = []; + this.import_wrapper = $(frm.fields_dict['import_html'].wrapper); + this.table_container = $(frm.fields_dict['table_container'].wrapper); + + const assets = [ + "/assets/frappe/css/frappe-datatable.css", + "/assets/frappe/js/lib/clusterize.min.js", + "/assets/frappe/js/lib/Sortable.min.js", + "/assets/frappe/js/lib/frappe-datatable.js" + ]; + + frappe.require(assets, () => { + this.make(); + }); + } + + make() { + let me = this; + frappe.upload.make({ + parent: me.import_wrapper, + args: { + method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', + allow_multiple: 0 + }, + no_socketio: true, + sample_url: "e.g. http://example.com/somefile.csv", + callback: function(attachment, r) { + if (!r.exc && r.message) { + me.data = r.message; + me.setup_transactions_dom(); + me.create_datatable(); + me.bind_events(); + } + } + }) + } + + setup_transactions_dom() { + this.table_container.append(` +
    +
    + +
    `) + } + + create_datatable() { + this.datatable = new DataTable('.transactions-table', { + columns: this.data.columns, + data: this.data.data + }) + } + + bind_events() { + this.table_container.on('click', '.transactions-btn', function() { + console.log("Test") + }) + } + + add_bank_entries() { + frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries', + {columns: this.data.datamanager.columns, data: this.data.datamanager.data, bank_account: this.frm.doc.bank_account} + ).then((result) => { + console.log(result) + }) + } +} + +erpnext.accounts.bankTransactionSync = class bankTransactionSync { + constructor(frm) { + this.frm = frm; + this.data = []; + this.import_wrapper = $(frm.fields_dict['import_html'].wrapper); + this.table_container = $(frm.fields_dict['table_container'].wrapper); + + + this.init_config() + const assets = [ + "/assets/frappe/css/frappe-datatable.css", + "/assets/frappe/js/lib/clusterize.min.js", + "/assets/frappe/js/lib/Sortable.min.js", + "/assets/frappe/js/lib/frappe-datatable.js" + ]; + + frappe.require(assets, () => { + this.make(); + }); + } + + init_config() { + let me = this; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') + .then(result => { + me.plaid_env = result.plaid_env; + me.plaid_public_key = result.plaid_public_key; + me.client_name = result.client_name; + me.sync_transactions() + }) + } + + sync_transactions() { + let me = this; + frappe.db.get_value("Bank Account", me.frm.doc.bank_account, "bank", (v) => { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', { + bank: v['bank'], + bank_account: me.frm.doc.bank_account + }) + .then((result) => { + console.log(result) + me.get_transactions(); + }) + }) + } + + get_transactions() { + let me = this; + frappe.db.get_list('Bank Transaction', { + fields: ['name', 'date', 'status', 'debit', 'credit', 'currency', 'description'], + filters: {"docstatus": 1}, + or_filters: [['reference_number', '=', '']] + + }).then((transactions) => { + me.transactions = transactions; + console.log(me) + }) + } + + make() { + + } +} + +erpnext.accounts.newInstitution = class newInstitution { + constructor(frm) { + this.frm = frm; + this.init_config() + } + + init_config() { + let me = this; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') + .then(result => { + if (result) { + me.plaid_env = result.plaid_env; + me.plaid_public_key = result.plaid_public_key; + me.client_name = result.client_name; + me.new_plaid_link() + } + }) + } + + plaid_success(token, response) { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response}) + .then((result) => { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, bank: result}) + }) + .then((result) => { + this.getBankAccounts(); + }) + } + + new_plaid_link() { + let me = this; + frappe.require('assets/js/frappe-vue.js', () => { + new Vue({ + el: $(frm.fields_dict['new_institution'].wrapper), + render(h) { + return h(PlaidLink, { + props: { + env: me.plaid_env, + publicKey: me.plaid_public_key, + clientName: me.client_name, + product: ["transactions", "auth"], + subtitle: "Test", + plaidSuccess: me.plaid_success + } + }) + } + }); + }) + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json new file mode 100644 index 00000000000..36a7e590636 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json @@ -0,0 +1,539 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2018-11-14 17:30:33.401641", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_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 a bank account", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bank_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": "Bank Account", + "length": 0, + "no_copy": 0, + "options": "Bank 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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "new_institution", + "fieldtype": "HTML", + "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": "Add a new institution/account", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.bank_account", + "fieldname": "section_break_2", + "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 an action", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "import_data", + "fieldtype": "Button", + "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": "Import Data", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sync_data", + "fieldtype": "Button", + "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": "Synchronize Data", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reconcile_data", + "fieldtype": "Button", + "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": "Reconcile Data", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "action_section", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "import_html", + "fieldtype": "HTML", + "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": "Bank Statement Import", + "length": 0, + "no_copy": 0, + "options": "", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reconcile_html", + "fieldtype": "HTML", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_10", + "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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "table_container", + "fieldtype": "HTML", + "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, + "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, + "translatable": 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": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2018-11-15 18:02:39.720945", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Reconciliation Dashboard", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "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": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py new file mode 100644 index 00000000000..03274408f94 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py @@ -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 +import frappe +from frappe.model.document import Document + +class BankReconciliationDashboard(Document): + pass diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js b/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js new file mode 100644 index 00000000000..3022a509876 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js @@ -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: Bank Reconciliation Dashboard", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Bank Reconciliation Dashboard + () => frappe.tests.make('Bank Reconciliation Dashboard', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py b/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py new file mode 100644 index 00000000000..9b7ede2e607 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py @@ -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 frappe +import unittest + +class TestBankReconciliationDashboard(unittest.TestCase): + pass diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index 570bd1b3723..9ede81a393a 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -77,70 +77,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.enabled==1", - "fieldname": "section_break_3", - "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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "last_sync_date", - "fieldtype": "Date", - "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": "Last Synchronization Date", - "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, - "translatable": 0, - "unique": 0 } ], "has_web_view": 0, @@ -153,7 +89,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-10-26 17:28:47.689735", + "modified": "2018-11-15 17:37:48.531027", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Plaid Settings", diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index f823a0fc492..476e56d5c45 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -110,7 +110,7 @@ def add_account_subtype(account_subtype): @frappe.whitelist() def sync_transactions(bank, bank_account=None): - last_sync_date = frappe.db.get_value("Plaid Settings", None, "last_sync_date") + last_sync_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date") if last_sync_date: start_date = formatdate(last_sync_date, "YYYY-MM-dd") else: @@ -124,7 +124,7 @@ def sync_transactions(bank, bank_account=None): for transaction in transactions: result.append(new_bank_transaction(transaction)) - frappe.db.set_value("Plaid Settings", None, "last_sync_date", getdate(end_date)) + frappe.db.set_value("Bank Account", bank_account, "last_integration_date", getdate(end_date)) return result except Exception: diff --git a/erpnext/public/build.json b/erpnext/public/build.json index aa059d4e91e..1f702e69cca 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,8 +1,7 @@ { "css/erpnext.css": [ "public/less/erpnext.less", - "public/less/hub.less", - "public/less/bankreconciliation.less" + "public/less/hub.less" ], "css/marketplace.css": [ "public/less/hub.less" @@ -17,9 +16,6 @@ "js/marketplace.min.js": [ "public/js/hub/marketplace.js" ], - "js/bankreconciliation.min.js": [ - "public/js/reconciliation/reconciliation_home.js" - ], "js/erpnext.min.js": [ "public/js/conf.js", "public/js/utils.js", @@ -52,8 +48,7 @@ "public/js/utils/customer_quick_entry.js", "public/js/education/student_button.html", "public/js/education/assessment_result_tool.html", - "public/js/hub/hub_factory.js", - "public/js/reconciliation/reconciliation_factory.js" + "public/js/hub/hub_factory.js" ], "js/item-dashboard.min.js": [ "stock/dashboard/item_dashboard.html", From 6025e498f2d8bbb266da9ad2b4024dd2f04b5a3e Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 27 Nov 2018 16:07:13 +0000 Subject: [PATCH 03/29] Bank reconciliation wip --- erpnext/accounts/doctype/bank/bank.json | 6 +- .../doctype/bank_account/bank_account.json | 11 +- .../doctype/bank_account/bank_account.py | 3 + .../PlaidLink.vue | 136 ----- .../bank_reconciliation_dashboard.js | 217 ------- .../bank_reconciliation_dashboard.json | 539 ------------------ .../bank_reconciliation_dashboard.py | 10 - .../test_bank_reconciliation_dashboard.js | 23 - .../test_bank_reconciliation_dashboard.py | 10 - .../bank_transaction/bank_transaction.json | 42 +- .../bank_transaction_upload.py | 12 +- .../bank_reconciliation}/__init__.py | 0 .../bank_reconciliation.js | 491 ++++++++++++++++ .../bank_reconciliation.json | 29 + .../bank_reconciliation.py | 109 ++++ .../bank_transaction_header.html | 21 + .../bank_transaction_row.html | 32 ++ .../linked_payment_row.html | 19 + erpnext/config/accounts.py | 14 + .../doctype/plaid_settings/plaid_connector.py | 1 - .../doctype/plaid_settings/plaid_settings.js | 2 +- .../doctype/plaid_settings/plaid_settings.py | 18 +- erpnext/public/js/reconciliation/Home.vue | 81 --- erpnext/public/js/reconciliation/Sidebar.vue | 49 -- .../reconciliation/components/AccountCard.vue | 78 --- .../components/BankAccountsContainer.vue | 71 --- .../reconciliation/components/EmptyState.vue | 45 -- .../components/NewAccountCard.vue | 71 --- .../components/TransactionCard.vue | 41 -- .../components/TransactionsContainer.vue | 72 --- .../js/reconciliation/pages/Dashboard.vue | 25 - .../reconciliation/pages/Reconciliation.vue | 155 ----- .../public/js/reconciliation/pages/Upload.vue | 115 ---- .../reconciliation/reconciliation_factory.js | 29 - .../js/reconciliation/reconciliation_home.js | 100 ---- .../public/js/reconciliation/vue-plugins.js | 17 - erpnext/public/less/bankreconciliation.less | 71 --- erpnext/public/less/erpnext.less | 35 +- 38 files changed, 817 insertions(+), 1983 deletions(-) delete mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/PlaidLink.vue delete mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js delete mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json delete mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py delete mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js delete mode 100644 erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py rename erpnext/accounts/{doctype/bank_reconciliation_dashboard => page/bank_reconciliation}/__init__.py (100%) create mode 100644 erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js create mode 100644 erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json create mode 100644 erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py create mode 100644 erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html create mode 100644 erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html create mode 100644 erpnext/accounts/page/bank_reconciliation/linked_payment_row.html delete mode 100644 erpnext/public/js/reconciliation/Home.vue delete mode 100644 erpnext/public/js/reconciliation/Sidebar.vue delete mode 100644 erpnext/public/js/reconciliation/components/AccountCard.vue delete mode 100644 erpnext/public/js/reconciliation/components/BankAccountsContainer.vue delete mode 100644 erpnext/public/js/reconciliation/components/EmptyState.vue delete mode 100644 erpnext/public/js/reconciliation/components/NewAccountCard.vue delete mode 100644 erpnext/public/js/reconciliation/components/TransactionCard.vue delete mode 100644 erpnext/public/js/reconciliation/components/TransactionsContainer.vue delete mode 100644 erpnext/public/js/reconciliation/pages/Dashboard.vue delete mode 100644 erpnext/public/js/reconciliation/pages/Reconciliation.vue delete mode 100644 erpnext/public/js/reconciliation/pages/Upload.vue delete mode 100644 erpnext/public/js/reconciliation/reconciliation_factory.js delete mode 100644 erpnext/public/js/reconciliation/reconciliation_home.js delete mode 100644 erpnext/public/js/reconciliation/vue-plugins.js delete mode 100644 erpnext/public/less/bankreconciliation.less diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json index b7e1c1f4933..4fa0e4f29ba 100644 --- a/erpnext/accounts/doctype/bank/bank.json +++ b/erpnext/accounts/doctype/bank/bank.json @@ -151,7 +151,7 @@ "columns": 0, "fieldname": "plaid_access_token", "fieldtype": "Data", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -160,7 +160,7 @@ "in_standard_filter": 0, "label": "Plaid Access Token", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, @@ -185,7 +185,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-25 15:20:38.837772", + "modified": "2018-11-27 16:12:13.938776", "modified_by": "Administrator", "module": "Accounts", "name": "Bank", diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index f2aa408b3b2..eb911a6260e 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -4,7 +4,7 @@ "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 1, - "autoname": "field:account_name", + "autoname": "", "beta": 0, "creation": "2017-05-29 21:35:13.136357", "custom": 0, @@ -44,7 +44,7 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, - "unique": 1 + "unique": 0 }, { "allow_bulk_edit": 0, @@ -830,7 +830,7 @@ "columns": 0, "fieldname": "integration_id", "fieldtype": "Data", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -839,7 +839,7 @@ "in_standard_filter": 0, "label": "Integration ID", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, @@ -860,6 +860,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "description": "Change this date manually to setup the next synchronization start date", "fieldname": "last_integration_date", "fieldtype": "Date", "hidden": 0, @@ -959,7 +960,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-11-15 17:37:10.340070", + "modified": "2018-11-27 16:32:17.612257", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 08f8248f3d7..8f672827939 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -13,6 +13,9 @@ class BankAccount(Document): """Load address and contacts in `__onload`""" load_address_and_contact(self) + def autoname(self): + self.name = self.account_name + " - " + self.bank + def on_trash(self): delete_contact_and_address('BankAccount', self.name) diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/PlaidLink.vue b/erpnext/accounts/doctype/bank_reconciliation_dashboard/PlaidLink.vue deleted file mode 100644 index 40f486c9d1d..00000000000 --- a/erpnext/accounts/doctype/bank_reconciliation_dashboard/PlaidLink.vue +++ /dev/null @@ -1,136 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js deleted file mode 100644 index 69e91a8ab92..00000000000 --- a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.js +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.provide("erpnext.accounts"); - - -frappe.ui.form.on('Bank Reconciliation Dashboard', { - refresh: function(frm) { - frm.disable_save(); - toggle_sidebar(frm); - frm.page.add_menu_item(__("Toggle Sidebar"), function() { - toggle_sidebar(frm); - }); - - new erpnext.accounts.newInstitution(frm); - }, - import_data: function(frm) { - new erpnext.accounts.bankTransactionUpload(frm); - }, - sync_data: function(frm) { - new erpnext.accounts.bankTransactionSync(frm); - }, - reconcile_data: function(frm) { - console.log("test") - }, - -}); - -let toggle_sidebar = function(frm) { - frm.sidebar.sidebar.toggle(); - frm.page.current_view.find('.layout-main-section-wrapper').toggleClass('col-md-10 col-md-12'); -} - -erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { - constructor(frm) { - this.frm = frm; - this.data = []; - this.import_wrapper = $(frm.fields_dict['import_html'].wrapper); - this.table_container = $(frm.fields_dict['table_container'].wrapper); - - const assets = [ - "/assets/frappe/css/frappe-datatable.css", - "/assets/frappe/js/lib/clusterize.min.js", - "/assets/frappe/js/lib/Sortable.min.js", - "/assets/frappe/js/lib/frappe-datatable.js" - ]; - - frappe.require(assets, () => { - this.make(); - }); - } - - make() { - let me = this; - frappe.upload.make({ - parent: me.import_wrapper, - args: { - method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', - allow_multiple: 0 - }, - no_socketio: true, - sample_url: "e.g. http://example.com/somefile.csv", - callback: function(attachment, r) { - if (!r.exc && r.message) { - me.data = r.message; - me.setup_transactions_dom(); - me.create_datatable(); - me.bind_events(); - } - } - }) - } - - setup_transactions_dom() { - this.table_container.append(` -
    -
    - -
    `) - } - - create_datatable() { - this.datatable = new DataTable('.transactions-table', { - columns: this.data.columns, - data: this.data.data - }) - } - - bind_events() { - this.table_container.on('click', '.transactions-btn', function() { - console.log("Test") - }) - } - - add_bank_entries() { - frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries', - {columns: this.data.datamanager.columns, data: this.data.datamanager.data, bank_account: this.frm.doc.bank_account} - ).then((result) => { - console.log(result) - }) - } -} - -erpnext.accounts.bankTransactionSync = class bankTransactionSync { - constructor(frm) { - this.frm = frm; - this.data = []; - this.import_wrapper = $(frm.fields_dict['import_html'].wrapper); - this.table_container = $(frm.fields_dict['table_container'].wrapper); - - - this.init_config() - const assets = [ - "/assets/frappe/css/frappe-datatable.css", - "/assets/frappe/js/lib/clusterize.min.js", - "/assets/frappe/js/lib/Sortable.min.js", - "/assets/frappe/js/lib/frappe-datatable.js" - ]; - - frappe.require(assets, () => { - this.make(); - }); - } - - init_config() { - let me = this; - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') - .then(result => { - me.plaid_env = result.plaid_env; - me.plaid_public_key = result.plaid_public_key; - me.client_name = result.client_name; - me.sync_transactions() - }) - } - - sync_transactions() { - let me = this; - frappe.db.get_value("Bank Account", me.frm.doc.bank_account, "bank", (v) => { - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', { - bank: v['bank'], - bank_account: me.frm.doc.bank_account - }) - .then((result) => { - console.log(result) - me.get_transactions(); - }) - }) - } - - get_transactions() { - let me = this; - frappe.db.get_list('Bank Transaction', { - fields: ['name', 'date', 'status', 'debit', 'credit', 'currency', 'description'], - filters: {"docstatus": 1}, - or_filters: [['reference_number', '=', '']] - - }).then((transactions) => { - me.transactions = transactions; - console.log(me) - }) - } - - make() { - - } -} - -erpnext.accounts.newInstitution = class newInstitution { - constructor(frm) { - this.frm = frm; - this.init_config() - } - - init_config() { - let me = this; - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') - .then(result => { - if (result) { - me.plaid_env = result.plaid_env; - me.plaid_public_key = result.plaid_public_key; - me.client_name = result.client_name; - me.new_plaid_link() - } - }) - } - - plaid_success(token, response) { - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response}) - .then((result) => { - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, bank: result}) - }) - .then((result) => { - this.getBankAccounts(); - }) - } - - new_plaid_link() { - let me = this; - frappe.require('assets/js/frappe-vue.js', () => { - new Vue({ - el: $(frm.fields_dict['new_institution'].wrapper), - render(h) { - return h(PlaidLink, { - props: { - env: me.plaid_env, - publicKey: me.plaid_public_key, - clientName: me.client_name, - product: ["transactions", "auth"], - subtitle: "Test", - plaidSuccess: me.plaid_success - } - }) - } - }); - }) - } -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json deleted file mode 100644 index 36a7e590636..00000000000 --- a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.json +++ /dev/null @@ -1,539 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2018-11-14 17:30:33.401641", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_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 a bank account", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_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": "Bank Account", - "length": 0, - "no_copy": 0, - "options": "Bank 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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column 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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "new_institution", - "fieldtype": "HTML", - "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": "Add a new institution/account", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.bank_account", - "fieldname": "section_break_2", - "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 an action", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "import_data", - "fieldtype": "Button", - "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": "Import Data", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column 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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sync_data", - "fieldtype": "Button", - "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": "Synchronize Data", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column 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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reconcile_data", - "fieldtype": "Button", - "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": "Reconcile Data", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "action_section", - "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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "import_html", - "fieldtype": "HTML", - "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": "Bank Statement Import", - "length": 0, - "no_copy": 0, - "options": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reconcile_html", - "fieldtype": "HTML", - "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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_10", - "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, - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "table_container", - "fieldtype": "HTML", - "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, - "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, - "translatable": 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": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-15 18:02:39.720945", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Reconciliation Dashboard", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "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": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py b/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py deleted file mode 100644 index 03274408f94..00000000000 --- a/erpnext/accounts/doctype/bank_reconciliation_dashboard/bank_reconciliation_dashboard.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 BankReconciliationDashboard(Document): - pass diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js b/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js deleted file mode 100644 index 3022a509876..00000000000 --- a/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Reconciliation Dashboard", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Reconciliation Dashboard - () => frappe.tests.make('Bank Reconciliation Dashboard', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py b/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py deleted file mode 100644 index 9b7ede2e607..00000000000 --- a/erpnext/accounts/doctype/bank_reconciliation_dashboard/test_bank_reconciliation_dashboard.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 TestBankReconciliationDashboard(unittest.TestCase): - pass diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 7c39087dfab..6f6e97ae355 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -223,7 +223,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Debit", "length": 0, @@ -255,7 +255,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Credit", "length": 0, @@ -382,7 +382,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Description", "length": 0, @@ -526,6 +526,39 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_entry", + "fieldtype": "Link", + "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": "Payment Entry", + "length": 0, + "no_copy": 0, + "options": "Payment Entry", + "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, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -538,7 +571,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-26 15:58:53.400200", + "modified": "2018-11-27 13:26:53.794350", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", @@ -609,6 +642,7 @@ "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", + "title_field": "bank_account", "track_changes": 0, "track_seen": 0, "track_views": 0 diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py index 7e591e4a5c3..96529679954 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -32,10 +32,12 @@ def upload_bank_statement(): @frappe.whitelist() def create_bank_entries(columns, data, bank_account): - bank_account = json.loads(bank_account) header_map = get_header_mapping(columns, bank_account) + count = 0 for d in json.loads(data): + if all(item is None for item in d) is True: + continue fields = {} for key, value in header_map.iteritems(): fields.update({key: d[int(value)-1]}) @@ -46,10 +48,12 @@ def create_bank_entries(columns, data, bank_account): }) bank_transaction.update(fields) bank_transaction.date = getdate(bank_transaction.date) - bank_transaction.bank_account = bank_account["name"] + bank_transaction.bank_account = bank_account bank_transaction.insert() + bank_transaction.submit() + count = count + 1 - return 'success' + return count def get_header_mapping(columns, bank_account): mapping = get_bank_mapping(bank_account) @@ -62,7 +66,7 @@ def get_header_mapping(columns, bank_account): return header_map def get_bank_mapping(bank_account): - bank_name = frappe.db.get_value("Bank Account", bank_account["name"], "bank") + bank_name = frappe.db.get_value("Bank Account", bank_account, "bank") bank = frappe.get_doc("Bank", bank_name) mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping} diff --git a/erpnext/accounts/doctype/bank_reconciliation_dashboard/__init__.py b/erpnext/accounts/page/bank_reconciliation/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_reconciliation_dashboard/__init__.py rename to erpnext/accounts/page/bank_reconciliation/__init__.py diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js new file mode 100644 index 00000000000..58b7c4a6c1d --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -0,0 +1,491 @@ +frappe.provide("erpnext.accounts"); + +frappe.pages['bank-reconciliation'].on_page_load = function(wrapper) { + new erpnext.accounts.bankReconciliation(wrapper); +} + +erpnext.accounts.bankReconciliation = class BankReconciliation { + constructor(wrapper) { + this.page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Bank Reconciliation', + single_column: true + }); + this.parent = wrapper; + this.page = this.parent.page; + + this.make(); + this.add_plaid_btn(); + } + + make() { + const me = this; + + me.$main_section = $(`
    `).appendTo(me.page.main); + + me.page.add_field({ + fieldtype: 'Link', + label: __('Bank Account'), + fieldname: 'bank_account', + options: "Bank Account", + onchange: function() { + if (this.value) { + me.bank_account = this.value; + me.add_actions(); + } else { + me.bank_account = null; + me.page.hide_actions_menu(); + } + } + }) + } + + add_plaid_btn() { + const me = this; + frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => { + if (r.enabled == "1") { + me.parent.page.add_inner_button(__('Link a new bank account'), function() { + new erpnext.accounts.plaidLink(this) + }) + } + }) + } + + add_actions() { + const me = this; + + me.page.show_actions_menu() + + me.page.add_action_item(__("Upload a statement"), function() { + me.clear_page_content(); + new erpnext.accounts.bankTransactionUpload(me); + }, true) + me.page.add_action_item(__("Synchronize this account"), function() { + me.clear_page_content(); + new erpnext.accounts.bankTransactionSync(me); + }, true) + me.page.add_action_item(__("Reconcile this account"), function() { + me.clear_page_content(); + me.make_reconciliation_tool(); + }, true) + } + + clear_page_content() { + const me = this; + $(me.page.body).find('.frappe-list').remove(); + me.$main_section.empty(); + } + + make_reconciliation_tool() { + const me = this; + console.log(me) + frappe.model.with_doctype("Bank Transaction", () => { + new erpnext.accounts.ReconciliationTool({ + parent: me.parent, + doctype: "Bank Transaction" + }); + }) + } +} + + +erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { + constructor(parent) { + this.parent = parent; + this.data = []; + + const assets = [ + "/assets/frappe/css/frappe-datatable.css", + "/assets/frappe/js/lib/clusterize.min.js", + "/assets/frappe/js/lib/Sortable.min.js", + "/assets/frappe/js/lib/frappe-datatable.js" + ]; + + frappe.require(assets, () => { + this.make(); + }); + } + + make() { + const me = this; + frappe.upload.make({ + args: { + method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', + allow_multiple: 0 + }, + no_socketio: true, + sample_url: "e.g. http://example.com/somefile.csv", + callback: function(attachment, r) { + if (!r.exc && r.message) { + me.data = r.message; + me.setup_transactions_dom(); + me.create_datatable(); + me.add_primary_action(); + } + } + }) + } + + setup_transactions_dom() { + const me = this; + me.parent.$main_section.append(`
    `) + } + + create_datatable() { + this.datatable = new DataTable('.transactions-table', { + columns: this.data.columns, + data: this.data.data + }) + } + + add_primary_action() { + const me = this; + me.parent.page.set_primary_action(__("Submit"), function() { + me.add_bank_entries() + }, null, __("Creating bank entries...")) + } + + add_bank_entries() { + const me = this; + frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries', + {columns: this.datatable.datamanager.columns, data: this.datatable.datamanager.data, bank_account: me.parent.bank_account} + ).then((result) => { + let result_title = __("{0} bank transaction(s) created", [result]) + let result_msg = ` +
    +
    ${result_title}
    +
    ` + me.parent.page.clear_primary_action(); + me.parent.$main_section.empty(); + me.parent.$main_section.append(result_msg); + frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'}); + }) + } +} + +erpnext.accounts.bankTransactionSync = class bankTransactionSync { + constructor(parent) { + this.parent = parent; + this.data = []; + + this.init_config() + } + + init_config() { + const me = this; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') + .then(result => { + me.plaid_env = result.plaid_env; + me.plaid_public_key = result.plaid_public_key; + me.client_name = result.client_name; + me.sync_transactions() + }) + } + + sync_transactions() { + const me = this; + frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', { + bank: v['bank'], + bank_account: me.parent.bank_account, + freeze: true + }) + .then((result) => { + console.log(result) + let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized") + let result_msg = ` +
    +
    ${result_title}
    +
    ` + this.parent.$main_section.append(result_msg) + frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'}); + }) + }) + } +} + +erpnext.accounts.plaidLink = class plaidLink { + constructor(parent) { + this.parent = parent; + this.product = ["transactions", "auth"]; + this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; + this.init_config(); + } + + init_config() { + const me = this; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') + .then(result => { + if (result !== "disabled") { + me.plaid_env = result.plaid_env; + me.plaid_public_key = result.plaid_public_key; + me.client_name = result.client_name; + me.init_plaid() + } + }) + } + + init_plaid() { + const me = this; + me.loadScript(me.plaidUrl) + .then(() => { + me.onScriptLoaded(me); + }) + .then(() => { + if (me.linkHandler) { + me.linkHandler.open(); + } + }) + .catch((error) => { + me.onScriptError(error) + }) + } + + loadScript(src) { + return new Promise(function (resolve, reject) { + if (document.querySelector('script[src="' + src + '"]')) { + resolve() + return + } + const el = document.createElement('script') + el.type = 'text/javascript' + el.async = true + el.src = src + el.addEventListener('load', resolve) + el.addEventListener('error', reject) + el.addEventListener('abort', reject) + document.head.appendChild(el) + }) + } + + onScriptLoaded(me) { + me.linkHandler = window.Plaid.create({ + clientName: me.client_name, + env: me.plaid_env, + key: me.plaid_public_key, + onSuccess: me.plaid_success, + product: me.product + }) + } + + onScriptError(error) { + console.error('There was an issue loading the link-initialize.js script'); + console.log(error); + } + + plaid_success(token, response) { + const me = this; + + frappe.prompt({ + fieldtype:"Link", + options: "Company", + label:__("Company"), + fieldname:"company", + reqd:1 + }, (data) => { + me.company = data.company; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response}) + .then((result) => { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, + bank: result, company: me.company}) + }) + .then((result) => { + console.log(result) + frappe.show_alert({message:__("Bank accounts added"), indicator:'green'}); + }) + }, __("Select a company"), __("Continue")); + } +} + + +erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.views.BaseList { + constructor(opts) { + super(opts); + this.show(); + } + + setup_defaults() { + super.setup_defaults(); + + this.doctype = 'Bank Transaction'; + this.fields = ['date', 'description', 'debit', 'credit', 'currency'] + + } + + setup_view() { + this.render_header(); + } + + setup_side_bar() { + // + } + + make_standard_filters() { + // + } + + freeze() { + this.$result.find('.list-count').html(`${__('Refreshing')}...`); + } + + get_args() { + const args = super.get_args(); + + return Object.assign({}, args, { + ...args.filters.push(["Bank Transaction", "docstatus", "=", 1], + ["Bank Transaction", "payment_entry", "=", ""]) + }); + + } + + update_data(r) { + let data = r.message || []; + + if (this.start === 0) { + this.data = data; + } else { + this.data = this.data.concat(data); + } + } + + render() { + const me = this; + this.$result.find('.list-row-container').remove(); + $('[data-fieldname="name"]').remove(); + me.data.map((value) => { + const row = $('
    ').data("data", value).appendTo(me.$result).get(0); + new erpnext.accounts.ReconciliationRow(row, value); + }) + + me.parent.page.hide_menu() + } + + render_header() { + const me = this; + if ($(this.wrapper).find('.transaction-header').length === 0) { + me.$result.append(frappe.render_template("bank_transaction_header")); + } + } +} + +erpnext.accounts.ReconciliationRow = class ReconciliationRow { + constructor(row, data) { + this.data = data; + this.row = row; + this.make(); + this.bind_events(); + } + + make() { + $(this.row).append(frappe.render_template("bank_transaction_row", this.data)) + } + + bind_events() { + const me = this; + $(me.row).on('click', '.clickable-section', function() { + me.bank_entry = $(this).attr("data-name"); + me.show_dialog($(this).attr("data-name")); + }) + + $(me.row).on('click', '.new-payment', function() { + me.bank_entry = $(this).attr("data-name"); + me.new_payment(); + }) + + $(me.row).on('click', '.new-invoice', function() { + me.bank_entry = $(this).attr("data-name"); + me.new_invoice(); + }) + } + + new_payment() { + const me = this; + const paid_amount = me.data.credit > 0 ? me.data.credit : me.data.debit; + const payment_type = me.data.credit > 0 ? "Receive": "Pay"; + const party_type = me.data.credit > 0 ? "Customer": "Supplier"; + + frappe.new_doc("Payment Entry", {"payment_type": payment_type, "paid_amount": paid_amount, + "party_type": party_type, "paid_from": me.data.bank_account}) + } + + new_invoice() { + const me = this; + const invoice_type = me.data.credit > 0 ? "Sales Invoice" : "Purchase Invoice"; + + frappe.new_doc(invoice_type) + } + + show_dialog(data) { + const me = this; + frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments', + {bank_transaction: data} + ) + .then((result) => { + me.make_dialog(result) + }) + } + + make_dialog(data) { + const me = this; + const fields = [ + { + fieldtype: 'Section Break', + fieldname: 'section_break_1', + label: __('Automatic Reconciliation') + }, + { + fieldtype: 'HTML', + fieldname: 'payment_proposals' + }, + { + fieldtype: 'Section Break', + fieldname: 'section_break_2', + label: __('Search for a payment') + }, + { + fieldtype: 'Link', + fieldname: 'payment_entry', + options: 'Payment Entry', + label: 'Payment Entry' + }, + { + fieldtype: 'HTML', + fieldname: 'payment_details' + }, + ]; + + me.dialog = new frappe.ui.Dialog({ + title: __("Choose a corresponding payment"), + fields: fields + }); + + const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper; + if (data.length > 0) { + data.map(value => { + proposals_wrapper.append(frappe.render_template("linked_payment_row", value)) + }) + } else { + const empty_data_msg = __("ERPNext could not find any matching payment entry") + proposals_wrapper.append(`
    ${empty_data_msg}
    `) + } + + $(me.dialog.body).on('click', '.reconciliation-btn', (e) => { + const payment_entry = $(e.target).attr('data-name'); + frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile', + {bank_transaction: me.bank_entry, payment_entry: payment_entry}) + .then((result) => console.log(result)) + }) + + $(me.dialog.body).on('blur', '.input-with-feedback', (e) => { + e.preventDefault(); + me.dialog.fields_dict['payment_details'].$wrapper.empty(); + frappe.db.get_doc("Payment Entry", e.target.value) + .then(doc => { + const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; + details_wrapper.append(frappe.render_template("linked_payment_row", doc)); + }) + + }); + me.dialog.show(); + } +} \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json new file mode 100644 index 00000000000..feea36860b1 --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json @@ -0,0 +1,29 @@ +{ + "content": null, + "creation": "2018-11-24 12:03:14.646669", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2018-11-24 12:03:14.646669", + "modified_by": "Administrator", + "module": "Accounts", + "name": "bank-reconciliation", + "owner": "Administrator", + "page_name": "bank-reconciliation", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Bank Reconciliation" +} \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py new file mode 100644 index 00000000000..54eda914f8d --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -0,0 +1,109 @@ +# -*- 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 import _ +import difflib +from operator import itemgetter + +@frappe.whitelist() +def get_linked_payments(bank_transaction): + + transaction = frappe.get_doc("Bank Transaction", bank_transaction) + + amount_matching = check_matching_amount(transaction) + description_matching = check_matching_descriptions(transaction) + + if amount_matching: + match = check_amount_vs_description(amount_matching, description_matching) + if match: + return match + else: + return merge_matching_lists(amount_matching, description_matching) + + else: + linked_payments = get_matching_transactions_payments(description_matching) + return linked_payments + +@frappe.whitelist() +def reconcile(bank_transaction, payment_entry): + transaction = frappe.get_doc("Bank Transaction", bank_transaction) + payment_entry = frappe.get_doc("Payment Entry", payment_entry) + + if transaction.payment_entry: + frappe.throw(_("This bank transaction is already linked to a payment entry")) + + if transaction.credit > 0 and payment_entry.payment_type == "Pay": + frappe.throw(_("The selected payment entry should be linked with a debitor bank transaction")) + + if transaction.debit > 0 and payment_entry.payment_type == "Receive": + frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction")) + + frappe.db.set_value("Bank Transaction", bank_transaction, "payment_entry", payment_entry) + linked_bank_transactions = frappe.get_all("Bank Transaction", filters={"payment_entry": payment_entry, "docstatus": 1}, + fields=["sum(debit) as debit", "sum(credit) as credit"]) + cleared_amount = (linked_bank_transactions[0].credit - linked_bank_transactions[0].debit) + + if cleared_amount == payment_entry.total_allocated_amount: + frappe.db.set_value("Payment Entry", payment_entry, "clearance_date", transaction.date) + +def check_matching_amount(transaction): + amount = transaction.credit if transaction.credit > 0 else transaction.debit + payment_type = "Receive" if transaction.credit > 0 else "Pay" + + payments = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", + "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["paid_amount", "like", "{0}%".format(amount)], + ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["clearance_date", "=", ""]]) + + return payments + + +def check_matching_descriptions(transaction): + bank_transactions = frappe.get_all("Bank Transaction", fields=["name", "description", "payment_entry", "date"], + filters=[["docstatus", "=", "1"], ["payment_entry", "!=", ""]]) + + result = [] + for bank_transaction in bank_transactions: + if bank_transaction.description: + seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description) + + if seq.ratio() > 0.5: + bank_transaction["ratio"] = seq.ratio() + result.append(bank_transaction) + + return result + +def check_amount_vs_description(amount_matching, description_matching): + result = [] + for match in amount_matching: + result.append([match for x in description_matching if match["name"]==x["payment_entry"]]) + + return match + +def merge_matching_lists(amount_matching, description_matching): + + for match in amount_matching: + if match["name"] in map(itemgetter('payment_entry'), description_matching): + index = map(itemgetter('payment_entry'), description_matching).index(match["name"]) + del description_matching[index] + + linked_payments = get_matching_transactions_payments(description_matching) + + result = amount_matching.append(linked_payments) + return sorted(result, key = lambda x: x["posting_date"], reverse=True) + +def get_matching_transactions_payments(description_matching): + payments = [x["payment_entry"] for x in description_matching] + + payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching} + + if payments: + payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", + "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]]) + + return sorted(payment_list, key=lambda x: payment_by_ratio[x["name"]]) + + else: + return [] \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html new file mode 100644 index 00000000000..94f183b793b --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html @@ -0,0 +1,21 @@ +
    +
    + +
    + {{ __("Description") }} +
    + + + +
    +
    +
    +
    diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html new file mode 100644 index 00000000000..ab83ebec311 --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html @@ -0,0 +1,32 @@ +
    +
    +
    + +
    + {{ description }} +
    + + + +
    + +
    +
    diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html new file mode 100644 index 00000000000..deeca942412 --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html @@ -0,0 +1,19 @@ +
    +
    +
    + +
    + +
    +

    {{ __("Amount:") }}

    {{ format_currency(paid_amount, paid_to_account_currency) }}
    +

    {{ __("Party:") }}

    {{ party }}
    +

    {{ __("Reference:") }}

    {{ reference_no }}
    +
    +
    + +
    +
    +
    diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 9d8e1bf3c34..3a8816ea498 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -76,6 +76,14 @@ def get_data(): { "type": "doctype", "name": "Item", + }, + { + "type": "doctype", + "name": "Bank", + }, + { + "type": "doctype", + "name": "Bank Account", } ] }, @@ -135,6 +143,12 @@ def get_data(): "name": "Bank Reconciliation", "description": _("Update bank payment dates with journals.") }, + { + "type": "page", + "label": _("Reconcile payments and bank transactions"), + "name": "bank-reconciliation", + "description": _("Link bank transactions with payments.") + }, { "type": "doctype", "label": _("Match Payments with Invoices"), diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index 24bb2e9fd9e..f4eb97b32ef 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -42,7 +42,6 @@ class PlaidConnector(): def auth(self): try: - print(self.access_token) self.client.Auth.get(self.access_token) print("Authentication successful.....") except ItemError as e: diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index 18519bb9b1a..44a261946cf 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -7,6 +7,6 @@ frappe.ui.form.on('Plaid Settings', { }, connect_btn: function(frm) { - frappe.set_route('bankreconciliation/synchronization'); + frappe.set_route('bank-reconciliation'); } }); \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 476e56d5c45..17acb6a6c9d 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -16,13 +16,14 @@ class PlaidSettings(Document): @frappe.whitelist() def plaid_configuration(): - return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site } - + if frappe.db.get_value("Plaid Settings", None, "enabled") == "1": + return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site } + else: + return "disabled" @frappe.whitelist() def add_institution(token, response): response = json.loads(response) - frappe.log_error(response) plaid = PlaidConnector() access_token = plaid.get_access_token(token) @@ -46,12 +47,14 @@ def add_institution(token, response): return bank @frappe.whitelist() -def add_bank_accounts(response, bank): +def add_bank_accounts(response, bank, company): response = json.loads(response) bank = json.loads(bank) - company = "Dokos" result = [] + default_gl_account = get_default_bank_cash_account(company, "Bank") + if not default_gl_account: + frappe.throw(_("Please setup a default bank account for company {0}".format(company))) for account in response["accounts"]: acc_type = frappe.db.get_value("Account Type", account["type"]) @@ -80,6 +83,8 @@ def add_bank_accounts(response, bank): result.append(new_account.name) + except frappe.UniqueValidationError as e: + frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name)) except Exception: frappe.throw(frappe.get_traceback()) @@ -135,7 +140,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): access_token = None if bank_account: - related_bank = frappe.db.get_values("Bank Account", dict(account_name=bank_account), ["bank", "integration_id"], as_dict=True) + related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True) access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token") account_id = related_bank[0].integration_id @@ -175,6 +180,7 @@ def new_bank_transaction(transaction): "description": transaction["name"] }) new_transaction.insert() + new_transaction.submit() result.append(new_transaction.name) diff --git a/erpnext/public/js/reconciliation/Home.vue b/erpnext/public/js/reconciliation/Home.vue deleted file mode 100644 index 57e6cefd694..00000000000 --- a/erpnext/public/js/reconciliation/Home.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - diff --git a/erpnext/public/js/reconciliation/Sidebar.vue b/erpnext/public/js/reconciliation/Sidebar.vue deleted file mode 100644 index e99bf978b2a..00000000000 --- a/erpnext/public/js/reconciliation/Sidebar.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/AccountCard.vue b/erpnext/public/js/reconciliation/components/AccountCard.vue deleted file mode 100644 index 99fda80626c..00000000000 --- a/erpnext/public/js/reconciliation/components/AccountCard.vue +++ /dev/null @@ -1,78 +0,0 @@ - - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/BankAccountsContainer.vue b/erpnext/public/js/reconciliation/components/BankAccountsContainer.vue deleted file mode 100644 index 099a73755cb..00000000000 --- a/erpnext/public/js/reconciliation/components/BankAccountsContainer.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/EmptyState.vue b/erpnext/public/js/reconciliation/components/EmptyState.vue deleted file mode 100644 index 490a67eb655..00000000000 --- a/erpnext/public/js/reconciliation/components/EmptyState.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/NewAccountCard.vue b/erpnext/public/js/reconciliation/components/NewAccountCard.vue deleted file mode 100644 index bf311ac44a0..00000000000 --- a/erpnext/public/js/reconciliation/components/NewAccountCard.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/TransactionCard.vue b/erpnext/public/js/reconciliation/components/TransactionCard.vue deleted file mode 100644 index 96141116947..00000000000 --- a/erpnext/public/js/reconciliation/components/TransactionCard.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/components/TransactionsContainer.vue b/erpnext/public/js/reconciliation/components/TransactionsContainer.vue deleted file mode 100644 index 8c25c146071..00000000000 --- a/erpnext/public/js/reconciliation/components/TransactionsContainer.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/pages/Dashboard.vue b/erpnext/public/js/reconciliation/pages/Dashboard.vue deleted file mode 100644 index f60a75ece5f..00000000000 --- a/erpnext/public/js/reconciliation/pages/Dashboard.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/pages/Reconciliation.vue b/erpnext/public/js/reconciliation/pages/Reconciliation.vue deleted file mode 100644 index b946e1be580..00000000000 --- a/erpnext/public/js/reconciliation/pages/Reconciliation.vue +++ /dev/null @@ -1,155 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/pages/Upload.vue b/erpnext/public/js/reconciliation/pages/Upload.vue deleted file mode 100644 index f88b42929e6..00000000000 --- a/erpnext/public/js/reconciliation/pages/Upload.vue +++ /dev/null @@ -1,115 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/reconciliation_factory.js b/erpnext/public/js/reconciliation/reconciliation_factory.js deleted file mode 100644 index b76722f6ff8..00000000000 --- a/erpnext/public/js/reconciliation/reconciliation_factory.js +++ /dev/null @@ -1,29 +0,0 @@ -frappe.provide('erpnext.bankreconciliation'); - -frappe.views.bankreconciliationFactory = class bankreconciliationFactory extends frappe.views.Factory { - show() { - if (frappe.pages.bankreconciliation) { - frappe.container.change_to('bankreconciliation'); - } else { - this.make('bankreconciliation'); - } - } - make(page_name) { - const assets = [ - '/assets/js/bankreconciliation.min.js' - ]; - frappe.require(assets, () => { - erpnext.bankreconciliation.home = new erpnext.bankreconciliation.Home({ - parent: this.make_page(true, page_name) - }); - }); - } -}; - -$(document).on('toolbar_setup', () => { - $('#toolbar-user .navbar-reload').after(` -
  • - ${__("Bank Reconciliation")} -
  • - `); -}); \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/reconciliation_home.js b/erpnext/public/js/reconciliation/reconciliation_home.js deleted file mode 100644 index fb7fe647149..00000000000 --- a/erpnext/public/js/reconciliation/reconciliation_home.js +++ /dev/null @@ -1,100 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import './vue-plugins'; - -import Home from './Home.vue'; -import Sidebar from './Sidebar.vue'; - -import EventEmitter from '../hub/event_emitter'; - -frappe.provide('erpnext.bankreconciliation'); -frappe.provide('frappe.route'); -frappe.provide('frappe.upload'); - -$.extend(erpnext.bankreconciliation, EventEmitter.prototype); -$.extend(frappe.route, EventEmitter.prototype); - -erpnext.bankreconciliation.Home = class bankreconciliation { - constructor({ parent }) { - this.$parent = $(parent); - this.page = parent.page; - this.company = frappe.defaults.get_user_default("Company"); - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.set_secondary_action(); - } - - make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); - - new Vue({ - el: $('
    ').appendTo(this.$sidebar)[0], - render: h => h(Sidebar) - }); - } - - make_body() { - let me = this; - me.$body = me.$parent.find('.layout-main-section'); - me.$page_container = $('
    ').appendTo(this.$body); - - new Vue({ - el: me.$page_container[0], - render(h) { - return h(Home, {props: { initCompany: me.company}}) - } - }); - } - - setup_header() { - this.page.set_title(__('Bank Reconciliation')); - } - - setup_events() { - this.$parent.on('click', '[data-route]', (e) => { - const $target = $(e.currentTarget); - const route = $target.data().route; - frappe.set_route(route); - }); - - this.$parent.on('click', '[data-action]', e => { - const $target = $(e.currentTarget); - const action = $target.data().action; - - if (action && this[action]) { - this[action].apply(this, $target); - } - }) - } - - set_secondary_action() { - let me = this; - this.page.set_secondary_action(this.company, function () { - me.company_selection_dialog(); - }) - } - - company_selection_dialog() { - let me = this; - let dialog = new frappe.ui.Dialog({ - title: __('Select another company'), - fields: [ - { - "label": "Company", - "fieldname": "company", - "fieldtype": "Link", - "options": "Company" - } - ], - primary_action_label: __('Confirm'), - primary_action: function(v) { - me.company = v.company; - erpnext.bankreconciliation.trigger('company_changed', v.company); - me.set_secondary_action(); - dialog.hide(); - }, - }) - dialog.show(); - } -}; \ No newline at end of file diff --git a/erpnext/public/js/reconciliation/vue-plugins.js b/erpnext/public/js/reconciliation/vue-plugins.js deleted file mode 100644 index 708d179177d..00000000000 --- a/erpnext/public/js/reconciliation/vue-plugins.js +++ /dev/null @@ -1,17 +0,0 @@ -import Vue from 'vue/dist/vue.js'; - -Vue.prototype.__ = window.__; -Vue.prototype.frappe = window.frappe; - -Vue.directive('route', { - bind(el, binding) { - const route = binding.value; - if (!route) return; - el.classList.add('cursor-pointer'); - el.dataset.route = route; - el.addEventListener('click', () => frappe.set_route(route)); - }, - unbind(el) { - el.classList.remove('cursor-pointer'); - } -}); \ No newline at end of file diff --git a/erpnext/public/less/bankreconciliation.less b/erpnext/public/less/bankreconciliation.less deleted file mode 100644 index 35c144f48e4..00000000000 --- a/erpnext/public/less/bankreconciliation.less +++ /dev/null @@ -1,71 +0,0 @@ - -@import "variables.less"; -@import (reference) 'common.less'; - -body[data-route*="bankreconciliation"] { - - .layout-side-section { - padding-top: 25px; - padding-left: 5px; - padding-right: 25px; - } - - [data-route], [data-action] { - cursor: pointer; - } - - .layout-main-section { - border: none; - font-size: @text-medium; - padding-top: 25px; - - @media (max-width: @screen-xs) { - padding-left: 20px; - padding-right: 20px; - } - } - - input, textarea { - font-size: @text-medium; - } - - .bankreconciliation-sidebar { - padding-top: 25px; - padding-right: 15px; - } - - .bankreconciliation-sidebar-group { - margin-bottom: 10px; - } - - .bankreconciliation-sidebar-item { - padding: 5px 8px; - margin-bottom: 3px; - border-radius: 4px; - border: 1px solid transparent; - - &.active, &:hover:not(.is-title) { - border-color: @border-color; - } - } - - .form-container { - .frappe-control { - max-width: 100% !important; - } - } - - .upload-btn-container { - margin-top: 20px; - } - - .account-card { - .selected { - background-color: #e2f4d0; - } - } - - .table-container { - margin-top: 20px; - } -} \ No newline at end of file diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index b746cc138f6..0c1751657e4 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -25,10 +25,10 @@ .app-icon-svg { display: inline-block; - margin: auto; - text-align: center; - border-radius: 16px; - cursor: pointer; + margin: auto; + text-align: center; + border-radius: 16px; + cursor: pointer; box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); } @@ -459,3 +459,30 @@ body[data-route="pos"] { padding-right: 45px; } } + +// Bank Reconciliation + +.plaid-btn { + margin-top: 24px; + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} + +.transactions-btn { + margin: 15px; +} + +[data-fieldname='reconcile_data'], +[data-fieldname='sync_data'], +[data-fieldname='import_data'] { + .btn { + color: #fff; + background-color: #8d99a6; + border-color: #7f8c9b; + } +} + +[data-fieldname='table_container'] { + margin: -15px -30px; +} \ No newline at end of file From 590d8d3d3ee918b35c7281d8f89e8af24830360f Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 29 Nov 2018 09:09:30 +0000 Subject: [PATCH 04/29] Bank reconciliation wip --- .../bank_transaction/bank_transaction.json | 165 ++++++++++++++---- .../bank_transaction/bank_transaction.py | 20 ++- .../bank_transaction/bank_transaction_list.js | 13 ++ .../bank_transaction_payments/__init__.py | 0 .../bank_transaction_payments.json | 109 ++++++++++++ .../bank_transaction_payments.py | 10 ++ .../bank_reconciliation.js | 60 +++++-- .../bank_reconciliation.py | 108 ++++++++---- 8 files changed, 398 insertions(+), 87 deletions(-) create mode 100644 erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js create mode 100644 erpnext/accounts/doctype/bank_transaction_payments/__init__.py create mode 100644 erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json create mode 100644 erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 6f6e97ae355..2a29ed0d957 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -495,6 +495,134 @@ "translatable": 0, "unique": 1 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_entries", + "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": "Payment Entries", + "length": 0, + "no_copy": 0, + "options": "Bank Transaction Payments", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_17", + "fieldtype": "Column 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, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "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": "Allocated Amount", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "unallocated_amount", + "fieldtype": "Currency", + "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": "Unallocated Amount", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -526,39 +654,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_entry", - "fieldtype": "Link", - "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": "Payment Entry", - "length": 0, - "no_copy": 0, - "options": "Payment Entry", - "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, - "translatable": 0, - "unique": 0 } ], "has_web_view": 0, @@ -571,7 +666,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-11-27 13:26:53.794350", + "modified": "2018-11-28 11:05:05.087606", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", @@ -640,7 +735,7 @@ "read_only": 0, "read_only_onload": 0, "show_name_in_global_search": 0, - "sort_field": "modified", + "sort_field": "date", "sort_order": "DESC", "title_field": "bank_account", "track_changes": 0, diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index efa9093339b..195816689f0 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -4,7 +4,25 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document +from frappe.utils import flt class BankTransaction(Document): - pass + def after_insert(self): + self.unallocated_amount = abs(flt(self.credit) - flt(self.debit)) + + def on_update_after_submit(self): + linked_payments = [x.payment_entry for x in self.payment_entries] + + if linked_payments: + allocated_amount = frappe.get_all("Payment Entry", filters=[["name", "in", linked_payments]], fields=["sum(paid_amount) as total"]) + + frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount[0].total)) + frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount[0].total)) + + else: + frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0) + frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit))) + + self.reload() \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js new file mode 100644 index 00000000000..ece3cd7e435 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js @@ -0,0 +1,13 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.listview_settings['Bank Transaction'] = { + add_fields: ["unallocated_amount"], + get_indicator: function(doc) { + if(flt(doc.unallocated_amount)>0) { + return [__("Unreconciled"), "orange", "unallocated_amount,>,0"] + } else if(flt(doc.unallocated_amount)==0) { + return [__("Reconciled"), "green", "unallocated_amount,=,0"]; + } + } +}; \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction_payments/__init__.py b/erpnext/accounts/doctype/bank_transaction_payments/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json new file mode 100644 index 00000000000..7b1ad0fe2ea --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json @@ -0,0 +1,109 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-11-28 08:55:40.815355", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_document", + "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": "Payment Document", + "length": 0, + "no_copy": 0, + "options": "DocType", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_entry", + "fieldtype": "Dynamic 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": "Payment Entry", + "length": 0, + "no_copy": 0, + "options": "payment_document", + "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, + "translatable": 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-11-28 12:34:41.685571", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Transaction Payments", + "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, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py new file mode 100644 index 00000000000..88ac38fde53 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py @@ -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 +import frappe +from frappe.model.document import Document + +class BankTransactionPayments(Document): + pass diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 58b7c4a6c1d..0aaf48891b8 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -43,7 +43,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { add_plaid_btn() { const me = this; frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => { - if (r.enabled == "1") { + if (r && r.enabled == "1") { me.parent.page.add_inner_button(__('Link a new bank account'), function() { new erpnext.accounts.plaidLink(this) }) @@ -116,6 +116,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { no_socketio: true, sample_url: "e.g. http://example.com/somefile.csv", callback: function(attachment, r) { + console.log(r) if (!r.exc && r.message) { me.data = r.message; me.setup_transactions_dom(); @@ -132,10 +133,19 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { } create_datatable() { - this.datatable = new DataTable('.transactions-table', { - columns: this.data.columns, - data: this.data.data - }) + try { + this.datatable = new DataTable('.transactions-table', { + columns: this.data.columns, + data: this.data.data + }) + } + catch(err) { + let msg = __(`Your file could not be processed by ERPNext. +
    It should be a standard CSV or XLSX file. +
    The headers should be in the first row.`) + frappe.throw(msg) + } + } add_primary_action() { @@ -333,7 +343,7 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi return Object.assign({}, args, { ...args.filters.push(["Bank Transaction", "docstatus", "=", 1], - ["Bank Transaction", "payment_entry", "=", ""]) + ["Bank Transaction", "unallocated_amount", ">", 0]) }); } @@ -366,6 +376,11 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi me.$result.append(frappe.render_template("bank_transaction_header")); } } + + static trigger_list_update() { + const reconciliation_list = erpnext.accounts.ReconciliationTool; + reconciliation_list && reconciliation_list.on_update(); + } } erpnext.accounts.ReconciliationRow = class ReconciliationRow { @@ -446,7 +461,15 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { fieldtype: 'Link', fieldname: 'payment_entry', options: 'Payment Entry', - label: 'Payment Entry' + label: 'Payment Entry', + get_query: () => { + return { + filters : [ + ["Payment Entry", "ifnull(clearance_date, '')", "=", ""], + ["Payment Entry", "docstatus", "=", 1] + ] + } + } }, { fieldtype: 'HTML', @@ -473,18 +496,23 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { const payment_entry = $(e.target).attr('data-name'); frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile', {bank_transaction: me.bank_entry, payment_entry: payment_entry}) - .then((result) => console.log(result)) + .then((result) => { + erpnext.accounts.ReconciliationTool.trigger_list_update(); + me.dialog.hide(); + }) }) $(me.dialog.body).on('blur', '.input-with-feedback', (e) => { - e.preventDefault(); - me.dialog.fields_dict['payment_details'].$wrapper.empty(); - frappe.db.get_doc("Payment Entry", e.target.value) - .then(doc => { - const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; - details_wrapper.append(frappe.render_template("linked_payment_row", doc)); - }) - + if (e.target.value) { + e.preventDefault(); + me.dialog.fields_dict['payment_details'].$wrapper.empty(); + frappe.db.get_doc("Payment Entry", e.target.value) + .then(doc => { + const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; + details_wrapper.append(frappe.render_template("linked_payment_row", doc)); + }) + } + }); me.dialog.show(); } diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 54eda914f8d..caccc9911b4 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -7,25 +7,7 @@ import frappe from frappe import _ import difflib from operator import itemgetter - -@frappe.whitelist() -def get_linked_payments(bank_transaction): - - transaction = frappe.get_doc("Bank Transaction", bank_transaction) - - amount_matching = check_matching_amount(transaction) - description_matching = check_matching_descriptions(transaction) - - if amount_matching: - match = check_amount_vs_description(amount_matching, description_matching) - if match: - return match - else: - return merge_matching_lists(amount_matching, description_matching) - - else: - linked_payments = get_matching_transactions_payments(description_matching) - return linked_payments +from frappe.utils import flt @frappe.whitelist() def reconcile(bank_transaction, payment_entry): @@ -41,13 +23,49 @@ def reconcile(bank_transaction, payment_entry): if transaction.debit > 0 and payment_entry.payment_type == "Receive": frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction")) - frappe.db.set_value("Bank Transaction", bank_transaction, "payment_entry", payment_entry) + add_payment_to_transaction(transaction, payment_entry) + clear_payment_entry(transaction, payment_entry) + + return 'reconciled' + +def add_payment_to_transaction(transaction, payment_entry): + transaction.append("payment_entries", {"payment_entry": payment_entry.name}) + transaction.save() + +def clear_payment_entry(transaction, payment_entry): linked_bank_transactions = frappe.get_all("Bank Transaction", filters={"payment_entry": payment_entry, "docstatus": 1}, fields=["sum(debit) as debit", "sum(credit) as credit"]) - cleared_amount = (linked_bank_transactions[0].credit - linked_bank_transactions[0].debit) - if cleared_amount == payment_entry.total_allocated_amount: - frappe.db.set_value("Payment Entry", payment_entry, "clearance_date", transaction.date) + cleared_amount = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit)) + + if cleared_amount == payment_entry.paid_amount: + frappe.db.set_value("Payment Entry", payment_entry.name, "clearance_date", transaction.date) + +@frappe.whitelist() +def get_linked_payments(bank_transaction): + + transaction = frappe.get_doc("Bank Transaction", bank_transaction) + + # Get all payment entries with a matching amount + amount_matching = check_matching_amount(transaction) + print(amount_matching) + + # Get some data from payment entries linked to a corresponding bank transaction + description_matching = check_matching_descriptions(transaction) + print(description_matching) + + """ + if amount_matching: + match = check_amount_vs_description(amount_matching, description_matching) + if match: + return match + else: + return merge_matching_lists(amount_matching, description_matching) + + else: + linked_payments = get_matching_transactions_payments(description_matching) + return linked_payments + """ def check_matching_amount(transaction): amount = transaction.credit if transaction.credit > 0 else transaction.debit @@ -55,32 +73,52 @@ def check_matching_amount(transaction): payments = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["paid_amount", "like", "{0}%".format(amount)], - ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["clearance_date", "=", ""]]) + ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""]]) return payments - def check_matching_descriptions(transaction): - bank_transactions = frappe.get_all("Bank Transaction", fields=["name", "description", "payment_entry", "date"], - filters=[["docstatus", "=", "1"], ["payment_entry", "!=", ""]]) + bank_transactions = frappe.db.sql(""" + SELECT + bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry + FROM + `tabBank Transaction` as bt + LEFT JOIN + `tabBank Transaction Payments` as btp + ON + bt.name = btp.parent + WHERE + bt.allocated_amount > 0 + AND + bt.docstatus = 1 + """, as_dict=True) - result = [] + selection = [] for bank_transaction in bank_transactions: if bank_transaction.description: seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description) if seq.ratio() > 0.5: bank_transaction["ratio"] = seq.ratio() - result.append(bank_transaction) + selection.append(bank_transaction) - return result + document_types = set([x["payment_document"] for x in selection]) + + for document_type in document_types: + print(document_type) + + + return selection def check_amount_vs_description(amount_matching, description_matching): result = [] + print(description_matching) + print(amount_matching) for match in amount_matching: - result.append([match for x in description_matching if match["name"]==x["payment_entry"]]) - - return match + m = [match for x in description_matching.payment_entries if match["name"]==x["payment_entry"]] + result.append(m) + print(result) + return result def merge_matching_lists(amount_matching, description_matching): @@ -100,10 +138,10 @@ def get_matching_transactions_payments(description_matching): payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching} if payments: - payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", + reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]]) - return sorted(payment_list, key=lambda x: payment_by_ratio[x["name"]]) + return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]]) else: return [] \ No newline at end of file From 31cb24f48d6d57cd8ab6991622c174390f6c2c51 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 29 Nov 2018 19:55:34 +0000 Subject: [PATCH 05/29] Bank reconciliation WIP --- .../bank_transaction/bank_transaction.js | 10 +- .../bank_reconciliation.js | 137 ++++++++--- .../bank_reconciliation.py | 218 ++++++++++++++---- .../bank_transaction_row.html | 2 + .../linked_payment_row.html | 18 +- 5 files changed, 302 insertions(+), 83 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 6960570be36..4d40751b1de 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('Bank Transaction', { - refresh: function(frm) { - + onload: function(frm) { + frm.set_query('payment_document', 'payment_entries', function(doc, cdt, cdn) { + return { + "filters": { + "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice"]] + } + }; + }); } }); diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 0aaf48891b8..9059689a794 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -78,7 +78,6 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { make_reconciliation_tool() { const me = this; - console.log(me) frappe.model.with_doctype("Bank Transaction", () => { new erpnext.accounts.ReconciliationTool({ parent: me.parent, @@ -116,7 +115,6 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { no_socketio: true, sample_url: "e.g. http://example.com/somefile.csv", callback: function(attachment, r) { - console.log(r) if (!r.exc && r.message) { me.data = r.message; me.setup_transactions_dom(); @@ -432,16 +430,23 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { show_dialog(data) { const me = this; + + frappe.db.get_value("Bank Account", me.data.bank_account, "account", (r) => { + me.gl_account = r.account; + }) + frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments', - {bank_transaction: data} - ) - .then((result) => { + {bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")} + ).then((result) => { + console.log(result) me.make_dialog(result) }) } make_dialog(data) { const me = this; + me.selected_payment = null; + const fields = [ { fieldtype: 'Section Break', @@ -459,18 +464,59 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { }, { fieldtype: 'Link', - fieldname: 'payment_entry', - options: 'Payment Entry', - label: 'Payment Entry', + fieldname: 'payment_doctype', + options: 'DocType', + label: 'Payment DocType', get_query: () => { return { - filters : [ - ["Payment Entry", "ifnull(clearance_date, '')", "=", ""], - ["Payment Entry", "docstatus", "=", 1] - ] + filters : { + "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice"]] + } } } }, + { + fieldtype: 'Column Break', + fieldname: 'column_break_1', + }, + { + fieldtype: 'Dynamic Link', + fieldname: 'payment_entry', + options: 'payment_doctype', + label: 'Payment Document', + get_query: () => { + let dt = this.dialog.fields_dict.payment_doctype.value; + if (dt === "Payment Entry") { + return { + filters : [ + ["Payment Entry", "ifnull(clearance_date, '')", "=", ""], + ["Payment Entry", "docstatus", "=", 1] + ] + } + } else if (dt === "Journal Entry") { + return { + query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.journal_entry_query", + filters : { + "bank_account": this.data.bank_account + } + } + } else if (dt === "Sales Invoice") { + return { + query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query" + } + } + }, + onchange: function() { + if (me.selected_payment !== this.value) { + me.selected_payment = this.value; + me.display_payment_details(this); + } + } + }, + { + fieldtype: 'Section Break', + fieldname: 'section_break_3' + }, { fieldtype: 'HTML', fieldname: 'payment_details' @@ -479,11 +525,12 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { me.dialog = new frappe.ui.Dialog({ title: __("Choose a corresponding payment"), - fields: fields + fields: fields, + size: "large" }); const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper; - if (data.length > 0) { + if (data && data.length > 0) { data.map(value => { proposals_wrapper.append(frappe.render_template("linked_payment_row", value)) }) @@ -494,26 +541,62 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { $(me.dialog.body).on('click', '.reconciliation-btn', (e) => { const payment_entry = $(e.target).attr('data-name'); + const payment_doctype = $(e.target).attr('data-doctype'); frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile', - {bank_transaction: me.bank_entry, payment_entry: payment_entry}) + {bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_entry: payment_entry}) .then((result) => { erpnext.accounts.ReconciliationTool.trigger_list_update(); me.dialog.hide(); }) }) - $(me.dialog.body).on('blur', '.input-with-feedback', (e) => { - if (e.target.value) { - e.preventDefault(); - me.dialog.fields_dict['payment_details'].$wrapper.empty(); - frappe.db.get_doc("Payment Entry", e.target.value) - .then(doc => { - const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; - details_wrapper.append(frappe.render_template("linked_payment_row", doc)); - }) - } - - }); me.dialog.show(); } + + display_payment_details(event) { + const me = this; + if (event.value) { + let dt = me.dialog.fields_dict.payment_doctype.value; + me.dialog.fields_dict['payment_details'].$wrapper.empty(); + frappe.db.get_doc(dt, event.value) + .then(doc => { + let displayed_docs = [] + if (dt === "Payment Entry") { + doc.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency; + displayed_docs.push(doc); + } else if (dt === "Journal Entry") { + doc.accounts.forEach(payment => { + if (payment.account === me.gl_account) { + payment.posting_date = doc.posting_date; + payment.party = doc.pay_to_recd_from; + payment.reference_no = doc.cheque_no; + payment.reference_date = doc.cheque_date; + payment.currency = payment.account_currency; + payment.paid_amount = payment.credit > 0 ? payment.credit : payment.debit; + payment.name = doc.name; + displayed_docs.push(payment); + } + }) + } else if (dt === "Sales Invoice") { + doc.payments.forEach(payment => { + if (payment.clearance_date === null || payment.clearance_date === "") { + payment.posting_date = doc.posting_date; + payment.party = doc.customer; + payment.reference_no = doc.remarks; + payment.currency = doc.currency; + payment.paid_amount = payment.amount; + payment.name = doc.name; + displayed_docs.push(payment); + } + }) + } + + const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; + displayed_docs.forEach(values => { + details_wrapper.append(frappe.render_template("linked_payment_row", values)); + }) + }) + } + + } } \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index caccc9911b4..9a05448eb49 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -8,76 +8,125 @@ from frappe import _ import difflib from operator import itemgetter from frappe.utils import flt +from six import iteritems @frappe.whitelist() -def reconcile(bank_transaction, payment_entry): +def reconcile(bank_transaction, payment_doctype, payment_entry): transaction = frappe.get_doc("Bank Transaction", bank_transaction) - payment_entry = frappe.get_doc("Payment Entry", payment_entry) + payment_entry = frappe.get_doc(payment_doctype, payment_entry) - if transaction.payment_entry: - frappe.throw(_("This bank transaction is already linked to a payment entry")) + if transaction.unallocated_amount == 0: + frappe.throw(_("This bank transaction is already fully reconciled")) + """ if transaction.credit > 0 and payment_entry.payment_type == "Pay": frappe.throw(_("The selected payment entry should be linked with a debitor bank transaction")) if transaction.debit > 0 and payment_entry.payment_type == "Receive": frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction")) + """ - add_payment_to_transaction(transaction, payment_entry) - clear_payment_entry(transaction, payment_entry) + add_payment_to_transaction(transaction, payment_doctype, payment_entry) + #clear_payment_entry(transaction, payment_doctype, payment_entry) return 'reconciled' -def add_payment_to_transaction(transaction, payment_entry): - transaction.append("payment_entries", {"payment_entry": payment_entry.name}) +def add_payment_to_transaction(transaction, payment_doctype, payment_entry): + transaction.append("payment_entries", {"payment_document": payment_doctype, "payment_entry": payment_entry.name}) transaction.save() -def clear_payment_entry(transaction, payment_entry): - linked_bank_transactions = frappe.get_all("Bank Transaction", filters={"payment_entry": payment_entry, "docstatus": 1}, +def clear_payment_entry(transaction, payment_doctype, payment_entry): + pass + """ + linked_bank_transactions = frappe.get_all("Bank Transaction Payments", filters={"payment_entry": payment_entry, "docstatus": 1}, fields=["sum(debit) as debit", "sum(credit) as credit"]) cleared_amount = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit)) if cleared_amount == payment_entry.paid_amount: - frappe.db.set_value("Payment Entry", payment_entry.name, "clearance_date", transaction.date) + frappe.db.set_value(payment_doctype, payment_entry.name, "clearance_date", transaction.date) + """ @frappe.whitelist() def get_linked_payments(bank_transaction): - transaction = frappe.get_doc("Bank Transaction", bank_transaction) + bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") # Get all payment entries with a matching amount - amount_matching = check_matching_amount(transaction) + amount_matching = check_matching_amount(bank_account, transaction) print(amount_matching) # Get some data from payment entries linked to a corresponding bank transaction - description_matching = check_matching_descriptions(transaction) + description_matching = get_matching_descriptions_data(bank_account, transaction) print(description_matching) - """ if amount_matching: - match = check_amount_vs_description(amount_matching, description_matching) - if match: - return match - else: - return merge_matching_lists(amount_matching, description_matching) + return check_amount_vs_description(amount_matching, description_matching) else: - linked_payments = get_matching_transactions_payments(description_matching) - return linked_payments - """ + print("else") + #linked_payments = get_matching_transactions_payments(description_matching) + #return linked_payments -def check_matching_amount(transaction): +def check_matching_amount(bank_account, transaction): + payments = [] amount = transaction.credit if transaction.credit > 0 else transaction.debit - payment_type = "Receive" if transaction.credit > 0 else "Pay" - payments = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", - "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["paid_amount", "like", "{0}%".format(amount)], - ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""]]) + payment_type = "Receive" if transaction.credit > 0 else "Pay" + account_from_to = "paid_to" if transaction.credit > 0 else "paid_from" + currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency" + payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date", + "party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)], + ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) + + payment_field = "jea.debit_in_account_currency" if transaction.credit > 0 else "jea.credit_in_account_currency" + journal_entries = frappe.db.sql(""" + SELECT + 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, + je.pay_to_recd_from as party, je.cheque_date as reference_date, %s as paid_amount + FROM + `tabJournal Entry Account` as jea + JOIN + `tabJournal Entry` as je + ON + jea.parent = je.name + WHERE + (je.clearance_date is null or je.clearance_date='0000-00-00') + AND + jea.account = '%s' + AND + %s like '%s' + AND + je.docstatus = 1 + """ % (payment_field, bank_account, payment_field, amount), as_dict=True) + + sales_invoices = frappe.db.sql(""" + SELECT + 'Sales Invoice' as doctype, si.name, si.customer as party, + si.posting_date, sip.amount as paid_amount + FROM + `tabSales Invoice Payment` as sip + JOIN + `tabSales Invoice` as si + ON + sip.parent = si.name + WHERE + (sip.clearance_date is null or sip.clearance_date='0000-00-00') + AND + sip.account = '%s' + AND + sip.amount like '%s' + AND + si.docstatus = 1 + """ % (bank_account, amount), as_dict=True) + + for data in [payment_entries, journal_entries, sales_invoices]: + if data: + payments.extend(data) return payments -def check_matching_descriptions(transaction): +def get_matching_descriptions_data(bank_account, transaction): bank_transactions = frappe.db.sql(""" SELECT bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry @@ -104,33 +153,42 @@ def check_matching_descriptions(transaction): document_types = set([x["payment_document"] for x in selection]) + links = {} for document_type in document_types: - print(document_type) + links[document_type] = [x["payment_entry"] for x in selection if x["payment_document"]==document_type] - return selection + data = [] + for key, value in iteritems(links): + if key == "Payment Entry": + data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]], fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no", "reference_date", "paid_amount"])) + if key == "Journal Entry": + data.extend(frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["'Journal Entry' as doctype", "posting_date", "paid_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date"])) + if key == "Sales Invoice": + data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer as party"])) + #if key == "Purchase Invoice": + # data.append(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["posting_date", "customer as party"])) + + return data def check_amount_vs_description(amount_matching, description_matching): result = [] - print(description_matching) - print(amount_matching) - for match in amount_matching: - m = [match for x in description_matching.payment_entries if match["name"]==x["payment_entry"]] - result.append(m) - print(result) - return result -def merge_matching_lists(amount_matching, description_matching): - - for match in amount_matching: - if match["name"] in map(itemgetter('payment_entry'), description_matching): - index = map(itemgetter('payment_entry'), description_matching).index(match["name"]) - del description_matching[index] + if description_matching: + for am_match in amount_matching: + for des_match in description_matching: + if am_match["party"] == des_match["party"]: + result.append(am_match) + continue - linked_payments = get_matching_transactions_payments(description_matching) + if hasattr(am_match, "reference_no") and hasattr(des_match, "reference_no"): + if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]) > 70: + result.append(am_match) - result = amount_matching.append(linked_payments) - return sorted(result, key = lambda x: x["posting_date"], reverse=True) + return sorted(result, key = lambda x: x["posting_date"], reverse=True) + + else: + return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True) def get_matching_transactions_payments(description_matching): payments = [x["payment_entry"] for x in description_matching] @@ -144,4 +202,68 @@ def get_matching_transactions_payments(description_matching): return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]]) else: - return [] \ No newline at end of file + return [] + +def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): + account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") + + return frappe.db.sql(""" + SELECT + jea.parent, je.pay_to_recd_from, + if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency) + FROM + `tabJournal Entry Account` as jea + LEFT JOIN + `tabJournal Entry` as je + ON + jea.parent = je.name + WHERE + (je.clearance_date is null or je.clearance_date='0000-00-00') + AND + jea.account = %(account)s + AND + jea.parent like %(txt)s + AND + je.docstatus = 1 + ORDER BY + if(locate(%(_txt)s, jea.parent), locate(%(_txt)s, jea.parent), 99999), + jea.parent + LIMIT + %(start)s, %(page_len)s""".format(**{ + 'key': searchfield, + }), { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len, + 'account': account + } + ) + +def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): + return frappe.db.sql(""" + SELECT + sip.parent, si.customer, sip.amount, sip.mode_of_payment + FROM + `tabSales Invoice Payment` as sip + LEFT JOIN + `tabSales Invoice` as si + ON + sip.parent = si.name + WHERE + (sip.clearance_date is null or sip.clearance_date='0000-00-00') + AND + sip.parent like %(txt)s + ORDER BY + if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999), + sip.parent + LIMIT + %(start)s, %(page_len)s""".format(**{ + 'key': searchfield, + }), { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len + } + ) \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html index ab83ebec311..7f63168f80f 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html +++ b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html @@ -23,6 +23,8 @@ diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html index deeca942412..eb9648bee9b 100644 --- a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html +++ b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html @@ -4,16 +4,22 @@
    -

    {{ __("Amount:") }}

    {{ format_currency(paid_amount, paid_to_account_currency) }}
    -

    {{ __("Party:") }}

    {{ party }}
    -

    {{ __("Reference:") }}

    {{ reference_no }}
    +

    {{ __("Amount") }}

    {{ format_currency(paid_amount, currency) }}
    + {% if (typeof party !== "undefined") %} +

    {{ __("Party") }}

    {{ party }}
    + {% endif %} + {% if (typeof reference_no !== "undefined") %} +

    {{ __("Reference") }}

    {{ reference_no }}
    + {% endif %}
    - +
    From 818492387a193cf433e76bbeee36962c79a012a5 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 10 Dec 2018 13:05:46 +0000 Subject: [PATCH 06/29] WIP --- .../bank_transaction/bank_transaction.js | 2 +- .../bank_transaction/bank_transaction.py | 11 +- .../bank_transaction_upload.py | 3 +- .../bank_transaction_payments.json | 38 +++++- .../purchase_invoice/purchase_invoice.json | 36 ++++++ .../bank_reconciliation.js | 58 ++++++--- .../bank_reconciliation.py | 113 ++++++++++++------ .../linked_payment_header.html | 21 ++++ .../linked_payment_row.html | 43 ++++--- erpnext/config/integrations.py | 5 + .../plaid_settings/plaid_settings.json | 4 +- .../doctype/expense_claim/expense_claim.json | 35 +++++- 12 files changed, 289 insertions(+), 80 deletions(-) create mode 100644 erpnext/accounts/page/bank_reconciliation/linked_payment_header.html diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 4d40751b1de..511e6871d56 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -6,7 +6,7 @@ frappe.ui.form.on('Bank Transaction', { frm.set_query('payment_document', 'payment_entries', function(doc, cdt, cdn) { return { "filters": { - "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice"]] + "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]] } }; }); diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 195816689f0..f1e3f7a2126 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -13,13 +13,12 @@ class BankTransaction(Document): self.unallocated_amount = abs(flt(self.credit) - flt(self.debit)) def on_update_after_submit(self): - linked_payments = [x.payment_entry for x in self.payment_entries] + allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]) + frappe.log_error(allocated_amount) - if linked_payments: - allocated_amount = frappe.get_all("Payment Entry", filters=[["name", "in", linked_payments]], fields=["sum(paid_amount) as total"]) - - frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount[0].total)) - frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount[0].total)) + if allocated_amount: + frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount)) + frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount)) else: frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py index 96529679954..d5d8c0e0465 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe import json from frappe.utils import getdate +from frappe.utils.dateutils import parse_date @frappe.whitelist() def upload_bank_statement(): @@ -47,7 +48,7 @@ def create_bank_entries(columns, data, bank_account): "doctype": "Bank Transaction" }) bank_transaction.update(fields) - bank_transaction.date = getdate(bank_transaction.date) + bank_transaction.date = getdate(parse_date(bank_transaction.date)) bank_transaction.bank_account = bank_account bank_transaction.insert() bank_transaction.submit() diff --git a/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json index 7b1ad0fe2ea..a75e866997d 100644 --- a/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json +++ b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json @@ -40,7 +40,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -73,7 +73,39 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "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": "Allocated Amount", + "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, "translatable": 0, @@ -90,7 +122,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-11-28 12:34:41.685571", + "modified": "2018-12-06 10:57:02.635141", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction Payments", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 41b3c13a3b9..2967bf6d9b4 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -3401,6 +3401,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "clearance_date", + "fieldtype": "Date", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Clearance Date", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -4726,7 +4758,11 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, +<<<<<<< 31cb24f48d6d57cd8ab6991622c174390f6c2c51 "modified": "2019-01-07 16:51:59.800081", +======= + "modified": "2018-12-06 09:00:41.508642", +>>>>>>> WIP "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 9059689a794..5a1fa4edef5 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -8,14 +8,14 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { constructor(wrapper) { this.page = frappe.ui.make_app_page({ parent: wrapper, - title: 'Bank Reconciliation', + title: __("Bank Reconciliation"), single_column: true }); this.parent = wrapper; this.page = this.parent.page; - this.make(); this.add_plaid_btn(); + this.make(); } make() { @@ -44,9 +44,12 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { const me = this; frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => { if (r && r.enabled == "1") { + me.plaid_status = "active" me.parent.page.add_inner_button(__('Link a new bank account'), function() { new erpnext.accounts.plaidLink(this) }) + } else { + me.plaid_status = "inactive" } }) } @@ -60,10 +63,14 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { me.clear_page_content(); new erpnext.accounts.bankTransactionUpload(me); }, true) - me.page.add_action_item(__("Synchronize this account"), function() { - me.clear_page_content(); - new erpnext.accounts.bankTransactionSync(me); - }, true) + + if (me.plaid_status==="active") { + me.page.add_action_item(__("Synchronize this account"), function() { + me.clear_page_content(); + new erpnext.accounts.bankTransactionSync(me); + }, true) + } + me.page.add_action_item(__("Reconcile this account"), function() { me.clear_page_content(); me.make_reconciliation_tool(); @@ -79,7 +86,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { make_reconciliation_tool() { const me = this; frappe.model.with_doctype("Bank Transaction", () => { - new erpnext.accounts.ReconciliationTool({ + erpnext.accounts.ReconciliationList = new erpnext.accounts.ReconciliationTool({ parent: me.parent, doctype: "Bank Transaction" }); @@ -310,11 +317,13 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi constructor(opts) { super(opts); this.show(); + console.log(this) } setup_defaults() { super.setup_defaults(); + this.page_title = __("Bank Reconciliation"); this.doctype = 'Bank Transaction'; this.fields = ['date', 'description', 'debit', 'credit', 'currency'] @@ -374,11 +383,6 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi me.$result.append(frappe.render_template("bank_transaction_header")); } } - - static trigger_list_update() { - const reconciliation_list = erpnext.accounts.ReconciliationTool; - reconciliation_list && reconciliation_list.on_update(); - } } erpnext.accounts.ReconciliationRow = class ReconciliationRow { @@ -400,6 +404,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { me.show_dialog($(this).attr("data-name")); }) + $(me.row).on('click', '.new-reconciliation', function() { + me.bank_entry = $(this).attr("data-name"); + me.show_dialog($(this).attr("data-name")); + }) + $(me.row).on('click', '.new-payment', function() { me.bank_entry = $(this).attr("data-name"); me.new_payment(); @@ -438,7 +447,6 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments', {bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")} ).then((result) => { - console.log(result) me.make_dialog(result) }) } @@ -470,7 +478,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { get_query: () => { return { filters : { - "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice"]] + "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]] } } } @@ -504,6 +512,20 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { return { query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query" } + } else if (dt === "Purchase Invoice") { + return { + filters : [ + ["Purchase Invoice", "ifnull(clearance_date, '')", "=", ""], + ["Purchase Invoice", "docstatus", "=", 1] + ] + } + } else if (dt === "Expense Claim") { + return { + filters : [ + ["Expense Claim", "ifnull(clearance_date, '')", "=", ""], + ["Expense Claim", "docstatus", "=", 1] + ] + } } }, onchange: function() { @@ -531,6 +553,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper; if (data && data.length > 0) { + proposals_wrapper.append(frappe.render_template("linked_payment_header")); data.map(value => { proposals_wrapper.append(frappe.render_template("linked_payment_row", value)) }) @@ -543,9 +566,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { const payment_entry = $(e.target).attr('data-name'); const payment_doctype = $(e.target).attr('data-doctype'); frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile', - {bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_entry: payment_entry}) + {bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_name: payment_entry}) .then((result) => { - erpnext.accounts.ReconciliationTool.trigger_list_update(); + setTimeout(function(){ + erpnext.accounts.ReconciliationList.refresh(); + }, 2000); me.dialog.hide(); }) }) @@ -592,6 +617,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { } const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; + details_wrapper.append(frappe.render_template("linked_payment_header")); displayed_docs.forEach(values => { details_wrapper.append(frappe.render_template("linked_payment_row", values)); }) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 9a05448eb49..0b0f301ddaa 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -11,41 +11,77 @@ from frappe.utils import flt from six import iteritems @frappe.whitelist() -def reconcile(bank_transaction, payment_doctype, payment_entry): +def reconcile(bank_transaction, payment_doctype, payment_name): transaction = frappe.get_doc("Bank Transaction", bank_transaction) - payment_entry = frappe.get_doc(payment_doctype, payment_entry) + payment_entry = frappe.get_doc(payment_doctype, payment_name) + + account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") + gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name)) if transaction.unallocated_amount == 0: frappe.throw(_("This bank transaction is already fully reconciled")) - - """ - if transaction.credit > 0 and payment_entry.payment_type == "Pay": + + if transaction.credit > 0 and gl_entry.credit > 0: frappe.throw(_("The selected payment entry should be linked with a debitor bank transaction")) - if transaction.debit > 0 and payment_entry.payment_type == "Receive": + if transaction.debit > 0 and gl_entry.debit > 0: frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction")) - """ - add_payment_to_transaction(transaction, payment_doctype, payment_entry) - #clear_payment_entry(transaction, payment_doctype, payment_entry) + add_payment_to_transaction(transaction, payment_entry, gl_entry) + clear_payment_entry(transaction, payment_entry, gl_entry) return 'reconciled' -def add_payment_to_transaction(transaction, payment_doctype, payment_entry): - transaction.append("payment_entries", {"payment_document": payment_doctype, "payment_entry": payment_entry.name}) +def add_payment_to_transaction(transaction, payment_entry, gl_entry): + transaction.append("payment_entries", { + "payment_document": payment_entry.doctype, + "payment_entry": payment_entry.name, + "allocated_amount": gl_entry.credit if gl_entry.credit > 0 else gl_entry.debit + }) transaction.save() -def clear_payment_entry(transaction, payment_doctype, payment_entry): - pass - """ - linked_bank_transactions = frappe.get_all("Bank Transaction Payments", filters={"payment_entry": payment_entry, "docstatus": 1}, - fields=["sum(debit) as debit", "sum(credit) as credit"]) +def clear_payment_entry(transaction, payment_entry, gl_entry): + linked_bank_transactions = frappe.db.sql(""" + SELECT + bt.credit, bt.debit + FROM + `tabBank Transaction Payments` as btp + LEFT JOIN + `tabBank Transaction` as bt on btp.parent=bt.name + WHERE + btp.payment_document = '%s' + AND + btp.payment_entry = '%s' + AND + bt.docstatus = 1 + """ % (payment_entry.doctype, payment_entry.name), as_dict=True) - cleared_amount = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit)) + amount_cleared = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit)) + amount_to_be_cleared = (flt(gl_entry.credit) - flt(gl_entry.debit)) - if cleared_amount == payment_entry.paid_amount: - frappe.db.set_value(payment_doctype, payment_entry.name, "clearance_date", transaction.date) - """ + if payment_entry.doctype == "Payment Entry": + clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction) + + elif payment_entry.doctype == "Journal Entry": + clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction) + + elif payment_entry.doctype == "Sales Invoice": + clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction) + + elif payment_entry.doctype == "Purchase Invoice": + clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction) + + elif payment_entry.doctype == "Expense Claim": + clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction) + +def clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction): + if amount_cleared == amount_to_be_cleared: + frappe.db.set_value(payment_entry.doctype, payment_entry.name, "clearance_date", transaction.date) + +def clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction): + if amount_cleared == amount_to_be_cleared: + frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.doctype, + parent=payment_entry.name), "clearance_date", transaction.date) @frappe.whitelist() def get_linked_payments(bank_transaction): @@ -63,10 +99,11 @@ def get_linked_payments(bank_transaction): if amount_matching: return check_amount_vs_description(amount_matching, description_matching) + elif description_matching: + return sorted(description_matching, key = lambda x: x["posting_date"], reverse=True) + else: - print("else") - #linked_payments = get_matching_transactions_payments(description_matching) - #return linked_payments + return [] def check_matching_amount(bank_account, transaction): payments = [] @@ -147,10 +184,12 @@ def get_matching_descriptions_data(bank_account, transaction): if bank_transaction.description: seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description) - if seq.ratio() > 0.5: + if seq.ratio() > 0.6: bank_transaction["ratio"] = seq.ratio() selection.append(bank_transaction) + print(selection) + document_types = set([x["payment_document"] for x in selection]) links = {} @@ -165,9 +204,11 @@ def get_matching_descriptions_data(bank_account, transaction): if key == "Journal Entry": data.extend(frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["'Journal Entry' as doctype", "posting_date", "paid_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date"])) if key == "Sales Invoice": - data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer as party"])) - #if key == "Purchase Invoice": - # data.append(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["posting_date", "customer as party"])) + data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer_name as party"])) + if key == "Purchase Invoice": + data.append(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["'Purchase Invoice' as doctype", "posting_date", "supplier_name as party"])) + if key == "Purchase Invoice": + data.append(frappe.get_all("Expense Claim", filters=[["name", "in", value]], fields=["'Expense Claim' as doctype", "posting_date", "employee_name as party"])) return data @@ -178,14 +219,18 @@ def check_amount_vs_description(amount_matching, description_matching): for am_match in amount_matching: for des_match in description_matching: if am_match["party"] == des_match["party"]: - result.append(am_match) - continue + if am_match not in result: + result.append(am_match) + continue if hasattr(am_match, "reference_no") and hasattr(des_match, "reference_no"): if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]) > 70: - result.append(am_match) - - return sorted(result, key = lambda x: x["posting_date"], reverse=True) + if am_match not in result: + result.append(am_match) + if result: + return sorted(result, key = lambda x: x["posting_date"], reverse=True) + else: + return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True) else: return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True) @@ -222,7 +267,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): AND jea.account = %(account)s AND - jea.parent like %(txt)s + (jea.parent like %(txt)s or je.pay_to_recd_from like %(txt)s) AND je.docstatus = 1 ORDER BY @@ -253,7 +298,7 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): WHERE (sip.clearance_date is null or sip.clearance_date='0000-00-00') AND - sip.parent like %(txt)s + (sip.parent like %(txt)s or si.customer like %(txt)s) ORDER BY if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999), sip.parent diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html new file mode 100644 index 00000000000..4542c36e0dc --- /dev/null +++ b/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html @@ -0,0 +1,21 @@ +
    +
    +
    + {{ __("Payment Name") }} +
    +
    + {{ __("Reference Date") }} +
    + + +
    + {{ __("Reference Number") }} +
    +
    +
    +
    +
    diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html index eb9648bee9b..bdbc9fce033 100644 --- a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html +++ b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html @@ -1,25 +1,36 @@ -
    -
    -
    - +
    +
    +
    + {{ name }}
    - \ No newline at end of file diff --git a/erpnext/config/integrations.py b/erpnext/config/integrations.py index 01e077fba39..52c9ab8e46c 100644 --- a/erpnext/config/integrations.py +++ b/erpnext/config/integrations.py @@ -35,6 +35,11 @@ def get_data(): "type": "doctype", "name": "Amazon MWS Settings", "description": _("Connect Amazon with ERPNext"), + }, + { + "type": "doctype", + "name": "Plaid Settings", + "description": _("Connect your bank accounts to ERPNext"), } ] } diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index 9ede81a393a..2c62808014a 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -52,7 +52,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.enabled==1", + "depends_on": "eval:(doc.enabled==1)&&(!doc.__islocal)", "fieldname": "connect_btn", "fieldtype": "Button", "hidden": 0, @@ -89,7 +89,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-11-15 17:37:48.531027", + "modified": "2018-12-07 10:28:10.837885", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Plaid Settings", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index fb4166d53c1..124da59f896 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, @@ -843,6 +844,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "clearance_date", + "fieldtype": "Date", + "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": "Clearance Date", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1153,7 +1186,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-08-21 14:44:42.340662", + "modified": "2018-12-06 09:43:25.056554", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", From e394cec194b4228dc581c25a62cdb5646436fcad Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 12 Dec 2018 14:14:23 +0000 Subject: [PATCH 07/29] Bank reconciliation dashboard --- .../bank_transaction/test_bank_transaction.py | 193 +++++++++++++++++- .../bank_reconciliation.js | 10 + .../bank_reconciliation.py | 6 +- .../bank_transaction_row.html | 1 + .../plaid_settings/plaid_settings.json | 35 +++- .../doctype/plaid_settings/plaid_settings.py | 12 +- erpnext/hooks.py | 3 +- 7 files changed, 250 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 911cac24565..9ecbad57305 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -5,6 +5,197 @@ from __future__ import unicode_literals import frappe import unittest +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry +from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments + +test_dependencies = ["Item", "Cost Center"] class TestBankTransaction(unittest.TestCase): - pass + def setUp(self): + add_transactions() + add_payments() + + # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. + def test_linked_payments(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic")) + linked_payments = get_linked_payments(bank_transaction.name) + self.assertTrue(linked_payments[0].party == "Conrad Electronic") + + # This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment + def test_reconcile(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) + reconcile(bank_transaction.name, "Payment Entry", payment.name) + + unallocated_amount = frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount") + self.assertTrue(unallocated_amount == 0) + + clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") + self.assertTrue(clearance_date is not None) + + # Check if ERPNext can correctly fetch a linked payment based on the party + def test_linked_payments_based_on_party(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G")) + linked_payments = get_linked_payments(bank_transaction.name) + self.assertTrue(len(linked_payments)==1) + + +def add_transactions(): + if frappe.flags.test_bank_transactions_created: + return + + frappe.set_user("Administrator") + try: + frappe.get_doc({ + "doctype": "Bank", + "bank_name":"Citi Bank", + }).insert() + + frappe.get_doc({ + "doctype": "Bank Account", + "account_name":"Checking Account", + "bank": "Citi Bank", + "account": "_Test Bank - _TC" + }).insert() + except frappe.DuplicateEntryError: + pass + + + doc = frappe.get_doc({ + "doctype": "Bank Transaction", + "description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G", + "date": "2018-10-23", + "debit": 1200, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank" + }).insert() + doc.submit() + + doc = frappe.get_doc({ + "doctype": "Bank Transaction", + "description":"1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G", + "date": "2018-10-23", + "debit": 1700, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank" + }).insert() + doc.submit() + + doc = frappe.get_doc({ + "doctype": "Bank Transaction", + "description":"Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic", + "date": "2018-10-26", + "debit": 690, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank" + }).insert() + doc.submit() + + doc = frappe.get_doc({ + "doctype": "Bank Transaction", + "description":"Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07", + "date": "2018-10-27", + "debit": 3900, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank" + }).insert() + doc.submit() + + doc = frappe.get_doc({ + "doctype": "Bank Transaction", + "description":"I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio", + "date": "2018-10-27", + "credit": 109080, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank" + }).insert() + doc.submit() + + frappe.flags.test_bank_transactions_created = True + +def add_payments(): + if frappe.flags.test_payments_created: + return + + frappe.set_user("Administrator") + + try: + frappe.get_doc({ + "doctype": "Supplier", + "supplier_group":"All Supplier Groups", + "supplier_type": "Company", + "supplier_name": "Conrad Electronic" + }).insert() + + except frappe.DuplicateEntryError: + pass + + pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe.reference_no = "Conrad Oct 18" + pe.reference_date = "2018-10-24" + pe.insert() + pe.submit() + + try: + frappe.get_doc({ + "doctype": "Supplier", + "supplier_group":"All Supplier Groups", + "supplier_type": "Company", + "supplier_name": "Mr G" + }).insert() + except frappe.DuplicateEntryError: + pass + + pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe.reference_no = "Herr G Oct 18" + pe.reference_date = "2018-10-24" + pe.insert() + pe.submit() + + pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe.reference_no = "Herr G Nov 18" + pe.reference_date = "2018-11-01" + pe.insert() + pe.submit() + + try: + frappe.get_doc({ + "doctype": "Supplier", + "supplier_group":"All Supplier Groups", + "supplier_type": "Company", + "supplier_name": "Poore Simon's" + }).insert() + except frappe.DuplicateEntryError: + pass + + pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe.reference_no = "Poore Simon's Oct 18" + pe.reference_date = "2018-10-28" + pe.insert() + pe.submit() + + try: + frappe.get_doc({ + "doctype": "Customer", + "customer_group":"All Customer Groups", + "customer_type": "Company", + "customer_name": "Fayva" + }).insert() + except frappe.DuplicateEntryError: + pass + + si = create_sales_invoice(customer="Fayva", qty=1, rate=109080) + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "Fayva Oct 18" + pe.reference_date = "2018-10-29" + pe.insert() + pe.submit() + + + frappe.flags.test_payments_created = True \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 5a1fa4edef5..ef7f356f56d 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -418,6 +418,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { me.bank_entry = $(this).attr("data-name"); me.new_invoice(); }) + + $(me.row).on('click', '.new-expense', function() { + me.bank_entry = $(this).attr("data-name"); + me.new_expense(); + }) } new_payment() { @@ -437,6 +442,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow { frappe.new_doc(invoice_type) } + new_invoice() { + frappe.new_doc("Expense Claim") + } + + show_dialog(data) { const me = this; diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 0b0f301ddaa..85d8cd3ebf7 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -57,7 +57,7 @@ def clear_payment_entry(transaction, payment_entry, gl_entry): """ % (payment_entry.doctype, payment_entry.name), as_dict=True) amount_cleared = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit)) - amount_to_be_cleared = (flt(gl_entry.credit) - flt(gl_entry.debit)) + amount_to_be_cleared = (flt(gl_entry.debit) - flt(gl_entry.credit)) if payment_entry.doctype == "Payment Entry": clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction) @@ -90,11 +90,9 @@ def get_linked_payments(bank_transaction): # Get all payment entries with a matching amount amount_matching = check_matching_amount(bank_account, transaction) - print(amount_matching) # Get some data from payment entries linked to a corresponding bank transaction description_matching = get_matching_descriptions_data(bank_account, transaction) - print(description_matching) if amount_matching: return check_amount_vs_description(amount_matching, description_matching) @@ -188,8 +186,6 @@ def get_matching_descriptions_data(bank_account, transaction): bank_transaction["ratio"] = seq.ratio() selection.append(bank_transaction) - print(selection) - document_types = set([x["payment_document"] for x in selection]) links = {} diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html index 7f63168f80f..755db564671 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html +++ b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html @@ -27,6 +27,7 @@
  • {{ __("New Payment") }}
  • {{ __("New Invoice") }}
  • +
  • {{ __("New Expense") }}
  • diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index 2c62808014a..35e0abe3d3e 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -45,6 +45,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.enabled==1", + "fieldname": "automatic_sync", + "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": "Synchronize all accounts every hour", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -89,7 +122,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-12-07 10:28:10.837885", + "modified": "2018-12-10 16:53:23.292974", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Plaid Settings", diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 17acb6a6c9d..85aa9fa4671 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -113,7 +113,7 @@ def add_account_subtype(account_subtype): frappe.throw(frappe.get_traceback()) @frappe.whitelist() -def sync_transactions(bank, bank_account=None): +def sync_transactions(bank, bank_account): last_sync_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date") if last_sync_date: @@ -135,7 +135,6 @@ def sync_transactions(bank, bank_account=None): except Exception: frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) - def get_transactions(bank, bank_account=None, start_date=None, end_date=None): access_token = None @@ -188,3 +187,12 @@ def new_bank_transaction(transaction): frappe.throw(frappe.get_traceback()) return result + +def automatic_synchronization(): + settings = frappe.get_doc("Plaid Settings", "Plaid Settings") + + if settings.enabled == 1 and settings.automatic_sync == 1: + plaid_accounts = frappe.get_all("Bank Account", filter={"integration_id": ["!=", ""]}, fields=["name", "bank"]) + + for plaid_account in plaid_accounts: + frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 3ab6752d8cd..2c607c0b364 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -226,7 +226,8 @@ scheduler_events = { "hourly": [ 'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails', "erpnext.accounts.doctype.subscription.subscription.process_all", - "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details" + "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details", + "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization", ], "daily": [ "erpnext.stock.reorder_item.reorder_item", From eae74249847337d992c774cb606d6883b2fbd914 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 12 Dec 2018 14:18:51 +0000 Subject: [PATCH 08/29] Cleanup dev --- erpnext/accounts/doctype/bank_transaction/bank_transaction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index f1e3f7a2126..80bf643a717 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -14,7 +14,6 @@ class BankTransaction(Document): def on_update_after_submit(self): allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]) - frappe.log_error(allocated_amount) if allocated_amount: frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount)) From 57c6b49d1a015b7bebcd72e0b103e42c22909084 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 12 Dec 2018 14:33:52 +0000 Subject: [PATCH 09/29] Dev cleanup --- .../accounts/page/bank_reconciliation/bank_reconciliation.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index ef7f356f56d..6553d5ca195 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -206,7 +206,6 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync { freeze: true }) .then((result) => { - console.log(result) let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized") let result_msg = `
    @@ -305,7 +304,6 @@ erpnext.accounts.plaidLink = class plaidLink { bank: result, company: me.company}) }) .then((result) => { - console.log(result) frappe.show_alert({message:__("Bank accounts added"), indicator:'green'}); }) }, __("Select a company"), __("Continue")); @@ -317,7 +315,6 @@ erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.vi constructor(opts) { super(opts); this.show(); - console.log(this) } setup_defaults() { From 6a4dae3a9d826f834a7d6b15bcd5c2c68b9a13f5 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 12 Dec 2018 17:45:22 +0000 Subject: [PATCH 10/29] Codacy corrections + sql queries --- .../account_subtype/account_subtype.py | 1 - .../account_subtype/test_account_subtype.py | 1 - .../doctype/account_type/account_type.py | 1 - .../doctype/account_type/test_account_type.py | 1 - erpnext/accounts/doctype/bank/bank.js | 8 ++--- .../bank_transaction/bank_transaction.js | 2 +- .../bank_transaction/bank_transaction.py | 5 ++- .../bank_transaction/bank_transaction_list.js | 2 +- .../bank_transaction_mapping.py | 1 - .../bank_transaction_payments.py | 1 - .../bank_reconciliation.py | 34 ++++++++++--------- .../doctype/plaid_settings/plaid_connector.py | 7 ++-- .../doctype/plaid_settings/plaid_settings.js | 6 +--- .../doctype/plaid_settings/plaid_settings.py | 4 +-- .../plaid_settings/test_plaid_settings.py | 1 - 15 files changed, 32 insertions(+), 43 deletions(-) diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.py b/erpnext/accounts/doctype/account_subtype/account_subtype.py index 27e61d81cbd..46c45cc733e 100644 --- a/erpnext/accounts/doctype/account_subtype/account_subtype.py +++ b/erpnext/accounts/doctype/account_subtype/account_subtype.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class AccountSubtype(Document): diff --git a/erpnext/accounts/doctype/account_subtype/test_account_subtype.py b/erpnext/accounts/doctype/account_subtype/test_account_subtype.py index 92f29bdf03e..c37b5b9db7d 100644 --- a/erpnext/accounts/doctype/account_subtype/test_account_subtype.py +++ b/erpnext/accounts/doctype/account_subtype/test_account_subtype.py @@ -3,7 +3,6 @@ # See license.txt from __future__ import unicode_literals -import frappe import unittest class TestAccountSubtype(unittest.TestCase): diff --git a/erpnext/accounts/doctype/account_type/account_type.py b/erpnext/accounts/doctype/account_type/account_type.py index 5d9d6a9472e..3e6429318b0 100644 --- a/erpnext/accounts/doctype/account_type/account_type.py +++ b/erpnext/accounts/doctype/account_type/account_type.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class AccountType(Document): diff --git a/erpnext/accounts/doctype/account_type/test_account_type.py b/erpnext/accounts/doctype/account_type/test_account_type.py index 3f7e25c67a8..824c2f66ae3 100644 --- a/erpnext/accounts/doctype/account_type/test_account_type.py +++ b/erpnext/accounts/doctype/account_type/test_account_type.py @@ -3,7 +3,6 @@ # See license.txt from __future__ import unicode_literals -import frappe import unittest class TestAccountType(unittest.TestCase): diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 50334861b7b..6c76b0f85ea 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -12,19 +12,19 @@ frappe.ui.form.on('Bank', { let add_fields_to_mapping_table = function (frm) { - let options = [] + let options = []; frappe.model.with_doctype("Bank Transaction", function() { - let meta = frappe.get_meta("Bank Transaction") + let meta = frappe.get_meta("Bank Transaction"); meta.fields.forEach(value => { if (!["Section Break", "Column Break"].includes(value.fieldtype)) { options.push(value.fieldname); } }) - }) + }); frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field", frm.doc.name).options = options; frm.fields_dict.bank_transaction_mapping.grid.refresh(); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 511e6871d56..cf041cd3c77 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Bank Transaction', { onload: function(frm) { - frm.set_query('payment_document', 'payment_entries', function(doc, cdt, cdn) { + frm.set_query('payment_document', 'payment_entries', function() { return { "filters": { "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]] diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 80bf643a717..d8b5f00f6ee 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe -from frappe import _ from frappe.model.document import Document from frappe.utils import flt @@ -18,9 +17,9 @@ class BankTransaction(Document): if allocated_amount: frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount)) frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount)) - + else: frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0) frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit))) - + self.reload() \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js index ece3cd7e435..db8f297597b 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js @@ -5,7 +5,7 @@ frappe.listview_settings['Bank Transaction'] = { add_fields: ["unallocated_amount"], get_indicator: function(doc) { if(flt(doc.unallocated_amount)>0) { - return [__("Unreconciled"), "orange", "unallocated_amount,>,0"] + return [__("Unreconciled"), "orange", "unallocated_amount,>,0"]; } else if(flt(doc.unallocated_amount)==0) { return [__("Reconciled"), "green", "unallocated_amount,=,0"]; } diff --git a/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py b/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py index 70b7ed8f71e..95a5bc33883 100644 --- a/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py +++ b/erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class BankTransactionMapping(Document): diff --git a/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py index 88ac38fde53..d6d7c109cf5 100644 --- a/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py +++ b/erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class BankTransactionPayments(Document): diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 85d8cd3ebf7..5c99429ed12 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import frappe from frappe import _ import difflib -from operator import itemgetter from frappe.utils import flt from six import iteritems @@ -34,7 +33,7 @@ def reconcile(bank_transaction, payment_doctype, payment_name): def add_payment_to_transaction(transaction, payment_entry, gl_entry): transaction.append("payment_entries", { - "payment_document": payment_entry.doctype, + "payment_document": payment_entry.doctype, "payment_entry": payment_entry.name, "allocated_amount": gl_entry.credit if gl_entry.credit > 0 else gl_entry.debit }) @@ -49,12 +48,12 @@ def clear_payment_entry(transaction, payment_entry, gl_entry): LEFT JOIN `tabBank Transaction` as bt on btp.parent=bt.name WHERE - btp.payment_document = '%s' + btp.payment_document = %s AND - btp.payment_entry = '%s' + btp.payment_entry = %s AND bt.docstatus = 1 - """ % (payment_entry.doctype, payment_entry.name), as_dict=True) + """, (payment_entry.doctype, payment_entry.name), as_dict=True) amount_cleared = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit)) amount_to_be_cleared = (flt(gl_entry.debit) - flt(gl_entry.credit)) @@ -87,7 +86,7 @@ def clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, tra def get_linked_payments(bank_transaction): transaction = frappe.get_doc("Bank Transaction", bank_transaction) bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") - + # Get all payment entries with a matching amount amount_matching = check_matching_amount(bank_account, transaction) @@ -110,7 +109,7 @@ def check_matching_amount(bank_account, transaction): payment_type = "Receive" if transaction.credit > 0 else "Pay" account_from_to = "paid_to" if transaction.credit > 0 else "paid_from" currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency" - payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date", + payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date", "party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)], ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) @@ -118,7 +117,7 @@ def check_matching_amount(bank_account, transaction): journal_entries = frappe.db.sql(""" SELECT 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, - je.pay_to_recd_from as party, je.cheque_date as reference_date, %s as paid_amount + je.pay_to_recd_from as party, je.cheque_date as reference_date, {0} as paid_amount FROM `tabJournal Entry Account` as jea JOIN @@ -128,12 +127,12 @@ def check_matching_amount(bank_account, transaction): WHERE (je.clearance_date is null or je.clearance_date='0000-00-00') AND - jea.account = '%s' + jea.account = %s AND - %s like '%s' + {0} like %s AND je.docstatus = 1 - """ % (payment_field, bank_account, payment_field, amount), as_dict=True) + """.format(payment_field), (bank_account, amount), as_dict=True) sales_invoices = frappe.db.sql(""" SELECT @@ -148,12 +147,12 @@ def check_matching_amount(bank_account, transaction): WHERE (sip.clearance_date is null or sip.clearance_date='0000-00-00') AND - sip.account = '%s' + sip.account = %s AND - sip.amount like '%s' + sip.amount like %s AND si.docstatus = 1 - """ % (bank_account, amount), as_dict=True) + """, (bank_account, amount), as_dict=True) for data in [payment_entries, journal_entries, sales_invoices]: if data: @@ -162,7 +161,10 @@ def check_matching_amount(bank_account, transaction): return payments def get_matching_descriptions_data(bank_account, transaction): - bank_transactions = frappe.db.sql(""" + if not transaction.description : + return [] + + bank_transactions = frappe.db.sql(""" SELECT bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry FROM @@ -237,7 +239,7 @@ def get_matching_transactions_payments(description_matching): payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching} if payments: - reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", + reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]]) return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]]) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index f4eb97b32ef..fbb0ebc2c80 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -import json import requests from plaid import Client from plaid.errors import APIError, ItemError @@ -34,7 +33,7 @@ class PlaidConnector(): def get_access_token(self, public_token): if public_token is None: frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error")) - + response = self.client.Item.public_token.exchange(public_token) access_token = response['access_token'] @@ -68,10 +67,10 @@ class PlaidConnector(): account_ids = [account_id] response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids) - + else: response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date) - + transactions = response['transactions'] while len(transactions) < response['total_transactions']: diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index 44a261946cf..9f0442dade8 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -2,11 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Plaid Settings', { - refresh: function(frm) { - - }, - - connect_btn: function(frm) { + connect_btn: function() { frappe.set_route('bank-reconciliation'); } }); \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 85aa9fa4671..0cf315fecec 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -83,7 +83,7 @@ def add_bank_accounts(response, bank, company): result.append(new_account.name) - except frappe.UniqueValidationError as e: + except frappe.UniqueValidationError: frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name)) except Exception: frappe.throw(frappe.get_traceback()) @@ -99,7 +99,7 @@ def add_account_type(account_type): "doctype": "Account Type", "account_type": account_type }).insert() - except: + except Exception: frappe.throw(frappe.get_traceback()) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index fdf285632ee..c227f19ebee 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -3,7 +3,6 @@ # See license.txt from __future__ import unicode_literals -import frappe import unittest class TestPlaidSettings(unittest.TestCase): From 94899981d31945253c9114eaa36494ebddbb1ad4 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 12 Dec 2018 17:49:40 +0000 Subject: [PATCH 11/29] Dev cleanup --- erpnext/public/less/erpnext.less | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index 0c1751657e4..8ed5f1adb05 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -458,31 +458,4 @@ body[data-route="pos"] { .list-item_content { padding-right: 45px; } -} - -// Bank Reconciliation - -.plaid-btn { - margin-top: 24px; - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; -} - -.transactions-btn { - margin: 15px; -} - -[data-fieldname='reconcile_data'], -[data-fieldname='sync_data'], -[data-fieldname='import_data'] { - .btn { - color: #fff; - background-color: #8d99a6; - border-color: #7f8c9b; - } -} - -[data-fieldname='table_container'] { - margin: -15px -30px; } \ No newline at end of file From cbe63ec4181722dc0bebbd684d722e02660418c1 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 08:04:05 +0000 Subject: [PATCH 12/29] Codacy corrections --- erpnext/accounts/doctype/bank/bank.js | 2 +- .../doctype/plaid_settings/plaid_settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 6c76b0f85ea..463d29c9f83 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -20,7 +20,7 @@ let add_fields_to_mapping_table = function (frm) { if (!["Section Break", "Column Break"].includes(value.fieldtype)) { options.push(value.fieldname); } - }) + }); }); frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field", diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 0cf315fecec..1484da2e64b 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -109,7 +109,7 @@ def add_account_subtype(account_subtype): "doctype": "Account Subtype", "account_subtype": account_subtype }).insert() - except: + except Exception: frappe.throw(frappe.get_traceback()) @frappe.whitelist() From e8f3050e270aacc2691fef3dd6d7b2f788e516da Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 08:19:50 +0000 Subject: [PATCH 13/29] Codacy corrections --- .../bank_reconciliation.py | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 5c99429ed12..ff2b6d87a00 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -114,7 +114,7 @@ def check_matching_amount(bank_account, transaction): ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) payment_field = "jea.debit_in_account_currency" if transaction.credit > 0 else "jea.credit_in_account_currency" - journal_entries = frappe.db.sql(""" + query = """ SELECT 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, je.pay_to_recd_from as party, je.cheque_date as reference_date, {0} as paid_amount @@ -132,7 +132,8 @@ def check_matching_amount(bank_account, transaction): {0} like %s AND je.docstatus = 1 - """.format(payment_field), (bank_account, amount), as_dict=True) + """.format(payment_field) + journal_entries = frappe.db.sql(query, (bank_account, amount), as_dict=True) sales_invoices = frappe.db.sql(""" SELECT @@ -249,8 +250,7 @@ def get_matching_transactions_payments(description_matching): def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") - - return frappe.db.sql(""" + query = """ SELECT jea.parent, je.pay_to_recd_from, if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency) @@ -274,17 +274,20 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): LIMIT %(start)s, %(page_len)s""".format(**{ 'key': searchfield, - }), { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len, - 'account': account - } - ) + }) + + return frappe.db.sql(query, + { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len, + 'account': account + } + ) def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql(""" + query = """ SELECT sip.parent, si.customer, sip.amount, sip.mode_of_payment FROM @@ -303,10 +306,13 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): LIMIT %(start)s, %(page_len)s""".format(**{ 'key': searchfield, - }), { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len - } - ) \ No newline at end of file + }) + + return frappe.db.sql(query, + { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len + } + ) \ No newline at end of file From f6d18e81e9aee59043d22cd29f0ad834d6d03dc3 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 10:09:49 +0000 Subject: [PATCH 14/29] Modify SQL queries and add a test case --- .../bank_transaction/test_bank_transaction.py | 22 +++++ .../bank_reconciliation.py | 98 ++++++++++--------- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 9ecbad57305..f92526cdab4 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -41,6 +41,11 @@ class TestBankTransaction(unittest.TestCase): linked_payments = get_linked_payments(bank_transaction.name) self.assertTrue(len(linked_payments)==1) + # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount + def test_debit_credit_output(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07")) + linked_payments = get_linked_payments(bank_transaction.name) + self.assertTrue(linked_payments[0].payment_type == "Pay") def add_transactions(): if frappe.flags.test_bank_transactions_created: @@ -173,6 +178,16 @@ def add_payments(): except frappe.DuplicateEntryError: pass + try: + frappe.get_doc({ + "doctype": "Customer", + "customer_group":"All Customer Groups", + "customer_type": "Company", + "customer_name": "Poore Simon's" + }).insert() + except frappe.DuplicateEntryError: + pass + pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Poore Simon's Oct 18" @@ -180,6 +195,13 @@ def add_payments(): pe.insert() pe.submit() + si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "Poore Simon's Oct 18" + pe.reference_date = "2018-10-28" + pe.insert() + pe.submit() + try: frappe.get_doc({ "doctype": "Customer", diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index ff2b6d87a00..4b7bd6cabd9 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -8,6 +8,7 @@ from frappe import _ import difflib from frappe.utils import flt from six import iteritems +from erpnext import get_company_currency @frappe.whitelist() def reconcile(bank_transaction, payment_doctype, payment_name): @@ -85,13 +86,13 @@ def clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, tra @frappe.whitelist() def get_linked_payments(bank_transaction): transaction = frappe.get_doc("Bank Transaction", bank_transaction) - bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") + bank_account = frappe.db.get_values("Bank Account", transaction.bank_account, ["account", "company"], as_dict=True) # Get all payment entries with a matching amount - amount_matching = check_matching_amount(bank_account, transaction) + amount_matching = check_matching_amount(bank_account[0].account, bank_account[0].company, transaction) # Get some data from payment entries linked to a corresponding bank transaction - description_matching = get_matching_descriptions_data(bank_account, transaction) + description_matching = get_matching_descriptions_data(bank_account[0].account, transaction) if amount_matching: return check_amount_vs_description(amount_matching, description_matching) @@ -102,22 +103,24 @@ def get_linked_payments(bank_transaction): else: return [] -def check_matching_amount(bank_account, transaction): +def check_matching_amount(bank_account, company, transaction): payments = [] amount = transaction.credit if transaction.credit > 0 else transaction.debit payment_type = "Receive" if transaction.credit > 0 else "Pay" account_from_to = "paid_to" if transaction.credit > 0 else "paid_from" currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency" + payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date", "party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)], ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) - payment_field = "jea.debit_in_account_currency" if transaction.credit > 0 else "jea.credit_in_account_currency" - query = """ + paid_amount_condition = "jea.debit_in_account_currency as paid_amount" if transaction.credit > 0 else "jea.credit_in_account_currency as paid_amount" + amount_condition = "AND jea.debit_in_account_currency like %s" if transaction.credit > 0 else "AND jea.credit_in_account_currency like %s" + journal_entries = frappe.db.sql(""" SELECT 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, - je.pay_to_recd_from as party, je.cheque_date as reference_date, {0} as paid_amount + je.pay_to_recd_from as party, je.cheque_date as reference_date, %s FROM `tabJournal Entry Account` as jea JOIN @@ -127,35 +130,49 @@ def check_matching_amount(bank_account, transaction): WHERE (je.clearance_date is null or je.clearance_date='0000-00-00') AND - jea.account = %s - AND - {0} like %s + jea.account = '%s' %s AND je.docstatus = 1 - """.format(payment_field) - journal_entries = frappe.db.sql(query, (bank_account, amount), as_dict=True) + """ % (paid_amount_condition, bank_account, amount_condition), amount, as_dict=True) - sales_invoices = frappe.db.sql(""" - SELECT - 'Sales Invoice' as doctype, si.name, si.customer as party, - si.posting_date, sip.amount as paid_amount - FROM - `tabSales Invoice Payment` as sip - JOIN - `tabSales Invoice` as si - ON - sip.parent = si.name - WHERE - (sip.clearance_date is null or sip.clearance_date='0000-00-00') - AND - sip.account = %s - AND - sip.amount like %s - AND - si.docstatus = 1 - """, (bank_account, amount), as_dict=True) + if transaction.credit > 0: + sales_invoices = frappe.db.sql(""" + SELECT + 'Sales Invoice' as doctype, si.name, si.customer as party, + si.posting_date, sip.amount as paid_amount + FROM + `tabSales Invoice Payment` as sip + JOIN + `tabSales Invoice` as si + ON + sip.parent = si.name + WHERE + (sip.clearance_date is null or sip.clearance_date='0000-00-00') + AND + sip.account = %s + AND + sip.amount like %s + AND + si.docstatus = 1 + """, (bank_account, amount), as_dict=True) + else: + sales_invoices = [] - for data in [payment_entries, journal_entries, sales_invoices]: + if transaction.debit > 0: + purchase_invoices = frappe.get_all("Purchase Invoice", fields=["'Purchase Invoice' as doctype", "name", "paid_amount", + "supplier as party", "posting_date", "currency"], filters=[["paid_amount", "like", "{0}%".format(amount)], + ["docstatus", "=", "1"], ["is_paid", "=", "1"], ["ifnull(clearance_date, '')", "=", ""], ["cash_bank_account", "=", "{0}".format(bank_account)]]) + + mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"])] + company_currency = get_company_currency(company) + + expense_claims = frappe.get_all("Expense Claim", fields=["'Expense Claim' as doctype", "name", "total_sanctioned_amount as paid_amount", + "employee as party", "posting_date", "'{0}' as currency".format(company_currency)], filters=[["total_sanctioned_amount", "like", "{0}%".format(amount)], + ["docstatus", "=", "1"], ["is_paid", "=", "1"], ["ifnull(clearance_date, '')", "=", ""], ["mode_of_payment", "in", "{0}".format(tuple(mode_of_payments))]]) + else: + purchase_invoices = expense_claims = [] + + for data in [payment_entries, journal_entries, sales_invoices, purchase_invoices, expense_claims]: if data: payments.extend(data) @@ -250,7 +267,8 @@ def get_matching_transactions_payments(description_matching): def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") - query = """ + + return frappe.db.sql(""" SELECT jea.parent, je.pay_to_recd_from, if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency) @@ -272,11 +290,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): if(locate(%(_txt)s, jea.parent), locate(%(_txt)s, jea.parent), 99999), jea.parent LIMIT - %(start)s, %(page_len)s""".format(**{ - 'key': searchfield, - }) - - return frappe.db.sql(query, + %(start)s, %(page_len)s""", { 'txt': "%%%s%%" % txt, '_txt': txt.replace("%", ""), @@ -287,7 +301,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): ) def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): - query = """ + return frappe.db.sql(""" SELECT sip.parent, si.customer, sip.amount, sip.mode_of_payment FROM @@ -304,11 +318,7 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999), sip.parent LIMIT - %(start)s, %(page_len)s""".format(**{ - 'key': searchfield, - }) - - return frappe.db.sql(query, + %(start)s, %(page_len)s""", { 'txt': "%%%s%%" % txt, '_txt': txt.replace("%", ""), From 58438f4e5b5a07cb2c1872338d9bdd658852a9cb Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 10:53:08 +0000 Subject: [PATCH 15/29] Duplicate query to avoid SQL injection --- .../account_subtype/account_subtype.js | 2 +- .../doctype/account_type/account_type.js | 2 +- .../bank_reconciliation.py | 59 +++++++++++++------ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.js b/erpnext/accounts/doctype/account_subtype/account_subtype.js index bc542f50207..30144adeea6 100644 --- a/erpnext/accounts/doctype/account_subtype/account_subtype.js +++ b/erpnext/accounts/doctype/account_subtype/account_subtype.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Account Subtype', { - refresh: function(frm) { + refresh: function() { } }); diff --git a/erpnext/accounts/doctype/account_type/account_type.js b/erpnext/accounts/doctype/account_type/account_type.js index 727634571cc..858b56c077a 100644 --- a/erpnext/accounts/doctype/account_type/account_type.js +++ b/erpnext/accounts/doctype/account_type/account_type.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Account Type', { - refresh: function(frm) { + refresh: function() { } }); diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 4b7bd6cabd9..6c0e4b8f0b2 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -115,25 +115,46 @@ def check_matching_amount(bank_account, company, transaction): "party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)], ["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) - paid_amount_condition = "jea.debit_in_account_currency as paid_amount" if transaction.credit > 0 else "jea.credit_in_account_currency as paid_amount" - amount_condition = "AND jea.debit_in_account_currency like %s" if transaction.credit > 0 else "AND jea.credit_in_account_currency like %s" - journal_entries = frappe.db.sql(""" - SELECT - 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, - je.pay_to_recd_from as party, je.cheque_date as reference_date, %s - FROM - `tabJournal Entry Account` as jea - JOIN - `tabJournal Entry` as je - ON - jea.parent = je.name - WHERE - (je.clearance_date is null or je.clearance_date='0000-00-00') - AND - jea.account = '%s' %s - AND - je.docstatus = 1 - """ % (paid_amount_condition, bank_account, amount_condition), amount, as_dict=True) + if transaction.credit > 0: + journal_entries = frappe.db.sql(""" + SELECT + 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, + je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.debit_in_account_currency as paid_amount + FROM + `tabJournal Entry Account` as jea + JOIN + `tabJournal Entry` as je + ON + jea.parent = je.name + WHERE + (je.clearance_date is null or je.clearance_date='0000-00-00') + AND + jea.account = %s + AND + jea.debit_in_account_currency like %s + AND + je.docstatus = 1 + """, (bank_account, amount), as_dict=True) + else: + journal_entries = frappe.db.sql(""" + SELECT + 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, + je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.credit_in_account_currency as paid_amount + FROM + `tabJournal Entry Account` as jea + JOIN + `tabJournal Entry` as je + ON + jea.parent = je.name + WHERE + (je.clearance_date is null or je.clearance_date='0000-00-00') + AND + jea.account = %s + AND + jea.credit_in_account_currency like %s + AND + je.docstatus = 1 + """, (bank_account, amount), as_dict=True) if transaction.credit > 0: sales_invoices = frappe.db.sql(""" From 7a1ea422711b7093beb225a541df42f6cebc3d31 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 15:23:22 +0000 Subject: [PATCH 16/29] Addition of test cases --- .../bank_transaction/test_bank_transaction.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index f92526cdab4..e7ce1f2b1cb 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -17,6 +17,20 @@ class TestBankTransaction(unittest.TestCase): add_transactions() add_payments() + def tearDown(self): + for bt in frappe.get_all("Bank Transaction"): + doc = frappe.get_doc("Bank Transaction", bt.name) + doc.cancel() + doc.delete() + + for pe in frappe.get_all("Payment Entry"): + doc = frappe.get_doc("Payment Entry", pe.name) + doc.cancel() + doc.delete() + + frappe.flags.test_bank_transactions_created = False + frappe.flags.test_payments_created = False + # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. def test_linked_payments(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic")) @@ -47,6 +61,28 @@ class TestBankTransaction(unittest.TestCase): linked_payments = get_linked_payments(bank_transaction.name) self.assertTrue(linked_payments[0].payment_type == "Pay") + # Check error if already reconciled + def test_already_reconciled(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) + reconcile(bank_transaction.name, "Payment Entry", payment.name) + + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) + self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) + + # Raise an error if creditor transaction vs creditor payment + def test_invalid_creditor_reconcilation(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio")) + payment = frappe.get_doc("Payment Entry", dict(party="Conrad Electronic", paid_amount=690)) + self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) + + # Raise an error if debitor transaction vs debitor payment + def test_invalid_debitor_reconcilation(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07")) + payment = frappe.get_doc("Payment Entry", dict(party="Fayva", paid_amount=109080)) + self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) + def add_transactions(): if frappe.flags.test_bank_transactions_created: return @@ -67,7 +103,6 @@ def add_transactions(): except frappe.DuplicateEntryError: pass - doc = frappe.get_doc({ "doctype": "Bank Transaction", "description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G", @@ -219,5 +254,4 @@ def add_payments(): pe.insert() pe.submit() - frappe.flags.test_payments_created = True \ No newline at end of file From aea2fbf82d8241fea20230fad0814d029b9a302d Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 16:14:40 +0000 Subject: [PATCH 17/29] Correct test case for Travis --- .../bank_transaction/test_bank_transaction.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index e7ce1f2b1cb..d9f189e3d69 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -17,20 +17,6 @@ class TestBankTransaction(unittest.TestCase): add_transactions() add_payments() - def tearDown(self): - for bt in frappe.get_all("Bank Transaction"): - doc = frappe.get_doc("Bank Transaction", bt.name) - doc.cancel() - doc.delete() - - for pe in frappe.get_all("Payment Entry"): - doc = frappe.get_doc("Payment Entry", pe.name) - doc.cancel() - doc.delete() - - frappe.flags.test_bank_transactions_created = False - frappe.flags.test_payments_created = False - # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. def test_linked_payments(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic")) @@ -63,10 +49,6 @@ class TestBankTransaction(unittest.TestCase): # Check error if already reconciled def test_already_reconciled(self): - bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) - payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) - reconcile(bank_transaction.name, "Payment Entry", payment.name) - bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) From c936f07a1e6df8967eadd3d0a7d09c6c17f72bdf Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 13 Dec 2018 17:03:15 +0000 Subject: [PATCH 18/29] Correct Travis error --- .../bank_transaction/test_bank_transaction.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index d9f189e3d69..5b62224331f 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -17,6 +17,19 @@ class TestBankTransaction(unittest.TestCase): add_transactions() add_payments() + def tearDown(self): + for bt in frappe.get_all("Bank Transaction"): + doc = frappe.get_doc("Bank Transaction", bt.name) + doc.cancel() + doc.delete() + + # Delete directly in DB to avoid validation errors for countries not allowing deletion + frappe.db.sql("""delete from `tabPayment Entry Reference`""") + frappe.db.sql("""delete from `tabPayment Entry`""") + + frappe.flags.test_bank_transactions_created = False + frappe.flags.test_payments_created = False + # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. def test_linked_payments(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic")) @@ -49,6 +62,10 @@ class TestBankTransaction(unittest.TestCase): # Check error if already reconciled def test_already_reconciled(self): + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) + reconcile(bank_transaction.name, "Payment Entry", payment.name) + bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) From 89923b84b1c7fd2075082c3bfc8492cda178bfd4 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 14 Dec 2018 10:29:01 +0000 Subject: [PATCH 19/29] UX enhancements --- .../accounts/page/bank_reconciliation/bank_reconciliation.js | 2 ++ .../page/bank_reconciliation/bank_transaction_row.html | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 6553d5ca195..306ffafb43b 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -22,6 +22,8 @@ erpnext.accounts.bankReconciliation = class BankReconciliation { const me = this; me.$main_section = $(`
    `).appendTo(me.page.main); + const empty_state = __("Upload a bank statement, link or reconcile a bank account") + me.$main_section.append(`
    ${empty_state}
    `) me.page.add_field({ fieldtype: 'Link', diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html index 755db564671..699fd673fcc 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html +++ b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html @@ -19,7 +19,8 @@
    -