From e394cec194b4228dc581c25a62cdb5646436fcad Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 12 Dec 2018 14:14:23 +0000 Subject: [PATCH] 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",