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